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', {
MASTER_GAIN).CH), and the From/To fields define a numeric range (e.g. 1–48 → CH1…CH48).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.