Serial request (#426)

* serial: simplify serialPool.get

serialPool.get() has a lot of arguments.
Just pass the whole serialConfig object instead.
Also introduce early termination to remove one level of indentation.
(Just set your diff tool to ignore all whitespace changes to see
how very little this patch changes)

* serial: move splitting logic onto serialPool

All SerialIn and SerialOut nodes for a given port
share the same splitting logic as it is indeed
set by the common configuration node.

Move the code from SerialIn into serialPool so that
it can be reused by the serial request node.
Notice how the 'data' event will no longer carry
single bytes, but the whole payload instead.

Also move the output encoding logic into serialPool.

* serial: add serial request node

Add a "serial request" node to handle simple request/response
protocols. This node allows for multiple instances, all
sharing the same underlying device.
Responses coming from the serial line will only be propagated
to the output of the node where the request was originally received
(contrary to the "serial in" nodes which all emit the data
received from the serial line).

Every request received as an input to the node, is transmitted
to the serial line, and a matching response must be received
before the next one can be transmitted.
Any input message received in the meantime is internally enqueued.

The node is essentially a merge of serial in and serial out.
It shares the same configuration with serial in and serial out
for any given port and will not affect the behavior of the
existing nodes.
This means you can use, alongside with the request node:
- as many serial in nodes as you want -- e.g. to "sniff"
- serial out to inject mailicious/tampering data onto the serial
  line, skipping the queueing mechanism

* serial request: provide some visual feedback on the node

add status indication:
- yellow "waiting" when a message is enqueued for sending
- green "OK" after an answer is received
- red "timeout" after a timeout occurs

More sofisticated output would include an indication of the number of messages
enqueued and the actual timeout remaining after sending.

* serial request: make default response timeout configurable

Notice it's a global setting (i.e. stored in the configuration node)
as opposed to per-node, but it can be overridden by setting msg.timeout.

* serial request: cosmetic changes

- added documentation about msg.port
- timeout field made wider so to accommodate default value of 10000ms
- replaced harcoded text with localizable strings for
  "waiting" and "timeout" messages

* serial: cleanup: remove node.tout

this was probably some leftover code from previous implementations.
Now all timeouts are handled within the connection objects.

* serial: cleanup: set obj.tout to null after clearing it

clearing a Timeout without setting it back to null *might* have
adverse effects on later code which would check its null-ity.
Let's just do it.

* serial: cosmetic: add some comments

* serial request: fix "split on timeout" case

In the case of "split on timeout" case, we're reusing the same
.tout for two different purposes:
1) to send a timeout message, in case no answer is received at all [request]
2) to split messages, after receiving the first character [in+request]

So in the case of serial request, checking whether .tout is already
set is no longer a valid condition for 2).
Let's just check whether i === 1, and clear the timeout set up by 1)
if it's already there.

* serial: add "split on silence" behavior

