njs-modbus 2.1.0 → 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 +264 -154
- package/README.zh-CN.md +314 -0
- package/dist/index.cjs +1887 -1066
- package/dist/index.d.ts +368 -215
- package/dist/index.mjs +1884 -1065
- 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 -17
- package/dist/src/slave/slave.d.ts +57 -33
- package/dist/src/types.d.ts +2 -2
- 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 +8 -8
- 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
|
-
}
|
|
720
|
-
try {
|
|
721
|
-
server.close();
|
|
722
1418
|
}
|
|
723
|
-
|
|
724
|
-
|
|
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.
|
|
1136
|
-
}
|
|
1137
|
-
getState(key) {
|
|
1138
|
-
let state = this._states.get(key);
|
|
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;
|
|
1792
|
+
connection.on('close', onClose);
|
|
1793
|
+
this._cleanupFns.add(() => connection.off('close', onClose));
|
|
1144
1794
|
}
|
|
1145
|
-
clearStateTimers(
|
|
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 = [];
|
|
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;
|
|
2073
|
+
connection.on('close', onClose);
|
|
2074
|
+
this._cleanupFns.add(() => connection.off('close', onClose));
|
|
1455
2075
|
}
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
framing(_buffer, response, connection) {
|
|
1460
|
-
if (_buffer.length < 6) {
|
|
1461
|
-
this.emit('framing-error', new ModbusError(ModbusErrorCode.INSUFFICIENT_DATA, 'Insufficient data length'));
|
|
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 = [];
|
|
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 = [];
|
|
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,6 +2400,30 @@ class ModbusMaster extends EventEmitter {
|
|
|
1778
2400
|
writable: true,
|
|
1779
2401
|
value: new MasterSession()
|
|
1780
2402
|
});
|
|
2403
|
+
Object.defineProperty(this, "_physicalLayer", {
|
|
2404
|
+
enumerable: true,
|
|
2405
|
+
configurable: true,
|
|
2406
|
+
writable: true,
|
|
2407
|
+
value: void 0
|
|
2408
|
+
});
|
|
2409
|
+
Object.defineProperty(this, "_protocol", {
|
|
2410
|
+
enumerable: true,
|
|
2411
|
+
configurable: true,
|
|
2412
|
+
writable: true,
|
|
2413
|
+
value: void 0
|
|
2414
|
+
});
|
|
2415
|
+
Object.defineProperty(this, "_appLayer", {
|
|
2416
|
+
enumerable: true,
|
|
2417
|
+
configurable: true,
|
|
2418
|
+
writable: true,
|
|
2419
|
+
value: void 0
|
|
2420
|
+
});
|
|
2421
|
+
Object.defineProperty(this, "_customFunctionCodes", {
|
|
2422
|
+
enumerable: true,
|
|
2423
|
+
configurable: true,
|
|
2424
|
+
writable: true,
|
|
2425
|
+
value: new Map()
|
|
2426
|
+
});
|
|
1781
2427
|
Object.defineProperty(this, "_queue", {
|
|
1782
2428
|
enumerable: true,
|
|
1783
2429
|
configurable: true,
|
|
@@ -1796,23 +2442,17 @@ class ModbusMaster extends EventEmitter {
|
|
|
1796
2442
|
writable: true,
|
|
1797
2443
|
value: 1
|
|
1798
2444
|
});
|
|
1799
|
-
Object.defineProperty(this, "
|
|
1800
|
-
enumerable: true,
|
|
1801
|
-
configurable: true,
|
|
1802
|
-
writable: true,
|
|
1803
|
-
value: 'none'
|
|
1804
|
-
});
|
|
1805
|
-
Object.defineProperty(this, "timeout", {
|
|
2445
|
+
Object.defineProperty(this, "_cleanupFns", {
|
|
1806
2446
|
enumerable: true,
|
|
1807
2447
|
configurable: true,
|
|
1808
2448
|
writable: true,
|
|
1809
|
-
value:
|
|
2449
|
+
value: new Set()
|
|
1810
2450
|
});
|
|
1811
|
-
Object.defineProperty(this, "
|
|
2451
|
+
Object.defineProperty(this, "_closePromise", {
|
|
1812
2452
|
enumerable: true,
|
|
1813
2453
|
configurable: true,
|
|
1814
2454
|
writable: true,
|
|
1815
|
-
value:
|
|
2455
|
+
value: null
|
|
1816
2456
|
});
|
|
1817
2457
|
Object.defineProperty(this, "writeFC1", {
|
|
1818
2458
|
enumerable: true,
|
|
@@ -1888,10 +2528,60 @@ class ModbusMaster extends EventEmitter {
|
|
|
1888
2528
|
});
|
|
1889
2529
|
this.timeout = (_a = options.timeout) !== null && _a !== void 0 ? _a : 1000;
|
|
1890
2530
|
this.concurrent = (_b = options.concurrent) !== null && _b !== void 0 ? _b : false;
|
|
1891
|
-
|
|
1892
|
-
|
|
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');
|
|
1893
2535
|
}
|
|
1894
|
-
|
|
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));
|
|
1895
2585
|
this.writeFC1 = this.readCoils;
|
|
1896
2586
|
this.writeFC2 = this.readDiscreteInputs;
|
|
1897
2587
|
this.writeFC3 = this.readHoldingRegisters;
|
|
@@ -1904,20 +2594,27 @@ class ModbusMaster extends EventEmitter {
|
|
|
1904
2594
|
this.handleFC22 = this.maskWriteRegister;
|
|
1905
2595
|
this.handleFC23 = this.readAndWriteMultipleRegisters;
|
|
1906
2596
|
this.handleFC43_14 = this.readDeviceIdentification;
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
this.
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
}
|
|
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);
|
|
1919
2613
|
}
|
|
1920
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
|
+
}
|
|
1921
2618
|
const task = () => this._exchange(adu, preCheck, timeout, broadcast);
|
|
1922
2619
|
if (this.concurrent) {
|
|
1923
2620
|
return task();
|
|
@@ -1937,14 +2634,20 @@ class ModbusMaster extends EventEmitter {
|
|
|
1937
2634
|
}
|
|
1938
2635
|
_drain() {
|
|
1939
2636
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1940
|
-
if (this._draining) {
|
|
2637
|
+
if (this._draining || this._physicalLayer.state !== exports.PhysicalState.OPEN) {
|
|
1941
2638
|
return;
|
|
1942
2639
|
}
|
|
1943
2640
|
this._draining = true;
|
|
1944
2641
|
try {
|
|
1945
2642
|
while (this._queue.length > 0) {
|
|
2643
|
+
if (this._physicalLayer.state !== exports.PhysicalState.OPEN) {
|
|
2644
|
+
return;
|
|
2645
|
+
}
|
|
1946
2646
|
const item = this._queue.shift();
|
|
1947
|
-
|
|
2647
|
+
try {
|
|
2648
|
+
yield item.run();
|
|
2649
|
+
}
|
|
2650
|
+
catch (_a) { }
|
|
1948
2651
|
}
|
|
1949
2652
|
}
|
|
1950
2653
|
finally {
|
|
@@ -1954,18 +2657,26 @@ class ModbusMaster extends EventEmitter {
|
|
|
1954
2657
|
}
|
|
1955
2658
|
_exchange(adu, preCheck, timeout, broadcast) {
|
|
1956
2659
|
return new Promise((resolve, reject) => {
|
|
1957
|
-
if (this.
|
|
1958
|
-
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'));
|
|
1959
2667
|
return;
|
|
1960
2668
|
}
|
|
1961
|
-
const
|
|
2669
|
+
const connection = appLayer.connection;
|
|
2670
|
+
const usesTid = appLayer.PROTOCOL === 'TCP' && !broadcast;
|
|
1962
2671
|
let tid;
|
|
1963
2672
|
if (usesTid) {
|
|
1964
|
-
|
|
1965
|
-
|
|
2673
|
+
do {
|
|
2674
|
+
tid = this._nextTid;
|
|
2675
|
+
this._nextTid = (this._nextTid + 1) % 65536 || 1;
|
|
2676
|
+
} while (this._masterSession.has(tid));
|
|
1966
2677
|
}
|
|
1967
2678
|
const key = tid !== null && tid !== void 0 ? tid : FIFO_KEY;
|
|
1968
|
-
const payload =
|
|
2679
|
+
const payload = appLayer.encode(Object.assign(Object.assign({}, adu), { transaction: tid }));
|
|
1969
2680
|
let settled = false;
|
|
1970
2681
|
const settle = (error, frame) => {
|
|
1971
2682
|
if (settled) {
|
|
@@ -1981,18 +2692,22 @@ class ModbusMaster extends EventEmitter {
|
|
|
1981
2692
|
resolve(frame);
|
|
1982
2693
|
}
|
|
1983
2694
|
};
|
|
1984
|
-
const timer = setTimeout(() => settle(new
|
|
2695
|
+
const timer = setTimeout(() => settle(new Error('Timeout')), timeout);
|
|
1985
2696
|
// FIFO mode: clear stale buffer state before this request.
|
|
1986
2697
|
// Concurrent mode: must not flush — other in-flight requests share the buffer.
|
|
1987
2698
|
if (!this.concurrent) {
|
|
1988
|
-
|
|
2699
|
+
appLayer.flush();
|
|
1989
2700
|
}
|
|
1990
|
-
|
|
2701
|
+
connection
|
|
1991
2702
|
.write(payload)
|
|
1992
2703
|
.then(() => {
|
|
1993
2704
|
if (settled) {
|
|
1994
2705
|
return;
|
|
1995
2706
|
}
|
|
2707
|
+
if (this._physicalLayer.state !== exports.PhysicalState.OPEN) {
|
|
2708
|
+
settle(new Error('Master is not open'));
|
|
2709
|
+
return;
|
|
2710
|
+
}
|
|
1996
2711
|
if (broadcast) {
|
|
1997
2712
|
settle(null);
|
|
1998
2713
|
return;
|
|
@@ -2005,7 +2720,7 @@ class ModbusMaster extends EventEmitter {
|
|
|
2005
2720
|
}
|
|
2006
2721
|
writeFC1Or2(unit, fc, address, length, timeout) {
|
|
2007
2722
|
const byteCount = Math.ceil(length / 8);
|
|
2008
|
-
const bufferTx = Buffer.
|
|
2723
|
+
const bufferTx = Buffer.allocUnsafe(4);
|
|
2009
2724
|
bufferTx.writeUInt16BE(address, 0);
|
|
2010
2725
|
bufferTx.writeUInt16BE(length, 2);
|
|
2011
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) => {
|
|
@@ -2022,7 +2737,7 @@ class ModbusMaster extends EventEmitter {
|
|
|
2022
2737
|
}
|
|
2023
2738
|
writeFC3Or4(unit, fc, address, length, timeout) {
|
|
2024
2739
|
const byteCount = length * 2;
|
|
2025
|
-
const bufferTx = Buffer.
|
|
2740
|
+
const bufferTx = Buffer.allocUnsafe(4);
|
|
2026
2741
|
bufferTx.writeUInt16BE(address, 0);
|
|
2027
2742
|
bufferTx.writeUInt16BE(length, 2);
|
|
2028
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) => {
|
|
@@ -2040,7 +2755,7 @@ class ModbusMaster extends EventEmitter {
|
|
|
2040
2755
|
}
|
|
2041
2756
|
writeSingleCoil(unit, address, value, timeout = this.timeout) {
|
|
2042
2757
|
const fc = exports.FunctionCode.WRITE_SINGLE_COIL;
|
|
2043
|
-
const bufferTx = Buffer.
|
|
2758
|
+
const bufferTx = Buffer.allocUnsafe(4);
|
|
2044
2759
|
bufferTx.writeUInt16BE(address, 0);
|
|
2045
2760
|
bufferTx.writeUInt16BE(value ? COIL_ON : COIL_OFF, 2);
|
|
2046
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) => {
|
|
@@ -2051,7 +2766,7 @@ class ModbusMaster extends EventEmitter {
|
|
|
2051
2766
|
}
|
|
2052
2767
|
writeSingleRegister(unit, address, value, timeout = this.timeout) {
|
|
2053
2768
|
const fc = exports.FunctionCode.WRITE_SINGLE_REGISTER;
|
|
2054
|
-
const bufferTx = Buffer.
|
|
2769
|
+
const bufferTx = Buffer.allocUnsafe(4);
|
|
2055
2770
|
bufferTx.writeUInt16BE(address, 0);
|
|
2056
2771
|
bufferTx.writeUInt16BE(value, 2);
|
|
2057
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) => {
|
|
@@ -2081,7 +2796,7 @@ class ModbusMaster extends EventEmitter {
|
|
|
2081
2796
|
writeMultipleRegisters(unit, address, value, timeout = this.timeout) {
|
|
2082
2797
|
const fc = exports.FunctionCode.WRITE_MULTIPLE_REGISTERS;
|
|
2083
2798
|
const byteCount = value.length * 2;
|
|
2084
|
-
const bufferTx = Buffer.
|
|
2799
|
+
const bufferTx = Buffer.allocUnsafe(5 + byteCount);
|
|
2085
2800
|
bufferTx.writeUInt16BE(address, 0);
|
|
2086
2801
|
bufferTx.writeUInt16BE(value.length, 2);
|
|
2087
2802
|
bufferTx.writeUInt8(byteCount, 4);
|
|
@@ -2116,7 +2831,7 @@ class ModbusMaster extends EventEmitter {
|
|
|
2116
2831
|
}
|
|
2117
2832
|
maskWriteRegister(unit, address, andMask, orMask, timeout = this.timeout) {
|
|
2118
2833
|
const fc = exports.FunctionCode.MASK_WRITE_REGISTER;
|
|
2119
|
-
const bufferTx = Buffer.
|
|
2834
|
+
const bufferTx = Buffer.allocUnsafe(6);
|
|
2120
2835
|
bufferTx.writeUInt16BE(address, 0);
|
|
2121
2836
|
bufferTx.writeUInt16BE(andMask, 2);
|
|
2122
2837
|
bufferTx.writeUInt16BE(orMask, 4);
|
|
@@ -2130,7 +2845,7 @@ class ModbusMaster extends EventEmitter {
|
|
|
2130
2845
|
const fc = exports.FunctionCode.READ_WRITE_MULTIPLE_REGISTERS;
|
|
2131
2846
|
const byteCount = write.value.length * 2;
|
|
2132
2847
|
const readByteCount = read.length * 2;
|
|
2133
|
-
const bufferTx = Buffer.
|
|
2848
|
+
const bufferTx = Buffer.allocUnsafe(9 + byteCount);
|
|
2134
2849
|
bufferTx.writeUInt16BE(read.address, 0);
|
|
2135
2850
|
bufferTx.writeUInt16BE(read.length, 2);
|
|
2136
2851
|
bufferTx.writeUInt16BE(write.address, 4);
|
|
@@ -2202,10 +2917,14 @@ class ModbusMaster extends EventEmitter {
|
|
|
2202
2917
|
});
|
|
2203
2918
|
}
|
|
2204
2919
|
addCustomFunctionCode(cfc) {
|
|
2205
|
-
|
|
2920
|
+
var _a;
|
|
2921
|
+
this._customFunctionCodes.set(cfc.fc, cfc);
|
|
2922
|
+
(_a = this._appLayer) === null || _a === void 0 ? void 0 : _a.addCustomFunctionCode(cfc);
|
|
2206
2923
|
}
|
|
2207
2924
|
removeCustomFunctionCode(fc) {
|
|
2208
|
-
|
|
2925
|
+
var _a;
|
|
2926
|
+
this._customFunctionCodes.delete(fc);
|
|
2927
|
+
(_a = this._appLayer) === null || _a === void 0 ? void 0 : _a.removeCustomFunctionCode(fc);
|
|
2209
2928
|
}
|
|
2210
2929
|
sendCustomFC(unit, fc, data, timeout = this.timeout) {
|
|
2211
2930
|
const payload = Buffer.isBuffer(data) ? data : Buffer.from(data);
|
|
@@ -2215,83 +2934,107 @@ class ModbusMaster extends EventEmitter {
|
|
|
2215
2934
|
}
|
|
2216
2935
|
});
|
|
2217
2936
|
}
|
|
2218
|
-
_clean(
|
|
2219
|
-
if (this._cleanLevel === 'destroy') {
|
|
2220
|
-
return;
|
|
2221
|
-
}
|
|
2222
|
-
if (this._cleanLevel === 'close' && level === 'close') {
|
|
2223
|
-
return;
|
|
2224
|
-
}
|
|
2225
|
-
const errorCode = level === 'destroy' ? ModbusErrorCode.MASTER_DESTROYED : ModbusErrorCode.MASTER_CLOSED;
|
|
2226
|
-
const message = level === 'destroy' ? 'Master destroyed' : 'Master closed';
|
|
2937
|
+
_clean(message) {
|
|
2227
2938
|
const queued = this._queue.splice(0);
|
|
2228
2939
|
for (const item of queued) {
|
|
2229
|
-
item.cancel(new
|
|
2230
|
-
}
|
|
2231
|
-
this._masterSession.stopAll(new
|
|
2232
|
-
|
|
2233
|
-
|
|
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
|
+
*/
|
|
2234
2952
|
open(...args) {
|
|
2235
|
-
if (this.
|
|
2236
|
-
return Promise.reject(new
|
|
2953
|
+
if (this._closePromise) {
|
|
2954
|
+
return Promise.reject(new Error('The port has been permanently closed and CANNOT be reopened'));
|
|
2237
2955
|
}
|
|
2238
|
-
this.
|
|
2239
|
-
this._nextTid = 1;
|
|
2240
|
-
return this.physicalLayer.open(...args);
|
|
2956
|
+
return this._physicalLayer.open(...args);
|
|
2241
2957
|
}
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
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
|
+
});
|
|
2248
2977
|
}
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
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
|
+
});
|
|
2257
2994
|
}
|
|
2258
2995
|
}
|
|
2259
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
|
+
}
|
|
2260
3003
|
class ModbusSlave extends EventEmitter {
|
|
2261
|
-
get
|
|
2262
|
-
return this.
|
|
3004
|
+
get state() {
|
|
3005
|
+
return this._physicalLayer.state;
|
|
2263
3006
|
}
|
|
2264
|
-
get
|
|
2265
|
-
return this.
|
|
3007
|
+
get physicalLayer() {
|
|
3008
|
+
return this._physicalLayer;
|
|
2266
3009
|
}
|
|
2267
|
-
constructor(
|
|
3010
|
+
constructor(options) {
|
|
2268
3011
|
var _a;
|
|
2269
3012
|
super();
|
|
2270
|
-
Object.defineProperty(this, "
|
|
3013
|
+
Object.defineProperty(this, "models", {
|
|
2271
3014
|
enumerable: true,
|
|
2272
3015
|
configurable: true,
|
|
2273
3016
|
writable: true,
|
|
2274
|
-
value:
|
|
3017
|
+
value: new Map()
|
|
2275
3018
|
});
|
|
2276
|
-
Object.defineProperty(this, "
|
|
3019
|
+
Object.defineProperty(this, "concurrent", {
|
|
2277
3020
|
enumerable: true,
|
|
2278
3021
|
configurable: true,
|
|
2279
3022
|
writable: true,
|
|
2280
|
-
value:
|
|
3023
|
+
value: void 0
|
|
2281
3024
|
});
|
|
2282
|
-
Object.defineProperty(this, "
|
|
3025
|
+
Object.defineProperty(this, "_physicalLayer", {
|
|
2283
3026
|
enumerable: true,
|
|
2284
3027
|
configurable: true,
|
|
2285
3028
|
writable: true,
|
|
2286
|
-
value:
|
|
3029
|
+
value: void 0
|
|
2287
3030
|
});
|
|
2288
|
-
Object.defineProperty(this, "
|
|
3031
|
+
Object.defineProperty(this, "_protocol", {
|
|
2289
3032
|
enumerable: true,
|
|
2290
3033
|
configurable: true,
|
|
2291
3034
|
writable: true,
|
|
2292
3035
|
value: void 0
|
|
2293
3036
|
});
|
|
2294
|
-
Object.defineProperty(this, "
|
|
3037
|
+
Object.defineProperty(this, "_appLayers", {
|
|
2295
3038
|
enumerable: true,
|
|
2296
3039
|
configurable: true,
|
|
2297
3040
|
writable: true,
|
|
@@ -2309,72 +3052,112 @@ class ModbusSlave extends EventEmitter {
|
|
|
2309
3052
|
writable: true,
|
|
2310
3053
|
value: new Map()
|
|
2311
3054
|
});
|
|
2312
|
-
Object.defineProperty(this, "
|
|
3055
|
+
Object.defineProperty(this, "_cleanupFns", {
|
|
2313
3056
|
enumerable: true,
|
|
2314
3057
|
configurable: true,
|
|
2315
3058
|
writable: true,
|
|
2316
|
-
value:
|
|
2317
|
-
});
|
|
2318
|
-
this.concurrent = (_a = options.concurrent) !== null && _a !== void 0 ? _a : false;
|
|
2319
|
-
if (this.concurrent && this.applicationLayer.PROTOCOL !== 'TCP') {
|
|
2320
|
-
throw new ModbusError(ModbusErrorCode.CONCURRENT_NOT_TCP, 'concurrent mode requires a Modbus TCP application layer');
|
|
2321
|
-
}
|
|
2322
|
-
this.applicationLayer.role = 'SLAVE';
|
|
2323
|
-
applicationLayer.on('framing', (frame, response, connection) => {
|
|
2324
|
-
if (!(frame.unit === 0x00 || this.models.has(frame.unit))) {
|
|
2325
|
-
return;
|
|
2326
|
-
}
|
|
2327
|
-
if (this.concurrent) {
|
|
2328
|
-
this._processFrame(frame, response).catch((error) => this.emit('error', error));
|
|
2329
|
-
return;
|
|
2330
|
-
}
|
|
2331
|
-
let q = this._queues.get(connection.id);
|
|
2332
|
-
if (!q) {
|
|
2333
|
-
q = { items: [], processing: false };
|
|
2334
|
-
this._queues.set(connection.id, q);
|
|
2335
|
-
}
|
|
2336
|
-
q.items.push({ frame, response });
|
|
2337
|
-
this._drain(connection.id, q);
|
|
2338
|
-
});
|
|
2339
|
-
physicalLayer.on('error', (error) => {
|
|
2340
|
-
this.emit('error', error);
|
|
3059
|
+
value: new Set()
|
|
2341
3060
|
});
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
q.items = [];
|
|
2348
|
-
if (!q.processing) {
|
|
2349
|
-
this._queues.delete(connection.id);
|
|
2350
|
-
}
|
|
3061
|
+
Object.defineProperty(this, "_closePromise", {
|
|
3062
|
+
enumerable: true,
|
|
3063
|
+
configurable: true,
|
|
3064
|
+
writable: true,
|
|
3065
|
+
value: null
|
|
2351
3066
|
});
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
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 = () => {
|
|
2357
3115
|
this.emit('close');
|
|
2358
|
-
|
|
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);
|
|
2359
3141
|
}
|
|
2360
|
-
handleFC1(model, frame, response) {
|
|
3142
|
+
handleFC1(appLayer, model, frame, response) {
|
|
2361
3143
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2362
3144
|
var _a;
|
|
2363
3145
|
if (frame.data.length !== 4) {
|
|
3146
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2364
3147
|
return;
|
|
2365
3148
|
}
|
|
2366
3149
|
if (!model.readCoils) {
|
|
2367
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
3150
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
2368
3151
|
return;
|
|
2369
3152
|
}
|
|
2370
3153
|
const address = (frame.data[0] << 8) | frame.data[1];
|
|
2371
3154
|
const length = (frame.data[2] << 8) | frame.data[3];
|
|
2372
3155
|
if (length < LIMITS.READ_COILS_MIN || length > LIMITS.READ_COILS_MAX) {
|
|
2373
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
3156
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2374
3157
|
return;
|
|
2375
3158
|
}
|
|
2376
3159
|
if (!checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).coils)) {
|
|
2377
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
3160
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
2378
3161
|
return;
|
|
2379
3162
|
}
|
|
2380
3163
|
try {
|
|
@@ -2385,31 +3168,32 @@ class ModbusSlave extends EventEmitter {
|
|
|
2385
3168
|
bufferTx[Math.floor(index / 8)] |= 1 << index % 8;
|
|
2386
3169
|
}
|
|
2387
3170
|
});
|
|
2388
|
-
yield response(
|
|
3171
|
+
yield response(appLayer.encode(Object.assign(Object.assign({}, frame), { data: prependByte(bufferTx.length, bufferTx) })));
|
|
2389
3172
|
}
|
|
2390
3173
|
catch (error) {
|
|
2391
|
-
yield this.responseError(frame, response, error);
|
|
3174
|
+
yield this.responseError(appLayer, frame, response, error);
|
|
2392
3175
|
}
|
|
2393
3176
|
});
|
|
2394
3177
|
}
|
|
2395
|
-
handleFC2(model, frame, response) {
|
|
3178
|
+
handleFC2(appLayer, model, frame, response) {
|
|
2396
3179
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2397
3180
|
var _a;
|
|
2398
3181
|
if (frame.data.length !== 4) {
|
|
3182
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2399
3183
|
return;
|
|
2400
3184
|
}
|
|
2401
3185
|
if (!model.readDiscreteInputs) {
|
|
2402
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
3186
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
2403
3187
|
return;
|
|
2404
3188
|
}
|
|
2405
3189
|
const address = (frame.data[0] << 8) | frame.data[1];
|
|
2406
3190
|
const length = (frame.data[2] << 8) | frame.data[3];
|
|
2407
3191
|
if (length < LIMITS.READ_COILS_MIN || length > LIMITS.READ_COILS_MAX) {
|
|
2408
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
3192
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2409
3193
|
return;
|
|
2410
3194
|
}
|
|
2411
3195
|
if (!checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).discreteInputs)) {
|
|
2412
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
3196
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
2413
3197
|
return;
|
|
2414
3198
|
}
|
|
2415
3199
|
try {
|
|
@@ -2420,152 +3204,157 @@ class ModbusSlave extends EventEmitter {
|
|
|
2420
3204
|
bufferTx[Math.floor(index / 8)] |= 1 << index % 8;
|
|
2421
3205
|
}
|
|
2422
3206
|
});
|
|
2423
|
-
yield response(
|
|
3207
|
+
yield response(appLayer.encode(Object.assign(Object.assign({}, frame), { data: prependByte(bufferTx.length, bufferTx) })));
|
|
2424
3208
|
}
|
|
2425
3209
|
catch (error) {
|
|
2426
|
-
yield this.responseError(frame, response, error);
|
|
3210
|
+
yield this.responseError(appLayer, frame, response, error);
|
|
2427
3211
|
}
|
|
2428
3212
|
});
|
|
2429
3213
|
}
|
|
2430
|
-
handleFC3(model, frame, response) {
|
|
3214
|
+
handleFC3(appLayer, model, frame, response) {
|
|
2431
3215
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2432
3216
|
var _a;
|
|
2433
3217
|
if (frame.data.length !== 4) {
|
|
3218
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2434
3219
|
return;
|
|
2435
3220
|
}
|
|
2436
3221
|
if (!model.readHoldingRegisters) {
|
|
2437
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
3222
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
2438
3223
|
return;
|
|
2439
3224
|
}
|
|
2440
3225
|
const address = (frame.data[0] << 8) | frame.data[1];
|
|
2441
3226
|
const length = (frame.data[2] << 8) | frame.data[3];
|
|
2442
3227
|
if (length < LIMITS.READ_REGISTERS_MIN || length > LIMITS.READ_REGISTERS_MAX) {
|
|
2443
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
3228
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2444
3229
|
return;
|
|
2445
3230
|
}
|
|
2446
3231
|
if (!checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).holdingRegisters)) {
|
|
2447
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
3232
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
2448
3233
|
return;
|
|
2449
3234
|
}
|
|
2450
3235
|
try {
|
|
2451
3236
|
const registers = yield model.readHoldingRegisters(address, length);
|
|
2452
|
-
const bufferTx = Buffer.
|
|
3237
|
+
const bufferTx = Buffer.allocUnsafe(length * 2);
|
|
2453
3238
|
registers.forEach((register, index) => {
|
|
2454
3239
|
bufferTx.writeUInt16BE(register, index * 2);
|
|
2455
3240
|
});
|
|
2456
|
-
yield response(
|
|
3241
|
+
yield response(appLayer.encode(Object.assign(Object.assign({}, frame), { data: prependByte(bufferTx.length, bufferTx) })));
|
|
2457
3242
|
}
|
|
2458
3243
|
catch (error) {
|
|
2459
|
-
yield this.responseError(frame, response, error);
|
|
3244
|
+
yield this.responseError(appLayer, frame, response, error);
|
|
2460
3245
|
}
|
|
2461
3246
|
});
|
|
2462
3247
|
}
|
|
2463
|
-
handleFC4(model, frame, response) {
|
|
3248
|
+
handleFC4(appLayer, model, frame, response) {
|
|
2464
3249
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2465
3250
|
var _a;
|
|
2466
3251
|
if (frame.data.length !== 4) {
|
|
3252
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2467
3253
|
return;
|
|
2468
3254
|
}
|
|
2469
3255
|
if (!model.readInputRegisters) {
|
|
2470
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
3256
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
2471
3257
|
return;
|
|
2472
3258
|
}
|
|
2473
3259
|
const address = (frame.data[0] << 8) | frame.data[1];
|
|
2474
3260
|
const length = (frame.data[2] << 8) | frame.data[3];
|
|
2475
3261
|
if (length < LIMITS.READ_REGISTERS_MIN || length > LIMITS.READ_REGISTERS_MAX) {
|
|
2476
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
3262
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2477
3263
|
return;
|
|
2478
3264
|
}
|
|
2479
3265
|
if (!checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).inputRegisters)) {
|
|
2480
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
3266
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
2481
3267
|
return;
|
|
2482
3268
|
}
|
|
2483
3269
|
try {
|
|
2484
3270
|
const registers = yield model.readInputRegisters(address, length);
|
|
2485
|
-
const bufferTx = Buffer.
|
|
3271
|
+
const bufferTx = Buffer.allocUnsafe(length * 2);
|
|
2486
3272
|
registers.forEach((register, index) => {
|
|
2487
3273
|
bufferTx.writeUInt16BE(register, index * 2);
|
|
2488
3274
|
});
|
|
2489
|
-
yield response(
|
|
3275
|
+
yield response(appLayer.encode(Object.assign(Object.assign({}, frame), { data: prependByte(bufferTx.length, bufferTx) })));
|
|
2490
3276
|
}
|
|
2491
3277
|
catch (error) {
|
|
2492
|
-
yield this.responseError(frame, response, error);
|
|
3278
|
+
yield this.responseError(appLayer, frame, response, error);
|
|
2493
3279
|
}
|
|
2494
3280
|
});
|
|
2495
3281
|
}
|
|
2496
|
-
handleFC5(model, frame, response) {
|
|
3282
|
+
handleFC5(appLayer, model, frame, response) {
|
|
2497
3283
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2498
3284
|
var _a;
|
|
2499
3285
|
if (frame.data.length !== 4) {
|
|
3286
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2500
3287
|
return;
|
|
2501
3288
|
}
|
|
2502
3289
|
if (!model.writeSingleCoil) {
|
|
2503
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
3290
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
2504
3291
|
return;
|
|
2505
3292
|
}
|
|
2506
3293
|
const address = (frame.data[0] << 8) | frame.data[1];
|
|
2507
3294
|
const value = (frame.data[2] << 8) | frame.data[3];
|
|
2508
3295
|
if (value !== COIL_OFF && value !== COIL_ON) {
|
|
2509
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
3296
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2510
3297
|
return;
|
|
2511
3298
|
}
|
|
2512
3299
|
if (!checkRange(address, (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).coils)) {
|
|
2513
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
3300
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
2514
3301
|
return;
|
|
2515
3302
|
}
|
|
2516
3303
|
try {
|
|
2517
3304
|
yield model.writeSingleCoil(address, value === COIL_ON);
|
|
2518
|
-
yield response(
|
|
3305
|
+
yield response(appLayer.encode(frame));
|
|
2519
3306
|
}
|
|
2520
3307
|
catch (error) {
|
|
2521
|
-
yield this.responseError(frame, response, error);
|
|
3308
|
+
yield this.responseError(appLayer, frame, response, error);
|
|
2522
3309
|
}
|
|
2523
3310
|
});
|
|
2524
3311
|
}
|
|
2525
|
-
handleFC6(model, frame, response) {
|
|
3312
|
+
handleFC6(appLayer, model, frame, response) {
|
|
2526
3313
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2527
3314
|
var _a;
|
|
2528
3315
|
if (frame.data.length !== 4) {
|
|
3316
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2529
3317
|
return;
|
|
2530
3318
|
}
|
|
2531
3319
|
if (!model.writeSingleRegister) {
|
|
2532
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
3320
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
2533
3321
|
return;
|
|
2534
3322
|
}
|
|
2535
3323
|
const address = (frame.data[0] << 8) | frame.data[1];
|
|
2536
3324
|
const value = (frame.data[2] << 8) | frame.data[3];
|
|
2537
3325
|
if (!checkRange(address, (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).holdingRegisters)) {
|
|
2538
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
3326
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
2539
3327
|
return;
|
|
2540
3328
|
}
|
|
2541
3329
|
try {
|
|
2542
3330
|
yield model.writeSingleRegister(address, value);
|
|
2543
|
-
yield response(
|
|
3331
|
+
yield response(appLayer.encode(frame));
|
|
2544
3332
|
}
|
|
2545
3333
|
catch (error) {
|
|
2546
|
-
yield this.responseError(frame, response, error);
|
|
3334
|
+
yield this.responseError(appLayer, frame, response, error);
|
|
2547
3335
|
}
|
|
2548
3336
|
});
|
|
2549
3337
|
}
|
|
2550
|
-
handleFC15(model, frame, response) {
|
|
3338
|
+
handleFC15(appLayer, model, frame, response) {
|
|
2551
3339
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2552
3340
|
var _a;
|
|
2553
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));
|
|
2554
3343
|
return;
|
|
2555
3344
|
}
|
|
2556
3345
|
if (!model.writeMultipleCoils && !model.writeSingleCoil) {
|
|
2557
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
3346
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
2558
3347
|
return;
|
|
2559
3348
|
}
|
|
2560
3349
|
const address = (frame.data[0] << 8) | frame.data[1];
|
|
2561
3350
|
const length = (frame.data[2] << 8) | frame.data[3];
|
|
2562
3351
|
const byteCount = frame.data[4];
|
|
2563
3352
|
if (length < LIMITS.READ_COILS_MIN || length > LIMITS.WRITE_COILS_MAX || byteCount !== Math.ceil(length / 8)) {
|
|
2564
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
3353
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2565
3354
|
return;
|
|
2566
3355
|
}
|
|
2567
3356
|
if (!checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).coils)) {
|
|
2568
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
3357
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
2569
3358
|
return;
|
|
2570
3359
|
}
|
|
2571
3360
|
const value = new Array(length);
|
|
@@ -2581,32 +3370,33 @@ class ModbusSlave extends EventEmitter {
|
|
|
2581
3370
|
yield model.writeSingleCoil(address + i, value[i]);
|
|
2582
3371
|
}
|
|
2583
3372
|
}
|
|
2584
|
-
yield response(
|
|
3373
|
+
yield response(appLayer.encode(Object.assign(Object.assign({}, frame), { data: frame.data.subarray(0, 4) })));
|
|
2585
3374
|
}
|
|
2586
3375
|
catch (error) {
|
|
2587
|
-
yield this.responseError(frame, response, error);
|
|
3376
|
+
yield this.responseError(appLayer, frame, response, error);
|
|
2588
3377
|
}
|
|
2589
3378
|
});
|
|
2590
3379
|
}
|
|
2591
|
-
handleFC16(model, frame, response) {
|
|
3380
|
+
handleFC16(appLayer, model, frame, response) {
|
|
2592
3381
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2593
3382
|
var _a;
|
|
2594
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));
|
|
2595
3385
|
return;
|
|
2596
3386
|
}
|
|
2597
3387
|
if (!model.writeMultipleRegisters && !model.writeSingleRegister) {
|
|
2598
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
3388
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
2599
3389
|
return;
|
|
2600
3390
|
}
|
|
2601
3391
|
const address = (frame.data[0] << 8) | frame.data[1];
|
|
2602
3392
|
const length = (frame.data[2] << 8) | frame.data[3];
|
|
2603
3393
|
const byteCount = frame.data[4];
|
|
2604
3394
|
if (length < LIMITS.READ_REGISTERS_MIN || length > LIMITS.WRITE_REGISTERS_MAX || byteCount !== length * 2) {
|
|
2605
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
3395
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2606
3396
|
return;
|
|
2607
3397
|
}
|
|
2608
3398
|
if (!checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).holdingRegisters)) {
|
|
2609
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
3399
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
2610
3400
|
return;
|
|
2611
3401
|
}
|
|
2612
3402
|
const value = new Array(length);
|
|
@@ -2622,21 +3412,22 @@ class ModbusSlave extends EventEmitter {
|
|
|
2622
3412
|
yield model.writeSingleRegister(address + i, value[i]);
|
|
2623
3413
|
}
|
|
2624
3414
|
}
|
|
2625
|
-
yield response(
|
|
3415
|
+
yield response(appLayer.encode(Object.assign(Object.assign({}, frame), { data: frame.data.subarray(0, 4) })));
|
|
2626
3416
|
}
|
|
2627
3417
|
catch (error) {
|
|
2628
|
-
yield this.responseError(frame, response, error);
|
|
3418
|
+
yield this.responseError(appLayer, frame, response, error);
|
|
2629
3419
|
}
|
|
2630
3420
|
});
|
|
2631
3421
|
}
|
|
2632
|
-
handleFC17(model, frame, response) {
|
|
3422
|
+
handleFC17(appLayer, model, frame, response) {
|
|
2633
3423
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2634
3424
|
var _a;
|
|
2635
3425
|
if (frame.data.length !== 0) {
|
|
3426
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2636
3427
|
return;
|
|
2637
3428
|
}
|
|
2638
3429
|
if (!model.reportServerId) {
|
|
2639
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
3430
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
2640
3431
|
return;
|
|
2641
3432
|
}
|
|
2642
3433
|
try {
|
|
@@ -2644,40 +3435,44 @@ class ModbusSlave extends EventEmitter {
|
|
|
2644
3435
|
const serverIdBytes = serverId;
|
|
2645
3436
|
const byteCount = serverIdBytes.length + 1 + additionalData.length;
|
|
2646
3437
|
if (byteCount > 255) {
|
|
2647
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.SERVER_DEVICE_FAILURE));
|
|
3438
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.SERVER_DEVICE_FAILURE));
|
|
2648
3439
|
return;
|
|
2649
3440
|
}
|
|
2650
3441
|
const allBytes = [...serverIdBytes, runIndicatorStatus ? 0xff : 0x00, ...additionalData];
|
|
2651
3442
|
if (allBytes.some((b) => !isUint8(b))) {
|
|
2652
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.SERVER_DEVICE_FAILURE));
|
|
3443
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.SERVER_DEVICE_FAILURE));
|
|
2653
3444
|
return;
|
|
2654
3445
|
}
|
|
2655
|
-
|
|
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 })));
|
|
2656
3450
|
}
|
|
2657
3451
|
catch (error) {
|
|
2658
|
-
yield this.responseError(frame, response, error);
|
|
3452
|
+
yield this.responseError(appLayer, frame, response, error);
|
|
2659
3453
|
}
|
|
2660
3454
|
});
|
|
2661
3455
|
}
|
|
2662
|
-
handleFC22(model, frame, response) {
|
|
3456
|
+
handleFC22(appLayer, model, frame, response) {
|
|
2663
3457
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2664
3458
|
var _a;
|
|
2665
3459
|
if (frame.data.length !== 6) {
|
|
3460
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2666
3461
|
return;
|
|
2667
3462
|
}
|
|
2668
3463
|
if (!model.maskWriteRegister && !(model.readHoldingRegisters && model.writeSingleRegister)) {
|
|
2669
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
3464
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
2670
3465
|
return;
|
|
2671
3466
|
}
|
|
2672
3467
|
const address = (frame.data[0] << 8) | frame.data[1];
|
|
2673
3468
|
const andMask = (frame.data[2] << 8) | frame.data[3];
|
|
2674
3469
|
const orMask = (frame.data[4] << 8) | frame.data[5];
|
|
2675
3470
|
if (!checkRange(address, (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).holdingRegisters)) {
|
|
2676
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
3471
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
2677
3472
|
return;
|
|
2678
3473
|
}
|
|
2679
3474
|
try {
|
|
2680
|
-
yield this.
|
|
3475
|
+
yield this._withAddressLock([address], () => __awaiter(this, void 0, void 0, function* () {
|
|
2681
3476
|
if (model.maskWriteRegister) {
|
|
2682
3477
|
yield model.maskWriteRegister(address, andMask, orMask);
|
|
2683
3478
|
}
|
|
@@ -2686,21 +3481,22 @@ class ModbusSlave extends EventEmitter {
|
|
|
2686
3481
|
yield model.writeSingleRegister(address, (value & andMask) | (orMask & (~andMask & 0xffff)));
|
|
2687
3482
|
}
|
|
2688
3483
|
}));
|
|
2689
|
-
yield response(
|
|
3484
|
+
yield response(appLayer.encode(frame));
|
|
2690
3485
|
}
|
|
2691
3486
|
catch (error) {
|
|
2692
|
-
yield this.responseError(frame, response, error);
|
|
3487
|
+
yield this.responseError(appLayer, frame, response, error);
|
|
2693
3488
|
}
|
|
2694
3489
|
});
|
|
2695
3490
|
}
|
|
2696
|
-
handleFC23(model, frame, response) {
|
|
3491
|
+
handleFC23(appLayer, model, frame, response) {
|
|
2697
3492
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2698
3493
|
var _a;
|
|
2699
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));
|
|
2700
3496
|
return;
|
|
2701
3497
|
}
|
|
2702
3498
|
if (!model.readHoldingRegisters || (!model.writeMultipleRegisters && !model.writeSingleRegister)) {
|
|
2703
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
3499
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
2704
3500
|
return;
|
|
2705
3501
|
}
|
|
2706
3502
|
const address = {
|
|
@@ -2717,11 +3513,11 @@ class ModbusSlave extends EventEmitter {
|
|
|
2717
3513
|
length.write < LIMITS.READ_REGISTERS_MIN ||
|
|
2718
3514
|
length.write > LIMITS.RW_REGISTERS_WRITE_MAX ||
|
|
2719
3515
|
byteCount !== length.write * 2) {
|
|
2720
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
3516
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2721
3517
|
return;
|
|
2722
3518
|
}
|
|
2723
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)) {
|
|
2724
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
3520
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
2725
3521
|
return;
|
|
2726
3522
|
}
|
|
2727
3523
|
const value = new Array(length.write);
|
|
@@ -2730,7 +3526,7 @@ class ModbusSlave extends EventEmitter {
|
|
|
2730
3526
|
}
|
|
2731
3527
|
try {
|
|
2732
3528
|
const writeAddresses = Array.from({ length: length.write }, (_, i) => address.write + i);
|
|
2733
|
-
yield this.
|
|
3529
|
+
yield this._withAddressLock(writeAddresses, () => __awaiter(this, void 0, void 0, function* () {
|
|
2734
3530
|
if (model.writeMultipleRegisters) {
|
|
2735
3531
|
yield model.writeMultipleRegisters(address.write, value);
|
|
2736
3532
|
}
|
|
@@ -2741,18 +3537,18 @@ class ModbusSlave extends EventEmitter {
|
|
|
2741
3537
|
}
|
|
2742
3538
|
}));
|
|
2743
3539
|
const registers = yield model.readHoldingRegisters(address.read, length.read);
|
|
2744
|
-
const bufferTx = Buffer.
|
|
3540
|
+
const bufferTx = Buffer.allocUnsafe(length.read * 2);
|
|
2745
3541
|
registers.forEach((register, index) => {
|
|
2746
3542
|
bufferTx.writeUInt16BE(register, index * 2);
|
|
2747
3543
|
});
|
|
2748
|
-
yield response(
|
|
3544
|
+
yield response(appLayer.encode(Object.assign(Object.assign({}, frame), { data: prependByte(bufferTx.length, bufferTx) })));
|
|
2749
3545
|
}
|
|
2750
3546
|
catch (error) {
|
|
2751
|
-
yield this.responseError(frame, response, error);
|
|
3547
|
+
yield this.responseError(appLayer, frame, response, error);
|
|
2752
3548
|
}
|
|
2753
3549
|
});
|
|
2754
3550
|
}
|
|
2755
|
-
handleFC43_14(model, frame, response) {
|
|
3551
|
+
handleFC43_14(appLayer, model, frame, response) {
|
|
2756
3552
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2757
3553
|
if (frame.data.length === 3) {
|
|
2758
3554
|
if (frame.data[0] === MEI_READ_DEVICE_ID && model.readDeviceIdentification) {
|
|
@@ -2779,13 +3575,13 @@ class ModbusSlave extends EventEmitter {
|
|
|
2779
3575
|
}
|
|
2780
3576
|
case exports.ReadDeviceIDCode.SPECIFIC_ACCESS: {
|
|
2781
3577
|
if (objectID > 0x06 && objectID < 0x80) {
|
|
2782
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
3578
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
2783
3579
|
return;
|
|
2784
3580
|
}
|
|
2785
3581
|
break;
|
|
2786
3582
|
}
|
|
2787
3583
|
default: {
|
|
2788
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
3584
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2789
3585
|
return;
|
|
2790
3586
|
}
|
|
2791
3587
|
}
|
|
@@ -2805,7 +3601,7 @@ class ModbusSlave extends EventEmitter {
|
|
|
2805
3601
|
}
|
|
2806
3602
|
if (!objects.has(objectID)) {
|
|
2807
3603
|
if (readDeviceIDCode === exports.ReadDeviceIDCode.SPECIFIC_ACCESS) {
|
|
2808
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
3604
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
2809
3605
|
return;
|
|
2810
3606
|
}
|
|
2811
3607
|
objectID = 0x00;
|
|
@@ -2813,7 +3609,7 @@ class ModbusSlave extends EventEmitter {
|
|
|
2813
3609
|
let maxId = 0;
|
|
2814
3610
|
for (const id of objects.keys()) {
|
|
2815
3611
|
if ((id >= 0x07 && id <= 0x7f) || id > 0xff) {
|
|
2816
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.SERVER_DEVICE_FAILURE));
|
|
3612
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.SERVER_DEVICE_FAILURE));
|
|
2817
3613
|
return;
|
|
2818
3614
|
}
|
|
2819
3615
|
if (id > maxId) {
|
|
@@ -2830,97 +3626,104 @@ class ModbusSlave extends EventEmitter {
|
|
|
2830
3626
|
}
|
|
2831
3627
|
const byteLength = Buffer.byteLength(value);
|
|
2832
3628
|
if (byteLength > 245) {
|
|
2833
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.SERVER_DEVICE_FAILURE));
|
|
3629
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.SERVER_DEVICE_FAILURE));
|
|
2834
3630
|
return;
|
|
2835
3631
|
}
|
|
2836
3632
|
if (lastID !== 0) {
|
|
2837
3633
|
continue;
|
|
2838
3634
|
}
|
|
2839
3635
|
if (byteLength + 2 > 253 - totalLength) {
|
|
2840
|
-
|
|
2841
|
-
lastID = id;
|
|
2842
|
-
}
|
|
3636
|
+
lastID = id;
|
|
2843
3637
|
}
|
|
2844
3638
|
else {
|
|
2845
3639
|
totalLength += byteLength + 2;
|
|
2846
|
-
ids.push(id);
|
|
3640
|
+
ids.push({ id, byteLength });
|
|
2847
3641
|
if (readDeviceIDCode === exports.ReadDeviceIDCode.SPECIFIC_ACCESS) {
|
|
2848
3642
|
break;
|
|
2849
3643
|
}
|
|
2850
3644
|
}
|
|
2851
3645
|
}
|
|
2852
|
-
ids.sort((a, b) => a - b);
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
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) {
|
|
2856
3660
|
const value = objects.get(id);
|
|
2857
|
-
|
|
2858
|
-
|
|
3661
|
+
data[offset++] = id;
|
|
3662
|
+
data[offset++] = byteLength;
|
|
3663
|
+
offset += data.write(value, offset);
|
|
2859
3664
|
}
|
|
2860
|
-
yield response(
|
|
3665
|
+
yield response(appLayer.encode(Object.assign(Object.assign({}, frame), { data })));
|
|
2861
3666
|
}
|
|
2862
3667
|
catch (error) {
|
|
2863
|
-
yield this.responseError(frame, response, error);
|
|
3668
|
+
yield this.responseError(appLayer, frame, response, error);
|
|
2864
3669
|
}
|
|
2865
3670
|
}
|
|
2866
3671
|
else {
|
|
2867
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
3672
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
2868
3673
|
}
|
|
2869
3674
|
}
|
|
2870
3675
|
});
|
|
2871
3676
|
}
|
|
2872
|
-
responseError(frame, response, error) {
|
|
3677
|
+
responseError(appLayer, frame, response, error) {
|
|
2873
3678
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2874
|
-
yield response(
|
|
3679
|
+
yield response(appLayer.encode(Object.assign(Object.assign({}, frame), { fc: frame.fc | EXCEPTION_OFFSET, data: Buffer.from([getCodeByError(error)]) })));
|
|
2875
3680
|
});
|
|
2876
3681
|
}
|
|
2877
|
-
_drain(
|
|
3682
|
+
_drain(appLayer, q) {
|
|
2878
3683
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2879
|
-
if (q.processing) {
|
|
3684
|
+
if (q.processing || this._physicalLayer.state !== exports.PhysicalState.OPEN) {
|
|
2880
3685
|
return;
|
|
2881
3686
|
}
|
|
2882
3687
|
q.processing = true;
|
|
2883
3688
|
try {
|
|
2884
|
-
while (q.
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
yield this._processFrame(frame, response);
|
|
2888
|
-
}
|
|
2889
|
-
catch (error) {
|
|
2890
|
-
this.emit('error', error);
|
|
3689
|
+
while (q.frames.length > 0) {
|
|
3690
|
+
if (this._physicalLayer.state !== exports.PhysicalState.OPEN) {
|
|
3691
|
+
return;
|
|
2891
3692
|
}
|
|
3693
|
+
const frame = q.frames.shift();
|
|
3694
|
+
yield this._processFrame(appLayer, frame);
|
|
2892
3695
|
}
|
|
2893
3696
|
}
|
|
2894
3697
|
finally {
|
|
2895
3698
|
q.processing = false;
|
|
2896
|
-
// Cleanup empty queue entries so the map doesn't grow unbounded across
|
|
2897
|
-
// ephemeral connections (UDP rinfo, brief TCP clients).
|
|
2898
|
-
if (q.items.length === 0 && this._queues.get(key) === q) {
|
|
2899
|
-
this._queues.delete(key);
|
|
2900
|
-
}
|
|
2901
3699
|
}
|
|
2902
3700
|
});
|
|
2903
3701
|
}
|
|
2904
|
-
_processFrame(
|
|
3702
|
+
_processFrame(appLayer, frame) {
|
|
2905
3703
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2906
3704
|
const response = (data) => __awaiter(this, void 0, void 0, function* () {
|
|
2907
|
-
if (frame.unit === 0x00) {
|
|
3705
|
+
if (frame.unit === 0x00 || this._physicalLayer.state !== exports.PhysicalState.OPEN) {
|
|
2908
3706
|
return;
|
|
2909
3707
|
}
|
|
2910
3708
|
try {
|
|
2911
|
-
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. */
|
|
2912
3713
|
}
|
|
2913
|
-
catch (error) { }
|
|
2914
3714
|
});
|
|
2915
3715
|
for (const model of frame.unit === 0x00 ? this.models.values() : [this.models.get(frame.unit)]) {
|
|
2916
|
-
|
|
3716
|
+
if (this._physicalLayer.state !== exports.PhysicalState.OPEN) {
|
|
3717
|
+
return;
|
|
3718
|
+
}
|
|
3719
|
+
const intercepted = yield this._intercept(appLayer, model, frame, response);
|
|
2917
3720
|
if (!intercepted) {
|
|
2918
|
-
yield this._handleFC(model, frame, response);
|
|
3721
|
+
yield this._handleFC(appLayer, model, frame, response);
|
|
2919
3722
|
}
|
|
2920
3723
|
}
|
|
2921
3724
|
});
|
|
2922
3725
|
}
|
|
2923
|
-
_intercept(model, frame, response) {
|
|
3726
|
+
_intercept(appLayer, model, frame, response) {
|
|
2924
3727
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2925
3728
|
if (!model.interceptor) {
|
|
2926
3729
|
return false;
|
|
@@ -2928,18 +3731,18 @@ class ModbusSlave extends EventEmitter {
|
|
|
2928
3731
|
try {
|
|
2929
3732
|
const data = yield model.interceptor(frame.fc, frame.data);
|
|
2930
3733
|
if (data !== undefined) {
|
|
2931
|
-
yield response(
|
|
3734
|
+
yield response(appLayer.encode(Object.assign(Object.assign({}, frame), { data })));
|
|
2932
3735
|
return true;
|
|
2933
3736
|
}
|
|
2934
3737
|
return false;
|
|
2935
3738
|
}
|
|
2936
3739
|
catch (error) {
|
|
2937
|
-
yield this.responseError(frame, response, error);
|
|
3740
|
+
yield this.responseError(appLayer, frame, response, error);
|
|
2938
3741
|
return true;
|
|
2939
3742
|
}
|
|
2940
3743
|
});
|
|
2941
3744
|
}
|
|
2942
|
-
|
|
3745
|
+
_withAddressLock(addresses, fn) {
|
|
2943
3746
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2944
3747
|
const sorted = [...new Set(addresses)].sort((a, b) => a - b);
|
|
2945
3748
|
const previous = sorted.map((addr) => { var _a; return (_a = this._locks.get(addr)) !== null && _a !== void 0 ? _a : Promise.resolve(); });
|
|
@@ -2962,55 +3765,55 @@ class ModbusSlave extends EventEmitter {
|
|
|
2962
3765
|
}
|
|
2963
3766
|
});
|
|
2964
3767
|
}
|
|
2965
|
-
_handleFC(model, frame, response) {
|
|
3768
|
+
_handleFC(appLayer, model, frame, response) {
|
|
2966
3769
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2967
3770
|
switch (frame.fc) {
|
|
2968
3771
|
case exports.FunctionCode.READ_COILS: {
|
|
2969
|
-
yield this.handleFC1(model, frame, response);
|
|
3772
|
+
yield this.handleFC1(appLayer, model, frame, response);
|
|
2970
3773
|
break;
|
|
2971
3774
|
}
|
|
2972
3775
|
case exports.FunctionCode.READ_DISCRETE_INPUTS: {
|
|
2973
|
-
yield this.handleFC2(model, frame, response);
|
|
3776
|
+
yield this.handleFC2(appLayer, model, frame, response);
|
|
2974
3777
|
break;
|
|
2975
3778
|
}
|
|
2976
3779
|
case exports.FunctionCode.READ_HOLDING_REGISTERS: {
|
|
2977
|
-
yield this.handleFC3(model, frame, response);
|
|
3780
|
+
yield this.handleFC3(appLayer, model, frame, response);
|
|
2978
3781
|
break;
|
|
2979
3782
|
}
|
|
2980
3783
|
case exports.FunctionCode.READ_INPUT_REGISTERS: {
|
|
2981
|
-
yield this.handleFC4(model, frame, response);
|
|
3784
|
+
yield this.handleFC4(appLayer, model, frame, response);
|
|
2982
3785
|
break;
|
|
2983
3786
|
}
|
|
2984
3787
|
case exports.FunctionCode.WRITE_SINGLE_COIL: {
|
|
2985
|
-
yield this.handleFC5(model, frame, response);
|
|
3788
|
+
yield this.handleFC5(appLayer, model, frame, response);
|
|
2986
3789
|
break;
|
|
2987
3790
|
}
|
|
2988
3791
|
case exports.FunctionCode.WRITE_SINGLE_REGISTER: {
|
|
2989
|
-
yield this.handleFC6(model, frame, response);
|
|
3792
|
+
yield this.handleFC6(appLayer, model, frame, response);
|
|
2990
3793
|
break;
|
|
2991
3794
|
}
|
|
2992
3795
|
case exports.FunctionCode.WRITE_MULTIPLE_COILS: {
|
|
2993
|
-
yield this.handleFC15(model, frame, response);
|
|
3796
|
+
yield this.handleFC15(appLayer, model, frame, response);
|
|
2994
3797
|
break;
|
|
2995
3798
|
}
|
|
2996
3799
|
case exports.FunctionCode.WRITE_MULTIPLE_REGISTERS: {
|
|
2997
|
-
yield this.handleFC16(model, frame, response);
|
|
3800
|
+
yield this.handleFC16(appLayer, model, frame, response);
|
|
2998
3801
|
break;
|
|
2999
3802
|
}
|
|
3000
3803
|
case exports.FunctionCode.REPORT_SERVER_ID: {
|
|
3001
|
-
yield this.handleFC17(model, frame, response);
|
|
3804
|
+
yield this.handleFC17(appLayer, model, frame, response);
|
|
3002
3805
|
break;
|
|
3003
3806
|
}
|
|
3004
3807
|
case exports.FunctionCode.MASK_WRITE_REGISTER: {
|
|
3005
|
-
yield this.handleFC22(model, frame, response);
|
|
3808
|
+
yield this.handleFC22(appLayer, model, frame, response);
|
|
3006
3809
|
break;
|
|
3007
3810
|
}
|
|
3008
3811
|
case exports.FunctionCode.READ_WRITE_MULTIPLE_REGISTERS: {
|
|
3009
|
-
yield this.handleFC23(model, frame, response);
|
|
3812
|
+
yield this.handleFC23(appLayer, model, frame, response);
|
|
3010
3813
|
break;
|
|
3011
3814
|
}
|
|
3012
3815
|
case exports.FunctionCode.READ_DEVICE_IDENTIFICATION: {
|
|
3013
|
-
yield this.handleFC43_14(model, frame, response);
|
|
3816
|
+
yield this.handleFC43_14(appLayer, model, frame, response);
|
|
3014
3817
|
break;
|
|
3015
3818
|
}
|
|
3016
3819
|
default: {
|
|
@@ -3018,14 +3821,14 @@ class ModbusSlave extends EventEmitter {
|
|
|
3018
3821
|
if (cfc === null || cfc === void 0 ? void 0 : cfc.handle) {
|
|
3019
3822
|
try {
|
|
3020
3823
|
const responseData = yield cfc.handle(frame.data, frame.unit);
|
|
3021
|
-
yield response(
|
|
3824
|
+
yield response(appLayer.encode(Object.assign(Object.assign({}, frame), { data: responseData })));
|
|
3022
3825
|
}
|
|
3023
3826
|
catch (error) {
|
|
3024
|
-
yield this.responseError(frame, response, error);
|
|
3827
|
+
yield this.responseError(appLayer, frame, response, error);
|
|
3025
3828
|
}
|
|
3026
3829
|
}
|
|
3027
3830
|
else {
|
|
3028
|
-
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
3831
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
3029
3832
|
}
|
|
3030
3833
|
break;
|
|
3031
3834
|
}
|
|
@@ -3041,53 +3844,70 @@ class ModbusSlave extends EventEmitter {
|
|
|
3041
3844
|
}
|
|
3042
3845
|
addCustomFunctionCode(cfc) {
|
|
3043
3846
|
this._customFunctionCodes.set(cfc.fc, cfc);
|
|
3044
|
-
this.
|
|
3847
|
+
for (const appLayer of this._appLayers.keys()) {
|
|
3848
|
+
appLayer.addCustomFunctionCode(cfc);
|
|
3849
|
+
}
|
|
3045
3850
|
}
|
|
3046
3851
|
removeCustomFunctionCode(fc) {
|
|
3047
3852
|
this._customFunctionCodes.delete(fc);
|
|
3048
|
-
this.
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
this.
|
|
3062
|
-
|
|
3063
|
-
this._customFunctionCodes.clear();
|
|
3064
|
-
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'));
|
|
3065
3868
|
}
|
|
3066
|
-
this.
|
|
3869
|
+
return this._physicalLayer.open(...args);
|
|
3067
3870
|
}
|
|
3068
|
-
|
|
3069
|
-
this
|
|
3070
|
-
|
|
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
|
+
});
|
|
3071
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
|
+
*/
|
|
3072
3898
|
close() {
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
if (this._cleanLevel === 'destroy') {
|
|
3081
|
-
return Promise.resolve();
|
|
3082
|
-
}
|
|
3083
|
-
this._clean('destroy');
|
|
3084
|
-
this.removeAllListeners();
|
|
3085
|
-
this.applicationLayer.destroy();
|
|
3086
|
-
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
|
+
});
|
|
3087
3906
|
}
|
|
3088
3907
|
}
|
|
3089
3908
|
|
|
3090
3909
|
exports.AbstractApplicationLayer = AbstractApplicationLayer;
|
|
3910
|
+
exports.AbstractPhysicalConnection = AbstractPhysicalConnection;
|
|
3091
3911
|
exports.AbstractPhysicalLayer = AbstractPhysicalLayer;
|
|
3092
3912
|
exports.AsciiApplicationLayer = AsciiApplicationLayer;
|
|
3093
3913
|
exports.COIL_OFF = COIL_OFF;
|
|
@@ -3097,7 +3917,6 @@ exports.LIMITS = LIMITS;
|
|
|
3097
3917
|
exports.MEI_READ_DEVICE_ID = MEI_READ_DEVICE_ID;
|
|
3098
3918
|
exports.MasterSession = MasterSession;
|
|
3099
3919
|
exports.ModbusError = ModbusError;
|
|
3100
|
-
exports.ModbusErrorCode = ModbusErrorCode;
|
|
3101
3920
|
exports.ModbusMaster = ModbusMaster;
|
|
3102
3921
|
exports.ModbusSlave = ModbusSlave;
|
|
3103
3922
|
exports.RtuApplicationLayer = RtuApplicationLayer;
|
|
@@ -3105,6 +3924,8 @@ exports.SerialPhysicalLayer = SerialPhysicalLayer;
|
|
|
3105
3924
|
exports.TcpApplicationLayer = TcpApplicationLayer;
|
|
3106
3925
|
exports.TcpClientPhysicalLayer = TcpClientPhysicalLayer;
|
|
3107
3926
|
exports.TcpServerPhysicalLayer = TcpServerPhysicalLayer;
|
|
3108
|
-
exports.
|
|
3927
|
+
exports.UdpClientPhysicalLayer = UdpClientPhysicalLayer;
|
|
3928
|
+
exports.UdpServerPhysicalLayer = UdpServerPhysicalLayer;
|
|
3929
|
+
exports.createPhysicalLayer = createPhysicalLayer;
|
|
3109
3930
|
exports.getCodeByError = getCodeByError;
|
|
3110
3931
|
exports.getErrorByCode = getErrorByCode;
|