njs-modbus 1.4.0 → 2.0.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 (59) hide show
  1. package/README.md +27 -11
  2. package/dist/index.cjs +2117 -1243
  3. package/dist/index.d.ts +312 -87
  4. package/dist/index.mjs +2110 -1244
  5. package/dist/src/error-code.d.ts +27 -1
  6. package/dist/src/index.d.ts +2 -0
  7. package/dist/src/layers/application/abstract-application-layer.d.ts +11 -8
  8. package/dist/src/layers/application/ascii-application-layer.d.ts +14 -10
  9. package/dist/src/layers/application/index.d.ts +2 -0
  10. package/dist/src/layers/application/rtu-application-layer.d.ts +52 -18
  11. package/dist/src/layers/application/tcp-application-layer.d.ts +4 -8
  12. package/dist/src/layers/physical/abstract-physical-layer.d.ts +5 -1
  13. package/dist/src/layers/physical/index.d.ts +1 -0
  14. package/dist/src/layers/physical/serial-physical-layer.d.ts +2 -0
  15. package/dist/src/layers/physical/tcp-client-physical-layer.d.ts +3 -0
  16. package/dist/src/layers/physical/tcp-server-physical-layer.d.ts +3 -1
  17. package/dist/src/layers/physical/udp-physical-layer.d.ts +25 -10
  18. package/dist/src/master/index.d.ts +2 -0
  19. package/dist/src/master/master-session.d.ts +18 -0
  20. package/dist/src/master/master.d.ts +27 -5
  21. package/dist/src/slave/index.d.ts +1 -1
  22. package/dist/src/slave/slave.d.ts +22 -3
  23. package/dist/src/types.d.ts +33 -2
  24. package/dist/src/utils/bitsToMs.d.ts +13 -0
  25. package/dist/src/utils/crc.d.ts +1 -1
  26. package/dist/src/utils/genConnectionId.d.ts +2 -0
  27. package/dist/src/utils/index.d.ts +5 -1
  28. package/dist/src/utils/isUint8.d.ts +8 -0
  29. package/dist/src/utils/predictRtuFrameLength.d.ts +20 -0
  30. package/dist/src/vars.d.ts +47 -0
  31. package/dist/test/ascii-hex-validation.test.d.ts +1 -0
  32. package/dist/test/ascii-tcp-fragmentation.test.d.ts +1 -0
  33. package/dist/test/check-range.test.d.ts +1 -0
  34. package/dist/test/fallback-atomic.test.d.ts +1 -0
  35. package/dist/test/fallback-serial.test.d.ts +1 -0
  36. package/dist/test/fc17-serverid-validation.test.d.ts +1 -0
  37. package/dist/test/fc43-conformity.test.d.ts +1 -0
  38. package/dist/test/fc43-utf8-objects.test.d.ts +1 -0
  39. package/dist/test/gen-connection-id.test.d.ts +1 -0
  40. package/dist/test/helpers/raw-tcp.d.ts +38 -0
  41. package/dist/test/master-concurrent.test.d.ts +1 -0
  42. package/dist/test/modbus-error.test.d.ts +1 -0
  43. package/dist/test/physical-lifecycle.test.d.ts +1 -0
  44. package/dist/test/predict-rtu.test.d.ts +1 -0
  45. package/dist/test/rtu-custom-fc.test.d.ts +1 -0
  46. package/dist/test/rtu-pool-overflow.test.d.ts +1 -0
  47. package/dist/test/rtu-t15-timing.test.d.ts +1 -0
  48. package/dist/test/rtu-t35-default.test.d.ts +1 -0
  49. package/dist/test/rtu-t35-strict.test.d.ts +1 -0
  50. package/dist/test/rtu-tcp-fragmentation.test.d.ts +1 -0
  51. package/dist/test/serial-e2e.test.d.ts +1 -0
  52. package/dist/test/slave-multi-connection.test.d.ts +1 -0
  53. package/dist/test/slave.test.d.ts +1 -0
  54. package/dist/test/tcp-fragmentation.test.d.ts +1 -0
  55. package/dist/test/udp-multi-client.test.d.ts +1 -0
  56. package/package.json +4 -1
  57. package/dist/src/utils/getThreePointFiveT.d.ts +0 -7
  58. /package/dist/test/{master.d.ts → adu-buffer.test.d.ts} +0 -0
  59. /package/dist/test/{slave.d.ts → ascii-hex-sentry.test.d.ts} +0 -0
