diff --git a/nodes/xilica-command.html b/nodes/xilica-command.html
new file mode 100644
index 0000000..5a5bd22
--- /dev/null
+++ b/nodes/xilica-command.html
@@ -0,0 +1,66 @@
+
+
+
+
+
+
diff --git a/nodes/xilica-command.js b/nodes/xilica-command.js
new file mode 100644
index 0000000..c67b2cc
--- /dev/null
+++ b/nodes/xilica-command.js
@@ -0,0 +1,243 @@
+const net = require("net");
+
+module.exports = function (RED) {
+ 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);
+};
+
diff --git a/nodes/xilica-connection.html b/nodes/xilica-connection.html
new file mode 100644
index 0000000..dcf4469
--- /dev/null
+++ b/nodes/xilica-connection.html
@@ -0,0 +1,52 @@
+
+
+
+
+
+
diff --git a/nodes/xilica-connection.js b/nodes/xilica-connection.js
new file mode 100644
index 0000000..26fd0bb
--- /dev/null
+++ b/nodes/xilica-connection.js
@@ -0,0 +1,16 @@
+module.exports = function (RED) {
+ 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" },
+ },
+ });
+};
+
diff --git a/nodes/xilica-ping.html b/nodes/xilica-ping.html
index 762dcc3..38aef85 100644
--- a/nodes/xilica-ping.html
+++ b/nodes/xilica-ping.html
@@ -10,53 +10,6 @@ RED.nodes.registerType('xilica-ping', {
icon: "font-awesome/fa-plug",
label: function () { return this.name || "xilica ping"; }
});
-
-RED.nodes.registerType('xilica-connection', {
- category: 'config',
- defaults: {
- name: { value: "" },
- host: { value: "", required: true },
- port: { value: 10007, required: true },
- timeout: { value: 5000 }
- },
- credentials: {
- password: { type: "password" }
- },
- label: function () {
- if (this.name) {
- return this.name;
- }
- if (this.host && this.port) {
- return this.host + ":" + this.port;
- }
- return "Xilica connection";
- }
-});
-
-RED.nodes.registerType('xilica-command', {
- category: 'Xilica',
- color: '#D8E7FF',
- defaults: {
- name: { value: "" },
- connection: { value: "", type: "xilica-connection", required: true },
- command: { value: "" },
- control: { value: "" },
- data: { value: "" },
- parseResponse: { value: true }
- },
- inputs: 1,
- outputs: 1,
- icon: "font-awesome/fa-plug",
- label: function () {
- if (this.name) {
- return this.name;
- }
- if (this.command) {
- return "xilica " + this.command.toLowerCase();
- }
- return "xilica command";
- }
-});
-
-
-
-
-
-
-
-
diff --git a/nodes/xilica-ping.js b/nodes/xilica-ping.js
index 3df5ceb..aed8443 100644
--- a/nodes/xilica-ping.js
+++ b/nodes/xilica-ping.js
@@ -1,10 +1,10 @@
-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);
+ send = send || function () {
+ this.send(msg);
+ }.bind(this);
msg.payload = "xilica-ping node is alive";
send(msg);
if (done) {
@@ -14,254 +14,5 @@ module.exports = function (RED) {
}
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);
};
diff --git a/package.json b/package.json
index b1cd422..42af8b5 100644
--- a/package.json
+++ b/package.json
@@ -7,8 +7,9 @@
"dependencies": {},
"node-red": {
"nodes": {
- "xilica-ping": "nodes/xilica-ping.js"
+ "xilica-ping": "nodes/xilica-ping.js",
+ "xilica-connection": "nodes/xilica-connection.js",
+ "xilica-command": "nodes/xilica-command.js"
}
}
}
-