Skip to content

Instantly share code, notes, and snippets.

@blindman2k
Created April 7, 2014 16:49
Show Gist options
  • Save blindman2k/10023960 to your computer and use it in GitHub Desktop.
Save blindman2k/10023960 to your computer and use it in GitHub Desktop.
// =============================================================================
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