njs-modbus 3.3.0 → 3.4.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 CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  English | [简体中文](./README.zh-CN.md)
4
4
 
5
- A pure JavaScript implementation of Modbus for Node.js.
5
+ A pure JavaScript implementation of Modbus for Node.js, optimized for throughput and low GC pressure.
6
6
 
7
7
  <!-- prettier-ignore-start -->
8
8
  [![npm download](http://img.shields.io/npm/dw/njs-modbus.svg?style=flat-square)](http://www.npm-stats.com/~packages/njs-modbus)
@@ -11,17 +11,20 @@ A pure JavaScript implementation of Modbus for Node.js.
11
11
  [![CI](https://img.shields.io/github/actions/workflow/status/xiejay97/njs-modbus/ci.yml?branch=main&style=flat-square)](https://github.com/xiejay97/njs-modbus/actions)
12
12
  <!-- prettier-ignore-end -->
13
13
 
14
- ## Features
14
+ ## Highlights
15
15
 
16
- - **Protocols:** Modbus TCP, RTU, ASCII
17
- - **Transports:** Serial, TCP client/server, UDP client/server
18
- - **Master:** FIFO or pipelined concurrent requests (protocol must be TCP)
19
- - **Slave:** Per-connection FIFO or concurrent processing (protocol must be TCP)
16
+ - **Protocols** Modbus TCP, RTU, ASCII
17
+ - **Transports** Serial, TCP client/server, UDP client/server, plus pluggable custom layers
18
+ - **Performance-first** — Parallel FIFO queues, lazy-deletion timer heap, inline BE reads/writes, zero-allocation hot paths
19
+ - **Master** FIFO or pipelined concurrent requests (TCP only)
20
+ - **Slave** — Per-connection FIFO or concurrent processing (TCP only)
20
21
  - **Custom function codes** with pluggable framing
21
- - **Broadcasting** (unit = 0)
22
+ - **Broadcasting** (`unit = 0`)
22
23
  - **Full TypeScript**
23
24
  - **Zero runtime dependencies** (peer-depends on `serialport` for serial)
24
25
 
26
+ ## Supported Function Codes
27
+
25
28
  | Code | Name |
26
29
  |------|------|
27
30
  | 01 | Read Coils |
@@ -116,20 +119,45 @@ const master = new ModbusMaster({
116
119
  });
117
120
  ```
118
121
 
119
- ## Physical Layers
122
+ ## Architecture
123
+
124
+ The library follows a strict two-layer design:
125
+
126
+ ```
127
+ ┌─────────────────────────────────────────┐
128
+ │ ModbusMaster / ModbusSlave │ ← User API, session/queue management
129
+ ├─────────────────────────────────────────┤
130
+ │ TcpApplicationLayer │ ← Protocol framing (MBAP / CRC16 / LRC)
131
+ │ RtuApplicationLayer │
132
+ │ AsciiApplicationLayer │
133
+ ├─────────────────────────────────────────┤
134
+ │ AbstractPhysicalConnection │ ← Per-connection I/O
135
+ ├─────────────────────────────────────────┤
136
+ │ AbstractPhysicalLayer │ ← Resource ownership (port/socket/server)
137
+ │ ├── TcpClientPhysicalLayer │
138
+ │ ├── TcpServerPhysicalLayer │
139
+ │ ├── UdpClientPhysicalLayer │
140
+ │ ├── UdpServerPhysicalLayer │
141
+ │ └── SerialPhysicalLayer │
142
+ └─────────────────────────────────────────┘
143
+ ```
144
+
145
+ Every transport is modeled as connection-oriented: physical layers emit `'connect'` with an `AbstractPhysicalConnection`, and a dedicated application layer instance is created per connection.
120
146
 
121
- All physical layers expose `open()` / `close()`, a `state` property, and events: `'open'`, `'connect'`, `'close'`, `'error'`.
147
+ ## Physical Layer
122
148
 
123
- | Type | Class | `open(...)` args |
124
- |------|-------|-----------------|
125
- | `SERIAL` | `SerialPhysicalLayer` | none |
126
- | `TCP_CLIENT` | `TcpClientPhysicalLayer` | `SocketConnectOpts` |
127
- | `TCP_SERVER` | `TcpServerPhysicalLayer` | `ListenOptions` |
128
- | `UDP_CLIENT` | `UdpClientPhysicalLayer` | `{ port, address }` |
129
- | `UDP_SERVER` | `UdpServerPhysicalLayer` | `BindOptions` |
130
- | `CUSTOM` | *(user-provided)* | *(user-defined)* |
149
+ All physical layers expose `open()` / `close()`, a `state` property, and the events documented in [Events](#events).
131
150
 
132
- ### Server options
151
+ | Type | `open(...)` Args | Description |
152
+ |------|-----------------|-------------|
153
+ | `SERIAL` | `()` | Serial port via `serialport` package |
154
+ | `TCP_CLIENT` | `SocketConnectOpts` | TCP client socket |
155
+ | `TCP_SERVER` | `ListenOptions` | TCP server |
156
+ | `UDP_CLIENT` | `{ port?, address? }` | UDP client |
157
+ | `UDP_SERVER` | `BindOptions` | UDP server |
158
+ | `CUSTOM` | *(user-defined)* | User-provided `AbstractPhysicalLayer` instance |
159
+
160
+ ### TCP Server options
133
161
 
134
162
  ```typescript
135
163
  const slave = new ModbusSlave({
@@ -145,7 +173,7 @@ const slave = new ModbusSlave({
145
173
  });
146
174
  ```
147
175
 
148
- ### Physical layer options
176
+ ### Physical layer option reference
149
177
 
150
178
  | Option | Type | Description |
151
179
  |--------|------|-------------|
@@ -154,54 +182,35 @@ const slave = new ModbusSlave({
154
182
  | `idleTimeout` | `number` | Idle timeout in ms before evicting a connection. Default `30000`. Pass `0` to disable. |
155
183
  | `socketOpts` / `serverOpts` | `object` | Forwarded to Node.js `createSocket()` / `createServer()`. |
156
184
 
157
- ## Custom Physical Layer
158
-
159
- Any byte-oriented transport can be used by implementing `AbstractPhysicalLayer` and `AbstractPhysicalConnection`, then passing `{ type: 'CUSTOM', layer: yourLayer }`.
160
-
161
- ```typescript
162
- import { AbstractPhysicalLayer, AbstractPhysicalConnection } from 'njs-modbus';
163
-
164
- class MyPhysicalLayer extends AbstractPhysicalLayer { /* open / close */ }
165
- class MyConnection extends AbstractPhysicalConnection { /* write / destroy, emit 'data' */ }
166
-
167
- const master = new ModbusMaster({
168
- physical: { type: 'CUSTOM', layer: new MyPhysicalLayer() },
169
- protocol: { type: 'TCP' },
170
- });
171
- ```
172
-
173
- See [`examples/websocket`](./examples/websocket) for a complete **Modbus TCP over WebSocket** implementation — client/server physical layers with a working demo.
185
+ ## Protocol Layer
174
186
 
175
- See [`examples/bluetooth`](./examples/bluetooth) for a **Modbus TCP over BLE** implementation using the Nordic UART Service (NUS) — includes a loopback test that runs without real BLE hardware.
187
+ | Protocol | Options | Description |
188
+ |----------|---------|-------------|
189
+ | `TCP` | none | Modbus TCP with MBAP header |
190
+ | `RTU` | `RtuProtocolOptions` | Modbus RTU with CRC16 |
191
+ | `ASCII` | `AsciiApplicationLayerOptions` | Modbus ASCII with LRC |
176
192
 
177
- ## Master API
178
-
179
- ```typescript
180
- new ModbusMaster({
181
- physical: { type: 'TCP_CLIENT' },
182
- protocol: { type: 'TCP' }, // 'TCP' | 'RTU' | 'ASCII'
183
- timeout?: 1000, // per-request timeout (ms)
184
- concurrent?: false, // pipelined mode (protocol: TCP only)
185
- })
186
- ```
187
-
188
- RTU framing options:
193
+ ### RTU options
189
194
 
190
195
  ```typescript
191
196
  protocol: {
192
197
  type: 'RTU',
193
198
  opts: {
194
- // Either a bare number (milliseconds), or `{ unit: 'bit' | 'ms', value: N }`.
195
- // Use `0` to disable the timer entirely (useful for lossless transports
196
- // such as RTU-over-TCP).
197
- intervalBetweenFrames: 20, // 20 ms
198
- interCharTimeout: { unit: 'bit', value: 10 }, // bit-time, needs baudRate
199
- poolSize: 1024,
199
+ // t3.5 inter-frame silence.
200
+ // Either a bare number (ms) or { unit: 'bit' | 'ms', value: N }.
201
+ // Use 0 to disable (useful for lossless transports like RTU-over-TCP).
202
+ intervalBetweenFrames: 20,
203
+
204
+ // t1.5 inter-character timeout (opt-in).
205
+ interCharTimeout: { unit: 'bit', value: 10 },
206
+
207
+ // Discard frames with t1.5 timing violations.
208
+ strictTiming: true,
200
209
  },
201
210
  }
202
211
  ```
203
212
 
204
- ASCII options:
213
+ ### ASCII options
205
214
 
206
215
  ```typescript
207
216
  protocol: {
@@ -212,11 +221,24 @@ protocol: {
212
221
  }
213
222
  ```
214
223
 
224
+ ## Master API
225
+
226
+ ### Constructor
227
+
228
+ ```typescript
229
+ new ModbusMaster({
230
+ physical: { type: 'TCP_CLIENT' },
231
+ protocol: { type: 'TCP' }, // 'TCP' | 'RTU' | 'ASCII'
232
+ timeout?: 1000, // per-request timeout (ms)
233
+ concurrent?: false, // pipelined mode (TCP only)
234
+ })
235
+ ```
236
+
215
237
  ### Methods
216
238
 
217
239
  | Method | Description |
218
240
  |--------|-------------|
219
- | `open(...args)` | Open the physical layer. Can only be called once; after `close()` the instance is dead. |
241
+ | `open(...args)` | Open the physical layer. One-shot: cannot reopen after `close()`. |
220
242
  | `close()` | Close immediately. In-flight and queued requests are rejected. |
221
243
  | `readCoils(unit, address, length, timeout?)` | FC 01 |
222
244
  | `readDiscreteInputs(unit, address, length, timeout?)` | FC 02 |
@@ -238,11 +260,13 @@ Broadcast (`unit = 0`) resolves `void` — slaves never respond.
238
260
 
239
261
  ## Slave API
240
262
 
263
+ ### Constructor
264
+
241
265
  ```typescript
242
266
  new ModbusSlave({
243
267
  physical: { type: 'TCP_SERVER' },
244
268
  protocol: { type: 'TCP' },
245
- concurrent?: false, // per-connection concurrent (protocol: TCP only)
269
+ concurrent?: false, // per-connection concurrent (TCP only)
246
270
  })
247
271
  ```
248
272
 
@@ -255,8 +279,8 @@ slave.add({
255
279
  // Optional: intercept any FC before default dispatch
256
280
  interceptor?: (fc, data) => Buffer | undefined,
257
281
 
258
- readCoils?: (address, length) => boolean[] | Uint8Array,
259
- readDiscreteInputs?: (address, length) => boolean[] | Uint8Array,
282
+ readCoils?: (address, length) => Uint8Array,
283
+ readDiscreteInputs?: (address, length) => Uint8Array,
260
284
  readHoldingRegisters?: (address, length) => number[] | Uint16Array,
261
285
  readInputRegisters?: (address, length) => number[] | Uint16Array,
262
286
 
@@ -283,31 +307,70 @@ slave.add({
283
307
 
284
308
  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`.
285
309
 
310
+ ### Methods
311
+
286
312
  | Method | Description |
287
313
  |--------|-------------|
288
314
  | `add(model)` | Register a slave model |
289
- | `remove(unit)` | Remove a slave model |
290
- | `open(...args)` | Open the physical layer. One-shot: cannot reopen after `close()`. |
315
+ | `remove(unit)` | Remove a model |
316
+ | `open(...args)` | Open and begin accepting connections. One-shot. |
291
317
  | `close()` | Close immediately |
292
- | `addCustomFunctionCode(cfc)` | Register a custom FC |
293
- | `removeCustomFunctionCode(fc)` | Unregister a custom FC |
318
+ | `addCustomFunctionCode(cfc)` | Register custom FC |
319
+ | `removeCustomFunctionCode(fc)` | Unregister custom FC |
320
+
321
+ ## Events
322
+
323
+ Both `ModbusMaster` and `ModbusSlave` are `EventEmitter`s. All events are typed.
324
+
325
+ ### Physical layer events
326
+
327
+ | Event | Arguments | Description |
328
+ |-------|-----------|-------------|
329
+ | `open` | `()` | Physical layer is ready to accept connections |
330
+ | `connect` | `(connection)` | A new connection has been established |
331
+ | `close` | `()` | Physical layer has closed |
332
+ | `error` | `(error)` | Physical layer error |
333
+
334
+ ### Connection debug events
335
+
336
+ | Event | Arguments | Description |
337
+ |-------|-----------|-------------|
338
+ | `tx` | `(buffer, connection)` | Raw data written to the wire |
339
+ | `rx` | `(buffer, connection)` | Raw data received from the wire |
340
+
341
+ ### Application layer events
342
+
343
+ | Event | Arguments | Description |
344
+ |-------|-----------|-------------|
345
+ | `framing` | `(frame, connection)` | A complete frame has been decoded |
346
+ | `framingError` | `(error, connection)` | Framing error (CRC/LRC failure, malformed MBAP, timing violation, etc.) |
347
+
348
+ ```typescript
349
+ master.on('framing', (frame, connection) => {
350
+ console.log('frame:', frame.unit, frame.fc, frame.transaction);
351
+ });
352
+
353
+ master.on('framingError', (error, connection) => {
354
+ console.log('framing error:', error.message);
355
+ });
356
+ ```
294
357
 
295
358
  ## Custom Function Codes
296
359
 
297
- `predictRequestLength` / `predictResponseLength` receive a shared pool buffer and byte offsets (`start`, `end`). Use `end - start` for bounds checks rather than `buffer.length`.
360
+ `predictRequestLength` / `predictResponseLength` receive a `getByte` accessor and the number of available `length` bytes. Use `length` for bounds checks do not access the buffer directly.
298
361
 
299
362
  ```typescript
300
363
  import type { CustomFunctionCode } from 'njs-modbus';
301
364
 
302
365
  const cfc: CustomFunctionCode = {
303
366
  fc: 0x50,
304
- predictRequestLength: (buffer, start, end) => {
305
- if (end - start < 2) return null;
306
- return 4 + buffer[start + 1];
367
+ predictRequestLength: (getByte, length) => {
368
+ if (length < 2) return 0; // need more bytes
369
+ return 4 + getByte(1);
307
370
  },
308
- predictResponseLength: (buffer, start, end) => {
309
- if (end - start < 2) return null;
310
- return 4 + buffer[start + 1];
371
+ predictResponseLength: (getByte, length) => {
372
+ if (length < 2) return 0;
373
+ return 4 + getByte(1);
311
374
  },
312
375
  handle: async (data, unit) => {
313
376
  return Buffer.from([data[1], data[0]]);
@@ -348,82 +411,65 @@ slave.add({
348
411
  });
349
412
  ```
350
413
 
351
- ## Performance
414
+ ## Custom Physical Layer
415
+
416
+ Any byte-oriented transport can be used by implementing `AbstractPhysicalLayer` and `AbstractPhysicalConnection`, then passing `{ type: 'CUSTOM', layer: yourLayer }`.
352
417
 
353
- Benchmarked against [jsmodbus](https://github.com/Cloud-Automation/node-modbus) and [modbus-serial](https://github.com/yaacov/node-modbus-serial).
418
+ ```typescript
419
+ import { AbstractPhysicalLayer, AbstractPhysicalConnection } from 'njs-modbus';
354
420
 
355
- | Metric | njs-modbus | jsmodbus | modbus-serial |
356
- |--------|-----------|----------|---------------|
357
- | TCP Throughput | **70,544 ops/sec** | 39,827 (0.56x) | 786 (0.01x) |
358
- | TCP P99 Latency | **53 µs** | 98 µs (1.83x) | 2,331 µs (43.7x) |
359
- | Concurrent (8 conn) | **75,810 ops/sec** | 41,754 (0.55x) | 5,620 (0.07x) |
360
- | RTU Serial (115200 baud) | **44 ops/sec** | 44 ops/sec | 45 ops/sec |
361
- | TCP Response Encode | **6.57M ops/sec** | 1.22M (0.19x) | 1.18M (0.18x) |
362
- | TCP Response Decode | **6.78M ops/sec** | 1.94M (0.29x) | 0.67M (0.10x) |
363
- | FC01 Read Coils | **85,170 ops/sec** | 617 (0.01x) | 861 (0.01x) |
421
+ class MyPhysicalLayer extends AbstractPhysicalLayer { /* open / close */ }
422
+ class MyConnection extends AbstractPhysicalConnection { /* write / destroy, emit 'data' */ }
364
423
 
365
- <details>
366
- <summary>Full benchmark results</summary>
424
+ const master = new ModbusMaster({
425
+ physical: { type: 'CUSTOM', layer: new MyPhysicalLayer() },
426
+ protocol: { type: 'TCP' },
427
+ });
428
+ ```
367
429
 
368
- Node.js v24.16.0 · AMD Ryzen 7 9800X3D · linux x64 · 3 runs × 120 s · [full report](./benchmark/RESULTS.md)
430
+ See [`examples/websocket`](./examples/websocket) for a complete **Modbus TCP over WebSocket** implementation.
369
431
 
370
- ### TCP Throughput (Single Connection)
432
+ See [`examples/bluetooth`](./examples/bluetooth) for a **Modbus TCP over BLE** implementation using the Nordic UART Service (NUS).
371
433
 
372
- ```
373
- njs-modbus │ ██████████████████████████████ 70,544 ops/sec 🏆 p99: 53 µs CPU: 91 µs/op
374
- jsmodbus │ █████████████████░░░░░░░░░░░░░ 39,827 ops/sec (0.56x) p99: 98 µs CPU: 161 µs/op
375
- modbus-serial │ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 786 ops/sec (0.01x) p99: 2,331 µs CPU: 8,176 µs/op
376
- ```
434
+ ## Performance
377
435
 
378
- ### Concurrent (8 Connections)
436
+ Benchmarked against [jsmodbus](https://github.com/Cloud-Automation/node-modbus) v4.0.10 and [modbus-serial](https://github.com/yaacov/node-modbus-serial) v8.0.25.
379
437
 
380
- ```
381
- njs-modbus │ ██████████████████████████████ 75,810 ops/sec 🏆 p99: 258 µs CPU: 91 µs/op
382
- jsmodbus │ █████████████████░░░░░░░░░░░░░ 41,754 ops/sec (0.55x) p99: 543 µs CPU: 167 µs/op
383
- modbus-serial │ ██░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 5,620 ops/sec (0.07x) p99: 3,180 µs CPU: 1,237 µs/op
384
- ```
438
+ Environment: AMD Ryzen 7 9800X3D 8-Core Processor · Node.js v24.16.0 · [full report](./benchmark/report_presentation.md)
385
439
 
386
- ### RTU Serial (115200 baud via socat PTY)
440
+ ### End-to-end TCP
387
441
 
388
- ```
389
- njs-modbus │ 44 ops/sec p99: 993 µs CPU: 729 µs/op GC: 2,613 ns/op
390
- jsmodbus │ 44 ops/sec p99: 1,019 µs CPU: 694 µs/op GC: 2,609 ns/op
391
- modbus-serial 45 ops/sec p99: 3,228 µs CPU: 765 µs/op GC: 3,864 ns/op
392
- ```
442
+ | Metric | njs-modbus | jsmodbus | modbus-serial |
443
+ |--------|-----------|----------|---------------|
444
+ | Single connection | **74,964 ops/sec** 🏆 | 55,996 (0.75x) | 853 (0.01x) |
445
+ | 8 concurrent clients | **10,188 ops/sec** 🏆 | 5,918 (0.58x) | 728 (0.07x) |
446
+ | P99 latency (single) | **17 µs** 🏆 | 15 µs (0.88x) | 1,206 µs (70.9x) |
393
447
 
394
- ### Frame Encode / Decode (CPU Micro-benchmark)
448
+ ### Frame encode / decode (CPU micro-benchmark)
395
449
 
396
- ```
397
- tcpReqEncode: njs-modbus 6.90M ops/sec 🏆 jsmodbus 0.78x modbus-serial 0.19x
398
- tcpResEncode: njs-modbus 6.57M ops/sec 🏆 jsmodbus 0.19x modbus-serial 0.18x
399
- tcpReqDecode: njs-modbus 6.80M ops/sec 🏆 jsmodbus 0.81x modbus-serial 0.17x
400
- tcpResDecode: njs-modbus 6.78M ops/sec 🏆 jsmodbus 0.29x modbus-serial 0.10x
401
- rtuReqEncode: njs-modbus 7.26M ops/sec 🏆 jsmodbus 0.35x modbus-serial 0.76x
402
- rtuReqDecode: njs-modbus 3.97M ops/sec jsmodbus 1.21x modbus-serial 1.78x 🏆
403
- asciiReqEncode: njs-modbus 5.64M ops/sec 🏆 modbus-serial 0.57x
404
- asciiReqDecode: njs-modbus 3.37M ops/sec 🏆 modbus-serial 0.34x
405
- ```
450
+ | Benchmark | njs-modbus | jsmodbus | modbus-serial |
451
+ |-----------|-----------|----------|---------------|
452
+ | TCP request encode | **9.61M ops/sec** 🏆 | 6.32M (0.66x) | 0.68M (0.07x) |
453
+ | TCP response encode | **7.51M ops/sec** 🏆 | 1.18M (0.16x) | 0.44M (0.06x) |
454
+ | TCP response decode | **8.55M ops/sec** 🏆 | 2.04M (0.24x) | 0.70M (0.08x) |
455
+ | RTU request encode | **9.42M ops/sec** 🏆 | 3.03M (0.32x) | 6.57M (0.70x) |
456
+ | ASCII request encode | **7.09M ops/sec** 🏆 | — | 4.11M (0.58x) |
457
+ | ASCII request decode | **4.91M ops/sec** 🏆 | — | 1.19M (0.24x) |
406
458
 
407
- ### Per-Function-Code TCP Throughput (Normal Payload)
459
+ ### Per-function-code TCP throughput (100 coils / 50 registers)
408
460
 
409
461
  | Function Code | njs-modbus | jsmodbus | modbus-serial |
410
462
  |---------------|-----------|----------|---------------|
411
- | FC01 Read Coils | **85,170** 🏆 | 617 (0.01x) | 861 (0.01x) |
412
- | FC02 Read Discrete Inputs | **85,570** 🏆 | 525 (0.01x) | 861 (0.01x) |
413
- | FC03 Read Holding Registers | **75,344** 🏆 | 40,081 (0.53x) | 852 (0.01x) |
414
- | FC04 Read Input Registers | **75,757** 🏆 | 49,524 (0.65x) | 866 (0.01x) |
415
- | FC05 Write Single Coil | **92,852** 🏆 | 53,521 (0.58x) | 871 (0.01x) |
416
- | FC06 Write Single Register | **92,864** 🏆 | 53,684 (0.58x) | 872 (0.01x) |
417
- | FC15 Write Multiple Coils | **86,434** 🏆 | 319 (0.00x) | 869 (0.01x) |
418
- | FC16 Write Multiple Registers | **76,053** 🏆 | 39,356 (0.52x) | 852 (0.01x) |
419
- | FC17 Report Server ID | **74,697** 🏆 | — | — |
420
- | FC22 Mask Write Register | **90,717** 🏆 | | |
421
- | FC23 Read/Write Multiple Registers | **85,652** 🏆 | — | — |
422
- | FC43 Read Device Identification | **78,030** 🏆 | — | 865 (0.01x) |
423
-
424
- > FC17 / FC22 / FC23 are not supported by jsmodbus or modbus-serial.
425
-
426
- </details>
463
+ | FC01 Read Coils | **75,430** 🏆 | 488 (0.01x) | 859 (0.01x) |
464
+ | FC03 Read Holding Registers | **82,028** 🏆 | 46,596 (0.57x) | 864 (0.01x) |
465
+ | FC06 Write Single Register | **86,110** 🏆 | 51,084 (0.59x) | 871 (0.01x) |
466
+ | FC15 Write Multiple Coils | **78,766** 🏆 | | 867 (0.01x) |
467
+ | FC17 Report Server ID | **79,185** 🏆 | | |
468
+ | FC22 Mask Write Register | **73,060** 🏆 | | |
469
+ | FC23 Read/Write Multiple Registers | **68,429** 🏆 | | |
470
+ | FC43 Read Device Identification | **62,176** 🏆 | | 868 (0.01x) |
471
+
472
+ > FC17 / FC22 / FC23 / FC43 are not supported by jsmodbus.
427
473
 
428
474
  ## License
429
475