Skip to content

Commit 9a74d61

Browse files
authored
Allow to pass socket options to TCP client (#519)
* add jsdocs * allow to pass more opts to socket * allow to pass AbortSignal to socket * add abort signal example * lint the code * remove vscode/ from gitignore * drop node v14 (reached EOL April 2023)
1 parent b57f338 commit 9a74d61

File tree

9 files changed

+206
-36
lines changed

9 files changed

+206
-36
lines changed

.eslintrc.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"es6": true
55
},
66
"parserOptions": {
7-
"ecmaVersion": 2017
7+
"ecmaVersion": 2020
88
},
99
"extends": "eslint:recommended",
1010
"rules": {

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: ci
22

3-
on:
3+
on:
44
push:
55
branches:
66
- master
@@ -12,7 +12,7 @@ jobs:
1212

1313
strategy:
1414
matrix:
15-
node-version: [14.x, 18.x, 20.x]
15+
node-version: [18.x, 20.x]
1616

1717
steps:
1818
- uses: actions/checkout@v2

.gitignore

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ lib-cov
1616

1717
# Coverage directory used by tools like istanbul
1818
coverage
19+
.nyc_output
1920

2021
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
2122
.grunt
@@ -33,6 +34,3 @@ modbus-serial
3334
docs
3435

3536
package-lock\.json
36-
37-
# Visual Studio Code
38-
.vscode/

.vscode/launch.json

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
// Use IntelliSense to learn about possible Node.js debug attributes.
3+
// Hover to view descriptions of existing attributes.
4+
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5+
"version": "0.2.0",
6+
"configurations": [
7+
{
8+
"name": "Debug Current File",
9+
"type": "node",
10+
"request": "launch",
11+
"program": "${file}"
12+
},
13+
{
14+
"type": "node",
15+
"request": "launch",
16+
"name": "Mocha single test",
17+
"program": "${workspaceFolder}/node_modules/mocha/bin/mocha.js",
18+
// current file
19+
"args": ["${file}"],
20+
"console": "internalConsole",
21+
"cwd": "${workspaceRoot}",
22+
"runtimeVersion": "16",
23+
}
24+
]
25+
}
26+

ModbusRTU.d.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Socket } from 'net';
1+
import { Socket, SocketConstructorOpts, TcpSocketConnectOpts } from 'net';
22
import { TestPort } from "./TestPort";
33
import { PortInfo } from '@serialport/bindings-cpp';
44

@@ -133,12 +133,14 @@ export interface SerialPortUnixPlatformOptions {
133133
vtime?: number;
134134
}
135135

