njs-modbus 2.1.0 → 3.1.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 +324 -147
- package/README.zh-CN.md +380 -0
- package/dist/index.cjs +2552 -2288
- package/dist/index.d.ts +400 -233
- package/dist/index.mjs +2548 -2287
- package/dist/src/error-code.d.ts +2 -24
- package/dist/src/layers/application/abstract-application-layer.d.ts +13 -8
- package/dist/src/layers/application/ascii-application-layer.d.ts +9 -11
- package/dist/src/layers/application/rtu-application-layer.d.ts +20 -47
- package/dist/src/layers/application/tcp-application-layer.d.ts +9 -8
- package/dist/src/layers/physical/abstract-physical-layer.d.ts +43 -14
- package/dist/src/layers/physical/index.d.ts +9 -3
- package/dist/src/layers/physical/serial-physical-layer.d.ts +43 -15
- package/dist/src/layers/physical/tcp-client-physical-layer.d.ts +13 -12
- package/dist/src/layers/physical/tcp-physical-connection.d.ts +16 -0
- package/dist/src/layers/physical/tcp-server-physical-layer.d.ts +24 -14
- package/dist/src/layers/physical/udp-client-physical-layer.d.ts +33 -0
- package/dist/src/layers/physical/udp-server-physical-layer.d.ts +50 -0
- package/dist/src/layers/physical/utils.d.ts +39 -0
- package/dist/src/layers/physical/vars.d.ts +11 -0
- package/dist/src/master/master-session.d.ts +3 -3
- package/dist/src/master/master.d.ts +55 -19
- package/dist/src/slave/slave.d.ts +58 -33
- package/dist/src/types.d.ts +2 -2
- package/dist/src/utils/callback.d.ts +8 -0
- package/dist/src/utils/crc.d.ts +1 -1
- package/dist/src/utils/index.d.ts +7 -4
- package/dist/src/utils/predictRtuFrameLength.d.ts +13 -16
- package/dist/src/utils/promisify-cb.d.ts +4 -0
- package/dist/src/utils/rtu-timing.d.ts +63 -0
- package/dist/src/utils/whitelist.d.ts +11 -0
- package/dist/src/vars.d.ts +2 -0
- package/package.json +15 -8
- package/dist/src/layers/physical/udp-physical-layer.d.ts +0 -42
- package/dist/src/utils/genConnectionId.d.ts +0 -2
- package/dist/test/adu-buffer.test.d.ts +0 -1
- package/dist/test/ascii-hex-sentry.test.d.ts +0 -1
- package/dist/test/ascii-hex-validation.test.d.ts +0 -1
- package/dist/test/ascii-tcp-fragmentation.test.d.ts +0 -1
- package/dist/test/check-range.test.d.ts +0 -1
- package/dist/test/fallback-atomic.test.d.ts +0 -1
- package/dist/test/fallback-serial.test.d.ts +0 -1
- package/dist/test/fc17-serverid-validation.test.d.ts +0 -1
- package/dist/test/fc43-conformity.test.d.ts +0 -1
- package/dist/test/fc43-utf8-objects.test.d.ts +0 -1
- package/dist/test/gen-connection-id.test.d.ts +0 -1
- package/dist/test/helpers/raw-tcp.d.ts +0 -38
- package/dist/test/master-concurrent.test.d.ts +0 -1
- package/dist/test/modbus-error.test.d.ts +0 -1
- package/dist/test/physical-lifecycle.test.d.ts +0 -1
- package/dist/test/predict-rtu.test.d.ts +0 -1
- package/dist/test/rtu-custom-fc.test.d.ts +0 -1
- package/dist/test/rtu-pool-overflow.test.d.ts +0 -1
- package/dist/test/rtu-t15-timing.test.d.ts +0 -1
- package/dist/test/rtu-t35-default.test.d.ts +0 -1
- package/dist/test/rtu-t35-strict.test.d.ts +0 -1
- package/dist/test/rtu-tcp-fragmentation.test.d.ts +0 -1
- package/dist/test/serial-e2e.test.d.ts +0 -1
- package/dist/test/slave-multi-connection.test.d.ts +0 -1
- package/dist/test/slave.test.d.ts +0 -1
- package/dist/test/tcp-fragmentation.test.d.ts +0 -1
- package/dist/test/udp-multi-client.test.d.ts +0 -1
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# njs-modbus
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
English | [简体中文](./README.zh-CN.md)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
A pure JavaScript implementation of Modbus for Node.js.
|
|
6
6
|
|
|
7
7
|
<!-- prettier-ignore-start -->
|
|
8
8
|
[](http://www.npm-stats.com/~packages/njs-modbus)
|
|
@@ -11,194 +11,371 @@ A pure JavaScript implementation of MODBUS for NodeJS.
|
|
|
11
11
|
[](https://github.com/xiejay97/njs-modbus/actions)
|
|
12
12
|
<!-- prettier-ignore-end -->
|
|
13
13
|
|
|
14
|
-
</div>
|
|
15
|
-
|
|
16
|
-
## Introduction
|
|
17
|
-
|
|
18
|
-
`njs-modbus` is designed as a layered architecture, including the physical layer and the application layer:
|
|
19
|
-
|
|
20
|
-
- Physical layer implements Serial Port, TCP/IP and UDP/IP.
|
|
21
|
-
- Application layer implements RTU, ASCII and TCP.
|
|
22
|
-
|
|
23
|
-
`njs-modbus` provide both client and server.
|
|
24
|
-
|
|
25
14
|
## Features
|
|
26
15
|
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
31
|
-
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
|
37
|
-
|
|
38
|
-
|
|
|
39
|
-
|
|
|
40
|
-
|
|
|
41
|
-
|
|
|
42
|
-
|
|
|
43
|
-
|
|
|
44
|
-
|
|
|
45
|
-
|
|
|
46
|
-
|
|
|
47
|
-
|
|
|
48
|
-
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
- Modbus RTU
|
|
53
|
-
- Modbus ASCII
|
|
54
|
-
- Modbus TCP/IP
|
|
55
|
-
- Modbus UDP/IP
|
|
56
|
-
- Modbus RTU/ASCII Over TCP/IP
|
|
57
|
-
- Modbus RTU/ASCII Over UDP/IP
|
|
58
|
-
|
|
59
|
-
#### Installation
|
|
16
|
+
- **Protocols:** Modbus TCP, RTU, ASCII
|
|
17
|
+
- **Transports:** Serial, TCP client/server, UDP client/server
|
|
18
|
+
- **Master:** FIFO or pipelined concurrent requests (TCP only)
|
|
19
|
+
- **Slave:** Per-connection FIFO or concurrent processing (TCP only)
|
|
20
|
+
- **Custom function codes** with pluggable framing
|
|
21
|
+
- **Broadcasting** (unit = 0)
|
|
22
|
+
- **Full TypeScript**
|
|
23
|
+
- **Zero runtime dependencies** (peer-depends on `serialport` for serial)
|
|
24
|
+
|
|
25
|
+
| Code | Name |
|
|
26
|
+
|------|------|
|
|
27
|
+
| 01 | Read Coils |
|
|
28
|
+
| 02 | Read Discrete Inputs |
|
|
29
|
+
| 03 | Read Holding Registers |
|
|
30
|
+
| 04 | Read Input Registers |
|
|
31
|
+
| 05 | Write Single Coil |
|
|
32
|
+
| 06 | Write Single Register |
|
|
33
|
+
| 15 | Write Multiple Coils |
|
|
34
|
+
| 16 | Write Multiple Registers |
|
|
35
|
+
| 17 | Report Server ID |
|
|
36
|
+
| 22 | Mask Write Register |
|
|
37
|
+
| 23 | Read/Write Multiple Registers |
|
|
38
|
+
| 43/14 | Read Device Identification |
|
|
39
|
+
|
|
40
|
+
## Installation
|
|
60
41
|
|
|
61
42
|
```bash
|
|
62
43
|
npm install njs-modbus
|
|
63
44
|
```
|
|
64
45
|
|
|
65
|
-
|
|
46
|
+
For serial transport, also install the peer dependency:
|
|
66
47
|
|
|
67
|
-
|
|
48
|
+
```bash
|
|
49
|
+
npm install serialport
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Quick Start
|
|
53
|
+
|
|
54
|
+
### TCP Master
|
|
68
55
|
|
|
69
56
|
```typescript
|
|
70
|
-
import {
|
|
57
|
+
import { ModbusMaster } from 'njs-modbus';
|
|
71
58
|
|
|
72
|
-
const
|
|
73
|
-
|
|
59
|
+
const master = new ModbusMaster({
|
|
60
|
+
physical: { type: 'TCP_CLIENT' },
|
|
61
|
+
protocol: { type: 'TCP' },
|
|
62
|
+
});
|
|
74
63
|
|
|
75
|
-
|
|
64
|
+
await master.open({ port: 502, host: '192.168.1.10' });
|
|
76
65
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
modbusMaster.readHoldingRegisters(1, 0, 10).then((res) => {
|
|
82
|
-
console.log(res);
|
|
83
|
-
});
|
|
84
|
-
})
|
|
85
|
-
.catch((error) => {
|
|
86
|
-
console.log(error);
|
|
87
|
-
});
|
|
66
|
+
const res = await master.readHoldingRegisters(1, 0, 10);
|
|
67
|
+
console.log(res.data);
|
|
68
|
+
|
|
69
|
+
await master.close();
|
|
88
70
|
```
|
|
89
71
|
|
|
90
|
-
###
|
|
72
|
+
### TCP Slave
|
|
91
73
|
|
|
92
74
|
```typescript
|
|
93
|
-
import
|
|
94
|
-
import { SerialPhysicalLayer, RtuApplicationLayer, ModbusSlave } from 'njs-modbus';
|
|
75
|
+
import { ModbusSlave } from 'njs-modbus';
|
|
95
76
|
|
|
96
|
-
const
|
|
97
|
-
|
|
77
|
+
const slave = new ModbusSlave({
|
|
78
|
+
physical: { type: 'TCP_SERVER' },
|
|
79
|
+
protocol: { type: 'TCP' },
|
|
80
|
+
});
|
|
98
81
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
holdingRegisters: new Map<number, number>(),
|
|
104
|
-
};
|
|
105
|
-
const slave1: ModbusSlaveModel = {
|
|
106
|
-
readDiscreteInputs: (address, length) => {
|
|
107
|
-
return Array.from({ length }).map((_, i) => {
|
|
108
|
-
const discreteInput = slave1Data.discreteInputs.get(address + i);
|
|
109
|
-
if (typeof discreteInput === 'undefined') {
|
|
110
|
-
return false;
|
|
111
|
-
}
|
|
112
|
-
return discreteInput;
|
|
113
|
-
});
|
|
82
|
+
slave.add({
|
|
83
|
+
unit: 1,
|
|
84
|
+
readHoldingRegisters: (address, length) => {
|
|
85
|
+
return Array.from({ length }, (_, i) => address + i);
|
|
114
86
|
},
|
|
87
|
+
});
|
|
115
88
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
slave1Data.coils.set(address, value);
|
|
89
|
+
await slave.open({ port: 502 });
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Serial RTU Master
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
const master = new ModbusMaster({
|
|
96
|
+
physical: {
|
|
97
|
+
type: 'SERIAL',
|
|
98
|
+
opts: { path: '/dev/ttyUSB0', baudRate: 9600 },
|
|
127
99
|
},
|
|
100
|
+
protocol: { type: 'RTU' },
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
await master.open();
|
|
104
|
+
const res = await master.readHoldingRegisters(1, 0, 10);
|
|
105
|
+
await master.close();
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### RTU over TCP
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
const master = new ModbusMaster({
|
|
112
|
+
physical: { type: 'TCP_CLIENT' },
|
|
113
|
+
protocol: { type: 'RTU' },
|
|
114
|
+
});
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Physical Layers
|
|
118
|
+
|
|
119
|
+
All physical layers expose `open()` / `close()`, a `state` property, and events: `'open'`, `'connect'`, `'close'`, `'error'`.
|
|
120
|
+
|
|
121
|
+
| Type | Class | `open(...)` args |
|
|
122
|
+
|------|-------|-----------------|
|
|
123
|
+
| `SERIAL` | `SerialPhysicalLayer` | none |
|
|
124
|
+
| `TCP_CLIENT` | `TcpClientPhysicalLayer` | `SocketConnectOpts` |
|
|
125
|
+
| `TCP_SERVER` | `TcpServerPhysicalLayer` | `ListenOptions` |
|
|
126
|
+
| `UDP_CLIENT` | `UdpClientPhysicalLayer` | `{ port, address }` |
|
|
127
|
+
| `UDP_SERVER` | `UdpServerPhysicalLayer` | `BindOptions` |
|
|
128
|
+
| `CUSTOM` | *(user-provided)* | *(user-defined)* |
|
|
129
|
+
|
|
130
|
+
### Server options
|
|
128
131
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
132
|
+
```typescript
|
|
133
|
+
const slave = new ModbusSlave({
|
|
134
|
+
physical: {
|
|
135
|
+
type: 'TCP_SERVER',
|
|
136
|
+
opts: {
|
|
137
|
+
whitelist: ['192.168.1.10', '10.0.0.5'], // IPv4-mapped IPv6 normalized automatically
|
|
138
|
+
maxConnections: 10,
|
|
139
|
+
idleTimeout: 30000, // evict inactive connections, 0 = disable
|
|
140
|
+
},
|
|
137
141
|
},
|
|
142
|
+
protocol: { type: 'TCP' },
|
|
143
|
+
});
|
|
144
|
+
```
|
|
138
145
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
146
|
+
### Physical layer options
|
|
147
|
+
|
|
148
|
+
| Option | Type | Description |
|
|
149
|
+
|--------|------|-------------|
|
|
150
|
+
| `whitelist` | `string[]` | Allowed client IPs. `::ffff:` prefix is stripped automatically. |
|
|
151
|
+
| `maxConnections` | `number` | Max concurrent connections. New connections are silently dropped when exceeded. |
|
|
152
|
+
| `idleTimeout` | `number` | Idle timeout in ms before evicting a connection. Default `30000`. Pass `0` to disable. |
|
|
153
|
+
| `socketOpts` / `serverOpts` | `object` | Forwarded to Node.js `createSocket()` / `createServer()`. |
|
|
154
|
+
|
|
155
|
+
## Master API
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
new ModbusMaster({
|
|
159
|
+
physical: { type: 'TCP_CLIENT' },
|
|
160
|
+
protocol: { type: 'TCP' }, // 'TCP' | 'RTU' | 'ASCII'
|
|
161
|
+
timeout?: 1000, // per-request timeout (ms)
|
|
162
|
+
concurrent?: false, // pipelined mode (TCP only)
|
|
163
|
+
})
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
RTU framing options:
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
protocol: {
|
|
170
|
+
type: 'RTU',
|
|
171
|
+
opts: {
|
|
172
|
+
// Either a bare number (milliseconds), or `{ unit: 'bit' | 'ms', value: N }`.
|
|
173
|
+
// Use `0` to disable the timer entirely (useful for lossless transports
|
|
174
|
+
// such as RTU-over-TCP).
|
|
175
|
+
intervalBetweenFrames: 20, // 20 ms
|
|
176
|
+
interCharTimeout: { unit: 'bit', value: 10 }, // bit-time, needs baudRate
|
|
177
|
+
poolSize: 1024,
|
|
147
178
|
},
|
|
148
|
-
|
|
149
|
-
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
ASCII options:
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
protocol: {
|
|
186
|
+
type: 'ASCII',
|
|
187
|
+
opts: {
|
|
188
|
+
lenientHex: true, // accept lowercase hex (a-f). Default: false (strict uppercase per spec)
|
|
150
189
|
},
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Methods
|
|
194
|
+
|
|
195
|
+
| Method | Description |
|
|
196
|
+
|--------|-------------|
|
|
197
|
+
| `open(...args)` | Open the physical layer. Can only be called once; after `close()` the instance is dead. |
|
|
198
|
+
| `close()` | Close immediately. In-flight and queued requests are rejected. |
|
|
199
|
+
| `readCoils(unit, address, length, timeout?)` | FC 01 |
|
|
200
|
+
| `readDiscreteInputs(unit, address, length, timeout?)` | FC 02 |
|
|
201
|
+
| `readHoldingRegisters(unit, address, length, timeout?)` | FC 03 |
|
|
202
|
+
| `readInputRegisters(unit, address, length, timeout?)` | FC 04 |
|
|
203
|
+
| `writeSingleCoil(unit, address, value, timeout?)` | FC 05 |
|
|
204
|
+
| `writeSingleRegister(unit, address, value, timeout?)` | FC 06 |
|
|
205
|
+
| `writeMultipleCoils(unit, address, values, timeout?)` | FC 15 |
|
|
206
|
+
| `writeMultipleRegisters(unit, address, values, timeout?)` | FC 16 |
|
|
207
|
+
| `reportServerId(unit, serverIdLength?, timeout?)` | FC 17 |
|
|
208
|
+
| `maskWriteRegister(unit, address, andMask, orMask, timeout?)` | FC 22 |
|
|
209
|
+
| `readAndWriteMultipleRegisters(unit, read, write, timeout?)` | FC 23 |
|
|
210
|
+
| `readDeviceIdentification(unit, readDeviceIDCode, objectId, timeout?)` | FC 43/14 |
|
|
211
|
+
| `sendCustomFC(unit, fc, data, timeout?)` | Send a user-defined function code |
|
|
212
|
+
| `addCustomFunctionCode(cfc)` | Register custom FC for framing |
|
|
213
|
+
| `removeCustomFunctionCode(fc)` | Unregister custom FC |
|
|
214
|
+
|
|
215
|
+
Broadcast (`unit = 0`) resolves `void` — slaves never respond.
|
|
216
|
+
|
|
217
|
+
## Slave API
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
new ModbusSlave({
|
|
221
|
+
physical: { type: 'TCP_SERVER' },
|
|
222
|
+
protocol: { type: 'TCP' },
|
|
223
|
+
concurrent?: false, // per-connection concurrent (TCP only)
|
|
224
|
+
})
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Register a model
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
slave.add({
|
|
231
|
+
unit: 1,
|
|
232
|
+
|
|
233
|
+
// Optional: intercept any FC before default dispatch
|
|
234
|
+
interceptor?: (fc, data) => Buffer | undefined,
|
|
235
|
+
|
|
236
|
+
readCoils?: (address, length) => boolean[],
|
|
237
|
+
readDiscreteInputs?: (address, length) => boolean[],
|
|
238
|
+
readHoldingRegisters?: (address, length) => number[],
|
|
239
|
+
readInputRegisters?: (address, length) => number[],
|
|
240
|
+
|
|
241
|
+
writeSingleCoil?: (address, value) => void,
|
|
242
|
+
writeMultipleCoils?: (address, values) => void,
|
|
151
243
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
244
|
+
writeSingleRegister?: (address, value) => void,
|
|
245
|
+
writeMultipleRegisters?: (address, values) => void,
|
|
246
|
+
|
|
247
|
+
maskWriteRegister?: (address, andMask, orMask) => void,
|
|
248
|
+
|
|
249
|
+
reportServerId?: () => ServerId,
|
|
250
|
+
readDeviceIdentification?: () => { [index: number]: string },
|
|
251
|
+
|
|
252
|
+
// Optional address range validation
|
|
253
|
+
getAddressRange?: () => ({
|
|
254
|
+
discreteInputs?: [number, number] | [number, number][];
|
|
255
|
+
coils?: [number, number] | [number, number][];
|
|
256
|
+
inputRegisters?: [number, number] | [number, number][];
|
|
257
|
+
holdingRegisters?: [number, number] | [number, number][];
|
|
164
258
|
}),
|
|
259
|
+
});
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Missing handlers return `ILLEGAL_FUNCTION`. Out-of-range addresses return `ILLEGAL_DATA_ADDRESS`. Handler throws become `SERVER_DEVICE_FAILURE` unless the error is a `ModbusError`.
|
|
263
|
+
|
|
264
|
+
| Method | Description |
|
|
265
|
+
|--------|-------------|
|
|
266
|
+
| `add(model)` | Register a slave model |
|
|
267
|
+
| `remove(unit)` | Remove a slave model |
|
|
268
|
+
| `open(...args)` | Open the physical layer. One-shot: cannot reopen after `close()`. |
|
|
269
|
+
| `close()` | Close immediately |
|
|
270
|
+
| `addCustomFunctionCode(cfc)` | Register a custom FC |
|
|
271
|
+
| `removeCustomFunctionCode(fc)` | Unregister a custom FC |
|
|
272
|
+
|
|
273
|
+
## Custom Function Codes
|
|
274
|
+
|
|
275
|
+
```typescript
|
|
276
|
+
import type { CustomFunctionCode } from 'njs-modbus';
|
|
277
|
+
|
|
278
|
+
const cfc: CustomFunctionCode = {
|
|
279
|
+
fc: 0x50,
|
|
280
|
+
predictRequestLength: (buffer) => {
|
|
281
|
+
if (buffer.length < 2) return null;
|
|
282
|
+
return 4 + buffer[1];
|
|
283
|
+
},
|
|
284
|
+
predictResponseLength: (buffer) => {
|
|
285
|
+
if (buffer.length < 2) return null;
|
|
286
|
+
return 4 + buffer[1];
|
|
287
|
+
},
|
|
288
|
+
handle: async (data, unit) => {
|
|
289
|
+
return Buffer.from([data[1], data[0]]);
|
|
290
|
+
},
|
|
165
291
|
};
|
|
166
292
|
|
|
167
|
-
|
|
168
|
-
|
|
293
|
+
// Slave: framing + dispatch
|
|
294
|
+
slave.addCustomFunctionCode(cfc);
|
|
169
295
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
console.log('opened');
|
|
174
|
-
})
|
|
175
|
-
.catch((error) => {
|
|
176
|
-
console.log(error);
|
|
177
|
-
});
|
|
296
|
+
// Master: framing only
|
|
297
|
+
master.addCustomFunctionCode(cfc);
|
|
298
|
+
const response = await master.sendCustomFC(1, 0x50, [0xab, 0xcd]);
|
|
178
299
|
```
|
|
179
300
|
|
|
180
|
-
|
|
301
|
+
## Error Handling
|
|
181
302
|
|
|
182
|
-
|
|
303
|
+
```typescript
|
|
304
|
+
import { ErrorCode, ModbusError, getErrorByCode } from 'njs-modbus';
|
|
305
|
+
|
|
306
|
+
// ErrorCode.ILLEGAL_FUNCTION = 0x01
|
|
307
|
+
// ErrorCode.ILLEGAL_DATA_ADDRESS = 0x02
|
|
308
|
+
// ErrorCode.ILLEGAL_DATA_VALUE = 0x03
|
|
309
|
+
// ErrorCode.SERVER_DEVICE_FAILURE = 0x04
|
|
310
|
+
// ErrorCode.ACKNOWLEDGE = 0x05
|
|
311
|
+
// ErrorCode.SERVER_DEVICE_BUSY = 0x06
|
|
312
|
+
// ErrorCode.MEMORY_PARITY_ERROR = 0x08
|
|
313
|
+
// ErrorCode.GATEWAY_PATH_UNAVAILABLE = 0x0a
|
|
314
|
+
// ErrorCode.GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND = 0x0b
|
|
315
|
+
|
|
316
|
+
slave.add({
|
|
317
|
+
unit: 1,
|
|
318
|
+
readHoldingRegisters: (address) => {
|
|
319
|
+
if (address > 100) {
|
|
320
|
+
throw getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS);
|
|
321
|
+
}
|
|
322
|
+
return [42];
|
|
323
|
+
},
|
|
324
|
+
});
|
|
325
|
+
```
|
|
183
326
|
|
|
184
|
-
|
|
327
|
+
## Performance
|
|
185
328
|
|
|
186
|
-
|
|
329
|
+
Benchmarked against [jsmodbus](https://github.com/Cloud-Automation/node-modbus) and [modbus-serial](https://github.com/yaacov/node-modbus-serial).
|
|
187
330
|
|
|
188
|
-
|
|
189
|
-
|
|
331
|
+
| Metric | njs-modbus | jsmodbus | modbus-serial |
|
|
332
|
+
|--------|-----------|----------|---------------|
|
|
333
|
+
| TCP Throughput | **5,527 ops/sec** | 3,239 (0.59x) | 371 (0.07x) |
|
|
334
|
+
| TCP P99 Latency | **2.71 ms** | 4.73 ms (1.75x) | 12.48 ms (4.61x) |
|
|
335
|
+
| TCP CPU Efficiency | **1,116 µs/op** | 1,950 (1.75x) | 16,715 (14.98x) |
|
|
336
|
+
| Concurrent (8 conn) | **5,274 ops/sec** | 3,416 (0.65x) | 1,815 (0.34x) |
|
|
337
|
+
| RTU CPU Efficiency | **1,762 µs/op** | 1,760 (1.00x) | 2,144 (1.22x) |
|
|
338
|
+
| TCP Res Encode | **2.04M ops/sec** | 373K (0.18x) | 411K (0.20x) |
|
|
339
|
+
| TCP Res Decode | **1.91M ops/sec** | 538K (0.28x) | 231K (0.12x) |
|
|
340
|
+
|
|
341
|
+
<details>
|
|
342
|
+
<summary>Full benchmark results</summary>
|
|
343
|
+
|
|
344
|
+
Node.js v24.15.0 · linux x64 · 3 runs × 300 s · [full report](./benchmark/RESULTS.md)
|
|
190
345
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
346
|
+
### TCP Throughput (Single Connection)
|
|
347
|
+
|
|
348
|
+
```
|
|
349
|
+
njs-modbus │ 5,527 ops/sec 🏆 CPU: 1,116 µs/op
|
|
350
|
+
jsmodbus │ 3,239 ops/sec (0.59x) CPU: 1,950 µs/op
|
|
351
|
+
modbus-serial │ 371 ops/sec (0.07x) CPU: 16,715 µs/op
|
|
194
352
|
```
|
|
195
353
|
|
|
196
|
-
|
|
354
|
+
### Concurrent (8 Connections)
|
|
197
355
|
|
|
198
|
-
|
|
356
|
+
```
|
|
357
|
+
njs-modbus │ 5,274 ops/sec 🏆 CPU: 1,392 µs/op
|
|
358
|
+
jsmodbus │ 3,416 ops/sec (0.65x) CPU: 2,131 µs/op
|
|
359
|
+
modbus-serial │ 1,815 ops/sec (0.34x) CPU: 3,962 µs/op
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
### RTU Serial (115200 baud simulated)
|
|
363
|
+
|
|
364
|
+
```
|
|
365
|
+
njs-modbus │ 44 ops/sec CPU: 1,762 µs/op
|
|
366
|
+
jsmodbus │ 44 ops/sec CPU: 1,760 µs/op
|
|
367
|
+
modbus-serial │ 44 ops/sec CPU: 2,144 µs/op
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### Encode / Decode (CPU Micro-benchmark)
|
|
371
|
+
|
|
372
|
+
```
|
|
373
|
+
tcpResEncode: njs-modbus 2.04M ops/sec (jsmodbus 0.18x, modbus-serial 0.20x)
|
|
374
|
+
tcpResDecode: njs-modbus 1.91M ops/sec (jsmodbus 0.28x, modbus-serial 0.12x)
|
|
375
|
+
```
|
|
199
376
|
|
|
200
|
-
|
|
377
|
+
</details>
|
|
201
378
|
|
|
202
379
|
## License
|
|
203
380
|
|
|
204
|
-
[](/LICENSE)
|