Created
          December 22, 2016 15:18 
        
      - 
      
- 
        Save jamesbulpin/e1624145a30f50c9136a0a159dedd537 to your computer and use it in GitHub Desktop. 
    Second version of an Octoblu-connected WS2811 LED string driver. Adds corodinate-based patterns and dot-matrix message display.
  
        
  
    
      This file contains hidden or 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
    
  
  
    
  | 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