diff --git a/libs/blinkstick.js b/libs/blinkstick.js index 447d1f4..0025eba 100644 --- a/libs/blinkstick.js +++ b/libs/blinkstick.js @@ -1,19 +1,22 @@ // requirements const logger = require('./logger'); const config = require('../config.json'); +const util = require('util'); // third party requirements const blinkstick = require('blinkstick'); const hexcolor = require('hex-color-regex'); // constants +// const LEDS = [0, 1, 2, 3, 4, 5, 6, 7]; +const LEDS_ALL = 'index_all'; const RANDOM = 'random'; const ANIMATION_STATE_INPROGRESS = 1; const ANIMATION_STATE_FINISH = 0; -// letiables +// variables let led; -let animation = {}; +const LEDS = new Map(); function findBlinkstick() { return new Promise((resolve, reject) => { @@ -25,21 +28,143 @@ function findBlinkstick() { }); } -// 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); - }) +// light it up +async function illuminate(blinkstickConfig) { + led = await findBlinkstick(); + let indices = getIndices(blinkstickConfig); + for (let index = 0; index < indices.length; index++) { + try { + await setLedState(indices[index], ANIMATION_STATE_INPROGRESS); + await singleAnimation(JSON.parse(JSON.stringify(blinkstickConfig)), indices[index]); + } catch (err) { + logger.error(err); + } + } } -// parse a color object from given request arguments +// turn the blinkstick or specified led off +async function powerOff(index) { + led = await findBlinkstick(); + // check for NaN + if (index !== index) { + index = LEDS_ALL; + } + let config = {color: '#000000', mode: 'poweroff', options: {index: index}}; + let indices = getIndices(config); + for (let index = 0; index < indices.length; index++) { + try { + await singleAnimation(JSON.parse(JSON.stringify(config)), indices[index]); + } catch (err) { + logger.error(err); + } + } + if (index !== LEDS_ALL) { + logger.info('led \'' + index + '\' powered off'); + return; + } + led.stop(); + led.turnOff(); + logger.info('blinkstick powered off'); +} + +// animations +function singleAnimation(config, index) { + return new Promise((resolve, reject) => { + config.options.index = index; + logger.debug('changing color of led \'' + config.options.index + '\' to \'' + config.color + '\' (mode: ' + config.mode + ')...'); + switch (config.mode) { + case 'morph': + led.morph(config.color, config.options, callback); + break; + case 'pulse': + led.pulse(config.color, config.options, callback); + break; + default: + led.setColor(config.color, config.options, callback); + } + + function callback(err) { + clearLedState(config.options.index); + if (err) { + reject('changing color of led \'' + config.options.index + '\' to \'' + config.color + '\' encountered an error > ' + err); + } + logger.debug('changed color of led \'' + config.options.index + '\' to \'' + config.color + '\' (mode: ' + config.mode + ')'); + resolve(); + } + }); +} + +// start pulsing +function startPulsing(blinkstickConfig) { + return new Promise((resolve, reject) => { + let animation = getAnimation(blinkstickConfig); + if (animation?.state === ANIMATION_STATE_FINISH || (blinkstickConfig.options.pulse.max > 0 && animation.done === blinkstickConfig.options.pulse.max)) { + clearAnimationProperties(blinkstickConfig); + return resolve('finished pulsing \'' + blinkstickConfig.color + '\''); + } + let currentAnimation = getAnimationByIndex(blinkstickConfig?.options?.index); + if (currentAnimation !== undefined && currentAnimation.id !== blinkstickConfig.id) { + stopAnimation(currentAnimation) + .then(logger.info) + .then(() => { + startPulsing(blinkstickConfig, resolve, reject) + }) + .catch(logger.error); + return; + } + animation = setAnimationProperties(blinkstickConfig); + led.pulse(blinkstickConfig.color, blinkstickConfig.options, (err) => { + if (err) { + clearAnimationProperties(blinkstickConfig); + return reject('error pulsing ' + msg + ' > ' + err); + } + animation.done++; + logger.debug('pulsed ' + msg + ' ' + animation.done + '/' + blinkstickConfig.options.pulse.max + ' times'); + startPulsing(blinkstickConfig) + .catch(reject); + }); + }); +} + +// led / index helper functions +function getIndices(blinkstickConfig) { + if (blinkstickConfig.options.index === LEDS_ALL) { + return [0, 1, 2, 3, 4, 5, 6, 7]; + } + return [blinkstickConfig.options.index]; +} + +async function setLedState(index, state) { + await stopLedAnimation(index); + LEDS.set(index, state); +} + +function clearLedState(index) { + LEDS.delete(index); +} + +function isLedAnimated(index) { + return (LEDS.get(index) || LEDS.get(LEDS_ALL)) !== undefined; +} + +function stopLedAnimation(index) { + return new Promise((resolve, reject) => { + waitForAnimationEnd(index, () => { + resolve(); + }); + }); +} + +function waitForAnimationEnd(index, callback) { + if (!isLedAnimated(index)) { + return callback(); + } + setTimeout(() => { + waitForAnimationEnd(index, callback); + }, 100); +} + +// color / parser functions function parseColor(value) { if (!value || value === RANDOM) { return RANDOM; @@ -54,7 +179,6 @@ function parseColor(value) { }(); } -// parse rgb color values function parseRGBColor(value) { if (value.indexOf(',') === -1 && isRGBValue(value)) { return convertRGBToHex(parseInt(value) || 0, 0, 0); @@ -77,17 +201,14 @@ function parseRGBColor(value) { } } -// 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; @@ -100,131 +221,10 @@ function parseHexColor(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 + powerOff, + LEDS_ALL }; \ No newline at end of file diff --git a/libs/server.js b/libs/server.js index 56c7dce..8a985f4 100644 --- a/libs/server.js +++ b/libs/server.js @@ -88,7 +88,7 @@ function handleRequests() { app.post('/off', (request, response) => { logger.http(request); response.end(); - blinkstick.powerOff(); + blinkstick.powerOff(parseInt(request.body.index)); }); // POST methods app.post('*', (request, response) => { @@ -107,11 +107,11 @@ function parseRequest(data) { "mode": data.mode || config.api.post.mode.default, "color": blinkstick.parseColor(data.color), "options": { + "index": blinkstick.LEDS_ALL, "steps": data.steps, "duration": data.duration || config.api.post.duration.default, "pulse": { - "max": data.pulses || 0, - "done": 0 + "max": data.pulses || 0 } } }; @@ -126,6 +126,9 @@ function parseRequest(data) { if (blinkstickConfig.options.duration < 100) { blinkstickConfig.options.duration = 100; } + if (blinkstickConfig.options.index === blinkstick.LEDS_ALL) { + blinkstickConfig.options.duration = blinkstickConfig.options.duration / 8; + } if (blinkstickConfig.options.steps === undefined || blinkstickConfig.options.steps === 0) { blinkstickConfig.options.steps = blinkstickConfig.options.duration / 10; }