package/dist/index.d.ts CHANGED
@@ -2,6 +2,61 @@ import EventEmitter from 'node:events';
2
2
  import { SocketConstructorOpts, SocketConnectOpts, NetConnectOpts, ListenOptions } from 'node:net';
3
3
  import { SocketOptions, BindOptions } from 'node:dgram';
4
4
 
5
+ 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
+ interface ApplicationDataUnit {
8
+ transaction?: number;
9
+ unit: number;
10
+ fc: number;
11
+ data: Buffer;
12
+ }
13
+ interface ServerId {
14
+ /** Server ID; may be 1 byte (number) or multi-byte (number[]) per device spec. */
15
+ serverId?: number | number[];
16
+ runIndicatorStatus?: boolean;
17
+ additionalData?: number[];
18
+ }
19
+ interface DeviceIdentification {
20
+ readDeviceIDCode: number;
21
+ conformityLevel: number;
22
+ moreFollows: boolean;
23
+ nextObjectId: number;
24
+ objects: {
25
+ id: number;
26
+ value: string;
27
+ }[];
28
+ }
29
+ /**
30
+ * Defines a non-standard / user-defined Modbus function code.
31
+ *
32
+ * Registration paths:
33
+ * - `RtuApplicationLayer.addCustomFunctionCode(cfc)` — framing only.
34
+ * - `ModbusSlave.addCustomFunctionCode(cfc)` — framing + slave-side dispatch via `handle`.
35
+ * - `ModbusMaster.addCustomFunctionCode(cfc)` + `ModbusMaster.sendCustomFC(...)` — framing + request issuance.
36
+ *
37
+ * The two `predict*` callbacks declare how to derive the total RTU frame length
38
+ * (PDU + 2-byte CRC) from leading bytes; they are required so the framing FSM
39
+ * can advance without the deleted sliding-window CRC fallback.
40
+ *
41
+ * Return `null` from a predictor to signal "need more bytes before I can decide".
42
+ * Return a positive integer (>= 4, <= 256) for the total frame length.
43
+ */
44
+ interface CustomFunctionCode {
45
+ fc: number;
46
+ /** Predict total RTU frame length for an incoming request (slave-side framing). */
47
+ predictRequestLength: (buffer: Buffer) => number | null;
48
+ /** Predict total RTU frame length for an incoming response (master-side framing). */
49
+ predictResponseLength: (buffer: Buffer) => number | null;
50
+ /**
51
+ * Slave-side handler. Receives PDU payload (bytes after `fc`, before CRC) and
52
+ * the unit ID being addressed; must return the PDU payload of the response.
53
+ *
54
+ * Throwing inside `handle` is turned into a Modbus exception response by the slave.
55
+ * If `handle` is omitted, the slave returns an ILLEGAL_FUNCTION exception for this FC.
56
+ */
57
+ handle?: FConvertPromise<(data: Buffer, unit: number) => Buffer>;
58
+ }
59
+
5
60
  declare enum ErrorCode {
6
61
  ILLEGAL_FUNCTION = 1,
7
62
  ILLEGAL_DATA_ADDRESS = 2,
@@ -13,13 +68,91 @@ declare enum ErrorCode {
13
68
  GATEWAY_PATH_UNAVAILABLE = 10,
14
69
  GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND = 11
15
70
  }
16
- declare function getErrorByCode(code: ErrorCode): Error;
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
+ declare class ModbusError extends Error {
94
+ readonly code: string;
95
+ constructor(code: string, message?: string);
96
+ }
97
+ declare function getErrorByCode(code: ErrorCode): ModbusError;
17
98
  declare function getCodeByError(err: Error): ErrorCode;
18
99
 
100
+ /**
101
+ * Standard Modbus function codes (V1.1b3 §6).
102
+ */
103
+ declare enum FunctionCode {
104
+ READ_COILS = 1,
105
+ READ_DISCRETE_INPUTS = 2,
106
+ READ_HOLDING_REGISTERS = 3,
107
+ READ_INPUT_REGISTERS = 4,
108
+ WRITE_SINGLE_COIL = 5,
109
+ WRITE_SINGLE_REGISTER = 6,
110
+ WRITE_MULTIPLE_COILS = 15,
111
+ WRITE_MULTIPLE_REGISTERS = 16,
112
+ REPORT_SERVER_ID = 17,
113
+ MASK_WRITE_REGISTER = 22,
114
+ READ_WRITE_MULTIPLE_REGISTERS = 23,
115
+ READ_DEVICE_IDENTIFICATION = 43
116
+ }
117
+ /** Exception response FC = request FC | EXCEPTION_OFFSET (V1.1b3 §7). */
118
+ declare const EXCEPTION_OFFSET = 128;
119
+ /** Coil value encoding for FC 5 / FC 15 (V1.1b3 §6.5/§6.11). */
120
+ declare const COIL_ON = 65280;
121
+ declare const COIL_OFF = 0;
122
+ /** FC 0x2B MEI sub-function selecting Read Device Identification (V1.1b3 §6.21). */
123
+ declare const MEI_READ_DEVICE_ID = 14;
124
+ /** Read Device ID code values inside an FC 0x2B / MEI 0x0E request. */
125
+ declare enum ReadDeviceIDCode {
126
+ BASIC_STREAM = 1,
127
+ REGULAR_STREAM = 2,
128
+ EXTENDED_STREAM = 3,
129
+ SPECIFIC_ACCESS = 4
130
+ }
131
+ /** Conformity level reported in an FC 0x2B / MEI 0x0E response. */
132
+ declare enum ConformityLevel {
133
+ BASIC = 129,
134
+ REGULAR = 130,
135
+ EXTENDED = 131
136
+ }
137
+ /** Modbus V1.1b3 PDU quantity limits. */
138
+ declare const LIMITS: {
139
+ readonly READ_COILS_MIN: 1;
140
+ readonly READ_COILS_MAX: 2000;
141
+ readonly READ_REGISTERS_MIN: 1;
142
+ readonly READ_REGISTERS_MAX: 125;
143
+ readonly WRITE_COILS_MAX: 1968;
144
+ readonly WRITE_REGISTERS_MAX: 123;
145
+ readonly RW_REGISTERS_WRITE_MAX: 121;
146
+ };
147
+
148
+ interface PhysicalConnection {
149
+ readonly id: string | number;
150
+ }
19
151
  interface AbstractPhysicalLayerEvents {
20
- data: [data: Buffer, response: (data: Buffer) => Promise<void>];
152
+ data: [data: Buffer, response: (data: Buffer) => Promise<void>, connection: PhysicalConnection];
21
153
  write: [data: Buffer];
22
154
  error: [error: Error];
155
+ 'connection-close': [connection: PhysicalConnection];
23
156
  close: [];
24
157
  }
25
158
  declare abstract class AbstractPhysicalLayer extends EventEmitter<AbstractPhysicalLayerEvents> {
@@ -60,6 +193,8 @@ interface SerialPhysicalLayerOptions {
60
193
  declare class SerialPhysicalLayer extends AbstractPhysicalLayer {
61
194
  TYPE: 'SERIAL' | 'NET';
62
195
  private _serialport;
196
+ private _connection;
197
+ private _isOpening;
63
198
  private _destroyed;
64
199
  private _baudRate;
65
200
  get isOpen(): boolean;
@@ -75,8 +210,11 @@ declare class SerialPhysicalLayer extends AbstractPhysicalLayer {
75
210
  declare class TcpClientPhysicalLayer extends AbstractPhysicalLayer {
76
211
  TYPE: 'SERIAL' | 'NET';
77
212
  private _socket;
213
+ private _connection;
78
214
  private _isOpen;
215
+ private _isOpening;
79
216
  private _destroyed;
217
+ private _socketOptions?;
80
218
  get isOpen(): boolean;
81
219
  get destroyed(): boolean;
82
220
  constructor(options?: SocketConstructorOpts);
@@ -90,8 +228,10 @@ declare class TcpServerPhysicalLayer extends AbstractPhysicalLayer {
90
228
  TYPE: 'SERIAL' | 'NET';
91
229
  private _server;
92
230
  private _isOpen;
231
+ private _isOpening;
93
232
  private _destroyed;
94
- private _sockets;
233
+ private _connections;
234
+ private _serverOptions?;
95
235
  get isOpen(): boolean;
96
236
  get destroyed(): boolean;
97
237
  constructor(options?: NetConnectOpts);
@@ -101,123 +241,151 @@ declare class TcpServerPhysicalLayer extends AbstractPhysicalLayer {
101
241
  destroy(): Promise<void>;
102
242
  }
103
243
 
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?: {
252
+ port?: number;
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
+ */
261
+ idleTimeout?: number;
262
+ }
104
263
  declare class UdpPhysicalLayer extends AbstractPhysicalLayer {
105
264
  TYPE: 'SERIAL' | 'NET';
106
265
  private _socket;
266
+ private _connections;
107
267
  private _isOpen;
268
+ private _isOpening;
108
269
  private _destroyed;
270
+ private _socketOptions?;
109
271
  private _port;
110
272
  private _address?;
273
+ private _idleTimeout;
111
274
  isServer: boolean;
112
275
  get isOpen(): boolean;
113
276
  get destroyed(): boolean;
114
- /**
115
- *
116
- * @param options
117
- * @param remote If omitted, as server.
118
- * Otherwise as client.
119
- */
120
- constructor(options?: Partial<SocketOptions>, remote?: {
121
- port?: number;
122
- address?: string;
123
- });
277
+ constructor(options?: UdpPhysicalLayerOptions);
124
278
  open(options?: BindOptions): Promise<void>;
279
+ private _handleMessage;
125
280
  write(data: Buffer): Promise<void>;
126
281
  close(): Promise<void>;
127
282
  destroy(): Promise<void>;
128
283
  }
129
284
 
130
- type FConvertPromise<F extends (...args: any) => any> = F extends (...args: infer A) => infer R ? ((...args: A) => Promise<R>) | ((...args: A) => R) : never;
131
- interface ApplicationDataUnit {
132
- transaction?: number;
133
- unit: number;
134
- fc: number;
135
- data: number[];
136
- }
137
- interface ServerId {
138
- serverId?: number;
139
- runIndicatorStatus?: boolean;
140
- additionalData?: number[];
141
- }
142
- interface DeviceIdentification {
143
- readDeviceIDCode: number;
144
- conformityLevel: number;
145
- moreFollows: boolean;
146
- nextObjectId: number;
147
- objects: {
148
- id: number;
149
- value: string;
150
- }[];
151
- }
152
-
153
285
  interface AbstractApplicationLayerEvents {
154
286
  framing: [frame: ApplicationDataUnit & {
155
287
  buffer: Buffer;
156
- }, response: (data: Buffer) => Promise<void>];
288
+ }, response: (data: Buffer) => Promise<void>, connection: PhysicalConnection];
289
+ 'framing-error': [error: Error];
157
290
  }
158
291
  declare abstract class AbstractApplicationLayer extends EventEmitter<AbstractApplicationLayerEvents> {
159
- abstract startWaitingResponse(preCheck: ((frame: ApplicationDataUnit & {
160
- buffer: Buffer;
161
- }) => boolean | number | undefined)[], callback: (error: Error | null, frame?: ApplicationDataUnit & {
162
- buffer: Buffer;
163
- }) => void): void;
164
- abstract stopWaitingResponse(): void;
292
+ abstract readonly PROTOCOL: 'TCP' | 'RTU' | 'ASCII';
293
+ private _role?;
294
+ get role(): 'MASTER' | 'SLAVE';
295
+ set role(value: 'MASTER' | 'SLAVE');
296
+ flush(): void;
297
+ addCustomFunctionCode(cfc: CustomFunctionCode): void;
298
+ removeCustomFunctionCode(fc: number): void;
165
299
  abstract encode(data: ApplicationDataUnit): Buffer;
166
300
  abstract destroy(): void;
167
301
  }
168
302
 
169
- declare class RtuApplicationLayer extends AbstractApplicationLayer {
170
- private _waitingResponse?;
171
- private _timerThreePointFive?;
172
- private _bufferRx;
173
- private _removeAllListeners;
174
- constructor(physicalLayer: SerialPhysicalLayer | TcpServerPhysicalLayer | TcpClientPhysicalLayer | UdpPhysicalLayer,
303
+ interface AsciiApplicationLayerOptions {
175
304
  /**
176
- * The time interval between two frames, support two formats:
177
- * - bit: `48bit` as default
178
- * - millisecond: `20ms`
305
+ * Accept lowercase hex digits (`a-f`) in addition to uppercase (`A-F`).
306
+ * Default false (strict per Modbus V1.1b3 §2.2 — uppercase only).
307
+ * Non-hex characters are always rejected with a `framing-error`.
179
308
  */
180
- intervalBetweenFrames?: `${number}bit` | `${number}ms`);
309
+ lenientHex?: boolean;
310
+ }
311
+ declare class AsciiApplicationLayer extends AbstractApplicationLayer {
312
+ readonly PROTOCOL: "ASCII";
313
+ readonly lenientHex: boolean;
314
+ private _states;
315
+ private _removeAllListeners;
316
+ constructor(physicalLayer: SerialPhysicalLayer | TcpServerPhysicalLayer | TcpClientPhysicalLayer | UdpPhysicalLayer, options?: AsciiApplicationLayerOptions);
317
+ private getState;
318
+ flush(): void;
181
319
  private framing;
182
- startWaitingResponse(preCheck: ((frame: ApplicationDataUnit & {
183
- buffer: Buffer;
184
- }) => boolean | number | undefined)[], callback: (error: Error | null, frame?: ApplicationDataUnit & {
185
- buffer: Buffer;
186
- }) => void): void;
187
- stopWaitingResponse(): void;
188
320
  encode(data: ApplicationDataUnit): Buffer;
189
321
  destroy(): void;
190
322
  }
191
323
 
192
- declare class AsciiApplicationLayer extends AbstractApplicationLayer {
193
- private _waitingResponse?;
194
- private _status;
195
- private _frame;
324
+ interface RtuApplicationLayerOptions {
325
+ /**
326
+ * Inter-frame silence (Modbus RTU t3.5). Defaults to `{ unit: 'bit', value: 38.5 }`
327
+ * for serial (3.5 character times × 11 bits/char per Modbus V1.02 §2.5.1.1);
328
+ * ignored for TCP/UDP transports.
329
+ * - `{ unit: 'bit', value: 38.5 }` — spec bit-time approximation
330
+ * - `{ unit: 'ms', value: 20 }` — explicit milliseconds
331
+ *
332
+ * Per Modbus V1.02 §2.5.1.1, at baud rates > 19200 the spec uses a fixed
333
+ * 1.75 ms regardless of the bit value supplied.
334
+ */
335
+ intervalBetweenFrames?: {
336
+ unit: 'bit' | 'ms';
337
+ value: number;
338
+ };
339
+ /**
340
+ * Inter-character timeout (Modbus RTU t1.5). Opt-in; **disabled** by default
341
+ * because Node.js `setTimeout` precision (~1 ms minimum, ~15.6 ms on Windows)
342
+ * is too coarse to reliably honor t1.5 at common baud rates without
343
+ * false-positive frame discards. When set, a mid-frame gap exceeding this
344
+ * duration discards the in-progress buffer and emits `framing-error`.
345
+ * Only takes effect on serial transports.
346
+ *
347
+ * - `{ unit: 'bit', value: 21 }` — bit-time approximation (~1.5 char times)
348
+ * - `{ unit: 'ms', value: 1 }` — explicit milliseconds
349
+ *
350
+ * Per Modbus V1.02 §2.5.1.1, at baud rates > 19200 the spec uses a fixed
351
+ * 0.75 ms regardless of the bit value supplied.
352
+ */
353
+ interCharTimeout?: {
354
+ unit: 'bit' | 'ms';
355
+ value: number;
356
+ };
357
+ }
358
+ declare class RtuApplicationLayer extends AbstractApplicationLayer {
359
+ readonly PROTOCOL: "RTU";
360
+ private _states;
361
+ private _customFunctionCodes;
196
362
  private _removeAllListeners;
197
- constructor(physicalLayer: SerialPhysicalLayer | TcpServerPhysicalLayer | TcpClientPhysicalLayer | UdpPhysicalLayer);
198
- private framing;
199
- startWaitingResponse(preCheck: ((frame: ApplicationDataUnit & {
200
- buffer: Buffer;
201
- }) => boolean | number | undefined)[], callback: (error: Error | null, frame?: ApplicationDataUnit & {
202
- buffer: Buffer;
203
- }) => void): void;
204
- stopWaitingResponse(): void;
363
+ private _threePointFiveT;
364
+ private _onePointFiveT;
365
+ constructor(physicalLayer: SerialPhysicalLayer | TcpServerPhysicalLayer | TcpClientPhysicalLayer | UdpPhysicalLayer, options?: RtuApplicationLayerOptions);
366
+ private getState;
367
+ private clearStateTimers;
368
+ private clearState;
369
+ flush(): void;
370
+ addCustomFunctionCode(cfc: CustomFunctionCode): void;
371
+ removeCustomFunctionCode(fc: number): void;
372
+ private flushBuffer;
373
+ private tryExtract;
374
+ private checkExpected;
375
+ private crcMatches;
376
+ private deliverFrame;
205
377
  encode(data: ApplicationDataUnit): Buffer;
206
378
  destroy(): void;
207
379
  }
208
380
 
209
381
  declare class TcpApplicationLayer extends AbstractApplicationLayer {
210
- private _waitingResponse?;
382
+ readonly PROTOCOL: "TCP";
211
383
  private _transactionId;
384
+ private _buffers;
212
385
  private _removeAllListeners;
213
386
  constructor(physicalLayer: TcpServerPhysicalLayer | TcpClientPhysicalLayer | UdpPhysicalLayer);
214
- private framing;
215
- startWaitingResponse(preCheck: ((frame: ApplicationDataUnit & {
216
- buffer: Buffer;
217
- }) => boolean | number | undefined)[], callback: (error: Error | null, frame?: ApplicationDataUnit & {
218
- buffer: Buffer;
219
- }) => void): void;
220
- stopWaitingResponse(): void;
387
+ private tryExtract;
388
+ private processFrame;
221
389
  encode(data: ApplicationDataUnit): Buffer;
222
390
  destroy(): void;
223
391
  }
@@ -233,14 +401,32 @@ interface ReturnValue<T> {
233
401
  data: T;
234
402
  buffer: Buffer;
235
403
  }
404
+ interface ModbusMasterOptions {
405
+ /** Per-request timeout in ms. Default 1000. */
406
+ timeout?: number;
407
+ /**
408
+ * Enable pipelined concurrent requests on a single connection.
409
+ * Only valid for Modbus TCP application layer.
410
+ * Default false (FIFO queue, requests are serialized).
411
+ */
412
+ concurrent?: boolean;
413
+ }
236
414
  declare class ModbusMaster<A extends AbstractApplicationLayer, P extends AbstractPhysicalLayer> extends EventEmitter<ModbusMasterEvents> {
237
415
  private applicationLayer;
238
416
  private physicalLayer;
417
+ private _masterSession;
418
+ private _queue;
419
+ private _draining;
420
+ private _nextTid;
421
+ private _closed;
239
422
  timeout: number;
423
+ readonly concurrent: boolean;
240
424
  get isOpen(): boolean;
241
425
  get destroyed(): boolean;
242
- constructor(applicationLayer: A, physicalLayer: P, timeout?: number);
243
- private waitResponse;
426
+ constructor(applicationLayer: A, physicalLayer: P, options?: ModbusMasterOptions);
427
+ private send;
428
+ private _drain;
429
+ private _exchange;
244
430
  private writeFC1Or2;
245
431
  writeFC1: this['readCoils'];
246
432
  readCoils(unit: 0, address: number, length: number, timeout?: number): Promise<void>;
@@ -268,8 +454,8 @@ declare class ModbusMaster<A extends AbstractApplicationLayer, P extends Abstrac
268
454
  writeMultipleRegisters(unit: 0, address: number, value: number[], timeout?: number): Promise<void>;
269
455
  writeMultipleRegisters(unit: number, address: number, value: number[], timeout?: number): Promise<ReturnValue<number[]>>;
270
456
  handleFC17: this['reportServerId'];
271
- reportServerId(unit: 0, timeout?: number): Promise<void>;
272
- reportServerId(unit: number, timeout?: number): Promise<ReturnValue<ServerId>>;
457
+ reportServerId(unit: 0, serverIdLength?: number, timeout?: number): Promise<void>;
458
+ reportServerId(unit: number, serverIdLength?: number, timeout?: number): Promise<ReturnValue<ServerId>>;
273
459
  handleFC22: this['maskWriteRegister'];
274
460
  maskWriteRegister(unit: 0, address: number, andMask: number, orMask: number, timeout?: number): Promise<void>;
275
461
  maskWriteRegister(unit: number, address: number, andMask: number, orMask: number, timeout?: number): Promise<ReturnValue<{
@@ -294,11 +480,31 @@ declare class ModbusMaster<A extends AbstractApplicationLayer, P extends Abstrac
294
480
  handleFC43_14: this['readDeviceIdentification'];
295
481
  readDeviceIdentification(unit: 0, readDeviceIDCode: number, objectId: number, timeout?: number): Promise<void>;
296
482
  readDeviceIdentification(unit: number, readDeviceIDCode: number, objectId: number, timeout?: number): Promise<ReturnValue<DeviceIdentification>>;
483
+ addCustomFunctionCode(cfc: CustomFunctionCode): void;
484
+ removeCustomFunctionCode(fc: number): void;
485
+ sendCustomFC(unit: 0, fc: number, data: Buffer | number[], timeout?: number): Promise<void>;
486
+ sendCustomFC(unit: number, fc: number, data: Buffer | number[], timeout?: number): Promise<Buffer>;
297
487
  open(...args: Parameters<P['open']>): Promise<void>;
298
488
  close(): Promise<void>;
299
489
  destroy(): Promise<void>;
300
490
  }
301
491
 
492
+ type Frame = ApplicationDataUnit & {
493
+ buffer: Buffer;
494
+ };
495
+ type PreCheck = (frame: Frame) => boolean | number | undefined;
496
+ type Callback = (error: Error | null, frame?: Frame) => void;
497
+ declare class MasterSession {
498
+ private _waiters;
499
+ start(key: string | number, preCheck: PreCheck[], callback: Callback): void;
500
+ stop(key: string | number): void;
501
+ stopAll(error: Error): void;
502
+ has(key: string | number): boolean;
503
+ handleFrame(frame: Frame): void;
504
+ handleError(error: Error): void;
505
+ private runPreChecks;
506
+ }
507
+
302
508
  interface ModbusSlaveModel {
303
509
  unit?: number;
304
510
  /**
@@ -307,7 +513,7 @@ interface ModbusSlaveModel {
307
513
  * If provide the return value, use this value as data of `PDU` to respond.
308
514
  * Otherwise keep the default read and write behavior.
309
515
  */
310
- interceptor?: FConvertPromise<(fc: number, data: number[]) => number[] | undefined>;
516
+ interceptor?: FConvertPromise<(fc: number, data: Buffer) => Buffer | undefined>;
311
517
  readDiscreteInputs?: FConvertPromise<(address: number, length: number) => boolean[]>;
312
518
  readCoils?: FConvertPromise<(address: number, length: number) => boolean[]>;
313
519
  writeSingleCoil?: FConvertPromise<(address: number, value: boolean) => void>;
@@ -341,13 +547,25 @@ interface ModbusSlaveEvents {
341
547
  error: [error: Error];
342
548
  close: [];
343
549
  }
550
+ interface ModbusSlaveOptions {
551
+ /**
552
+ * Pipelined concurrent processing of requests within one connection.
553
+ * Only valid for Modbus TCP application layer (TID disambiguates responses).
554
+ * Default false — per-connection FIFO. RTU/ASCII + concurrent=true throws.
555
+ */
556
+ concurrent?: boolean;
557
+ }
344
558
  declare class ModbusSlave<A extends AbstractApplicationLayer, P extends AbstractPhysicalLayer> extends EventEmitter<ModbusSlaveEvents> {
345
559
  private applicationLayer;
346
560
  private physicalLayer;
347
561
  models: Map<number, ModbusSlaveModel>;
562
+ readonly concurrent: boolean;
563
+ private _queues;
564
+ private _customFunctionCodes;
565
+ private _locks;
348
566
  get isOpen(): boolean;
349
567
  get destroyed(): boolean;
350
- constructor(applicationLayer: A, physicalLayer: P);
568
+ constructor(applicationLayer: A, physicalLayer: P, options?: ModbusSlaveOptions);
351
569
  private handleFC1;
352
570
  private handleFC2;
353
571
  private handleFC3;
@@ -361,12 +579,19 @@ declare class ModbusSlave<A extends AbstractApplicationLayer, P extends Abstract
361
579
  private handleFC23;
362
580
  private handleFC43_14;
363
581
  private responseError;
582
+ private _drain;
583
+ private _processFrame;
584
+ private _intercept;
585
+ private withAddressLock;
586
+ private _handleFC;
364
587
  add(model: ModbusSlaveModel): void;
365
588
  remove(unit: number): void;
589
+ addCustomFunctionCode(cfc: CustomFunctionCode): void;
590
+ removeCustomFunctionCode(fc: number): void;
366
591
  open(...args: Parameters<P['open']>): Promise<void>;
367
592
  close(): Promise<void>;
368
593
  destroy(): Promise<void>;
369
594
  }
370
595
 
371
- export { AbstractApplicationLayer, AbstractPhysicalLayer, AsciiApplicationLayer, ErrorCode, ModbusMaster, ModbusSlave, RtuApplicationLayer, SerialPhysicalLayer, TcpApplicationLayer, TcpClientPhysicalLayer, TcpServerPhysicalLayer, UdpPhysicalLayer, getCodeByError, getErrorByCode };
372
- export type { ModbusSlaveModel };
596
+ 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 };
597
+ export type { ApplicationDataUnit, AsciiApplicationLayerOptions, Callback$1 as Callback, CustomFunctionCode, DeviceIdentification, FConvertPromise, ModbusMasterOptions, ModbusSlaveModel, ModbusSlaveOptions, PhysicalConnection, RtuApplicationLayerOptions, ServerId };