Compare commits

..

10 commits

12 changed files with 2239 additions and 53 deletions

5
.gitignore vendored
View file

@ -1,6 +1,3 @@
.vscode/
.idea/
config.json
node_modules/
package-lock.json
yarn.lock
npm-debug.log

28
.vscode/launch.json vendored Normal file
View file

@ -0,0 +1,28 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"runtimeVersion": "8",
"request": "launch",
"name": "blinky",
"skipFiles": [
"<node_internals>/**"
],
"program": "${workspaceFolder}/blinky.js"
},
{
"type": "node",
"runtimeVersion": "8",
"request": "launch",
"name": "blinky cli",
"skipFiles": [
"<node_internals>/**"
],
"program": "${workspaceFolder}/blinky.js",
"args": [
"--get-blinksticks"
]
}
]
}

View file

@ -1,4 +1,5 @@
MIT License Copyright (c) <year> <copyright holders>
# MIT License
**Copyright (c) 2022 Daniel Sommer \<daniel.sommer@velvettear.de\>**
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -11,9 +12,9 @@ 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
**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.
OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.**

View file

@ -40,8 +40,6 @@ run:
`echo "KERNEL==\"hidraw*\", SUBSYSTEM==\"hidraw\", ATTRS{idVendor}==\"20a0\", ATTRS{idProduct}==\"41e5\", MODE=\"0666\"" | sudo tee /etc/udev/rules.d/85-blinkstick-hid.rules`
and reboot your system
## systemd
**for security reasons it is highly recommended to not run blinky with root permissions!**

View file

@ -26,7 +26,7 @@ async function main() {
if (await cli.handleArguments()) {
util.exit();
}
await controller.findBlinkstick();
controller.mapBlinkSticks();
logger.info(await server.start());
server.handleRequests();
}

View file

