// requirements const logger = require('./logger'); // third party requirements const blinkstick = require('blinkstick'); const async = require('async'); const hexcolor = require('hex-color-regex'); // constants const COLORTYPE_RGB = "rgb"; const COLORTYPE_HEX = "hex"; const ANIMATION_STATE_INFINITE = 2; const ANIMATION_STATE_INPROGRESS = 1; const ANIMATION_STATE_FINISH = 0; // variables let led; let animation = {}; function findBlinkstick() { return new Promise(function (resolve, reject) { led = blinkstick.findFirst(); if (led !== undefined && led !== null) { return resolve(led); } return reject("could not find any blinkstick"); }); } // turn the blinkstick off function powerOff() { led.turnOff(); logger.info("blinkstick powered off"); } // parse a color object from given request arguments function parseColor(blinkstickConfig) { return new Promise(function (resolve, reject) { if (blinkstickConfig.color == "random") { resolve(color); } if (blinkstickConfig.color == undefined || blinkstickConfig.color.length === 0) { reject("no hex color code given, request will be ignored"); } let parsedColor = parseRGBColor(blinkstickConfig.color); if (!parsedColor) { parsedColor = parseHexColor(blinkstickConfig.color); } if (!parsedColor) { return reject("could not parse given color code '" + color + "'"); } blinkstickConfig.color = parsedColor; resolve(blinkstickConfig); }); } // parse rgb color values function parseRGBColor(colorValues) { let color = {}; if (colorValues.indexOf(",") == -1) { if (isRGBValue(colorValues)) { color.type = COLORTYPE_RGB; color.red = parseInt(colorValues); return color; } } else { const splittedValues = colorValues.split(","); for (let index = 0; index < splittedValues.length; index++) { const value = splittedValues[index]; if (!isRGBValue(value)) { continue; } if (index == 0) { color.red = parseInt(value); } else if (index === 1) { color.green = parseInt(value); } else if (index === 2) { color.blue = parseInt(value); } } if (color && (color.red || color.green || color.blue)) { color.type = COLORTYPE_RGB; return color; } } if (isRGBValue(colorValues.red) || isRGBValue(colorValues.green) || isRGBValue(colorValues.blue)) { color.type = COLORTYPE_RGB; return colorValues; } return undefined; } // check a single rgb value function isRGBValue(value) { return value != undefined && value.length > 0 && !isNaN(value) && value >= 0 && value <= 255; } // parse hex color values function parseHexColor(colorValues) { if (colorValues[0] !== '#') { colorValues = '#' + colorValues; } if (colorValues.length === 4) { colorValues.type = COLORTYPE_HEX; return (colorValues[0] + colorValues[1] + colorValues[1] + colorValues[2] + colorValues[2] + colorValues[3] + colorValues[3]); } if (colorValues.length === 7 && hexcolor({ strict: true }).test(colorValues)) { colorValues.type = COLORTYPE_HEX; return colorValues; } return undefined; } // pass the options to the blinkstick accordingly function illuminate(blinkstickConfig) { return new Promise(function (resolve, reject) { waitForAnimation let color; findBlinkstick().then(function () { if (blinkstickConfig.color.type === COLORTYPE_HEX) { color = blinkstickConfig.color.red; } else { color = blinkstickConfig.color.red + ", " + blinkstickConfig.color.green + ", " + blinkstickConfig.color.blue; } switch (blinkstickConfig.mode) { case "morph": morph(blinkstickConfig, color, resolve, reject); break; case "pulse": startPulsing(blinkstickConfig, color, resolve, reject); break; default: setColor(blinkstickConfig, color, resolve, reject); break; } }) .catch(reject); }); } // set a static color function setColor(blinkstickConfig, color, resolve, reject) { stopAnimation() .then(function () { setAnimationProperties(blinkstickConfig); led.setColor(blinkstickConfig.color.red, blinkstickConfig.color.green, blinkstickConfig.color.blue, function (err) { clearAnimationProperties(); logger.debug("setting color to '" + color + "'..."); if (err) { return reject("error setting color '" + color + "' (" + err + ")"); } return resolve("set color to '" + color + "'"); }); }); } // morph to a color function morph(blinkstickConfig, color, resolve, reject) { stopAnimation() .then(function () { setAnimationProperties(blinkstickConfig); led.morph(blinkstickConfig.color.red, blinkstickConfig.color.green, blinkstickConfig.color.blue, blinkstickConfig.options, function (err) { clearAnimationProperties(); logger.debug("morphing color to '" + color + "'..."); if (err) { return reject("error morphing color to '" + color + "' (" + err + ")"); } return resolve("morphed color to '" + color + "'"); }); }); } // start pulsing function startPulsing(blinkstickConfig, color, resolve, reject) { if (animation.state == ANIMATION_STATE_FINISH || (blinkstickConfig.options.pulse.max > 0 && (blinkstickConfig.options.pulse.done && blinkstickConfig.options.pulse.done < blinkstickConfig.options.pulse.max))) { clearAnimationProperties(); return resolve("finished pulsing color '" + color + "'"); } if (animation.id && animation.id != blinkstickConfig.id) { stopAnimation() .then(logger.info) .then(function () { startPulsing(blinkstickConfig, color, resolve, reject) }) .catch(logger.error); return; } setAnimationProperties(blinkstickConfig); led.pulse(blinkstickConfig.color.red, blinkstickConfig.color.green, blinkstickConfig.color.blue, blinkstickConfig.options, function (err) { if (err) { clearAnimationProperties(); return reject("error pulsing color '" + color + "' (" + err + ")"); } blinkstickConfig.options.pulse.done++; logger.debug("pulsed color '" + color + "' " + blinkstickConfig.options.pulse.done + "/" + blinkstickConfig.options.pulse.max + " times"); startPulsing(blinkstickConfig, color, resolve, reject); }); } // set properties for the current animation function setAnimationProperties(blinkstickConfig) { if (animation.id == blinkstickConfig.id) { return; } led.animationsEnabled = true; animation.id = blinkstickConfig.id; if (blinkstickConfig.options.pulse && blinkstickConfig.options.pulse.max === 0) { animation.state = ANIMATION_STATE_INFINITE; } else { animation.state = ANIMATION_STATE_INPROGRESS; } } // clear properties for the current animation function clearAnimationProperties() { led.stop(); animation = {}; } // stop the current animation function stopAnimation() { return new Promise(function (resolve, reject) { if (!isAnimationInProgress()) { return resolve(); } animation.state = ANIMATION_STATE_FINISH; waitForAnimation(Date.now(), resolve, reject); }); } // wait for current animation function waitForAnimation(timestamp, resolve, reject) { if (!isAnimationInProgress()) { return resolve(); } setTimeout(function () { waitForAnimation(timestamp, resolve, reject); }, 100); } // is currently an animation in progress function isAnimationInProgress() { return animation != undefined && animation.state != undefined; } // is currently an infinite animation in progress function isInfiniteAnimationInProgress() { return isAnimationInProgress() && animation.state == ANIMATION_STATE_INFINITE; } // exports module.exports = { parseColor, illuminate, isAnimationInProgress, isInfiniteAnimationInProgress, stopAnimation, powerOff };