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.
Files changed (62) hide show
  1. package/README.md +324 -147
  2. package/README.zh-CN.md +380 -0
  3. package/dist/index.cjs +2552 -2288
  4. package/dist/index.d.ts +400 -233
  5. package/dist/index.mjs +2548 -2287
  6. package/dist/src/error-code.d.ts +2 -24
  7. package/dist/src/layers/application/abstract-application-layer.d.ts +13 -8
  8. package/dist/src/layers/application/ascii-application-layer.d.ts +9 -11
  9. package/dist/src/layers/application/rtu-application-layer.d.ts +20 -47
  10. package/dist/src/layers/application/tcp-application-layer.d.ts +9 -8
  11. package/dist/src/layers/physical/abstract-physical-layer.d.ts +43 -14
  12. package/dist/src/layers/physical/index.d.ts +9 -3
  13. package/dist/src/layers/physical/serial-physical-layer.d.ts +43 -15
  14. package/dist/src/layers/physical/tcp-client-physical-layer.d.ts +13 -12
  15. package/dist/src/layers/physical/tcp-physical-connection.d.ts +16 -0
  16. package/dist/src/layers/physical/tcp-server-physical-layer.d.ts +24 -14
  17. package/dist/src/layers/physical/udp-client-physical-layer.d.ts +33 -0
  18. package/dist/src/layers/physical/udp-server-physical-layer.d.ts +50 -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-session.d.ts +3 -3
  22. package/dist/src/master/master.d.ts +55 -19
  23. package/dist/src/slave/slave.d.ts +58 -33
  24. package/dist/src/types.d.ts +2 -2
  25. package/dist/src/utils/callback.d.ts +8 -0
  26. package/dist/src/utils/crc.d.ts +1 -1
  27. package/dist/src/utils/index.d.ts +7 -4
  28. package/dist/src/utils/predictRtuFrameLength.d.ts +13 -16
  29. package/dist/src/utils/promisify-cb.d.ts +4 -0
  30. package/dist/src/utils/rtu-timing.d.ts +63 -0
  31. package/dist/src/utils/whitelist.d.ts +11 -0
  32. package/dist/src/vars.d.ts +2 -0
  33. package/package.json +15 -8
  34. package/dist/src/layers/physical/udp-physical-layer.d.ts +0 -42
  35. package/dist/src/utils/genConnectionId.d.ts +0 -2
  36. package/dist/test/adu-buffer.test.d.ts +0 -1
  37. package/dist/test/ascii-hex-sentry.test.d.ts +0 -1
  38. package/dist/test/ascii-hex-validation.test.d.ts +0 -1
  39. package/dist/test/ascii-tcp-fragmentation.test.d.ts +0 -1
  40. package/dist/test/check-range.test.d.ts +0 -1
  41. package/dist/test/fallback-atomic.test.d.ts +0 -1
  42. package/dist/test/fallback-serial.test.d.ts +0 -1
  43. package/dist/test/fc17-serverid-validation.test.d.ts +0 -1
  44. package/dist/test/fc43-conformity.test.d.ts +0 -1
  45. package/dist/test/fc43-utf8-objects.test.d.ts +0 -1
  46. package/dist/test/gen-connection-id.test.d.ts +0 -1
  47. package/dist/test/helpers/raw-tcp.d.ts +0 -38
  48. package/dist/test/master-concurrent.test.d.ts +0 -1
  49. package/dist/test/modbus-error.test.d.ts +0 -1
  50. package/dist/test/physical-lifecycle.test.d.ts +0 -1
  51. package/dist/test/predict-rtu.test.d.ts +0 -1
  52. package/dist/test/rtu-custom-fc.test.d.ts +0 -1
  53. package/dist/test/rtu-pool-overflow.test.d.ts +0 -1
  54. package/dist/test/rtu-t15-timing.test.d.ts +0 -1
  55. package/dist/test/rtu-t35-default.test.d.ts +0 -1
  56. package/dist/test/rtu-t35-strict.test.d.ts +0 -1
  57. package/dist/test/rtu-tcp-fragmentation.test.d.ts +0 -1
  58. package/dist/test/serial-e2e.test.d.ts +0 -1
  59. package/dist/test/slave-multi-connection.test.d.ts +0 -1
  60. package/dist/test/slave.test.d.ts +0 -1
  61. package/dist/test/tcp-fragmentation.test.d.ts +0 -1
  62. 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;
@@ -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;
@@ -134,6 +113,8 @@ declare enum ConformityLevel {
134
113
  REGULAR = 130,
135
114
  EXTENDED = 131
136
115
  }
116
+ /** Shared empty Buffer to avoid repeated allocations. */
117
+ declare const EMPTY_BUFFER: Buffer<ArrayBuffer>;
137
118
  /** Modbus V1.1b3 PDU quantity limits. */
