node-zserial 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +102 -0
- package/locales/en-US/zserial.html +84 -0
- package/locales/en-US/zserial.json +75 -0
- package/package.json +26 -0
- package/zserial.html +101 -0
- package/zserial.js +630 -0
package/README.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
node-red-node-serialport
|
|
2
|
+
========================
|
|
3
|
+
|
|
4
|
+
<a href="http://nodered.org" target="noderedinfo">Node-RED</a> nodes to talk to
|
|
5
|
+
hardware serial ports.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
To install the stable version use the `Menu - Manage palette - Install` option and search for node-red-node-serialport, or run the following command in your Node-RED user directory, typically `~/.node-red`
|
|
10
|
+
|
|
11
|
+
npm i node-red-node-serialport
|
|
12
|
+
|
|
13
|
+
During install there may be multiple messages about optional compilation.
|
|
14
|
+
These may look like failures... as they report as failure to compile errors -
|
|
15
|
+
but often are warnings and the node will continue to install and, assuming nothing else
|
|
16
|
+
failed, you should be able to use it. Occasionally some platforms *will* require
|
|
17
|
+
you to install the full set of tools in order to compile the underlying package.
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
Provides four nodes - one to receive messages, and one to send, a request node which can send then wait for a response, and a control node that allows dynamic control of the ports in use.
|
|
22
|
+
|
|
23
|
+
### Input
|
|
24
|
+
|
|
25
|
+
Reads data from a local serial port.
|
|
26
|
+
|
|
27
|
+
Clicking on the search icon will attempt to autodetect serial ports attached to
|
|
28
|
+
the device, however you many need to manually specify it. COM1, /dev/ttyUSB0, etc
|
|
29
|
+
|
|
30
|
+
It can either
|
|
31
|
+
|
|
32
|
+
- wait for a "split" character (default \n). Also accepts hex notation (0x0a).
|
|
33
|
+
- wait for a timeout in milliseconds from the first character received
|
|
34
|
+
- wait to fill a fixed sized buffer
|
|
35
|
+
|
|
36
|
+
It then outputs `msg.payload` as either a UTF8 ascii string or a binary Buffer object.
|
|
37
|
+
|
|
38
|
+
If no split character is specified, or a timeout or buffer size of 0, then a stream
|
|
39
|
+
of single characters is sent - again either as ascii chars or size 1 binary buffers.
|
|
40
|
+
|
|
41
|
+
### Output
|
|
42
|
+
|
|
43
|
+
Provides a connection to an outbound serial port.
|
|
44
|
+
|
|
45
|
+
Only the `msg.payload` is sent.
|
|
46
|
+
|
|
47
|
+
Optionally the character used to split the input can be appended to every message sent out to the serial port.
|
|
48
|
+
|
|
49
|
+
### Request
|
|
50
|
+
|
|
51
|
+
Provides a connection to a request/response serial port.
|
|
52
|
+
|
|
53
|
+
This node behaves as a tightly coupled combination of serial in and serial out nodes, with which it shares the configuration.
|
|
54
|
+
|
|
55
|
+
Send the request message in `msg.payload` as you would do with a serial out 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 serial in node), or after a timeout occurs, a message is produced on the output, with msg.payload containing the received response (or missing in case if timeout), msg.status containing relevant info, and all other fields preserved.
|
|
56
|
+
|
|
57
|
+
For consistency with the serial in node, msg.port is also set to the name of the port selected.
|
|
58
|
+
|
|
59
|
+
### Control
|
|
60
|
+
|
|
61
|
+
When the node-red starts, the flow(program) picks up the pre-programmed serial port, open it, and starts the communication. But there are some cases the port needs to switch to a different port, stop, and start again. For example, in order to upload a new binary for Arduino, the serial port needs to be stopped relased from the nodered, and start it again after uploading. Or when the FTDI device re-connects after disconnecting for any reason, it may be possible that the port number changes, and the end user of the flow can't change the port.
|
|
62
|
+
|
|
63
|
+
This node provides the ability to:
|
|
64
|
+
|
|
65
|
+
1. change the serial port and its configuration on the run time programatically.
|
|
66
|
+
2. stop the communication and release the serial port.
|
|
67
|
+
3. reopen the port and restart the communications.
|
|
68
|
+
|
|
69
|
+
In order to control the communication, send a **msg.payload** to the control node.
|
|
70
|
+
|
|
71
|
+
{
|
|
72
|
+
"serialport": "/dev/ttyUSB0",
|
|
73
|
+
"serialbaud": 115200,
|
|
74
|
+
"databits": 8,
|
|
75
|
+
"parity": "none",
|
|
76
|
+
"stopbits": 1,
|
|
77
|
+
"enabled": true
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
changes the serial port and the configuration on the fly.
|
|
81
|
+
|
|
82
|
+
The following optional parameters will change the configuration only if they are present.
|
|
83
|
+
Any combination of them can be passed to change/control the serial communication
|
|
84
|
+
|
|
85
|
+
- serialport
|
|
86
|
+
- serialbaud
|
|
87
|
+
- databits
|
|
88
|
+
- parity
|
|
89
|
+
- stopbits
|
|
90
|
+
- dtr
|
|
91
|
+
- rts
|
|
92
|
+
- cts
|
|
93
|
+
- dsr
|
|
94
|
+
- enabled
|
|
95
|
+
|
|
96
|
+
If the `enabled` property is not present, it will default to `true`.
|
|
97
|
+
|
|
98
|
+
`{"enabled":true}` or `{"enabled":false}` will start or stop the communication.
|
|
99
|
+
|
|
100
|
+
If `enabled` is passed along with other parameters, the configuration will be changed and the port will be either started or remain stopped, ready to be started later depending on its value.
|
|
101
|
+
|
|
102
|
+
Any input message will cause the node to output the current port configuration.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
|
|
2
|
+
<script type="text/html" data-help-name="zserial in">
|
|
3
|
+
<p>从本地串口读取数据。</p>
|
|
4
|
+
<h3>输出</h3>
|
|
5
|
+
<dl class="message-properties">
|
|
6
|
+
<dt>payload <span class="property-type">string | buffer</span></dt>
|
|
7
|
+
<dd>通过串口接收到的数据</dd>
|
|
8
|
+
<dt>port <span class="property-type">string</span></dt>
|
|
9
|
+
<dd>串口名称</dd>
|
|
10
|
+
</dl>
|
|
11
|
+
<p>该节点可以
|
|
12
|
+
<ul>
|
|
13
|
+
<li>等待一个“分隔”字符(默认 \n)。也接受转义快捷方式(如 \n)或十六进制表示(0x0d)。</li>
|
|
14
|
+
<li>从接收到第一个字符开始,等待指定的超时时间(毫秒)。</li>
|
|
15
|
+
<li>在接收到最后一个字符后,等待指定的静默时间(毫秒)。</li>
|
|
16
|
+
<li>等待填满固定大小的缓冲区。</li>
|
|
17
|
+
</ul>
|
|
18
|
+
</p>
|
|
19
|
+
<p>然后输出 <code>msg.payload</code>,类型为 UTF-8 ASCII 字符串或二进制缓冲区对象。</p>
|
|
20
|
+
<p><code>msg.port</code> 设置为所选串口的名称。</p>
|
|
21
|
+
<p>如果未指定分隔字符,或超时时间或缓冲区大小为 0,
|
|
22
|
+
则会以单字符流的方式发送数据——同样可以是 ASCII 字符或长度为 1 的二进制缓冲区。</p>
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<script type="text/html" data-help-name="zserial out">
|
|
26
|
+
<p>通过本地串口发送数据。</p>
|
|
27
|
+
<h3>输入</h3>
|
|
28
|
+
<dl class="message-properties">
|
|
29
|
+
<dt>payload <span class="property-type">string | buffer</span></dt>
|
|
30
|
+
<dd>要通过串口发送的数据</dd>
|
|
31
|
+
<dt class="optional">baudrate <span class="property-type">string</span></dt>
|
|
32
|
+
<dd>串口波特率(可选)</dd>
|
|
33
|
+
</dl>
|
|
34
|
+
<p>仅发送 <code>msg.payload</code>。</p>
|
|
35
|
+
<p>可以通过 <code>msg.baudrate</code> 可选地更改波特率。</p>
|
|
36
|
+
<p>可选地,每条发送到串口的消息都可以自动追加用于分隔输入的新行字符。</p>
|
|
37
|
+
<p>可以通过缓冲区对象发送二进制数据。</p>
|
|
38
|
+
</script>
|
|
39
|
+
|
|
40
|
+
<script type="text/html" data-help-name="zserial request">
|
|
41
|
+
<p>提供一个请求/响应串口的连接。</p>
|
|
42
|
+
<p>该节点的行为类似于 <code>serial in</code> 和 <code>serial out</code> 节点的紧密结合,并共享其配置。</p>
|
|
43
|
+
<p>像使用 <code>serial out</code> 节点一样,将请求消息放在 <code>msg.payload</code> 中发送。
|
|
44
|
+
消息将按照严格的 FIFO(先进先出)队列转发到串口,每次等待一个响应后才发送下一个请求。
|
|
45
|
+
一旦收到响应(与 <code>serial in</code> 节点的逻辑相同),或发生超时,
|
|
46
|
+
将向输出发送一条消息(见下方输出),
|
|
47
|
+
<code>msg.payload</code> 包含收到的响应(如超时则无),
|
|
48
|
+
其他属性保持不变。</p>
|
|
49
|
+
<p>为与 <code>serial in</code> 节点一致,<code>msg.port</code> 设置为所选串口的名称。</p>
|
|
50
|
+
<h3>输入</h3>
|
|
51
|
+
<ul>
|
|
52
|
+
<li><code>msg.timeout</code> 为超时时间(毫秒),超时后将带 <code>msg.status</code> 为 <code>"ERR_TIMEOUT"</code> 且无 payload 的消息输出。
|
|
53
|
+
如果未设置,默认值为 10000(10 秒)。</li>
|
|
54
|
+
<li><code>msg.count</code> 如果设置,将覆盖配置的字符数,只要小于配置值。</li>
|
|
55
|
+
<li><code>msg.waitfor</code> 必须为单个字符、转义码或十六进制码。
|
|
56
|
+
如果设置,节点将在数据流中匹配到该字符后开始输出。</li>
|
|
57
|
+
<li>可选地通过 <code>msg.baudrate</code> 更改波特率。</li>
|
|
58
|
+
</ul>
|
|
59
|
+
<h3>输出</h3>
|
|
60
|
+
<ul>
|
|
61
|
+
<li><code>msg.payload</code> 为响应内容。如无响应则该属性被移除。</li>
|
|
62
|
+
<li><code>msg.status</code> 为 <code>"OK"</code>(收到响应)或 <code>"ERR_TIMEOUT"</code>(超时)。</li>
|
|
63
|
+
<li>输入消息的其他属性将被保留。</li>
|
|
64
|
+
</ul>
|
|
65
|
+
</script>
|
|
66
|
+
|
|
67
|
+
<script type="text/html" data-help-name="zserial-port">
|
|
68
|
+
<p>本地串口的配置选项。</p>
|
|
69
|
+
<p>点击搜索按钮可返回可用串口列表,或可直接输入已知串口位置。</p>
|
|
70
|
+
<p>DTR、RTS、CTS 和 DSR 开关可用于永久拉高或拉低相应的流控引脚,例如通过这些引脚为设备供电。</p>
|
|
71
|
+
<p>节点可选地等待匹配预定义字符。</p>
|
|
72
|
+
<p>串口数据接收方式可以
|
|
73
|
+
<ul>
|
|
74
|
+
<li>等待一个“分隔”字符(默认 \n)。也接受转义快捷方式(如 \n)或十六进制表示(0x0d)。</li>
|
|
75
|
+
<li>从接收到第一个字符开始,等待指定的超时时间(毫秒)。</li>
|
|
76
|
+
<li>在接收到最后一个字符后,等待指定的静默时间(毫秒)。</li>
|
|
77
|
+
<li>等待填满固定大小的缓冲区。</li>
|
|
78
|
+
</ul>
|
|
79
|
+
</p>
|
|
80
|
+
<p>然后输出接收到的数据,类型为 UTF-8 ASCII 字符串或二进制缓冲区对象。</p>
|
|
81
|
+
<p>如果未指定分隔字符,或超时时间或缓冲区大小为 0,则会以单字符流的方式发送数据——同样可以是 ASCII 字符或长度为 1 的二进制缓冲区。</p>
|
|
82
|
+
<p>可选地,每条发送到串口的消息都可以自动追加用于分隔输入的新行字符。</p>
|
|
83
|
+
</script>
|
|
84
|
+
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"serial": {
|
|
3
|
+
"status": {
|
|
4
|
+
"waiting": "waiting",
|
|
5
|
+
"timeout": "timeout",
|
|
6
|
+
"stopped": "stopped"
|
|
7
|
+
},
|
|
8
|
+
"label": {
|
|
9
|
+
"serialport": "Serial Port",
|
|
10
|
+
"settings": "Settings",
|
|
11
|
+
"baudrate": "Baud Rate",
|
|
12
|
+
"databits": "Data Bits",
|
|
13
|
+
"parity": "Parity",
|
|
14
|
+
"stopbits": "Stop Bits",
|
|
15
|
+
"input": "Input",
|
|
16
|
+
"split": "Split input",
|
|
17
|
+
"deliver": "and deliver",
|
|
18
|
+
"output": "Output",
|
|
19
|
+
"request": "Request",
|
|
20
|
+
"responsetimeout": "Default response timeout",
|
|
21
|
+
"ms": "ms",
|
|
22
|
+
"serial": "serial",
|
|
23
|
+
"none": "none",
|
|
24
|
+
"start": "Optionally wait for a start character of",
|
|
25
|
+
"startor": ", then"
|
|
26
|
+
},
|
|
27
|
+
"placeholder": {
|
|
28
|
+
"serialport": "for example: /dev/ttyUSB0/"
|
|
29
|
+
},
|
|
30
|
+
"parity": {
|
|
31
|
+
"none": "None",
|
|
32
|
+
"even": "Even",
|
|
33
|
+
"mark": "Mark",
|
|
34
|
+
"odd": "Odd",
|
|
35
|
+
"space": "Space"
|
|
36
|
+
},
|
|
37
|
+
"linestates": {
|
|
38
|
+
"none": "auto",
|
|
39
|
+
"high": "High",
|
|
40
|
+
"low": "Low"
|
|
41
|
+
},
|
|
42
|
+
"split": {
|
|
43
|
+
"character": "on the character",
|
|
44
|
+
"timeout": "after a timeout of",
|
|
45
|
+
"silent": "after a silence of",
|
|
46
|
+
"lengths": "into fixed lengths of"
|
|
47
|
+
},
|
|
48
|
+
"output": {
|
|
49
|
+
"ascii": "ASCII strings",
|
|
50
|
+
"binary": "binary buffers"
|
|
51
|
+
},
|
|
52
|
+
"addsplit": "Add character to output messages",
|
|
53
|
+
"tip": {
|
|
54
|
+
"responsetimeout": "Tip: The default response timeout can be overridden by setting msg.timeout.",
|
|
55
|
+
"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).",
|
|
56
|
+
"silent": "Tip: In line-silent mode timeout is restarted upon arrival of any character (i.e. inter-byte timeout).",
|
|
57
|
+
"timeout": "Tip: In timeout mode timeout starts from arrival of first character.",
|
|
58
|
+
"count": "Tip: In count mode msg.count can override the configured count as long as it smaller than the configured value.",
|
|
59
|
+
"waitfor": "Tip: Optional. Leave blank to receive all data. Can accept chars ($), escape codes (\\n), or hex codes (0x02)." ,
|
|
60
|
+
"addchar": "Tip: This character is added to every message sent out to the serial port. Usually \\r or \\n."
|
|
61
|
+
},
|
|
62
|
+
"onopen": "serial port __port__ opened at __baud__ baud __config__",
|
|
63
|
+
"errors": {
|
|
64
|
+
"missing-conf": "missing serial config",
|
|
65
|
+
"serial-port": "serial port",
|
|
66
|
+
"error": "serial port __port__ error: __error__",
|
|
67
|
+
"unexpected-close": "serial port __port__ closed unexpectedly",
|
|
68
|
+
"disconnected": "serial port __port__ disconnected",
|
|
69
|
+
"closed": "serial port __port__ closed",
|
|
70
|
+
"list": "Failed to list ports. Please enter manually.",
|
|
71
|
+
"badbaudrate": "Baudrate is invalid"
|
|
72
|
+
},
|
|
73
|
+
"stopped": "__port__ stopped"
|
|
74
|
+
}
|
|
75
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "node-zserial",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Node-RED nodes to talk to serial ports",
|
|
5
|
+
"dependencies": {
|
|
6
|
+
"serialport": "^12.0.0"
|
|
7
|
+
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/bensonzhow/node-red-zserialport.git"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"node-red",
|
|
14
|
+
"serial"
|
|
15
|
+
],
|
|
16
|
+
"node-red": {
|
|
17
|
+
"version": ">=3.0.0",
|
|
18
|
+
"nodes": {
|
|
19
|
+
"zserial": "zserial.js"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=16.0.0"
|
|
24
|
+
},
|
|
25
|
+
"contributors": []
|
|
26
|
+
}
|
package/zserial.html
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
<script type="text/html" data-template-name="zserial request">
|
|
2
|
+
<div class="form-row">
|
|
3
|
+
<label for="node-inputoutput-name"><i class="fa fa-tag"></i> <span data-i18n="node-red:common.label.name"></span></label>
|
|
4
|
+
<input type="text" id="node-input-name" data-i18n="[placeholder]node-red:common.label.name">
|
|
5
|
+
</div>
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<script type="text/javascript">
|
|
9
|
+
RED.nodes.registerType('zserial request',{
|
|
10
|
+
category: 'zutils',
|
|
11
|
+
defaults: {
|
|
12
|
+
name: {name:""}
|
|
13
|
+
},
|
|
14
|
+
color:"BurlyWood",
|
|
15
|
+
inputs:1,
|
|
16
|
+
outputs:1,
|
|
17
|
+
icon: "serial.png",
|
|
18
|
+
align: "left",
|
|
19
|
+
label: function() {
|
|
20
|
+
return this.name || "zserial request";
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
<script type="text/html" data-template-name="zserial in">
|
|
28
|
+
<div class="form-row">
|
|
29
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="node-red:common.label.name"></span></label>
|
|
30
|
+
<input type="text" id="node-input-name" data-i18n="[placeholder]node-red:common.label.name">
|
|
31
|
+
</div>
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
<script type="text/javascript">
|
|
35
|
+
RED.nodes.registerType('zserial in',{
|
|
36
|
+
category: 'zutils',
|
|
37
|
+
defaults: {
|
|
38
|
+
name: {name:""}
|
|
39
|
+
},
|
|
40
|
+
color:"BurlyWood",
|
|
41
|
+
inputs:1,
|
|
42
|
+
outputs:1,
|
|
43
|
+
icon: "serial.png",
|
|
44
|
+
label: function() {
|
|
45
|
+
return this.name || "zserial in";
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
</script>
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
<script type="text/html" data-template-name="zserial out">
|
|
54
|
+
<div class="form-row">
|
|
55
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="node-red:common.label.name"></span></label>
|
|
56
|
+
<input type="text" id="node-input-name" data-i18n="[placeholder]node-red:common.label.name">
|
|
57
|
+
</div>
|
|
58
|
+
</script>
|
|
59
|
+
|
|
60
|
+
<script type="text/javascript">
|
|
61
|
+
RED.nodes.registerType('zserial out',{
|
|
62
|
+
category: 'zutils',
|
|
63
|
+
defaults: {
|
|
64
|
+
name: {name:""}
|
|
65
|
+
},
|
|
66
|
+
color:"BurlyWood",
|
|
67
|
+
inputs:1,
|
|
68
|
+
outputs:0,
|
|
69
|
+
icon: "serial.png",
|
|
70
|
+
align: "right",
|
|
71
|
+
label: function() {
|
|
72
|
+
return this.name || "zserial out";
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
</script>
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
<script type="text/html" data-template-name="zserial closeAll">
|
|
80
|
+
<div class="form-row">
|
|
81
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="node-red:common.label.name"></span></label>
|
|
82
|
+
<input type="text" id="node-input-name" data-i18n="[placeholder]node-red:common.label.name">
|
|
83
|
+
</div>
|
|
84
|
+
</script>
|
|
85
|
+
|
|
86
|
+
<script type="text/javascript">
|
|
87
|
+
RED.nodes.registerType('zserial closeAll',{
|
|
88
|
+
category: 'zutils',
|
|
89
|
+
defaults: {
|
|
90
|
+
name: {name:""}
|
|
91
|
+
},
|
|
92
|
+
color:"BurlyWood",
|
|
93
|
+
inputs:1,
|
|
94
|
+
outputs:1,
|
|
95
|
+
icon: "serial.png",
|
|
96
|
+
align: "right",
|
|
97
|
+
label: function() {
|
|
98
|
+
return this.name || "zserial closeAll";
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
</script>
|
package/zserial.js
ADDED
|
@@ -0,0 +1,630 @@
|
|
|
1
|
+
|
|
2
|
+
module.exports = function (RED) {
|
|
3
|
+
/*jshint -W082 */
|
|
4
|
+
"use strict";
|
|
5
|
+
var settings = RED.settings;
|
|
6
|
+
var events = require("events");
|
|
7
|
+
const { SerialPort } = require('serialport');
|
|
8
|
+
var bufMaxSize = 32768; // Max serial buffer size, for inputs...
|
|
9
|
+
const serialReconnectTime = settings.serialReconnectTime || 15000;
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
function SerialCloseAllNode(n) {
|
|
13
|
+
RED.nodes.createNode(this, n);
|
|
14
|
+
var node = this;
|
|
15
|
+
|
|
16
|
+
function onMsg(msg, send, done){
|
|
17
|
+
if (msg.serialConfig) {
|
|
18
|
+
serialPool.close(msg.serialConfig.serialport,(err) => {
|
|
19
|
+
if (err) {
|
|
20
|
+
msg.status="Close Error"
|
|
21
|
+
}else{
|
|
22
|
+
msg.status="Closed Ok"
|
|
23
|
+
}
|
|
24
|
+
node.send(msg);
|
|
25
|
+
done();
|
|
26
|
+
},node);
|
|
27
|
+
}else{
|
|
28
|
+
serialPool.closeAll(() => {
|
|
29
|
+
node.send(msg);
|
|
30
|
+
done();
|
|
31
|
+
},node);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
this.on("input", function (msg, send, done) {
|
|
35
|
+
onMsg(msg, send, done);
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
}
|
|
39
|
+
RED.nodes.registerType("zserial closeAll", SerialCloseAllNode);
|
|
40
|
+
function SerialOutNode(n) {
|
|
41
|
+
RED.nodes.createNode(this, n);
|
|
42
|
+
var node = this;
|
|
43
|
+
function onMsg(msg, send, done) {
|
|
44
|
+
|
|
45
|
+
if (!msg.serialConfig) {
|
|
46
|
+
node.error('msg.serialConfig 没有定义,已创建更新波特率使用 msg.baudrate');
|
|
47
|
+
msg.status = "NO_SERIAL_CONFIG";
|
|
48
|
+
node.send(msg);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
let curPort = serialPool.get(msg.serialConfig);
|
|
52
|
+
|
|
53
|
+
if (msg.hasOwnProperty("baudrate")) {
|
|
54
|
+
var baud = parseInt(msg.baudrate);
|
|
55
|
+
if (isNaN(baud)) {
|
|
56
|
+
node.error(RED._("serial.errors.badbaudrate"), msg);
|
|
57
|
+
} else {
|
|
58
|
+
curPort.update({ baudRate: baud }, function (err, res) {
|
|
59
|
+
if (err) {
|
|
60
|
+
node.error(err);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (!msg.hasOwnProperty("payload")) {
|
|
66
|
+
node.warn(RED._("serial.errors.nopayload"));
|
|
67
|
+
return;
|
|
68
|
+
} // do nothing unless we have a payload
|
|
69
|
+
|
|
70
|
+
setCallback(msg);
|
|
71
|
+
|
|
72
|
+
// 是否队列发送
|
|
73
|
+
if (msg.hasOwnProperty("queueSend") && msg.queueSend === true) {
|
|
74
|
+
curPort.enqueue(msg, node, function (err, res) {
|
|
75
|
+
if (err) {
|
|
76
|
+
node.error(err);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
} else {
|
|
80
|
+
var payload = curPort.encodePayload(msg.payload);
|
|
81
|
+
curPort.write(payload, function (err, res) {
|
|
82
|
+
if (err) {
|
|
83
|
+
node.error(err);
|
|
84
|
+
msg.status = "ERR_WRITE";
|
|
85
|
+
} else {
|
|
86
|
+
msg.status = "OK";
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function setCallback(msg) {
|
|
93
|
+
let curPort = serialPool.get(msg.serialConfig);
|
|
94
|
+
// 确保只绑定一次事件
|
|
95
|
+
if (curPort._isBindOnOutEventInit) {
|
|
96
|
+
// if (done) done();
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
curPort._isBindOnOutEventInit = true;
|
|
100
|
+
|
|
101
|
+
curPort.on('ready', function () {
|
|
102
|
+
node.status({ fill: "green", shape: "dot", text: "node-red:common.status.connected" });
|
|
103
|
+
});
|
|
104
|
+
curPort.on('closed', function () {
|
|
105
|
+
node.status({ fill: "red", shape: "ring", text: "node-red:common.status.not-connected" });
|
|
106
|
+
});
|
|
107
|
+
curPort.on('stopped', function () {
|
|
108
|
+
node.status({ fill: "grey", shape: "ring", text: "serial.status.stopped" });
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
this.on("input", function (msg, send, done) {
|
|
114
|
+
onMsg(msg, send, done);
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
}
|
|
119
|
+
RED.nodes.registerType("zserial out", SerialOutNode);
|
|
120
|
+
|
|
121
|
+
function SerialInNode(n) {
|
|
122
|
+
RED.nodes.createNode(this, n);
|
|
123
|
+
var node = this;
|
|
124
|
+
function onMsg(msg, send, done) {
|
|
125
|
+
setCallback();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
this.on("input", function (msg, send, done) {
|
|
129
|
+
onMsg(msg, send, done);
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
function setCallback() {
|
|
135
|
+
let conns = serialPool.curConnections();
|
|
136
|
+
let connKeys = Object.keys(conns) || [];
|
|
137
|
+
// node.warn(connKeys);
|
|
138
|
+
node.status({ fill: "green", shape: "dot", text: "当前连接数:" + connKeys.length });
|
|
139
|
+
|
|
140
|
+
connKeys.forEach(function (key) {
|
|
141
|
+
let curPort = conns[key];
|
|
142
|
+
if (!curPort) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
// 确保只绑定一次事件
|
|
146
|
+
if (curPort._isBindOnInEventInit) {
|
|
147
|
+
// if (done) done();
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
curPort._isBindOnInEventInit = true;
|
|
151
|
+
curPort.on('data', function (msgout) {
|
|
152
|
+
node.send(msgout);
|
|
153
|
+
});
|
|
154
|
+
curPort.on('ready', function () {
|
|
155
|
+
node.status({ fill: "green", shape: "dot", text: "node-red:common.status.connected" });
|
|
156
|
+
});
|
|
157
|
+
curPort.on('closed', function () {
|
|
158
|
+
node.status({ fill: "red", shape: "ring", text: "node-red:common.status.not-connected" });
|
|
159
|
+
});
|
|
160
|
+
curPort.on('stopped', function () {
|
|
161
|
+
node.status({ fill: "grey", shape: "ring", text: "serial.status.stopped" });
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
let connNumChange = function () {
|
|
166
|
+
// node.warn('connNumChange');
|
|
167
|
+
setCallback();
|
|
168
|
+
};
|
|
169
|
+
try {
|
|
170
|
+
serialPool.on('connNumChange', connNumChange);
|
|
171
|
+
} catch (error) {
|
|
172
|
+
node.error("绑定事件监听器时出错: " + error.toString());
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
this.on("close", function (done) {
|
|
176
|
+
try {
|
|
177
|
+
serialPool.off('connNumChange', connNumChange);
|
|
178
|
+
serialPool.closeAll(done, node);
|
|
179
|
+
} catch (error) {
|
|
180
|
+
// node.warn("移除事件监听器时出错: " + error.toString());
|
|
181
|
+
done();
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
}
|
|
187
|
+
RED.nodes.registerType("zserial in", SerialInNode);
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
// request data and waits for reply
|
|
191
|
+
function SerialRequestNode(n) {
|
|
192
|
+
RED.nodes.createNode(this, n);
|
|
193
|
+
var node = this;
|
|
194
|
+
function onMsg(msg, send, done) {
|
|
195
|
+
|
|
196
|
+
if (!msg.serialConfig) {
|
|
197
|
+
node.error('msg.serialConfig 没有定义,已创建更新波特率使用 msg.baudrate');
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
let curPort = serialPool.get(msg.serialConfig);
|
|
201
|
+
|
|
202
|
+
if (msg.hasOwnProperty("baudrate")) {
|
|
203
|
+
var baud = parseInt(msg.baudrate);
|
|
204
|
+
if (isNaN(baud)) {
|
|
205
|
+
node.error(RED._("serial.errors.badbaudrate"), msg);
|
|
206
|
+
} else {
|
|
207
|
+
curPort.update({ baudRate: baud }, function (err, res) {
|
|
208
|
+
if (err) {
|
|
209
|
+
var errmsg = err.toString().replace("Serialport", "Serialport " + curPort.serial.path);
|
|
210
|
+
node.error(errmsg, msg);
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
if (!msg.hasOwnProperty("payload")) { return; } // do nothing unless we have a payload
|
|
216
|
+
if (msg.hasOwnProperty("count") && (typeof msg.count === "number") && (msg.serialConfig.out === "count")) {
|
|
217
|
+
msg.serialConfig.newline = msg.count;
|
|
218
|
+
}
|
|
219
|
+
if (msg.hasOwnProperty("flush") && msg.flush === true) { curPort.serial.flush(); }
|
|
220
|
+
let statusText = `waiting:${curPort.serial.path}`
|
|
221
|
+
node.status({ fill: "yellow", shape: "dot", text: "serial.status.waiting:" + curPort.serial.path });
|
|
222
|
+
curPort.enqueue(msg, node, function (err, res) {
|
|
223
|
+
if (err) {
|
|
224
|
+
node.error(err)
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
setCallback(msg, done);
|
|
229
|
+
}
|
|
230
|
+
function setCallback(msg, done) {
|
|
231
|
+
let curPort = serialPool.get(msg.serialConfig);
|
|
232
|
+
// 确保只绑定一次事件
|
|
233
|
+
if (curPort._isBindEventInit) {
|
|
234
|
+
// if (done) done();
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
// node.warn("setCallback called for " + curPort.serial.path);
|
|
238
|
+
curPort._isBindEventInit = true;
|
|
239
|
+
|
|
240
|
+
curPort.on('data', function (msgout, sender) {
|
|
241
|
+
// node.warn("对象绑定::"+ node == sender);
|
|
242
|
+
// serial request will only process incoming data pertaining to its own request (i.e. when it's at the head of the queue)
|
|
243
|
+
if (sender !== node) { return; }
|
|
244
|
+
node.status({ fill: "green", shape: "dot", text: "node-red:common.status.ok" });
|
|
245
|
+
msgout.status = "OK";
|
|
246
|
+
node.send(msgout);
|
|
247
|
+
// if (done) done();
|
|
248
|
+
});
|
|
249
|
+
curPort.on('timeout', function (msgout, sender) {
|
|
250
|
+
if (sender !== node) { return; }
|
|
251
|
+
msgout.status = "ERR_TIMEOUT";
|
|
252
|
+
node.status({ fill: "red", shape: "ring", text: "serial.status.timeout" });
|
|
253
|
+
node.send(msgout);
|
|
254
|
+
// if (done) done();
|
|
255
|
+
});
|
|
256
|
+
curPort.on('ready', function () {
|
|
257
|
+
node.status({ fill: "green", shape: "dot", text: "node-red:common.status.connected" });
|
|
258
|
+
});
|
|
259
|
+
curPort.on('closed', function () {
|
|
260
|
+
node.status({ fill: "red", shape: "ring", text: "node-red:common.status.not-connected" });
|
|
261
|
+
});
|
|
262
|
+
curPort.on('stopped', function () {
|
|
263
|
+
node.status({ fill: "grey", shape: "ring", text: "serial.status.stopped" });
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
this.on("input", function (msg, send, done) {
|
|
268
|
+
onMsg(msg, send, done);
|
|
269
|
+
})
|
|
270
|
+
this.on("close", function (done) {
|
|
271
|
+
// node.warn("close serial in node")
|
|
272
|
+
try {
|
|
273
|
+
serialPool.closeAll(done, node);
|
|
274
|
+
} catch (error) {
|
|
275
|
+
done();
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
RED.nodes.registerType("zserial request", SerialRequestNode);
|
|
281
|
+
|
|
282
|
+
var serialPool = (function () {
|
|
283
|
+
var connections = {};
|
|
284
|
+
var _zemitter = new events.EventEmitter();
|
|
285
|
+
|
|
286
|
+
return {
|
|
287
|
+
on: function (event, callback) { _zemitter.on(event, callback); },
|
|
288
|
+
off: function (event, callback) { _zemitter.off(event, callback); },
|
|
289
|
+
curConnections: function () { return connections; },
|
|
290
|
+
get: function (serialConfig, node) {
|
|
291
|
+
// make local copy of configuration -- perhaps not needed?
|
|
292
|
+
var port = serialConfig.serialport,
|
|
293
|
+
baud = serialConfig.serialbaud || 57600,
|
|
294
|
+
databits = serialConfig.databits || 8,
|
|
295
|
+
parity = serialConfig.parity || 'none',
|
|
296
|
+
stopbits = serialConfig.stopbits || 1,
|
|
297
|
+
dtr = serialConfig.dtr || 'none',
|
|
298
|
+
rts = serialConfig.rts || 'none',
|
|
299
|
+
cts = serialConfig.cts || 'none',
|
|
300
|
+
dsr = serialConfig.dsr || 'none',
|
|
301
|
+
newline = "" + (serialConfig.newline || '\\n'),
|
|
302
|
+
spliton = serialConfig.out || 'char',
|
|
303
|
+
waitfor = serialConfig.waitfor || '',
|
|
304
|
+
binoutput = serialConfig.bin || 'false',
|
|
305
|
+
addchar = serialConfig.addchar || '',
|
|
306
|
+
responsetimeout = serialConfig.responsetimeout || 10000;
|
|
307
|
+
var id = port;
|
|
308
|
+
// just return the connection object if already have one
|
|
309
|
+
// key is the port (file path)
|
|
310
|
+
if (connections[id]) { return connections[id]; }
|
|
311
|
+
|
|
312
|
+
// State variables to be used by the on('data') handler
|
|
313
|
+
var i = 0; // position in the buffer
|
|
314
|
+
// .newline is misleading as its meaning depends on the split input policy:
|
|
315
|
+
// "char" : a msg will be sent after a character with value .newline is received
|
|
316
|
+
// "time" : a msg will be sent after .newline milliseconds
|
|
317
|
+
// "count" : a msg will be sent after .newline characters
|
|
318
|
+
// if we use "count", we already know how big the buffer will be
|
|
319
|
+
var bufSize = (spliton === "count") ? Number(newline) : bufMaxSize;
|
|
320
|
+
|
|
321
|
+
waitfor = waitfor.replace("\\n", "\n").replace("\\r", "\r")
|
|
322
|
+
.replace("\\t", "\t").replace("\\e", "\e")
|
|
323
|
+
.replace("\\f", "\f").replace("\\0", "\0"); // jshint ignore:line
|
|
324
|
+
if (waitfor.substr(0, 2) == "0x") { waitfor = parseInt(waitfor, 16); }
|
|
325
|
+
if (waitfor.length === 1) { waitfor = waitfor.charCodeAt(0); }
|
|
326
|
+
var active = (waitfor === "") ? true : false;
|
|
327
|
+
var buf = new Buffer.alloc(bufSize);
|
|
328
|
+
|
|
329
|
+
var splitc; // split character
|
|
330
|
+
// Parse the split character onto a 1-char buffer we can immediately compare against
|
|
331
|
+
if (newline.substr(0, 2) == "0x") {
|
|
332
|
+
splitc = new Buffer.from([newline]);
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
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
|
|
336
|
+
}
|
|
337
|
+
if (addchar === true) { addchar = splitc; }
|
|
338
|
+
addchar = addchar.replace("\\n", "\n").replace("\\r", "\r").replace("\\t", "\t").replace("\\e", "\e").replace("\\f", "\f").replace("\\0", "\0"); // jshint ignore:line
|
|
339
|
+
|
|
340
|
+
if (addchar.substr(0, 2) == "0x") { addchar = new Buffer.from([addchar]); }
|
|
341
|
+
connections[id] = (function () {
|
|
342
|
+
var obj = {
|
|
343
|
+
_emitter: new events.EventEmitter(),
|
|
344
|
+
_isBindEventInit: false,
|
|
345
|
+
serial: null,
|
|
346
|
+
_closing: false,
|
|
347
|
+
tout: null,
|
|
348
|
+
queue: [],
|
|
349
|
+
on: function (a, b) { this._emitter.on(a, b); },
|
|
350
|
+
once: function (a, b) { this._emitter.once(a, b); },
|
|
351
|
+
close: function (cb) {
|
|
352
|
+
this.serial.close(cb);
|
|
353
|
+
},
|
|
354
|
+
encodePayload: function (payload) {
|
|
355
|
+
if (!Buffer.isBuffer(payload)) {
|
|
356
|
+
if (typeof payload === "object") {
|
|
357
|
+
payload = JSON.stringify(payload);
|
|
358
|
+
}
|
|
359
|
+
else {
|
|
360
|
+
payload = payload.toString();
|
|
361
|
+
}
|
|
362
|
+
if (addchar !== "") { payload += addchar; }
|
|
363
|
+
}
|
|
364
|
+
else if (addchar !== "") {
|
|
365
|
+
payload = Buffer.concat([payload, Buffer.from(addchar)]);
|
|
366
|
+
}
|
|
367
|
+
return payload;
|
|
368
|
+
},
|
|
369
|
+
write: function (m, cb) { this.serial.write(m, cb); },
|
|
370
|
+
update: function (m, cb) { this.serial.update(m, cb); },
|
|
371
|
+
enqueue: function (msg, sender, cb) {
|
|
372
|
+
var payload = this.encodePayload(msg.payload);
|
|
373
|
+
var qobj = {
|
|
374
|
+
sender: sender,
|
|
375
|
+
msg: msg,
|
|
376
|
+
payload: payload,
|
|
377
|
+
cb: cb,
|
|
378
|
+
}
|
|
379
|
+
this.queue.push(qobj);
|
|
380
|
+
|
|
381
|
+
// If we're enqueing the first message in line,
|
|
382
|
+
// we shall send it right away
|
|
383
|
+
if (this.queue.length === 1) {
|
|
384
|
+
this.writehead();
|
|
385
|
+
}
|
|
386
|
+
},
|
|
387
|
+
writehead: function () {
|
|
388
|
+
if (!this.queue.length) { return; }
|
|
389
|
+
var qobj = this.queue[0];
|
|
390
|
+
this.write(qobj.payload, qobj.cb);
|
|
391
|
+
var msg = qobj.msg;
|
|
392
|
+
var timeout = msg.timeout || responsetimeout;
|
|
393
|
+
this.tout = setTimeout(function () {
|
|
394
|
+
this.tout = null;
|
|
395
|
+
var msgout = obj.dequeue() || {};
|
|
396
|
+
msgout.port = port;
|
|
397
|
+
// if we have some leftover stuff, just send it
|
|
398
|
+
if (i !== 0) {
|
|
399
|
+
var m = buf.slice(0, i);
|
|
400
|
+
m = Buffer.from(m);
|
|
401
|
+
i = 0;
|
|
402
|
+
if (binoutput !== "bin") { m = m.toString(); }
|
|
403
|
+
msgout.payload = m;
|
|
404
|
+
}
|
|
405
|
+
/* Notify the sender that a timeout occurred */
|
|
406
|
+
obj._emitter.emit('timeout', msgout, qobj.sender);
|
|
407
|
+
}, timeout);
|
|
408
|
+
},
|
|
409
|
+
dequeue: function () {
|
|
410
|
+
// if we are trying to dequeue stuff from an
|
|
411
|
+
// empty queue, that's an unsolicited message
|
|
412
|
+
if (!this.queue.length) { return null; }
|
|
413
|
+
var msg = Object.assign({}, this.queue[0].msg);
|
|
414
|
+
msg = Object.assign(msg, {
|
|
415
|
+
request_payload: msg.payload,
|
|
416
|
+
request_msgid: msg._msgid,
|
|
417
|
+
});
|
|
418
|
+
delete msg.payload;
|
|
419
|
+
if (this.tout) {
|
|
420
|
+
clearTimeout(obj.tout);
|
|
421
|
+
obj.tout = null;
|
|
422
|
+
}
|
|
423
|
+
this.queue.shift();
|
|
424
|
+
this.writehead();
|
|
425
|
+
return msg;
|
|
426
|
+
},
|
|
427
|
+
}
|
|
428
|
+
//newline = newline.replace("\\n","\n").replace("\\r","\r");
|
|
429
|
+
obj._emitter.setMaxListeners(50);
|
|
430
|
+
var olderr = "";
|
|
431
|
+
var setupSerial = function () {
|
|
432
|
+
obj.serial = new SerialPort({
|
|
433
|
+
path: port,
|
|
434
|
+
baudRate: baud,
|
|
435
|
+
dataBits: databits,
|
|
436
|
+
parity: parity,
|
|
437
|
+
stopBits: stopbits,
|
|
438
|
+
//parser: serialp.parsers.raw,
|
|
439
|
+
autoOpen: true
|
|
440
|
+
}, function (err, results) {
|
|
441
|
+
if (err) {
|
|
442
|
+
if (err.toString() !== olderr) {
|
|
443
|
+
olderr = err.toString();
|
|
444
|
+
RED.log.error("Err1:[serialconfig:" + serialConfig.id + "] " + RED._("serial.errors.error", { port: port, error: olderr }), {});
|
|
445
|
+
}
|
|
446
|
+
obj.tout = setTimeout(function () {
|
|
447
|
+
setupSerial();
|
|
448
|
+
}, serialReconnectTime);
|
|
449
|
+
}
|
|
450
|
+
// RED.log.error("init:::::::::::::::::connNumChange");
|
|
451
|
+
_zemitter.emit('connNumChange', Object.keys(connections).length)
|
|
452
|
+
});
|
|
453
|
+
obj.serial.on('error', function (err) {
|
|
454
|
+
RED.log.error("Err2:[serialconfig:" + serialConfig.id + "] " + RED._("serial.errors.error", { port: port, error: err.toString() }), {});
|
|
455
|
+
obj._emitter.emit('closed');
|
|
456
|
+
if (obj.tout) { clearTimeout(obj.tout); }
|
|
457
|
+
obj.tout = setTimeout(function () {
|
|
458
|
+
setupSerial();
|
|
459
|
+
}, serialReconnectTime);
|
|
460
|
+
});
|
|
461
|
+
obj.serial.on('close', function () {
|
|
462
|
+
if (!obj._closing) {
|
|
463
|
+
if (olderr !== "unexpected") {
|
|
464
|
+
olderr = "unexpected";
|
|
465
|
+
RED.log.error("Err3:[serialconfig:" + serialConfig.id + "] " + RED._("serial.errors.unexpected-close", { port: port }), {});
|
|
466
|
+
}
|
|
467
|
+
obj._emitter.emit('closed');
|
|
468
|
+
if (obj.tout) { clearTimeout(obj.tout); }
|
|
469
|
+
obj.tout = setTimeout(function () {
|
|
470
|
+
setupSerial();
|
|
471
|
+
}, serialReconnectTime);
|
|
472
|
+
}
|
|
473
|
+
else {
|
|
474
|
+
obj._emitter.emit('stopped');
|
|
475
|
+
}
|
|
476
|
+
});
|
|
477
|
+
obj.serial.on('open', function () {
|
|
478
|
+
olderr = "";
|
|
479
|
+
RED.log.info("[serialconfig:" + serialConfig.id + "] " + RED._("serial.onopen", { port: port, baud: baud, config: databits + "" + parity.charAt(0).toUpperCase() + stopbits }));
|
|
480
|
+
// Set flow control pins if necessary. Must be set all in same command.
|
|
481
|
+
var flags = {};
|
|
482
|
+
if (dtr != "none") { flags.dtr = (dtr != "low"); }
|
|
483
|
+
if (rts != "none") { flags.rts = (rts != "low"); }
|
|
484
|
+
if (cts != "none") { flags.cts = (cts != "low"); }
|
|
485
|
+
if (dsr != "none") { flags.dsr = (dsr != "low"); }
|
|
486
|
+
if (dtr != "none" || rts != "none" || cts != "none" || dsr != "none") { obj.serial.set(flags); }
|
|
487
|
+
if (obj.tout) { clearTimeout(obj.tout); obj.tout = null; }
|
|
488
|
+
//obj.serial.flush();
|
|
489
|
+
obj._emitter.emit('ready');
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
obj.serial.on('data', function (d) {
|
|
493
|
+
RED.log.info("data::::" + d);
|
|
494
|
+
function emitData(data) {
|
|
495
|
+
|
|
496
|
+
if (active === true) {
|
|
497
|
+
var m = Buffer.from(data);
|
|
498
|
+
var last_sender = null;
|
|
499
|
+
if (obj.queue.length) { last_sender = obj.queue[0].sender; }
|
|
500
|
+
if (binoutput !== "bin") { m = m.toString(); }
|
|
501
|
+
var msgout = obj.dequeue() || {};
|
|
502
|
+
msgout.payload = m;
|
|
503
|
+
msgout.port = port;
|
|
504
|
+
obj._emitter.emit('data', msgout, last_sender);
|
|
505
|
+
}
|
|
506
|
+
active = (waitfor === "") ? true : false;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
for (var z = 0; z < d.length; z++) {
|
|
510
|
+
var c = d[z];
|
|
511
|
+
if (c === waitfor) { active = true; }
|
|
512
|
+
if (!active) { continue; }
|
|
513
|
+
// handle the trivial case first -- single char buffer
|
|
514
|
+
if ((newline === 0) || (newline === "")) {
|
|
515
|
+
emitData(new Buffer.from([c]));
|
|
516
|
+
continue;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// save incoming data into local buffer
|
|
520
|
+
buf[i] = c;
|
|
521
|
+
i += 1;
|
|
522
|
+
|
|
523
|
+
// do the timer thing
|
|
524
|
+
if (spliton === "time" || spliton === "interbyte") {
|
|
525
|
+
// start the timeout at the first character in case of regular timeout
|
|
526
|
+
// restart it at the last character of the this event in case of interbyte timeout
|
|
527
|
+
if ((spliton === "time" && i === 1) ||
|
|
528
|
+
(spliton === "interbyte" && z === d.length - 1)) {
|
|
529
|
+
// if we had a response timeout set, clear it:
|
|
530
|
+
// we'll emit at least 1 character at some point anyway
|
|
531
|
+
if (obj.tout) {
|
|
532
|
+
clearTimeout(obj.tout);
|
|
533
|
+
obj.tout = null;
|
|
534
|
+
}
|
|
535
|
+
obj.tout = setTimeout(function () {
|
|
536
|
+
obj.tout = null;
|
|
537
|
+
emitData(buf.slice(0, i));
|
|
538
|
+
i = 0;
|
|
539
|
+
}, newline);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
// count bytes into a buffer...
|
|
543
|
+
else if (spliton === "count") {
|
|
544
|
+
newline = serialConfig.newline;
|
|
545
|
+
if (i >= parseInt(newline)) {
|
|
546
|
+
emitData(buf.slice(0, i));
|
|
547
|
+
i = 0;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
// look to match char...
|
|
551
|
+
else if (spliton === "char") {
|
|
552
|
+
if ((c === splitc[0]) || (i === bufMaxSize)) {
|
|
553
|
+
emitData(buf.slice(0, i));
|
|
554
|
+
i = 0;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
});
|
|
559
|
+
// obj.serial.on("disconnect",function() {
|
|
560
|
+
// RED.log.error(RED._("serial.errors.disconnected",{port:port}));
|
|
561
|
+
// });
|
|
562
|
+
}
|
|
563
|
+
setupSerial();
|
|
564
|
+
return obj;
|
|
565
|
+
}());
|
|
566
|
+
return connections[id];
|
|
567
|
+
},
|
|
568
|
+
close: function (port, done, node) {
|
|
569
|
+
if (connections[port]) {
|
|
570
|
+
if (connections[port].tout != null) {
|
|
571
|
+
clearTimeout(connections[port].tout);
|
|
572
|
+
}
|
|
573
|
+
connections[port]._closing = true;
|
|
574
|
+
connections[port]._isBindOnOutEventInit = false;
|
|
575
|
+
connections[port]._isBindOnInEventInit = false;
|
|
576
|
+
connections[port]._isBindEventInit = false;
|
|
577
|
+
serialPool.zlog(node, "开始执行关闭");
|
|
578
|
+
try {
|
|
579
|
+
connections[port].close(function () {
|
|
580
|
+
serialPool.zlog(node, "关闭成功");
|
|
581
|
+
RED.log.info(RED._("serial.errors.closed", { port: port }), {});
|
|
582
|
+
done();
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
catch (err) {
|
|
586
|
+
done(err);
|
|
587
|
+
}
|
|
588
|
+
delete connections[port];
|
|
589
|
+
// RED.log.error("close:::::::::::::::::connNumChange");
|
|
590
|
+
_zemitter.emit('connNumChange', Object.keys(connections).length)
|
|
591
|
+
}
|
|
592
|
+
else {
|
|
593
|
+
done();
|
|
594
|
+
}
|
|
595
|
+
},
|
|
596
|
+
closeAll(done, node) {
|
|
597
|
+
serialPool.zlog(node, "开始关闭所有串口连接");
|
|
598
|
+
serialPool.zlog(node, connections);
|
|
599
|
+
var keys = Object.keys(connections), total = keys.length;
|
|
600
|
+
serialPool.zlog(node, "需要关闭的连接数: " + total);
|
|
601
|
+
|
|
602
|
+
// 如果没有连接需要关闭,立即完成
|
|
603
|
+
if (keys.length === 0) {
|
|
604
|
+
serialPool.zlog(node, "没有串口连接需要关闭");
|
|
605
|
+
done();
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
try {
|
|
609
|
+
for (var i = 0; i < keys.length; i++) {
|
|
610
|
+
serialPool.close(keys[i], function (err) {
|
|
611
|
+
total--;
|
|
612
|
+
if (total <= 0) {
|
|
613
|
+
done();
|
|
614
|
+
serialPool.zlog(node, "全部关闭完成");
|
|
615
|
+
}
|
|
616
|
+
}, node);
|
|
617
|
+
}
|
|
618
|
+
} catch (error) {
|
|
619
|
+
done(error);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
},
|
|
623
|
+
zlog(node, msg) {
|
|
624
|
+
// node && node.warn(msg);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}());
|
|
628
|
+
|
|
629
|
+
|
|
630
|
+
}
|