From 38a3d1caf949554bb9ace38e7265ef088726a518 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niko=20Vuji=C4=87?= Date: Wed, 31 Dec 2025 09:39:48 +0100 Subject: [PATCH] added xilica-listen node --- nodes/xilica-listen.html | 145 ++++++++++++++++++++++++++++++++++++ nodes/xilica-listen.js | 142 +++++++++++++++++++++++++++++++++++ nodes/xilica-subscribe.html | 8 +- package.json | 3 +- 4 files changed, 295 insertions(+), 3 deletions(-) create mode 100644 nodes/xilica-listen.html create mode 100644 nodes/xilica-listen.js diff --git a/nodes/xilica-listen.html b/nodes/xilica-listen.html new file mode 100644 index 0000000..a2b3dcb --- /dev/null +++ b/nodes/xilica-listen.html @@ -0,0 +1,145 @@ + + + + + + diff --git a/nodes/xilica-listen.js b/nodes/xilica-listen.js new file mode 100644 index 0000000..e98706b --- /dev/null +++ b/nodes/xilica-listen.js @@ -0,0 +1,142 @@ +module.exports = function (RED) { + function buildControlNamesFromRules(rules) { + const names = []; + if (!Array.isArray(rules)) { + return names; + } + + rules.forEach((rule) => { + if (!rule) { + return; + } + + const type = (rule.t || rule.type || "eq").toString(); + + if (type === "idx") { + const base = (rule.v || rule.base || "").trim(); + if (!base) { + return; + } + + let from = parseInt(rule.from, 10); + let to = parseInt(rule.to, 10); + + if (Number.isNaN(from) && Number.isNaN(to)) { + return; + } + if (Number.isNaN(from)) { + from = to; + } + if (Number.isNaN(to)) { + to = from; + } + if (from > to) { + const tmp = from; + from = to; + to = tmp; + } + + for (let i = from; i <= to; i += 1) { + names.push(base + i); + } + } else { + const name = (rule.v || rule.value || "").trim(); + if (name) { + names.push(name); + } + } + }); + + return names; + } + + function XilicaListen(config) { + RED.nodes.createNode(this, config); + const node = this; + + node.rules = Array.isArray(config.rules) ? config.rules : []; + + node.on("input", (msg, send, done) => { + send = + send || + function () { + node.send.apply(node, arguments); + }; + done = done || function () {}; + + let text = msg.payload; + if (Buffer.isBuffer(text)) { + text = text.toString("utf8"); + } else if (typeof text !== "string") { + text = String(text || ""); + } + + if (!text) { + done(); + return; + } + + const rules = Array.isArray(msg.rules) ? msg.rules : node.rules; + const names = buildControlNamesFromRules(rules); + const matchAll = names.length === 0; + const allowed = new Set(names); + + const lines = text.split(/\r?\n/); + + lines.forEach((line) => { + if (!line) { + return; + } + + let type = null; + let control = null; + let data = null; + let payload = line; + + if (payload[0] === "#") { + payload = payload.slice(1); + const idx = payload.indexOf("="); + if (idx === -1) { + return; + } + control = payload.slice(0, idx).trim(); + data = payload.slice(idx + 1); + type = "notification"; + } else { + const idx = payload.indexOf("="); + if (idx === -1) { + return; + } + control = payload.slice(0, idx).trim(); + data = payload.slice(idx + 1); + type = "value"; + } + + if (!control) { + return; + } + if (!matchAll && !allowed.has(control)) { + return; + } + + const out = Object.assign({}, msg); + out.control = control; + out.data = data; + out.type = type; + out.raw = line; + out.payload = { + type, + control, + data, + }; + + send(out); + }); + + done(); + }); + } + + RED.nodes.registerType("xilica-listen", XilicaListen); +}; + diff --git a/nodes/xilica-subscribe.html b/nodes/xilica-subscribe.html index 3b1f059..ab6cbe9 100644 --- a/nodes/xilica-subscribe.html +++ b/nodes/xilica-subscribe.html @@ -133,7 +133,10 @@ RED.nodes.registerType('xilica-subscribe', {
- +
@@ -146,5 +149,6 @@ RED.nodes.registerType('xilica-subscribe', {
  • Type ==: the Value field is used as a full control name (e.g. MASTER_GAIN).
  • Type Indexed: the Value field is a base string (e.g. CH), and the From/To fields define a numeric range (e.g. 1–48 → CH1CH48).
  • -

    The Transport field controls the subscription transport string, typically TCP. The node generates commands like SUBSCRIBE CH1 "TCP".

    +

    Transport selects the subscription transport (TCP or UDP). The node generates commands like SUBSCRIBE CH1 "TCP" or SUBSCRIBE CH1 "UDP".

    +

    The actual Xilica device connection (IP/port/password) is chosen by the xilica-command node that this node is wired into, via its xilica-connection config.

    diff --git a/package.json b/package.json index ef6bb44..02fbd3b 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "xilica-connection": "nodes/xilica-connection.js", "xilica-command": "nodes/xilica-command.js", "xilica-interval": "nodes/xilica-interval.js", - "xilica-subscribe": "nodes/xilica-subscribe.js" + "xilica-subscribe": "nodes/xilica-subscribe.js", + "xilica-listen": "nodes/xilica-listen.js" } } }