138
119
  declare const LIMITS: {
139
120
  readonly READ_COILS_MIN: 1;
@@ -145,24 +126,16 @@ declare const LIMITS: {
145
126
  readonly RW_REGISTERS_WRITE_MAX: 121;
146
127
  };
147
128
 
148
- interface PhysicalConnection {
149
- readonly id: string | number;
129
+ declare enum PhysicalState {
130
+ OPENING = "opening",
131
+ OPEN = "open",
132
+ CLOSING = "closing",
133
+ CLOSED = "closed"
150
134
  }
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>;
135
+ declare enum PhysicalConnectionState {
136
+ CONNECTED = "connected",
137
+ DESTROYING = "destroying",
138
+ DESTROYED = "destroyed"
166
139
  }
167
140
 
168
141
  interface SerialPhysicalLayerOptions {
@@ -172,132 +145,244 @@ interface SerialPhysicalLayerOptions {
172
145
  * 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
146
  */
174
147
  baudRate: number;
175
- /** Must be one of these: 5, 6, 7, or 8 defaults to 8 */
148
+ /** Must be one of these: 5, 6, 7, or 8. Defaults to 8 */
176
149
  dataBits?: 5 | 6 | 7 | 8;
177
150
  /** Prevent other processes from opening the port. Windows does not currently support `false`. Defaults to true */
178
151
  lock?: boolean;
179
- /** Must be 1, 1.5 or 2 defaults to 1 */
152
+ /** Must be 1, 1.5 or 2. Defaults to 1 */
180
153
  stopBits?: 1 | 1.5 | 2;
181
- parity?: string;
154
+ /** Device parity. Defaults to none */
155
+ parity?: 'none' | 'even' | 'odd' | 'mark' | 'space';
182
156
  /** Flow control Setting. Defaults to false */
183
157
  rtscts?: boolean;
184
158
  /** Flow control Setting. Defaults to false */
185
159
  xon?: boolean;
186
160
  /** Flow control Setting. Defaults to false */
187
161
  xoff?: boolean;
188
- /** Flow control Setting defaults to false*/
162
+ /** Flow control Setting. Defaults to false */
189
163
  xany?: boolean;
190
164
  /** drop DTR on close. Defaults to true */
191
165
  hupcl?: boolean;
166
+ /** The size of the read and write buffers. Defaults to 64k */
167
+ highWaterMark?: number;
168
+ /** Emit 'end' on port close. Defaults to false */
169
+ endOnClose?: boolean;
170
+ /** see `man termios`. Defaults to 1 (Darwin/Linux only) */
171
+ vmin?: number;
172
+ /** see `man termios`. Defaults to 0 (Darwin/Linux only) */
173
+ vtime?: number;
174
+ /** RTS mode. Defaults to handshake (Windows only) */
175
+ rtsMode?: 'handshake' | 'enable' | 'toggle';
192
176
  }
193
177
  declare class SerialPhysicalLayer extends AbstractPhysicalLayer {
194
- TYPE: 'SERIAL' | 'NET';
178
+ readonly TYPE: "SERIAL";
179
+ private _state;
180
+ private _connections;
195
181
  private _serialport;
196
- private _connection;
197
- private _isOpening;
198
- private _destroyed;
182
+ private _serialportOpts;
183
+ private _path;
199
184
  private _baudRate;
200
- get isOpen(): boolean;
201
- get destroyed(): boolean;
185
+ private _pendingOpenCbs;
186
+ private _pendingCloseCbs;
187
+ private _cleanupFns;
188
+ get state(): PhysicalState;
189
+ get serialport(): SerialPort | null;
190
+ get path(): string;
202
191
  get baudRate(): number;
203
192
  constructor(options: SerialPhysicalLayerOptions);
204
- open(): Promise<void>;
205
- write(data: Buffer): Promise<void>;
206
- close(): Promise<void>;
207
- destroy(): Promise<void>;
193
+ open(cb?: (err?: Error | null) => void): void;
194
+ close(cb?: (err?: Error | null) => void): void;
208
195
  }
209
196
 
210
197
  declare class TcpClientPhysicalLayer extends AbstractPhysicalLayer {
211
- TYPE: 'SERIAL' | 'NET';
198
+ readonly TYPE: "TCP_CLIENT";
199
+ private _state;
200
+ private _connections;
212
201
  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;
202
+ private _socketOpts?;
203
+ private _pendingOpenCbs;
204
+ private _pendingCloseCbs;
205
+ private _cleanupFns;
206
+ get state(): PhysicalState;
207
+ get socket(): Socket | null;
220
208
  constructor(options?: SocketConstructorOpts);
221
- open(options?: SocketConnectOpts): Promise<void>;
222
- write(data: Buffer): Promise<void>;
223
- close(): Promise<void>;
224
- destroy(): Promise<void>;
209
+ open(options?: SocketConnectOpts | ((err?: Error | null) => void), cb?: (err?: Error | null) => void): void;
210
+ close(cb?: (err?: Error | null) => void): 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);
238
- open(options?: ListenOptions): Promise<void>;
239
- write(data: Buffer): Promise<void>;
240
- close(): Promise<void>;
241
- destroy(): Promise<void>;
227
+ private _server;
228
+ private _opts;
229
+ private _pendingOpenCbs;
230
+ private _pendingCloseCbs;
231
+ private _cleanupFns;
232
+ get state(): PhysicalState;
233
+ get server(): Server | null;
234
+ constructor(options?: TcpServerPhysicalLayerOptions);
235
+ open(options?: ListenOptions | ((err?: Error | null) => void), cb?: (err?: Error | null) => void): void;
236
+ close(cb?: (err?: Error | null) => void): void;
242
237
  }
243
238
 
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?: {
239
+ declare class UdpClientPhysicalLayer extends AbstractPhysicalLayer {
240
+ readonly TYPE: "UDP_CLIENT";
241
+ private _state;
242
+ private _connections;
243
+ private _socket;
244
+ private _socketOpts;
245
+ private _pendingOpenCbs;
246
+ private _pendingCloseCbs;
247
+ private _cleanupFns;
248
+ get state(): PhysicalState;
249
+ get socket(): Socket$1 | null;
250
+ constructor(options?: Partial<SocketOptions>);
251
+ open(remote?: {
252
252
  port?: number;
253
253
  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
- */
254
+ } | ((err?: Error | null) => void), cb?: (err?: Error | null) => void): void;
255
+ close(cb?: (err?: Error | null) => void): void;
256
+ }
257
+
258
+ interface UdpServerPhysicalLayerOptions {
259
+ /** Allowed client IP addresses. IPv4-mapped IPv6 addresses (e.g., ::ffff:192.168.1.1) are normalized before checking. */
260
+ whitelist?: string[];
261
+ /** Maximum number of concurrent virtual client connections. When exceeded, datagrams from new clients are silently ignored. */
262
+ maxConnections?: number;
263
+ /** Connection idle timeout in ms. Pass `0` to disable eviction. Defaults to 30000. */
261
264
  idleTimeout?: number;
265
+ /** Forwarded to `dgram.createSocket()`. */
266
+ socketOpts?: Partial<SocketOptions>;
262
267
  }
263
- declare class UdpPhysicalLayer extends AbstractPhysicalLayer {
264
- TYPE: 'SERIAL' | 'NET';
265
- private _socket;
268
+ declare class UdpServerPhysicalLayer extends AbstractPhysicalLayer {
269
+ readonly TYPE: "UDP_SERVER";
270
+ private _state;
266
271
  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);
278
- open(options?: BindOptions): Promise<void>;
279
- private _handleMessage;
280
- write(data: Buffer): Promise<void>;
281
- close(): Promise<void>;
282
- destroy(): Promise<void>;
272
+ private _socket;
273
+ private _opts;
274
+ private _pendingOpenCbs;
275
+ private _pendingCloseCbs;
276
+ private _cleanupFns;
277
+ get state(): PhysicalState;
278
+ get socket(): Socket$1 | null;
279
+ constructor(options?: UdpServerPhysicalLayerOptions);
280
+ /**
281
+ * Bind the UDP socket and start accepting datagrams.
282
+ *
283
+ * @param options Bind options (port, address, etc.). Defaults to port 502.
284
+ * @param cb Callback invoked when binding completes or fails.
285
+ */
286
+ open(options?: BindOptions | ((err?: Error | null) => void), cb?: (err?: Error | null) => void): void;
287
+ close(cb?: (err?: Error | null) => void): void;
288
+ }
289
+
290
+ interface AbstractPhysicalConnectionEvents {
291
+ data: [data: Buffer];
292
+ close: [];
293
+ }
294
+ /**
295
+ * A one-way data transmission channel.
296
+ *
297
+ * State transitions are unidirectional: CONNECTED → DESTROYING → DESTROYED.
298
+ * Once closed, the instance cannot be reused; create a new connection instead.
299
+ */
300
+ declare abstract class AbstractPhysicalConnection extends EventEmitter<AbstractPhysicalConnectionEvents> {
301
+ abstract readonly state: PhysicalConnectionState;
302
+ abstract readonly physicalLayer: AbstractPhysicalLayer;
303
+ abstract write(data: Buffer, cb?: (err?: Error | null) => void): void;
304
+ abstract destroy(cb?: (err?: Error | null) => void): void;
305
+ }
306
+ interface AbstractPhysicalLayerEvents {
307
+ open: [];
308
+ connect: [connection: AbstractPhysicalConnection];
309
+ close: [];
310
+ error: [error: Error];
311
+ }
312
+ /**
313
+ * An abstraction over local hardware or network resources.
314
+ *
315
+ * `open()` acquires the resource (serial port, TCP socket, UDP socket, etc.).
316
+ * Once ready, it emits `connect` with an {@link AbstractPhysicalConnection},
317
+ * unifying serial, TCP client, TCP server, and UDP under a single
318
+ * "connection-oriented" model similar to a TCP server accepting sockets.
319
+ */
320
+ declare abstract class AbstractPhysicalLayer extends EventEmitter<AbstractPhysicalLayerEvents> {
321
+ abstract readonly TYPE: 'SERIAL' | 'TCP_CLIENT' | 'TCP_SERVER' | 'UDP_CLIENT' | 'UDP_SERVER';
322
+ is(type: 'SERIAL'): this is SerialPhysicalLayer;
323
+ is(type: 'TCP_CLIENT'): this is TcpClientPhysicalLayer;
324
+ is(type: 'TCP_SERVER'): this is TcpServerPhysicalLayer;
325
+ is(type: 'UDP_CLIENT'): this is UdpClientPhysicalLayer;
326
+ is(type: 'UDP_SERVER'): this is UdpServerPhysicalLayer;
327
+ abstract readonly state: PhysicalState;
328
+ /** Last argument is the callback: `(err?: Error | null) => void`. Callback is optional. */
329
+ abstract open(...args: any[]): void;
330
+ abstract close(cb?: (err?: Error | null) => void): void;
283
331
  }
284
332
 
333
+ type PhysicalConfig = {
334
+ type: 'SERIAL';
335
+ opts: SerialPhysicalLayerOptions;
336
+ } | {
337
+ type: 'TCP_CLIENT';
338
+ socketOpts?: SocketConstructorOpts;
339
+ } | {
340
+ type: 'TCP_SERVER';
341
+ opts?: TcpServerPhysicalLayerOptions;
342
+ } | {
343
+ type: 'UDP_CLIENT';
344
+ socketOpts?: Partial<SocketOptions>;
345
+ } | {
346
+ type: 'UDP_SERVER';
347
+ opts?: UdpServerPhysicalLayerOptions;
348
+ } | {
349
+ type: 'CUSTOM';
350
+ layer: AbstractPhysicalLayer;
351
+ };
352
+ type OpenArgs<T extends PhysicalConfig> = T extends {
353
+ type: 'SERIAL';
354
+ } ? Parameters<SerialPhysicalLayer['open']> : T extends {
355
+ type: 'TCP_CLIENT';
356
+ } ? Parameters<TcpClientPhysicalLayer['open']> : T extends {
357
+ type: 'TCP_SERVER';
358
+ } ? Parameters<TcpServerPhysicalLayer['open']> : T extends {
359
+ type: 'UDP_CLIENT';
360
+ } ? Parameters<UdpClientPhysicalLayer['open']> : T extends {
361
+ type: 'UDP_SERVER';
362
+ } ? Parameters<UdpServerPhysicalLayer['open']> : never;
363
+ declare function createPhysicalLayer(config: PhysicalConfig): AbstractPhysicalLayer;
364
+
285
365
  interface AbstractApplicationLayerEvents {
286
366
  framing: [frame: ApplicationDataUnit & {
287
367
  buffer: Buffer;
288
- }, response: (data: Buffer) => Promise<void>, connection: PhysicalConnection];
368
+ }];
289
369
  'framing-error': [error: Error];
290
370
  }
371
+ /**
372
+ * Application-layer protocol handler bound to a single physical connection.
373
+ *
374
+ * Its lifetime follows the channel: created when the underlying connection is
375
+ * established and discarded when the connection closes. Subclasses implement
376
+ * ASCII, RTU, or TCP framing rules.
377
+ */
291
378
  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');
379
+ abstract readonly PROTOCOL: 'ASCII' | 'RTU' | 'TCP';
380
+ abstract ROLE: 'MASTER' | 'SLAVE';
381
+ abstract readonly connection: AbstractPhysicalConnection;
296
382
  flush(): void;
297
383
  addCustomFunctionCode(cfc: CustomFunctionCode): void;
298
384
  removeCustomFunctionCode(fc: number): void;
299
- abstract encode(data: ApplicationDataUnit): Buffer;
300
- abstract destroy(): void;
385
+ abstract encode(unit: number, fc: number, data: Buffer, transaction?: number): Buffer;
301
386
  }
