Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
Second version of an Octoblu-connected WS2811 LED string driver. Adds corodinate-based patterns and dot-matrix message display.
var ws281x = require('rpi-ws281x-native');
var meshblu = require('meshblu');
var meshbluJSON = require("./meshblu.json");
var tinycolor = require("tinycolor2");
var mic = require('microphone');
var VUMeter = require('vu-meter')
var mqtt = require('mqtt');
var font5x7 = require('./font.js');
var fs = require('fs');
try {
var xy = require('./xy.json');
}
catch (ex) {
var xy = null;
}
restartTimer = setTimeout(function() {
console.log("[" + new Date() + "] " + "Exiting after pre-set delay.");
var state = {};
state.mode = mode;
state.color = color;
state.dmmessage = dmmessage;
fs.writeFile("/tmp/ws2811.json", JSON.stringify(state), function(err) {
process.exit(0);
});
}, 3600000);
// Specifies how you want your message payload to be passed
// from Octoblu to your device
var MESSAGE_SCHEMA = {
type: 'object',
properties: {
mode: {
type: 'string',
required: true
},
color: {
type: 'integer',
required: false
}
}
};
var NUM_LEDS = 100,
pixelData = new Uint32Array(NUM_LEDS),
modulation = new Uint32Array(NUM_LEDS);
var mode = "";
var color = 0xffffff;
var dmmessage = " XMAS";
try {
var state = require('/tmp/ws2811.json');
mode = state.mode;
color = state.color;
dmmessage = state.dmmessage;
}
catch (ex) {
}
var dmarray;
var dmcolor;
var colorcycle = 255;
function renderMessage() {
dmarray = new Uint32Array(dmmessage.length * 7 + 5);
dmcolor = new Uint32Array(dmmessage.length * 7 + 5);
for (var i = 0; i < dmmessage.length; i++) {
var fonttablebase = (dmmessage.charCodeAt(i) - 0x20) * 5;
for (var j = 0; j < 5; j++) {
dmarray[i*7+j] = font5x7[fonttablebase + j];
dmcolor[i*7+j] = colorcycle;
}
if (colorcycle == 0xff0000) {
colorcycle = 255;
} else {
colorcycle = colorcycle << 8;
}
}
}
renderMessage();
// Rainbow colours
rbcolor = [];
["red", "orange", "yellow", "green", "blue", "indigo", "violet"].forEach(function (c) {
var rgb = tinycolor(c).toRgb();
rbcolor.push(rgb2Int(rgb.r, rgb.g, rgb.b));
});
ws281x.init(NUM_LEDS);
var meter = new VUMeter();
var vu = 0;
last_vals = []
for (var i =0; i < 50; i++) {
last_vals.push(-33.0);
}
last_val_index = 0;
meter.on('data', function(data) {
last_vals[last_val_index] = data[0];
last_val_index++;
if (last_val_index >= last_vals.length) last_val_index = 0;
var min = last_vals.reduce(function(a, b) {return Math.min(a,b);}, 100);
var max = last_vals.reduce(function(a, b) {return Math.max(a,b);}, -100);
var x = Math.floor(50*(data[0] - min)/(max-min));
if (x < 0) x = 0;
if (x > 49) x = 49;
vu = x;
//console.log("Input=%s min/max %s/%s vu=%s", data[0], min, max, vu);
});
mic.audioStream.on('data', function(data) {
meter.write(data, function(err) {
if (err) {
console.log("mic.audioStream.on error: " + str(err));
}
});
});
mic.startCapture();
// ---- trap the SIGINT and reset before exit
process.on('SIGINT', function () {
ws281x.reset();
process.nextTick(function () { process.exit(0); });
});
// ---- animation-loop
var offset = 0;
setInterval(function () {
for (var i = 0; i < NUM_LEDS; i++) {
if ((offset % 40) == (i % 40)) {
modulation[i] = Math.round(Math.random());
}
switch (mode) {
case "colorwheel":
pixelData[i] = colorwheel((offset + i) % 256);
break;
case "bars":
case "yoyo":
case "yo-yo":
pixelData[i] = bars(i, offset % (NUM_LEDS*2));
break;
case "solid":
pixelData[i] = color;
break;
case "octoblu":
pixelData[i] = octoblu(i);
break;
case "concentric":
pixelData[i] = concentric(i, Math.floor(offset/10)%10);
break;
case "echo":
case "vu":
if (xy) {
pixelData[i] = vubarxy(i);
}
else {
pixelData[i] = vubar(i);
}
break;
case "snowfall":
pixelData[i] = snowfall(i, (offset/2) % 100);
break;
case "twinkle":
case "twinklewhite":
pixelData[i] = twinklewhite(i);
break;
case "flashing":
case "twinklecolor":
pixelData[i] = twinklecolor(i);
break;
case "nothing":
pixelData[i] = 0;
break;
case "dotmatrix":
pixelData[i] = dotmatrix(i);
break;
case "xytest":
pixelData[i] = xytest(i);
break;
case "rainbow":
pixelData[i] = rainbow(i);
break;
}
}
offset = (offset + 1);
ws281x.render(pixelData);
}, 1000 / 120);
console.log('Press <ctrl>+C to exit.');
function bars(pos, step) {
if (step > (NUM_LEDS-1)) {
step = (NUM_LEDS*2) - 1 - step;
}
if (step >= pos) {
return colorwheel((offset + pos) % 256);
}
return 0;
}
var snowfall_rg = {}
function snowfall(pos, step) {
if (xy) {
pos = xy.xy[pos][1];
}
else {
pos = 99 - pos;
}
if (Math.abs(pos-step) < 5) {
var rg = 0;
if (snowfall_rg[pos]) {
rg = snowfall_rg[pos];
}
else {
rg = 127 + Math.random() * 128;
snowfall_rg[pos] = rg;
}
return rgb2Int(rg, rg, 255);
}
snowfall_rg[pos] = null;
return rgb2Int(0, 0, 0);
}
function twinklewhite(pos) {
return modulation[pos] * rgb2Int(255, 255, 255);
}
function twinklecolor(pos) {
return modulation[pos] * colorwheel((offset + pos)%256);
}
// rainbow-colors, taken from http://goo.gl/Cs3H0v
function colorwheel(pos) {
pos = 255 - pos;
if (pos < 85) { return rgb2Int(255 - pos * 3, 0, pos * 3); }
else if (pos < 170) { pos -= 85; return rgb2Int(0, pos * 3, 255 - pos * 3); }
else { pos -= 170; return rgb2Int(pos * 3, 255 - pos * 3, 0); }
}
function octoblu(pos) {
var segment = (pos % 50)/6.25;
if (segment < 7) {
return rgb2Int(0, 0, 255);
}
return rgb2Int(0, 255, 0);
}
function concentric(pos, step) {
if (step >= 5) step = 9 - step;
var ppos = Math.floor(pos/50);
if (ppos == step) return colorwheel((offset + pos) % 256);
return 0;
}
function vubar(pos) {
var vpos = pos / (NUM_LEDS/50);
if (vpos <= vu) return colorwheel((offset + pos) % 256);
return 0;
}
function vubarxy(pos) {
var vpos = (100 - xy.xy[pos][1]) / (NUM_LEDS/50);
if (vpos <= vu) return colorwheel((offset + pos) % 256);
return 0;
}
function xytest(pos) {
var phase = Math.floor(offset/200)%4;
switch (phase) {
case 0:
if (Math.abs((offset%200)/2 - xy.xy[pos][0]) < 5.0) {
return color;
} else {
return 0;
}
break;
case 1:
if (Math.abs((offset%200)/2 - xy.xy[pos][1]) < 5.0) {
return color;
} else {
return 0;
}
break;
case 2:
if (Math.abs(xy.xy[pos][0] + xy.xy[pos][1] - (offset%200)) < 10.0) {
return color;
} else {
return 0;
}
break;
case 3:
if (Math.abs(100 + xy.xy[pos][0] - xy.xy[pos][1] - (offset%200)) < 10.0) {
return color;
} else {
return 0;
}
break;
}
}
function rainbow(pos) {
var z = xy.xy[pos][0] + xy.xy[pos][1] - ((offset/3)%200);
if (z < 0) z += 200;
if (z < 28) {
return rbcolor[0];
}
if (z < 57) {
return rbcolor[1];
}
if (z < 86) {
return rbcolor[2];
}
if (z < 114) {
return rbcolor[3];
}
if (z < 143) {
return rbcolor[4];
}
if (z < 171) {
return rbcolor[5];
}
return rbcolor[6];
}
function dotmatrix_x(x) {
if (x < 30.0) return 0;
if (x < 43.0) return 1;
if (x < 57.0) return 2;
if (x < 70.0) return 3;
return 4;
}
function dotmatrix_y(y) {
if (y < 25.0) return 8;
if (y < 40.0) return 0;
if (y < 50.0) return 1;
if (y < 60.0) return 2;
if (y < 70.0) return 3;
if (y < 80.0) return 4;
if (y < 90.0) return 5;
return 6;
}
function dotmatrix(pos) {
var x = xy.xy[pos][0];
var ydot = dotmatrix_y(xy.xy[pos][1]);
var idx = Math.floor(((offset + x)/10)%dmarray.length);
var column = dmarray[idx];
if ((1<<ydot)&column) {
return dmcolor[idx];
}
return 0;
}
function rgb2Int(r, g, b) {
return ((g & 0xff) << 16) + ((r & 0xff) << 8) + (b & 0xff);
}
connectionTimer = setTimeout(function() {
console.log("Timeout setting up connections, exiting.");
process.exit(1);
}, 120000);
// Not specifying a UUID/Token auto-registers a new device
var conn = meshblu.createConnection({
"uuid": meshbluJSON.uuid,
"token": meshbluJSON.token,
"server": "meshblu.octoblu.com",
"port": 80
});
conn.on('notReady', function(data){
console.log('Octoblu UUID FAILED AUTHENTICATION!');
console.log(data);
});
function handle_message(newmode, newcolor) {
//mic.stopCapture();
if (newmode == "solid") {
if (newcolor) {
var c = parseInt(newcolor);
if (isNaN(c)) {
var rgb = tinycolor(newcolor).toRgb();
c = rgb2Int(rgb.r, rgb.g, rgb.b);
}
color = c;
console.log("Setting colour " + c);
}
}
//if (newmode == "vu") {
// mic.startCapture();
//}
mode = newmode;
}
conn.on('ready', function(rdata){
console.log('UUID AUTHENTICATED!');
console.log(rdata);
clearTimeout(connectionTimer);
connectionTimer = undefined;
conn.update({
"uuid": meshbluJSON.uuid,
"token": meshbluJSON.token,
"messageSchema": MESSAGE_SCHEMA,
});
conn.on('message', function(data){
console.log('Octoblu message received');
console.log(data);
mode = data.mode;
color = ("color" in data)?data.color:null;
if (("text" in data) && data.text) {
dmmessage = " " + data.text.toUpperCase();
renderMessage();
offset = 0;
}
handle_message(mode, color)
});
});
var client = mqtt.connect('mqtt://127.0.0.1', {protocolId: 'MQIsdp', protocolVersion: 3});
client.on('connect', function () {
console.log("Connected to MQTT");
client.subscribe('IoTree/#');
});
client.on('message', function (topic, message) {
console.log("MQTT RX t=" + topic + " m=" + message.toString());
switch (topic) {
case "IoTree/mode":
handle_message(message.toString(), null);
break;
case "IoTree/color":
handle_message("solid", message.toString());
break;
case "IoTree/message":
dmmessage = message.toString();
renderMessage();
handle_message("dotmatrix", null);
break;
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment