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
- nodejs
- nodejs 8.x
- yarn / npm
- node-gyp (probably; to build the node-hid module)

View file

@ -1,23 +1,41 @@
// requirements
const blinkstick = require('./libs/blinkstick');
const server = require('./libs/server');
const logger = require('./libs/logger');
const server = require('./libs/server.js');
const util = require('./libs/util.js');
const logger = require('./libs/logger.js');
const packageJSON = require('./package');
const INTERRUPTS = ['SIGINT', 'SIGTERM'];
global.config = process.argv[2] || __dirname + '/config.json';
// handle interrupts
handleInterrupts();
// start the application
main();
// main - let's get this party started
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(server.handleRequests)
.catch(exit);
}
// ... and it all comes crashing down
function exit(err) {
let code = 0;
function exit(err, code) {
if (code == undefined) {
code = 0;
if (err != undefined) {
code = 1;
}
}
if (err) {
logger.error(err);
logger.error(packageJSON.name + " ended due to an error");
@ -26,3 +44,11 @@ function exit(err) {
}
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": {
"listen": "0.0.0.0",
"port": 3000,
"timestamp": "DD.MM.YYYY HH:mm:ss:SS"
"port": 3000
},
"log": {
"level": "info"
"level": "debug",
"timestamp": "DD.MM.YYYY HH:mm:ss:SS"
},
"api": {
"get": {

View file

@ -7,8 +7,7 @@ const blinkstick = require('blinkstick');
const hexcolor = require('hex-color-regex');
// constants
const RANDOM = "random"
const ANIMATION_STATE_INFINITE = 2;
const RANDOM = 'random'
const ANIMATION_STATE_INPROGRESS = 1;
const ANIMATION_STATE_FINISH = 0;
@ -17,19 +16,27 @@ let led;
let animation = {};
function findBlinkstick() {
return new Promise(function (resolve, reject) {
return new Promise((resolve, reject) => {
led = blinkstick.findFirst();
if (led !== undefined && led !== null) {
return resolve(led);
}
return reject("could not find any blinkstick");
return reject('could not find any blinkstick');
});
}
// turn the blinkstick off
function powerOff() {
return new Promise((resolve, reject) => {
findBlinkstick()
.then((led) => {
led.stop();
led.turnOff();
logger.info("blinkstick powered off");
logger.info('blinkstick powered off');
resolve();
})
.catch(reject);
})
}
// parse a color object from given request arguments
@ -42,17 +49,17 @@ function parseColor(value) {
parsedColor = parseHexColor(value);
}
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;
}();
}
// parse rgb color values
function parseRGBColor(value) {
if (value.indexOf(",") == -1 && isRGBValue(value)) {
if (value.indexOf(',') == -1 && isRGBValue(value)) {
return convertRGBToHex(parseInt(value) || 0, 0, 0);
} else {
const splittedValues = value.split(",");
const splittedValues = value.split(',');
let color = {};
for (let index = 0; index < splittedValues.length; index++) {
const value = splittedValues[index];
@ -77,7 +84,7 @@ function isRGBValue(value) {
// convert rgb to hex color values
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
@ -99,10 +106,10 @@ function illuminate(blinkstickConfig) {
findBlinkstick()
.then(function () {
switch (blinkstickConfig.mode) {
case "morph":
case 'morph':
morph(blinkstickConfig, resolve, reject);
break;
case "pulse":
case 'pulse':
startPulsing(blinkstickConfig, resolve, reject);
break;
default:
@ -119,13 +126,13 @@ function setColor(blinkstickConfig, resolve, reject) {
stopAnimation()
.then(function () {
setAnimationProperties(blinkstickConfig);
logger.debug("setting color to '" + blinkstickConfig.color + "'...");
logger.debug('setting color to \'' + blinkstickConfig.color + '\'...');
led.setColor(blinkstickConfig.color, blinkstickConfig.options, function (err) {
clearAnimationProperties();
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()
.then(function () {
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) {
clearAnimationProperties();
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
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();
return resolve("finished pulsing color '" + blinkstickConfig.color + "'");
return resolve('finished pulsing color \'' + blinkstickConfig.color + '\'');
}
if (animation.id && animation.id != blinkstickConfig.id) {
stopAnimation()
@ -165,10 +173,10 @@ function startPulsing(blinkstickConfig, resolve, reject) {
led.pulse(blinkstickConfig.color, blinkstickConfig.options, function (err) {
if (err) {
clearAnimationProperties();
return reject("error pulsing color '" + blinkstickConfig.color + "' (" + err + ")");
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");
logger.debug('pulsed color \'' + blinkstickConfig.color + '\' ' + blinkstickConfig.options.pulse.done + '/' + blinkstickConfig.options.pulse.max + ' times');
startPulsing(blinkstickConfig, resolve, reject);
});
}
@ -180,11 +188,7 @@ function setAnimationProperties(blinkstickConfig) {
}
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
@ -219,11 +223,6 @@ 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,

View file

@ -1,21 +1,35 @@
// requirements
const config = require("../config.json");
// third party requirements
const moment = require("moment");
const moment = require('moment');
// constants
const LOG_PREFIX_DEBUG = "debug";
const LOG_PREFIX_INFO = "info";
const LOG_PREFIX_WARNING = "warning";
const LOG_PREFIX_ERROR = "error";
const LOG_PREFIX_DEBUG = 'debug';
const LOG_PREFIX_INFO = 'info';
const LOG_PREFIX_WARNING = 'warning';
const LOG_PREFIX_ERROR = 'error';
const LOGLEVEL_DEBUG = 0;
const LOGLEVEL_INFO = 1;
const LOGLEVEL_WARNING = 2;
const LOGLEVEL_ERROR = 3;
// set loglevel on "require"
const loglevel = function() {
switch (config.log.level) {
var loglevel = getLogLevel();
var timestamp = getTimestamp();
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 LOGLEVEL_DEBUG:
return LOGLEVEL_DEBUG;
@ -31,20 +45,31 @@ const loglevel = function() {
default:
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
function logRequest(request) {
let message = "[" + request.method + "] url: \"" + request.url + "\"";
function http(request) {
if (request == undefined) {
return;
}
let message = '[' + request.method + '] url: \'' + request.url + '\'';
let counter = 1;
for (let param in request.body) {
message += ", parameter " + counter + ": \"" + param + "=" + request.body[param] + "\"";
message += ', parameter ' + counter + ': \'' + param + '=' + request.body[param] + '\'';
counter++;
}
debug(message.trim());
}
// prefix log with "info"
// prefix log with 'info'
function info(message) {
if (loglevel > LOGLEVEL_INFO) {
return;
@ -52,28 +77,34 @@ function info(message) {
trace(message);
}
// prefix log with "info"
// prefix log with 'info'
function warn(message) {
if (loglevel > LOGLEVEL_WARNING) {
return;
}
trace(message, "warning");
trace(message, 'warning');
}
// prefix log with "debug"
// prefix log with 'debug'
function debug(message) {
if (loglevel > LOGLEVEL_DEBUG) {
return;
}
trace(message, "debug");
trace(message, 'debug');
}
// prefix log with "error"
// prefix log with 'error'
function error(message) {
if (loglevel > LOGLEVEL_ERROR) {
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
@ -82,31 +113,32 @@ function trace(message, prefix) {
return;
}
if (prefix === undefined || prefix === null || prefix.length === 0) {
prefix = "info";
prefix = 'info';
}
let print;
switch (prefix) {
case "error":
case 'error':
print = console.error;
break;
case "debug":
case 'debug':
print = console.debug;
break;
case "warning":
case 'warning':
print = console.warn;
break;
default:
print = console.log;
}
message = moment().format(config.server.timestamp) + " | " + prefix + " > " + message;
message = moment().format(timestamp) + ' | ' + prefix + ' > ' + message;
print(message);
}
// exports
module.exports = {
initialize,
info,
warn,
debug,
error,
logRequest
};
http
}

View file

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

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
}