// requirements const logger = require('./logger'); const config = require('../config.json'); // third party requirements const blinkstick = require('blinkstick'); const hexcolor = require('hex-color-regex'); // constants const RANDOM = 'random'; const ANIMATION_STATE_INPROGRESS = 1; const ANIMATION_STATE_FINISH = 0; // letiables let led; let animation = {}; function findBlinkstick() { return new Promise((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() { return new Promise((resolve, reject) => { findBlinkstick() .then((led) => { led.stop(); led.turnOff(); logger.info('blinkstick powered off'); resolve(); }) .catch(reject); }) } // parse a color object from given request arguments function parseColor(value) { if (!value || value === RANDOM) { return RANDOM; } let parsedColor = parseRGBColor(value); if (!parsedColor) { parsedColor = parseHexColor(value); } return parsedColor || function () { logger.warn('could not parse color value \'' + value + '\', defaulting to \'' + config.api.post.color.default + '\''); return config.api.post.color.default; }(); } // parse rgb color values function parseRGBColor(value) { if (value.indexOf(',') === -1 && isRGBValue(value)) { return convertRGBToHex(parseInt(value) || 0, 0, 0); } else { const splittedValues = value.split(','); let color = {}; for (let index = 0; index < splittedValues.length; index++) { const value = splittedValues[index]; if (index === 0) { color.red = parseInt(value) || 0; } else if (index === 1) { color.green = parseInt(value) || 0; } else if (index === 2) { color.blue = parseInt(value) || 0; } } if (isRGBValue(color.red) && isRGBValue(color.green) && isRGBValue(color.blue)) { return convertRGBToHex(color.red, color.green, color.blue); } } } // check a single rgb value function isRGBValue(value) { return value !== undefined && !isNaN(value) && value >= 0 && value <= 255; } // convert rgb to hex color values function convertRGBToHex(red, green, blue) { return '#' + ((1 << 24) + (red << 16) + (green << 8) + blue).toString(16).slice(1); } // parse hex color values function parseHexColor(value) { if (value[0] !== '#') { value = '#' + value; } if (value.length === 4) { value = (value[0] + value[1] + value[1] + value[2] + value[2] + value[3] + value[3]); } if (hexcolor({strict: true}).test(value)) { return value; } } // pass the options to the blinkstick accordingly function illuminate(blinkstickConfig) { return new Promise((resolve, reject) => { findBlinkstick() .then(() => { switch (blinkstickConfig.mode) { case 'morph': morph(blinkstickConfig, resolve, reject); break; case 'pulse': startPulsing(blinkstickConfig, resolve, reject); break; default: setColor(blinkstickConfig, resolve, reject); break; } }) .catch(reject); }); } // set a static color function setColor(blinkstickConfig, resolve, reject) { stopAnimation() .then(() => { setAnimationProperties(blinkstickConfig); logger.debug('setting color to \'' + blinkstickConfig.color + '\'...'); led.setColor(blinkstickConfig.color, blinkstickConfig.options, (err) => { clearAnimationProperties(); if (err) { return reject('error setting color \'' + blinkstickConfig.color + '\' (' + err + ')'); } return resolve('set color to \'' + blinkstickConfig.color + '\''); }); }); } // morph to a color function morph(blinkstickConfig, resolve, reject) { stopAnimation() .then(() => { setAnimationProperties(blinkstickConfig); logger.debug('morphing color to \'' + blinkstickConfig.color + '\'...'); led.morph(blinkstickConfig.color, blinkstickConfig.options, (err) => { clearAnimationProperties(); if (err) { return reject('error morphing color to \'' + blinkstickConfig.color + '\' (' + err + ')'); } return resolve('morphed color to \'' + blinkstickConfig.color + '\''); }); }); } // start pulsing function startPulsing(blinkstickConfig, 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 \'' + blinkstickConfig.color + '\''); } if (animation.id && animation.id !== blinkstickConfig.id) { stopAnimation() .then(logger.info) .then(function () { startPulsing(blinkstickConfig, resolve, reject) }) .catch(logger.error); return; } setAnimationProperties(blinkstickConfig); led.pulse(blinkstickConfig.color, blinkstickConfig.options, (err) => { if (err) { clearAnimationProperties(); return reject('error pulsing color \'' + blinkstickConfig.color + '\' (' + err + ')'); } blinkstickConfig.options.pulse.done++; logger.debug('pulsed color \'' + blinkstickConfig.color + '\' ' + blinkstickConfig.options.pulse.done + '/' + blinkstickConfig.options.pulse.max + ' times'); startPulsing(blinkstickConfig, resolve, reject); }); } // set properties for the current animation function setAnimationProperties(blinkstickConfig) { if (animation.id === blinkstickConfig.id) { return; } led.animationsEnabled = true; animation.id = blinkstickConfig.id; 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((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(() => { waitForAnimation(timestamp, resolve, reject); }, 100); } // is currently an animation in progress function isAnimationInProgress() { return animation !== undefined && animation.state !== undefined; } // exports module.exports = { parseColor, illuminate, powerOff };