HtMA/palash

"close enough"

 

Week 12: Interfaces: The Web

For this week, I remade the lcd board from last week with a better lcd and hooked it up to the web. Besides the LCD, I changed routing on the board to put the wifi module on the non copper side which create a more mechanically sound board.

Initially, I was having contrast adjustment problems. The contrast at VO set to 1/100th of 5V but that seems to make everything fade out.

Making it closer to 5V didn't help either. In the end, I hooked up a variable resistor to V0 to adjust the contrast. It is very finicky and LCD dependent so I would suggest others do the same, ie. go the adjustable resistor route rather than hard code it in.

Now to getting the bus times.

MBTA's API

Boston's public transit system, MBTA, has a REST API for querying bus arrival predictions.

Details on the API here:

http://realtime.mbta.com/portal

Here are some example calls:

http://realtime.mbta.com/developer/api/v2/routes?api_key=wX9NwuHnZU2ToO7GmGR9uw&format=json

given a route_id (from the last query), you find the stops

http://realtime.mbta.com/developer/api/v2/stopsbyroute?api_key=wX9NwuHnZU2ToO7GmGR9uw&route=01&format=json

So for instance a stop I am interested in is:

{"stop_order":"13",
 "stop_id":"75",
 "stop_name":"84 Massachusetts Ave",
 "parent_station":"",
 "parent_station_name":"",
 "stop_lat":"42.35894",
 "stop_lon":"-71.093628"
 },

With this you get the predicted arrivals for a stop: http://realtime.mbta.com/developer/api/v2/predictionsbystop?api_key=wX9NwuHnZU2ToO7GmGR9uw&stop=75&format=json

{"stop_id":"75",
 "stop_name":"84 Massachusetts Ave",
 "mode":
  [
   {"route_type":"3",
    "mode_name":"Bus",
     "route":
      [
       {"route_id":"01",
        "route_name":"1",
        "direction":
         [
          {"direction_id":"1",
           "direction_name":"Inbound",
            "trip":
            [
             {"trip_id":"24538170",
              "trip_name":"10:01 pm from Massachusetts Ave @ Holyoke St to Dudley Station",
              "trip_headsign":"Dudley Station via Mass. Ave.",
              "sch_arr_dt":"1417403640",
              "sch_dep_dt":"1417403640",
              "pre_dt":"1417403640",
              "pre_away":"958",
              "vehicle":{"vehicle_id":"y2146","vehicle_lat":"42.3730506896973","vehicle_lon":"-71.1176147460938","vehicle_timestamp":"1417402627"}
             },
             {"trip_id":"24538171",
              "trip_name":"10:19 pm from Massachusetts Ave @ Holyoke St to Dudley Station",
              "trip_headsign":"Dudley Station via Mass. Ave.",
              "sch_arr_dt":"1417404720",
              "sch_dep_dt":"1417404720",
              "pre_dt":"1417404720",
              "pre_away":"2038"
             }
            ]
           }
         ]
        }
      ]
    }
  ],
"alert_headers":[]
}

In summary, a bunch of nested stuff with the predicted arrival (pre_away) buried in there. This data structure amounted to about 964 bytes. Too big for the atmega to process. Particularly for complex stops. So what the job called for was a web proxy that will simplify the api data into something more fit for the LCD or the tiny processing power of the avrs.

AppEngine API Proxy

I used Google's AppEngine to write a python server that scraped MBTA and converted the json response to something simpler.

code

In main.py

from google.appengine.api import memcache
from flask import Flask
import mbta

@app.route('/stop/<stop_id>')                   # simple url parsing
def stop(stop_id):
    assert stop_id == '91' or stop_id == '93'   # minimize abuse by internet

    data = memcache.get('stop:%s' % stop_id)
    if data is not None:
        return 'C' + data                       # return cached data
    else:
        data = mbta.text_for_stop_id(stop_id)   # fetch data from mbta's api. format.
    memcache.add('stop:%s' % stop_id, data, 30) # cache for 30s
    return data

In mbta.py, here is the meat of the program:

import urllib2
import json

def api2_url(request, **kwargs):
    params = kwargs.keys()
    url = '%s%s?api_key=%s&%s=%s&format=json' % (URL_BASE, request, API_KEY, params[0], kwargs[params[\
0]]);
    return url

def get_predictions_by_stop(stop_id):
    url = api2_url('predictionsbystop', stop=stop_id)
    try:
        result = urllib2.urlopen(url)
        return result.read()
    except urllib2.URLError, e:
        return None

def text_for_stop_id(stop_id):
    json_str = get_predictions_by_stop(stop_id)
    if json_str is None:
        return '%s Error' % server_time()
    bus_times = parse_bus_arrival_times(json_str)     # basically pretty prints that json
    return server_time() + '\n'.join(bus_times)

app.yaml

Configuration file for appengine. Very simple:

application: busview
version: 1
runtime: python27
api_version: 1
threadsafe: yes
module: default

handlers:
- url: .*
  script: main.app

running dev server:

in the directory ubuntu:appengine$ dev_appserver.py .

Uploading app to appengine

ubuntu:appengine$ appcfg.py -A verdant-cable-780 update app

The result

going to: http://verdant-cable-780.appspot.com/stop/75 gives me this:

C39 01 11m,29m

server_time, bus-direction and arrival times.

As I keep the board plugged in, I can see the qps on appengine's dashboard:

ESP8266

The esp8266 and avr communication is pretty brittle on the timing and took a bit of fussing around to get it right. I based my code off the ITEADLIB_Arduino_ESP8266 library.