added xilica-listen node
This commit is contained in:
145
nodes/xilica-listen.html
Normal file
145
nodes/xilica-listen.html
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
<script type="text/javascript">
|
||||||
|
RED.nodes.registerType('xilica-listen', {
|
||||||
|
category: 'Xilica',
|
||||||
|
color: '#D8E7FF',
|
||||||
|
defaults: {
|
||||||
|
name: { value: "" },
|
||||||
|
rules: { value: [] }
|
||||||
|
},
|
||||||
|
inputs: 1,
|
||||||
|
outputs: 1,
|
||||||
|
icon: "font-awesome/fa-plug",
|
||||||
|
label: function () {
|
||||||
|
if (this.name) {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
return "xilica listen";
|
||||||
|
},
|
||||||
|
oneditprepare: function () {
|
||||||
|
var node = this;
|
||||||
|
var rules = node.rules || [];
|
||||||
|
|
||||||
|
$("#node-input-rules").editableList({
|
||||||
|
addItem: function (container, index, rule) {
|
||||||
|
rule = rule || {};
|
||||||
|
|
||||||
|
var row = $('<div/>').appendTo(container);
|
||||||
|
|
||||||
|
var typeField = $('<select/>', {
|
||||||
|
class: "node-input-rule-type",
|
||||||
|
style: "width: 120px; margin-right: 5px;"
|
||||||
|
}).appendTo(row);
|
||||||
|
$('<option/>', { value: "eq", text: "==" }).appendTo(typeField);
|
||||||
|
$('<option/>', { value: "idx", text: "Indexed" }).appendTo(typeField);
|
||||||
|
|
||||||
|
var valueField = $('<input/>', {
|
||||||
|
class: "node-input-rule-value",
|
||||||
|
type: "text",
|
||||||
|
style: "width: 180px; margin-right: 5px;"
|
||||||
|
}).appendTo(row);
|
||||||
|
|
||||||
|
var fromField = $('<input/>', {
|
||||||
|
class: "node-input-rule-from",
|
||||||
|
type: "number",
|
||||||
|
style: "width: 80px; margin-right: 5px;",
|
||||||
|
placeholder: "From"
|
||||||
|
}).appendTo(row);
|
||||||
|
|
||||||
|
var toField = $('<input/>', {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script type="text/x-red" data-template-name="xilica-listen">
|
||||||
|
<div class="form-row">
|
||||||
|
<label for="node-input-name">Name</label>
|
||||||
|
<input type="text" id="node-input-name">
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<label>Rules</label>
|
||||||
|
<ol id="node-input-rules"></ol>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script type="text/x-red" data-help-name="xilica-listen">
|
||||||
|
<p>Parses Xilica Solaro notification/value lines and filters them by control name.</p>
|
||||||
|
<p>Wire the output of a <code>tcp in</code> or <code>udp in</code> node (connected to the Solaro subscription stream) into this node.</p>
|
||||||
|
<p><strong>Rules</strong> define which controls to pass through, similar to the Switch node:</p>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Type ==</strong>: the Value field is a full control name (e.g. <code>MASTER_GAIN</code>).</li>
|
||||||
|
<li><strong>Type Indexed</strong>: the Value field is a base string (e.g. <code>CH</code>), and From/To define a numeric range (e.g. 1–48 → <code>CH1</code>…<code>CH48</code>).</li>
|
||||||
|
</ul>
|
||||||
|
<p>If no rules are defined, all notification/value lines are passed through.</p>
|
||||||
|
<p>For each matching line, the node outputs a message with:</p>
|
||||||
|
<ul>
|
||||||
|
<li><code>msg.payload</code> = <code>{ type, control, data }</code></li>
|
||||||
|
<li><code>msg.control</code> = control name</li>
|
||||||
|
<li><code>msg.data</code> = data string</li>
|
||||||
|
<li><code>msg.type</code> = <code>"notification"</code> or <code>"value"</code></li>
|
||||||
|
<li><code>msg.raw</code> = original line</li>
|
||||||
|
</ul>
|
||||||
|
<p>Attach a Debug node after this node to inspect live updates for the selected controls.</p>
|
||||||
|
</script>
|
||||||
|
|
||||||
142
nodes/xilica-listen.js
Normal file
142
nodes/xilica-listen.js
Normal file
@@ -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);
|
||||||
|
};
|
||||||
|
|
||||||
@@ -133,7 +133,10 @@ RED.nodes.registerType('xilica-subscribe', {
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<label for="node-input-transport">Transport</label>
|
<label for="node-input-transport">Transport</label>
|
||||||
<input type="text" id="node-input-transport" placeholder="TCP">
|
<select id="node-input-transport">
|
||||||
|
<option value="TCP">TCP</option>
|
||||||
|
<option value="UDP">UDP</option>
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -146,5 +149,6 @@ RED.nodes.registerType('xilica-subscribe', {
|
|||||||
<li><strong>Type ==</strong>: the Value field is used as a full control name (e.g. <code>MASTER_GAIN</code>).</li>
|
<li><strong>Type ==</strong>: the Value field is used as a full control name (e.g. <code>MASTER_GAIN</code>).</li>
|
||||||
<li><strong>Type Indexed</strong>: the Value field is a base string (e.g. <code>CH</code>), and the From/To fields define a numeric range (e.g. 1–48 → <code>CH1</code>…<code>CH48</code>).</li>
|
<li><strong>Type Indexed</strong>: the Value field is a base string (e.g. <code>CH</code>), and the From/To fields define a numeric range (e.g. 1–48 → <code>CH1</code>…<code>CH48</code>).</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p>The Transport field controls the subscription transport string, typically <code>TCP</code>. The node generates commands like <code>SUBSCRIBE CH1 "TCP"</code>.</p>
|
<p><strong>Transport</strong> selects the subscription transport (<code>TCP</code> or <code>UDP</code>). The node generates commands like <code>SUBSCRIBE CH1 "TCP"</code> or <code>SUBSCRIBE CH1 "UDP"</code>.</p>
|
||||||
|
<p>The actual Xilica device connection (IP/port/password) is chosen by the <code>xilica-command</code> node that this node is wired into, via its <code>xilica-connection</code> config.</p>
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -11,7 +11,8 @@
|
|||||||
"xilica-connection": "nodes/xilica-connection.js",
|
"xilica-connection": "nodes/xilica-connection.js",
|
||||||
"xilica-command": "nodes/xilica-command.js",
|
"xilica-command": "nodes/xilica-command.js",
|
||||||
"xilica-interval": "nodes/xilica-interval.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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user