2021-04-06 16:41:49 +02:00
|
|
|
// requirements
|
|
|
|
const logger = require('./logger');
|
2021-04-07 12:04:06 +02:00
|
|
|
const config = require('../config.json');
|
2021-04-06 16:41:49 +02:00
|
|
|
|
|
|
|
// third party requirements
|
|
|
|
const blinkstick = require('blinkstick');
|
|
|
|
const hexcolor = require('hex-color-regex');
|
|
|
|
|
|
|
|
// constants
|
2021-04-07 12:04:06 +02:00
|
|
|
const RANDOM = "random"
|
2021-04-06 16:41:49 +02:00
|
|
|
const ANIMATION_STATE_INFINITE = 2;
|
|
|
|
const ANIMATION_STATE_INPROGRESS = 1;
|
|
|
|
const ANIMATION_STATE_FINISH = 0;
|
|
|
|
|
|
|
|
// variables
|
|
|
|
let led;
|
|
|
|
let animation = {};
|
|
|
|
|
|
|
|
function findBlinkstick() {
|
|
|
|
return new Promise(function (resolve, reject) {
|
|
|
|
led = blinkstick.findFirst();
|
|
|
|
if (led !== undefined && led !== null) {
|
|
|
|
return resolve(led);
|
|
|
|
}
|
|
|
|
return reject("could not find any blinkstick");
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// turn the blinkstick off
|
|
|
|
function powerOff() {
|
|
|
|
led.turnOff();
|
|
|
|
logger.info("blinkstick powered off");
|
|
|
|
}
|
|
|
|
|
|
|
|
// parse a color object from given request arguments
|
2021-04-07 12:04:06 +02:00
|
|
|
function parseColor(value) {
|
|
|
|
if (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;
|
|
|
|
}();
|
2021-04-06 16:41:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// parse rgb color values
|
2021-04-07 12:04:06 +02:00
|
|
|
function parseRGBColor(value) {
|
|
|
|
if (value.indexOf(",") == -1 && isRGBValue(value)) {
|
|
|
|
return convertRGBToHex(parseInt(value) || 0, 0, 0);
|
2021-04-06 16:41:49 +02:00
|
|
|
} else {
|
2021-04-07 12:04:06 +02:00
|
|
|
const splittedValues = value.split(",");
|
|
|
|
let color = {};
|
2021-04-06 16:41:49 +02:00
|
|
|
for (let index = 0; index < splittedValues.length; index++) {
|
|
|
|
const value = splittedValues[index];
|
|
|
|
if (index == 0) {
|
2021-04-07 12:04:06 +02:00
|
|
|
color.red = parseInt(value) || 0;
|
2021-04-06 16:41:49 +02:00
|
|
|
} else if (index === 1) {
|
2021-04-07 12:04:06 +02:00
|
|
|
color.green = parseInt(value) || 0;
|
2021-04-06 16:41:49 +02:00
|
|
|
} else if (index === 2) {
|
2021-04-07 12:04:06 +02:00
|
|
|
color.blue = parseInt(value) || 0;
|
2021-04-06 16:41:49 +02:00
|
|
|
}
|
|
|
|
}
|
2021-04-07 12:04:06 +02:00
|
|
|
if (isRGBValue(color.red) && isRGBValue(color.green) && isRGBValue(color.blue)) {
|
|
|
|
return convertRGBToHex(color.red, color.green, color.blue);
|
2021-04-06 16:41:49 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// check a single rgb value
|
|
|
|
function isRGBValue(value) {
|
2021-04-07 12:04:06 +02:00
|
|
|
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);
|
2021-04-06 16:41:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// parse hex color values
|
2021-04-07 12:04:06 +02:00
|
|
|
function parseHexColor(value) {
|
|
|
|
if (value[0] != '#') {
|
|
|
|
value = '#' + value;
|
2021-04-06 16:41:49 +02:00
|
|
|
}
|
2021-04-07 12:04:06 +02:00
|
|
|
if (value.length === 4) {
|
|
|
|
value = (value[0] + value[1] + value[1] + value[2] + value[2] + value[3] + value[3]);
|
2021-04-06 16:41:49 +02:00
|
|
|
}
|
2021-04-07 12:04:06 +02:00
|
|
|
if (hexcolor({ strict: true }).test(value)) {
|
|
|
|
return value;
|
2021-04-06 16:41:49 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// pass the options to the blinkstick accordingly
|
|
|
|
function illuminate(blinkstickConfig) {
|
|
|
|
return new Promise(function (resolve, reject) {
|
2021-04-07 12:04:06 +02:00
|
|
|
findBlinkstick()
|
|
|
|
.then(function () {
|
|
|
|
switch (blinkstickConfig.mode) {
|
|
|
|
case "morph":
|
|
|
|
morph(blinkstickConfig, resolve, reject);
|
|
|
|
break;
|
|
|
|
case "pulse":
|
|
|
|
startPulsing(blinkstickConfig, resolve, reject);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
setColor(blinkstickConfig, resolve, reject);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
})
|
2021-04-06 16:41:49 +02:00
|
|
|
.catch(reject);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// set a static color
|
2021-04-07 12:04:06 +02:00
|
|
|
function setColor(blinkstickConfig, resolve, reject) {
|
2021-04-06 16:41:49 +02:00
|
|
|
stopAnimation()
|
|
|
|
.then(function () {
|
|
|
|
setAnimationProperties(blinkstickConfig);
|
2021-04-07 12:04:06 +02:00
|
|
|
logger.debug("setting color to '" + blinkstickConfig.color + "'...");
|
|
|
|
led.setColor(blinkstickConfig.color, blinkstickConfig.options, function (err) {
|
2021-04-06 16:41:49 +02:00
|
|
|
clearAnimationProperties();
|
|
|
|
if (err) {
|
2021-04-07 12:04:06 +02:00
|
|
|
return reject("error setting color '" + blinkstickConfig.color + "' (" + err + ")");
|
2021-04-06 16:41:49 +02:00
|
|
|
}
|
2021-04-07 12:04:06 +02:00
|
|
|
return resolve("set color to '" + blinkstickConfig.color + "'");
|
2021-04-06 16:41:49 +02:00
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// morph to a color
|
2021-04-07 12:04:06 +02:00
|
|
|
function morph(blinkstickConfig, resolve, reject) {
|
2021-04-06 16:41:49 +02:00
|
|
|
stopAnimation()
|
|
|
|
.then(function () {
|
|
|
|
setAnimationProperties(blinkstickConfig);
|
2021-04-07 12:04:06 +02:00
|
|
|
logger.debug("morphing color to '" + blinkstickConfig.color + "'...");
|
|
|
|
led.morph(blinkstickConfig.color, blinkstickConfig.options, function (err) {
|
2021-04-06 16:41:49 +02:00
|
|
|
clearAnimationProperties();
|
|
|
|
if (err) {
|
2021-04-07 12:04:06 +02:00
|
|
|
return reject("error morphing color to '" + blinkstickConfig.color + "' (" + err + ")");
|
2021-04-06 16:41:49 +02:00
|
|
|
}
|
2021-04-07 12:04:06 +02:00
|
|
|
return resolve("morphed color to '" + blinkstickConfig.color + "'");
|
2021-04-06 16:41:49 +02:00
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// start pulsing
|
2021-04-07 12:04:06 +02:00
|
|
|
function startPulsing(blinkstickConfig, resolve, reject) {
|
2021-04-06 16:41:49 +02:00
|
|
|
if (animation.state == ANIMATION_STATE_FINISH || (blinkstickConfig.options.pulse.max > 0 && (blinkstickConfig.options.pulse.done && blinkstickConfig.options.pulse.done < blinkstickConfig.options.pulse.max))) {
|
|
|
|
clearAnimationProperties();
|
2021-04-07 12:04:06 +02:00
|
|
|
return resolve("finished pulsing color '" + blinkstickConfig.color + "'");
|
2021-04-06 16:41:49 +02:00
|
|
|
}
|
|
|
|
if (animation.id && animation.id != blinkstickConfig.id) {
|
|
|
|
stopAnimation()
|
|
|
|
.then(logger.info)
|
|
|
|
.then(function () {
|
2021-04-07 12:04:06 +02:00
|
|
|
startPulsing(blinkstickConfig, resolve, reject)
|
2021-04-06 16:41:49 +02:00
|
|
|
})
|
|
|
|
.catch(logger.error);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
setAnimationProperties(blinkstickConfig);
|
2021-04-07 12:04:06 +02:00
|
|
|
led.pulse(blinkstickConfig.color, blinkstickConfig.options, function (err) {
|
2021-04-06 16:41:49 +02:00
|
|
|
if (err) {
|
|
|
|
clearAnimationProperties();
|
2021-04-07 12:04:06 +02:00
|
|
|
return reject("error pulsing color '" + blinkstickConfig.color + "' (" + err + ")");
|
2021-04-06 16:41:49 +02:00
|
|
|
}
|
|
|
|
blinkstickConfig.options.pulse.done++;
|
2021-04-07 12:04:06 +02:00
|
|
|
logger.debug("pulsed color '" + blinkstickConfig.color + "' " + blinkstickConfig.options.pulse.done + "/" + blinkstickConfig.options.pulse.max + " times");
|
|
|
|
startPulsing(blinkstickConfig, resolve, reject);
|
2021-04-06 16:41:49 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// set properties for the current animation
|
|
|
|
function setAnimationProperties(blinkstickConfig) {
|
|
|
|
if (animation.id == blinkstickConfig.id) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
led.animationsEnabled = true;
|
|
|
|
animation.id = blinkstickConfig.id;
|
|
|
|
if (blinkstickConfig.options.pulse && blinkstickConfig.options.pulse.max === 0) {
|
|
|
|
animation.state = ANIMATION_STATE_INFINITE;
|
|
|
|
} else {
|
|
|
|
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(function (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(function () {
|
|
|
|
waitForAnimation(timestamp, resolve, reject);
|
|
|
|
}, 100);
|
|
|
|
}
|
|
|
|
|
|
|
|
// is currently an animation in progress
|
|
|
|
function isAnimationInProgress() {
|
|
|
|
return animation != undefined && animation.state != undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
// is currently an infinite animation in progress
|
|
|
|
function isInfiniteAnimationInProgress() {
|
|
|
|
return isAnimationInProgress() && animation.state == ANIMATION_STATE_INFINITE;
|
|
|
|
}
|
|
|
|
|
|
|
|
// exports
|
|
|
|
module.exports = {
|
|
|
|
parseColor,
|
|
|
|
illuminate,
|
|
|
|
powerOff
|
|
|
|
};
|