Raspberry Pi Python message-forwarder/proxy 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