njs-modbus 3.4.0 → 4.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.
package/dist/index.d.ts DELETED
@@ -1,867 +0,0 @@
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';
4
- import EventEmitter from 'node:events';
5
-
6
- type Callback$1<T> = (error: Error | null, value: T) => void;
7
- type MaybeAsyncFunction<F extends (...args: any) => any> = F extends (...args: infer A) => infer R ? ((...args: A) => Promise<R>) | ((...args: A) => R) : never;
8
- interface ApplicationDataUnit {
9
- transaction?: number;
10
- unit: number;
11
- fc: number;
12
- data: Buffer;
13
- }
14
- interface ServerId {
15
- /** Server ID as a byte array (e.g. single-byte IDs use `[id]`). */
16
- serverId?: number[];
17
- runIndicatorStatus?: boolean;
18
- /** Additional data bytes. Using Buffer avoids intermediate array conversions. */
19
- additionalData?: Buffer;
20
- }
21
- interface DeviceIdentification {
22
- readDeviceIDCode: number;
23
- conformityLevel: number;
24
- moreFollows: boolean;
25
- nextObjectId: number;
26
- objects: {
27
- id: number;
28
- value: string;
29
- }[];
30
- }
31
- /**
32
- * Defines a non-standard / user-defined Modbus function code.
33
- *
34
- * Registration paths:
35
- * - `RtuApplicationLayer.addCustomFunctionCode(cfc)` — framing only.
36
- * - `ModbusSlave.addCustomFunctionCode(cfc)` — framing + slave-side dispatch via `handle`.
37
- * - `ModbusMaster.addCustomFunctionCode(cfc)` + `ModbusMaster.sendCustomFC(...)` — framing + request issuance.
38
- *
39
- * The two `predict*` callbacks declare how to derive the total RTU frame length
40
- * (PDU + 2-byte CRC) from leading bytes; they are required so the framing FSM
41
- * can advance without the deleted sliding-window CRC fallback.
42
- *
43
- * Return `0` (`PREDICT_NEED_MORE`) to signal "need more bytes before I can decide".
44
- * Return `-1` (`PREDICT_UNKNOWN`) to signal "cannot determine the length".
45
- * Return a positive integer (>= 4, <= 256) for the total frame length.
46
- */
47
- interface CustomFunctionCode {
48
- fc: number;
49
- /**
50
- * Predict total RTU frame length for an incoming request (slave-side framing).
51
- *
52
- * @param getByte — Zero-based accessor; `getByte(i)` returns the byte at offset
53
- * `i` within the candidate frame (0 = unit ID, 1 = function code, …).
54
- * @param length — Number of bytes available so far. Use this for bounds checks
55
- * rather than any buffer property.
56
- * @returns Total frame length (>= 4, <= 256), `0` if more bytes are needed,
57
- * or `-1` if the length cannot be determined.
58
- */
59
- predictRequestLength: (getByte: (idx: number) => number, length: number) => number;
60
- /**
61
- * Predict total RTU frame length for an incoming response (master-side framing).
62
- *
63
- * @param getByte — Zero-based accessor; `getByte(i)` returns the byte at offset
64
- * `i` within the candidate frame (0 = unit ID, 1 = function code, …).
65
- * @param length — Number of bytes available so far. Use this for bounds checks
66
- * rather than any buffer property.
67
- * @returns Total frame length (>= 4, <= 256), `0` if more bytes are needed,
68
- * or `-1` if the length cannot be determined.
69
- */
70
- predictResponseLength: (getByte: (idx: number) => number, length: number) => number;
71
- /**
72
- * Slave-side handler. Receives PDU payload (bytes after `fc`, before CRC) and
73
- * the unit ID being addressed; must return the PDU payload of the response.
74
- *
75
- * Throwing inside `handle` is turned into a Modbus exception response by the slave.
76
- * If `handle` is omitted, the slave returns an ILLEGAL_FUNCTION exception for this FC.
77
- */
78
- handle?: MaybeAsyncFunction<(data: Buffer, unit: number) => Buffer>;
79
- }
80
-
81
- declare enum ErrorCode {
82
- ILLEGAL_FUNCTION = 1,
83
- ILLEGAL_DATA_ADDRESS = 2,
84
- ILLEGAL_DATA_VALUE = 3,
85
- SERVER_DEVICE_FAILURE = 4,
86
- ACKNOWLEDGE = 5,
87
- SERVER_DEVICE_BUSY = 6,
88
- MEMORY_PARITY_ERROR = 8,
89
- GATEWAY_PATH_UNAVAILABLE = 10,
90
- GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND = 11
91
- }
92
- declare class ModbusError extends Error {
93
- readonly code: ErrorCode;
94
- constructor(code: ErrorCode, message?: string);
95
- }
96
- declare function getErrorByCode(code: ErrorCode): ModbusError;
97
- declare function getCodeByError(err: Error): ErrorCode;
98
-
99
- /**
100
- * Standard Modbus function codes (V1.1b3 §6).
101
- */
102
- declare enum FunctionCode {
103
- READ_COILS = 1,
104
- READ_DISCRETE_INPUTS = 2,
105
- READ_HOLDING_REGISTERS = 3,
106
- READ_INPUT_REGISTERS = 4,
107
- WRITE_SINGLE_COIL = 5,
108
- WRITE_SINGLE_REGISTER = 6,
109
- WRITE_MULTIPLE_COILS = 15,
110
- WRITE_MULTIPLE_REGISTERS = 16,
111
- REPORT_SERVER_ID = 17,
112
- MASK_WRITE_REGISTER = 22,
113
- READ_WRITE_MULTIPLE_REGISTERS = 23,
114
- READ_DEVICE_IDENTIFICATION = 43
115
- }
116
- /** Exception response FC = request FC | EXCEPTION_OFFSET (V1.1b3 §7). */
117
- declare const EXCEPTION_OFFSET = 128;
118
- /** Coil value encoding for FC 5 / FC 15 (V1.1b3 §6.5/§6.11). */
119
- declare const COIL_ON = 65280;
120
- declare const COIL_OFF = 0;
121
- /** FC 0x2B MEI sub-function selecting Read Device Identification (V1.1b3 §6.21). */
122
- declare const MEI_READ_DEVICE_ID = 14;
123
- /** Read Device ID code values inside an FC 0x2B / MEI 0x0E request. */
124
- declare enum ReadDeviceIDCode {
125
- BASIC_STREAM = 1,
126
- REGULAR_STREAM = 2,
127
- EXTENDED_STREAM = 3,
128
- SPECIFIC_ACCESS = 4
129
- }
130
- /** Conformity level reported in an FC 0x2B / MEI 0x0E response. */
131
- declare enum ConformityLevel {
132
- BASIC = 129,
133
- REGULAR = 130,
134
- EXTENDED = 131
135
- }
136
- /** Shared empty Buffer to avoid repeated allocations. */
137
- declare const EMPTY_BUFFER: Buffer<ArrayBuffer>;
138
- /** Shared no-op function to avoid repeated allocations. */
139
- declare const NOOP: () => void;
140
- /** Modbus V1.1b3 PDU quantity limits. */
141
- declare const LIMITS: {
142
- readonly READ_COILS_MIN: 1;
143
- readonly READ_COILS_MAX: 2000;
144
- readonly READ_REGISTERS_MIN: 1;
145
- readonly READ_REGISTERS_MAX: 125;
146
- readonly WRITE_COILS_MAX: 1968;
147
- readonly WRITE_REGISTERS_MAX: 123;
148
- readonly RW_REGISTERS_WRITE_MAX: 121;
149
- };
150
-
151
- declare enum PhysicalState {
152
- OPENING = "opening",
153
- OPEN = "open",
154
- CLOSING = "closing",
155
- CLOSED = "closed"
156
- }
157
- declare enum PhysicalConnectionState {
158
- CONNECTED = "connected",
159
- DESTROYING = "destroying",
160
- DESTROYED = "destroyed"
161
- }
162
-
163
- interface SerialPhysicalLayerOptions {
164
- /** The system path of the serial port you want to open. For example, `/dev/tty.XXX` on Mac/Linux, or `COM1` on Windows */
165
- path: string;
166
- /**
167
- * 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.
168
- */
169
- baudRate: number;
170
- /** Must be one of these: 5, 6, 7, or 8. Defaults to 8 */
171
- dataBits?: 5 | 6 | 7 | 8;
172
- /** Prevent other processes from opening the port. Windows does not currently support `false`. Defaults to true */
173
- lock?: boolean;
174
- /** Must be 1, 1.5 or 2. Defaults to 1 */
175
- stopBits?: 1 | 1.5 | 2;
176
- /** Device parity. Defaults to none */
177
- parity?: 'none' | 'even' | 'odd' | 'mark' | 'space';
178
- /** Flow control Setting. Defaults to false */
179
- rtscts?: boolean;
180
- /** Flow control Setting. Defaults to false */
181
- xon?: boolean;
182
- /** Flow control Setting. Defaults to false */
183
- xoff?: boolean;
184
- /** Flow control Setting. Defaults to false */
185
- xany?: boolean;
186
- /** drop DTR on close. Defaults to true */
187
- hupcl?: boolean;
188
- /** The size of the read and write buffers. Defaults to 64k */
189
- highWaterMark?: number;
190
- /** Emit 'end' on port close. Defaults to false */
191
- endOnClose?: boolean;
192
- /** see `man termios`. Defaults to 1 (Darwin/Linux only) */
193
- vmin?: number;
194
- /** see `man termios`. Defaults to 0 (Darwin/Linux only) */
195
- vtime?: number;
196
- /** RTS mode. Defaults to handshake (Windows only) */
197
- rtsMode?: 'handshake' | 'enable' | 'toggle';
198
- }
199
- declare class SerialPhysicalLayer extends AbstractPhysicalLayer {
200
- readonly TYPE: "SERIAL";
201
- private _state;
202
- private _connections;
203
- private _serialport;
204
- private _serialportOpts;
205
- private _path;
206
- private _baudRate;
207
- private _pendingOpenCbs;
208
- private _pendingCloseCbs;
209
- private _cleanupFns;
210
- get state(): PhysicalState;
211
- get serialport(): SerialPort | null;
212
- get path(): string;
213
- get baudRate(): number;
214
- constructor(options: SerialPhysicalLayerOptions);
215
- open(cb?: (err?: Error | null) => void): void;
216
- close(cb?: (err?: Error | null) => void): void;
217
- }
218
-
219
- declare class TcpClientPhysicalLayer extends AbstractPhysicalLayer {
220
- readonly TYPE: "TCP_CLIENT";
221
- private _state;
222
- private _connections;
223
- private _socket;
224
- private _socketOpts?;
225
- private _pendingOpenCbs;
226
- private _pendingCloseCbs;
227
- private _cleanupFns;
228
- get state(): PhysicalState;
229
- get socket(): Socket | null;
230
- constructor(options?: SocketConstructorOpts);
231
- open(cb?: (err?: Error | null) => void): void;
232
- open(options: SocketConnectOpts, cb?: (err?: Error | null) => void): void;
233
- close(cb?: (err?: Error | null) => void): void;
234
- }
235
-
236
- interface TcpServerPhysicalLayerOptions {
237
- /** Allowed client IP addresses. IPv4-mapped IPv6 addresses (e.g., ::ffff:192.168.1.1) are normalized before checking. */
238
- whitelist?: string[];
239
- /** Maximum number of concurrent connections. When exceeded, new connections are silently dropped by Node.js. */
240
- maxConnections?: number;
241
- /** Idle timeout in ms. Connection is destroyed if no data is received within this time. 0 = disabled. */
242
- idleTimeout?: number;
243
- /** Forwarded to `net.createServer()`. */
244
- serverOpts?: ServerOpts;
245
- }
246
- declare class TcpServerPhysicalLayer extends AbstractPhysicalLayer {
247
- readonly TYPE: "TCP_SERVER";
248
- private _state;
249
- private _connections;
250
- private _server;
251
- private _opts;
252
- private _pendingOpenCbs;
253
- private _pendingCloseCbs;
254
- private _cleanupFns;
255
- get state(): PhysicalState;
256
- get server(): Server | null;
257
- constructor(options?: TcpServerPhysicalLayerOptions);
258
- open(cb?: (err?: Error | null) => void): void;
259
- open(options: ListenOptions, cb?: (err?: Error | null) => void): void;
260
- close(cb?: (err?: Error | null) => void): void;
261
- }
262
-
263
- declare class UdpClientPhysicalLayer extends AbstractPhysicalLayer {
264
- readonly TYPE: "UDP_CLIENT";
265
- private _state;
266
- private _connections;
267
- private _socket;
268
- private _socketOpts;
269
- private _pendingOpenCbs;
270
- private _pendingCloseCbs;
271
- private _cleanupFns;
272
- get state(): PhysicalState;
273
- get socket(): Socket$1 | null;
274
- constructor(options?: Partial<SocketOptions>);
275
- open(cb?: (err?: Error | null) => void): void;
276
- open(remote: {
277
- port?: number;
278
- address?: string;
279
- }, cb?: (err?: Error | null) => void): void;
280
- close(cb?: (err?: Error | null) => void): void;
281
- }
282
-
283
- interface UdpServerPhysicalLayerOptions {
284
- /** Allowed client IP addresses. IPv4-mapped IPv6 addresses (e.g., ::ffff:192.168.1.1) are normalized before checking. */
285
- whitelist?: string[];
286
- /** Maximum number of concurrent virtual client connections. When exceeded, datagrams from new clients are silently ignored. */
287
- maxConnections?: number;
288
- /** Connection idle timeout in ms. Pass `0` to disable eviction. Defaults to 30000. */
289
- idleTimeout?: number;
290
- /** Forwarded to `dgram.createSocket()`. */
291
- socketOpts?: Partial<SocketOptions>;
292
- }
293
- declare class UdpServerPhysicalLayer extends AbstractPhysicalLayer {
294
- readonly TYPE: "UDP_SERVER";
295
- private _state;
296
- private _connections;
297
- private _socket;
298
- private _opts;
299
- private _pendingOpenCbs;
300
- private _pendingCloseCbs;
301
- private _cleanupFns;
302
- get state(): PhysicalState;
303
- get socket(): Socket$1 | null;
304
- constructor(options?: UdpServerPhysicalLayerOptions);
305
- /**
306
- * Bind the UDP socket and start accepting datagrams.
307
- *
308
- * @param options Bind options (port, address, etc.). Defaults to port 502.
309
- * @param cb Callback invoked when binding completes or fails.
310
- */
311
- open(cb?: (err?: Error | null) => void): void;
312
- open(options: BindOptions, cb?: (err?: Error | null) => void): void;
313
- close(cb?: (err?: Error | null) => void): void;
314
- }
315
-
316
- interface AbstractPhysicalConnectionEvents {
317
- data: [data: Buffer];
318
- close: [];
319
- /**
320
- * Emitted when raw data has been successfully written to the wire.
321
- * The `buffer` is a live reference — mutating it will corrupt the frame
322
- * that the connection is still processing. Treat it as read-only.
323
- */
324
- tx: [buffer: Buffer];
325
- /**
326
- * Emitted when raw data is received from the wire.
327
- * The `buffer` is a live reference — mutating it will corrupt the frame
328
- * that the application layer is about to parse. Treat it as read-only.
329
- */
330
- rx: [buffer: Buffer];
331
- }
332
- /**
333
- * A one-way data transmission channel.
334
- *
335
- * State transitions are unidirectional: CONNECTED → DESTROYING → DESTROYED.
336
- * Once closed, the instance cannot be reused; create a new connection instead.
337
- */
338
- declare abstract class AbstractPhysicalConnection extends EventEmitter<AbstractPhysicalConnectionEvents> {
339
- abstract readonly state: PhysicalConnectionState;
340
- abstract readonly physicalLayer: AbstractPhysicalLayer;
341
- abstract write(data: Buffer, cb?: (err?: Error | null) => void): void;
342
- abstract destroy(cb?: (err?: Error | null) => void): void;
343
- }
344
- interface AbstractPhysicalLayerEvents {
345
- open: [];
346
- connect: [connection: AbstractPhysicalConnection];
347
- close: [];
348
- error: [error: Error];
349
- }
350
- /** Debug events emitted by individual connections and proxied through Master/Slave. */
351
- interface ConnectionDebugEvents {
352
- /**
353
- * Emitted when raw data has been successfully written to the wire.
354
- * The `buffer` is a live reference — mutating it will corrupt the frame
355
- * that the connection is still processing. Treat it as read-only.
356
- */
357
- tx: [buffer: Buffer, connection: AbstractPhysicalConnection];
358
- /**
359
- * Emitted when raw data is received from the wire.
360
- * The `buffer` is a live reference — mutating it will corrupt the frame
361
- * that the application layer is about to parse. Treat it as read-only.
362
- */
363
- rx: [buffer: Buffer, connection: AbstractPhysicalConnection];
364
- }
365
- /** Application-layer framing events emitted by Master/Slave per connection. */
366
- interface ConnectionApplicationEvents {
367
- framing: [frame: ApplicationDataUnit & {
368
- buffer: Buffer;
369
- }, connection: AbstractPhysicalConnection];
370
- framingError: [error: Error, connection: AbstractPhysicalConnection];
371
- }
372
- /**
373
- * An abstraction over local hardware or network resources.
374
- *
375
- * `open()` acquires the resource (serial port, TCP socket, UDP socket, etc.).
376
- * Once ready, it emits `connect` with an {@link AbstractPhysicalConnection},
377
- * unifying serial, TCP client, TCP server, and UDP under a single
378
- * "connection-oriented" model similar to a TCP server accepting sockets.
379
- */
380
- declare abstract class AbstractPhysicalLayer extends EventEmitter<AbstractPhysicalLayerEvents> {
381
- abstract readonly TYPE: 'SERIAL' | 'TCP_CLIENT' | 'TCP_SERVER' | 'UDP_CLIENT' | 'UDP_SERVER';
382
- is(type: 'SERIAL'): this is SerialPhysicalLayer;
383
- is(type: 'TCP_CLIENT'): this is TcpClientPhysicalLayer;
384
- is(type: 'TCP_SERVER'): this is TcpServerPhysicalLayer;
385
- is(type: 'UDP_CLIENT'): this is UdpClientPhysicalLayer;
386
- is(type: 'UDP_SERVER'): this is UdpServerPhysicalLayer;
387
- abstract readonly state: PhysicalState;
388
- /** Last argument is the callback: `(err?: Error | null) => void`. Callback is optional. */
389
- abstract open(...args: any[]): void;
390
- abstract close(cb?: (err?: Error | null) => void): void;
391
- }
392
-
393
- type PhysicalConfig = {
394
- type: 'SERIAL';
395
- opts: SerialPhysicalLayerOptions;
396
- } | {
397
- type: 'TCP_CLIENT';
398
- socketOpts?: SocketConstructorOpts;
399
- } | {
400
- type: 'TCP_SERVER';
401
- opts?: TcpServerPhysicalLayerOptions;
402
- } | {
403
- type: 'UDP_CLIENT';
404
- socketOpts?: Partial<SocketOptions>;
405
- } | {
406
- type: 'UDP_SERVER';
407
- opts?: UdpServerPhysicalLayerOptions;
408
- } | {
409
- type: 'CUSTOM';
410
- layer: AbstractPhysicalLayer;
411
- };
412
- /**
413
- * User-facing arguments for `Master.open()` / `Slave.open()`.
414
- *
415
- * These match the physical layer's `open()` signatures **without** the trailing
416
- * callback — `promisifyCb` appends it internally so the user gets a `Promise`.
417
- *
418
- * | Config | User-facing args |
419
- * |---------------|--------------------------------------|
420
- * | SERIAL | `()` — configured at construction |
421
- * | TCP_CLIENT | `(opts?)` — `SocketConnectOpts` |
422
- * | TCP_SERVER | `(opts?)` — `ListenOptions` |
423
- * | UDP_CLIENT | `(remote?)` — `{ port?, address? }` |
424
- * | UDP_SERVER | `(opts?)` — `BindOptions` |
425
- * | CUSTOM | `never` — call `layer.open()` directly |
426
- */
427
- type OpenArgs<T extends PhysicalConfig> = T extends {
428
- type: 'SERIAL';
429
- } ? [] : T extends {
430
- type: 'TCP_CLIENT';
431
- } ? [options?: SocketConnectOpts] : T extends {
432
- type: 'TCP_SERVER';
433
- } ? [options?: ListenOptions] : T extends {
434
- type: 'UDP_CLIENT';
435
- } ? [remote?: {
436
- port?: number;
437
- address?: string;
438
- }] : T extends {
439
- type: 'UDP_SERVER';
440
- } ? [options?: BindOptions] : never;
441
- declare function createPhysicalLayer(config: PhysicalConfig): AbstractPhysicalLayer;
442
-
443
- /**
444
- * Application-layer protocol handler bound to a single physical connection.
445
- *
446
- * Its lifetime follows the channel: created when the underlying connection is
447
- * established and discarded when the connection closes. Subclasses implement
448
- * ASCII, RTU, or TCP framing rules.
449
- */
450
- declare abstract class AbstractApplicationLayer {
451
- abstract readonly PROTOCOL: 'ASCII' | 'RTU' | 'TCP';
452
- abstract ROLE: 'MASTER' | 'SLAVE';
453
- abstract readonly connection: AbstractPhysicalConnection;
454
- /** Called when a complete frame is decoded. Defaults to no-op. */
455
- onFraming: (frame: ApplicationDataUnit & {
456
- buffer: Buffer;
457
- }) => void;
458
- /** Called when a framing error is detected. Defaults to no-op. */
459
- onFramingError: (error: Error) => void;
460
- flush(): void;
461
- addCustomFunctionCode(cfc: CustomFunctionCode): void;
462
- removeCustomFunctionCode(fc: number): void;
463
- abstract encode(unit: number, fc: number, data: Buffer, transaction?: number): Buffer;
464
- }
465
-
466
- interface AsciiApplicationLayerOptions {
467
- /**
468
- * Accept lowercase hex digits (`a-f`) in addition to uppercase (`A-F`).
469
- * Default false (strict per Modbus V1.1b3 §2.2 — uppercase only).
470
- * Non-hex characters are always rejected with a `framing-error`.
471
- */
472
- lenientHex?: boolean;
473
- }
474
- declare class AsciiApplicationLayer extends AbstractApplicationLayer {
475
- readonly PROTOCOL: "ASCII";
476
- readonly ROLE: 'MASTER' | 'SLAVE';
477
- private _connection;
478
- private _state;
479
- private _cleanupCbs;
480
- get connection(): AbstractPhysicalConnection;
481
- constructor(role: 'MASTER' | 'SLAVE', connection: AbstractPhysicalConnection, options?: AsciiApplicationLayerOptions);
482
- private framing;
483
- flush(): void;
484
- encode(unit: number, fc: number, data: Buffer, transaction?: number): Buffer;
485
- }
486
-
487
- interface RtuApplicationLayerOptions {
488
- /** Inter-frame silence in milliseconds (Modbus RTU t3.5). 0 = disabled (immediate parse). */
489
- intervalBetweenFrames?: number;
490
- /** Inter-character timeout in milliseconds (Modbus RTU t1.5). Opt-in. */
491
- interCharTimeout?: number;
492
- /**
493
- * Enforces strict Modbus RTU timing. When true, any frame containing a t1.5
494
- * inter-character timeout event will be discarded immediately, even if the CRC16 is valid.
495
- * @default false
496
- */
497
- strictTiming?: boolean;
498
- }
499
- declare class RtuApplicationLayer extends AbstractApplicationLayer {
500
- readonly PROTOCOL: "RTU";
501
- readonly ROLE: 'MASTER' | 'SLAVE';
502
- private _connection;
503
- private _residual;
504
- private _residualLen;
505
- private _expectedLen;
506
- private _t15Time;
507
- private _t35Time;
508
- private _t15Strict;
509
- private _t15Timer?;
510
- private _t35Timer?;
511
- private _t15Marker;
512
- private _customFunctionCodes;
513
- private _cleanupCbs;
514
- get connection(): AbstractPhysicalConnection;
515
- constructor(role: 'MASTER' | 'SLAVE', connection: AbstractPhysicalConnection, options?: RtuApplicationLayerOptions);
516
- flush(): void;
517
- addCustomFunctionCode(cfc: CustomFunctionCode): void;
518
- removeCustomFunctionCode(fc: number): void;
519
- encode(unit: number, fc: number, data: Buffer, transaction?: number): Buffer;
520
- }
521
-
522
- declare class TcpApplicationLayer extends AbstractApplicationLayer {
523
- readonly PROTOCOL: "TCP";
524
- readonly ROLE: 'MASTER' | 'SLAVE';
525
- private _connection;
526
- private _transactionId;
527
- private _residual;
528
- private _residualLen;
529
- private _expectedLen;
530
- private _cleanupCbs;
531
- get connection(): AbstractPhysicalConnection;
532
- constructor(role: 'MASTER' | 'SLAVE', connection: AbstractPhysicalConnection);
533
- flush(): void;
534
- encode(unit: number, fc: number, data: Buffer, transaction?: number): Buffer;
535
- }
536
-
537
- /**
538
- * RTU timing parameter — accepts either:
539
- * - a bare `number` in milliseconds (`0` to disable the timer entirely)
540
- * - `{ unit: 'ms', value: N }` — explicit milliseconds (equivalent to bare `N`)
541
- * - `{ unit: 'bit', value: N }` — bit-time approximation, derived from `baudRate`
542
- *
543
- * The bare-number form is the recommended default; the object form exists for
544
- * specs that quote bit-time. Pass `0` (or `{ unit: 'ms', value: 0 }`) to disable
545
- * the timer; either form short-circuits the baudRate-derived fallback.
546
- */
547
- type RtuTimingValue = number | {
548
- unit: 'bit' | 'ms';
549
- value: number;
550
- };
551
- /** User-facing RTU protocol options (supports both bit and ms units). */
552
- interface RtuProtocolOptions {
553
- /**
554
- * Inter-frame silence (Modbus RTU t3.5).
555
- *
556
- * - `20` or `{ unit: 'ms', value: 20 }` — 20 ms
557
- * - `{ unit: 'bit', value: 38.5 }` — spec bit-time approximation (default when `baudRate` is provided)
558
- * - `0` — disable t3.5 timing (immediate parse on every chunk; useful for
559
- * lossless transports such as RTU-over-TCP or PTY-based tests where the
560
- * wire's silence semantics do not apply)
561
- *
562
- * Per Modbus V1.02 §2.5.1.1, at baud rates > 19200 a fixed 1.75 ms is used
563
- * regardless of the bit value.
564
- */
565
- intervalBetweenFrames?: RtuTimingValue;
566
- /**
567
- * Inter-character timeout (Modbus RTU t1.5). Opt-in; **disabled** by default.
568
- *
569
- * - `1` or `{ unit: 'ms', value: 1 }` — 1 ms
570
- * - `{ unit: 'bit', value: 21 }` — bit-time approximation (~1.5 char times)
571
- * - `0` — disable explicitly
572
- *
573
- * Per Modbus V1.02 §2.5.1.1, at baud rates > 19200 a fixed 0.75 ms is used
574
- * regardless of the bit value.
575
- */
576
- interCharTimeout?: RtuTimingValue;
577
- /**
578
- * Enforces strict Modbus RTU timing. When true, any frame containing a t1.5
579
- * inter-character timeout event will be discarded immediately, even if the
580
- * CRC16 is valid.
581
- * @default false
582
- */
583
- strictTiming?: boolean;
584
- }
585
-
586
- interface ReturnValue<T> {
587
- transaction?: number;
588
- unit: number;
589
- fc: number;
590
- data: T;
591
- buffer: Buffer;
592
- }
593
- interface ModbusMasterOptions {
594
- /** Per-request timeout in ms. Default 1000. */
595
- timeout?: number;
596
- /**
597
- * Enable pipelined concurrent requests on a single connection.
598
- * Only valid for Modbus TCP application layer.
599
- * Default false (FIFO queue, requests are serialized).
600
- */
601
- concurrent?: boolean;
602
- physical: PhysicalConfig;
603
- protocol: {
604
- type: 'RTU';
605
- opts?: RtuProtocolOptions;
606
- } | {
607
- type: 'TCP';
608
- } | {
609
- type: 'ASCII';
610
- opts?: AsciiApplicationLayerOptions;
611
- };
612
- }
613
- declare class ModbusMaster<T extends ModbusMasterOptions = ModbusMasterOptions> extends EventEmitter<AbstractPhysicalLayerEvents & ConnectionDebugEvents & ConnectionApplicationEvents> {
614
- readonly timeout: number;
615
- readonly concurrent: boolean;
616
- private _masterSession;
617
- private _physicalLayer;
618
- private _protocol;
619
- private _appLayer?;
620
- private _customFunctionCodes;
621
- private _queueUnits;
622
- private _queueFcs;
623
- private _queueDatas;
624
- private _queueTimeouts;
625
- private _queueBroadcasts;
626
- private _queueCallbacks;
627
- private _queueHead;
628
- private _queueLen;
629
- private _draining;
630
- private _nextTid;
631
- private _cleanupFns;
632
- private _closePromise;
633
- private _nextExchangeId;
634
- private _pendingExchanges;
635
- private _timerHeap;
636
- get state(): PhysicalState;
637
- get physicalLayer(): AbstractPhysicalLayer;
638
- constructor(options: T);
639
- private _createAppLayer;
640
- private _send;
641
- private _drain;
642
- private _processNext;
643
- private _exchange;
644
- private writeFC1Or2;
645
- writeFC1: this['readCoils'];
646
- readCoils(unit: 0, address: number, length: number, timeout?: number): Promise<void>;
647
- readCoils(unit: number, address: number, length: number, timeout?: number): Promise<ReturnValue<Uint8Array>>;
648
- writeFC2: this['readDiscreteInputs'];
649
- readDiscreteInputs(unit: 0, address: number, length: number, timeout?: number): Promise<void>;
650
- readDiscreteInputs(unit: number, address: number, length: number, timeout?: number): Promise<ReturnValue<Uint8Array>>;
651
- private writeFC3Or4;
652
- writeFC3: this['readHoldingRegisters'];
653
- readHoldingRegisters(unit: 0, address: number, length: number, timeout?: number): Promise<void>;
654
- readHoldingRegisters(unit: number, address: number, length: number, timeout?: number): Promise<ReturnValue<number[]>>;
655
- writeFC4: this['readInputRegisters'];
656
- readInputRegisters(unit: 0, address: number, length: number, timeout?: number): Promise<void>;
657
- readInputRegisters(unit: number, address: number, length: number, timeout?: number): Promise<ReturnValue<number[]>>;
658
- writeFC5: this['writeSingleCoil'];
659
- writeSingleCoil(unit: 0, address: number, value: number, timeout?: number): Promise<void>;
660
- writeSingleCoil(unit: number, address: number, value: number, timeout?: number): Promise<ReturnValue<number>>;
661
- writeFC6: this['writeSingleRegister'];
662
- writeSingleRegister(unit: 0, address: number, value: number, timeout?: number): Promise<void>;
663
- writeSingleRegister(unit: number, address: number, value: number, timeout?: number): Promise<ReturnValue<number>>;
664
- writeFC15: this['writeMultipleCoils'];
665
- writeMultipleCoils(unit: 0, address: number, value: Uint8Array, timeout?: number): Promise<void>;
666
- writeMultipleCoils(unit: number, address: number, value: Uint8Array, timeout?: number): Promise<ReturnValue<Uint8Array>>;
667
- writeFC16: this['writeMultipleRegisters'];
668
- writeMultipleRegisters(unit: 0, address: number, value: number[], timeout?: number): Promise<void>;
669
- writeMultipleRegisters(unit: number, address: number, value: number[], timeout?: number): Promise<ReturnValue<number[]>>;
670
- handleFC17: this['reportServerId'];
671
- reportServerId(unit: 0, serverIdLength?: number, timeout?: number): Promise<void>;
672
- reportServerId(unit: number, serverIdLength?: number, timeout?: number): Promise<ReturnValue<ServerId>>;
673
- handleFC22: this['maskWriteRegister'];
674
- maskWriteRegister(unit: 0, address: number, andMask: number, orMask: number, timeout?: number): Promise<void>;
675
- maskWriteRegister(unit: number, address: number, andMask: number, orMask: number, timeout?: number): Promise<ReturnValue<{
676
- andMask: number;
677
- orMask: number;
678
- }>>;
679
- handleFC23: this['readAndWriteMultipleRegisters'];
680
- readAndWriteMultipleRegisters(unit: 0, read: {
681
- address: number;
682
- length: number;
683
- }, write: {
684
- address: number;
685
- value: number[];
686
- }, timeout?: number): Promise<void>;
687
- readAndWriteMultipleRegisters(unit: number, read: {
688
- address: number;
689
- length: number;
690
- }, write: {
691
- address: number;
692
- value: number[];
693
- }, timeout?: number): Promise<ReturnValue<number[]>>;
694
- handleFC43_14: this['readDeviceIdentification'];
695
- readDeviceIdentification(unit: 0, readDeviceIDCode: number, objectId: number, timeout?: number): Promise<void>;
696
- readDeviceIdentification(unit: number, readDeviceIDCode: number, objectId: number, timeout?: number): Promise<ReturnValue<DeviceIdentification>>;
697
- addCustomFunctionCode(cfc: CustomFunctionCode): void;
698
- removeCustomFunctionCode(fc: number): void;
699
- sendCustomFC(unit: 0, fc: number, data: Buffer | number[], timeout?: number): Promise<void>;
700
- sendCustomFC(unit: number, fc: number, data: Buffer | number[], timeout?: number): Promise<Buffer>;
701
- /**
702
- * Open the underlying physical layer and establish a connection.
703
- *
704
- * A `ModbusMaster` instance can only be opened once. Once {@link close}
705
- * is called — explicitly or because the physical layer disconnected —
706
- * the instance is permanently closed and cannot be reopened.
707
- * Create a new `ModbusMaster` if a new connection is required.
708
- */
709
- open(...args: OpenArgs<T['physical']>): Promise<void>;
710
- private _close;
711
- /**
712
- * Permanently close the master and release all resources.
713
- *
714
- * After calling this method the instance is considered dead:
715
- * - No further requests can be sent.
716
- * - The instance cannot be reopened via {@link open}.
717
- * - All event listeners registered on this master are removed.
718
- */
719
- close(): Promise<void>;
720
- }
721
-
722
- type Frame = ApplicationDataUnit & {
723
- buffer: Buffer;
724
- };
725
- type Callback = (error: Error | null, frame?: Frame) => void;
726
- declare class MasterSession {
727
- private _waiters;
728
- /** Register a callback for `key`. No timer — timeout is managed by the caller. */
729
- start(key: string | number, callback: Callback): void;
730
- /** Cancel a pending waiter without firing its callback. */
731
- stop(key: string | number): void;
732
- stopAll(error: Error): void;
733
- has(key: string | number): boolean;
734
- handleFrame(frame: Frame): void;
735
- handleError(error: Error): void;
736
- }
737
-
738
- interface ModbusSlaveModel {
739
- unit?: number;
740
- /**
741
- * Intercept read and write behavior.
742
- *
743
- * If provide the return value, use this value as data of `PDU` to respond.
744
- * Otherwise keep the default read and write behavior.
745
- */
746
- interceptor?: MaybeAsyncFunction<(fc: number, data: Buffer) => Buffer | undefined>;
747
- readDiscreteInputs?: MaybeAsyncFunction<(address: number, length: number) => Uint8Array>;
748
- readCoils?: MaybeAsyncFunction<(address: number, length: number) => Uint8Array>;
749
- writeSingleCoil?: MaybeAsyncFunction<(address: number, value: number) => void>;
750
- /**
751
- * If omitted, defaults to loop and call `writeSingleCoil`.
752
- */
753
- writeMultipleCoils?: MaybeAsyncFunction<(address: number, value: Uint8Array) => void>;
754
- /** Return `Uint16Array` to avoid the `Array.from` allocation overhead. */
755
- readInputRegisters?: MaybeAsyncFunction<(address: number, length: number) => number[] | Uint16Array>;
756
- /** Return `Uint16Array` to avoid the `Array.from` allocation overhead. */
757
- readHoldingRegisters?: MaybeAsyncFunction<(address: number, length: number) => number[] | Uint16Array>;
758
- writeSingleRegister?: MaybeAsyncFunction<(address: number, value: number) => void>;
759
- /**
760
- * If omitted, defaults to loop and call `writeSingleRegister`.
761
- */
762
- writeMultipleRegisters?: MaybeAsyncFunction<(address: number, value: number[]) => void>;
763
- /**
764
- * If omitted, defaults to call `readHoldingRegisters` and `writeSingleRegister`.
765
- */
766
- maskWriteRegister?: MaybeAsyncFunction<(address: number, andMask: number, orMask: number) => void>;
767
- reportServerId?: MaybeAsyncFunction<() => ServerId>;
768
- readDeviceIdentification?: MaybeAsyncFunction<() => {
769
- [index: number]: string;
770
- }>;
771
- getAddressRange?: () => {
772
- discreteInputs?: [number, number] | [number, number][];
773
- coils?: [number, number] | [number, number][];
774
- inputRegisters?: [number, number] | [number, number][];
775
- holdingRegisters?: [number, number] | [number, number][];
776
- };
777
- }
778
- interface ModbusSlaveOptions {
779
- /**
780
- * Pipelined concurrent processing of requests within one connection.
781
- * Only valid for Modbus TCP application layer (TID disambiguates responses).
782
- * Default false — per-connection FIFO. RTU/ASCII + concurrent=true throws.
783
- */
784
- concurrent?: boolean;
785
- physical: PhysicalConfig;
786
- protocol: {
787
- type: 'RTU';
788
- opts?: RtuProtocolOptions;
789
- } | {
790
- type: 'TCP';
791
- } | {
792
- type: 'ASCII';
793
- opts?: AsciiApplicationLayerOptions;
794
- };
795
- }
796
- declare class ModbusSlave<T extends ModbusSlaveOptions = ModbusSlaveOptions> extends EventEmitter<AbstractPhysicalLayerEvents & ConnectionDebugEvents & ConnectionApplicationEvents> {
797
- readonly models: Map<number, ModbusSlaveModel>;
798
- readonly concurrent: boolean;
799
- private _physicalLayer;
800
- private _protocol;
801
- private _appLayers;
802
- private _customFunctionCodes;
803
- private _intervalLocks;
804
- private _cleanupFns;
805
- private _closePromise;
806
- get state(): PhysicalState;
807
- get physicalLayer(): AbstractPhysicalLayer;
808
- constructor(options: T);
809
- private _createAppLayer;
810
- private handleFC1;
811
- private handleFC2;
812
- private handleFC3;
813
- private handleFC4;
814
- private handleFC5;
815
- private handleFC6;
816
- private handleFC15;
817
- private handleFC16;
818
- private handleFC17;
819
- private handleFC22;
820
- private handleFC23;
821
- private handleFC43_14;
822
- private responseError;
823
- private _drain;
824
- private _processFrame;
825
- private _intercept;
826
- /**
827
- * Serialize a code block over the half-open address interval `[lo, hi)`.
828
- * The block runs after all previously-installed locks whose intervals
829
- * overlap with this one have completed. Two non-overlapping intervals
830
- * execute in parallel.
831
- *
832
- * Locks are tracked in a flat array (`_intervalLocks`); the typical depth
833
- * is 0-2 entries, so the linear overlap scan is sub-µs. Compare with the
834
- * old per-address Map design, where FC23 writing 121 registers allocated
835
- * ~125 objects per request (one Promise.resolve / address + Set + sort +
836
- * Promise.all); this version allocates 1-3.
837
- */
838
- private _withIntervalLock;
839
- private _handleFC;
840
- private _handleCustomFC;
841
- add(model: ModbusSlaveModel): void;
842
- remove(unit: number): void;
843
- addCustomFunctionCode(cfc: CustomFunctionCode): void;
844
- removeCustomFunctionCode(fc: number): void;
845
- /**
846
- * Open the underlying physical layer and begin accepting connections.
847
- *
848
- * A `ModbusSlave` instance can only be opened once. Once {@link close}
849
- * is called — explicitly or because the physical layer disconnected —
850
- * the instance is permanently closed and cannot be reopened.
851
- * Create a new `ModbusSlave` if a new server is required.
852
- */
853
- open(...args: OpenArgs<T['physical']>): Promise<void>;
854
- private _close;
855
- /**
856
- * Permanently close the slave and release all resources.
857
- *
858
- * After calling this method the instance is considered dead:
859
- * - No further requests can be processed.
860
- * - The instance cannot be reopened via {@link open}.
861
- * - All event listeners registered on this slave are removed.
862
- */
863
- close(): Promise<void>;
864
- }
865
-
866
- 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, NOOP, PhysicalConnectionState, PhysicalState, ReadDeviceIDCode, RtuApplicationLayer, SerialPhysicalLayer, TcpApplicationLayer, TcpClientPhysicalLayer, TcpServerPhysicalLayer, UdpClientPhysicalLayer, UdpServerPhysicalLayer, createPhysicalLayer, getCodeByError, getErrorByCode };
867
- export type { AbstractPhysicalLayerEvents, ApplicationDataUnit, AsciiApplicationLayerOptions, Callback$1 as Callback, ConnectionApplicationEvents, ConnectionDebugEvents, CustomFunctionCode, DeviceIdentification, MaybeAsyncFunction, ModbusMasterOptions, ModbusSlaveModel, ModbusSlaveOptions, OpenArgs, PhysicalConfig, RtuApplicationLayerOptions, ServerId, TcpServerPhysicalLayerOptions, UdpServerPhysicalLayerOptions };