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