Comments: 0
NOTE: This code is a first attempt at Python 3 message-forwarder/proxy for the HTML/CSS/JS frontend on smartphones/tablets.
I will be migrating to a Phonegap app + Node.JS architecture in the coming weeks, so these notes will soon become irrelevant to my setup.
The main point of this blog post is to document what was done!
From a previous post the Raspberry Pi uses WebIOPi which has a built-in Python server that serves up the HTML/CSS/JS to smartphones/tablets. The JS sends HTML requests to the Python functions which then forwards/proxies the messages to relevant devices/services and returns responses where appliable.
The main reasons for using message forwarding are:
I have intentionally kept the message forwarding on the Pi fairly 'dumb' with all the 'smarts' residing in JS on the smartphones/tablets.
A closer look at the HTML/CSS/JS and how it sends requests to the Pi
#!/usr/bin/python import os import webiopi import http.client import urllib.request import urllib.parse import socket import select import serial import time import ast import sys import cgi from os import getenv import re import subprocess # debug stuff: use "pprint(getmembers(obj))" from inspect import getmembers from pprint import pprint # global CONSTANTS PUSHOVER_HOST = 'api.pushover.net' PUSHOVER_PATH = '/1/messages.json' PUSHOVER_APP_TOKEN = 'INSERT YOUR OWN HERE' PUSHOVER_USER_KEY = 'INSERT YOUR OWN HERE' # global vars globalSerialPort = serial.Serial("/dev/ttyAMA0", baudrate=19200, timeout=0.5) globalAction = '' # start debug output webiopi.setDebug() # Called by WebIOPi at server startup def setup(): webiopi.debug('@ setup') timestamp = int(time.time()) webiopi.debug('# timestamp='+repr(timestamp)) # continuous loop (show in debug console that script is still alive) def loop(): webiopi.debug('###') time.sleep(5) # Called by WebIOPi at server shutdown def destroy(): webiopi.debug('@ destroy') # get AV action (to show correct page on other devices) @webiopi.macro def getAction(): webiopi.debug('@ getAction') webiopi.debug('# globalAction='+globalAction) return globalAction # set AV action @webiopi.macro def setAction(action): global globalAction webiopi.debug('@ setAction') webiopi.debug('# action='+action) globalAction = action return 0 # proxy request to URL with data and headers @webiopi.macro def rPiHTTPproxy(httpVerb,protocol,host,path,data,headers): webiopi.debug('@ rPiHTTPproxy') # get & display parameters httpVerb = urllib.parse.unquote(httpVerb) protocol = urllib.parse.unquote(protocol) host = urllib.parse.unquote(host) path = urllib.parse.unquote(path) url = protocol + host + path data = urllib.parse.unquote(data) headers = urllib.parse.unquote(headers) webiopi.debug('# httpVerb='+httpVerb) webiopi.debug('# protocol='+protocol); webiopi.debug('# host='+host) webiopi.debug('# path='+path) webiopi.debug('# data='+data) webiopi.debug('# url='+url) webiopi.debug('# headers='+headers) # decide how to construct the message if not headers: if httpVerb == "GET": # construct GET request webiopi.debug('# GET w/o headers') req = urllib.request.Request(url) elif httpVerb == "PUT": # construct PUT request webiopi.debug('# PUT w/o headers') req = urllib.request.Request(url, data.encode('utf-8')) req.get_method = lambda: 'PUT' else: # construct POST request webiopi.debug('# POST w/o headers') req = urllib.request.Request(url, data.encode('utf-8')) else: headers = ast.literal_eval(headers) webiopi.debug('# headers object=') webiopi.debug(headers) if httpVerb == "GET": # construct GET request webiopi.debug('# GET with headers') req = urllib.request.Request(url, None, headers) elif httpVerb == "PUT": # construct PUT request webiopi.debug('# PUT with headers') if protocol == 'https://': conn = http.client.HTTPSConnection(host) else: conn = http.client.HTTPConnection(host) # PUT with headers requires extra attention, so send here & return response conn.connect() conn.request("PUT", path, body=data, headers=headers) resp = conn.getresponse() webiopi.debug(resp.status) webiopi.debug(resp.reason) responseBytes = resp.read() try: response = responseBytes.decode('utf-8') webiopi.debug('# rPiHTTPproxy:response') webiopi.debug(response) return response except: webiopi.debug('# rPiHTTPproxy:responseBytes') pprint(getmembers(responseBytes)) return responseBytes else: # construct POST request webiopi.debug('# POST with headers') req = urllib.request.Request(url, data.encode('utf-8'), headers) # send the request, return response f = urllib.request.urlopen(req) responseBytes = f.read() try: response = responseBytes.decode('utf-8') webiopi.debug('# rPiHTTPproxy:response') webiopi.debug(response) return response except: webiopi.debug('# rPiHTTPproxy:responseBytes') webiopi.debug(responseBytes) return responseBytes # open TCP socket with requested IP:PORT, send message, return response @webiopi.macro def rPiTCPsocketProxy(ip,port,data): webiopi.debug('@ rPiTCPsocketProxy') # get & display parameters ip = urllib.parse.unquote(ip) port = urllib.parse.unquote(port) data = urllib.parse.unquote(data) data = data+'\x0d' dataBytes = data.encode('utf-8') webiopi.debug('# ip='+ip) webiopi.debug('# port='+port) webiopi.debug('# data='+data) # open socket addr = (ip, int(port)) sock = socket.socket() sock.settimeout(3) try: sock.connect(addr) except: webiopi.debug('### could not connect to TCP socket ip:port') return 'EXCEPTION: could not connect to TCP socket ip:port' sock.settimeout(1) # send message sock.sendall(dataBytes) time.sleep(0.1) return # don't bother with response, for now # get response try: responseBytes = sock.recv(1024) except: webiopi.debug('### no TCP socket response') return 'EXCEPTION: no TCP socket response' sock.close() response = responseBytes.decode('utf-8') webiopi.debug('# rPiTCPsocketProxy:response='+response) return response # open UDP socket with requested IP:PORT, send message, return response @webiopi.macro def rPiUDPproxy(ip,port,msgNum,data): webiopi.debug('@ rPiUDPproxy') # get & display parameters ip = urllib.parse.unquote(ip) port = urllib.parse.unquote(port) msgNum = urllib.parse.unquote(msgNum) data = urllib.parse.unquote(data) data = msgNum+','+data+'\x0d' dataBytes = data.encode('utf-8') webiopi.debug('# ip='+ip) webiopi.debug('# port='+port) webiopi.debug('# msgNum='+msgNum) webiopi.debug('# data='+data) webiopi.debug(dataBytes) # open socket & send datagram addr = (ip, int(port)) sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.SOL_UDP) sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) sock.sendto(dataBytes, addr) sock.close() # av app has no need for response from UDP return 1 # send RS232 command, return response @webiopi.macro def rPiRS232proxy(command, value): global globalSerialPort webiopi.debug('@ rPiRS232proxy') # get & display parameters command = urllib.parse.unquote(command) webiopi.debug('# command='+command) value = urllib.parse.unquote(value) webiopi.debug('# value='+value) if value == '=': value = '?)' # send each character with a tiny gap (many serial devices need time to process each byte and don't buffer incoming bytes) data = command+value; webiopi.debug('# data='+data) for c in data b = bytearray(c,'ascii') webiopi.debug(b) globalSerialPort.write(b) time.sleep(0.05); # get the response & return it responseBytes = globalSerialPort.read(40) webiopi.debug(responseBytes) try: response = responseBytes.decode('utf-8') webiopi.debug('# rPiRS232proxy:response') webiopi.debug(response) return response except: webiopi.debug('# rPiRS232proxy:responseBytes') webiopi.debug(responseBytes) return responseBytes # send push notification to smartphone using Pushover API https://pushover.net/api @webiopi.macro def rPiPushNotification(message): webiopi.debug('@ rPiPushNotification') # get & display parameters message = urllib.parse.unquote(message) webiopi.debug('# message='+message) # construct & send the notification data = 'token=' + PUSHOVER_APP_TOKEN + '&user=' + PUSHOVER_USER_KEY + '&message=' + message + '&title=AV' webiopi.debug('# data='+data) response = rPiHTTPproxy('POST','https://', PUSHOVER_HOST, PUSHOVER_PATH, data, '') return response # get channel JSON from Sky's EPG service @webiopi.macro def rPiGetEPGchannelInfo(epgNumber): webiopi.debug('@ rPiGetEPGchannelInfo') # get & display parameters epgNumber = urllib.parse.unquote(epgNumber) webiopi.debug('# epgNumber='+epgNumber) # construct the message & send it, return response url = "http://epgservices.sky.com/5.1.1/api/2.0/channel/json/"+epgNumber+"/now/nnl/1" headers = {'User-agent':'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.66 Safari/537.36'} req = urllib.request.Request(url, None, headers) f = urllib.request.urlopen(req) responseBytes = f.read() try: response = responseBytes.decode('utf-8') webiopi.debug('# rPiGetEPGchannelInfo:response') webiopi.debug(response) return response except: webiopi.debug('# rPiGetEPGchannelInfo:responseBytes') webiopi.debug(responseBytes) return responseBytes