Update: As of 2016-03-31 the Oxontime service has changed provider and the API is no longer functional. Presumably a new API will be made available at some point. This post remains for posterity.
The Oxontime Real Time Information (RTI) system provides real time predictions for bus departures in Oxfordshire, which are displayed on screens at many bus stops. The data is also available via an API. I thought it would be fun to use an Adafruit 16x2 LCD display and a Raspberry Pi to make my own personal display (photo below).
The LCD display ships as a kit so you’ll need to do some soldering before you can use it. There are assembly instructions available on the Adafruit website on how to do this. These instructions also include how to install the RPi.GPIO
and Adafruit_CharLCD
modules required to control the display. This was my first time soldering something and I found the common soldering problems page on the Adafruit website very helpful when things weren’t working.
The first step is to find the “system code number” (SCN) for the stop you want to download the departures for. This is not the same as the SMS number displayed at the stops. You can use the script below to print out the SCN, SMS number and name of all of the available stops.
import requests
import json
def download_markers():
headers = {'Content-type': 'application/json'}
url = 'http://www.oxontime.com/MapWebService.asmx/GetMarker'
data = {
"Layers": "naptanbus",
"DateType": 0,
"FromDate": "",
"ToDate": "",
"ZoomLevel": "3.0",
"Easting": 402451,
"Northing": 149757,
"EastingEnd": 530431,
"NorthingEnd": 263157,
"IsLonLat": False}
response = requests.post(url=url, data=json.dumps(data), headers=headers)
response_json = json.loads(response.text)['d']
return response_json
def parse_markers(markers):
for marker in markers:
for cluster in marker['Clusters']:
for marker in cluster['Markers']:
scn, name, sms = marker['Summary'][0:3]
yield(scn, name, sms)
if __name__ == '__main__':
markers = download_markers()
for scn, name, sms in parse_markers(markers):
print('{} {} {}'.format(scn, sms, name))
This script will print the SCN, SMS number and name for all available stops in the format below:
300000037G 68423286 Market Place 300000037VR 68438743 Valley Road 300000037JC 68439374 Jarvis Court
We can use grep
and the SMS number of the stop to find the corresponding SCN. For example, the SMS number of one of the stops on St Clements Street is 69323265, which has an SCN of “340001126YOR”. Some trial an error may be reqired, as stops on opposite sides of a street will often have the same name.
python markers.py > markers.txt
grep 69323265 markers.txt
The next step is to use the SCN to download the departures information, and display it on the LCD screen. This is done using the script below:
import time
import requests
import json
def download_departures(scn):
data = {'SystemCodeNumber': scn}
headers = {'Content-type': 'application/json'}
url = 'http://www.oxontime.com/MapWebService.asmx/GetDepartures'
response = requests.post(url=url, data=json.dumps(data), headers=headers)
response_json = json.loads(response.text)['d']
return response_json
def parse_departures(departures):
for departure in departures:
service = departure['Service']
if service is not None:
destination = departure['Destination'].replace(' ', ' ')
time = departure['Time'].replace(' ', ' ')
yield({'service': service, 'destination': destination, 'time': time})
return
def format_departures_for_lcd(departures, display='time'):
text = ''
for departure in departures[0:2]:
right = departure[display][0:(15-len(departure['service']))]
text += '{service:{w}}{r}\n'.format(w=(16-len(right)), r=right, **departure)
return text
if __name__ == '__main__':
import sys
scn = sys.argv[-1] # get scn from command line arguments
import Adafruit_CharLCD as LCD
lcd = LCD.Adafruit_CharLCDPlate()
lcd.set_color(1,1,1)
lcd.clear()
try:
while True:
# download the departures information
data = download_departures(scn)
departures = list(parse_departures(data))
for n in range(0, 8):
if n % 2 == 0:
text = format_departures_for_lcd(departures, 'time')
wait = 10
else:
text = format_departures_for_lcd(departures, 'destination')
wait = 5
# update the display
lcd.clear()
lcd.message(text)
time.sleep(wait)
except KeyboardInterrupt:
pass
finally:
# don't forget to turn the display off!
lcd.clear()
lcd.set_color(0,0,0)
The download_departures
function downloads the departures information for the stop requested, returning the raw JSON data. The parse_departures
function parses the data to make it easier to work with. The format_departures_for_lcd
function constructs a 16 column by 2 row string, ready to be shown on the LCD display. Finally, the functions above are tied together in a loop that displays the times for 10 seconds, then the destinations for 5 seconds. In this example the departures data is only downloaded once every 60 seconds; don’t abuse the service by accessing it more than is needed!
Don’t forget that to use the Adafruit_CharLCD
module you will need to run Python as superuser:
sudo python oxontime.py 340001126YOR