njs-modbus 2.0.1 → 3.0.2
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 +265 -154
- package/README.zh-CN.md +314 -0
- package/dist/index.cjs +1887 -1074
- package/dist/index.d.ts +369 -217
- package/dist/index.mjs +1884 -1073
- package/dist/src/error-code.d.ts +2 -24
- package/dist/src/layers/application/abstract-application-layer.d.ts +12 -7
- package/dist/src/layers/application/ascii-application-layer.d.ts +8 -9
- package/dist/src/layers/application/rtu-application-layer.d.ts +21 -44
- package/dist/src/layers/application/tcp-application-layer.d.ts +8 -6
- package/dist/src/layers/physical/abstract-physical-layer.d.ts +40 -12
- package/dist/src/layers/physical/index.d.ts +9 -3
- package/dist/src/layers/physical/serial-physical-layer.d.ts +43 -13
- package/dist/src/layers/physical/tcp-client-physical-layer.d.ts +12 -10
- package/dist/src/layers/physical/tcp-physical-connection.d.ts +17 -0
- package/dist/src/layers/physical/tcp-server-physical-layer.d.ts +23 -12
- package/dist/src/layers/physical/udp-client-physical-layer.d.ts +35 -0
- package/dist/src/layers/physical/udp-server-physical-layer.d.ts +54 -0
- package/dist/src/layers/physical/utils.d.ts +39 -0
- package/dist/src/layers/physical/vars.d.ts +11 -0
- package/dist/src/master/master.d.ts +45 -18
- package/dist/src/slave/slave.d.ts +57 -33
- package/dist/src/types.d.ts +3 -3
- package/dist/src/utils/index.d.ts +4 -2
- package/dist/src/utils/rtu-timing.d.ts +49 -0
- package/dist/src/utils/whitelist.d.ts +11 -0
- package/package.json +9 -7
- package/dist/src/layers/physical/udp-physical-layer.d.ts +0 -42
- package/dist/src/utils/genConnectionId.d.ts +0 -2
- package/dist/test/adu-buffer.test.d.ts +0 -1
- package/dist/test/ascii-hex-sentry.test.d.ts +0 -1
- package/dist/test/ascii-hex-validation.test.d.ts +0 -1
- package/dist/test/ascii-tcp-fragmentation.test.d.ts +0 -1
- package/dist/test/check-range.test.d.ts +0 -1
- package/dist/test/fallback-atomic.test.d.ts +0 -1
- package/dist/test/fallback-serial.test.d.ts +0 -1
- package/dist/test/fc17-serverid-validation.test.d.ts +0 -1
- package/dist/test/fc43-conformity.test.d.ts +0 -1
- package/dist/test/fc43-utf8-objects.test.d.ts +0 -1
- package/dist/test/gen-connection-id.test.d.ts +0 -1
- package/dist/test/helpers/raw-tcp.d.ts +0 -38
- package/dist/test/master-concurrent.test.d.ts +0 -1
- package/dist/test/modbus-error.test.d.ts +0 -1
- package/dist/test/physical-lifecycle.test.d.ts +0 -1
- package/dist/test/predict-rtu.test.d.ts +0 -1
- package/dist/test/rtu-custom-fc.test.d.ts +0 -1
- package/dist/test/rtu-pool-overflow.test.d.ts +0 -1
- package/dist/test/rtu-t15-timing.test.d.ts +0 -1
- package/dist/test/rtu-t35-default.test.d.ts +0 -1
- package/dist/test/rtu-t35-strict.test.d.ts +0 -1
- package/dist/test/rtu-tcp-fragmentation.test.d.ts +0 -1
- package/dist/test/serial-e2e.test.d.ts +0 -1
- package/dist/test/slave-multi-connection.test.d.ts +0 -1
- package/dist/test/slave.test.d.ts +0 -1
- package/dist/test/tcp-fragmentation.test.d.ts +0 -1
- package/dist/test/udp-multi-client.test.d.ts +0 -1
package/dist/index.cjs
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
var EventEmitter = require('node:events');
|
|
4
4
|
var serialport = require('serialport');
|
|
5
|
-
var node_crypto = require('node:crypto');
|
|
6
5
|
var node_net = require('node:net');
|
|
7
6
|
var node_dgram = require('node:dgram');
|
|
8
7
|
|
|
@@ -18,32 +17,20 @@ exports.ErrorCode = void 0;
|
|
|
18
17
|
ErrorCode[ErrorCode["GATEWAY_PATH_UNAVAILABLE"] = 10] = "GATEWAY_PATH_UNAVAILABLE";
|
|
19
18
|
ErrorCode[ErrorCode["GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND"] = 11] = "GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND";
|
|
20
19
|
})(exports.ErrorCode || (exports.ErrorCode = {}));
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
PORT_NOT_OPEN: 'EPORT_NOT_OPEN',
|
|
32
|
-
NOT_SUPPORTED: 'ENOT_SUPPORTED',
|
|
33
|
-
INVALID_DATA: 'EINVALID_DATA',
|
|
34
|
-
INVALID_HEX: 'EINVALID_HEX',
|
|
35
|
-
CRC_MISMATCH: 'ECRC_MISMATCH',
|
|
36
|
-
LRC_MISMATCH: 'ELRC_MISMATCH',
|
|
37
|
-
INCOMPLETE_FRAME: 'EINCOMPLETE_FRAME',
|
|
38
|
-
T1_5_EXCEEDED: 'ET1_5_EXCEEDED',
|
|
39
|
-
UNKNOWN_FC: 'EUNKNOWN_FC',
|
|
40
|
-
INVALID_ROLE: 'EINVALID_ROLE',
|
|
41
|
-
RANGE: 'ERANGE',
|
|
20
|
+
const ERROR_MESSAGES = {
|
|
21
|
+
[exports.ErrorCode.ILLEGAL_FUNCTION]: 'Illegal function',
|
|
22
|
+
[exports.ErrorCode.ILLEGAL_DATA_ADDRESS]: 'Illegal data address',
|
|
23
|
+
[exports.ErrorCode.ILLEGAL_DATA_VALUE]: 'Illegal data value',
|
|
24
|
+
[exports.ErrorCode.SERVER_DEVICE_FAILURE]: 'Server device failure',
|
|
25
|
+
[exports.ErrorCode.ACKNOWLEDGE]: 'Acknowledge',
|
|
26
|
+
[exports.ErrorCode.SERVER_DEVICE_BUSY]: 'Server device busy',
|
|
27
|
+
[exports.ErrorCode.MEMORY_PARITY_ERROR]: 'Memory parity error',
|
|
28
|
+
[exports.ErrorCode.GATEWAY_PATH_UNAVAILABLE]: 'Gateway path unavailable',
|
|
29
|
+
[exports.ErrorCode.GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND]: 'Gateway target device failed to respond',
|
|
42
30
|
};
|
|
43
|
-
const MODBUS_PREFIX = 'MODBUS_ERROR_CODE_';
|
|
44
31
|
class ModbusError extends Error {
|
|
45
|
-
constructor(code, message) {
|
|
46
|
-
super(message
|
|
32
|
+
constructor(code, message = ERROR_MESSAGES[code]) {
|
|
33
|
+
super(message);
|
|
47
34
|
Object.defineProperty(this, "code", {
|
|
48
35
|
enumerable: true,
|
|
49
36
|
configurable: true,
|
|
@@ -54,18 +41,15 @@ class ModbusError extends Error {
|
|
|
54
41
|
}
|
|
55
42
|
}
|
|
56
43
|
function getErrorByCode(code) {
|
|
57
|
-
return new ModbusError(
|
|
44
|
+
return new ModbusError(code);
|
|
58
45
|
}
|
|
59
46
|
function getCodeByError(err) {
|
|
60
|
-
if (err
|
|
61
|
-
const
|
|
62
|
-
if (
|
|
63
|
-
return
|
|
47
|
+
if (err.name === 'ModbusError' && 'code' in err) {
|
|
48
|
+
const code = err.code;
|
|
49
|
+
if (code in exports.ErrorCode) {
|
|
50
|
+
return code;
|
|
64
51
|
}
|
|
65
52
|
}
|
|
66
|
-
if (err.message.startsWith(MODBUS_PREFIX)) {
|
|
67
|
-
return Number(err.message.slice(MODBUS_PREFIX.length));
|
|
68
|
-
}
|
|
69
53
|
return exports.ErrorCode.SERVER_DEVICE_FAILURE;
|
|
70
54
|
}
|
|
71
55
|
|
|
@@ -120,23 +104,584 @@ const LIMITS = {
|
|
|
120
104
|
RW_REGISTERS_WRITE_MAX: 0x0079,
|
|
121
105
|
};
|
|
122
106
|
|
|
107
|
+
/**
|
|
108
|
+
* A one-way data transmission channel.
|
|
109
|
+
*
|
|
110
|
+
* State transitions are unidirectional: CONNECTED → DESTROYING → DESTROYED.
|
|
111
|
+
* Once closed, the instance cannot be reused; create a new connection instead.
|
|
112
|
+
*/
|
|
113
|
+
class AbstractPhysicalConnection extends EventEmitter {
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* An abstraction over local hardware or network resources.
|
|
117
|
+
*
|
|
118
|
+
* `open()` acquires the resource (serial port, TCP socket, UDP socket, etc.).
|
|
119
|
+
* Once ready, it emits `connect` with an {@link AbstractPhysicalConnection},
|
|
120
|
+
* unifying serial, TCP client, TCP server, and UDP under a single
|
|
121
|
+
* "connection-oriented" model similar to a TCP server accepting sockets.
|
|
122
|
+
*/
|
|
123
123
|
class AbstractPhysicalLayer extends EventEmitter {
|
|
124
|
+
is(type) {
|
|
125
|
+
return this.TYPE === type;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
exports.PhysicalState = void 0;
|
|
130
|
+
(function (PhysicalState) {
|
|
131
|
+
PhysicalState["OPENING"] = "opening";
|
|
132
|
+
PhysicalState["OPEN"] = "open";
|
|
133
|
+
PhysicalState["CLOSING"] = "closing";
|
|
134
|
+
PhysicalState["CLOSED"] = "closed";
|
|
135
|
+
})(exports.PhysicalState || (exports.PhysicalState = {}));
|
|
136
|
+
exports.PhysicalConnectionState = void 0;
|
|
137
|
+
(function (PhysicalConnectionState) {
|
|
138
|
+
PhysicalConnectionState["CONNECTED"] = "connected";
|
|
139
|
+
PhysicalConnectionState["DESTROYING"] = "destroying";
|
|
140
|
+
PhysicalConnectionState["DESTROYED"] = "destroyed";
|
|
141
|
+
})(exports.PhysicalConnectionState || (exports.PhysicalConnectionState = {}));
|
|
142
|
+
|
|
143
|
+
class SerialPhysicalConnection extends AbstractPhysicalConnection {
|
|
144
|
+
get state() {
|
|
145
|
+
return this._state;
|
|
146
|
+
}
|
|
147
|
+
get physicalLayer() {
|
|
148
|
+
return this._physicalLayer;
|
|
149
|
+
}
|
|
150
|
+
constructor(physicalLayer, serialport) {
|
|
151
|
+
super();
|
|
152
|
+
Object.defineProperty(this, "_state", {
|
|
153
|
+
enumerable: true,
|
|
154
|
+
configurable: true,
|
|
155
|
+
writable: true,
|
|
156
|
+
value: exports.PhysicalConnectionState.CONNECTED
|
|
157
|
+
});
|
|
158
|
+
Object.defineProperty(this, "_physicalLayer", {
|
|
159
|
+
enumerable: true,
|
|
160
|
+
configurable: true,
|
|
161
|
+
writable: true,
|
|
162
|
+
value: void 0
|
|
163
|
+
});
|
|
164
|
+
Object.defineProperty(this, "_serialport", {
|
|
165
|
+
enumerable: true,
|
|
166
|
+
configurable: true,
|
|
167
|
+
writable: true,
|
|
168
|
+
value: void 0
|
|
169
|
+
});
|
|
170
|
+
Object.defineProperty(this, "_destroyPromise", {
|
|
171
|
+
enumerable: true,
|
|
172
|
+
configurable: true,
|
|
173
|
+
writable: true,
|
|
174
|
+
value: null
|
|
175
|
+
});
|
|
176
|
+
Object.defineProperty(this, "_resolveDestroy", {
|
|
177
|
+
enumerable: true,
|
|
178
|
+
configurable: true,
|
|
179
|
+
writable: true,
|
|
180
|
+
value: null
|
|
181
|
+
});
|
|
182
|
+
Object.defineProperty(this, "_cleanupFns", {
|
|
183
|
+
enumerable: true,
|
|
184
|
+
configurable: true,
|
|
185
|
+
writable: true,
|
|
186
|
+
value: new Set()
|
|
187
|
+
});
|
|
188
|
+
this._physicalLayer = physicalLayer;
|
|
189
|
+
this._serialport = serialport;
|
|
190
|
+
const onData = (chunk) => {
|
|
191
|
+
if (this.state !== exports.PhysicalConnectionState.CONNECTED) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
this.emit('data', chunk);
|
|
195
|
+
};
|
|
196
|
+
serialport.on('data', onData);
|
|
197
|
+
this._cleanupFns.add(() => serialport.off('data', onData));
|
|
198
|
+
const onSerialError = (err) => {
|
|
199
|
+
this.physicalLayer.emit('error', err);
|
|
200
|
+
};
|
|
201
|
+
serialport.on('error', onSerialError);
|
|
202
|
+
this._cleanupFns.add(() => serialport.off('error', onSerialError));
|
|
203
|
+
const cleanupClose = () => serialport.off('close', onClose);
|
|
204
|
+
const onClose = () => {
|
|
205
|
+
var _a;
|
|
206
|
+
this._cleanupFns.delete(cleanupClose);
|
|
207
|
+
this._state = exports.PhysicalConnectionState.DESTROYED;
|
|
208
|
+
this._destroyPromise = null;
|
|
209
|
+
for (const fn of this._cleanupFns) {
|
|
210
|
+
fn();
|
|
211
|
+
}
|
|
212
|
+
this._cleanupFns.clear();
|
|
213
|
+
this.emit('close');
|
|
214
|
+
(_a = this._resolveDestroy) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
215
|
+
this._resolveDestroy = null;
|
|
216
|
+
};
|
|
217
|
+
serialport.once('close', onClose);
|
|
218
|
+
this._cleanupFns.add(cleanupClose);
|
|
219
|
+
}
|
|
220
|
+
write(data) {
|
|
221
|
+
return new Promise((resolve, reject) => {
|
|
222
|
+
if (this.state === exports.PhysicalConnectionState.CONNECTED) {
|
|
223
|
+
this._serialport.write(data, (error) => {
|
|
224
|
+
if (error) {
|
|
225
|
+
reject(error);
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
resolve();
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
reject(new Error('Connection is not connected'));
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
destroy() {
|
|
238
|
+
if (this.state === exports.PhysicalConnectionState.DESTROYED) {
|
|
239
|
+
return Promise.resolve();
|
|
240
|
+
}
|
|
241
|
+
if (this.state === exports.PhysicalConnectionState.DESTROYING) {
|
|
242
|
+
return this._destroyPromise;
|
|
243
|
+
}
|
|
244
|
+
this._state = exports.PhysicalConnectionState.DESTROYING;
|
|
245
|
+
this._destroyPromise = new Promise((resolve, reject) => {
|
|
246
|
+
this._resolveDestroy = resolve;
|
|
247
|
+
this._serialport.close((err) => {
|
|
248
|
+
if (err) {
|
|
249
|
+
// Design choice: serialport.close() only rejects in extreme hardware
|
|
250
|
+
// deadlock scenarios (e.g., stuck USB-to-serial adapter). At that
|
|
251
|
+
// point the device is unrecoverable from software; propagating the
|
|
252
|
+
// error is the best we can do.
|
|
253
|
+
this._destroyPromise = null;
|
|
254
|
+
this._resolveDestroy = null;
|
|
255
|
+
reject(err);
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
return this._destroyPromise;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
class SerialPhysicalLayer extends AbstractPhysicalLayer {
|
|
263
|
+
get state() {
|
|
264
|
+
return this._state;
|
|
265
|
+
}
|
|
266
|
+
get serialport() {
|
|
267
|
+
return this._serialport;
|
|
268
|
+
}
|
|
269
|
+
get path() {
|
|
270
|
+
return this._path;
|
|
271
|
+
}
|
|
272
|
+
get baudRate() {
|
|
273
|
+
return this._baudRate;
|
|
274
|
+
}
|
|
275
|
+
constructor(options) {
|
|
276
|
+
super();
|
|
277
|
+
Object.defineProperty(this, "TYPE", {
|
|
278
|
+
enumerable: true,
|
|
279
|
+
configurable: true,
|
|
280
|
+
writable: true,
|
|
281
|
+
value: 'SERIAL'
|
|
282
|
+
});
|
|
283
|
+
Object.defineProperty(this, "_state", {
|
|
284
|
+
enumerable: true,
|
|
285
|
+
configurable: true,
|
|
286
|
+
writable: true,
|
|
287
|
+
value: exports.PhysicalState.CLOSED
|
|
288
|
+
});
|
|
289
|
+
Object.defineProperty(this, "_connections", {
|
|
290
|
+
enumerable: true,
|
|
291
|
+
configurable: true,
|
|
292
|
+
writable: true,
|
|
293
|
+
value: new Set()
|
|
294
|
+
});
|
|
295
|
+
Object.defineProperty(this, "_serialport", {
|
|
296
|
+
enumerable: true,
|
|
297
|
+
configurable: true,
|
|
298
|
+
writable: true,
|
|
299
|
+
value: null
|
|
300
|
+
});
|
|
301
|
+
Object.defineProperty(this, "_serialportOpts", {
|
|
302
|
+
enumerable: true,
|
|
303
|
+
configurable: true,
|
|
304
|
+
writable: true,
|
|
305
|
+
value: void 0
|
|
306
|
+
});
|
|
307
|
+
Object.defineProperty(this, "_path", {
|
|
308
|
+
enumerable: true,
|
|
309
|
+
configurable: true,
|
|
310
|
+
writable: true,
|
|
311
|
+
value: void 0
|
|
312
|
+
});
|
|
313
|
+
Object.defineProperty(this, "_baudRate", {
|
|
314
|
+
enumerable: true,
|
|
315
|
+
configurable: true,
|
|
316
|
+
writable: true,
|
|
317
|
+
value: void 0
|
|
318
|
+
});
|
|
319
|
+
Object.defineProperty(this, "_openPromise", {
|
|
320
|
+
enumerable: true,
|
|
321
|
+
configurable: true,
|
|
322
|
+
writable: true,
|
|
323
|
+
value: null
|
|
324
|
+
});
|
|
325
|
+
Object.defineProperty(this, "_closePromise", {
|
|
326
|
+
enumerable: true,
|
|
327
|
+
configurable: true,
|
|
328
|
+
writable: true,
|
|
329
|
+
value: null
|
|
330
|
+
});
|
|
331
|
+
Object.defineProperty(this, "_resolveClose", {
|
|
332
|
+
enumerable: true,
|
|
333
|
+
configurable: true,
|
|
334
|
+
writable: true,
|
|
335
|
+
value: null
|
|
336
|
+
});
|
|
337
|
+
Object.defineProperty(this, "_cleanupFns", {
|
|
338
|
+
enumerable: true,
|
|
339
|
+
configurable: true,
|
|
340
|
+
writable: true,
|
|
341
|
+
value: new Set()
|
|
342
|
+
});
|
|
343
|
+
this._serialportOpts = options;
|
|
344
|
+
this._path = options.path;
|
|
345
|
+
this._baudRate = options.baudRate;
|
|
346
|
+
}
|
|
347
|
+
open() {
|
|
348
|
+
if (this.state === exports.PhysicalState.OPEN) {
|
|
349
|
+
return Promise.resolve();
|
|
350
|
+
}
|
|
351
|
+
if (this.state === exports.PhysicalState.OPENING) {
|
|
352
|
+
return this._openPromise;
|
|
353
|
+
}
|
|
354
|
+
if (this.state === exports.PhysicalState.CLOSING) {
|
|
355
|
+
return Promise.reject(new Error('Port is closing'));
|
|
356
|
+
}
|
|
357
|
+
this._state = exports.PhysicalState.OPENING;
|
|
358
|
+
this._openPromise = new Promise((resolve, reject) => {
|
|
359
|
+
const serialport$1 = new serialport.SerialPort(Object.assign(Object.assign({}, this._serialportOpts), { autoOpen: false }));
|
|
360
|
+
this._serialport = serialport$1;
|
|
361
|
+
serialport$1.open((err) => {
|
|
362
|
+
if (err) {
|
|
363
|
+
this._state = exports.PhysicalState.CLOSED;
|
|
364
|
+
this._openPromise = null;
|
|
365
|
+
this._serialport = null;
|
|
366
|
+
reject(err);
|
|
367
|
+
}
|
|
368
|
+
else {
|
|
369
|
+
this._state = exports.PhysicalState.OPEN;
|
|
370
|
+
this._openPromise = null;
|
|
371
|
+
this.emit('open');
|
|
372
|
+
resolve();
|
|
373
|
+
const connection = new SerialPhysicalConnection(this, serialport$1);
|
|
374
|
+
this._connections.add(connection);
|
|
375
|
+
const cleanupConnectionClose = () => connection.off('close', onConnectionClose);
|
|
376
|
+
const onConnectionClose = () => {
|
|
377
|
+
var _a;
|
|
378
|
+
this._cleanupFns.delete(cleanupConnectionClose);
|
|
379
|
+
this._connections.delete(connection);
|
|
380
|
+
this._state = exports.PhysicalState.CLOSED;
|
|
381
|
+
this._closePromise = null;
|
|
382
|
+
for (const fn of this._cleanupFns) {
|
|
383
|
+
fn();
|
|
384
|
+
}
|
|
385
|
+
this._cleanupFns.clear();
|
|
386
|
+
this._serialport = null;
|
|
387
|
+
this.emit('close');
|
|
388
|
+
(_a = this._resolveClose) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
389
|
+
this._resolveClose = null;
|
|
390
|
+
};
|
|
391
|
+
connection.once('close', onConnectionClose);
|
|
392
|
+
this._cleanupFns.add(cleanupConnectionClose);
|
|
393
|
+
this.emit('connect', connection);
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
});
|
|
397
|
+
return this._openPromise;
|
|
398
|
+
}
|
|
399
|
+
close() {
|
|
400
|
+
if (this.state === exports.PhysicalState.CLOSED) {
|
|
401
|
+
return Promise.resolve();
|
|
402
|
+
}
|
|
403
|
+
if (this.state === exports.PhysicalState.CLOSING) {
|
|
404
|
+
return this._closePromise;
|
|
405
|
+
}
|
|
406
|
+
if (this.state === exports.PhysicalState.OPENING) {
|
|
407
|
+
return Promise.reject(new Error('Port is opening'));
|
|
408
|
+
}
|
|
409
|
+
this._state = exports.PhysicalState.CLOSING;
|
|
410
|
+
this._closePromise = new Promise((resolve, reject) => {
|
|
411
|
+
this._resolveClose = resolve;
|
|
412
|
+
Promise.all(Array.from(this._connections).map((connection) => connection.destroy())).catch((err) => {
|
|
413
|
+
this._resolveClose = null;
|
|
414
|
+
reject(err);
|
|
415
|
+
});
|
|
416
|
+
});
|
|
417
|
+
return this._closePromise;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
class TcpPhysicalConnection extends AbstractPhysicalConnection {
|
|
422
|
+
get state() {
|
|
423
|
+
return this._state;
|
|
424
|
+
}
|
|
425
|
+
get physicalLayer() {
|
|
426
|
+
return this._physicalLayer;
|
|
427
|
+
}
|
|
428
|
+
constructor(physicalLayer, socket) {
|
|
429
|
+
super();
|
|
430
|
+
Object.defineProperty(this, "_state", {
|
|
431
|
+
enumerable: true,
|
|
432
|
+
configurable: true,
|
|
433
|
+
writable: true,
|
|
434
|
+
value: exports.PhysicalConnectionState.CONNECTED
|
|
435
|
+
});
|
|
436
|
+
Object.defineProperty(this, "_physicalLayer", {
|
|
437
|
+
enumerable: true,
|
|
438
|
+
configurable: true,
|
|
439
|
+
writable: true,
|
|
440
|
+
value: void 0
|
|
441
|
+
});
|
|
442
|
+
Object.defineProperty(this, "_socket", {
|
|
443
|
+
enumerable: true,
|
|
444
|
+
configurable: true,
|
|
445
|
+
writable: true,
|
|
446
|
+
value: void 0
|
|
447
|
+
});
|
|
448
|
+
Object.defineProperty(this, "_destroyPromise", {
|
|
449
|
+
enumerable: true,
|
|
450
|
+
configurable: true,
|
|
451
|
+
writable: true,
|
|
452
|
+
value: null
|
|
453
|
+
});
|
|
454
|
+
Object.defineProperty(this, "_resolveDestroy", {
|
|
455
|
+
enumerable: true,
|
|
456
|
+
configurable: true,
|
|
457
|
+
writable: true,
|
|
458
|
+
value: null
|
|
459
|
+
});
|
|
460
|
+
Object.defineProperty(this, "_cleanupFns", {
|
|
461
|
+
enumerable: true,
|
|
462
|
+
configurable: true,
|
|
463
|
+
writable: true,
|
|
464
|
+
value: new Set()
|
|
465
|
+
});
|
|
466
|
+
this._physicalLayer = physicalLayer;
|
|
467
|
+
this._socket = socket;
|
|
468
|
+
const onData = (chunk) => {
|
|
469
|
+
if (this.state !== exports.PhysicalConnectionState.CONNECTED) {
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
this.emit('data', chunk);
|
|
473
|
+
};
|
|
474
|
+
socket.on('data', onData);
|
|
475
|
+
this._cleanupFns.add(() => socket.off('data', onData));
|
|
476
|
+
const onSocketError = (err) => {
|
|
477
|
+
this.physicalLayer.emit('error', err);
|
|
478
|
+
};
|
|
479
|
+
socket.on('error', onSocketError);
|
|
480
|
+
this._cleanupFns.add(() => socket.off('error', onSocketError));
|
|
481
|
+
const cleanupClose = () => socket.off('close', onClose);
|
|
482
|
+
const onClose = () => {
|
|
483
|
+
var _a;
|
|
484
|
+
this._cleanupFns.delete(cleanupClose);
|
|
485
|
+
this._state = exports.PhysicalConnectionState.DESTROYED;
|
|
486
|
+
this._destroyPromise = null;
|
|
487
|
+
for (const fn of this._cleanupFns) {
|
|
488
|
+
fn();
|
|
489
|
+
}
|
|
490
|
+
this._cleanupFns.clear();
|
|
491
|
+
this.emit('close');
|
|
492
|
+
(_a = this._resolveDestroy) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
493
|
+
this._resolveDestroy = null;
|
|
494
|
+
};
|
|
495
|
+
socket.once('close', onClose);
|
|
496
|
+
this._cleanupFns.add(cleanupClose);
|
|
497
|
+
}
|
|
498
|
+
write(data) {
|
|
499
|
+
return new Promise((resolve, reject) => {
|
|
500
|
+
if (this.state === exports.PhysicalConnectionState.CONNECTED) {
|
|
501
|
+
this._socket.write(data, (error) => {
|
|
502
|
+
if (error) {
|
|
503
|
+
reject(error);
|
|
504
|
+
}
|
|
505
|
+
else {
|
|
506
|
+
resolve();
|
|
507
|
+
}
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
else {
|
|
511
|
+
reject(new Error('Connection is not connected'));
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
destroy() {
|
|
516
|
+
if (this.state === exports.PhysicalConnectionState.DESTROYED) {
|
|
517
|
+
return Promise.resolve();
|
|
518
|
+
}
|
|
519
|
+
if (this.state === exports.PhysicalConnectionState.DESTROYING) {
|
|
520
|
+
return this._destroyPromise;
|
|
521
|
+
}
|
|
522
|
+
this._state = exports.PhysicalConnectionState.DESTROYING;
|
|
523
|
+
this._destroyPromise = new Promise((resolve) => {
|
|
524
|
+
this._resolveDestroy = resolve;
|
|
525
|
+
this._socket.destroy();
|
|
526
|
+
});
|
|
527
|
+
return this._destroyPromise;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
class TcpClientPhysicalLayer extends AbstractPhysicalLayer {
|
|
532
|
+
get state() {
|
|
533
|
+
return this._state;
|
|
534
|
+
}
|
|
535
|
+
get socket() {
|
|
536
|
+
return this._socket;
|
|
537
|
+
}
|
|
538
|
+
constructor(options) {
|
|
539
|
+
super();
|
|
540
|
+
Object.defineProperty(this, "TYPE", {
|
|
541
|
+
enumerable: true,
|
|
542
|
+
configurable: true,
|
|
543
|
+
writable: true,
|
|
544
|
+
value: 'TCP_CLIENT'
|
|
545
|
+
});
|
|
546
|
+
Object.defineProperty(this, "_state", {
|
|
547
|
+
enumerable: true,
|
|
548
|
+
configurable: true,
|
|
549
|
+
writable: true,
|
|
550
|
+
value: exports.PhysicalState.CLOSED
|
|
551
|
+
});
|
|
552
|
+
Object.defineProperty(this, "_connections", {
|
|
553
|
+
enumerable: true,
|
|
554
|
+
configurable: true,
|
|
555
|
+
writable: true,
|
|
556
|
+
value: new Set()
|
|
557
|
+
});
|
|
558
|
+
Object.defineProperty(this, "_socket", {
|
|
559
|
+
enumerable: true,
|
|
560
|
+
configurable: true,
|
|
561
|
+
writable: true,
|
|
562
|
+
value: null
|
|
563
|
+
});
|
|
564
|
+
Object.defineProperty(this, "_socketOpts", {
|
|
565
|
+
enumerable: true,
|
|
566
|
+
configurable: true,
|
|
567
|
+
writable: true,
|
|
568
|
+
value: void 0
|
|
569
|
+
});
|
|
570
|
+
Object.defineProperty(this, "_openPromise", {
|
|
571
|
+
enumerable: true,
|
|
572
|
+
configurable: true,
|
|
573
|
+
writable: true,
|
|
574
|
+
value: null
|
|
575
|
+
});
|
|
576
|
+
Object.defineProperty(this, "_closePromise", {
|
|
577
|
+
enumerable: true,
|
|
578
|
+
configurable: true,
|
|
579
|
+
writable: true,
|
|
580
|
+
value: null
|
|
581
|
+
});
|
|
582
|
+
Object.defineProperty(this, "_resolveClose", {
|
|
583
|
+
enumerable: true,
|
|
584
|
+
configurable: true,
|
|
585
|
+
writable: true,
|
|
586
|
+
value: null
|
|
587
|
+
});
|
|
588
|
+
Object.defineProperty(this, "_cleanupFns", {
|
|
589
|
+
enumerable: true,
|
|
590
|
+
configurable: true,
|
|
591
|
+
writable: true,
|
|
592
|
+
value: new Set()
|
|
593
|
+
});
|
|
594
|
+
this._socketOpts = options;
|
|
595
|
+
}
|
|
596
|
+
open(options) {
|
|
597
|
+
if (this.state === exports.PhysicalState.OPEN) {
|
|
598
|
+
return Promise.resolve();
|
|
599
|
+
}
|
|
600
|
+
if (this.state === exports.PhysicalState.OPENING) {
|
|
601
|
+
return this._openPromise;
|
|
602
|
+
}
|
|
603
|
+
if (this.state === exports.PhysicalState.CLOSING) {
|
|
604
|
+
return Promise.reject(new Error('Port is closing'));
|
|
605
|
+
}
|
|
606
|
+
this._state = exports.PhysicalState.OPENING;
|
|
607
|
+
this._openPromise = new Promise((resolve, reject) => {
|
|
608
|
+
const socket = new node_net.Socket(this._socketOpts);
|
|
609
|
+
this._socket = socket;
|
|
610
|
+
const onConnect = () => {
|
|
611
|
+
socket.off('error', onError);
|
|
612
|
+
this._state = exports.PhysicalState.OPEN;
|
|
613
|
+
this._openPromise = null;
|
|
614
|
+
this.emit('open');
|
|
615
|
+
resolve();
|
|
616
|
+
const connection = new TcpPhysicalConnection(this, socket);
|
|
617
|
+
this._connections.add(connection);
|
|
618
|
+
const cleanupConnectionClose = () => connection.off('close', onConnectionClose);
|
|
619
|
+
const onConnectionClose = () => {
|
|
620
|
+
var _a;
|
|
621
|
+
this._cleanupFns.delete(cleanupConnectionClose);
|
|
622
|
+
this._connections.delete(connection);
|
|
623
|
+
this._state = exports.PhysicalState.CLOSED;
|
|
624
|
+
this._closePromise = null;
|
|
625
|
+
for (const fn of this._cleanupFns) {
|
|
626
|
+
fn();
|
|
627
|
+
}
|
|
628
|
+
this._cleanupFns.clear();
|
|
629
|
+
this._socket = null;
|
|
630
|
+
this.emit('close');
|
|
631
|
+
(_a = this._resolveClose) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
632
|
+
this._resolveClose = null;
|
|
633
|
+
};
|
|
634
|
+
connection.once('close', onConnectionClose);
|
|
635
|
+
this._cleanupFns.add(cleanupConnectionClose);
|
|
636
|
+
this.emit('connect', connection);
|
|
637
|
+
};
|
|
638
|
+
const onError = (err) => {
|
|
639
|
+
socket.off('connect', onConnect);
|
|
640
|
+
this._state = exports.PhysicalState.CLOSED;
|
|
641
|
+
this._openPromise = null;
|
|
642
|
+
this._socket = null;
|
|
643
|
+
reject(err);
|
|
644
|
+
};
|
|
645
|
+
socket.once('connect', onConnect);
|
|
646
|
+
socket.once('error', onError);
|
|
647
|
+
socket.connect(options !== null && options !== void 0 ? options : { port: 502 });
|
|
648
|
+
});
|
|
649
|
+
return this._openPromise;
|
|
650
|
+
}
|
|
651
|
+
close() {
|
|
652
|
+
if (this.state === exports.PhysicalState.CLOSED) {
|
|
653
|
+
return Promise.resolve();
|
|
654
|
+
}
|
|
655
|
+
if (this.state === exports.PhysicalState.CLOSING) {
|
|
656
|
+
return this._closePromise;
|
|
657
|
+
}
|
|
658
|
+
if (this.state === exports.PhysicalState.OPENING) {
|
|
659
|
+
return Promise.reject(new Error('Port is opening'));
|
|
660
|
+
}
|
|
661
|
+
this._state = exports.PhysicalState.CLOSING;
|
|
662
|
+
this._closePromise = new Promise((resolve, reject) => {
|
|
663
|
+
this._resolveClose = resolve;
|
|
664
|
+
Promise.all(Array.from(this._connections).map((connection) => connection.destroy())).catch((err) => {
|
|
665
|
+
this._resolveClose = null;
|
|
666
|
+
reject(err);
|
|
667
|
+
});
|
|
668
|
+
});
|
|
669
|
+
return this._closePromise;
|
|
670
|
+
}
|
|
124
671
|
}
|
|
125
672
|
|
|
126
673
|
function inRange(n, [min, max]) {
|
|
127
674
|
return n >= min && n <= max;
|
|
128
675
|
}
|
|
676
|
+
function isRangeArray(range) {
|
|
677
|
+
return Array.isArray(range[0]);
|
|
678
|
+
}
|
|
129
679
|
function checkRange(value, range) {
|
|
130
|
-
if (!range) {
|
|
680
|
+
if (!range || range.length === 0) {
|
|
131
681
|
return true;
|
|
132
682
|
}
|
|
133
683
|
const values = Array.isArray(value) ? value : [value];
|
|
134
|
-
if (
|
|
135
|
-
const [min, max] = range;
|
|
136
|
-
const [lo, hi] = min <= max ? [min, max] : [max, min];
|
|
137
|
-
return values.every((n) => inRange(n, [lo, hi]));
|
|
138
|
-
}
|
|
139
|
-
else if (range.length > 0) {
|
|
684
|
+
if (isRangeArray(range)) {
|
|
140
685
|
for (const r of range) {
|
|
141
686
|
const [min, max] = r;
|
|
142
687
|
const [lo, hi] = min <= max ? [min, max] : [max, min];
|
|
@@ -146,7 +691,9 @@ function checkRange(value, range) {
|
|
|
146
691
|
}
|
|
147
692
|
return false;
|
|
148
693
|
}
|
|
149
|
-
|
|
694
|
+
const [min, max] = range;
|
|
695
|
+
const [lo, hi] = min <= max ? [min, max] : [max, min];
|
|
696
|
+
return values.every((n) => inRange(n, [lo, hi]));
|
|
150
697
|
}
|
|
151
698
|
|
|
152
699
|
const TABLE = [
|
|
@@ -175,11 +722,6 @@ function crc(data, seed = 0xffff) {
|
|
|
175
722
|
return crc;
|
|
176
723
|
}
|
|
177
724
|
|
|
178
|
-
/** Cross-process unique id: `${prefix}-${uuid-v4}`. */
|
|
179
|
-
function genConnectionId(prefix) {
|
|
180
|
-
return `${prefix}-${node_crypto.randomUUID()}`;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
725
|
/**
|
|
184
726
|
* Convert a number of bits to milliseconds at a given baud rate.
|
|
185
727
|
*
|
|
@@ -305,15 +847,64 @@ function predictFc43_14Response(buffer) {
|
|
|
305
847
|
return { kind: 'length', length: offset + 2 };
|
|
306
848
|
}
|
|
307
849
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
850
|
+
/**
|
|
851
|
+
* Resolve Modbus RTU timing parameters from user options into milliseconds.
|
|
852
|
+
*
|
|
853
|
+
* - `intervalBetweenFrames` (t3.5): if omitted and `baudRate` is present,
|
|
854
|
+
* defaults to 38.5 bits per Modbus V1.02 §2.5.1.1.
|
|
855
|
+
* - `interCharTimeout` (t1.5): opt-in; only resolved when explicitly provided.
|
|
856
|
+
*
|
|
857
|
+
* Per the spec, at baud rates > 19200 fixed values are used
|
|
858
|
+
* (1.75 ms for t3.5, 0.75 ms for t1.5) regardless of the bit value.
|
|
859
|
+
*/
|
|
860
|
+
function resolveRtuTiming(opts = {}, baudRate) {
|
|
861
|
+
var _a, _b, _c;
|
|
862
|
+
let intervalBetweenFrames = 0;
|
|
863
|
+
let interCharTimeout = 0;
|
|
864
|
+
if (((_a = opts.intervalBetweenFrames) === null || _a === void 0 ? void 0 : _a.unit) === 'ms') {
|
|
865
|
+
intervalBetweenFrames = opts.intervalBetweenFrames.value;
|
|
311
866
|
}
|
|
312
|
-
|
|
313
|
-
|
|
867
|
+
else if (baudRate !== undefined) {
|
|
868
|
+
const bitValue = (_c = (_b = opts.intervalBetweenFrames) === null || _b === void 0 ? void 0 : _b.value) !== null && _c !== void 0 ? _c : 38.5;
|
|
869
|
+
intervalBetweenFrames = baudRate > 19200 ? 1.75 : Math.ceil(bitsToMs(baudRate, bitValue));
|
|
314
870
|
}
|
|
315
|
-
|
|
316
|
-
|
|
871
|
+
if (opts.interCharTimeout) {
|
|
872
|
+
if (opts.interCharTimeout.unit === 'ms') {
|
|
873
|
+
interCharTimeout = opts.interCharTimeout.value;
|
|
874
|
+
}
|
|
875
|
+
else if (baudRate !== undefined) {
|
|
876
|
+
interCharTimeout = baudRate > 19200 ? 0.75 : Math.ceil(bitsToMs(baudRate, opts.interCharTimeout.value));
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
return { intervalBetweenFrames, interCharTimeout };
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
/**
|
|
883
|
+
* Normalize an IP address by stripping the IPv4-mapped IPv6 prefix.
|
|
884
|
+
* This ensures consistent comparison of addresses like `::ffff:192.168.1.1`.
|
|
885
|
+
*/
|
|
886
|
+
function normalizeAddress(address = '') {
|
|
887
|
+
return address.replace(/^::ffff:/, '');
|
|
888
|
+
}
|
|
889
|
+
/**
|
|
890
|
+
* Check whether a remote address is allowed by the given whitelist.
|
|
891
|
+
* IPv4-mapped IPv6 addresses are normalized before comparison.
|
|
892
|
+
* Returns `true` when whitelist is absent or empty.
|
|
893
|
+
*/
|
|
894
|
+
function isWhitelisted(address, whitelist) {
|
|
895
|
+
if (!whitelist || whitelist.length === 0) {
|
|
896
|
+
return true;
|
|
897
|
+
}
|
|
898
|
+
const normalized = normalizeAddress(address);
|
|
899
|
+
return whitelist.includes(normalized);
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
class TcpServerPhysicalLayer extends AbstractPhysicalLayer {
|
|
903
|
+
get state() {
|
|
904
|
+
return this._state;
|
|
905
|
+
}
|
|
906
|
+
get server() {
|
|
907
|
+
return this._server;
|
|
317
908
|
}
|
|
318
909
|
constructor(options) {
|
|
319
910
|
super();
|
|
@@ -321,327 +912,502 @@ class SerialPhysicalLayer extends AbstractPhysicalLayer {
|
|
|
321
912
|
enumerable: true,
|
|
322
913
|
configurable: true,
|
|
323
914
|
writable: true,
|
|
324
|
-
value: '
|
|
915
|
+
value: 'TCP_SERVER'
|
|
325
916
|
});
|
|
326
|
-
Object.defineProperty(this, "
|
|
917
|
+
Object.defineProperty(this, "_state", {
|
|
918
|
+
enumerable: true,
|
|
919
|
+
configurable: true,
|
|
920
|
+
writable: true,
|
|
921
|
+
value: exports.PhysicalState.CLOSED
|
|
922
|
+
});
|
|
923
|
+
Object.defineProperty(this, "_connections", {
|
|
924
|
+
enumerable: true,
|
|
925
|
+
configurable: true,
|
|
926
|
+
writable: true,
|
|
927
|
+
value: new Set()
|
|
928
|
+
});
|
|
929
|
+
Object.defineProperty(this, "_server", {
|
|
930
|
+
enumerable: true,
|
|
931
|
+
configurable: true,
|
|
932
|
+
writable: true,
|
|
933
|
+
value: null
|
|
934
|
+
});
|
|
935
|
+
Object.defineProperty(this, "_opts", {
|
|
327
936
|
enumerable: true,
|
|
328
937
|
configurable: true,
|
|
329
938
|
writable: true,
|
|
330
939
|
value: void 0
|
|
331
940
|
});
|
|
332
|
-
Object.defineProperty(this, "
|
|
941
|
+
Object.defineProperty(this, "_openPromise", {
|
|
333
942
|
enumerable: true,
|
|
334
943
|
configurable: true,
|
|
335
944
|
writable: true,
|
|
336
945
|
value: null
|
|
337
946
|
});
|
|
338
|
-
Object.defineProperty(this, "
|
|
947
|
+
Object.defineProperty(this, "_closePromise", {
|
|
339
948
|
enumerable: true,
|
|
340
949
|
configurable: true,
|
|
341
950
|
writable: true,
|
|
342
|
-
value:
|
|
951
|
+
value: null
|
|
343
952
|
});
|
|
344
|
-
Object.defineProperty(this, "
|
|
953
|
+
Object.defineProperty(this, "_resolveClose", {
|
|
345
954
|
enumerable: true,
|
|
346
955
|
configurable: true,
|
|
347
956
|
writable: true,
|
|
348
|
-
value:
|
|
957
|
+
value: null
|
|
349
958
|
});
|
|
350
|
-
Object.defineProperty(this, "
|
|
959
|
+
Object.defineProperty(this, "_cleanupFns", {
|
|
351
960
|
enumerable: true,
|
|
352
961
|
configurable: true,
|
|
353
962
|
writable: true,
|
|
354
|
-
value:
|
|
963
|
+
value: new Set()
|
|
355
964
|
});
|
|
356
|
-
this.
|
|
357
|
-
this._baudRate = options.baudRate;
|
|
965
|
+
this._opts = options !== null && options !== void 0 ? options : {};
|
|
358
966
|
}
|
|
359
|
-
open() {
|
|
360
|
-
if (this.
|
|
361
|
-
return Promise.
|
|
967
|
+
open(options) {
|
|
968
|
+
if (this.state === exports.PhysicalState.OPEN) {
|
|
969
|
+
return Promise.resolve();
|
|
362
970
|
}
|
|
363
|
-
if (this.
|
|
364
|
-
return
|
|
971
|
+
if (this.state === exports.PhysicalState.OPENING) {
|
|
972
|
+
return this._openPromise;
|
|
365
973
|
}
|
|
366
|
-
this.
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
}
|
|
376
|
-
reject(error);
|
|
974
|
+
if (this.state === exports.PhysicalState.CLOSING) {
|
|
975
|
+
return Promise.reject(new Error('Port is closing'));
|
|
976
|
+
}
|
|
977
|
+
this._state = exports.PhysicalState.OPENING;
|
|
978
|
+
this._openPromise = new Promise((resolve, reject) => {
|
|
979
|
+
var _a;
|
|
980
|
+
const server = node_net.createServer(this._opts.serverOpts, (socket) => {
|
|
981
|
+
if (!isWhitelisted(socket.remoteAddress, this._opts.whitelist)) {
|
|
982
|
+
socket.destroy();
|
|
377
983
|
return;
|
|
378
984
|
}
|
|
379
|
-
|
|
380
|
-
this.
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
985
|
+
let cleanupTimeout = null;
|
|
986
|
+
if (this._opts.idleTimeout) {
|
|
987
|
+
socket.setTimeout(this._opts.idleTimeout);
|
|
988
|
+
cleanupTimeout = () => socket.off('timeout', onTimeout);
|
|
989
|
+
const onTimeout = () => {
|
|
990
|
+
this._cleanupFns.delete(cleanupTimeout);
|
|
991
|
+
socket.destroy();
|
|
992
|
+
};
|
|
993
|
+
socket.once('timeout', onTimeout);
|
|
994
|
+
this._cleanupFns.add(cleanupTimeout);
|
|
995
|
+
}
|
|
996
|
+
const connection = new TcpPhysicalConnection(this, socket);
|
|
997
|
+
this._connections.add(connection);
|
|
998
|
+
const cleanupConnectionClose = () => connection.off('close', onConnectionClose);
|
|
999
|
+
const onConnectionClose = () => {
|
|
1000
|
+
if (cleanupTimeout) {
|
|
1001
|
+
cleanupTimeout();
|
|
1002
|
+
this._cleanupFns.delete(cleanupTimeout);
|
|
392
1003
|
}
|
|
393
|
-
|
|
1004
|
+
this._cleanupFns.delete(cleanupConnectionClose);
|
|
1005
|
+
this._connections.delete(connection);
|
|
1006
|
+
};
|
|
1007
|
+
connection.once('close', onConnectionClose);
|
|
1008
|
+
this._cleanupFns.add(cleanupConnectionClose);
|
|
1009
|
+
this.emit('connect', connection);
|
|
1010
|
+
});
|
|
1011
|
+
this._server = server;
|
|
1012
|
+
if (this._opts.maxConnections !== undefined) {
|
|
1013
|
+
server.maxConnections = this._opts.maxConnections;
|
|
1014
|
+
}
|
|
1015
|
+
const onConnect = () => {
|
|
1016
|
+
server.off('error', onError);
|
|
1017
|
+
this._state = exports.PhysicalState.OPEN;
|
|
1018
|
+
this._openPromise = null;
|
|
1019
|
+
this.emit('open');
|
|
394
1020
|
resolve();
|
|
1021
|
+
{
|
|
1022
|
+
const onError = (err) => {
|
|
1023
|
+
this.emit('error', err);
|
|
1024
|
+
};
|
|
1025
|
+
server.on('error', onError);
|
|
1026
|
+
this._cleanupFns.add(() => server.off('error', onError));
|
|
1027
|
+
const cleanupClose = () => server.off('close', onClose);
|
|
1028
|
+
const onClose = () => {
|
|
1029
|
+
var _a;
|
|
1030
|
+
this._cleanupFns.delete(cleanupClose);
|
|
1031
|
+
this._state = exports.PhysicalState.CLOSED;
|
|
1032
|
+
this._closePromise = null;
|
|
1033
|
+
for (const fn of this._cleanupFns) {
|
|
1034
|
+
fn();
|
|
1035
|
+
}
|
|
1036
|
+
this._cleanupFns.clear();
|
|
1037
|
+
this._server = null;
|
|
1038
|
+
this.emit('close');
|
|
1039
|
+
(_a = this._resolveClose) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
1040
|
+
this._resolveClose = null;
|
|
1041
|
+
};
|
|
1042
|
+
server.once('close', onClose);
|
|
1043
|
+
this._cleanupFns.add(cleanupClose);
|
|
1044
|
+
}
|
|
1045
|
+
};
|
|
1046
|
+
const onError = (err) => {
|
|
1047
|
+
server.off('listening', onConnect);
|
|
1048
|
+
this._state = exports.PhysicalState.CLOSED;
|
|
1049
|
+
this._openPromise = null;
|
|
1050
|
+
this._server = null;
|
|
1051
|
+
reject(err);
|
|
1052
|
+
};
|
|
1053
|
+
server.once('listening', onConnect);
|
|
1054
|
+
server.once('error', onError);
|
|
1055
|
+
server.listen(Object.assign(Object.assign({}, options), { port: (_a = options === null || options === void 0 ? void 0 : options.port) !== null && _a !== void 0 ? _a : 502 }));
|
|
1056
|
+
});
|
|
1057
|
+
return this._openPromise;
|
|
1058
|
+
}
|
|
1059
|
+
close() {
|
|
1060
|
+
if (this.state === exports.PhysicalState.CLOSED) {
|
|
1061
|
+
return Promise.resolve();
|
|
1062
|
+
}
|
|
1063
|
+
if (this.state === exports.PhysicalState.CLOSING) {
|
|
1064
|
+
return this._closePromise;
|
|
1065
|
+
}
|
|
1066
|
+
if (this.state === exports.PhysicalState.OPENING) {
|
|
1067
|
+
return Promise.reject(new Error('Port is opening'));
|
|
1068
|
+
}
|
|
1069
|
+
this._state = exports.PhysicalState.CLOSING;
|
|
1070
|
+
this._closePromise = new Promise((resolve, reject) => {
|
|
1071
|
+
this._resolveClose = resolve;
|
|
1072
|
+
this.server.close();
|
|
1073
|
+
Promise.all(Array.from(this._connections).map((connection) => connection.destroy())).catch((err) => {
|
|
1074
|
+
this._resolveClose = null;
|
|
1075
|
+
reject(err);
|
|
395
1076
|
});
|
|
396
1077
|
});
|
|
1078
|
+
return this._closePromise;
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
class UdpClientPhysicalConnection extends AbstractPhysicalConnection {
|
|
1083
|
+
get state() {
|
|
1084
|
+
return this._state;
|
|
1085
|
+
}
|
|
1086
|
+
get physicalLayer() {
|
|
1087
|
+
return this._physicalLayer;
|
|
1088
|
+
}
|
|
1089
|
+
constructor(physicalLayer, socket) {
|
|
1090
|
+
super();
|
|
1091
|
+
Object.defineProperty(this, "_state", {
|
|
1092
|
+
enumerable: true,
|
|
1093
|
+
configurable: true,
|
|
1094
|
+
writable: true,
|
|
1095
|
+
value: exports.PhysicalConnectionState.CONNECTED
|
|
1096
|
+
});
|
|
1097
|
+
Object.defineProperty(this, "_physicalLayer", {
|
|
1098
|
+
enumerable: true,
|
|
1099
|
+
configurable: true,
|
|
1100
|
+
writable: true,
|
|
1101
|
+
value: void 0
|
|
1102
|
+
});
|
|
1103
|
+
Object.defineProperty(this, "_socket", {
|
|
1104
|
+
enumerable: true,
|
|
1105
|
+
configurable: true,
|
|
1106
|
+
writable: true,
|
|
1107
|
+
value: void 0
|
|
1108
|
+
});
|
|
1109
|
+
Object.defineProperty(this, "_destroyPromise", {
|
|
1110
|
+
enumerable: true,
|
|
1111
|
+
configurable: true,
|
|
1112
|
+
writable: true,
|
|
1113
|
+
value: null
|
|
1114
|
+
});
|
|
1115
|
+
Object.defineProperty(this, "_resolveDestroy", {
|
|
1116
|
+
enumerable: true,
|
|
1117
|
+
configurable: true,
|
|
1118
|
+
writable: true,
|
|
1119
|
+
value: null
|
|
1120
|
+
});
|
|
1121
|
+
Object.defineProperty(this, "_cleanupFns", {
|
|
1122
|
+
enumerable: true,
|
|
1123
|
+
configurable: true,
|
|
1124
|
+
writable: true,
|
|
1125
|
+
value: new Set()
|
|
1126
|
+
});
|
|
1127
|
+
this._physicalLayer = physicalLayer;
|
|
1128
|
+
this._socket = socket;
|
|
1129
|
+
const onMessage = (msg) => {
|
|
1130
|
+
if (this.state !== exports.PhysicalConnectionState.CONNECTED) {
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
1133
|
+
this.emit('data', msg);
|
|
1134
|
+
};
|
|
1135
|
+
socket.on('message', onMessage);
|
|
1136
|
+
this._cleanupFns.add(() => socket.off('message', onMessage));
|
|
1137
|
+
const onSocketError = (err) => {
|
|
1138
|
+
this.physicalLayer.emit('error', err);
|
|
1139
|
+
};
|
|
1140
|
+
socket.on('error', onSocketError);
|
|
1141
|
+
this._cleanupFns.add(() => socket.off('error', onSocketError));
|
|
1142
|
+
const cleanupClose = () => socket.off('close', onClose);
|
|
1143
|
+
const onClose = () => {
|
|
1144
|
+
var _a;
|
|
1145
|
+
this._cleanupFns.delete(cleanupClose);
|
|
1146
|
+
this._state = exports.PhysicalConnectionState.DESTROYED;
|
|
1147
|
+
this._destroyPromise = null;
|
|
1148
|
+
for (const fn of this._cleanupFns) {
|
|
1149
|
+
fn();
|
|
1150
|
+
}
|
|
1151
|
+
this._cleanupFns.clear();
|
|
1152
|
+
this.emit('close');
|
|
1153
|
+
(_a = this._resolveDestroy) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
1154
|
+
this._resolveDestroy = null;
|
|
1155
|
+
};
|
|
1156
|
+
socket.once('close', onClose);
|
|
1157
|
+
this._cleanupFns.add(cleanupClose);
|
|
397
1158
|
}
|
|
398
1159
|
write(data) {
|
|
399
1160
|
return new Promise((resolve, reject) => {
|
|
400
|
-
if (this.
|
|
401
|
-
this.
|
|
1161
|
+
if (this.state === exports.PhysicalConnectionState.CONNECTED) {
|
|
1162
|
+
this._socket.send(data, (error) => {
|
|
402
1163
|
if (error) {
|
|
403
1164
|
reject(error);
|
|
404
1165
|
}
|
|
405
1166
|
else {
|
|
406
|
-
this.emit('write', data);
|
|
407
1167
|
resolve();
|
|
408
1168
|
}
|
|
409
1169
|
});
|
|
410
1170
|
}
|
|
411
1171
|
else {
|
|
412
|
-
reject(new
|
|
1172
|
+
reject(new Error('Connection is not connected'));
|
|
413
1173
|
}
|
|
414
1174
|
});
|
|
415
1175
|
}
|
|
416
|
-
close() {
|
|
417
|
-
if (!this._serialport.isOpen) {
|
|
418
|
-
return Promise.resolve();
|
|
419
|
-
}
|
|
420
|
-
return new Promise((resolve) => {
|
|
421
|
-
this._serialport.once('close', () => resolve());
|
|
422
|
-
this._serialport.close();
|
|
423
|
-
});
|
|
424
|
-
}
|
|
425
1176
|
destroy() {
|
|
426
|
-
if (this.
|
|
1177
|
+
if (this.state === exports.PhysicalConnectionState.DESTROYED) {
|
|
427
1178
|
return Promise.resolve();
|
|
428
1179
|
}
|
|
429
|
-
this.
|
|
430
|
-
|
|
431
|
-
|
|
1180
|
+
if (this.state === exports.PhysicalConnectionState.DESTROYING) {
|
|
1181
|
+
return this._destroyPromise;
|
|
1182
|
+
}
|
|
1183
|
+
this._state = exports.PhysicalConnectionState.DESTROYING;
|
|
1184
|
+
this._destroyPromise = new Promise((resolve) => {
|
|
1185
|
+
this._resolveDestroy = resolve;
|
|
1186
|
+
this._socket.close();
|
|
432
1187
|
});
|
|
1188
|
+
return this._destroyPromise;
|
|
433
1189
|
}
|
|
434
1190
|
}
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
return this._isOpen;
|
|
1191
|
+
class UdpClientPhysicalLayer extends AbstractPhysicalLayer {
|
|
1192
|
+
get state() {
|
|
1193
|
+
return this._state;
|
|
439
1194
|
}
|
|
440
|
-
get
|
|
441
|
-
return this.
|
|
1195
|
+
get socket() {
|
|
1196
|
+
return this._socket;
|
|
442
1197
|
}
|
|
443
1198
|
constructor(options) {
|
|
1199
|
+
var _a;
|
|
444
1200
|
super();
|
|
445
1201
|
Object.defineProperty(this, "TYPE", {
|
|
446
1202
|
enumerable: true,
|
|
447
1203
|
configurable: true,
|
|
448
1204
|
writable: true,
|
|
449
|
-
value: '
|
|
1205
|
+
value: 'UDP_CLIENT'
|
|
450
1206
|
});
|
|
451
|
-
Object.defineProperty(this, "
|
|
1207
|
+
Object.defineProperty(this, "_state", {
|
|
452
1208
|
enumerable: true,
|
|
453
1209
|
configurable: true,
|
|
454
1210
|
writable: true,
|
|
455
|
-
value:
|
|
1211
|
+
value: exports.PhysicalState.CLOSED
|
|
456
1212
|
});
|
|
457
|
-
Object.defineProperty(this, "
|
|
1213
|
+
Object.defineProperty(this, "_connections", {
|
|
1214
|
+
enumerable: true,
|
|
1215
|
+
configurable: true,
|
|
1216
|
+
writable: true,
|
|
1217
|
+
value: new Set()
|
|
1218
|
+
});
|
|
1219
|
+
Object.defineProperty(this, "_socket", {
|
|
458
1220
|
enumerable: true,
|
|
459
1221
|
configurable: true,
|
|
460
1222
|
writable: true,
|
|
461
1223
|
value: null
|
|
462
1224
|
});
|
|
463
|
-
Object.defineProperty(this, "
|
|
1225
|
+
Object.defineProperty(this, "_socketOpts", {
|
|
464
1226
|
enumerable: true,
|
|
465
1227
|
configurable: true,
|
|
466
1228
|
writable: true,
|
|
467
|
-
value:
|
|
1229
|
+
value: void 0
|
|
468
1230
|
});
|
|
469
|
-
Object.defineProperty(this, "
|
|
1231
|
+
Object.defineProperty(this, "_openPromise", {
|
|
470
1232
|
enumerable: true,
|
|
471
1233
|
configurable: true,
|
|
472
1234
|
writable: true,
|
|
473
|
-
value:
|
|
1235
|
+
value: null
|
|
474
1236
|
});
|
|
475
|
-
Object.defineProperty(this, "
|
|
1237
|
+
Object.defineProperty(this, "_closePromise", {
|
|
476
1238
|
enumerable: true,
|
|
477
1239
|
configurable: true,
|
|
478
1240
|
writable: true,
|
|
479
|
-
value:
|
|
1241
|
+
value: null
|
|
480
1242
|
});
|
|
481
|
-
Object.defineProperty(this, "
|
|
1243
|
+
Object.defineProperty(this, "_resolveClose", {
|
|
482
1244
|
enumerable: true,
|
|
483
1245
|
configurable: true,
|
|
484
1246
|
writable: true,
|
|
485
|
-
value:
|
|
1247
|
+
value: null
|
|
486
1248
|
});
|
|
487
|
-
this
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
}
|
|
493
|
-
if (this._isOpen || this._isOpening) {
|
|
494
|
-
return Promise.reject(new ModbusError(ModbusErrorCode.PORT_ALREADY_OPEN, 'Port is already open'));
|
|
495
|
-
}
|
|
496
|
-
this._isOpening = true;
|
|
497
|
-
const socket = new node_net.Socket(this._socketOptions);
|
|
498
|
-
const connection = { id: genConnectionId('tcp-client') };
|
|
499
|
-
this._socket = socket;
|
|
500
|
-
this._connection = connection;
|
|
501
|
-
return new Promise((resolve, reject) => {
|
|
502
|
-
let connected = false;
|
|
503
|
-
socket.on('data', (data) => {
|
|
504
|
-
this.emit('data', data, (d) => this.write(d), connection);
|
|
505
|
-
});
|
|
506
|
-
socket.on('error', (err) => {
|
|
507
|
-
if (connected) {
|
|
508
|
-
this.emit('error', err);
|
|
509
|
-
}
|
|
510
|
-
else {
|
|
511
|
-
if (this._connection === connection) {
|
|
512
|
-
this._isOpening = false;
|
|
513
|
-
this._socket = null;
|
|
514
|
-
this._connection = null;
|
|
515
|
-
}
|
|
516
|
-
socket.removeAllListeners();
|
|
517
|
-
reject(err);
|
|
518
|
-
}
|
|
519
|
-
});
|
|
520
|
-
socket.once('close', () => {
|
|
521
|
-
if (this._connection === connection) {
|
|
522
|
-
this._isOpen = false;
|
|
523
|
-
this._isOpening = false;
|
|
524
|
-
this._socket = null;
|
|
525
|
-
this._connection = null;
|
|
526
|
-
socket.removeAllListeners();
|
|
527
|
-
this.emit('connection-close', connection);
|
|
528
|
-
this.emit('close');
|
|
529
|
-
}
|
|
530
|
-
});
|
|
531
|
-
socket.connect(options !== null && options !== void 0 ? options : { port: 502 }, () => {
|
|
532
|
-
connected = true;
|
|
533
|
-
this._isOpening = false;
|
|
534
|
-
this._isOpen = true;
|
|
535
|
-
resolve();
|
|
536
|
-
});
|
|
1249
|
+
Object.defineProperty(this, "_cleanupFns", {
|
|
1250
|
+
enumerable: true,
|
|
1251
|
+
configurable: true,
|
|
1252
|
+
writable: true,
|
|
1253
|
+
value: new Set()
|
|
537
1254
|
});
|
|
1255
|
+
this._socketOpts = Object.assign(Object.assign({}, options), { type: (_a = options === null || options === void 0 ? void 0 : options.type) !== null && _a !== void 0 ? _a : 'udp4' });
|
|
538
1256
|
}
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
1257
|
+
open(remote) {
|
|
1258
|
+
if (this.state === exports.PhysicalState.OPEN) {
|
|
1259
|
+
return Promise.resolve();
|
|
1260
|
+
}
|
|
1261
|
+
if (this.state === exports.PhysicalState.OPENING) {
|
|
1262
|
+
return this._openPromise;
|
|
1263
|
+
}
|
|
1264
|
+
if (this.state === exports.PhysicalState.CLOSING) {
|
|
1265
|
+
return Promise.reject(new Error('Port is closing'));
|
|
1266
|
+
}
|
|
1267
|
+
this._state = exports.PhysicalState.OPENING;
|
|
1268
|
+
this._openPromise = new Promise((resolve, reject) => {
|
|
1269
|
+
const socket = node_dgram.createSocket(this._socketOpts);
|
|
1270
|
+
this._socket = socket;
|
|
1271
|
+
const onListening = () => {
|
|
1272
|
+
var _a;
|
|
1273
|
+
socket.off('error', onError);
|
|
1274
|
+
socket.connect((_a = remote === null || remote === void 0 ? void 0 : remote.port) !== null && _a !== void 0 ? _a : 502, remote === null || remote === void 0 ? void 0 : remote.address);
|
|
1275
|
+
this._state = exports.PhysicalState.OPEN;
|
|
1276
|
+
this._openPromise = null;
|
|
1277
|
+
this.emit('open');
|
|
1278
|
+
resolve();
|
|
1279
|
+
const connection = new UdpClientPhysicalConnection(this, socket);
|
|
1280
|
+
this._connections.add(connection);
|
|
1281
|
+
const cleanupConnectionClose = () => connection.off('close', onConnectionClose);
|
|
1282
|
+
const onConnectionClose = () => {
|
|
1283
|
+
var _a;
|
|
1284
|
+
this._cleanupFns.delete(cleanupConnectionClose);
|
|
1285
|
+
this._connections.delete(connection);
|
|
1286
|
+
this._state = exports.PhysicalState.CLOSED;
|
|
1287
|
+
this._closePromise = null;
|
|
1288
|
+
for (const fn of this._cleanupFns) {
|
|
1289
|
+
fn();
|
|
549
1290
|
}
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
1291
|
+
this._cleanupFns.clear();
|
|
1292
|
+
this._socket = null;
|
|
1293
|
+
this.emit('close');
|
|
1294
|
+
(_a = this._resolveClose) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
1295
|
+
this._resolveClose = null;
|
|
1296
|
+
};
|
|
1297
|
+
connection.once('close', onConnectionClose);
|
|
1298
|
+
this._cleanupFns.add(cleanupConnectionClose);
|
|
1299
|
+
this.emit('connect', connection);
|
|
1300
|
+
};
|
|
1301
|
+
const onError = (err) => {
|
|
1302
|
+
socket.off('listening', onListening);
|
|
1303
|
+
this._state = exports.PhysicalState.CLOSED;
|
|
1304
|
+
this._openPromise = null;
|
|
1305
|
+
this._socket = null;
|
|
1306
|
+
reject(err);
|
|
1307
|
+
};
|
|
1308
|
+
socket.once('listening', onListening);
|
|
1309
|
+
socket.once('error', onError);
|
|
1310
|
+
socket.bind();
|
|
555
1311
|
});
|
|
1312
|
+
return this._openPromise;
|
|
556
1313
|
}
|
|
557
1314
|
close() {
|
|
558
|
-
if (
|
|
1315
|
+
if (this.state === exports.PhysicalState.CLOSED) {
|
|
559
1316
|
return Promise.resolve();
|
|
560
1317
|
}
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
socket.once('close', () => resolve());
|
|
564
|
-
socket.destroy();
|
|
565
|
-
});
|
|
566
|
-
}
|
|
567
|
-
destroy() {
|
|
568
|
-
if (this._destroyed) {
|
|
569
|
-
return Promise.resolve();
|
|
1318
|
+
if (this.state === exports.PhysicalState.CLOSING) {
|
|
1319
|
+
return this._closePromise;
|
|
570
1320
|
}
|
|
571
|
-
this.
|
|
572
|
-
|
|
573
|
-
|
|
1321
|
+
if (this.state === exports.PhysicalState.OPENING) {
|
|
1322
|
+
return Promise.reject(new Error('Port is opening'));
|
|
1323
|
+
}
|
|
1324
|
+
this._state = exports.PhysicalState.CLOSING;
|
|
1325
|
+
this._closePromise = new Promise((resolve, reject) => {
|
|
1326
|
+
this._resolveClose = resolve;
|
|
1327
|
+
Promise.all(Array.from(this._connections).map((connection) => connection.destroy())).catch((err) => {
|
|
1328
|
+
this._resolveClose = null;
|
|
1329
|
+
reject(err);
|
|
1330
|
+
});
|
|
574
1331
|
});
|
|
1332
|
+
return this._closePromise;
|
|
575
1333
|
}
|
|
576
1334
|
}
|
|
577
1335
|
|
|
578
|
-
class
|
|
579
|
-
get
|
|
580
|
-
return this.
|
|
1336
|
+
class UdpServerPhysicalConnection extends AbstractPhysicalConnection {
|
|
1337
|
+
get state() {
|
|
1338
|
+
return this._state;
|
|
581
1339
|
}
|
|
582
|
-
get
|
|
583
|
-
return this.
|
|
1340
|
+
get physicalLayer() {
|
|
1341
|
+
return this._physicalLayer;
|
|
584
1342
|
}
|
|
585
|
-
constructor(
|
|
1343
|
+
constructor(physicalLayer, socket, remote, idleTimeout, messageEventDelegation) {
|
|
586
1344
|
super();
|
|
587
|
-
Object.defineProperty(this, "
|
|
588
|
-
enumerable: true,
|
|
589
|
-
configurable: true,
|
|
590
|
-
writable: true,
|
|
591
|
-
value: 'NET'
|
|
592
|
-
});
|
|
593
|
-
Object.defineProperty(this, "_server", {
|
|
1345
|
+
Object.defineProperty(this, "_state", {
|
|
594
1346
|
enumerable: true,
|
|
595
1347
|
configurable: true,
|
|
596
1348
|
writable: true,
|
|
597
|
-
value:
|
|
1349
|
+
value: exports.PhysicalConnectionState.CONNECTED
|
|
598
1350
|
});
|
|
599
|
-
Object.defineProperty(this, "
|
|
1351
|
+
Object.defineProperty(this, "_physicalLayer", {
|
|
600
1352
|
enumerable: true,
|
|
601
1353
|
configurable: true,
|
|
602
1354
|
writable: true,
|
|
603
|
-
value:
|
|
1355
|
+
value: void 0
|
|
604
1356
|
});
|
|
605
|
-
Object.defineProperty(this, "
|
|
1357
|
+
Object.defineProperty(this, "_socket", {
|
|
606
1358
|
enumerable: true,
|
|
607
1359
|
configurable: true,
|
|
608
1360
|
writable: true,
|
|
609
|
-
value:
|
|
1361
|
+
value: void 0
|
|
610
1362
|
});
|
|
611
|
-
Object.defineProperty(this, "
|
|
1363
|
+
Object.defineProperty(this, "_remote", {
|
|
612
1364
|
enumerable: true,
|
|
613
1365
|
configurable: true,
|
|
614
1366
|
writable: true,
|
|
615
|
-
value:
|
|
1367
|
+
value: void 0
|
|
616
1368
|
});
|
|
617
|
-
Object.defineProperty(this, "
|
|
1369
|
+
Object.defineProperty(this, "_idleTid", {
|
|
618
1370
|
enumerable: true,
|
|
619
1371
|
configurable: true,
|
|
620
1372
|
writable: true,
|
|
621
|
-
value:
|
|
1373
|
+
value: null
|
|
622
1374
|
});
|
|
623
|
-
Object.defineProperty(this, "
|
|
1375
|
+
Object.defineProperty(this, "_cleanupFns", {
|
|
624
1376
|
enumerable: true,
|
|
625
1377
|
configurable: true,
|
|
626
1378
|
writable: true,
|
|
627
|
-
value:
|
|
1379
|
+
value: new Set()
|
|
628
1380
|
});
|
|
629
|
-
this.
|
|
1381
|
+
this._physicalLayer = physicalLayer;
|
|
1382
|
+
this._socket = socket;
|
|
1383
|
+
this._remote = remote;
|
|
1384
|
+
const onMessage = (msg) => {
|
|
1385
|
+
if (this.state !== exports.PhysicalConnectionState.CONNECTED) {
|
|
1386
|
+
return;
|
|
1387
|
+
}
|
|
1388
|
+
if (this._idleTid !== null) {
|
|
1389
|
+
clearTimeout(this._idleTid);
|
|
1390
|
+
this._idleTid = null;
|
|
1391
|
+
if (idleTimeout > 0) {
|
|
1392
|
+
this._idleTid = setTimeout(() => {
|
|
1393
|
+
this.destroy();
|
|
1394
|
+
}, idleTimeout);
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
this.emit('data', msg);
|
|
1398
|
+
};
|
|
1399
|
+
messageEventDelegation.add(onMessage);
|
|
1400
|
+
this._cleanupFns.add(() => messageEventDelegation.remove(onMessage));
|
|
1401
|
+
if (idleTimeout > 0) {
|
|
1402
|
+
this._idleTid = setTimeout(() => {
|
|
1403
|
+
this.destroy();
|
|
1404
|
+
}, idleTimeout);
|
|
1405
|
+
}
|
|
630
1406
|
}
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
if (this._isOpen || this._isOpening) {
|
|
636
|
-
return Promise.reject(new ModbusError(ModbusErrorCode.PORT_ALREADY_OPEN, 'Port is already open'));
|
|
637
|
-
}
|
|
638
|
-
this._isOpening = true;
|
|
639
|
-
this._connections.clear();
|
|
640
|
-
const server = node_net.createServer(this._serverOptions, (socket) => {
|
|
641
|
-
const conn = { id: genConnectionId('tcp-server') };
|
|
642
|
-
this._connections.set(socket, conn);
|
|
643
|
-
const response = (data) => new Promise((resolve, reject) => {
|
|
644
|
-
socket.write(data, (error) => {
|
|
1407
|
+
write(data) {
|
|
1408
|
+
return new Promise((resolve, reject) => {
|
|
1409
|
+
if (this.state === exports.PhysicalConnectionState.CONNECTED) {
|
|
1410
|
+
this._socket.send(data, this._remote.port, this._remote.address, (error) => {
|
|
645
1411
|
if (error) {
|
|
646
1412
|
reject(error);
|
|
647
1413
|
}
|
|
@@ -649,114 +1415,46 @@ class TcpServerPhysicalLayer extends AbstractPhysicalLayer {
|
|
|
649
1415
|
resolve();
|
|
650
1416
|
}
|
|
651
1417
|
});
|
|
652
|
-
});
|
|
653
|
-
socket.on('data', (data) => {
|
|
654
|
-
this.emit('data', data, response, conn);
|
|
655
|
-
});
|
|
656
|
-
socket.on('error', (err) => {
|
|
657
|
-
this.emit('error', err);
|
|
658
|
-
});
|
|
659
|
-
socket.once('close', () => {
|
|
660
|
-
if (this._connections.delete(socket)) {
|
|
661
|
-
this.emit('connection-close', conn);
|
|
662
|
-
}
|
|
663
|
-
socket.removeAllListeners();
|
|
664
|
-
});
|
|
665
|
-
});
|
|
666
|
-
this._server = server;
|
|
667
|
-
return new Promise((resolve, reject) => {
|
|
668
|
-
var _a;
|
|
669
|
-
let listening = false;
|
|
670
|
-
server.once('error', (err) => {
|
|
671
|
-
if (listening) {
|
|
672
|
-
this.emit('error', err);
|
|
673
|
-
}
|
|
674
|
-
else {
|
|
675
|
-
if (this._server === server) {
|
|
676
|
-
this._isOpening = false;
|
|
677
|
-
this._server = null;
|
|
678
|
-
}
|
|
679
|
-
server.removeAllListeners();
|
|
680
|
-
reject(err);
|
|
681
|
-
}
|
|
682
|
-
});
|
|
683
|
-
server.once('close', () => {
|
|
684
|
-
if (this._server === server) {
|
|
685
|
-
this._isOpen = false;
|
|
686
|
-
this._isOpening = false;
|
|
687
|
-
this._server = null;
|
|
688
|
-
const conns = Array.from(this._connections.values());
|
|
689
|
-
this._connections.clear();
|
|
690
|
-
for (const conn of conns) {
|
|
691
|
-
this.emit('connection-close', conn);
|
|
692
|
-
}
|
|
693
|
-
this.emit('close');
|
|
694
|
-
}
|
|
695
|
-
});
|
|
696
|
-
server.listen(Object.assign(Object.assign({}, options), { port: (_a = options === null || options === void 0 ? void 0 : options.port) !== null && _a !== void 0 ? _a : 502 }), () => {
|
|
697
|
-
listening = true;
|
|
698
|
-
this._isOpening = false;
|
|
699
|
-
this._isOpen = true;
|
|
700
|
-
resolve();
|
|
701
|
-
});
|
|
702
|
-
});
|
|
703
|
-
}
|
|
704
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
705
|
-
write(data) {
|
|
706
|
-
return new Promise((resolve, reject) => {
|
|
707
|
-
reject(new ModbusError(ModbusErrorCode.NOT_SUPPORTED, 'Not supported'));
|
|
708
|
-
});
|
|
709
|
-
}
|
|
710
|
-
close() {
|
|
711
|
-
if (!this._server) {
|
|
712
|
-
return Promise.resolve();
|
|
713
|
-
}
|
|
714
|
-
const server = this._server;
|
|
715
|
-
return new Promise((resolve) => {
|
|
716
|
-
server.once('close', () => resolve());
|
|
717
|
-
for (const [socket] of this._connections) {
|
|
718
|
-
socket.destroy();
|
|
719
1418
|
}
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
}
|
|
723
|
-
catch (_a) {
|
|
724
|
-
resolve();
|
|
1419
|
+
else {
|
|
1420
|
+
reject(new Error('Connection is not connected'));
|
|
725
1421
|
}
|
|
726
1422
|
});
|
|
727
1423
|
}
|
|
728
1424
|
destroy() {
|
|
729
|
-
|
|
730
|
-
|
|
1425
|
+
this._state = exports.PhysicalConnectionState.DESTROYED;
|
|
1426
|
+
for (const fn of this._cleanupFns) {
|
|
1427
|
+
fn();
|
|
731
1428
|
}
|
|
732
|
-
this.
|
|
733
|
-
|
|
734
|
-
this.
|
|
735
|
-
|
|
1429
|
+
this._cleanupFns.clear();
|
|
1430
|
+
if (this._idleTid !== null) {
|
|
1431
|
+
clearTimeout(this._idleTid);
|
|
1432
|
+
this._idleTid = null;
|
|
1433
|
+
}
|
|
1434
|
+
this.emit('close');
|
|
1435
|
+
return Promise.resolve();
|
|
736
1436
|
}
|
|
737
1437
|
}
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
return this._isOpen;
|
|
1438
|
+
class UdpServerPhysicalLayer extends AbstractPhysicalLayer {
|
|
1439
|
+
get state() {
|
|
1440
|
+
return this._state;
|
|
742
1441
|
}
|
|
743
|
-
get
|
|
744
|
-
return this.
|
|
1442
|
+
get socket() {
|
|
1443
|
+
return this._socket;
|
|
745
1444
|
}
|
|
746
1445
|
constructor(options) {
|
|
747
|
-
var _a, _b, _c, _d;
|
|
748
1446
|
super();
|
|
749
1447
|
Object.defineProperty(this, "TYPE", {
|
|
750
1448
|
enumerable: true,
|
|
751
1449
|
configurable: true,
|
|
752
1450
|
writable: true,
|
|
753
|
-
value: '
|
|
1451
|
+
value: 'UDP_SERVER'
|
|
754
1452
|
});
|
|
755
|
-
Object.defineProperty(this, "
|
|
1453
|
+
Object.defineProperty(this, "_state", {
|
|
756
1454
|
enumerable: true,
|
|
757
1455
|
configurable: true,
|
|
758
1456
|
writable: true,
|
|
759
|
-
value:
|
|
1457
|
+
value: exports.PhysicalState.CLOSED
|
|
760
1458
|
});
|
|
761
1459
|
Object.defineProperty(this, "_connections", {
|
|
762
1460
|
enumerable: true,
|
|
@@ -764,233 +1462,197 @@ class UdpPhysicalLayer extends AbstractPhysicalLayer {
|
|
|
764
1462
|
writable: true,
|
|
765
1463
|
value: new Map()
|
|
766
1464
|
});
|
|
767
|
-
Object.defineProperty(this, "
|
|
768
|
-
enumerable: true,
|
|
769
|
-
configurable: true,
|
|
770
|
-
writable: true,
|
|
771
|
-
value: false
|
|
772
|
-
});
|
|
773
|
-
Object.defineProperty(this, "_isOpening", {
|
|
774
|
-
enumerable: true,
|
|
775
|
-
configurable: true,
|
|
776
|
-
writable: true,
|
|
777
|
-
value: false
|
|
778
|
-
});
|
|
779
|
-
Object.defineProperty(this, "_destroyed", {
|
|
1465
|
+
Object.defineProperty(this, "_socket", {
|
|
780
1466
|
enumerable: true,
|
|
781
1467
|
configurable: true,
|
|
782
1468
|
writable: true,
|
|
783
|
-
value:
|
|
1469
|
+
value: null
|
|
784
1470
|
});
|
|
785
|
-
Object.defineProperty(this, "
|
|
1471
|
+
Object.defineProperty(this, "_opts", {
|
|
786
1472
|
enumerable: true,
|
|
787
1473
|
configurable: true,
|
|
788
1474
|
writable: true,
|
|
789
1475
|
value: void 0
|
|
790
1476
|
});
|
|
791
|
-
Object.defineProperty(this, "
|
|
1477
|
+
Object.defineProperty(this, "_openPromise", {
|
|
792
1478
|
enumerable: true,
|
|
793
1479
|
configurable: true,
|
|
794
1480
|
writable: true,
|
|
795
|
-
value:
|
|
1481
|
+
value: null
|
|
796
1482
|
});
|
|
797
|
-
Object.defineProperty(this, "
|
|
1483
|
+
Object.defineProperty(this, "_closePromise", {
|
|
798
1484
|
enumerable: true,
|
|
799
1485
|
configurable: true,
|
|
800
1486
|
writable: true,
|
|
801
|
-
value:
|
|
1487
|
+
value: null
|
|
802
1488
|
});
|
|
803
|
-
Object.defineProperty(this, "
|
|
1489
|
+
Object.defineProperty(this, "_resolveClose", {
|
|
804
1490
|
enumerable: true,
|
|
805
1491
|
configurable: true,
|
|
806
1492
|
writable: true,
|
|
807
|
-
value:
|
|
1493
|
+
value: null
|
|
808
1494
|
});
|
|
809
|
-
Object.defineProperty(this, "
|
|
1495
|
+
Object.defineProperty(this, "_cleanupFns", {
|
|
810
1496
|
enumerable: true,
|
|
811
1497
|
configurable: true,
|
|
812
1498
|
writable: true,
|
|
813
|
-
value:
|
|
1499
|
+
value: new Set()
|
|
814
1500
|
});
|
|
815
|
-
this.
|
|
816
|
-
this.isServer = !(options === null || options === void 0 ? void 0 : options.remote);
|
|
817
|
-
this._port = (_b = (_a = options === null || options === void 0 ? void 0 : options.remote) === null || _a === void 0 ? void 0 : _a.port) !== null && _b !== void 0 ? _b : 502;
|
|
818
|
-
this._address = (_c = options === null || options === void 0 ? void 0 : options.remote) === null || _c === void 0 ? void 0 : _c.address;
|
|
819
|
-
this._idleTimeout = (_d = options === null || options === void 0 ? void 0 : options.idleTimeout) !== null && _d !== void 0 ? _d : 30000;
|
|
1501
|
+
this._opts = options !== null && options !== void 0 ? options : {};
|
|
820
1502
|
}
|
|
1503
|
+
/**
|
|
1504
|
+
* Bind the UDP socket and start accepting datagrams.
|
|
1505
|
+
*
|
|
1506
|
+
* @param options Bind options (port, address, etc.). Defaults to port 502.
|
|
1507
|
+
* @param [idleTimeout=30000] Maximum idle time in milliseconds before an
|
|
1508
|
+
* inactive client connection is evicted. Pass `0` to disable eviction
|
|
1509
|
+
* (connections never time out). Disabling eviction may cause unbounded
|
|
1510
|
+
* memory growth if the server sees many unique clients.
|
|
1511
|
+
*/
|
|
821
1512
|
open(options) {
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
return Promise.reject(new ModbusError(ModbusErrorCode.PORT_DESTROYED, 'Port is destroyed'));
|
|
1513
|
+
if (this.state === exports.PhysicalState.OPEN) {
|
|
1514
|
+
return Promise.resolve();
|
|
825
1515
|
}
|
|
826
|
-
if (this.
|
|
827
|
-
return
|
|
1516
|
+
if (this.state === exports.PhysicalState.OPENING) {
|
|
1517
|
+
return this._openPromise;
|
|
828
1518
|
}
|
|
829
|
-
this.
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
this.
|
|
833
|
-
|
|
834
|
-
var _a;
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
if (
|
|
838
|
-
this.emit('error', err);
|
|
839
|
-
}
|
|
840
|
-
else {
|
|
841
|
-
if (this._socket === socket) {
|
|
842
|
-
this._isOpening = false;
|
|
843
|
-
this._socket = null;
|
|
844
|
-
}
|
|
845
|
-
socket.removeAllListeners();
|
|
846
|
-
reject(err);
|
|
847
|
-
}
|
|
848
|
-
});
|
|
849
|
-
socket.once('close', () => {
|
|
850
|
-
if (this._socket !== socket) {
|
|
1519
|
+
if (this.state === exports.PhysicalState.CLOSING) {
|
|
1520
|
+
return Promise.reject(new Error('Port is closing'));
|
|
1521
|
+
}
|
|
1522
|
+
this._state = exports.PhysicalState.OPENING;
|
|
1523
|
+
this._openPromise = new Promise((resolve, reject) => {
|
|
1524
|
+
var _a, _b, _c;
|
|
1525
|
+
const socket = node_dgram.createSocket(Object.assign(Object.assign({}, this._opts.socketOpts), { type: (_b = (_a = this._opts.socketOpts) === null || _a === void 0 ? void 0 : _a.type) !== null && _b !== void 0 ? _b : 'udp4' }), (msg, rinfo) => {
|
|
1526
|
+
var _a;
|
|
1527
|
+
if (!isWhitelisted(rinfo.address, this._opts.whitelist)) {
|
|
851
1528
|
return;
|
|
852
1529
|
}
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
this._connections.clear();
|
|
859
|
-
for (const entry of entries) {
|
|
860
|
-
if (entry.idleTimer) {
|
|
861
|
-
clearTimeout(entry.idleTimer);
|
|
1530
|
+
const id = `${rinfo.address}:${rinfo.port}`;
|
|
1531
|
+
let connection = this._connections.get(id);
|
|
1532
|
+
if (!connection) {
|
|
1533
|
+
if (this._opts.maxConnections !== undefined && this._connections.size >= this._opts.maxConnections) {
|
|
1534
|
+
return;
|
|
862
1535
|
}
|
|
863
|
-
|
|
1536
|
+
const listeners = new Set();
|
|
1537
|
+
const conn = new UdpServerPhysicalConnection(this, socket, rinfo, (_a = this._opts.idleTimeout) !== null && _a !== void 0 ? _a : 30000, {
|
|
1538
|
+
add: (listener) => {
|
|
1539
|
+
listeners.add(listener);
|
|
1540
|
+
},
|
|
1541
|
+
remove: (listener) => {
|
|
1542
|
+
listeners.delete(listener);
|
|
1543
|
+
},
|
|
1544
|
+
});
|
|
1545
|
+
connection = { connection: conn, listeners };
|
|
1546
|
+
this._connections.set(id, connection);
|
|
1547
|
+
const cleanupConnectionClose = () => conn.off('close', onConnectionClose);
|
|
1548
|
+
const onConnectionClose = () => {
|
|
1549
|
+
this._cleanupFns.delete(cleanupConnectionClose);
|
|
1550
|
+
this._connections.delete(id);
|
|
1551
|
+
};
|
|
1552
|
+
conn.once('close', onConnectionClose);
|
|
1553
|
+
this._cleanupFns.add(cleanupConnectionClose);
|
|
1554
|
+
this.emit('connect', conn);
|
|
1555
|
+
}
|
|
1556
|
+
for (const fn of connection.listeners) {
|
|
1557
|
+
fn(msg, rinfo);
|
|
864
1558
|
}
|
|
865
|
-
this.emit('close');
|
|
866
1559
|
});
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
});
|
|
874
|
-
}
|
|
875
|
-
else {
|
|
876
|
-
started = true;
|
|
877
|
-
this._isOpening = false;
|
|
878
|
-
this._isOpen = true;
|
|
1560
|
+
this._socket = socket;
|
|
1561
|
+
const onListening = () => {
|
|
1562
|
+
socket.off('error', onError);
|
|
1563
|
+
this._state = exports.PhysicalState.OPEN;
|
|
1564
|
+
this._openPromise = null;
|
|
1565
|
+
this.emit('open');
|
|
879
1566
|
resolve();
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
}
|
|
905
|
-
const conn = entry.conn;
|
|
906
|
-
entry.idleTimer = setTimeout(() => {
|
|
907
|
-
const e = this._connections.get(key);
|
|
908
|
-
if (!e || e.conn !== conn) {
|
|
909
|
-
return;
|
|
910
|
-
}
|
|
911
|
-
this._connections.delete(key);
|
|
912
|
-
this.emit('connection-close', conn);
|
|
913
|
-
}, this._idleTimeout);
|
|
914
|
-
}
|
|
915
|
-
const conn = entry.conn;
|
|
916
|
-
const response = (data) => new Promise((resolve, reject) => {
|
|
917
|
-
socket.send(data, rinfo.port, rinfo.address, (error) => {
|
|
918
|
-
if (error) {
|
|
919
|
-
reject(error);
|
|
920
|
-
}
|
|
921
|
-
else {
|
|
922
|
-
resolve();
|
|
1567
|
+
{
|
|
1568
|
+
const onError = (err) => {
|
|
1569
|
+
this.emit('error', err);
|
|
1570
|
+
};
|
|
1571
|
+
socket.on('error', onError);
|
|
1572
|
+
this._cleanupFns.add(() => socket.off('error', onError));
|
|
1573
|
+
const cleanupClose = () => socket.off('close', onClose);
|
|
1574
|
+
const onClose = () => {
|
|
1575
|
+
var _a;
|
|
1576
|
+
this._socket = null;
|
|
1577
|
+
this._cleanupFns.delete(cleanupClose);
|
|
1578
|
+
this._state = exports.PhysicalState.CLOSED;
|
|
1579
|
+
this._closePromise = null;
|
|
1580
|
+
for (const fn of this._cleanupFns) {
|
|
1581
|
+
fn();
|
|
1582
|
+
}
|
|
1583
|
+
this._cleanupFns.clear();
|
|
1584
|
+
this._socket = null;
|
|
1585
|
+
this.emit('close');
|
|
1586
|
+
(_a = this._resolveClose) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
1587
|
+
this._resolveClose = null;
|
|
1588
|
+
};
|
|
1589
|
+
socket.once('close', onClose);
|
|
1590
|
+
this._cleanupFns.add(cleanupClose);
|
|
923
1591
|
}
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
else {
|
|
936
|
-
this.emit('write', data);
|
|
937
|
-
resolve();
|
|
938
|
-
}
|
|
939
|
-
});
|
|
940
|
-
}
|
|
941
|
-
else {
|
|
942
|
-
reject(new ModbusError(ModbusErrorCode.PORT_NOT_OPEN, 'Port is not open'));
|
|
943
|
-
}
|
|
1592
|
+
};
|
|
1593
|
+
const onError = (err) => {
|
|
1594
|
+
socket.off('listening', onListening);
|
|
1595
|
+
this._state = exports.PhysicalState.CLOSED;
|
|
1596
|
+
this._openPromise = null;
|
|
1597
|
+
this._socket = null;
|
|
1598
|
+
reject(err);
|
|
1599
|
+
};
|
|
1600
|
+
socket.once('listening', onListening);
|
|
1601
|
+
socket.once('error', onError);
|
|
1602
|
+
socket.bind(Object.assign(Object.assign({}, options), { port: (_c = options === null || options === void 0 ? void 0 : options.port) !== null && _c !== void 0 ? _c : 502 }));
|
|
944
1603
|
});
|
|
1604
|
+
return this._openPromise;
|
|
945
1605
|
}
|
|
946
1606
|
close() {
|
|
947
|
-
if (
|
|
1607
|
+
if (this.state === exports.PhysicalState.CLOSED) {
|
|
948
1608
|
return Promise.resolve();
|
|
949
1609
|
}
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
1610
|
+
if (this.state === exports.PhysicalState.CLOSING) {
|
|
1611
|
+
return this._closePromise;
|
|
1612
|
+
}
|
|
1613
|
+
if (this.state === exports.PhysicalState.OPENING) {
|
|
1614
|
+
return Promise.reject(new Error('Port is opening'));
|
|
1615
|
+
}
|
|
1616
|
+
this._state = exports.PhysicalState.CLOSING;
|
|
1617
|
+
this._closePromise = new Promise((resolve) => {
|
|
1618
|
+
this._resolveClose = resolve;
|
|
1619
|
+
this.socket.close();
|
|
1620
|
+
// Snapshot connections so destruction (which removes from the Map)
|
|
1621
|
+
// does not interfere with iteration.
|
|
1622
|
+
const connections = Array.from(this._connections.values());
|
|
1623
|
+
for (const { connection } of connections) {
|
|
1624
|
+
connection.destroy();
|
|
958
1625
|
}
|
|
959
1626
|
});
|
|
1627
|
+
return this._closePromise;
|
|
960
1628
|
}
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
function createPhysicalLayer(config) {
|
|
1632
|
+
switch (config.type) {
|
|
1633
|
+
case 'SERIAL':
|
|
1634
|
+
return new SerialPhysicalLayer(config.opts);
|
|
1635
|
+
case 'TCP_CLIENT':
|
|
1636
|
+
return new TcpClientPhysicalLayer(config.socketOpts);
|
|
1637
|
+
case 'TCP_SERVER':
|
|
1638
|
+
return new TcpServerPhysicalLayer(config.opts);
|
|
1639
|
+
case 'UDP_CLIENT':
|
|
1640
|
+
return new UdpClientPhysicalLayer(config.socketOpts);
|
|
1641
|
+
case 'UDP_SERVER':
|
|
1642
|
+
return new UdpServerPhysicalLayer(config.opts);
|
|
1643
|
+
case 'CUSTOM':
|
|
1644
|
+
return config.layer;
|
|
969
1645
|
}
|
|
970
1646
|
}
|
|
971
1647
|
|
|
1648
|
+
/**
|
|
1649
|
+
* Application-layer protocol handler bound to a single physical connection.
|
|
1650
|
+
*
|
|
1651
|
+
* Its lifetime follows the channel: created when the underlying connection is
|
|
1652
|
+
* established and discarded when the connection closes. Subclasses implement
|
|
1653
|
+
* ASCII, RTU, or TCP framing rules.
|
|
1654
|
+
*/
|
|
972
1655
|
class AbstractApplicationLayer extends EventEmitter {
|
|
973
|
-
constructor() {
|
|
974
|
-
super(...arguments);
|
|
975
|
-
Object.defineProperty(this, "_role", {
|
|
976
|
-
enumerable: true,
|
|
977
|
-
configurable: true,
|
|
978
|
-
writable: true,
|
|
979
|
-
value: void 0
|
|
980
|
-
});
|
|
981
|
-
}
|
|
982
|
-
get role() {
|
|
983
|
-
if (!this._role) {
|
|
984
|
-
throw new ModbusError(ModbusErrorCode.INVALID_ROLE, 'Application layer role not set');
|
|
985
|
-
}
|
|
986
|
-
return this._role;
|
|
987
|
-
}
|
|
988
|
-
set role(value) {
|
|
989
|
-
if (this._role && this._role !== value) {
|
|
990
|
-
throw new ModbusError(ModbusErrorCode.INVALID_ROLE, `Application layer role already set to ${this._role}`);
|
|
991
|
-
}
|
|
992
|
-
this._role = value;
|
|
993
|
-
}
|
|
994
1656
|
flush() {
|
|
995
1657
|
// no-op — override in subclasses
|
|
996
1658
|
}
|
|
@@ -1003,7 +1665,10 @@ class AbstractApplicationLayer extends EventEmitter {
|
|
|
1003
1665
|
const MAX_FRAME_LENGTH = 256;
|
|
1004
1666
|
const MIN_FRAME_LENGTH = 4;
|
|
1005
1667
|
class RtuApplicationLayer extends AbstractApplicationLayer {
|
|
1006
|
-
|
|
1668
|
+
get connection() {
|
|
1669
|
+
return this._connection;
|
|
1670
|
+
}
|
|
1671
|
+
constructor(role, connection, options = {}) {
|
|
1007
1672
|
super();
|
|
1008
1673
|
Object.defineProperty(this, "PROTOCOL", {
|
|
1009
1674
|
enumerable: true,
|
|
@@ -1011,23 +1676,29 @@ class RtuApplicationLayer extends AbstractApplicationLayer {
|
|
|
1011
1676
|
writable: true,
|
|
1012
1677
|
value: 'RTU'
|
|
1013
1678
|
});
|
|
1014
|
-
Object.defineProperty(this, "
|
|
1679
|
+
Object.defineProperty(this, "ROLE", {
|
|
1015
1680
|
enumerable: true,
|
|
1016
1681
|
configurable: true,
|
|
1017
1682
|
writable: true,
|
|
1018
|
-
value:
|
|
1683
|
+
value: void 0
|
|
1019
1684
|
});
|
|
1020
|
-
Object.defineProperty(this, "
|
|
1685
|
+
Object.defineProperty(this, "_connection", {
|
|
1021
1686
|
enumerable: true,
|
|
1022
1687
|
configurable: true,
|
|
1023
1688
|
writable: true,
|
|
1024
|
-
value:
|
|
1689
|
+
value: void 0
|
|
1025
1690
|
});
|
|
1026
|
-
Object.defineProperty(this, "
|
|
1691
|
+
Object.defineProperty(this, "_state", {
|
|
1027
1692
|
enumerable: true,
|
|
1028
1693
|
configurable: true,
|
|
1029
1694
|
writable: true,
|
|
1030
|
-
value:
|
|
1695
|
+
value: void 0
|
|
1696
|
+
});
|
|
1697
|
+
Object.defineProperty(this, "_poolSize", {
|
|
1698
|
+
enumerable: true,
|
|
1699
|
+
configurable: true,
|
|
1700
|
+
writable: true,
|
|
1701
|
+
value: void 0
|
|
1031
1702
|
});
|
|
1032
1703
|
Object.defineProperty(this, "_threePointFiveT", {
|
|
1033
1704
|
enumerable: true,
|
|
@@ -1041,39 +1712,29 @@ class RtuApplicationLayer extends AbstractApplicationLayer {
|
|
|
1041
1712
|
writable: true,
|
|
1042
1713
|
value: void 0
|
|
1043
1714
|
});
|
|
1044
|
-
Object.defineProperty(this, "
|
|
1715
|
+
Object.defineProperty(this, "_customFunctionCodes", {
|
|
1045
1716
|
enumerable: true,
|
|
1046
1717
|
configurable: true,
|
|
1047
1718
|
writable: true,
|
|
1048
|
-
value:
|
|
1719
|
+
value: new Map()
|
|
1049
1720
|
});
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
}
|
|
1066
|
-
else {
|
|
1067
|
-
onePointFiveT = baudRate > 19200 ? 0.75 : Math.ceil(bitsToMs(baudRate, interCharTimeout.value));
|
|
1068
|
-
}
|
|
1069
|
-
}
|
|
1070
|
-
}
|
|
1071
|
-
this._threePointFiveT = threePointFiveT;
|
|
1072
|
-
this._onePointFiveT = onePointFiveT;
|
|
1073
|
-
const handleData = (data, response, connection) => {
|
|
1074
|
-
let state = this.getState(connection.id);
|
|
1721
|
+
Object.defineProperty(this, "_cleanupFns", {
|
|
1722
|
+
enumerable: true,
|
|
1723
|
+
configurable: true,
|
|
1724
|
+
writable: true,
|
|
1725
|
+
value: new Set()
|
|
1726
|
+
});
|
|
1727
|
+
this.ROLE = role;
|
|
1728
|
+
this._connection = connection;
|
|
1729
|
+
const { intervalBetweenFrames, interCharTimeout, poolSize } = options;
|
|
1730
|
+
this._poolSize = poolSize !== null && poolSize !== void 0 ? poolSize : MAX_FRAME_LENGTH * 2;
|
|
1731
|
+
this._state = { pool: Buffer.alloc(this._poolSize), start: 0, end: 0 };
|
|
1732
|
+
this._threePointFiveT = intervalBetweenFrames !== null && intervalBetweenFrames !== void 0 ? intervalBetweenFrames : 0;
|
|
1733
|
+
this._onePointFiveT = interCharTimeout !== null && interCharTimeout !== void 0 ? interCharTimeout : 0;
|
|
1734
|
+
const onData = (data) => {
|
|
1735
|
+
let state = this._state;
|
|
1075
1736
|
if (state.t15Expired && state.end > state.start) {
|
|
1076
|
-
this.emit('framing-error', new
|
|
1737
|
+
this.emit('framing-error', new Error('Inter-character timeout (t1.5) exceeded'));
|
|
1077
1738
|
state.start = 0;
|
|
1078
1739
|
state.end = 0;
|
|
1079
1740
|
}
|
|
@@ -1082,11 +1743,11 @@ class RtuApplicationLayer extends AbstractApplicationLayer {
|
|
|
1082
1743
|
while (dataOffset < data.length) {
|
|
1083
1744
|
const space = state.pool.length - state.end;
|
|
1084
1745
|
if (space === 0) {
|
|
1085
|
-
this.clearStateTimers(
|
|
1086
|
-
this.flushBuffer(
|
|
1087
|
-
state = this.
|
|
1746
|
+
this.clearStateTimers();
|
|
1747
|
+
this.flushBuffer(this._threePointFiveT > 0);
|
|
1748
|
+
state = this._state;
|
|
1088
1749
|
if (state.pool.length - state.end === 0) {
|
|
1089
|
-
this.emit('framing-error', new
|
|
1750
|
+
this.emit('framing-error', new Error('Frame buffer exhausted before complete frame received'));
|
|
1090
1751
|
state.start = 0;
|
|
1091
1752
|
state.end = 0;
|
|
1092
1753
|
state.t15Expired = false;
|
|
@@ -1098,10 +1759,10 @@ class RtuApplicationLayer extends AbstractApplicationLayer {
|
|
|
1098
1759
|
state.end += toCopy;
|
|
1099
1760
|
dataOffset += toCopy;
|
|
1100
1761
|
}
|
|
1101
|
-
this.clearStateTimers(
|
|
1762
|
+
this.clearStateTimers();
|
|
1102
1763
|
const commitFrame = () => {
|
|
1103
|
-
this.clearStateTimers(
|
|
1104
|
-
this.flushBuffer(
|
|
1764
|
+
this.clearStateTimers();
|
|
1765
|
+
this.flushBuffer(this._threePointFiveT > 0);
|
|
1105
1766
|
};
|
|
1106
1767
|
if (state.end - state.start >= MAX_FRAME_LENGTH) {
|
|
1107
1768
|
commitFrame();
|
|
@@ -1119,30 +1780,20 @@ class RtuApplicationLayer extends AbstractApplicationLayer {
|
|
|
1119
1780
|
commitFrame();
|
|
1120
1781
|
}
|
|
1121
1782
|
};
|
|
1122
|
-
|
|
1123
|
-
this.
|
|
1124
|
-
const
|
|
1125
|
-
this.
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
for (const key of [...this._states.keys()]) {
|
|
1131
|
-
this.clearState(key);
|
|
1132
|
-
}
|
|
1783
|
+
connection.on('data', onData);
|
|
1784
|
+
this._cleanupFns.add(() => connection.off('data', onData));
|
|
1785
|
+
const onClose = () => {
|
|
1786
|
+
for (const fn of this._cleanupFns) {
|
|
1787
|
+
fn();
|
|
1788
|
+
}
|
|
1789
|
+
this._cleanupFns.clear();
|
|
1790
|
+
this.clearStateTimers();
|
|
1133
1791
|
};
|
|
1134
|
-
|
|
1135
|
-
this.
|
|
1792
|
+
connection.on('close', onClose);
|
|
1793
|
+
this._cleanupFns.add(() => connection.off('close', onClose));
|
|
1136
1794
|
}
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
if (!state) {
|
|
1140
|
-
state = { pool: Buffer.alloc(MAX_FRAME_LENGTH * 2), start: 0, end: 0 };
|
|
1141
|
-
this._states.set(key, state);
|
|
1142
|
-
}
|
|
1143
|
-
return state;
|
|
1144
|
-
}
|
|
1145
|
-
clearStateTimers(state) {
|
|
1795
|
+
clearStateTimers() {
|
|
1796
|
+
const state = this._state;
|
|
1146
1797
|
if (state.timer) {
|
|
1147
1798
|
clearTimeout(state.timer);
|
|
1148
1799
|
state.timer = undefined;
|
|
@@ -1152,45 +1803,18 @@ class RtuApplicationLayer extends AbstractApplicationLayer {
|
|
|
1152
1803
|
state.interCharTimer = undefined;
|
|
1153
1804
|
}
|
|
1154
1805
|
}
|
|
1155
|
-
|
|
1156
|
-
const state = this.
|
|
1157
|
-
if (state) {
|
|
1158
|
-
this.clearStateTimers(state);
|
|
1159
|
-
}
|
|
1160
|
-
this._states.delete(key);
|
|
1161
|
-
}
|
|
1162
|
-
flush() {
|
|
1163
|
-
for (const state of this._states.values()) {
|
|
1164
|
-
this.clearStateTimers(state);
|
|
1165
|
-
state.start = 0;
|
|
1166
|
-
state.end = 0;
|
|
1167
|
-
}
|
|
1168
|
-
this._states.clear();
|
|
1169
|
-
}
|
|
1170
|
-
addCustomFunctionCode(cfc) {
|
|
1171
|
-
if (!isUint8(cfc.fc)) {
|
|
1172
|
-
throw new ModbusError(ModbusErrorCode.RANGE, `fc must be an integer in 0..255, got ${cfc.fc}`);
|
|
1173
|
-
}
|
|
1174
|
-
this._customFunctionCodes.set(cfc.fc, cfc);
|
|
1175
|
-
}
|
|
1176
|
-
removeCustomFunctionCode(fc) {
|
|
1177
|
-
this._customFunctionCodes.delete(fc);
|
|
1178
|
-
}
|
|
1179
|
-
flushBuffer(key, response, connection, strict) {
|
|
1180
|
-
const state = this._states.get(key);
|
|
1181
|
-
if (!state) {
|
|
1182
|
-
return;
|
|
1183
|
-
}
|
|
1806
|
+
flushBuffer(strict) {
|
|
1807
|
+
const state = this._state;
|
|
1184
1808
|
while (state.end - state.start > 0) {
|
|
1185
1809
|
const buffer = state.pool.subarray(state.start, state.end);
|
|
1186
1810
|
const result = this.tryExtract(buffer);
|
|
1187
1811
|
if (result.kind === 'frame') {
|
|
1188
1812
|
state.start += result.frame.length;
|
|
1189
|
-
this.deliverFrame(result.frame
|
|
1813
|
+
this.deliverFrame(result.frame);
|
|
1190
1814
|
}
|
|
1191
1815
|
else if (result.kind === 'skip') {
|
|
1192
1816
|
if (strict) {
|
|
1193
|
-
this.emit('framing-error', new
|
|
1817
|
+
this.emit('framing-error', new Error('CRC mismatch'));
|
|
1194
1818
|
state.start = 0;
|
|
1195
1819
|
state.end = 0;
|
|
1196
1820
|
state.t15Expired = false;
|
|
@@ -1204,14 +1828,14 @@ class RtuApplicationLayer extends AbstractApplicationLayer {
|
|
|
1204
1828
|
continue;
|
|
1205
1829
|
}
|
|
1206
1830
|
if (strict) {
|
|
1207
|
-
this.emit('framing-error', new
|
|
1831
|
+
this.emit('framing-error', new Error(state.t15Expired ? 'Inter-character timeout (t1.5) exceeded' : 'Incomplete frame at t3.5'));
|
|
1208
1832
|
state.start = 0;
|
|
1209
1833
|
state.end = 0;
|
|
1210
1834
|
state.t15Expired = false;
|
|
1211
1835
|
return;
|
|
1212
1836
|
}
|
|
1213
1837
|
if (state.t15Expired) {
|
|
1214
|
-
this.emit('framing-error', new
|
|
1838
|
+
this.emit('framing-error', new Error('Inter-character timeout (t1.5) exceeded'));
|
|
1215
1839
|
state.start = 0;
|
|
1216
1840
|
state.end = 0;
|
|
1217
1841
|
state.t15Expired = false;
|
|
@@ -1232,15 +1856,22 @@ class RtuApplicationLayer extends AbstractApplicationLayer {
|
|
|
1232
1856
|
state.end -= state.start;
|
|
1233
1857
|
state.start = 0;
|
|
1234
1858
|
}
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1859
|
+
}
|
|
1860
|
+
deliverFrame(buffer) {
|
|
1861
|
+
const frameBuf = Buffer.from(buffer);
|
|
1862
|
+
const frame = {
|
|
1863
|
+
unit: frameBuf[0],
|
|
1864
|
+
fc: frameBuf[1],
|
|
1865
|
+
data: frameBuf.subarray(2, frameBuf.length - 2),
|
|
1866
|
+
buffer: frameBuf,
|
|
1867
|
+
};
|
|
1868
|
+
this.emit('framing', frame);
|
|
1238
1869
|
}
|
|
1239
1870
|
tryExtract(buffer) {
|
|
1240
1871
|
if (buffer.length < MIN_FRAME_LENGTH) {
|
|
1241
1872
|
return { kind: 'insufficient' };
|
|
1242
1873
|
}
|
|
1243
|
-
const isResponse = this.
|
|
1874
|
+
const isResponse = this.ROLE === 'MASTER';
|
|
1244
1875
|
const fc = buffer[1];
|
|
1245
1876
|
const cfc = this._customFunctionCodes.get(fc);
|
|
1246
1877
|
if (cfc) {
|
|
@@ -1260,12 +1891,12 @@ class RtuApplicationLayer extends AbstractApplicationLayer {
|
|
|
1260
1891
|
}
|
|
1261
1892
|
return {
|
|
1262
1893
|
kind: 'error',
|
|
1263
|
-
error: new
|
|
1894
|
+
error: new Error(`Unknown function code 0x${fc.toString(16).padStart(2, '0')} — register a CustomFunctionCode to frame this FC`),
|
|
1264
1895
|
};
|
|
1265
1896
|
}
|
|
1266
1897
|
checkExpected(buffer, expected) {
|
|
1267
1898
|
if (expected > MAX_FRAME_LENGTH || expected < MIN_FRAME_LENGTH) {
|
|
1268
|
-
return { kind: 'error', error: new
|
|
1899
|
+
return { kind: 'error', error: new Error('Invalid data') };
|
|
1269
1900
|
}
|
|
1270
1901
|
if (buffer.length < expected) {
|
|
1271
1902
|
return { kind: 'insufficient' };
|
|
@@ -1278,40 +1909,28 @@ class RtuApplicationLayer extends AbstractApplicationLayer {
|
|
|
1278
1909
|
crcMatches(buffer, length) {
|
|
1279
1910
|
return buffer.readUInt16LE(length - 2) === crc(buffer.subarray(0, length - 2));
|
|
1280
1911
|
}
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1912
|
+
flush() {
|
|
1913
|
+
this.clearStateTimers();
|
|
1914
|
+
this._state.start = 0;
|
|
1915
|
+
this._state.end = 0;
|
|
1916
|
+
}
|
|
1917
|
+
addCustomFunctionCode(cfc) {
|
|
1918
|
+
if (!isUint8(cfc.fc)) {
|
|
1919
|
+
throw new Error(`fc must be an integer in 0..255, got ${cfc.fc}`);
|
|
1920
|
+
}
|
|
1921
|
+
this._customFunctionCodes.set(cfc.fc, cfc);
|
|
1922
|
+
}
|
|
1923
|
+
removeCustomFunctionCode(fc) {
|
|
1924
|
+
this._customFunctionCodes.delete(fc);
|
|
1290
1925
|
}
|
|
1291
1926
|
encode(data) {
|
|
1292
|
-
const buffer = Buffer.
|
|
1927
|
+
const buffer = Buffer.allocUnsafe(data.data.length + 4);
|
|
1293
1928
|
buffer.writeUInt8(data.unit, 0);
|
|
1294
1929
|
buffer.writeUInt8(data.fc, 1);
|
|
1295
1930
|
buffer.set(data.data, 2);
|
|
1296
1931
|
buffer.writeUInt16LE(crc(buffer.subarray(0, -2)), buffer.length - 2);
|
|
1297
1932
|
return buffer;
|
|
1298
1933
|
}
|
|
1299
|
-
destroy() {
|
|
1300
|
-
if (this._destroyed) {
|
|
1301
|
-
return;
|
|
1302
|
-
}
|
|
1303
|
-
this._destroyed = true;
|
|
1304
|
-
this.removeAllListeners();
|
|
1305
|
-
for (const removeListener of this._removeAllListeners) {
|
|
1306
|
-
removeListener();
|
|
1307
|
-
}
|
|
1308
|
-
this._removeAllListeners.length = 0;
|
|
1309
|
-
for (const state of this._states.values()) {
|
|
1310
|
-
this.clearStateTimers(state);
|
|
1311
|
-
}
|
|
1312
|
-
this._states.clear();
|
|
1313
|
-
this._customFunctionCodes.clear();
|
|
1314
|
-
}
|
|
1315
1934
|
}
|
|
1316
1935
|
|
|
1317
1936
|
const CHAR_CODE = {
|
|
@@ -1336,7 +1955,10 @@ for (let i = 0x61; i <= 0x66; i++) {
|
|
|
1336
1955
|
}
|
|
1337
1956
|
const HEX_ENCODE = new Uint8Array('0123456789ABCDEF'.split('').map((c) => c.charCodeAt(0)));
|
|
1338
1957
|
class AsciiApplicationLayer extends AbstractApplicationLayer {
|
|
1339
|
-
|
|
1958
|
+
get connection() {
|
|
1959
|
+
return this._connection;
|
|
1960
|
+
}
|
|
1961
|
+
constructor(role, connection, options = {}) {
|
|
1340
1962
|
var _a;
|
|
1341
1963
|
super();
|
|
1342
1964
|
Object.defineProperty(this, "PROTOCOL", {
|
|
@@ -1345,30 +1967,38 @@ class AsciiApplicationLayer extends AbstractApplicationLayer {
|
|
|
1345
1967
|
writable: true,
|
|
1346
1968
|
value: 'ASCII'
|
|
1347
1969
|
});
|
|
1970
|
+
Object.defineProperty(this, "ROLE", {
|
|
1971
|
+
enumerable: true,
|
|
1972
|
+
configurable: true,
|
|
1973
|
+
writable: true,
|
|
1974
|
+
value: void 0
|
|
1975
|
+
});
|
|
1348
1976
|
Object.defineProperty(this, "lenientHex", {
|
|
1349
1977
|
enumerable: true,
|
|
1350
1978
|
configurable: true,
|
|
1351
1979
|
writable: true,
|
|
1352
1980
|
value: void 0
|
|
1353
1981
|
});
|
|
1354
|
-
Object.defineProperty(this, "
|
|
1982
|
+
Object.defineProperty(this, "_connection", {
|
|
1355
1983
|
enumerable: true,
|
|
1356
1984
|
configurable: true,
|
|
1357
1985
|
writable: true,
|
|
1358
|
-
value:
|
|
1986
|
+
value: void 0
|
|
1359
1987
|
});
|
|
1360
|
-
Object.defineProperty(this, "
|
|
1988
|
+
Object.defineProperty(this, "_state", {
|
|
1361
1989
|
enumerable: true,
|
|
1362
1990
|
configurable: true,
|
|
1363
1991
|
writable: true,
|
|
1364
|
-
value: []
|
|
1992
|
+
value: { status: 'idle', frame: [] }
|
|
1365
1993
|
});
|
|
1366
|
-
Object.defineProperty(this, "
|
|
1994
|
+
Object.defineProperty(this, "_cleanupFns", {
|
|
1367
1995
|
enumerable: true,
|
|
1368
1996
|
configurable: true,
|
|
1369
1997
|
writable: true,
|
|
1370
|
-
value:
|
|
1998
|
+
value: new Set()
|
|
1371
1999
|
});
|
|
2000
|
+
this.ROLE = role;
|
|
2001
|
+
this._connection = connection;
|
|
1372
2002
|
this.lenientHex = (_a = options.lenientHex) !== null && _a !== void 0 ? _a : false;
|
|
1373
2003
|
const lenientHex = this.lenientHex;
|
|
1374
2004
|
const isHexChar = (value) => {
|
|
@@ -1383,8 +2013,8 @@ class AsciiApplicationLayer extends AbstractApplicationLayer {
|
|
|
1383
2013
|
}
|
|
1384
2014
|
return false;
|
|
1385
2015
|
};
|
|
1386
|
-
const
|
|
1387
|
-
const state = this.
|
|
2016
|
+
const onData = (data) => {
|
|
2017
|
+
const state = this._state;
|
|
1388
2018
|
data.forEach((value) => {
|
|
1389
2019
|
switch (state.status) {
|
|
1390
2020
|
case 'idle': {
|
|
@@ -1404,12 +2034,12 @@ class AsciiApplicationLayer extends AbstractApplicationLayer {
|
|
|
1404
2034
|
else if (state.frame.length >= MAX_ASCII_PAYLOAD) {
|
|
1405
2035
|
state.status = 'idle';
|
|
1406
2036
|
state.frame = [];
|
|
1407
|
-
this.emit('framing-error', new
|
|
2037
|
+
this.emit('framing-error', new Error('Invalid data'));
|
|
1408
2038
|
}
|
|
1409
2039
|
else if (!isHexChar(value)) {
|
|
1410
2040
|
state.status = 'idle';
|
|
1411
2041
|
state.frame = [];
|
|
1412
|
-
this.emit('framing-error', new
|
|
2042
|
+
this.emit('framing-error', new Error('Invalid hex character'));
|
|
1413
2043
|
}
|
|
1414
2044
|
else {
|
|
1415
2045
|
state.frame.push(value);
|
|
@@ -1424,7 +2054,7 @@ class AsciiApplicationLayer extends AbstractApplicationLayer {
|
|
|
1424
2054
|
else {
|
|
1425
2055
|
state.status = 'idle';
|
|
1426
2056
|
if (value === CHAR_CODE.LF) {
|
|
1427
|
-
this.framing(Buffer.from(state.frame)
|
|
2057
|
+
this.framing(Buffer.from(state.frame));
|
|
1428
2058
|
}
|
|
1429
2059
|
}
|
|
1430
2060
|
break;
|
|
@@ -1432,64 +2062,56 @@ class AsciiApplicationLayer extends AbstractApplicationLayer {
|
|
|
1432
2062
|
}
|
|
1433
2063
|
});
|
|
1434
2064
|
};
|
|
1435
|
-
|
|
1436
|
-
this.
|
|
1437
|
-
const
|
|
1438
|
-
this.
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
const handleClose = () => {
|
|
1443
|
-
this._states.clear();
|
|
2065
|
+
connection.on('data', onData);
|
|
2066
|
+
this._cleanupFns.add(() => connection.off('data', onData));
|
|
2067
|
+
const onClose = () => {
|
|
2068
|
+
for (const fn of this._cleanupFns) {
|
|
2069
|
+
fn();
|
|
2070
|
+
}
|
|
2071
|
+
this._cleanupFns.clear();
|
|
1444
2072
|
};
|
|
1445
|
-
|
|
1446
|
-
this.
|
|
1447
|
-
}
|
|
1448
|
-
getState(key) {
|
|
1449
|
-
let state = this._states.get(key);
|
|
1450
|
-
if (!state) {
|
|
1451
|
-
state = { status: 'idle', frame: [] };
|
|
1452
|
-
this._states.set(key, state);
|
|
1453
|
-
}
|
|
1454
|
-
return state;
|
|
1455
|
-
}
|
|
1456
|
-
flush() {
|
|
1457
|
-
this._states.clear();
|
|
2073
|
+
connection.on('close', onClose);
|
|
2074
|
+
this._cleanupFns.add(() => connection.off('close', onClose));
|
|
1458
2075
|
}
|
|
1459
|
-
framing(
|
|
1460
|
-
if (
|
|
1461
|
-
this.emit('framing-error', new
|
|
2076
|
+
framing(hexChars) {
|
|
2077
|
+
if (hexChars.length < 6) {
|
|
2078
|
+
this.emit('framing-error', new Error('Insufficient data length'));
|
|
1462
2079
|
return;
|
|
1463
2080
|
}
|
|
1464
|
-
if (
|
|
1465
|
-
this.emit('framing-error', new
|
|
2081
|
+
if (hexChars.length % 2 !== 0) {
|
|
2082
|
+
this.emit('framing-error', new Error('Invalid data'));
|
|
1466
2083
|
return;
|
|
1467
2084
|
}
|
|
1468
|
-
const
|
|
1469
|
-
for (let i = 0; i <
|
|
1470
|
-
const hi = HEX_DECODE[
|
|
1471
|
-
const lo = HEX_DECODE[
|
|
2085
|
+
const decoded = Buffer.allocUnsafe(hexChars.length / 2);
|
|
2086
|
+
for (let i = 0; i < hexChars.length; i += 2) {
|
|
2087
|
+
const hi = HEX_DECODE[hexChars[i]];
|
|
2088
|
+
const lo = HEX_DECODE[hexChars[i + 1]];
|
|
2089
|
+
// Defensive: the FSM should already have filtered non-hex characters,
|
|
2090
|
+
// but guard here in case framing is ever called directly.
|
|
1472
2091
|
if (hi === 0xff || lo === 0xff) {
|
|
1473
|
-
this.emit('framing-error', new
|
|
2092
|
+
this.emit('framing-error', new Error('Invalid hex character'));
|
|
1474
2093
|
return;
|
|
1475
2094
|
}
|
|
1476
|
-
|
|
2095
|
+
decoded[i / 2] = (hi << 4) | lo;
|
|
1477
2096
|
}
|
|
1478
2097
|
const frame = {
|
|
1479
|
-
unit:
|
|
1480
|
-
fc:
|
|
1481
|
-
data:
|
|
1482
|
-
buffer:
|
|
2098
|
+
unit: decoded[0],
|
|
2099
|
+
fc: decoded[1],
|
|
2100
|
+
data: decoded.subarray(2, decoded.length - 1),
|
|
2101
|
+
buffer: hexChars,
|
|
1483
2102
|
};
|
|
1484
|
-
const lrcPassed =
|
|
2103
|
+
const lrcPassed = decoded[decoded.length - 1] === lrc(decoded.subarray(0, decoded.length - 1));
|
|
1485
2104
|
if (!lrcPassed) {
|
|
1486
|
-
this.emit('framing-error', new
|
|
2105
|
+
this.emit('framing-error', new Error('LRC check failed'));
|
|
1487
2106
|
return;
|
|
1488
2107
|
}
|
|
1489
|
-
this.emit('framing', frame
|
|
2108
|
+
this.emit('framing', frame);
|
|
2109
|
+
}
|
|
2110
|
+
flush() {
|
|
2111
|
+
this._state = { status: 'idle', frame: [] };
|
|
1490
2112
|
}
|
|
1491
2113
|
encode(data) {
|
|
1492
|
-
const buffer = Buffer.
|
|
2114
|
+
const buffer = Buffer.allocUnsafe(data.data.length + 3);
|
|
1493
2115
|
buffer.writeUInt8(data.unit, 0);
|
|
1494
2116
|
buffer.writeUInt8(data.fc, 1);
|
|
1495
2117
|
buffer.set(data.data, 2);
|
|
@@ -1505,24 +2127,14 @@ class AsciiApplicationLayer extends AbstractApplicationLayer {
|
|
|
1505
2127
|
out[out.length - 1] = CHAR_CODE.LF;
|
|
1506
2128
|
return out;
|
|
1507
2129
|
}
|
|
1508
|
-
destroy() {
|
|
1509
|
-
if (this._destroyed) {
|
|
1510
|
-
return;
|
|
1511
|
-
}
|
|
1512
|
-
this._destroyed = true;
|
|
1513
|
-
this.removeAllListeners();
|
|
1514
|
-
for (const removeListener of this._removeAllListeners) {
|
|
1515
|
-
removeListener();
|
|
1516
|
-
}
|
|
1517
|
-
this._removeAllListeners.length = 0;
|
|
1518
|
-
this._states.clear();
|
|
1519
|
-
}
|
|
1520
2130
|
}
|
|
1521
2131
|
|
|
1522
2132
|
const MAX_TCP_FRAME = 260;
|
|
1523
|
-
const EMPTY_BUFFER = Buffer.alloc(0);
|
|
1524
2133
|
class TcpApplicationLayer extends AbstractApplicationLayer {
|
|
1525
|
-
|
|
2134
|
+
get connection() {
|
|
2135
|
+
return this._connection;
|
|
2136
|
+
}
|
|
2137
|
+
constructor(role, connection) {
|
|
1526
2138
|
super();
|
|
1527
2139
|
Object.defineProperty(this, "PROTOCOL", {
|
|
1528
2140
|
enumerable: true,
|
|
@@ -1530,38 +2142,50 @@ class TcpApplicationLayer extends AbstractApplicationLayer {
|
|
|
1530
2142
|
writable: true,
|
|
1531
2143
|
value: 'TCP'
|
|
1532
2144
|
});
|
|
1533
|
-
Object.defineProperty(this, "
|
|
2145
|
+
Object.defineProperty(this, "ROLE", {
|
|
1534
2146
|
enumerable: true,
|
|
1535
2147
|
configurable: true,
|
|
1536
2148
|
writable: true,
|
|
1537
|
-
value:
|
|
2149
|
+
value: void 0
|
|
1538
2150
|
});
|
|
1539
|
-
Object.defineProperty(this, "
|
|
2151
|
+
Object.defineProperty(this, "_connection", {
|
|
1540
2152
|
enumerable: true,
|
|
1541
2153
|
configurable: true,
|
|
1542
2154
|
writable: true,
|
|
1543
|
-
value:
|
|
2155
|
+
value: void 0
|
|
1544
2156
|
});
|
|
1545
|
-
Object.defineProperty(this, "
|
|
2157
|
+
Object.defineProperty(this, "_transactionId", {
|
|
1546
2158
|
enumerable: true,
|
|
1547
2159
|
configurable: true,
|
|
1548
2160
|
writable: true,
|
|
1549
|
-
value:
|
|
2161
|
+
value: 1
|
|
1550
2162
|
});
|
|
1551
|
-
Object.defineProperty(this, "
|
|
2163
|
+
Object.defineProperty(this, "_buffer", {
|
|
1552
2164
|
enumerable: true,
|
|
1553
2165
|
configurable: true,
|
|
1554
2166
|
writable: true,
|
|
1555
|
-
value:
|
|
2167
|
+
value: Buffer.alloc(0)
|
|
1556
2168
|
});
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
2169
|
+
Object.defineProperty(this, "_cleanupFns", {
|
|
2170
|
+
enumerable: true,
|
|
2171
|
+
configurable: true,
|
|
2172
|
+
writable: true,
|
|
2173
|
+
value: new Set()
|
|
2174
|
+
});
|
|
2175
|
+
this.ROLE = role;
|
|
2176
|
+
this._connection = connection;
|
|
2177
|
+
const onData = (data) => {
|
|
2178
|
+
let buffer = this._buffer;
|
|
2179
|
+
if (buffer.length === 0) {
|
|
2180
|
+
buffer = data;
|
|
2181
|
+
}
|
|
2182
|
+
else if (data.length > 0) {
|
|
2183
|
+
buffer = Buffer.concat([buffer, data]);
|
|
2184
|
+
}
|
|
1561
2185
|
while (buffer.length > 0) {
|
|
1562
2186
|
const result = this.tryExtract(buffer);
|
|
1563
2187
|
if (result.kind === 'frame') {
|
|
1564
|
-
this.processFrame(result.frame
|
|
2188
|
+
this.processFrame(result.frame);
|
|
1565
2189
|
buffer = result.rest;
|
|
1566
2190
|
}
|
|
1567
2191
|
else if (result.kind === 'insufficient') {
|
|
@@ -1569,48 +2193,53 @@ class TcpApplicationLayer extends AbstractApplicationLayer {
|
|
|
1569
2193
|
}
|
|
1570
2194
|
else {
|
|
1571
2195
|
this.emit('framing-error', result.error);
|
|
1572
|
-
buffer =
|
|
2196
|
+
buffer = Buffer.alloc(0);
|
|
1573
2197
|
break;
|
|
1574
2198
|
}
|
|
1575
2199
|
}
|
|
1576
2200
|
if (buffer.length === 0) {
|
|
1577
|
-
this.
|
|
2201
|
+
this._buffer = Buffer.alloc(0);
|
|
2202
|
+
}
|
|
2203
|
+
else if (buffer === data) {
|
|
2204
|
+
// Copy into a right-sized buffer so we do not retain the potentially
|
|
2205
|
+
// large backing buffer of the original incoming data (Node.js pool).
|
|
2206
|
+
const copy = Buffer.allocUnsafe(buffer.length);
|
|
2207
|
+
buffer.copy(copy);
|
|
2208
|
+
this._buffer = copy;
|
|
1578
2209
|
}
|
|
1579
2210
|
else {
|
|
1580
|
-
this.
|
|
2211
|
+
this._buffer = buffer;
|
|
1581
2212
|
}
|
|
1582
2213
|
};
|
|
1583
|
-
|
|
1584
|
-
this.
|
|
1585
|
-
const
|
|
1586
|
-
this.
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
const handleClose = () => {
|
|
1591
|
-
this._buffers.clear();
|
|
2214
|
+
connection.on('data', onData);
|
|
2215
|
+
this._cleanupFns.add(() => connection.off('data', onData));
|
|
2216
|
+
const onClose = () => {
|
|
2217
|
+
for (const fn of this._cleanupFns) {
|
|
2218
|
+
fn();
|
|
2219
|
+
}
|
|
2220
|
+
this._cleanupFns.clear();
|
|
1592
2221
|
};
|
|
1593
|
-
|
|
1594
|
-
this.
|
|
2222
|
+
connection.on('close', onClose);
|
|
2223
|
+
this._cleanupFns.add(() => connection.off('close', onClose));
|
|
1595
2224
|
}
|
|
1596
2225
|
tryExtract(buffer) {
|
|
1597
2226
|
if (buffer.length < 8) {
|
|
1598
2227
|
return { kind: 'insufficient' };
|
|
1599
2228
|
}
|
|
1600
2229
|
if (buffer[2] !== 0 || buffer[3] !== 0) {
|
|
1601
|
-
return { kind: 'error', error: new
|
|
2230
|
+
return { kind: 'error', error: new Error('Invalid data') };
|
|
1602
2231
|
}
|
|
1603
2232
|
const length = buffer.readUInt16BE(4);
|
|
1604
2233
|
const total = 6 + length;
|
|
1605
2234
|
if (total > MAX_TCP_FRAME || length < 2) {
|
|
1606
|
-
return { kind: 'error', error: new
|
|
2235
|
+
return { kind: 'error', error: new Error('Invalid data') };
|
|
1607
2236
|
}
|
|
1608
2237
|
if (buffer.length < total) {
|
|
1609
2238
|
return { kind: 'insufficient' };
|
|
1610
2239
|
}
|
|
1611
2240
|
return { kind: 'frame', frame: buffer.subarray(0, total), rest: buffer.subarray(total) };
|
|
1612
2241
|
}
|
|
1613
|
-
processFrame(buffer
|
|
2242
|
+
processFrame(buffer) {
|
|
1614
2243
|
const frame = {
|
|
1615
2244
|
transaction: buffer.readUInt16BE(0),
|
|
1616
2245
|
unit: buffer[6],
|
|
@@ -1618,12 +2247,15 @@ class TcpApplicationLayer extends AbstractApplicationLayer {
|
|
|
1618
2247
|
data: buffer.subarray(8),
|
|
1619
2248
|
buffer,
|
|
1620
2249
|
};
|
|
1621
|
-
this.emit('framing', frame
|
|
2250
|
+
this.emit('framing', frame);
|
|
2251
|
+
}
|
|
2252
|
+
flush() {
|
|
2253
|
+
this._buffer = Buffer.alloc(0);
|
|
1622
2254
|
}
|
|
1623
2255
|
encode(data) {
|
|
1624
2256
|
var _a;
|
|
1625
2257
|
const transaction = (_a = data.transaction) !== null && _a !== void 0 ? _a : this._transactionId;
|
|
1626
|
-
const buffer = Buffer.
|
|
2258
|
+
const buffer = Buffer.allocUnsafe(data.data.length + 8);
|
|
1627
2259
|
buffer.writeUInt16BE(transaction, 0);
|
|
1628
2260
|
buffer.writeUInt16BE(0, 2);
|
|
1629
2261
|
buffer.writeUInt16BE(data.data.length + 2, 4);
|
|
@@ -1635,18 +2267,6 @@ class TcpApplicationLayer extends AbstractApplicationLayer {
|
|
|
1635
2267
|
}
|
|
1636
2268
|
return buffer;
|
|
1637
2269
|
}
|
|
1638
|
-
destroy() {
|
|
1639
|
-
if (this._destroyed) {
|
|
1640
|
-
return;
|
|
1641
|
-
}
|
|
1642
|
-
this._destroyed = true;
|
|
1643
|
-
this.removeAllListeners();
|
|
1644
|
-
for (const removeListener of this._removeAllListeners) {
|
|
1645
|
-
removeListener();
|
|
1646
|
-
}
|
|
1647
|
-
this._removeAllListeners.length = 0;
|
|
1648
|
-
this._buffers.clear();
|
|
1649
|
-
}
|
|
1650
2270
|
}
|
|
1651
2271
|
|
|
1652
2272
|
const FIFO_KEY = 'fifo';
|
|
@@ -1683,8 +2303,10 @@ class MasterSession {
|
|
|
1683
2303
|
return;
|
|
1684
2304
|
}
|
|
1685
2305
|
if (!this.runPreChecks(waiter, frame)) {
|
|
2306
|
+
this._waiters.delete(key);
|
|
1686
2307
|
return;
|
|
1687
2308
|
}
|
|
2309
|
+
this._waiters.delete(key);
|
|
1688
2310
|
waiter.callback(null, frame);
|
|
1689
2311
|
}
|
|
1690
2312
|
handleError(error) {
|
|
@@ -1695,22 +2317,22 @@ class MasterSession {
|
|
|
1695
2317
|
for (const check of waiter.preCheck) {
|
|
1696
2318
|
const res = check(frame);
|
|
1697
2319
|
if (typeof res === 'undefined') {
|
|
1698
|
-
waiter.callback(new
|
|
2320
|
+
waiter.callback(new Error('Insufficient data length'));
|
|
1699
2321
|
return false;
|
|
1700
2322
|
}
|
|
1701
2323
|
if (typeof res === 'number') {
|
|
1702
2324
|
if (frame.data.length < res) {
|
|
1703
|
-
waiter.callback(new
|
|
2325
|
+
waiter.callback(new Error('Insufficient data length'));
|
|
1704
2326
|
return false;
|
|
1705
2327
|
}
|
|
1706
2328
|
if (frame.data.length !== res) {
|
|
1707
|
-
waiter.callback(new
|
|
2329
|
+
waiter.callback(new Error('Invalid response'));
|
|
1708
2330
|
return false;
|
|
1709
2331
|
}
|
|
1710
2332
|
continue;
|
|
1711
2333
|
}
|
|
1712
2334
|
if (!res) {
|
|
1713
|
-
waiter.callback(new
|
|
2335
|
+
waiter.callback(new Error('Invalid response'));
|
|
1714
2336
|
return false;
|
|
1715
2337
|
}
|
|
1716
2338
|
}
|
|
@@ -1751,26 +2373,26 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
|
|
|
1751
2373
|
};
|
|
1752
2374
|
|
|
1753
2375
|
class ModbusMaster extends EventEmitter {
|
|
1754
|
-
get
|
|
1755
|
-
return this.
|
|
2376
|
+
get state() {
|
|
2377
|
+
return this._physicalLayer.state;
|
|
1756
2378
|
}
|
|
1757
|
-
get
|
|
1758
|
-
return this.
|
|
2379
|
+
get physicalLayer() {
|
|
2380
|
+
return this._physicalLayer;
|
|
1759
2381
|
}
|
|
1760
|
-
constructor(
|
|
2382
|
+
constructor(options) {
|
|
1761
2383
|
var _a, _b;
|
|
1762
2384
|
super();
|
|
1763
|
-
Object.defineProperty(this, "
|
|
2385
|
+
Object.defineProperty(this, "timeout", {
|
|
1764
2386
|
enumerable: true,
|
|
1765
2387
|
configurable: true,
|
|
1766
2388
|
writable: true,
|
|
1767
|
-
value:
|
|
2389
|
+
value: void 0
|
|
1768
2390
|
});
|
|
1769
|
-
Object.defineProperty(this, "
|
|
2391
|
+
Object.defineProperty(this, "concurrent", {
|
|
1770
2392
|
enumerable: true,
|
|
1771
2393
|
configurable: true,
|
|
1772
2394
|
writable: true,
|
|
1773
|
-
value:
|
|
2395
|
+
value: void 0
|
|
1774
2396
|
});
|
|
1775
2397
|
Object.defineProperty(this, "_masterSession", {
|
|
1776
2398
|
enumerable: true,
|
|
@@ -1778,47 +2400,59 @@ class ModbusMaster extends EventEmitter {
|
|
|
1778
2400
|
writable: true,
|
|
1779
2401
|
value: new MasterSession()
|
|
1780
2402
|
});
|
|
1781
|
-
Object.defineProperty(this, "
|
|
2403
|
+
Object.defineProperty(this, "_physicalLayer", {
|
|
1782
2404
|
enumerable: true,
|
|
1783
2405
|
configurable: true,
|
|
1784
2406
|
writable: true,
|
|
1785
|
-
value:
|
|
2407
|
+
value: void 0
|
|
1786
2408
|
});
|
|
1787
|
-
Object.defineProperty(this, "
|
|
2409
|
+
Object.defineProperty(this, "_protocol", {
|
|
1788
2410
|
enumerable: true,
|
|
1789
2411
|
configurable: true,
|
|
1790
2412
|
writable: true,
|
|
1791
|
-
value:
|
|
2413
|
+
value: void 0
|
|
1792
2414
|
});
|
|
1793
|
-
Object.defineProperty(this, "
|
|
2415
|
+
Object.defineProperty(this, "_appLayer", {
|
|
1794
2416
|
enumerable: true,
|
|
1795
2417
|
configurable: true,
|
|
1796
2418
|
writable: true,
|
|
1797
|
-
value:
|
|
2419
|
+
value: void 0
|
|
2420
|
+
});
|
|
2421
|
+
Object.defineProperty(this, "_customFunctionCodes", {
|
|
2422
|
+
enumerable: true,
|
|
2423
|
+
configurable: true,
|
|
2424
|
+
writable: true,
|
|
2425
|
+
value: new Map()
|
|
2426
|
+
});
|
|
2427
|
+
Object.defineProperty(this, "_queue", {
|
|
2428
|
+
enumerable: true,
|
|
2429
|
+
configurable: true,
|
|
2430
|
+
writable: true,
|
|
2431
|
+
value: []
|
|
1798
2432
|
});
|
|
1799
|
-
Object.defineProperty(this, "
|
|
2433
|
+
Object.defineProperty(this, "_draining", {
|
|
1800
2434
|
enumerable: true,
|
|
1801
2435
|
configurable: true,
|
|
1802
2436
|
writable: true,
|
|
1803
2437
|
value: false
|
|
1804
2438
|
});
|
|
1805
|
-
Object.defineProperty(this, "
|
|
2439
|
+
Object.defineProperty(this, "_nextTid", {
|
|
1806
2440
|
enumerable: true,
|
|
1807
2441
|
configurable: true,
|
|
1808
2442
|
writable: true,
|
|
1809
|
-
value:
|
|
2443
|
+
value: 1
|
|
1810
2444
|
});
|
|
1811
|
-
Object.defineProperty(this, "
|
|
2445
|
+
Object.defineProperty(this, "_cleanupFns", {
|
|
1812
2446
|
enumerable: true,
|
|
1813
2447
|
configurable: true,
|
|
1814
2448
|
writable: true,
|
|
1815
|
-
value:
|
|
2449
|
+
value: new Set()
|
|
1816
2450
|
});
|
|
1817
|
-
Object.defineProperty(this, "
|
|
2451
|
+
Object.defineProperty(this, "_closePromise", {
|
|
1818
2452
|
enumerable: true,
|
|
1819
2453
|
configurable: true,
|
|
1820
2454
|
writable: true,
|
|
1821
|
-
value:
|
|
2455
|
+
value: null
|
|
1822
2456
|
});
|
|
1823
2457
|
Object.defineProperty(this, "writeFC1", {
|
|
1824
2458
|
enumerable: true,
|
|
@@ -1894,10 +2528,60 @@ class ModbusMaster extends EventEmitter {
|
|
|
1894
2528
|
});
|
|
1895
2529
|
this.timeout = (_a = options.timeout) !== null && _a !== void 0 ? _a : 1000;
|
|
1896
2530
|
this.concurrent = (_b = options.concurrent) !== null && _b !== void 0 ? _b : false;
|
|
1897
|
-
|
|
1898
|
-
|
|
2531
|
+
this._physicalLayer = createPhysicalLayer(options.physical);
|
|
2532
|
+
this._protocol = options.protocol;
|
|
2533
|
+
if (this.concurrent && options.protocol.type !== 'TCP') {
|
|
2534
|
+
throw new Error('concurrent mode requires a Modbus TCP application layer');
|
|
1899
2535
|
}
|
|
1900
|
-
|
|
2536
|
+
const onOpen = () => {
|
|
2537
|
+
this.emit('open');
|
|
2538
|
+
};
|
|
2539
|
+
this._physicalLayer.on('open', onOpen);
|
|
2540
|
+
this._cleanupFns.add(() => this._physicalLayer.off('open', onOpen));
|
|
2541
|
+
const onConnect = (connection) => {
|
|
2542
|
+
const appLayer = this._createAppLayer(connection);
|
|
2543
|
+
this._appLayer = appLayer;
|
|
2544
|
+
for (const cfc of this._customFunctionCodes.values()) {
|
|
2545
|
+
appLayer.addCustomFunctionCode(cfc);
|
|
2546
|
+
}
|
|
2547
|
+
const cleanupFraming = () => appLayer.off('framing', onFraming);
|
|
2548
|
+
const onFraming = (frame) => {
|
|
2549
|
+
this._masterSession.handleFrame(frame);
|
|
2550
|
+
};
|
|
2551
|
+
appLayer.on('framing', onFraming);
|
|
2552
|
+
this._cleanupFns.add(cleanupFraming);
|
|
2553
|
+
const cleanupFramingError = () => appLayer.off('framing-error', onFramingError);
|
|
2554
|
+
const onFramingError = (error) => {
|
|
2555
|
+
this._masterSession.handleError(error);
|
|
2556
|
+
};
|
|
2557
|
+
appLayer.on('framing-error', onFramingError);
|
|
2558
|
+
this._cleanupFns.add(cleanupFramingError);
|
|
2559
|
+
const cleanupClose = () => connection.off('close', onClose);
|
|
2560
|
+
const onClose = () => {
|
|
2561
|
+
cleanupFraming();
|
|
2562
|
+
cleanupFramingError();
|
|
2563
|
+
cleanupClose();
|
|
2564
|
+
this._cleanupFns.delete(cleanupFraming);
|
|
2565
|
+
this._cleanupFns.delete(cleanupFramingError);
|
|
2566
|
+
this._cleanupFns.delete(cleanupClose);
|
|
2567
|
+
};
|
|
2568
|
+
connection.on('close', onClose);
|
|
2569
|
+
this._cleanupFns.add(cleanupClose);
|
|
2570
|
+
this.emit('connect', connection);
|
|
2571
|
+
};
|
|
2572
|
+
this._physicalLayer.on('connect', onConnect);
|
|
2573
|
+
this._cleanupFns.add(() => this._physicalLayer.off('connect', onConnect));
|
|
2574
|
+
const onClose = () => {
|
|
2575
|
+
this.emit('close');
|
|
2576
|
+
this.close();
|
|
2577
|
+
};
|
|
2578
|
+
this._physicalLayer.on('close', onClose);
|
|
2579
|
+
this._cleanupFns.add(() => this._physicalLayer.off('close', onClose));
|
|
2580
|
+
const onError = (err) => {
|
|
2581
|
+
this.emit('error', err);
|
|
2582
|
+
};
|
|
2583
|
+
this._physicalLayer.on('error', onError);
|
|
2584
|
+
this._cleanupFns.add(() => this._physicalLayer.off('error', onError));
|
|
1901
2585
|
this.writeFC1 = this.readCoils;
|
|
1902
2586
|
this.writeFC2 = this.readDiscreteInputs;
|
|
1903
2587
|
this.writeFC3 = this.readHoldingRegisters;
|
|
@@ -1910,20 +2594,27 @@ class ModbusMaster extends EventEmitter {
|
|
|
1910
2594
|
this.handleFC22 = this.maskWriteRegister;
|
|
1911
2595
|
this.handleFC23 = this.readAndWriteMultipleRegisters;
|
|
1912
2596
|
this.handleFC43_14 = this.readDeviceIdentification;
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
this.
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
}
|
|
2597
|
+
}
|
|
2598
|
+
_createAppLayer(connection) {
|
|
2599
|
+
var _a;
|
|
2600
|
+
if (this._protocol.type === 'RTU') {
|
|
2601
|
+
const baudRate = this._physicalLayer.is('SERIAL') ? this._physicalLayer.baudRate : undefined;
|
|
2602
|
+
const { intervalBetweenFrames, interCharTimeout } = resolveRtuTiming(this._protocol.opts, baudRate);
|
|
2603
|
+
return new RtuApplicationLayer('MASTER', connection, {
|
|
2604
|
+
intervalBetweenFrames,
|
|
2605
|
+
interCharTimeout,
|
|
2606
|
+
poolSize: (_a = this._protocol.opts) === null || _a === void 0 ? void 0 : _a.poolSize,
|
|
2607
|
+
});
|
|
2608
|
+
}
|
|
2609
|
+
if (this._protocol.type === 'TCP') {
|
|
2610
|
+
return new TcpApplicationLayer('MASTER', connection);
|
|
2611
|
+
}
|
|
2612
|
+
return new AsciiApplicationLayer('MASTER', connection, this._protocol.opts);
|
|
1925
2613
|
}
|
|
1926
2614
|
send(adu, preCheck, timeout, broadcast) {
|
|
2615
|
+
if (this._physicalLayer.state !== exports.PhysicalState.OPEN) {
|
|
2616
|
+
return Promise.reject(new Error('Master is not open'));
|
|
2617
|
+
}
|
|
1927
2618
|
const task = () => this._exchange(adu, preCheck, timeout, broadcast);
|
|
1928
2619
|
if (this.concurrent) {
|
|
1929
2620
|
return task();
|
|
@@ -1943,14 +2634,20 @@ class ModbusMaster extends EventEmitter {
|
|
|
1943
2634
|
}
|
|
1944
2635
|
_drain() {
|
|
1945
2636
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1946
|
-
if (this._draining) {
|
|
2637
|
+
if (this._draining || this._physicalLayer.state !== exports.PhysicalState.OPEN) {
|
|
1947
2638
|
return;
|
|
1948
2639
|
}
|
|
1949
2640
|
this._draining = true;
|
|
1950
2641
|
try {
|
|
1951
2642
|
while (this._queue.length > 0) {
|
|
2643
|
+
if (this._physicalLayer.state !== exports.PhysicalState.OPEN) {
|
|
2644
|
+
return;
|
|
2645
|
+
}
|
|
1952
2646
|
const item = this._queue.shift();
|
|
1953
|
-
|
|
2647
|
+
try {
|
|
2648
|
+
yield item.run();
|
|
2649
|
+
}
|
|
2650
|
+
catch (_a) { }
|
|
1954
2651
|
}
|
|
1955
2652
|
}
|
|
1956
2653
|
finally {
|
|
@@ -1960,18 +2657,26 @@ class ModbusMaster extends EventEmitter {
|
|
|
1960
2657
|
}
|
|
1961
2658
|
_exchange(adu, preCheck, timeout, broadcast) {
|
|
1962
2659
|
return new Promise((resolve, reject) => {
|
|
1963
|
-
if (this.
|
|
1964
|
-
reject(new
|
|
2660
|
+
if (this._physicalLayer.state !== exports.PhysicalState.OPEN) {
|
|
2661
|
+
reject(new Error('Master is not open'));
|
|
2662
|
+
return;
|
|
2663
|
+
}
|
|
2664
|
+
const appLayer = this._appLayer;
|
|
2665
|
+
if (!appLayer) {
|
|
2666
|
+
reject(new Error('Connection not established'));
|
|
1965
2667
|
return;
|
|
1966
2668
|
}
|
|
1967
|
-
const
|
|
2669
|
+
const connection = appLayer.connection;
|
|
2670
|
+
const usesTid = appLayer.PROTOCOL === 'TCP' && !broadcast;
|
|
1968
2671
|
let tid;
|
|
1969
2672
|
if (usesTid) {
|
|
1970
|
-
|
|
1971
|
-
|
|
2673
|
+
do {
|
|
2674
|
+
tid = this._nextTid;
|
|
2675
|
+
this._nextTid = (this._nextTid + 1) % 65536 || 1;
|
|
2676
|
+
} while (this._masterSession.has(tid));
|
|
1972
2677
|
}
|
|
1973
2678
|
const key = tid !== null && tid !== void 0 ? tid : FIFO_KEY;
|
|
1974
|
-
const payload =
|
|
2679
|
+
const payload = appLayer.encode(Object.assign(Object.assign({}, adu), { transaction: tid }));
|
|
1975
2680
|
let settled = false;
|
|
1976
2681
|
const settle = (error, frame) => {
|
|
1977
2682
|
if (settled) {
|
|
@@ -1987,18 +2692,22 @@ class ModbusMaster extends EventEmitter {
|
|
|
1987
2692
|
resolve(frame);
|
|
1988
2693
|
}
|
|
1989
2694
|
};
|
|
1990
|
-
const timer = setTimeout(() => settle(new
|
|
2695
|
+
const timer = setTimeout(() => settle(new Error('Timeout')), timeout);
|
|
1991
2696
|
// FIFO mode: clear stale buffer state before this request.
|
|
1992
2697
|
// Concurrent mode: must not flush — other in-flight requests share the buffer.
|
|
1993
2698
|
if (!this.concurrent) {
|
|
1994
|
-
|
|
2699
|
+
appLayer.flush();
|
|
1995
2700
|
}
|
|
1996
|
-
|
|
2701
|
+
connection
|
|
1997
2702
|
.write(payload)
|
|
1998
2703
|
.then(() => {
|
|
1999
2704
|
if (settled) {
|
|
2000
2705
|
return;
|
|
2001
2706
|
}
|
|
2707
|
+
if (this._physicalLayer.state !== exports.PhysicalState.OPEN) {
|
|
2708
|
+
settle(new Error('Master is not open'));
|
|
2709
|
+
return;
|
|
2710
|
+
}
|
|
2002
2711
|
if (broadcast) {
|
|
2003
2712
|
settle(null);
|
|
2004
2713
|
return;
|
|
@@ -2011,7 +2720,7 @@ class ModbusMaster extends EventEmitter {
|
|
|
2011
2720
|
}
|
|
2012
2721
|
writeFC1Or2(unit, fc, address, length, timeout) {
|
|
2013
2722
|
const byteCount = Math.ceil(length / 8);
|
|
2014
|
-
const bufferTx = Buffer.
|
|
2723
|
+
const bufferTx = Buffer.allocUnsafe(4);
|
|
2015
2724
|
bufferTx.writeUInt16BE(address, 0);
|
|
2016
2725
|
bufferTx.writeUInt16BE(length, 2);
|
|
2017
2726
|
return this.send({ unit, fc, data: bufferTx }, [(frame) => frame.unit === unit && frame.fc === fc, () => 1 + byteCount, (frame) => frame.data[0] === byteCount], timeout, unit === 0).then((frame) => {
|
|
@@ -2028,7 +2737,7 @@ class ModbusMaster extends EventEmitter {
|
|
|
2028
2737
|
}
|
|
2029
2738
|
writeFC3Or4(unit, fc, address, length, timeout) {
|
|
2030
2739
|
const byteCount = length * 2;
|
|
2031
|
-
const bufferTx = Buffer.
|
|
2740
|
+
const bufferTx = Buffer.allocUnsafe(4);
|
|
2032
2741
|
bufferTx.writeUInt16BE(address, 0);
|
|
2033
2742
|
bufferTx.writeUInt16BE(length, 2);
|
|
2034
2743
|
return this.send({ unit, fc, data: bufferTx }, [(frame) => frame.unit === unit && frame.fc === fc, () => 1 + byteCount, (frame) => frame.data[0] === byteCount], timeout, unit === 0).then((frame) => {
|
|
@@ -2046,7 +2755,7 @@ class ModbusMaster extends EventEmitter {
|
|
|
2046
2755
|
}
|
|
2047
2756
|
writeSingleCoil(unit, address, value, timeout = this.timeout) {
|
|
2048
2757
|
const fc = exports.FunctionCode.WRITE_SINGLE_COIL;
|
|
2049
|
-
const bufferTx = Buffer.
|
|
2758
|
+
const bufferTx = Buffer.allocUnsafe(4);
|
|
2050
2759
|
bufferTx.writeUInt16BE(address, 0);
|
|
2051
2760
|
bufferTx.writeUInt16BE(value ? COIL_ON : COIL_OFF, 2);
|
|
2052
2761
|
return this.send({ unit, fc, data: bufferTx }, [(frame) => frame.unit === unit && frame.fc === fc, () => bufferTx.length, (frame) => frame.data.equals(bufferTx)], timeout, unit === 0).then((frame) => {
|
|
@@ -2057,7 +2766,7 @@ class ModbusMaster extends EventEmitter {
|
|
|
2057
2766
|
}
|
|
2058
2767
|
writeSingleRegister(unit, address, value, timeout = this.timeout) {
|
|
2059
2768
|
const fc = exports.FunctionCode.WRITE_SINGLE_REGISTER;
|
|
2060
|
-
const bufferTx = Buffer.
|
|
2769
|
+
const bufferTx = Buffer.allocUnsafe(4);
|
|
2061
2770
|
bufferTx.writeUInt16BE(address, 0);
|
|
2062
2771
|
bufferTx.writeUInt16BE(value, 2);
|
|
2063
2772
|
return this.send({ unit, fc, data: bufferTx }, [(frame) => frame.unit === unit && frame.fc === fc, () => bufferTx.length, (frame) => frame.data.equals(bufferTx)], timeout, unit === 0).then((frame) => {
|
|
@@ -2087,7 +2796,7 @@ class ModbusMaster extends EventEmitter {
|
|
|
2087
2796
|
writeMultipleRegisters(unit, address, value, timeout = this.timeout) {
|
|
2088
2797
|
const fc = exports.FunctionCode.WRITE_MULTIPLE_REGISTERS;
|
|
2089
2798
|
const byteCount = value.length * 2;
|
|
2090
|
-
const bufferTx = Buffer.
|
|
2799
|
+
const bufferTx = Buffer.allocUnsafe(5 + byteCount);
|
|
2091
2800
|
bufferTx.writeUInt16BE(address, 0);
|
|
2092
2801
|
bufferTx.writeUInt16BE(value.length, 2);
|
|
2093
2802
|
bufferTx.writeUInt8(byteCount, 4);
|
|
@@ -2113,7 +2822,7 @@ class ModbusMaster extends EventEmitter {
|
|
|
2113
2822
|
if (frame) {
|
|
2114
2823
|
const runStatusIndex = 1 + serverIdLength;
|
|
2115
2824
|
return Object.assign(Object.assign({}, frame), { data: {
|
|
2116
|
-
serverId:
|
|
2825
|
+
serverId: Array.from(frame.data.subarray(1, runStatusIndex)),
|
|
2117
2826
|
runIndicatorStatus: frame.data[runStatusIndex] === 0xff,
|
|
2118
2827
|
additionalData: Array.from(frame.data.subarray(runStatusIndex + 1)),
|
|
2119
2828
|
} });
|
|
@@ -2122,7 +2831,7 @@ class ModbusMaster extends EventEmitter {
|
|
|
2122
2831
|
}
|
|
2123
2832
|
maskWriteRegister(unit, address, andMask, orMask, timeout = this.timeout) {
|
|
2124
2833
|
const fc = exports.FunctionCode.MASK_WRITE_REGISTER;
|
|
2125
|
-
const bufferTx = Buffer.
|
|
2834
|
+
const bufferTx = Buffer.allocUnsafe(6);
|
|
2126
2835
|
bufferTx.writeUInt16BE(address, 0);
|
|
2127
2836
|
bufferTx.writeUInt16BE(andMask, 2);
|
|
2128
2837
|
bufferTx.writeUInt16BE(orMask, 4);
|
|
@@ -2136,7 +2845,7 @@ class ModbusMaster extends EventEmitter {
|
|
|
2136
2845
|
const fc = exports.FunctionCode.READ_WRITE_MULTIPLE_REGISTERS;
|
|
2137
2846
|
const byteCount = write.value.length * 2;
|
|
2138
2847
|
const readByteCount = read.length * 2;
|
|
2139
|
-
const bufferTx = Buffer.
|
|
2848
|
+
const bufferTx = Buffer.allocUnsafe(9 + byteCount);
|
|
2140
2849
|
bufferTx.writeUInt16BE(read.address, 0);
|
|
2141
2850
|
bufferTx.writeUInt16BE(read.length, 2);
|
|
2142
2851
|
bufferTx.writeUInt16BE(write.address, 4);
|
|
@@ -2208,10 +2917,14 @@ class ModbusMaster extends EventEmitter {
|
|
|
2208
2917
|
});
|
|
2209
2918
|
}
|
|
2210
2919
|
addCustomFunctionCode(cfc) {
|
|
2211
|
-
|
|
2920
|
+
var _a;
|
|
2921
|
+
this._customFunctionCodes.set(cfc.fc, cfc);
|
|
2922
|
+
(_a = this._appLayer) === null || _a === void 0 ? void 0 : _a.addCustomFunctionCode(cfc);
|
|
2212
2923
|
}
|
|
2213
2924
|
removeCustomFunctionCode(fc) {
|
|
2214
|
-
|
|
2925
|
+
var _a;
|
|
2926
|
+
this._customFunctionCodes.delete(fc);
|
|
2927
|
+
(_a = this._appLayer) === null || _a === void 0 ? void 0 : _a.removeCustomFunctionCode(fc);
|
|
2215
2928
|
}
|
|
2216
2929
|
sendCustomFC(unit, fc, data, timeout = this.timeout) {
|
|
2217
2930
|
const payload = Buffer.isBuffer(data) ? data : Buffer.from(data);
|
|
@@ -2221,85 +2934,107 @@ class ModbusMaster extends EventEmitter {
|
|
|
2221
2934
|
}
|
|
2222
2935
|
});
|
|
2223
2936
|
}
|
|
2224
|
-
_clean(
|
|
2225
|
-
if (this._cleanLevel === 'destroy') {
|
|
2226
|
-
return;
|
|
2227
|
-
}
|
|
2228
|
-
if (this._cleanLevel === 'close' && level === 'close') {
|
|
2229
|
-
return;
|
|
2230
|
-
}
|
|
2231
|
-
const errorCode = level === 'destroy' ? ModbusErrorCode.MASTER_DESTROYED : ModbusErrorCode.MASTER_CLOSED;
|
|
2232
|
-
const message = level === 'destroy' ? 'Master destroyed' : 'Master closed';
|
|
2233
|
-
this._closed = true;
|
|
2937
|
+
_clean(message) {
|
|
2234
2938
|
const queued = this._queue.splice(0);
|
|
2235
2939
|
for (const item of queued) {
|
|
2236
|
-
item.cancel(new
|
|
2237
|
-
}
|
|
2238
|
-
this._masterSession.stopAll(new
|
|
2239
|
-
|
|
2240
|
-
|
|
2940
|
+
item.cancel(new Error(message));
|
|
2941
|
+
}
|
|
2942
|
+
this._masterSession.stopAll(new Error(message));
|
|
2943
|
+
}
|
|
2944
|
+
/**
|
|
2945
|
+
* Open the underlying physical layer and begin accepting connections.
|
|
2946
|
+
*
|
|
2947
|
+
* A `ModbusMaster` instance can only be opened once. Once {@link close}
|
|
2948
|
+
* is called — explicitly or because the physical layer disconnected —
|
|
2949
|
+
* the instance is permanently closed and cannot be reopened.
|
|
2950
|
+
* Create a new `ModbusMaster` if a new connection is required.
|
|
2951
|
+
*/
|
|
2241
2952
|
open(...args) {
|
|
2242
|
-
if (this.
|
|
2243
|
-
return Promise.reject(new
|
|
2953
|
+
if (this._closePromise) {
|
|
2954
|
+
return Promise.reject(new Error('The port has been permanently closed and CANNOT be reopened'));
|
|
2244
2955
|
}
|
|
2245
|
-
this.
|
|
2246
|
-
this._closed = false;
|
|
2247
|
-
this._nextTid = 1;
|
|
2248
|
-
return this.physicalLayer.open(...args);
|
|
2956
|
+
return this._physicalLayer.open(...args);
|
|
2249
2957
|
}
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2958
|
+
_close() {
|
|
2959
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
2960
|
+
for (const fn of this._cleanupFns) {
|
|
2961
|
+
fn();
|
|
2962
|
+
}
|
|
2963
|
+
this._cleanupFns.clear();
|
|
2964
|
+
this._clean('Master is closing');
|
|
2965
|
+
let err = null;
|
|
2966
|
+
try {
|
|
2967
|
+
yield this._physicalLayer.close();
|
|
2968
|
+
}
|
|
2969
|
+
catch (error) {
|
|
2970
|
+
err = error;
|
|
2971
|
+
}
|
|
2972
|
+
this.removeAllListeners();
|
|
2973
|
+
if (err) {
|
|
2974
|
+
return Promise.reject(err);
|
|
2975
|
+
}
|
|
2976
|
+
});
|
|
2256
2977
|
}
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2978
|
+
/**
|
|
2979
|
+
* Permanently close the master and release all resources.
|
|
2980
|
+
*
|
|
2981
|
+
* After calling this method the instance is considered dead:
|
|
2982
|
+
* - No further requests can be sent.
|
|
2983
|
+
* - The instance cannot be reopened via {@link open}.
|
|
2984
|
+
* - All event listeners registered on this master are removed.
|
|
2985
|
+
*/
|
|
2986
|
+
close() {
|
|
2987
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
2988
|
+
if (this._closePromise) {
|
|
2989
|
+
return this._closePromise;
|
|
2990
|
+
}
|
|
2991
|
+
this._closePromise = this._close();
|
|
2992
|
+
return this._closePromise;
|
|
2993
|
+
});
|
|
2265
2994
|
}
|
|
2266
2995
|
}
|
|
2267
2996
|
|
|
2997
|
+
function prependByte(byte, buffer) {
|
|
2998
|
+
const result = Buffer.allocUnsafe(buffer.length + 1);
|
|
2999
|
+
result[0] = byte;
|
|
3000
|
+
buffer.copy(result, 1);
|
|
3001
|
+
return result;
|
|
3002
|
+
}
|
|
2268
3003
|
class ModbusSlave extends EventEmitter {
|
|
2269
|
-
get
|
|
2270
|
-
return this.
|
|
3004
|
+
get state() {
|
|
3005
|
+
return this._physicalLayer.state;
|
|
2271
3006
|
}
|
|
2272
|
-
get
|
|
2273
|
-
return this.
|
|
3007
|
+
get physicalLayer() {
|
|
3008
|
+
return this._physicalLayer;
|
|
2274
3009
|
}
|
|
2275
|
-
constructor(
|
|
3010
|
+
constructor(options) {
|
|
2276
3011
|
var _a;
|
|
2277
3012
|
super();
|
|
2278
|
-
Object.defineProperty(this, "
|
|
3013
|
+
Object.defineProperty(this, "models", {
|
|
2279
3014
|
enumerable: true,
|
|
2280
3015
|
configurable: true,
|
|
2281
3016
|
writable: true,
|
|
2282
|
-
value:
|
|
3017
|
+
value: new Map()
|
|
2283
3018
|
});
|
|
2284
|
-
Object.defineProperty(this, "
|
|
3019
|
+
Object.defineProperty(this, "concurrent", {
|
|
2285
3020
|
enumerable: true,
|
|
2286
3021
|
configurable: true,
|
|
2287
3022
|
writable: true,
|
|
2288
|
-
value:
|
|
3023
|
+
value: void 0
|
|
2289
3024
|
});
|
|
2290
|
-
Object.defineProperty(this, "
|
|
3025
|
+
Object.defineProperty(this, "_physicalLayer", {
|
|
2291
3026
|
enumerable: true,
|
|
2292
3027
|
configurable: true,
|
|
2293
3028
|
writable: true,
|
|
2294
|
-
value:
|
|
3029
|
+
value: void 0
|
|
2295
3030
|
});
|
|
2296
|
-
Object.defineProperty(this, "
|
|
3031
|
+
Object.defineProperty(this, "_protocol", {
|
|
2297
3032
|
enumerable: true,
|
|
2298
3033
|
configurable: true,
|
|
2299
3034
|
writable: true,
|
|
2300
3035
|
value: void 0
|
|
2301
3036
|
});
|
|
2302
|
-
Object.defineProperty(this, "
|
|
3037
|
+
Object.defineProperty(this, "_appLayers", {
|
|
2303
3038
|
enumerable: true,
|
|
2304
3039
|
configurable: true,
|
|
2305
3040
|
writable: true,
|
|
@@ -2317,72 +3052,112 @@ class ModbusSlave extends EventEmitter {
|
|
|
2317
3052
|
writable: true,
|
|
2318
3053
|
value: new Map()
|
|
2319
3054
|
});
|
|
2320
|
-
Object.defineProperty(this, "
|
|
3055
|
+
Object.defineProperty(this, "_cleanupFns", {
|
|
2321
3056
|
enumerable: true,
|
|
2322
3057
|
configurable: true,
|
|
2323
3058
|
writable: true,
|
|
2324
|
-
value:
|
|
2325
|
-
});
|
|
2326
|
-
this.concurrent = (_a = options.concurrent) !== null && _a !== void 0 ? _a : false;
|
|
2327
|
-
if (this.concurrent && this.applicationLayer.PROTOCOL !== 'TCP') {
|
|
2328
|
-
throw new ModbusError(ModbusErrorCode.CONCURRENT_NOT_TCP, 'concurrent mode requires a Modbus TCP application layer');
|
|
2329
|
-
}
|
|
2330
|
-
this.applicationLayer.role = 'SLAVE';
|
|
2331
|
-
applicationLayer.on('framing', (frame, response, connection) => {
|
|
2332
|
-
if (!(frame.unit === 0x00 || this.models.has(frame.unit))) {
|
|
2333
|
-
return;
|
|
2334
|
-
}
|
|
2335
|
-
if (this.concurrent) {
|
|
2336
|
-
this._processFrame(frame, response).catch((error) => this.emit('error', error));
|
|
2337
|
-
return;
|
|
2338
|
-
}
|
|
2339
|
-
let q = this._queues.get(connection.id);
|
|
2340
|
-
if (!q) {
|
|
2341
|
-
q = { items: [], processing: false };
|
|
2342
|
-
this._queues.set(connection.id, q);
|
|
2343
|
-
}
|
|
2344
|
-
q.items.push({ frame, response });
|
|
2345
|
-
this._drain(connection.id, q);
|
|
2346
|
-
});
|
|
2347
|
-
physicalLayer.on('error', (error) => {
|
|
2348
|
-
this.emit('error', error);
|
|
3059
|
+
value: new Set()
|
|
2349
3060
|
});
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
q.items.length = 0;
|
|
2356
|
-
if (!q.processing) {
|
|
2357
|
-
this._queues.delete(connection.id);
|
|
2358
|
-
}
|
|
3061
|
+
Object.defineProperty(this, "_closePromise", {
|
|
3062
|
+
enumerable: true,
|
|
3063
|
+
configurable: true,
|
|
3064
|
+
writable: true,
|
|
3065
|
+
value: null
|
|
2359
3066
|
});
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
3067
|
+
this.concurrent = (_a = options.concurrent) !== null && _a !== void 0 ? _a : false;
|
|
3068
|
+
this._physicalLayer = createPhysicalLayer(options.physical);
|
|
3069
|
+
this._protocol = options.protocol;
|
|
3070
|
+
if (this.concurrent && options.protocol.type !== 'TCP') {
|
|
3071
|
+
throw new Error('concurrent mode requires a Modbus TCP application layer');
|
|
3072
|
+
}
|
|
3073
|
+
const onOpen = () => this.emit('open');
|
|
3074
|
+
this._physicalLayer.on('open', onOpen);
|
|
3075
|
+
this._cleanupFns.add(() => this._physicalLayer.off('open', onOpen));
|
|
3076
|
+
const onConnect = (connection) => {
|
|
3077
|
+
const appLayer = this._createAppLayer(connection);
|
|
3078
|
+
const appLayerData = { queues: { frames: [], processing: false } };
|
|
3079
|
+
this._appLayers.set(appLayer, appLayerData);
|
|
3080
|
+
for (const cfc of this._customFunctionCodes.values()) {
|
|
3081
|
+
appLayer.addCustomFunctionCode(cfc);
|
|
3082
|
+
}
|
|
3083
|
+
const cleanupFraming = () => appLayer.off('framing', onFraming);
|
|
3084
|
+
const onFraming = (frame) => {
|
|
3085
|
+
if (this._physicalLayer.state !== exports.PhysicalState.OPEN) {
|
|
3086
|
+
return;
|
|
3087
|
+
}
|
|
3088
|
+
if (!(frame.unit === 0x00 || this.models.has(frame.unit))) {
|
|
3089
|
+
return;
|
|
3090
|
+
}
|
|
3091
|
+
if (this.concurrent) {
|
|
3092
|
+
this._processFrame(appLayer, frame);
|
|
3093
|
+
return;
|
|
3094
|
+
}
|
|
3095
|
+
appLayerData.queues.frames.push(frame);
|
|
3096
|
+
this._drain(appLayer, appLayerData.queues);
|
|
3097
|
+
};
|
|
3098
|
+
appLayer.on('framing', onFraming);
|
|
3099
|
+
this._cleanupFns.add(cleanupFraming);
|
|
3100
|
+
const cleanupClose = () => connection.off('close', onClose);
|
|
3101
|
+
const onClose = () => {
|
|
3102
|
+
cleanupFraming();
|
|
3103
|
+
cleanupClose();
|
|
3104
|
+
this._cleanupFns.delete(cleanupFraming);
|
|
3105
|
+
this._cleanupFns.delete(cleanupClose);
|
|
3106
|
+
this._appLayers.delete(appLayer);
|
|
3107
|
+
};
|
|
3108
|
+
connection.on('close', onClose);
|
|
3109
|
+
this._cleanupFns.add(cleanupClose);
|
|
3110
|
+
this.emit('connect', connection);
|
|
3111
|
+
};
|
|
3112
|
+
this._physicalLayer.on('connect', onConnect);
|
|
3113
|
+
this._cleanupFns.add(() => this._physicalLayer.off('connect', onConnect));
|
|
3114
|
+
const onClose = () => {
|
|
2365
3115
|
this.emit('close');
|
|
2366
|
-
|
|
3116
|
+
this.close();
|
|
3117
|
+
};
|
|
3118
|
+
this._physicalLayer.on('close', onClose);
|
|
3119
|
+
this._cleanupFns.add(() => this._physicalLayer.off('close', onClose));
|
|
3120
|
+
const onError = (err) => {
|
|
3121
|
+
this.emit('error', err);
|
|
3122
|
+
};
|
|
3123
|
+
this._physicalLayer.on('error', onError);
|
|
3124
|
+
this._cleanupFns.add(() => this._physicalLayer.off('error', onError));
|
|
3125
|
+
}
|
|
3126
|
+
_createAppLayer(connection) {
|
|
3127
|
+
var _a;
|
|
3128
|
+
if (this._protocol.type === 'RTU') {
|
|
3129
|
+
const baudRate = this._physicalLayer.is('SERIAL') ? this._physicalLayer.baudRate : undefined;
|
|
3130
|
+
const { intervalBetweenFrames, interCharTimeout } = resolveRtuTiming(this._protocol.opts, baudRate);
|
|
3131
|
+
return new RtuApplicationLayer('SLAVE', connection, {
|
|
3132
|
+
intervalBetweenFrames,
|
|
3133
|
+
interCharTimeout,
|
|
3134
|
+
poolSize: (_a = this._protocol.opts) === null || _a === void 0 ? void 0 : _a.poolSize,
|
|
3135
|
+
});
|
|
3136
|
+
}
|
|
3137
|
+
if (this._protocol.type === 'TCP') {
|
|
3138
|
+
return new TcpApplicationLayer('SLAVE', connection);
|
|
3139
|
+
}
|
|
3140
|
+
return new AsciiApplicationLayer('SLAVE', connection, this._protocol.opts);
|
|
2367
3141
|
}
|
|
2368
|
-
handleFC1(model, frame, response) {
|
|
3142
|
+
handleFC1(appLayer, model, frame, response) {
|
|
2369
3143
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2370
3144
|
var _a;
|
|
2371
3145
|
if (frame.data.length !== 4) {
|
|
3146
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2372
3147
|
return;
|
|
2373
3148
|
}
|
|
2374
3149
|
if (!model.readCoils) {
|
|
2375
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
3150
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
2376
3151
|
return;
|
|
2377
3152
|
}
|
|
2378
3153
|
const address = (frame.data[0] << 8) | frame.data[1];
|
|
2379
3154
|
const length = (frame.data[2] << 8) | frame.data[3];
|
|
2380
3155
|
if (length < LIMITS.READ_COILS_MIN || length > LIMITS.READ_COILS_MAX) {
|
|
2381
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
3156
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2382
3157
|
return;
|
|
2383
3158
|
}
|
|
2384
3159
|
if (!checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).coils)) {
|
|
2385
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
3160
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
2386
3161
|
return;
|
|
2387
3162
|
}
|
|
2388
3163
|
try {
|
|
@@ -2393,31 +3168,32 @@ class ModbusSlave extends EventEmitter {
|
|
|
2393
3168
|
bufferTx[Math.floor(index / 8)] |= 1 << index % 8;
|
|
2394
3169
|
}
|
|
2395
3170
|
});
|
|
2396
|
-
yield response(
|
|
3171
|
+
yield response(appLayer.encode(Object.assign(Object.assign({}, frame), { data: prependByte(bufferTx.length, bufferTx) })));
|
|
2397
3172
|
}
|
|
2398
3173
|
catch (error) {
|
|
2399
|
-
yield this.responseError(frame, response, error);
|
|
3174
|
+
yield this.responseError(appLayer, frame, response, error);
|
|
2400
3175
|
}
|
|
2401
3176
|
});
|
|
2402
3177
|
}
|
|
2403
|
-
handleFC2(model, frame, response) {
|
|
3178
|
+
handleFC2(appLayer, model, frame, response) {
|
|
2404
3179
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2405
3180
|
var _a;
|
|
2406
3181
|
if (frame.data.length !== 4) {
|
|
3182
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2407
3183
|
return;
|
|
2408
3184
|
}
|
|
2409
3185
|
if (!model.readDiscreteInputs) {
|
|
2410
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
3186
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
2411
3187
|
return;
|
|
2412
3188
|
}
|
|
2413
3189
|
const address = (frame.data[0] << 8) | frame.data[1];
|
|
2414
3190
|
const length = (frame.data[2] << 8) | frame.data[3];
|
|
2415
3191
|
if (length < LIMITS.READ_COILS_MIN || length > LIMITS.READ_COILS_MAX) {
|
|
2416
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
3192
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2417
3193
|
return;
|
|
2418
3194
|
}
|
|
2419
3195
|
if (!checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).discreteInputs)) {
|
|
2420
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
3196
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
2421
3197
|
return;
|
|
2422
3198
|
}
|
|
2423
3199
|
try {
|
|
@@ -2428,152 +3204,157 @@ class ModbusSlave extends EventEmitter {
|
|
|
2428
3204
|
bufferTx[Math.floor(index / 8)] |= 1 << index % 8;
|
|
2429
3205
|
}
|
|
2430
3206
|
});
|
|
2431
|
-
yield response(
|
|
3207
|
+
yield response(appLayer.encode(Object.assign(Object.assign({}, frame), { data: prependByte(bufferTx.length, bufferTx) })));
|
|
2432
3208
|
}
|
|
2433
3209
|
catch (error) {
|
|
2434
|
-
yield this.responseError(frame, response, error);
|
|
3210
|
+
yield this.responseError(appLayer, frame, response, error);
|
|
2435
3211
|
}
|
|
2436
3212
|
});
|
|
2437
3213
|
}
|
|
2438
|
-
handleFC3(model, frame, response) {
|
|
3214
|
+
handleFC3(appLayer, model, frame, response) {
|
|
2439
3215
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2440
3216
|
var _a;
|
|
2441
3217
|
if (frame.data.length !== 4) {
|
|
3218
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2442
3219
|
return;
|
|
2443
3220
|
}
|
|
2444
3221
|
if (!model.readHoldingRegisters) {
|
|
2445
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
3222
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
2446
3223
|
return;
|
|
2447
3224
|
}
|
|
2448
3225
|
const address = (frame.data[0] << 8) | frame.data[1];
|
|
2449
3226
|
const length = (frame.data[2] << 8) | frame.data[3];
|
|
2450
3227
|
if (length < LIMITS.READ_REGISTERS_MIN || length > LIMITS.READ_REGISTERS_MAX) {
|
|
2451
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
3228
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2452
3229
|
return;
|
|
2453
3230
|
}
|
|
2454
3231
|
if (!checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).holdingRegisters)) {
|
|
2455
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
3232
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
2456
3233
|
return;
|
|
2457
3234
|
}
|
|
2458
3235
|
try {
|
|
2459
3236
|
const registers = yield model.readHoldingRegisters(address, length);
|
|
2460
|
-
const bufferTx = Buffer.
|
|
3237
|
+
const bufferTx = Buffer.allocUnsafe(length * 2);
|
|
2461
3238
|
registers.forEach((register, index) => {
|
|
2462
3239
|
bufferTx.writeUInt16BE(register, index * 2);
|
|
2463
3240
|
});
|
|
2464
|
-
yield response(
|
|
3241
|
+
yield response(appLayer.encode(Object.assign(Object.assign({}, frame), { data: prependByte(bufferTx.length, bufferTx) })));
|
|
2465
3242
|
}
|
|
2466
3243
|
catch (error) {
|
|
2467
|
-
yield this.responseError(frame, response, error);
|
|
3244
|
+
yield this.responseError(appLayer, frame, response, error);
|
|
2468
3245
|
}
|
|
2469
3246
|
});
|
|
2470
3247
|
}
|
|
2471
|
-
handleFC4(model, frame, response) {
|
|
3248
|
+
handleFC4(appLayer, model, frame, response) {
|
|
2472
3249
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2473
3250
|
var _a;
|
|
2474
3251
|
if (frame.data.length !== 4) {
|
|
3252
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2475
3253
|
return;
|
|
2476
3254
|
}
|
|
2477
3255
|
if (!model.readInputRegisters) {
|
|
2478
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
3256
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
2479
3257
|
return;
|
|
2480
3258
|
}
|
|
2481
3259
|
const address = (frame.data[0] << 8) | frame.data[1];
|
|
2482
3260
|
const length = (frame.data[2] << 8) | frame.data[3];
|
|
2483
3261
|
if (length < LIMITS.READ_REGISTERS_MIN || length > LIMITS.READ_REGISTERS_MAX) {
|
|
2484
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
3262
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2485
3263
|
return;
|
|
2486
3264
|
}
|
|
2487
3265
|
if (!checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).inputRegisters)) {
|
|
2488
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
3266
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
2489
3267
|
return;
|
|
2490
3268
|
}
|
|
2491
3269
|
try {
|
|
2492
3270
|
const registers = yield model.readInputRegisters(address, length);
|
|
2493
|
-
const bufferTx = Buffer.
|
|
3271
|
+
const bufferTx = Buffer.allocUnsafe(length * 2);
|
|
2494
3272
|
registers.forEach((register, index) => {
|
|
2495
3273
|
bufferTx.writeUInt16BE(register, index * 2);
|
|
2496
3274
|
});
|
|
2497
|
-
yield response(
|
|
3275
|
+
yield response(appLayer.encode(Object.assign(Object.assign({}, frame), { data: prependByte(bufferTx.length, bufferTx) })));
|
|
2498
3276
|
}
|
|
2499
3277
|
catch (error) {
|
|
2500
|
-
yield this.responseError(frame, response, error);
|
|
3278
|
+
yield this.responseError(appLayer, frame, response, error);
|
|
2501
3279
|
}
|
|
2502
3280
|
});
|
|
2503
3281
|
}
|
|
2504
|
-
handleFC5(model, frame, response) {
|
|
3282
|
+
handleFC5(appLayer, model, frame, response) {
|
|
2505
3283
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2506
3284
|
var _a;
|
|
2507
3285
|
if (frame.data.length !== 4) {
|
|
3286
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2508
3287
|
return;
|
|
2509
3288
|
}
|
|
2510
3289
|
if (!model.writeSingleCoil) {
|
|
2511
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
3290
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
2512
3291
|
return;
|
|
2513
3292
|
}
|
|
2514
3293
|
const address = (frame.data[0] << 8) | frame.data[1];
|
|
2515
3294
|
const value = (frame.data[2] << 8) | frame.data[3];
|
|
2516
3295
|
if (value !== COIL_OFF && value !== COIL_ON) {
|
|
2517
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
3296
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2518
3297
|
return;
|
|
2519
3298
|
}
|
|
2520
3299
|
if (!checkRange(address, (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).coils)) {
|
|
2521
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
3300
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
2522
3301
|
return;
|
|
2523
3302
|
}
|
|
2524
3303
|
try {
|
|
2525
3304
|
yield model.writeSingleCoil(address, value === COIL_ON);
|
|
2526
|
-
yield response(
|
|
3305
|
+
yield response(appLayer.encode(frame));
|
|
2527
3306
|
}
|
|
2528
3307
|
catch (error) {
|
|
2529
|
-
yield this.responseError(frame, response, error);
|
|
3308
|
+
yield this.responseError(appLayer, frame, response, error);
|
|
2530
3309
|
}
|
|
2531
3310
|
});
|
|
2532
3311
|
}
|
|
2533
|
-
handleFC6(model, frame, response) {
|
|
3312
|
+
handleFC6(appLayer, model, frame, response) {
|
|
2534
3313
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2535
3314
|
var _a;
|
|
2536
3315
|
if (frame.data.length !== 4) {
|
|
3316
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2537
3317
|
return;
|
|
2538
3318
|
}
|
|
2539
3319
|
if (!model.writeSingleRegister) {
|
|
2540
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
3320
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
2541
3321
|
return;
|
|
2542
3322
|
}
|
|
2543
3323
|
const address = (frame.data[0] << 8) | frame.data[1];
|
|
2544
3324
|
const value = (frame.data[2] << 8) | frame.data[3];
|
|
2545
3325
|
if (!checkRange(address, (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).holdingRegisters)) {
|
|
2546
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
3326
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
2547
3327
|
return;
|
|
2548
3328
|
}
|
|
2549
3329
|
try {
|
|
2550
3330
|
yield model.writeSingleRegister(address, value);
|
|
2551
|
-
yield response(
|
|
3331
|
+
yield response(appLayer.encode(frame));
|
|
2552
3332
|
}
|
|
2553
3333
|
catch (error) {
|
|
2554
|
-
yield this.responseError(frame, response, error);
|
|
3334
|
+
yield this.responseError(appLayer, frame, response, error);
|
|
2555
3335
|
}
|
|
2556
3336
|
});
|
|
2557
3337
|
}
|
|
2558
|
-
handleFC15(model, frame, response) {
|
|
3338
|
+
handleFC15(appLayer, model, frame, response) {
|
|
2559
3339
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2560
3340
|
var _a;
|
|
2561
3341
|
if (frame.data.length <= 5 || frame.data.length !== 5 + frame.data[4]) {
|
|
3342
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2562
3343
|
return;
|
|
2563
3344
|
}
|
|
2564
3345
|
if (!model.writeMultipleCoils && !model.writeSingleCoil) {
|
|
2565
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
3346
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
2566
3347
|
return;
|
|
2567
3348
|
}
|
|
2568
3349
|
const address = (frame.data[0] << 8) | frame.data[1];
|
|
2569
3350
|
const length = (frame.data[2] << 8) | frame.data[3];
|
|
2570
3351
|
const byteCount = frame.data[4];
|
|
2571
3352
|
if (length < LIMITS.READ_COILS_MIN || length > LIMITS.WRITE_COILS_MAX || byteCount !== Math.ceil(length / 8)) {
|
|
2572
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
3353
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2573
3354
|
return;
|
|
2574
3355
|
}
|
|
2575
3356
|
if (!checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).coils)) {
|
|
2576
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
3357
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
2577
3358
|
return;
|
|
2578
3359
|
}
|
|
2579
3360
|
const value = new Array(length);
|
|
@@ -2589,32 +3370,33 @@ class ModbusSlave extends EventEmitter {
|
|
|
2589
3370
|
yield model.writeSingleCoil(address + i, value[i]);
|
|
2590
3371
|
}
|
|
2591
3372
|
}
|
|
2592
|
-
yield response(
|
|
3373
|
+
yield response(appLayer.encode(Object.assign(Object.assign({}, frame), { data: frame.data.subarray(0, 4) })));
|
|
2593
3374
|
}
|
|
2594
3375
|
catch (error) {
|
|
2595
|
-
yield this.responseError(frame, response, error);
|
|
3376
|
+
yield this.responseError(appLayer, frame, response, error);
|
|
2596
3377
|
}
|
|
2597
3378
|
});
|
|
2598
3379
|
}
|
|
2599
|
-
handleFC16(model, frame, response) {
|
|
3380
|
+
handleFC16(appLayer, model, frame, response) {
|
|
2600
3381
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2601
3382
|
var _a;
|
|
2602
3383
|
if (frame.data.length <= 5 || frame.data.length !== 5 + frame.data[4]) {
|
|
3384
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2603
3385
|
return;
|
|
2604
3386
|
}
|
|
2605
3387
|
if (!model.writeMultipleRegisters && !model.writeSingleRegister) {
|
|
2606
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
3388
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
2607
3389
|
return;
|
|
2608
3390
|
}
|
|
2609
3391
|
const address = (frame.data[0] << 8) | frame.data[1];
|
|
2610
3392
|
const length = (frame.data[2] << 8) | frame.data[3];
|
|
2611
3393
|
const byteCount = frame.data[4];
|
|
2612
3394
|
if (length < LIMITS.READ_REGISTERS_MIN || length > LIMITS.WRITE_REGISTERS_MAX || byteCount !== length * 2) {
|
|
2613
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
3395
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2614
3396
|
return;
|
|
2615
3397
|
}
|
|
2616
3398
|
if (!checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).holdingRegisters)) {
|
|
2617
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
3399
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
2618
3400
|
return;
|
|
2619
3401
|
}
|
|
2620
3402
|
const value = new Array(length);
|
|
@@ -2630,62 +3412,67 @@ class ModbusSlave extends EventEmitter {
|
|
|
2630
3412
|
yield model.writeSingleRegister(address + i, value[i]);
|
|
2631
3413
|
}
|
|
2632
3414
|
}
|
|
2633
|
-
yield response(
|
|
3415
|
+
yield response(appLayer.encode(Object.assign(Object.assign({}, frame), { data: frame.data.subarray(0, 4) })));
|
|
2634
3416
|
}
|
|
2635
3417
|
catch (error) {
|
|
2636
|
-
yield this.responseError(frame, response, error);
|
|
3418
|
+
yield this.responseError(appLayer, frame, response, error);
|
|
2637
3419
|
}
|
|
2638
3420
|
});
|
|
2639
3421
|
}
|
|
2640
|
-
handleFC17(model, frame, response) {
|
|
3422
|
+
handleFC17(appLayer, model, frame, response) {
|
|
2641
3423
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2642
3424
|
var _a;
|
|
2643
3425
|
if (frame.data.length !== 0) {
|
|
3426
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2644
3427
|
return;
|
|
2645
3428
|
}
|
|
2646
3429
|
if (!model.reportServerId) {
|
|
2647
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
3430
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
2648
3431
|
return;
|
|
2649
3432
|
}
|
|
2650
3433
|
try {
|
|
2651
|
-
const { serverId = (_a = model.unit) !== null && _a !== void 0 ? _a : 1, runIndicatorStatus = true, additionalData = [] } = yield model.reportServerId();
|
|
2652
|
-
const serverIdBytes =
|
|
3434
|
+
const { serverId = [(_a = model.unit) !== null && _a !== void 0 ? _a : 1], runIndicatorStatus = true, additionalData = [] } = yield model.reportServerId();
|
|
3435
|
+
const serverIdBytes = serverId;
|
|
2653
3436
|
const byteCount = serverIdBytes.length + 1 + additionalData.length;
|
|
2654
3437
|
if (byteCount > 255) {
|
|
2655
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.SERVER_DEVICE_FAILURE));
|
|
3438
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.SERVER_DEVICE_FAILURE));
|
|
2656
3439
|
return;
|
|
2657
3440
|
}
|
|
2658
3441
|
const allBytes = [...serverIdBytes, runIndicatorStatus ? 0xff : 0x00, ...additionalData];
|
|
2659
3442
|
if (allBytes.some((b) => !isUint8(b))) {
|
|
2660
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.SERVER_DEVICE_FAILURE));
|
|
3443
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.SERVER_DEVICE_FAILURE));
|
|
2661
3444
|
return;
|
|
2662
3445
|
}
|
|
2663
|
-
|
|
3446
|
+
const data = Buffer.allocUnsafe(byteCount + 1);
|
|
3447
|
+
data[0] = byteCount;
|
|
3448
|
+
data.set(allBytes, 1);
|
|
3449
|
+
yield response(appLayer.encode(Object.assign(Object.assign({}, frame), { data })));
|
|
2664
3450
|
}
|
|
2665
3451
|
catch (error) {
|
|
2666
|
-
yield this.responseError(frame, response, error);
|
|
3452
|
+
yield this.responseError(appLayer, frame, response, error);
|
|
2667
3453
|
}
|
|
2668
3454
|
});
|
|
2669
3455
|
}
|
|
2670
|
-
handleFC22(model, frame, response) {
|
|
3456
|
+
handleFC22(appLayer, model, frame, response) {
|
|
2671
3457
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2672
3458
|
var _a;
|
|
2673
3459
|
if (frame.data.length !== 6) {
|
|
3460
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2674
3461
|
return;
|
|
2675
3462
|
}
|
|
2676
3463
|
if (!model.maskWriteRegister && !(model.readHoldingRegisters && model.writeSingleRegister)) {
|
|
2677
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
3464
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
2678
3465
|
return;
|
|
2679
3466
|
}
|
|
2680
3467
|
const address = (frame.data[0] << 8) | frame.data[1];
|
|
2681
3468
|
const andMask = (frame.data[2] << 8) | frame.data[3];
|
|
2682
3469
|
const orMask = (frame.data[4] << 8) | frame.data[5];
|
|
2683
3470
|
if (!checkRange(address, (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).holdingRegisters)) {
|
|
2684
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
3471
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
2685
3472
|
return;
|
|
2686
3473
|
}
|
|
2687
3474
|
try {
|
|
2688
|
-
yield this.
|
|
3475
|
+
yield this._withAddressLock([address], () => __awaiter(this, void 0, void 0, function* () {
|
|
2689
3476
|
if (model.maskWriteRegister) {
|
|
2690
3477
|
yield model.maskWriteRegister(address, andMask, orMask);
|
|
2691
3478
|
}
|
|
@@ -2694,21 +3481,22 @@ class ModbusSlave extends EventEmitter {
|
|
|
2694
3481
|
yield model.writeSingleRegister(address, (value & andMask) | (orMask & (~andMask & 0xffff)));
|
|
2695
3482
|
}
|
|
2696
3483
|
}));
|
|
2697
|
-
yield response(
|
|
3484
|
+
yield response(appLayer.encode(frame));
|
|
2698
3485
|
}
|
|
2699
3486
|
catch (error) {
|
|
2700
|
-
yield this.responseError(frame, response, error);
|
|
3487
|
+
yield this.responseError(appLayer, frame, response, error);
|
|
2701
3488
|
}
|
|
2702
3489
|
});
|
|
2703
3490
|
}
|
|
2704
|
-
handleFC23(model, frame, response) {
|
|
3491
|
+
handleFC23(appLayer, model, frame, response) {
|
|
2705
3492
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2706
3493
|
var _a;
|
|
2707
3494
|
if (frame.data.length <= 9 || frame.data.length !== 9 + frame.data[8]) {
|
|
3495
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2708
3496
|
return;
|
|
2709
3497
|
}
|
|
2710
3498
|
if (!model.readHoldingRegisters || (!model.writeMultipleRegisters && !model.writeSingleRegister)) {
|
|
2711
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
3499
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
2712
3500
|
return;
|
|
2713
3501
|
}
|
|
2714
3502
|
const address = {
|
|
@@ -2725,11 +3513,11 @@ class ModbusSlave extends EventEmitter {
|
|
|
2725
3513
|
length.write < LIMITS.READ_REGISTERS_MIN ||
|
|
2726
3514
|
length.write > LIMITS.RW_REGISTERS_WRITE_MAX ||
|
|
2727
3515
|
byteCount !== length.write * 2) {
|
|
2728
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
3516
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2729
3517
|
return;
|
|
2730
3518
|
}
|
|
2731
3519
|
if (!checkRange([address.read, address.read + length.read, address.write, address.write + length.write], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).holdingRegisters)) {
|
|
2732
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
3520
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
2733
3521
|
return;
|
|
2734
3522
|
}
|
|
2735
3523
|
const value = new Array(length.write);
|
|
@@ -2738,7 +3526,7 @@ class ModbusSlave extends EventEmitter {
|
|
|
2738
3526
|
}
|
|
2739
3527
|
try {
|
|
2740
3528
|
const writeAddresses = Array.from({ length: length.write }, (_, i) => address.write + i);
|
|
2741
|
-
yield this.
|
|
3529
|
+
yield this._withAddressLock(writeAddresses, () => __awaiter(this, void 0, void 0, function* () {
|
|
2742
3530
|
if (model.writeMultipleRegisters) {
|
|
2743
3531
|
yield model.writeMultipleRegisters(address.write, value);
|
|
2744
3532
|
}
|
|
@@ -2749,18 +3537,18 @@ class ModbusSlave extends EventEmitter {
|
|
|
2749
3537
|
}
|
|
2750
3538
|
}));
|
|
2751
3539
|
const registers = yield model.readHoldingRegisters(address.read, length.read);
|
|
2752
|
-
const bufferTx = Buffer.
|
|
3540
|
+
const bufferTx = Buffer.allocUnsafe(length.read * 2);
|
|
2753
3541
|
registers.forEach((register, index) => {
|
|
2754
3542
|
bufferTx.writeUInt16BE(register, index * 2);
|
|
2755
3543
|
});
|
|
2756
|
-
yield response(
|
|
3544
|
+
yield response(appLayer.encode(Object.assign(Object.assign({}, frame), { data: prependByte(bufferTx.length, bufferTx) })));
|
|
2757
3545
|
}
|
|
2758
3546
|
catch (error) {
|
|
2759
|
-
yield this.responseError(frame, response, error);
|
|
3547
|
+
yield this.responseError(appLayer, frame, response, error);
|
|
2760
3548
|
}
|
|
2761
3549
|
});
|
|
2762
3550
|
}
|
|
2763
|
-
handleFC43_14(model, frame, response) {
|
|
3551
|
+
handleFC43_14(appLayer, model, frame, response) {
|
|
2764
3552
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2765
3553
|
if (frame.data.length === 3) {
|
|
2766
3554
|
if (frame.data[0] === MEI_READ_DEVICE_ID && model.readDeviceIdentification) {
|
|
@@ -2787,13 +3575,13 @@ class ModbusSlave extends EventEmitter {
|
|
|
2787
3575
|
}
|
|
2788
3576
|
case exports.ReadDeviceIDCode.SPECIFIC_ACCESS: {
|
|
2789
3577
|
if (objectID > 0x06 && objectID < 0x80) {
|
|
2790
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
3578
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
2791
3579
|
return;
|
|
2792
3580
|
}
|
|
2793
3581
|
break;
|
|
2794
3582
|
}
|
|
2795
3583
|
default: {
|
|
2796
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
3584
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2797
3585
|
return;
|
|
2798
3586
|
}
|
|
2799
3587
|
}
|
|
@@ -2813,7 +3601,7 @@ class ModbusSlave extends EventEmitter {
|
|
|
2813
3601
|
}
|
|
2814
3602
|
if (!objects.has(objectID)) {
|
|
2815
3603
|
if (readDeviceIDCode === exports.ReadDeviceIDCode.SPECIFIC_ACCESS) {
|
|
2816
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
3604
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
2817
3605
|
return;
|
|
2818
3606
|
}
|
|
2819
3607
|
objectID = 0x00;
|
|
@@ -2821,7 +3609,7 @@ class ModbusSlave extends EventEmitter {
|
|
|
2821
3609
|
let maxId = 0;
|
|
2822
3610
|
for (const id of objects.keys()) {
|
|
2823
3611
|
if ((id >= 0x07 && id <= 0x7f) || id > 0xff) {
|
|
2824
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.SERVER_DEVICE_FAILURE));
|
|
3612
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.SERVER_DEVICE_FAILURE));
|
|
2825
3613
|
return;
|
|
2826
3614
|
}
|
|
2827
3615
|
if (id > maxId) {
|
|
@@ -2838,97 +3626,104 @@ class ModbusSlave extends EventEmitter {
|
|
|
2838
3626
|
}
|
|
2839
3627
|
const byteLength = Buffer.byteLength(value);
|
|
2840
3628
|
if (byteLength > 245) {
|
|
2841
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.SERVER_DEVICE_FAILURE));
|
|
3629
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.SERVER_DEVICE_FAILURE));
|
|
2842
3630
|
return;
|
|
2843
3631
|
}
|
|
2844
3632
|
if (lastID !== 0) {
|
|
2845
3633
|
continue;
|
|
2846
3634
|
}
|
|
2847
3635
|
if (byteLength + 2 > 253 - totalLength) {
|
|
2848
|
-
|
|
2849
|
-
lastID = id;
|
|
2850
|
-
}
|
|
3636
|
+
lastID = id;
|
|
2851
3637
|
}
|
|
2852
3638
|
else {
|
|
2853
3639
|
totalLength += byteLength + 2;
|
|
2854
|
-
ids.push(id);
|
|
3640
|
+
ids.push({ id, byteLength });
|
|
2855
3641
|
if (readDeviceIDCode === exports.ReadDeviceIDCode.SPECIFIC_ACCESS) {
|
|
2856
3642
|
break;
|
|
2857
3643
|
}
|
|
2858
3644
|
}
|
|
2859
3645
|
}
|
|
2860
|
-
ids.sort((a, b) => a - b);
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
3646
|
+
ids.sort((a, b) => a.id - b.id);
|
|
3647
|
+
let dataLength = 6;
|
|
3648
|
+
for (const { byteLength } of ids) {
|
|
3649
|
+
dataLength += 2 + byteLength;
|
|
3650
|
+
}
|
|
3651
|
+
const data = Buffer.allocUnsafe(dataLength);
|
|
3652
|
+
let offset = 0;
|
|
3653
|
+
data[offset++] = MEI_READ_DEVICE_ID;
|
|
3654
|
+
data[offset++] = readDeviceIDCode;
|
|
3655
|
+
data[offset++] = conformityLevel;
|
|
3656
|
+
data[offset++] = lastID === 0 ? 0x00 : 0xff;
|
|
3657
|
+
data[offset++] = lastID;
|
|
3658
|
+
data[offset++] = ids.length;
|
|
3659
|
+
for (const { id, byteLength } of ids) {
|
|
2864
3660
|
const value = objects.get(id);
|
|
2865
|
-
|
|
2866
|
-
|
|
3661
|
+
data[offset++] = id;
|
|
3662
|
+
data[offset++] = byteLength;
|
|
3663
|
+
offset += data.write(value, offset);
|
|
2867
3664
|
}
|
|
2868
|
-
yield response(
|
|
3665
|
+
yield response(appLayer.encode(Object.assign(Object.assign({}, frame), { data })));
|
|
2869
3666
|
}
|
|
2870
3667
|
catch (error) {
|
|
2871
|
-
yield this.responseError(frame, response, error);
|
|
3668
|
+
yield this.responseError(appLayer, frame, response, error);
|
|
2872
3669
|
}
|
|
2873
3670
|
}
|
|
2874
3671
|
else {
|
|
2875
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
3672
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
2876
3673
|
}
|
|
2877
3674
|
}
|
|
2878
3675
|
});
|
|
2879
3676
|
}
|
|
2880
|
-
responseError(frame, response, error) {
|
|
3677
|
+
responseError(appLayer, frame, response, error) {
|
|
2881
3678
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2882
|
-
yield response(
|
|
3679
|
+
yield response(appLayer.encode(Object.assign(Object.assign({}, frame), { fc: frame.fc | EXCEPTION_OFFSET, data: Buffer.from([getCodeByError(error)]) })));
|
|
2883
3680
|
});
|
|
2884
3681
|
}
|
|
2885
|
-
_drain(
|
|
3682
|
+
_drain(appLayer, q) {
|
|
2886
3683
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2887
|
-
if (q.processing) {
|
|
3684
|
+
if (q.processing || this._physicalLayer.state !== exports.PhysicalState.OPEN) {
|
|
2888
3685
|
return;
|
|
2889
3686
|
}
|
|
2890
3687
|
q.processing = true;
|
|
2891
3688
|
try {
|
|
2892
|
-
while (q.
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
yield this._processFrame(frame, response);
|
|
2896
|
-
}
|
|
2897
|
-
catch (error) {
|
|
2898
|
-
this.emit('error', error);
|
|
3689
|
+
while (q.frames.length > 0) {
|
|
3690
|
+
if (this._physicalLayer.state !== exports.PhysicalState.OPEN) {
|
|
3691
|
+
return;
|
|
2899
3692
|
}
|
|
3693
|
+
const frame = q.frames.shift();
|
|
3694
|
+
yield this._processFrame(appLayer, frame);
|
|
2900
3695
|
}
|
|
2901
3696
|
}
|
|
2902
3697
|
finally {
|
|
2903
3698
|
q.processing = false;
|
|
2904
|
-
// Cleanup empty queue entries so the map doesn't grow unbounded across
|
|
2905
|
-
// ephemeral connections (UDP rinfo, brief TCP clients).
|
|
2906
|
-
if (q.items.length === 0 && this._queues.get(key) === q) {
|
|
2907
|
-
this._queues.delete(key);
|
|
2908
|
-
}
|
|
2909
3699
|
}
|
|
2910
3700
|
});
|
|
2911
3701
|
}
|
|
2912
|
-
_processFrame(
|
|
3702
|
+
_processFrame(appLayer, frame) {
|
|
2913
3703
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2914
3704
|
const response = (data) => __awaiter(this, void 0, void 0, function* () {
|
|
2915
|
-
if (frame.unit === 0x00) {
|
|
3705
|
+
if (frame.unit === 0x00 || this._physicalLayer.state !== exports.PhysicalState.OPEN) {
|
|
2916
3706
|
return;
|
|
2917
3707
|
}
|
|
2918
3708
|
try {
|
|
2919
|
-
yield
|
|
3709
|
+
yield appLayer.connection.write(data);
|
|
3710
|
+
}
|
|
3711
|
+
catch (_a) {
|
|
3712
|
+
/* A write failure usually means the connection is already broken; let the connection's close event handle cleanup. */
|
|
2920
3713
|
}
|
|
2921
|
-
catch (error) { }
|
|
2922
3714
|
});
|
|
2923
3715
|
for (const model of frame.unit === 0x00 ? this.models.values() : [this.models.get(frame.unit)]) {
|
|
2924
|
-
|
|
3716
|
+
if (this._physicalLayer.state !== exports.PhysicalState.OPEN) {
|
|
3717
|
+
return;
|
|
3718
|
+
}
|
|
3719
|
+
const intercepted = yield this._intercept(appLayer, model, frame, response);
|
|
2925
3720
|
if (!intercepted) {
|
|
2926
|
-
yield this._handleFC(model, frame, response);
|
|
3721
|
+
yield this._handleFC(appLayer, model, frame, response);
|
|
2927
3722
|
}
|
|
2928
3723
|
}
|
|
2929
3724
|
});
|
|
2930
3725
|
}
|
|
2931
|
-
_intercept(model, frame, response) {
|
|
3726
|
+
_intercept(appLayer, model, frame, response) {
|
|
2932
3727
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2933
3728
|
if (!model.interceptor) {
|
|
2934
3729
|
return false;
|
|
@@ -2936,18 +3731,18 @@ class ModbusSlave extends EventEmitter {
|
|
|
2936
3731
|
try {
|
|
2937
3732
|
const data = yield model.interceptor(frame.fc, frame.data);
|
|
2938
3733
|
if (data !== undefined) {
|
|
2939
|
-
yield response(
|
|
3734
|
+
yield response(appLayer.encode(Object.assign(Object.assign({}, frame), { data })));
|
|
2940
3735
|
return true;
|
|
2941
3736
|
}
|
|
2942
3737
|
return false;
|
|
2943
3738
|
}
|
|
2944
3739
|
catch (error) {
|
|
2945
|
-
yield this.responseError(frame, response, error);
|
|
3740
|
+
yield this.responseError(appLayer, frame, response, error);
|
|
2946
3741
|
return true;
|
|
2947
3742
|
}
|
|
2948
3743
|
});
|
|
2949
3744
|
}
|
|
2950
|
-
|
|
3745
|
+
_withAddressLock(addresses, fn) {
|
|
2951
3746
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2952
3747
|
const sorted = [...new Set(addresses)].sort((a, b) => a - b);
|
|
2953
3748
|
const previous = sorted.map((addr) => { var _a; return (_a = this._locks.get(addr)) !== null && _a !== void 0 ? _a : Promise.resolve(); });
|
|
@@ -2970,55 +3765,55 @@ class ModbusSlave extends EventEmitter {
|
|
|
2970
3765
|
}
|
|
2971
3766
|
});
|
|
2972
3767
|
}
|
|
2973
|
-
_handleFC(model, frame, response) {
|
|
3768
|
+
_handleFC(appLayer, model, frame, response) {
|
|
2974
3769
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2975
3770
|
switch (frame.fc) {
|
|
2976
3771
|
case exports.FunctionCode.READ_COILS: {
|
|
2977
|
-
yield this.handleFC1(model, frame, response);
|
|
3772
|
+
yield this.handleFC1(appLayer, model, frame, response);
|
|
2978
3773
|
break;
|
|
2979
3774
|
}
|
|
2980
3775
|
case exports.FunctionCode.READ_DISCRETE_INPUTS: {
|
|
2981
|
-
yield this.handleFC2(model, frame, response);
|
|
3776
|
+
yield this.handleFC2(appLayer, model, frame, response);
|
|
2982
3777
|
break;
|
|
2983
3778
|
}
|
|
2984
3779
|
case exports.FunctionCode.READ_HOLDING_REGISTERS: {
|
|
2985
|
-
yield this.handleFC3(model, frame, response);
|
|
3780
|
+
yield this.handleFC3(appLayer, model, frame, response);
|
|
2986
3781
|
break;
|
|
2987
3782
|
}
|
|
2988
3783
|
case exports.FunctionCode.READ_INPUT_REGISTERS: {
|
|
2989
|
-
yield this.handleFC4(model, frame, response);
|
|
3784
|
+
yield this.handleFC4(appLayer, model, frame, response);
|
|
2990
3785
|
break;
|
|
2991
3786
|
}
|
|
2992
3787
|
case exports.FunctionCode.WRITE_SINGLE_COIL: {
|
|
2993
|
-
yield this.handleFC5(model, frame, response);
|
|
3788
|
+
yield this.handleFC5(appLayer, model, frame, response);
|
|
2994
3789
|
break;
|
|
2995
3790
|
}
|
|
2996
3791
|
case exports.FunctionCode.WRITE_SINGLE_REGISTER: {
|
|
2997
|
-
yield this.handleFC6(model, frame, response);
|
|
3792
|
+
yield this.handleFC6(appLayer, model, frame, response);
|
|
2998
3793
|
break;
|
|
2999
3794
|
}
|
|
3000
3795
|
case exports.FunctionCode.WRITE_MULTIPLE_COILS: {
|
|
3001
|
-
yield this.handleFC15(model, frame, response);
|
|
3796
|
+
yield this.handleFC15(appLayer, model, frame, response);
|
|
3002
3797
|
break;
|
|
3003
3798
|
}
|
|
3004
3799
|
case exports.FunctionCode.WRITE_MULTIPLE_REGISTERS: {
|
|
3005
|
-
yield this.handleFC16(model, frame, response);
|
|
3800
|
+
yield this.handleFC16(appLayer, model, frame, response);
|
|
3006
3801
|
break;
|
|
3007
3802
|
}
|
|
3008
3803
|
case exports.FunctionCode.REPORT_SERVER_ID: {
|
|
3009
|
-
yield this.handleFC17(model, frame, response);
|
|
3804
|
+
yield this.handleFC17(appLayer, model, frame, response);
|
|
3010
3805
|
break;
|
|
3011
3806
|
}
|
|
3012
3807
|
case exports.FunctionCode.MASK_WRITE_REGISTER: {
|
|
3013
|
-
yield this.handleFC22(model, frame, response);
|
|
3808
|
+
yield this.handleFC22(appLayer, model, frame, response);
|
|
3014
3809
|
break;
|
|
3015
3810
|
}
|
|
3016
3811
|
case exports.FunctionCode.READ_WRITE_MULTIPLE_REGISTERS: {
|
|
3017
|
-
yield this.handleFC23(model, frame, response);
|
|
3812
|
+
yield this.handleFC23(appLayer, model, frame, response);
|
|
3018
3813
|
break;
|
|
3019
3814
|
}
|
|
3020
3815
|
case exports.FunctionCode.READ_DEVICE_IDENTIFICATION: {
|
|
3021
|
-
yield this.handleFC43_14(model, frame, response);
|
|
3816
|
+
yield this.handleFC43_14(appLayer, model, frame, response);
|
|
3022
3817
|
break;
|
|
3023
3818
|
}
|
|
3024
3819
|
default: {
|
|
@@ -3026,14 +3821,14 @@ class ModbusSlave extends EventEmitter {
|
|
|
3026
3821
|
if (cfc === null || cfc === void 0 ? void 0 : cfc.handle) {
|
|
3027
3822
|
try {
|
|
3028
3823
|
const responseData = yield cfc.handle(frame.data, frame.unit);
|
|
3029
|
-
yield response(
|
|
3824
|
+
yield response(appLayer.encode(Object.assign(Object.assign({}, frame), { data: responseData })));
|
|
3030
3825
|
}
|
|
3031
3826
|
catch (error) {
|
|
3032
|
-
yield this.responseError(frame, response, error);
|
|
3827
|
+
yield this.responseError(appLayer, frame, response, error);
|
|
3033
3828
|
}
|
|
3034
3829
|
}
|
|
3035
3830
|
else {
|
|
3036
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
3831
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
3037
3832
|
}
|
|
3038
3833
|
break;
|
|
3039
3834
|
}
|
|
@@ -3049,53 +3844,70 @@ class ModbusSlave extends EventEmitter {
|
|
|
3049
3844
|
}
|
|
3050
3845
|
addCustomFunctionCode(cfc) {
|
|
3051
3846
|
this._customFunctionCodes.set(cfc.fc, cfc);
|
|
3052
|
-
this.
|
|
3847
|
+
for (const appLayer of this._appLayers.keys()) {
|
|
3848
|
+
appLayer.addCustomFunctionCode(cfc);
|
|
3849
|
+
}
|
|
3053
3850
|
}
|
|
3054
3851
|
removeCustomFunctionCode(fc) {
|
|
3055
3852
|
this._customFunctionCodes.delete(fc);
|
|
3056
|
-
this.
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
this.
|
|
3070
|
-
|
|
3071
|
-
this._customFunctionCodes.clear();
|
|
3072
|
-
this.models.clear();
|
|
3853
|
+
for (const appLayer of this._appLayers.keys()) {
|
|
3854
|
+
appLayer.removeCustomFunctionCode(fc);
|
|
3855
|
+
}
|
|
3856
|
+
}
|
|
3857
|
+
/**
|
|
3858
|
+
* Open the underlying physical layer and begin accepting connections.
|
|
3859
|
+
*
|
|
3860
|
+
* A `ModbusSlave` instance can only be opened once. Once {@link close}
|
|
3861
|
+
* is called — explicitly or because the physical layer disconnected —
|
|
3862
|
+
* the instance is permanently closed and cannot be reopened.
|
|
3863
|
+
* Create a new `ModbusSlave` if a new server is required.
|
|
3864
|
+
*/
|
|
3865
|
+
open(...args) {
|
|
3866
|
+
if (this._closePromise) {
|
|
3867
|
+
return Promise.reject(new Error('The port has been permanently closed and CANNOT be reopened'));
|
|
3073
3868
|
}
|
|
3074
|
-
this.
|
|
3869
|
+
return this._physicalLayer.open(...args);
|
|
3075
3870
|
}
|
|
3076
|
-
|
|
3077
|
-
this
|
|
3078
|
-
|
|
3871
|
+
_close() {
|
|
3872
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
3873
|
+
for (const fn of this._cleanupFns) {
|
|
3874
|
+
fn();
|
|
3875
|
+
}
|
|
3876
|
+
this._cleanupFns.clear();
|
|
3877
|
+
let err = null;
|
|
3878
|
+
try {
|
|
3879
|
+
yield this._physicalLayer.close();
|
|
3880
|
+
}
|
|
3881
|
+
catch (error) {
|
|
3882
|
+
err = error;
|
|
3883
|
+
}
|
|
3884
|
+
this.removeAllListeners();
|
|
3885
|
+
if (err) {
|
|
3886
|
+
return Promise.reject(err);
|
|
3887
|
+
}
|
|
3888
|
+
});
|
|
3079
3889
|
}
|
|
3890
|
+
/**
|
|
3891
|
+
* Permanently close the slave and release all resources.
|
|
3892
|
+
*
|
|
3893
|
+
* After calling this method the instance is considered dead:
|
|
3894
|
+
* - No further requests can be processed.
|
|
3895
|
+
* - The instance cannot be reopened via {@link open}.
|
|
3896
|
+
* - All event listeners registered on this slave are removed.
|
|
3897
|
+
*/
|
|
3080
3898
|
close() {
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
if (this._cleanLevel === 'destroy') {
|
|
3089
|
-
return Promise.resolve();
|
|
3090
|
-
}
|
|
3091
|
-
this._clean('destroy');
|
|
3092
|
-
this.removeAllListeners();
|
|
3093
|
-
this.applicationLayer.destroy();
|
|
3094
|
-
return this.physicalLayer.destroy();
|
|
3899
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
3900
|
+
if (this._closePromise) {
|
|
3901
|
+
return this._closePromise;
|
|
3902
|
+
}
|
|
3903
|
+
this._closePromise = this._close();
|
|
3904
|
+
return this._closePromise;
|
|
3905
|
+
});
|
|
3095
3906
|
}
|
|
3096
3907
|
}
|
|
3097
3908
|
|
|
3098
3909
|
exports.AbstractApplicationLayer = AbstractApplicationLayer;
|
|
3910
|
+
exports.AbstractPhysicalConnection = AbstractPhysicalConnection;
|
|
3099
3911
|
exports.AbstractPhysicalLayer = AbstractPhysicalLayer;
|
|
3100
3912
|
exports.AsciiApplicationLayer = AsciiApplicationLayer;
|
|
3101
3913
|
exports.COIL_OFF = COIL_OFF;
|
|
@@ -3105,7 +3917,6 @@ exports.LIMITS = LIMITS;
|
|
|
3105
3917
|
exports.MEI_READ_DEVICE_ID = MEI_READ_DEVICE_ID;
|
|
3106
3918
|
exports.MasterSession = MasterSession;
|
|
3107
3919
|
exports.ModbusError = ModbusError;
|
|
3108
|
-
exports.ModbusErrorCode = ModbusErrorCode;
|
|
3109
3920
|
exports.ModbusMaster = ModbusMaster;
|
|
3110
3921
|
exports.ModbusSlave = ModbusSlave;
|
|
3111
3922
|
exports.RtuApplicationLayer = RtuApplicationLayer;
|
|
@@ -3113,6 +3924,8 @@ exports.SerialPhysicalLayer = SerialPhysicalLayer;
|
|
|
3113
3924
|
exports.TcpApplicationLayer = TcpApplicationLayer;
|
|
3114
3925
|
exports.TcpClientPhysicalLayer = TcpClientPhysicalLayer;
|
|
3115
3926
|
exports.TcpServerPhysicalLayer = TcpServerPhysicalLayer;
|
|
3116
|
-
exports.
|
|
3927
|
+
exports.UdpClientPhysicalLayer = UdpClientPhysicalLayer;
|
|
3928
|
+
exports.UdpServerPhysicalLayer = UdpServerPhysicalLayer;
|
|
3929
|
+
exports.createPhysicalLayer = createPhysicalLayer;
|
|
3117
3930
|
exports.getCodeByError = getCodeByError;
|
|
3118
3931
|
exports.getErrorByCode = getErrorByCode;
|