2013-10-30 21:38:23 +01:00
|
|
|
|
2014-06-29 00:35:21 +02:00
|
|
|
module.exports = function(RED) {
|
|
|
|
"use strict";
|
|
|
|
var blinkstick = require("blinkstick");
|
2013-10-30 21:38:23 +01:00
|
|
|
|
2016-08-06 19:42:10 +02:00
|
|
|
var availableModes = ["normal", "inverted", "neopixel"];
|
2014-10-01 11:02:44 +02:00
|
|
|
var availableTasks = ["set_color", "blink", "pulse", "morph"];
|
|
|
|
|
2014-06-29 00:35:21 +02:00
|
|
|
Object.size = function(obj) {
|
|
|
|
var size = 0;
|
|
|
|
for (var key in obj) { if (obj.hasOwnProperty(key)) { size++; } }
|
|
|
|
return size;
|
|
|
|
};
|
2013-10-30 21:38:23 +01:00
|
|
|
|
2014-10-01 11:02:44 +02:00
|
|
|
//Helper function to convert decimal number to hex with padding
|
|
|
|
function decimalToHex(d, padding) {
|
|
|
|
var hex = Number(d).toString(16);
|
|
|
|
padding = typeof (padding) === "undefined" || padding === null ? padding = 2 : padding;
|
|
|
|
|
|
|
|
while (hex.length < padding) {
|
|
|
|
hex = "0" + hex;
|
|
|
|
}
|
|
|
|
|
|
|
|
return hex;
|
|
|
|
}
|
|
|
|
|
|
|
|
function validateInt(value, defaultValue) {
|
|
|
|
return typeof (value) === "undefined" || value === null ? value = defaultValue : parseInt(value);
|
|
|
|
}
|
|
|
|
|
|
|
|
function validate(value, defaultValue) {
|
|
|
|
return typeof (value) === "undefined" || value === null ? value = defaultValue : value;
|
|
|
|
}
|
|
|
|
|
2017-01-29 18:45:44 +01:00
|
|
|
function validateArray(value, defaultValue) {
|
2016-11-06 21:26:19 +01:00
|
|
|
return typeof (value) === "undefined" || Array.isArray(value) ? value : defaultValue;
|
|
|
|
}
|
|
|
|
|
2014-10-01 11:02:44 +02:00
|
|
|
function validatePayloadObject (obj) {
|
|
|
|
var
|
|
|
|
task = validate(obj.task),
|
|
|
|
delay = validateInt(obj.delay),
|
|
|
|
repeats = validateInt(obj.repeats),
|
|
|
|
duration = validateInt(obj.duration),
|
|
|
|
steps = validateInt(obj.steps),
|
|
|
|
repeat = validate(obj.repeat),
|
2016-11-06 21:26:19 +01:00
|
|
|
color = validate(obj.color),
|
|
|
|
channel = validateInt(obj.channel),
|
|
|
|
index = validateInt(obj.index),
|
|
|
|
row = validateArray(obj.row);
|
2014-10-01 11:02:44 +02:00
|
|
|
|
|
|
|
if (typeof(task) !== 'undefined' && availableTasks.indexOf(task) === -1) {
|
|
|
|
return "Task is invalid";
|
|
|
|
}
|
|
|
|
|
2016-11-06 21:26:19 +01:00
|
|
|
if (typeof(color) === 'undefined' && typeof(row) === 'undefined') {
|
2014-10-01 11:02:44 +02:00
|
|
|
return "Color parameter is not set";
|
|
|
|
}
|
|
|
|
|
|
|
|
return { 'task': task, 'delay': delay, 'repeats': repeats, 'duration': duration, 'steps': steps,
|
2016-11-06 21:26:19 +01:00
|
|
|
'repeat': repeat, 'color': color, 'channel': channel, 'index': index, 'row': row };
|
2014-10-01 11:02:44 +02:00
|
|
|
}
|
|
|
|
|
2014-06-29 00:35:21 +02:00
|
|
|
function BlinkStick(n) {
|
|
|
|
RED.nodes.createNode(this,n);
|
2014-10-01 11:02:44 +02:00
|
|
|
|
|
|
|
this.name = n.name;
|
|
|
|
this.serial = n.serial;
|
2017-01-29 18:45:44 +01:00
|
|
|
this.mode = n.mode || "normal";
|
2014-10-01 11:02:44 +02:00
|
|
|
this.task = n.task || "set_color";
|
|
|
|
this.delay = n.delay || 500;
|
|
|
|
this.repeats = n.repeats || 1;
|
|
|
|
this.duration = n.duration || 1000;
|
|
|
|
this.steps = n.steps || 50;
|
|
|
|
this.repeat = n.repeat;
|
2016-11-06 21:26:19 +01:00
|
|
|
this.channel = 0;
|
|
|
|
this.index = 0;
|
|
|
|
this.row = [];
|
2014-10-01 11:02:44 +02:00
|
|
|
this.closing = false;
|
|
|
|
this.color = '';
|
|
|
|
this.previousColor = '';
|
|
|
|
|
|
|
|
var p1 = /[0-9]+,[0-9]+,[0-9]+/;
|
2014-06-29 00:35:21 +02:00
|
|
|
var node = this;
|
2014-10-01 11:02:44 +02:00
|
|
|
//Keeps track for active animations
|
|
|
|
var animationComplete = true;
|
2013-10-30 21:38:23 +01:00
|
|
|
|
2014-10-01 11:02:44 +02:00
|
|
|
//Find BlinkStick based on serial number if supplied, otherwise look for first
|
|
|
|
//Blinkstick in the USB device list
|
|
|
|
var findBlinkStick = function (callback) {
|
|
|
|
if (typeof(node.serial) == 'string' && node.serial.replace(/\s+/g,'') !== '') {
|
|
|
|
blinkstick.findBySerial(node.serial, function (device) {
|
|
|
|
node.led = device;
|
|
|
|
|
|
|
|
if (Object.size(node.led) === 0) {
|
2015-03-25 09:33:02 +01:00
|
|
|
node.status({fill:"red",shape:"ring",text:"not found"});
|
2014-10-01 11:02:44 +02:00
|
|
|
node.error("BlinkStick with serial number " + node.serial + " not found");
|
2017-01-29 18:45:44 +01:00
|
|
|
}
|
|
|
|
else {
|
2014-10-01 11:02:44 +02:00
|
|
|
node.status({fill:"green",shape:"dot",text:"connected"});
|
2017-01-29 18:45:44 +01:00
|
|
|
if (node.mode == "normal") {node.led.setMode(0);}
|
|
|
|
else if (node.mode == "inverted") {node.led.setMode(1);}
|
|
|
|
else if (node.mode == "neopixel") {node.led.setMode(2);}
|
2015-03-26 19:55:03 +01:00
|
|
|
if (callback) { callback(); }
|
2013-10-30 21:38:23 +01:00
|
|
|
}
|
2014-10-01 11:02:44 +02:00
|
|
|
});
|
2017-01-29 18:45:44 +01:00
|
|
|
}
|
|
|
|
else {
|
2014-10-01 11:02:44 +02:00
|
|
|
node.led = blinkstick.findFirst();
|
|
|
|
|
|
|
|
if (Object.size(node.led) === 0) {
|
2015-03-25 09:33:02 +01:00
|
|
|
node.status({fill:"red",shape:"ring",text:"not found"});
|
2014-10-01 11:02:44 +02:00
|
|
|
node.error("No BlinkStick found");
|
2017-01-29 18:45:44 +01:00
|
|
|
}
|
|
|
|
else {
|
2014-10-01 11:02:44 +02:00
|
|
|
node.status({fill:"green",shape:"dot",text:"connected"});
|
2017-01-29 18:45:44 +01:00
|
|
|
if (node.mode == "normal") {node.led.setMode(0);}
|
|
|
|
else if (node.mode == "inverted") {node.led.setMode(1);}
|
|
|
|
else if (node.mode == "neopixel") {node.led.setMode(2);}
|
2015-03-26 19:55:03 +01:00
|
|
|
if (callback) { callback(); }
|
2013-10-30 21:38:23 +01:00
|
|
|
}
|
2014-10-01 11:02:44 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
//Check if repeat is enabled. Works only for pulse and blink tasks
|
|
|
|
var canRepeat = function () {
|
|
|
|
return node.task == "pulse" || node.task == "blink";
|
|
|
|
};
|
|
|
|
|
|
|
|
//Event handler for all animation complete events
|
|
|
|
var blinkstickAnimationComplete = function (err) {
|
|
|
|
if (typeof(err) !== 'undefined') {
|
|
|
|
node.warn(err);
|
|
|
|
|
|
|
|
if (typeof(err.name) === 'undefined' || err.name !== 'ReferenceError') {
|
|
|
|
//USB error occurred when BlinkStick was animating
|
2015-04-16 11:58:34 +02:00
|
|
|
node.led.close(function () {
|
2014-10-01 11:02:44 +02:00
|
|
|
node.led = undefined;
|
|
|
|
findBlinkStick();
|
|
|
|
});
|
2013-10-30 21:38:23 +01:00
|
|
|
}
|
|
|
|
}
|
2014-10-01 11:02:44 +02:00
|
|
|
|
|
|
|
animationComplete = true;
|
|
|
|
|
|
|
|
//Apply queued color animation
|
|
|
|
if (!node.closing && node.color !== '') {
|
|
|
|
//Apply new color only if there was no error or queued color is different from the previous color
|
|
|
|
if (typeof(err) === 'undefined' || typeof(err) !== 'undefined' && node.color != node.previousColor) {
|
|
|
|
applyColor();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
var applyColor = function () {
|
|
|
|
animationComplete = false;
|
|
|
|
|
|
|
|
//Store the value of color to check if it has changed
|
|
|
|
node.previousColor = node.color;
|
|
|
|
|
|
|
|
try {
|
|
|
|
//Select animation to perform
|
|
|
|
if (node.task == "pulse") {
|
2016-11-06 21:26:19 +01:00
|
|
|
node.led.pulse(node.color, {'duration': node.duration, 'steps': node.steps, 'channel': node.channel, 'index': node.index }, blinkstickAnimationComplete);
|
2017-01-29 18:45:44 +01:00
|
|
|
}
|
|
|
|
else if (node.task == "morph") {
|
2016-11-06 21:26:19 +01:00
|
|
|
node.led.morph(node.color, {'duration': node.duration, 'steps': node.steps, 'channel': node.channel, 'index': node.index }, blinkstickAnimationComplete);
|
2017-01-29 18:45:44 +01:00
|
|
|
}
|
|
|
|
else if (node.task == "blink") {
|
2016-11-06 21:26:19 +01:00
|
|
|
node.led.blink(node.color,{'repeats': node.repeats, 'delay': node.delay, 'channel': node.channel, 'index': node.index }, blinkstickAnimationComplete);
|
2017-01-29 18:45:44 +01:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
if (node.row.length > 0) {
|
2016-11-06 21:26:19 +01:00
|
|
|
var dat = [];
|
|
|
|
for (var i = 0; i < node.row.length; i++) {
|
|
|
|
if (typeof node.row[i] === "string") { // if string then assume must be colour names
|
|
|
|
var params = node.led.interpretParameters(node.row[i]); // lookup colour code from name
|
|
|
|
if (params) {
|
|
|
|
dat.push(params.green);
|
|
|
|
dat.push(params.red);
|
|
|
|
dat.push(params.blue);
|
|
|
|
}
|
|
|
|
else { node.warn("invalid colour: "+node.row[i]); }
|
|
|
|
}
|
|
|
|
else { // otherwise lets use numbers 0-255
|
|
|
|
dat.push(node.row[i+1]);
|
|
|
|
dat.push(node.row[i]);
|
|
|
|
dat.push(node.row[i+2]);
|
|
|
|
i += 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ((dat.length % 3) === 0) { // by now length must be a multiple of 3
|
|
|
|
node.led.setColors(node.channel, dat, blinkstickAnimationComplete);
|
|
|
|
}
|
|
|
|
else {
|
2017-01-29 18:45:44 +01:00
|
|
|
node.warn("Colour array length not / 3");
|
2016-11-06 21:26:19 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
node.led.setColor(node.color, {'channel': node.channel, 'index': node.index}, blinkstickAnimationComplete);
|
|
|
|
}
|
2014-10-01 11:02:44 +02:00
|
|
|
}
|
2017-01-29 18:45:44 +01:00
|
|
|
}
|
|
|
|
catch (err) {
|
2014-11-20 09:44:05 +01:00
|
|
|
if (err.toString().indexOf("setColor") !== -1) {
|
|
|
|
node.led.setColour(node.color, blinkstickAnimationComplete);
|
|
|
|
node.warn("Old version - please upgrade Blinkstick npm");
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
node.warn("BlinkStick missing ? " + err);
|
|
|
|
}
|
2015-03-25 09:33:02 +01:00
|
|
|
node.log(err);
|
2014-10-01 11:02:44 +02:00
|
|
|
//Reset animation
|
|
|
|
animationComplete = true;
|
|
|
|
//Clear color
|
|
|
|
node.color = '';
|
|
|
|
//Look for a BlinkStick
|
|
|
|
findBlinkStick();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
//Clear color value until next one is received, unless repeat option is set to true
|
|
|
|
if (!node.repeat || !canRepeat()) {
|
|
|
|
node.color = '';
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
findBlinkStick();
|
|
|
|
|
|
|
|
this.on("input", function(msg) {
|
2015-03-25 09:33:02 +01:00
|
|
|
if (typeof(node.led) !== "undefined") {
|
|
|
|
if (typeof(msg.payload) === 'object' ) {
|
|
|
|
// if it's an array then hopefully it's r,g,b,r,g,b or name,name,name
|
|
|
|
if (Array.isArray(msg.payload)) {
|
|
|
|
if (Object.size(node.led) !== 0) {
|
2015-03-26 19:55:03 +01:00
|
|
|
var dat = [];
|
2015-03-25 09:33:02 +01:00
|
|
|
for (var i = 0; i < msg.payload.length; i++) {
|
|
|
|
if (typeof msg.payload[i] === "string") { // if string then assume must be colour names
|
|
|
|
var params = node.led.interpretParameters(msg.payload[i]); // lookup colour code from name
|
|
|
|
if (params) {
|
2015-03-26 19:55:03 +01:00
|
|
|
dat.push(params.green);
|
|
|
|
dat.push(params.red);
|
|
|
|
dat.push(params.blue);
|
2015-03-25 09:33:02 +01:00
|
|
|
}
|
|
|
|
else { node.warn("invalid colour: "+msg.payload[i]); }
|
|
|
|
}
|
|
|
|
else { // otherwise lets use numbers 0-255
|
2015-03-26 19:55:03 +01:00
|
|
|
dat.push(msg.payload[i+1]);
|
|
|
|
dat.push(msg.payload[i]);
|
|
|
|
dat.push(msg.payload[i+2]);
|
2015-03-25 09:33:02 +01:00
|
|
|
i += 2;
|
2015-02-07 20:31:37 +01:00
|
|
|
}
|
|
|
|
}
|
2015-03-26 19:55:03 +01:00
|
|
|
if ((dat.length % 3) === 0) { // by now length must be a multiple of 3
|
|
|
|
node.led.setColors(0, dat, function(err) {
|
2015-03-25 09:33:02 +01:00
|
|
|
if (err) { node.log(err); }
|
|
|
|
});
|
2015-02-07 20:31:37 +01:00
|
|
|
}
|
2015-03-25 09:33:02 +01:00
|
|
|
else {
|
|
|
|
node.warn("Colour array length not / 3");
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
} // else if no blinkstick let it get caught below
|
|
|
|
}
|
|
|
|
// not an array - must be the "normal" object....
|
|
|
|
else {
|
|
|
|
var data = validatePayloadObject(msg.payload);
|
|
|
|
if (typeof(data) === 'object') {
|
|
|
|
node.task = data.task ? data.task : node.task;
|
|
|
|
node.delay = data.delay ? data.delay : node.delay;
|
|
|
|
node.repeats = data.repeats ? data.repeats : node.repeats;
|
|
|
|
node.duration = data.duration ? data.duration : node.duration;
|
|
|
|
node.steps = data.steps ? data.steps : node.steps;
|
|
|
|
node.repeat = data.repeat ? data.repeat : node.repeat;
|
|
|
|
node.color = data.color ? data.color : node.color;
|
2016-11-06 21:26:19 +01:00
|
|
|
node.channel = typeof(data.channel) !== 'undefined' ? data.channel : node.channel;
|
|
|
|
node.index = data.index ? data.index : node.index;
|
|
|
|
node.row = data.row ? data.row : node.row;
|
2017-01-29 18:45:44 +01:00
|
|
|
}
|
|
|
|
else {
|
2015-03-25 09:33:02 +01:00
|
|
|
node.error(data);
|
|
|
|
return;
|
2015-02-07 20:31:37 +01:00
|
|
|
}
|
|
|
|
}
|
2017-01-29 18:45:44 +01:00
|
|
|
}
|
|
|
|
else if (p1.test(msg.payload)) {
|
2015-03-25 09:33:02 +01:00
|
|
|
//Color value is represented as "red,green,blue" string of bytes
|
|
|
|
var rgb = msg.payload.split(",");
|
2014-10-01 11:02:44 +02:00
|
|
|
|
2015-03-25 09:33:02 +01:00
|
|
|
//Convert color value back to HEX string for easier implementation
|
|
|
|
node.color = "#" + decimalToHex(parseInt(rgb[0])&255) +
|
|
|
|
decimalToHex(parseInt(rgb[1])&255) + decimalToHex(parseInt(rgb[2])&255);
|
2017-01-29 18:45:44 +01:00
|
|
|
}
|
|
|
|
else {
|
2015-03-25 09:33:02 +01:00
|
|
|
//Sanitize color value
|
|
|
|
node.color = msg.payload.toLowerCase().replace(/\s+/g,'');
|
2015-04-17 22:07:25 +02:00
|
|
|
if (node.color === "amber") { node.color = "#FFBF00"; }
|
2016-02-22 19:56:42 +01:00
|
|
|
if (node.color === "off") { node.color = "#000000"; }
|
2014-10-01 11:02:44 +02:00
|
|
|
}
|
2015-03-25 09:33:02 +01:00
|
|
|
|
|
|
|
if (Object.size(node.led) !== 0) {
|
|
|
|
//Start color animation, otherwise the color is queued until current animation completes
|
2014-10-01 11:02:44 +02:00
|
|
|
if (animationComplete) {
|
|
|
|
applyColor();
|
|
|
|
}
|
2017-01-29 18:45:44 +01:00
|
|
|
}
|
|
|
|
else {
|
2015-03-25 09:33:02 +01:00
|
|
|
//Attempt to find BlinkStick and start animation if it's found
|
|
|
|
findBlinkStick(function() {
|
|
|
|
if (animationComplete) {
|
|
|
|
applyColor();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
node.status({fill:"red",shape:"ring",text:"not found"});
|
|
|
|
node.error("BlinkStick not found",msg);
|
2014-09-08 22:42:16 +02:00
|
|
|
}
|
2014-06-29 00:35:21 +02:00
|
|
|
});
|
|
|
|
|
2014-10-01 11:02:44 +02:00
|
|
|
this.on("close", function() {
|
|
|
|
//Set the flag to finish all animations
|
|
|
|
this.closing = true;
|
|
|
|
|
|
|
|
if (Object.size(node.led) !== 0) {
|
|
|
|
//Close device and stop animations
|
2014-11-20 09:44:05 +01:00
|
|
|
if (typeof this.led.close === "function") { this.led.close(); }
|
|
|
|
else { node.warn("Please upgrade blinkstick npm"); }
|
2014-10-01 11:02:44 +02:00
|
|
|
}
|
|
|
|
});
|
2013-10-30 21:38:23 +01:00
|
|
|
}
|
|
|
|
|
2014-06-29 00:35:21 +02:00
|
|
|
RED.nodes.registerType("blinkstick",BlinkStick);
|
2014-10-01 11:02:44 +02:00
|
|
|
|
2015-02-06 22:10:14 +01:00
|
|
|
RED.httpAdmin.get("/blinksticklist", RED.auth.needsPermission("blinkstick.read"), function(req,res) {
|
2014-10-01 11:02:44 +02:00
|
|
|
blinkstick.findAllSerials(function(serials) {
|
2015-02-06 22:10:14 +01:00
|
|
|
res.json(serials);
|
2014-10-01 11:02:44 +02:00
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|