smithtek-node-red-lora
Version:
NodeRED nodes.
971 lines (882 loc) • 29.2 kB
JavaScript
module.exports = function (RED) {
"use strict";
var fs = require("fs");
var path = require("path");
var events = require("events");
var SerialPort = require("serialport").SerialPort;
var bufMaxSize = 32768; // Max serial buffer size, for inputs...
const serialReconnectTime = 15000;
function LoraConfigNode(config) {
RED.nodes.createNode(this, config);
var node = this;
const DEFAULT_TIMEOUT = 5000;
var name = config.name;
var port = config.serialport;
var serialbaud = parseInt(config.serialbaud) || 9600;
var databits = parseInt(config.databits) || 8;
var parity = config.parity || "none";
var stopbits = parseInt(config.stopbits) || 1;
var rf_frequency = parseFloat(config.rf_frequency) || 433;
var rf_factor = parseInt(config.rf_factor) || 2048;
var rf_mode = config.rf_mode || "Normal";
var rf_bw = config.rf_bw || "125k";
var rf_netid = parseInt(config.rf_netid) || 0;
var rf_power = parseInt(config.rf_power) || 7;
var mode = config.mode.toLowerCase() || "write";
var newline = config.newline; /* overloaded: split character, timeout, or character count */
var addchar = config.addchar || "";
var bin = config.bin || "false";
var out = config.out || "char";
var waitfor = config.waitfor || "";
var responsetimeout = config.responsetimeout || 10000;
var serialPort = null;
var timerCloseSerial = null;
var serialConfig = {
serialport: port,
serialbaud: serialbaud || 9600,
databits: databits || 8,
parity: parity || "none",
stopbits: stopbits || 1,
dtr: "none",
rts: "none",
cts: "none",
dsr: "none",
bin: bin || "false",
out: out || "char",
newline: newline || "\n",
/* overloaded: split character, timeout, or character count */
addchar: addchar || "",
waitfor: waitfor || "",
responsetimeout: responsetimeout || 10000,
};
function closePort() {
if (serialPort) {
serialPort.close(function (error) {
if (error) {
node.log('failed to close: ' + error);
} else {
node.log('serial port closed in r/w mode');
}
});
}
serialPort = null;
}
node.log("serialConfig: " + serialConfig);
node.log(JSON.stringify(serialConfig));
node.log("mode: " + mode);
node.serialConfig = serialConfig;
node.port = serialPool.get(node.serialConfig);
if (mode === "service") {
node.log("Start open port");
serialPool.open(node.serialConfig.serialport);
}
node.on("input", function (msg) {
if (msg.hasOwnProperty("mode")) {
if (
msg.mode.toLowerCase() === "read" ||
msg.mode.toLowerCase() === "write" ||
msg.mode.toLowerCase() === "service"
) {
mode = msg.mode.toLowerCase();
node.log("Updated new mode: " + mode);
}
}
if (mode === "service") {
node.log("serialPool is open: " + serialPool.isOpen(node.serialConfig.serialport));
if (!serialPool.isOpen(node.serialConfig.serialport)) {
node.log("Start open port");
serialPool.open(node.serialConfig.serialport);
}
if (msg.hasOwnProperty("baudrate")) {
var baud = parseInt(msg.baudrate);
if (isNaN(baud)) {
node.error("badbaudrate error", msg);
} else {
node.port.update(
{
baudRate: baud,
},
function (err, res) {
if (err) {
var errmsg = err
.toString()
.replace(
"Serialport",
"Serialport " + node.port.serial.path
);
node.error(errmsg, msg);
}
}
);
}
}
if (!msg.hasOwnProperty("payload")) {
return;
} // do nothing unless we have a payload
var payload = node.port.encodePayload(msg.payload);
node.port.write(payload, function (err, res) {
if (err) {
var errmsg = err
.toString()
.replace("Serialport", "Serialport " + node.port.serial.path);
node.error(errmsg, msg);
}
});
} else {
port = config.serialport;
serialbaud = parseInt(config.serialbaud) || 9600;
databits = parseInt(config.databits) || 8;
parity = config.parity || "none";
stopbits = parseInt(config.stopbits) || 1;
rf_frequency = parseFloat(config.rf_frequency) || 433;
rf_factor = parseInt(config.rf_factor) || 2048;
rf_mode = config.rf_mode || "Normal";
rf_bw = config.rf_bw || "125k";
rf_netid = parseInt(config.rf_netid) || 0;
rf_power = parseInt(config.rf_power) || 7;
serialPool.close(node.serialConfig.serialport);
if (msg.hasOwnProperty("payload")) {
if (mode === "write") {
if (msg.payload.hasOwnProperty("config")) {
if (msg.payload.config.hasOwnProperty("rf_frequency")) {
rf_frequency = parseFloat(msg.payload.config.rf_frequency);
}
if (msg.payload.config.hasOwnProperty("rf_factor")) {
rf_factor = parseInt(msg.payload.config.rf_factor);
}
if (msg.payload.config.hasOwnProperty("rf_mode")) {
rf_mode = msg.payload.config.rf_mode.toLowerCase();
}
if (msg.payload.config.hasOwnProperty("rf_bw")) {
rf_bw = msg.payload.config.rf_bw.toLowerCase();
}
if (msg.payload.config.hasOwnProperty("rf_netid")) {
rf_netid = parseInt(msg.payload.config.rf_netid);
}
if (msg.payload.config.hasOwnProperty("rf_power")) {
rf_power = parseInt(msg.payload.config.rf_power);
}
}
}
} else {
msg.payload = {};
}
if (typeof msg.payload != "object") {
msg.payload = {};
}
node.log("serialport " + port);
node.log("serialbaud " + serialbaud);
node.log("databits " + databits);
node.log("parity " + parity);
node.log("stopbits " + stopbits);
node.log("rf_frequency " + rf_frequency);
node.log("rf_factor " + rf_factor);
node.log("rf_mode " + rf_mode);
node.log("rf_bw " + rf_bw);
node.log("rf_netid " + rf_netid);
node.log("rf_power " + rf_power);
node.log("mode " + mode);
serialPort = new SerialPort({
path: port,
baudRate: serialbaud,
dataBits: databits,
parity: parity,
stopBits: stopbits,
autoOpen: false,
}); // this is the openImmediately flag [default is true]
function crc(frame) {
var sum = 0;
for (var i = 0; i < frame.length - 3; i++) {
sum += frame[i];
}
var crc_checksum = sum % 256;
return crc_checksum;
}
function generate_data() {
var data = new Uint8Array(12);
switch (serialbaud) {
case 1200:
data[0] = 1;
break;
case 2400:
data[0] = 2;
break;
case 4800:
data[0] = 3;
break;
case 9600:
data[0] = 4;
break;
case 16200:
data[0] = 5;
break;
case 38400:
data[0] = 6;
break;
case 57600:
data[0] = 7;
break;
default:
data[0] = 4;
}
switch (parity) {
case "none":
data[1] = 0;
break;
case "odd":
data[1] = 1;
break;
case "even":
data[1] = 2;
break;
default:
data[1] = 0;
}
let prequency_value = parseInt(
parseInt(rf_frequency * 1000000) / 61.035
);
data[2] = prequency_value >> 16;
data[3] = prequency_value >> 8;
data[4] = prequency_value;
switch (rf_factor) {
case 128:
data[5] = 7;
break;
case 256:
data[5] = 8;
break;
case 512:
data[5] = 9;
break;
case 1024:
data[5] = 10;
break;
case 2048:
data[5] = 11;
break;
case 4096:
data[5] = 12;
break;
default:
data[5] = 12;
}
rf_mode = rf_mode.toLowerCase();
switch (rf_mode) {
case "normal":
data[6] = 0;
break;
case "saving":
data[6] = 1;
break;
default:
data[6] = 0;
}
rf_bw = rf_bw.toLowerCase();
switch (rf_bw) {
case "62.5k":
data[7] = 6;
break;
case "125k":
data[7] = 7;
break;
case "250k":
data[7] = 8;
break;
case "500k":
data[7] = 9;
break;
default:
data[7] = 7;
}
data[8] = 0;
data[9] = 0;
data[10] = parseInt(rf_netid);
data[11] = parseInt(rf_power);
return data;
}
function generate_frame() {
var data = new Uint8Array(23);
for (var i = 0; i < data.length; i++) {
data[i] = 0;
}
data[0] = 0xaf;
data[1] = 0xaf;
data[2] = 0x00;
data[3] = 0x00;
data[4] = 0xaf;
data[5] = 0x80;
mode = mode.toLowerCase();
if (mode == "read") {
data[6] = 0x02;
} else if (mode == "write") {
data[6] = 0x01;
}
data[7] = 12;
var data_send = generate_data();
if (mode == "write") {
for (var i = 0; i < data_send.length; i++) {
data[i + 8] = data_send[i];
}
}
data[20] = crc(data);
data[21] = 0x0d;
data[22] = 0x0a;
return data;
}
function create_output(buffer) {
msg.payload.status = "Success";
if (mode === "read") {
msg.payload.response = {};
switch (buffer[8]) {
case 1:
// msg.payload.response.baudrate = 1200;
break;
case 2:
// msg.payload.response.baudrate = 2400;
break;
case 3:
// msg.payload.response.baudrate = 4800;
break;
case 4:
// msg.payload.response.baudrate = 9600;
break;
case 5:
// msg.payload.response.baudrate = 19200;
break;
case 6:
// msg.payload.response.baudrate = 38400;
break;
case 7:
// msg.payload.response.baudrate = 57600;
break;
default:
// msg.payload.response.baudrate = 9600;
break;
}
switch (buffer[9]) {
case 0:
// msg.payload.response.parity = "none";
break;
case 1:
// msg.payload.response.parity = "odd";
break;
case 2:
// msg.payload.response.parity = "even";
break;
default:
// msg.payload.response.parity = "none";
break;
}
let prequency_value =
(buffer[10] << 16) + (buffer[11] << 8) + buffer[12];
prequency_value = (prequency_value * 61.035) / 1000000;
msg.payload.response.frequency = prequency_value.toFixed(4);
switch (buffer[13]) {
case 7:
msg.payload.response.spreading_factor = 128;
break;
case 8:
msg.payload.response.spreading_factor = 256;
break;
case 9:
msg.payload.response.spreading_factor = 512;
break;
case 10:
msg.payload.response.spreading_factor = 1024;
break;
case 11:
msg.payload.response.spreading_factor = 2048;
break;
case 12:
msg.payload.response.spreading_factor = 4096;
break;
default:
msg.payload.response.spreading_factor = 0;
break;
}
switch (buffer[14]) {
case 0:
// msg.payload.response.rf_mode = "normal";
break;
case 1:
// msg.payload.response.rf_mode = "saving";
break;
default:
// msg.payload.response.rf_mode = "normal";
break;
}
switch (buffer[15]) {
case 6:
msg.payload.response.bw = "62.5k";
break;
case 7:
msg.payload.response.bw = "125k";
break;
case 8:
msg.payload.response.bw = "250k";
break;
case 9:
msg.payload.response.bw = "500k";
break;
default:
msg.payload.response.bw = 0;
break;
}
// msg.payload.response.id = parseInt(buffer[17] * 256 + buffer[16]);
msg.payload.response.net_id = parseInt(buffer[18]);
msg.payload.response.power_dBm = parseInt(buffer[19]);
} else if (mode === "write") {
}
// msg.payload.databuffer = buffer;
}
function writeAndDrain(data, callback) {
serialPort.flush();
serialPort.write(data, function (error) {
if (error) {
} else {
serialPort.drain(callback);
}
});
}
function myTimerCloseSerial() {
clearInterval(timerCloseSerial);
closePort();
msg.payload.status = "ERROR: timeout reading data";
node.send(msg);
}
serialPort.open(function (error) {
if (error) {
msg.payload.status = "ERROR: failed to open port";
node.send(msg);
} else {
node.log('serial port opened in r/w mode');
var data_send = generate_frame();
writeAndDrain(data_send, function (error) {
if (error) {
msg.payload.status = "ERROR: write data failed" + error;
node.send(msg);
closePort();
} else {
timerCloseSerial = setInterval(
myTimerCloseSerial,
DEFAULT_TIMEOUT
);
}
});
var bufferData = new Uint8Array(23);
var idx = 0;
serialPort.on("data", function (data) {
for (var i = 0; i < data.length; i++) {
bufferData[idx] = data[i];
idx += 1;
}
if (idx < 23) {
} else if (idx == 23) {
create_output(bufferData);
node.send(msg);
if (timerCloseSerial) {
clearInterval(timerCloseSerial);
closePort();
}
}
});
serialPort.on("error", function (error) {
msg.payload.status = "ERROR: Serial port on error: " + error;
node.send(msg);
closePort();
});
}
});
}
});
node.port.on("data", function (msgout, sender) {
node.send(msgout);
});
this.port.on('timeout', function(msgout, sender) {
if (sender !== node) { return; }
msgout.status = "ERR_TIMEOUT";
node.status({fill:"red",shape:"ring",text:"timeout"});
node.send(msgout);
});
node.port.on("ready", function () {
node.status({
fill: "green",
shape: "dot",
text: "connected",
});
});
node.port.on("closed", function () {
node.status({
fill: "red",
shape: "ring",
text: "not-connected",
});
});
node.on("close", function (done) {
if (timerCloseSerial) {
clearInterval(timerCloseSerial);
}
if (node.serialConfig) {
serialPool.close(node.serialConfig.serialport, done);
}
closePort();
done();
});
}
RED.nodes.registerType("loraconfig", LoraConfigNode);
var serialPool = (function () {
var connections = {};
return {
get: function (serialConfig) {
var port = serialConfig.serialport,
baud = serialConfig.serialbaud,
databits = serialConfig.databits,
parity = serialConfig.parity,
stopbits = serialConfig.stopbits,
dtr = serialConfig.dtr,
rts = serialConfig.rts,
cts = serialConfig.cts,
dsr = serialConfig.dsr,
newline = serialConfig.newline,
spliton = serialConfig.out,
waitfor = serialConfig.waitfor,
binoutput = serialConfig.bin,
addchar = serialConfig.addchar,
responsetimeout = serialConfig.responsetimeout;
var id = port;
if (connections[id]) {
return connections[id];
}
var i = 0; // position in the buffer
var bufSize = spliton === "count" ? Number(newline) : bufMaxSize;
waitfor = waitfor
.replace("\\n", "\n")
.replace("\\r", "\r")
.replace("\\t", "\t")
.replace("\\e", "e")
.replace("\\f", "\f")
.replace("\\0", "\0"); // jshint ignore:line
if (waitfor.substr(0, 2) == "0x") {
waitfor = parseInt(waitfor, 16);
}
if (waitfor.length === 1) {
waitfor = waitfor.charCodeAt(0);
}
var active = waitfor === "" ? true : false;
var buf = new Buffer.alloc(bufSize);
var splitc; // split character
if (newline.substr(0, 2) == "0x") {
splitc = new Buffer.from([newline]);
} else {
splitc = new Buffer.from(
newline
.replace("\\n", "\n")
.replace("\\r", "\r")
.replace("\\t", "\t")
.replace("\\e", "e")
.replace("\\f", "\f")
.replace("\\0", "\0")
); // jshint ignore:line
}
if (addchar === true) {
addchar = splitc;
}
addchar = addchar
.replace("\\n", "\n")
.replace("\\r", "\r")
.replace("\\t", "\t")
.replace("\\e", "e")
.replace("\\f", "\f")
.replace("\\0", "\0"); // jshint ignore:line
if (addchar.substr(0, 2) == "0x") {
addchar = new Buffer.from([addchar]);
}
connections[id] = (function () {
var obj = {
_emitter: new events.EventEmitter(),
serial: null,
_closing: false,
tout: null,
queue: [],
on: function (a, b) {
this._emitter.on(a, b);
},
open:function (cb) {
this.serial.open(cb);
},
isOpen: function () {
return this.serial.isOpen;
},
close: function (cb) {
this.serial.close(cb);
},
encodePayload: function (payload) {
if (!Buffer.isBuffer(payload)) {
if (typeof payload === "object") {
payload = JSON.stringify(payload);
} else {
payload = payload.toString();
}
if (addchar !== "") {
payload += addchar;
}
} else if (addchar !== "") {
payload = Buffer.concat([payload, addchar]);
}
return payload;
},
write: function (m, cb) {
this.serial.write(m, cb);
},
update: function (m, cb) {
this.serial.update(m, cb);
},
enqueue: function (msg, sender, cb) {
var payload = this.encodePayload(msg.payload);
var qobj = {
sender: sender,
msg: msg,
payload: payload,
cb: cb,
};
this.queue.push(qobj);
if (this.queue.length === 1) {
this.writehead();
}
},
writehead: function () {
if (!this.queue.length) {
return;
}
var qobj = this.queue[0];
this.write(qobj.payload, qobj.cb);
var msg = qobj.msg;
var timeout = msg.timeout || responsetimeout;
this.tout = setTimeout(function () {
this.tout = null;
var msgout = obj.dequeue() || {};
msgout.port = port;
if (i !== 0) {
var m = buf.slice(0, i);
m = Buffer.from(m);
i = 0;
if (binoutput !== "bin") {
m = m.toString();
}
msgout.payload = m;
}
/* Notify the sender that a timeout occurred */
obj._emitter.emit("timeout", msgout, qobj.sender);
}, timeout);
},
dequeue: function () {
if (!this.queue.length) {
return null;
}
var msg = Object.assign({}, this.queue[0].msg);
msg = Object.assign(msg, {
request_payload: msg.payload,
request_msgid: msg._msgid,
});
delete msg.payload;
if (this.tout) {
clearTimeout(obj.tout);
obj.tout = null;
}
this.queue.shift();
this.writehead();
return msg;
},
};
var olderr = "";
var setupSerial = function () {
obj.serial = new SerialPort(
{
path: port,
baudRate: baud,
dataBits: databits,
parity: parity,
stopBits: stopbits,
autoOpen: false,
},
function (err, results) {
if (err) {
if (err.toString() !== olderr) {
olderr = err.toString();
RED.log.error(
"[serialconfig:" + port + "] " + "port error",
{}
);
}
obj.tout = setTimeout(function () {
setupSerial();
}, serialReconnectTime);
}
}
);
obj.serial.on("error", function (err) {
RED.log.error("[serialconfig:" + port + "] " + "port error", {});
obj._emitter.emit("closed");
if (obj.tout) {
clearTimeout(obj.tout);
}
obj.tout = setTimeout(function () {
setupSerial();
}, serialReconnectTime);
});
obj.serial.on("close", function () {
if (!obj._closing) {
if (olderr !== "unexpected") {
olderr = "unexpected";
RED.log.error(
"[serialconfig:" +
serialConfig.id +
"] " +
"unexpected close",
{}
);
}
obj._emitter.emit("closed");
if (obj.tout) {
clearTimeout(obj.tout);
}
obj.tout = setTimeout(function () {
setupSerial();
}, serialReconnectTime);
}
});
obj.serial.on("open", function () {
olderr = "";
RED.log.info("[serialconfig:" + port + "] " + "on open", {});
var flags = {};
if (dtr != "none") {
flags.dtr = dtr != "low";
}
if (rts != "none") {
flags.rts = rts != "low";
}
if (cts != "none") {
flags.cts = cts != "low";
}
if (dsr != "none") {
flags.dsr = dsr != "low";
}
if (
dtr != "none" ||
rts != "none" ||
cts != "none" ||
dsr != "none"
) {
obj.serial.set(flags);
}
if (obj.tout) {
clearTimeout(obj.tout);
obj.tout = null;
}
obj._emitter.emit("ready");
});
obj.serial.on("data", function (d) {
function emitData(data) {
if (active === true) {
var m = Buffer.from(data);
var last_sender = null;
if (obj.queue.length) {
last_sender = obj.queue[0].sender;
}
if (binoutput !== "bin") {
m = m.toString();
}
var msgout = obj.dequeue() || {};
msgout.payload = m;
msgout.port = port;
obj._emitter.emit("data", msgout, last_sender);
}
active = waitfor === "" ? true : false;
}
for (var z = 0; z < d.length; z++) {
var c = d[z];
if (c === waitfor) {
active = true;
}
if (!active) {
continue;
}
if (newline === 0 || newline === "") {
emitData(new Buffer.from([c]));
continue;
}
buf[i] = c;
i += 1;
if (spliton === "time" || spliton === "interbyte") {
if (
(spliton === "time" && i === 1) ||
(spliton === "interbyte" && z === d.length - 1)
) {
if (obj.tout) {
clearTimeout(obj.tout);
obj.tout = null;
}
obj.tout = setTimeout(function () {
obj.tout = null;
emitData(buf.slice(0, i));
i = 0;
}, newline);
}
}
else if (spliton === "count") {
newline = serialConfig.newline;
if (i >= parseInt(newline)) {
emitData(buf.slice(0, i));
i = 0;
}
}
else if (spliton === "char") {
if (c === splitc[0] || i === bufMaxSize) {
emitData(buf.slice(0, i));
i = 0;
}
}
}
});
};
setupSerial();
return obj;
})();
return connections[id];
},
open: function (port, done) {
if (connections[port]) {
try {
connections[port].open(function () {
RED.log.info("port openned", {});
});
} catch (err) {
RED.log.info("port openn error" + err, {});
}
} else {
}
},
isOpen: function (port, done) {
if (connections[port]) {
return connections[port].isOpen();
} else {
return false;
}
},
close: function (port, done) {
if (connections[port]) {
if (connections[port].tout != null) {
clearTimeout(connections[port].tout);
}
connections[port]._closing = true;
try {
connections[port].close(function () {
RED.log.info("port closed", {});
});
} catch (err) {}
} else {
}
},
};
})();
};