UNPKG

smithtek-node-red-lora

Version:

NodeRED nodes.

971 lines (882 loc) 29.2 kB
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 { } }, }; })(); };