220 lines
5.2 KiB
JavaScript
220 lines
5.2 KiB
JavaScript
const net = require("net");
|
|
|
|
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 normaliseAction(action) {
|
|
const a = (action || "").toString().toLowerCase();
|
|
if (a === "unsubscribe" || a === "unsub") {
|
|
return "UNSUBSCRIBE";
|
|
}
|
|
return "SUBSCRIBE";
|
|
}
|
|
|
|
function XilicaSubscribe(config) {
|
|
RED.nodes.createNode(this, config);
|
|
const node = this;
|
|
|
|
node.action = config.action || "subscribe";
|
|
node.rules = Array.isArray(config.rules) ? config.rules : [];
|
|
node.transport = config.transport || "TCP";
|
|
node.connectionConfig = RED.nodes.getNode(config.connection);
|
|
|
|
node.on("input", (msg, send, done) => {
|
|
send =
|
|
send ||
|
|
function () {
|
|
node.send.apply(node, arguments);
|
|
};
|
|
done = done || function () {};
|
|
|
|
const action = normaliseAction(msg.action || node.action);
|
|
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 = buildControlNamesFromRules(rules);
|
|
|
|
if (!names.length) {
|
|
node.warn("xilica-subscribe: no rules defined");
|
|
send(msg);
|
|
done();
|
|
return;
|
|
}
|
|
|
|
const lines = names.map(
|
|
(name) => action + " " + name + ' "' + transport + '"',
|
|
);
|
|
|
|
const rawCommand = lines.join("\r");
|
|
|
|
const conn = node.connectionConfig;
|
|
if (!conn || !conn.host) {
|
|
node.status({
|
|
fill: "red",
|
|
shape: "ring",
|
|
text: "no connection",
|
|
});
|
|
node.warn(
|
|
"xilica-subscribe: no connection configured, outputting commands only",
|
|
);
|
|
msg.payload = rawCommand;
|
|
send(msg);
|
|
done();
|
|
return;
|
|
}
|
|
|
|
const host = conn.host;
|
|
const port = conn.port || 10007;
|
|
const timeout = conn.timeout || 5000;
|
|
const password =
|
|
conn.credentials && conn.credentials.password
|
|
? conn.credentials.password
|
|
: null;
|
|
|
|
const client = net.createConnection({ host, port });
|
|
let buffer = "";
|
|
let phase = password ? "login" : "command";
|
|
let finished = false;
|
|
|
|
function finishSuccess() {
|
|
if (finished) {
|
|
return;
|
|
}
|
|
finished = true;
|
|
client.end();
|
|
node.status({});
|
|
|
|
msg.raw = buffer;
|
|
msg.payload = rawCommand;
|
|
|
|
send(msg);
|
|
done();
|
|
}
|
|
|
|
function finishError(err) {
|
|
if (finished) {
|
|
return;
|
|
}
|
|
finished = true;
|
|
client.destroy();
|
|
node.status({ fill: "red", shape: "ring", text: "error" });
|
|
node.error(err, msg);
|
|
done(err);
|
|
}
|
|
|
|
client.setTimeout(timeout);
|
|
|
|
client.on("connect", () => {
|
|
node.status({ fill: "green", shape: "dot", text: "connected" });
|
|
try {
|
|
if (phase === "login") {
|
|
client.write("LOGIN " + password + "\r");
|
|
} else {
|
|
client.write(rawCommand + "\r");
|
|
}
|
|
} catch (err) {
|
|
finishError(err);
|
|
}
|
|
});
|
|
|
|
client.on("data", (dataBuf) => {
|
|
buffer += dataBuf.toString("utf8");
|
|
let idx = buffer.indexOf("\r");
|
|
while (idx !== -1) {
|
|
const line = buffer.slice(0, idx);
|
|
buffer = buffer.slice(idx + 1);
|
|
|
|
if (phase === "login") {
|
|
if (line && line.startsWith("ERROR=")) {
|
|
finishError(new Error("Login failed: " + line));
|
|
return;
|
|
}
|
|
phase = "command";
|
|
try {
|
|
client.write(rawCommand + "\r");
|
|
} catch (err) {
|
|
finishError(err);
|
|
return;
|
|
}
|
|
}
|
|
|
|
idx = buffer.indexOf("\r");
|
|
}
|
|
});
|
|
|
|
client.on("timeout", () => {
|
|
if (phase === "command") {
|
|
finishSuccess();
|
|
} else {
|
|
finishError(new Error("Timeout sending SUBSCRIBE to Xilica"));
|
|
}
|
|
});
|
|
|
|
client.on("error", (err) => {
|
|
finishError(err);
|
|
});
|
|
|
|
client.on("end", () => {
|
|
if (!finished) {
|
|
finishSuccess();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
RED.nodes.registerType("xilica-subscribe", XilicaSubscribe);
|
|
};
|