Last active
August 29, 2015 14:08
-
-
Save electricimp/4a76db846df284838cc0 to your computer and use it in GitHub Desktop.
Twitter Scrolling Display Project
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
// Twitter Keys | |
const API_KEY = "YOUR API KEY" | |
const API_SECRET = "YOUR API SECRET" | |
const AUTH_TOKEN = "YOUR AUTH TOKEN" | |
const TOKEN_SECRET = "YOUR TOKEN SECRET" | |
// Twitter Access Class | |
class Twitter | |
{ | |
// OAuth | |
_consumerKey = null | |
_consumerSecret = null | |
_accessToken = null | |
_accessSecret = null | |
// URLs | |
streamUrl = "https://stream.twitter.com/1.1/" | |
tweetUrl = "https://api.twitter.com/1.1/statuses/update.json" | |
// Streaming | |
streamingRequest = null | |
_reconnectTimeout = null | |
_buffer = null | |
constructor (consumerKey, consumerSecret, accessToken, accessSecret) | |
{ | |
this._consumerKey = consumerKey | |
this._consumerSecret = consumerSecret | |
this._accessToken = accessToken | |
this._accessSecret = accessSecret | |
this._reconnectTimeout = 60 | |
this._buffer = "" | |
} | |
/*************************************************************************** | |
* function: Tweet | |
* Posts a tweet to the user's timeline | |
* | |
* Params: | |
* status - the tweet | |
* cb - an optional callback | |
* | |
* Return: | |
* bool indicating whether the tweet was successful(if no cb was supplied) | |
* nothing(if a callback was supplied) | |
**************************************************************************/ | |
function tweet(status, cb = null) | |
{ | |
local headers = { } | |
local request = _oAuth1Request(tweetUrl, headers, { "status": status} ) | |
if (cb == null) | |
{ | |
local response = request.sendsync() | |
if (response && response.statuscode != 200) | |
{ | |
server.log(format("Error updating_status tweet. HTTP Status Code %i:\r\n%s", response.statuscode, response.body)) | |
return false | |
} | |
else | |
{ | |
return true | |
} | |
} | |
else | |
{ | |
request.sendasync(cb) | |
} | |
} | |
/*************************************************************************** | |
* function: Stream | |
* Opens a connection to twitter's streaming API | |
* | |
* Params: | |
* searchTerms - what we're searching for | |
* onTweet - callback function that executes whenever there is data | |
* onError - callback function that executes whenever there is an error | |
**************************************************************************/ | |
function stream(searchTerms, onTweet, onError = null) | |
{ | |
server.log("Opening stream for: " + searchTerms) | |
// Set default error handler | |
if (onError == null) onError = _defaultErrorHandler.bindenv(this) | |
local method = "statuses/filter.json" | |
local headers = { } | |
local post = { track = searchTerms } | |
local request = _oAuth1Request(streamUrl + method, headers, post) | |
this.streamingRequest = request.sendasync(function(resp){ | |
// connection timeout | |
server.log("Stream Closed (" + resp.statuscode + ": " + resp.body +")") | |
// if we have autoreconnect set | |
if (resp.statuscode == 28 || resp.statuscode == 200) | |
{ | |
stream(searchTerms, onTweet, onError) | |
} | |
else if (resp.statuscode == 420) | |
{ | |
imp.wakeup(_reconnectTimeout, function() { stream(searchTerms, onTweet, onError); }.bindenv(this)) | |
_reconnectTimeout *= 2 | |
} | |
}.bindenv(this), | |
function(body) { | |
try | |
{ | |
if (body.len() == 2) | |
{ | |
_reconnectTimeout = 60 | |
_buffer = "" | |
return | |
} | |
local data = null | |
try | |
{ | |
data = http.jsondecode(body) | |
} | |
catch(ex) | |
{ | |
_buffer += body | |
try | |
{ | |
data = http.jsondecode(_buffer) | |
} | |
catch (ex) | |
{ | |
return | |
} | |
} | |
if (data == null) return | |
// if it's an error | |
if ("errors" in data) | |
{ | |
server.log("Got an error") | |
onError(data.errors) | |
return | |
} | |
else | |
{ | |
if (_looksLikeATweet(data)) | |
{ | |
onTweet(data) | |
return | |
} | |
} | |
} | |
catch(ex) | |
{ | |
// if an error occured, invoke error handler | |
onError([{ message = "Squirrel Error - " + ex, code = -1 }]) | |
} | |
}.bindenv(this) | |
) | |
} | |
/***** Private Function - Do Not Call *****/ | |
function _encode(str) | |
{ | |
return http.urlencode({ s = str }).slice(2) | |
} | |
function _oAuth1Request(postUrl, headers, data) | |
{ | |
local time = time() | |
local nonce = time | |
local parm_string = http.urlencode({ oauth_consumer_key = _consumerKey }) | |
parm_string += "&" + http.urlencode({ oauth_nonce = nonce }) | |
parm_string += "&" + http.urlencode({ oauth_signature_method = "HMAC-SHA1" }) | |
parm_string += "&" + http.urlencode({ oauth_timestamp = time }) | |
parm_string += "&" + http.urlencode({ oauth_token = _accessToken }) | |
parm_string += "&" + http.urlencode({ oauth_version = "1.0" }) | |
parm_string += "&" + http.urlencode(data) | |
local signature_string = "POST&" + _encode(postUrl) + "&" + _encode(parm_string) | |
local key = format("%s&%s", _encode(_consumerSecret), _encode(_accessSecret)) | |
local sha1 = _encode(http.base64encode(http.hash.hmacsha1(signature_string, key))) | |
local auth_header = "oauth_consumer_key=\""+_consumerKey+"\", " | |
auth_header += "oauth_nonce=\""+nonce+"\", " | |
auth_header += "oauth_signature=\""+sha1+"\", " | |
auth_header += "oauth_signature_method=\""+"HMAC-SHA1"+"\", " | |
auth_header += "oauth_timestamp=\""+time+"\", " | |
auth_header += "oauth_token=\""+_accessToken+"\", " | |
auth_header += "oauth_version=\"1.0\"" | |
local headers = { "Authorization": "OAuth " + auth_header } | |
local url = postUrl + "?" + http.urlencode(data) | |
local request = http.post(url, headers, "") | |
return request | |
} | |
function _looksLikeATweet(data) | |
{ | |
return ( | |
"created_at" in data && | |
"id" in data && | |
"text" in data && | |
"user" in data | |
); | |
} | |
function _defaultErrorHandler(errors) | |
{ | |
foreach(error in errors) | |
{ | |
server.log("ERROR " + error.code + ": " + error.message); | |
} | |
} | |
} | |
// Global Variables | |
twitter <- Twitter(API_KEY, API_SECRET, AUTH_TOKEN, TOKEN_SECRET) | |
searchString <- "electricimp" | |
// Program Functions | |
function onTweet(tweetData) | |
{ | |
// log the tweet, and who tweeted it (there is a LOT more info in tweetData) | |
device.send("tweet", format("%s - %s", tweetData.user.screen_name, tweetData.text)) | |
} | |
function requestHandler(request, response) | |
{ | |
try | |
{ | |
if ("search" in request.query) | |
{ | |
searchString = request.query.search | |
twitter.stream(searchString, onTweet) | |
} | |
response.send(200, "Now searching for " + searchString) | |
} | |
catch (error) | |
{ | |
response.send(500, "Server error - try again in a moment") | |
} | |
} | |
// Program Start | |
// Register HTTP request handler | |
http.onrequest(requestHandler) | |
// Start searching Twitter stream with default string | |
twitter.stream(searchString, onTweet) | |
// Copyright (c) 2013-2104 Electric Imp | |
// This file is licensed under the MIT License | |
// http://opensource.org/licenses/MIT |
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
class HT16K33QUAD | |
{ | |
// Squirrel class for 0.54-inch four-digit, 14-segment LED displays driven by the HT16K33 controller | |
// For example: http://shop.pimoroni.com/products/quad-alphanumeric-display-0-54-digits-w-i2c-backpack | |
// and: http://www.adafruit.com/product/1912 | |
// Communicates with any imp I2C bus | |
// Written by Tony Smith (@smittytone) August/September 2014 | |
// Version 1.1 | |
// HT16K33 registers and HT16K33-specific variables | |
HT16K33_REGISTER_DISPLAY_ON = "\x81" | |
HT16K33_REGISTER_DISPLAY_OFF = "\x80" | |
HT16K33_REGISTER_SYSTEM_ON = "\x21" | |
HT16K33_REGISTER_SYSTEM_OFF = "\x20" | |
HT16K33_DISPLAY_ADDRESS = "\x00" | |
HT16K33_I2C_ADDRESS = 0x70 | |
HT16K33_BLANK_CHAR = 62 | |
HT16K33_MINUS_CHAR = 17 | |
HT16K33_CHAR_COUNT = 77 | |
HT16K33_DP_VALUE = 0x4000 | |
// Class properties; null for those defined in the Constructor | |
_buffer = null | |
_digits = null | |
_led = null | |
_ledAddress = 0 | |
constructor(impI2CBus, ht16k33Address = 0x70) | |
{ | |
// The parameter is whichever imp I2C bus is to be used for the HT16K33 | |
_led = impI2CBus | |
_ledAddress = ht16k33Address << 1 | |
// Buffer stores the character matrix values for each row of the display | |
// Quad LED has 16-bit values. There are four individual characters per display | |
_buffer = [0x0000, 0x0000, 0x0000, 0x0000] | |
// digits store character matrices for 0-9, A-F, a-z, space and various symbols | |
_digits = [ | |
0x003F, 0x1200, 0x00DB, 0x008F, 0x12E0, 0x00ED, 0x00FD, 0x0C01, 0x00FF, 0x00EF, // 0-9 | |
0x00F7, 0x128F, 0x0039, 0x120F, 0x0079, 0x0071, // A-F | |
0x00BD, 0x00F6, 0x1200, 0x001E, 0x2470, 0x0038, 0x0536, 0x2136, 0x003F, 0x00F3, // G-P | |
0x203F, 0x20F3, 0x00ED, 0x1201, 0x003E, 0x0C30, 0x2836, 0x2D00, 0x1500, 0x0C09, // Q-Z | |
0x1058, 0x2078, 0x00D8, 0x088E, 0x0858, 0x0C80, 0x048E, 0x1070, 0x1000, 0x000E, // a-j | |
0x3600, 0x0030, 0x10D4, 0x1050, 0x00DC, 0x0170, 0x0486, 0x0050, 0x2088, 0x0078, // k-t | |
0x001C, 0x2004, 0x2814, 0x28C0, 0x200C, 0x0848, // u-z | |
0x0000, // blank | |
0x0006, 0x0220, 0x12CE, 0x12ED, 0x0C24, 0x235D, 0x0400, 0x2400, 0x0900, 0x3FC0, | |
0x12C0, 0x0800, 0x00C0, 0x0000, 0x0C00, 0x10BD | |
] | |
} | |
function init(clearCharacter = 62, brightness = 15) | |
{ | |
// Initialises the display | |
// | |
// Parameters: | |
// 1. Integer index for the _digits[] character matrix to zero the display to; default: space | |
// 2. Integer value for the display brightness, between 0 and 15; default: 15 | |
// Configure the I2C bus | |
_led.configure(CLOCK_SPEED_100_KHZ) | |
// Clear the character buffer | |
clearBuffer(clearCharacter) | |
// Set the brightness (which also wipes and power cyles the display) | |
setBrightness(brightness) | |
} | |
function clearBuffer(clearCharacter = 62) | |
{ | |
// Fills the buffer with a blank character, or the digits[] character matrix whose index is provided | |
// | |
// Parameter: | |
// 1. Integer index for the _digits[] character matrix to zero the display to; default: space | |
if (clearCharacter < 0 || clearCharacter > HT16K33_CHAR_COUNT) clearCharacter = HT16K33_BLANK_CHAR | |
_buffer[0] = _digits[clearCharacter] | |
_buffer[1] = _digits[clearCharacter] | |
_buffer[2] = _digits[clearCharacter] | |
_buffer[3] = _digits[clearCharacter] | |
} | |
function writeChar(rowNumber = 0, charValue = 0xFFFF, hasDot = false) | |
{ | |
// Puts the input character matrix (a 16-bit integer) into the specified row, | |
// adding a decimal point if required. Character matrix value is calculated by | |
// setting the bit(s) representing the segment(s) you want illuminated: | |
// | |
// 0 9 | |
// _ | |
// 5 | | 1 8 \ | / 10 | |
// | | \|/ | |
// 6 - - 7 | |
// 4 | | 2 /|\ | |
// | _ | 11 / | \ 13 . 14 | |
// 3 12 | |
// | |
// Bit 14 is the period, but this is set with parameter 3 | |
// Nb. Bit 15 is not read by the display | |
// | |
// Parameters: | |
// 1. Integer index indicating the display character to write to; default: 0 (left-most) | |
// 2. '16-bit' integer value for the LED segments to illuminate; default: 0xFFFF (all on) | |
// 3. Boolean value specifying whether the decimal poing is lit; default: false | |
// Bail on incorrect row numbers or character values | |
if (rowNumber < 0 || rowNumber > 3) return | |
if (charValue < 0 || charValue > 0xFFFF) return | |
// Write the character to the _buffer[] | |
if (hasDot) char_value = charValue | HT16K33_DP_VALUE | |
_buffer[rowNumber] = charValue | |
} | |
function writeNumber(rowNumber = 0, integerValue = 0, hasDot = false) | |
{ | |
// Puts the number - ie. index of _digits[] - into the specified row, | |
// adding a decimal point if required | |
// | |
// Parameters: | |
// 1. Integer index indicating the display character to write to; default: 0 (left-most) | |
// 2. Integer value for number to write; default: 0 | |
// 3. Boolean value specifying whether the decimal poing is lit; default: false | |
// Bail on incorrect row numbers or character values | |
if (integerValue < 0 || integerValue > 15) return | |
if (rowNumber < 0 || rowNumber > 3) return | |
setBufferValue(rowNumber, integerValue, hasDot) | |
} | |
function writeLetter(rowNumber = 0, ascii = 65, hasDot = false) | |
{ | |
// Puts the number - ie. index of digits[] - into the specified row, | |
// adding a decimal point if required | |
// | |
// Parameters: | |
// 1. Integer index indicating the display character to write to; default: 0 (left-most) | |
// 2. Integer Ascii value for character to write; default: A | |
// 3. Boolean value specifying whether the decimal poing is lit; default: false | |
// Bail on incorrect row number | |
if (rowNumber < 0 || rowNumber > 3) return | |
local integerValue = 0 | |
if (ascii > 31 && ascii < 48) integerValue = ascii + 30 | |
if (ascii > 47 && ascii < 58) integerValue = ascii - 48 | |
if (ascii > 64 && ascii < 91) integerValue = ascii - 55 | |
if (ascii > 96 && ascii < 123) integerValue = ascii - 61 | |
if (ascii == 64) integerValue = 78 | |
setBufferValue(rowNumber, integerValue, hasDot) | |
} | |
function setBufferValue(rowNumber, integerValue, hasDot) | |
{ | |
// Sets a _buffer[] entry to the character stored in _digits[] | |
if (hasDot) | |
{ | |
_buffer[rowNumber] = _digits[integerValue] | HT16K33_DP_VALUE | |
} | |
else | |
{ | |
_buffer[rowNumber] = _digits[integerValue] | |
} | |
} | |
function updateDisplay() | |
{ | |
// Converts the row-indexed buffer[] values into a single, combined | |
// string and writes it to the HT16K33 via I2C | |
local dataString = HT16K33_DISPLAY_ADDRESS | |
for (local i = 0 ; i < 4 ; i++) | |
{ | |
// Convert 16-bit character data into two 8-bit values for transmission | |
local upperByte = _buffer[i] | |
upperByte = upperByte >> 8 | |
local lowerByte = _buffer[i] | |
lowerByte = lowerByte & 0x00FF | |
dataString = dataString + lowerByte.tochar() + upperByte.tochar() | |
} | |
// Write the combined datastring to I2C | |
_led.write(_ledAddress, dataString) | |
} | |
function setBrightness(brightness = 15) | |
{ | |
// This function is called when the app changes the clock's brightness | |
// Default: 15 | |
if (brightness > 15) brightness = 15 | |
if (brightness < 0) brightness = 0 | |
brightness = brightness + 224 | |
// Wipe the display completely first, but retain its current contents | |
local sbuffer = [0, 0, 0, 0] | |
foreach (index, value in _buffer) | |
{ | |
sbuffer[index] = _buffer[index] | |
} | |
// Clear the display | |
clearBuffer(HT16K33_BLANK_CHAR) | |
updateDisplay() | |
// Power cycle the display | |
powerDown() | |
powerUp() | |
// Write the new brightness value to the HT16K33 | |
_led.write(_ledAddress, brightness.tochar() + "\x00") | |
// Restore the character buffer | |
foreach (index, value in sbuffer) | |
{ | |
_buffer[index] = sbuffer[index] | |
} | |
// And display the original contents | |
updateDisplay() | |
} | |
function powerDown() | |
{ | |
_led.write(_ledAddress, HT16K33_REGISTER_DISPLAY_OFF) | |
_led.write(_ledAddress, HT16K33_REGISTER_SYSTEM_OFF) | |
} | |
function powerUp() | |
{ | |
_led.write(_ledAddress, HT16K33_REGISTER_SYSTEM_ON) | |
_led.write(_ledAddress, HT16K33_REGISTER_DISPLAY_ON) | |
} | |
} | |
// Program Functions | |
function scroller() | |
{ | |
loopTimer = imp.wakeup(scrollSpeed, scroller) | |
local a = hardware.millis() | |
local staticFlag = false | |
local displayString = "" | |
loopCount++ | |
if (message.len() <= 8) | |
{ | |
displayString = message + " ".slice(8 - message.len()) | |
staticFlag = true | |
} | |
else | |
{ | |
local end = scrollPos + 8 | |
if (end > message.len()) end = message.len() | |
displayString = message.slice(scrollPos, end) | |
if (displayString.len() < message.len()) displayString = displayString + message.slice(0, 8 - displayString.len()) | |
scrollPos++ | |
if (scrollPos >= message.len()) scrollPos = 0 | |
} | |
// Set the first display’s four characters | |
display1.writeLetter(0, format("%d", displayString[0]).tointeger(), false) | |
display1.writeLetter(1, format("%d", displayString[1]).tointeger(), false) | |
display1.writeLetter(2, format("%d", displayString[2]).tointeger(), false) | |
display1.writeLetter(3, format("%d", displayString[3]).tointeger(), false) | |
// Set the second display’s four characters | |
display2.writeLetter(0, format("%d", displayString[4]).tointeger(), false) | |
display2.writeLetter(1, format("%d", displayString[5]).tointeger(), false) | |
display2.writeLetter(2, format("%d", displayString[6]).tointeger(), false) | |
display2.writeLetter(3, format("%d", displayString[7]).tointeger(), false) | |
// Update the displays | |
display1.updateDisplay() | |
display2.updateDisplay() | |
} | |
function setMessage(messageString) | |
{ | |
if (loopTimer) imp.cancelwakeup(loopTimer) | |
message = messageString + " - " | |
scrollPos = 0 | |
display1.clearBuffer() | |
display2.clearBuffer() | |
scroller() | |
} | |
// Global Variables | |
message <- "Waiting for Tweets" | |
scrollPos <- 0 | |
scrollCount <- 0 | |
scrollSpeed <- 0.2 | |
loopTimer <- null | |
loopCount <- 0 | |
// Set up two display objects | |
// Not the different address values (second parameter) - solding the A0 pins | |
// sets the address to 0x71. To add extra display units, give them unique | |
// addresses by soldering A1 etc pins | |
display1 <- HT16K33QUAD(hardware.i2c89, 0x70) | |
display2 <- HT16K33QUAD(hardware.i2c89, 0x71) | |
display1.init(display1.HT16K33_BLANK_CHAR, 0) | |
display2.init(display2.HT16K33_BLANK_CHAR, 0) | |
scroller() | |
// Register the function to call when the agent sends a new Tweet | |
agent.on("tweet", setMessage) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment