njs-modbus 2.0.1 → 3.0.2

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.
Files changed (56) hide show
  1. package/README.md +265 -154
  2. package/README.zh-CN.md +314 -0
  3. package/dist/index.cjs +1887 -1074
  4. package/dist/index.d.ts +369 -217
  5. package/dist/index.mjs +1884 -1073
  6. package/dist/src/error-code.d.ts +2 -24
  7. package/dist/src/layers/application/abstract-application-layer.d.ts +12 -7
  8. package/dist/src/layers/application/ascii-application-layer.d.ts +8 -9
  9. package/dist/src/layers/application/rtu-application-layer.d.ts +21 -44
  10. package/dist/src/layers/application/tcp-application-layer.d.ts +8 -6
  11. package/dist/src/layers/physical/abstract-physical-layer.d.ts +40 -12
  12. package/dist/src/layers/physical/index.d.ts +9 -3
  13. package/dist/src/layers/physical/serial-physical-layer.d.ts +43 -13
  14. package/dist/src/layers/physical/tcp-client-physical-layer.d.ts +12 -10
  15. package/dist/src/layers/physical/tcp-physical-connection.d.ts +17 -0
  16. package/dist/src/layers/physical/tcp-server-physical-layer.d.ts +23 -12
  17. package/dist/src/layers/physical/udp-client-physical-layer.d.ts +35 -0
  18. package/dist/src/layers/physical/udp-server-physical-layer.d.ts +54 -0
  19. package/dist/src/layers/physical/utils.d.ts +39 -0
  20. package/dist/src/layers/physical/vars.d.ts +11 -0
  21. package/dist/src/master/master.d.ts +45 -18
  22. package/dist/src/slave/slave.d.ts +57 -33
  23. package/dist/src/types.d.ts +3 -3
  24. package/dist/src/utils/index.d.ts +4 -2
  25. package/dist/src/utils/rtu-timing.d.ts +49 -0
  26. package/dist/src/utils/whitelist.d.ts +11 -0
  27. package/package.json +9 -7
  28. package/dist/src/layers/physical/udp-physical-layer.d.ts +0 -42
  29. package/dist/src/utils/genConnectionId.d.ts +0 -2
  30. package/dist/test/adu-buffer.test.d.ts +0 -1
  31. package/dist/test/ascii-hex-sentry.test.d.ts +0 -1
  32. package/dist/test/ascii-hex-validation.test.d.ts +0 -1
  33. package/dist/test/ascii-tcp-fragmentation.test.d.ts +0 -1
  34. package/dist/test/check-range.test.d.ts +0 -1
  35. package/dist/test/fallback-atomic.test.d.ts +0 -1
  36. package/dist/test/fallback-serial.test.d.ts +0 -1
  37. package/dist/test/fc17-serverid-validation.test.d.ts +0 -1
  38. package/dist/test/fc43-conformity.test.d.ts +0 -1
  39. package/dist/test/fc43-utf8-objects.test.d.ts +0 -1
  40. package/dist/test/gen-connection-id.test.d.ts +0 -1
  41. package/dist/test/helpers/raw-tcp.d.ts +0 -38
  42. package/dist/test/master-concurrent.test.d.ts +0 -1
  43. package/dist/test/modbus-error.test.d.ts +0 -1
  44. package/dist/test/physical-lifecycle.test.d.ts +0 -1
  45. package/dist/test/predict-rtu.test.d.ts +0 -1
  46. package/dist/test/rtu-custom-fc.test.d.ts +0 -1
  47. package/dist/test/rtu-pool-overflow.test.d.ts +0 -1
  48. package/dist/test/rtu-t15-timing.test.d.ts +0 -1
  49. package/dist/test/rtu-t35-default.test.d.ts +0 -1
  50. package/dist/test/rtu-t35-strict.test.d.ts +0 -1
  51. package/dist/test/rtu-tcp-fragmentation.test.d.ts +0 -1
  52. package/dist/test/serial-e2e.test.d.ts +0 -1
  53. package/dist/test/slave-multi-connection.test.d.ts +0 -1
  54. package/dist/test/slave.test.d.ts +0 -1
  55. package/dist/test/tcp-fragmentation.test.d.ts +0 -1
  56. package/dist/test/udp-multi-client.test.d.ts +0 -1
