Let serialport wiatfor a char before starting output

This commit is contained in:
Dave Conway-Jones 2019-04-04 22:20:21 +01:00
parent 60af3ffa28
commit 0b11d68c02
No known key found for this signature in database
GPG Key ID: 9E7F9C73F5168CD4
4 changed files with 92 additions and 50 deletions

View File

@ -99,7 +99,7 @@
with which it shares the configuration.</p> 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. <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. 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), Once a response is received (with the same logic of a <code>serial in</code> node), or after a timeout occurs, a message is sent 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> 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> <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> <h3>Inputs</h3>
@ -108,7 +108,8 @@
<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. <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). If not present, the default value is 10000 (10s).
</li> </li>
<li>Any other field will be propagated to the output so to allow to match responses with requests.</li> <li><code>msg.count</code> if set this will override the configured number of characters as long as it is less than the number configured.</li>
<li><code>msg.waitfor</code> single character, escape code, or hex code. If set, the node will wait until it matches that character in the stream and then start the output.</li>
</ul> </ul>
<h3>Outputs</h3> <h3>Outputs</h3>
<ul> <ul>
@ -178,59 +179,66 @@
</select> </select>
</td></tr></table> </td></tr></table>
</div> </div>
<br/> <div class="form-row" style="margin-top:16px; margin-bottom:0;">
<div class="form-row">
<label><i class="fa fa-sign-in"></i> <span data-i18n="serial.label.input"></span></label> <label><i class="fa fa-sign-in"></i> <span data-i18n="serial.label.input"></span></label>
</div> </div>
<div class="form-row" style="padding-left:10px;"> <div class="form-row" style="padding-left:18px; margin-bottom:4px;">
<span data-i18n="serial.label.start"></span>
<input type="text" id="node-config-input-waitfor" style="width:50px; height:28px;">
<span data-i18n="serial.label.startor"></span>
</div>
<div class="form-row" style="padding-left:18px; margin-bottom:4px;">
<span data-i18n="serial.label.split"></span> <span data-i18n="serial.label.split"></span>
<select type="text" id="node-config-input-out" style="margin-left:5px; width:200px;"> <select type="text" id="node-config-input-out" style="margin-left:11px; width:200px; height:28px;">
<option value="char" data-i18n="serial.split.character"></option> <option value="char" data-i18n="serial.split.character"></option>
<option value="time" data-i18n="serial.split.timeout"></option> <option value="time" data-i18n="serial.split.timeout"></option>
<option value="interbyte" data-i18n="serial.split.silent"></option> <option value="interbyte" data-i18n="serial.split.silent"></option>
<option value="count" data-i18n="serial.split.lengths"></option> <option value="count" data-i18n="serial.split.lengths"></option>
</select> </select>
<input type="text" id="node-config-input-newline" style="width:50px;"> <input type="text" id="node-config-input-newline" style="width:50px; height:28px;">
<span id="node-units"></span> <span id="node-units"></span>
</div> </div>
<div class="form-row" style="padding-left:10px;"> <div class="form-row" style="padding-left:18px; margin-bottom:4px;">
<span data-i18n="serial.label.deliver"></span> <span data-i18n="serial.label.deliver"></span>
<select type="text" id="node-config-input-bin" style="margin-left:5px; width:150px;"> <select type="text" id="node-config-input-bin" style="margin-left:5px; width:150px; height:28px;">
<option value="false" data-i18n="serial.output.ascii"></option> <option value="false" data-i18n="serial.output.ascii"></option>
<option value="bin" data-i18n="serial.output.binary"></option> <option value="bin" data-i18n="serial.output.binary"></option>
</select> </select>
</div> </div>
<br/>
<div id="node-config-addchar"> <div id="node-config-addchar">
<div class="form-row"> <div class="form-row" style="margin-top:16px; margin-bottom:0;">
<label><i class="fa fa-sign-out"></i> <span data-i18n="serial.label.output"></span></label> <label><i class="fa fa-sign-out"></i> <span data-i18n="serial.label.output"></span></label>
</div> </div>
<div class="form-row"> <div class="form-row" style="padding-left:18px; margin-bottom:4px;">
<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> <label style="width:auto;" for="node-config-input-addchar"><span data-i18n="serial.addsplit"></span></label>
<input type="text" id="node-config-input-addchar" style="width:50px; height:28px;">
</div> </div>
</div> </div>
<br/> <div id="node-config-req">
<div id="node-config-addchar"> <div class="form-row" style="margin-top:16px; margin-bottom:0;">
<div class="form-row">
<label><i class="fa fa-exchange"></i> <span data-i18n="serial.label.request"></span></label> <label><i class="fa fa-exchange"></i> <span data-i18n="serial.label.request"></span></label>
</div> </div>
<div class="form-row" style="padding-left:10px;"> <div class="form-row" style="padding-left:18px; margin-bottom:18px;">
<span data-i18n="serial.label.responsetimeout"></span> <span data-i18n="serial.label.responsetimeout"></span>
<input type="text" id="node-config-input-responsetimeout" style="width:60px;"> <input type="text" id="node-config-input-responsetimeout" style="width:60px; height:28px;">
<span data-i18n="serial.label.ms"></span> <span data-i18n="serial.label.ms"></span>
</div> </div>
</div> </div>
<div class="form-tips" id="tip-waitfor" hidden><span data-i18n="serial.tip.waitfor"></span></div>
<div class="form-tips" id="tip-addchar" hidden><span data-i18n="serial.tip.addchar"></span></div>
<div class="form-tips" id="tip-responsetimeout" hidden><span data-i18n="serial.tip.responsetimeout"></span></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-split"><span data-i18n="serial.tip.split"></span></div>
<div class="form-tips" id="tip-timeout" 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> <div class="form-tips" id="tip-silent" hidden><span data-i18n="serial.tip.silent"></span></div>
<div class="form-tips" id="tip-count" hidden><span data-i18n="serial.tip.count"></span></div>
</script> </script>
<script type="text/x-red" data-help-name="serial-port"> <script type="text/x-red" data-help-name="serial-port">
<p>Provides configuration options for a serial port.</p> <p>Provides configuration options for a serial port.</p>
<p>The search button should return a list of available serial ports to choose from, or you <p>The search button should return a list of available serial ports to choose from, or you
can type in the location if known.</p> can type in the location if known.</p>
<p>The input can be split on a fixed character, after a timeout, or after a fixed number of characters.</p> <p>The node can optionally wait until it matches a pre-defined character.
The data can then be split on a fixed character, after a timeout, or after a fixed number of characters.</p>
<p>If using a character, it can be specified as either the character, the escaped shortcut (e.g. \n), or the hex code (e.g. 0x0d).</p> <p>If using a character, it can be specified as either the character, the escaped shortcut (e.g. \n), or the hex code (e.g. 0x0d).</p>
</script> </script>
@ -244,6 +252,7 @@
databits: {value:8,required:true}, databits: {value:8,required:true},
parity: {value:"none",required:true}, parity: {value:"none",required:true},
stopbits: {value:1,required:true}, stopbits: {value:1,required:true},
waitfor: {value:""},
newline: {value:"\\n"}, newline: {value:"\\n"},
bin: {value:"false"}, bin: {value:"false"},
out: {value:"char"}, out: {value:"char"},
@ -290,41 +299,50 @@
if ($("#node-config-input-out").val() == "char") { if ($("#node-config-input-out").val() == "char") {
if (previous != "char") { $("#node-config-input-newline").val("\\n"); } if (previous != "char") { $("#node-config-input-newline").val("\\n"); }
$("#node-units").text(""); $("#node-units").text("");
$("#node-config-addchar").show(); // $("#node-config-addchar").show();
$("#tip-split").show(); $("#tip-split").show();
$("#tip-timeout").hide(); $("#tip-timeout").hide();
$("#tip-silent").hide(); $("#tip-silent").hide();
$("#tip-count").hide();
} }
else if ($("#node-config-input-out").val() == "time") { else if ($("#node-config-input-out").val() == "time") {
if (previous != "time") { $("#node-config-input-newline").val("0"); } if (previous != "time") { $("#node-config-input-newline").val("0"); }
$("#node-units").text("ms"); $("#node-units").text("ms");
$("#node-config-addchar").hide(); // $("#node-config-addchar").hide();
$("#node-config-input-addchar").val("false"); // $("#node-config-input-addchar").val("false");
$("#tip-split").hide(); $("#tip-split").hide();
$("#tip-timeout").show(); $("#tip-timeout").show();
$("#tip-silent").hide(); $("#tip-silent").hide();
$("#tip-count").hide();
} }
else if ($("#node-config-input-out").val() == "interbyte") { else if ($("#node-config-input-out").val() == "interbyte") {
if (previous != "interbyte") { $("#node-config-input-newline").val("0"); } if (previous != "interbyte") { $("#node-config-input-newline").val("0"); }
$("#node-units").text("ms"); $("#node-units").text("ms");
$("#node-config-addchar").hide(); // $("#node-config-addchar").hide();
$("#node-config-input-addchar").val("false"); // $("#node-config-input-addchar").val("false");
$("#tip-split").hide(); $("#tip-split").hide();
$("#tip-timeout").hide(); $("#tip-timeout").hide();
$("#tip-silent").show(); $("#tip-silent").show();
$("#tip-count").hide();
} }
else { else {
if (previous != "count") { $("#node-config-input-newline").val(""); } if (previous != "count") { $("#node-config-input-newline").val(""); }
$("#node-units").text("chars"); $("#node-units").text("chars");
$("#node-config-addchar").hide(); // $("#node-config-addchar").hide();
$("#node-config-input-addchar").val("false"); // $("#node-config-input-addchar").val("false");
$("#tip-split").hide(); $("#tip-split").hide();
$("#tip-timeout").hide(); $("#tip-timeout").hide();
$("#tip-silent").hide(); $("#tip-silent").hide();
$("#tip-count").show();
} }
}); });
$("#node-config-input-responsetimeout").on('focus', function () { $("#tip-responsetimeout").show(); }); $("#node-config-input-responsetimeout").on('focus', function () { $("#tip-responsetimeout").show(); });
$("#node-config-input-responsetimeout").on('blur', function () { $("#tip-responsetimeout").hide(); });
$("#node-config-input-waitfor").on('focus', function () { $("#tip-waitfor").show(); });
$("#node-config-input-waitfor").on('blur', function () { $("#tip-waitfor").hide(); });
$("#node-config-input-addchar").on('focus', function () { $("#tip-addchar").show(); });
$("#node-config-input-addchar").on('blur', function () { $("#tip-addchar").hide(); });
try { try {
$("#node-config-input-serialport").autocomplete( "destroy" ); $("#node-config-input-serialport").autocomplete( "destroy" );

View File

@ -13,13 +13,14 @@ module.exports = function(RED) {
RED.nodes.createNode(this,n); RED.nodes.createNode(this,n);
this.serialport = n.serialport; this.serialport = n.serialport;
this.newline = n.newline; /* overloaded: split character, timeout, or character count */ this.newline = n.newline; /* overloaded: split character, timeout, or character count */
this.addchar = n.addchar || "false"; this.addchar = n.addchar || "";
this.serialbaud = parseInt(n.serialbaud) || 57600; this.serialbaud = parseInt(n.serialbaud) || 57600;
this.databits = parseInt(n.databits) || 8; this.databits = parseInt(n.databits) || 8;
this.parity = n.parity || "none"; this.parity = n.parity || "none";
this.stopbits = parseInt(n.stopbits) || 1; this.stopbits = parseInt(n.stopbits) || 1;
this.bin = n.bin || "false"; this.bin = n.bin || "false";
this.out = n.out || "char"; this.out = n.out || "char";
this.waitfor = n.waitfor || "";
this.responsetimeout = n.responsetimeout || 10000; this.responsetimeout = n.responsetimeout || 10000;
} }
RED.nodes.registerType("serial-port",SerialPortNode); RED.nodes.registerType("serial-port",SerialPortNode);
@ -117,6 +118,10 @@ module.exports = function(RED) {
// Serial Out // Serial Out
node.on("input",function(msg) { node.on("input",function(msg) {
if (!msg.hasOwnProperty("payload")) { return; } // do nothing unless we have a payload if (!msg.hasOwnProperty("payload")) { return; } // do nothing unless we have a payload
if (msg.hasOwnProperty("count") && (typeof msg.count === "number") && (node.serialConfig.out === "count")) {
node.serialConfig.newline = msg.count;
}
if (msg.hasOwnProperty("flush") && msg.flush === true) { node.port.serial.flush(); }
node.status({fill:"yellow",shape:"dot",text:"serial.status.waiting"}); node.status({fill:"yellow",shape:"dot",text:"serial.status.waiting"});
node.port.enqueue(msg,node,function(err,res) { node.port.enqueue(msg,node,function(err,res) {
if (err) { if (err) {
@ -176,6 +181,7 @@ module.exports = function(RED) {
stopbits = serialConfig.stopbits, stopbits = serialConfig.stopbits,
newline = serialConfig.newline, newline = serialConfig.newline,
spliton = serialConfig.out, spliton = serialConfig.out,
waitfor = serialConfig.waitfor,
binoutput = serialConfig.bin, binoutput = serialConfig.bin,
addchar = serialConfig.addchar, addchar = serialConfig.addchar,
responsetimeout = serialConfig.responsetimeout; responsetimeout = serialConfig.responsetimeout;
@ -191,17 +197,25 @@ module.exports = function(RED) {
// "time" : a msg will be sent after .newline milliseconds // "time" : a msg will be sent after .newline milliseconds
// "count" : a msg will be sent after .newline characters // "count" : a msg will be sent after .newline characters
// if we use "count", we already know how big the buffer will be // if we use "count", we already know how big the buffer will be
var bufSize = spliton == "count" ? Number(newline): bufMaxSize; var bufSize = (spliton === "count") ? Number(newline): bufMaxSize;
var buf = new Buffer(bufSize);
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); }
console.log("WF2",waitfor);
var active = (waitfor === "") ? true : false;
var buf = new Buffer.alloc(bufSize);
var splitc; // split character var splitc; // split character
// Parse the split character onto a 1-char buffer we can immediately compare against // Parse the split character onto a 1-char buffer we can immediately compare against
if (newline.substr(0,2) == "0x") { if (newline.substr(0,2) == "0x") {
splitc = new Buffer([parseInt(newline)]); splitc = new Buffer.alloc([parseInt(newline,16)]);
} }
else { 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 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; }
console.log("AC:",addchar,":");
connections[id] = (function() { connections[id] = (function() {
var obj = { var obj = {
@ -220,10 +234,10 @@ module.exports = function(RED) {
else { else {
payload = payload.toString(); payload = payload.toString();
} }
if ((spliton === "char") && (addchar === true)) { payload += splitc; } if ((spliton === "char") && (addchar !== "")) { payload += addchar; }
} }
else if ((spliton === "char") && (addchar === true) && (splitc !== "")) { else if ((spliton === "char") && (addchar !== "")) {
payload = Buffer.concat([payload,splitc]); payload = Buffer.concat([payload,addchar]);
} }
return payload; return payload;
}, },
@ -287,6 +301,7 @@ module.exports = function(RED) {
//newline = newline.replace("\\n","\n").replace("\\r","\r"); //newline = newline.replace("\\n","\n").replace("\\r","\r");
var olderr = ""; var olderr = "";
var setupSerial = function() { var setupSerial = function() {
obj.serial = new serialp(port,{ obj.serial = new serialp(port,{
baudRate: baud, baudRate: baud,
dataBits: databits, dataBits: databits,
@ -331,23 +346,26 @@ module.exports = function(RED) {
obj.serial.on('data',function(d) { obj.serial.on('data',function(d) {
function emitData(data) { function emitData(data) {
var m = Buffer.from(data); if (active === true) {
var last_sender = null; var m = Buffer.from(data);
if (obj.queue.length) { last_sender = obj.queue[0].sender; } var last_sender = null;
if (binoutput !== "bin") { m = m.toString(); } if (obj.queue.length) { last_sender = obj.queue[0].sender; }
var msgout = obj.dequeue() || {}; if (binoutput !== "bin") { m = m.toString(); }
msgout.payload = m; var msgout = obj.dequeue() || {};
msgout.port = port; msgout.payload = m;
obj._emitter.emit('data', msgout.port = port;
msgout, obj._emitter.emit('data', msgout, last_sender);
last_sender); }
active = (waitfor === "") ? true : false;
} }
for (var z=0; z<d.length; z++) { for (var z=0; z<d.length; z++) {
var c = d[z]; var c = d[z];
if (c === waitfor) { active = true; }
// handle the trivial case first -- single char buffer // handle the trivial case first -- single char buffer
if (!active) { return; }
if ((newline === 0)||(newline === "")) { if ((newline === 0)||(newline === "")) {
emitData(new Buffer([c])); emitData(new Buffer.from([c]));
continue; continue;
} }
@ -376,6 +394,7 @@ module.exports = function(RED) {
} }
// count bytes into a buffer... // count bytes into a buffer...
else if (spliton === "count") { else if (spliton === "count") {
newline = serialConfig.newline;
if ( i >= parseInt(newline)) { if ( i >= parseInt(newline)) {
emitData(buf.slice(0,i)); emitData(buf.slice(0,i));
i=0; i=0;

View File

@ -19,7 +19,9 @@
"responsetimeout": "Default response timeout", "responsetimeout": "Default response timeout",
"ms": "ms", "ms": "ms",
"serial": "serial", "serial": "serial",
"none": "none" "none": "none",
"start": "Optionally wait for a start character of",
"startor": ", then"
}, },
"placeholder": { "placeholder": {
"serialport": "for example: /dev/ttyUSB0/" "serialport": "for example: /dev/ttyUSB0/"
@ -41,12 +43,15 @@
"ascii": "ascii strings", "ascii": "ascii strings",
"binary": "binary buffers" "binary": "binary buffers"
}, },
"addsplit": "add split character to output messages", "addsplit": "Add character to output messages",
"tip": { "tip": {
"responsetimeout": "Tip: The default response timeout can be overridden by setting msg.timeout.", "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.", "split": "Tip: the \"Split on\" character is used to split the input into separate messages. Can accept chars ($), escape codes (\\n), or hex codes (0x03).",
"silent": "Tip: In line-silent mode timeout is restarted upon arrival of any character (i.e. inter-byte timeout).", "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." "timeout": "Tip: In timeout mode timeout starts from arrival of first character.",
"count": "Tip: In count mode msg.count can override the configured count as long as it smaller than the configured value.",
"waitfor": "Tip: Optional. Leave blank to receive all data. Can accept chars ($), escape codes (\\n), or hex codes (0x02)." ,
"addchar": "Tip: This character is added to every message sent out to the serial port. Usually \\r or \\n."
}, },
"onopen": "serial port __port__ opened at __baud__ baud __config__", "onopen": "serial port __port__ opened at __baud__ baud __config__",
"errors": { "errors": {

View File

@ -1,6 +1,6 @@
{ {
"name" : "node-red-node-serialport", "name" : "node-red-node-serialport",
"version" : "0.7.1", "version" : "0.8.0",
"description" : "Node-RED nodes to talk to serial ports", "description" : "Node-RED nodes to talk to serial ports",
"dependencies" : { "dependencies" : {
"serialport" : "^7.1.4" "serialport" : "^7.1.4"