initial commit
This commit is contained in:
commit
7be704eeb9
12 changed files with 719 additions and 0 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
node_modules/
|
||||
package-lock.json
|
||||
npm-debug.log
|
14
.vscode/launch.json
vendored
Normal file
14
.vscode/launch.json
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Launch Program",
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
],
|
||||
"program": "${workspaceFolder}/blinky.js"
|
||||
}
|
||||
]
|
||||
}
|
19
LICENSE
Normal file
19
LICENSE
Normal file
|
@ -0,0 +1,19 @@
|
|||
MIT License Copyright (c) <year> <copyright holders>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice (including the next
|
||||
paragraph) shall be included in all copies or substantial portions of the
|
||||
Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
|
||||
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
|
||||
OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
7
README.md
Normal file
7
README.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
# blinky
|
||||
|
||||
control your blinkstick via http GET and POST requests
|
||||
|
||||
## requirements
|
||||
|
||||
- node v8.17.0
|
28
blinky.js
Normal file
28
blinky.js
Normal file
|
@ -0,0 +1,28 @@
|
|||
// requirements
|
||||
const blinkstick = require('./libs/blinkstick');
|
||||
const server = require('./libs/server');
|
||||
const logger = require('./libs/logger');
|
||||
const packageJSON = require('./package');
|
||||
|
||||
// start the application
|
||||
main();
|
||||
|
||||
// main - let's get this party started
|
||||
function main() {
|
||||
server.start()
|
||||
.then(logger.info)
|
||||
.then(server.handleRequests)
|
||||
.catch(exit);
|
||||
}
|
||||
|
||||
// ... and it all comes crashing down
|
||||
function exit(err) {
|
||||
let code = 0;
|
||||
if (err) {
|
||||
logger.error(err);
|
||||
logger.error(packageJSON.name + " ended due to an error");
|
||||
} else {
|
||||
logger.info(packageJSON.name + " shutting down gracefully")
|
||||
}
|
||||
process.exit(code);
|
||||
}
|
43
config.json
Normal file
43
config.json
Normal file
|
@ -0,0 +1,43 @@
|
|||
{
|
||||
"server": {
|
||||
"listen": "0.0.0.0",
|
||||
"port": 3000,
|
||||
"timestamp": "DD.MM.YYYY HH:mm:ss:SS"
|
||||
},
|
||||
"log": {
|
||||
"level": "info"
|
||||
},
|
||||
"api": {
|
||||
"get": {
|
||||
"description": "show this page"
|
||||
},
|
||||
"post": {
|
||||
"mode": {
|
||||
"available": "set, morph, pulse",
|
||||
"default": "set",
|
||||
"description": "specifies the color change mode"
|
||||
},
|
||||
"color": {
|
||||
"available": "random, hex color codes (#ffffff), rgb color codes (255, 255, 255)",
|
||||
"default": "random",
|
||||
"description": "specifies the color to change to"
|
||||
},
|
||||
"duration": {
|
||||
"available": "number values",
|
||||
"default": 1000,
|
||||
"description": "specifies the duration of the color change animation in milliseconds"
|
||||
},
|
||||
"steps": {
|
||||
"available": "number values",
|
||||
"default": "[duration] / 10",
|
||||
"description": "specifies the amount of steps for the color change"
|
||||
},
|
||||
"pulses": {
|
||||
"restrictions": "pulse",
|
||||
"available": "number values",
|
||||
"default": "0 (infinite)",
|
||||
"description": "specifies the number of pulses"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
259
libs/blinkstick.js
Normal file
259
libs/blinkstick.js
Normal file
|
@ -0,0 +1,259 @@
|
|||
// requirements
|
||||
const logger = require('./logger');
|
||||
|
||||
// third party requirements
|
||||
const blinkstick = require('blinkstick');
|
||||
const async = require('async');
|
||||
const hexcolor = require('hex-color-regex');
|
||||
|
||||
// constants
|
||||
const COLORTYPE_RGB = "rgb";
|
||||
const COLORTYPE_HEX = "hex";
|
||||
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
|
||||
function parseColor(blinkstickConfig) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
if (blinkstickConfig.color == "random") {
|
||||
resolve(color);
|
||||
}
|
||||
if (blinkstickConfig.color == undefined || blinkstickConfig.color.length === 0) {
|
||||
reject("no hex color code given, request will be ignored");
|
||||
}
|
||||
let parsedColor = parseRGBColor(blinkstickConfig.color);
|
||||
if (!parsedColor) {
|
||||
parsedColor = parseHexColor(blinkstickConfig.color);
|
||||
}
|
||||
if (!parsedColor) {
|
||||
return reject("could not parse given color code '" + color + "'");
|
||||
}
|
||||
blinkstickConfig.color = parsedColor;
|
||||
resolve(blinkstickConfig);
|
||||
});
|
||||
}
|
||||
|
||||
// parse rgb color values
|
||||
function parseRGBColor(colorValues) {
|
||||
let color = {};
|
||||
if (colorValues.indexOf(",") == -1) {
|
||||
if (isRGBValue(colorValues)) {
|
||||
color.type = COLORTYPE_RGB;
|
||||
color.red = parseInt(colorValues);
|
||||
return color;
|
||||
}
|
||||
} else {
|
||||
const splittedValues = colorValues.split(",");
|
||||
for (let index = 0; index < splittedValues.length; index++) {
|
||||
const value = splittedValues[index];
|
||||
if (!isRGBValue(value)) {
|
||||
continue;
|
||||
}
|
||||
if (index == 0) {
|
||||
color.red = parseInt(value);
|
||||
} else if (index === 1) {
|
||||
color.green = parseInt(value);
|
||||
} else if (index === 2) {
|
||||
color.blue = parseInt(value);
|
||||
}
|
||||
}
|
||||
if (color && (color.red || color.green || color.blue)) {
|
||||
color.type = COLORTYPE_RGB;
|
||||
return color;
|
||||
}
|
||||
}
|
||||
if (isRGBValue(colorValues.red) || isRGBValue(colorValues.green) || isRGBValue(colorValues.blue)) {
|
||||
color.type = COLORTYPE_RGB;
|
||||
return colorValues;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// check a single rgb value
|
||||
function isRGBValue(value) {
|
||||
return value != undefined && value.length > 0 && !isNaN(value) && value >= 0 && value <= 255;
|
||||
}
|
||||
|
||||
// parse hex color values
|
||||
function parseHexColor(colorValues) {
|
||||
if (colorValues[0] !== '#') {
|
||||
colorValues = '#' + colorValues;
|
||||
}
|
||||
if (colorValues.length === 4) {
|
||||
colorValues.type = COLORTYPE_HEX;
|
||||
return (colorValues[0] + colorValues[1] + colorValues[1] + colorValues[2] + colorValues[2] + colorValues[3] + colorValues[3]);
|
||||
}
|
||||
if (colorValues.length === 7 && hexcolor({ strict: true }).test(colorValues)) {
|
||||
colorValues.type = COLORTYPE_HEX;
|
||||
return colorValues;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// pass the options to the blinkstick accordingly
|
||||
function illuminate(blinkstickConfig) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
waitForAnimation
|
||||
let color;
|
||||
findBlinkstick().then(function () {
|
||||
if (blinkstickConfig.color.type === COLORTYPE_HEX) {
|
||||
color = blinkstickConfig.color.red;
|
||||
} else {
|
||||
color = blinkstickConfig.color.red + ", " + blinkstickConfig.color.green + ", " + blinkstickConfig.color.blue;
|
||||
}
|
||||
switch (blinkstickConfig.mode) {
|
||||
case "morph":
|
||||
morph(blinkstickConfig, color, resolve, reject);
|
||||
break;
|
||||
case "pulse":
|
||||
startPulsing(blinkstickConfig, color, resolve, reject);
|
||||
break;
|
||||
default:
|
||||
setColor(blinkstickConfig, color, resolve, reject);
|
||||
break;
|
||||
}
|
||||
})
|
||||
.catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
// set a static color
|
||||
function setColor(blinkstickConfig, color, resolve, reject) {
|
||||
stopAnimation()
|
||||
.then(function () {
|
||||
setAnimationProperties(blinkstickConfig);
|
||||
led.setColor(blinkstickConfig.color.red, blinkstickConfig.color.green, blinkstickConfig.color.blue, function (err) {
|
||||
clearAnimationProperties();
|
||||
logger.debug("setting color to '" + color + "'...");
|
||||
if (err) {
|
||||
return reject("error setting color '" + color + "' (" + err + ")");
|
||||
}
|
||||
return resolve("set color to '" + color + "'");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// morph to a color
|
||||
function morph(blinkstickConfig, color, resolve, reject) {
|
||||
stopAnimation()
|
||||
.then(function () {
|
||||
setAnimationProperties(blinkstickConfig);
|
||||
led.morph(blinkstickConfig.color.red, blinkstickConfig.color.green, blinkstickConfig.color.blue, blinkstickConfig.options, function (err) {
|
||||
clearAnimationProperties();
|
||||
logger.debug("morphing color to '" + color + "'...");
|
||||
if (err) {
|
||||
return reject("error morphing color to '" + color + "' (" + err + ")");
|
||||
}
|
||||
return resolve("morphed color to '" + color + "'");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// start pulsing
|
||||
function startPulsing(blinkstickConfig, color, 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 '" + color + "'");
|
||||
}
|
||||
if (animation.id && animation.id != blinkstickConfig.id) {
|
||||
stopAnimation()
|
||||
.then(logger.info)
|
||||
.then(function () {
|
||||
startPulsing(blinkstickConfig, color, resolve, reject)
|
||||
})
|
||||
.catch(logger.error);
|
||||
return;
|
||||
}
|
||||
setAnimationProperties(blinkstickConfig);
|
||||
led.pulse(blinkstickConfig.color.red, blinkstickConfig.color.green, blinkstickConfig.color.blue, blinkstickConfig.options, function (err) {
|
||||
if (err) {
|
||||
clearAnimationProperties();
|
||||
return reject("error pulsing color '" + color + "' (" + err + ")");
|
||||
}
|
||||
blinkstickConfig.options.pulse.done++;
|
||||
logger.debug("pulsed color '" + color + "' " + blinkstickConfig.options.pulse.done + "/" + blinkstickConfig.options.pulse.max + " times");
|
||||
startPulsing(blinkstickConfig, color, resolve, reject);
|
||||
});
|
||||
}
|
||||
|
||||
// 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,
|
||||
isAnimationInProgress,
|
||||
isInfiniteAnimationInProgress,
|
||||
stopAnimation,
|
||||
powerOff
|
||||
};
|
112
libs/logger.js
Normal file
112
libs/logger.js
Normal file
|
@ -0,0 +1,112 @@
|
|||
// requirements
|
||||
const config = require("../config.json");
|
||||
// third party requirements
|
||||
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 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) {
|
||||
case LOG_PREFIX_DEBUG:
|
||||
case LOGLEVEL_DEBUG:
|
||||
return LOGLEVEL_DEBUG;
|
||||
case LOG_PREFIX_INFO:
|
||||
case LOGLEVEL_INFO:
|
||||
return LOGLEVEL_INFO;
|
||||
case LOG_PREFIX_WARNING:
|
||||
case LOGLEVEL_WARNING:
|
||||
return LOGLEVEL_WARNING;
|
||||
case LOG_PREFIX_ERROR:
|
||||
case LOGLEVEL_ERROR:
|
||||
return LOGLEVEL_ERROR;
|
||||
default:
|
||||
return LOGLEVEL_INFO;
|
||||
}
|
||||
}();
|
||||
|
||||
// log a http request
|
||||
function logRequest(request) {
|
||||
let message = "[" + request.method + "] url: \"" + request.url + "\"";
|
||||
let counter = 1;
|
||||
for (let param in request.body) {
|
||||
message += ", parameter " + counter + ": \"" + param + "=" + request.body[param] + "\"";
|
||||
counter++;
|
||||
}
|
||||
debug(message.trim());
|
||||
}
|
||||
|
||||
// prefix log with "info"
|
||||
function info(message) {
|
||||
if (loglevel > LOGLEVEL_INFO) {
|
||||
return;
|
||||
}
|
||||
trace(message);
|
||||
}
|
||||
|
||||
// prefix log with "info"
|
||||
function warn(message) {
|
||||
if (loglevel > LOGLEVEL_WARNING) {
|
||||
return;
|
||||
}
|
||||
trace(message, "warning");
|
||||
}
|
||||
|
||||
// prefix log with "debug"
|
||||
function debug(message) {
|
||||
if (loglevel > LOGLEVEL_DEBUG) {
|
||||
return;
|
||||
}
|
||||
trace(message, "debug");
|
||||
}
|
||||
|
||||
// prefix log with "error"
|
||||
function error(message) {
|
||||
if (loglevel > LOGLEVEL_ERROR) {
|
||||
return;
|
||||
}
|
||||
trace(message, "error");
|
||||
}
|
||||
|
||||
// default logging function
|
||||
function trace(message, prefix) {
|
||||
if (message === undefined || message === null || message.length === 0) {
|
||||
return;
|
||||
}
|
||||
if (prefix === undefined || prefix === null || prefix.length === 0) {
|
||||
prefix = "info";
|
||||
}
|
||||
let print;
|
||||
switch (prefix) {
|
||||
case "error":
|
||||
print = console.error;
|
||||
break;
|
||||
case "debug":
|
||||
print = console.debug;
|
||||
break;
|
||||
case "warning":
|
||||
print = console.warn;
|
||||
break;
|
||||
default:
|
||||
print = console.log;
|
||||
}
|
||||
message = moment().format(config.server.timestamp) + " | " + prefix + " > " + message;
|
||||
print(message);
|
||||
}
|
||||
|
||||
// exports
|
||||
module.exports = {
|
||||
info,
|
||||
warn,
|
||||
debug,
|
||||
error,
|
||||
logRequest
|
||||
};
|
125
libs/server.js
Normal file
125
libs/server.js
Normal file
|
@ -0,0 +1,125 @@
|
|||
// requirements
|
||||
const path = require('path');
|
||||
const config = require('../config');
|
||||
const packageJSON = require('../package.json');
|
||||
const logger = require('./logger');
|
||||
const blinkstick = require('./blinkstick');
|
||||
// third party requirements
|
||||
const express = require('express');
|
||||
const favicon = require('serve-favicon')
|
||||
const parser = require('body-parser');
|
||||
|
||||
// setup express, blinkstick and other stuff
|
||||
const app = express();
|
||||
app.use(favicon(path.join(path.dirname(__dirname), "public", "favicon.ico")));
|
||||
app.use(parser.json());
|
||||
app.use(parser.urlencoded({ extended: true }));
|
||||
|
||||
// get the html content for get requests
|
||||
// TODO: replace with template engine (vue.js)
|
||||
function getHTML() {
|
||||
let welcomeMessage =
|
||||
"<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>";
|
||||
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>description</th>";
|
||||
Object.keys(config.api.post).forEach(function (argument) {
|
||||
welcomeMessage +=
|
||||
"<tr>" +
|
||||
"<td>" + argument + "</td>" +
|
||||
"<td>" + config.api.post[argument].available + "</td>" +
|
||||
"<td>" + config.api.post[argument].default + "</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
|
||||
function start() {
|
||||
return new Promise(function (resolve, reject) {
|
||||
app.listen(config.server.port, config.server.listen)
|
||||
.on("listening", function () {
|
||||
return resolve("server listening on " + config.server.listen + ":" + config.server.port + "...")
|
||||
})
|
||||
.on("error", function (err) {
|
||||
return reject("error starting server (" + err + ")");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function handleRequests() {
|
||||
// GET methods
|
||||
app.get('*', function (request, response) {
|
||||
logger.logRequest(request);
|
||||
response.send(getHTML());
|
||||
response.end();
|
||||
});
|
||||
// POST methods
|
||||
app.post('*', function (request, response) {
|
||||
logger.logRequest(request);
|
||||
if (!blinkstick.isAnimationInProgress() || blinkstick.isInfiniteAnimationInProgress()) {
|
||||
response.end();
|
||||
blinkstick.parseColor(parseRequest(request.body))
|
||||
.then(blinkstick.illuminate)
|
||||
.then(logger.info)
|
||||
.catch(logger.error);
|
||||
return;
|
||||
}
|
||||
response.sendStatus(503);
|
||||
});
|
||||
}
|
||||
|
||||
// parse the request and return an object with sane defaults
|
||||
function parseRequest(data) {
|
||||
const blinkstickConfig = {
|
||||
"id": Math.random(),
|
||||
"mode": data["mode"] || config.api.post.mode.default,
|
||||
"color": data["color"] || config.api.post.color.default,
|
||||
"options": {
|
||||
"duration": data["duration"] || config.api.post.duration.default,
|
||||
"pulse": {
|
||||
"max": data["pulses"] || 0,
|
||||
"done": 0
|
||||
}
|
||||
}
|
||||
};
|
||||
blinkstickConfig.options.steps = blinkstickConfig.options.duration / 10;
|
||||
if (blinkstickConfig.options.duration < 100) {
|
||||
blinkstickConfig.options.duration = 100;
|
||||
}
|
||||
return blinkstickConfig;
|
||||
}
|
||||
|
||||
// exports
|
||||
module.exports = {
|
||||
start,
|
||||
handleRequests
|
||||
};
|
23
package.json
Normal file
23
package.json
Normal file
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"name": "blinky",
|
||||
"version": "0.0.1",
|
||||
"description": "control a blinkstick via http requests",
|
||||
"main": "blinky.js",
|
||||
"scripts": {
|
||||
"test": "echo \"error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [
|
||||
"blinkstick",
|
||||
"http"
|
||||
],
|
||||
"author": "Daniel Sommer <daniel.sommer@velvettear.de>",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"blinkstick": "^1.2.0",
|
||||
"body-parser": "^1.19.0",
|
||||
"express": "^4.17.1",
|
||||
"hex-color-regex": "^1.1.0",
|
||||
"moment": "^2.29.1",
|
||||
"serve-favicon": "^2.5.0"
|
||||
}
|
||||
}
|
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 62 KiB |
86
scripts/blinky-feed.sh
Executable file
86
scripts/blinky-feed.sh
Executable file
|
@ -0,0 +1,86 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# author: Daniel Sommer <daniel.sommer@velvettear.de>
|
||||
# license: MIT
|
||||
|
||||
# blinky
|
||||
blinky_url="127.0.0.1:3000"
|
||||
blinky_mode="morph"
|
||||
blinky_duration="2500"
|
||||
|
||||
# temperature
|
||||
max_temp="85000"
|
||||
crit_temp="90000"
|
||||
threshold_upper="66"
|
||||
threshold_lower="33"
|
||||
thermal_zones=(
|
||||
"*"
|
||||
)
|
||||
|
||||
# check for root permissions
|
||||
[[ "$EUID" != 0 ]] && printf "error: permission denied!\n" && exit 1
|
||||
|
||||
# argument to lowercase
|
||||
arg="${1,,}"
|
||||
|
||||
# functions
|
||||
|
||||
# convert a temperature value to a rgb color value
|
||||
function temperatureToRGB() {
|
||||
[[ ! "$1" ]] && printf "error: did not receive a temperature value to convert to a rgb value\n" && exit 1
|
||||
printf "converting temperature to rgb value... "
|
||||
percentage="$(bc <<< "$1 / ( $max_temp / 100 )")"
|
||||
[[ "$percentage" -gt "100" ]] && percentage="100"
|
||||
color_main="$(bc <<< "$percentage * 2.55")"
|
||||
color_supplement="$(bc <<< "255 - $color_main")"
|
||||
color_unused="0"
|
||||
if [[ "$percentage" -ge "$threshold_upper" ]]; then
|
||||
result="$color_main, $color_supplement, $color_unused"
|
||||
elif [[ "$percentage" -ge "33" ]]; then
|
||||
result="$color_supplement, $color_main, $color_unused"
|
||||
else
|
||||
result="$color_unused, $color_supplement, $color_main"
|
||||
fi
|
||||
printf "result: '$result'\n"
|
||||
}
|
||||
|
||||
# send http post request to blinky
|
||||
function sendPOST() {
|
||||
[[ ! "$1" ]] && printf "error: did not receive any arguments for post request\n" && exit 1
|
||||
cmd="curl -X POST"
|
||||
for arg in "$@"; do
|
||||
cmd="$cmd -d \"$arg\""
|
||||
done
|
||||
cmd="$cmd \"$blinky_url\""
|
||||
printf "sending post request '$cmd'...\n"
|
||||
eval "$cmd"
|
||||
}
|
||||
|
||||
# get (average) temperature from defined thermal zones
|
||||
function getTemperature() {
|
||||
counter="0"
|
||||
printf "getting temperature value from thermal zone(s) "
|
||||
for zone in "${thermal_zones[@]}"; do
|
||||
printf "'"$zone"'... "
|
||||
for value in $(cat "/sys/devices/virtual/thermal/thermal_zone"$zone"/temp"); do
|
||||
[[ ! "$temp" ]] && temp="$value" || temp=$(( $temp + $value ))
|
||||
(( counter++ ))
|
||||
done
|
||||
done
|
||||
result="$(( $temp / $counter ))"
|
||||
printf "result: '$result'\n"
|
||||
[[ "$result" -ge "$crit_temp" ]] && printf "warning: critical temperature reached\n" && blinky_mode="pulse" && blinky_duration="500" && return 0
|
||||
[[ "$result" -ge "$max_temp" ]] && printf "warning: maximum temperature reached\n" && blinky_mode="pulse" && blinky_duration="1500" && return 0
|
||||
}
|
||||
|
||||
# main part
|
||||
case "$arg" in
|
||||
-t|--temp|--temperature)
|
||||
getTemperature
|
||||
temperatureToRGB "$result"
|
||||
sendPOST "color=$result" "mode=$blinky_mode" "duration=$blinky_duration"
|
||||
;;
|
||||
*)
|
||||
printf "error: no argument given or argument unknown!\n" && exit 1
|
||||
;;
|
||||
esac
|
Loading…
Reference in a new issue