@@ -0,0 +1,314 @@
1
+ # njs-modbus
2
+
3
+ [English](./README.md) | 简体中文
4
+
5
+ Node.js 的纯 JavaScript Modbus 实现。
6
+
7
+ <!-- prettier-ignore-start -->
8
+ [![npm download](http://img.shields.io/npm/dw/njs-modbus.svg?style=flat-square)](http://www.npm-stats.com/~packages/njs-modbus)
9
+ [![npm latest package](http://img.shields.io/npm/v/njs-modbus/latest.svg?style=flat-square)](https://www.npmjs.com/package/njs-modbus)
10
+ [![npm bundle size](https://img.shields.io/bundlephobia/minzip/njs-modbus?style=flat-square)](https://bundlephobia.com/package/njs-modbus)
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
+ <!-- prettier-ignore-end -->
13
+
14
+ ## 特性
15
+
16
+ - **协议:** Modbus TCP、RTU、ASCII
17
+ - **传输:** 串口、TCP 客户端/服务端、UDP 客户端/服务端
18
+ - **主站:** FIFO 或流水线并发请求(仅 TCP)
19
+ - **从站:** 每连接 FIFO 或并发处理(仅 TCP)
20
+ - **自定义功能码**,支持可插拔帧解析
21
+ - **广播**(单元号 = 0)
22
+ - **完整 TypeScript**
23
+ - **零运行时依赖**(仅在使用串口时 peer-depends `serialport`)
24
+
25
+ | 码值 | 名称 |
26
+ |------|------|
27
+ | 01 | 读取线圈 |
28
+ | 02 | 读取离散输入 |
29
+ | 03 | 读取保持寄存器 |
30
+ | 04 | 读取输入寄存器 |
31
+ | 05 | 写单个线圈 |
32
+ | 06 | 写单个寄存器 |
33
+ | 15 | 写多个线圈 |
34
+ | 16 | 写多个寄存器 |
35
+ | 17 | 报告服务器 ID |
36
+ | 22 | 掩码写寄存器 |
37
+ | 23 | 读/写多个寄存器 |
38
+ | 43/14 | 读取设备标识 |
39
+
40
+ ## 安装
41
+
42
+ ```bash
43
+ npm install njs-modbus
44
+ ```
45
+
46
+ 使用串口传输时,还需安装 peer dependency:
47
+
48
+ ```bash
49
+ npm install serialport
50
+ ```
51
+
52
+ ## 快速开始
53
+
54
+ ### TCP 主站
55
+
56
+ ```typescript
57
+ import { ModbusMaster } from 'njs-modbus';
58
+
59
+ const master = new ModbusMaster({
60
+ physical: { type: 'TCP_CLIENT' },
61
+ protocol: { type: 'TCP' },
62
+ });
63
+
64
+ await master.open({ port: 502, host: '192.168.1.10' });
65
+
66
+ const res = await master.readHoldingRegisters(1, 0, 10);
67
+ console.log(res.data);
68
+
69
+ await master.close();
70
+ ```
71
+
72
+ ### TCP 从站
73
+
74
+ ```typescript
75
+ import { ModbusSlave } from 'njs-modbus';
76
+
77
+ const slave = new ModbusSlave({
78
+ physical: { type: 'TCP_SERVER' },
79
+ protocol: { type: 'TCP' },
80
+ });
81
+
82
+ slave.add({
83
+ unit: 1,
84
+ readHoldingRegisters: (address, length) => {
85
+ return Array.from({ length }, (_, i) => address + i);
86
+ },
87
+ });
88
+
89
+ await slave.open({ port: 502 });
90
+ ```
91
+
92
+ ### 串口 RTU 主站
93
+
94
+ ```typescript
95
+ const master = new ModbusMaster({
96
+ physical: {
97
+ type: 'SERIAL',
98
+ opts: { path: '/dev/ttyUSB0', baudRate: 9600 },
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
+ ## 物理层
118
+
119
+ 所有物理层暴露 `open()` / `close()`、`state` 属性,以及事件:`'open'`、`'connect'`、`'close'`、`'error'`。
120
+
121
+ | 类型 | 类 | `open(...)` 参数 |
122
+ |------|-------|-----------------|
123
+ | `SERIAL` | `SerialPhysicalLayer` | 无 |
124
+ | `TCP_CLIENT` | `TcpClientPhysicalLayer` | `SocketConnectOpts` |
125
+ | `TCP_SERVER` | `TcpServerPhysicalLayer` | `ListenOptions` |
126
+ | `UDP_CLIENT` | `UdpClientPhysicalLayer` | `{ port, address }` |
127
+ | `UDP_SERVER` | `UdpServerPhysicalLayer` | `BindOptions` |
128
+
129
+ ### 服务端配置
130
+
131
+ ```typescript
132
+ const slave = new ModbusSlave({
133
+ physical: {
134
+ type: 'TCP_SERVER',
135
+ opts: {
136
+ whitelist: ['192.168.1.10', '10.0.0.5'], // IPv4-mapped IPv6 自动规范化
137
+ maxConnections: 10,
138
+ idleTimeout: 30000, // 驱逐不活跃连接,0 = 禁用
139
+ },
140
+ },
141
+ protocol: { type: 'TCP' },
142
+ });
143
+ ```
144
+
145
+ ### 物理层选项
146
+
147
+ | 选项 | 类型 | 说明 |
148
+ |--------|------|-------------|
149
+ | `whitelist` | `string[]` | 允许的客户端 IP。`::ffff:` 前缀自动剥离。 |
150
+ | `maxConnections` | `number` | 最大并发连接数。超出后新连接静默丢弃。 |
151
+ | `idleTimeout` | `number` | 空闲超时(毫秒),超时后驱逐连接。默认 `30000`,传 `0` 禁用。 |
152
+ | `socketOpts` / `serverOpts` | `object` | 透传给 Node.js `createSocket()` / `createServer()`。 |
153
+
154
+ ## 主站 API
155
+
156
+ ```typescript
157
+ new ModbusMaster({
158
+ physical: { type: 'TCP_CLIENT' },
159
+ protocol: { type: 'TCP' }, // 'TCP' | 'RTU' | 'ASCII'
160
+ timeout?: 1000, // 单次请求超时(毫秒)
161
+ concurrent?: false, // 流水线模式(仅 TCP)
162
+ })
163
+ ```
164
+
165
+ RTU / ASCII 帧选项:
166
+
167
+ ```typescript
168
+ protocol: {
169
+ type: 'RTU',
170
+ opts: {
171
+ intervalBetweenFrames: { unit: 'ms', value: 20 },
172
+ interCharTimeout: { unit: 'bit', value: 10 },
173
+ poolSize: 1024,
174
+ },
175
+ }
176
+ ```
177
+
178
+ ### 方法
179
+
180
+ | 方法 | 说明 |
181
+ |--------|-------------|
182
+ | `open(...args)` | 打开物理层。只能调用一次;`close()` 后实例永久失效。 |
183
+ | `close()` | 立即关闭。进行中和队列中的请求会被 reject。 |
184
+ | `readCoils(unit, address, length, timeout?)` | FC 01 |
185
+ | `readDiscreteInputs(unit, address, length, timeout?)` | FC 02 |
186
+ | `readHoldingRegisters(unit, address, length, timeout?)` | FC 03 |
187
+ | `readInputRegisters(unit, address, length, timeout?)` | FC 04 |
188
+ | `writeSingleCoil(unit, address, value, timeout?)` | FC 05 |
189
+ | `writeSingleRegister(unit, address, value, timeout?)` | FC 06 |
190
+ | `writeMultipleCoils(unit, address, values, timeout?)` | FC 15 |
191
+ | `writeMultipleRegisters(unit, address, values, timeout?)` | FC 16 |
192
+ | `reportServerId(unit, serverIdLength?, timeout?)` | FC 17 |
193
+ | `maskWriteRegister(unit, address, andMask, orMask, timeout?)` | FC 22 |
194
+ | `readAndWriteMultipleRegisters(unit, read, write, timeout?)` | FC 23 |
195
+ | `readDeviceIdentification(unit, readDeviceIDCode, objectId, timeout?)` | FC 43/14 |
196
+ | `sendCustomFC(unit, fc, data, timeout?)` | 发送用户自定义功能码 |
197
+ | `addCustomFunctionCode(cfc)` | 注册自定义功能码用于帧解析 |
198
+ | `removeCustomFunctionCode(fc)` | 注销自定义功能码 |
199
+
200
+ 广播(`unit = 0`)resolve `void` — 从站不响应。
201
+
202
+ ## 从站 API
203
+
204
+ ```typescript
205
+ new ModbusSlave({
206
+ physical: { type: 'TCP_SERVER' },
207
+ protocol: { type: 'TCP' },
208
+ concurrent?: false, // 每连接并发(仅 TCP)
209
+ })
210
+ ```
211
+
212
+ ### 注册模型
213
+
214
+ ```typescript
215
+ slave.add({
216
+ unit: 1,
217
+
218
+ // 可选:在默认分发前拦截任意功能码
219
+ interceptor?: (fc, data) => Buffer | undefined,
220
+
221
+ readCoils?: (address, length) => boolean[],
222
+ readDiscreteInputs?: (address, length) => boolean[],
223
+ readHoldingRegisters?: (address, length) => number[],
224
+ readInputRegisters?: (address, length) => number[],
225
+
226
+ writeSingleCoil?: (address, value) => void,
227
+ writeMultipleCoils?: (address, values) => void,
228
+
229
+ writeSingleRegister?: (address, value) => void,
230
+ writeMultipleRegisters?: (address, values) => void,
231
+
232
+ maskWriteRegister?: (address, andMask, orMask) => void,
233
+
234
+ reportServerId?: () => ServerId,
235
+ readDeviceIdentification?: () => { [index: number]: string },
236
+
237
+ // 可选地址范围校验
238
+ getAddressRange?: () => ({
239
+ discreteInputs?: [number, number] | [number, number][];
240
+ coils?: [number, number] | [number, number][];
241
+ inputRegisters?: [number, number] | [number, number][];
242
+ holdingRegisters?: [number, number] | [number, number][];
243
+ }),
244
+ });
245
+ ```
246
+
247
+ 缺失的处理器返回 `ILLEGAL_FUNCTION`。越界地址返回 `ILLEGAL_DATA_ADDRESS`。处理器抛出的异常变为 `SERVER_DEVICE_FAILURE`,除非错误是 `ModbusError`。
248
+
249
+ | 方法 | 说明 |
250
+ |--------|-------------|
251
+ | `add(model)` | 注册从站模型 |
252
+ | `remove(unit)` | 移除从站模型 |
253
+ | `open(...args)` | 打开物理层。一次性:close() 后不可重新打开。 |
254
+ | `close()` | 立即关闭 |
255
+ | `addCustomFunctionCode(cfc)` | 注册自定义功能码 |
256
+ | `removeCustomFunctionCode(fc)` | 注销自定义功能码 |
257
+
258
+ ## 自定义功能码
259
+
260
+ ```typescript
261
+ import type { CustomFunctionCode } from 'njs-modbus';
262
+
263
+ const cfc: CustomFunctionCode = {
264
+ fc: 0x50,
265
+ predictRequestLength: (buffer) => {
266
+ if (buffer.length < 2) return null;
267
+ return 4 + buffer[1];
268
+ },
269
+ predictResponseLength: (buffer) => {
270
+ if (buffer.length < 2) return null;
271
+ return 4 + buffer[1];
272
+ },
273
+ handle: async (data, unit) => {
274
+ return Buffer.from([data[1], data[0]]);
275
+ },
276
+ };
277
+
278
+ // 从站:帧解析 + 分发
279
+ slave.addCustomFunctionCode(cfc);
280
+
281
+ // 主站:仅帧解析
282
+ master.addCustomFunctionCode(cfc);
283
+ const response = await master.sendCustomFC(1, 0x50, [0xab, 0xcd]);
284
+ ```
285
+
286
+ ## 错误处理
287
+
288
+ ```typescript
289
+ import { ErrorCode, ModbusError, getErrorByCode } from 'njs-modbus';
290
+
291
+ // ErrorCode.ILLEGAL_FUNCTION = 0x01
292
+ // ErrorCode.ILLEGAL_DATA_ADDRESS = 0x02
293
+ // ErrorCode.ILLEGAL_DATA_VALUE = 0x03
294
+ // ErrorCode.SERVER_DEVICE_FAILURE = 0x04
295
+ // ErrorCode.ACKNOWLEDGE = 0x05
296
+ // ErrorCode.SERVER_DEVICE_BUSY = 0x06
297
+ // ErrorCode.MEMORY_PARITY_ERROR = 0x08
298
+ // ErrorCode.GATEWAY_PATH_UNAVAILABLE = 0x0a
299
+ // ErrorCode.GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND = 0x0b
300
+
301
+ slave.add({
302
+ unit: 1,
303
+ readHoldingRegisters: (address) => {
304
+ if (address > 100) {
305
+ throw getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS);
306
+ }
307
+ return [42];
308
+ },
309
+ });
310
+ ```
311
+
312
+ ## 许可证
313
+
314
+ [![license](https://img.shields.io/github/license/xiejay97/njs-modbus?style=flat-square)](/LICENSE)