njs-modbus 3.1.0 → 3.2.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/README.md +20 -0
- package/README.zh-CN.md +20 -0
- package/dist/index.cjs +878 -390
- package/dist/index.d.ts +96 -30
- package/dist/index.mjs +878 -391
- package/dist/utils.cjs +439 -0
- package/dist/utils.d.ts +161 -0
- package/dist/utils.mjs +425 -0
- package/package.json +16 -3
- package/dist/src/error-code.d.ts +0 -17
- package/dist/src/index.d.ts +0 -7
- package/dist/src/layers/application/abstract-application-layer.d.ts +0 -26
- package/dist/src/layers/application/ascii-application-layer.d.ts +0 -23
- package/dist/src/layers/application/index.d.ts +0 -6
- package/dist/src/layers/application/rtu-application-layer.d.ts +0 -34
- package/dist/src/layers/application/tcp-application-layer.d.ts +0 -16
- package/dist/src/layers/physical/abstract-physical-layer.d.ts +0 -50
- package/dist/src/layers/physical/index.d.ts +0 -12
- package/dist/src/layers/physical/serial-physical-layer.d.ts +0 -70
- package/dist/src/layers/physical/tcp-client-physical-layer.d.ts +0 -19
- package/dist/src/layers/physical/tcp-physical-connection.d.ts +0 -16
- package/dist/src/layers/physical/tcp-server-physical-layer.d.ts +0 -28
- package/dist/src/layers/physical/udp-client-physical-layer.d.ts +0 -33
- package/dist/src/layers/physical/udp-server-physical-layer.d.ts +0 -50
- package/dist/src/layers/physical/utils.d.ts +0 -39
- package/dist/src/layers/physical/vars.d.ts +0 -11
- package/dist/src/master/index.d.ts +0 -3
- package/dist/src/master/master-session.d.ts +0 -18
- package/dist/src/master/master.d.ts +0 -140
- package/dist/src/slave/index.d.ts +0 -2
- package/dist/src/slave/slave.d.ts +0 -119
- package/dist/src/types.d.ts +0 -54
- package/dist/src/utils/bitsToMs.d.ts +0 -13
- package/dist/src/utils/callback.d.ts +0 -8
- package/dist/src/utils/checkRange.d.ts +0 -1
- package/dist/src/utils/crc.d.ts +0 -1
- package/dist/src/utils/index.d.ts +0 -11
- package/dist/src/utils/isUint8.d.ts +0 -8
- package/dist/src/utils/lrc.d.ts +0 -1
- package/dist/src/utils/predictRtuFrameLength.d.ts +0 -17
- package/dist/src/utils/promisify-cb.d.ts +0 -4
- package/dist/src/utils/rtu-timing.d.ts +0 -63
- package/dist/src/utils/whitelist.d.ts +0 -11
- package/dist/src/vars.d.ts +0 -49
package/dist/utils.mjs
ADDED
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert a number of bits to milliseconds at a given baud rate.
|
|
3
|
+
*
|
|
4
|
+
* Used to derive Modbus RTU timing intervals from bit counts — e.g. 38.5 bits
|
|
5
|
+
* = 3.5 character times at 11 bits/char (t3.5 inter-frame silence), or 16.5
|
|
6
|
+
* bits = 1.5 character times (t1.5 inter-character timeout), per Modbus V1.02
|
|
7
|
+
* §2.5.1.1.
|
|
8
|
+
*
|
|
9
|
+
* @param baudRate Serial port baud rate.
|
|
10
|
+
* @param bits Number of bits to convert.
|
|
11
|
+
* @returns Duration in milliseconds.
|
|
12
|
+
*/
|
|
13
|
+
function bitsToMs(baudRate, bits) {
|
|
14
|
+
return (bits * 1000) / baudRate;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Drain a pending-callback array: invoke each callback with the given error (or null).
|
|
19
|
+
*
|
|
20
|
+
* Handles `null`/`undefined` entries (from optional `cb?` parameters) gracefully.
|
|
21
|
+
* Used by physical layers and connections to resolve queued
|
|
22
|
+
* open / close / destroy callbacks.
|
|
23
|
+
*/
|
|
24
|
+
function drainCbs(cbs, err) {
|
|
25
|
+
if (!cbs)
|
|
26
|
+
return;
|
|
27
|
+
for (const cb of cbs) {
|
|
28
|
+
cb?.(err);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function inRange(n, [min, max]) {
|
|
33
|
+
return n >= min && n <= max;
|
|
34
|
+
}
|
|
35
|
+
function isRangeArray(range) {
|
|
36
|
+
return Array.isArray(range[0]);
|
|
37
|
+
}
|
|
38
|
+
function checkRange(value, range) {
|
|
39
|
+
if (!range || range.length === 0) {
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
const values = Array.isArray(value) ? value : [value];
|
|
43
|
+
if (isRangeArray(range)) {
|
|
44
|
+
for (const r of range) {
|
|
45
|
+
const [min, max] = r;
|
|
46
|
+
const [lo, hi] = min <= max ? [min, max] : [max, min];
|
|
47
|
+
if (values.every((n) => inRange(n, [lo, hi]))) {
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
const [min, max] = range;
|
|
54
|
+
const [lo, hi] = min <= max ? [min, max] : [max, min];
|
|
55
|
+
return values.every((n) => inRange(n, [lo, hi]));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const TABLE = [
|
|
59
|
+
0x0000, 0xc0c1, 0xc181, 0x0140, 0xc301, 0x03c0, 0x0280, 0xc241, 0xc601, 0x06c0, 0x0780, 0xc741, 0x0500, 0xc5c1, 0xc481, 0x0440, 0xcc01,
|
|
60
|
+
0x0cc0, 0x0d80, 0xcd41, 0x0f00, 0xcfc1, 0xce81, 0x0e40, 0x0a00, 0xcac1, 0xcb81, 0x0b40, 0xc901, 0x09c0, 0x0880, 0xc841, 0xd801, 0x18c0,
|
|
61
|
+
0x1980, 0xd941, 0x1b00, 0xdbc1, 0xda81, 0x1a40, 0x1e00, 0xdec1, 0xdf81, 0x1f40, 0xdd01, 0x1dc0, 0x1c80, 0xdc41, 0x1400, 0xd4c1, 0xd581,
|
|
62
|
+
0x1540, 0xd701, 0x17c0, 0x1680, 0xd641, 0xd201, 0x12c0, 0x1380, 0xd341, 0x1100, 0xd1c1, 0xd081, 0x1040, 0xf001, 0x30c0, 0x3180, 0xf141,
|
|
63
|
+
0x3300, 0xf3c1, 0xf281, 0x3240, 0x3600, 0xf6c1, 0xf781, 0x3740, 0xf501, 0x35c0, 0x3480, 0xf441, 0x3c00, 0xfcc1, 0xfd81, 0x3d40, 0xff01,
|
|
64
|
+
0x3fc0, 0x3e80, 0xfe41, 0xfa01, 0x3ac0, 0x3b80, 0xfb41, 0x3900, 0xf9c1, 0xf881, 0x3840, 0x2800, 0xe8c1, 0xe981, 0x2940, 0xeb01, 0x2bc0,
|
|
65
|
+
0x2a80, 0xea41, 0xee01, 0x2ec0, 0x2f80, 0xef41, 0x2d00, 0xedc1, 0xec81, 0x2c40, 0xe401, 0x24c0, 0x2580, 0xe541, 0x2700, 0xe7c1, 0xe681,
|
|
66
|
+
0x2640, 0x2200, 0xe2c1, 0xe381, 0x2340, 0xe101, 0x21c0, 0x2080, 0xe041, 0xa001, 0x60c0, 0x6180, 0xa141, 0x6300, 0xa3c1, 0xa281, 0x6240,
|
|
67
|
+
0x6600, 0xa6c1, 0xa781, 0x6740, 0xa501, 0x65c0, 0x6480, 0xa441, 0x6c00, 0xacc1, 0xad81, 0x6d40, 0xaf01, 0x6fc0, 0x6e80, 0xae41, 0xaa01,
|
|
68
|
+
0x6ac0, 0x6b80, 0xab41, 0x6900, 0xa9c1, 0xa881, 0x6840, 0x7800, 0xb8c1, 0xb981, 0x7940, 0xbb01, 0x7bc0, 0x7a80, 0xba41, 0xbe01, 0x7ec0,
|
|
69
|
+
0x7f80, 0xbf41, 0x7d00, 0xbdc1, 0xbc81, 0x7c40, 0xb401, 0x74c0, 0x7580, 0xb541, 0x7700, 0xb7c1, 0xb681, 0x7640, 0x7200, 0xb2c1, 0xb381,
|
|
70
|
+
0x7340, 0xb101, 0x71c0, 0x7080, 0xb041, 0x5000, 0x90c1, 0x9181, 0x5140, 0x9301, 0x53c0, 0x5280, 0x9241, 0x9601, 0x56c0, 0x5780, 0x9741,
|
|
71
|
+
0x5500, 0x95c1, 0x9481, 0x5440, 0x9c01, 0x5cc0, 0x5d80, 0x9d41, 0x5f00, 0x9fc1, 0x9e81, 0x5e40, 0x5a00, 0x9ac1, 0x9b81, 0x5b40, 0x9901,
|
|
72
|
+
0x59c0, 0x5880, 0x9841, 0x8801, 0x48c0, 0x4980, 0x8941, 0x4b00, 0x8bc1, 0x8a81, 0x4a40, 0x4e00, 0x8ec1, 0x8f81, 0x4f40, 0x8d01, 0x4dc0,
|
|
73
|
+
0x4c80, 0x8c41, 0x4400, 0x84c1, 0x8581, 0x4540, 0x8701, 0x47c0, 0x4680, 0x8641, 0x8201, 0x42c0, 0x4380, 0x8341, 0x4100, 0x81c1, 0x8081,
|
|
74
|
+
0x4040,
|
|
75
|
+
];
|
|
76
|
+
function crc(data, start = 0, end = data.length) {
|
|
77
|
+
let crc = 0xffff;
|
|
78
|
+
for (let index = start; index < end; index++) {
|
|
79
|
+
crc = (TABLE[(crc ^ data[index]) & 0xff] ^ (crc >> 8)) & 0xffff;
|
|
80
|
+
}
|
|
81
|
+
return crc;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Returns true when `n` is an integer in the unsigned-byte range [0, 255].
|
|
86
|
+
*
|
|
87
|
+
* Used for byte-level Modbus payload validation (function-code values, raw
|
|
88
|
+
* byte arrays in FC17/FC43 responses) — rejects negative, fractional, NaN,
|
|
89
|
+
* Infinity, and out-of-range values uniformly.
|
|
90
|
+
*/
|
|
91
|
+
function isUint8(n) {
|
|
92
|
+
return Number.isInteger(n) && n >= 0 && n <= 255;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function lrc(data, start = 0, end = data.length) {
|
|
96
|
+
let sum = 0;
|
|
97
|
+
for (let i = start; i < end; i++) {
|
|
98
|
+
sum += data[i];
|
|
99
|
+
}
|
|
100
|
+
return (~sum + 1) & 0xff;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Standard Modbus function codes (V1.1b3 §6).
|
|
105
|
+
*/
|
|
106
|
+
var FunctionCode;
|
|
107
|
+
(function (FunctionCode) {
|
|
108
|
+
FunctionCode[FunctionCode["READ_COILS"] = 1] = "READ_COILS";
|
|
109
|
+
FunctionCode[FunctionCode["READ_DISCRETE_INPUTS"] = 2] = "READ_DISCRETE_INPUTS";
|
|
110
|
+
FunctionCode[FunctionCode["READ_HOLDING_REGISTERS"] = 3] = "READ_HOLDING_REGISTERS";
|
|
111
|
+
FunctionCode[FunctionCode["READ_INPUT_REGISTERS"] = 4] = "READ_INPUT_REGISTERS";
|
|
112
|
+
FunctionCode[FunctionCode["WRITE_SINGLE_COIL"] = 5] = "WRITE_SINGLE_COIL";
|
|
113
|
+
FunctionCode[FunctionCode["WRITE_SINGLE_REGISTER"] = 6] = "WRITE_SINGLE_REGISTER";
|
|
114
|
+
FunctionCode[FunctionCode["WRITE_MULTIPLE_COILS"] = 15] = "WRITE_MULTIPLE_COILS";
|
|
115
|
+
FunctionCode[FunctionCode["WRITE_MULTIPLE_REGISTERS"] = 16] = "WRITE_MULTIPLE_REGISTERS";
|
|
116
|
+
FunctionCode[FunctionCode["REPORT_SERVER_ID"] = 17] = "REPORT_SERVER_ID";
|
|
117
|
+
FunctionCode[FunctionCode["MASK_WRITE_REGISTER"] = 22] = "MASK_WRITE_REGISTER";
|
|
118
|
+
FunctionCode[FunctionCode["READ_WRITE_MULTIPLE_REGISTERS"] = 23] = "READ_WRITE_MULTIPLE_REGISTERS";
|
|
119
|
+
FunctionCode[FunctionCode["READ_DEVICE_IDENTIFICATION"] = 43] = "READ_DEVICE_IDENTIFICATION";
|
|
120
|
+
})(FunctionCode || (FunctionCode = {}));
|
|
121
|
+
/** Exception response FC = request FC | EXCEPTION_OFFSET (V1.1b3 §7). */
|
|
122
|
+
const EXCEPTION_OFFSET = 0x80;
|
|
123
|
+
/** FC 0x2B MEI sub-function selecting Read Device Identification (V1.1b3 §6.21). */
|
|
124
|
+
const MEI_READ_DEVICE_ID = 0x0e;
|
|
125
|
+
/** Read Device ID code values inside an FC 0x2B / MEI 0x0E request. */
|
|
126
|
+
var ReadDeviceIDCode;
|
|
127
|
+
(function (ReadDeviceIDCode) {
|
|
128
|
+
ReadDeviceIDCode[ReadDeviceIDCode["BASIC_STREAM"] = 1] = "BASIC_STREAM";
|
|
129
|
+
ReadDeviceIDCode[ReadDeviceIDCode["REGULAR_STREAM"] = 2] = "REGULAR_STREAM";
|
|
130
|
+
ReadDeviceIDCode[ReadDeviceIDCode["EXTENDED_STREAM"] = 3] = "EXTENDED_STREAM";
|
|
131
|
+
ReadDeviceIDCode[ReadDeviceIDCode["SPECIFIC_ACCESS"] = 4] = "SPECIFIC_ACCESS";
|
|
132
|
+
})(ReadDeviceIDCode || (ReadDeviceIDCode = {}));
|
|
133
|
+
/** Conformity level reported in an FC 0x2B / MEI 0x0E response. */
|
|
134
|
+
var ConformityLevel;
|
|
135
|
+
(function (ConformityLevel) {
|
|
136
|
+
ConformityLevel[ConformityLevel["BASIC"] = 129] = "BASIC";
|
|
137
|
+
ConformityLevel[ConformityLevel["REGULAR"] = 130] = "REGULAR";
|
|
138
|
+
ConformityLevel[ConformityLevel["EXTENDED"] = 131] = "EXTENDED";
|
|
139
|
+
})(ConformityLevel || (ConformityLevel = {}));
|
|
140
|
+
/** Shared empty Buffer to avoid repeated allocations. */
|
|
141
|
+
Buffer.alloc(0);
|
|
142
|
+
|
|
143
|
+
const REQUEST_FIXED_LENGTHS = {
|
|
144
|
+
[FunctionCode.READ_COILS]: 8,
|
|
145
|
+
[FunctionCode.READ_DISCRETE_INPUTS]: 8,
|
|
146
|
+
[FunctionCode.READ_HOLDING_REGISTERS]: 8,
|
|
147
|
+
[FunctionCode.READ_INPUT_REGISTERS]: 8,
|
|
148
|
+
[FunctionCode.WRITE_SINGLE_COIL]: 8,
|
|
149
|
+
[FunctionCode.WRITE_SINGLE_REGISTER]: 8,
|
|
150
|
+
[FunctionCode.REPORT_SERVER_ID]: 4,
|
|
151
|
+
[FunctionCode.MASK_WRITE_REGISTER]: 10,
|
|
152
|
+
[FunctionCode.READ_DEVICE_IDENTIFICATION]: 7,
|
|
153
|
+
};
|
|
154
|
+
const REQUEST_BYTE_COUNT = {
|
|
155
|
+
[FunctionCode.WRITE_MULTIPLE_COILS]: { offset: 6, extra: 9 },
|
|
156
|
+
[FunctionCode.WRITE_MULTIPLE_REGISTERS]: { offset: 6, extra: 9 },
|
|
157
|
+
[FunctionCode.READ_WRITE_MULTIPLE_REGISTERS]: { offset: 10, extra: 13 },
|
|
158
|
+
};
|
|
159
|
+
const RESPONSE_FIXED_LENGTHS = {
|
|
160
|
+
[FunctionCode.WRITE_SINGLE_COIL]: 8,
|
|
161
|
+
[FunctionCode.WRITE_SINGLE_REGISTER]: 8,
|
|
162
|
+
[FunctionCode.WRITE_MULTIPLE_COILS]: 8,
|
|
163
|
+
[FunctionCode.WRITE_MULTIPLE_REGISTERS]: 8,
|
|
164
|
+
[FunctionCode.MASK_WRITE_REGISTER]: 10,
|
|
165
|
+
};
|
|
166
|
+
const RESPONSE_BYTE_COUNT = {
|
|
167
|
+
[FunctionCode.READ_COILS]: { offset: 2, extra: 5 },
|
|
168
|
+
[FunctionCode.READ_DISCRETE_INPUTS]: { offset: 2, extra: 5 },
|
|
169
|
+
[FunctionCode.READ_HOLDING_REGISTERS]: { offset: 2, extra: 5 },
|
|
170
|
+
[FunctionCode.READ_INPUT_REGISTERS]: { offset: 2, extra: 5 },
|
|
171
|
+
[FunctionCode.REPORT_SERVER_ID]: { offset: 2, extra: 5 },
|
|
172
|
+
[FunctionCode.READ_WRITE_MULTIPLE_REGISTERS]: { offset: 2, extra: 5 },
|
|
173
|
+
};
|
|
174
|
+
/** Sentinel: caller needs to feed more bytes before length can be determined. */
|
|
175
|
+
const PREDICT_NEED_MORE = 0;
|
|
176
|
+
/** Sentinel: function code is not in the standard tables. */
|
|
177
|
+
const PREDICT_UNKNOWN = -1;
|
|
178
|
+
/**
|
|
179
|
+
* Predict the total RTU frame length (PDU + 2-byte CRC) given the leading bytes.
|
|
180
|
+
*
|
|
181
|
+
* Returns a sentinel-encoded number to avoid per-call object allocation on the
|
|
182
|
+
* RTU decode hot path:
|
|
183
|
+
* - Positive integer (>= 4): total frame length, function code is known.
|
|
184
|
+
* - {@link PREDICT_NEED_MORE} (0): function code is known but more bytes are
|
|
185
|
+
* required (typically waiting on the byteCount byte).
|
|
186
|
+
* - {@link PREDICT_UNKNOWN} (-1): function code is not in the standard tables —
|
|
187
|
+
* the framing layer must defer to a registered `CustomFunctionCode` or treat
|
|
188
|
+
* this as a framing error.
|
|
189
|
+
*/
|
|
190
|
+
function predictRtuFrameLength(buffer, start, end, isResponse) {
|
|
191
|
+
if (end - start < 2) {
|
|
192
|
+
return PREDICT_NEED_MORE;
|
|
193
|
+
}
|
|
194
|
+
const fc = buffer[start + 1];
|
|
195
|
+
if (isResponse && (fc & EXCEPTION_OFFSET) !== 0) {
|
|
196
|
+
return 5;
|
|
197
|
+
}
|
|
198
|
+
const fixed = (isResponse ? RESPONSE_FIXED_LENGTHS : REQUEST_FIXED_LENGTHS)[fc];
|
|
199
|
+
if (fixed !== undefined) {
|
|
200
|
+
return fixed;
|
|
201
|
+
}
|
|
202
|
+
const bc = (isResponse ? RESPONSE_BYTE_COUNT : REQUEST_BYTE_COUNT)[fc];
|
|
203
|
+
if (bc !== undefined) {
|
|
204
|
+
if (end - start <= bc.offset) {
|
|
205
|
+
return PREDICT_NEED_MORE;
|
|
206
|
+
}
|
|
207
|
+
return bc.extra + buffer[start + bc.offset];
|
|
208
|
+
}
|
|
209
|
+
if (isResponse && fc === FunctionCode.READ_DEVICE_IDENTIFICATION) {
|
|
210
|
+
return predictFc43_14Response(buffer, start, end);
|
|
211
|
+
}
|
|
212
|
+
return PREDICT_UNKNOWN;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Walk the variable-length FC 0x2B / MEI 0x0E (Read Device Identification)
|
|
216
|
+
* response structure per Modbus V1.1b3 §6.21.
|
|
217
|
+
*
|
|
218
|
+
* Layout (after unit and fc):
|
|
219
|
+
* mei(1) rdic(1) conformity(1) more(1) nextObjId(1) numObjs(1)
|
|
220
|
+
* [objId(1) objLen(1) objData(objLen)] × numObjs
|
|
221
|
+
* CRC(2)
|
|
222
|
+
*/
|
|
223
|
+
function predictFc43_14Response(buffer, start, end) {
|
|
224
|
+
if (end - start < 8) {
|
|
225
|
+
return PREDICT_NEED_MORE;
|
|
226
|
+
}
|
|
227
|
+
if (buffer[start + 2] !== MEI_READ_DEVICE_ID) {
|
|
228
|
+
return PREDICT_UNKNOWN;
|
|
229
|
+
}
|
|
230
|
+
const numObjs = buffer[start + 7];
|
|
231
|
+
let cursor = start + 8;
|
|
232
|
+
for (let i = 0; i < numObjs; i++) {
|
|
233
|
+
if (end < cursor + 2) {
|
|
234
|
+
return PREDICT_NEED_MORE;
|
|
235
|
+
}
|
|
236
|
+
const objLen = buffer[cursor + 1];
|
|
237
|
+
cursor += 2 + objLen;
|
|
238
|
+
}
|
|
239
|
+
return cursor - start + 2;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Convert a callback-style `(cb: (err?) => void) => void` call into a Promise.
|
|
244
|
+
*/
|
|
245
|
+
function promisifyCb(fn) {
|
|
246
|
+
return new Promise((resolve, reject) => {
|
|
247
|
+
fn((err) => {
|
|
248
|
+
if (err)
|
|
249
|
+
reject(err);
|
|
250
|
+
else
|
|
251
|
+
resolve();
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Resolve a single RTU timing parameter to milliseconds.
|
|
258
|
+
*
|
|
259
|
+
* Returns `undefined` when the caller did not supply the parameter at all
|
|
260
|
+
* (so the caller knows to fall back to a spec default). An explicit `0` or
|
|
261
|
+
* `{ value: 0 }` returns `0` — the parameter is set, just disabled.
|
|
262
|
+
*/
|
|
263
|
+
function resolveOne(value, baudRate, fastBaudMs) {
|
|
264
|
+
if (value === undefined) {
|
|
265
|
+
return undefined;
|
|
266
|
+
}
|
|
267
|
+
if (typeof value === 'number') {
|
|
268
|
+
return value;
|
|
269
|
+
}
|
|
270
|
+
if (value.unit === 'ms') {
|
|
271
|
+
return value.value;
|
|
272
|
+
}
|
|
273
|
+
// unit === 'bit' — needs baudRate to convert
|
|
274
|
+
if (baudRate === undefined) {
|
|
275
|
+
return undefined;
|
|
276
|
+
}
|
|
277
|
+
return baudRate > 19200 ? fastBaudMs : Math.ceil(bitsToMs(baudRate, value.value));
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Resolve Modbus RTU timing parameters from user options into milliseconds.
|
|
281
|
+
*
|
|
282
|
+
* - `intervalBetweenFrames` (t3.5): if omitted and `baudRate` is present,
|
|
283
|
+
* defaults to 38.5 bits per Modbus V1.02 §2.5.1.1. Pass `0` to disable.
|
|
284
|
+
* - `interCharTimeout` (t1.5): opt-in; only resolved when explicitly provided.
|
|
285
|
+
*
|
|
286
|
+
* Per the spec, at baud rates > 19200 fixed values are used
|
|
287
|
+
* (1.75 ms for t3.5, 0.75 ms for t1.5) regardless of the bit value.
|
|
288
|
+
*/
|
|
289
|
+
function resolveRtuTiming(opts = {}, baudRate) {
|
|
290
|
+
let intervalBetweenFrames = resolveOne(opts.intervalBetweenFrames, baudRate, 1.75);
|
|
291
|
+
if (intervalBetweenFrames === undefined) {
|
|
292
|
+
// Spec default: t3.5 derived from baudRate, or 0 when neither option nor
|
|
293
|
+
// baudRate were supplied.
|
|
294
|
+
intervalBetweenFrames = baudRate !== undefined ? (baudRate > 19200 ? 1.75 : Math.ceil(bitsToMs(baudRate, 38.5))) : 0;
|
|
295
|
+
}
|
|
296
|
+
let interCharTimeout = resolveOne(opts.interCharTimeout, baudRate, 0.75);
|
|
297
|
+
if (interCharTimeout === undefined) {
|
|
298
|
+
// t1.5 is opt-in — no spec-default fallback.
|
|
299
|
+
interCharTimeout = 0;
|
|
300
|
+
}
|
|
301
|
+
return { intervalBetweenFrames, interCharTimeout };
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/** @internal
|
|
305
|
+
* Zero-allocation binary min-heap for coalescing per-request timeouts.
|
|
306
|
+
*
|
|
307
|
+
* Uses two parallel numeric arrays (no object allocation per entry).
|
|
308
|
+
* Lazy deletion: callers never remove from the heap; expired entries
|
|
309
|
+
* are silently dropped when they surface at the top.
|
|
310
|
+
*/
|
|
311
|
+
class TimerHeap {
|
|
312
|
+
_deadlines = [];
|
|
313
|
+
_ids = [];
|
|
314
|
+
_timer = null;
|
|
315
|
+
_onFire;
|
|
316
|
+
/** Pre-bound tick handler to avoid creating a new arrow function on every setTimeout. */
|
|
317
|
+
_boundTick;
|
|
318
|
+
constructor(onFire) {
|
|
319
|
+
this._onFire = onFire;
|
|
320
|
+
this._boundTick = this._onTick.bind(this);
|
|
321
|
+
}
|
|
322
|
+
/** Number of pending timers in the heap. */
|
|
323
|
+
get size() {
|
|
324
|
+
return this._deadlines.length;
|
|
325
|
+
}
|
|
326
|
+
/** Schedule an entry. If it becomes the new top, the global timer is re-scheduled (pre-emption). */
|
|
327
|
+
add(id, ms) {
|
|
328
|
+
const deadline = performance.now() + ms;
|
|
329
|
+
let i = this._deadlines.length;
|
|
330
|
+
this._deadlines.push(deadline);
|
|
331
|
+
this._ids.push(id);
|
|
332
|
+
// sift up
|
|
333
|
+
while (i > 0) {
|
|
334
|
+
const p = (i - 1) >> 1;
|
|
335
|
+
if (this._deadlines[p] <= deadline)
|
|
336
|
+
break;
|
|
337
|
+
this._deadlines[i] = this._deadlines[p];
|
|
338
|
+
this._ids[i] = this._ids[p];
|
|
339
|
+
i = p;
|
|
340
|
+
}
|
|
341
|
+
this._deadlines[i] = deadline;
|
|
342
|
+
this._ids[i] = id;
|
|
343
|
+
// Only reschedule when the new entry became the heap top.
|
|
344
|
+
if (i === 0)
|
|
345
|
+
this._refresh();
|
|
346
|
+
}
|
|
347
|
+
/** Dispose without firing callbacks. */
|
|
348
|
+
clear() {
|
|
349
|
+
if (this._timer) {
|
|
350
|
+
clearTimeout(this._timer);
|
|
351
|
+
this._timer = null;
|
|
352
|
+
}
|
|
353
|
+
this._deadlines.length = 0;
|
|
354
|
+
this._ids.length = 0;
|
|
355
|
+
}
|
|
356
|
+
_refresh() {
|
|
357
|
+
if (this._timer) {
|
|
358
|
+
clearTimeout(this._timer);
|
|
359
|
+
this._timer = null;
|
|
360
|
+
}
|
|
361
|
+
if (this._deadlines.length === 0)
|
|
362
|
+
return;
|
|
363
|
+
const delay = Math.max(0, Math.ceil(this._deadlines[0] - performance.now()));
|
|
364
|
+
this._timer = setTimeout(this._boundTick, delay);
|
|
365
|
+
}
|
|
366
|
+
_onTick() {
|
|
367
|
+
this._timer = null;
|
|
368
|
+
const now = performance.now();
|
|
369
|
+
while (this._deadlines.length > 0 && this._deadlines[0] <= now) {
|
|
370
|
+
const id = this._pop();
|
|
371
|
+
this._onFire(id);
|
|
372
|
+
}
|
|
373
|
+
this._refresh();
|
|
374
|
+
}
|
|
375
|
+
/** Pop and return the id with the earliest deadline. Assumes heap is non-empty. */
|
|
376
|
+
_pop() {
|
|
377
|
+
const topId = this._ids[0];
|
|
378
|
+
const lastId = this._ids.pop();
|
|
379
|
+
const lastDeadline = this._deadlines.pop();
|
|
380
|
+
const n = this._deadlines.length;
|
|
381
|
+
if (n > 0) {
|
|
382
|
+
let i = 0;
|
|
383
|
+
// sift down
|
|
384
|
+
while (true) {
|
|
385
|
+
let min = i;
|
|
386
|
+
const l = i * 2 + 1;
|
|
387
|
+
const r = l + 1;
|
|
388
|
+
if (l < n && this._deadlines[l] < this._deadlines[min])
|
|
389
|
+
min = l;
|
|
390
|
+
if (r < n && this._deadlines[r] < this._deadlines[min])
|
|
391
|
+
min = r;
|
|
392
|
+
if (min === i)
|
|
393
|
+
break;
|
|
394
|
+
this._deadlines[i] = this._deadlines[min];
|
|
395
|
+
this._ids[i] = this._ids[min];
|
|
396
|
+
i = min;
|
|
397
|
+
}
|
|
398
|
+
this._deadlines[i] = lastDeadline;
|
|
399
|
+
this._ids[i] = lastId;
|
|
400
|
+
}
|
|
401
|
+
return topId;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Normalize an IP address by stripping the IPv4-mapped IPv6 prefix.
|
|
407
|
+
* This ensures consistent comparison of addresses like `::ffff:192.168.1.1`.
|
|
408
|
+
*/
|
|
409
|
+
function normalizeAddress(address = '') {
|
|
410
|
+
return address.replace(/^::ffff:/, '');
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Check whether a remote address is allowed by the given whitelist.
|
|
414
|
+
* IPv4-mapped IPv6 addresses are normalized before comparison.
|
|
415
|
+
* Returns `true` when whitelist is absent or empty.
|
|
416
|
+
*/
|
|
417
|
+
function isWhitelisted(address, whitelist) {
|
|
418
|
+
if (!whitelist || whitelist.length === 0) {
|
|
419
|
+
return true;
|
|
420
|
+
}
|
|
421
|
+
const normalized = normalizeAddress(address);
|
|
422
|
+
return whitelist.includes(normalized);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
export { PREDICT_NEED_MORE, PREDICT_UNKNOWN, TimerHeap, bitsToMs, checkRange, crc, drainCbs, isUint8, isWhitelisted, lrc, predictRtuFrameLength, promisifyCb, resolveRtuTiming };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "njs-modbus",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.0",
|
|
4
4
|
"description": "A pure JavaScript implementation of Modbus for Node.js.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"modbus",
|
|
@@ -26,15 +26,28 @@
|
|
|
26
26
|
"module": "dist/index.mjs",
|
|
27
27
|
"source": "src/index.ts",
|
|
28
28
|
"types": "dist/index.d.ts",
|
|
29
|
+
"exports": {
|
|
30
|
+
".": {
|
|
31
|
+
"import": "./dist/index.mjs",
|
|
32
|
+
"require": "./dist/index.cjs",
|
|
33
|
+
"types": "./dist/index.d.ts"
|
|
34
|
+
},
|
|
35
|
+
"./utils": {
|
|
36
|
+
"import": "./dist/utils.mjs",
|
|
37
|
+
"require": "./dist/utils.cjs",
|
|
38
|
+
"types": "./dist/utils.d.ts"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
29
41
|
"files": [
|
|
30
42
|
"dist",
|
|
31
43
|
"*.d.ts"
|
|
32
44
|
],
|
|
33
45
|
"scripts": {
|
|
46
|
+
"build": "node -e \"fs.rmSync('dist', {recursive: true, force: true})\" && rollup -c && node -e \"fs.rmSync('dist/src', {recursive: true, force: true})\"",
|
|
47
|
+
"test": "tsx --test test/**/*.test.ts",
|
|
34
48
|
"benchmark": "tsx benchmark/encode-decode.ts && echo && tsx benchmark/tcp-throughput.ts && echo && tsx benchmark/concurrent.ts",
|
|
49
|
+
"benchmark:all-fcs": "tsx benchmark/all-fcs.ts",
|
|
35
50
|
"benchmark:report": "tsx benchmark/generate-report.ts",
|
|
36
|
-
"build": "node -e \"fs.rmSync('dist', {recursive: true, force: true})\" && rollup -c",
|
|
37
|
-
"test": "tsx --test test/**/*.test.ts",
|
|
38
51
|
"util:sort-package-json": "sort-package-json"
|
|
39
52
|
},
|
|
40
53
|
"devDependencies": {
|
package/dist/src/error-code.d.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
export declare enum ErrorCode {
|
|
2
|
-
ILLEGAL_FUNCTION = 1,
|
|
3
|
-
ILLEGAL_DATA_ADDRESS = 2,
|
|
4
|
-
ILLEGAL_DATA_VALUE = 3,
|
|
5
|
-
SERVER_DEVICE_FAILURE = 4,
|
|
6
|
-
ACKNOWLEDGE = 5,
|
|
7
|
-
SERVER_DEVICE_BUSY = 6,
|
|
8
|
-
MEMORY_PARITY_ERROR = 8,
|
|
9
|
-
GATEWAY_PATH_UNAVAILABLE = 10,
|
|
10
|
-
GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND = 11
|
|
11
|
-
}
|
|
12
|
-
export declare class ModbusError extends Error {
|
|
13
|
-
readonly code: ErrorCode;
|
|
14
|
-
constructor(code: ErrorCode, message?: string);
|
|
15
|
-
}
|
|
16
|
-
export declare function getErrorByCode(code: ErrorCode): ModbusError;
|
|
17
|
-
export declare function getCodeByError(err: Error): ErrorCode;
|
package/dist/src/index.d.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import type { ApplicationDataUnit, CustomFunctionCode } from '../../types';
|
|
2
|
-
import type { AbstractPhysicalConnection } from '../physical';
|
|
3
|
-
import EventEmitter from 'node:events';
|
|
4
|
-
interface AbstractApplicationLayerEvents {
|
|
5
|
-
framing: [frame: ApplicationDataUnit & {
|
|
6
|
-
buffer: Buffer;
|
|
7
|
-
}];
|
|
8
|
-
'framing-error': [error: Error];
|
|
9
|
-
}
|
|
10
|
-
/**
|
|
11
|
-
* Application-layer protocol handler bound to a single physical connection.
|
|
12
|
-
*
|
|
13
|
-
* Its lifetime follows the channel: created when the underlying connection is
|
|
14
|
-
* established and discarded when the connection closes. Subclasses implement
|
|
15
|
-
* ASCII, RTU, or TCP framing rules.
|
|
16
|
-
*/
|
|
17
|
-
export declare abstract class AbstractApplicationLayer extends EventEmitter<AbstractApplicationLayerEvents> {
|
|
18
|
-
abstract readonly PROTOCOL: 'ASCII' | 'RTU' | 'TCP';
|
|
19
|
-
abstract ROLE: 'MASTER' | 'SLAVE';
|
|
20
|
-
abstract readonly connection: AbstractPhysicalConnection;
|
|
21
|
-
flush(): void;
|
|
22
|
-
addCustomFunctionCode(cfc: CustomFunctionCode): void;
|
|
23
|
-
removeCustomFunctionCode(fc: number): void;
|
|
24
|
-
abstract encode(unit: number, fc: number, data: Buffer, transaction?: number): Buffer;
|
|
25
|
-
}
|
|
26
|
-
export {};
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import type { AbstractPhysicalConnection } from '../physical';
|
|
2
|
-
import { AbstractApplicationLayer } from './abstract-application-layer';
|
|
3
|
-
export interface AsciiApplicationLayerOptions {
|
|
4
|
-
/**
|
|
5
|
-
* Accept lowercase hex digits (`a-f`) in addition to uppercase (`A-F`).
|
|
6
|
-
* Default false (strict per Modbus V1.1b3 §2.2 — uppercase only).
|
|
7
|
-
* Non-hex characters are always rejected with a `framing-error`.
|
|
8
|
-
*/
|
|
9
|
-
lenientHex?: boolean;
|
|
10
|
-
}
|
|
11
|
-
export declare class AsciiApplicationLayer extends AbstractApplicationLayer {
|
|
12
|
-
readonly PROTOCOL: "ASCII";
|
|
13
|
-
readonly ROLE: 'MASTER' | 'SLAVE';
|
|
14
|
-
readonly lenientHex: boolean;
|
|
15
|
-
private _connection;
|
|
16
|
-
private _state;
|
|
17
|
-
private _cleanupFns;
|
|
18
|
-
get connection(): AbstractPhysicalConnection;
|
|
19
|
-
constructor(role: 'MASTER' | 'SLAVE', connection: AbstractPhysicalConnection, options?: AsciiApplicationLayerOptions);
|
|
20
|
-
private framing;
|
|
21
|
-
flush(): void;
|
|
22
|
-
encode(unit: number, fc: number, data: Buffer, transaction?: number): Buffer;
|
|
23
|
-
}
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
export type { AsciiApplicationLayerOptions } from './ascii-application-layer';
|
|
2
|
-
export type { RtuApplicationLayerOptions } from './rtu-application-layer';
|
|
3
|
-
export { AbstractApplicationLayer } from './abstract-application-layer';
|
|
4
|
-
export { RtuApplicationLayer } from './rtu-application-layer';
|
|
5
|
-
export { AsciiApplicationLayer } from './ascii-application-layer';
|
|
6
|
-
export { TcpApplicationLayer } from './tcp-application-layer';
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import type { CustomFunctionCode } from '../../types';
|
|
2
|
-
import type { AbstractPhysicalConnection } from '../physical';
|
|
3
|
-
import { AbstractApplicationLayer } from './abstract-application-layer';
|
|
4
|
-
export interface RtuApplicationLayerOptions {
|
|
5
|
-
/** Inter-frame silence in milliseconds (Modbus RTU t3.5). 0 = disabled (immediate parse). */
|
|
6
|
-
intervalBetweenFrames?: number;
|
|
7
|
-
/** Inter-character timeout in milliseconds (Modbus RTU t1.5). Opt-in. */
|
|
8
|
-
interCharTimeout?: number;
|
|
9
|
-
/**
|
|
10
|
-
* Buffer pool size per connection (bytes). Defaults to `MAX_FRAME_LENGTH * 2`
|
|
11
|
-
* (512 bytes). Increase this if you expect frames larger than 256 bytes or
|
|
12
|
-
* heavy pipelining on a single connection.
|
|
13
|
-
*/
|
|
14
|
-
poolSize?: number;
|
|
15
|
-
}
|
|
16
|
-
export declare class RtuApplicationLayer extends AbstractApplicationLayer {
|
|
17
|
-
readonly PROTOCOL: "RTU";
|
|
18
|
-
readonly ROLE: 'MASTER' | 'SLAVE';
|
|
19
|
-
private _connection;
|
|
20
|
-
private _state;
|
|
21
|
-
private _poolSize;
|
|
22
|
-
private _threePointFiveT;
|
|
23
|
-
private _onePointFiveT;
|
|
24
|
-
private _customFunctionCodes;
|
|
25
|
-
private _cleanupFns;
|
|
26
|
-
get connection(): AbstractPhysicalConnection;
|
|
27
|
-
constructor(role: 'MASTER' | 'SLAVE', connection: AbstractPhysicalConnection, options?: RtuApplicationLayerOptions);
|
|
28
|
-
private clearStateTimers;
|
|
29
|
-
private flushBuffer;
|
|
30
|
-
flush(): void;
|
|
31
|
-
addCustomFunctionCode(cfc: CustomFunctionCode): void;
|
|
32
|
-
removeCustomFunctionCode(fc: number): void;
|
|
33
|
-
encode(unit: number, fc: number, data: Buffer, transaction?: number): Buffer;
|
|
34
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import type { AbstractPhysicalConnection } from '../physical';
|
|
2
|
-
import { AbstractApplicationLayer } from './abstract-application-layer';
|
|
3
|
-
export declare class TcpApplicationLayer extends AbstractApplicationLayer {
|
|
4
|
-
readonly PROTOCOL: "TCP";
|
|
5
|
-
readonly ROLE: 'MASTER' | 'SLAVE';
|
|
6
|
-
private _connection;
|
|
7
|
-
private _transactionId;
|
|
8
|
-
private _buffer;
|
|
9
|
-
private _cleanupFns;
|
|
10
|
-
get connection(): AbstractPhysicalConnection;
|
|
11
|
-
constructor(role: 'MASTER' | 'SLAVE', connection: AbstractPhysicalConnection);
|
|
12
|
-
private tryExtract;
|
|
13
|
-
private processFrame;
|
|
14
|
-
flush(): void;
|
|
15
|
-
encode(unit: number, fc: number, data: Buffer, transaction?: number): Buffer;
|
|
16
|
-
}
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import type { SerialPhysicalLayer } from './serial-physical-layer';
|
|
2
|
-
import type { TcpClientPhysicalLayer } from './tcp-client-physical-layer';
|
|
3
|
-
import type { TcpServerPhysicalLayer } from './tcp-server-physical-layer';
|
|
4
|
-
import type { UdpClientPhysicalLayer } from './udp-client-physical-layer';
|
|
5
|
-
import type { UdpServerPhysicalLayer } from './udp-server-physical-layer';
|
|
6
|
-
import type { PhysicalConnectionState, PhysicalState } from './vars';
|
|
7
|
-
import EventEmitter from 'node:events';
|
|
8
|
-
interface AbstractPhysicalConnectionEvents {
|
|
9
|
-
data: [data: Buffer];
|
|
10
|
-
close: [];
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* A one-way data transmission channel.
|
|
14
|
-
*
|
|
15
|
-
* State transitions are unidirectional: CONNECTED → DESTROYING → DESTROYED.
|
|
16
|
-
* Once closed, the instance cannot be reused; create a new connection instead.
|
|
17
|
-
*/
|
|
18
|
-
export declare abstract class AbstractPhysicalConnection extends EventEmitter<AbstractPhysicalConnectionEvents> {
|
|
19
|
-
abstract readonly state: PhysicalConnectionState;
|
|
20
|
-
abstract readonly physicalLayer: AbstractPhysicalLayer;
|
|
21
|
-
abstract write(data: Buffer, cb?: (err?: Error | null) => void): void;
|
|
22
|
-
abstract destroy(cb?: (err?: Error | null) => void): void;
|
|
23
|
-
}
|
|
24
|
-
export interface AbstractPhysicalLayerEvents {
|
|
25
|
-
open: [];
|
|
26
|
-
connect: [connection: AbstractPhysicalConnection];
|
|
27
|
-
close: [];
|
|
28
|
-
error: [error: Error];
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* An abstraction over local hardware or network resources.
|
|
32
|
-
*
|
|
33
|
-
* `open()` acquires the resource (serial port, TCP socket, UDP socket, etc.).
|
|
34
|
-
* Once ready, it emits `connect` with an {@link AbstractPhysicalConnection},
|
|
35
|
-
* unifying serial, TCP client, TCP server, and UDP under a single
|
|
36
|
-
* "connection-oriented" model similar to a TCP server accepting sockets.
|
|
37
|
-
*/
|
|
38
|
-
export declare abstract class AbstractPhysicalLayer extends EventEmitter<AbstractPhysicalLayerEvents> {
|
|
39
|
-
abstract readonly TYPE: 'SERIAL' | 'TCP_CLIENT' | 'TCP_SERVER' | 'UDP_CLIENT' | 'UDP_SERVER';
|
|
40
|
-
is(type: 'SERIAL'): this is SerialPhysicalLayer;
|
|
41
|
-
is(type: 'TCP_CLIENT'): this is TcpClientPhysicalLayer;
|
|
42
|
-
is(type: 'TCP_SERVER'): this is TcpServerPhysicalLayer;
|
|
43
|
-
is(type: 'UDP_CLIENT'): this is UdpClientPhysicalLayer;
|
|
44
|
-
is(type: 'UDP_SERVER'): this is UdpServerPhysicalLayer;
|
|
45
|
-
abstract readonly state: PhysicalState;
|
|
46
|
-
/** Last argument is the callback: `(err?: Error | null) => void`. Callback is optional. */
|
|
47
|
-
abstract open(...args: any[]): void;
|
|
48
|
-
abstract close(cb?: (err?: Error | null) => void): void;
|
|
49
|
-
}
|
|
50
|
-
export {};
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
export type { AbstractPhysicalLayerEvents } from './abstract-physical-layer';
|
|
2
|
-
export type { PhysicalConfig, OpenArgs } from './utils';
|
|
3
|
-
export type { TcpServerPhysicalLayerOptions } from './tcp-server-physical-layer';
|
|
4
|
-
export type { UdpServerPhysicalLayerOptions } from './udp-server-physical-layer';
|
|
5
|
-
export { AbstractPhysicalConnection, AbstractPhysicalLayer } from './abstract-physical-layer';
|
|
6
|
-
export { SerialPhysicalLayer } from './serial-physical-layer';
|
|
7
|
-
export { TcpClientPhysicalLayer } from './tcp-client-physical-layer';
|
|
8
|
-
export { TcpServerPhysicalLayer } from './tcp-server-physical-layer';
|
|
9
|
-
export { UdpClientPhysicalLayer } from './udp-client-physical-layer';
|
|
10
|
-
export { UdpServerPhysicalLayer } from './udp-server-physical-layer';
|
|
11
|
-
export { PhysicalState, PhysicalConnectionState } from './vars';
|
|
12
|
-
export { createPhysicalLayer } from './utils';
|