136-
export interface TcpPortOptions {
136+
export interface TcpPortOptions extends TcpSocketConnectOpts {
137137
port?: number;
138138
localAddress?: string;
139139
family?: number;
140140
ip?: string;
141141
timeout?: number;
142+
socket: Socket;
143+
socketOpts: SocketConstructorOpts
142144
}
143145

144146
export interface UdpPortOptions {
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
"use strict";
2+
3+
// create an empty modbus client
4+
// let ModbusRTU = require("modbus-serial");
5+
const ModbusRTU = require("../index");
6+
const client = new ModbusRTU();
7+
8+
const abortController = new AbortController();
9+
const { signal } = abortController;
10+
signal.addEventListener("abort", () => {
11+
console.log("Abort signal received by the abort controller");
12+
});
13+
14+
async function connect() {
15+
await client.connectTCP("127.0.0.1", {
16+
port: 8502,
17+
socketOpts: {
18+
signal: signal
19+
}
20+
});
21+
client.setID(1);
22+
client.setTimeout(2000);
23+
}
24+
25+
async function readRegisters() {
26+
const data = await client.readHoldingRegisters(5, 4);
27+
console.log("Received:", data.data);
28+
}
29+
30+
async function runner() {
31+
await connect();
32+
33+
setTimeout(() => {
34+
abortController.abort("Aborting request");
35+
}, 1000);
36+
37+
await readRegisters();
38+
}
39+
40+
runner()
41+
.then(() => {
42+
if (signal.aborted) {
43+
if (signal.reason) {
44+
console.log(`Request aborted with reason: ${signal.reason}`);
45+
} else {
46+
console.log("Request aborted but no reason was given.");
47+
}
48+
} else {
49+
console.log("Request not aborted");
50+
}
51+
})
52+
.catch((error) => {
53+
console.error(error);
54+
})
55+
.finally(async() => {
56+
console.log("Close client");
57+
await client.close();
58+
});

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"description": "A pure JavaScript implemetation of MODBUS-RTU (Serial and TCP) for NodeJS.",
55
"main": "index.js",
66
"scripts": {
7-
"test": "mocha --recursive"
7+
"test": "nyc --reporter=lcov --reporter=text mocha --recursive"
88
},
99
"repository": {
1010
"type": "git",
@@ -34,6 +34,7 @@
3434
"mocha": "^10.2.0",
3535
"mocha-eslint": "^7.0.0",
3636
"mockery": "^2.1.0",
37+
"nyc": "^15.1.0",
3738
"pump": "^3.0.0",
3839
"sinon": "^15.2.0",
3940
"web-bluetooth-mock": "^1.2.0",

ports/tcpport.js

Lines changed: 55 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -17,32 +17,59 @@ class TcpPort extends EventEmitter {
1717
/**
1818
* Simulate a modbus-RTU port using modbus-TCP connection.
1919
*
20-
* @param ip
21-
* @param options
20+
* @param {string} ip - IP address of Modbus slave.
21+
* @param {{
22+
* port?: number,
23+
* localAddress?: string,
24+
* family?: 0|4|6,
25+
* timeout?: number,
26+
* socket?: net.Socket
27+
* socketOpts?: {
28+
* fd: number,
29+
* allowHalfOpen?: boolean,
30+
* readable?: boolean,
31+
* writable?: boolean,
32+
* signal?: AbortSignal
33+
* },
34+
* } & net.TcpSocketConnectOpts} options - Options object.
2235
* options.port: Nonstandard Modbus port (default is 502).
2336
* options.localAddress: Local IP address to bind to, default is any.
2437
* options.family: 4 = IPv4-only, 6 = IPv6-only, 0 = either (default).
2538
* @constructor
2639
*/
2740
constructor(ip, options) {
2841
super();
29-
const modbus = this;
42+
const self = this;
43+
/** @type {boolean} Flag to indicate if port is open */
3044
this.openFlag = false;
45+
/** @type {(err?: Error) => void} */
3146
this.callback = null;
3247
this._transactionIdWrite = 1;
48+
/** @type {net.Socket?} - Optional custom socket */
3349
this._externalSocket = null;
3450

3551
if(typeof ip === "object") {
3652
options = ip;
53+
ip = undefined;
3754
}
3855

39-
if (typeof(options) === "undefined") options = {};
56+
if (typeof options === "undefined") options = {};
4057

41-
this.connectOptions = {
42-
host: ip || options.ip,
43-
port: options.port || MODBUS_PORT,
44-
localAddress: options.localAddress,
45-
family: options.family
58+
this.socketOpts = undefined;
59+
if (options.socketOpts) {
60+
this.socketOpts = options.socketOpts;
61+
delete options.socketOpts;
62+
}
63+
64+
/** @type {net.TcpSocketConnectOpts} - Options for net.connect(). */
65+
this.connectOptions = {
66+
// Default options
67+
...{
68+
host: ip || options.ip,
69+
port: MODBUS_PORT
70+
},
71+
// User options
72+
...options
4673
};
4774

4875
if(options.socket) {
@@ -55,18 +82,20 @@ class TcpPort extends EventEmitter {
5582
}
5683

5784
// handle callback - call a callback function only once, for the first event
58-
// it will triger
85+
// it will trigger
5986
const handleCallback = function(had_error) {
60-
if (modbus.callback) {
61-
modbus.callback(had_error);
62-
modbus.callback = null;
87+
if (self.callback) {
88+
self.callback(had_error);
89+
self.callback = null;
6390
}
6491
};
6592

6693
// init a socket
67-
this._client = this._externalSocket || new net.Socket();
94+
this._client = this._externalSocket || new net.Socket(this.socketOpts);
6895

6996
if (options.timeout) this._client.setTimeout(options.timeout);
97+
98+
// register events handlers
7099
this._client.on("data", function(data) {
71100
let buffer;
72101
let crc;
@@ -89,34 +118,34 @@ class TcpPort extends EventEmitter {
89118
buffer.writeUInt16LE(crc, buffer.length - CRC_LENGTH);
90119

91120
// update transaction id and emit data
92-
modbus._transactionIdRead = data.readUInt16BE(0);
93-
modbus.emit("data", buffer);
121+
self._transactionIdRead = data.readUInt16BE(0);
122+
self.emit("data", buffer);
94123

95124
// debug
96-
modbusSerialDebug({ action: "parsed tcp port", buffer: buffer, transactionId: modbus._transactionIdRead });
125+
modbusSerialDebug({ action: "parsed tcp port", buffer: buffer, transactionId: self._transactionIdRead });
97126

98127
// reset data
99128
data = data.slice(length + MIN_MBAP_LENGTH);
100129
}
101130
});
102131

103132
this._client.on("connect", function() {
104-
modbus.openFlag = true;
133+
self.openFlag = true;
105134
modbusSerialDebug("TCP port: signal connect");
106135
handleCallback();
107136
});
108137

109138
this._client.on("close", function(had_error) {
110-
modbus.openFlag = false;
139+
self.openFlag = false;
111140
modbusSerialDebug("TCP port: signal close: " + had_error);
112141
handleCallback(had_error);
113142

114-
modbus.emit("close");
115-
modbus.removeAllListeners();
143+
self.emit("close");
144+
self.removeAllListeners();
116145
});
117146

118147
this._client.on("error", function(had_error) {
119-
modbus.openFlag = false;
148+
self.openFlag = false;
120149
modbusSerialDebug("TCP port: signal error: " + had_error);
121150
handleCallback(had_error);
122151
});
@@ -142,7 +171,7 @@ class TcpPort extends EventEmitter {
142171
/**
143172
* Simulate successful port open.
144173
*
145-
* @param callback
174+
* @param {(err?: Error) => void} callback
146175
*/
147176
open(callback) {
148177
if(this._externalSocket === null) {
@@ -159,7 +188,7 @@ class TcpPort extends EventEmitter {
159188
/**
160189
* Simulate successful close port.
161190
*
162-
* @param callback
191+
* @param {(err?: Error) => void} callback
163192
*/
164193
close(callback) {
165194
this.callback = callback;
@@ -170,7 +199,7 @@ class TcpPort extends EventEmitter {
170199
/**
171200
* Simulate successful destroy port.
172201
*
173-
* @param callback
202+
* @param {(err?: Error) => void} callback
174203
*/
175204
destroy(callback) {
176205
this.callback = callback;
@@ -182,7 +211,7 @@ class TcpPort extends EventEmitter {
182211
/**
183212
* Send data to a modbus-tcp slave.
184213
*
185-
* @param data
214+
* @param {Buffer} data
186215
*/
187216
write(data) {
188217
if(data.length < MIN_DATA_LENGTH) {

0 commit comments

Comments
 (0)