Created
April 7, 2014 16:49
-
-
Save blindman2k/10023960 to your computer and use it in GitHub Desktop.
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
// ============================================================================= | |
const NUMPIXELS = 24; | |
class NeoPixels { | |
// when instantiated, the neopixel class will fill this array with blobs to | |
// represent the waveforms to send the numbers 0 to 255. This allows the blobs to be | |
// copied in directly, instead of being built for each pixel - which makes the class faster. | |
bits = null; | |
// Like bits, this blob holds the waveform to send the color [0,0,0], to clear pixels faster | |
clearblob = null; | |
// private variables passed into the constructor | |
spi = null; // imp SPI interface (pre-configured) | |
frameSize = null; // number of pixels per frame | |
frame = null; // a blob to hold the current frame | |
// _spi - A configured spi (MSB_FIRST, 7.5MHz) | |
// _frameSize - Number of Pixels per frame | |
constructor(_spi, _frameSize) { | |
// This class uses SPI to emulate the newpixels' one-wire protocol. | |
// This requires one byte per bit to send data at 7.5 MHz via SPI. | |
// These consts define the "waveform" to represent a zero or one | |
const SPICLK = 3750; // kHz | |
const ZERO = 0xC0; | |
const ONE = 0xF8; | |
const BYTESPERPIXEL = 24; | |
clearblob = blob(BYTESPERPIXEL); | |
spi = _spi; | |
frameSize = _frameSize; | |
frame = blob(frameSize*BYTESPERPIXEL + 1); | |
// prepare the bits array and the clearblob blob | |
initialize(); | |
clearFrame(); | |
writeFrame(); | |
} | |
// fill the array of representative 1-wire waveforms. | |
// done by the constructor at instantiation. | |
function initialize() { | |
// fill the bits array first | |
bits = array(256); | |
for (local i = 0; i < 256; i++) { | |
local valblob = blob(BYTESPERPIXEL / 3); | |
valblob.writen((i & 0x80) ? ONE:ZERO,'b'); | |
valblob.writen((i & 0x40) ? ONE:ZERO,'b'); | |
valblob.writen((i & 0x20) ? ONE:ZERO,'b'); | |
valblob.writen((i & 0x10) ? ONE:ZERO,'b'); | |
valblob.writen((i & 0x08) ? ONE:ZERO,'b'); | |
valblob.writen((i & 0x04) ? ONE:ZERO,'b'); | |
valblob.writen((i & 0x02) ? ONE:ZERO,'b'); | |
valblob.writen((i & 0x01) ? ONE:ZERO,'b'); | |
bits[i] = valblob; | |
} | |
// now fill the clearblob | |
for(local j = 0; j < BYTESPERPIXEL; j++) { | |
clearblob.writen(ZERO, 'b'); | |
} | |
} | |
// sets a pixel in the frame buffer | |
// but does not write it to the pixel strip | |
// color is an array of the form [r, g, b] | |
function writePixel(p, color) { | |
p = p % NUMPIXELS; | |
frame.seek(p*BYTESPERPIXEL); | |
frame.writeblob(bits[color[1]]); | |
frame.writeblob(bits[color[0]]); | |
frame.writeblob(bits[color[2]]); | |
} | |
// Clears the frame buffer | |
// but does not write it to the pixel strip | |
function clearFrame(color = null) { | |
frame.seek(0); | |
if (color == null) { | |
for (local p = 0; p < frameSize; p++) { | |
frame.writeblob(clearblob); | |
} | |
} else { | |
local r = bits[color[0]]; | |
local g = bits[color[1]]; | |
local b = bits[color[2]]; | |
for (local p = 0; p < frameSize; p++) { | |
frame.writeblob(g); | |
frame.writeblob(r); | |
frame.writeblob(b); | |
} | |
} | |
// must have a null at the end to drive MOSI low | |
frame.writen(0x00,'b'); | |
} | |
// writes the frame buffer to the pixel strip | |
// ie - this function changes the pixel strip | |
function writeFrame() { | |
spi.write(frame); | |
} | |
// ============================================================================= | |
/** | |
* Converts an HSL color value to RGB. Conversion formula | |
* adapted from http://en.wikipedia.org/wiki/HSL_color_space. | |
* Assumes h, s, and l are contained in the set [0, 1] and | |
* returns r, g, and b in the set [0, 255]. | |
* | |
* @param Number h The hue | |
* @param Number s The saturation | |
* @param Number l The lightness | |
* @return Array The RGB representation | |
*/ | |
function hsl2rgb(h, s, l) { | |
local hue2rgb = function(p, q, t) { | |
if(t < 0.0) t += 1.0; | |
if(t > 1.0) t -= 1.0; | |
if(t < 0.16666666) return p + (q - p) * 6.0 * t; | |
if(t < 0.5) return q; | |
if(t < 0.66666666) return p + (q - p) * (0.66666666 - t) * 6.0; | |
return p; | |
} | |
local r = 0.0; | |
local g = 0.0; | |
local b = 0.0; | |
h /= 255.0; | |
s /= 255.0; | |
l /= 255.0; | |
if (s == 0) { | |
r = g = b = l; // achromatic | |
} else if (l == 0) { | |
r = g = b = l; // off | |
} else { | |
local q = l < 0.5 ? l * (1.0 + s) : l + s - l * s; | |
local p = 2.0 * l - q; | |
r = hue2rgb(p, q, h + 0.33333333); | |
g = hue2rgb(p, q, h); | |
b = hue2rgb(p, q, h - 0.33333333); | |
} | |
local rgb = [math.floor(r*255.0), math.floor(g*255.0), math.floor(b*255.0)]; | |
return rgb; | |
} | |
} | |
// ============================================================================= | |
class Timer { | |
self = null; | |
cancelled = false; | |
paused = false; | |
running = false; | |
callback = null; | |
interval = 0; | |
params = null; | |
send_self = false; | |
alarm_timer = null; | |
// ------------------------------------------------------------------------- | |
constructor(_params = null, _send_self = false) { | |
params = _params; | |
send_self = _send_self; | |
self = this; | |
} | |
// ------------------------------------------------------------------------- | |
function tzoffset(offset = null) { | |
// Store and retrieve the tzoffset from the global scope | |
if (!("timer_tzoffset" in ::getroottable())) ::timer_tzoffset <- 0; | |
if (offset != null) ::timer_tzoffset <- offset; | |
return ::timer_tzoffset; | |
} | |
// ------------------------------------------------------------------------- | |
function update(_params) { | |
params = _params; | |
return self; | |
} | |
// ------------------------------------------------------------------------- | |
function set(_duration, _callback = null) { | |
if (_callback) callback = _callback; | |
running = true; | |
cancelled = false; | |
paused = false; | |
if (alarm_timer) imp.cancelwakeup(alarm_timer); | |
if (_duration == 0) { | |
alarm(); | |
} else { | |
alarm_timer = imp.wakeup(_duration, alarm.bindenv(self)) | |
} | |
return self; | |
} | |
// ------------------------------------------------------------------------- | |
function repeat(_interval, _callback) { | |
interval = _interval; | |
return set(_interval, _callback); | |
} | |
// ------------------------------------------------------------------------- | |
function reset(_interval) { | |
interval = _interval; | |
} | |
// ------------------------------------------------------------------------- | |
function now() { | |
return alarm(true); | |
} | |
// ------------------------------------------------------------------------- | |
function at(_time, _callback) { | |
if (typeof _time == "string") { | |
local target = strtodate(_time, tzoffset()) | |
_time = target.time; | |
} | |
local diff = _time - time(); | |
if (diff < 0) diff = 0; | |
return set(diff, _callback) | |
} | |
// ------------------------------------------------------------------------- | |
function daily(_time, _callback) { | |
interval = 24*60*60; | |
return at(_time, _callback) | |
} | |
// ------------------------------------------------------------------------- | |
function hourly(_time, _callback) { | |
interval = 60*60; | |
return at(_time, _callback) | |
} | |
// ------------------------------------------------------------------------- | |
function minutely(_time, _callback) { | |
interval = 60; | |
return at(_time, _callback) | |
} | |
// ------------------------------------------------------------------------- | |
function repeat_from(_time, _interval, _callback) { | |
interval = _interval; | |
return at(_time, _callback) | |
} | |
// ------------------------------------------------------------------------- | |
function cancel() { | |
if (alarm_timer) imp.cancelwakeup(alarm_timer); | |
alarm_timer = null; | |
cancelled = true; | |
running = false; | |
callback = null; | |
return self; | |
} | |
// ------------------------------------------------------------------------- | |
function pause() { | |
paused = true; | |
return self; | |
} | |
// ------------------------------------------------------------------------- | |
function unpause() { | |
paused = false; | |
return self; | |
} | |
// ------------------------------------------------------------------------- | |
function alarm(immediate = false) { | |
if (!immediate) { | |
if (interval > 0 && !cancelled) { | |
alarm_timer = imp.wakeup(interval, alarm.bindenv(self)) | |
} else { | |
running = false; | |
alarm_timer = null; | |
} | |
} | |
if (callback && !cancelled && !paused) { | |
if (!send_self && params == null) { | |
callback(); | |
} else if (send_self && params == null) { | |
callback(self); | |
} else if (!send_self && params != null) { | |
callback(params); | |
} else if (send_self && params != null) { | |
callback(self, params); | |
} | |
} | |
return this; | |
} | |
// ------------------------------------------------------------------------- | |
// Converts a string (of various formats) to a time stamp | |
function strtodate(str, tz=0) { | |
// Prepare the variables | |
local year, month, day, hour, min, sec; | |
// Capture the components of the date time string | |
local ex = regexp(@" ([a-zA-Z]+) ([0-9]+), ([0-9]+) ([0-9]+):([0-9]+) ([AP]M)"); | |
local ca = ex.capture(str); | |
if (ca != null) { | |
year = str.slice(ca[3].begin, ca[3].end).tointeger(); | |
month = str.slice(ca[1].begin, ca[1].end); | |
switch (month) { | |
case "January": month = 0; break; case "February": month = 1; break; case "March": month = 2; break; | |
case "April": month = 3; break; case "May": month = 4; break; case "June": month = 5; break; | |
case "July": month = 6; break; case "August": month = 7; break; case "September": month = 8; break; | |
case "October": month = 9; break; case "November": month = 10; break; case "December": month = 11; break; | |
default: throw "Invalid month"; | |
} | |
day = str.slice(ca[2].begin, ca[2].end).tointeger()-1; | |
hour = str.slice(ca[4].begin, ca[4].end).tointeger(); | |
min = str.slice(ca[5].begin, ca[5].end).tointeger(); | |
sec = 0; | |
// Tweak the 12-hour clock | |
if (hour == 12) hour = 0; | |
if (str.slice(ca[6].begin, ca[6].end) == "PM") hour += 12; | |
} else { | |
ex = regexp(@"([0-9]+):([0-9]+)(:([0-9]+))?"); | |
ca = ex.capture(str); | |
if (ca.len() == 5) { | |
local local_now = date(time() + tz); | |
year = local_now.year; | |
month = local_now.month; | |
day = local_now.day-1; | |
hour = str.slice(ca[1].begin, ca[1].end).tointeger(); | |
min = str.slice(ca[2].begin, ca[2].end).tointeger(); | |
if (ca[4].begin == ca[4].end) sec = 0; | |
else sec = str.slice(ca[4].begin, ca[4].end).tointeger(); | |
// Tweak the 24 hour clock | |
if (hour*60*60 + min*60 + sec < local_now.hour*60*60 + local_now.min*60 + local_now.sec) { | |
hour += 24; | |
} | |
// Adjust back to UTC | |
tz = -tz; | |
} else { | |
throw "We are currently expecting, exactly, this format: 'Tuesday, January 7, 2014 9:57 AM'"; | |
} | |
} | |
// Do some bounds checking now | |
if (year < 2012 || year > 2017) throw "Only 2012 to 2017 is currently supported"; | |
// Work out how many seconds since January 1st | |
local epoch_offset = { "2012":1325376000, "2013":1356998400, "2014":1388534400, "2015":1420070400, "2016":1451606400, "2017":1483228800 }; | |
local seconds_per_month = [ 2678400, 2419200, 2678400, 2592000, 2678400, 2592000, 2678400, 2678400, 2592000, 2678400, 2592000, 2678400]; | |
local leap = ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0); | |
if (leap) seconds_per_month[1] = 2505600; | |
local offset = epoch_offset[year.tostring()]; | |
for (local m = 0; m < month; m++) offset += seconds_per_month[m]; | |
offset += (day * 86400); | |
offset += (hour * 3600); | |
offset += (min * 60); | |
offset += sec; | |
offset += tz; | |
// Finally, generate a date object from the offset | |
local dateobj = date(offset); | |
dateobj.str <- format("%02d-%02d-%02d %02d:%02d:%02d Z", dateobj.year, dateobj.month+1, dateobj.day, dateobj.hour, dateobj.min, dateobj.sec); | |
return dateobj; | |
} | |
} | |
// ============================================================================= | |
function clock() { | |
local t = date(time() - (7*60*60)); | |
local u = (hardware.millis() / 41.666).tointeger(); | |
local s = (t.sec / 2.5).tointeger(); | |
local m = (t.min / 2.5).tointeger(); | |
local h = (t.hour % 12) * 2; | |
if (t.min >= 30) h += 1; | |
// fill the frame (with blue), set the seconds, minutes and then hours | |
pcf([0, 0, 0x03]); | |
pwp(u + NUMPIXELS/2, [0x00, 0x00, 0x00]); | |
pwp(s + NUMPIXELS/2, [0x20, 0x20, 0x00]); | |
pwp(m + NUMPIXELS/2, [0x20, 0x00, 0x00]); | |
pwp(h + NUMPIXELS/2, [0x00, 0x20, 0x00]); | |
} | |
function whirlpool() { | |
local rgb = pixels.hsl2rgb(frame % 256, 255, 35); | |
pwp(frame, rgb) | |
} | |
function fader() { | |
local rgb = pixels.hsl2rgb(frame % 256, 255, 35); | |
pcf(rgb); | |
} | |
function worm() { | |
local t0, t1, t2, t3, t4, t5; | |
t0 = pixels.hsl2rgb(frame % 256, 255, 65); | |
t1 = pixels.hsl2rgb(frame % 256, 255, 33); | |
t2 = pixels.hsl2rgb(frame % 256, 255, 17); | |
t3 = pixels.hsl2rgb(frame % 256, 255, 9); | |
t4 = pixels.hsl2rgb(frame % 256, 255, 5); | |
t5 = pixels.hsl2rgb(frame % 256, 255, 1); | |
pwp(frame + 0, t5) | |
pwp(frame + 1, t4) | |
pwp(frame + 2, t3) | |
pwp(frame + 3, t2) | |
pwp(frame + 4, t1) | |
pwp(frame + 5, t0) | |
} | |
function six_slices() { | |
pwp(frame + 0, [40, 0, 0]) | |
pwp(frame + 4, [0, 40, 0]) | |
pwp(frame + 8, [0, 0, 40]) | |
pwp(frame + 12, [40, 0, 40]) | |
pwp(frame + 16, [0, 40, 40]) | |
pwp(frame + 20, [40, 40, 0]) | |
} | |
function four_slices() { | |
local t0, t1, t2, t3; | |
t0 = pixels.hsl2rgb((frame + 0*256/4) % 256, 255, 30); | |
t1 = pixels.hsl2rgb((frame + 1*256/4) % 256, 255, 30); | |
t2 = pixels.hsl2rgb((frame + 2*256/4) % 256, 255, 30); | |
t3 = pixels.hsl2rgb((frame + 3*256/4) % 256, 255, 30); | |
pwp(frame + 0*NUMPIXELS/4, t0) | |
pwp(frame + 1*NUMPIXELS/4, t1) | |
pwp(frame + 2*NUMPIXELS/4, t2) | |
pwp(frame + 3*NUMPIXELS/4, t3) | |
} | |
function three_slices() { | |
local t0, t1, t2; | |
t0 = pixels.hsl2rgb((frame + 0) % 256, 255, 30); | |
t1 = pixels.hsl2rgb((frame + 85) % 256, 255, 30); | |
t2 = pixels.hsl2rgb((frame + 170) % 256, 255, 30); | |
pwp(frame + 0, t0) | |
pwp(frame + 8, t1) | |
pwp(frame + 16, t2) | |
} | |
function two_slices() { | |
local t0, t1; | |
t0 = pixels.hsl2rgb((frame + 0) % 256, 255, 30); | |
t1 = pixels.hsl2rgb((frame + 128) % 256, 255, 30); | |
pwp(frame + 0, t0) | |
pwp(frame + 12, t1) | |
} | |
starshsl <- []; | |
function paparazzi() { | |
// Setup the hsl starfield | |
if (starshsl.len() == 0) { | |
for (local i = 0; i < NUMPIXELS; i++) starshsl.push(null); | |
} | |
// Add a new star | |
if (math.rand() % 20 == 0) { | |
local newstar_pos = math.rand() % NUMPIXELS; | |
if (starshsl[newstar_pos] == null) { | |
starshsl[newstar_pos] = [100, 0, 100]; | |
} | |
} | |
for (local i = 0; i < NUMPIXELS; i++) { | |
// Draw the star | |
local star = starshsl[i]; | |
if (star == null) { | |
// This is background | |
pwp(i, [0,0,5]); | |
} else { | |
// This is a star | |
pwp(i, pixels.hsl2rgb(star[0], star[1], star[2])) | |
// Decay it | |
if (star[2] >= 6) { | |
// Dieing star | |
star[2] = star[2] * 95 / 100; | |
} else { | |
// Dead star | |
star = null; | |
} | |
starshsl[i] = star; | |
} | |
} | |
} | |
function rainbow() { | |
pcf(); | |
for (local i = NUMPIXELS/4; i <= 3*NUMPIXELS/4; i++) { | |
local h = (frame+i)%50 * 5; | |
pwp(i, pixels.hsl2rgb(h, 255, 50)); | |
} | |
} | |
function carnival() { | |
for (local i = 0; i < NUMPIXELS; i++) { | |
if (i % 3 == 0) pwp(i+frame, [255, 0, 0]); | |
if (i % 3 == 1) pwp(i+frame, [255, 255, 255]); | |
if (i % 3 == 2) pwp(i+frame, [0, 0, 255]); | |
} | |
} | |
function greenandgold() { | |
for (local i = 0; i < NUMPIXELS/2; i++) { | |
pwp(i + frame, pixels.hsl2rgb(40 + 50*i/NUMPIXELS*2, 255, 50)) | |
pwp(NUMPIXELS-i-1 + frame, pixels.hsl2rgb(40 + 50*i/NUMPIXELS*2, 255, 50)) | |
} | |
} | |
mix <- {r = math.rand() % NUMPIXELS, g = math.rand() % NUMPIXELS, b = math.rand() % NUMPIXELS}; | |
function mixer() { | |
mix.r = (mix.r + 1) % NUMPIXELS; | |
if (frame%3 > 0) mix.g = (mix.g + NUMPIXELS - 1) % NUMPIXELS; | |
local r1 = (mix.r + 1) % NUMPIXELS; | |
local r2 = (mix.r + 2) % NUMPIXELS; | |
local r3 = (mix.r + 3) % NUMPIXELS; | |
for (local i = 0; i < NUMPIXELS; i++) { | |
local rgb = [0, 0, 0]; | |
if (i == r1 || i == r2 || i == r3) rgb[0] += 255; | |
if (i == mix.g) rgb[1] += 255; | |
if (i == mix.b) rgb[2] += 255; | |
pwp(i, rgb); | |
} | |
} | |
function test() { | |
switch (frame % 4) { | |
case 0: pcf([0xFF, 0, 0]); break; | |
case 1: pcf([0, 0xFF, 0]); break; | |
case 2: pcf([0, 0, 0xFF]); break; | |
case 3: pcf([0xFF, 0xFF, 0xFF]); break; | |
case 4: pcf([0, 0, 0]); break; | |
} | |
} | |
// ============================================================================= | |
frame <- math.rand() % 256; | |
animation <- -1; | |
animations <- [ | |
{ fn = test, spd = 2.000 }, | |
{ fn = mixer, spd = 0.100 }, | |
{ fn = greenandgold, spd = 0.050 }, | |
{ fn = carnival, spd = 0.100 }, | |
{ fn = rainbow, spd = 0.050 }, | |
{ fn = two_slices, spd = 0.050 }, | |
{ fn = three_slices, spd = 0.075 }, | |
{ fn = four_slices, spd = 0.100 }, | |
{ fn = six_slices, spd = 0.100 }, | |
{ fn = worm, spd = 0.050 }, | |
{ fn = whirlpool, spd = 0.010 }, | |
{ fn = fader, spd = 0.100 }, | |
{ fn = paparazzi, spd = 0.010 }, | |
{ fn = clock, spd = 0.050 } | |
]; | |
function animate() { | |
animations[animation].fn(); | |
pixels.writeFrame(); | |
frame = (frame + 1) % (256 * NUMPIXELS); | |
} | |
function nextanimation() { | |
animation = (animation + 1) % animations.len(); | |
animate_timer.reset(animations[animation].spd); | |
} | |
// ============================================================================= | |
// Start! | |
server.setsendtimeoutpolicy(RETURN_ON_ERROR, WAIT_TIL_SENT, 20); | |
server.log("Started") | |
spi <- hardware.spi257; | |
spi.configure(MSB_FIRST, SPICLK); | |
pixels <- NeoPixels(spi, NUMPIXELS); | |
pwp <- pixels.writePixel.bindenv(pixels); | |
pcf <- pixels.clearFrame.bindenv(pixels); | |
animate_timer <- Timer().repeat(0.1, animate); | |
Timer().repeat(20, nextanimation).now(); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment