About a year ago, I used a Raspberry Pi 3+ and 7-inch LCD screen to create a bus schedule for my morning bus routes. It’s a huge time and stress saver to have always-on predictions instead of finding my phone and navigating to the bus prediction app.

However, the LCD screen is super bright and consumes a lot of power. For a recent hardware hackathon I attended, I decided to re-build this project using the lower-power Raspberry Pi Zero W and the much-much-lower-power PaPiRus e-ink display.

7-inch LCD power hog

Hardware Preparation

The PaPiRus comes with a HAT that connects to the Pi Zero W through a set of header pins. The pins need to be soldered to the Pi Zero and then the PaPiRus HAT snaps into the board.

Software Preparation

ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev

network={

ssid="YOUR_NETWORK_NAME"

psk="YOUR_PASSWORD"

key_mgmt=WPA-PSK

}

5. Enable ssh access by creating a file:

$ touch /Volumes/boot/ssh

Now you can eject the card, put it in the Pi Zero W and boot it up. If you have access to your router’s admin interface, you can find the IP address for the Pi in the DHCP client list. If you don’t have access to that, you can put on your cowboy hat and scan your network:

for i in `seq 2 254`; do

ssh -o ConnectTimeout=1 -vv pi@192.168.0.$i

done

By default, the username is pi and the password is raspberry . I recommend using screen or tmux to keep a session you can reconnect to.

Python Resources

https://github.com/PiSupply/PaPiRus — the official Python API for manipulating the PaPiRus screen from a Raspberry Pi.

https://github.com/billtubbs/text-bitmaps — Instead of doing a full-screen refresh, the PaPiRus allows you to do a partial update and only reset the affected pixels. This doesn’t work with the text writing API, but does work with the Bitmap image API. This text-bitmaps library helps convert text into Bitmap images to be loaded.

https://github.com/derwiki/nextbus-eink — all of the code used in this post, fully functional and ready to go out of the box.

Loading Predictions

This is a simple GET request for an XML payload. In the interest of jankiness, I’ve decided to parse the XML response with a simple regex instead of a full XML parser.

import urllib

import re

'10 Inbound Wisconsin & Madera': '

'48 Inbound Wisconsin & 25th': '

} URLS = {'10 Inbound Wisconsin & Madera': ' http://webservices.nextbus.com/service/publicXMLFeed?command=predictions&a=sf-muni&r=10&s=6966' , # noqa E501'48 Inbound Wisconsin & 25th': ' http://webservices.nextbus.com/service/publicXMLFeed?command=predictions&a=sf-muni&r=48&s=3513' , # noqa E501 SECONDS_MINUTES_RE = re.compile('seconds="(\d+)" minutes="(\d+)"') def format_prediction(seconds, minutes):

return '{minutes}m{seconds}s'.format(

minutes=minutes,

seconds=int(seconds) % 60,

) def request_predictions():

predictions = {}

for stop, url in URLS.items():

connection = urllib.urlopen(url)

xml = connection.read()

connection.close()

predictions[stop] = [

format_prediction(*seconds_minutes)

for seconds_minutes

in SECONDS_MINUTES_RE.findall(xml)

]

return predictions print(request_predictions())

Sample output:

{'10 Inbound Wisconsin & Madera': ['10m0s', '36m0s', '51m0s', '83m0s'],

'48 Inbound Wisconsin & 25th': ['10m48s', '17m4s', '38m34s', '49m40s', '59m23s']}

Tying It All Together

All that’s left is an infinite loop that fetches predictions and displays them on the e-ink display. We use a display() helper function to handle converting text to bitmap and then displaying it on the screen. Beyond that, we have a while True that pauses for 5 seconds. The try/except is helpful because sometimes the Nextbus API doesn’t respond (often in the middle of the night).