diff --git a/nodes/xilica-subscribe.html b/nodes/xilica-subscribe.html index ef4bc7c..3b1f059 100644 --- a/nodes/xilica-subscribe.html +++ b/nodes/xilica-subscribe.html @@ -5,10 +5,7 @@ RED.nodes.registerType('xilica-subscribe', { defaults: { name: { value: "" }, action: { value: "subscribe" }, - mode: { value: "list" }, - targets: { value: "" }, - startIndex: { value: 1 }, - endIndex: { value: 1 }, + rules: { value: [] }, transport: { value: "TCP" } }, inputs: 1, @@ -20,6 +17,100 @@ RED.nodes.registerType('xilica-subscribe', { return this.name; } return "xilica " + act; + }, + oneditprepare: function () { + var node = this; + var rules = node.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 = rules; } }); @@ -37,25 +128,8 @@ RED.nodes.registerType('xilica-subscribe', {
- - -
-
- - -
-
- - -
-
- - + +
    @@ -67,13 +141,10 @@ RED.nodes.registerType('xilica-subscribe', {

    Builds SUBSCRIBE or UNSUBSCRIBE commands for a Xilica Solaro processor.

    On each input message, the node sets msg.payload to one or more commands separated by carriage returns. Wire the output into a xilica-command node to send them over TCP.

    Action selects whether to generate SUBSCRIBE or UNSUBSCRIBE commands.

    -

    Mode controls how the Targets field is interpreted:

    +

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

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

    - diff --git a/nodes/xilica-subscribe.js b/nodes/xilica-subscribe.js index 7b77901..03cea7f 100644 --- a/nodes/xilica-subscribe.js +++ b/nodes/xilica-subscribe.js @@ -1,76 +1,53 @@ module.exports = function (RED) { - 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); + function buildControlNamesFromRules(rules) { const names = []; - if (startIndex > endIndex) { - const tmp = startIndex; - startIndex = endIndex; - endIndex = tmp; + if (!Array.isArray(rules)) { + return names; } - 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) { + rules.forEach((rule) => { + if (!rule) { return; } - for (let i = startIndex; i <= endIndex; i += 1) { - names.push(prefix + i + suffix); + + 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 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); + return names; } function normaliseAction(action) { @@ -86,10 +63,7 @@ module.exports = function (RED) { const node = this; node.action = config.action || "subscribe"; - 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.rules = Array.isArray(config.rules) ? config.rules : []; node.transport = config.transport || "TCP"; node.on("input", (msg, send, done) => { @@ -101,37 +75,17 @@ module.exports = function (RED) { done = done || function () {}; const action = normaliseAction(msg.action || node.action); - 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 (Object.prototype.hasOwnProperty.call(msg, "endIndex")) { - const v = parseInt(msg.endIndex, 10); - if (!Number.isNaN(v)) { - endIndex = v; - } - } + const rules = Array.isArray(msg.rules) ? msg.rules : node.rules; const transport = typeof msg.transport === "string" && msg.transport.trim().length ? msg.transport.trim() : node.transport; - const names = buildControlNames(mode, targets || "", startIndex, endIndex); + const names = buildControlNamesFromRules(rules); if (!names.length) { - node.warn("xilica-subscribe: no targets defined"); + node.warn("xilica-subscribe: no rules defined"); send(msg); done(); return; @@ -150,4 +104,3 @@ module.exports = function (RED) { RED.nodes.registerType("xilica-subscribe", XilicaSubscribe); }; -