njs-modbus 2.0.1 → 3.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +265 -154
- package/README.zh-CN.md +314 -0
- package/dist/index.cjs +1887 -1074
- package/dist/index.d.ts +369 -217
- package/dist/index.mjs +1884 -1073
- package/dist/src/error-code.d.ts +2 -24
- package/dist/src/layers/application/abstract-application-layer.d.ts +12 -7
- package/dist/src/layers/application/ascii-application-layer.d.ts +8 -9
- package/dist/src/layers/application/rtu-application-layer.d.ts +21 -44
- package/dist/src/layers/application/tcp-application-layer.d.ts +8 -6
- package/dist/src/layers/physical/abstract-physical-layer.d.ts +40 -12
- package/dist/src/layers/physical/index.d.ts +9 -3
- package/dist/src/layers/physical/serial-physical-layer.d.ts +43 -13
- package/dist/src/layers/physical/tcp-client-physical-layer.d.ts +12 -10
- package/dist/src/layers/physical/tcp-physical-connection.d.ts +17 -0
- package/dist/src/layers/physical/tcp-server-physical-layer.d.ts +23 -12
- package/dist/src/layers/physical/udp-client-physical-layer.d.ts +35 -0
- package/dist/src/layers/physical/udp-server-physical-layer.d.ts +54 -0
- package/dist/src/layers/physical/utils.d.ts +39 -0
- package/dist/src/layers/physical/vars.d.ts +11 -0
- package/dist/src/master/master.d.ts +45 -18
- package/dist/src/slave/slave.d.ts +57 -33
- package/dist/src/types.d.ts +3 -3
- package/dist/src/utils/index.d.ts +4 -2
- package/dist/src/utils/rtu-timing.d.ts +49 -0
- package/dist/src/utils/whitelist.d.ts +11 -0
- package/package.json +9 -7
- package/dist/src/layers/physical/udp-physical-layer.d.ts +0 -42
- package/dist/src/utils/genConnectionId.d.ts +0 -2
- package/dist/test/adu-buffer.test.d.ts +0 -1
- package/dist/test/ascii-hex-sentry.test.d.ts +0 -1
- package/dist/test/ascii-hex-validation.test.d.ts +0 -1
- package/dist/test/ascii-tcp-fragmentation.test.d.ts +0 -1
- package/dist/test/check-range.test.d.ts +0 -1
- package/dist/test/fallback-atomic.test.d.ts +0 -1
- package/dist/test/fallback-serial.test.d.ts +0 -1
- package/dist/test/fc17-serverid-validation.test.d.ts +0 -1
- package/dist/test/fc43-conformity.test.d.ts +0 -1
- package/dist/test/fc43-utf8-objects.test.d.ts +0 -1
- package/dist/test/gen-connection-id.test.d.ts +0 -1
- package/dist/test/helpers/raw-tcp.d.ts +0 -38
- package/dist/test/master-concurrent.test.d.ts +0 -1
- package/dist/test/modbus-error.test.d.ts +0 -1
- package/dist/test/physical-lifecycle.test.d.ts +0 -1
- package/dist/test/predict-rtu.test.d.ts +0 -1
- package/dist/test/rtu-custom-fc.test.d.ts +0 -1
- package/dist/test/rtu-pool-overflow.test.d.ts +0 -1
- package/dist/test/rtu-t15-timing.test.d.ts +0 -1
- package/dist/test/rtu-t35-default.test.d.ts +0 -1
- package/dist/test/rtu-t35-strict.test.d.ts +0 -1
- package/dist/test/rtu-tcp-fragmentation.test.d.ts +0 -1
- package/dist/test/serial-e2e.test.d.ts +0 -1
- package/dist/test/slave-multi-connection.test.d.ts +0 -1
- package/dist/test/slave.test.d.ts +0 -1
- package/dist/test/tcp-fragmentation.test.d.ts +0 -1
- package/dist/test/udp-multi-client.test.d.ts +0 -1
package/dist/index.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
1416
|
}
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
}
|
|
721
|
-
catch (_a) {
|
|
722
|
-
resolve();
|
|
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.
|
|
1790
|
+
connection.on('close', onClose);
|
|
1791
|
+
this._cleanupFns.add(() => connection.off('close', onClose));
|
|
1134
1792
|
}
|
|
1135
|
-
|
|
1136
|
-
|
|
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;
|
|
1142
|
-
}
|
|
1143
|
-
clearStateTimers(state) {
|
|
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.length = 0;
|
|
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;
|
|
1453
|
-
}
|
|
1454
|
-
flush() {
|
|
1455
|
-
this._states.clear();
|
|
2071
|
+
connection.on('close', onClose);
|
|
2072
|
+
this._cleanupFns.add(() => connection.off('close', onClose));
|
|
1456
2073
|
}
|
|
1457
|
-
framing(
|
|
1458
|
-
if (
|
|
1459
|
-
this.emit('framing-error', new
|
|
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.length = 0;
|
|
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.length = 0;
|
|
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,47 +2398,59 @@ class ModbusMaster extends EventEmitter {
|
|
|
1776
2398
|
writable: true,
|
|
1777
2399
|
value: new MasterSession()
|
|
1778
2400
|
});
|
|
1779
|
-
Object.defineProperty(this, "
|
|
2401
|
+
Object.defineProperty(this, "_physicalLayer", {
|
|
1780
2402
|
enumerable: true,
|
|
1781
2403
|
configurable: true,
|
|
1782
2404
|
writable: true,
|
|
1783
|
-
value:
|
|
2405
|
+
value: void 0
|
|
1784
2406
|
});
|
|
1785
|
-
Object.defineProperty(this, "
|
|
2407
|
+
Object.defineProperty(this, "_protocol", {
|
|
1786
2408
|
enumerable: true,
|
|
1787
2409
|
configurable: true,
|
|
1788
2410
|
writable: true,
|
|
1789
|
-
value:
|
|
2411
|
+
value: void 0
|
|
1790
2412
|
});
|
|
1791
|
-
Object.defineProperty(this, "
|
|
2413
|
+
Object.defineProperty(this, "_appLayer", {
|
|
1792
2414
|
enumerable: true,
|
|
1793
2415
|
configurable: true,
|
|
1794
2416
|
writable: true,
|
|
1795
|
-
value:
|
|
2417
|
+
value: void 0
|
|
2418
|
+
});
|
|
2419
|
+
Object.defineProperty(this, "_customFunctionCodes", {
|
|
2420
|
+
enumerable: true,
|
|
2421
|
+
configurable: true,
|
|
2422
|
+
writable: true,
|
|
2423
|
+
value: new Map()
|
|
2424
|
+
});
|
|
2425
|
+
Object.defineProperty(this, "_queue", {
|
|
2426
|
+
enumerable: true,
|
|
2427
|
+
configurable: true,
|
|
2428
|
+
writable: true,
|
|
2429
|
+
value: []
|
|
1796
2430
|
});
|
|
1797
|
-
Object.defineProperty(this, "
|
|
2431
|
+
Object.defineProperty(this, "_draining", {
|
|
1798
2432
|
enumerable: true,
|
|
1799
2433
|
configurable: true,
|
|
1800
2434
|
writable: true,
|
|
1801
2435
|
value: false
|
|
1802
2436
|
});
|
|
1803
|
-
Object.defineProperty(this, "
|
|
2437
|
+
Object.defineProperty(this, "_nextTid", {
|
|
1804
2438
|
enumerable: true,
|
|
1805
2439
|
configurable: true,
|
|
1806
2440
|
writable: true,
|
|
1807
|
-
value:
|
|
2441
|
+
value: 1
|
|
1808
2442
|
});
|
|
1809
|
-
Object.defineProperty(this, "
|
|
2443
|
+
Object.defineProperty(this, "_cleanupFns", {
|
|
1810
2444
|
enumerable: true,
|
|
1811
2445
|
configurable: true,
|
|
1812
2446
|
writable: true,
|
|
1813
|
-
value:
|
|
2447
|
+
value: new Set()
|
|
1814
2448
|
});
|
|
1815
|
-
Object.defineProperty(this, "
|
|
2449
|
+
Object.defineProperty(this, "_closePromise", {
|
|
1816
2450
|
enumerable: true,
|
|
1817
2451
|
configurable: true,
|
|
1818
2452
|
writable: true,
|
|
1819
|
-
value:
|
|
2453
|
+
value: null
|
|
1820
2454
|
});
|
|
1821
2455
|
Object.defineProperty(this, "writeFC1", {
|
|
1822
2456
|
enumerable: true,
|
|
@@ -1892,10 +2526,60 @@ class ModbusMaster extends EventEmitter {
|
|
|
1892
2526
|
});
|
|
1893
2527
|
this.timeout = (_a = options.timeout) !== null && _a !== void 0 ? _a : 1000;
|
|
1894
2528
|
this.concurrent = (_b = options.concurrent) !== null && _b !== void 0 ? _b : false;
|
|
1895
|
-
|
|
1896
|
-
|
|
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');
|
|
1897
2533
|
}
|
|
1898
|
-
|
|
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));
|
|
1899
2583
|
this.writeFC1 = this.readCoils;
|
|
1900
2584
|
this.writeFC2 = this.readDiscreteInputs;
|
|
1901
2585
|
this.writeFC3 = this.readHoldingRegisters;
|
|
@@ -1908,20 +2592,27 @@ class ModbusMaster extends EventEmitter {
|
|
|
1908
2592
|
this.handleFC22 = this.maskWriteRegister;
|
|
1909
2593
|
this.handleFC23 = this.readAndWriteMultipleRegisters;
|
|
1910
2594
|
this.handleFC43_14 = this.readDeviceIdentification;
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
this.
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
}
|
|
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);
|
|
1923
2611
|
}
|
|
1924
2612
|
send(adu, preCheck, timeout, broadcast) {
|
|
2613
|
+
if (this._physicalLayer.state !== PhysicalState.OPEN) {
|
|
2614
|
+
return Promise.reject(new Error('Master is not open'));
|
|
2615
|
+
}
|
|
1925
2616
|
const task = () => this._exchange(adu, preCheck, timeout, broadcast);
|
|
1926
2617
|
if (this.concurrent) {
|
|
1927
2618
|
return task();
|
|
@@ -1941,14 +2632,20 @@ class ModbusMaster extends EventEmitter {
|
|
|
1941
2632
|
}
|
|
1942
2633
|
_drain() {
|
|
1943
2634
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1944
|
-
if (this._draining) {
|
|
2635
|
+
if (this._draining || this._physicalLayer.state !== PhysicalState.OPEN) {
|
|
1945
2636
|
return;
|
|
1946
2637
|
}
|
|
1947
2638
|
this._draining = true;
|
|
1948
2639
|
try {
|
|
1949
2640
|
while (this._queue.length > 0) {
|
|
2641
|
+
if (this._physicalLayer.state !== PhysicalState.OPEN) {
|
|
2642
|
+
return;
|
|
2643
|
+
}
|
|
1950
2644
|
const item = this._queue.shift();
|
|
1951
|
-
|
|
2645
|
+
try {
|
|
2646
|
+
yield item.run();
|
|
2647
|
+
}
|
|
2648
|
+
catch (_a) { }
|
|
1952
2649
|
}
|
|
1953
2650
|
}
|
|
1954
2651
|
finally {
|
|
@@ -1958,18 +2655,26 @@ class ModbusMaster extends EventEmitter {
|
|
|
1958
2655
|
}
|
|
1959
2656
|
_exchange(adu, preCheck, timeout, broadcast) {
|
|
1960
2657
|
return new Promise((resolve, reject) => {
|
|
1961
|
-
if (this.
|
|
1962
|
-
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'));
|
|
1963
2665
|
return;
|
|
1964
2666
|
}
|
|
1965
|
-
const
|
|
2667
|
+
const connection = appLayer.connection;
|
|
2668
|
+
const usesTid = appLayer.PROTOCOL === 'TCP' && !broadcast;
|
|
1966
2669
|
let tid;
|
|
1967
2670
|
if (usesTid) {
|
|
1968
|
-
|
|
1969
|
-
|
|
2671
|
+
do {
|
|
2672
|
+
tid = this._nextTid;
|
|
2673
|
+
this._nextTid = (this._nextTid + 1) % 65536 || 1;
|
|
2674
|
+
} while (this._masterSession.has(tid));
|
|
1970
2675
|
}
|
|
1971
2676
|
const key = tid !== null && tid !== void 0 ? tid : FIFO_KEY;
|
|
1972
|
-
const payload =
|
|
2677
|
+
const payload = appLayer.encode(Object.assign(Object.assign({}, adu), { transaction: tid }));
|
|
1973
2678
|
let settled = false;
|
|
1974
2679
|
const settle = (error, frame) => {
|
|
1975
2680
|
if (settled) {
|
|
@@ -1985,18 +2690,22 @@ class ModbusMaster extends EventEmitter {
|
|
|
1985
2690
|
resolve(frame);
|
|
1986
2691
|
}
|
|
1987
2692
|
};
|
|
1988
|
-
const timer = setTimeout(() => settle(new
|
|
2693
|
+
const timer = setTimeout(() => settle(new Error('Timeout')), timeout);
|
|
1989
2694
|
// FIFO mode: clear stale buffer state before this request.
|
|
1990
2695
|
// Concurrent mode: must not flush — other in-flight requests share the buffer.
|
|
1991
2696
|
if (!this.concurrent) {
|
|
1992
|
-
|
|
2697
|
+
appLayer.flush();
|
|
1993
2698
|
}
|
|
1994
|
-
|
|
2699
|
+
connection
|
|
1995
2700
|
.write(payload)
|
|
1996
2701
|
.then(() => {
|
|
1997
2702
|
if (settled) {
|
|
1998
2703
|
return;
|
|
1999
2704
|
}
|
|
2705
|
+
if (this._physicalLayer.state !== PhysicalState.OPEN) {
|
|
2706
|
+
settle(new Error('Master is not open'));
|
|
2707
|
+
return;
|
|
2708
|
+
}
|
|
2000
2709
|
if (broadcast) {
|
|
2001
2710
|
settle(null);
|
|
2002
2711
|
return;
|
|
@@ -2009,7 +2718,7 @@ class ModbusMaster extends EventEmitter {
|
|
|
2009
2718
|
}
|
|
2010
2719
|
writeFC1Or2(unit, fc, address, length, timeout) {
|
|
2011
2720
|
const byteCount = Math.ceil(length / 8);
|
|
2012
|
-
const bufferTx = Buffer.
|
|
2721
|
+
const bufferTx = Buffer.allocUnsafe(4);
|
|
2013
2722
|
bufferTx.writeUInt16BE(address, 0);
|
|
2014
2723
|
bufferTx.writeUInt16BE(length, 2);
|
|
2015
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) => {
|
|
@@ -2026,7 +2735,7 @@ class ModbusMaster extends EventEmitter {
|
|
|
2026
2735
|
}
|
|
2027
2736
|
writeFC3Or4(unit, fc, address, length, timeout) {
|
|
2028
2737
|
const byteCount = length * 2;
|
|
2029
|
-
const bufferTx = Buffer.
|
|
2738
|
+
const bufferTx = Buffer.allocUnsafe(4);
|
|
2030
2739
|
bufferTx.writeUInt16BE(address, 0);
|
|
2031
2740
|
bufferTx.writeUInt16BE(length, 2);
|
|
2032
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) => {
|
|
@@ -2044,7 +2753,7 @@ class ModbusMaster extends EventEmitter {
|
|
|
2044
2753
|
}
|
|
2045
2754
|
writeSingleCoil(unit, address, value, timeout = this.timeout) {
|
|
2046
2755
|
const fc = FunctionCode.WRITE_SINGLE_COIL;
|
|
2047
|
-
const bufferTx = Buffer.
|
|
2756
|
+
const bufferTx = Buffer.allocUnsafe(4);
|
|
2048
2757
|
bufferTx.writeUInt16BE(address, 0);
|
|
2049
2758
|
bufferTx.writeUInt16BE(value ? COIL_ON : COIL_OFF, 2);
|
|
2050
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) => {
|
|
@@ -2055,7 +2764,7 @@ class ModbusMaster extends EventEmitter {
|
|
|
2055
2764
|
}
|
|
2056
2765
|
writeSingleRegister(unit, address, value, timeout = this.timeout) {
|
|
2057
2766
|
const fc = FunctionCode.WRITE_SINGLE_REGISTER;
|
|
2058
|
-
const bufferTx = Buffer.
|
|
2767
|
+
const bufferTx = Buffer.allocUnsafe(4);
|
|
2059
2768
|
bufferTx.writeUInt16BE(address, 0);
|
|
2060
2769
|
bufferTx.writeUInt16BE(value, 2);
|
|
2061
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) => {
|
|
@@ -2085,7 +2794,7 @@ class ModbusMaster extends EventEmitter {
|
|
|
2085
2794
|
writeMultipleRegisters(unit, address, value, timeout = this.timeout) {
|
|
2086
2795
|
const fc = FunctionCode.WRITE_MULTIPLE_REGISTERS;
|
|
2087
2796
|
const byteCount = value.length * 2;
|
|
2088
|
-
const bufferTx = Buffer.
|
|
2797
|
+
const bufferTx = Buffer.allocUnsafe(5 + byteCount);
|
|
2089
2798
|
bufferTx.writeUInt16BE(address, 0);
|
|
2090
2799
|
bufferTx.writeUInt16BE(value.length, 2);
|
|
2091
2800
|
bufferTx.writeUInt8(byteCount, 4);
|
|
@@ -2111,7 +2820,7 @@ class ModbusMaster extends EventEmitter {
|
|
|
2111
2820
|
if (frame) {
|
|
2112
2821
|
const runStatusIndex = 1 + serverIdLength;
|
|
2113
2822
|
return Object.assign(Object.assign({}, frame), { data: {
|
|
2114
|
-
serverId:
|
|
2823
|
+
serverId: Array.from(frame.data.subarray(1, runStatusIndex)),
|
|
2115
2824
|
runIndicatorStatus: frame.data[runStatusIndex] === 0xff,
|
|
2116
2825
|
additionalData: Array.from(frame.data.subarray(runStatusIndex + 1)),
|
|
2117
2826
|
} });
|
|
@@ -2120,7 +2829,7 @@ class ModbusMaster extends EventEmitter {
|
|
|
2120
2829
|
}
|
|
2121
2830
|
maskWriteRegister(unit, address, andMask, orMask, timeout = this.timeout) {
|
|
2122
2831
|
const fc = FunctionCode.MASK_WRITE_REGISTER;
|
|
2123
|
-
const bufferTx = Buffer.
|
|
2832
|
+
const bufferTx = Buffer.allocUnsafe(6);
|
|
2124
2833
|
bufferTx.writeUInt16BE(address, 0);
|
|
2125
2834
|
bufferTx.writeUInt16BE(andMask, 2);
|
|
2126
2835
|
bufferTx.writeUInt16BE(orMask, 4);
|
|
@@ -2134,7 +2843,7 @@ class ModbusMaster extends EventEmitter {
|
|
|
2134
2843
|
const fc = FunctionCode.READ_WRITE_MULTIPLE_REGISTERS;
|
|
2135
2844
|
const byteCount = write.value.length * 2;
|
|
2136
2845
|
const readByteCount = read.length * 2;
|
|
2137
|
-
const bufferTx = Buffer.
|
|
2846
|
+
const bufferTx = Buffer.allocUnsafe(9 + byteCount);
|
|
2138
2847
|
bufferTx.writeUInt16BE(read.address, 0);
|
|
2139
2848
|
bufferTx.writeUInt16BE(read.length, 2);
|
|
2140
2849
|
bufferTx.writeUInt16BE(write.address, 4);
|
|
@@ -2206,10 +2915,14 @@ class ModbusMaster extends EventEmitter {
|
|
|
2206
2915
|
});
|
|
2207
2916
|
}
|
|
2208
2917
|
addCustomFunctionCode(cfc) {
|
|
2209
|
-
|
|
2918
|
+
var _a;
|
|
2919
|
+
this._customFunctionCodes.set(cfc.fc, cfc);
|
|
2920
|
+
(_a = this._appLayer) === null || _a === void 0 ? void 0 : _a.addCustomFunctionCode(cfc);
|
|
2210
2921
|
}
|
|
2211
2922
|
removeCustomFunctionCode(fc) {
|
|
2212
|
-
|
|
2923
|
+
var _a;
|
|
2924
|
+
this._customFunctionCodes.delete(fc);
|
|
2925
|
+
(_a = this._appLayer) === null || _a === void 0 ? void 0 : _a.removeCustomFunctionCode(fc);
|
|
2213
2926
|
}
|
|
2214
2927
|
sendCustomFC(unit, fc, data, timeout = this.timeout) {
|
|
2215
2928
|
const payload = Buffer.isBuffer(data) ? data : Buffer.from(data);
|
|
@@ -2219,85 +2932,107 @@ class ModbusMaster extends EventEmitter {
|
|
|
2219
2932
|
}
|
|
2220
2933
|
});
|
|
2221
2934
|
}
|
|
2222
|
-
_clean(
|
|
2223
|
-
if (this._cleanLevel === 'destroy') {
|
|
2224
|
-
return;
|
|
2225
|
-
}
|
|
2226
|
-
if (this._cleanLevel === 'close' && level === 'close') {
|
|
2227
|
-
return;
|
|
2228
|
-
}
|
|
2229
|
-
const errorCode = level === 'destroy' ? ModbusErrorCode.MASTER_DESTROYED : ModbusErrorCode.MASTER_CLOSED;
|
|
2230
|
-
const message = level === 'destroy' ? 'Master destroyed' : 'Master closed';
|
|
2231
|
-
this._closed = true;
|
|
2935
|
+
_clean(message) {
|
|
2232
2936
|
const queued = this._queue.splice(0);
|
|
2233
2937
|
for (const item of queued) {
|
|
2234
|
-
item.cancel(new
|
|
2235
|
-
}
|
|
2236
|
-
this._masterSession.stopAll(new
|
|
2237
|
-
|
|
2238
|
-
|
|
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
|
+
*/
|
|
2239
2950
|
open(...args) {
|
|
2240
|
-
if (this.
|
|
2241
|
-
return Promise.reject(new
|
|
2951
|
+
if (this._closePromise) {
|
|
2952
|
+
return Promise.reject(new Error('The port has been permanently closed and CANNOT be reopened'));
|
|
2242
2953
|
}
|
|
2243
|
-
this.
|
|
2244
|
-
this._closed = false;
|
|
2245
|
-
this._nextTid = 1;
|
|
2246
|
-
return this.physicalLayer.open(...args);
|
|
2954
|
+
return this._physicalLayer.open(...args);
|
|
2247
2955
|
}
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
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
|
+
});
|
|
2254
2975
|
}
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
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
|
+
});
|
|
2263
2992
|
}
|
|
2264
2993
|
}
|
|
2265
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
|
+
}
|
|
2266
3001
|
class ModbusSlave extends EventEmitter {
|
|
2267
|
-
get
|
|
2268
|
-
return this.
|
|
3002
|
+
get state() {
|
|
3003
|
+
return this._physicalLayer.state;
|
|
2269
3004
|
}
|
|
2270
|
-
get
|
|
2271
|
-
return this.
|
|
3005
|
+
get physicalLayer() {
|
|
3006
|
+
return this._physicalLayer;
|
|
2272
3007
|
}
|
|
2273
|
-
constructor(
|
|
3008
|
+
constructor(options) {
|
|
2274
3009
|
var _a;
|
|
2275
3010
|
super();
|
|
2276
|
-
Object.defineProperty(this, "
|
|
3011
|
+
Object.defineProperty(this, "models", {
|
|
2277
3012
|
enumerable: true,
|
|
2278
3013
|
configurable: true,
|
|
2279
3014
|
writable: true,
|
|
2280
|
-
value:
|
|
3015
|
+
value: new Map()
|
|
2281
3016
|
});
|
|
2282
|
-
Object.defineProperty(this, "
|
|
3017
|
+
Object.defineProperty(this, "concurrent", {
|
|
2283
3018
|
enumerable: true,
|
|
2284
3019
|
configurable: true,
|
|
2285
3020
|
writable: true,
|
|
2286
|
-
value:
|
|
3021
|
+
value: void 0
|
|
2287
3022
|
});
|
|
2288
|
-
Object.defineProperty(this, "
|
|
3023
|
+
Object.defineProperty(this, "_physicalLayer", {
|
|
2289
3024
|
enumerable: true,
|
|
2290
3025
|
configurable: true,
|
|
2291
3026
|
writable: true,
|
|
2292
|
-
value:
|
|
3027
|
+
value: void 0
|
|
2293
3028
|
});
|
|
2294
|
-
Object.defineProperty(this, "
|
|
3029
|
+
Object.defineProperty(this, "_protocol", {
|
|
2295
3030
|
enumerable: true,
|
|
2296
3031
|
configurable: true,
|
|
2297
3032
|
writable: true,
|
|
2298
3033
|
value: void 0
|
|
2299
3034
|
});
|
|
2300
|
-
Object.defineProperty(this, "
|
|
3035
|
+
Object.defineProperty(this, "_appLayers", {
|
|
2301
3036
|
enumerable: true,
|
|
2302
3037
|
configurable: true,
|
|
2303
3038
|
writable: true,
|
|
@@ -2315,72 +3050,112 @@ class ModbusSlave extends EventEmitter {
|
|
|
2315
3050
|
writable: true,
|
|
2316
3051
|
value: new Map()
|
|
2317
3052
|
});
|
|
2318
|
-
Object.defineProperty(this, "
|
|
3053
|
+
Object.defineProperty(this, "_cleanupFns", {
|
|
2319
3054
|
enumerable: true,
|
|
2320
3055
|
configurable: true,
|
|
2321
3056
|
writable: true,
|
|
2322
|
-
value:
|
|
2323
|
-
});
|
|
2324
|
-
this.concurrent = (_a = options.concurrent) !== null && _a !== void 0 ? _a : false;
|
|
2325
|
-
if (this.concurrent && this.applicationLayer.PROTOCOL !== 'TCP') {
|
|
2326
|
-
throw new ModbusError(ModbusErrorCode.CONCURRENT_NOT_TCP, 'concurrent mode requires a Modbus TCP application layer');
|
|
2327
|
-
}
|
|
2328
|
-
this.applicationLayer.role = 'SLAVE';
|
|
2329
|
-
applicationLayer.on('framing', (frame, response, connection) => {
|
|
2330
|
-
if (!(frame.unit === 0x00 || this.models.has(frame.unit))) {
|
|
2331
|
-
return;
|
|
2332
|
-
}
|
|
2333
|
-
if (this.concurrent) {
|
|
2334
|
-
this._processFrame(frame, response).catch((error) => this.emit('error', error));
|
|
2335
|
-
return;
|
|
2336
|
-
}
|
|
2337
|
-
let q = this._queues.get(connection.id);
|
|
2338
|
-
if (!q) {
|
|
2339
|
-
q = { items: [], processing: false };
|
|
2340
|
-
this._queues.set(connection.id, q);
|
|
2341
|
-
}
|
|
2342
|
-
q.items.push({ frame, response });
|
|
2343
|
-
this._drain(connection.id, q);
|
|
2344
|
-
});
|
|
2345
|
-
physicalLayer.on('error', (error) => {
|
|
2346
|
-
this.emit('error', error);
|
|
3057
|
+
value: new Set()
|
|
2347
3058
|
});
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
q.items.length = 0;
|
|
2354
|
-
if (!q.processing) {
|
|
2355
|
-
this._queues.delete(connection.id);
|
|
2356
|
-
}
|
|
3059
|
+
Object.defineProperty(this, "_closePromise", {
|
|
3060
|
+
enumerable: true,
|
|
3061
|
+
configurable: true,
|
|
3062
|
+
writable: true,
|
|
3063
|
+
value: null
|
|
2357
3064
|
});
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
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 = () => {
|
|
2363
3113
|
this.emit('close');
|
|
2364
|
-
|
|
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);
|
|
2365
3139
|
}
|
|
2366
|
-
handleFC1(model, frame, response) {
|
|
3140
|
+
handleFC1(appLayer, model, frame, response) {
|
|
2367
3141
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2368
3142
|
var _a;
|
|
2369
3143
|
if (frame.data.length !== 4) {
|
|
3144
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2370
3145
|
return;
|
|
2371
3146
|
}
|
|
2372
3147
|
if (!model.readCoils) {
|
|
2373
|
-
yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
|
|
3148
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
|
|
2374
3149
|
return;
|
|
2375
3150
|
}
|
|
2376
3151
|
const address = (frame.data[0] << 8) | frame.data[1];
|
|
2377
3152
|
const length = (frame.data[2] << 8) | frame.data[3];
|
|
2378
3153
|
if (length < LIMITS.READ_COILS_MIN || length > LIMITS.READ_COILS_MAX) {
|
|
2379
|
-
yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
|
|
3154
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2380
3155
|
return;
|
|
2381
3156
|
}
|
|
2382
3157
|
if (!checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).coils)) {
|
|
2383
|
-
yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
3158
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
2384
3159
|
return;
|
|
2385
3160
|
}
|
|
2386
3161
|
try {
|
|
@@ -2391,31 +3166,32 @@ class ModbusSlave extends EventEmitter {
|
|
|
2391
3166
|
bufferTx[Math.floor(index / 8)] |= 1 << index % 8;
|
|
2392
3167
|
}
|
|
2393
3168
|
});
|
|
2394
|
-
yield response(
|
|
3169
|
+
yield response(appLayer.encode(Object.assign(Object.assign({}, frame), { data: prependByte(bufferTx.length, bufferTx) })));
|
|
2395
3170
|
}
|
|
2396
3171
|
catch (error) {
|
|
2397
|
-
yield this.responseError(frame, response, error);
|
|
3172
|
+
yield this.responseError(appLayer, frame, response, error);
|
|
2398
3173
|
}
|
|
2399
3174
|
});
|
|
2400
3175
|
}
|
|
2401
|
-
handleFC2(model, frame, response) {
|
|
3176
|
+
handleFC2(appLayer, model, frame, response) {
|
|
2402
3177
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2403
3178
|
var _a;
|
|
2404
3179
|
if (frame.data.length !== 4) {
|
|
3180
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2405
3181
|
return;
|
|
2406
3182
|
}
|
|
2407
3183
|
if (!model.readDiscreteInputs) {
|
|
2408
|
-
yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
|
|
3184
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
|
|
2409
3185
|
return;
|
|
2410
3186
|
}
|
|
2411
3187
|
const address = (frame.data[0] << 8) | frame.data[1];
|
|
2412
3188
|
const length = (frame.data[2] << 8) | frame.data[3];
|
|
2413
3189
|
if (length < LIMITS.READ_COILS_MIN || length > LIMITS.READ_COILS_MAX) {
|
|
2414
|
-
yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
|
|
3190
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2415
3191
|
return;
|
|
2416
3192
|
}
|
|
2417
3193
|
if (!checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).discreteInputs)) {
|
|
2418
|
-
yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
3194
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
2419
3195
|
return;
|
|
2420
3196
|
}
|
|
2421
3197
|
try {
|
|
@@ -2426,152 +3202,157 @@ class ModbusSlave extends EventEmitter {
|
|
|
2426
3202
|
bufferTx[Math.floor(index / 8)] |= 1 << index % 8;
|
|
2427
3203
|
}
|
|
2428
3204
|
});
|
|
2429
|
-
yield response(
|
|
3205
|
+
yield response(appLayer.encode(Object.assign(Object.assign({}, frame), { data: prependByte(bufferTx.length, bufferTx) })));
|
|
2430
3206
|
}
|
|
2431
3207
|
catch (error) {
|
|
2432
|
-
yield this.responseError(frame, response, error);
|
|
3208
|
+
yield this.responseError(appLayer, frame, response, error);
|
|
2433
3209
|
}
|
|
2434
3210
|
});
|
|
2435
3211
|
}
|
|
2436
|
-
handleFC3(model, frame, response) {
|
|
3212
|
+
handleFC3(appLayer, model, frame, response) {
|
|
2437
3213
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2438
3214
|
var _a;
|
|
2439
3215
|
if (frame.data.length !== 4) {
|
|
3216
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2440
3217
|
return;
|
|
2441
3218
|
}
|
|
2442
3219
|
if (!model.readHoldingRegisters) {
|
|
2443
|
-
yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
|
|
3220
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
|
|
2444
3221
|
return;
|
|
2445
3222
|
}
|
|
2446
3223
|
const address = (frame.data[0] << 8) | frame.data[1];
|
|
2447
3224
|
const length = (frame.data[2] << 8) | frame.data[3];
|
|
2448
3225
|
if (length < LIMITS.READ_REGISTERS_MIN || length > LIMITS.READ_REGISTERS_MAX) {
|
|
2449
|
-
yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
|
|
3226
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2450
3227
|
return;
|
|
2451
3228
|
}
|
|
2452
3229
|
if (!checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).holdingRegisters)) {
|
|
2453
|
-
yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
3230
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
2454
3231
|
return;
|
|
2455
3232
|
}
|
|
2456
3233
|
try {
|
|
2457
3234
|
const registers = yield model.readHoldingRegisters(address, length);
|
|
2458
|
-
const bufferTx = Buffer.
|
|
3235
|
+
const bufferTx = Buffer.allocUnsafe(length * 2);
|
|
2459
3236
|
registers.forEach((register, index) => {
|
|
2460
3237
|
bufferTx.writeUInt16BE(register, index * 2);
|
|
2461
3238
|
});
|
|
2462
|
-
yield response(
|
|
3239
|
+
yield response(appLayer.encode(Object.assign(Object.assign({}, frame), { data: prependByte(bufferTx.length, bufferTx) })));
|
|
2463
3240
|
}
|
|
2464
3241
|
catch (error) {
|
|
2465
|
-
yield this.responseError(frame, response, error);
|
|
3242
|
+
yield this.responseError(appLayer, frame, response, error);
|
|
2466
3243
|
}
|
|
2467
3244
|
});
|
|
2468
3245
|
}
|
|
2469
|
-
handleFC4(model, frame, response) {
|
|
3246
|
+
handleFC4(appLayer, model, frame, response) {
|
|
2470
3247
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2471
3248
|
var _a;
|
|
2472
3249
|
if (frame.data.length !== 4) {
|
|
3250
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2473
3251
|
return;
|
|
2474
3252
|
}
|
|
2475
3253
|
if (!model.readInputRegisters) {
|
|
2476
|
-
yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
|
|
3254
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
|
|
2477
3255
|
return;
|
|
2478
3256
|
}
|
|
2479
3257
|
const address = (frame.data[0] << 8) | frame.data[1];
|
|
2480
3258
|
const length = (frame.data[2] << 8) | frame.data[3];
|
|
2481
3259
|
if (length < LIMITS.READ_REGISTERS_MIN || length > LIMITS.READ_REGISTERS_MAX) {
|
|
2482
|
-
yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
|
|
3260
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2483
3261
|
return;
|
|
2484
3262
|
}
|
|
2485
3263
|
if (!checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).inputRegisters)) {
|
|
2486
|
-
yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
3264
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
2487
3265
|
return;
|
|
2488
3266
|
}
|
|
2489
3267
|
try {
|
|
2490
3268
|
const registers = yield model.readInputRegisters(address, length);
|
|
2491
|
-
const bufferTx = Buffer.
|
|
3269
|
+
const bufferTx = Buffer.allocUnsafe(length * 2);
|
|
2492
3270
|
registers.forEach((register, index) => {
|
|
2493
3271
|
bufferTx.writeUInt16BE(register, index * 2);
|
|
2494
3272
|
});
|
|
2495
|
-
yield response(
|
|
3273
|
+
yield response(appLayer.encode(Object.assign(Object.assign({}, frame), { data: prependByte(bufferTx.length, bufferTx) })));
|
|
2496
3274
|
}
|
|
2497
3275
|
catch (error) {
|
|
2498
|
-
yield this.responseError(frame, response, error);
|
|
3276
|
+
yield this.responseError(appLayer, frame, response, error);
|
|
2499
3277
|
}
|
|
2500
3278
|
});
|
|
2501
3279
|
}
|
|
2502
|
-
handleFC5(model, frame, response) {
|
|
3280
|
+
handleFC5(appLayer, model, frame, response) {
|
|
2503
3281
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2504
3282
|
var _a;
|
|
2505
3283
|
if (frame.data.length !== 4) {
|
|
3284
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2506
3285
|
return;
|
|
2507
3286
|
}
|
|
2508
3287
|
if (!model.writeSingleCoil) {
|
|
2509
|
-
yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
|
|
3288
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
|
|
2510
3289
|
return;
|
|
2511
3290
|
}
|
|
2512
3291
|
const address = (frame.data[0] << 8) | frame.data[1];
|
|
2513
3292
|
const value = (frame.data[2] << 8) | frame.data[3];
|
|
2514
3293
|
if (value !== COIL_OFF && value !== COIL_ON) {
|
|
2515
|
-
yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
|
|
3294
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2516
3295
|
return;
|
|
2517
3296
|
}
|
|
2518
3297
|
if (!checkRange(address, (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).coils)) {
|
|
2519
|
-
yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
3298
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
2520
3299
|
return;
|
|
2521
3300
|
}
|
|
2522
3301
|
try {
|
|
2523
3302
|
yield model.writeSingleCoil(address, value === COIL_ON);
|
|
2524
|
-
yield response(
|
|
3303
|
+
yield response(appLayer.encode(frame));
|
|
2525
3304
|
}
|
|
2526
3305
|
catch (error) {
|
|
2527
|
-
yield this.responseError(frame, response, error);
|
|
3306
|
+
yield this.responseError(appLayer, frame, response, error);
|
|
2528
3307
|
}
|
|
2529
3308
|
});
|
|
2530
3309
|
}
|
|
2531
|
-
handleFC6(model, frame, response) {
|
|
3310
|
+
handleFC6(appLayer, model, frame, response) {
|
|
2532
3311
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2533
3312
|
var _a;
|
|
2534
3313
|
if (frame.data.length !== 4) {
|
|
3314
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2535
3315
|
return;
|
|
2536
3316
|
}
|
|
2537
3317
|
if (!model.writeSingleRegister) {
|
|
2538
|
-
yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
|
|
3318
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
|
|
2539
3319
|
return;
|
|
2540
3320
|
}
|
|
2541
3321
|
const address = (frame.data[0] << 8) | frame.data[1];
|
|
2542
3322
|
const value = (frame.data[2] << 8) | frame.data[3];
|
|
2543
3323
|
if (!checkRange(address, (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).holdingRegisters)) {
|
|
2544
|
-
yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
3324
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
2545
3325
|
return;
|
|
2546
3326
|
}
|
|
2547
3327
|
try {
|
|
2548
3328
|
yield model.writeSingleRegister(address, value);
|
|
2549
|
-
yield response(
|
|
3329
|
+
yield response(appLayer.encode(frame));
|
|
2550
3330
|
}
|
|
2551
3331
|
catch (error) {
|
|
2552
|
-
yield this.responseError(frame, response, error);
|
|
3332
|
+
yield this.responseError(appLayer, frame, response, error);
|
|
2553
3333
|
}
|
|
2554
3334
|
});
|
|
2555
3335
|
}
|
|
2556
|
-
handleFC15(model, frame, response) {
|
|
3336
|
+
handleFC15(appLayer, model, frame, response) {
|
|
2557
3337
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2558
3338
|
var _a;
|
|
2559
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));
|
|
2560
3341
|
return;
|
|
2561
3342
|
}
|
|
2562
3343
|
if (!model.writeMultipleCoils && !model.writeSingleCoil) {
|
|
2563
|
-
yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
|
|
3344
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
|
|
2564
3345
|
return;
|
|
2565
3346
|
}
|
|
2566
3347
|
const address = (frame.data[0] << 8) | frame.data[1];
|
|
2567
3348
|
const length = (frame.data[2] << 8) | frame.data[3];
|
|
2568
3349
|
const byteCount = frame.data[4];
|
|
2569
3350
|
if (length < LIMITS.READ_COILS_MIN || length > LIMITS.WRITE_COILS_MAX || byteCount !== Math.ceil(length / 8)) {
|
|
2570
|
-
yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
|
|
3351
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2571
3352
|
return;
|
|
2572
3353
|
}
|
|
2573
3354
|
if (!checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).coils)) {
|
|
2574
|
-
yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
3355
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
2575
3356
|
return;
|
|
2576
3357
|
}
|
|
2577
3358
|
const value = new Array(length);
|
|
@@ -2587,32 +3368,33 @@ class ModbusSlave extends EventEmitter {
|
|
|
2587
3368
|
yield model.writeSingleCoil(address + i, value[i]);
|
|
2588
3369
|
}
|
|
2589
3370
|
}
|
|
2590
|
-
yield response(
|
|
3371
|
+
yield response(appLayer.encode(Object.assign(Object.assign({}, frame), { data: frame.data.subarray(0, 4) })));
|
|
2591
3372
|
}
|
|
2592
3373
|
catch (error) {
|
|
2593
|
-
yield this.responseError(frame, response, error);
|
|
3374
|
+
yield this.responseError(appLayer, frame, response, error);
|
|
2594
3375
|
}
|
|
2595
3376
|
});
|
|
2596
3377
|
}
|
|
2597
|
-
handleFC16(model, frame, response) {
|
|
3378
|
+
handleFC16(appLayer, model, frame, response) {
|
|
2598
3379
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2599
3380
|
var _a;
|
|
2600
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));
|
|
2601
3383
|
return;
|
|
2602
3384
|
}
|
|
2603
3385
|
if (!model.writeMultipleRegisters && !model.writeSingleRegister) {
|
|
2604
|
-
yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
|
|
3386
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
|
|
2605
3387
|
return;
|
|
2606
3388
|
}
|
|
2607
3389
|
const address = (frame.data[0] << 8) | frame.data[1];
|
|
2608
3390
|
const length = (frame.data[2] << 8) | frame.data[3];
|
|
2609
3391
|
const byteCount = frame.data[4];
|
|
2610
3392
|
if (length < LIMITS.READ_REGISTERS_MIN || length > LIMITS.WRITE_REGISTERS_MAX || byteCount !== length * 2) {
|
|
2611
|
-
yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
|
|
3393
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2612
3394
|
return;
|
|
2613
3395
|
}
|
|
2614
3396
|
if (!checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).holdingRegisters)) {
|
|
2615
|
-
yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
3397
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
2616
3398
|
return;
|
|
2617
3399
|
}
|
|
2618
3400
|
const value = new Array(length);
|
|
@@ -2628,62 +3410,67 @@ class ModbusSlave extends EventEmitter {
|
|
|
2628
3410
|
yield model.writeSingleRegister(address + i, value[i]);
|
|
2629
3411
|
}
|
|
2630
3412
|
}
|
|
2631
|
-
yield response(
|
|
3413
|
+
yield response(appLayer.encode(Object.assign(Object.assign({}, frame), { data: frame.data.subarray(0, 4) })));
|
|
2632
3414
|
}
|
|
2633
3415
|
catch (error) {
|
|
2634
|
-
yield this.responseError(frame, response, error);
|
|
3416
|
+
yield this.responseError(appLayer, frame, response, error);
|
|
2635
3417
|
}
|
|
2636
3418
|
});
|
|
2637
3419
|
}
|
|
2638
|
-
handleFC17(model, frame, response) {
|
|
3420
|
+
handleFC17(appLayer, model, frame, response) {
|
|
2639
3421
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2640
3422
|
var _a;
|
|
2641
3423
|
if (frame.data.length !== 0) {
|
|
3424
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2642
3425
|
return;
|
|
2643
3426
|
}
|
|
2644
3427
|
if (!model.reportServerId) {
|
|
2645
|
-
yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
|
|
3428
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
|
|
2646
3429
|
return;
|
|
2647
3430
|
}
|
|
2648
3431
|
try {
|
|
2649
|
-
const { serverId = (_a = model.unit) !== null && _a !== void 0 ? _a : 1, runIndicatorStatus = true, additionalData = [] } = yield model.reportServerId();
|
|
2650
|
-
const serverIdBytes =
|
|
3432
|
+
const { serverId = [(_a = model.unit) !== null && _a !== void 0 ? _a : 1], runIndicatorStatus = true, additionalData = [] } = yield model.reportServerId();
|
|
3433
|
+
const serverIdBytes = serverId;
|
|
2651
3434
|
const byteCount = serverIdBytes.length + 1 + additionalData.length;
|
|
2652
3435
|
if (byteCount > 255) {
|
|
2653
|
-
yield this.responseError(frame, response, getErrorByCode(ErrorCode.SERVER_DEVICE_FAILURE));
|
|
3436
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.SERVER_DEVICE_FAILURE));
|
|
2654
3437
|
return;
|
|
2655
3438
|
}
|
|
2656
3439
|
const allBytes = [...serverIdBytes, runIndicatorStatus ? 0xff : 0x00, ...additionalData];
|
|
2657
3440
|
if (allBytes.some((b) => !isUint8(b))) {
|
|
2658
|
-
yield this.responseError(frame, response, getErrorByCode(ErrorCode.SERVER_DEVICE_FAILURE));
|
|
3441
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.SERVER_DEVICE_FAILURE));
|
|
2659
3442
|
return;
|
|
2660
3443
|
}
|
|
2661
|
-
|
|
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 })));
|
|
2662
3448
|
}
|
|
2663
3449
|
catch (error) {
|
|
2664
|
-
yield this.responseError(frame, response, error);
|
|
3450
|
+
yield this.responseError(appLayer, frame, response, error);
|
|
2665
3451
|
}
|
|
2666
3452
|
});
|
|
2667
3453
|
}
|
|
2668
|
-
handleFC22(model, frame, response) {
|
|
3454
|
+
handleFC22(appLayer, model, frame, response) {
|
|
2669
3455
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2670
3456
|
var _a;
|
|
2671
3457
|
if (frame.data.length !== 6) {
|
|
3458
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2672
3459
|
return;
|
|
2673
3460
|
}
|
|
2674
3461
|
if (!model.maskWriteRegister && !(model.readHoldingRegisters && model.writeSingleRegister)) {
|
|
2675
|
-
yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
|
|
3462
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
|
|
2676
3463
|
return;
|
|
2677
3464
|
}
|
|
2678
3465
|
const address = (frame.data[0] << 8) | frame.data[1];
|
|
2679
3466
|
const andMask = (frame.data[2] << 8) | frame.data[3];
|
|
2680
3467
|
const orMask = (frame.data[4] << 8) | frame.data[5];
|
|
2681
3468
|
if (!checkRange(address, (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).holdingRegisters)) {
|
|
2682
|
-
yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
3469
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
2683
3470
|
return;
|
|
2684
3471
|
}
|
|
2685
3472
|
try {
|
|
2686
|
-
yield this.
|
|
3473
|
+
yield this._withAddressLock([address], () => __awaiter(this, void 0, void 0, function* () {
|
|
2687
3474
|
if (model.maskWriteRegister) {
|
|
2688
3475
|
yield model.maskWriteRegister(address, andMask, orMask);
|
|
2689
3476
|
}
|
|
@@ -2692,21 +3479,22 @@ class ModbusSlave extends EventEmitter {
|
|
|
2692
3479
|
yield model.writeSingleRegister(address, (value & andMask) | (orMask & (~andMask & 0xffff)));
|
|
2693
3480
|
}
|
|
2694
3481
|
}));
|
|
2695
|
-
yield response(
|
|
3482
|
+
yield response(appLayer.encode(frame));
|
|
2696
3483
|
}
|
|
2697
3484
|
catch (error) {
|
|
2698
|
-
yield this.responseError(frame, response, error);
|
|
3485
|
+
yield this.responseError(appLayer, frame, response, error);
|
|
2699
3486
|
}
|
|
2700
3487
|
});
|
|
2701
3488
|
}
|
|
2702
|
-
handleFC23(model, frame, response) {
|
|
3489
|
+
handleFC23(appLayer, model, frame, response) {
|
|
2703
3490
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2704
3491
|
var _a;
|
|
2705
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));
|
|
2706
3494
|
return;
|
|
2707
3495
|
}
|
|
2708
3496
|
if (!model.readHoldingRegisters || (!model.writeMultipleRegisters && !model.writeSingleRegister)) {
|
|
2709
|
-
yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
|
|
3497
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
|
|
2710
3498
|
return;
|
|
2711
3499
|
}
|
|
2712
3500
|
const address = {
|
|
@@ -2723,11 +3511,11 @@ class ModbusSlave extends EventEmitter {
|
|
|
2723
3511
|
length.write < LIMITS.READ_REGISTERS_MIN ||
|
|
2724
3512
|
length.write > LIMITS.RW_REGISTERS_WRITE_MAX ||
|
|
2725
3513
|
byteCount !== length.write * 2) {
|
|
2726
|
-
yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
|
|
3514
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2727
3515
|
return;
|
|
2728
3516
|
}
|
|
2729
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)) {
|
|
2730
|
-
yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
3518
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
2731
3519
|
return;
|
|
2732
3520
|
}
|
|
2733
3521
|
const value = new Array(length.write);
|
|
@@ -2736,7 +3524,7 @@ class ModbusSlave extends EventEmitter {
|
|
|
2736
3524
|
}
|
|
2737
3525
|
try {
|
|
2738
3526
|
const writeAddresses = Array.from({ length: length.write }, (_, i) => address.write + i);
|
|
2739
|
-
yield this.
|
|
3527
|
+
yield this._withAddressLock(writeAddresses, () => __awaiter(this, void 0, void 0, function* () {
|
|
2740
3528
|
if (model.writeMultipleRegisters) {
|
|
2741
3529
|
yield model.writeMultipleRegisters(address.write, value);
|
|
2742
3530
|
}
|
|
@@ -2747,18 +3535,18 @@ class ModbusSlave extends EventEmitter {
|
|
|
2747
3535
|
}
|
|
2748
3536
|
}));
|
|
2749
3537
|
const registers = yield model.readHoldingRegisters(address.read, length.read);
|
|
2750
|
-
const bufferTx = Buffer.
|
|
3538
|
+
const bufferTx = Buffer.allocUnsafe(length.read * 2);
|
|
2751
3539
|
registers.forEach((register, index) => {
|
|
2752
3540
|
bufferTx.writeUInt16BE(register, index * 2);
|
|
2753
3541
|
});
|
|
2754
|
-
yield response(
|
|
3542
|
+
yield response(appLayer.encode(Object.assign(Object.assign({}, frame), { data: prependByte(bufferTx.length, bufferTx) })));
|
|
2755
3543
|
}
|
|
2756
3544
|
catch (error) {
|
|
2757
|
-
yield this.responseError(frame, response, error);
|
|
3545
|
+
yield this.responseError(appLayer, frame, response, error);
|
|
2758
3546
|
}
|
|
2759
3547
|
});
|
|
2760
3548
|
}
|
|
2761
|
-
handleFC43_14(model, frame, response) {
|
|
3549
|
+
handleFC43_14(appLayer, model, frame, response) {
|
|
2762
3550
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2763
3551
|
if (frame.data.length === 3) {
|
|
2764
3552
|
if (frame.data[0] === MEI_READ_DEVICE_ID && model.readDeviceIdentification) {
|
|
@@ -2785,13 +3573,13 @@ class ModbusSlave extends EventEmitter {
|
|
|
2785
3573
|
}
|
|
2786
3574
|
case ReadDeviceIDCode.SPECIFIC_ACCESS: {
|
|
2787
3575
|
if (objectID > 0x06 && objectID < 0x80) {
|
|
2788
|
-
yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
3576
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
2789
3577
|
return;
|
|
2790
3578
|
}
|
|
2791
3579
|
break;
|
|
2792
3580
|
}
|
|
2793
3581
|
default: {
|
|
2794
|
-
yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
|
|
3582
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2795
3583
|
return;
|
|
2796
3584
|
}
|
|
2797
3585
|
}
|
|
@@ -2811,7 +3599,7 @@ class ModbusSlave extends EventEmitter {
|
|
|
2811
3599
|
}
|
|
2812
3600
|
if (!objects.has(objectID)) {
|
|
2813
3601
|
if (readDeviceIDCode === ReadDeviceIDCode.SPECIFIC_ACCESS) {
|
|
2814
|
-
yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
3602
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
2815
3603
|
return;
|
|
2816
3604
|
}
|
|
2817
3605
|
objectID = 0x00;
|
|
@@ -2819,7 +3607,7 @@ class ModbusSlave extends EventEmitter {
|
|
|
2819
3607
|
let maxId = 0;
|
|
2820
3608
|
for (const id of objects.keys()) {
|
|
2821
3609
|
if ((id >= 0x07 && id <= 0x7f) || id > 0xff) {
|
|
2822
|
-
yield this.responseError(frame, response, getErrorByCode(ErrorCode.SERVER_DEVICE_FAILURE));
|
|
3610
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.SERVER_DEVICE_FAILURE));
|
|
2823
3611
|
return;
|
|
2824
3612
|
}
|
|
2825
3613
|
if (id > maxId) {
|
|
@@ -2836,97 +3624,104 @@ class ModbusSlave extends EventEmitter {
|
|
|
2836
3624
|
}
|
|
2837
3625
|
const byteLength = Buffer.byteLength(value);
|
|
2838
3626
|
if (byteLength > 245) {
|
|
2839
|
-
yield this.responseError(frame, response, getErrorByCode(ErrorCode.SERVER_DEVICE_FAILURE));
|
|
3627
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.SERVER_DEVICE_FAILURE));
|
|
2840
3628
|
return;
|
|
2841
3629
|
}
|
|
2842
3630
|
if (lastID !== 0) {
|
|
2843
3631
|
continue;
|
|
2844
3632
|
}
|
|
2845
3633
|
if (byteLength + 2 > 253 - totalLength) {
|
|
2846
|
-
|
|
2847
|
-
lastID = id;
|
|
2848
|
-
}
|
|
3634
|
+
lastID = id;
|
|
2849
3635
|
}
|
|
2850
3636
|
else {
|
|
2851
3637
|
totalLength += byteLength + 2;
|
|
2852
|
-
ids.push(id);
|
|
3638
|
+
ids.push({ id, byteLength });
|
|
2853
3639
|
if (readDeviceIDCode === ReadDeviceIDCode.SPECIFIC_ACCESS) {
|
|
2854
3640
|
break;
|
|
2855
3641
|
}
|
|
2856
3642
|
}
|
|
2857
3643
|
}
|
|
2858
|
-
ids.sort((a, b) => a - b);
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
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) {
|
|
2862
3658
|
const value = objects.get(id);
|
|
2863
|
-
|
|
2864
|
-
|
|
3659
|
+
data[offset++] = id;
|
|
3660
|
+
data[offset++] = byteLength;
|
|
3661
|
+
offset += data.write(value, offset);
|
|
2865
3662
|
}
|
|
2866
|
-
yield response(
|
|
3663
|
+
yield response(appLayer.encode(Object.assign(Object.assign({}, frame), { data })));
|
|
2867
3664
|
}
|
|
2868
3665
|
catch (error) {
|
|
2869
|
-
yield this.responseError(frame, response, error);
|
|
3666
|
+
yield this.responseError(appLayer, frame, response, error);
|
|
2870
3667
|
}
|
|
2871
3668
|
}
|
|
2872
3669
|
else {
|
|
2873
|
-
yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
|
|
3670
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
|
|
2874
3671
|
}
|
|
2875
3672
|
}
|
|
2876
3673
|
});
|
|
2877
3674
|
}
|
|
2878
|
-
responseError(frame, response, error) {
|
|
3675
|
+
responseError(appLayer, frame, response, error) {
|
|
2879
3676
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2880
|
-
yield response(
|
|
3677
|
+
yield response(appLayer.encode(Object.assign(Object.assign({}, frame), { fc: frame.fc | EXCEPTION_OFFSET, data: Buffer.from([getCodeByError(error)]) })));
|
|
2881
3678
|
});
|
|
2882
3679
|
}
|
|
2883
|
-
_drain(
|
|
3680
|
+
_drain(appLayer, q) {
|
|
2884
3681
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2885
|
-
if (q.processing) {
|
|
3682
|
+
if (q.processing || this._physicalLayer.state !== PhysicalState.OPEN) {
|
|
2886
3683
|
return;
|
|
2887
3684
|
}
|
|
2888
3685
|
q.processing = true;
|
|
2889
3686
|
try {
|
|
2890
|
-
while (q.
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
yield this._processFrame(frame, response);
|
|
2894
|
-
}
|
|
2895
|
-
catch (error) {
|
|
2896
|
-
this.emit('error', error);
|
|
3687
|
+
while (q.frames.length > 0) {
|
|
3688
|
+
if (this._physicalLayer.state !== PhysicalState.OPEN) {
|
|
3689
|
+
return;
|
|
2897
3690
|
}
|
|
3691
|
+
const frame = q.frames.shift();
|
|
3692
|
+
yield this._processFrame(appLayer, frame);
|
|
2898
3693
|
}
|
|
2899
3694
|
}
|
|
2900
3695
|
finally {
|
|
2901
3696
|
q.processing = false;
|
|
2902
|
-
// Cleanup empty queue entries so the map doesn't grow unbounded across
|
|
2903
|
-
// ephemeral connections (UDP rinfo, brief TCP clients).
|
|
2904
|
-
if (q.items.length === 0 && this._queues.get(key) === q) {
|
|
2905
|
-
this._queues.delete(key);
|
|
2906
|
-
}
|
|
2907
3697
|
}
|
|
2908
3698
|
});
|
|
2909
3699
|
}
|
|
2910
|
-
_processFrame(
|
|
3700
|
+
_processFrame(appLayer, frame) {
|
|
2911
3701
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2912
3702
|
const response = (data) => __awaiter(this, void 0, void 0, function* () {
|
|
2913
|
-
if (frame.unit === 0x00) {
|
|
3703
|
+
if (frame.unit === 0x00 || this._physicalLayer.state !== PhysicalState.OPEN) {
|
|
2914
3704
|
return;
|
|
2915
3705
|
}
|
|
2916
3706
|
try {
|
|
2917
|
-
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. */
|
|
2918
3711
|
}
|
|
2919
|
-
catch (error) { }
|
|
2920
3712
|
});
|
|
2921
3713
|
for (const model of frame.unit === 0x00 ? this.models.values() : [this.models.get(frame.unit)]) {
|
|
2922
|
-
|
|
3714
|
+
if (this._physicalLayer.state !== PhysicalState.OPEN) {
|
|
3715
|
+
return;
|
|
3716
|
+
}
|
|
3717
|
+
const intercepted = yield this._intercept(appLayer, model, frame, response);
|
|
2923
3718
|
if (!intercepted) {
|
|
2924
|
-
yield this._handleFC(model, frame, response);
|
|
3719
|
+
yield this._handleFC(appLayer, model, frame, response);
|
|
2925
3720
|
}
|
|
2926
3721
|
}
|
|
2927
3722
|
});
|
|
2928
3723
|
}
|
|
2929
|
-
_intercept(model, frame, response) {
|
|
3724
|
+
_intercept(appLayer, model, frame, response) {
|
|
2930
3725
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2931
3726
|
if (!model.interceptor) {
|
|
2932
3727
|
return false;
|
|
@@ -2934,18 +3729,18 @@ class ModbusSlave extends EventEmitter {
|
|
|
2934
3729
|
try {
|
|
2935
3730
|
const data = yield model.interceptor(frame.fc, frame.data);
|
|
2936
3731
|
if (data !== undefined) {
|
|
2937
|
-
yield response(
|
|
3732
|
+
yield response(appLayer.encode(Object.assign(Object.assign({}, frame), { data })));
|
|
2938
3733
|
return true;
|
|
2939
3734
|
}
|
|
2940
3735
|
return false;
|
|
2941
3736
|
}
|
|
2942
3737
|
catch (error) {
|
|
2943
|
-
yield this.responseError(frame, response, error);
|
|
3738
|
+
yield this.responseError(appLayer, frame, response, error);
|
|
2944
3739
|
return true;
|
|
2945
3740
|
}
|
|
2946
3741
|
});
|
|
2947
3742
|
}
|
|
2948
|
-
|
|
3743
|
+
_withAddressLock(addresses, fn) {
|
|
2949
3744
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2950
3745
|
const sorted = [...new Set(addresses)].sort((a, b) => a - b);
|
|
2951
3746
|
const previous = sorted.map((addr) => { var _a; return (_a = this._locks.get(addr)) !== null && _a !== void 0 ? _a : Promise.resolve(); });
|
|
@@ -2968,55 +3763,55 @@ class ModbusSlave extends EventEmitter {
|
|
|
2968
3763
|
}
|
|
2969
3764
|
});
|
|
2970
3765
|
}
|
|
2971
|
-
_handleFC(model, frame, response) {
|
|
3766
|
+
_handleFC(appLayer, model, frame, response) {
|
|
2972
3767
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2973
3768
|
switch (frame.fc) {
|
|
2974
3769
|
case FunctionCode.READ_COILS: {
|
|
2975
|
-
yield this.handleFC1(model, frame, response);
|
|
3770
|
+
yield this.handleFC1(appLayer, model, frame, response);
|
|
2976
3771
|
break;
|
|
2977
3772
|
}
|
|
2978
3773
|
case FunctionCode.READ_DISCRETE_INPUTS: {
|
|
2979
|
-
yield this.handleFC2(model, frame, response);
|
|
3774
|
+
yield this.handleFC2(appLayer, model, frame, response);
|
|
2980
3775
|
break;
|
|
2981
3776
|
}
|
|
2982
3777
|
case FunctionCode.READ_HOLDING_REGISTERS: {
|
|
2983
|
-
yield this.handleFC3(model, frame, response);
|
|
3778
|
+
yield this.handleFC3(appLayer, model, frame, response);
|
|
2984
3779
|
break;
|
|
2985
3780
|
}
|
|
2986
3781
|
case FunctionCode.READ_INPUT_REGISTERS: {
|
|
2987
|
-
yield this.handleFC4(model, frame, response);
|
|
3782
|
+
yield this.handleFC4(appLayer, model, frame, response);
|
|
2988
3783
|
break;
|
|
2989
3784
|
}
|
|
2990
3785
|
case FunctionCode.WRITE_SINGLE_COIL: {
|
|
2991
|
-
yield this.handleFC5(model, frame, response);
|
|
3786
|
+
yield this.handleFC5(appLayer, model, frame, response);
|
|
2992
3787
|
break;
|
|
2993
3788
|
}
|
|
2994
3789
|
case FunctionCode.WRITE_SINGLE_REGISTER: {
|
|
2995
|
-
yield this.handleFC6(model, frame, response);
|
|
3790
|
+
yield this.handleFC6(appLayer, model, frame, response);
|
|
2996
3791
|
break;
|
|
2997
3792
|
}
|
|
2998
3793
|
case FunctionCode.WRITE_MULTIPLE_COILS: {
|
|
2999
|
-
yield this.handleFC15(model, frame, response);
|
|
3794
|
+
yield this.handleFC15(appLayer, model, frame, response);
|
|
3000
3795
|
break;
|
|
3001
3796
|
}
|
|
3002
3797
|
case FunctionCode.WRITE_MULTIPLE_REGISTERS: {
|
|
3003
|
-
yield this.handleFC16(model, frame, response);
|
|
3798
|
+
yield this.handleFC16(appLayer, model, frame, response);
|
|
3004
3799
|
break;
|
|
3005
3800
|
}
|
|
3006
3801
|
case FunctionCode.REPORT_SERVER_ID: {
|
|
3007
|
-
yield this.handleFC17(model, frame, response);
|
|
3802
|
+
yield this.handleFC17(appLayer, model, frame, response);
|
|
3008
3803
|
break;
|
|
3009
3804
|
}
|
|
3010
3805
|
case FunctionCode.MASK_WRITE_REGISTER: {
|
|
3011
|
-
yield this.handleFC22(model, frame, response);
|
|
3806
|
+
yield this.handleFC22(appLayer, model, frame, response);
|
|
3012
3807
|
break;
|
|
3013
3808
|
}
|
|
3014
3809
|
case FunctionCode.READ_WRITE_MULTIPLE_REGISTERS: {
|
|
3015
|
-
yield this.handleFC23(model, frame, response);
|
|
3810
|
+
yield this.handleFC23(appLayer, model, frame, response);
|
|
3016
3811
|
break;
|
|
3017
3812
|
}
|
|
3018
3813
|
case FunctionCode.READ_DEVICE_IDENTIFICATION: {
|
|
3019
|
-
yield this.handleFC43_14(model, frame, response);
|
|
3814
|
+
yield this.handleFC43_14(appLayer, model, frame, response);
|
|
3020
3815
|
break;
|
|
3021
3816
|
}
|
|
3022
3817
|
default: {
|
|
@@ -3024,14 +3819,14 @@ class ModbusSlave extends EventEmitter {
|
|
|
3024
3819
|
if (cfc === null || cfc === void 0 ? void 0 : cfc.handle) {
|
|
3025
3820
|
try {
|
|
3026
3821
|
const responseData = yield cfc.handle(frame.data, frame.unit);
|
|
3027
|
-
yield response(
|
|
3822
|
+
yield response(appLayer.encode(Object.assign(Object.assign({}, frame), { data: responseData })));
|
|
3028
3823
|
}
|
|
3029
3824
|
catch (error) {
|
|
3030
|
-
yield this.responseError(frame, response, error);
|
|
3825
|
+
yield this.responseError(appLayer, frame, response, error);
|
|
3031
3826
|
}
|
|
3032
3827
|
}
|
|
3033
3828
|
else {
|
|
3034
|
-
yield this.responseError(frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
|
|
3829
|
+
yield this.responseError(appLayer, frame, response, getErrorByCode(ErrorCode.ILLEGAL_FUNCTION));
|
|
3035
3830
|
}
|
|
3036
3831
|
break;
|
|
3037
3832
|
}
|
|
@@ -3047,50 +3842,66 @@ class ModbusSlave extends EventEmitter {
|
|
|
3047
3842
|
}
|
|
3048
3843
|
addCustomFunctionCode(cfc) {
|
|
3049
3844
|
this._customFunctionCodes.set(cfc.fc, cfc);
|
|
3050
|
-
this.
|
|
3845
|
+
for (const appLayer of this._appLayers.keys()) {
|
|
3846
|
+
appLayer.addCustomFunctionCode(cfc);
|
|
3847
|
+
}
|
|
3051
3848
|
}
|
|
3052
3849
|
removeCustomFunctionCode(fc) {
|
|
3053
3850
|
this._customFunctionCodes.delete(fc);
|
|
3054
|
-
this.
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
this.
|
|
3068
|
-
|
|
3069
|
-
this._customFunctionCodes.clear();
|
|
3070
|
-
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'));
|
|
3071
3866
|
}
|
|
3072
|
-
this.
|
|
3867
|
+
return this._physicalLayer.open(...args);
|
|
3073
3868
|
}
|
|
3074
|
-
|
|
3075
|
-
this
|
|
3076
|
-
|
|
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
|
+
});
|
|
3077
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
|
+
*/
|
|
3078
3896
|
close() {
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
if (this._cleanLevel === 'destroy') {
|
|
3087
|
-
return Promise.resolve();
|
|
3088
|
-
}
|
|
3089
|
-
this._clean('destroy');
|
|
3090
|
-
this.removeAllListeners();
|
|
3091
|
-
this.applicationLayer.destroy();
|
|
3092
|
-
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
|
+
});
|
|
3093
3904
|
}
|
|
3094
3905
|
}
|
|
3095
3906
|
|
|
3096
|
-
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 };
|