optimized and extended functionality

This commit is contained in:
Daniel Sommer 2022-02-21 00:48:29 +01:00
parent 069faa91f8
commit faee049169
7 changed files with 194 additions and 88 deletions

View file

@ -4,7 +4,7 @@ control your blinkstick via http requests
## requirements ## requirements
- nodejs - nodejs 8.x
- yarn / npm - yarn / npm
- node-gyp (probably; to build the node-hid module) - node-gyp (probably; to build the node-hid module)

View file

@ -1,23 +1,41 @@
// requirements // requirements
const blinkstick = require('./libs/blinkstick'); const server = require('./libs/server.js');
const server = require('./libs/server'); const util = require('./libs/util.js');
const logger = require('./libs/logger'); const logger = require('./libs/logger.js');
const packageJSON = require('./package'); const packageJSON = require('./package');
const INTERRUPTS = ['SIGINT', 'SIGTERM'];
global.config = process.argv[2] || __dirname + '/config.json';
// handle interrupts
handleInterrupts();
// start the application // start the application
main(); main();
// main - let's get this party started // main - let's get this party started
function main() { function main() {
server.start() util.fileExists(global.config)
.then((result) => {
global.config = require(result.path);
global.config.path = result.path;
})
.then(logger.initialize)
.then(server.start)
.then(logger.info) .then(logger.info)
.then(server.handleRequests) .then(server.handleRequests)
.catch(exit); .catch(exit);
} }
// ... and it all comes crashing down // ... and it all comes crashing down
function exit(err) { function exit(err, code) {
let code = 0; if (code == undefined) {
code = 0;
if (err != undefined) {
code = 1;
}
}
if (err) { if (err) {
logger.error(err); logger.error(err);
logger.error(packageJSON.name + " ended due to an error"); logger.error(packageJSON.name + " ended due to an error");
@ -26,3 +44,11 @@ function exit(err) {
} }
process.exit(code); process.exit(code);
} }
function handleInterrupts() {
for (var index = 0; index < INTERRUPTS.length; index++) {
process.once(INTERRUPTS[index], (code) => {
exit(null, code);
});
}
}

View file

@ -1,11 +1,11 @@
{ {
"server": { "server": {
"listen": "0.0.0.0", "listen": "0.0.0.0",
"port": 3000, "port": 3000
"timestamp": "DD.MM.YYYY HH:mm:ss:SS"
}, },
"log": { "log": {
"level": "info" "level": "debug",
"timestamp": "DD.MM.YYYY HH:mm:ss:SS"
}, },
"api": { "api": {
"get": { "get": {

View file

@ -7,8 +7,7 @@ const blinkstick = require('blinkstick');
const hexcolor = require('hex-color-regex'); const hexcolor = require('hex-color-regex');
// constants // constants
const RANDOM = "random" const RANDOM = 'random'
const ANIMATION_STATE_INFINITE = 2;
const ANIMATION_STATE_INPROGRESS = 1; const ANIMATION_STATE_INPROGRESS = 1;
const ANIMATION_STATE_FINISH = 0; const ANIMATION_STATE_FINISH = 0;
@ -17,19 +16,27 @@ let led;
let animation = {}; let animation = {};
function findBlinkstick() { function findBlinkstick() {
return new Promise(function (resolve, reject) { return new Promise((resolve, reject) => {
led = blinkstick.findFirst(); led = blinkstick.findFirst();
if (led !== undefined && led !== null) { if (led !== undefined && led !== null) {
return resolve(led); return resolve(led);
} }
return reject("could not find any blinkstick"); return reject('could not find any blinkstick');
}); });
} }
// turn the blinkstick off // turn the blinkstick off
function powerOff() { function powerOff() {
return new Promise((resolve, reject) => {
findBlinkstick()
.then((led) => {
led.stop();
led.turnOff(); led.turnOff();
logger.info("blinkstick powered off"); logger.info('blinkstick powered off');
resolve();
})
.catch(reject);
})
} }
// parse a color object from given request arguments // parse a color object from given request arguments
@ -42,17 +49,17 @@ function parseColor(value) {
parsedColor = parseHexColor(value); parsedColor = parseHexColor(value);
} }
return parsedColor || function () { return parsedColor || function () {
logger.warn("could not parse color value '" + value + "', defaulting to '" + config.api.post.color.default + "'"); logger.warn('could not parse color value \'' + value + '\', defaulting to \'' + config.api.post.color.default + '\'');
return config.api.post.color.default; return config.api.post.color.default;
}(); }();
} }
// parse rgb color values // 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);
} else { } else {
const splittedValues = value.split(","); const splittedValues = value.split(',');
let color = {}; let color = {};
for (let index = 0; index < splittedValues.length; index++) { for (let index = 0; index < splittedValues.length; index++) {
const value = splittedValues[index]; const value = splittedValues[index];
@ -77,7 +84,7 @@ function isRGBValue(value) {
// convert rgb to hex color values // 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 // parse hex color values
@ -99,10 +106,10 @@ function illuminate(blinkstickConfig) {
findBlinkstick() findBlinkstick()
.then(function () { .then(function () {
switch (blinkstickConfig.mode) { switch (blinkstickConfig.mode) {
case "morph": case 'morph':
morph(blinkstickConfig, resolve, reject); morph(blinkstickConfig, resolve, reject);
break; break;
case "pulse": case 'pulse':
startPulsing(blinkstickConfig, resolve, reject); startPulsing(blinkstickConfig, resolve, reject);
break; break;
default: default:
@ -119,13 +126,13 @@ function setColor(blinkstickConfig, resolve, reject) {
stopAnimation() stopAnimation()
.then(function () { .then(function () {
setAnimationProperties(blinkstickConfig); setAnimationProperties(blinkstickConfig);
logger.debug("setting color to '" + blinkstickConfig.color + "'..."); logger.debug('setting color to \'' + blinkstickConfig.color + '\'...');
led.setColor(blinkstickConfig.color, blinkstickConfig.options, function (err) { led.setColor(blinkstickConfig.color, blinkstickConfig.options, function (err) {
clearAnimationProperties(); clearAnimationProperties();
if (err) { if (err) {
return reject("error setting color '" + blinkstickConfig.color + "' (" + err + ")"); return reject('error setting color \'' + blinkstickConfig.color + '\' (' + err + ')');
} }
return resolve("set color to '" + blinkstickConfig.color + "'"); return resolve('set color to \'' + blinkstickConfig.color + '\'');
}); });
}); });
} }
@ -135,22 +142,23 @@ function morph(blinkstickConfig, resolve, reject) {
stopAnimation() stopAnimation()
.then(function () { .then(function () {
setAnimationProperties(blinkstickConfig); setAnimationProperties(blinkstickConfig);
logger.debug("morphing color to '" + blinkstickConfig.color + "'..."); logger.debug('morphing color to \'' + blinkstickConfig.color + '\'...');
logger.debug('OPTS: ' + JSON.stringify(blinkstickConfig.options));
led.morph(blinkstickConfig.color, blinkstickConfig.options, function (err) { led.morph(blinkstickConfig.color, blinkstickConfig.options, function (err) {
clearAnimationProperties(); clearAnimationProperties();
if (err) { if (err) {
return reject("error morphing color to '" + blinkstickConfig.color + "' (" + err + ")"); return reject('error morphing color to \'' + blinkstickConfig.color + '\' (' + err + ')');
} }
return resolve("morphed color to '" + blinkstickConfig.color + "'"); return resolve('morphed color to \'' + blinkstickConfig.color + '\'');
}); });
}); });
} }
// start pulsing // start pulsing
function startPulsing(blinkstickConfig, resolve, reject) { 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))) { if (animation.state == ANIMATION_STATE_FINISH || (blinkstickConfig.options.pulse.max > 0 && (blinkstickConfig.options.pulse.done && blinkstickConfig.options.pulse.done == blinkstickConfig.options.pulse.max))) {
clearAnimationProperties(); clearAnimationProperties();
return resolve("finished pulsing color '" + blinkstickConfig.color + "'"); return resolve('finished pulsing color \'' + blinkstickConfig.color + '\'');
} }
if (animation.id && animation.id != blinkstickConfig.id) { if (animation.id && animation.id != blinkstickConfig.id) {
stopAnimation() stopAnimation()
@ -165,10 +173,10 @@ function startPulsing(blinkstickConfig, resolve, reject) {
led.pulse(blinkstickConfig.color, blinkstickConfig.options, function (err) { led.pulse(blinkstickConfig.color, blinkstickConfig.options, function (err) {
if (err) { if (err) {
clearAnimationProperties(); clearAnimationProperties();
return reject("error pulsing color '" + blinkstickConfig.color + "' (" + err + ")"); return reject('error pulsing color \'' + blinkstickConfig.color + '\' (' + err + ')');
} }
blinkstickConfig.options.pulse.done++; blinkstickConfig.options.pulse.done++;
logger.debug("pulsed color '" + blinkstickConfig.color + "' " + blinkstickConfig.options.pulse.done + "/" + blinkstickConfig.options.pulse.max + " times"); logger.debug('pulsed color \'' + blinkstickConfig.color + '\' ' + blinkstickConfig.options.pulse.done + '/' + blinkstickConfig.options.pulse.max + ' times');
startPulsing(blinkstickConfig, resolve, reject); startPulsing(blinkstickConfig, resolve, reject);
}); });
} }
@ -180,11 +188,7 @@ function setAnimationProperties(blinkstickConfig) {
} }
led.animationsEnabled = true; led.animationsEnabled = true;
animation.id = blinkstickConfig.id; animation.id = blinkstickConfig.id;
if (blinkstickConfig.options.pulse && blinkstickConfig.options.pulse.max === 0) {
animation.state = ANIMATION_STATE_INFINITE;
} else {
animation.state = ANIMATION_STATE_INPROGRESS; animation.state = ANIMATION_STATE_INPROGRESS;
}
} }
// clear properties for the current animation // clear properties for the current animation
@ -219,11 +223,6 @@ function isAnimationInProgress() {
return animation != undefined && animation.state != undefined; return animation != undefined && animation.state != undefined;
} }
// is currently an infinite animation in progress
function isInfiniteAnimationInProgress() {
return isAnimationInProgress() && animation.state == ANIMATION_STATE_INFINITE;
}
// exports // exports
module.exports = { module.exports = {
parseColor, parseColor,

View file

@ -1,21 +1,35 @@
// requirements const moment = require('moment');
const config = require("../config.json");
// third party requirements
const moment = require("moment");
// constants // constants
const LOG_PREFIX_DEBUG = "debug"; const LOG_PREFIX_DEBUG = 'debug';
const LOG_PREFIX_INFO = "info"; const LOG_PREFIX_INFO = 'info';
const LOG_PREFIX_WARNING = "warning"; const LOG_PREFIX_WARNING = 'warning';
const LOG_PREFIX_ERROR = "error"; const LOG_PREFIX_ERROR = 'error';
const LOGLEVEL_DEBUG = 0; const LOGLEVEL_DEBUG = 0;
const LOGLEVEL_INFO = 1; const LOGLEVEL_INFO = 1;
const LOGLEVEL_WARNING = 2; const LOGLEVEL_WARNING = 2;
const LOGLEVEL_ERROR = 3; const LOGLEVEL_ERROR = 3;
// set loglevel on "require" var loglevel = getLogLevel();
const loglevel = function() { var timestamp = getTimestamp();
switch (config.log.level) {
function initialize() {
return new Promise((resolve, reject) => {
if (global.config == undefined) {
reject('could not initialize logger, config is undefined');
}
loglevel = getLogLevel();
timestamp = getTimestamp();
resolve();
});
}
// get the loglevel
function getLogLevel() {
if (global.config == undefined || global.config.log == undefined || global.config.log.level == undefined) {
return LOGLEVEL_INFO;
}
switch (global.config.log.level) {
case LOG_PREFIX_DEBUG: case LOG_PREFIX_DEBUG:
case LOGLEVEL_DEBUG: case LOGLEVEL_DEBUG:
return LOGLEVEL_DEBUG; return LOGLEVEL_DEBUG;
@ -31,20 +45,31 @@ const loglevel = function() {
default: default:
return LOGLEVEL_INFO; return LOGLEVEL_INFO;
} }
}(); }
// get the timestamp format
function getTimestamp() {
if (global.config != undefined && global.config.log != undefined && global.config.log.format != undefined) {
return global.config.log.timestamp;
}
return "DD.MM.YYYY HH:mm:ss:SS";
}
// log a http request // log a http request
function logRequest(request) { function http(request) {
let message = "[" + request.method + "] url: \"" + request.url + "\""; if (request == undefined) {
return;
}
let message = '[' + request.method + '] url: \'' + request.url + '\'';
let counter = 1; let counter = 1;
for (let param in request.body) { for (let param in request.body) {
message += ", parameter " + counter + ": \"" + param + "=" + request.body[param] + "\""; message += ', parameter ' + counter + ': \'' + param + '=' + request.body[param] + '\'';
counter++; counter++;
} }
debug(message.trim()); debug(message.trim());
} }
// prefix log with "info" // prefix log with 'info'
function info(message) { function info(message) {
if (loglevel > LOGLEVEL_INFO) { if (loglevel > LOGLEVEL_INFO) {
return; return;
@ -52,28 +77,34 @@ function info(message) {
trace(message); trace(message);
} }
// prefix log with "info" // prefix log with 'info'
function warn(message) { function warn(message) {
if (loglevel > LOGLEVEL_WARNING) { if (loglevel > LOGLEVEL_WARNING) {
return; return;
} }
trace(message, "warning"); trace(message, 'warning');
} }
// prefix log with "debug" // prefix log with 'debug'
function debug(message) { function debug(message) {
if (loglevel > LOGLEVEL_DEBUG) { if (loglevel > LOGLEVEL_DEBUG) {
return; return;
} }
trace(message, "debug"); trace(message, 'debug');
} }
// prefix log with "error" // prefix log with 'error'
function error(message) { function error(message) {
if (loglevel > LOGLEVEL_ERROR) { if (loglevel > LOGLEVEL_ERROR) {
return; return;
} }
trace(message, "error"); if (message.errors != undefined) {
for (var index = 0; index < message.errors.length; index++) {
trace(message.errors[index], 'error');
}
return;
}
trace(message, 'error');
} }
// default logging function // default logging function
@ -82,31 +113,32 @@ function trace(message, prefix) {
return; return;
} }
if (prefix === undefined || prefix === null || prefix.length === 0) { if (prefix === undefined || prefix === null || prefix.length === 0) {
prefix = "info"; prefix = 'info';
} }
let print; let print;
switch (prefix) { switch (prefix) {
case "error": case 'error':
print = console.error; print = console.error;
break; break;
case "debug": case 'debug':
print = console.debug; print = console.debug;
break; break;
case "warning": case 'warning':
print = console.warn; print = console.warn;
break; break;
default: default:
print = console.log; print = console.log;
} }
message = moment().format(config.server.timestamp) + " | " + prefix + " > " + message; message = moment().format(timestamp) + ' | ' + prefix + ' > ' + message;
print(message); print(message);
} }
// exports // exports
module.exports = { module.exports = {
initialize,
info, info,
warn, warn,
debug, debug,
error, error,
logRequest http
}; }

View file

@ -77,19 +77,23 @@ function start() {
function handleRequests() { function handleRequests() {
// GET methods // GET methods
app.get('*', function (request, response) { app.get('*', (request, response) => {
logger.logRequest(request); logger.http(request);
response.send(getHTML()); response.send(getHTML());
response.end(); response.end();
}); });
app.post('/off', (request, response) => {
logger.http(request);
response.end();
blinkstick.powerOff();
});
// POST methods // POST methods
app.post('*', function (request, response) { app.post('*', (request, response) => {
logger.logRequest(request); logger.http(request);
response.end(); response.end();
blinkstick.illuminate(parseRequest(request.body)) blinkstick.illuminate(parseRequest(request.body))
.then(logger.info) .then(logger.info)
.catch(logger.error); .catch(logger.error);
return;
}); });
} }
@ -97,20 +101,26 @@ function handleRequests() {
function parseRequest(data) { function parseRequest(data) {
let blinkstickConfig = { let blinkstickConfig = {
"id": Math.random(), "id": Math.random(),
"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": {
"duration": data["duration"] || config.api.post.duration.default, "steps": data.steps,
"duration": data.duration || config.api.post.duration.default,
"pulse": { "pulse": {
"max": data["pulses"] || 0, "max": data.pulses || 0,
"done": 0 "done": 0
} }
} }
}; };
blinkstickConfig.options.steps = blinkstickConfig.options.duration / 10; if (data.index != undefined) {
blinkstickConfig.options.index = data.index;
}
if (blinkstickConfig.options.duration < 100) { if (blinkstickConfig.options.duration < 100) {
blinkstickConfig.options.duration = 100; blinkstickConfig.options.duration = 100;
} }
if (blinkstickConfig.options.steps == undefined || blinkstickConfig.options.steps == 0) {
blinkstickConfig.options.steps = blinkstickConfig.options.duration / 10;
}
return blinkstickConfig; return blinkstickConfig;
} }

39
libs/util.js Normal file
View file

@ -0,0 +1,39 @@
const realpath = require('fs').realpath;
const stat = require('fs').stat;
function fileExists(file) {
return new Promise((resolve, reject) => {
if (file == undefined) {
reject('can not check the existence of an undefined file');
}
resolvePath(file)
.then((path) => {
stat(path, (err, stats) => {
if (err) {
reject(err);
}
resolve({ path, stats });
})
})
.catch(reject);
});
}
function resolvePath(file) {
return new Promise((resolve, reject) => {
if (file == undefined) {
reject('can not resolve a path to an undefined file');
}
realpath(file, (err, resolvedPath) => {
if (err) {
reject('resolving path \'' + file + '\' encountered an error >>> ' + err);
}
resolve(resolvedPath);
})
});
}
module.exports = {
fileExists,
resolvePath
}