njs-modbus 3.4.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +61 -21
- package/README.md +303 -367
- package/README.zh-CN.md +303 -367
- package/dist/index.cjs +7206 -4301
- package/dist/index.d.cts +2862 -0
- package/dist/index.d.mts +2862 -0
- package/dist/index.mjs +7162 -4286
- package/package.json +66 -62
- package/dist/index.d.ts +0 -867
- package/dist/utils.cjs +0 -564
- package/dist/utils.d.ts +0 -164
- package/dist/utils.mjs +0 -547
package/dist/index.d.cts
ADDED
|
@@ -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 };
|