add a fourth logic to split incoming data into messages.
The existing "split on timeout" logic starts the timeout upon
reception of the first character.
This might lead to unwanted behavior if for instance Node-RED is
restarted, as data might accumulate into OS buffers (see #410).
A different logic might be to only emit a message when enough time
has passed, without any new data being received (line silent), a.k.a.
interbyte timeout.
This commit is contained in:
iurly 2018-07-09 12:14:08 +02:00 committed by Dave Conway-Jones
parent b4fca36ab6
commit 27a1038993
3 changed files with 398 additions and 173 deletions

View File

@ -82,6 +82,63 @@
});
</script>
<script type="text/x-red" data-template-name="serial request">
<div class="form-row node-input-serial">
<label for="node-input-serial"><i class="fa fa-random"></i> <span data-i18n="serial.label.serialport"></span></label>
<input type="text" id="node-input-serial">
</div>
<div class="form-row">
<label for="node-inputoutput-name"><i class="fa fa-tag"></i> <span data-i18n="node-red:common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]node-red:common.label.name">
</div>
</script>
<script type="text/x-red" data-help-name="serial request">
<p>Provides a connection to a request/response serial port.</p>
<p>This node behaves as a tightly coupled combination of <code>serial in</code> and <code>serial out</code> nodes,
with which it shares the configuration.</p>
<p>Send the request message in <code>msg.payload</code> as you would do with a <code>serial out</code> node.
The message will be forwarded to the serial port following a strict FIFO (First In, First Out) queue, waiting for a single response before transmitting the next request.
Once a response is received (with the same logic of a <code>serial in</code> node), or after a timeout occurs, a message is produced to the output (see Outputs below),
with <code>msg.payload</code> containing the received response (or missing in case if timeout) and all other fields preserved.</p>
<p>For consistency with the <code>serial in</code> node, <code>msg.port</code> is set to the name of the port selected.</p>
<h3>Inputs</h3>
<ul>
<li>
<code>msg.timeout</code> is the timeout (in ms) after which the incoming message is propagated to the output with <code>msg.status</code> set to <code>"ERR_TIMEOUT"</code> and missing payload.
If not present, the default value is 10000 (10s).
</li>
<li>Any other field will be propagated to the output so to allow to match responses with requests.</li>
</ul>
<h3>Outputs</h3>
<ul>
<li><code>msg.payload</code> is the response. If no response occured, this field is removed.</li>
<li><code>msg.status</code> is <code>"OK"</code> in case a response is received, or <code>"ERR_TIMEOUT"</code> if a timeout occurs.</li>
<li>Any other field coming from the input will be preserved.</li>
</ul>
</script>
<script type="text/javascript">
RED.nodes.registerType('serial request',{
category: 'function',
defaults: {
name: {name:""},
serial: {type:"serial-port",required:true}
},
color:"BurlyWood",
inputs:1,
outputs:1,
icon: "serial.png",
align: "left",
label: function() {
var serialNode = RED.nodes.node(this.serial);
return this.name||(serialNode?serialNode.label().split(":")[0]:this._("serial.label.serial"));
},
labelStyle: function() {
return this.name?"node_label_italic":"";
}
});
</script>
<script type="text/x-red" data-template-name="serial-port">
<div class="form-row">
@ -130,6 +187,7 @@
<select type="text" id="node-config-input-out" style="margin-left:5px; width:200px;">
<option value="char" data-i18n="serial.split.character"></option>
<option value="time" data-i18n="serial.split.timeout"></option>
<option value="interbyte" data-i18n="serial.split.silent"></option>
<option value="count" data-i18n="serial.split.lengths"></option>
</select>
<input type="text" id="node-config-input-newline" style="width:50px;">
@ -151,8 +209,21 @@
<input style="width:30px; margin-left:10px; vertical-align:top;" type="checkbox" id="node-config-input-addchar"><label style="width:auto;" for="node-config-input-addchar"><span data-i18n="serial.addsplit"></span></label>
</div>
</div>
<br/>
<div id="node-config-addchar">
<div class="form-row">
<label><i class="fa fa-exchange"></i> <span data-i18n="serial.label.request"></span></label>
</div>
<div class="form-row" style="padding-left:10px;">
<span data-i18n="serial.label.responsetimeout"></span>
<input type="text" id="node-config-input-responsetimeout" style="width:60px;">
<span data-i18n="serial.label.ms"></span>
</div>
</div>
<div class="form-tips" id="tip-responsetimeout" hidden><span data-i18n="serial.tip.responsetimeout"></span></div>
<div class="form-tips" id="tip-split"><span data-i18n="serial.tip.split"></span></div>
<div class="form-tips" id="tip-bin" hidden><span data-i18n="serial.tip.timeout"></span></div>
<div class="form-tips" id="tip-timeout" hidden><span data-i18n="serial.tip.timeout"></span></div>
<div class="form-tips" id="tip-silent" hidden><span data-i18n="serial.tip.silent"></span></div>
</script>
<script type="text/x-red" data-help-name="serial-port">
@ -176,7 +247,8 @@
newline: {value:"\\n"},
bin: {value:"false"},
out: {value:"char"},
addchar: {value:false}
addchar: {value:false},
responsetimeout: {value: 10000}
},
label: function() {
this.serialbaud = this.serialbaud || 57600;
@ -220,7 +292,8 @@
$("#node-units").text("");
$("#node-config-addchar").show();
$("#tip-split").show();
$("#tip-bin").hide();
$("#tip-timeout").hide();
$("#tip-silent").hide();
}
else if ($("#node-config-input-out").val() == "time") {
if (previous != "time") { $("#node-config-input-newline").val("0"); }
@ -228,7 +301,17 @@
$("#node-config-addchar").hide();
$("#node-config-input-addchar").val("false");
$("#tip-split").hide();
$("#tip-bin").show();
$("#tip-timeout").show();
$("#tip-silent").hide();
}
else if ($("#node-config-input-out").val() == "interbyte") {
if (previous != "interbyte") { $("#node-config-input-newline").val("0"); }
$("#node-units").text("ms");
$("#node-config-addchar").hide();
$("#node-config-input-addchar").val("false");
$("#tip-split").hide();
$("#tip-timeout").hide();
$("#tip-silent").show();
}
else {
if (previous != "count") { $("#node-config-input-newline").val(""); }
@ -236,10 +319,13 @@
$("#node-config-addchar").hide();
$("#node-config-input-addchar").val("false");
$("#tip-split").hide();
$("#tip-bin").hide();
$("#tip-timeout").hide();
$("#tip-silent").hide();
}
});
$("#node-config-input-responsetimeout").on('focus', function () { $("#tip-responsetimeout").show(); });
try {
$("#node-config-input-serialport").autocomplete( "destroy" );
} catch(err) {

View File

@ -8,10 +8,11 @@ module.exports = function(RED) {
// TODO: 'serialPool' should be encapsulated in SerialPortNode
// Configuration Node
function SerialPortNode(n) {
RED.nodes.createNode(this,n);
this.serialport = n.serialport;
this.newline = n.newline;
this.newline = n.newline; /* overloaded: split character, timeout, or character count */
this.addchar = n.addchar || "false";
this.serialbaud = parseInt(n.serialbaud) || 57600;
this.databits = parseInt(n.databits) || 8;
@ -19,9 +20,12 @@ module.exports = function(RED) {
this.stopbits = parseInt(n.stopbits) || 1;
this.bin = n.bin || "false";
this.out = n.out || "char";
this.responsetimeout = n.responsetimeout || 10000;
}
RED.nodes.registerType("serial-port",SerialPortNode);
// receives msgs and sends them to the serial port
function SerialOutNode(n) {
RED.nodes.createNode(this,n);
this.serial = n.serial;
@ -29,42 +33,17 @@ module.exports = function(RED) {
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.newline.substr(0,2) == "0x") {
node.addCh = new Buffer([parseInt(node.serialConfig.newline)]);
}
else {
node.addCh = 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
}
node.port = serialPool.get(this.serialConfig);
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);
}
else {
payload = payload.toString();
}
if ((node.serialConfig.out === "char") && (node.serialConfig.addchar === true)) { payload += node.addCh; }
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 if ((node.serialConfig.out === "char") && (node.serialConfig.addchar === true) && (node.addCh !== "")) {
payload = Buffer.concat([payload,node.addCh]);
}
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() {
node.status({fill:"green",shape:"dot",text:"node-red:common.status.connected"});
@ -89,6 +68,7 @@ module.exports = function(RED) {
RED.nodes.registerType("serial out",SerialOutNode);
// receives data from the serial port and emits msgs
function SerialInNode(n) {
RED.nodes.createNode(this,n);
this.serial = n.serial;
@ -96,81 +76,11 @@ module.exports = function(RED) {
if (this.serialConfig) {
var node = this;
node.tout = null;
var buf;
if (node.serialConfig.out != "count") { buf = new Buffer(bufMaxSize); }
else { buf = new Buffer(Number(node.serialConfig.newline)); }
var i = 0;
node.status({fill:"grey",shape:"dot",text:"node-red:common.status.not-connected"});
node.port = serialPool.get(this.serialConfig.serialport,
this.serialConfig.serialbaud,
this.serialConfig.databits,
this.serialConfig.parity,
this.serialConfig.stopbits,
this.serialConfig.newline
);
node.port = serialPool.get(this.serialConfig);
var splitc;
if (node.serialConfig.newline.substr(0,2) == "0x") {
splitc = new Buffer([parseInt(node.serialConfig.newline)]);
}
else {
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
}
this.port.on('data', function(msg) {
// single char buffer
if ((node.serialConfig.newline === 0)||(node.serialConfig.newline === "")) {
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}); }
}
else {
// do the timer thing
if (node.serialConfig.out === "time") {
if (node.tout) {
i += 1;
buf[i] = msg;
}
else {
node.tout = setTimeout(function () {
node.tout = null;
var m = new Buffer(i+1);
buf.copy(m,0,0,i+1);
if (node.serialConfig.bin !== "bin") { m = m.toString(); }
node.send({"payload": m, port:node.serialConfig.serialport});
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)) {
var m = new Buffer(i);
buf.copy(m,0,0,i);
if (node.serialConfig.bin !== "bin") { m = m.toString(); }
node.send({"payload":m, port:node.serialConfig.serialport});
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)) {
var n = new Buffer(i);
buf.copy(n,0,0,i);
if (node.serialConfig.bin !== "bin") { n = n.toString(); }
node.send({"payload":n, port:node.serialConfig.serialport});
n = null;
i = 0;
}
}
}
this.port.on('data', function(msgout) {
node.send(msgout);
});
this.port.on('ready', function() {
node.status({fill:"green",shape:"dot",text:"node-red:common.status.connected"});
@ -195,79 +105,298 @@ module.exports = function(RED) {
RED.nodes.registerType("serial in",SerialInNode);
/******* REQUEST *********/
function SerialRequestNode(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);
// Serial Out
node.on("input",function(msg) {
if (!msg.hasOwnProperty("payload")) { return; } // do nothing unless we have a payload
node.status({fill:"yellow",shape:"dot",text:"serial.status.waiting"});
node.port.enqueue(msg,node,function(err,res) {
if (err) {
var errmsg = err.toString().replace("Serialport","Serialport "+node.port.serial.path);
node.error(errmsg,msg);
}
});
});
// Serial In
this.port.on('data', function(msgout, sender) {
// serial request will only process incoming data pertaining to its own request (i.e. when it's at the head of the queue)
if (sender !== node) { return; }
node.status({fill:"green",shape:"dot",text:"node-red:common.status.ok"});
msgout.status = "OK";
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:"serial.status.timeout"});
node.send(msgout);
});
// Common part
node.port.on('ready', function() {
node.status({fill:"green",shape:"dot",text:"node-red:common.status.connected"});
});
node.port.on('closed', function() {
node.status({fill:"red",shape:"ring",text:"node-red:common.status.not-connected"});
});
}
else {
this.error(RED._("serial.errors.missing-conf"));
}
this.on("close", function(done) {
if (this.serialConfig) {
serialPool.close(this.serialConfig.serialport,done);
}
else {
done();
}
});
}
RED.nodes.registerType("serial request", SerialRequestNode);
var serialPool = (function() {
var connections = {};
return {
get:function(port,baud,databits,parity,stopbits,newline,callback) {
get:function(serialConfig) {
// make local copy of configuration -- perhaps not needed?
var port = serialConfig.serialport,
baud = serialConfig.serialbaud,
databits = serialConfig.databits,
parity = serialConfig.parity,
stopbits = serialConfig.stopbits,
newline = serialConfig.newline,
spliton = serialConfig.out,
binoutput = serialConfig.bin,
addchar = serialConfig.addchar,
responsetimeout = serialConfig.responsetimeout;
var id = port;
if (!connections[id]) {
connections[id] = (function() {
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");
var olderr = "";
var setupSerial = function() {
obj.serial = new serialp(port,{
baudRate: baud,
dataBits: databits,
parity: parity,
stopBits: stopbits,
//parser: serialp.parsers.raw,
autoOpen: true
}, function(err, results) {
if (err) {
if (err.toString() !== olderr) {
olderr = err.toString();
RED.log.error(RED._("serial.errors.error",{port:port,error:olderr}));
}
obj.tout = setTimeout(function() {
setupSerial();
}, settings.serialReconnectTime);
// just return the connection object if already have one
// key is the port (file path)
if (connections[id]) { return connections[id]; }
// State variables to be used by the on('data') handler
var i = 0; // position in the buffer
// .newline is misleading as its meaning depends on the split input policy:
// "char" : a msg will be sent after a character with value .newline is received
// "time" : a msg will be sent after .newline milliseconds
// "count" : a msg will be sent after .newline characters
// if we use "count", we already know how big the buffer will be
var bufSize = spliton == "count" ? Number(newline): bufMaxSize;
var buf = new Buffer(bufSize);
var splitc; // split character
// Parse the split character onto a 1-char buffer we can immediately compare against
if (newline.substr(0,2) == "0x") {
splitc = new Buffer([parseInt(newline)]);
}
else {
splitc = new Buffer(newline.replace("\\n","\n").replace("\\r","\r").replace("\\t","\t").replace("\\e","\e").replace("\\f","\f").replace("\\0","\0")); // jshint ignore:line
}
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); },
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 ((spliton === "char") && (addchar === true)) { payload += splitc; }
}
else if ((spliton === "char") && (addchar === true) && (splitc !== "")) {
payload = Buffer.concat([payload,splitc]);
}
return payload;
},
write: function(m,cb) { this.serial.write(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 we're enqueing the first message in line,
// we shall send it right away
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 we have some leftover stuff, just send it
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 we are trying to dequeue stuff from an
// empty queue, that's an unsolicited message
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,
});
obj.serial.on('error', function(err) {
RED.log.error(RED._("serial.errors.error",{port:port,error:err.toString()}));
delete msg.payload;
if (this.tout) {
clearTimeout(obj.tout);
obj.tout = null;
}
this.queue.shift();
this.writehead();
return msg;
},
}
//newline = newline.replace("\\n","\n").replace("\\r","\r");
var olderr = "";
var setupSerial = function() {
obj.serial = new serialp(port,{
baudRate: baud,
dataBits: databits,
parity: parity,
stopBits: stopbits,
//parser: serialp.parsers.raw,
autoOpen: true
}, function(err, results) {
if (err) {
if (err.toString() !== olderr) {
olderr = err.toString();
RED.log.error(RED._("serial.errors.error",{port:port,error:olderr}));
}
obj.tout = setTimeout(function() {
setupSerial();
}, settings.serialReconnectTime);
}
});
obj.serial.on('error', function(err) {
RED.log.error(RED._("serial.errors.error",{port:port,error:err.toString()}));
obj._emitter.emit('closed');
obj.tout = setTimeout(function() {
setupSerial();
}, settings.serialReconnectTime);
});
obj.serial.on('close', function() {
if (!obj._closing) {
RED.log.error(RED._("serial.errors.unexpected-close",{port:port}));
obj._emitter.emit('closed');
obj.tout = setTimeout(function() {
setupSerial();
}, settings.serialReconnectTime);
});
obj.serial.on('close', function() {
if (!obj._closing) {
RED.log.error(RED._("serial.errors.unexpected-close",{port:port}));
obj._emitter.emit('closed');
obj.tout = setTimeout(function() {
setupSerial();
}, settings.serialReconnectTime);
}
});
obj.serial.on('open',function() {
olderr = "";
RED.log.info(RED._("serial.onopen",{port:port,baud:baud,config: databits+""+parity.charAt(0).toUpperCase()+stopbits}));
if (obj.tout) { clearTimeout(obj.tout); obj.tout = null; }
//obj.serial.flush();
obj._emitter.emit('ready');
});
obj.serial.on('data',function(d) {
function emitData(data) {
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);
}
for (var z=0; z<d.length; z++) {
var c = d[z];
// handle the trivial case first -- single char buffer
if ((newline === 0)||(newline === "")) {
emitData(new Buffer([c]));
continue;
}
});
obj.serial.on('open',function() {
olderr = "";
RED.log.info(RED._("serial.onopen",{port:port,baud:baud,config: databits+""+parity.charAt(0).toUpperCase()+stopbits}));
if (obj.tout) { clearTimeout(obj.tout); }
//obj.serial.flush();
obj._emitter.emit('ready');
});
obj.serial.on('data',function(d) {
for (var z=0; z<d.length; z++) {
obj._emitter.emit('data',d[z]);
// save incoming data into local buffer
buf[i] = c;
i += 1;
// do the timer thing
if (spliton === "time" || spliton === "interbyte") {
// start the timeout at the first character in case of regular timeout
// restart it at the last character of the this event in case of interbyte timeout
if ((spliton === "time" && i === 1) ||
(spliton === "interbyte" && z === d.length-1)) {
// if we had a response timeout set, clear it:
// we'll emit at least 1 character at some point anyway
if (obj.tout) {
clearTimeout(obj.tout);
obj.tout = null;
}
obj.tout = setTimeout(function () {
obj.tout = null;
emitData(buf.slice(0, i));
i=0;
}, newline);
}
}
});
// obj.serial.on("disconnect",function() {
// RED.log.error(RED._("serial.errors.disconnected",{port:port}));
// });
}
setupSerial();
return obj;
}());
}
// count bytes into a buffer...
else if (spliton === "count") {
if ( i >= parseInt(newline)) {
emitData(buf.slice(0,i));
i=0;
}
}
// look to match char...
else if (spliton === "char") {
if ((c === splitc[0]) || (i === bufMaxSize)) {
emitData(buf.slice(0,i));
i=0;
}
}
}
});
// obj.serial.on("disconnect",function() {
// RED.log.error(RED._("serial.errors.disconnected",{port:port}));
// });
}
setupSerial();
return obj;
}());
return connections[id];
},
close: function(port,done) {

View File

@ -1,5 +1,9 @@
{
"serial": {
"status": {
"waiting": "waiting",
"timeout": "timeout"
},
"label": {
"serialport": "Serial Port",
"settings": "Settings",
@ -11,6 +15,9 @@
"split": "Split input",
"deliver": "and deliver",
"output": "Output",
"request": "Request",
"responsetimeout": "Default response timeout",
"ms": "ms",
"serial": "serial",
"none": "none"
},
@ -27,6 +34,7 @@
"split": {
"character": "on the character",
"timeout": "after a timeout of",
"silent": "after a silence of",
"lengths": "into fixed lengths of"
},
"output": {
@ -35,7 +43,9 @@
},
"addsplit": "add split character to output messages",
"tip": {
"responsetimeout": "Tip: The default response timeout can be overridden by setting msg.timeout.",
"split": "Tip: the \"Split on\" character is used to split the input into separate messages. It can also be added to every message sent out to the serial port.",
"silent": "Tip: In line-silent mode timeout is restarted upon arrival of any character (i.e. inter-byte timeout).",
"timeout": "Tip: In timeout mode timeout starts from arrival of first character."
},
"onopen": "serial port __port__ opened at __baud__ baud __config__",