diff --git a/config.json b/config.json index 135fd37..8c1758b 100644 --- a/config.json +++ b/config.json @@ -12,11 +12,13 @@ "description": "show this page" }, "post": { - "mode": { - "available": "set, morph, pulse", - "default": "set", - "description": "specifies the color change mode" - }, + "endpoints": [ + "/set", + "/morph", + "/blink", + "/pulse", + "/poweroff" + ], "color": { "available": "random, hex color codes (#ffffff), rgb color codes (255, 255, 255)", "default": "random", @@ -24,8 +26,8 @@ }, "index": { "available": "number values", - "default": "undefined", - "description": "specifies the led index" + "default": "all", + "description": "specifies the led index / defaults to all leds" }, "duration": { "available": "number values", @@ -35,13 +37,19 @@ "steps": { "available": "number values", "default": "[duration] / 10", - "description": "specifies the amount of steps for the color change" + "description": "specifies the number of steps for the color change" }, - "pulses": { - "restrictions": "pulse", + "repeats": { + "restrictions": "blink, pulse", "available": "number values", "default": "0 (infinite)", - "description": "specifies the number of pulses" + "description": "specifies the number of blinks/pulses" + }, + "delay": { + "restrictions": "blink", + "available": "number values", + "default": "500", + "description": "delay between blinks in milliseconds " } } } diff --git a/libs/blinkstick.js b/libs/blinkstick.js index 9bd778d..441b866 100644 --- a/libs/blinkstick.js +++ b/libs/blinkstick.js @@ -1,155 +1,155 @@ -// 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; +const LED_ANIMATED = 0; + +const LEDS_ALL = 'all'; const MODE_SET = 'set'; const MODE_MORPH = 'morph'; +const MODE_BLINK = 'blink'; const MODE_PULSE = 'pulse'; const MODE_POWEROFF = 'poweroff'; -// variables -let led; const LEDS = new Map(); +let stop = false; + +// find a connected blinkstick function findBlinkstick() { return new Promise((resolve, reject) => { - led = blinkstick.findFirst(); - if (led !== undefined && led !== null) { - return resolve(led); + let led = blinkstick.findFirst(); + if (led === undefined) { + reject('could not find any blinkstick'); } - return reject('could not find any blinkstick'); + resolve(led); }); } -// 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]); - blinkstickConfig.options.pulse++; - if (maxPulsesReached(blinkstickConfig)) { - return; - } - } catch (err) { - logger.error(err); - } - } - if (!maxPulsesReached(blinkstickConfig)) { - return illuminate(blinkstickConfig); - } -} - -function maxPulsesReached(blinkstickConfig) { - if (blinkstickConfig.mode !== MODE_PULSE) { - return true; - } - return (blinkstickConfig.options.pulse.max === 0 || blinkstickConfig.options.pulse.done > blinkstickConfig.options.pulse.max); -} - -// 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: MODE_POWEROFF, options: {index: index}}; - let indices = getIndices(config); - for (let index = 0; index < indices.length; index++) { - try { +// simple animation (set the color / morph to color) +async function simple(config) { + try { + await forceStop(); + config.timestamp = new Date().getTime(); + let indices = getIndices(config); + for (let index = 0; index < indices.length; index++) { + await setLedState(indices[index], LED_ANIMATED); await singleAnimation(JSON.parse(JSON.stringify(config)), indices[index]); - } catch (err) { - logger.error(err); + await clearLedState(indices[index]); } + return { + status: 'ok', + color: config.color, + indices: indices, + time: (new Date().getTime() - config.timestamp) + 'ms' + }; + } catch (err) { + logger.error(err); + return err; } - if (index !== LEDS_ALL) { - logger.info('led \'' + index + '\' powered off'); - return; +} + +// complex animation (pulse / blink) +async function complex(config) { + try { + if (config.timestamp === undefined) { + await powerOff({id: Math.random(), mode: MODE_POWEROFF, color: '#000000', options: {index: LEDS_ALL}}); + config.timestamp = new Date().getTime(); + } + let indices = getIndices(config); + for (let index = 0; index < indices.length; index++) { + if (shouldLedFinish(config)) { + return {status: 'ok', time: (new Date().getTime() - config.timestamp) + 'ms'}; + } + await setLedState(indices[index], LED_ANIMATED); + await singleAnimation(JSON.parse(JSON.stringify(config)), indices[index]); + await clearLedState(indices[index]); + config.repetitions.done++; + } + return complex(config); + } catch (err) { + logger.error(err); + return err; + } +} + +// power the blinkstick (or just a specific led) off +async function powerOff(config) { + try { + await forceStop(); + config.timestamp = new Date().getTime(); + let indices = getIndices(config); + for (let index = 0; index < indices.length; index++) { + await singleAnimation(JSON.parse(JSON.stringify(config)), indices[index]); + logger.info('led \'' + indices[index] + '\' powered off'); + } + if (config.options.index === LEDS_ALL) { + const blinkstick = await findBlinkstick(); + blinkstick.stop(); + blinkstick.turnOff(); + logger.info('blinkstick powered off'); + } + return {status: 'ok', indices: indices, time: (new Date().getTime() - config.timestamp) + 'ms'}; + } catch (err) { + return err; } - led.stop(); - led.turnOff(); - logger.info('blinkstick powered off'); } // animations -function singleAnimation(config, index) { - return new Promise((resolve, reject) => { +async function singleAnimation(config, index) { + try { config.options.index = index; - logger.debug('changing color of led \'' + config.options.index + '\' to \'' + config.color + '\' (mode: ' + config.mode + ')...'); - switch (config.mode) { - case MODE_MORPH: - led.morph(config.color, config.options, callback); - break; - case MODE_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); + const blinkstick = await findBlinkstick(); + return await new Promise((resolve, reject) => { + logger.debug('changing color of led \'' + config.options.index + '\' to \'' + config.color + '\' (mode: ' + config.mode + ')...'); + switch (config.mode) { + case MODE_MORPH: + blinkstick.morph(config.color, config.options, callback); + break; + case MODE_BLINK: + blinkstick.blink(config.color, config.options, callback); + break; + case MODE_PULSE: + blinkstick.pulse(config.color, config.options, callback); + break; + default: + blinkstick.setColor(config.color, config.options, callback); + break; } - logger.debug('changed color of led \'' + config.options.index + '\' to \'' + config.color + '\' (mode: ' + config.mode + ')'); - resolve(); - } - }); + + function callback(err) { + 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(); + } + }); + } catch (err) { + return err; + } } -// TODO: IMPLEMENT FUNCTION -function continousAnimation(config, index) { - -} - -// start pulsing -function startPulsing(blinkstickConfig) { +async function forceStop() { 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); + stop = true; + waitForAllAnimationsEnd(() => { + stop = false; + resolve(); }); }); } +function shouldLedFinish(config) { + if (stop) { + return true; + } + if (config.mode === MODE_BLINK && config.mode === MODE_PULSE) { + return config.repetitions.max !== 0 && config.repetitions.done >= config.repetitions.max + } + return false; +} + // led / index helper functions function getIndices(blinkstickConfig) { if (blinkstickConfig.options.index === LEDS_ALL) { @@ -164,7 +164,10 @@ async function setLedState(index, state) { } function clearLedState(index) { - LEDS.delete(index); + return new Promise((resolve, reject) => { + LEDS.delete(index); + resolve(); + }); } function isLedAnimated(index) { @@ -188,71 +191,35 @@ function waitForAnimationEnd(index, callback) { }, 100); } -// color / parser functions -function parseColor(value) { - if (!value || value === RANDOM) { - return RANDOM; +function waitForAllAnimationsEnd(callback) { + if (LEDS.size === 0) { + return callback(); } - 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; - }(); + setTimeout(() => { + waitForAllAnimationsEnd(callback); + }, 100); } -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); - } +function isInfiniteAnimation(config) { + if (config.mode !== MODE_BLINK && config.mode !== MODE_PULSE) { + return false; } -} - -function isRGBValue(value) { - return value !== undefined && !isNaN(value) && value >= 0 && value <= 255; -} - -function convertRGBToHex(red, green, blue) { - return '#' + ((1 << 24) + (red << 16) + (green << 8) + blue).toString(16).slice(1); -} - -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; + if (config.options.index === LEDS_ALL) { + return config.repetitions.max === 0; } + return config.options.repeats === 0; } // exports module.exports = { - parseColor, - illuminate, + simple, + complex, powerOff, + isInfiniteAnimation, LEDS_ALL, MODE_SET, MODE_MORPH, + MODE_BLINK, MODE_PULSE, MODE_POWEROFF }; \ No newline at end of file diff --git a/libs/index.js b/libs/index.js new file mode 100644 index 0000000..c75366a --- /dev/null +++ b/libs/index.js @@ -0,0 +1,66 @@ +const packageJSON = require('../package.json'); +const config = require('../config.json'); + +function get() { + let html = + '' + + '
' + + '' + config.api.get.description + '
' + + 'argument | ' + + 'available values | ' + + 'default | ' + + 'restrictions | ' + + 'description | '; + Object.keys(config.api.post).forEach((argument) => { + if (argument === 'endpoints') { + return; + } + let restrictions = config.api.post[argument].restrictions || ''; + html += + '
---|---|---|---|---|
' + argument + ' | ' + + '' + config.api.post[argument].available + ' | ' + + '' + config.api.post[argument].default + ' | ' + + '' + restrictions + ' | ' + + '' + config.api.post[argument].description + ' | ' + + '
" + config.api.get.description + "
" + - "argument | " + - "available values | " + - "default | " + - "restrictions | " + - "description | "; - Object.keys(config.api.post).forEach(function (argument) { - let restrictions = config.api.post[argument].restrictions || ""; - welcomeMessage += - "
---|---|---|---|---|
" + argument + " | " + - "" + config.api.post[argument].available + " | " + - "" + config.api.post[argument].default + " | " + - "" + restrictions + " | " + - "" + config.api.post[argument].description + " | " + - "