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
package/dist/index.d.ts CHANGED
@@ -1,9 +1,10 @@
1
+ import { SerialPort } from 'serialport';
2
+ import { Socket, SocketConstructorOpts, SocketConnectOpts, Server, ServerOpts, ListenOptions } from 'node:net';
3
+ import { Socket as Socket$1, SocketOptions, BindOptions } from 'node:dgram';
1
4
  import EventEmitter from 'node:events';
2
- import { SocketConstructorOpts, SocketConnectOpts, NetConnectOpts, ListenOptions } from 'node:net';
3
- import { SocketOptions, BindOptions } from 'node:dgram';
4
5
 
5
6
  type Callback$1<T> = (error: Error | null, value: T) => void;
6
- type FConvertPromise<F extends (...args: any) => any> = F extends (...args: infer A) => infer R ? ((...args: A) => Promise<R>) | ((...args: A) => R) : never;
7
+ type MaybeAsyncFunction<F extends (...args: any) => any> = F extends (...args: infer A) => infer R ? ((...args: A) => Promise<R>) | ((...args: A) => R) : never;
7
8
  interface ApplicationDataUnit {
8
9
  transaction?: number;
9
10
  unit: number;
@@ -12,7 +13,7 @@ interface ApplicationDataUnit {
12
13
  }
13
14
  interface ServerId {
14
15
  /** Server ID; may be 1 byte (number) or multi-byte (number[]) per device spec. */
15
- serverId?: number | number[];
16
+ serverId?: number[];
16
17
  runIndicatorStatus?: boolean;
17
18
  additionalData?: number[];
18
19
  }
@@ -54,7 +55,7 @@ interface CustomFunctionCode {
54
55
  * Throwing inside `handle` is turned into a Modbus exception response by the slave.
55
56
  * If `handle` is omitted, the slave returns an ILLEGAL_FUNCTION exception for this FC.
56
57
  */
57
- handle?: FConvertPromise<(data: Buffer, unit: number) => Buffer>;
58
+ handle?: MaybeAsyncFunction<(data: Buffer, unit: number) => Buffer>;
58
59
  }
59
60
 
60
61
  declare enum ErrorCode {
@@ -68,31 +69,9 @@ declare enum ErrorCode {
68
69
  GATEWAY_PATH_UNAVAILABLE = 10,
69
70
  GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND = 11
70
71
  }
71
- /** Internal error codes for programmatic error handling. */
72
- declare const ModbusErrorCode: {
73
- readonly TIMEOUT: "ETIMEOUT";
74
- readonly INVALID_RESPONSE: "EINVALID_RESPONSE";
75
- readonly INSUFFICIENT_DATA: "EINSUFFICIENT_DATA";
76
- readonly MASTER_CLOSED: "EMASTER_CLOSED";
77
- readonly MASTER_DESTROYED: "EMASTER_DESTROYED";
78
- readonly CONCURRENT_NOT_TCP: "ECONCURRENT_NOT_TCP";
79
- readonly PORT_DESTROYED: "EPORT_DESTROYED";
80
- readonly PORT_ALREADY_OPEN: "EPORT_ALREADY_OPEN";
81
- readonly PORT_NOT_OPEN: "EPORT_NOT_OPEN";
82
- readonly NOT_SUPPORTED: "ENOT_SUPPORTED";
83
- readonly INVALID_DATA: "EINVALID_DATA";
84
- readonly INVALID_HEX: "EINVALID_HEX";
85
- readonly CRC_MISMATCH: "ECRC_MISMATCH";
86
- readonly LRC_MISMATCH: "ELRC_MISMATCH";
87
- readonly INCOMPLETE_FRAME: "EINCOMPLETE_FRAME";
88
- readonly T1_5_EXCEEDED: "ET1_5_EXCEEDED";
89
- readonly UNKNOWN_FC: "EUNKNOWN_FC";
90
- readonly INVALID_ROLE: "EINVALID_ROLE";
91
- readonly RANGE: "ERANGE";
92
- };
93
72
  declare class ModbusError extends Error {
94
- readonly code: string;
95
- constructor(code: string, message?: string);
73
+ readonly code: ErrorCode;
74
+ constructor(code: ErrorCode, message?: string);
96
75
  }
97
76
  declare function getErrorByCode(code: ErrorCode): ModbusError;
98
77
  declare function getCodeByError(err: Error): ErrorCode;
@@ -145,24 +124,16 @@ declare const LIMITS: {
145
124
  readonly RW_REGISTERS_WRITE_MAX: 121;
146
125
  };
147
126
 
148
- interface PhysicalConnection {
149
- readonly id: string | number;
127
+ declare enum PhysicalState {
128
+ OPENING = "opening",
129
+ OPEN = "open",
130
+ CLOSING = "closing",
131
+ CLOSED = "closed"
150
132
  }
151
- interface AbstractPhysicalLayerEvents {
152
- data: [data: Buffer, response: (data: Buffer) => Promise<void>, connection: PhysicalConnection];
153
- write: [data: Buffer];
154
- error: [error: Error];
155
- 'connection-close': [connection: PhysicalConnection];
156
- close: [];
157
- }
158
- declare abstract class AbstractPhysicalLayer extends EventEmitter<AbstractPhysicalLayerEvents> {
159
- abstract readonly TYPE: 'SERIAL' | 'NET';
160
- abstract readonly isOpen: boolean;
161
- abstract readonly destroyed: boolean;
162
- abstract open(...args: any[]): Promise<void>;
163
- abstract write(data: Buffer): Promise<void>;
164
- abstract close(): Promise<void>;
165
- abstract destroy(): Promise<void>;
133
+ declare enum PhysicalConnectionState {
134
+ CONNECTED = "connected",
135
+ DESTROYING = "destroying",
136
+ DESTROYED = "destroyed"
166
137
  }
167
138
 
168
139
  interface SerialPhysicalLayerOptions {
@@ -172,132 +143,251 @@ interface SerialPhysicalLayerOptions {
172
143
  * The baud rate of the port to be opened. This should match one of the commonly available baud rates, such as 110, 300, 1200, 2400, 4800, 9600, 14400, 19200, 38400, 57600, or 115200. Custom rates are supported best effort per platform. The device connected to the serial port is not guaranteed to support the requested baud rate, even if the port itself supports that baud rate.
173
144
  */
174
145
  baudRate: number;
175
- /** Must be one of these: 5, 6, 7, or 8 defaults to 8 */
146
+ /** Must be one of these: 5, 6, 7, or 8. Defaults to 8 */
176
147
  dataBits?: 5 | 6 | 7 | 8;
177
148
  /** Prevent other processes from opening the port. Windows does not currently support `false`. Defaults to true */
178
149
  lock?: boolean;
179
- /** Must be 1, 1.5 or 2 defaults to 1 */
150
+ /** Must be 1, 1.5 or 2. Defaults to 1 */
180
151
  stopBits?: 1 | 1.5 | 2;
181
- parity?: string;
152
+ /** Device parity. Defaults to none */
153
+ parity?: 'none' | 'even' | 'odd' | 'mark' | 'space';
182
154
  /** Flow control Setting. Defaults to false */
183
155
  rtscts?: boolean;
184
156
  /** Flow control Setting. Defaults to false */
185
157
  xon?: boolean;
186
158
  /** Flow control Setting. Defaults to false */
187
159
  xoff?: boolean;
188
- /** Flow control Setting defaults to false*/
160
+ /** Flow control Setting. Defaults to false */
189
161
  xany?: boolean;
190
162
  /** drop DTR on close. Defaults to true */
191
163
  hupcl?: boolean;
164
+ /** The size of the read and write buffers. Defaults to 64k */
165
+ highWaterMark?: number;
166
+ /** Emit 'end' on port close. Defaults to false */
167
+ endOnClose?: boolean;
168
+ /** see `man termios`. Defaults to 1 (Darwin/Linux only) */
169
+ vmin?: number;
170
+ /** see `man termios`. Defaults to 0 (Darwin/Linux only) */
171
+ vtime?: number;
172
+ /** RTS mode. Defaults to handshake (Windows only) */
173
+ rtsMode?: 'handshake' | 'enable' | 'toggle';
192
174
  }
193
175
  declare class SerialPhysicalLayer extends AbstractPhysicalLayer {
194
- TYPE: 'SERIAL' | 'NET';
176
+ readonly TYPE: "SERIAL";
177
+ private _state;
178
+ private _connections;
195
179
  private _serialport;
196
- private _connection;
197
- private _isOpening;
198
- private _destroyed;
180
+ private _serialportOpts;
181
+ private _path;
199
182
  private _baudRate;
200
- get isOpen(): boolean;
201
- get destroyed(): boolean;
183
+ private _openPromise;
184
+ private _closePromise;
185
+ private _resolveClose;
186
+ private _cleanupFns;
187
+ get state(): PhysicalState;
188
+ get serialport(): SerialPort | null;
189
+ get path(): string;
202
190
  get baudRate(): number;
203
191
  constructor(options: SerialPhysicalLayerOptions);
204
192
  open(): Promise<void>;
205
- write(data: Buffer): Promise<void>;
206
193
  close(): Promise<void>;
207
- destroy(): Promise<void>;
208
194
  }
209
195
 
210
196
  declare class TcpClientPhysicalLayer extends AbstractPhysicalLayer {
211
- TYPE: 'SERIAL' | 'NET';
197
+ readonly TYPE: "TCP_CLIENT";
198
+ private _state;
199
+ private _connections;
212
200
  private _socket;
213
- private _connection;
214
- private _isOpen;
215
- private _isOpening;
216
- private _destroyed;
217
- private _socketOptions?;
218
- get isOpen(): boolean;
219
- get destroyed(): boolean;
201
+ private _socketOpts?;
202
+ private _openPromise;
203
+ private _closePromise;
204
+ private _resolveClose;
205
+ private _cleanupFns;
206
+ get state(): PhysicalState;
207
+ get socket(): Socket | null;
220
208
  constructor(options?: SocketConstructorOpts);
221
209
  open(options?: SocketConnectOpts): Promise<void>;
222
- write(data: Buffer): Promise<void>;
223
210
  close(): Promise<void>;
224
- destroy(): Promise<void>;
225
211
  }
226
212
 
213
+ interface TcpServerPhysicalLayerOptions {
214
+ /** Allowed client IP addresses. IPv4-mapped IPv6 addresses (e.g., ::ffff:192.168.1.1) are normalized before checking. */
215
+ whitelist?: string[];
216
+ /** Maximum number of concurrent connections. When exceeded, new connections are silently dropped by Node.js. */
217
+ maxConnections?: number;
218
+ /** Idle timeout in ms. Connection is destroyed if no data is received within this time. 0 = disabled. */
219
+ idleTimeout?: number;
220
+ /** Forwarded to `net.createServer()`. */
221
+ serverOpts?: ServerOpts;
222
+ }
227
223
  declare class TcpServerPhysicalLayer extends AbstractPhysicalLayer {
228
- TYPE: 'SERIAL' | 'NET';
229
- private _server;
230
- private _isOpen;
231
- private _isOpening;
232
- private _destroyed;
224
+ readonly TYPE: "TCP_SERVER";
225
+ private _state;
233
226
  private _connections;
234
- private _serverOptions?;
235
- get isOpen(): boolean;
236
- get destroyed(): boolean;
237
- constructor(options?: NetConnectOpts);
227
+ private _server;
228
+ private _opts;
229
+ private _openPromise;
230
+ private _closePromise;
231
+ private _resolveClose;
232
+ private _cleanupFns;
233
+ get state(): PhysicalState;
234
+ get server(): Server | null;
235
+ constructor(options?: TcpServerPhysicalLayerOptions);
238
236
  open(options?: ListenOptions): Promise<void>;
239
- write(data: Buffer): Promise<void>;
240
237
  close(): Promise<void>;
241
- destroy(): Promise<void>;
242
238
  }
243
239
 
244
- interface UdpPhysicalLayerOptions {
245
- /** dgram Socket creation options. */
246
- socket?: Partial<SocketOptions>;
247
- /**
248
- * Provided → client mode (send to this single remote, filter inbound).
249
- * Omitted → server mode (bind locally, accept from any rinfo).
250
- */
251
- remote?: {
240
+ declare class UdpClientPhysicalLayer extends AbstractPhysicalLayer {
241
+ readonly TYPE: "UDP_CLIENT";
242
+ private _state;
243
+ private _connections;
244
+ private _socket;
245
+ private _socketOpts;
246
+ private _openPromise;
247
+ private _closePromise;
248
+ private _resolveClose;
249
+ private _cleanupFns;
250
+ get state(): PhysicalState;
251
+ get socket(): Socket$1 | null;
252
+ constructor(options?: Partial<SocketOptions>);
253
+ open(remote?: {
252
254
  port?: number;
253
255
  address?: string;
254
- };
255
- /**
256
- * Server mode only. Each unique rinfo (`address:port`) gets its own
257
- * `PhysicalConnection`; if no datagram arrives within this many ms, the
258
- * connection is evicted (`connection-close` emitted, upper-layer framing
259
- * state released). Default 30000. Set 0 to disable eviction.
260
- */
256
+ }): Promise<void>;
257
+ close(): Promise<void>;
258
+ }
259
+
260
+ interface UdpServerPhysicalLayerOptions {
261
+ /** Allowed client IP addresses. IPv4-mapped IPv6 addresses (e.g., ::ffff:192.168.1.1) are normalized before checking. */
262
+ whitelist?: string[];
263
+ /** Maximum number of concurrent virtual client connections. When exceeded, datagrams from new clients are silently ignored. */
264
+ maxConnections?: number;
265
+ /** Connection idle timeout in ms. Pass `0` to disable eviction. Defaults to 30000. */
261
266
  idleTimeout?: number;
267
+ /** Forwarded to `dgram.createSocket()`. */
268
+ socketOpts?: Partial<SocketOptions>;
262
269
  }
263
- declare class UdpPhysicalLayer extends AbstractPhysicalLayer {
264
- TYPE: 'SERIAL' | 'NET';
265
- private _socket;
270
+ declare class UdpServerPhysicalLayer extends AbstractPhysicalLayer {
271
+ readonly TYPE: "UDP_SERVER";
272
+ private _state;
266
273
  private _connections;
267
- private _isOpen;
268
- private _isOpening;
269
- private _destroyed;
270
- private _socketOptions?;
271
- private _port;
272
- private _address?;
273
- private _idleTimeout;
274
- isServer: boolean;
275
- get isOpen(): boolean;
276
- get destroyed(): boolean;
277
- constructor(options?: UdpPhysicalLayerOptions);
274
+ private _socket;
275
+ private _opts;
276
+ private _openPromise;
277
+ private _closePromise;
278
+ private _resolveClose;
279
+ private _cleanupFns;
280
+ get state(): PhysicalState;
281
+ get socket(): Socket$1 | null;
282
+ constructor(options?: UdpServerPhysicalLayerOptions);
283
+ /**
284
+ * Bind the UDP socket and start accepting datagrams.
285
+ *
286
+ * @param options Bind options (port, address, etc.). Defaults to port 502.
287
+ * @param [idleTimeout=30000] Maximum idle time in milliseconds before an
288
+ * inactive client connection is evicted. Pass `0` to disable eviction
289
+ * (connections never time out). Disabling eviction may cause unbounded
290
+ * memory growth if the server sees many unique clients.
291
+ */
278
292
  open(options?: BindOptions): Promise<void>;
279
- private _handleMessage;
280
- write(data: Buffer): Promise<void>;
281
293
  close(): Promise<void>;
282
- destroy(): Promise<void>;
283
294
  }
284
295
 
296
+ interface AbstractPhysicalConnectionEvents {
297
+ data: [data: Buffer];
298
+ close: [];
299
+ }
300
+ /**
301
+ * A one-way data transmission channel.
302
+ *
303
+ * State transitions are unidirectional: CONNECTED → DESTROYING → DESTROYED.
304
+ * Once closed, the instance cannot be reused; create a new connection instead.
305
+ */
306
+ declare abstract class AbstractPhysicalConnection extends EventEmitter<AbstractPhysicalConnectionEvents> {
307
+ abstract readonly state: PhysicalConnectionState;
308
+ abstract readonly physicalLayer: AbstractPhysicalLayer;
309
+ abstract write(data: Buffer): Promise<void>;
310
+ abstract destroy(): Promise<void>;
311
+ }
312
+ interface AbstractPhysicalLayerEvents {
313
+ open: [];
314
+ connect: [connection: AbstractPhysicalConnection];
315
+ close: [];
316
+ error: [error: Error];
317
+ }
318
+ /**
319
+ * An abstraction over local hardware or network resources.
320
+ *
321
+ * `open()` acquires the resource (serial port, TCP socket, UDP socket, etc.).
322
+ * Once ready, it emits `connect` with an {@link AbstractPhysicalConnection},
323
+ * unifying serial, TCP client, TCP server, and UDP under a single
324
+ * "connection-oriented" model similar to a TCP server accepting sockets.
325
+ */
326
+ declare abstract class AbstractPhysicalLayer extends EventEmitter<AbstractPhysicalLayerEvents> {
327
+ abstract readonly TYPE: 'SERIAL' | 'TCP_CLIENT' | 'TCP_SERVER' | 'UDP_CLIENT' | 'UDP_SERVER';
328
+ is(type: 'SERIAL'): this is SerialPhysicalLayer;
329
+ is(type: 'TCP_CLIENT'): this is TcpClientPhysicalLayer;
330
+ is(type: 'TCP_SERVER'): this is TcpServerPhysicalLayer;
331
+ is(type: 'UDP_CLIENT'): this is UdpClientPhysicalLayer;
332
+ is(type: 'UDP_SERVER'): this is UdpServerPhysicalLayer;
333
+ abstract readonly state: PhysicalState;
334
+ abstract open(...args: any[]): Promise<void>;
335
+ abstract close(): Promise<void>;
336
+ }
337
+
338
+ type PhysicalConfig = {
339
+ type: 'SERIAL';
340
+ opts: SerialPhysicalLayerOptions;
341
+ } | {
342
+ type: 'TCP_CLIENT';
343
+ socketOpts?: SocketConstructorOpts;
344
+ } | {
345
+ type: 'TCP_SERVER';
346
+ opts?: TcpServerPhysicalLayerOptions;
347
+ } | {
348
+ type: 'UDP_CLIENT';
349
+ socketOpts?: Partial<SocketOptions>;
350
+ } | {
351
+ type: 'UDP_SERVER';
352
+ opts?: UdpServerPhysicalLayerOptions;
353
+ } | {
354
+ type: 'CUSTOM';
355
+ layer: AbstractPhysicalLayer;
356
+ };
357
+ type OpenArgs<T extends PhysicalConfig> = T extends {
358
+ type: 'SERIAL';
359
+ } ? Parameters<SerialPhysicalLayer['open']> : T extends {
360
+ type: 'TCP_CLIENT';
361
+ } ? Parameters<TcpClientPhysicalLayer['open']> : T extends {
362
+ type: 'TCP_SERVER';
363
+ } ? Parameters<TcpServerPhysicalLayer['open']> : T extends {
364
+ type: 'UDP_CLIENT';
365
+ } ? Parameters<UdpClientPhysicalLayer['open']> : T extends {
366
+ type: 'UDP_SERVER';
367
+ } ? Parameters<UdpServerPhysicalLayer['open']> : never;
368
+ declare function createPhysicalLayer(config: PhysicalConfig): AbstractPhysicalLayer;
369
+
285
370
  interface AbstractApplicationLayerEvents {
286
371
  framing: [frame: ApplicationDataUnit & {
287
372
  buffer: Buffer;
288
- }, response: (data: Buffer) => Promise<void>, connection: PhysicalConnection];
373
+ }];
289
374
  'framing-error': [error: Error];
290
375
  }
376
+ /**
377
+ * Application-layer protocol handler bound to a single physical connection.
378
+ *
379
+ * Its lifetime follows the channel: created when the underlying connection is
380
+ * established and discarded when the connection closes. Subclasses implement
381
+ * ASCII, RTU, or TCP framing rules.
382
+ */
291
383
  declare abstract class AbstractApplicationLayer extends EventEmitter<AbstractApplicationLayerEvents> {
292
- abstract readonly PROTOCOL: 'TCP' | 'RTU' | 'ASCII';
293
- private _role?;
294
- get role(): 'MASTER' | 'SLAVE';
295
- set role(value: 'MASTER' | 'SLAVE');
384
+ abstract readonly PROTOCOL: 'ASCII' | 'RTU' | 'TCP';
385
+ abstract ROLE: 'MASTER' | 'SLAVE';
386
+ abstract readonly connection: AbstractPhysicalConnection;
296
387
  flush(): void;
297
388
  addCustomFunctionCode(cfc: CustomFunctionCode): void;
298
389
  removeCustomFunctionCode(fc: number): void;
299
390
  abstract encode(data: ApplicationDataUnit): Buffer;
300
- abstract destroy(): void;
301
391
  }
302
392
 
303
393
  interface AsciiApplicationLayerOptions {
@@ -310,93 +400,103 @@ interface AsciiApplicationLayerOptions {
310
400
  }
311
401
  declare class AsciiApplicationLayer extends AbstractApplicationLayer {
312
402
  readonly PROTOCOL: "ASCII";
403
+ readonly ROLE: 'MASTER' | 'SLAVE';
313
404
  readonly lenientHex: boolean;
314
- private _states;
315
- private _removeAllListeners;
316
- private _destroyed;
317
- constructor(physicalLayer: SerialPhysicalLayer | TcpServerPhysicalLayer | TcpClientPhysicalLayer | UdpPhysicalLayer, options?: AsciiApplicationLayerOptions);
318
- private getState;
319
- flush(): void;
405
+ private _connection;
406
+ private _state;
407
+ private _cleanupFns;
408
+ get connection(): AbstractPhysicalConnection;
409
+ constructor(role: 'MASTER' | 'SLAVE', connection: AbstractPhysicalConnection, options?: AsciiApplicationLayerOptions);
320
410
  private framing;
411
+ flush(): void;
321
412
  encode(data: ApplicationDataUnit): Buffer;
322
- destroy(): void;
323
413
  }
324
414
 
325
415
  interface RtuApplicationLayerOptions {
416
+ /** Inter-frame silence in milliseconds (Modbus RTU t3.5). 0 = disabled (immediate parse). */
417
+ intervalBetweenFrames?: number;
418
+ /** Inter-character timeout in milliseconds (Modbus RTU t1.5). Opt-in. */
419
+ interCharTimeout?: number;
326
420
  /**
327
- * Inter-frame silence (Modbus RTU t3.5). Defaults to `{ unit: 'bit', value: 38.5 }`
328
- * for serial (3.5 character times × 11 bits/char per Modbus V1.02 §2.5.1.1);
329
- * ignored for TCP/UDP transports.
330
- * - `{ unit: 'bit', value: 38.5 }` — spec bit-time approximation
331
- * - `{ unit: 'ms', value: 20 }` — explicit milliseconds
332
- *
333
- * Per Modbus V1.02 §2.5.1.1, at baud rates > 19200 the spec uses a fixed
334
- * 1.75 ms regardless of the bit value supplied.
335
- */
336
- intervalBetweenFrames?: {
337
- unit: 'bit' | 'ms';
338
- value: number;
339
- };
340
- /**
341
- * Inter-character timeout (Modbus RTU t1.5). Opt-in; **disabled** by default
342
- * because Node.js `setTimeout` precision (~1 ms minimum, ~15.6 ms on Windows)
343
- * is too coarse to reliably honor t1.5 at common baud rates without
344
- * false-positive frame discards. When set, a mid-frame gap exceeding this
345
- * duration discards the in-progress buffer and emits `framing-error`.
346
- * Only takes effect on serial transports.
347
- *
348
- * - `{ unit: 'bit', value: 21 }` — bit-time approximation (~1.5 char times)
349
- * - `{ unit: 'ms', value: 1 }` — explicit milliseconds
350
- *
351
- * Per Modbus V1.02 §2.5.1.1, at baud rates > 19200 the spec uses a fixed
352
- * 0.75 ms regardless of the bit value supplied.
421
+ * Buffer pool size per connection (bytes). Defaults to `MAX_FRAME_LENGTH * 2`
422
+ * (512 bytes). Increase this if you expect frames larger than 256 bytes or
423
+ * heavy pipelining on a single connection.
353
424
  */
354
- interCharTimeout?: {
355
- unit: 'bit' | 'ms';
356
- value: number;
357
- };
425
+ poolSize?: number;
358
426
  }
359
427
  declare class RtuApplicationLayer extends AbstractApplicationLayer {
360
428
  readonly PROTOCOL: "RTU";
361
- private _states;
362
- private _customFunctionCodes;
363
- private _removeAllListeners;
429
+ readonly ROLE: 'MASTER' | 'SLAVE';
430
+ private _connection;
431
+ private _state;
432
+ private _poolSize;
364
433
  private _threePointFiveT;
365
434
  private _onePointFiveT;
366
- private _destroyed;
367
- constructor(physicalLayer: SerialPhysicalLayer | TcpServerPhysicalLayer | TcpClientPhysicalLayer | UdpPhysicalLayer, options?: RtuApplicationLayerOptions);
368
- private getState;
435
+ private _customFunctionCodes;
436
+ private _cleanupFns;
437
+ get connection(): AbstractPhysicalConnection;
438
+ constructor(role: 'MASTER' | 'SLAVE', connection: AbstractPhysicalConnection, options?: RtuApplicationLayerOptions);
369
439
  private clearStateTimers;
370
- private clearState;
371
- flush(): void;
372
- addCustomFunctionCode(cfc: CustomFunctionCode): void;
373
- removeCustomFunctionCode(fc: number): void;
374
440
  private flushBuffer;
441
+ private deliverFrame;
375
442
  private tryExtract;
376
443
  private checkExpected;
377
444
  private crcMatches;
378
- private deliverFrame;
445
+ flush(): void;
446
+ addCustomFunctionCode(cfc: CustomFunctionCode): void;
447
+ removeCustomFunctionCode(fc: number): void;
379
448
  encode(data: ApplicationDataUnit): Buffer;
380
- destroy(): void;
381
449
  }
382
450
 
383
451
  declare class TcpApplicationLayer extends AbstractApplicationLayer {
384
452
  readonly PROTOCOL: "TCP";
453
+ readonly ROLE: 'MASTER' | 'SLAVE';
454
+ private _connection;
385
455
  private _transactionId;
386
- private _buffers;
387
- private _removeAllListeners;
388
- private _destroyed;
389
- constructor(physicalLayer: TcpServerPhysicalLayer | TcpClientPhysicalLayer | UdpPhysicalLayer);
456
+ private _buffer;
457
+ private _cleanupFns;
458
+ get connection(): AbstractPhysicalConnection;
459
+ constructor(role: 'MASTER' | 'SLAVE', connection: AbstractPhysicalConnection);
390
460
  private tryExtract;
391
461
  private processFrame;
462
+ flush(): void;
392
463
  encode(data: ApplicationDataUnit): Buffer;
393
- destroy(): void;
394
464
  }
395
465
 
396
- interface ModbusMasterEvents {
397
- error: [error: Error];
398
- close: [];
466
+ /** User-facing RTU protocol options (supports both bit and ms units). */
467
+ interface RtuProtocolOptions {
468
+ /**
469
+ * Inter-frame silence (Modbus RTU t3.5).
470
+ * - `{ unit: 'bit', value: 38.5 }` — spec bit-time approximation (default when `baudRate` is provided)
471
+ * - `{ unit: 'ms', value: 20 }` — explicit milliseconds
472
+ *
473
+ * Per Modbus V1.02 §2.5.1.1, at baud rates > 19200 a fixed 1.75 ms is used
474
+ * regardless of the bit value.
475
+ */
476
+ intervalBetweenFrames?: {
477
+ unit: 'bit' | 'ms';
478
+ value: number;
479
+ };
480
+ /**
481
+ * Inter-character timeout (Modbus RTU t1.5). Opt-in; **disabled** by default.
482
+ * - `{ unit: 'bit', value: 21 }` — bit-time approximation (~1.5 char times)
483
+ * - `{ unit: 'ms', value: 1 }` — explicit milliseconds
484
+ *
485
+ * Per Modbus V1.02 §2.5.1.1, at baud rates > 19200 a fixed 0.75 ms is used
486
+ * regardless of the bit value.
487
+ */
488
+ interCharTimeout?: {
489
+ unit: 'bit' | 'ms';
490
+ value: number;
491
+ };
492
+ /**
493
+ * Buffer pool size per connection (bytes). Defaults to `MAX_FRAME_LENGTH * 2`
494
+ * (512 bytes). Increase this if you expect frames larger than 256 bytes or
495
+ * heavy pipelining on a single connection.
496
+ */
497
+ poolSize?: number;
399
498
  }
499
+
400
500
  interface ReturnValue<T> {
401
501
  transaction?: number;
402
502
  unit: number;
@@ -413,21 +513,34 @@ interface ModbusMasterOptions {
413
513
  * Default false (FIFO queue, requests are serialized).
414
514
  */
415
515
  concurrent?: boolean;
516
+ physical: PhysicalConfig;
517
+ protocol: {
518
+ type: 'RTU';
519
+ opts?: RtuProtocolOptions;
520
+ } | {
521
+ type: 'TCP';
522
+ } | {
523
+ type: 'ASCII';
524
+ opts?: AsciiApplicationLayerOptions;
525
+ };
416
526
  }
417
- declare class ModbusMaster<A extends AbstractApplicationLayer, P extends AbstractPhysicalLayer> extends EventEmitter<ModbusMasterEvents> {
418
- private applicationLayer;
419
- private physicalLayer;
527
+ declare class ModbusMaster<T extends ModbusMasterOptions = ModbusMasterOptions> extends EventEmitter<AbstractPhysicalLayerEvents> {
528
+ readonly timeout: number;
529
+ readonly concurrent: boolean;
420
530
  private _masterSession;
531
+ private _physicalLayer;
532
+ private _protocol;
533
+ private _appLayer?;
534
+ private _customFunctionCodes;
421
535
  private _queue;
422
536
  private _draining;
423
537
  private _nextTid;
424
- private _closed;
425
- private _cleanLevel;
426
- timeout: number;
427
- readonly concurrent: boolean;
428
- get isOpen(): boolean;
429
- get destroyed(): boolean;
430
- constructor(applicationLayer: A, physicalLayer: P, options?: ModbusMasterOptions);
538
+ private _cleanupFns;
539
+ private _closePromise;
540
+ get state(): PhysicalState;
541
+ get physicalLayer(): AbstractPhysicalLayer;
542
+ constructor(options: T);
543
+ private _createAppLayer;
431
544
  private send;
432
545
  private _drain;
433
546
  private _exchange;
@@ -489,9 +602,25 @@ declare class ModbusMaster<A extends AbstractApplicationLayer, P extends Abstrac
489
602
  sendCustomFC(unit: 0, fc: number, data: Buffer | number[], timeout?: number): Promise<void>;
490
603
  sendCustomFC(unit: number, fc: number, data: Buffer | number[], timeout?: number): Promise<Buffer>;
491
604
  private _clean;
492
- open(...args: Parameters<P['open']>): Promise<void>;
605
+ /**
606
+ * Open the underlying physical layer and begin accepting connections.
607
+ *
608
+ * A `ModbusMaster` instance can only be opened once. Once {@link close}
609
+ * is called — explicitly or because the physical layer disconnected —
610
+ * the instance is permanently closed and cannot be reopened.
611
+ * Create a new `ModbusMaster` if a new connection is required.
612
+ */
613
+ open(...args: OpenArgs<T['physical']>): Promise<void>;
614
+ private _close;
615
+ /**
616
+ * Permanently close the master and release all resources.
617
+ *
618
+ * After calling this method the instance is considered dead:
619
+ * - No further requests can be sent.
620
+ * - The instance cannot be reopened via {@link open}.
621
+ * - All event listeners registered on this master are removed.
622
+ */
493
623
  close(): Promise<void>;
494
- destroy(): Promise<void>;
495
624
  }
496
625
 
497
626
  type Frame = ApplicationDataUnit & {
@@ -518,27 +647,27 @@ interface ModbusSlaveModel {
518
647
  * If provide the return value, use this value as data of `PDU` to respond.
519
648
  * Otherwise keep the default read and write behavior.
520
649
  */
521
- interceptor?: FConvertPromise<(fc: number, data: Buffer) => Buffer | undefined>;
522
- readDiscreteInputs?: FConvertPromise<(address: number, length: number) => boolean[]>;
523
- readCoils?: FConvertPromise<(address: number, length: number) => boolean[]>;
524
- writeSingleCoil?: FConvertPromise<(address: number, value: boolean) => void>;
650
+ interceptor?: MaybeAsyncFunction<(fc: number, data: Buffer) => Buffer | undefined>;
651
+ readDiscreteInputs?: MaybeAsyncFunction<(address: number, length: number) => boolean[]>;
652
+ readCoils?: MaybeAsyncFunction<(address: number, length: number) => boolean[]>;
653
+ writeSingleCoil?: MaybeAsyncFunction<(address: number, value: boolean) => void>;
525
654
  /**
526
655
  * If omitted, defaults to loop and call `writeSingleCoil`.
527
656
  */
528
- writeMultipleCoils?: FConvertPromise<(address: number, value: boolean[]) => void>;
529
- readInputRegisters?: FConvertPromise<(address: number, length: number) => number[]>;
530
- readHoldingRegisters?: FConvertPromise<(address: number, length: number) => number[]>;
531
- writeSingleRegister?: FConvertPromise<(address: number, value: number) => void>;
657
+ writeMultipleCoils?: MaybeAsyncFunction<(address: number, value: boolean[]) => void>;
658
+ readInputRegisters?: MaybeAsyncFunction<(address: number, length: number) => number[]>;
659
+ readHoldingRegisters?: MaybeAsyncFunction<(address: number, length: number) => number[]>;
660
+ writeSingleRegister?: MaybeAsyncFunction<(address: number, value: number) => void>;
532
661
  /**
533
662
  * If omitted, defaults to loop and call `writeSingleRegister`.
534
663
  */
535
- writeMultipleRegisters?: FConvertPromise<(address: number, value: number[]) => void>;
664
+ writeMultipleRegisters?: MaybeAsyncFunction<(address: number, value: number[]) => void>;
536
665
  /**
537
666
  * If omitted, defaults to call `readHoldingRegisters` and `writeSingleRegister`.
538
667
  */
539
- maskWriteRegister?: FConvertPromise<(address: number, andMask: number, orMask: number) => void>;
540
- reportServerId?: FConvertPromise<() => ServerId>;
541
- readDeviceIdentification?: FConvertPromise<() => {
668
+ maskWriteRegister?: MaybeAsyncFunction<(address: number, andMask: number, orMask: number) => void>;
669
+ reportServerId?: MaybeAsyncFunction<() => ServerId>;
670
+ readDeviceIdentification?: MaybeAsyncFunction<() => {
542
671
  [index: number]: string;
543
672
  }>;
544
673
  getAddressRange?: () => {
@@ -548,10 +677,6 @@ interface ModbusSlaveModel {
548
677
  holdingRegisters?: [number, number] | [number, number][];
549
678
  };
550
679
  }
551
- interface ModbusSlaveEvents {
552
- error: [error: Error];
553
- close: [];
554
- }
555
680
  interface ModbusSlaveOptions {
556
681
  /**
557
682
  * Pipelined concurrent processing of requests within one connection.
@@ -559,19 +684,31 @@ interface ModbusSlaveOptions {
559
684
  * Default false — per-connection FIFO. RTU/ASCII + concurrent=true throws.
560
685
  */
561
686
  concurrent?: boolean;
687
+ physical: PhysicalConfig;
688
+ protocol: {
689
+ type: 'RTU';
690
+ opts?: RtuProtocolOptions;
691
+ } | {
692
+ type: 'TCP';
693
+ } | {
694
+ type: 'ASCII';
695
+ opts?: AsciiApplicationLayerOptions;
696
+ };
562
697
  }
563
- declare class ModbusSlave<A extends AbstractApplicationLayer, P extends AbstractPhysicalLayer> extends EventEmitter<ModbusSlaveEvents> {
564
- private applicationLayer;
565
- private physicalLayer;
566
- models: Map<number, ModbusSlaveModel>;
698
+ declare class ModbusSlave<T extends ModbusSlaveOptions = ModbusSlaveOptions> extends EventEmitter<AbstractPhysicalLayerEvents> {
699
+ readonly models: Map<number, ModbusSlaveModel>;
567
700
  readonly concurrent: boolean;
568
- private _queues;
701
+ private _physicalLayer;
702
+ private _protocol;
703
+ private _appLayers;
569
704
  private _customFunctionCodes;
570
705
  private _locks;
571
- private _cleanLevel;
572
- get isOpen(): boolean;
573
- get destroyed(): boolean;
574
- constructor(applicationLayer: A, physicalLayer: P, options?: ModbusSlaveOptions);
706
+ private _cleanupFns;
707
+ private _closePromise;
708
+ get state(): PhysicalState;
709
+ get physicalLayer(): AbstractPhysicalLayer;
710
+ constructor(options: T);
711
+ private _createAppLayer;
575
712
  private handleFC1;
576
713
  private handleFC2;
577
714
  private handleFC3;
@@ -588,17 +725,32 @@ declare class ModbusSlave<A extends AbstractApplicationLayer, P extends Abstract
588
725
  private _drain;
589
726
  private _processFrame;
590
727
  private _intercept;
591
- private withAddressLock;
728
+ private _withAddressLock;
592
729
  private _handleFC;
593
730
  add(model: ModbusSlaveModel): void;
594
731
  remove(unit: number): void;
595
732
  addCustomFunctionCode(cfc: CustomFunctionCode): void;
596
733
  removeCustomFunctionCode(fc: number): void;
597
- private _clean;
598
- open(...args: Parameters<P['open']>): Promise<void>;
734
+ /**
735
+ * Open the underlying physical layer and begin accepting connections.
736
+ *
737
+ * A `ModbusSlave` instance can only be opened once. Once {@link close}
738
+ * is called — explicitly or because the physical layer disconnected —
739
+ * the instance is permanently closed and cannot be reopened.
740
+ * Create a new `ModbusSlave` if a new server is required.
741
+ */
742
+ open(...args: OpenArgs<T['physical']>): Promise<void>;
743
+ private _close;
744
+ /**
745
+ * Permanently close the slave and release all resources.
746
+ *
747
+ * After calling this method the instance is considered dead:
748
+ * - No further requests can be processed.
749
+ * - The instance cannot be reopened via {@link open}.
750
+ * - All event listeners registered on this slave are removed.
751
+ */
599
752
  close(): Promise<void>;
600
- destroy(): Promise<void>;
601
753
  }
602
754
 
603
- export { AbstractApplicationLayer, AbstractPhysicalLayer, AsciiApplicationLayer, COIL_OFF, COIL_ON, ConformityLevel, EXCEPTION_OFFSET, ErrorCode, FunctionCode, LIMITS, MEI_READ_DEVICE_ID, MasterSession, ModbusError, ModbusErrorCode, ModbusMaster, ModbusSlave, ReadDeviceIDCode, RtuApplicationLayer, SerialPhysicalLayer, TcpApplicationLayer, TcpClientPhysicalLayer, TcpServerPhysicalLayer, UdpPhysicalLayer, getCodeByError, getErrorByCode };
604
- export type { ApplicationDataUnit, AsciiApplicationLayerOptions, Callback$1 as Callback, CustomFunctionCode, DeviceIdentification, FConvertPromise, ModbusMasterOptions, ModbusSlaveModel, ModbusSlaveOptions, PhysicalConnection, RtuApplicationLayerOptions, ServerId };
755
+ export { AbstractApplicationLayer, AbstractPhysicalConnection, AbstractPhysicalLayer, AsciiApplicationLayer, COIL_OFF, COIL_ON, ConformityLevel, EXCEPTION_OFFSET, ErrorCode, FunctionCode, LIMITS, MEI_READ_DEVICE_ID, MasterSession, ModbusError, ModbusMaster, ModbusSlave, PhysicalConnectionState, PhysicalState, ReadDeviceIDCode, RtuApplicationLayer, SerialPhysicalLayer, TcpApplicationLayer, TcpClientPhysicalLayer, TcpServerPhysicalLayer, UdpClientPhysicalLayer, UdpServerPhysicalLayer, createPhysicalLayer, getCodeByError, getErrorByCode };
756
+ export type { AbstractPhysicalLayerEvents, ApplicationDataUnit, AsciiApplicationLayerOptions, Callback$1 as Callback, CustomFunctionCode, DeviceIdentification, MaybeAsyncFunction, ModbusMasterOptions, ModbusSlaveModel, ModbusSlaveOptions, OpenArgs, PhysicalConfig, RtuApplicationLayerOptions, ServerId, TcpServerPhysicalLayerOptions, UdpServerPhysicalLayerOptions };