@ -5,6 +5,16 @@
},
"blinkstick": {
"cache": true,
"map": [
{
"id": "square",
"serial": "BS006537-3.0"
},
{
"id": "strip",
"serial": "BS042165-3.0"
}
],
"serials": [
"BS006537-3.0"
]
@ -15,7 +25,10 @@
},
"api": {
"get": {
"description": "show this page"
"description": "show this page",
"endpoints": [
"/color"
]
},
"post": {
"endpoints": [
@ -25,6 +38,11 @@
"/pulse",
"/poweroff"
],
"blinkstick": {
"available": "string values",
"default": "undefined",
"description": "specifies the blinkstick by the id given in the config.json file or by serial"
},
"color": {
"available": "random, hex color codes (#ffffff), rgb color codes (255, 255, 255)",
"default": "random",

View file

@ -11,7 +11,7 @@ async function handleArguments() {
continue;
case constants.ARG_LIST:
case constants.ARG_LIST_SHORT:
logger.info('blinksticks: ' + JSON.stringify(await controller.findBlinkstick(constants.ALL, true)));
logger.info('blinksticks: ' + JSON.stringify(await controller.getBlinkstick(constants.ALL, false)));
handled++;
break;
}

View file

@ -5,36 +5,81 @@ const blinkstick = require('blinkstick');
const LEDAnimations = new Map();
let blinksticks;
let blinksticks = new Map();
// find connected blinkstick(s)
async function findBlinkstick(index, ignoreFilter) {
if (!global.config.blinkstick?.cache || blinksticks === undefined) {
blinksticks = blinkstick.findAll();
if (!ignoreFilter && global.config.blinkstick?.serials?.length > 0) {
blinksticks = blinksticks.filter((blinkstick) => {
return global.config.blinkstick.serials.includes(blinkstick.serial);
});
if (blinksticks.length === 0) {
throw new Error('could not find any blinkstick matching the defined serial(s)');
// get connected blinkstick by id (or return the first one found)
async function getBlinkstick(id, filter) {
if (!global.config.blinkstick?.cache || blinksticks.size === 0) {
mapBlinkSticks(filter);
}
if (id === undefined) {
return blinksticks.values().next().value;
}
if (id === constants.ALL) {
return Array.from(blinksticks.values());
}
if (!blinksticks.has(id)) {
throw new Error('could not find any blinkstick matching the given id \'' + id + '\'');
}
return blinksticks.get(id);
}
// map found blinksticks
function mapBlinkSticks(filter) {
const foundBlinksticks = blinkstick.findAll();
if (filter === undefined) {
filter = global.config?.blinkstick?.map?.length > 0;
}
blinksticks = new Map();
filter = filter && global.config.blinkstick?.map?.length > 0;
for (let blinkstickIndex = 0; blinkstickIndex < foundBlinksticks.length; blinkstickIndex++) {
const serial = foundBlinksticks[blinkstickIndex].serial;
if (!filter) {
blinksticks.set(serial, foundBlinksticks[blinkstickIndex]);
continue;
}
for (filterIndex = 0; filterIndex < global.config.blinkstick.map.length; filterIndex++) {
let tmp = global.config.blinkstick.map[filterIndex];
if (tmp.serial !== serial) {
continue;
}
blinksticks.set(tmp.id || serial, foundBlinksticks[blinkstickIndex]);
break;
}
}
}
if (blinksticks.length === 0) {
if (blinksticks.size === 0) {
if (filter) {
throw new Error('could not find any blinkstick matching the given serial(s)');
} else {
throw new Error('could not find any blinkstick, make sure at least one blinkstick is connected');
}
if (index === undefined) {
index = 0;
} else if (index !== constants.ALL) {
index = parseInt(index) || 0;
}
if (index > blinksticks.length - 1) {
throw new Error('there is no blinkstick for index \'' + index + '\'');
}
// reset a blinkstick
async function resetBlinkstick(id) {
if (blinksticks === undefined || blinksticks.length === 0) {
return;
}
if (index === constants.ALL) {
return blinksticks;
let tmp;
if (id === constants.ALL) {
tmp = await getBlinkstick(id);
for (let index = 0; index < tmp.length; index++) {
if (tmp[index] === undefined) {
continue;
}
return blinksticks[index];
tmp[index].close();
}
blinksticks.clear();
} else {
tmp = await getBlinkstick(id);
if (tmp === undefined) {
return;
}
tmp.close();
blinksticks.delete(id);
}
mapBlinkSticks();
}
// simple animation (set the color / morph to color)
@ -79,6 +124,16 @@ async function complex(config) {
// power the blinkstick (or just a specific led) off
async function powerOff(config) {
config.timestamp = new Date().getTime();
if (config.blinkstick === constants.ALL) {
const promises = [];
const blinkstickNames = Array.from(blinksticks.keys());
for (let index = 0; index < blinkstickNames.length; index++) {
const tmp = JSON.parse(JSON.stringify(config));
tmp.blinkstick = blinkstickNames[index];
promises.push(powerOff(tmp));
}
return await Promise.allSettled(promises);
}
let indexes = getIndices(config);
if (config.options.index === constants.ALL) {
LEDAnimations.set(constants.ALL, { stop: new Date().getTime() });
@ -89,7 +144,7 @@ async function powerOff(config) {
logger.info('led \'' + indexes[index] + '\' powered off');
}
if (config.options.index === constants.ALL) {
const blinkstick = await findBlinkstick();
const blinkstick = await getBlinkstick();
blinkstick.turnOff();
LEDAnimations.clear();
logger.info('blinkstick powered off');
@ -100,7 +155,7 @@ async function powerOff(config) {
// animations
async function singleAnimation(config, index) {
config.options.index = index;
const blinkstick = await findBlinkstick();
const blinkstick = await getBlinkstick(config.blinkstick);
return await new Promise((resolve, reject) => {
logger.debug('changing color of led \'' + config.options.index + '\' to \'' + config.color + '\' (mode: ' + config.mode + ' | options: ' + JSON.stringify(config.options) + ')...');
setLEDAnimated(config.options.index);
@ -141,12 +196,47 @@ function getIndices(blinkstickConfig) {
return [blinkstickConfig.options.index];
}
async function getColor(index) {
if (index === undefined) {
index = 0;
async function getColors(blinkstick, index) {
let blinksticksToCheck = [];
if (blinkstick === undefined) {
blinksticksToCheck = Array.from(blinksticks.keys());
} else {
blinksticksToCheck.push(blinkstick);
}
let indices = [0, 1, 2, 3, 4, 5, 6, 7];
if (index !== undefined && index !== constants.AL && !isNaN(index)) {
index = [index];
}
let results = [];
for (let blinkstickIndex = 0; blinkstickIndex < blinksticksToCheck.length; blinkstickIndex++) {
const tmpBlinkstick = blinksticksToCheck[blinkstickIndex];
let result = {
blinkstick: tmpBlinkstick,
leds: []
};
for (let ledIndex = 0; ledIndex < indices.length; ledIndex++) {
result.leds.push({
index: ledIndex,
color: await getColor({
blinkstick: tmpBlinkstick,
options: {
index: ledIndex
}
})
});
}
results.push(result);
}
return results;
}
async function getColor(config) {
let index = 0;
if (!isNaN(config.options.index)) {
index = parseInt(config.options.index);
}
logger.debug('getting color for led with index \'' + index + '\'');
const blinkstick = await findBlinkstick();
const blinkstick = await getBlinkstick(config.blinkstick);
return await new Promise((resolve, reject) => {
blinkstick.getColorString(index, (err, color) => {
if (err) {
@ -162,7 +252,7 @@ async function setColorIfRandom(config) {
if (config.options.index !== constants.ALL || config.color !== constants.RANDOM) {
return;
}
config.color = await getColor(0);
config.color = await getColor(config);
}
async function stopLEDsAccordingly(config) {
@ -278,9 +368,11 @@ function isInfiniteAnimation(config) {
// exports
module.exports = {
findBlinkstick,
getBlinkstick,
mapBlinkSticks,
simple,
complex,
powerOff,
isInfiniteAnimation
isInfiniteAnimation,
getColors
}

View file

@ -20,9 +20,15 @@ function get() {
'<div>' +
'<h2>get:</h2>' +
'<p>' + config.api.get.description + '</p>' +
'</div>';
'<h3>endpoints: </>';
for (let index = 0; index < config.api.get.endpoints.length; index++) {
if (index > 0) {
html += ', ';
}
html += config.api.get.endpoints[index];
}
html +=
'</div>' +
'<div>' +
'<h2>post:</h2>' +
'<h3>endpoints: </>';

View file

@ -8,6 +8,7 @@ function parseRequest(requestBody, mode) {
}
let config = {
'id': Math.random(),
'blinkstick': parseBlinkstick(requestBody.blinkstick),
'mode': mode,
'options': {
'index': parseIndex(requestBody.index),
@ -39,6 +40,14 @@ function parseRequest(requestBody, mode) {
return config;
}
// parse the blinkstick
function parseBlinkstick(blinkstick) {
if (blinkstick === undefined) {
return undefined;
}
return blinkstick;
}
// parse the index
function parseIndex(index) {
if (index === undefined) {
@ -155,5 +164,7 @@ function parseHexColor(value) {
// exports
module.exports = {
parseRequest
parseRequest,
parseBlinkstick,
parseIndex
};

View file

@ -12,7 +12,7 @@ const bodyparser = require('body-parser');
const app = express();
app.use(favicon(path.join(path.dirname(__dirname), 'public', 'favicon.ico')));
app.use(bodyparser.json());
app.use(bodyparser.urlencoded({extended: true}));
app.use(bodyparser.urlencoded({ extended: true }));
const html = require('./index.js').get();
@ -30,6 +30,11 @@ async function start() {
}
function handleRequests() {
// GET '/color'
app.get('/color', (request, response) => {
logger.http(request);
handleGETColor(request.body.blinkstick, request.body.index, response);
});
// GET html page
app.get('*', (request, response) => {
logger.http(request);
@ -63,19 +68,36 @@ function handleRequests() {
});
}
async function handleGETColor(blinkstick, index, response) {
try {
let result = await controller.getColors(blinkstick, index);
response.end(JSON.stringify(result));
} catch (err) {
if (response === undefined) {
return;
}
logger.error(err);
response.status(500);
response.end(JSON.stringify({ status: 'error', error: err.message }));
}
}
async function handleSimpleAnimation(config, response) {
try {
response.end(JSON.stringify(await controller.simple(config)));
} catch (err) {
if (response === undefined) {
return;
}
logger.error(err);
response.status(500);
response.end(JSON.stringify({status: 'error', error: err.message}));
response.end(JSON.stringify({ status: 'error', error: err.message }));
}
}
async function handleComplexAnimation(config, response) {
if (controller.isInfiniteAnimation(config)) {
response.end(JSON.stringify({status: 'ok', time: 'infinite'}));
response.end(JSON.stringify({ status: 'ok', time: 'infinite' }));
response = undefined;
}
try {
@ -90,16 +112,19 @@ async function handleComplexAnimation(config, response) {
}
logger.error(err);
response.status(500);
response.end(JSON.stringify({status: 'error', error: err.message}));
response.end(JSON.stringify({ status: 'error', error: err.message }));
}
}
async function handlePowerOff(config, response) {
try {
if (config.blinkstick === undefined) {
config.blinkstick = constants.ALL;
}
response.end(JSON.stringify(await controller.powerOff(config)));
} catch (err) {
response.status(500);
response.end(JSON.stringify({status: 'error', error: err.message}));
response.end(JSON.stringify({ status: 'error', error: err.message }));
}
}

2010
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff