first contrib
This commit is contained in:
@@ -1,12 +1,267 @@
|
||||
module.exports = function(RED) {
|
||||
const net = require("net");
|
||||
|
||||
module.exports = function (RED) {
|
||||
function XilicaPing(config) {
|
||||
RED.nodes.createNode(this, config);
|
||||
this.on("input", (msg, send, done) => {
|
||||
send = send || function () { this.send(msg); }.bind(this);
|
||||
msg.payload = "xilica-ping node is alive";
|
||||
send(msg);
|
||||
done();
|
||||
if (done) {
|
||||
done();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
RED.nodes.registerType("xilica-ping", XilicaPing);
|
||||
|
||||
function XilicaConnection(config) {
|
||||
RED.nodes.createNode(this, config);
|
||||
this.host = config.host;
|
||||
this.port = parseInt(config.port, 10) || 10007;
|
||||
this.timeout = parseInt(config.timeout, 10) || 5000;
|
||||
this.credentials = this.credentials || {};
|
||||
}
|
||||
|
||||
RED.nodes.registerType("xilica-connection", XilicaConnection, {
|
||||
credentials: {
|
||||
password: { type: "password" },
|
||||
},
|
||||
});
|
||||
|
||||
function parseResponseLines(lines) {
|
||||
const results = [];
|
||||
lines.forEach((line) => {
|
||||
if (!line) {
|
||||
return;
|
||||
}
|
||||
if (line === "OK") {
|
||||
results.push({ type: "ok" });
|
||||
return;
|
||||
}
|
||||
if (line.startsWith("ERROR=")) {
|
||||
const code = parseInt(line.substring(6), 10);
|
||||
results.push({ type: "error", code, raw: line });
|
||||
return;
|
||||
}
|
||||
if (line[0] === "#") {
|
||||
const payload = line.slice(1);
|
||||
const idx = payload.indexOf("=");
|
||||
if (idx !== -1) {
|
||||
const control = payload.slice(0, idx);
|
||||
const data = payload.slice(idx + 1);
|
||||
results.push({
|
||||
type: "notification",
|
||||
control,
|
||||
data,
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
const idx = line.indexOf("=");
|
||||
if (idx !== -1) {
|
||||
const control = line.slice(0, idx);
|
||||
const data = line.slice(idx + 1);
|
||||
results.push({
|
||||
type: "value",
|
||||
control,
|
||||
data,
|
||||
});
|
||||
return;
|
||||
}
|
||||
results.push({ type: "raw", raw: line });
|
||||
});
|
||||
|
||||
if (results.length === 1) {
|
||||
return results[0];
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
function XilicaCommand(config) {
|
||||
RED.nodes.createNode(this, config);
|
||||
const node = this;
|
||||
|
||||
node.connectionConfig = RED.nodes.getNode(config.connection);
|
||||
node.command = config.command || "";
|
||||
node.control = config.control || "";
|
||||
node.data = config.data || "";
|
||||
node.parseResponse = config.parseResponse !== false;
|
||||
|
||||
if (!node.connectionConfig) {
|
||||
node.status({ fill: "red", shape: "ring", text: "no connection" });
|
||||
}
|
||||
|
||||
node.on("input", (msg, send, done) => {
|
||||
send = send || function () { node.send.apply(node, arguments); };
|
||||
done = done || function () {};
|
||||
|
||||
const conn = node.connectionConfig;
|
||||
if (!conn || !conn.host) {
|
||||
node.status({ fill: "red", shape: "ring", text: "no host" });
|
||||
node.error("No Xilica connection configured", msg);
|
||||
done();
|
||||
return;
|
||||
}
|
||||
|
||||
let rawCommand = "";
|
||||
if (typeof msg.payload === "string" && msg.payload.trim()) {
|
||||
rawCommand = msg.payload.trim();
|
||||
}
|
||||
|
||||
let cmd = node.command;
|
||||
let control = node.control;
|
||||
let data = node.data;
|
||||
|
||||
if (msg.command) {
|
||||
cmd = msg.command;
|
||||
}
|
||||
if (msg.control) {
|
||||
control = msg.control;
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(msg, "data")) {
|
||||
data = msg.data;
|
||||
}
|
||||
|
||||
if (!rawCommand) {
|
||||
if (!cmd) {
|
||||
node.status({ fill: "red", shape: "ring", text: "missing command" });
|
||||
node.error("Missing command", msg);
|
||||
done();
|
||||
return;
|
||||
}
|
||||
|
||||
rawCommand = String(cmd).trim();
|
||||
if (control) {
|
||||
rawCommand += " " + String(control).trim();
|
||||
}
|
||||
if (data !== undefined && data !== "") {
|
||||
rawCommand += " " + String(data).trim();
|
||||
}
|
||||
}
|
||||
|
||||
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";
|
||||
const responseLines = [];
|
||||
let finished = false;
|
||||
let idleTimer = null;
|
||||
const idleTimeout = 100;
|
||||
|
||||
function clearIdle() {
|
||||
if (idleTimer) {
|
||||
clearTimeout(idleTimer);
|
||||
idleTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
function scheduleIdleFinish() {
|
||||
clearIdle();
|
||||
idleTimer = setTimeout(() => {
|
||||
finishSuccess();
|
||||
}, idleTimeout);
|
||||
}
|
||||
|
||||
function finishSuccess() {
|
||||
if (finished) {
|
||||
return;
|
||||
}
|
||||
finished = true;
|
||||
clearIdle();
|
||||
client.end();
|
||||
node.status({});
|
||||
|
||||
msg.raw = responseLines.slice();
|
||||
if (node.parseResponse) {
|
||||
msg.payload = parseResponseLines(responseLines);
|
||||
} else {
|
||||
msg.payload = responseLines.join("\n");
|
||||
}
|
||||
|
||||
send(msg);
|
||||
done();
|
||||
}
|
||||
|
||||
function finishError(err) {
|
||||
if (finished) {
|
||||
return;
|
||||
}
|
||||
finished = true;
|
||||
clearIdle();
|
||||
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);
|
||||
}
|
||||
} else {
|
||||
if (line) {
|
||||
responseLines.push(line);
|
||||
}
|
||||
scheduleIdleFinish();
|
||||
}
|
||||
|
||||
idx = buffer.indexOf("\r");
|
||||
}
|
||||
});
|
||||
|
||||
client.on("timeout", () => {
|
||||
finishError(new Error("Timeout waiting for response from Xilica"));
|
||||
});
|
||||
|
||||
client.on("error", (err) => {
|
||||
finishError(err);
|
||||
});
|
||||
|
||||
client.on("end", () => {
|
||||
if (!finished && responseLines.length > 0) {
|
||||
finishSuccess();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
RED.nodes.registerType("xilica-command", XilicaCommand);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user