heavy rework to support individual leds, pulse mode currently broken

This commit is contained in:
Daniel Sommer 2022-02-22 16:42:49 +01:00
parent 5b22396e0d
commit 836d91ce04
2 changed files with 148 additions and 145 deletions

View file

@ -1,19 +1,22 @@
// requirements // requirements
const logger = require('./logger'); const logger = require('./logger');
const config = require('../config.json'); const config = require('../config.json');
const util = require('util');
// third party requirements // third party requirements
const blinkstick = require('blinkstick'); const blinkstick = require('blinkstick');
const hexcolor = require('hex-color-regex'); const hexcolor = require('hex-color-regex');
// constants // constants
// const LEDS = [0, 1, 2, 3, 4, 5, 6, 7];
const LEDS_ALL = 'index_all';
const RANDOM = 'random'; const RANDOM = 'random';
const ANIMATION_STATE_INPROGRESS = 1; const ANIMATION_STATE_INPROGRESS = 1;
const ANIMATION_STATE_FINISH = 0; const ANIMATION_STATE_FINISH = 0;
// letiables // variables
let led; let led;
let animation = {}; const LEDS = new Map();
function findBlinkstick() { function findBlinkstick() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@ -25,21 +28,143 @@ function findBlinkstick() {
}); });
} }
// turn the blinkstick off // light it up
function powerOff() { async function illuminate(blinkstickConfig) {
return new Promise((resolve, reject) => { led = await findBlinkstick();
findBlinkstick() let indices = getIndices(blinkstickConfig);
.then((led) => { for (let index = 0; index < indices.length; index++) {
led.stop(); try {
led.turnOff(); await setLedState(indices[index], ANIMATION_STATE_INPROGRESS);
logger.info('blinkstick powered off'); await singleAnimation(JSON.parse(JSON.stringify(blinkstickConfig)), indices[index]);
resolve(); } catch (err) {
}) logger.error(err);
.catch(reject); }
}) }
} }
// 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) { function parseColor(value) {
if (!value || value === RANDOM) { if (!value || value === RANDOM) {
return RANDOM; return RANDOM;
@ -54,7 +179,6 @@ function parseColor(value) {
}(); }();
} }
// parse rgb color values
function parseRGBColor(value) { function parseRGBColor(value) {
if (value.indexOf(',') === -1 && isRGBValue(value)) { if (value.indexOf(',') === -1 && isRGBValue(value)) {
return convertRGBToHex(parseInt(value) || 0, 0, 0); return convertRGBToHex(parseInt(value) || 0, 0, 0);
@ -77,17 +201,14 @@ function parseRGBColor(value) {
} }
} }
// check a single rgb value
function isRGBValue(value) { function isRGBValue(value) {
return value !== undefined && !isNaN(value) && value >= 0 && value <= 255; return value !== undefined && !isNaN(value) && value >= 0 && value <= 255;
} }
// convert rgb to hex color values
function convertRGBToHex(red, green, blue) { function convertRGBToHex(red, green, blue) {
return '#' + ((1 << 24) + (red << 16) + (green << 8) + blue).toString(16).slice(1); return '#' + ((1 << 24) + (red << 16) + (green << 8) + blue).toString(16).slice(1);
} }
// parse hex color values
function parseHexColor(value) { function parseHexColor(value) {
if (value[0] !== '#') { if (value[0] !== '#') {
value = '#' + value; 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 // exports
module.exports = { module.exports = {
parseColor, parseColor,
illuminate, illuminate,
powerOff powerOff,
LEDS_ALL
}; };

View file

@ -88,7 +88,7 @@ function handleRequests() {
app.post('/off', (request, response) => { app.post('/off', (request, response) => {
logger.http(request); logger.http(request);
response.end(); response.end();
blinkstick.powerOff(); blinkstick.powerOff(parseInt(request.body.index));
}); });
// POST methods // POST methods
app.post('*', (request, response) => { app.post('*', (request, response) => {
@ -107,11 +107,11 @@ function parseRequest(data) {
"mode": data.mode || config.api.post.mode.default, "mode": data.mode || config.api.post.mode.default,
"color": blinkstick.parseColor(data.color), "color": blinkstick.parseColor(data.color),
"options": { "options": {
"index": blinkstick.LEDS_ALL,
"steps": data.steps, "steps": data.steps,
"duration": data.duration || config.api.post.duration.default, "duration": data.duration || config.api.post.duration.default,
"pulse": { "pulse": {
"max": data.pulses || 0, "max": data.pulses || 0
"done": 0
} }
} }
}; };
@ -126,6 +126,9 @@ function parseRequest(data) {
if (blinkstickConfig.options.duration < 100) { if (blinkstickConfig.options.duration < 100) {
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) { if (blinkstickConfig.options.steps === undefined || blinkstickConfig.options.steps === 0) {
blinkstickConfig.options.steps = blinkstickConfig.options.duration / 10; blinkstickConfig.options.steps = blinkstickConfig.options.duration / 10;
} }