reworked almost the whole project - everything should work now as expected
This commit is contained in:
parent
eac3aa5e5e
commit
61c43fdd9b
6 changed files with 513 additions and 290 deletions
30
config.json
30
config.json
|
@ -12,11 +12,13 @@
|
||||||
"description": "show this page"
|
"description": "show this page"
|
||||||
},
|
},
|
||||||
"post": {
|
"post": {
|
||||||
"mode": {
|
"endpoints": [
|
||||||
"available": "set, morph, pulse",
|
"/set",
|
||||||
"default": "set",
|
"/morph",
|
||||||
"description": "specifies the color change mode"
|
"/blink",
|
||||||
},
|
"/pulse",
|
||||||
|
"/poweroff"
|
||||||
|
],
|
||||||
"color": {
|
"color": {
|
||||||
"available": "random, hex color codes (#ffffff), rgb color codes (255, 255, 255)",
|
"available": "random, hex color codes (#ffffff), rgb color codes (255, 255, 255)",
|
||||||
"default": "random",
|
"default": "random",
|
||||||
|
@ -24,8 +26,8 @@
|
||||||
},
|
},
|
||||||
"index": {
|
"index": {
|
||||||
"available": "number values",
|
"available": "number values",
|
||||||
"default": "undefined",
|
"default": "all",
|
||||||
"description": "specifies the led index"
|
"description": "specifies the led index / defaults to all leds"
|
||||||
},
|
},
|
||||||
"duration": {
|
"duration": {
|
||||||
"available": "number values",
|
"available": "number values",
|
||||||
|
@ -35,13 +37,19 @@
|
||||||
"steps": {
|
"steps": {
|
||||||
"available": "number values",
|
"available": "number values",
|
||||||
"default": "[duration] / 10",
|
"default": "[duration] / 10",
|
||||||
"description": "specifies the amount of steps for the color change"
|
"description": "specifies the number of steps for the color change"
|
||||||
},
|
},
|
||||||
"pulses": {
|
"repeats": {
|
||||||
"restrictions": "pulse",
|
"restrictions": "blink, pulse",
|
||||||
"available": "number values",
|
"available": "number values",
|
||||||
"default": "0 (infinite)",
|
"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 "
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,109 +1,123 @@
|
||||||
// requirements
|
|
||||||
const logger = require('./logger');
|
const logger = require('./logger');
|
||||||
const config = require('../config.json');
|
|
||||||
const util = require('util');
|
|
||||||
|
|
||||||
// third party requirements
|
|
||||||
const blinkstick = require('blinkstick');
|
const blinkstick = require('blinkstick');
|
||||||
const hexcolor = require('hex-color-regex');
|
|
||||||
|
|
||||||
// constants
|
const LED_ANIMATED = 0;
|
||||||
// const LEDS = [0, 1, 2, 3, 4, 5, 6, 7];
|
|
||||||
const LEDS_ALL = 'index_all';
|
const LEDS_ALL = 'all';
|
||||||
const RANDOM = 'random';
|
|
||||||
const ANIMATION_STATE_INPROGRESS = 1;
|
|
||||||
const ANIMATION_STATE_FINISH = 0;
|
|
||||||
|
|
||||||
const MODE_SET = 'set';
|
const MODE_SET = 'set';
|
||||||
const MODE_MORPH = 'morph';
|
const MODE_MORPH = 'morph';
|
||||||
|
const MODE_BLINK = 'blink';
|
||||||
const MODE_PULSE = 'pulse';
|
const MODE_PULSE = 'pulse';
|
||||||
const MODE_POWEROFF = 'poweroff';
|
const MODE_POWEROFF = 'poweroff';
|
||||||
|
|
||||||
// variables
|
|
||||||
let led;
|
|
||||||
const LEDS = new Map();
|
const LEDS = new Map();
|
||||||
|
|
||||||
|
let stop = false;
|
||||||
|
|
||||||
|
// find a connected blinkstick
|
||||||
function findBlinkstick() {
|
function findBlinkstick() {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
led = blinkstick.findFirst();
|
let led = blinkstick.findFirst();
|
||||||
if (led !== undefined && led !== null) {
|
if (led === undefined) {
|
||||||
return resolve(led);
|
reject('could not find any blinkstick');
|
||||||
}
|
}
|
||||||
return reject('could not find any blinkstick');
|
resolve(led);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// light it up
|
// simple animation (set the color / morph to color)
|
||||||
async function illuminate(blinkstickConfig) {
|
async function simple(config) {
|
||||||
led = await findBlinkstick();
|
|
||||||
let indices = getIndices(blinkstickConfig);
|
|
||||||
for (let index = 0; index < indices.length; index++) {
|
|
||||||
try {
|
try {
|
||||||
await setLedState(indices[index], ANIMATION_STATE_INPROGRESS);
|
await forceStop();
|
||||||
await singleAnimation(JSON.parse(JSON.stringify(blinkstickConfig)), indices[index]);
|
config.timestamp = new Date().getTime();
|
||||||
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);
|
let indices = getIndices(config);
|
||||||
for (let index = 0; index < indices.length; index++) {
|
for (let index = 0; index < indices.length; index++) {
|
||||||
try {
|
await setLedState(indices[index], LED_ANIMATED);
|
||||||
await singleAnimation(JSON.parse(JSON.stringify(config)), indices[index]);
|
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) {
|
} catch (err) {
|
||||||
logger.error(err);
|
logger.error(err);
|
||||||
|
return err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (index !== LEDS_ALL) {
|
|
||||||
logger.info('led \'' + index + '\' powered off');
|
// complex animation (pulse / blink)
|
||||||
return;
|
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();
|
||||||
}
|
}
|
||||||
led.stop();
|
let indices = getIndices(config);
|
||||||
led.turnOff();
|
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');
|
logger.info('blinkstick powered off');
|
||||||
}
|
}
|
||||||
|
return {status: 'ok', indices: indices, time: (new Date().getTime() - config.timestamp) + 'ms'};
|
||||||
|
} catch (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// animations
|
// animations
|
||||||
function singleAnimation(config, index) {
|
async function singleAnimation(config, index) {
|
||||||
return new Promise((resolve, reject) => {
|
try {
|
||||||
config.options.index = index;
|
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 + ')...');
|
logger.debug('changing color of led \'' + config.options.index + '\' to \'' + config.color + '\' (mode: ' + config.mode + ')...');
|
||||||
switch (config.mode) {
|
switch (config.mode) {
|
||||||
case MODE_MORPH:
|
case MODE_MORPH:
|
||||||
led.morph(config.color, config.options, callback);
|
blinkstick.morph(config.color, config.options, callback);
|
||||||
|
break;
|
||||||
|
case MODE_BLINK:
|
||||||
|
blinkstick.blink(config.color, config.options, callback);
|
||||||
break;
|
break;
|
||||||
case MODE_PULSE:
|
case MODE_PULSE:
|
||||||
led.pulse(config.color, config.options, callback);
|
blinkstick.pulse(config.color, config.options, callback);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
led.setColor(config.color, config.options, callback);
|
blinkstick.setColor(config.color, config.options, callback);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
function callback(err) {
|
function callback(err) {
|
||||||
clearLedState(config.options.index);
|
|
||||||
if (err) {
|
if (err) {
|
||||||
reject('changing color of led \'' + config.options.index + '\' to \'' + config.color + '\' encountered an error > ' + err);
|
reject('changing color of led \'' + config.options.index + '\' to \'' + config.color + '\' encountered an error > ' + err);
|
||||||
}
|
}
|
||||||
|
@ -111,45 +125,31 @@ function singleAnimation(config, index) {
|
||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} catch (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: IMPLEMENT FUNCTION
|
async function forceStop() {
|
||||||
function continousAnimation(config, index) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// start pulsing
|
|
||||||
function startPulsing(blinkstickConfig) {
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let animation = getAnimation(blinkstickConfig);
|
stop = true;
|
||||||
if (animation?.state === ANIMATION_STATE_FINISH || (blinkstickConfig.options.pulse.max > 0 && animation.done === blinkstickConfig.options.pulse.max)) {
|
waitForAllAnimationsEnd(() => {
|
||||||
clearAnimationProperties(blinkstickConfig);
|
stop = false;
|
||||||
return resolve('finished pulsing \'' + blinkstickConfig.color + '\'');
|
resolve();
|
||||||
}
|
|
||||||
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);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
// led / index helper functions
|
||||||
function getIndices(blinkstickConfig) {
|
function getIndices(blinkstickConfig) {
|
||||||
if (blinkstickConfig.options.index === LEDS_ALL) {
|
if (blinkstickConfig.options.index === LEDS_ALL) {
|
||||||
|
@ -164,7 +164,10 @@ async function setLedState(index, state) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearLedState(index) {
|
function clearLedState(index) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
LEDS.delete(index);
|
LEDS.delete(index);
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function isLedAnimated(index) {
|
function isLedAnimated(index) {
|
||||||
|
@ -188,71 +191,35 @@ function waitForAnimationEnd(index, callback) {
|
||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
// color / parser functions
|
function waitForAllAnimationsEnd(callback) {
|
||||||
function parseColor(value) {
|
if (LEDS.size === 0) {
|
||||||
if (!value || value === RANDOM) {
|
return callback();
|
||||||
return RANDOM;
|
|
||||||
}
|
}
|
||||||
let parsedColor = parseRGBColor(value);
|
setTimeout(() => {
|
||||||
if (!parsedColor) {
|
waitForAllAnimationsEnd(callback);
|
||||||
parsedColor = parseHexColor(value);
|
}, 100);
|
||||||
}
|
|
||||||
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) {
|
function isInfiniteAnimation(config) {
|
||||||
if (value.indexOf(',') === -1 && isRGBValue(value)) {
|
if (config.mode !== MODE_BLINK && config.mode !== MODE_PULSE) {
|
||||||
return convertRGBToHex(parseInt(value) || 0, 0, 0);
|
return false;
|
||||||
} 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 (config.options.index === LEDS_ALL) {
|
||||||
|
return config.repetitions.max === 0;
|
||||||
}
|
}
|
||||||
if (isRGBValue(color.red) && isRGBValue(color.green) && isRGBValue(color.blue)) {
|
return config.options.repeats === 0;
|
||||||
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
|
// exports
|
||||||
module.exports = {
|
module.exports = {
|
||||||
parseColor,
|
simple,
|
||||||
illuminate,
|
complex,
|
||||||
powerOff,
|
powerOff,
|
||||||
|
isInfiniteAnimation,
|
||||||
LEDS_ALL,
|
LEDS_ALL,
|
||||||
MODE_SET,
|
MODE_SET,
|
||||||
MODE_MORPH,
|
MODE_MORPH,
|
||||||
|
MODE_BLINK,
|
||||||
MODE_PULSE,
|
MODE_PULSE,
|
||||||
MODE_POWEROFF
|
MODE_POWEROFF
|
||||||
};
|
};
|
66
libs/index.js
Normal file
66
libs/index.js
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
const packageJSON = require('../package.json');
|
||||||
|
const config = require('../config.json');
|
||||||
|
|
||||||
|
function get() {
|
||||||
|
let html =
|
||||||
|
'<html>' +
|
||||||
|
'<head>' +
|
||||||
|
'<title>' + packageJSON.name + ' ' + packageJSON.version + '</title>' +
|
||||||
|
'<style>' +
|
||||||
|
'body { background: #2a2a2a; color: #f1f1f1; margin: 1.25% }' +
|
||||||
|
'th, td { border: 1px solid #919191; padding: 6px; text-align: left }' +
|
||||||
|
'</style>' +
|
||||||
|
'</head>' +
|
||||||
|
'<body>' +
|
||||||
|
'<div>' +
|
||||||
|
'<h1>' + packageJSON.name + ' ' + packageJSON.version + '</h1>' +
|
||||||
|
'</div>' +
|
||||||
|
'<hr>';
|
||||||
|
html +=
|
||||||
|
'<div>' +
|
||||||
|
'<h2>get:</h2>' +
|
||||||
|
'<p>' + config.api.get.description + '</p>' +
|
||||||
|
'</div>';
|
||||||
|
|
||||||
|
html +=
|
||||||
|
'<div>' +
|
||||||
|
'<h2>post:</h2>' +
|
||||||
|
'<h3>endpoints: </>';
|
||||||
|
for (let index = 0; index < config.api.post.endpoints.length; index++) {
|
||||||
|
if (index > 0) {
|
||||||
|
html += ', ';
|
||||||
|
}
|
||||||
|
html += config.api.post.endpoints[index];
|
||||||
|
}
|
||||||
|
html += '</h3>' +
|
||||||
|
'<table style=\'width:100%\'>' +
|
||||||
|
'<th>argument</th>' +
|
||||||
|
'<th>available values</th>' +
|
||||||
|
'<th>default</th>' +
|
||||||
|
'<th>restrictions</th>' +
|
||||||
|
'<th>description</th>';
|
||||||
|
Object.keys(config.api.post).forEach((argument) => {
|
||||||
|
if (argument === 'endpoints') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let restrictions = config.api.post[argument].restrictions || '';
|
||||||
|
html +=
|
||||||
|
'<tr>' +
|
||||||
|
'<td>' + argument + '</td>' +
|
||||||
|
'<td>' + config.api.post[argument].available + '</td>' +
|
||||||
|
'<td>' + config.api.post[argument].default + '</td>' +
|
||||||
|
'<td>' + restrictions + '</td>' +
|
||||||
|
'<td>' + config.api.post[argument].description + '</td>' +
|
||||||
|
'</tr>';
|
||||||
|
});
|
||||||
|
html +=
|
||||||
|
'</table>' +
|
||||||
|
'</div>' +
|
||||||
|
'</body>' +
|
||||||
|
'</html>';
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
get
|
||||||
|
};
|
|
@ -52,7 +52,7 @@ function getTimestamp() {
|
||||||
if (global.config !== undefined && global.config.log !== undefined && global.config.log.format !== undefined) {
|
if (global.config !== undefined && global.config.log !== undefined && global.config.log.format !== undefined) {
|
||||||
return global.config.log.timestamp;
|
return global.config.log.timestamp;
|
||||||
}
|
}
|
||||||
return "DD.MM.YYYY HH:mm:ss:SS";
|
return 'DD.MM.YYYY HH:mm:ss:SS';
|
||||||
}
|
}
|
||||||
|
|
||||||
// log a http request
|
// log a http request
|
||||||
|
|
188
libs/parser.js
Normal file
188
libs/parser.js
Normal file
|
@ -0,0 +1,188 @@
|
||||||
|
const logger = require('./logger.js');
|
||||||
|
const hexcolor = require('hex-color-regex');
|
||||||
|
|
||||||
|
const LEDS_ALL = require('./blinkstick.js').LEDS_ALL;
|
||||||
|
const MODE_SET = require('./blinkstick.js').MODE_SET;
|
||||||
|
const MODE_MORPH = require('./blinkstick.js').MODE_MORPH;
|
||||||
|
const MODE_BLINK = require('./blinkstick.js').MODE_BLINK;
|
||||||
|
const MODE_PULSE = require('./blinkstick.js').MODE_PULSE;
|
||||||
|
const MODE_POWEROFF = require('./blinkstick.js').MODE_POWEROFF;
|
||||||
|
|
||||||
|
const DURATION_DEFAULT = require('../config.json').api.post.duration.default || 1000;
|
||||||
|
const DELAY_DEFAULT = require('../config.json').api.post.delay.default || 500;
|
||||||
|
|
||||||
|
const COLOR_RANDOM = 'random';
|
||||||
|
|
||||||
|
// parse a http post and return an object with sane defaults
|
||||||
|
function parseRequest(requestBody, mode) {
|
||||||
|
if (mode === undefined) {
|
||||||
|
mode = MODE_SET;
|
||||||
|
}
|
||||||
|
let config = {
|
||||||
|
'id': Math.random(),
|
||||||
|
'mode': mode,
|
||||||
|
'options': {
|
||||||
|
'index': parseIndex(requestBody.index),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (config.mode === MODE_POWEROFF) {
|
||||||
|
config.color = '#000000';
|
||||||
|
return config;
|
||||||
|
} else {
|
||||||
|
config.color = parseColor(requestBody.color);
|
||||||
|
}
|
||||||
|
switch (config.mode) {
|
||||||
|
case MODE_MORPH:
|
||||||
|
config.options.duration = parseDuration(requestBody.duration, config.options.index);
|
||||||
|
config.options.steps = parseSteps(requestBody.steps, config.options.duration);
|
||||||
|
break;
|
||||||
|
case MODE_BLINK:
|
||||||
|
config.options.repeats = parseRepeats(requestBody.repeats, config.options.index);
|
||||||
|
config.repetitions = parseRepetitions(requestBody.repeats, config.options.index);
|
||||||
|
config.options.delay = parseDelay(requestBody.delay);
|
||||||
|
break;
|
||||||
|
case MODE_PULSE:
|
||||||
|
config.options.duration = parseDuration(requestBody.duration, config.options.index, MODE_PULSE);
|
||||||
|
config.options.steps = parseSteps(requestBody.steps, config.options.duration);
|
||||||
|
config.options.repeats = parseRepeats(requestBody.repeats, config.options.index);
|
||||||
|
config.repetitions = parseRepetitions(requestBody.repeats, config.options.index);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse the index
|
||||||
|
function parseIndex(index) {
|
||||||
|
if (index === undefined) {
|
||||||
|
return LEDS_ALL;
|
||||||
|
}
|
||||||
|
index = parseInt(index);
|
||||||
|
if (index !== index) {
|
||||||
|
return LEDS_ALL;
|
||||||
|
}
|
||||||
|
if (index < 0) {
|
||||||
|
index = 0;
|
||||||
|
} else if (index > 7) {
|
||||||
|
index = 7;
|
||||||
|
}
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse the duration
|
||||||
|
function parseDuration(duration, index, mode) {
|
||||||
|
if (duration === undefined) {
|
||||||
|
duration = DURATION_DEFAULT;
|
||||||
|
}
|
||||||
|
if (index === LEDS_ALL && mode !== MODE_PULSE) {
|
||||||
|
duration = duration / 8;
|
||||||
|
}
|
||||||
|
if (duration < 100) {
|
||||||
|
duration = 100;
|
||||||
|
}
|
||||||
|
return duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse the steps
|
||||||
|
function parseSteps(steps, duration) {
|
||||||
|
if (duration === undefined) {
|
||||||
|
duration = DURATION_DEFAULT;
|
||||||
|
}
|
||||||
|
if (steps === undefined || steps === 0) {
|
||||||
|
steps = duration / 10;
|
||||||
|
}
|
||||||
|
return steps;
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse the repeats
|
||||||
|
function parseRepeats(repeats, index) {
|
||||||
|
if (index === LEDS_ALL) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
repeats = parseInt(repeats);
|
||||||
|
if (repeats !== repeats) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return repeats;
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse the repetitions
|
||||||
|
function parseRepetitions(repeats, index) {
|
||||||
|
if (index !== LEDS_ALL) {
|
||||||
|
return {done: 0, max: 1};
|
||||||
|
}
|
||||||
|
repeats = parseInt(repeats);
|
||||||
|
if (repeats !== repeats) {
|
||||||
|
repeats = 0;
|
||||||
|
}
|
||||||
|
return {done: 0, max: repeats};
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse the delay
|
||||||
|
function parseDelay(delay) {
|
||||||
|
if (delay === undefined) {
|
||||||
|
delay = DELAY_DEFAULT;
|
||||||
|
}
|
||||||
|
return delay;
|
||||||
|
}
|
||||||
|
|
||||||
|
// color / parser functions
|
||||||
|
function parseColor(value) {
|
||||||
|
if (value === undefined || value === COLOR_RANDOM) {
|
||||||
|
return COLOR_RANDOM;
|
||||||
|
}
|
||||||
|
let parsedColor = parseRGBColor(value);
|
||||||
|
if (!parsedColor) {
|
||||||
|
parsedColor = parseHexColor(value);
|
||||||
|
}
|
||||||
|
return parsedColor || function () {
|
||||||
|
logger.warn('could not parse color value \'' + value + '\', defaulting to \'' + COLOR_RANDOM + '\'');
|
||||||
|
return COLOR_RANDOM;
|
||||||
|
}();
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = {
|
||||||
|
parseRequest,
|
||||||
|
};
|
206
libs/server.js
206
libs/server.js
|
@ -1,139 +1,133 @@
|
||||||
// requirements
|
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const config = require('../config');
|
const config = require('../config');
|
||||||
const packageJSON = require('../package.json');
|
const packageJSON = require('../package.json');
|
||||||
const logger = require('./logger');
|
const logger = require('./logger');
|
||||||
const blinkstick = require('./blinkstick');
|
const blinkstick = require('./blinkstick');
|
||||||
// third party requirements
|
const parser = require('./parser.js');
|
||||||
|
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const favicon = require('serve-favicon');
|
const favicon = require('serve-favicon');
|
||||||
const parser = require('body-parser');
|
const bodyparser = require('body-parser');
|
||||||
|
|
||||||
// setup express, blinkstick and other stuff
|
|
||||||
const app = express();
|
const app = express();
|
||||||
app.use(favicon(path.join(path.dirname(__dirname), "public", "favicon.ico")));
|
app.use(favicon(path.join(path.dirname(__dirname), 'public', 'favicon.ico')));
|
||||||
app.use(parser.json());
|
app.use(bodyparser.json());
|
||||||
app.use(parser.urlencoded({ extended: true }));
|
app.use(bodyparser.urlencoded({extended: true}));
|
||||||
|
|
||||||
// get the html content for get requests
|
const MODE_SET = require('./blinkstick.js').MODE_SET;
|
||||||
// TODO: replace with template engine (vue.js)
|
const MODE_MORPH = require('./blinkstick.js').MODE_MORPH;
|
||||||
function getHTML() {
|
const MODE_BLINK = require('./blinkstick.js').MODE_BLINK;
|
||||||
let welcomeMessage =
|
const MODE_PULSE = require('./blinkstick.js').MODE_PULSE;
|
||||||
"<html>" +
|
const MODE_POWEROFF = require('./blinkstick.js').MODE_POWEROFF;
|
||||||
"<head>" +
|
|
||||||
"<title>" + packageJSON.name + " " + packageJSON.version + "</title>" +
|
const html = require('./index.js').get();
|
||||||
"<style>" +
|
|
||||||
"body { background: #2a2a2a; color: #f1f1f1; margin: 1.25% }" +
|
|
||||||
"th, td { border: 1px solid #919191; padding: 6px; text-align: left }" +
|
|
||||||
"</style>" +
|
|
||||||
"</head>" +
|
|
||||||
"<body>" +
|
|
||||||
"<div>" +
|
|
||||||
"<h1>" + packageJSON.name + " " + packageJSON.version + "</h1>" +
|
|
||||||
"</div>" +
|
|
||||||
"<hr>";
|
|
||||||
welcomeMessage +=
|
|
||||||
"<div>" +
|
|
||||||
"<h2>get:</h2>" +
|
|
||||||
"<p>" + config.api.get.description + "</p>" +
|
|
||||||
"</div>";
|
|
||||||
welcomeMessage +=
|
|
||||||
"<div>" +
|
|
||||||
"<h2>post:</h2>" +
|
|
||||||
"<table style=\"width:100%\">" +
|
|
||||||
"<th>argument</th>" +
|
|
||||||
"<th>available values</th>" +
|
|
||||||
"<th>default</th>" +
|
|
||||||
"<th>restrictions</th>" +
|
|
||||||
"<th>description</th>";
|
|
||||||
Object.keys(config.api.post).forEach(function (argument) {
|
|
||||||
let restrictions = config.api.post[argument].restrictions || "";
|
|
||||||
welcomeMessage +=
|
|
||||||
"<tr>" +
|
|
||||||
"<td>" + argument + "</td>" +
|
|
||||||
"<td>" + config.api.post[argument].available + "</td>" +
|
|
||||||
"<td>" + config.api.post[argument].default + "</td>" +
|
|
||||||
"<td>" + restrictions + "</td>" +
|
|
||||||
"<td>" + config.api.post[argument].description + "</td>" +
|
|
||||||
"</tr>";
|
|
||||||
});
|
|
||||||
welcomeMessage +=
|
|
||||||
"</table>" +
|
|
||||||
"</div>" +
|
|
||||||
"</body>" +
|
|
||||||
"</html>";
|
|
||||||
return welcomeMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
// run the express http server and handle gets/posts
|
// run the express http server and handle gets/posts
|
||||||
function start() {
|
function start() {
|
||||||
return new Promise(function (resolve, reject) {
|
return new Promise(function (resolve, reject) {
|
||||||
app.listen(config.server.port, config.server.listen)
|
app.listen(config.server.port, config.server.listen)
|
||||||
.on("listening", function () {
|
.on('listening', function () {
|
||||||
return resolve("server listening on " + config.server.listen + ":" + config.server.port + "...")
|
return resolve('server listening on ' + config.server.listen + ':' + config.server.port + '...')
|
||||||
})
|
})
|
||||||
.on("error", function (err) {
|
.on('error', function (err) {
|
||||||
return reject("error starting server (" + err + ")");
|
return reject('error starting server (' + err + ')');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleRequests() {
|
async function handleRequests() {
|
||||||
// GET methods
|
// GET html page
|
||||||
app.get('*', (request, response) => {
|
app.get('*', (request, response) => {
|
||||||
logger.http(request);
|
logger.http(request);
|
||||||
response.send(getHTML());
|
response.send(html);
|
||||||
response.end();
|
response.end();
|
||||||
});
|
});
|
||||||
app.post('/off', (request, response) => {
|
// POST '/set'
|
||||||
|
app.post('/' + MODE_SET, (request, response) => {
|
||||||
logger.http(request);
|
logger.http(request);
|
||||||
response.end();
|
blinkstick.simple(parser.parseRequest(request.body, MODE_SET))
|
||||||
blinkstick.powerOff(parseInt(request.body.index));
|
.then((result) => {
|
||||||
|
response.end(JSON.stringify(result));
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
response.status(500);
|
||||||
|
response.end({status: 'error', error: err});
|
||||||
});
|
});
|
||||||
// POST methods
|
});
|
||||||
app.post('*', (request, response) => {
|
// POST '/morph'
|
||||||
|
app.post('/' + MODE_MORPH, (request, response) => {
|
||||||
logger.http(request);
|
logger.http(request);
|
||||||
response.end();
|
blinkstick.simple(parser.parseRequest(request.body, MODE_MORPH))
|
||||||
blinkstick.illuminate(parseRequest(request.body))
|
.then((result) => {
|
||||||
.then(logger.info)
|
response.end(JSON.stringify(result));
|
||||||
.catch(logger.error);
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
response.status(500);
|
||||||
|
response.end({status: 'error', error: err});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// POST '/blink'
|
||||||
|
app.post('/' + MODE_BLINK, (request, response) => {
|
||||||
|
logger.http(request);
|
||||||
|
const config = parser.parseRequest(request.body, MODE_BLINK);
|
||||||
|
if (blinkstick.isInfiniteAnimation(config)) {
|
||||||
|
response.end(JSON.stringify({status: 'ok'}));
|
||||||
|
response = undefined;
|
||||||
|
}
|
||||||
|
blinkstick.complex(config)
|
||||||
|
.then((result) => {
|
||||||
|
if (response === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
response.end(JSON.stringify(result));
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
if (response === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
response.status(500);
|
||||||
|
response.end({status: 'error', error: err});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// POST '/pulse'
|
||||||
|
app.post('/' + MODE_PULSE, (request, response) => {
|
||||||
|
logger.http(request);
|
||||||
|
const config = parser.parseRequest(request.body, MODE_PULSE);
|
||||||
|
if (blinkstick.isInfiniteAnimation(config)) {
|
||||||
|
response.end(JSON.stringify({status: 'ok'}));
|
||||||
|
response = undefined;
|
||||||
|
}
|
||||||
|
blinkstick.complex(config)
|
||||||
|
.then((result) => {
|
||||||
|
if (response === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
response.end(JSON.stringify(result));
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
if (response === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
response.status(500);
|
||||||
|
response.end({status: 'error', error: err});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// POST '/poweroff'
|
||||||
|
app.post('/' + MODE_POWEROFF, (request, response) => {
|
||||||
|
logger.http(request);
|
||||||
|
blinkstick.powerOff(parser.parseRequest(request.body, MODE_POWEROFF))
|
||||||
|
.then((result) => {
|
||||||
|
response.end(JSON.stringify(result));
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
response.status(500);
|
||||||
|
response.end({status: 'error', error: err});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse the request and return an object with sane defaults
|
function checkForInfiniteAnimation(response, config) {
|
||||||
function parseRequest(data) {
|
|
||||||
let blinkstickConfig = {
|
|
||||||
"id": Math.random(),
|
|
||||||
"mode": data.mode || config.api.post.mode.default,
|
|
||||||
"color": blinkstick.parseColor(data.color),
|
|
||||||
"options": {
|
|
||||||
"index": blinkstick.LEDS_ALL,
|
|
||||||
"steps": data.steps,
|
|
||||||
"duration": data.duration || config.api.post.duration.default,
|
|
||||||
"pulse": {
|
|
||||||
"done": 0,
|
|
||||||
"max": data.pulses || 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if (data.index !== undefined) {
|
|
||||||
blinkstickConfig.options.index = parseInt(data.index);
|
|
||||||
if (blinkstickConfig.options.index < 0) {
|
|
||||||
blinkstickConfig.options.index = 0;
|
|
||||||
} else if (blinkstickConfig.options.index > 7) {
|
|
||||||
blinkstickConfig.options.index = 7;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (blinkstickConfig.options.duration < 100) {
|
|
||||||
blinkstickConfig.options.duration = 100;
|
|
||||||
}
|
|
||||||
if (blinkstickConfig.options.index === blinkstick.LEDS_ALL && blinkstickConfig.mode === blinkstick.MODE_MORPH) {
|
|
||||||
blinkstickConfig.options.duration = blinkstickConfig.options.duration / 8;
|
|
||||||
}
|
|
||||||
if (blinkstickConfig.options.steps === undefined || blinkstickConfig.options.steps === 0) {
|
|
||||||
blinkstickConfig.options.steps = blinkstickConfig.options.duration / 10;
|
|
||||||
}
|
|
||||||
return blinkstickConfig;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// exports
|
// exports
|
||||||
|
|
Loading…
Reference in a new issue