blinky/libs/blinkstick.js

258 lines
No EOL
7.8 KiB
JavaScript

// 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 MODE_SET = 'set';
const MODE_MORPH = 'morph';
const MODE_PULSE = 'pulse';
const MODE_POWEROFF = 'poweroff';
// variables
let led;
const LEDS = new Map();
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');
});
}
// 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 {
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 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);
}
logger.debug('changed color of led \'' + config.options.index + '\' to \'' + config.color + '\' (mode: ' + config.mode + ')');
resolve();
}
});
}
// TODO: IMPLEMENT FUNCTION
function continousAnimation(config, index) {
}
// 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;
}
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;
}();
}
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 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;
}
}
// exports
module.exports = {
parseColor,
illuminate,
powerOff,
LEDS_ALL,
MODE_SET,
MODE_MORPH,
MODE_PULSE,
MODE_POWEROFF
};