2015-06-13 19:45:38 +02:00
|
|
|
|
|
|
|
module.exports = function(RED) {
|
|
|
|
"use strict";
|
|
|
|
var settings = RED.settings;
|
|
|
|
var events = require("events");
|
|
|
|
var serialp = require("serialport");
|
|
|
|
var bufMaxSize = 32768; // Max serial buffer size, for inputs...
|
|
|
|
|
|
|
|
// TODO: 'serialPool' should be encapsulated in SerialPortNode
|
|
|
|
|
|
|
|
function SerialPortNode(n) {
|
|
|
|
RED.nodes.createNode(this,n);
|
|
|
|
this.serialport = n.serialport;
|
|
|
|
this.newline = n.newline;
|
|
|
|
this.addchar = n.addchar || "false";
|
|
|
|
this.serialbaud = parseInt(n.serialbaud) || 57600;
|
|
|
|
this.databits = parseInt(n.databits) || 8;
|
|
|
|
this.parity = n.parity || "none";
|
|
|
|
this.stopbits = parseInt(n.stopbits) || 1;
|
|
|
|
this.bin = n.bin || "false";
|
|
|
|
this.out = n.out || "char";
|
|
|
|
}
|
|
|
|
RED.nodes.registerType("serial-port",SerialPortNode);
|
|
|
|
|
|
|
|
function SerialOutNode(n) {
|
|
|
|
RED.nodes.createNode(this,n);
|
|
|
|
this.serial = n.serial;
|
|
|
|
this.serialConfig = RED.nodes.getNode(this.serial);
|
|
|
|
|
|
|
|
if (this.serialConfig) {
|
|
|
|
var node = this;
|
|
|
|
node.port = serialPool.get(this.serialConfig.serialport,
|
|
|
|
this.serialConfig.serialbaud,
|
|
|
|
this.serialConfig.databits,
|
|
|
|
this.serialConfig.parity,
|
|
|
|
this.serialConfig.stopbits,
|
|
|
|
this.serialConfig.newline);
|
|
|
|
node.addCh = "";
|
|
|
|
if (node.serialConfig.addchar == "true" || node.serialConfig.addchar === true) {
|
2015-06-16 15:38:36 +02:00
|
|
|
node.addCh = this.serialConfig.newline.replace("\\n","\n").replace("\\r","\r").replace("\\t","\t").replace("\\e","\e").replace("\\f","\f").replace("\\0","\0"); // jshint ignore:line
|
2015-06-13 19:45:38 +02:00
|
|
|
}
|
|
|
|
node.on("input",function(msg) {
|
|
|
|
if (msg.hasOwnProperty("payload")) {
|
|
|
|
var payload = msg.payload;
|
|
|
|
if (!Buffer.isBuffer(payload)) {
|
|
|
|
if (typeof payload === "object") {
|
|
|
|
payload = JSON.stringify(payload);
|
2017-01-29 18:45:44 +01:00
|
|
|
}
|
|
|
|
else {
|
2015-06-13 19:45:38 +02:00
|
|
|
payload = payload.toString();
|
|
|
|
}
|
2018-01-28 18:00:48 +01:00
|
|
|
if (node.out === "char") { payload += node.addCh; }
|
2017-01-29 18:45:44 +01:00
|
|
|
}
|
2018-01-28 18:00:48 +01:00
|
|
|
else if ((node.addCh !== "") && (node.out === "char")) {
|
2017-06-30 20:52:20 +02:00
|
|
|
payload = Buffer.concat([payload,new Buffer(node.addCh)]);
|
2015-06-13 19:45:38 +02:00
|
|
|
}
|
|
|
|
node.port.write(payload,function(err,res) {
|
|
|
|
if (err) {
|
|
|
|
var errmsg = err.toString().replace("Serialport","Serialport "+node.port.serial.path);
|
|
|
|
node.error(errmsg,msg);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
node.port.on('ready', function() {
|
2015-07-07 22:31:28 +02:00
|
|
|
node.status({fill:"green",shape:"dot",text:"node-red:common.status.connected"});
|
2015-06-13 19:45:38 +02:00
|
|
|
});
|
|
|
|
node.port.on('closed', function() {
|
2015-07-07 22:31:28 +02:00
|
|
|
node.status({fill:"red",shape:"ring",text:"node-red:common.status.not-connected"});
|
2015-06-13 19:45:38 +02:00
|
|
|
});
|
2017-01-29 18:45:44 +01:00
|
|
|
}
|
|
|
|
else {
|
2015-06-16 11:36:19 +02:00
|
|
|
this.error(RED._("serial.errors.missing-conf"));
|
2015-06-13 19:45:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
this.on("close", function(done) {
|
|
|
|
if (this.serialConfig) {
|
|
|
|
serialPool.close(this.serialConfig.serialport,done);
|
2017-01-29 18:45:44 +01:00
|
|
|
}
|
|
|
|
else {
|
2015-06-13 19:45:38 +02:00
|
|
|
done();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
RED.nodes.registerType("serial out",SerialOutNode);
|
|
|
|
|
|
|
|
|
|
|
|
function SerialInNode(n) {
|
|
|
|
RED.nodes.createNode(this,n);
|
|
|
|
this.serial = n.serial;
|
|
|
|
this.serialConfig = RED.nodes.getNode(this.serial);
|
|
|
|
|
|
|
|
if (this.serialConfig) {
|
|
|
|
var node = this;
|
|
|
|
node.tout = null;
|
|
|
|
var buf;
|
2017-06-30 20:52:20 +02:00
|
|
|
if (node.serialConfig.out != "count") { buf = new Buffer(bufMaxSize); }
|
|
|
|
else { buf = new Buffer(Number(node.serialConfig.newline)); }
|
2015-06-13 19:45:38 +02:00
|
|
|
var i = 0;
|
2015-07-07 22:31:28 +02:00
|
|
|
node.status({fill:"grey",shape:"dot",text:"node-red:common.status.not-connected"});
|
2015-06-13 19:45:38 +02:00
|
|
|
node.port = serialPool.get(this.serialConfig.serialport,
|
|
|
|
this.serialConfig.serialbaud,
|
|
|
|
this.serialConfig.databits,
|
|
|
|
this.serialConfig.parity,
|
|
|
|
this.serialConfig.stopbits,
|
|
|
|
this.serialConfig.newline
|
|
|
|
);
|
|
|
|
|
|
|
|
var splitc;
|
|
|
|
if (node.serialConfig.newline.substr(0,2) == "0x") {
|
2017-06-30 20:52:20 +02:00
|
|
|
splitc = new Buffer([parseInt(node.serialConfig.newline)]);
|
2017-01-29 18:45:44 +01:00
|
|
|
}
|
|
|
|
else {
|
2017-06-30 20:52:20 +02:00
|
|
|
splitc = new Buffer(node.serialConfig.newline.replace("\\n","\n").replace("\\r","\r").replace("\\t","\t").replace("\\e","\e").replace("\\f","\f").replace("\\0","\0")); // jshint ignore:line
|
2015-06-13 19:45:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
this.port.on('data', function(msg) {
|
|
|
|
// single char buffer
|
|
|
|
if ((node.serialConfig.newline === 0)||(node.serialConfig.newline === "")) {
|
2018-03-01 19:21:22 +01:00
|
|
|
if (node.serialConfig.bin !== "bin") { node.send({"payload": String.fromCharCode(msg), port:node.serialConfig.serialport}); }
|
|
|
|
else { node.send({"payload": new Buffer([msg]), port:node.serialConfig.serialport}); }
|
2015-06-13 19:45:38 +02:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
// do the timer thing
|
2015-06-16 15:38:36 +02:00
|
|
|
if (node.serialConfig.out === "time") {
|
2015-06-13 19:45:38 +02:00
|
|
|
if (node.tout) {
|
|
|
|
i += 1;
|
|
|
|
buf[i] = msg;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
node.tout = setTimeout(function () {
|
|
|
|
node.tout = null;
|
2017-06-30 20:52:20 +02:00
|
|
|
var m = new Buffer(i+1);
|
2015-06-13 19:45:38 +02:00
|
|
|
buf.copy(m,0,0,i+1);
|
|
|
|
if (node.serialConfig.bin !== "bin") { m = m.toString(); }
|
2018-03-01 19:21:22 +01:00
|
|
|
node.send({"payload": m, port:node.serialConfig.serialport});
|
2015-06-13 19:45:38 +02:00
|
|
|
m = null;
|
|
|
|
}, node.serialConfig.newline);
|
|
|
|
i = 0;
|
|
|
|
buf[0] = msg;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// count bytes into a buffer...
|
|
|
|
else if (node.serialConfig.out === "count") {
|
|
|
|
buf[i] = msg;
|
|
|
|
i += 1;
|
|
|
|
if ( i >= parseInt(node.serialConfig.newline)) {
|
2017-06-30 20:52:20 +02:00
|
|
|
var m = new Buffer(i);
|
2015-06-13 19:45:38 +02:00
|
|
|
buf.copy(m,0,0,i);
|
|
|
|
if (node.serialConfig.bin !== "bin") { m = m.toString(); }
|
2018-03-01 19:21:22 +01:00
|
|
|
node.send({"payload":m, port:node.serialConfig.serialport});
|
2015-06-13 19:45:38 +02:00
|
|
|
m = null;
|
|
|
|
i = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// look to match char...
|
|
|
|
else if (node.serialConfig.out === "char") {
|
|
|
|
buf[i] = msg;
|
|
|
|
i += 1;
|
|
|
|
if ((msg === splitc[0]) || (i === bufMaxSize)) {
|
2017-06-30 20:52:20 +02:00
|
|
|
var n = new Buffer(i);
|
2015-06-16 15:38:36 +02:00
|
|
|
buf.copy(n,0,0,i);
|
|
|
|
if (node.serialConfig.bin !== "bin") { n = n.toString(); }
|
2018-03-01 19:21:22 +01:00
|
|
|
node.send({"payload":n, port:node.serialConfig.serialport});
|
2015-06-16 15:38:36 +02:00
|
|
|
n = null;
|
2015-06-13 19:45:38 +02:00
|
|
|
i = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
this.port.on('ready', function() {
|
2015-07-07 22:31:28 +02:00
|
|
|
node.status({fill:"green",shape:"dot",text:"node-red:common.status.connected"});
|
2015-06-13 19:45:38 +02:00
|
|
|
});
|
|
|
|
this.port.on('closed', function() {
|
2015-07-07 22:31:28 +02:00
|
|
|
node.status({fill:"red",shape:"ring",text:"node-red:common.status.not-connected"});
|
2015-06-13 19:45:38 +02:00
|
|
|
});
|
2017-01-29 18:45:44 +01:00
|
|
|
}
|
|
|
|
else {
|
2015-06-16 11:36:19 +02:00
|
|
|
this.error(RED._("serial.errors.missing-conf"));
|
2015-06-13 19:45:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
this.on("close", function(done) {
|
|
|
|
if (this.serialConfig) {
|
|
|
|
serialPool.close(this.serialConfig.serialport,done);
|
2017-01-29 18:45:44 +01:00
|
|
|
}
|
|
|
|
else {
|
2015-06-13 19:45:38 +02:00
|
|
|
done();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
RED.nodes.registerType("serial in",SerialInNode);
|
|
|
|
|
|
|
|
|
2015-06-16 15:38:36 +02:00
|
|
|
var serialPool = (function() {
|
2015-06-13 19:45:38 +02:00
|
|
|
var connections = {};
|
|
|
|
return {
|
|
|
|
get:function(port,baud,databits,parity,stopbits,newline,callback) {
|
|
|
|
var id = port;
|
|
|
|
if (!connections[id]) {
|
2015-06-16 15:38:36 +02:00
|
|
|
connections[id] = (function() {
|
2015-06-13 19:45:38 +02:00
|
|
|
var obj = {
|
|
|
|
_emitter: new events.EventEmitter(),
|
|
|
|
serial: null,
|
|
|
|
_closing: false,
|
|
|
|
tout: null,
|
|
|
|
on: function(a,b) { this._emitter.on(a,b); },
|
|
|
|
close: function(cb) { this.serial.close(cb); },
|
|
|
|
write: function(m,cb) { this.serial.write(m,cb); },
|
|
|
|
}
|
|
|
|
//newline = newline.replace("\\n","\n").replace("\\r","\r");
|
2015-11-02 11:40:53 +01:00
|
|
|
var olderr = "";
|
2015-06-13 19:45:38 +02:00
|
|
|
var setupSerial = function() {
|
2016-10-13 15:12:08 +02:00
|
|
|
obj.serial = new serialp(port,{
|
2017-11-04 18:12:48 +01:00
|
|
|
baudRate: baud,
|
|
|
|
dataBits: databits,
|
2015-06-16 15:38:36 +02:00
|
|
|
parity: parity,
|
2017-11-04 18:12:48 +01:00
|
|
|
stopBits: stopbits,
|
|
|
|
//parser: serialp.parsers.raw,
|
2016-10-13 15:12:08 +02:00
|
|
|
autoOpen: true
|
|
|
|
}, function(err, results) {
|
2015-11-02 11:40:53 +01:00
|
|
|
if (err) {
|
|
|
|
if (err.toString() !== olderr) {
|
|
|
|
olderr = err.toString();
|
|
|
|
RED.log.error(RED._("serial.errors.error",{port:port,error:olderr}));
|
|
|
|
}
|
2015-11-09 16:57:10 +01:00
|
|
|
obj.tout = setTimeout(function() {
|
|
|
|
setupSerial();
|
|
|
|
}, settings.serialReconnectTime);
|
2015-11-02 11:40:53 +01:00
|
|
|
}
|
|
|
|
});
|
2015-06-13 19:45:38 +02:00
|
|
|
obj.serial.on('error', function(err) {
|
2015-06-16 11:36:19 +02:00
|
|
|
RED.log.error(RED._("serial.errors.error",{port:port,error:err.toString()}));
|
2015-06-13 19:45:38 +02:00
|
|
|
obj._emitter.emit('closed');
|
|
|
|
obj.tout = setTimeout(function() {
|
|
|
|
setupSerial();
|
|
|
|
}, settings.serialReconnectTime);
|
|
|
|
});
|
|
|
|
obj.serial.on('close', function() {
|
|
|
|
if (!obj._closing) {
|
2015-06-16 11:36:19 +02:00
|
|
|
RED.log.error(RED._("serial.errors.unexpected-close",{port:port}));
|
2015-06-13 19:45:38 +02:00
|
|
|
obj._emitter.emit('closed');
|
|
|
|
obj.tout = setTimeout(function() {
|
|
|
|
setupSerial();
|
|
|
|
}, settings.serialReconnectTime);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
obj.serial.on('open',function() {
|
2015-11-02 11:40:53 +01:00
|
|
|
olderr = "";
|
2015-06-16 11:36:19 +02:00
|
|
|
RED.log.info(RED._("serial.onopen",{port:port,baud:baud,config: databits+""+parity.charAt(0).toUpperCase()+stopbits}));
|
2015-06-13 19:45:38 +02:00
|
|
|
if (obj.tout) { clearTimeout(obj.tout); }
|
|
|
|
//obj.serial.flush();
|
|
|
|
obj._emitter.emit('ready');
|
|
|
|
});
|
|
|
|
obj.serial.on('data',function(d) {
|
2015-06-16 15:38:36 +02:00
|
|
|
for (var z=0; z<d.length; z++) {
|
|
|
|
obj._emitter.emit('data',d[z]);
|
|
|
|
}
|
2015-06-13 19:45:38 +02:00
|
|
|
});
|
2017-11-04 18:12:48 +01:00
|
|
|
// obj.serial.on("disconnect",function() {
|
|
|
|
// RED.log.error(RED._("serial.errors.disconnected",{port:port}));
|
|
|
|
// });
|
2015-06-13 19:45:38 +02:00
|
|
|
}
|
|
|
|
setupSerial();
|
|
|
|
return obj;
|
2015-06-16 15:38:36 +02:00
|
|
|
}());
|
2015-06-13 19:45:38 +02:00
|
|
|
}
|
|
|
|
return connections[id];
|
|
|
|
},
|
|
|
|
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() {
|
2015-06-16 11:36:19 +02:00
|
|
|
RED.log.info(RED._("serial.errors.closed",{port:port}));
|
2015-06-13 19:45:38 +02:00
|
|
|
done();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
catch(err) { }
|
|
|
|
delete connections[port];
|
2017-01-29 18:45:44 +01:00
|
|
|
}
|
|
|
|
else {
|
2015-06-13 19:45:38 +02:00
|
|
|
done();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-06-16 15:38:36 +02:00
|
|
|
}());
|
2015-06-13 19:45:38 +02:00
|
|
|
|
|
|
|
RED.httpAdmin.get("/serialports", RED.auth.needsPermission('serial.read'), function(req,res) {
|
|
|
|
serialp.list(function (err, ports) {
|
|
|
|
res.json(ports);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|