const logger = require('./logger'); const blinkstick = require('blinkstick'); 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'; const LEDS = new Map(); let stop = false; // find a connected blinkstick function findBlinkstick() { return new Promise((resolve, reject) => { let led = blinkstick.findFirst(); if (led === undefined) { reject('could not find any blinkstick'); } resolve(led); }); } // 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]); 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; } } // 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; } } // animations async function singleAnimation(config, index) { try { config.options.index = index; 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; } 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; } } async function forceStop() { return new Promise((resolve, 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) { 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) { return new Promise((resolve, reject) => { LEDS.delete(index); resolve(); }); } 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); } function waitForAllAnimationsEnd(callback) { if (LEDS.size === 0) { return callback(); } setTimeout(() => { waitForAllAnimationsEnd(callback); }, 100); } function isInfiniteAnimation(config) { if (config.mode !== MODE_BLINK && config.mode !== MODE_PULSE) { return false; } if (config.options.index === LEDS_ALL) { return config.repetitions.max === 0; } return config.options.repeats === 0; } // exports module.exports = { simple, complex, powerOff, isInfiniteAnimation, LEDS_ALL, MODE_SET, MODE_MORPH, MODE_BLINK, MODE_PULSE, MODE_POWEROFF };