302
387
 
303
388
  interface AsciiApplicationLayerOptions {
@@ -310,93 +395,113 @@ interface AsciiApplicationLayerOptions {
310
395
  }
311
396
  declare class AsciiApplicationLayer extends AbstractApplicationLayer {
312
397
  readonly PROTOCOL: "ASCII";
398
+ readonly ROLE: 'MASTER' | 'SLAVE';
313
399
  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;
400
+ private _connection;
401
+ private _state;
402
+ private _cleanupFns;
403
+ get connection(): AbstractPhysicalConnection;
404
+ constructor(role: 'MASTER' | 'SLAVE', connection: AbstractPhysicalConnection, options?: AsciiApplicationLayerOptions);
320
405
  private framing;
321
- encode(data: ApplicationDataUnit): Buffer;
322
- destroy(): void;
406
+ flush(): void;
407
+ encode(unit: number, fc: number, data: Buffer, transaction?: number): Buffer;
323
408
  }
324
409
 
325
410
  interface RtuApplicationLayerOptions {
411
+ /** Inter-frame silence in milliseconds (Modbus RTU t3.5). 0 = disabled (immediate parse). */
412
+ intervalBetweenFrames?: number;
413
+ /** Inter-character timeout in milliseconds (Modbus RTU t1.5). Opt-in. */
414
+ interCharTimeout?: number;
326
415
  /**
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.
416
+ * Buffer pool size per connection (bytes). Defaults to `MAX_FRAME_LENGTH * 2`
417
+ * (512 bytes). Increase this if you expect frames larger than 256 bytes or
418
+ * heavy pipelining on a single connection.
353
419
  */
354
- interCharTimeout?: {
355
- unit: 'bit' | 'ms';
356
- value: number;
357
- };
420
+ poolSize?: number;
358
421
  }
359
422
  declare class RtuApplicationLayer extends AbstractApplicationLayer {
360
423
  readonly PROTOCOL: "RTU";
361
- private _states;
362
- private _customFunctionCodes;
363
- private _removeAllListeners;
424
+ readonly ROLE: 'MASTER' | 'SLAVE';
425
+ private _connection;
426
+ private _state;
427
+ private _poolSize;
364
428
  private _threePointFiveT;
365
429
  private _onePointFiveT;
366
- private _destroyed;
367
- constructor(physicalLayer: SerialPhysicalLayer | TcpServerPhysicalLayer | TcpClientPhysicalLayer | UdpPhysicalLayer, options?: RtuApplicationLayerOptions);
368
- private getState;
430
+ private _customFunctionCodes;
431
+ private _cleanupFns;
432
+ get connection(): AbstractPhysicalConnection;
433
+ constructor(role: 'MASTER' | 'SLAVE', connection: AbstractPhysicalConnection, options?: RtuApplicationLayerOptions);
369
434
  private clearStateTimers;
370
- private clearState;
435
+ private flushBuffer;
371
436
  flush(): void;
372
437
  addCustomFunctionCode(cfc: CustomFunctionCode): void;
373
438
  removeCustomFunctionCode(fc: number): void;
374
- private flushBuffer;
375
- private tryExtract;
376
- private checkExpected;
377
- private crcMatches;
378
- private deliverFrame;
379
- encode(data: ApplicationDataUnit): Buffer;
380
- destroy(): void;
439
+ encode(unit: number, fc: number, data: Buffer, transaction?: number): Buffer;
381
440
  }
382
441
 
383
442
  declare class TcpApplicationLayer extends AbstractApplicationLayer {
384
443
  readonly PROTOCOL: "TCP";
444
+ readonly ROLE: 'MASTER' | 'SLAVE';
445
+ private _connection;
385
446
  private _transactionId;
386
- private _buffers;
387
- private _removeAllListeners;
388
- private _destroyed;
389
- constructor(physicalLayer: TcpServerPhysicalLayer | TcpClientPhysicalLayer | UdpPhysicalLayer);
447
+ private _buffer;
448
+ private _cleanupFns;
449
+ get connection(): AbstractPhysicalConnection;
450
+ constructor(role: 'MASTER' | 'SLAVE', connection: AbstractPhysicalConnection);
390
451
  private tryExtract;
391
452
  private processFrame;
392
- encode(data: ApplicationDataUnit): Buffer;
393
- destroy(): void;
453
+ flush(): void;
454
+ encode(unit: number, fc: number, data: Buffer, transaction?: number): Buffer;
394
455
  }
395
456
 
396
- interface ModbusMasterEvents {
397
- error: [error: Error];
398
- close: [];
457
+ /**
458
+ * RTU timing parameter — accepts either:
459
+ * - a bare `number` in milliseconds (`0` to disable the timer entirely)
460
+ * - `{ unit: 'ms', value: N }` — explicit milliseconds (equivalent to bare `N`)
461
+ * - `{ unit: 'bit', value: N }` — bit-time approximation, derived from `baudRate`
462
+ *
463
+ * The bare-number form is the recommended default; the object form exists for
464
+ * specs that quote bit-time. Pass `0` (or `{ unit: 'ms', value: 0 }`) to disable
465
+ * the timer; either form short-circuits the baudRate-derived fallback.
466
+ */
467
+ type RtuTimingValue = number | {
468
+ unit: 'bit' | 'ms';
469
+ value: number;
470
+ };
471
+ /** User-facing RTU protocol options (supports both bit and ms units). */
472
+ interface RtuProtocolOptions {
473
+ /**
474
+ * Inter-frame silence (Modbus RTU t3.5).
475
+ *
476
+ * - `20` or `{ unit: 'ms', value: 20 }` — 20 ms
477
+ * - `{ unit: 'bit', value: 38.5 }` — spec bit-time approximation (default when `baudRate` is provided)
478
+ * - `0` — disable t3.5 timing (immediate parse on every chunk; useful for
479
+ * lossless transports such as RTU-over-TCP or PTY-based tests where the
480
+ * wire's silence semantics do not apply)
481
+ *
482
+ * Per Modbus V1.02 §2.5.1.1, at baud rates > 19200 a fixed 1.75 ms is used
483
+ * regardless of the bit value.
484
+ */
485
+ intervalBetweenFrames?: RtuTimingValue;
486
+ /**
487
+ * Inter-character timeout (Modbus RTU t1.5). Opt-in; **disabled** by default.
488
+ *
489
+ * - `1` or `{ unit: 'ms', value: 1 }` — 1 ms
490
+ * - `{ unit: 'bit', value: 21 }` — bit-time approximation (~1.5 char times)
491
+ * - `0` — disable explicitly
492
+ *
493
+ * Per Modbus V1.02 §2.5.1.1, at baud rates > 19200 a fixed 0.75 ms is used
494
+ * regardless of the bit value.
495
+ */
496
+ interCharTimeout?: RtuTimingValue;
497
+ /**
498
+ * Buffer pool size per connection (bytes). Defaults to `MAX_FRAME_LENGTH * 2`
499
+ * (512 bytes). Increase this if you expect frames larger than 256 bytes or
500
+ * heavy pipelining on a single connection.
501
+ */
502
+ poolSize?: number;
399
503
  }
504
+
400
505
  interface ReturnValue<T> {
401
506
  transaction?: number;
402
507
  unit: number;
@@ -413,22 +518,45 @@ interface ModbusMasterOptions {
413
518
  * Default false (FIFO queue, requests are serialized).
414
519
  */
415
520
  concurrent?: boolean;
521
+ physical: PhysicalConfig;
522
+ protocol: {
523
+ type: 'RTU';
524
+ opts?: RtuProtocolOptions;
525
+ } | {
526
+ type: 'TCP';
527
+ } | {
528
+ type: 'ASCII';
529
+ opts?: AsciiApplicationLayerOptions;
530
+ };
416
531
  }
417
- declare class ModbusMaster<A extends AbstractApplicationLayer, P extends AbstractPhysicalLayer> extends EventEmitter<ModbusMasterEvents> {
418
- private applicationLayer;
419
- private physicalLayer;
532
+ declare class ModbusMaster<T extends ModbusMasterOptions = ModbusMasterOptions> extends EventEmitter<AbstractPhysicalLayerEvents> {
533
+ readonly timeout: number;
534
+ readonly concurrent: boolean;
420
535
  private _masterSession;
421
- private _queue;
536
+ private _physicalLayer;
537
+ private _protocol;
538
+ private _appLayer?;
539
+ private _customFunctionCodes;
540
+ private _queueUnits;
541
+ private _queueFcs;
542
+ private _queueDatas;
543
+ private _queueTimeouts;
544
+ private _queueBroadcasts;
545
+ private _queueResolves;
546
+ private _queueRejects;
547
+ private _queueHead;
548
+ private _queueLen;
422
549
  private _draining;
423
550
  private _nextTid;
424
- private _cleanLevel;
425
- timeout: number;
426
- readonly concurrent: boolean;
427
- get isOpen(): boolean;
428
- get destroyed(): boolean;
429
- constructor(applicationLayer: A, physicalLayer: P, options?: ModbusMasterOptions);
551
+ private _cleanupFns;
552
+ private _closePromise;
553
+ get state(): PhysicalState;
554
+ get physicalLayer(): AbstractPhysicalLayer;
555
+ constructor(options: T);
556
+ private _createAppLayer;
430
557
  private send;
431
558
  private _drain;
559
+ private _processNext;
432
560
  private _exchange;
433
561
  private writeFC1Or2;
434
562
  writeFC1: this['readCoils'];
@@ -487,26 +615,41 @@ declare class ModbusMaster<A extends AbstractApplicationLayer, P extends Abstrac
487
615
  removeCustomFunctionCode(fc: number): void;
488
616
  sendCustomFC(unit: 0, fc: number, data: Buffer | number[], timeout?: number): Promise<void>;
489
617
  sendCustomFC(unit: number, fc: number, data: Buffer | number[], timeout?: number): Promise<Buffer>;
490
- private _clean;
491
- open(...args: Parameters<P['open']>): Promise<void>;
618
+ /**
619
+ * Open the underlying physical layer and begin accepting connections.
620
+ *
621
+ * A `ModbusMaster` instance can only be opened once. Once {@link close}
622
+ * is called — explicitly or because the physical layer disconnected —
623
+ * the instance is permanently closed and cannot be reopened.
624
+ * Create a new `ModbusMaster` if a new connection is required.
625
+ */
626
+ open(...args: OpenArgs<T['physical']>): Promise<void>;
627
+ private _close;
628
+ /**
629
+ * Permanently close the master and release all resources.
630
+ *
631
+ * After calling this method the instance is considered dead:
632
+ * - No further requests can be sent.
633
+ * - The instance cannot be reopened via {@link open}.
634
+ * - All event listeners registered on this master are removed.
635
+ */
492
636
  close(): Promise<void>;
493
- destroy(): Promise<void>;
494
637
  }
495
638
 
496
639
  type Frame = ApplicationDataUnit & {
497
640
  buffer: Buffer;
498
641
  };
499
- type PreCheck = (frame: Frame) => boolean | number | undefined;
500
642
  type Callback = (error: Error | null, frame?: Frame) => void;
501
643
  declare class MasterSession {
502
644
  private _waiters;
503
- start(key: string | number, preCheck: PreCheck[], callback: Callback): void;
645
+ /** Register a callback for `key`. No timer timeout is managed by the caller. */
646
+ start(key: string | number, callback: Callback): void;
647
+ /** Cancel a pending waiter without firing its callback. */
504
648
  stop(key: string | number): void;
505
649
  stopAll(error: Error): void;
506
650
  has(key: string | number): boolean;
507
651
  handleFrame(frame: Frame): void;
508
652
  handleError(error: Error): void;
509
- private runPreChecks;
510
653
  }
511
654
 
512
655
  interface ModbusSlaveModel {
@@ -517,27 +660,27 @@ interface ModbusSlaveModel {
517
660
  * If provide the return value, use this value as data of `PDU` to respond.
518
661
  * Otherwise keep the default read and write behavior.
519
662
  */
520
- interceptor?: FConvertPromise<(fc: number, data: Buffer) => Buffer | undefined>;
521
- readDiscreteInputs?: FConvertPromise<(address: number, length: number) => boolean[]>;
522
- readCoils?: FConvertPromise<(address: number, length: number) => boolean[]>;
523
- writeSingleCoil?: FConvertPromise<(address: number, value: boolean) => void>;
663
+ interceptor?: MaybeAsyncFunction<(fc: number, data: Buffer) => Buffer | undefined>;
664
+ readDiscreteInputs?: MaybeAsyncFunction<(address: number, length: number) => boolean[]>;
665
+ readCoils?: MaybeAsyncFunction<(address: number, length: number) => boolean[]>;
666
+ writeSingleCoil?: MaybeAsyncFunction<(address: number, value: boolean) => void>;
524
667
  /**
525
668
  * If omitted, defaults to loop and call `writeSingleCoil`.
526
669
  */
527
- writeMultipleCoils?: FConvertPromise<(address: number, value: boolean[]) => void>;
528
- readInputRegisters?: FConvertPromise<(address: number, length: number) => number[]>;
529
- readHoldingRegisters?: FConvertPromise<(address: number, length: number) => number[]>;
530
- writeSingleRegister?: FConvertPromise<(address: number, value: number) => void>;
670
+ writeMultipleCoils?: MaybeAsyncFunction<(address: number, value: boolean[]) => void>;
671
+ readInputRegisters?: MaybeAsyncFunction<(address: number, length: number) => number[]>;
672
+ readHoldingRegisters?: MaybeAsyncFunction<(address: number, length: number) => number[]>;
673
+ writeSingleRegister?: MaybeAsyncFunction<(address: number, value: number) => void>;
531
674
  /**
532
675
  * If omitted, defaults to loop and call `writeSingleRegister`.
533
676
  */
534
- writeMultipleRegisters?: FConvertPromise<(address: number, value: number[]) => void>;
677
+ writeMultipleRegisters?: MaybeAsyncFunction<(address: number, value: number[]) => void>;
535
678
  /**
536
679
  * If omitted, defaults to call `readHoldingRegisters` and `writeSingleRegister`.
537
680
  */
538
- maskWriteRegister?: FConvertPromise<(address: number, andMask: number, orMask: number) => void>;
539
- reportServerId?: FConvertPromise<() => ServerId>;
540
- readDeviceIdentification?: FConvertPromise<() => {
681
+ maskWriteRegister?: MaybeAsyncFunction<(address: number, andMask: number, orMask: number) => void>;
682
+ reportServerId?: MaybeAsyncFunction<() => ServerId>;
683
+ readDeviceIdentification?: MaybeAsyncFunction<() => {
541
684
  [index: number]: string;
542
685
  }>;
543
686
  getAddressRange?: () => {
@@ -547,10 +690,6 @@ interface ModbusSlaveModel {
547
690
  holdingRegisters?: [number, number] | [number, number][];
548
691
  };
549
692
  }
550
- interface ModbusSlaveEvents {
551
- error: [error: Error];
552
- close: [];
553
- }
554
693
  interface ModbusSlaveOptions {
555
694
  /**
556
695
  * Pipelined concurrent processing of requests within one connection.
@@ -558,19 +697,31 @@ interface ModbusSlaveOptions {
558
697
  * Default false — per-connection FIFO. RTU/ASCII + concurrent=true throws.
559
698
  */
560
699
  concurrent?: boolean;
700
+ physical: PhysicalConfig;
701
+ protocol: {
702
+ type: 'RTU';
703
+ opts?: RtuProtocolOptions;
704
+ } | {
705
+ type: 'TCP';
706
+ } | {
707
+ type: 'ASCII';
708
+ opts?: AsciiApplicationLayerOptions;
709
+ };
561
710
  }
562
- declare class ModbusSlave<A extends AbstractApplicationLayer, P extends AbstractPhysicalLayer> extends EventEmitter<ModbusSlaveEvents> {
563
- private applicationLayer;
564
- private physicalLayer;
565
- models: Map<number, ModbusSlaveModel>;
711
+ declare class ModbusSlave<T extends ModbusSlaveOptions = ModbusSlaveOptions> extends EventEmitter<AbstractPhysicalLayerEvents> {
712
+ readonly models: Map<number, ModbusSlaveModel>;
566
713
  readonly concurrent: boolean;
567
- private _queues;
714
+ private _physicalLayer;
715
+ private _protocol;
716
+ private _appLayers;
568
717
  private _customFunctionCodes;
569
718
  private _locks;
570
- private _cleanLevel;
571
- get isOpen(): boolean;
572
- get destroyed(): boolean;
573
- constructor(applicationLayer: A, physicalLayer: P, options?: ModbusSlaveOptions);
719
+ private _cleanupFns;
720
+ private _closePromise;
721
+ get state(): PhysicalState;
722
+ get physicalLayer(): AbstractPhysicalLayer;
723
+ constructor(options: T);
724
+ private _createAppLayer;
574
725
  private handleFC1;
575
726
  private handleFC2;
576
727
  private handleFC3;
@@ -587,17 +738,33 @@ declare class ModbusSlave<A extends AbstractApplicationLayer, P extends Abstract
587
738
  private _drain;
588
739
  private _processFrame;
589
740
  private _intercept;
590
- private withAddressLock;
741
+ private _withAddressLock;
591
742
  private _handleFC;
743
+ private _handleCustomFC;
592
744
  add(model: ModbusSlaveModel): void;
593
745
  remove(unit: number): void;
594
746
  addCustomFunctionCode(cfc: CustomFunctionCode): void;
595
747
  removeCustomFunctionCode(fc: number): void;
596
- private _clean;
597
- open(...args: Parameters<P['open']>): Promise<void>;
748
+ /**
749
+ * Open the underlying physical layer and begin accepting connections.
750
+ *
751
+ * A `ModbusSlave` instance can only be opened once. Once {@link close}
752
+ * is called — explicitly or because the physical layer disconnected —
753
+ * the instance is permanently closed and cannot be reopened.
754
+ * Create a new `ModbusSlave` if a new server is required.
755
+ */
756
+ open(...args: OpenArgs<T['physical']>): Promise<void>;
757
+ private _close;
758
+ /**
759
+ * Permanently close the slave and release all resources.
760
+ *
761
+ * After calling this method the instance is considered dead:
762
+ * - No further requests can be processed.
763
+ * - The instance cannot be reopened via {@link open}.
764
+ * - All event listeners registered on this slave are removed.
765
+ */
598
766
  close(): Promise<void>;
599
- destroy(): Promise<void>;
600
767
  }
601
768
 
602
- 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 };
603
- export type { ApplicationDataUnit, AsciiApplicationLayerOptions, Callback$1 as Callback, CustomFunctionCode, DeviceIdentification, FConvertPromise, ModbusMasterOptions, ModbusSlaveModel, ModbusSlaveOptions, PhysicalConnection, RtuApplicationLayerOptions, ServerId };
769
+ export { AbstractApplicationLayer, AbstractPhysicalConnection, AbstractPhysicalLayer, AsciiApplicationLayer, COIL_OFF, COIL_ON, ConformityLevel, EMPTY_BUFFER, 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 };
770
+ export type { AbstractPhysicalLayerEvents, ApplicationDataUnit, AsciiApplicationLayerOptions, Callback$1 as Callback, CustomFunctionCode, DeviceIdentification, MaybeAsyncFunction, ModbusMasterOptions, ModbusSlaveModel, ModbusSlaveOptions, OpenArgs, PhysicalConfig, RtuApplicationLayerOptions, ServerId, TcpServerPhysicalLayerOptions, UdpServerPhysicalLayerOptions };