diff --git a/nodes/xilica-listen.html b/nodes/xilica-listen.html index cc93f85..58f364f 100644 --- a/nodes/xilica-listen.html +++ b/nodes/xilica-listen.html @@ -5,7 +5,7 @@ RED.nodes.registerType('xilica-listen', { defaults: { name: { value: "" }, connection: { value: "", type: "xilica-connection" }, - rules: { value: "[]" } + rules: { value: [] } }, inputs: 1, outputs: 1, @@ -18,10 +18,15 @@ RED.nodes.registerType('xilica-listen', { }, oneditprepare: function () { var node = this; - var rules; - try { - rules = node.rules ? JSON.parse(node.rules) : []; - } catch (e) { + var rules = node.rules || []; + if (typeof rules === "string") { + try { + rules = JSON.parse(rules); + } catch (e) { + rules = []; + } + } + if (!Array.isArray(rules)) { rules = []; } @@ -113,7 +118,7 @@ RED.nodes.registerType('xilica-listen', { rules.push(rule); }); - this.rules = JSON.stringify(rules); + this.rules = rules; } }); diff --git a/nodes/xilica-listen.js b/nodes/xilica-listen.js index f3e240e..4c3cf0d 100644 --- a/nodes/xilica-listen.js +++ b/nodes/xilica-listen.js @@ -54,15 +54,15 @@ module.exports = function (RED) { RED.nodes.createNode(this, config); const node = this; - let storedRules = config.rules; - if (typeof storedRules === "string") { + let initialRules = config.rules; + if (typeof initialRules === "string") { try { - storedRules = JSON.parse(storedRules); + initialRules = JSON.parse(initialRules); } catch (e) { - storedRules = []; + initialRules = []; } } - node.rules = Array.isArray(storedRules) ? storedRules : []; + node.rules = Array.isArray(initialRules) ? initialRules : []; node.on("input", (msg, send, done) => { send = diff --git a/nodes/xilica-subscribe.html b/nodes/xilica-subscribe.html index 76e5a84..5117f30 100644 --- a/nodes/xilica-subscribe.html +++ b/nodes/xilica-subscribe.html @@ -6,7 +6,10 @@ RED.nodes.registerType('xilica-subscribe', { name: { value: "" }, action: { value: "subscribe" }, connection: { value: "", type: "xilica-connection" }, - rules: { value: "[]" }, + mode: { value: "list" }, + targets: { value: "" }, + startIndex: { value: 1 }, + endIndex: { value: 1 }, transport: { value: "TCP" } }, inputs: 1, @@ -18,105 +21,6 @@ RED.nodes.registerType('xilica-subscribe', { return this.name; } return "xilica " + act; - }, - oneditprepare: function () { - var node = this; - var rules; - try { - rules = node.rules ? JSON.parse(node.rules) : []; - } catch (e) { - rules = []; - } - - $("#node-input-rules").editableList({ - addItem: function (container, index, rule) { - rule = rule || {}; - - var row = $('
').appendTo(container); - - var typeField = $('', { - class: "node-input-rule-value", - type: "text", - style: "width: 180px; margin-right: 5px;" - }).appendTo(row); - - var fromField = $('', { - class: "node-input-rule-from", - type: "number", - style: "width: 80px; margin-right: 5px;", - placeholder: "From" - }).appendTo(row); - - var toField = $('', { - class: "node-input-rule-to", - type: "number", - style: "width: 80px;", - placeholder: "To" - }).appendTo(row); - - function updateVisibility() { - var t = typeField.val(); - if (t === "idx") { - fromField.show(); - toField.show(); - } else { - fromField.hide(); - toField.hide(); - } - } - - typeField.on("change", updateVisibility); - - typeField.val(rule.t || "eq"); - valueField.val(rule.v || ""); - if (rule.from !== undefined) { - fromField.val(rule.from); - } - if (rule.to !== undefined) { - toField.val(rule.to); - } - - updateVisibility(); - }, - removable: true, - sortable: true - }); - - (rules || []).forEach(function (r) { - $("#node-input-rules").editableList('addItem', r); - }); - }, - oneditsave: function () { - var rules = []; - $("#node-input-rules").editableList('items').each(function () { - var row = $(this); - var type = $(".node-input-rule-type", row).val() || "eq"; - var value = $(".node-input-rule-value", row).val() || ""; - var from = $(".node-input-rule-from", row).val(); - var to = $(".node-input-rule-to", row).val(); - - var rule = { t: type, v: value }; - - if (type === "idx") { - if (from !== "") { - rule.from = parseInt(from, 10); - } - if (to !== "") { - rule.to = parseInt(to, 10); - } - } - - rules.push(rule); - }); - - this.rules = JSON.stringify(rules); } }); @@ -138,8 +42,25 @@ RED.nodes.registerType('xilica-subscribe', {
- -
    + + +
    +
    + + +
    +
    + + +
    +
    + +
    @@ -154,10 +75,12 @@ RED.nodes.registerType('xilica-subscribe', {

    Builds SUBSCRIBE or UNSUBSCRIBE commands for a Xilica Solaro processor.

    On each input message, the node builds the commands and sends them directly to the selected xilica-connection. It then outputs a message with the commands that were sent.

    Action selects whether to generate SUBSCRIBE or UNSUBSCRIBE commands.

    -

    Rules define which control names to generate, similar to the Switch node's rule list:

    +

    Mode controls how the Targets field is interpreted:

    Transport selects the subscription transport (TCP or UDP). The node generates commands like SUBSCRIBE CH1 "TCP" or SUBSCRIBE CH1 "UDP" and sends them over the selected connection.

    The Xilica device connection (IP/port/password) is chosen directly on this node via the Connection field.

    diff --git a/nodes/xilica-subscribe.js b/nodes/xilica-subscribe.js index 9491aac..2fca89f 100644 --- a/nodes/xilica-subscribe.js +++ b/nodes/xilica-subscribe.js @@ -1,57 +1,80 @@ const net = require("net"); module.exports = function (RED) { - function buildControlNamesFromRules(rules) { + function buildNamesFromList(targets) { + return targets + .split(/\r?\n/) + .map((l) => l.trim()) + .filter((l) => l.length > 0); + } + + function buildNamesStartsWith(targets, startIndex, endIndex) { + const prefixes = buildNamesFromList(targets); const names = []; - if (!Array.isArray(rules)) { - return names; + if (startIndex > endIndex) { + const tmp = startIndex; + startIndex = endIndex; + endIndex = tmp; } - - 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); - } + prefixes.forEach((prefix) => { + for (let i = startIndex; i <= endIndex; i += 1) { + names.push(prefix + i); } }); - return names; } + function buildNamesEndsWith(targets, startIndex, endIndex) { + const suffixes = buildNamesFromList(targets); + const names = []; + if (startIndex > endIndex) { + const tmp = startIndex; + startIndex = endIndex; + endIndex = tmp; + } + suffixes.forEach((suffix) => { + for (let i = startIndex; i <= endIndex; i += 1) { + names.push(String(i) + suffix); + } + }); + return names; + } + + function buildNamesStartsAndEnds(targets, startIndex, endIndex) { + const patterns = buildNamesFromList(targets); + const names = []; + if (startIndex > endIndex) { + const tmp = startIndex; + startIndex = endIndex; + endIndex = tmp; + } + patterns.forEach((pattern) => { + const parts = pattern.split("|"); + const prefix = (parts[0] || "").trim(); + const suffix = (parts[1] || "").trim(); + if (!prefix && !suffix) { + return; + } + for (let i = startIndex; i <= endIndex; i += 1) { + names.push(prefix + i + suffix); + } + }); + return names; + } + + function buildControlNames(mode, targets, startIndex, endIndex) { + if (mode === "startsWith") { + return buildNamesStartsWith(targets, startIndex, endIndex); + } + if (mode === "endsWith") { + return buildNamesEndsWith(targets, startIndex, endIndex); + } + if (mode === "startsAndEnds") { + return buildNamesStartsAndEnds(targets, startIndex, endIndex); + } + return buildNamesFromList(targets); + } + function normaliseAction(action) { const a = (action || "").toString().toLowerCase(); if (a === "unsubscribe" || a === "unsub") { @@ -65,17 +88,12 @@ module.exports = function (RED) { const node = this; node.action = config.action || "subscribe"; - let storedRules = config.rules; - if (typeof storedRules === "string") { - try { - storedRules = JSON.parse(storedRules); - } catch (e) { - storedRules = []; - } - } - node.rules = Array.isArray(storedRules) ? storedRules : []; - node.transport = config.transport || "TCP"; node.connectionConfig = RED.nodes.getNode(config.connection); + node.mode = config.mode || "list"; + node.targets = config.targets || ""; + node.startIndex = parseInt(config.startIndex, 10) || 1; + node.endIndex = parseInt(config.endIndex, 10) || node.startIndex; + node.transport = config.transport || "TCP"; node.on("input", (msg, send, done) => { send = @@ -86,16 +104,26 @@ module.exports = function (RED) { done = done || function () {}; const action = normaliseAction(msg.action || node.action); - let rules = msg.rules || node.rules; - if (typeof rules === "string") { - try { - rules = JSON.parse(rules); - } catch (e) { - rules = []; + const mode = (msg.mode || node.mode || "list").toString(); + + const targets = + typeof msg.targets === "string" && msg.targets.trim().length + ? msg.targets + : node.targets; + + let startIndex = node.startIndex; + let endIndex = node.endIndex; + if (Object.prototype.hasOwnProperty.call(msg, "startIndex")) { + const v = parseInt(msg.startIndex, 10); + if (!Number.isNaN(v)) { + startIndex = v; } } - if (!Array.isArray(rules)) { - rules = []; + if (Object.prototype.hasOwnProperty.call(msg, "endIndex")) { + const v = parseInt(msg.endIndex, 10); + if (!Number.isNaN(v)) { + endIndex = v; + } } const transport = @@ -103,7 +131,7 @@ module.exports = function (RED) { ? msg.transport.trim() : node.transport; - const names = buildControlNamesFromRules(rules); + const names = buildControlNames(mode, targets || "", startIndex, endIndex); if (!names.length) { node.warn("xilica-subscribe: no rules defined");