njs-modbus 3.2.0 → 3.3.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 +64 -35
- package/README.zh-CN.md +64 -35
- package/dist/index.cjs +536 -236
- package/dist/index.d.ts +12 -6
- package/dist/index.mjs +536 -236
- package/dist/utils.cjs +251 -154
- package/dist/utils.d.ts +29 -27
- package/dist/utils.mjs +251 -154
- package/package.json +8 -1
package/dist/utils.mjs
CHANGED
|
@@ -22,40 +22,75 @@ function bitsToMs(baudRate, bits) {
|
|
|
22
22
|
* open / close / destroy callbacks.
|
|
23
23
|
*/
|
|
24
24
|
function drainCbs(cbs, err) {
|
|
25
|
-
if (!cbs)
|
|
25
|
+
if (!cbs) {
|
|
26
26
|
return;
|
|
27
|
+
}
|
|
27
28
|
for (const cb of cbs) {
|
|
28
29
|
cb?.(err);
|
|
29
30
|
}
|
|
30
31
|
}
|
|
31
32
|
|
|
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
33
|
function checkRange(value, range) {
|
|
39
34
|
if (!range || range.length === 0) {
|
|
40
35
|
return true;
|
|
41
36
|
}
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
37
|
+
const isMultiRange = Array.isArray(range[0]);
|
|
38
|
+
const isValueArray = Array.isArray(value);
|
|
39
|
+
if (!isValueArray && !isMultiRange) {
|
|
40
|
+
const r = range;
|
|
41
|
+
const min = r[0], max = r[1];
|
|
42
|
+
const v = value;
|
|
43
|
+
return min <= max ? v >= min && v <= max : v >= max && v <= min;
|
|
44
|
+
}
|
|
45
|
+
if (!isValueArray && isMultiRange) {
|
|
46
|
+
const ranges = range;
|
|
47
|
+
const v = value;
|
|
48
|
+
for (let i = 0; i < ranges.length; i++) {
|
|
49
|
+
const min = ranges[i][0], max = ranges[i][1];
|
|
50
|
+
const lo = min <= max ? min : max;
|
|
51
|
+
const hi = min <= max ? max : min;
|
|
52
|
+
if (v >= lo && v <= hi) {
|
|
48
53
|
return true;
|
|
49
54
|
}
|
|
50
55
|
}
|
|
51
56
|
return false;
|
|
52
57
|
}
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
58
|
+
const values = value;
|
|
59
|
+
if (values.length === 0) {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
if (!isMultiRange) {
|
|
63
|
+
const r = range;
|
|
64
|
+
const min = r[0], max = r[1];
|
|
65
|
+
const lo = min <= max ? min : max;
|
|
66
|
+
const hi = min <= max ? max : min;
|
|
67
|
+
for (let i = 0; i < values.length; i++) {
|
|
68
|
+
if (values[i] < lo || values[i] > hi) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
const ranges = range;
|
|
75
|
+
for (let i = 0; i < ranges.length; i++) {
|
|
76
|
+
const min = ranges[i][0], max = ranges[i][1];
|
|
77
|
+
const lo = min <= max ? min : max;
|
|
78
|
+
const hi = min <= max ? max : min;
|
|
79
|
+
let allInRange = true;
|
|
80
|
+
for (let j = 0; j < values.length; j++) {
|
|
81
|
+
if (values[j] < lo || values[j] > hi) {
|
|
82
|
+
allInRange = false;
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (allInRange) {
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return false;
|
|
56
91
|
}
|
|
57
92
|
|
|
58
|
-
const TABLE = [
|
|
93
|
+
const TABLE = new Uint16Array([
|
|
59
94
|
0x0000, 0xc0c1, 0xc181, 0x0140, 0xc301, 0x03c0, 0x0280, 0xc241, 0xc601, 0x06c0, 0x0780, 0xc741, 0x0500, 0xc5c1, 0xc481, 0x0440, 0xcc01,
|
|
60
95
|
0x0cc0, 0x0d80, 0xcd41, 0x0f00, 0xcfc1, 0xce81, 0x0e40, 0x0a00, 0xcac1, 0xcb81, 0x0b40, 0xc901, 0x09c0, 0x0880, 0xc841, 0xd801, 0x18c0,
|
|
61
96
|
0x1980, 0xd941, 0x1b00, 0xdbc1, 0xda81, 0x1a40, 0x1e00, 0xdec1, 0xdf81, 0x1f40, 0xdd01, 0x1dc0, 0x1c80, 0xdc41, 0x1400, 0xd4c1, 0xd581,
|
|
@@ -72,11 +107,11 @@ const TABLE = [
|
|
|
72
107
|
0x59c0, 0x5880, 0x9841, 0x8801, 0x48c0, 0x4980, 0x8941, 0x4b00, 0x8bc1, 0x8a81, 0x4a40, 0x4e00, 0x8ec1, 0x8f81, 0x4f40, 0x8d01, 0x4dc0,
|
|
73
108
|
0x4c80, 0x8c41, 0x4400, 0x84c1, 0x8581, 0x4540, 0x8701, 0x47c0, 0x4680, 0x8641, 0x8201, 0x42c0, 0x4380, 0x8341, 0x4100, 0x81c1, 0x8081,
|
|
74
109
|
0x4040,
|
|
75
|
-
];
|
|
76
|
-
function crc(data, start
|
|
110
|
+
]);
|
|
111
|
+
function crc(data, start, end) {
|
|
77
112
|
let crc = 0xffff;
|
|
78
113
|
for (let index = start; index < end; index++) {
|
|
79
|
-
crc =
|
|
114
|
+
crc = TABLE[(crc ^ data[index]) & 0xff] ^ (crc >> 8);
|
|
80
115
|
}
|
|
81
116
|
return crc;
|
|
82
117
|
}
|
|
@@ -89,15 +124,15 @@ function crc(data, start = 0, end = data.length) {
|
|
|
89
124
|
* Infinity, and out-of-range values uniformly.
|
|
90
125
|
*/
|
|
91
126
|
function isUint8(n) {
|
|
92
|
-
return
|
|
127
|
+
return (n & 0xff) === n;
|
|
93
128
|
}
|
|
94
129
|
|
|
95
|
-
function lrc(data, start
|
|
130
|
+
function lrc(data, start, end) {
|
|
96
131
|
let sum = 0;
|
|
97
132
|
for (let i = start; i < end; i++) {
|
|
98
133
|
sum += data[i];
|
|
99
134
|
}
|
|
100
|
-
return
|
|
135
|
+
return -sum & 0xff;
|
|
101
136
|
}
|
|
102
137
|
|
|
103
138
|
/**
|
|
@@ -140,103 +175,93 @@ var ConformityLevel;
|
|
|
140
175
|
/** Shared empty Buffer to avoid repeated allocations. */
|
|
141
176
|
Buffer.alloc(0);
|
|
142
177
|
|
|
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
178
|
const PREDICT_NEED_MORE = 0;
|
|
176
|
-
/** Sentinel: function code is not in the standard tables. */
|
|
177
179
|
const PREDICT_UNKNOWN = -1;
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
180
|
+
const REQ_TABLE = new Int32Array(256);
|
|
181
|
+
const RES_TABLE = new Int32Array(256);
|
|
182
|
+
(function initTables() {
|
|
183
|
+
REQ_TABLE[FunctionCode.READ_COILS] = 8;
|
|
184
|
+
REQ_TABLE[FunctionCode.READ_DISCRETE_INPUTS] = 8;
|
|
185
|
+
REQ_TABLE[FunctionCode.READ_HOLDING_REGISTERS] = 8;
|
|
186
|
+
REQ_TABLE[FunctionCode.READ_INPUT_REGISTERS] = 8;
|
|
187
|
+
REQ_TABLE[FunctionCode.WRITE_SINGLE_COIL] = 8;
|
|
188
|
+
REQ_TABLE[FunctionCode.WRITE_SINGLE_REGISTER] = 8;
|
|
189
|
+
REQ_TABLE[FunctionCode.REPORT_SERVER_ID] = 4;
|
|
190
|
+
REQ_TABLE[FunctionCode.MASK_WRITE_REGISTER] = 10;
|
|
191
|
+
REQ_TABLE[FunctionCode.READ_DEVICE_IDENTIFICATION] = 7;
|
|
192
|
+
REQ_TABLE[FunctionCode.WRITE_MULTIPLE_COILS] = -1545;
|
|
193
|
+
REQ_TABLE[FunctionCode.WRITE_MULTIPLE_REGISTERS] = -1545;
|
|
194
|
+
REQ_TABLE[FunctionCode.READ_WRITE_MULTIPLE_REGISTERS] = -2573;
|
|
195
|
+
RES_TABLE[FunctionCode.WRITE_SINGLE_COIL] = 8;
|
|
196
|
+
RES_TABLE[FunctionCode.WRITE_SINGLE_REGISTER] = 8;
|
|
197
|
+
RES_TABLE[FunctionCode.WRITE_MULTIPLE_COILS] = 8;
|
|
198
|
+
RES_TABLE[FunctionCode.WRITE_MULTIPLE_REGISTERS] = 8;
|
|
199
|
+
RES_TABLE[FunctionCode.MASK_WRITE_REGISTER] = 10;
|
|
200
|
+
RES_TABLE[FunctionCode.READ_COILS] = -517;
|
|
201
|
+
RES_TABLE[FunctionCode.READ_DISCRETE_INPUTS] = -517;
|
|
202
|
+
RES_TABLE[FunctionCode.READ_HOLDING_REGISTERS] = -517;
|
|
203
|
+
RES_TABLE[FunctionCode.READ_INPUT_REGISTERS] = -517;
|
|
204
|
+
RES_TABLE[FunctionCode.REPORT_SERVER_ID] = -517;
|
|
205
|
+
RES_TABLE[FunctionCode.READ_WRITE_MULTIPLE_REGISTERS] = -517;
|
|
206
|
+
RES_TABLE[FunctionCode.READ_DEVICE_IDENTIFICATION] = -999;
|
|
207
|
+
})();
|
|
190
208
|
function predictRtuFrameLength(buffer, start, end, isResponse) {
|
|
191
|
-
|
|
209
|
+
const len = end - start;
|
|
210
|
+
if (len < 2) {
|
|
192
211
|
return PREDICT_NEED_MORE;
|
|
193
212
|
}
|
|
194
213
|
const fc = buffer[start + 1];
|
|
195
|
-
if (isResponse
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
214
|
+
if (isResponse) {
|
|
215
|
+
if ((fc & EXCEPTION_OFFSET) !== 0) {
|
|
216
|
+
return 5;
|
|
217
|
+
}
|
|
218
|
+
const val = RES_TABLE[fc];
|
|
219
|
+
if (val > 0) {
|
|
220
|
+
return val;
|
|
221
|
+
}
|
|
222
|
+
if (val < 0) {
|
|
223
|
+
if (val === -999) {
|
|
224
|
+
// FC 43 / MEI 14 response — inline to avoid function-call overhead on
|
|
225
|
+
// the framing hot path (even though this FC is uncommon).
|
|
226
|
+
if (end - start < 8) {
|
|
227
|
+
return PREDICT_NEED_MORE;
|
|
228
|
+
}
|
|
229
|
+
if (buffer[start + 2] !== MEI_READ_DEVICE_ID) {
|
|
230
|
+
return PREDICT_UNKNOWN;
|
|
231
|
+
}
|
|
232
|
+
const numObjs = buffer[start + 7];
|
|
233
|
+
let cursor = start + 8;
|
|
234
|
+
for (let i = 0; i < numObjs; i++) {
|
|
235
|
+
if (end < cursor + 2) {
|
|
236
|
+
return PREDICT_NEED_MORE;
|
|
237
|
+
}
|
|
238
|
+
cursor += 2 + buffer[cursor + 1];
|
|
239
|
+
}
|
|
240
|
+
return cursor - start + 2;
|
|
241
|
+
}
|
|
242
|
+
const decode = -val;
|
|
243
|
+
const offset = decode >> 8;
|
|
244
|
+
if (len <= offset) {
|
|
245
|
+
return PREDICT_NEED_MORE;
|
|
246
|
+
}
|
|
247
|
+
return (decode & 0xff) + buffer[start + offset];
|
|
206
248
|
}
|
|
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
249
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
250
|
+
else {
|
|
251
|
+
const val = REQ_TABLE[fc];
|
|
252
|
+
if (val > 0) {
|
|
253
|
+
return val;
|
|
254
|
+
}
|
|
255
|
+
if (val < 0) {
|
|
256
|
+
const decode = -val;
|
|
257
|
+
const offset = decode >> 8;
|
|
258
|
+
if (len <= offset) {
|
|
259
|
+
return PREDICT_NEED_MORE;
|
|
260
|
+
}
|
|
261
|
+
return (decode & 0xff) + buffer[start + offset];
|
|
235
262
|
}
|
|
236
|
-
const objLen = buffer[cursor + 1];
|
|
237
|
-
cursor += 2 + objLen;
|
|
238
263
|
}
|
|
239
|
-
return
|
|
264
|
+
return PREDICT_UNKNOWN;
|
|
240
265
|
}
|
|
241
266
|
|
|
242
267
|
/**
|
|
@@ -245,10 +270,12 @@ function predictFc43_14Response(buffer, start, end) {
|
|
|
245
270
|
function promisifyCb(fn) {
|
|
246
271
|
return new Promise((resolve, reject) => {
|
|
247
272
|
fn((err) => {
|
|
248
|
-
if (err)
|
|
273
|
+
if (err) {
|
|
249
274
|
reject(err);
|
|
250
|
-
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
251
277
|
resolve();
|
|
278
|
+
}
|
|
252
279
|
});
|
|
253
280
|
});
|
|
254
281
|
}
|
|
@@ -301,102 +328,172 @@ function resolveRtuTiming(opts = {}, baudRate) {
|
|
|
301
328
|
return { intervalBetweenFrames, interCharTimeout };
|
|
302
329
|
}
|
|
303
330
|
|
|
304
|
-
/**
|
|
305
|
-
*
|
|
331
|
+
/**
|
|
332
|
+
* Hybrid timer manager: uses native `setTimeout` for low concurrency
|
|
333
|
+
* and switches to a binary min-heap when concurrency exceeds the threshold.
|
|
334
|
+
*
|
|
335
|
+
* Benchmarks (add + clear throughput, Node 24, x64):
|
|
336
|
+
* 1 concurrent: setTimeout ~1.7× faster than heap
|
|
337
|
+
* 2 concurrent: setTimeout ~1.6× faster than heap
|
|
338
|
+
* 5 concurrent: setTimeout ~1.5-1.9× faster than heap
|
|
339
|
+
* 10 concurrent: roughly equal
|
|
340
|
+
* 20 concurrent: heap ~1.3× faster than setTimeout[]
|
|
341
|
+
* 50 concurrent: heap ~1.4-1.7× faster than setTimeout[]
|
|
306
342
|
*
|
|
307
|
-
*
|
|
308
|
-
*
|
|
309
|
-
*
|
|
343
|
+
* The crossover point is around 10 concurrent timers, so the default
|
|
344
|
+
* `concurrentThreshold = 2` keeps the common 1-2 request case on the
|
|
345
|
+
* fast direct path while delegating to the heap for larger batches.
|
|
310
346
|
*/
|
|
311
347
|
class TimerHeap {
|
|
312
348
|
_deadlines = [];
|
|
313
349
|
_ids = [];
|
|
350
|
+
_seqs = [];
|
|
351
|
+
_counter = 0;
|
|
314
352
|
_timer = null;
|
|
315
353
|
_onFire;
|
|
316
|
-
/** Pre-bound tick handler to avoid creating a new arrow function on every setTimeout. */
|
|
317
354
|
_boundTick;
|
|
318
|
-
|
|
355
|
+
_threshold;
|
|
356
|
+
_mode = 'direct';
|
|
357
|
+
_directTimers = new Map();
|
|
358
|
+
/**
|
|
359
|
+
* @param onFire Callback invoked with the timer id when it expires.
|
|
360
|
+
* @param concurrentThreshold Maximum number of timers kept as individual
|
|
361
|
+
* native `setTimeout` handles. Once exceeded, all timers migrate to
|
|
362
|
+
* the internal heap and share a single native timer. Default is 2.
|
|
363
|
+
*/
|
|
364
|
+
constructor(onFire, concurrentThreshold = 2) {
|
|
319
365
|
this._onFire = onFire;
|
|
320
366
|
this._boundTick = this._onTick.bind(this);
|
|
367
|
+
this._threshold = concurrentThreshold;
|
|
321
368
|
}
|
|
322
|
-
/** Number of pending timers in the heap. */
|
|
323
369
|
get size() {
|
|
324
|
-
return this._deadlines.length;
|
|
370
|
+
return this._mode === 'direct' ? this._directTimers.size : this._deadlines.length;
|
|
325
371
|
}
|
|
326
|
-
/** Schedule an entry. If it becomes the new top, the global timer is re-scheduled (pre-emption). */
|
|
327
372
|
add(id, ms) {
|
|
373
|
+
if (this._mode === 'direct' && this._directTimers.size + 1 <= this._threshold) {
|
|
374
|
+
const deadline = performance.now() + ms;
|
|
375
|
+
const handle = setTimeout(() => {
|
|
376
|
+
if (this._mode !== 'direct') {
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
this._directTimers.delete(id);
|
|
380
|
+
this._onFire(id);
|
|
381
|
+
}, ms);
|
|
382
|
+
this._directTimers.set(id, { handle, deadline });
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
if (this._mode === 'direct') {
|
|
386
|
+
this._mode = 'heap';
|
|
387
|
+
for (const [existingId, { handle, deadline }] of this._directTimers) {
|
|
388
|
+
clearTimeout(handle);
|
|
389
|
+
const remaining = Math.max(0, Math.ceil(deadline - performance.now()));
|
|
390
|
+
if (remaining === 0) {
|
|
391
|
+
this._onFire(existingId);
|
|
392
|
+
}
|
|
393
|
+
else {
|
|
394
|
+
this._heapAdd(existingId, remaining);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
this._directTimers.clear();
|
|
398
|
+
}
|
|
399
|
+
this._heapAdd(id, ms);
|
|
400
|
+
}
|
|
401
|
+
clear() {
|
|
402
|
+
for (const { handle } of this._directTimers.values()) {
|
|
403
|
+
clearTimeout(handle);
|
|
404
|
+
}
|
|
405
|
+
this._directTimers.clear();
|
|
406
|
+
this._mode = 'direct';
|
|
407
|
+
if (this._timer) {
|
|
408
|
+
clearTimeout(this._timer);
|
|
409
|
+
this._timer = null;
|
|
410
|
+
}
|
|
411
|
+
this._deadlines.length = 0;
|
|
412
|
+
this._ids.length = 0;
|
|
413
|
+
this._seqs.length = 0;
|
|
414
|
+
this._counter = 0;
|
|
415
|
+
}
|
|
416
|
+
_heapAdd(id, ms) {
|
|
328
417
|
const deadline = performance.now() + ms;
|
|
418
|
+
const seq = this._counter++;
|
|
329
419
|
let i = this._deadlines.length;
|
|
330
420
|
this._deadlines.push(deadline);
|
|
331
421
|
this._ids.push(id);
|
|
332
|
-
|
|
422
|
+
this._seqs.push(seq);
|
|
333
423
|
while (i > 0) {
|
|
334
424
|
const p = (i - 1) >> 1;
|
|
335
|
-
|
|
425
|
+
const parentComesFirst = this._deadlines[p] < deadline || (this._deadlines[p] === deadline && this._seqs[p] < seq);
|
|
426
|
+
if (parentComesFirst) {
|
|
336
427
|
break;
|
|
428
|
+
}
|
|
337
429
|
this._deadlines[i] = this._deadlines[p];
|
|
338
430
|
this._ids[i] = this._ids[p];
|
|
431
|
+
this._seqs[i] = this._seqs[p];
|
|
339
432
|
i = p;
|
|
340
433
|
}
|
|
341
434
|
this._deadlines[i] = deadline;
|
|
342
435
|
this._ids[i] = id;
|
|
343
|
-
|
|
344
|
-
if (i === 0)
|
|
436
|
+
this._seqs[i] = seq;
|
|
437
|
+
if (i === 0) {
|
|
345
438
|
this._refresh();
|
|
346
|
-
}
|
|
347
|
-
/** Dispose without firing callbacks. */
|
|
348
|
-
clear() {
|
|
349
|
-
if (this._timer) {
|
|
350
|
-
clearTimeout(this._timer);
|
|
351
|
-
this._timer = null;
|
|
352
439
|
}
|
|
353
|
-
this._deadlines.length = 0;
|
|
354
|
-
this._ids.length = 0;
|
|
355
440
|
}
|
|
356
441
|
_refresh() {
|
|
357
442
|
if (this._timer) {
|
|
358
443
|
clearTimeout(this._timer);
|
|
359
444
|
this._timer = null;
|
|
360
445
|
}
|
|
361
|
-
if (this._deadlines.length === 0)
|
|
446
|
+
if (this._deadlines.length === 0) {
|
|
362
447
|
return;
|
|
448
|
+
}
|
|
363
449
|
const delay = Math.max(0, Math.ceil(this._deadlines[0] - performance.now()));
|
|
364
|
-
|
|
450
|
+
const safeDelay = Math.min(delay, 2147483647);
|
|
451
|
+
this._timer = setTimeout(this._boundTick, safeDelay);
|
|
365
452
|
}
|
|
366
453
|
_onTick() {
|
|
367
454
|
this._timer = null;
|
|
368
455
|
const now = performance.now();
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
456
|
+
try {
|
|
457
|
+
while (this._deadlines.length > 0 && this._deadlines[0] <= now) {
|
|
458
|
+
const id = this._pop();
|
|
459
|
+
this._onFire(id);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
finally {
|
|
463
|
+
this._refresh();
|
|
372
464
|
}
|
|
373
|
-
this._refresh();
|
|
374
465
|
}
|
|
375
|
-
/** Pop and return the id with the earliest deadline. Assumes heap is non-empty. */
|
|
376
466
|
_pop() {
|
|
377
467
|
const topId = this._ids[0];
|
|
378
468
|
const lastId = this._ids.pop();
|
|
379
469
|
const lastDeadline = this._deadlines.pop();
|
|
470
|
+
const lastSeq = this._seqs.pop();
|
|
380
471
|
const n = this._deadlines.length;
|
|
381
472
|
if (n > 0) {
|
|
382
473
|
let i = 0;
|
|
383
|
-
|
|
384
|
-
while (
|
|
385
|
-
let
|
|
386
|
-
const
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
474
|
+
const half = n >> 1;
|
|
475
|
+
while (i < half) {
|
|
476
|
+
let minChild = (i << 1) + 1;
|
|
477
|
+
const rightChild = minChild + 1;
|
|
478
|
+
if (rightChild < n) {
|
|
479
|
+
const rightComesFirst = this._deadlines[rightChild] < this._deadlines[minChild] ||
|
|
480
|
+
(this._deadlines[rightChild] === this._deadlines[minChild] && this._seqs[rightChild] < this._seqs[minChild]);
|
|
481
|
+
if (rightComesFirst) {
|
|
482
|
+
minChild = rightChild;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
const lastComesFirst = lastDeadline < this._deadlines[minChild] || (lastDeadline === this._deadlines[minChild] && lastSeq < this._seqs[minChild]);
|
|
486
|
+
if (lastComesFirst) {
|
|
393
487
|
break;
|
|
394
|
-
|
|
395
|
-
this.
|
|
396
|
-
i =
|
|
488
|
+
}
|
|
489
|
+
this._deadlines[i] = this._deadlines[minChild];
|
|
490
|
+
this._ids[i] = this._ids[minChild];
|
|
491
|
+
this._seqs[i] = this._seqs[minChild];
|
|
492
|
+
i = minChild;
|
|
397
493
|
}
|
|
398
494
|
this._deadlines[i] = lastDeadline;
|
|
399
495
|
this._ids[i] = lastId;
|
|
496
|
+
this._seqs[i] = lastSeq;
|
|
400
497
|
}
|
|
401
498
|
return topId;
|
|
402
499
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "njs-modbus",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.0",
|
|
4
4
|
"description": "A pure JavaScript implementation of Modbus for Node.js.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"modbus",
|
|
@@ -46,8 +46,15 @@
|
|
|
46
46
|
"build": "node -e \"fs.rmSync('dist', {recursive: true, force: true})\" && rollup -c && node -e \"fs.rmSync('dist/src', {recursive: true, force: true})\"",
|
|
47
47
|
"test": "tsx --test test/**/*.test.ts",
|
|
48
48
|
"benchmark": "tsx benchmark/encode-decode.ts && echo && tsx benchmark/tcp-throughput.ts && echo && tsx benchmark/concurrent.ts",
|
|
49
|
+
"benchmark:encode-decode": "tsx benchmark/encode-decode.ts",
|
|
50
|
+
"benchmark:tcp": "tsx benchmark/tcp-throughput.ts",
|
|
51
|
+
"benchmark:concurrent": "tsx benchmark/concurrent.ts",
|
|
52
|
+
"benchmark:rtu": "tsx benchmark/rtu-throughput.ts",
|
|
49
53
|
"benchmark:all-fcs": "tsx benchmark/all-fcs.ts",
|
|
50
54
|
"benchmark:report": "tsx benchmark/generate-report.ts",
|
|
55
|
+
"benchmark:report:quick": "tsx benchmark/generate-report.ts -- --duration 10s --runs 2",
|
|
56
|
+
"benchmark:report:full": "tsx benchmark/generate-report.ts -- --duration 120s --runs 3 --max-payload",
|
|
57
|
+
"benchmark:profile": "tsx benchmark/all-fcs.ts -- --duration 30s --runs 1 --only fc03 --libs njs-modbus --profile",
|
|
51
58
|
"util:sort-package-json": "sort-package-json"
|
|
52
59
|
},
|
|
53
60
|
"devDependencies": {
|