Skip to content

Instantly share code, notes, and snippets.

@electricimp
Last active August 29, 2015 14:08
Show Gist options
  • Save electricimp/4a76db846df284838cc0 to your computer and use it in GitHub Desktop.
Save electricimp/4a76db846df284838cc0 to your computer and use it in GitHub Desktop.
Twitter Scrolling Display Project
// 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
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