njs-modbus 3.3.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.
@@ -0,0 +1,2862 @@
1
+ import EventEmitter from "node:events";
2
+ import { SerialPort } from "serialport";
3
+ import { ListenOptions, Server, ServerOpts, Socket, SocketConnectOpts, SocketConstructorOpts } from "node:net";
4
+ import { CommonConnectionOptions, ConnectionOptions, SecureContextOptions, Server as Server$1, TLSSocket, TlsOptions } from "node:tls";
5
+ import { BindOptions, RemoteInfo, Socket as Socket$1, SocketOptions } from "node:dgram";
6
+
7
+ //#region src/error-code.d.ts
8
+ /**
9
+ * Modbus protocol exception codes (V1.1b3 §7).
10
+ *
11
+ * The wire-byte appears immediately after a function code with the exception
12
+ * bit (0x80) set. Values are sparse on purpose — 0x00, 0x07, 0x09 and gaps
13
+ * above 0x0B are reserved by the spec and must not be used by custom slaves.
14
+ */
15
+ declare enum ErrorCode {
16
+ /** Function code received in the request is not supported. */
17
+ ILLEGAL_FUNCTION = 1,
18
+ /** Data address in the request is not allowed on this slave. */
19
+ ILLEGAL_DATA_ADDRESS = 2,
20
+ /** Value in the request data field is not allowed for this function. */
21
+ ILLEGAL_DATA_VALUE = 3,
22
+ /** An unrecoverable error occurred while the slave was processing the request. */
23
+ SERVER_DEVICE_FAILURE = 4,
24
+ /** Slave has accepted the request and is processing it, but this will take time. */
25
+ ACKNOWLEDGE = 5,
26
+ /** Slave is engaged in processing a long-duration program command. */
27
+ SERVER_DEVICE_BUSY = 6,
28
+ /** Slave parity error in memory or associated device. */
29
+ MEMORY_PARITY_ERROR = 8,
30
+ /** Gateway could not allocate an internal communication path to the target device. */
31
+ GATEWAY_PATH_UNAVAILABLE = 10,
32
+ /** No response was received from the target device behind the gateway. */
33
+ GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND = 11
34
+ }
35
+ /**
36
+ * Strongly-typed `Error` subclass that carries a Modbus exception code.
37
+ *
38
+ * Throwing this from a slave handler causes the slave dispatcher to encode
39
+ * the exception response (FC | 0x80 followed by `code`); throwing it from a
40
+ * master callback path surfaces the slave's reported exception to user code.
41
+ *
42
+ * The `name` property is fixed to `'ModbusError'` so {@link getCodeByError}
43
+ * can reliably identify it across realm / V8 isolate boundaries without
44
+ * relying on `instanceof`.
45
+ */
46
+ declare class ModbusError extends Error {
47
+ readonly code: ErrorCode;
48
+ /**
49
+ * @param code Modbus exception code carried on the wire.
50
+ * @param message Human-readable description; defaults to the spec-defined
51
+ * English label for `code`.
52
+ */
53
+ constructor(code: ErrorCode, message?: string);
54
+ }
55
+ /**
56
+ * Construct a {@link ModbusError} from a wire-level exception code.
57
+ *
58
+ * @param code Modbus exception code byte (0x01..0x0B per V1.1b3 §7).
59
+ * @returns Newly allocated `ModbusError` with the spec-defined message.
60
+ */
61
+ declare function getErrorByCode(code: ErrorCode): ModbusError;
62
+ /**
63
+ * Map an arbitrary `Error` back to a Modbus exception code for transport on
64
+ * the wire. Used by the slave dispatch path when a user handler throws.
65
+ *
66
+ * Recognises `ModbusError` instances by `name === 'ModbusError'` and a valid
67
+ * `code`; everything else is normalized to `SERVER_DEVICE_FAILURE` (0x04),
68
+ * the spec-defined catch-all for internal slave failures.
69
+ *
70
+ * @param err Error thrown by user code (or surfaced by the runtime).
71
+ * @returns A wire-encodable {@link ErrorCode}; never throws.
72
+ */
73
+ declare function getCodeByError(err: Error): ErrorCode;
74
+ //#endregion
75
+ //#region src/types.d.ts
76
+ /**
77
+ * Modbus Application Data Unit (ADU) — the protocol-agnostic, transport-stripped
78
+ * representation passed between the application layer and the master/slave
79
+ * orchestrators.
80
+ *
81
+ * Concretely the framing layers strip away their transport header / trailer
82
+ * (MBAP for TCP, address + CRC for RTU, `:` / `\r\n` + LRC for ASCII) and
83
+ * emit a normalized ADU; the inverse path encodes the ADU back onto the wire.
84
+ */
85
+ interface ApplicationDataUnit {
86
+ /**
87
+ * MBAP transaction identifier (TCP only). Set on TCP, undefined on RTU/ASCII.
88
+ * 16-bit unsigned, big-endian on the wire (per Modbus Messaging on TCP/IP §3.1).
89
+ */
90
+ transaction?: number;
91
+ /**
92
+ * Unit / slave address byte (0..247 per spec; 0 = broadcast, 248..255 reserved).
93
+ */
94
+ unit: number;
95
+ /**
96
+ * Modbus function code (1 byte, 0..255; bit 0x80 reserved for exceptions).
97
+ */
98
+ fc: number;
99
+ /**
100
+ * PDU payload — bytes after the function code and before the transport
101
+ * checksum (i.e. neither CRC16 nor LRC nor MBAP header is included here).
102
+ */
103
+ data: Buffer;
104
+ }
105
+ /**
106
+ * A parsed Modbus frame as emitted by the framing layer.
107
+ *
108
+ * Extends {@link ApplicationDataUnit} with the raw, on-wire buffer that
109
+ * produced it. The raw buffer is useful for audit logging, replay, and
110
+ * diagnostic events.
111
+ */
112
+ type ModbusFrame = ApplicationDataUnit & {
113
+ buffer: Buffer;
114
+ };
115
+ /**
116
+ * Slave-side configuration block used to answer FC 17 (`REPORT_SERVER_ID`).
117
+ *
118
+ * Bytes are emitted in the order: `serverId` array → `runIndicatorStatus`
119
+ * (0x00 OFF / 0xFF ON) → `additionalData`.
120
+ */
121
+ interface ServerId {
122
+ /**
123
+ * Server ID as a byte array (e.g. single-byte IDs use `[id]`).
124
+ */
125
+ serverId?: Uint8Array;
126
+ /**
127
+ * Run-indicator status; encoded as 0xFF (ON) or 0x00 (OFF) on the wire.
128
+ */
129
+ runIndicatorStatus?: boolean;
130
+ /**
131
+ * Additional data bytes. Using Buffer avoids intermediate array conversions.
132
+ */
133
+ additionalData?: Buffer;
134
+ }
135
+ /**
136
+ * Slave-side configuration block used to answer FC 43 / MEI 14
137
+ * (`READ_DEVICE_IDENTIFICATION`, V1.1b3 §6.21).
138
+ *
139
+ * Each entry in `objects` represents a TLV (id, length-prefixed string) tuple
140
+ * on the wire; the framing layer fragments the response when the cumulative
141
+ * payload would exceed the 253-byte PDU limit and toggles `moreFollows`
142
+ * accordingly.
143
+ */
144
+ interface DeviceIdentification {
145
+ /**
146
+ * Echoed Read Device ID code (1..4); see `ReadDeviceIDCode`.
147
+ */
148
+ readDeviceIDCode: number;
149
+ /**
150
+ * Conformity level reported back to the master (0x81/0x82/0x83).
151
+ */
152
+ conformityLevel: number;
153
+ /**
154
+ * `true` when the response is fragmented and more objects follow.
155
+ */
156
+ moreFollows: boolean;
157
+ /**
158
+ * Next object id to query when `moreFollows` is set; 0 when terminated.
159
+ */
160
+ nextObjectId: number;
161
+ /**
162
+ * TLV objects to return; each `value` is encoded as ASCII bytes.
163
+ */
164
+ objects: {
165
+ id: number;
166
+ value: string;
167
+ }[];
168
+ }
169
+ /**
170
+ * Modbus ADU queue processing strategy.
171
+ *
172
+ * Controls pruning, deduplication, and scheduling behavior when new requests arrive.
173
+ */
174
+ type ModbusQueueStrategy = /** Strict first-in-first-out, execute in queued order. */'fifo'
175
+ /**
176
+ * Last-arrived overwrites; new requests clear all stale unexecuted items in
177
+ * the queue. This is the default used by {@link ModbusMaster} and
178
+ * {@link ModbusSlave} when no strategy is specified.
179
+ */
180
+ | 'drop-stale' /** Smart deduplication based on ADU fingerprint. */ | 'deduplicate'
181
+ /**
182
+ * Concurrent async dispatch (⚠️ Modbus TCP or multi-link Master only, use
183
+ * with caution on RTU bus).
184
+ */
185
+ | 'concurrent';
186
+ /**
187
+ * Defines a non-standard / user-defined Modbus function code.
188
+ *
189
+ * Registration paths:
190
+ * - `RtuApplicationLayer.addCustomFunctionCode(cfc)` — framing only.
191
+ * - `ModbusSlave.addCustomFunctionCode(cfc)` — framing + slave-side dispatch via `handle`.
192
+ * - `ModbusMaster.addCustomFunctionCode(cfc)` + `ModbusMaster.sendCustomFC(...)` — framing + request issuance.
193
+ *
194
+ * The two `predict*` callbacks declare how to derive the total RTU frame length
195
+ * (PDU + 2-byte CRC) from leading bytes; they are required so the framing FSM
196
+ * can advance without the deleted sliding-window CRC fallback.
197
+ *
198
+ * Return `0` to signal "need more bytes before I can decide".
199
+ * Return `-1` to signal "cannot determine the length".
200
+ * Return a positive integer (>= 4, <= 256) for the total frame length.
201
+ */
202
+ interface CustomFunctionCode {
203
+ /**
204
+ * Function code byte (0..255, excluding 0x80 exception bit).
205
+ */
206
+ fc: number;
207
+ /**
208
+ * Optional address-range extractor for access-control gating.
209
+ *
210
+ * @param unit Unit / slave address byte (0..247).
211
+ * @param fc Function code byte.
212
+ * @param data PDU payload bytes (after the FC).
213
+ * @returns Address ranges touched by this request, keyed by table name.
214
+ *
215
+ * @example Declare that a custom FC touches holding registers 0..N-1.
216
+ * ```ts
217
+ * {
218
+ * fc: 0x65,
219
+ * requestAddressRange: (_unit, _fc, data) => ({
220
+ * holdingRegisters: [[0, data.length - 1]],
221
+ * }),
222
+ * }
223
+ * ```
224
+ */
225
+ requestAddressRange?: (unit: number, fc: number, data: Buffer) => { [P in 'discreteInputs' | 'coils' | 'inputRegisters' | 'holdingRegisters']?: [startAddress: number, endAddress: number][] };
226
+ /**
227
+ * Optional fingerprint extractor for deduplication queue strategy.
228
+ *
229
+ * @param unit Unit / slave address byte (0..247).
230
+ * @param fc Function code byte.
231
+ * @param data PDU payload bytes (after the FC).
232
+ * @returns 32-bit unsigned fingerprint, or `null` to disable deduplication for this request.
233
+ */
234
+ requestFingerprint?: (unit: number, fc: number, data: Buffer) => number;
235
+ }
236
+ /**
237
+ * Optional access-control policy evaluated by the master before a request
238
+ * enters the queue and by the slave before a request is dispatched to a
239
+ * unit handler.
240
+ *
241
+ * Each hook may return synchronously or asynchronously:
242
+ * - `true` — allow the operation.
243
+ * - `false` — deny with {@link UnauthorizedAccessError}.
244
+ * - {@link ErrorCode} — deny and surface as a typed {@link ModbusError}.
245
+ *
246
+ * Omit a hook to disable that gate.
247
+ *
248
+ * @example Synchronous unit whitelist.
249
+ * ```ts
250
+ * { checkUnit: (unit) => unit === 1 }
251
+ * ```
252
+ *
253
+ * @example Asynchronous authorization against an external directory.
254
+ * ```ts
255
+ * {
256
+ * checkUnit: async (unit) => {
257
+ * const allowed = await policyService.isUnitAllowed(unit);
258
+ * return allowed;
259
+ * },
260
+ * }
261
+ * ```
262
+ *
263
+ * @example Returning a typed Modbus exception code.
264
+ * ```ts
265
+ * { checkRuntime: () => ErrorCode.SERVER_DEVICE_BUSY }
266
+ * ```
267
+ */
268
+ interface AccessAuthorizer {
269
+ /**
270
+ * Authorize the target unit / slave address.
271
+ *
272
+ * @param unit Unit / slave address byte (0..247, inclusive).
273
+ * @returns `true`, `false`, or an {@link ErrorCode}.
274
+ */
275
+ checkUnit?: (unit: number) => ErrorCode | boolean | Promise<ErrorCode | boolean>;
276
+ /**
277
+ * Authorize the address range touched by the request.
278
+ *
279
+ * Only invoked for standard function codes and for custom function codes
280
+ * that declare {@link CustomFunctionCode.requestAddressRange}.
281
+ *
282
+ * @param unit Unit / slave address byte (0..247, inclusive).
283
+ * @param table Modbus table being accessed.
284
+ * @param addressRange Inclusive zero-based `[startAddress, endAddress]` pair.
285
+ * @returns `true`, `false`, or an {@link ErrorCode}.
286
+ */
287
+ checkAddress?: (unit: number, table: 'discreteInputs' | 'coils' | 'inputRegisters' | 'holdingRegisters', addressRange: [startAddress: number, endAddress: number]) => ErrorCode | boolean | Promise<ErrorCode | boolean>;
288
+ /**
289
+ * Last-chance runtime authorization evaluated immediately before wire I/O.
290
+ *
291
+ * On the master this runs after the queue drains; on the slave it runs
292
+ * after the unit handler produces a successful response.
293
+ *
294
+ * @param unit Unit / slave address byte (0..247, inclusive).
295
+ * @param fc Function code byte (0..255).
296
+ * @param data PDU payload bytes (length 0..253).
297
+ * @returns `true`, `false`, or an {@link ErrorCode}.
298
+ */
299
+ checkRuntime?: (unit: number, fc: number, data: Buffer) => ErrorCode | boolean | Promise<ErrorCode | boolean>;
300
+ }
301
+ //#endregion
302
+ //#region src/utils/adu-fingerprint.d.ts
303
+ /**
304
+ * Compute a 32-bit MurmurHash3-x86 fingerprint over up to four single-byte
305
+ * header arguments concatenated with an optional binary buffer.
306
+ *
307
+ * Used as the canonical hashing primitive for `aduFingerprint` — the
308
+ * deduplication queue strategy keys on this value, so the algorithm must be
309
+ * stable across releases (any change here invalidates in-flight queue state
310
+ * for callers running a mixed-version cluster).
311
+ *
312
+ * The four header bytes are packed little-endian into the same 32-bit word
313
+ * as the buffer payload, so e.g. `(unit, fc, length-byte)` with no buffer
314
+ * produces the exact same hash as a 3-byte buffer with no header.
315
+ *
316
+ * @param buffer Trailing binary payload, or `null` for header-only hashing.
317
+ * @param n1 Optional 1st header byte (low 8 bits used; rest discarded).
318
+ * @param n2 Optional 2nd header byte.
319
+ * @param n3 Optional 3rd header byte.
320
+ * @param n4 Optional 4th header byte.
321
+ * @returns 32-bit unsigned hash in `[0, 0xFFFFFFFF]`.
322
+ *
323
+ * @note Hot Path: Strictly Inline. Do not refactor into sub-routines.
324
+ */
325
+ declare function generateAduHashFingerprint(buffer: Buffer | null, n1?: number, n2?: number, n3?: number, n4?: number): number;
326
+ //#endregion
327
+ //#region src/utils/ip-whitelist.d.ts
328
+ /**
329
+ * A single whitelist entry: an exact IP string, an IPv4 CIDR string, or a
330
+ * custom predicate.
331
+ *
332
+ * String entries are canonicalized at construction time. Predicate functions
333
+ * receive the already-canonicalized remote address.
334
+ */
335
+ type WhitelistEntry = string | ((address: string) => boolean);
336
+ //#endregion
337
+ //#region src/utils/compact-event.d.ts
338
+ /** Sentinel type used as the default event map for an untyped emitter. */
339
+ type DefaultEventMap = [never];
340
+ /**
341
+ * Event map contract: either a record of event names to argument tuples, or
342
+ * the {@link DefaultEventMap} sentinel for untyped emitters.
343
+ *
344
+ * @template T The emitter's event map type.
345
+ */
346
+ type EventMap<T> = Record<keyof T, any[]> | DefaultEventMap;
347
+ /**
348
+ * Valid event key type for a given event map.
349
+ *
350
+ * @template K Candidate key from the typed map.
351
+ * @template T The emitter's event map type.
352
+ */
353
+ type Key<K, T> = T extends DefaultEventMap ? string | symbol : K | keyof T;
354
+ /** Shorthand for an arbitrary argument list. */
355
+ type AnyRest = [...args: any[]];
356
+ /**
357
+ * Argument tuple for a specific event key.
358
+ *
359
+ * @template K Candidate key from the typed map.
360
+ * @template T The emitter's event map type.
361
+ */
362
+ type Args<K, T> = T extends DefaultEventMap ? AnyRest : K extends keyof T ? T[K] : never;
363
+ /**
364
+ * Listener signature for a specific event key and fallback shape.
365
+ *
366
+ * @template K Candidate key from the typed map.
367
+ * @template T The emitter's event map type.
368
+ * @template F Fallback listener signature when the map is untyped.
369
+ */
370
+ type Listener<K, T, F> = T extends DefaultEventMap ? F : K extends keyof T ? T[K] extends unknown[] ? (...args: T[K]) => void : never : never;
371
+ /**
372
+ * Listener signature with a generic fallback, used by {@link CompactEventEmitter.on}.
373
+ *
374
+ * @template K Candidate key from the typed map.
375
+ * @template T The emitter's event map type.
376
+ */
377
+ type Listener1<K, T> = Listener<K, T, (...args: any[]) => void>;
378
+ /**
379
+ * A tiny, zero-dependency event emitter optimized for typed, internal use.
380
+ *
381
+ * Supports the same `on`/`once`/`off`/`emit` surface as Node's
382
+ * `EventEmitter`, but stores listeners in a flat object keyed by event name
383
+ * and unrolls the emit loop for up to five arguments to avoid rest-array
384
+ * allocation on common call patterns.
385
+ *
386
+ * @template T Typed event map. Use the default `DefaultEventMap` for an
387
+ * untyped emitter that accepts any string/symbol event.
388
+ */
389
+ declare class CompactEventEmitter<T extends EventMap<T> = DefaultEventMap> {
390
+ /** Alias for {@link on}. */
391
+ addListener: this['on'];
392
+ /** Alias for {@link off}. */
393
+ removeListener: this['off'];
394
+ private _registry;
395
+ constructor();
396
+ /**
397
+ * Register a listener for `eventName`.
398
+ *
399
+ * @template K Candidate event key inferred from the typed map.
400
+ * @param eventName Event name to subscribe to.
401
+ * @param listener Callback invoked when the event fires.
402
+ * @returns `this` for chaining.
403
+ */
404
+ on<K>(eventName: Key<K, T>, listener: Listener1<K, T>): this;
405
+ /**
406
+ * Register a one-time listener for `eventName`.
407
+ *
408
+ * The listener is removed automatically before it is invoked.
409
+ *
410
+ * @template K Candidate event key inferred from the typed map.
411
+ * @param eventName Event name to subscribe to.
412
+ * @param listener Callback invoked when the event fires.
413
+ * @returns `this` for chaining.
414
+ */
415
+ once<K>(eventName: Key<K, T>, listener: Listener1<K, T>): this;
416
+ /**
417
+ * Remove a previously registered listener for `eventName`.
418
+ *
419
+ * @template K Candidate event key inferred from the typed map.
420
+ * @param eventName Event name to unsubscribe from.
421
+ * @param listener Listener reference to remove.
422
+ * @returns `this` for chaining.
423
+ */
424
+ off<K>(eventName: Key<K, T>, listener: Listener1<K, T>): this;
425
+ /**
426
+ * Remove all listeners for a specific event, or for all events.
427
+ *
428
+ * @template K Candidate event key inferred from the typed map.
429
+ * @param event Optional event name. When omitted, every listener is cleared.
430
+ * @returns `this` for chaining.
431
+ */
432
+ removeAllListeners(event?: Key<unknown, T>): this;
433
+ /**
434
+ * Synchronously invoke all listeners for `eventName`.
435
+ *
436
+ * @template K Candidate event key inferred from the typed map.
437
+ * @param eventName Event name to emit.
438
+ * @param args Arguments passed to each listener.
439
+ * @returns `true` if listeners were registered, `false` otherwise.
440
+ */
441
+ protected emit<K>(eventName: Key<K, T>, ...args: Args<K, T>): boolean;
442
+ /**
443
+ * Emit a single-argument event, but only compute the payload if listeners
444
+ * are registered.
445
+ *
446
+ * @template K Candidate event key inferred from the typed map.
447
+ * @param eventName Event name to emit.
448
+ * @param payloadFactory Factory called once when at least one listener exists.
449
+ * @returns `true` if listeners were registered, `false` otherwise.
450
+ */
451
+ protected emitLazy<K>(eventName: Key<K, T>, payloadFactory: () => Args<K, T>[0]): boolean;
452
+ }
453
+ //#endregion
454
+ //#region src/utils/crc.d.ts
455
+ /**
456
+ * Compute CRC-16 (Modbus) over a single contiguous byte range.
457
+ *
458
+ * The loop body is deliberately one statement — `crc = TABLE[(crc ^ byte) & 0xff] ^ (crc >>> 8)` —
459
+ * so V8 keeps `crc` in a register and emits a single 16-bit table load per
460
+ * byte.
461
+ *
462
+ * @param data Source bytes (any `Uint8Array`-compatible view, including `Buffer`).
463
+ * @param start Inclusive byte offset where the CRC computation starts.
464
+ * @param end Exclusive byte offset where the CRC computation stops.
465
+ * @returns 16-bit CRC value in `[0, 0xFFFF]`. Caller is responsible for
466
+ * little-endian wire encoding (low byte first per Modbus RTU §2.5.1.2).
467
+ *
468
+ * @note Hot Path: Strictly Inline. Do not refactor into sub-routines.
469
+ */
470
+ declare function crcFixed(data: Uint8Array, start: number, end: number): number;
471
+ //#endregion
472
+ //#region src/utils/lrc.d.ts
473
+ /**
474
+ * Compute Modbus ASCII LRC (Longitudinal Redundancy Check, V1.1b3 §2.5.2.2).
475
+ *
476
+ * Algorithm — sum all bytes in the range, take the two's complement of the
477
+ * low 8 bits, return as a single byte. Equivalent to the spec's
478
+ * `((-sum) & 0xff)` formulation.
479
+ *
480
+ * On the wire ASCII frames carry the LRC as two hex characters
481
+ * before the `\r\n` terminator — caller is responsible for hex encoding.
482
+ *
483
+ * @param data Source bytes (the binary-decoded PDU including unit + FC + payload).
484
+ * @param start Inclusive byte offset where the LRC computation starts.
485
+ * @param end Exclusive byte offset where the LRC computation stops.
486
+ * @returns 8-bit LRC value in `[0, 0xFF]`.
487
+ *
488
+ * @note Hot Path: Strictly Inline. Do not refactor into sub-routines.
489
+ */
490
+ declare function lrc(data: Uint8Array, start: number, end: number): number;
491
+ //#endregion
492
+ //#region src/vars.d.ts
493
+ /**
494
+ * Standard Modbus function codes (V1.1b3 §6).
495
+ *
496
+ * Values are the wire-byte that follows the unit / MBAP header. Bit 0x80 is
497
+ * reserved as the exception flag (see `EXCEPTION_OFFSET`); function
498
+ * codes never overlap with the exception space.
499
+ */
500
+ declare enum FunctionCode {
501
+ /** FC 1 — Read Coils. */
502
+ READ_COILS = 1,
503
+ /** FC 2 — Read Discrete Inputs. */
504
+ READ_DISCRETE_INPUTS = 2,
505
+ /** FC 3 — Read Holding Registers. */
506
+ READ_HOLDING_REGISTERS = 3,
507
+ /** FC 4 — Read Input Registers. */
508
+ READ_INPUT_REGISTERS = 4,
509
+ /** FC 5 — Write Single Coil. */
510
+ WRITE_SINGLE_COIL = 5,
511
+ /** FC 6 — Write Single Register. */
512
+ WRITE_SINGLE_REGISTER = 6,
513
+ /** FC 8 — Diagnostics. */
514
+ DIAGNOSTICS = 8,
515
+ /** FC 15 — Write Multiple Coils. */
516
+ WRITE_MULTIPLE_COILS = 15,
517
+ /** FC 16 — Write Multiple Registers. */
518
+ WRITE_MULTIPLE_REGISTERS = 16,
519
+ /** FC 17 — Report Server ID. */
520
+ REPORT_SERVER_ID = 17,
521
+ /** FC 22 — Mask Write Register. */
522
+ MASK_WRITE_REGISTER = 22,
523
+ /** FC 23 — Read/Write Multiple Registers. */
524
+ READ_WRITE_MULTIPLE_REGISTERS = 23,
525
+ /** FC 43 — Read Device Identification. */
526
+ READ_DEVICE_IDENTIFICATION = 43
527
+ }
528
+ /**
529
+ * Error thrown when an access-control hook denies a request.
530
+ *
531
+ * This is a normal `Error` subclass with a fixed `name` so callers can
532
+ * distinguish access denials from transport or protocol failures.
533
+ */
534
+ declare class UnauthorizedAccessError extends Error {
535
+ /**
536
+ * @param message Human-readable denial reason.
537
+ */
538
+ constructor(message?: string);
539
+ }
540
+ //#endregion
541
+ //#region src/layers/protocol/types.d.ts
542
+ /**
543
+ * Discriminant for {@link FrameErrorEvent} describing why a frame was rejected by
544
+ * a protocol framing layer.
545
+ */
546
+ type FrameErrorEventType = /** ASCII hex body contained a character outside `0..9` / `A..F` (or lowercase when strict). */'hex_character_invalid' /** ASCII LRC sum-of-bytes check did not match the trailing LRC byte. */ | 'lrc_check_failed' /** ASCII hex body exceeded the 512-character limit. */ | 'frame_too_long' /** ASCII hex body was shorter than the 6 characters required for unit + FC + LRC. */ | 'frame_length_insufficient' /** ASCII hex body had an odd character count or TCP MBAP length field was out of range. */ | 'frame_length_invalid' /** RTU t3.5 inter-frame silence expired before a complete frame was assembled. */ | 't3.5_timeout' /** RTU t1.5 inter-character timeout expired (emitted only when strict timing is enabled). */ | 't1.5_timeout' /** TCP MBAP protocol identifier was not `0x0000`. */ | 'protocol_id_invalid';
547
+ /**
548
+ * Payload emitted with the `frameError` event when a protocol layer discards a
549
+ * malformed, incomplete, or out-of-spec frame.
550
+ */
551
+ interface FrameErrorEvent {
552
+ /** Error classification; see {@link FrameErrorEventType}. */
553
+ type: FrameErrorEventType;
554
+ /**
555
+ * Human-readable description of the failure; sufficient to diagnose the
556
+ * problem without reading {@link raw}.
557
+ */
558
+ message: string;
559
+ /**
560
+ * The raw, unprocessable bytes that caused the failure.
561
+ * This is a snapshot of the bad frame data and is safe to inspect or log.
562
+ * Its length is capped at the protocol-specific maximum frame length.
563
+ */
564
+ raw: Buffer;
565
+ /** TCP MBAP transaction identifier (big-endian, 16-bit), when available. */
566
+ transaction?: number;
567
+ /** Modbus function code byte (0..255), extracted from the frame when available. */
568
+ fc?: number;
569
+ }
570
+ //#endregion
571
+ //#region src/layers/protocol/abstract-protocol-layer.d.ts
572
+ /**
573
+ * Base class for all Modbus protocol framing layers.
574
+ *
575
+ * Concrete implementations (TCP, RTU, ASCII) own the wire-specific bytes that
576
+ * wrap the protocol-agnostic {@link ApplicationDataUnit}: MBAP headers, CRC16
577
+ * trailers, LRC sums, `:` / `\r\n` delimiters, and timing-driven frame boundaries.
578
+ *
579
+ * The layer exposes typed callbacks (`onFrame`, `onFrameError`) for eager
580
+ * delivery, plus lazy variants (`onFrameLazy`, `onFrameErrorLazy`) for
581
+ * consumers that only need the payload when an observer is actually attached.
582
+ * It also owns a registry for non-standard function codes so that RTU framing
583
+ * can predict custom frame lengths without allocating intermediate buffers.
584
+ *
585
+ * @abstract
586
+ */
587
+ declare abstract class AbstractProtocolLayer {
588
+ /**
589
+ * Wire protocol identifier implemented by this layer.
590
+ * Set as a `const` literal so downstream type narrowing can distinguish
591
+ * TCP / RTU / ASCII paths without runtime inspection.
592
+ */
593
+ abstract readonly PROTOCOL: 'TCP' | 'RTU' | 'ASCII';
594
+ /**
595
+ * Role of the owning stack.
596
+ * - `MASTER` — initiates requests and decodes responses.
597
+ * - `SLAVE` — listens for requests and encodes responses.
598
+ */
599
+ abstract ROLE: 'MASTER' | 'SLAVE';
600
+ /** Callback invoked when a complete, valid frame is decoded. Receives the frame eagerly. */
601
+ onFrame?: (frame: ModbusFrame) => void;
602
+ /** Callback invoked when a complete, valid frame is decoded. Receives a lazy producer. */
603
+ onFrameLazy?: (lazy: () => ModbusFrame) => void;
604
+ /** Callback invoked when a malformed or incomplete frame is discarded. Receives the event eagerly. */
605
+ onFrameError?: (event: FrameErrorEvent) => void;
606
+ /** Callback invoked when a malformed or incomplete frame is discarded. Receives a lazy producer. */
607
+ onFrameErrorLazy?: (lazy: () => FrameErrorEvent) => void;
608
+ /**
609
+ * Registry of custom function codes for variable-length frame prediction.
610
+ *
611
+ * Indexed by function-code byte (`0..255`); empty slots are `undefined`.
612
+ * Concrete layers (especially RTU) use this array to decide how many bytes to
613
+ * expect before declaring a frame complete.
614
+ */
615
+ customFunctionCodes: (CustomFunctionCode | undefined)[];
616
+ /**
617
+ * Register a custom function code for framing-level length prediction.
618
+ *
619
+ * @param cfc Custom function code descriptor.
620
+ * @returns `void`.
621
+ * @throws When `cfc.fc` is not an integer in `0..255`.
622
+ */
623
+ addCustomFunctionCode(cfc: CustomFunctionCode): void;
624
+ /**
625
+ * Remove a previously registered custom function code.
626
+ *
627
+ * @param fc Function code byte (0..255) to deregister.
628
+ * @returns `void`.
629
+ */
630
+ removeCustomFunctionCode(fc: number): void;
631
+ /**
632
+ * Reset any internal framing state (residual bytes, timers, FSM state).
633
+ * Safe to call when the transport signals a disconnect or when re-syncing.
634
+ *
635
+ * @returns `void`.
636
+ */
637
+ flush(): void;
638
+ /**
639
+ * Decode incoming wire bytes and invoke `onFrame` / `onFrameError` callbacks,
640
+ * falling back to `onFrameLazy` / `onFrameErrorLazy` when the eager variants are
641
+ * not registered.
642
+ *
643
+ * Implementations may retain unprocessed trailing bytes internally to handle
644
+ * fragmented or coalesced network chunks.
645
+ *
646
+ * @param data Raw bytes received from the transport. Must not be modified.
647
+ * @returns `void`.
648
+ */
649
+ abstract decode(data: Buffer): void;
650
+ /**
651
+ * Encode a protocol-agnostic ADU into wire bytes.
652
+ *
653
+ * @param unit Unit / slave address byte (0..247 per spec).
654
+ * @param fc Modbus function code byte (0..255, bit 0x80 reserved for exceptions).
655
+ * @param data PDU payload bytes (everything after the function code, before the checksum).
656
+ * @param transaction TCP MBAP transaction identifier (16-bit unsigned, big-endian
657
+ * on the wire). Ignored by RTU/ASCII; for TCP masters, omitting auto-increments.
658
+ * @returns The fully framed wire buffer, ready for the transport layer.
659
+ * @throws When the payload exceeds the protocol-specific maximum PDU length.
660
+ */
661
+ abstract encode(unit: number, fc: number, data: Buffer, transaction?: number): Buffer;
662
+ }
663
+ //#endregion
664
+ //#region src/layers/protocol/ascii-protocol-layer.d.ts
665
+ /** User-facing ASCII protocol options. */
666
+ interface AsciiProtocolLayerOptions {
667
+ /**
668
+ * Allow lowercase hex digits (`a..f`) in incoming frames.
669
+ * The Modbus ASCII spec requires uppercase only; enable lenience only when
670
+ * interoperating with non-compliant peers.
671
+ * @default false
672
+ */
673
+ lenientHex?: boolean;
674
+ }
675
+ /**
676
+ * Modbus ASCII protocol framing layer.
677
+ *
678
+ * Frames begin with a colon (`:`), carry two hex characters per byte, and end
679
+ * with `\r\n`. An LRC sum-of-bytes checksum immediately precedes the CRLF. The
680
+ * layer parses frames from arbitrary byte chunks using a small FSM and supports
681
+ * strict uppercase hex or lenient lowercase hex.
682
+ */
683
+ declare class AsciiProtocolLayer extends AbstractProtocolLayer {
684
+ /** Always `'ASCII'` for this implementation. */
685
+ readonly PROTOCOL: 'ASCII';
686
+ /** Role of the owning stack — `'MASTER'` or `'SLAVE'`. */
687
+ readonly ROLE: 'MASTER' | 'SLAVE';
688
+ private _status;
689
+ private _frame;
690
+ private _frameLen;
691
+ private _lenientHex;
692
+ private _hexTable;
693
+ /**
694
+ * @param role `'MASTER'` for request issuance / response decoding,
695
+ * `'SLAVE'` for request decoding / response issuance.
696
+ * @param options ASCII framing options.
697
+ * @returns A new {@link AsciiProtocolLayer} instance.
698
+ */
699
+ constructor(role: 'MASTER' | 'SLAVE', options?: AsciiProtocolLayerOptions);
700
+ /**
701
+ * Reset the framing FSM to its `idle` state and discard any partial frame.
702
+ *
703
+ * @returns `void`.
704
+ */
705
+ flush(): void;
706
+ /**
707
+ * Decode incoming ASCII bytes into ADU `frame` events.
708
+ *
709
+ * @param data Raw bytes received from the transport. Must not be modified.
710
+ * @returns `void`.
711
+ *
712
+ * @note Hot Path: Strictly Inline. Do not refactor into sub-routines.
713
+ */
714
+ decode(data: Buffer): void;
715
+ /**
716
+ * Encode a unit/FC/payload tuple into a complete Modbus ASCII frame.
717
+ *
718
+ * @param unit Unit / slave address byte (0..247).
719
+ * @param fc Modbus function code byte (0..255).
720
+ * @param data PDU payload bytes (length 0..253).
721
+ * @param transaction Ignored by ASCII; present only for signature compatibility.
722
+ * @returns The framed ASCII buffer (`:` + uppercase hex body + LRC + `\r\n`).
723
+ *
724
+ * @note Hot Path: Strictly Inline. Do not refactor into sub-routines.
725
+ */
726
+ encode(unit: number, fc: number, data: Buffer, transaction?: number): Buffer;
727
+ }
728
+ //#endregion
729
+ //#region src/layers/protocol/rtu-protocol-layer.d.ts
730
+ /**
731
+ * RTU timing parameter — accepts either:
732
+ * - a bare `number` in milliseconds (`0` to disable the timer entirely)
733
+ * - `{ unit: 'ms', value: N }` — explicit milliseconds (equivalent to bare `N`)
734
+ * - `{ unit: 'bit', value: N }` — bit-time approximation, derived from `baudRate`
735
+ *
736
+ * The bare-number form is the recommended default; the object form exists for
737
+ * specs that quote bit-time. Pass `0` (or `{ unit: 'ms', value: 0 }`) to disable
738
+ * the timer; either form short-circuits the baudRate-derived fallback.
739
+ */
740
+ type RtuTimingValue = number | {
741
+ unit: 'bit' | 'ms';
742
+ value: number;
743
+ };
744
+ /** User-facing RTU protocol options (supports both bit and ms units). */
745
+ interface RtuProtocolLayerOptions {
746
+ /**
747
+ * Serial baud rate, in bits per second.
748
+ * Required when using `{ unit: 'bit', value: N }` timing values; ignored otherwise.
749
+ */
750
+ baudRate?: number;
751
+ /**
752
+ * Inter-frame silence (Modbus RTU t3.5).
753
+ *
754
+ * - `20` or `{ unit: 'ms', value: 20 }` — 20 ms
755
+ * - `{ unit: 'bit', value: 38.5 }` — spec bit-time approximation (default when `baudRate` is provided)
756
+ * - `0` — disable t3.5 timing (immediate parse on every chunk; useful for
757
+ * lossless transports such as RTU-over-TCP or PTY-based tests where the
758
+ * wire's silence semantics do not apply)
759
+ *
760
+ * Per Modbus V1.02 §2.5.1.1, at baud rates > 19200 a fixed 1.75 ms is used
761
+ * regardless of the bit value.
762
+ */
763
+ intervalBetweenFrames?: RtuTimingValue;
764
+ /**
765
+ * Inter-character timeout (Modbus RTU t1.5). Opt-in; **disabled** by default.
766
+ *
767
+ * - `1` or `{ unit: 'ms', value: 1 }` — 1 ms
768
+ * - `{ unit: 'bit', value: 21 }` — bit-time approximation (~1.5 char times)
769
+ * - `0` — disable explicitly
770
+ *
771
+ * Per Modbus V1.02 §2.5.1.1, at baud rates > 19200 a fixed 0.75 ms is used
772
+ * regardless of the bit value.
773
+ */
774
+ interCharTimeout?: RtuTimingValue;
775
+ /**
776
+ * Enforces strict Modbus RTU timing. When true, any frame containing a t1.5
777
+ * inter-character timeout event will be discarded immediately, even if the
778
+ * CRC16 is valid.
779
+ * @default false
780
+ */
781
+ strictTiming?: boolean;
782
+ }
783
+ /**
784
+ * RTU-specific narrowing of the protocol-layer contract.
785
+ *
786
+ * Merged with the {@link RtuProtocolLayer} class via TypeScript declaration
787
+ * merging. It overrides the inherited {@link AbstractProtocolLayer.customFunctionCodes}
788
+ * and {@link AbstractProtocolLayer.addCustomFunctionCode} signatures so that
789
+ * every RTU custom function code must declare a `determineFrameLength` callback; the
790
+ * RTU framing FSM needs it to determine the total frame length without a
791
+ * sliding-window CRC fallback.
792
+ */
793
+ interface RtuProtocolLayer {
794
+ /**
795
+ * Registry of RTU custom function codes.
796
+ *
797
+ * Unlike the base {@link AbstractProtocolLayer.customFunctionCodes} array, each
798
+ * entry must include a `determineFrameLength` callback so the framing layer can
799
+ * predict the total frame length from leading bytes.
800
+ */
801
+ customFunctionCodes: ((CustomFunctionCode & {
802
+ determineFrameLength: (getByte: (idx: number) => number, length: number) => number;
803
+ }) | undefined)[];
804
+ /**
805
+ * Register a custom function code for RTU framing.
806
+ *
807
+ * @param cfc Custom function code descriptor. Must include `determineFrameLength`
808
+ * so the RTU framing layer can derive the total frame length
809
+ * (unit + FC + PDU + 2-byte CRC) from the bytes received so far.
810
+ * @returns `void`.
811
+ * @throws When `cfc.fc` is not an integer in `0..255`.
812
+ */
813
+ addCustomFunctionCode: (cfc: CustomFunctionCode & {
814
+ determineFrameLength: (getByte: (idx: number) => number, length: number) => number;
815
+ }) => void;
816
+ /**
817
+ * Remove a previously registered custom function code.
818
+ *
819
+ * @param fc Function code byte (0..255) to deregister.
820
+ * @returns `void`.
821
+ */
822
+ removeCustomFunctionCode: (fc: number) => void;
823
+ }
824
+ /**
825
+ * Modbus RTU protocol framing layer.
826
+ *
827
+ * Parses binary RTU frames (unit + FC + PDU + CRC16) from arbitrary byte chunks,
828
+ * validates CRC16, and optionally enforces Modbus V1.02 t1.5 / t3.5 timing using
829
+ * a single t3.5 `setTimeout` deadline plus inter-chunk timestamp comparison for
830
+ * t1.5. Custom function codes can be registered so variable-length frames can be
831
+ * predicted without a sliding-window CRC fallback.
832
+ */
833
+ declare class RtuProtocolLayer extends AbstractProtocolLayer {
834
+ /** Always `'RTU'` for this implementation. */
835
+ readonly PROTOCOL: 'RTU';
836
+ /** Role of the owning stack — `'MASTER'` or `'SLAVE'`. */
837
+ readonly ROLE: 'MASTER' | 'SLAVE';
838
+ private _residual;
839
+ private _residualLen;
840
+ private _expectedLen;
841
+ private _isMaster;
842
+ private _t15Time;
843
+ private _t35Time;
844
+ private _t15Strict;
845
+ private _t35Timer?;
846
+ private _t15Marker;
847
+ private _lastChunkTime;
848
+ private _timingEnabled;
849
+ /**
850
+ * @param role `'MASTER'` for request issuance / response decoding,
851
+ * `'SLAVE'` for request decoding / response issuance.
852
+ * @param options RTU timing and strictness options.
853
+ * @returns A new {@link RtuProtocolLayer} instance.
854
+ * @throws When `t3.5` is configured to be less than `t1.5`.
855
+ */
856
+ constructor(role: 'MASTER' | 'SLAVE', options?: RtuProtocolLayerOptions);
857
+ /**
858
+ * Resolve a {@link RtuTimingValue} into a concrete millisecond value.
859
+ *
860
+ * @param value Raw timing option supplied by the caller.
861
+ * @param baudRate Baud rate, required only for `{ unit: 'bit' }` values.
862
+ * @param fastBaudMs Fixed millisecond value used when `baudRate > 19200`.
863
+ * @returns Resolved milliseconds, or `undefined` when the value requires a
864
+ * `baudRate` that was not provided.
865
+ */
866
+ private _resolveTime;
867
+ /**
868
+ * Reset the framing FSM — drop residual bytes, expected-length pin, and
869
+ * any pending silence safety timer. Used by the master/slave after
870
+ * a transport hiccup to re-align with the next clean frame boundary.
871
+ *
872
+ * @returns `void`.
873
+ */
874
+ flush(): void;
875
+ /**
876
+ * Decode incoming RTU bytes into ADU `frame` events.
877
+ *
878
+ * Incoming bus activity unconditionally cancels the pending t3.5 silence
879
+ * deadline. The gap since the previous chunk is then compared against t3.5
880
+ * (frame boundary) and, when configured, t1.5 (inter-character). The t3.5
881
+ * deadline is re-armed at the end of the pass whenever residual bytes remain.
882
+ * Frames are predicted from leading bytes using standard FC tables,
883
+ * custom-function-code predictors, or the shared RTU predictor; only once the
884
+ * full frame is available is the CRC16 validated. Frames that experience a
885
+ * t1.5 gap may be discarded when strict timing is enabled.
886
+ *
887
+ * @param data Raw bytes received from the transport. Must not be modified.
888
+ * @returns `void`.
889
+ *
890
+ * @note Hot Path: Strictly Inline. Do not refactor into sub-routines.
891
+ */
892
+ decode(data: Buffer): void;
893
+ /**
894
+ * Encode a unit/FC/payload tuple into a complete Modbus RTU frame.
895
+ *
896
+ * @param unit Unit / slave address byte (0..247).
897
+ * @param fc Modbus function code byte (0..255).
898
+ * @param data PDU payload bytes (length 0..254).
899
+ * @param transaction Ignored by RTU; present only for signature compatibility.
900
+ * @returns The framed RTU buffer (length `4 + data.length`) with little-endian CRC16.
901
+ *
902
+ * @note Hot Path: Strictly Inline. Do not refactor into sub-routines.
903
+ */
904
+ encode(unit: number, fc: number, data: Buffer, transaction?: number): Buffer;
905
+ }
906
+ //#endregion
907
+ //#region src/layers/protocol/tcp-protocol-layer.d.ts
908
+ /**
909
+ * Modbus TCP/IP protocol framing layer.
910
+ *
911
+ * Handles the 7-byte MBAP header (transaction identifier, protocol ID `0x0000`,
912
+ * length, unit ID) followed by the PDU. Frames are parsed from arbitrary byte
913
+ * chunks, including fragmented or coalesced TCP reads, using a flat residual
914
+ * buffer.
915
+ */
916
+ declare class TcpProtocolLayer extends AbstractProtocolLayer {
917
+ /** Always `'TCP'` for this implementation. */
918
+ readonly PROTOCOL: 'TCP';
919
+ /** Role of the owning stack — `'MASTER'` or `'SLAVE'`. */
920
+ readonly ROLE: 'MASTER' | 'SLAVE';
921
+ private _residual;
922
+ private _residualLen;
923
+ /**
924
+ * @param role `'MASTER'` for request issuance / response decoding,
925
+ * `'SLAVE'` for request decoding / response issuance.
926
+ * @returns A new {@link TcpProtocolLayer} instance.
927
+ */
928
+ constructor(role: 'MASTER' | 'SLAVE');
929
+ /**
930
+ * Reset the framing FSM — drop residual bytes.
931
+ *
932
+ * @returns `void`.
933
+ */
934
+ flush(): void;
935
+ /**
936
+ * Decode incoming TCP bytes into ADU `frame` events.
937
+ *
938
+ * @param data Raw bytes received from the transport. Must not be modified.
939
+ * @returns `void`.
940
+ * @note Hot Path: Strictly Inline. Do not refactor into sub-routines.
941
+ */
942
+ decode(data: Buffer): void;
943
+ /**
944
+ * Encode a unit/FC/payload tuple into a complete Modbus TCP frame.
945
+ *
946
+ * @param unit Unit / slave address byte (0..247).
947
+ * @param fc Modbus function code byte (0..255).
948
+ * @param data PDU payload bytes (length 0..253; caller must respect the
949
+ * 253-byte PDU ceiling, as this routine allocates `data.length + 8` bytes
950
+ * without a runtime ceiling check).
951
+ * @param transaction 16-bit TCP transaction identifier (big-endian on the
952
+ * wire). The caller supplies and tracks transaction IDs; this layer does not
953
+ * maintain an internal counter.
954
+ * @returns The framed TCP buffer (length `8 + data.length`).
955
+ *
956
+ * @note Hot Path: Strictly Inline. Do not refactor into sub-routines.
957
+ */
958
+ encode(unit: number, fc: number, data: Buffer, transaction: number): Buffer;
959
+ }
960
+ //#endregion
961
+ //#region src/layers/abstract-pipeline-adapter.d.ts
962
+ /**
963
+ * Events emitted by an {@link AbstractPipelineAdapter}.
964
+ */
965
+ interface AbstractPipelineAdapterEvents {
966
+ /** Raw bytes received from the transport, passed to protocol layers. */
967
+ data: [data: Buffer];
968
+ }
969
+ /**
970
+ * Structural contract for a write-only adapter that sits between a protocol
971
+ * layer and the underlying transport.
972
+ *
973
+ * Implementations are provided by the plugin layer (serial / TCP / UDP); the
974
+ * protocol layer only needs the {@link write} contract and the `data` event.
975
+ */
976
+ interface AbstractPipelineAdapter {
977
+ /**
978
+ * Write a raw frame to the transport.
979
+ *
980
+ * @param data Encoded frame bytes to transmit; must not be modified.
981
+ * @param cb Optional callback invoked once the write completes or fails.
982
+ */
983
+ write: (data: Buffer, cb?: (err: Error | null) => void) => void;
984
+ /** Subscribe to the `data` event. */
985
+ on: (event: 'data', listener: (data: Buffer) => void) => this;
986
+ /** Subscribe once to the `data` event. */
987
+ once: (event: 'data', listener: (data: Buffer) => void) => this;
988
+ /** Unsubscribe from the `data` event. */
989
+ off: (event: 'data', listener: (data: Buffer) => void) => this;
990
+ /** Emit a `data` event. */
991
+ emit: (event: 'data', data: Buffer) => boolean;
992
+ }
993
+ //#endregion
994
+ //#region src/master/master.d.ts
995
+ /**
996
+ * Events emitted by {@link ModbusMaster}.
997
+ */
998
+ interface ModbusMasterEvents {
999
+ /** A frame failed validation; see {@link FrameErrorEvent}. */
1000
+ frameError: [event: FrameErrorEvent];
1001
+ }
1002
+ /**
1003
+ * Construction-time configuration for {@link ModbusMaster}.
1004
+ *
1005
+ * The `protocol` discriminator selects the application-layer codec (RTU / TCP /
1006
+ * ASCII); each protocol accepts an optional `opts` bag (RTU/ASCII framing
1007
+ * options, or `{ transactionId }` for TCP to seed the 16-bit MBAP transaction
1008
+ * counter); `queueStrategy` and `timeout` tune the request scheduler; and an
1009
+ * optional `customFunctionCodes` array can be seeded at construction time so the
1010
+ * master recognises non-standard function codes immediately.
1011
+ *
1012
+ * @template P Transport protocol literal — `'TCP'`, `'RTU'`, or `'ASCII'`.
1013
+ */
1014
+ interface ModbusMasterOptions<P extends 'TCP' | 'RTU' | 'ASCII'> {
1015
+ pipelineAdapter: AbstractPipelineAdapter;
1016
+ protocol: P extends 'TCP' ? {
1017
+ type: 'TCP';
1018
+ opts?: {
1019
+ transactionId?: number;
1020
+ };
1021
+ } : P extends 'RTU' ? {
1022
+ type: 'RTU';
1023
+ opts?: RtuProtocolLayerOptions;
1024
+ } : {
1025
+ type: 'ASCII';
1026
+ opts?: AsciiProtocolLayerOptions;
1027
+ };
1028
+ /**
1029
+ * Modbus ADU queue processing strategy.
1030
+ * Controls pruning, deduplication, and scheduling behavior when new requests arrive.
1031
+ * - 'fifo': strict first-in-first-out, execute in queued order.
1032
+ * - 'drop-stale' (default): last-arrived overwrites; new requests clear all stale unexecuted items in the queue.
1033
+ * - 'deduplicate': smart deduplication based on ADU fingerprint.
1034
+ * - 'concurrent': concurrent async dispatch (⚠️ Modbus TCP or multi-link Master only, use with caution on RTU bus).
1035
+ */
1036
+ queueStrategy?: P extends 'TCP' ? ModbusQueueStrategy : Exclude<ModbusQueueStrategy, 'concurrent'>;
1037
+ /** Per-request timeout in ms. Default 1000. */
1038
+ timeout?: number;
1039
+ }
1040
+ /**
1041
+ * Resolved response from a Modbus slave / server.
1042
+ *
1043
+ * `transaction` is only present for Modbus TCP (MBAP transaction id); it is
1044
+ * omitted for RTU and ASCII transports. `buffer` is the raw on-wire ADU that
1045
+ * produced this response — useful for audit logs or replay debugging.
1046
+ *
1047
+ * @template T Type of the decoded payload (`Uint8Array` for bit reads,
1048
+ * `Uint16Array` for register reads, `number` for single-value writes, etc.).
1049
+ */
1050
+ interface ModbusResponse<T> {
1051
+ transaction?: number;
1052
+ unit: number;
1053
+ fc: number;
1054
+ data: T;
1055
+ buffer: Buffer;
1056
+ }
1057
+ /**
1058
+ * Modbus master / client orchestrator.
1059
+ *
1060
+ * One instance owns:
1061
+ * 1. A single {@link AbstractProtocolLayer} (RTU / TCP / ASCII codec) created
1062
+ * at construction time.
1063
+ * 2. A single {@link AbstractPipelineAdapter} supplied at construction time.
1064
+ * 3. A request queue with the chosen {@link ModbusQueueStrategy}.
1065
+ * 4. A `MasterSession` that matches inbound frames to in-flight
1066
+ * requests by transaction id (TCP) or strict FIFO (RTU/ASCII).
1067
+ * 5. A global lazy-deletion `TimerHeap` that arms one native
1068
+ * `setTimeout` per pending exchange under load.
1069
+ *
1070
+ * The instance emits decoded frames and framing errors on the protocol
1071
+ * layer's event surface so external observers can log or audit the wire
1072
+ * traffic without touching the internal queue machinery.
1073
+ *
1074
+ * Lifecycle notes:
1075
+ * - A protocol layer is created once in the constructor and lives as long as
1076
+ * the master instance.
1077
+ * - The pipeline layer is supplied at construction time and exists for the
1078
+ * lifetime of the master instance.
1079
+ *
1080
+ * @template P Transport protocol literal — `'TCP'`, `'RTU'`, or `'ASCII'`.
1081
+ */
1082
+ declare class ModbusMaster<P extends 'TCP' | 'RTU' | 'ASCII'> extends CompactEventEmitter<ModbusMasterEvents> {
1083
+ /** Resolved queue strategy — see {@link ModbusQueueStrategy}. */
1084
+ readonly queueStrategy: ModbusQueueStrategy;
1085
+ /** Default per-request timeout in milliseconds. */
1086
+ readonly timeout: number;
1087
+ /**
1088
+ * The next 16-bit TCP transaction identifier that will be stamped into an
1089
+ * outbound MBAP header. For RTU and ASCII transports the counter is unused
1090
+ * and remains at its initial value.
1091
+ */
1092
+ get transactionId(): number;
1093
+ /** `true` after {@link destroy} has been called. */
1094
+ get destroyed(): boolean;
1095
+ private _destroyed;
1096
+ private _masterSession;
1097
+ private _protocolLayer;
1098
+ private _pipelineAdapter;
1099
+ private _accessAuthorizer?;
1100
+ private _queueUnits;
1101
+ private _queueFcs;
1102
+ private _queueDatas;
1103
+ private _queueTimeouts;
1104
+ private _queueBroadcasts;
1105
+ private _queueCallbacks;
1106
+ private _queueFingerprints;
1107
+ private _queueHead;
1108
+ private _queueLen;
1109
+ private _draining;
1110
+ private _transactionId;
1111
+ private _cleanupSession;
1112
+ private _nextExchangeId;
1113
+ private _pendingExchanges;
1114
+ private _timerHeap;
1115
+ /**
1116
+ * @param options Construction options; `protocol` is mandatory,
1117
+ * `queueStrategy` defaults to `'drop-stale'`, and `timeout`
1118
+ * defaults to 1000 ms. An optional `customFunctionCodes` array can be
1119
+ * supplied to pre-register non-standard function codes.
1120
+ * @returns A new {@link ModbusMaster} instance.
1121
+ * @throws `Error('Concurrent mode requires a Modbus TCP protocol layer')`
1122
+ * when `queueStrategy: 'concurrent'` is paired with a non-TCP protocol —
1123
+ * RTU/ASCII have no transaction id, so concurrent dispatch would have
1124
+ * no way to match responses back to requests.
1125
+ */
1126
+ constructor(options: ModbusMasterOptions<P>);
1127
+ /**
1128
+ * Queue-aware request entry point.
1129
+ *
1130
+ * Behaviour by {@link queueStrategy}:
1131
+ * - **`'concurrent'`** (TCP only): bypass the queue entirely and dispatch
1132
+ * immediately via `_exchange`.
1133
+ * - **`'fifo'`**: append to the queue; drained one-at-a-time.
1134
+ * - **`'drop-stale'`** (default): reject every pending request with
1135
+ * `Error('Request dropped by drop-stale strategy')`, then enqueue the
1136
+ * new one — keeps only the latest intent.
1137
+ * - **`'deduplicate'`**: scan the pending queue and reject every request
1138
+ * whose ADU fingerprint matches the new one, then enqueue.
1139
+ *
1140
+ * Authorization gates are evaluated in the order
1141
+ * function-code support → `checkUnit` → `checkAddress` before any queue
1142
+ * insertion or wire I/O, so rejected requests fail fast and never enter
1143
+ * the queue.
1144
+ *
1145
+ * @param unit Unit / slave address byte (0..247).
1146
+ * @param fc Modbus function code byte (0..255).
1147
+ * @param data PDU payload bytes (length 0..253).
1148
+ * @param timeout Per-request timeout override in milliseconds.
1149
+ * @param broadcast `true` when `unit === 0` (no response awaited).
1150
+ * @param callback Callback invoked with `(err, frame)` on completion.
1151
+ * @returns `void`.
1152
+ * @throws Via `callback`: `Error('Master has been destroyed')` when the master
1153
+ * instance has already been destroyed; `Error('Unsupported function code 0x..')`
1154
+ * when the FC is neither a standard nor registered custom code;
1155
+ * `Error('Request denied by access authorizer')` / {@link UnauthorizedAccessError}
1156
+ * when `checkUnit` or `checkAddress` rejects the request; typed {@link ModbusError}
1157
+ * when either gate returns a numeric {@link ErrorCode}.
1158
+ */
1159
+ private _send;
1160
+ /**
1161
+ * Kick off the queue-drain loop if not already running. Idempotent — a
1162
+ * second concurrent call returns without re-entering, so the loop stays
1163
+ * single-threaded for the FIFO / drop-stale / deduplicate strategies.
1164
+ */
1165
+ private _drain;
1166
+ /**
1167
+ * Pop the head request off the parallel-array queue, dispatch it via
1168
+ * `_exchange`, and chain the next pop onto the request's callback.
1169
+ *
1170
+ * Uses a head-index dequeue (O(1) per pop) instead of `Array.shift()`
1171
+ * (O(N) reindex × 6 parallel arrays). The arrays are shrunk back to
1172
+ * length 0 once the queue empties so the backing storage is reused
1173
+ * across drain cycles instead of growing unboundedly.
1174
+ *
1175
+ * The drain loop is guarded against synchronous callbacks from
1176
+ * `_exchange` (early error paths such as a detached pipeline or
1177
+ * access-authorizer rejection): synchronous completion iterates via the
1178
+ * `while` loop, asynchronous completion resumes through the callback.
1179
+ * This prevents unbounded stack growth when many consecutive requests
1180
+ * fail before entering the transport.
1181
+ */
1182
+ private _processNext;
1183
+ /**
1184
+ * Single request/response exchange — the lowest level of the master's
1185
+ * write path. Owns the lazy-deletion timer architecture:
1186
+ *
1187
+ * 1. Allocate an `exchangeId` and register a {@link PendingEntry} so
1188
+ * every async checkpoint can detect cancellation in O(1).
1189
+ * 2. Arm the timeout via `TimerHeap`: one shared native
1190
+ * `setTimeout` for ≥3 in-flight requests; per-request timers below
1191
+ * that. On expiry, the heap callback flips `settled = true` and
1192
+ * fires `Error('Request timed out')`.
1193
+ * 3. For non-broadcast TCP frames, allocate the next free transaction
1194
+ * id (skipping ids already in the session map), encode the ADU, and
1195
+ * register a `MasterSession` waiter keyed on the tid **before**
1196
+ * `pipeline.write()` is called. Registering the waiter first prevents
1197
+ * responses that arrive before the write callback runs (loopback,
1198
+ * mock transports, or very fast slaves) from being discarded.
1199
+ * 4. For non-broadcast RTU/ASCII frames, the session waiter is keyed on
1200
+ * the `FIFO_KEY` constant — only one in-flight request can match at
1201
+ * a time, which is enforced by the upstream queue.
1202
+ * 5. For broadcasts (`unit === 0`), no response is expected — the
1203
+ * callback fires as soon as the write resolves (success) or the
1204
+ * timeout elapses.
1205
+ *
1206
+ * Stale frames (response arrived after `settled = true`) and stale
1207
+ * timer fires (response arrived first) are silently discarded — the
1208
+ * `settled` flag is the single source of truth.
1209
+ *
1210
+ * @param unit Unit / slave address byte (0..247).
1211
+ * @param fc Modbus function code byte (0..255).
1212
+ * @param data PDU payload bytes (length 0..253).
1213
+ * @param timeout Per-request timeout override in milliseconds.
1214
+ * @param broadcast `true` when `unit === 0` (no response awaited).
1215
+ * @param callback Callback invoked with `(err, frame)` on completion.
1216
+ * @returns `void`.
1217
+ * @throws Via `callback`: `Error('Request denied by access authorizer')` /
1218
+ * {@link UnauthorizedAccessError} when the configured
1219
+ * {@link AccessAuthorizer.checkRuntime} callback rejects the request;
1220
+ * typed {@link ModbusError} when `checkRuntime` returns a numeric
1221
+ * {@link ErrorCode}; the error reported by
1222
+ * {@link AbstractPipelineAdapter.write} when the transport rejects the
1223
+ * outbound bytes; `Error('Request timed out')` when the heap deadline
1224
+ * elapses; typed {@link ModbusError} when the slave returns an exception
1225
+ * response.
1226
+ */
1227
+ private _exchange;
1228
+ /**
1229
+ * Execute the wire write for a single exchange.
1230
+ *
1231
+ * This is the synchronous continuation of `_exchange` after access
1232
+ * control has approved the request. It encodes the frame, registers the
1233
+ * session waiter, arms the timer, and writes to the pipeline adapter.
1234
+ *
1235
+ * @param unit Unit / slave address byte (0..247).
1236
+ * @param fc Modbus function code byte (0..255).
1237
+ * @param data PDU payload bytes (length 0..253).
1238
+ * @param timeout Per-request timeout override in milliseconds.
1239
+ * @param broadcast `true` when `unit === 0` (no response awaited).
1240
+ * @param callback Callback invoked with `(err, frame)` on completion.
1241
+ * @returns `void`.
1242
+ */
1243
+ private _runExchange;
1244
+ /**
1245
+ * Shared implementation for FC 1 (Read Coils) and FC 2 (Read Discrete Inputs).
1246
+ *
1247
+ * The two FCs share an identical request body (`address` + `length`,
1248
+ * each big-endian 16-bit) and an identical response shape (a byte-count
1249
+ * prefix followed by `(length + 7) >> 3` packed bits, LSB-first inside
1250
+ * each byte per V1.1b3 §6.1). This helper unpacks the bit-packed payload
1251
+ * into a `number[]` of 0/1 values via an inlined per-byte 8-way
1252
+ * shift-and-mask.
1253
+ *
1254
+ * @param unit Unit / slave address byte (0..247).
1255
+ * @param fc Function code byte — `FunctionCode.READ_COILS` or
1256
+ * `FunctionCode.READ_DISCRETE_INPUTS`.
1257
+ * @param address Zero-based starting address (0..0xFFFF).
1258
+ * @param length Number of discretes to read (1..2000).
1259
+ * @param timeout Per-request timeout override in milliseconds.
1260
+ * @returns Promise resolving to `{ unit, fc, data, buffer }` where `data`
1261
+ * is a length-`length` `(0 | 1)[]`, or `void` for broadcast.
1262
+ *
1263
+ * @note Hot Path: Strictly Inline. Do not refactor into sub-routines.
1264
+ */
1265
+ private writeFC1Or2;
1266
+ /** Alias for {@link readCoils} — kept for users that prefer the FC-numeric naming. */
1267
+ writeFC1: this['readCoils'];
1268
+ /**
1269
+ * FC 1 — Read Coils (V1.1b3 §6.1). Reads `length` discrete coil values
1270
+ * starting at `address`.
1271
+ *
1272
+ * @param unit Unit / slave address. Pass `0` for broadcast (no response
1273
+ * awaited; resolves with `void`).
1274
+ * @param address Zero-based coil starting address (0..0xFFFF).
1275
+ * @param length Number of coils to read (1..2000 per spec).
1276
+ * @param timeout Per-request timeout override in milliseconds; defaults
1277
+ * to {@link timeout}.
1278
+ * @returns Promise resolving to `{ unit, fc, data, buffer }` where `data`
1279
+ * is a length-`length` `(0 | 1)[]` of 0/1 values.
1280
+ * @throws `Error('Request timed out')` when no response arrives within `timeout`;
1281
+ * typed {@link ModbusError} when the slave returns an exception response.
1282
+ */
1283
+ readCoils(unit: 0, address: number, length: number, timeout?: number): Promise<void>;
1284
+ readCoils(unit: number, address: number, length: number, timeout?: number): Promise<ModbusResponse<(0 | 1)[]>>;
1285
+ /** Alias for {@link readDiscreteInputs}. */
1286
+ writeFC2: this['readDiscreteInputs'];
1287
+ /**
1288
+ * FC 2 — Read Discrete Inputs (V1.1b3 §6.2). Identical request/response
1289
+ * shape to FC 1, against the read-only discrete-input table.
1290
+ *
1291
+ * @param unit Unit / slave address; `0` = broadcast.
1292
+ * @param address Zero-based discrete-input starting address (0..0xFFFF).
1293
+ * @param length Number of inputs to read (1..2000).
1294
+ * @param timeout Per-request timeout override (ms).
1295
+ * @returns Promise resolving to a `(0 | 1)[]` of 0/1 values, or `void` for broadcast.
1296
+ * @throws Same as {@link readCoils}.
1297
+ */
1298
+ readDiscreteInputs(unit: 0, address: number, length: number, timeout?: number): Promise<void>;
1299
+ readDiscreteInputs(unit: number, address: number, length: number, timeout?: number): Promise<ModbusResponse<(0 | 1)[]>>;
1300
+ /**
1301
+ * Shared implementation for FC 3 (Read Holding Registers) and FC 4 (Read
1302
+ * Input Registers).
1303
+ *
1304
+ * Both FCs share an identical request body (`address` + `length`, each
1305
+ * big-endian 16-bit) and an identical response shape (byte-count prefix
1306
+ * followed by `length` big-endian 16-bit values). The response loop uses
1307
+ * inline `(buf[i] << 8) | buf[i+1]` reads instead of `readUInt16BE` —
1308
+ * at FC 3's max length of 125 that saves 250 bounds-check pairs per
1309
+ * response — per CLAUDE.md "Hot Paths: Strictly Inline".
1310
+ *
1311
+ * @param unit Unit / slave address byte (0..247).
1312
+ * @param fc Function code byte — `FunctionCode.READ_HOLDING_REGISTERS` or
1313
+ * `FunctionCode.READ_INPUT_REGISTERS`.
1314
+ * @param address Zero-based starting address (0..0xFFFF).
1315
+ * @param length Number of registers to read (1..125).
1316
+ * @param timeout Per-request timeout override in milliseconds.
1317
+ * @returns Promise resolving to `{ unit, fc, data, buffer }` where `data`
1318
+ * is a length-`length` `number[]` of 16-bit register values, or `void`
1319
+ * for broadcast.
1320
+ *
1321
+ * @note Hot Path: Strictly Inline. Do not refactor into sub-routines.
1322
+ */
1323
+ private writeFC3Or4;
1324
+ /** Alias for {@link readHoldingRegisters}. */
1325
+ writeFC3: this['readHoldingRegisters'];
1326
+ /**
1327
+ * FC 3 — Read Holding Registers (V1.1b3 §6.3). Reads `length` holding
1328
+ * registers starting at `address`.
1329
+ *
1330
+ * @param unit Unit / slave address; `0` = broadcast.
1331
+ * @param address Zero-based register starting address (0..0xFFFF).
1332
+ * @param length Number of registers to read (1..125 per spec).
1333
+ * @param timeout Per-request timeout override (ms).
1334
+ * @returns Promise resolving to a length-`length` `number[]` of 16-bit
1335
+ * register values.
1336
+ * @throws Same as {@link readCoils}.
1337
+ */
1338
+ readHoldingRegisters(unit: 0, address: number, length: number, timeout?: number): Promise<void>;
1339
+ readHoldingRegisters(unit: number, address: number, length: number, timeout?: number): Promise<ModbusResponse<number[]>>;
1340
+ /** Alias for {@link readInputRegisters}. */
1341
+ writeFC4: this['readInputRegisters'];
1342
+ /**
1343
+ * FC 4 — Read Input Registers (V1.1b3 §6.4). Identical request/response
1344
+ * shape to FC 3, against the read-only input-register table.
1345
+ *
1346
+ * @param unit Unit / slave address; `0` = broadcast.
1347
+ * @param address Zero-based register starting address (0..0xFFFF).
1348
+ * @param length Number of registers to read (1..125).
1349
+ * @param timeout Per-request timeout override (ms).
1350
+ * @returns Promise resolving to a `number[]` of 16-bit register values.
1351
+ * @throws Same as {@link readCoils}.
1352
+ */
1353
+ readInputRegisters(unit: 0, address: number, length: number, timeout?: number): Promise<void>;
1354
+ readInputRegisters(unit: number, address: number, length: number, timeout?: number): Promise<ModbusResponse<number[]>>;
1355
+ /** Alias for {@link writeSingleCoil}. */
1356
+ writeFC5: this['writeSingleCoil'];
1357
+ /**
1358
+ * FC 5 — Write Single Coil (V1.1b3 §6.5). Sets a single coil to ON / OFF.
1359
+ *
1360
+ * @param unit Unit / slave address; `0` = broadcast.
1361
+ * @param address Zero-based coil address (0..0xFFFF).
1362
+ * @param value Coil state — pass `0` for OFF (`0x0000` on the wire) and any
1363
+ * non-zero value for ON (`0xFF00`).
1364
+ * @param timeout Per-request timeout override (ms).
1365
+ * @returns Promise resolving to `{ data: value, ... }` echoed from the slave.
1366
+ * @throws Same as {@link readCoils}.
1367
+ */
1368
+ writeSingleCoil(unit: 0, address: number, value: number, timeout?: number): Promise<void>;
1369
+ writeSingleCoil(unit: number, address: number, value: number, timeout?: number): Promise<ModbusResponse<number>>;
1370
+ /** Alias for {@link writeSingleRegister}. */
1371
+ writeFC6: this['writeSingleRegister'];
1372
+ /**
1373
+ * FC 6 — Write Single Register (V1.1b3 §6.6). Writes one 16-bit value
1374
+ * into the holding-register table.
1375
+ *
1376
+ * @param unit Unit / slave address; `0` = broadcast.
1377
+ * @param address Zero-based register address (0..0xFFFF).
1378
+ * @param value Big-endian 16-bit value to write (0..0xFFFF).
1379
+ * @param timeout Per-request timeout override (ms).
1380
+ * @returns Promise resolving to `{ data: value, ... }` echoed from the slave.
1381
+ * @throws Same as {@link readCoils}.
1382
+ */
1383
+ writeSingleRegister(unit: 0, address: number, value: number, timeout?: number): Promise<void>;
1384
+ writeSingleRegister(unit: number, address: number, value: number, timeout?: number): Promise<ModbusResponse<number>>;
1385
+ /** Alias for {@link diagnosticsReturnQueryData}. */
1386
+ handleFC8_0: this['diagnosticsReturnQueryData'];
1387
+ /**
1388
+ * FC 8 / Sub-function 0x0000 — Diagnostics: Return Query Data (V1.1b3 §6.8).
1389
+ * Sends a 2-byte sub-function code (`0x0000`) and a 2-byte data value; the
1390
+ * slave echoes both back verbatim. Used for loopback / communication testing.
1391
+ *
1392
+ * @param unit Unit / slave address; `0` = broadcast.
1393
+ * @param data 16-bit diagnostic data value (0..0xFFFF) sent big-endian.
1394
+ * @param timeout Per-request timeout override in milliseconds.
1395
+ * @returns Promise resolving to `{ data: number, ... }` where `data` is the
1396
+ * echoed 16-bit value.
1397
+ * @throws Same as {@link readCoils}; `Error('Response echo does not match request')`
1398
+ * when the echoed data does not match the request.
1399
+ */
1400
+ diagnosticsReturnQueryData(unit: 0, data: number, timeout?: number): Promise<void>;
1401
+ diagnosticsReturnQueryData(unit: number, data: number, timeout?: number): Promise<ModbusResponse<number>>;
1402
+ /** Alias for {@link writeMultipleCoils}. */
1403
+ writeFC15: this['writeMultipleCoils'];
1404
+ /**
1405
+ * FC 15 — Write Multiple Coils (V1.1b3 §6.11). Writes a contiguous block
1406
+ * of coil values starting at `address`.
1407
+ *
1408
+ * Coil values are bit-packed LSB-first into the request body — the
1409
+ * inline 8-way pack in this method's body matches the 8-way unpack in
1410
+ * `writeFC1Or2` for symmetry.
1411
+ *
1412
+ * @param unit Unit / slave address; `0` = broadcast.
1413
+ * @param address Zero-based coil starting address (0..0xFFFF).
1414
+ * @param value Coil values to write — `ArrayLike<0 | 1>` where element `i`
1415
+ * is `0` (OFF) or `1` (ON). Length must be 1..1968 per spec.
1416
+ * @param timeout Per-request timeout override (ms).
1417
+ * @returns Promise resolving to `{ data: value, ... }` (the original input
1418
+ * value, not the wire echo, for caller convenience).
1419
+ * @throws Same as {@link readCoils}.
1420
+ */
1421
+ writeMultipleCoils(unit: 0, address: number, value: ArrayLike<0 | 1>, timeout?: number): Promise<void>;
1422
+ writeMultipleCoils<T extends ArrayLike<0 | 1> = ArrayLike<0 | 1>>(unit: number, address: number, value: T, timeout?: number): Promise<ModbusResponse<T>>;
1423
+ /** Alias for {@link writeMultipleRegisters}. */
1424
+ writeFC16: this['writeMultipleRegisters'];
1425
+ /**
1426
+ * FC 16 — Write Multiple Registers (V1.1b3 §6.12). Writes a contiguous
1427
+ * block of 16-bit holding-register values starting at `address`.
1428
+ *
1429
+ * @param unit Unit / slave address; `0` = broadcast.
1430
+ * @param address Zero-based register starting address (0..0xFFFF).
1431
+ * @param value Register values to write as an `ArrayLike<number>` (each
1432
+ * element is a 16-bit word, 0..0xFFFF). Length must be 1..123 per spec.
1433
+ * @param timeout Per-request timeout override (ms).
1434
+ * @returns Promise resolving to `{ data: value, ... }` (the original input
1435
+ * value; the wire echo is just the address+quantity tuple).
1436
+ * @throws Same as {@link readCoils}.
1437
+ */
1438
+ writeMultipleRegisters(unit: 0, address: number, value: ArrayLike<number>, timeout?: number): Promise<void>;
1439
+ writeMultipleRegisters<T extends ArrayLike<number> = ArrayLike<number>>(unit: number, address: number, value: T, timeout?: number): Promise<ModbusResponse<T>>;
1440
+ /** Alias for {@link reportServerId}. */
1441
+ handleFC17: this['reportServerId'];
1442
+ /**
1443
+ * FC 17 — Report Server ID (V1.1b3 §6.13). Queries the slave for its
1444
+ * vendor-specific identification block.
1445
+ *
1446
+ * @param unit Unit / slave address; `0` = broadcast.
1447
+ * @param serverIdLength Number of bytes the slave is expected to use for
1448
+ * `serverId`. Defaults to `1`. The remaining bytes are surfaced as
1449
+ * `additionalData` so legacy multi-byte server IDs can be parsed
1450
+ * without losing the trailing payload.
1451
+ * @param timeout Per-request timeout override (ms).
1452
+ * @returns Promise resolving to `{ data: ServerId, ... }`.
1453
+ * @throws Same as {@link readCoils}; `Error('Report server ID response too short')`
1454
+ * when the response is too short to contain `serverIdLength` bytes;
1455
+ * `Error('Report server ID length mismatch')` when the embedded byte-count
1456
+ * disagrees with the actual response length.
1457
+ */
1458
+ reportServerId(unit: 0, serverIdLength?: number, timeout?: number): Promise<void>;
1459
+ reportServerId(unit: number, serverIdLength?: number, timeout?: number): Promise<ModbusResponse<ServerId>>;
1460
+ /** Alias for {@link maskWriteRegister}. */
1461
+ handleFC22: this['maskWriteRegister'];
1462
+ /**
1463
+ * FC 22 — Mask Write Register (V1.1b3 §6.16). Atomically updates one
1464
+ * holding register: `result = (current AND andMask) OR (orMask AND NOT andMask)`.
1465
+ *
1466
+ * @param unit Unit / slave address; `0` = broadcast.
1467
+ * @param address Zero-based register address (0..0xFFFF).
1468
+ * @param andMask 16-bit AND mask (0..0xFFFF).
1469
+ * @param orMask 16-bit OR mask (0..0xFFFF).
1470
+ * @param timeout Per-request timeout override (ms).
1471
+ * @returns Promise resolving to `{ data: { andMask, orMask }, ... }`
1472
+ * echoed from the slave.
1473
+ * @throws Same as {@link readCoils}.
1474
+ */
1475
+ maskWriteRegister(unit: 0, address: number, andMask: number, orMask: number, timeout?: number): Promise<void>;
1476
+ maskWriteRegister(unit: number, address: number, andMask: number, orMask: number, timeout?: number): Promise<ModbusResponse<{
1477
+ andMask: number;
1478
+ orMask: number;
1479
+ }>>;
1480
+ /** Alias for {@link readAndWriteMultipleRegisters}. */
1481
+ handleFC23: this['readAndWriteMultipleRegisters'];
1482
+ /**
1483
+ * FC 23 — Read/Write Multiple Registers (V1.1b3 §6.17). Atomically
1484
+ * performs a write-then-read against two register windows in one frame.
1485
+ *
1486
+ * @param unit Unit / slave address; `0` = broadcast.
1487
+ * @param read Read window: `{ address, length }` (length 1..125).
1488
+ * @param write Write window: `{ address, value }` where `value` is an
1489
+ * `ArrayLike<number>` of 1..121 registers.
1490
+ * @param timeout Per-request timeout override (ms).
1491
+ * @returns Promise resolving to `{ data: number[], ... }` containing the
1492
+ * read window values **after** the write has been applied.
1493
+ * @throws Same as {@link readCoils}.
1494
+ */
1495
+ readAndWriteMultipleRegisters(unit: 0, read: {
1496
+ address: number;
1497
+ length: number;
1498
+ }, write: {
1499
+ address: number;
1500
+ value: ArrayLike<number>;
1501
+ }, timeout?: number): Promise<void>;
1502
+ readAndWriteMultipleRegisters<T extends ArrayLike<number> = ArrayLike<number>>(unit: number, read: {
1503
+ address: number;
1504
+ length: number;
1505
+ }, write: {
1506
+ address: number;
1507
+ value: T;
1508
+ }, timeout?: number): Promise<ModbusResponse<number[]>>;
1509
+ /** Alias for {@link readDeviceIdentification}. */
1510
+ handleFC43_14: this['readDeviceIdentification'];
1511
+ /**
1512
+ * FC 43 / MEI 14 — Read Device Identification (V1.1b3 §6.21). Queries
1513
+ * the slave's TLV identification table.
1514
+ *
1515
+ * The response is parsed into a {@link DeviceIdentification} record:
1516
+ * each TLV is unpacked into `{ id, value }` (where `value` is the ASCII
1517
+ * decoding of the byte run), and `moreFollows` / `nextObjectId` are
1518
+ * surfaced verbatim so callers can fragment-walk the table by re-issuing
1519
+ * the call with `objectId = nextObjectId` until `moreFollows === false`.
1520
+ *
1521
+ * @param unit Unit / slave address; `0` = broadcast.
1522
+ * @param readDeviceIDCode `1`/`2`/`3` for streaming basic / regular /
1523
+ * extended; `4` for individual access — see `ReadDeviceIDCode`.
1524
+ * @param objectId Starting object id (0..0xFF). For streaming
1525
+ * reads, the slave returns objects starting from this id.
1526
+ * @param timeout Per-request timeout override (ms).
1527
+ * @returns Promise resolving to `{ data: DeviceIdentification, ... }`.
1528
+ * @throws Same as {@link readCoils}; `Error('Read device identification response too short')`
1529
+ * when the response is below the 6-byte MEI header; `Error('Invalid read device identification response')`
1530
+ * when the MEI byte / readDeviceIDCode does not match; `Error('Device identification object count mismatch')`
1531
+ * when the embedded number-of-objects disagrees with the parsed count; and
1532
+ * `Error('Device identification length mismatch')` when the total length
1533
+ * does not match the cumulative TLV body length.
1534
+ */
1535
+ readDeviceIdentification(unit: 0, readDeviceIDCode: number, objectId: number, timeout?: number): Promise<void>;
1536
+ readDeviceIdentification(unit: number, readDeviceIDCode: number, objectId: number, timeout?: number): Promise<ModbusResponse<DeviceIdentification>>;
1537
+ /**
1538
+ * Issue a request against a custom (non-standard) function code.
1539
+ *
1540
+ * The FC must already be registered via {@link addCustomFunctionCode} on
1541
+ * the master — otherwise the queue layer rejects the call with
1542
+ * `Error('Unsupported function code 0x..')`.
1543
+ *
1544
+ * @param unit Unit / slave address; `0` = broadcast.
1545
+ * @param fc Custom function-code byte.
1546
+ * @param data Request PDU payload (bytes after FC, before checksum).
1547
+ * Accepts a `Buffer` for arbitrary bytes, or a `number[]` for a
1548
+ * word-oriented payload that will be encoded big-endian.
1549
+ * @param timeout Per-request timeout override (ms).
1550
+ * @returns Promise resolving to the raw response PDU `Buffer` (no FC,
1551
+ * no checksum) — caller is responsible for parsing the body.
1552
+ * @throws `Error('Request timed out')` when no response arrives within `timeout`;
1553
+ * typed {@link ModbusError} when the slave returns an exception response.
1554
+ */
1555
+ sendCustomFC(unit: 0, fc: number, data: Buffer | number[], timeout?: number): Promise<void>;
1556
+ sendCustomFC(unit: number, fc: number, data: Buffer | number[], timeout?: number): Promise<Buffer>;
1557
+ /**
1558
+ * Register a custom (non-standard) function code for outbound requests.
1559
+ *
1560
+ * For RTU transports the descriptor must also include `determineFrameLength` so the
1561
+ * framing layer can determine the total frame length without buffering.
1562
+ *
1563
+ * @param cfc Custom function-code descriptor.
1564
+ */
1565
+ addCustomFunctionCode(cfc: P extends 'RTU' ? CustomFunctionCode & {
1566
+ determineFrameLength: (getByte: (idx: number) => number, length: number) => number;
1567
+ } : CustomFunctionCode): void;
1568
+ /**
1569
+ * Unregister a previously added custom function code.
1570
+ *
1571
+ * @param fc Function code byte to remove.
1572
+ */
1573
+ removeCustomFunctionCode(fc: number): void;
1574
+ /** Remove all custom function codes registered on this master. */
1575
+ removeAllCustomFunctionCodes(): void;
1576
+ /**
1577
+ * Install an access-control policy on the master.
1578
+ *
1579
+ * The policy is evaluated for every outbound request:
1580
+ * - `checkUnit` and `checkAddress` are evaluated in `_send`, before
1581
+ * queue insertion, so rejected requests fail fast and never enter the queue.
1582
+ * - `checkRuntime` is evaluated in `_exchange`, after the queue drains but
1583
+ * before any wire I/O.
1584
+ *
1585
+ * Requests that fail any gate are rejected locally and never sent to the
1586
+ * transport.
1587
+ *
1588
+ * @param authorizer The policy to enforce. Omit a hook to disable that gate.
1589
+ * @returns `this` for chaining.
1590
+ *
1591
+ * @example
1592
+ * ```ts
1593
+ * master.setAccessAuthorizer({
1594
+ * checkUnit: (unit) => unit === 1,
1595
+ * checkAddress: (_unit, table, [start, end]) =>
1596
+ * table === 'holdingRegisters' && start >= 0 && end < 100,
1597
+ * });
1598
+ * ```
1599
+ */
1600
+ setAccessAuthorizer(authorizer: AccessAuthorizer): void;
1601
+ /**
1602
+ * Remove the access-control policy installed by {@link setAccessAuthorizer}.
1603
+ *
1604
+ * After this call all outbound requests flow through the master without any
1605
+ * access-control gate until a new authorizer is installed.
1606
+ *
1607
+ * @returns `this` for chaining.
1608
+ */
1609
+ deleteAccessAuthorizer(): void;
1610
+ /**
1611
+ * Destroy the master and cancel all pending activity.
1612
+ *
1613
+ * After this call the instance is unusable: pending queue entries and
1614
+ * in-flight exchanges are rejected with `Error('Master has been destroyed')`,
1615
+ * timers are cleared, and listeners are removed.
1616
+ */
1617
+ destroy(): void;
1618
+ }
1619
+ //#endregion
1620
+ //#region src/slave/types.d.ts
1621
+ /**
1622
+ * Discriminated callback arguments for a slave handler that returns `D`.
1623
+ *
1624
+ * On success the handler receives `[null, data]`; on failure it receives
1625
+ * `[errorCode, undefined]`.
1626
+ *
1627
+ * @template D Payload type returned on success.
1628
+ */
1629
+ type CallbackArgs<D> = [errorCode: null, data: D] | [errorCode: ErrorCode, data: undefined];
1630
+ /**
1631
+ * Slave handler callback shape.
1632
+ *
1633
+ * When `D` is `void` the callback is invoked with only the error code;
1634
+ * otherwise it receives the discriminated tuple from {@link CallbackArgs}.
1635
+ *
1636
+ * @template D Payload type returned on success.
1637
+ */
1638
+ type Callback<D> = D extends void ? (errorCode: ErrorCode | null) => void : (...args: CallbackArgs<D>) => void;
1639
+ /**
1640
+ * Lazy variant of {@link CallbackArgs}: the data payload is wrapped in a
1641
+ * zero-argument factory so the slave can defer response encoding until the
1642
+ * framing layer is ready to write.
1643
+ *
1644
+ * @template D Payload type returned on success.
1645
+ */
1646
+ type CallbackLazyArgs<D> = [errorCode: null, data: () => D] | [errorCode: ErrorCode, data: undefined];
1647
+ /**
1648
+ * Lazy variant of {@link Callback} used by the slave dispatcher.
1649
+ *
1650
+ * The payload factory is only called when the response is actually encoded,
1651
+ * letting handlers return large or computed payloads without allocation
1652
+ * unless the request is authorized and the pipeline is connected.
1653
+ *
1654
+ * @template D Payload type returned on success.
1655
+ */
1656
+ type CallbackLazy<D> = (...args: CallbackLazyArgs<D>) => void;
1657
+ /**
1658
+ * Unit model contract implemented by user code and registered with
1659
+ * {@link ModbusSlave.addUnit}.
1660
+ *
1661
+ * Each property is an optional handler for one Modbus function code. The
1662
+ * slave dispatcher validates the request shape, invokes the matching handler,
1663
+ * and encodes the response. Handlers should follow the callback convention
1664
+ * `[errorCode, data]` (or just `errorCode` for void payloads).
1665
+ */
1666
+ interface ModbusUnitModel {
1667
+ /**
1668
+ * Unit identifier this model claims.
1669
+ *
1670
+ * Valid range is `1..247` per the Modbus spec; `0` is reserved for broadcast.
1671
+ * Defaults to `1` when omitted; pass an explicit value when a single slave
1672
+ * instance hosts multiple unit IDs.
1673
+ */
1674
+ unit?: number;
1675
+ /**
1676
+ * FC 2 — Read Discrete Inputs.
1677
+ *
1678
+ * @param address Zero-based input starting address (0..0xFFFF).
1679
+ * @param length Number of inputs to read (1..2000).
1680
+ * @param callback Invoked with `[null, values]` on success or
1681
+ * `[errorCode, undefined]` on failure; `values` may be any `ArrayLike<0 | 1>`
1682
+ * (e.g. `number[]`), where each element represents OFF (`0`) or ON (`1`).
1683
+ */
1684
+ readDiscreteInputs?: (address: number, length: number, callback: Callback<ArrayLike<0 | 1>>) => void;
1685
+ /**
1686
+ * FC 1 — Read Coils.
1687
+ *
1688
+ * @param address Zero-based coil starting address (0..0xFFFF).
1689
+ * @param length Number of coils to read (1..2000).
1690
+ * @param callback Invoked with `[null, values]` on success or
1691
+ * `[errorCode, undefined]` on failure; `values` may be any `ArrayLike<0 | 1>`
1692
+ * (e.g. `number[]`), where each element represents OFF (`0`) or ON (`1`).
1693
+ */
1694
+ readCoils?: (address: number, length: number, callback: Callback<ArrayLike<0 | 1>>) => void;
1695
+ /**
1696
+ * FC 5 — Write Single Coil.
1697
+ *
1698
+ * @param address Zero-based coil address (0..0xFFFF).
1699
+ * @param value Coil state — `0` (OFF) or `1` (ON).
1700
+ * @param callback Invoked with `[null]` on success or `[errorCode]` on failure.
1701
+ */
1702
+ writeSingleCoil?: (address: number, value: number, callback: Callback<void>) => void;
1703
+ /**
1704
+ * FC 15 — Write Multiple Coils.
1705
+ *
1706
+ * @param address Zero-based coil starting address (0..0xFFFF).
1707
+ * @param value Coil states — array of `0` (OFF) or `1` (ON), length 1..1968.
1708
+ * @param callback Invoked with `[null]` on success or `[errorCode]` on failure.
1709
+ */
1710
+ writeMultipleCoils?: (address: number, value: (0 | 1)[], callback: Callback<void>) => void;
1711
+ /**
1712
+ * FC 4 — Read Input Registers.
1713
+ *
1714
+ * @param address Zero-based register starting address (0..0xFFFF).
1715
+ * @param length Number of registers to read (1..125).
1716
+ * @param callback Invoked with `[null, values]` on success or
1717
+ * `[errorCode, undefined]` on failure; `values` may be any `ArrayLike<number>`
1718
+ * of 16-bit words (e.g. `number[]`, `Uint16Array`, `Buffer`).
1719
+ */
1720
+ readInputRegisters?: (address: number, length: number, callback: Callback<ArrayLike<number>>) => void;
1721
+ /**
1722
+ * FC 3 — Read Holding Registers.
1723
+ *
1724
+ * @param address Zero-based register starting address (0..0xFFFF).
1725
+ * @param length Number of registers to read (1..125).
1726
+ * @param callback Invoked with `[null, values]` on success or
1727
+ * `[errorCode, undefined]` on failure; `values` may be any `ArrayLike<number>`
1728
+ * of 16-bit words (e.g. `number[]`, `Uint16Array`, `Buffer`).
1729
+ */
1730
+ readHoldingRegisters?: (address: number, length: number, callback: Callback<ArrayLike<number>>) => void;
1731
+ /**
1732
+ * FC 6 — Write Single Register.
1733
+ *
1734
+ * @param address Zero-based register address (0..0xFFFF).
1735
+ * @param value Big-endian 16-bit value to write (0..0xFFFF).
1736
+ * @param callback Invoked with `[null]` on success or `[errorCode]` on failure.
1737
+ */
1738
+ writeSingleRegister?: (address: number, value: number, callback: Callback<void>) => void;
1739
+ /**
1740
+ * FC 16 — Write Multiple Registers.
1741
+ *
1742
+ * @param address Zero-based register starting address (0..0xFFFF).
1743
+ * @param value Register values to write as a `number[]` of 16-bit words,
1744
+ * length 1..123.
1745
+ * @param callback Invoked with `[null]` on success or `[errorCode]` on failure.
1746
+ */
1747
+ writeMultipleRegisters?: (address: number, value: number[], callback: Callback<void>) => void;
1748
+ /**
1749
+ * FC 22 — Mask Write Register.
1750
+ *
1751
+ * @param address Zero-based register address (0..0xFFFF).
1752
+ * @param andMask 16-bit AND mask (0..0xFFFF).
1753
+ * @param orMask 16-bit OR mask (0..0xFFFF).
1754
+ * @param callback Invoked with `[null]` on success or `[errorCode]` on failure.
1755
+ */
1756
+ maskWriteRegister?: (address: number, andMask: number, orMask: number, callback: Callback<void>) => void;
1757
+ /**
1758
+ * FC 8 / Sub-function 0x0000 — Diagnostics: Return Query Data.
1759
+ *
1760
+ * The handler receives the 16-bit diagnostic data from the request and may
1761
+ * inspect, log, or reject it; the response is always the original request PDU
1762
+ * echoed verbatim.
1763
+ *
1764
+ * @param data 16-bit diagnostic data value from the request (0..0xFFFF).
1765
+ * @param callback Invoked with `[null]` on success or `[errorCode]` on failure.
1766
+ */
1767
+ diagnosticsReturnQueryData?: (data: number, callback: Callback<void>) => void;
1768
+ /**
1769
+ * FC 17 — Report Server ID.
1770
+ *
1771
+ * @param callback Invoked with `[null, serverId]` on success or
1772
+ * `[errorCode, undefined]` on failure.
1773
+ */
1774
+ reportServerId?: (callback: Callback<ServerId>) => void;
1775
+ /**
1776
+ * FC 43 / MEI 14 — Read Device Identification.
1777
+ *
1778
+ * Return the slave's TLV identification table keyed by object id (0..255).
1779
+ * Object ids 0x07..0x7F are reserved by the spec and rejected by the
1780
+ * dispatcher with `SERVER_DEVICE_FAILURE`.
1781
+ *
1782
+ * @param callback Invoked with `[null, objects]` on success or
1783
+ * `[errorCode, undefined]` on failure.
1784
+ */
1785
+ readDeviceIdentification?: (callback: Callback<{
1786
+ [index: number]: string;
1787
+ }>) => void;
1788
+ }
1789
+ /**
1790
+ * Discriminant for {@link ProtocolExceptionEvent} describing the reason a slave
1791
+ * produced a Modbus exception response.
1792
+ */
1793
+ type ProtocolExceptionEventType = /** Function code is not supported by this slave or the framing layer. */'function_illegal' /** Function code is legal but no handler is implemented for the target unit. */ | 'function_not_implemented' /** PDU length, value range, or structure violates the Modbus specification. */ | 'data_value_illegal' /** Data address or object id is outside the allowed range. */ | 'data_address_illegal' /** Slave handler detected an internal/device-side failure while building the response. */ | 'server_device_failure' /** Target unit is not registered on this slave session. */ | 'gateway_path_unavailable';
1794
+ /**
1795
+ * Payload emitted with the `protocolException` event when the slave responds
1796
+ * with a Modbus exception function code.
1797
+ *
1798
+ * @example
1799
+ * ```ts
1800
+ * slave.on('protocolException', (event) => {
1801
+ * logger.info({ exception: event }, 'modbus exception');
1802
+ * });
1803
+ * ```
1804
+ *
1805
+ * @see {@link ProtocolExceptionEventType}
1806
+ */
1807
+ interface ProtocolExceptionEvent {
1808
+ /** Error classification; see {@link ProtocolExceptionEventType}. */
1809
+ type: ProtocolExceptionEventType;
1810
+ /**
1811
+ * Human-readable description of the failure; sufficient to diagnose the
1812
+ * problem without inspecting {@link data}.
1813
+ */
1814
+ message: string;
1815
+ /** TCP MBAP transaction identifier (big-endian, 16-bit), when available. */
1816
+ transaction?: number;
1817
+ /** Modbus unit/slave address byte (0..247). */
1818
+ unit: number;
1819
+ /** Modbus function code byte (0..255). */
1820
+ fc: number;
1821
+ /**
1822
+ * Snapshot of the PDU payload that triggered the exception.
1823
+ * This is a copy of the original bytes and is safe to inspect or log.
1824
+ */
1825
+ data: Buffer;
1826
+ }
1827
+ /**
1828
+ * Discriminant for {@link AccessAuditEvent} describing a request that was
1829
+ * rejected by the configured access authorizer.
1830
+ */
1831
+ type AccessAuditEventType = /** Unit address was rejected by the configured access authorizer. */'unit_access_denied' /** Address range or table was rejected by the configured access authorizer. */ | 'address_access_denied' /** Runtime authorization denied or returned an exception code. */ | 'runtime_access_denied';
1832
+ /**
1833
+ * Payload emitted with the `accessAudit` event when a request is rejected by
1834
+ * the configured access authorizer.
1835
+ *
1836
+ * @example
1837
+ * ```ts
1838
+ * slave.on('accessAudit', (event) => {
1839
+ * logger.warn({ audit: event }, 'modbus access denied');
1840
+ * });
1841
+ * ```
1842
+ *
1843
+ * @see {@link AccessAuditEventType} {@link AccessAuthorizer}
1844
+ */
1845
+ interface AccessAuditEvent {
1846
+ /** Audit classification; see {@link AccessAuditEventType}. */
1847
+ type: AccessAuditEventType;
1848
+ /**
1849
+ * Human-readable description of the audit event; sufficient to diagnose the
1850
+ * problem without inspecting {@link data}.
1851
+ */
1852
+ message: string;
1853
+ /** TCP MBAP transaction identifier (big-endian, 16-bit), when available. */
1854
+ transaction?: number;
1855
+ /** Modbus unit/slave address byte (0..247). */
1856
+ unit: number;
1857
+ /** Modbus function code byte (0..255). */
1858
+ fc: number;
1859
+ /**
1860
+ * Snapshot of the PDU payload that triggered the audit event.
1861
+ * This is a copy of the original bytes and is safe to inspect or log.
1862
+ */
1863
+ data: Buffer;
1864
+ }
1865
+ /**
1866
+ * Discriminant for {@link PipelineFaultEvent} describing a transport-layer
1867
+ * failure while emitting a response frame.
1868
+ */
1869
+ type PipelineFaultEventType = /** The pipeline layer failed to write the encoded response frame. */'write_failed';
1870
+ /**
1871
+ * Payload emitted with the `pipelineFault` event when the slave has produced a
1872
+ * response but the underlying pipeline layer could not transmit it.
1873
+ */
1874
+ interface PipelineFaultEvent {
1875
+ /** Fault classification; see {@link PipelineFaultEventType}. */
1876
+ type: PipelineFaultEventType;
1877
+ /**
1878
+ * Human-readable description of the failure; sufficient to diagnose the
1879
+ * problem without inspecting {@link data} / {@link responseRaw}.
1880
+ */
1881
+ message: string;
1882
+ /** TCP MBAP transaction identifier (big-endian, 16-bit), when available. */
1883
+ transaction?: number;
1884
+ /** Modbus unit/slave address byte (0..247). */
1885
+ unit: number;
1886
+ /** Modbus function code byte (0..255). */
1887
+ fc: number;
1888
+ /**
1889
+ * Snapshot of the request PDU payload that triggered the attempted response.
1890
+ * This is a copy of the original bytes and is safe to inspect or log.
1891
+ */
1892
+ data: Buffer;
1893
+ /**
1894
+ * The raw, encoded response frame that the pipeline layer failed to write.
1895
+ * This is the exact buffer passed to the pipeline layer's write operation.
1896
+ */
1897
+ responseRaw: Buffer;
1898
+ /** The error returned by the pipeline layer's write operation. */
1899
+ error: Error;
1900
+ }
1901
+ //#endregion
1902
+ //#region src/slave/slave.d.ts
1903
+ /**
1904
+ * Events emitted by {@link ModbusSlave}.
1905
+ */
1906
+ interface ModbusSlaveEvents {
1907
+ /** A frame failed validation; see {@link FrameErrorEvent}. */
1908
+ frameError: [event: FrameErrorEvent];
1909
+ /** The slave produced a Modbus exception response; see {@link ProtocolExceptionEvent}. */
1910
+ protocolException: [event: ProtocolExceptionEvent];
1911
+ /** A request was rejected by the configured access authorizer; see {@link AccessAuditEvent}. */
1912
+ accessAudit: [event: AccessAuditEvent];
1913
+ /** The pipeline layer failed to transmit a response; see {@link PipelineFaultEvent}. */
1914
+ pipelineFault: [event: PipelineFaultEvent];
1915
+ }
1916
+ /**
1917
+ * Construction-time configuration for {@link ModbusSlave}.
1918
+ *
1919
+ * The `protocol` discriminator selects the application-layer codec (RTU / TCP /
1920
+ * ASCII); `queueStrategy` and `enableWriteRangeLock` tune the frame scheduler.
1921
+ *
1922
+ * @template P Transport protocol literal — `'TCP'`, `'RTU'`, or `'ASCII'`.
1923
+ */
1924
+ interface ModbusSlaveOptions<P extends 'TCP' | 'RTU' | 'ASCII'> {
1925
+ pipelineAdapter: AbstractPipelineAdapter;
1926
+ protocol: P extends 'TCP' ? {
1927
+ type: 'TCP';
1928
+ } : P extends 'RTU' ? {
1929
+ type: 'RTU';
1930
+ opts?: RtuProtocolLayerOptions;
1931
+ } : {
1932
+ type: 'ASCII';
1933
+ opts?: AsciiProtocolLayerOptions;
1934
+ };
1935
+ /**
1936
+ * Modbus ADU queue processing strategy.
1937
+ * Controls pruning, deduplication, and scheduling behavior when new frames arrive.
1938
+ * - 'fifo': strict first-in-first-out, execute in queued order.
1939
+ * - 'drop-stale' (default): last-arrived overwrites; new frames clear all stale unexecuted items in the queue.
1940
+ * - 'deduplicate': smart deduplication based on ADU fingerprint.
1941
+ * - 'concurrent': concurrent async dispatch (⚠️ Modbus TCP only, use with caution on RTU bus).
1942
+ */
1943
+ queueStrategy?: P extends 'TCP' ? ModbusQueueStrategy : Exclude<ModbusQueueStrategy, 'concurrent'>;
1944
+ /**
1945
+ * When `queueStrategy` is `'concurrent'`, serialize concurrent write
1946
+ * requests (FC05/06/15/16) whose address ranges overlap on the same unit.
1947
+ * Set to `false` for purely synchronous in-memory slaves that do not need
1948
+ * the coordination overhead.
1949
+ * @default true
1950
+ */
1951
+ enableWriteRangeLock?: boolean;
1952
+ }
1953
+ /**
1954
+ * Modbus slave / server orchestrator.
1955
+ *
1956
+ * One instance owns:
1957
+ * 1. A single {@link AbstractProtocolLayer} (RTU / TCP / ASCII codec) created
1958
+ * at construction time.
1959
+ * 2. A single {@link AbstractPipelineAdapter} supplied at construction time.
1960
+ * 3. A {@link ModbusQueueStrategy} frame queue for non-concurrent modes.
1961
+ * 4. Per-unit write-range locking for concurrent TCP mode when
1962
+ * `enableWriteRangeLock` is `true`.
1963
+ * 5. Typed access control, protocol, and pipeline fault events.
1964
+ *
1965
+ * Register unit models with {@link addUnit}, install an optional
1966
+ * {@link AccessAuthorizer} with {@link setAccessAuthorizer}, and destroy with
1967
+ * {@link destroy} when the transport closes.
1968
+ *
1969
+ * @template P Transport protocol literal — `'TCP'`, `'RTU'`, or `'ASCII'`.
1970
+ */
1971
+ declare class ModbusSlave<P extends 'TCP' | 'RTU' | 'ASCII'> extends CompactEventEmitter<ModbusSlaveEvents> {
1972
+ readonly queueStrategy: ModbusQueueStrategy;
1973
+ readonly enableWriteRangeLock: boolean;
1974
+ /** `true` after {@link destroy} has been called. */
1975
+ get destroyed(): boolean;
1976
+ private _destroyed;
1977
+ private _units;
1978
+ private _protocolLayer;
1979
+ private _pipelineAdapter;
1980
+ private _cfcResponses;
1981
+ private _accessAuthorizer?;
1982
+ private _queue;
1983
+ private _cleanupSession;
1984
+ private _unitWriteQueues;
1985
+ private _flushingPending;
1986
+ private _pendingFlushPending;
1987
+ /**
1988
+ * @param options Construction options; `protocol` is mandatory,
1989
+ * `queueStrategy` defaults to `'drop-stale'`, and `enableWriteRangeLock`
1990
+ * defaults to `true`.
1991
+ * @throws `Error('Concurrent mode requires a Modbus TCP protocol layer')`
1992
+ * when `queueStrategy: 'concurrent'` is paired with a non-TCP protocol.
1993
+ */
1994
+ constructor(options: ModbusSlaveOptions<P>);
1995
+ private _handleFC1;
1996
+ private _handleFC2;
1997
+ private _handleFC3;
1998
+ private _handleFC4;
1999
+ private _handleFC5;
2000
+ private _handleFC6;
2001
+ private _handleFC8_0;
2002
+ private _handleFC15;
2003
+ private _handleFC16;
2004
+ private _handleFC17;
2005
+ private _handleFC22;
2006
+ private _handleFC23;
2007
+ private _handleFC43_14;
2008
+ /**
2009
+ * Dispatch a validated ADU to the appropriate FC handler.
2010
+ *
2011
+ * Kept synchronous so the slave dispatch path avoids one `async/await`
2012
+ * suspend/resume per request.
2013
+ *
2014
+ * @param model Unit model that will handle the request.
2015
+ * @param frame Parsed ADU including the PDU payload.
2016
+ * @param callback Lazy callback that receives the encoded response PDU.
2017
+ * @note Hot Path: Strictly Inline. Do not refactor into sub-routines.
2018
+ */
2019
+ private _handleFC;
2020
+ private _flushPendingWrites;
2021
+ private _executeUnitWrite;
2022
+ private _processUnitWrite;
2023
+ private _onLockedUnitWriteDone;
2024
+ private _processFrame;
2025
+ /**
2026
+ * Register a unit model that will handle requests for `unit`.
2027
+ *
2028
+ * @param unit Unit / slave address (1..247).
2029
+ * @param model Handler model implementing the function codes to support.
2030
+ * @returns `this` for chaining.
2031
+ * @throws When `unit` is not an integer in `1..247`.
2032
+ */
2033
+ addUnit(unit: number, model: ModbusUnitModel): this;
2034
+ /**
2035
+ * Unregister the model for `unit`.
2036
+ *
2037
+ * @param unit Unit / slave address to remove.
2038
+ * @returns `this` for chaining.
2039
+ */
2040
+ removeUnit(unit: number): void;
2041
+ /**
2042
+ * Unregister every unit model on this slave.
2043
+ *
2044
+ * @returns `this` for chaining.
2045
+ */
2046
+ removeAllUnits(): void;
2047
+ /**
2048
+ * Register a custom function code and its handler on this slave.
2049
+ *
2050
+ * For RTU transports the descriptor must also include `determineFrameLength` so the
2051
+ * framing layer can determine the total frame length without buffering.
2052
+ *
2053
+ * @param cfc Custom function-code descriptor.
2054
+ * @param response Handler called to produce the response PDU.
2055
+ * @returns `this` for chaining.
2056
+ */
2057
+ addCustomFunctionCode(cfc: P extends 'RTU' ? CustomFunctionCode & {
2058
+ determineFrameLength: (getByte: (idx: number) => number, length: number) => number;
2059
+ } : CustomFunctionCode, response: (unit: number, fc: number, data: Buffer, callback: CallbackLazy<Buffer>) => void): void;
2060
+ /**
2061
+ * Unregister a previously added custom function code.
2062
+ *
2063
+ * @param fc Function code byte to remove.
2064
+ * @returns `this` for chaining.
2065
+ */
2066
+ removeCustomFunctionCode(fc: number): void;
2067
+ /**
2068
+ * Remove all custom function codes registered on this slave.
2069
+ *
2070
+ * @returns `this` for chaining.
2071
+ */
2072
+ removeAllCustomFunctionCodes(): void;
2073
+ /**
2074
+ * Install an access-control policy on the slave.
2075
+ *
2076
+ * The policy is evaluated for every inbound frame:
2077
+ * - `checkUnit` and `checkAddress` run before dispatch.
2078
+ * - `checkRuntime` runs after the unit handler produces a successful response
2079
+ * but before the response is encoded and written.
2080
+ *
2081
+ * @param authorizer The policy to enforce. Omit a hook to disable that gate.
2082
+ * @returns `this` for chaining.
2083
+ *
2084
+ * @example
2085
+ * ```ts
2086
+ * slave.setAccessAuthorizer({
2087
+ * checkUnit: (unit) => allowedUnits.has(unit),
2088
+ * checkAddress: (_unit, table, [start, end]) =>
2089
+ * table === 'holdingRegisters' && start >= 0 && end < 100,
2090
+ * });
2091
+ * ```
2092
+ */
2093
+ setAccessAuthorizer(authorizer: AccessAuthorizer): void;
2094
+ /**
2095
+ * Remove the access-control policy installed by {@link setAccessAuthorizer}.
2096
+ *
2097
+ * After this call all inbound frames flow through the slave without any
2098
+ * access-control gate until a new authorizer is installed.
2099
+ *
2100
+ * @returns `this` for chaining.
2101
+ */
2102
+ deleteAccessAuthorizer(): void;
2103
+ /**
2104
+ * Destroy the slave and release all resources.
2105
+ *
2106
+ * After this call the instance is unusable: protocol callbacks are cleaned,
2107
+ * queues are cleared, units and custom function codes are removed, and
2108
+ * listeners are detached.
2109
+ *
2110
+ * @returns `this` for chaining.
2111
+ */
2112
+ destroy(): void;
2113
+ }
2114
+ //#endregion
2115
+ //#region src/plugins/connection-security-options.d.ts
2116
+ /**
2117
+ * Shared security and resource-limit options for TCP and UDP server physical
2118
+ * layers.
2119
+ *
2120
+ * The same field names are used for both transports. For UDP, `maxConnections`
2121
+ * and `maxConnectionsPerIp` limit concurrent peers rather than OS-level
2122
+ * connections.
2123
+ */
2124
+ interface ConnectionSecurityOptions {
2125
+ /**
2126
+ * Allowed remote addresses. Each entry is either an exact IP string, an IPv4
2127
+ * CIDR string, or a predicate that receives the canonicalized address.
2128
+ *
2129
+ * If omitted or empty, all addresses are allowed.
2130
+ */
2131
+ whitelist?: WhitelistEntry[];
2132
+ /**
2133
+ * Maximum number of concurrent connections (TCP) or peers (UDP).
2134
+ *
2135
+ * If omitted, no limit is enforced. `0` is treated as "no limit".
2136
+ */
2137
+ maxConnections?: number;
2138
+ /**
2139
+ * Maximum number of concurrent connections (TCP) or peers (UDP) allowed from
2140
+ * a single remote IP address.
2141
+ *
2142
+ * If omitted, no limit is enforced. `0` is treated as "no limit".
2143
+ */
2144
+ maxConnectionsPerIp?: number;
2145
+ /**
2146
+ * Inactivity timeout before a connection or peer pipeline is automatically
2147
+ * destroyed (unit: ms).
2148
+ *
2149
+ * If omitted, no idle timeout is enforced for either TCP or UDP. `0`
2150
+ * disables the timer.
2151
+ */
2152
+ idleTimeout?: number;
2153
+ }
2154
+ /**
2155
+ * Security and resource-limit options for {@link TcpServerPhysicalLayer}.
2156
+ */
2157
+ type TcpConnectionSecurityOptions = ConnectionSecurityOptions;
2158
+ /**
2159
+ * Security and resource-limit options for {@link UdpServerPhysicalLayer}.
2160
+ */
2161
+ type UdpConnectionSecurityOptions = ConnectionSecurityOptions;
2162
+ /**
2163
+ * Security and resource-limit options for {@link TlsServerPhysicalLayer}.
2164
+ */
2165
+ type TlsConnectionSecurityOptions = ConnectionSecurityOptions;
2166
+ //#endregion
2167
+ //#region src/plugins/vars.d.ts
2168
+ /**
2169
+ * Lifecycle states of a physical layer (serial port, TCP server/client, UDP socket).
2170
+ */
2171
+ declare enum PhysicalLayerState {
2172
+ OPENING = "opening",
2173
+ OPEN = "open",
2174
+ CLOSING = "closing",
2175
+ CLOSED = "closed"
2176
+ }
2177
+ /**
2178
+ * Lifecycle states of a pipeline layer (a single connection / stream).
2179
+ */
2180
+ declare enum PipelineLayerState {
2181
+ CONNECTED = "connected",
2182
+ DESTROYING = "destroying",
2183
+ DESTROYED = "destroyed"
2184
+ }
2185
+ //#endregion
2186
+ //#region src/plugins/abstract-pipeline-layer.d.ts
2187
+ /**
2188
+ * Events emitted by {@link AbstractPipelineLayer}.
2189
+ */
2190
+ interface AbstractPipelineLayerEvents {
2191
+ /** Raw bytes received from the transport, forwarded to the protocol layer. */
2192
+ data: [data: Buffer];
2193
+ /** The pipeline layer has closed. */
2194
+ close: [];
2195
+ /** A frame was transmitted on the wire. */
2196
+ tx: [buffer: Buffer];
2197
+ /** A raw chunk was received from the transport. */
2198
+ rx: [buffer: Buffer];
2199
+ }
2200
+ /**
2201
+ * Abstract pipeline layer that owns a single connection / stream.
2202
+ *
2203
+ * Pipeline layers bridge the physical transport and the protocol layer:
2204
+ * they emit `data` events carrying raw bytes and expose `write` for outbound
2205
+ * frames. Implementations are transport-specific (serial, TCP, UDP).
2206
+ */
2207
+ declare abstract class AbstractPipelineLayer extends EventEmitter<AbstractPipelineLayerEvents> {
2208
+ /** Current lifecycle state of the pipeline. */
2209
+ abstract readonly state: PipelineLayerState;
2210
+ /**
2211
+ * Write a raw frame to the transport.
2212
+ *
2213
+ * @param data Encoded frame bytes (unit: byte). Must not be modified.
2214
+ * @param cb Optional callback invoked on completion or error.
2215
+ * @returns `void`.
2216
+ */
2217
+ abstract write(data: Buffer, cb?: (err: Error | null) => void): void;
2218
+ /**
2219
+ * Tear down the pipeline.
2220
+ *
2221
+ * @param cb Optional callback invoked once the layer is destroyed.
2222
+ * @returns `void`.
2223
+ */
2224
+ abstract destroy(cb?: (err: Error | null) => void): void;
2225
+ }
2226
+ //#endregion
2227
+ //#region src/plugins/abstract-physical-layer.d.ts
2228
+ /**
2229
+ * Events emitted by {@link AbstractPhysicalLayer}.
2230
+ */
2231
+ interface AbstractPhysicalLayerEvents {
2232
+ /** The physical layer is open and ready to accept connections. */
2233
+ open: [];
2234
+ /** A new pipeline connection has been established. */
2235
+ connect: [pipeline: AbstractPipelineLayer];
2236
+ /**
2237
+ * A TCP connection or UDP peer was rejected by a security or resource-limit
2238
+ * policy.
2239
+ */
2240
+ connectionRejected: [event: {
2241
+ /** Policy that caused the rejection. */reason: 'whitelist' | 'max_connections' | 'max_connections_per_ip'; /** Canonicalized remote IP address. */
2242
+ address: string; /** Remote port; present for UDP peers and optionally for TCP connections. */
2243
+ port?: number;
2244
+ }];
2245
+ /** The physical layer has closed. */
2246
+ close: [];
2247
+ /** A non-fatal transport error occurred. */
2248
+ error: [error: Error];
2249
+ }
2250
+ /**
2251
+ * Abstract physical layer that manages the lifecycle of a transport endpoint.
2252
+ *
2253
+ * Subclasses open a serial port, TCP client/server socket, or UDP socket and
2254
+ * emit {@link AbstractPipelineLayer} instances through the `connect` event.
2255
+ */
2256
+ declare abstract class AbstractPhysicalLayer extends EventEmitter<AbstractPhysicalLayerEvents> {
2257
+ /** Current lifecycle state of the physical layer. */
2258
+ abstract readonly state: PhysicalLayerState;
2259
+ /**
2260
+ * Open the transport endpoint.
2261
+ *
2262
+ * @param args Transport-specific open arguments (port path, listen options, etc.).
2263
+ * @returns `void`.
2264
+ */
2265
+ abstract open(...args: any[]): void;
2266
+ /**
2267
+ * Close the transport endpoint.
2268
+ *
2269
+ * @param cb Optional callback invoked once the layer is closed.
2270
+ * @returns `void`.
2271
+ */
2272
+ abstract close(cb?: (err: Error | null) => void): void;
2273
+ }
2274
+ //#endregion
2275
+ //#region src/plugins/serial/serial-physical-layer.d.ts
2276
+ /**
2277
+ * Construction-time configuration for {@link SerialPhysicalLayer}.
2278
+ *
2279
+ * Forwarded almost verbatim to `node-serialport`; the per-field doc strings
2280
+ * mirror the upstream `OpenOptions` semantics so users do not have to cross-
2281
+ * reference two manuals. The path / baudRate pair is the only required
2282
+ * tuple — every other field has a hardware-safe default.
2283
+ */
2284
+ interface SerialPhysicalLayerOptions {
2285
+ /** The system path of the serial port you want to open. For example, `/dev/tty.XXX` on Mac/Linux, or `COM1` on Windows */
2286
+ path: string;
2287
+ /**
2288
+ * 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.
2289
+ */
2290
+ baudRate: number;
2291
+ /** Must be one of these: 5, 6, 7, or 8. Defaults to 8 */
2292
+ dataBits?: 5 | 6 | 7 | 8;
2293
+ /** Prevent other processes from opening the port. Windows does not currently support `false`. Defaults to true */
2294
+ lock?: boolean;
2295
+ /** Must be 1, 1.5 or 2. Defaults to 1 */
2296
+ stopBits?: 1 | 1.5 | 2;
2297
+ /** Device parity. Defaults to none */
2298
+ parity?: 'none' | 'even' | 'odd' | 'mark' | 'space';
2299
+ /** Flow control Setting. Defaults to false */
2300
+ rtscts?: boolean;
2301
+ /** Flow control Setting. Defaults to false */
2302
+ xon?: boolean;
2303
+ /** Flow control Setting. Defaults to false */
2304
+ xoff?: boolean;
2305
+ /** Flow control Setting. Defaults to false */
2306
+ xany?: boolean;
2307
+ /** drop DTR on close. Defaults to true */
2308
+ hupcl?: boolean;
2309
+ /** The size of the read and write buffers. Defaults to 64k */
2310
+ highWaterMark?: number;
2311
+ /** Emit 'end' on port close. Defaults to false */
2312
+ endOnClose?: boolean;
2313
+ /** see `man termios`. Defaults to 1 (Darwin/Linux only) */
2314
+ vmin?: number;
2315
+ /** see `man termios`. Defaults to 0 (Darwin/Linux only) */
2316
+ vtime?: number;
2317
+ /** RTS mode. Defaults to handshake (Windows only) */
2318
+ rtsMode?: 'handshake' | 'enable' | 'toggle';
2319
+ }
2320
+ /**
2321
+ * Serial pipeline layer backed by a `node-serialport` `SerialPort`.
2322
+ *
2323
+ * Buffers writes so that only one `SerialPort.write`+`drain` cycle is in
2324
+ * flight at a time, matching the half-duplex expectations of Modbus RTU/ASCII.
2325
+ */
2326
+ declare class SerialPipelineLayer extends AbstractPipelineLayer {
2327
+ private _state;
2328
+ private _physicalLayer;
2329
+ private _serialport;
2330
+ private _isDraining;
2331
+ private _writeDataQueue;
2332
+ private _writeCbQueue;
2333
+ private _pendingDestroyCbs;
2334
+ private _cleanupFns;
2335
+ /** Current lifecycle state of this pipeline. */
2336
+ get state(): PipelineLayerState;
2337
+ /** Parent physical layer that created this pipeline. */
2338
+ get physicalLayer(): AbstractPhysicalLayer;
2339
+ /** Underlying `SerialPort` instance. */
2340
+ get serialport(): SerialPort;
2341
+ /**
2342
+ * @param physicalLayer Parent physical layer.
2343
+ * @param serialport Opened `SerialPort` instance.
2344
+ */
2345
+ constructor(physicalLayer: SerialPhysicalLayer, serialport: SerialPort);
2346
+ /**
2347
+ * Write a frame to the serial port.
2348
+ *
2349
+ * Writes are serialized so that each frame is fully drained before the next
2350
+ * one starts.
2351
+ *
2352
+ * @param data Encoded frame bytes (unit: byte).
2353
+ * @param cb Optional callback invoked once the write (and drain) completes.
2354
+ * @returns `void`.
2355
+ */
2356
+ write(data: Buffer, cb?: (err: Error | null) => void): void;
2357
+ private _executeWrite;
2358
+ private _next;
2359
+ private _flushQueueWithError;
2360
+ /**
2361
+ * Close the underlying serial port and tear down the pipeline.
2362
+ *
2363
+ * @param cb Optional callback invoked once the port is closed.
2364
+ * @returns `void`.
2365
+ */
2366
+ destroy(cb?: (err: Error | null) => void): void;
2367
+ }
2368
+ /**
2369
+ * Serial physical layer that opens a `node-serialport` port and emits a
2370
+ * single {@link SerialPipelineLayer} on `connect`.
2371
+ */
2372
+ declare class SerialPhysicalLayer extends AbstractPhysicalLayer {
2373
+ private _state;
2374
+ private _pipelines;
2375
+ private _serialport;
2376
+ private _serialportOpts;
2377
+ private _path;
2378
+ private _baudRate;
2379
+ private _pendingOpenCbs;
2380
+ private _pendingCloseCbs;
2381
+ private _cleanupFns;
2382
+ /** Current lifecycle state of this physical layer. */
2383
+ get state(): PhysicalLayerState;
2384
+ /** Underlying `SerialPort` instance when open, otherwise `null`. */
2385
+ get serialport(): SerialPort | null;
2386
+ /** System path of the serial port (e.g., `/dev/ttyUSB0`). */
2387
+ get path(): string;
2388
+ /** Configured baud rate. */
2389
+ get baudRate(): number;
2390
+ /**
2391
+ * @param options Serial port open options.
2392
+ */
2393
+ constructor(options: SerialPhysicalLayerOptions);
2394
+ /**
2395
+ * Open the serial port.
2396
+ *
2397
+ * @param cb Optional callback invoked once the port is open.
2398
+ * @returns `void`.
2399
+ */
2400
+ open(cb?: (err: Error | null) => void): void;
2401
+ /**
2402
+ * Close the serial port and destroy any associated pipelines.
2403
+ *
2404
+ * @param cb Optional callback invoked once the port is closed.
2405
+ * @returns `void`.
2406
+ */
2407
+ close(cb?: (err: Error | null) => void): void;
2408
+ }
2409
+ //#endregion
2410
+ //#region src/plugins/tcp/tcp-pipeline-layer.d.ts
2411
+ /**
2412
+ * TCP pipeline layer backed by a `node:net` `Socket`.
2413
+ *
2414
+ * Forwards `data` events from the socket and emits `tx` after a successful
2415
+ * `socket.write`. An optional `idleTimeout` destroys the pipeline after the
2416
+ * specified milliseconds with no received data.
2417
+ */
2418
+ declare class TcpPipelineLayer extends AbstractPipelineLayer {
2419
+ private _state;
2420
+ private _physicalLayer;
2421
+ private _socket;
2422
+ private _idleTid;
2423
+ private _pendingDestroyCbs;
2424
+ private _cleanupFns;
2425
+ /** Current lifecycle state of this pipeline. */
2426
+ get state(): PipelineLayerState;
2427
+ /** Parent physical layer that created this pipeline. */
2428
+ get physicalLayer(): AbstractPhysicalLayer;
2429
+ /** Underlying `Socket` instance. */
2430
+ get socket(): Socket;
2431
+ /**
2432
+ * @param physicalLayer Parent physical layer.
2433
+ * @param socket Connected `Socket` instance.
2434
+ * @param idleTimeout Optional inactivity timeout (unit: ms). `0` disables the
2435
+ * timer.
2436
+ */
2437
+ constructor(physicalLayer: AbstractPhysicalLayer, socket: Socket, idleTimeout?: number);
2438
+ /**
2439
+ * Write a frame to the TCP socket.
2440
+ *
2441
+ * @param data Encoded frame bytes (unit: byte).
2442
+ * @param cb Optional callback invoked once the write completes.
2443
+ * @returns `void`.
2444
+ */
2445
+ write(data: Buffer, cb?: (err: Error | null) => void): void;
2446
+ /**
2447
+ * Destroy the underlying socket and tear down the pipeline.
2448
+ *
2449
+ * @param cb Optional callback invoked once the layer is destroyed.
2450
+ * @returns `void`.
2451
+ */
2452
+ destroy(cb?: (err: Error | null) => void): void;
2453
+ }
2454
+ //#endregion
2455
+ //#region src/plugins/tcp/tcp-client-physical-layer.d.ts
2456
+ /**
2457
+ * TCP client physical layer.
2458
+ *
2459
+ * Opens a `node:net` socket to a remote host and emits a {@link TcpPipelineLayer}
2460
+ * on `connect`.
2461
+ */
2462
+ declare class TcpClientPhysicalLayer extends AbstractPhysicalLayer {
2463
+ private _state;
2464
+ private _pipelines;
2465
+ private _socket;
2466
+ private _socketOpts?;
2467
+ private _pendingOpenCbs;
2468
+ private _pendingCloseCbs;
2469
+ private _cleanupFns;
2470
+ /** Current lifecycle state of this physical layer. */
2471
+ get state(): PhysicalLayerState;
2472
+ /** Underlying `Socket` instance when connected, otherwise `null`. */
2473
+ get socket(): Socket | null;
2474
+ /**
2475
+ * @param options Optional `Socket` constructor options.
2476
+ */
2477
+ constructor(options?: SocketConstructorOpts);
2478
+ /**
2479
+ * Connect to a remote Modbus TCP endpoint.
2480
+ *
2481
+ * @param options Connection target (`host` and `port`). Port defaults to `502`
2482
+ * (unit: port number).
2483
+ * @param cb Optional callback invoked once the socket connects.
2484
+ * @returns `void`.
2485
+ */
2486
+ open(options: SocketConnectOpts, cb?: (err: Error | null) => void): void;
2487
+ /**
2488
+ * Close the socket and destroy any associated pipelines.
2489
+ *
2490
+ * @param cb Optional callback invoked once the layer is closed.
2491
+ * @returns `void`.
2492
+ */
2493
+ close(cb?: (err: Error | null) => void): void;
2494
+ }
2495
+ //#endregion
2496
+ //#region src/plugins/tcp/tcp-server-physical-layer.d.ts
2497
+ /**
2498
+ * TCP server physical layer.
2499
+ *
2500
+ * Listens on a local port and emits a {@link TcpPipelineLayer} for every
2501
+ * incoming connection.
2502
+ */
2503
+ declare class TcpServerPhysicalLayer extends AbstractPhysicalLayer {
2504
+ private _state;
2505
+ private _pipelines;
2506
+ private _server;
2507
+ private _serverOpts?;
2508
+ private _whitelist;
2509
+ private _maxConnections;
2510
+ private _maxConnectionsPerIp;
2511
+ private _idleTimeout;
2512
+ private _pipelinesByIp;
2513
+ private _pendingOpenCbs;
2514
+ private _pendingCloseCbs;
2515
+ private _cleanupFns;
2516
+ /** Current lifecycle state of this physical layer. */
2517
+ get state(): PhysicalLayerState;
2518
+ /** Underlying `Server` instance when listening, otherwise `null`. */
2519
+ get server(): Server | null;
2520
+ /**
2521
+ * @param options Optional `Server` constructor options.
2522
+ * @param security Optional security and resource-limit options.
2523
+ */
2524
+ constructor(options?: ServerOpts, security?: TcpConnectionSecurityOptions);
2525
+ /**
2526
+ * Start listening for incoming Modbus TCP connections.
2527
+ *
2528
+ * @param options Listen options. Port defaults to `502` (unit: port number).
2529
+ * @param cb Optional callback invoked once the server is listening.
2530
+ * @returns `void`.
2531
+ */
2532
+ open(options: ListenOptions, cb?: (err: Error | null) => void): void;
2533
+ /**
2534
+ * Close the server and destroy any active connection pipelines.
2535
+ *
2536
+ * @param cb Optional callback invoked once the server is closed.
2537
+ * @returns `void`.
2538
+ */
2539
+ close(cb?: (err: Error | null) => void): void;
2540
+ }
2541
+ //#endregion
2542
+ //#region src/plugins/tls/tls-pipeline-layer.d.ts
2543
+ /**
2544
+ * TLS pipeline layer backed by a `node:tls` `TLSSocket`.
2545
+ *
2546
+ * Forwards `data` events from the TLS socket and emits `tx` after a successful
2547
+ * `socket.write`. An optional `idleTimeout` destroys the pipeline after the
2548
+ * specified milliseconds with no received data.
2549
+ */
2550
+ declare class TlsPipelineLayer extends AbstractPipelineLayer {
2551
+ private _state;
2552
+ private _physicalLayer;
2553
+ private _socket;
2554
+ private _idleTid;
2555
+ private _pendingDestroyCbs;
2556
+ private _cleanupFns;
2557
+ /** Current lifecycle state of this pipeline. */
2558
+ get state(): PipelineLayerState;
2559
+ /** Parent physical layer that created this pipeline. */
2560
+ get physicalLayer(): AbstractPhysicalLayer;
2561
+ /** Underlying `TLSSocket` instance. */
2562
+ get socket(): TLSSocket;
2563
+ /**
2564
+ * @param physicalLayer Parent physical layer.
2565
+ * @param socket Connected `TLSSocket` instance.
2566
+ * @param idleTimeout Optional inactivity timeout (unit: ms). `0` disables the
2567
+ * timer.
2568
+ */
2569
+ constructor(physicalLayer: AbstractPhysicalLayer, socket: TLSSocket, idleTimeout?: number);
2570
+ /**
2571
+ * Write a frame to the TLS socket.
2572
+ *
2573
+ * @param data Encoded frame bytes (unit: byte).
2574
+ * @param cb Optional callback invoked once the write completes.
2575
+ * @returns `void`.
2576
+ */
2577
+ write(data: Buffer, cb?: (err: Error | null) => void): void;
2578
+ /**
2579
+ * Destroy the underlying TLS socket and tear down the pipeline.
2580
+ *
2581
+ * @param cb Optional callback invoked once the layer is destroyed.
2582
+ * @returns `void`.
2583
+ */
2584
+ destroy(cb?: (err: Error | null) => void): void;
2585
+ }
2586
+ //#endregion
2587
+ //#region src/plugins/tls/tls-client-physical-layer.d.ts
2588
+ /**
2589
+ * TLS client physical layer.
2590
+ *
2591
+ * Opens a `node:tls` connection to a remote host and emits a {@link TlsPipelineLayer}
2592
+ * on `secureConnect`.
2593
+ */
2594
+ declare class TlsClientPhysicalLayer extends AbstractPhysicalLayer {
2595
+ private _state;
2596
+ private _pipelines;
2597
+ private _socket;
2598
+ private _tlsOpts?;
2599
+ private _pendingOpenCbs;
2600
+ private _pendingCloseCbs;
2601
+ private _cleanupFns;
2602
+ /** Current lifecycle state of this physical layer. */
2603
+ get state(): PhysicalLayerState;
2604
+ /** Underlying `TLSSocket` instance when connected, otherwise `null`. */
2605
+ get socket(): TLSSocket | null;
2606
+ /**
2607
+ * @param options Optional TLS context options (cert/key/ca/etc.).
2608
+ */
2609
+ constructor(options?: SecureContextOptions & CommonConnectionOptions);
2610
+ /**
2611
+ * Connect to a remote Modbus TLS endpoint.
2612
+ *
2613
+ * @param options Connection target (`host` and `port`) merged with the
2614
+ * constructor TLS options. Port defaults to `502` (unit: port number).
2615
+ * @param cb Optional callback invoked once the TLS handshake completes.
2616
+ * @returns `void`.
2617
+ */
2618
+ open(options: ConnectionOptions, cb?: (err: Error | null) => void): void;
2619
+ /**
2620
+ * Close the TLS socket and destroy any associated pipelines.
2621
+ *
2622
+ * @param cb Optional callback invoked once the layer is closed.
2623
+ * @returns `void`.
2624
+ */
2625
+ close(cb?: (err: Error | null) => void): void;
2626
+ }
2627
+ //#endregion
2628
+ //#region src/plugins/tls/tls-server-physical-layer.d.ts
2629
+ /**
2630
+ * TLS server physical layer.
2631
+ *
2632
+ * Listens on a local port and emits a {@link TlsPipelineLayer} for every
2633
+ * incoming TLS connection after the handshake succeeds.
2634
+ */
2635
+ declare class TlsServerPhysicalLayer extends AbstractPhysicalLayer {
2636
+ private _state;
2637
+ private _pipelines;
2638
+ private _server;
2639
+ private _tlsOpts?;
2640
+ private _whitelist;
2641
+ private _maxConnections;
2642
+ private _maxConnectionsPerIp;
2643
+ private _idleTimeout;
2644
+ private _pipelinesByIp;
2645
+ private _pendingOpenCbs;
2646
+ private _pendingCloseCbs;
2647
+ private _cleanupFns;
2648
+ /** Current lifecycle state of this physical layer. */
2649
+ get state(): PhysicalLayerState;
2650
+ /** Underlying `Server` instance when listening, otherwise `null`. */
2651
+ get server(): Server$1 | null;
2652
+ /**
2653
+ * @param options Optional TLS context options for `tls.createServer`.
2654
+ * @param security Optional security and resource-limit options.
2655
+ */
2656
+ constructor(options?: TlsOptions, security?: TlsConnectionSecurityOptions);
2657
+ /**
2658
+ * Start listening for incoming Modbus TLS connections.
2659
+ *
2660
+ * @param options Listen options. Port defaults to `502` (unit: port number).
2661
+ * @param cb Optional callback invoked once the server is listening.
2662
+ * @returns `void`.
2663
+ */
2664
+ open(options: ListenOptions, cb?: (err: Error | null) => void): void;
2665
+ /**
2666
+ * Close the server and destroy any active connection pipelines.
2667
+ *
2668
+ * @param cb Optional callback invoked once the server is closed.
2669
+ * @returns `void`.
2670
+ */
2671
+ close(cb?: (err: Error | null) => void): void;
2672
+ }
2673
+ //#endregion
2674
+ //#region src/plugins/udp/udp-client-physical-layer.d.ts
2675
+ /**
2676
+ * UDP client pipeline layer backed by a connected `node:dgram` `Socket`.
2677
+ *
2678
+ * Emits `data` for incoming datagrams and `tx` after a successful `send`.
2679
+ */
2680
+ declare class UdpClientPipelineLayer extends AbstractPipelineLayer {
2681
+ private _state;
2682
+ private _physicalLayer;
2683
+ private _socket;
2684
+ private _pendingDestroyCbs;
2685
+ private _cleanupFns;
2686
+ /** Current lifecycle state of this pipeline. */
2687
+ get state(): PipelineLayerState;
2688
+ /** Parent physical layer that created this pipeline. */
2689
+ get physicalLayer(): AbstractPhysicalLayer;
2690
+ /** Underlying `Socket` instance. */
2691
+ get socket(): Socket$1;
2692
+ /**
2693
+ * @param physicalLayer Parent physical layer.
2694
+ * @param socket Connected UDP `Socket` instance.
2695
+ */
2696
+ constructor(physicalLayer: UdpClientPhysicalLayer, socket: Socket$1);
2697
+ /**
2698
+ * Send a frame through the connected UDP socket.
2699
+ *
2700
+ * @param data Encoded frame bytes (unit: byte).
2701
+ * @param cb Optional callback invoked once the datagram is sent.
2702
+ * @returns `void`.
2703
+ */
2704
+ write(data: Buffer, cb?: (err: Error | null) => void): void;
2705
+ /**
2706
+ * Close the UDP socket and tear down the pipeline.
2707
+ *
2708
+ * @param cb Optional callback invoked once the socket is closed.
2709
+ * @returns `void`.
2710
+ */
2711
+ destroy(cb?: (err: Error | null) => void): void;
2712
+ }
2713
+ /**
2714
+ * UDP client physical layer.
2715
+ *
2716
+ * Binds a local UDP socket, connects it to a remote endpoint, and emits a
2717
+ * {@link UdpClientPipelineLayer} on `connect`.
2718
+ */
2719
+ declare class UdpClientPhysicalLayer extends AbstractPhysicalLayer {
2720
+ private _state;
2721
+ private _pipelines;
2722
+ private _socket;
2723
+ private _socketOpts;
2724
+ private _pendingOpenCbs;
2725
+ private _pendingCloseCbs;
2726
+ private _cleanupFns;
2727
+ /** Current lifecycle state of this physical layer. */
2728
+ get state(): PhysicalLayerState;
2729
+ /** Underlying `Socket` instance when open, otherwise `null`. */
2730
+ get socket(): Socket$1 | null;
2731
+ /**
2732
+ * @param options Optional `SocketOptions`. Defaults to `udp4`.
2733
+ */
2734
+ constructor(options?: Partial<SocketOptions>);
2735
+ /**
2736
+ * Open a connected UDP socket to a remote Modbus endpoint.
2737
+ *
2738
+ * @param remote Remote endpoint (`address` and `port`). Port defaults to `502`
2739
+ * (unit: port number).
2740
+ * @param cb Optional callback invoked once the socket is bound and connected.
2741
+ * @returns `void`.
2742
+ */
2743
+ open(remote: {
2744
+ port?: number;
2745
+ address?: string;
2746
+ }, cb?: (err?: Error | null) => void): void;
2747
+ /**
2748
+ * Close the UDP socket and destroy any associated pipelines.
2749
+ *
2750
+ * @param cb Optional callback invoked once the layer is closed.
2751
+ * @returns `void`.
2752
+ */
2753
+ close(cb?: (err: Error | null) => void): void;
2754
+ }
2755
+ //#endregion
2756
+ //#region src/plugins/udp/udp-server-physical-layer.d.ts
2757
+ /**
2758
+ * UDP server-side pipeline layer representing one remote peer.
2759
+ *
2760
+ * Created by {@link UdpServerPhysicalLayer} for each unique remote address;
2761
+ * destroyed automatically after `idleTimeout` ms with no traffic.
2762
+ */
2763
+ declare class UdpServerPipelineLayer extends AbstractPipelineLayer {
2764
+ private _state;
2765
+ private _physicalLayer;
2766
+ private _socket;
2767
+ private _remote;
2768
+ private _idleTid;
2769
+ private _cleanupFns;
2770
+ /** Current lifecycle state of this pipeline. */
2771
+ get state(): PipelineLayerState;
2772
+ /** Parent physical layer that created this pipeline. */
2773
+ get physicalLayer(): AbstractPhysicalLayer;
2774
+ /** Underlying shared UDP `Socket` instance. */
2775
+ get socket(): Socket$1;
2776
+ /** Remote peer address information. */
2777
+ get remote(): RemoteInfo;
2778
+ /**
2779
+ * @param physicalLayer Parent physical layer.
2780
+ * @param socket Shared UDP `Socket` instance.
2781
+ * @param remote Remote peer information.
2782
+ * @param idleTimeout Inactivity timeout (unit: ms); `0` disables the timer.
2783
+ * @param messageEventDelegation Hook for subscribing to datagrams scoped to this peer.
2784
+ */
2785
+ constructor(physicalLayer: UdpServerPhysicalLayer, socket: Socket$1, remote: RemoteInfo, idleTimeout: number, messageEventDelegation: {
2786
+ add: (listener: (msg: Buffer, rinfo: RemoteInfo) => void) => void;
2787
+ remove: (listener: (...args: any[]) => void) => void;
2788
+ });
2789
+ /**
2790
+ * Send a frame to the remote peer.
2791
+ *
2792
+ * @param data Encoded frame bytes (unit: byte).
2793
+ * @param cb Optional callback invoked once the datagram is sent.
2794
+ * @returns `void`.
2795
+ */
2796
+ write(data: Buffer, cb?: (err: Error | null) => void): void;
2797
+ /**
2798
+ * Tear down this peer pipeline and stop the idle timer.
2799
+ *
2800
+ * @param cb Optional callback invoked once the pipeline is destroyed.
2801
+ * @returns `void`.
2802
+ */
2803
+ destroy(cb?: (err: Error | null) => void): void;
2804
+ }
2805
+ /**
2806
+ * UDP server physical layer.
2807
+ *
2808
+ * Binds a UDP socket, creates a {@link UdpServerPipelineLayer} for every
2809
+ * unique remote peer, and emits it through the `connect` event.
2810
+ */
2811
+ declare class UdpServerPhysicalLayer extends AbstractPhysicalLayer {
2812
+ private _state;
2813
+ private _pipelines;
2814
+ private _socket;
2815
+ private _socketOpts;
2816
+ private _whitelist;
2817
+ private _maxConnections;
2818
+ private _maxConnectionsPerIp;
2819
+ private _idleTimeout;
2820
+ private _pipelinesByIp;
2821
+ private _pendingOpenCbs;
2822
+ private _pendingCloseCbs;
2823
+ private _cleanupFns;
2824
+ /** Current lifecycle state of this physical layer. */
2825
+ get state(): PhysicalLayerState;
2826
+ /** Underlying `Socket` instance when bound, otherwise `null`. */
2827
+ get socket(): Socket$1 | null;
2828
+ /**
2829
+ * @param options Optional `SocketOptions`. Defaults to `udp4`.
2830
+ * @param security Optional security and resource-limit options.
2831
+ */
2832
+ constructor(options?: SocketOptions, security?: UdpConnectionSecurityOptions);
2833
+ /**
2834
+ * Bind the UDP socket and start accepting datagrams from remote peers.
2835
+ *
2836
+ * @param options Bind options. Port defaults to `502` (unit: port number).
2837
+ * @param cb Optional callback invoked once the socket is bound.
2838
+ * @returns `void`.
2839
+ */
2840
+ open(options: BindOptions, cb?: (err: Error | null) => void): void;
2841
+ /**
2842
+ * Close the UDP socket and destroy all peer pipelines.
2843
+ *
2844
+ * @param cb Optional callback invoked once the socket is closed.
2845
+ * @returns `void`.
2846
+ */
2847
+ close(cb?: (err: Error | null) => void): void;
2848
+ }
2849
+ //#endregion
2850
+ //#region src/index.d.ts
2851
+ /**
2852
+ * Runtime version string of the installed `njs-modbus` package.
2853
+ *
2854
+ * Resolved from the bundler-injected `__VERSION__` define when present;
2855
+ * falls back to `'1.0.0-dev'` for unbundled source consumption (e.g., CI
2856
+ * tests linking the workspace directly). Useful for client-side telemetry,
2857
+ * version-gated diagnostics, and log lines that need to pin protocol
2858
+ * behaviour to a specific release.
2859
+ */
2860
+ declare const NJS_MODBUS_VERSION: string;
2861
+ //#endregion
2862
+ export { AbstractPhysicalLayer, AbstractPipelineAdapter, AbstractPipelineAdapterEvents, AbstractPipelineLayer, AbstractProtocolLayer, type AccessAuditEvent, type AccessAuditEventType, type AccessAuthorizer, type ApplicationDataUnit, AsciiProtocolLayer, type AsciiProtocolLayerOptions, type ConnectionSecurityOptions, type CustomFunctionCode, type DeviceIdentification, ErrorCode, type FrameErrorEvent, type FrameErrorEventType, FunctionCode, ModbusError, type ModbusFrame, ModbusMaster, type ModbusMasterOptions, type ModbusQueueStrategy, type ModbusResponse, ModbusSlave, type ModbusSlaveOptions, type ModbusUnitModel, NJS_MODBUS_VERSION, PhysicalLayerState, type PipelineFaultEvent, type PipelineFaultEventType, PipelineLayerState, type ProtocolExceptionEvent, type ProtocolExceptionEventType, RtuProtocolLayer, type RtuProtocolLayerOptions, SerialPhysicalLayer, type SerialPhysicalLayerOptions, SerialPipelineLayer, type ServerId, TcpClientPhysicalLayer, type TcpConnectionSecurityOptions, TcpPipelineLayer, TcpProtocolLayer, TcpServerPhysicalLayer, TlsClientPhysicalLayer, type TlsConnectionSecurityOptions, TlsPipelineLayer, TlsServerPhysicalLayer, UdpClientPhysicalLayer, UdpClientPipelineLayer, type UdpConnectionSecurityOptions, UdpServerPhysicalLayer, UdpServerPipelineLayer, UnauthorizedAccessError, type WhitelistEntry, crcFixed as crc, generateAduHashFingerprint, getCodeByError, getErrorByCode, lrc };