aboutsummaryrefslogtreecommitdiff
path: root/src/NeoRuntimeManager/IPC.cjs
diff options
context:
space:
mode:
authorjakob.stendahl <jakob.stendahl@infomedia.dk>2022-12-17 21:31:41 +0100
committerJakob Stendahl <jakob.stendahl@outlook.com>2022-12-17 21:31:41 +0100
commit1e588718a855ae2871a8841f6c6e621f49795454 (patch)
tree6599b3959554b307a571a73373114cb2d34a98ef /src/NeoRuntimeManager/IPC.cjs
parent6c37c28d7044a813fcde9ef80bf8852529b8305f (diff)
downloadLuxcena-Neo-1e588718a855ae2871a8841f6c6e621f49795454.tar.gz
Luxcena-Neo-1e588718a855ae2871a8841f6c6e621f49795454.zip
Start moving to esm, work on updater
Diffstat (limited to 'src/NeoRuntimeManager/IPC.cjs')
-rw-r--r--src/NeoRuntimeManager/IPC.cjs199
1 files changed, 199 insertions, 0 deletions
diff --git a/src/NeoRuntimeManager/IPC.cjs b/src/NeoRuntimeManager/IPC.cjs
new file mode 100644
index 0000000..0684418
--- /dev/null
+++ b/src/NeoRuntimeManager/IPC.cjs
@@ -0,0 +1,199 @@
+/**
+ * This module is used to communicate with a python NeoRuntime instance.
+ *
+ * @author jakobst1n.
+ * @since 3.10.2021
+ */
+
+const net = require("net");
+let logger = require("../Logger/index.cjs");
+
+/** @type {int} How long wait between each reconnection attempt */
+const RECONNECT_INTERVAL = 1000;
+/** @type {Object} ENUM-ish for command that can be sent to neoruntime */
+const COMMAND = Object.freeze({SET_GLOB : 0,
+ SET_VAR : 1,
+ SET_SEND_STRIP_BUF: 2});
+/** @type {Object} ENUM-ish for globvars */
+const GLOBVAR = Object.freeze({POWER_ON : 0,
+ BRIGHTNESS: 1});
+/** @type {Object} ENUM-ish for what type of data neoruntime sends */
+const DATATYPE = Object.freeze({STATES : 1,
+ STRIP_BUF: 2,
+ MATRIX: 3});
+
+/**
+ * class that will keep a active connection to a socket if possible, and
+ * automatically reconnect. It will emit events when data is received,
+ * and it will send commands to the process. */
+class IPC {
+
+ constructor(_socketFile, _eventEmitter) {
+ this.socketFile = _socketFile;
+ this.eventEmitter = _eventEmitter;
+
+ this.client;
+ this.connected = false;
+ this.reconnectInterval = false;
+
+ this.globvars = {};
+ this.variables = {};
+
+ this.reconnect();
+ }
+
+ /**
+ * If we are not already attempting to reconnect, this will start a
+ * interval that tries to reconnect. */
+ reconnect() {
+ if (this.reconnectInterval === false) {
+ this.reconnectInterval = setInterval(this.tryOpenSocketConnection.bind(this), RECONNECT_INTERVAL);
+ }
+ }
+
+ /**
+ * This will attempt to connect to the socket, and then setup all listeners
+ * if it succedes. */
+ tryOpenSocketConnection() {
+ // logger.info("Attempting to start IPC");
+
+ this.client = net.createConnection(this.socketFile)
+ .on('connect', () => {
+ clearInterval(this.reconnectInterval);
+ this.reconnectInterval = false;
+ logger.info("IPC Connected.");
+ })
+ .on("ready", () => {
+ this.connected = true;
+ })
+ .on('data', (data) => {
+ let json_data;
+ switch (data[0]) {
+ case DATATYPE.STATES:
+ try {
+ json_data = JSON.parse(data.toString("ascii", 1));
+ } catch (e) {
+ logger.warning("Could not parse json data from neoruntime");
+ return;
+ }
+
+ if (json_data.hasOwnProperty("globvars")) {
+ forEachDiff(json_data["globvars"], this.globvars, (key, newVal) => {
+ this.eventEmitter.emit("change", key, newVal);
+ });
+ this.globvars = json_data["globvars"];
+ }
+ if (json_data.hasOwnProperty("variables")) {
+ forEachDiff(json_data["variables"], this.variables, (key, newVal) => {
+ this.eventEmitter.emit("change", `variable/${key}`, newVal);
+ });
+ this.variables = json_data["variables"];
+ }
+ break;
+
+ case DATATYPE.MATRIX:
+ try {
+ json_data = JSON.parse(data.toString("ascii", 1));
+ } catch (e) {
+ logger.warning("Could not parse json data from neoruntime");
+ console.log(e);
+ }
+ this.eventEmitter.emit("matrix", json_data);
+ break;
+
+ case DATATYPE.STRIP_BUF:
+ this.eventEmitter.emit("strip_buffer", Array.from(data.values()).slice(1));
+ break;
+
+ default:
+ logger.info(data);
+ }
+
+ })
+ .on("timeout", () => {
+ logger.info("IPC Timeout");
+ })
+ .on("close", (hadError) => {
+ logger.info("IPC Close, hadError: ", hadError);
+ this.connected = false;
+ this.reconnect();
+ })
+ .on("end", () => {
+ logger.info("IPC End");
+ this.connected = false;
+ })
+ .on('error', (data) => {
+ logger.info('IPC Server not active.');
+ this.connected = false;
+ this.reconnect();
+ })
+ ;
+ }
+
+ /**
+ * Will send a command to the socket if we have a active connection,
+ * if not it will just drop the command. there is no queue implemented
+ * for such events. */
+ sendCommand(commandType, name, value) {
+ if (this.connected) {
+ let buf;
+
+ switch (commandType) {
+ case (COMMAND.SET_GLOB):
+ buf = Buffer.allocUnsafe(3);
+ buf[1] = name;
+ buf[2] = value;
+ break;
+
+ case (COMMAND.SET_VAR):
+ if (name.length > 32) { return {success: false, reason: "name too long", detail: "max size of name is 32 bytes"}; }
+ if (value.length > 93) { return {success: false, reason: "value too long", detail: "max size of value is 93 bytes"}; }
+ buf = Buffer.allocUnsafe(3 + name.length + value.length);
+ buf[1] = name.length;
+ buf[2] = value.length;
+ buf.write(name, 3, name.length, "ascii");
+ buf.write(value, 3+name.length, value.length, "ascii");
+ break;
+
+ case (COMMAND.SET_SEND_STRIP_BUF):
+ buf = Buffer.allocUnsafe(2);
+ buf[1] = (name) ? 1 : 0;
+ break;
+
+ default:
+ logger.warning(`IPC UNKNOWN COMMANDTYPE ${commandType}`)
+ return {success: false, reason: "ipc command unknown", detail: commandType};
+ }
+
+ buf[0] = commandType;
+ this.client.write(buf);
+ return {success: true}
+ }
+ return {success: false, reason: "socket not connected", detail: "This usually means the python script is not running"};
+ }
+
+}
+
+const isObject = v => v && typeof v === 'object';
+
+/**
+ * Will call callback on all the differences between the dicts
+ */
+function forEachDiff(dict1, dict2, callback) {
+ for (const key of new Set([...Object.keys(dict1), ...Object.keys(dict2)])) {
+ if (isObject(dict1[key]) && isObject(dict2[key])) {
+ if (dict1[key].value !== dict2[key].value) {
+ callback(key, dict1[key]);
+ }
+ } else if (dict1[key] !== dict2[key]) {
+ if (isObject(dict2[key]) && (dict1[key] == null)) {
+ dict2[key].value = null;
+ callback(key, dict2[key])
+ } else {
+ callback(key, dict1[key]);
+ }
+ }
+ }
+}
+
+module.exports = {IPC, COMMAND, GLOBVAR};