Mini Public Transit Board

First World Problems

I live in San Francisco where our coffee is strong, our alcohol are stronger, and our public transit system (Muni) is weak.  OK, that’s not entirely fair, but Muni is slow and unreliable: things generally don’t follow any sort of reliable/timed schedule of arrival at any given stop.  This means you have to know the time of the next bus/light rail before heading out or there’s a good chance you’ll be waiting at stops for long periods of time at the stop.  There are a variety of apps you can add to your mobile device to check the current status.  Some are more reliable than others: I’m guessing some may cache or go through an intermediary system that introduces further inaccuracies, because they don’t all show anything close to the same times if you look at them.

Muni also has signs at a number of stops around the city that show the upcoming arrival times coming to the stop.  Presumably, these are the absolute “source of truth,” but the problem is, again, you have to get to the stop before you can ever see that.

Example Muni sign showing the next 28 bus at this stop

Example Muni sign showing the next 28 bus at this stop

Wouldn’t it be cool if we could build one of these, miniaturize it, and keep it in your home?  I think so!  Let’s do that!  First, we’ll need a platform to run it on.  Presumably an Arduino would be ideal, but I had some old Raspberry Pis (original RPi model Bs!) sitting around along with some low memory SD cards.  I have other newer versions and this seems like a good use for that old(er) hardware.  OK, so we have an old RPi and an old SD card, we need a display.  The RPi has HDMI out, which is cool, but I want a more “authentic feel” of the display.  It’d be cool to get the full LED panel they have (which I found a similar one here), but I decided to stick to a simple 16×2 LCD with I2C here for only $2.  Maybe I’ll do the full panel as a future project.


Ok, so now we want to hook up the LCD panel to the RPi.  Easy!  I2C has pins for VCC, ground, a clock (SCL) and a data (SDA), and we just need to find the RPi GPIO pins for each.  RaspberryPi Spy has a nice set of pinouts here.  So we’ll hook up GND to pin 6, VCC to pin 2, SDA to pin 3 (SDA0), and SCL to pin 5 (SCL0).  It looks like this when done:

I2C connection between LCD and Raspberry Pi

I2C connection between LCD and Raspberry Pi

Control Software

OK, with that done, let’s get some software installed.  I chose DietPi as my distro for this project because it’s small and lightweight, but really most distros that fit on the card would do.  After installing and setting up DietPi with my wifi information, we want some real software to control the LCD and get Muni updates.  The first part of that is creating something to control the LCD.  There was a thread on the Raspberry Pi forums about how to do this, which Michael Horne nicely wrapped up the outcome of which on his blog here.  Just copy the contents of and from his blog into files named the same on the RPi.  I then just created a “” command to take in 2 command line arguments and display them using this library.  It’s incredibly simple looks like this:

If you have more than 2 LCD lines, just change the numlines variable value.  Now, running

should display “hello there” on line 1 and “world!” on line 2.

LCD hello world test

Success!  Our hardware and basic control software is all working.  Now we just need something to tell the LCD what to display.  Fortunately, Muni uses NextBus for realtime GPS tracking, so we have an easy, authoritative, RESTful official API to grab from.

Prediction Software

I looked into using a variety of Python modules to pull the feed data, but pretty much all of them seemed broken in various ways (using string matching to find stops, completely wrong documentation, code that hadn’t been updated in years, code that required downloading all routes and stops before looking up predictions, etc).  The same seemed generally true of many perl modules I saw.  I didn’t spend a lot of time looking into them all or trying to fix them, as the API is simple enough to use directly.  We just need to know what route(s) and stop(s) we want to go to.  That can be accessed by pulling up a prediction on the NextBus website and analyzing the URL.  For example, by selecting the N-inbound stop at Carl & Cole to King & 4th, the URL is!/sf-muni/N/N____I_F00/3911/5239

The first bolded bit (sf-muni) is the agency, the second bolded bit is the route (N), and the 3rd bolded bit is the stop ID (3911).  The other parts of the URL are the specific route code (N___I_F00) and the destination stop ID (5239).  I’ll assume we don’t care about those for this exercise.  After that, the API defines that we can get multiple predictions in 1 call by accessing

Where each stop is in the format “route|stopid”.  It gives the following example:

Cool!  Let’s string that together.  I love perl, so here I’ve made a little perl script that pulls the predictions for stops, adds and displays the 2 routes (that I actually have time to get to) that will be the soonest.

You’ll need to install the LWP::Simple and XML::Simple perl modules to make this work.  And now if we run perl, we get something like this:

LCD with predictions

Nice!  Now we’re all set.  The NextBus API states:

All polling commands such as for obtaining vehicle locations should only be run at the most once every 10 seconds.

So we can just set this up on a cron job once every minute and be safely in the bounds.

Bonus: Chariot Predictions

Because Muni is slow, unreliable, uncomfortable, and doesn’t go where some people want it to go, it’s opened up to competition in the private sector in the form of Chariot, a private bus service that generally links the slowest places to travel from to downtown / CalTrain.  They don’t have an official published API, but their website seems to ping a bus tracking URL every few seconds to get current data & predictions.  For example, the Richmond Racer seems to ping this URL:

It’s pretty simple from there to see what it’s doing.  A simple script can pull this data in as well.

Because Chariot’s API uses HTTPS, you’ll also need to install the LWP::Protocol::https perl module if you don’t already have it.  Now we can add this to a crontab as well (but just M-F, since those are the only days Chariot operates) and offset it by 30 seconds from the Muni prediction display.  Then we get the next Muni buses for 30 seconds followed by the next Chariot buses for 30 seconds.

As for packaging, I had an old box that one of my watches came in to put it into.  It would be nice to wrap it into something nicer with holes cut out in specific locations, but I don’t have access to the appropriate cutting tools.  Finished product:

Packaged Predictor

Leave a Reply

Your email address will not be published. Required fields are marked *