Last active
August 29, 2015 14:18
-
-
Save ElectricImpSampleCode/56030df7ac3e990de433 to your computer and use it in GitHub Desktop.
Code for the London Bus Time Tracker learning card.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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 } ); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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