Skip to content

Instantly share code, notes, and snippets.

@ElectricImpSampleCode
Last active August 29, 2015 14:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ElectricImpSampleCode/56030df7ac3e990de433 to your computer and use it in GitHub Desktop.
Save ElectricImpSampleCode/56030df7ac3e990de433 to your computer and use it in GitHub Desktop.
Code for the London Bus Time Tracker learning card.
// Agent Code
class TFLData {
_apikey = null;
_appID = null;
_stops = null;
_url = null;
_listTimes = 4;
constructor (apiKey = null, appID = null, busStops = null, listTimes = 4) {
_apikey = apiKey;
_appID = appID;
if (listTimes <= 0) listTimes = 4;
if (listTimes > 10) listTimes = 10;
_listTimes = listTimes;
if (apiKey == null || appID == null) {
// Log error
server.log("[ERROR] Incomplete TFL access credentials supplied.");
} else {
// _stops is an array of tables, each defining a stop we are interested in
_stops = busStops;
_url = "http://countdown.api.tfl.gov.uk/interfaces/ura/instant_V1";
}
}
function getBusTimesRequest(stopID = 0, lineID = "") {
if (stopID == 0 || lineID == "") return null;
local requrl = _url + "?StopID=" + stopID.tostring() + "&LineID=" + lineID + "&ReturnList=EstimatedTime";
local results = http.get(requrl).sendsync();
if (results.statuscode == 200) {
// We have successfully received the data we want, so package it and return it
local data = {};
data.bus <- lineID;
data.times <- _processResults(results.body);
return data;
} else {
// We didn't get data back: return null for error
server.log("API access error: " + results.statuscode);
return null;
}
}
function getBusTimes() {
// Gets the times for all the currently loaded stops
// Returns null for no data, or an array of tables containing the bus times
if (_stops == null || stops.len() == 0) return null;
local data = [];
foreach (stop in _stops) {
// Each stop is a table with two fields:
// busNum (string) and busStop (int)
local result = getBusTimesRequest(stop.stopID, stop.lineID);
if (result != null) data.append(result);
}
if (data.len() != 0) {
return data;
} else {
return null;
}
}
function _processResults(data) {
local dataArray = split(data, "\r");
local timesArray = [];
foreach (index, item in dataArray) {
// Ignore first item
if (index != 0) {
local sArray = split(item, ",");
local otime = sArray[1].slice(0, 10).tointeger();
local diff = time() - otime;
if (diff < 0) diff = diff * -1;
timesArray.append(diff / 60.0);
}
}
timesArray.sort(function(a, b){
if (a > b) return 1;
if (a < b) return -1;
return 0;
})
local lString = "";
foreach (index, item in timesArray) {
if (index < _listTimes) {
if (item < 1.0) {
lString = lString + "<1, ";
} else {
lString = lString + format("%u, " item.tointeger());
}
}
}
if (lString.len() > 1) lString = lString.slice(0, lString.len() - 2);
return lString;
}
}
// CONSTANTS
const appID = "YOUR_APPLICATION_ID_HERE";
const appKey = "YOUR_APPLICATION_KEY_HERE";
// 'MAIN' VARIABLES
local deviceOfflineFlag = false;
// Set up stops data
local stops = [
{ lineID = "W7", stopID = 1144 },
{ lineID = "134", stopID = 1148 },
{ lineID = "43", stopID = 1148 }
];
// Instantiate TFL link
local tfl = TFLData(appID, appKey, stops);
// FUNCTIONS
function getBus() {
if (!deviceOfflineFlag) {
local timeData = tfl.getBusTimes();
device.send("allstops", timeData);
}
imp.wakeup(60, getBus);
}
// Don't do anything until signalled by the device
device.on("ready", function(value){
device.onconnect(function() {
deviceOfflineFlag = false;
});
getBus();
});
device.ondisconnect(function() { deviceOfflineFlag = true } );
// Device Code
// Import Electric Imp’s Button library
#require "Button.class.nut:1.0.0"
class LCD {
// A Squirrel class to drive a 16 x 2 to 20 x 4 character LCD driven by a Hitachi HD44780 controller
// via an MCP23008 interface chip on an Adafruit I2C backpack [http://www.adafruit.com/product/292]
// Communicates with any imp I2C bus
// Written by Tony Smith (@smittytone) October 2014
// Version 1.1. Licensed under MIT Licence
MCP23008_IODIR = "\x00";
MCP23008_GPIO = "\x09";
// HD44780 Commands
LCD_CLEARDISPLAY = 0x01;
LCD_RETURNHOME = 0x02;
LCD_ENTRYMODESET = 0x04;
LCD_DISPLAYCONTROL = 0x08;
LCD_CURSORSHIFT = 0x10;
LCD_FUNCTIONSET = 0x20;
LCD_SETCGRAMADDR = 0x40;
LCD_SETDDRAMADDR = 0x80;
// Flags for display entry mode
LCD_ENTRYRIGHT = 0x00;
LCD_ENTRYLEFT = 0x02;
LCD_ENTRYSHIFTINCREMENT = 0x01;
LCD_ENTRYSHIFTDECREMENT = 0x00;
// Flags for display on/off control
LCD_DISPLAYON = 0x04;
LCD_DISPLAYOFF = 0x00;
LCD_CURSORON = 0x02;
LCD_CURSOROFF = 0x00;
LCD_BLINKON = 0x01;
LCD_BLINKOFF = 0x00;
LCD_BACKLIGHT = 0x80;
// Flags for display/cursor shift
LCD_DISPLAYMOVE = 0x08;
LCD_CURSORMOVE = 0x00;
LCD_MOVERIGHT = 0x04;
LCD_MOVELEFT = 0x00;
// Flags for display mode
LCD_8BITMODE = 0x10;
LCD_4BITMODE = 0x00;
LCD_2LINE = 0x08;
LCD_1LINE = 0x00;
LCD_5x10DOTS = 0x04;
LCD_5x8DOTS = 0x00;
HIGH = 1;
LOW = 0;
_lcdWidth = 0;
_lcdHeight = 0;
_currentLine = 0;
_currentCol = 0;
_displayControl = 0;
_device = null;
_devAddress = 0;
constructor(impI2Cbus, mcp2008address = 0x20) {
// Constructor function takes chosen CONFIGURED imp I2C bus
// Note: I2C address is fixed
_device = impI2Cbus;
_devAddress = mcp2008address << 1;
}
function init(chars = 16, rows = 2) {
// Initializes the display
//
// Parameters:
// 1. Integer value for the number of characters per line that the display supports (Default: 16)
// 2. Integer value for the number of lines that the display supports (Default: 2)
if (rows < 1) rows = 1;
if (chars < 1) chars = 1;
_lcdWidth = chars;
_lcdHeight = rows;
local displayFunction = LCD_4BITMODE | LCD_1LINE | LCD_5x8DOTS;
if (_lcdHeight > 1) displayFunction = displayFunction | LCD_2LINE;
delay(5);
_device.write(_devAddress, MCP23008_IODIR + "\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00");
_device.write(_devAddress, MCP23008_IODIR + "\x00");
// Must write the next two lines two further times each
_device.write(_devAddress, MCP23008_GPIO + "\x9C");
_device.write(_devAddress, MCP23008_GPIO + "\x98");
_device.write(_devAddress, MCP23008_GPIO + "\x9C");
_device.write(_devAddress, MCP23008_GPIO + "\x98");
_device.write(_devAddress, MCP23008_GPIO + "\x9C");
_device.write(_devAddress, MCP23008_GPIO + "\x98");
_device.write(_devAddress, MCP23008_GPIO + "\x94");
_device.write(_devAddress, MCP23008_GPIO + "\x90");
delay(5);
_sendCommand(LCD_FUNCTIONSET | displayFunction);
delay(5);
_sendCommand(LCD_FUNCTIONSET | displayFunction);
delay(5);
displayOn();
clearScreen();
local displayMode = LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT;
_sendCommand(LCD_ENTRYMODESET | displayMode);
setBacklight(HIGH);
}
function clearScreen() {
// Clear the display
_sendCommand(LCD_CLEARDISPLAY);
delay(2);
}
function displayOn() {
// Power up and activate the display
_displayControl = _displayControl | LCD_DISPLAYON;
_sendCommand(LCD_DISPLAYCONTROL | _displayControl);
}
function displayOff() {
// Power down the display
_displayControl = _displayControl | LCD_DISPLAYOFF;
_sendCommand(LCD_DISPLAYCONTROL | _displayControl);
}
function setBacklight(setting) {
// Set the backlight
//
// Parameters:
// 1. Boolean value indicating whether the backlight should be on or off
//
// Note: Backlight intensity is not supported by HD44780
if (setting == HIGH || setting == true) {
_displayControl = _displayControl | 0x80;
_device.write(_devAddress, MCP23008_GPIO + "\x80");
} else {
_displayControl = _displayControl & 0x7F;
_device.write(_devAddress, MCP23008_GPIO + "\x00");
}
}
function setCursor(col, row) {
// Set the print cursor at the selected co-ordinates
//
// Parameters:
// 1. Integer for the column number
// 2. Integer for the row number
//
// Note: Column and row values begin at zero
local rowOffsets = [0x00, 0x40, 0x14, 0x54];
if (row < 0) row = 0;
if (row >= _lcdHeight) row = _lcdHeight - 1;
if (col < 0) col = 0;
if (col >= _lcdWidth) col = _lcdWidth - 1;
_sendCommand(LCD_SETDDRAMADDR | (col + rowOffsets[row]));
_currentLine = col;
_currentCol = col;
}
function print(stringToPrint = "") {
// Print a string at the current cursor location
//
// Parameters:
// 1. String value to be printed
if (stringToPrint == "" || stringToPrint == null) return;
if (typeof stringToPrint != "string") return;
for (local i = 0 ; i < stringToPrint.len() ; i++) {
_writeData(stringToPrint[i]);
}
}
function printChar(ascii = 65) {
// Print a string at the current cursor location
//
// Parameters:
// 1. Integer value for the Ascii code of the character to be printed
if (ascii < 0) ascii = 32;
if (ascii > 7 && ascii < 32) ascii = 32;
if (ascii > 127) ascii = 32;
_writeData(ascii);
}
function centerText(stringToPrint) {
// Returns a string formatted to be centred on the display, ie. it pads with spaces
// so the string can be printed at column 0
//
// Parameters:
// 1. String value to be printed
if (stringToPrint == "" || stringToPrint == null) return;
if (typeof stringToPrint != "string") return;
local inset = stringToPrint.len();
if (inset > _lcdWidth) {
inset = inset - _lcdWidth;
inset = inset / 2;
stringToPrint = stringToPrint.slice(inset, inset + _lcdWidth);
} else if (inset < _lcdWidth) {
inset = _lcdWidth - inset;
inset = inset / 2;
local spaces = " ";
stringToPrint = spaces.slice(0, inset) + stringToPrint + spaces.slice(0, inset);
}
return stringToPrint;
}
function createCustomChar(charMatrix = [], asciiCode = 0) {
// Set one of the HD44780's eight custom 5 x 8 user-definable characters
if (asciiCode < 0 || asciiCode > 7) return;
if (charMatrix.len() == 0 || charMatrix == null) return;
_sendCommand(LCD_SETCGRAMADDR | (asciiCode << 3))
for (local i = 0 ; i < 8 ; i++) {
_writeData(charMatrix[i]);
}
}
function delay(value) {
// Blocking delay for ‘value’ milliseconds
local a = hardware.millis() + value;
while (hardware.millis() < a) {};
}
// PRIVATE FUNCTIONS - DO NOT CALL DIRECTLY
function _sendCommand(command) {
// Send a command to the HD77480
_send(command, LOW);
}
function _writeData(dataByte) {
// Send data to the HD77480
_send(dataByte, HIGH);
}
function _send(value, rsPinValue) {
// Generic send function to pass both a command or data byte (value) to the HD44780
// We use the controller's 4-bit mode to send the data in two batches, each of which
// also contains the HD44780 pin settings we need. Each byte sent is packaged as follows:
//
// Register Select pin = bit 1
// Enable pin = bit 2
// Data pin 4 = bit 3
// Data pin 5 = bit 4
// Data pin 6 = bit 5
// Data pin 7 = bit 6
// Backlight = bit 7
//
// The format is set by the backpack, which decodes the data and puts it onto the appropriate
// HD44780 lines
// Get the passed value's upper four bits and shift down to bits 6-3 (data pins)
// of the byte we will actually send
local buffer = (value & 0xF0) >> 1;
if (rsPinValue == HIGH) {
// Set the transmission byte's E + RS bits
buffer = buffer | 0x06;
} else {
// Set the transmission byte's E bit
buffer = buffer | 0x04;
}
// Set backlight bit if the backlight has been enabled in _displayControl
if (_displayControl & LCD_BACKLIGHT) {
buffer = buffer | 0x80;
} else {
buffer = buffer & 0x7F;
}
// Write the byte ot the backpack via I2C
_device.write(_devAddress, MCP23008_GPIO + buffer.tochar());
// Clear the E bit and send the byte again
buffer = buffer & 0xFB;
_device.write(_devAddress, MCP23008_GPIO + buffer.tochar());
// Get the passed value's lower four bits and shift down to bits 6-3
buffer = (value & 0x0F) << 3;
if (rsPinValue == HIGH) {
// Set the transmission byte's E + RS bits
buffer = buffer | 0x06;
} else {
// Set the transmission byte's E bit
buffer = buffer | 0x04;
}
// Set backlight bit if the backlight has been enabled in _displayControl
if (_displayControl & LCD_BACKLIGHT) {
buffer = buffer | 0x80;
} else {
buffer = buffer & 0x7F;
}
// Write the byte ot the backpack via I2C
_device.write(_devAddress, MCP23008_GPIO + buffer.tochar());
// clear the E bit and send again
buffer = buffer & 0xFB;
_device.write(_devAddress, MCP23008_GPIO + buffer.tochar());
}
}
// 'MAIN' VARIABLES
local startFlag = true;
local backlightFlag = true;
local stopCount = 0;
local refreshTime = 3.0;
local stopData = null;
local disconnectedFlag = false;
local destinations = {
"W7" : "Finsbury Park",
"134" : "Tott. Ct. Rd",
"43" : "London Bridge"
};
// Set up the LCD
hardware.i2c89.configure(CLOCK_SPEED_400_KHZ);
local display = LCD(hardware.i2c89);
display.init(20, 4);
display.setCursor(3,1);
display.print("Bus Times 1.0");
display.setCursor(3,2);
display.print("==============");
// FUNCTIONS
function selectRelease() {
backlightFlag = !backlightFlag;
display.setBacklight(backlightFlag);
}
function showAllStops(data) {
stopData = data;
if (startFlag) {
startFlag = false;
imp.wakeup(5, displayLoop);
}
}
function displayStops() {
display.clearScreen();
if (stopData == null) return;
for (local line = 0 ; line < 2 ; line++) {
local a = stopCount + line;
if (a < stopData.len()) {
local stop = stopData[a];
display.setCursor(0, line * 2);
display.print(stop.bus + " ~ " + destinations[stop.bus] + ":");
display.setCursor(0, (line * 2) + 1);
display.print(stop.times + " mins");
}
}
stopCount = stopCount + 2;
if (stopCount > stopData.len()) stopCount = 0;
if (disconnectedFlag) {
// Display 'DISC' in the last column of the LCD
// to indicate the device has disconnected
display.setCursor(19, 0);
display.print("D");
display.setCursor(19, 1);
display.print("I");
display.setCursor(19, 2);
display.print("S");
display.setCursor(19, 3);
display.print("C");
}
}
function displayLoop() {
imp.wakeup(refreshTime, displayLoop);
displayStops();
}
function disconnectionHandler(reason) {
if (reason != SERVER_CONNECTED) {
disconnectedFlag = true;
imp.wakeup(600.0, function(){
server.connect(disconnectionHandler, 30);
});
} else {
// Server reconnected; tell agent we're back up
agent.send("ready", true);
disconnectedFlag = false;
}
}
// Set up disconnection policy
server.setsendtimeoutpolicy(RETURN_ON_ERROR, WAIT_TIL_SENT, 60);
server.onunexpecteddisconnect(disconnectionHandler);
// Set up the switch that controls the LCD backlight
// (save energy when no one's around!)
local select = Button(hardware.pin7, DIGITAL_IN_PULLUP, 1, null, selectRelease);
// Configure agent communications
agent.on("allstops", showAllStops);
// Tell the agent the device is ready now
imp.wakeup(4.0, function(){ agent.send("ready", true); });
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment