njs-modbus 1.4.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -11
- package/dist/index.cjs +2117 -1243
- package/dist/index.d.ts +312 -87
- package/dist/index.mjs +2110 -1244
- package/dist/src/error-code.d.ts +27 -1
- package/dist/src/index.d.ts +2 -0
- package/dist/src/layers/application/abstract-application-layer.d.ts +11 -8
- package/dist/src/layers/application/ascii-application-layer.d.ts +14 -10
- package/dist/src/layers/application/index.d.ts +2 -0
- package/dist/src/layers/application/rtu-application-layer.d.ts +52 -18
- package/dist/src/layers/application/tcp-application-layer.d.ts +4 -8
- package/dist/src/layers/physical/abstract-physical-layer.d.ts +5 -1
- package/dist/src/layers/physical/index.d.ts +1 -0
- package/dist/src/layers/physical/serial-physical-layer.d.ts +2 -0
- package/dist/src/layers/physical/tcp-client-physical-layer.d.ts +3 -0
- package/dist/src/layers/physical/tcp-server-physical-layer.d.ts +3 -1
- package/dist/src/layers/physical/udp-physical-layer.d.ts +25 -10
- package/dist/src/master/index.d.ts +2 -0
- package/dist/src/master/master-session.d.ts +18 -0
- package/dist/src/master/master.d.ts +27 -5
- package/dist/src/slave/index.d.ts +1 -1
- package/dist/src/slave/slave.d.ts +22 -3
- package/dist/src/types.d.ts +33 -2
- package/dist/src/utils/bitsToMs.d.ts +13 -0
- package/dist/src/utils/crc.d.ts +1 -1
- package/dist/src/utils/genConnectionId.d.ts +2 -0
- package/dist/src/utils/index.d.ts +5 -1
- package/dist/src/utils/isUint8.d.ts +8 -0
- package/dist/src/utils/predictRtuFrameLength.d.ts +20 -0
- package/dist/src/vars.d.ts +47 -0
- package/dist/test/ascii-hex-validation.test.d.ts +1 -0
- package/dist/test/ascii-tcp-fragmentation.test.d.ts +1 -0
- package/dist/test/check-range.test.d.ts +1 -0
- package/dist/test/fallback-atomic.test.d.ts +1 -0
- package/dist/test/fallback-serial.test.d.ts +1 -0
- package/dist/test/fc17-serverid-validation.test.d.ts +1 -0
- package/dist/test/fc43-conformity.test.d.ts +1 -0
- package/dist/test/fc43-utf8-objects.test.d.ts +1 -0
- package/dist/test/gen-connection-id.test.d.ts +1 -0
- package/dist/test/helpers/raw-tcp.d.ts +38 -0
- package/dist/test/master-concurrent.test.d.ts +1 -0
- package/dist/test/modbus-error.test.d.ts +1 -0
- package/dist/test/physical-lifecycle.test.d.ts +1 -0
- package/dist/test/predict-rtu.test.d.ts +1 -0
- package/dist/test/rtu-custom-fc.test.d.ts +1 -0
- package/dist/test/rtu-pool-overflow.test.d.ts +1 -0
- package/dist/test/rtu-t15-timing.test.d.ts +1 -0
- package/dist/test/rtu-t35-default.test.d.ts +1 -0
- package/dist/test/rtu-t35-strict.test.d.ts +1 -0
- package/dist/test/rtu-tcp-fragmentation.test.d.ts +1 -0
- package/dist/test/serial-e2e.test.d.ts +1 -0
- package/dist/test/slave-multi-connection.test.d.ts +1 -0
- package/dist/test/slave.test.d.ts +1 -0
- package/dist/test/tcp-fragmentation.test.d.ts +1 -0
- package/dist/test/udp-multi-client.test.d.ts +1 -0
- package/package.json +4 -1
- package/dist/src/utils/getThreePointFiveT.d.ts +0 -7
- /package/dist/test/{master.d.ts → adu-buffer.test.d.ts} +0 -0
- /package/dist/test/{slave.d.ts → ascii-hex-sentry.test.d.ts} +0 -0
package/dist/index.cjs
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
var EventEmitter = require('node:events');
|
|
4
4
|
var serialport = require('serialport');
|
|
5
|
+
var node_crypto = require('node:crypto');
|
|
5
6
|
var node_net = require('node:net');
|
|
6
7
|
var node_dgram = require('node:dgram');
|
|
7
8
|
|
|
@@ -17,20 +18,293 @@ exports.ErrorCode = void 0;
|
|
|
17
18
|
ErrorCode[ErrorCode["GATEWAY_PATH_UNAVAILABLE"] = 10] = "GATEWAY_PATH_UNAVAILABLE";
|
|
18
19
|
ErrorCode[ErrorCode["GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND"] = 11] = "GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND";
|
|
19
20
|
})(exports.ErrorCode || (exports.ErrorCode = {}));
|
|
20
|
-
|
|
21
|
+
/** Internal error codes for programmatic error handling. */
|
|
22
|
+
const ModbusErrorCode = {
|
|
23
|
+
TIMEOUT: 'ETIMEOUT',
|
|
24
|
+
INVALID_RESPONSE: 'EINVALID_RESPONSE',
|
|
25
|
+
INSUFFICIENT_DATA: 'EINSUFFICIENT_DATA',
|
|
26
|
+
MASTER_CLOSED: 'EMASTER_CLOSED',
|
|
27
|
+
MASTER_DESTROYED: 'EMASTER_DESTROYED',
|
|
28
|
+
CONCURRENT_NOT_TCP: 'ECONCURRENT_NOT_TCP',
|
|
29
|
+
PORT_DESTROYED: 'EPORT_DESTROYED',
|
|
30
|
+
PORT_ALREADY_OPEN: 'EPORT_ALREADY_OPEN',
|
|
31
|
+
PORT_NOT_OPEN: 'EPORT_NOT_OPEN',
|
|
32
|
+
NOT_SUPPORTED: 'ENOT_SUPPORTED',
|
|
33
|
+
INVALID_DATA: 'EINVALID_DATA',
|
|
34
|
+
INVALID_HEX: 'EINVALID_HEX',
|
|
35
|
+
CRC_MISMATCH: 'ECRC_MISMATCH',
|
|
36
|
+
LRC_MISMATCH: 'ELRC_MISMATCH',
|
|
37
|
+
INCOMPLETE_FRAME: 'EINCOMPLETE_FRAME',
|
|
38
|
+
T1_5_EXCEEDED: 'ET1_5_EXCEEDED',
|
|
39
|
+
UNKNOWN_FC: 'EUNKNOWN_FC',
|
|
40
|
+
INVALID_ROLE: 'EINVALID_ROLE',
|
|
41
|
+
RANGE: 'ERANGE',
|
|
42
|
+
};
|
|
43
|
+
const MODBUS_PREFIX = 'MODBUS_ERROR_CODE_';
|
|
44
|
+
class ModbusError extends Error {
|
|
45
|
+
constructor(code, message) {
|
|
46
|
+
super(message !== null && message !== void 0 ? message : code);
|
|
47
|
+
Object.defineProperty(this, "code", {
|
|
48
|
+
enumerable: true,
|
|
49
|
+
configurable: true,
|
|
50
|
+
writable: true,
|
|
51
|
+
value: code
|
|
52
|
+
});
|
|
53
|
+
this.name = 'ModbusError';
|
|
54
|
+
}
|
|
55
|
+
}
|
|
21
56
|
function getErrorByCode(code) {
|
|
22
|
-
return new
|
|
57
|
+
return new ModbusError(String(code), `${MODBUS_PREFIX}${code}`);
|
|
23
58
|
}
|
|
24
59
|
function getCodeByError(err) {
|
|
25
|
-
if (err
|
|
26
|
-
|
|
60
|
+
if (err instanceof ModbusError) {
|
|
61
|
+
const num = Number(err.code);
|
|
62
|
+
if (!Number.isNaN(num) && num in exports.ErrorCode) {
|
|
63
|
+
return num;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (err.message.startsWith(MODBUS_PREFIX)) {
|
|
67
|
+
return Number(err.message.slice(MODBUS_PREFIX.length));
|
|
27
68
|
}
|
|
28
69
|
return exports.ErrorCode.SERVER_DEVICE_FAILURE;
|
|
29
70
|
}
|
|
30
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Standard Modbus function codes (V1.1b3 §6).
|
|
74
|
+
*/
|
|
75
|
+
exports.FunctionCode = void 0;
|
|
76
|
+
(function (FunctionCode) {
|
|
77
|
+
FunctionCode[FunctionCode["READ_COILS"] = 1] = "READ_COILS";
|
|
78
|
+
FunctionCode[FunctionCode["READ_DISCRETE_INPUTS"] = 2] = "READ_DISCRETE_INPUTS";
|
|
79
|
+
FunctionCode[FunctionCode["READ_HOLDING_REGISTERS"] = 3] = "READ_HOLDING_REGISTERS";
|
|
80
|
+
FunctionCode[FunctionCode["READ_INPUT_REGISTERS"] = 4] = "READ_INPUT_REGISTERS";
|
|
81
|
+
FunctionCode[FunctionCode["WRITE_SINGLE_COIL"] = 5] = "WRITE_SINGLE_COIL";
|
|
82
|
+
FunctionCode[FunctionCode["WRITE_SINGLE_REGISTER"] = 6] = "WRITE_SINGLE_REGISTER";
|
|
83
|
+
FunctionCode[FunctionCode["WRITE_MULTIPLE_COILS"] = 15] = "WRITE_MULTIPLE_COILS";
|
|
84
|
+
FunctionCode[FunctionCode["WRITE_MULTIPLE_REGISTERS"] = 16] = "WRITE_MULTIPLE_REGISTERS";
|
|
85
|
+
FunctionCode[FunctionCode["REPORT_SERVER_ID"] = 17] = "REPORT_SERVER_ID";
|
|
86
|
+
FunctionCode[FunctionCode["MASK_WRITE_REGISTER"] = 22] = "MASK_WRITE_REGISTER";
|
|
87
|
+
FunctionCode[FunctionCode["READ_WRITE_MULTIPLE_REGISTERS"] = 23] = "READ_WRITE_MULTIPLE_REGISTERS";
|
|
88
|
+
FunctionCode[FunctionCode["READ_DEVICE_IDENTIFICATION"] = 43] = "READ_DEVICE_IDENTIFICATION";
|
|
89
|
+
})(exports.FunctionCode || (exports.FunctionCode = {}));
|
|
90
|
+
/** Exception response FC = request FC | EXCEPTION_OFFSET (V1.1b3 §7). */
|
|
91
|
+
const EXCEPTION_OFFSET = 0x80;
|
|
92
|
+
/** Coil value encoding for FC 5 / FC 15 (V1.1b3 §6.5/§6.11). */
|
|
93
|
+
const COIL_ON = 0xff00;
|
|
94
|
+
const COIL_OFF = 0x0000;
|
|
95
|
+
/** FC 0x2B MEI sub-function selecting Read Device Identification (V1.1b3 §6.21). */
|
|
96
|
+
const MEI_READ_DEVICE_ID = 0x0e;
|
|
97
|
+
/** Read Device ID code values inside an FC 0x2B / MEI 0x0E request. */
|
|
98
|
+
exports.ReadDeviceIDCode = void 0;
|
|
99
|
+
(function (ReadDeviceIDCode) {
|
|
100
|
+
ReadDeviceIDCode[ReadDeviceIDCode["BASIC_STREAM"] = 1] = "BASIC_STREAM";
|
|
101
|
+
ReadDeviceIDCode[ReadDeviceIDCode["REGULAR_STREAM"] = 2] = "REGULAR_STREAM";
|
|
102
|
+
ReadDeviceIDCode[ReadDeviceIDCode["EXTENDED_STREAM"] = 3] = "EXTENDED_STREAM";
|
|
103
|
+
ReadDeviceIDCode[ReadDeviceIDCode["SPECIFIC_ACCESS"] = 4] = "SPECIFIC_ACCESS";
|
|
104
|
+
})(exports.ReadDeviceIDCode || (exports.ReadDeviceIDCode = {}));
|
|
105
|
+
/** Conformity level reported in an FC 0x2B / MEI 0x0E response. */
|
|
106
|
+
exports.ConformityLevel = void 0;
|
|
107
|
+
(function (ConformityLevel) {
|
|
108
|
+
ConformityLevel[ConformityLevel["BASIC"] = 129] = "BASIC";
|
|
109
|
+
ConformityLevel[ConformityLevel["REGULAR"] = 130] = "REGULAR";
|
|
110
|
+
ConformityLevel[ConformityLevel["EXTENDED"] = 131] = "EXTENDED";
|
|
111
|
+
})(exports.ConformityLevel || (exports.ConformityLevel = {}));
|
|
112
|
+
/** Modbus V1.1b3 PDU quantity limits. */
|
|
113
|
+
const LIMITS = {
|
|
114
|
+
READ_COILS_MIN: 0x0001,
|
|
115
|
+
READ_COILS_MAX: 0x07d0,
|
|
116
|
+
READ_REGISTERS_MIN: 0x0001,
|
|
117
|
+
READ_REGISTERS_MAX: 0x007d,
|
|
118
|
+
WRITE_COILS_MAX: 0x07b0,
|
|
119
|
+
WRITE_REGISTERS_MAX: 0x007b,
|
|
120
|
+
RW_REGISTERS_WRITE_MAX: 0x0079,
|
|
121
|
+
};
|
|
122
|
+
|
|
31
123
|
class AbstractPhysicalLayer extends EventEmitter {
|
|
32
124
|
}
|
|
33
125
|
|
|
126
|
+
function inRange(n, [min, max]) {
|
|
127
|
+
return n >= min && n <= max;
|
|
128
|
+
}
|
|
129
|
+
function checkRange(value, range) {
|
|
130
|
+
if (!range) {
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
const values = Array.isArray(value) ? value : [value];
|
|
134
|
+
if (typeof range[0] === 'number' && typeof range[1] === 'number') {
|
|
135
|
+
const [min, max] = range;
|
|
136
|
+
const [lo, hi] = min <= max ? [min, max] : [max, min];
|
|
137
|
+
return values.every((n) => inRange(n, [lo, hi]));
|
|
138
|
+
}
|
|
139
|
+
else if (range.length > 0) {
|
|
140
|
+
for (const r of range) {
|
|
141
|
+
const [min, max] = r;
|
|
142
|
+
const [lo, hi] = min <= max ? [min, max] : [max, min];
|
|
143
|
+
if (values.every((n) => inRange(n, [lo, hi]))) {
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const TABLE = [
|
|
153
|
+
0x0000, 0xc0c1, 0xc181, 0x0140, 0xc301, 0x03c0, 0x0280, 0xc241, 0xc601, 0x06c0, 0x0780, 0xc741, 0x0500, 0xc5c1, 0xc481, 0x0440, 0xcc01,
|
|
154
|
+
0x0cc0, 0x0d80, 0xcd41, 0x0f00, 0xcfc1, 0xce81, 0x0e40, 0x0a00, 0xcac1, 0xcb81, 0x0b40, 0xc901, 0x09c0, 0x0880, 0xc841, 0xd801, 0x18c0,
|
|
155
|
+
0x1980, 0xd941, 0x1b00, 0xdbc1, 0xda81, 0x1a40, 0x1e00, 0xdec1, 0xdf81, 0x1f40, 0xdd01, 0x1dc0, 0x1c80, 0xdc41, 0x1400, 0xd4c1, 0xd581,
|
|
156
|
+
0x1540, 0xd701, 0x17c0, 0x1680, 0xd641, 0xd201, 0x12c0, 0x1380, 0xd341, 0x1100, 0xd1c1, 0xd081, 0x1040, 0xf001, 0x30c0, 0x3180, 0xf141,
|
|
157
|
+
0x3300, 0xf3c1, 0xf281, 0x3240, 0x3600, 0xf6c1, 0xf781, 0x3740, 0xf501, 0x35c0, 0x3480, 0xf441, 0x3c00, 0xfcc1, 0xfd81, 0x3d40, 0xff01,
|
|
158
|
+
0x3fc0, 0x3e80, 0xfe41, 0xfa01, 0x3ac0, 0x3b80, 0xfb41, 0x3900, 0xf9c1, 0xf881, 0x3840, 0x2800, 0xe8c1, 0xe981, 0x2940, 0xeb01, 0x2bc0,
|
|
159
|
+
0x2a80, 0xea41, 0xee01, 0x2ec0, 0x2f80, 0xef41, 0x2d00, 0xedc1, 0xec81, 0x2c40, 0xe401, 0x24c0, 0x2580, 0xe541, 0x2700, 0xe7c1, 0xe681,
|
|
160
|
+
0x2640, 0x2200, 0xe2c1, 0xe381, 0x2340, 0xe101, 0x21c0, 0x2080, 0xe041, 0xa001, 0x60c0, 0x6180, 0xa141, 0x6300, 0xa3c1, 0xa281, 0x6240,
|
|
161
|
+
0x6600, 0xa6c1, 0xa781, 0x6740, 0xa501, 0x65c0, 0x6480, 0xa441, 0x6c00, 0xacc1, 0xad81, 0x6d40, 0xaf01, 0x6fc0, 0x6e80, 0xae41, 0xaa01,
|
|
162
|
+
0x6ac0, 0x6b80, 0xab41, 0x6900, 0xa9c1, 0xa881, 0x6840, 0x7800, 0xb8c1, 0xb981, 0x7940, 0xbb01, 0x7bc0, 0x7a80, 0xba41, 0xbe01, 0x7ec0,
|
|
163
|
+
0x7f80, 0xbf41, 0x7d00, 0xbdc1, 0xbc81, 0x7c40, 0xb401, 0x74c0, 0x7580, 0xb541, 0x7700, 0xb7c1, 0xb681, 0x7640, 0x7200, 0xb2c1, 0xb381,
|
|
164
|
+
0x7340, 0xb101, 0x71c0, 0x7080, 0xb041, 0x5000, 0x90c1, 0x9181, 0x5140, 0x9301, 0x53c0, 0x5280, 0x9241, 0x9601, 0x56c0, 0x5780, 0x9741,
|
|
165
|
+
0x5500, 0x95c1, 0x9481, 0x5440, 0x9c01, 0x5cc0, 0x5d80, 0x9d41, 0x5f00, 0x9fc1, 0x9e81, 0x5e40, 0x5a00, 0x9ac1, 0x9b81, 0x5b40, 0x9901,
|
|
166
|
+
0x59c0, 0x5880, 0x9841, 0x8801, 0x48c0, 0x4980, 0x8941, 0x4b00, 0x8bc1, 0x8a81, 0x4a40, 0x4e00, 0x8ec1, 0x8f81, 0x4f40, 0x8d01, 0x4dc0,
|
|
167
|
+
0x4c80, 0x8c41, 0x4400, 0x84c1, 0x8581, 0x4540, 0x8701, 0x47c0, 0x4680, 0x8641, 0x8201, 0x42c0, 0x4380, 0x8341, 0x4100, 0x81c1, 0x8081,
|
|
168
|
+
0x4040,
|
|
169
|
+
];
|
|
170
|
+
function crc(data, seed = 0xffff) {
|
|
171
|
+
let crc = seed;
|
|
172
|
+
for (let index = 0; index < data.length; index++) {
|
|
173
|
+
crc = (TABLE[(crc ^ data[index]) & 0xff] ^ (crc >> 8)) & 0xffff;
|
|
174
|
+
}
|
|
175
|
+
return crc;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Cross-process unique id: `${prefix}-${uuid-v4}`. */
|
|
179
|
+
function genConnectionId(prefix) {
|
|
180
|
+
return `${prefix}-${node_crypto.randomUUID()}`;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Convert a number of bits to milliseconds at a given baud rate.
|
|
185
|
+
*
|
|
186
|
+
* Used to derive Modbus RTU timing intervals from bit counts — e.g. 38.5 bits
|
|
187
|
+
* = 3.5 character times at 11 bits/char (t3.5 inter-frame silence), or 16.5
|
|
188
|
+
* bits = 1.5 character times (t1.5 inter-character timeout), per Modbus V1.02
|
|
189
|
+
* §2.5.1.1.
|
|
190
|
+
*
|
|
191
|
+
* @param baudRate Serial port baud rate.
|
|
192
|
+
* @param bits Number of bits to convert.
|
|
193
|
+
* @returns Duration in milliseconds.
|
|
194
|
+
*/
|
|
195
|
+
function bitsToMs(baudRate, bits) {
|
|
196
|
+
return (bits * 1000) / baudRate;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Returns true when `n` is an integer in the unsigned-byte range [0, 255].
|
|
201
|
+
*
|
|
202
|
+
* Used for byte-level Modbus payload validation (function-code values, raw
|
|
203
|
+
* byte arrays in FC17/FC43 responses) — rejects negative, fractional, NaN,
|
|
204
|
+
* Infinity, and out-of-range values uniformly.
|
|
205
|
+
*/
|
|
206
|
+
function isUint8(n) {
|
|
207
|
+
return Number.isInteger(n) && n >= 0 && n <= 255;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function lrc(data) {
|
|
211
|
+
return (~data.reduce((sum, n) => sum + n, 0) + 1) & 0xff;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const REQUEST_FIXED_LENGTHS = {
|
|
215
|
+
[exports.FunctionCode.READ_COILS]: 8,
|
|
216
|
+
[exports.FunctionCode.READ_DISCRETE_INPUTS]: 8,
|
|
217
|
+
[exports.FunctionCode.READ_HOLDING_REGISTERS]: 8,
|
|
218
|
+
[exports.FunctionCode.READ_INPUT_REGISTERS]: 8,
|
|
219
|
+
[exports.FunctionCode.WRITE_SINGLE_COIL]: 8,
|
|
220
|
+
[exports.FunctionCode.WRITE_SINGLE_REGISTER]: 8,
|
|
221
|
+
[exports.FunctionCode.REPORT_SERVER_ID]: 4,
|
|
222
|
+
[exports.FunctionCode.MASK_WRITE_REGISTER]: 10,
|
|
223
|
+
[exports.FunctionCode.READ_DEVICE_IDENTIFICATION]: 7,
|
|
224
|
+
};
|
|
225
|
+
const REQUEST_BYTE_COUNT = {
|
|
226
|
+
[exports.FunctionCode.WRITE_MULTIPLE_COILS]: { offset: 6, extra: 9 },
|
|
227
|
+
[exports.FunctionCode.WRITE_MULTIPLE_REGISTERS]: { offset: 6, extra: 9 },
|
|
228
|
+
[exports.FunctionCode.READ_WRITE_MULTIPLE_REGISTERS]: { offset: 10, extra: 13 },
|
|
229
|
+
};
|
|
230
|
+
const RESPONSE_FIXED_LENGTHS = {
|
|
231
|
+
[exports.FunctionCode.WRITE_SINGLE_COIL]: 8,
|
|
232
|
+
[exports.FunctionCode.WRITE_SINGLE_REGISTER]: 8,
|
|
233
|
+
[exports.FunctionCode.WRITE_MULTIPLE_COILS]: 8,
|
|
234
|
+
[exports.FunctionCode.WRITE_MULTIPLE_REGISTERS]: 8,
|
|
235
|
+
[exports.FunctionCode.MASK_WRITE_REGISTER]: 10,
|
|
236
|
+
};
|
|
237
|
+
const RESPONSE_BYTE_COUNT = {
|
|
238
|
+
[exports.FunctionCode.READ_COILS]: { offset: 2, extra: 5 },
|
|
239
|
+
[exports.FunctionCode.READ_DISCRETE_INPUTS]: { offset: 2, extra: 5 },
|
|
240
|
+
[exports.FunctionCode.READ_HOLDING_REGISTERS]: { offset: 2, extra: 5 },
|
|
241
|
+
[exports.FunctionCode.READ_INPUT_REGISTERS]: { offset: 2, extra: 5 },
|
|
242
|
+
[exports.FunctionCode.REPORT_SERVER_ID]: { offset: 2, extra: 5 },
|
|
243
|
+
[exports.FunctionCode.READ_WRITE_MULTIPLE_REGISTERS]: { offset: 2, extra: 5 },
|
|
244
|
+
};
|
|
245
|
+
/**
|
|
246
|
+
* Predict the total RTU frame length (PDU + 2-byte CRC) given the leading bytes.
|
|
247
|
+
*
|
|
248
|
+
* Returns a discriminated result so callers can distinguish:
|
|
249
|
+
* - `{ kind: 'length' }`: function code is known and total frame length is determined.
|
|
250
|
+
* - `{ kind: 'need-more' }`: function code is known, but more bytes are required
|
|
251
|
+
* (typically waiting on the byteCount byte).
|
|
252
|
+
* - `{ kind: 'unknown' }`: function code is not in the standard tables — the
|
|
253
|
+
* framing layer must defer to a registered `CustomFunctionCode` or treat this
|
|
254
|
+
* as a framing error.
|
|
255
|
+
*/
|
|
256
|
+
function predictRtuFrameLength(buffer, isResponse) {
|
|
257
|
+
if (buffer.length < 2) {
|
|
258
|
+
return { kind: 'need-more' };
|
|
259
|
+
}
|
|
260
|
+
const fc = buffer[1];
|
|
261
|
+
if (isResponse && (fc & EXCEPTION_OFFSET) !== 0) {
|
|
262
|
+
return { kind: 'length', length: 5 };
|
|
263
|
+
}
|
|
264
|
+
const fixed = (isResponse ? RESPONSE_FIXED_LENGTHS : REQUEST_FIXED_LENGTHS)[fc];
|
|
265
|
+
if (fixed !== undefined) {
|
|
266
|
+
return { kind: 'length', length: fixed };
|
|
267
|
+
}
|
|
268
|
+
const bc = (isResponse ? RESPONSE_BYTE_COUNT : REQUEST_BYTE_COUNT)[fc];
|
|
269
|
+
if (bc !== undefined) {
|
|
270
|
+
if (buffer.length <= bc.offset) {
|
|
271
|
+
return { kind: 'need-more' };
|
|
272
|
+
}
|
|
273
|
+
return { kind: 'length', length: bc.extra + buffer[bc.offset] };
|
|
274
|
+
}
|
|
275
|
+
if (isResponse && fc === exports.FunctionCode.READ_DEVICE_IDENTIFICATION) {
|
|
276
|
+
return predictFc43_14Response(buffer);
|
|
277
|
+
}
|
|
278
|
+
return { kind: 'unknown' };
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Walk the variable-length FC 0x2B / MEI 0x0E (Read Device Identification)
|
|
282
|
+
* response structure per Modbus V1.1b3 §6.21.
|
|
283
|
+
*
|
|
284
|
+
* Layout (after unit and fc):
|
|
285
|
+
* mei(1) rdic(1) conformity(1) more(1) nextObjId(1) numObjs(1)
|
|
286
|
+
* [objId(1) objLen(1) objData(objLen)] × numObjs
|
|
287
|
+
* CRC(2)
|
|
288
|
+
*/
|
|
289
|
+
function predictFc43_14Response(buffer) {
|
|
290
|
+
if (buffer.length < 8) {
|
|
291
|
+
return { kind: 'need-more' };
|
|
292
|
+
}
|
|
293
|
+
if (buffer[2] !== MEI_READ_DEVICE_ID) {
|
|
294
|
+
return { kind: 'unknown' };
|
|
295
|
+
}
|
|
296
|
+
const numObjs = buffer[7];
|
|
297
|
+
let offset = 8;
|
|
298
|
+
for (let i = 0; i < numObjs; i++) {
|
|
299
|
+
if (buffer.length < offset + 2) {
|
|
300
|
+
return { kind: 'need-more' };
|
|
301
|
+
}
|
|
302
|
+
const objLen = buffer[offset + 1];
|
|
303
|
+
offset += 2 + objLen;
|
|
304
|
+
}
|
|
305
|
+
return { kind: 'length', length: offset + 2 };
|
|
306
|
+
}
|
|
307
|
+
|
|
34
308
|
class SerialPhysicalLayer extends AbstractPhysicalLayer {
|
|
35
309
|
get isOpen() {
|
|
36
310
|
return this._serialport.isOpen;
|
|
@@ -55,6 +329,18 @@ class SerialPhysicalLayer extends AbstractPhysicalLayer {
|
|
|
55
329
|
writable: true,
|
|
56
330
|
value: void 0
|
|
57
331
|
});
|
|
332
|
+
Object.defineProperty(this, "_connection", {
|
|
333
|
+
enumerable: true,
|
|
334
|
+
configurable: true,
|
|
335
|
+
writable: true,
|
|
336
|
+
value: null
|
|
337
|
+
});
|
|
338
|
+
Object.defineProperty(this, "_isOpening", {
|
|
339
|
+
enumerable: true,
|
|
340
|
+
configurable: true,
|
|
341
|
+
writable: true,
|
|
342
|
+
value: false
|
|
343
|
+
});
|
|
58
344
|
Object.defineProperty(this, "_destroyed", {
|
|
59
345
|
enumerable: true,
|
|
60
346
|
configurable: true,
|
|
@@ -71,27 +357,41 @@ class SerialPhysicalLayer extends AbstractPhysicalLayer {
|
|
|
71
357
|
this._baudRate = options.baudRate;
|
|
72
358
|
}
|
|
73
359
|
open() {
|
|
74
|
-
if (this.
|
|
75
|
-
return Promise.reject(new
|
|
360
|
+
if (this._destroyed) {
|
|
361
|
+
return Promise.reject(new ModbusError(ModbusErrorCode.PORT_DESTROYED, 'Port is destroyed'));
|
|
362
|
+
}
|
|
363
|
+
if (this.isOpen || this._isOpening) {
|
|
364
|
+
return Promise.reject(new ModbusError(ModbusErrorCode.PORT_ALREADY_OPEN, 'Port is already open'));
|
|
76
365
|
}
|
|
366
|
+
this._isOpening = true;
|
|
367
|
+
const connection = { id: genConnectionId('serial') };
|
|
368
|
+
this._connection = connection;
|
|
77
369
|
return new Promise((resolve, reject) => {
|
|
78
370
|
this._serialport.open((error) => {
|
|
79
371
|
if (error) {
|
|
372
|
+
this._isOpening = false;
|
|
373
|
+
if (this._connection === connection) {
|
|
374
|
+
this._connection = null;
|
|
375
|
+
}
|
|
80
376
|
reject(error);
|
|
377
|
+
return;
|
|
81
378
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
379
|
+
this._isOpening = false;
|
|
380
|
+
this._serialport.on('data', (data) => {
|
|
381
|
+
this.emit('data', data, (d) => this.write(d), connection);
|
|
382
|
+
});
|
|
383
|
+
this._serialport.on('error', (err) => {
|
|
384
|
+
this.emit('error', err);
|
|
385
|
+
});
|
|
386
|
+
this._serialport.once('close', () => {
|
|
387
|
+
if (this._connection === connection) {
|
|
90
388
|
this._serialport.removeAllListeners();
|
|
389
|
+
this._connection = null;
|
|
390
|
+
this.emit('connection-close', connection);
|
|
91
391
|
this.emit('close');
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
resolve();
|
|
95
395
|
});
|
|
96
396
|
});
|
|
97
397
|
}
|
|
@@ -109,22 +409,24 @@ class SerialPhysicalLayer extends AbstractPhysicalLayer {
|
|
|
109
409
|
});
|
|
110
410
|
}
|
|
111
411
|
else {
|
|
112
|
-
reject(new
|
|
412
|
+
reject(new ModbusError(ModbusErrorCode.PORT_NOT_OPEN, 'Port is not open'));
|
|
113
413
|
}
|
|
114
414
|
});
|
|
115
415
|
}
|
|
116
416
|
close() {
|
|
417
|
+
if (!this._serialport.isOpen) {
|
|
418
|
+
return Promise.resolve();
|
|
419
|
+
}
|
|
117
420
|
return new Promise((resolve) => {
|
|
118
|
-
this._serialport.
|
|
119
|
-
this._serialport.close(
|
|
120
|
-
resolve();
|
|
121
|
-
});
|
|
421
|
+
this._serialport.once('close', () => resolve());
|
|
422
|
+
this._serialport.close();
|
|
122
423
|
});
|
|
123
424
|
}
|
|
124
425
|
destroy() {
|
|
125
426
|
this._destroyed = true;
|
|
126
|
-
this.
|
|
127
|
-
|
|
427
|
+
return this.close().then(() => {
|
|
428
|
+
this.removeAllListeners();
|
|
429
|
+
});
|
|
128
430
|
}
|
|
129
431
|
}
|
|
130
432
|
|
|
@@ -147,7 +449,13 @@ class TcpClientPhysicalLayer extends AbstractPhysicalLayer {
|
|
|
147
449
|
enumerable: true,
|
|
148
450
|
configurable: true,
|
|
149
451
|
writable: true,
|
|
150
|
-
value:
|
|
452
|
+
value: null
|
|
453
|
+
});
|
|
454
|
+
Object.defineProperty(this, "_connection", {
|
|
455
|
+
enumerable: true,
|
|
456
|
+
configurable: true,
|
|
457
|
+
writable: true,
|
|
458
|
+
value: null
|
|
151
459
|
});
|
|
152
460
|
Object.defineProperty(this, "_isOpen", {
|
|
153
461
|
enumerable: true,
|
|
@@ -155,46 +463,79 @@ class TcpClientPhysicalLayer extends AbstractPhysicalLayer {
|
|
|
155
463
|
writable: true,
|
|
156
464
|
value: false
|
|
157
465
|
});
|
|
466
|
+
Object.defineProperty(this, "_isOpening", {
|
|
467
|
+
enumerable: true,
|
|
468
|
+
configurable: true,
|
|
469
|
+
writable: true,
|
|
470
|
+
value: false
|
|
471
|
+
});
|
|
158
472
|
Object.defineProperty(this, "_destroyed", {
|
|
159
473
|
enumerable: true,
|
|
160
474
|
configurable: true,
|
|
161
475
|
writable: true,
|
|
162
476
|
value: false
|
|
163
477
|
});
|
|
164
|
-
this
|
|
478
|
+
Object.defineProperty(this, "_socketOptions", {
|
|
479
|
+
enumerable: true,
|
|
480
|
+
configurable: true,
|
|
481
|
+
writable: true,
|
|
482
|
+
value: void 0
|
|
483
|
+
});
|
|
484
|
+
this._socketOptions = options;
|
|
165
485
|
}
|
|
166
486
|
open(options) {
|
|
167
|
-
if (this.
|
|
168
|
-
return Promise.reject(new
|
|
487
|
+
if (this._destroyed) {
|
|
488
|
+
return Promise.reject(new ModbusError(ModbusErrorCode.PORT_DESTROYED, 'Port is destroyed'));
|
|
489
|
+
}
|
|
490
|
+
if (this._isOpen || this._isOpening) {
|
|
491
|
+
return Promise.reject(new ModbusError(ModbusErrorCode.PORT_ALREADY_OPEN, 'Port is already open'));
|
|
169
492
|
}
|
|
493
|
+
this._isOpening = true;
|
|
494
|
+
const socket = new node_net.Socket(this._socketOptions);
|
|
495
|
+
const connection = { id: genConnectionId('tcp-client') };
|
|
496
|
+
this._socket = socket;
|
|
497
|
+
this._connection = connection;
|
|
170
498
|
return new Promise((resolve, reject) => {
|
|
171
|
-
let
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
this._isOpen = true;
|
|
175
|
-
this._socket.on('data', (data) => {
|
|
176
|
-
this.emit('data', data, (data) => this.write(data));
|
|
177
|
-
});
|
|
178
|
-
this._socket.on('close', () => {
|
|
179
|
-
this._isOpen = false;
|
|
180
|
-
this._socket.removeAllListeners();
|
|
181
|
-
this.emit('close');
|
|
182
|
-
});
|
|
183
|
-
resolve();
|
|
499
|
+
let connected = false;
|
|
500
|
+
socket.on('data', (data) => {
|
|
501
|
+
this.emit('data', data, (d) => this.write(d), connection);
|
|
184
502
|
});
|
|
185
|
-
|
|
186
|
-
if (
|
|
187
|
-
this.emit('error',
|
|
503
|
+
socket.on('error', (err) => {
|
|
504
|
+
if (connected) {
|
|
505
|
+
this.emit('error', err);
|
|
188
506
|
}
|
|
189
507
|
else {
|
|
190
|
-
|
|
508
|
+
if (this._connection === connection) {
|
|
509
|
+
this._isOpening = false;
|
|
510
|
+
this._socket = null;
|
|
511
|
+
this._connection = null;
|
|
512
|
+
}
|
|
513
|
+
socket.removeAllListeners();
|
|
514
|
+
reject(err);
|
|
515
|
+
}
|
|
516
|
+
});
|
|
517
|
+
socket.once('close', () => {
|
|
518
|
+
if (this._connection === connection) {
|
|
519
|
+
this._isOpen = false;
|
|
520
|
+
this._isOpening = false;
|
|
521
|
+
this._socket = null;
|
|
522
|
+
this._connection = null;
|
|
523
|
+
socket.removeAllListeners();
|
|
524
|
+
this.emit('connection-close', connection);
|
|
525
|
+
this.emit('close');
|
|
191
526
|
}
|
|
192
527
|
});
|
|
528
|
+
socket.connect(options !== null && options !== void 0 ? options : { port: 502 }, () => {
|
|
529
|
+
connected = true;
|
|
530
|
+
this._isOpening = false;
|
|
531
|
+
this._isOpen = true;
|
|
532
|
+
resolve();
|
|
533
|
+
});
|
|
193
534
|
});
|
|
194
535
|
}
|
|
195
536
|
write(data) {
|
|
196
537
|
return new Promise((resolve, reject) => {
|
|
197
|
-
if (this.
|
|
538
|
+
if (this._isOpen && this._socket) {
|
|
198
539
|
this._socket.write(data, (error) => {
|
|
199
540
|
if (error) {
|
|
200
541
|
reject(error);
|
|
@@ -206,22 +547,25 @@ class TcpClientPhysicalLayer extends AbstractPhysicalLayer {
|
|
|
206
547
|
});
|
|
207
548
|
}
|
|
208
549
|
else {
|
|
209
|
-
reject(new
|
|
550
|
+
reject(new ModbusError(ModbusErrorCode.PORT_NOT_OPEN, 'Port is not open'));
|
|
210
551
|
}
|
|
211
552
|
});
|
|
212
553
|
}
|
|
213
554
|
close() {
|
|
555
|
+
if (!this._socket) {
|
|
556
|
+
return Promise.resolve();
|
|
557
|
+
}
|
|
558
|
+
const socket = this._socket;
|
|
214
559
|
return new Promise((resolve) => {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
this._socket.destroy();
|
|
218
|
-
resolve();
|
|
560
|
+
socket.once('close', () => resolve());
|
|
561
|
+
socket.destroy();
|
|
219
562
|
});
|
|
220
563
|
}
|
|
221
564
|
destroy() {
|
|
222
565
|
this._destroyed = true;
|
|
223
|
-
this.
|
|
224
|
-
|
|
566
|
+
return this.close().then(() => {
|
|
567
|
+
this.removeAllListeners();
|
|
568
|
+
});
|
|
225
569
|
}
|
|
226
570
|
}
|
|
227
571
|
|
|
@@ -244,7 +588,7 @@ class TcpServerPhysicalLayer extends AbstractPhysicalLayer {
|
|
|
244
588
|
enumerable: true,
|
|
245
589
|
configurable: true,
|
|
246
590
|
writable: true,
|
|
247
|
-
value:
|
|
591
|
+
value: null
|
|
248
592
|
});
|
|
249
593
|
Object.defineProperty(this, "_isOpen", {
|
|
250
594
|
enumerable: true,
|
|
@@ -252,91 +596,129 @@ class TcpServerPhysicalLayer extends AbstractPhysicalLayer {
|
|
|
252
596
|
writable: true,
|
|
253
597
|
value: false
|
|
254
598
|
});
|
|
599
|
+
Object.defineProperty(this, "_isOpening", {
|
|
600
|
+
enumerable: true,
|
|
601
|
+
configurable: true,
|
|
602
|
+
writable: true,
|
|
603
|
+
value: false
|
|
604
|
+
});
|
|
255
605
|
Object.defineProperty(this, "_destroyed", {
|
|
256
606
|
enumerable: true,
|
|
257
607
|
configurable: true,
|
|
258
608
|
writable: true,
|
|
259
609
|
value: false
|
|
260
610
|
});
|
|
261
|
-
Object.defineProperty(this, "
|
|
611
|
+
Object.defineProperty(this, "_connections", {
|
|
612
|
+
enumerable: true,
|
|
613
|
+
configurable: true,
|
|
614
|
+
writable: true,
|
|
615
|
+
value: new Map()
|
|
616
|
+
});
|
|
617
|
+
Object.defineProperty(this, "_serverOptions", {
|
|
262
618
|
enumerable: true,
|
|
263
619
|
configurable: true,
|
|
264
620
|
writable: true,
|
|
265
|
-
value:
|
|
621
|
+
value: void 0
|
|
266
622
|
});
|
|
267
|
-
this.
|
|
268
|
-
|
|
623
|
+
this._serverOptions = options;
|
|
624
|
+
}
|
|
625
|
+
open(options) {
|
|
626
|
+
if (this._destroyed) {
|
|
627
|
+
return Promise.reject(new ModbusError(ModbusErrorCode.PORT_DESTROYED, 'Port is destroyed'));
|
|
628
|
+
}
|
|
629
|
+
if (this._isOpen || this._isOpening) {
|
|
630
|
+
return Promise.reject(new ModbusError(ModbusErrorCode.PORT_ALREADY_OPEN, 'Port is already open'));
|
|
631
|
+
}
|
|
632
|
+
this._isOpening = true;
|
|
633
|
+
this._connections.clear();
|
|
634
|
+
const server = node_net.createServer(this._serverOptions, (socket) => {
|
|
635
|
+
const conn = { id: genConnectionId('tcp-server') };
|
|
636
|
+
this._connections.set(socket, conn);
|
|
637
|
+
const response = (data) => new Promise((resolve, reject) => {
|
|
638
|
+
socket.write(data, (error) => {
|
|
639
|
+
if (error) {
|
|
640
|
+
reject(error);
|
|
641
|
+
}
|
|
642
|
+
else {
|
|
643
|
+
resolve();
|
|
644
|
+
}
|
|
645
|
+
});
|
|
646
|
+
});
|
|
269
647
|
socket.on('data', (data) => {
|
|
270
|
-
this.emit('data', data,
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
}
|
|
275
|
-
else {
|
|
276
|
-
resolve();
|
|
277
|
-
}
|
|
278
|
-
});
|
|
279
|
-
}));
|
|
648
|
+
this.emit('data', data, response, conn);
|
|
649
|
+
});
|
|
650
|
+
socket.on('error', (err) => {
|
|
651
|
+
this.emit('error', err);
|
|
280
652
|
});
|
|
281
653
|
socket.once('close', () => {
|
|
654
|
+
if (this._connections.delete(socket)) {
|
|
655
|
+
this.emit('connection-close', conn);
|
|
656
|
+
}
|
|
282
657
|
socket.removeAllListeners();
|
|
283
|
-
this._sockets.delete(socket);
|
|
284
658
|
});
|
|
285
659
|
});
|
|
286
|
-
|
|
287
|
-
open(options) {
|
|
288
|
-
if (this.destroyed) {
|
|
289
|
-
return Promise.reject(new Error('Port is destroyed'));
|
|
290
|
-
}
|
|
660
|
+
this._server = server;
|
|
291
661
|
return new Promise((resolve, reject) => {
|
|
292
662
|
var _a;
|
|
293
|
-
let
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
663
|
+
let listening = false;
|
|
664
|
+
server.once('error', (err) => {
|
|
665
|
+
if (listening) {
|
|
666
|
+
this.emit('error', err);
|
|
667
|
+
}
|
|
668
|
+
else {
|
|
669
|
+
if (this._server === server) {
|
|
670
|
+
this._isOpening = false;
|
|
671
|
+
this._server = null;
|
|
672
|
+
}
|
|
673
|
+
server.removeAllListeners();
|
|
674
|
+
reject(err);
|
|
675
|
+
}
|
|
676
|
+
});
|
|
677
|
+
server.once('close', () => {
|
|
678
|
+
if (this._server === server) {
|
|
299
679
|
this._isOpen = false;
|
|
300
|
-
this.
|
|
301
|
-
|
|
302
|
-
|
|
680
|
+
this._isOpening = false;
|
|
681
|
+
this._server = null;
|
|
682
|
+
const conns = Array.from(this._connections.values());
|
|
683
|
+
this._connections.clear();
|
|
684
|
+
for (const conn of conns) {
|
|
685
|
+
this.emit('connection-close', conn);
|
|
303
686
|
}
|
|
304
687
|
this.emit('close');
|
|
305
|
-
});
|
|
306
|
-
resolve();
|
|
307
|
-
});
|
|
308
|
-
this._server.on('error', (error) => {
|
|
309
|
-
if (called) {
|
|
310
|
-
this.emit('error', error);
|
|
311
|
-
}
|
|
312
|
-
else {
|
|
313
|
-
reject(error);
|
|
314
688
|
}
|
|
315
689
|
});
|
|
690
|
+
server.listen(Object.assign(Object.assign({}, options), { port: (_a = options === null || options === void 0 ? void 0 : options.port) !== null && _a !== void 0 ? _a : 502 }), () => {
|
|
691
|
+
listening = true;
|
|
692
|
+
this._isOpening = false;
|
|
693
|
+
this._isOpen = true;
|
|
694
|
+
resolve();
|
|
695
|
+
});
|
|
316
696
|
});
|
|
317
697
|
}
|
|
318
698
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
319
699
|
write(data) {
|
|
320
700
|
return new Promise((resolve, reject) => {
|
|
321
|
-
reject(new
|
|
701
|
+
reject(new ModbusError(ModbusErrorCode.NOT_SUPPORTED, 'Not supported'));
|
|
322
702
|
});
|
|
323
703
|
}
|
|
324
704
|
close() {
|
|
705
|
+
if (!this._server) {
|
|
706
|
+
return Promise.resolve();
|
|
707
|
+
}
|
|
708
|
+
const server = this._server;
|
|
325
709
|
return new Promise((resolve) => {
|
|
326
|
-
|
|
327
|
-
this.
|
|
328
|
-
|
|
329
|
-
socket.removeAllListeners();
|
|
710
|
+
server.once('close', () => resolve());
|
|
711
|
+
for (const [socket] of this._connections) {
|
|
712
|
+
socket.destroy();
|
|
330
713
|
}
|
|
331
|
-
|
|
332
|
-
resolve();
|
|
333
|
-
});
|
|
714
|
+
server.close();
|
|
334
715
|
});
|
|
335
716
|
}
|
|
336
717
|
destroy() {
|
|
337
718
|
this._destroyed = true;
|
|
338
|
-
this.
|
|
339
|
-
|
|
719
|
+
return this.close().then(() => {
|
|
720
|
+
this.removeAllListeners();
|
|
721
|
+
});
|
|
340
722
|
}
|
|
341
723
|
}
|
|
342
724
|
|
|
@@ -347,14 +729,8 @@ class UdpPhysicalLayer extends AbstractPhysicalLayer {
|
|
|
347
729
|
get destroyed() {
|
|
348
730
|
return this._destroyed;
|
|
349
731
|
}
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
* @param options
|
|
353
|
-
* @param remote If omitted, as server.
|
|
354
|
-
* Otherwise as client.
|
|
355
|
-
*/
|
|
356
|
-
constructor(options, remote) {
|
|
357
|
-
var _a, _b;
|
|
732
|
+
constructor(options) {
|
|
733
|
+
var _a, _b, _c, _d;
|
|
358
734
|
super();
|
|
359
735
|
Object.defineProperty(this, "TYPE", {
|
|
360
736
|
enumerable: true,
|
|
@@ -366,7 +742,13 @@ class UdpPhysicalLayer extends AbstractPhysicalLayer {
|
|
|
366
742
|
enumerable: true,
|
|
367
743
|
configurable: true,
|
|
368
744
|
writable: true,
|
|
369
|
-
value:
|
|
745
|
+
value: null
|
|
746
|
+
});
|
|
747
|
+
Object.defineProperty(this, "_connections", {
|
|
748
|
+
enumerable: true,
|
|
749
|
+
configurable: true,
|
|
750
|
+
writable: true,
|
|
751
|
+
value: new Map()
|
|
370
752
|
});
|
|
371
753
|
Object.defineProperty(this, "_isOpen", {
|
|
372
754
|
enumerable: true,
|
|
@@ -374,12 +756,24 @@ class UdpPhysicalLayer extends AbstractPhysicalLayer {
|
|
|
374
756
|
writable: true,
|
|
375
757
|
value: false
|
|
376
758
|
});
|
|
759
|
+
Object.defineProperty(this, "_isOpening", {
|
|
760
|
+
enumerable: true,
|
|
761
|
+
configurable: true,
|
|
762
|
+
writable: true,
|
|
763
|
+
value: false
|
|
764
|
+
});
|
|
377
765
|
Object.defineProperty(this, "_destroyed", {
|
|
378
766
|
enumerable: true,
|
|
379
767
|
configurable: true,
|
|
380
768
|
writable: true,
|
|
381
769
|
value: false
|
|
382
770
|
});
|
|
771
|
+
Object.defineProperty(this, "_socketOptions", {
|
|
772
|
+
enumerable: true,
|
|
773
|
+
configurable: true,
|
|
774
|
+
writable: true,
|
|
775
|
+
value: void 0
|
|
776
|
+
});
|
|
383
777
|
Object.defineProperty(this, "_port", {
|
|
384
778
|
enumerable: true,
|
|
385
779
|
configurable: true,
|
|
@@ -392,186 +786,225 @@ class UdpPhysicalLayer extends AbstractPhysicalLayer {
|
|
|
392
786
|
writable: true,
|
|
393
787
|
value: void 0
|
|
394
788
|
});
|
|
395
|
-
Object.defineProperty(this, "
|
|
789
|
+
Object.defineProperty(this, "_idleTimeout", {
|
|
396
790
|
enumerable: true,
|
|
397
791
|
configurable: true,
|
|
398
792
|
writable: true,
|
|
399
793
|
value: void 0
|
|
400
794
|
});
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
}
|
|
407
|
-
else {
|
|
408
|
-
resolve();
|
|
409
|
-
}
|
|
410
|
-
});
|
|
411
|
-
}));
|
|
795
|
+
Object.defineProperty(this, "isServer", {
|
|
796
|
+
enumerable: true,
|
|
797
|
+
configurable: true,
|
|
798
|
+
writable: true,
|
|
799
|
+
value: void 0
|
|
412
800
|
});
|
|
413
|
-
this.
|
|
414
|
-
this.
|
|
415
|
-
this.
|
|
801
|
+
this._socketOptions = options === null || options === void 0 ? void 0 : options.socket;
|
|
802
|
+
this.isServer = !(options === null || options === void 0 ? void 0 : options.remote);
|
|
803
|
+
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;
|
|
804
|
+
this._address = (_c = options === null || options === void 0 ? void 0 : options.remote) === null || _c === void 0 ? void 0 : _c.address;
|
|
805
|
+
this._idleTimeout = (_d = options === null || options === void 0 ? void 0 : options.idleTimeout) !== null && _d !== void 0 ? _d : 30000;
|
|
416
806
|
}
|
|
417
807
|
open(options) {
|
|
418
|
-
|
|
419
|
-
|
|
808
|
+
var _a, _b;
|
|
809
|
+
if (this._destroyed) {
|
|
810
|
+
return Promise.reject(new ModbusError(ModbusErrorCode.PORT_DESTROYED, 'Port is destroyed'));
|
|
811
|
+
}
|
|
812
|
+
if (this._isOpen || this._isOpening) {
|
|
813
|
+
return Promise.reject(new ModbusError(ModbusErrorCode.PORT_ALREADY_OPEN, 'Port is already open'));
|
|
420
814
|
}
|
|
815
|
+
this._isOpening = true;
|
|
816
|
+
this._connections.clear();
|
|
817
|
+
const socket = node_dgram.createSocket(Object.assign(Object.assign({}, this._socketOptions), { type: (_b = (_a = this._socketOptions) === null || _a === void 0 ? void 0 : _a.type) !== null && _b !== void 0 ? _b : 'udp4' }), (msg, rinfo) => this._handleMessage(socket, msg, rinfo));
|
|
818
|
+
this._socket = socket;
|
|
421
819
|
return new Promise((resolve, reject) => {
|
|
422
820
|
var _a;
|
|
821
|
+
let started = false;
|
|
822
|
+
socket.on('error', (err) => {
|
|
823
|
+
if (started) {
|
|
824
|
+
this.emit('error', err);
|
|
825
|
+
}
|
|
826
|
+
else {
|
|
827
|
+
if (this._socket === socket) {
|
|
828
|
+
this._isOpening = false;
|
|
829
|
+
this._socket = null;
|
|
830
|
+
}
|
|
831
|
+
socket.removeAllListeners();
|
|
832
|
+
reject(err);
|
|
833
|
+
}
|
|
834
|
+
});
|
|
835
|
+
socket.once('close', () => {
|
|
836
|
+
if (this._socket !== socket) {
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
this._isOpen = false;
|
|
840
|
+
this._isOpening = false;
|
|
841
|
+
this._socket = null;
|
|
842
|
+
socket.removeAllListeners();
|
|
843
|
+
const entries = Array.from(this._connections.values());
|
|
844
|
+
this._connections.clear();
|
|
845
|
+
for (const entry of entries) {
|
|
846
|
+
if (entry.idleTimer) {
|
|
847
|
+
clearTimeout(entry.idleTimer);
|
|
848
|
+
}
|
|
849
|
+
this.emit('connection-close', entry.conn);
|
|
850
|
+
}
|
|
851
|
+
this.emit('close');
|
|
852
|
+
});
|
|
423
853
|
if (this.isServer) {
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
854
|
+
socket.bind(Object.assign(Object.assign({}, options), { port: (_a = options === null || options === void 0 ? void 0 : options.port) !== null && _a !== void 0 ? _a : 502 }), () => {
|
|
855
|
+
started = true;
|
|
856
|
+
this._isOpening = false;
|
|
427
857
|
this._isOpen = true;
|
|
428
|
-
this._socket.on('close', () => {
|
|
429
|
-
this._isOpen = false;
|
|
430
|
-
this._socket.removeAllListeners();
|
|
431
|
-
this.emit('close');
|
|
432
|
-
});
|
|
433
858
|
resolve();
|
|
434
859
|
});
|
|
435
|
-
this._socket.on('error', (error) => {
|
|
436
|
-
if (called) {
|
|
437
|
-
this.emit('error', error);
|
|
438
|
-
}
|
|
439
|
-
else {
|
|
440
|
-
reject(error);
|
|
441
|
-
}
|
|
442
|
-
});
|
|
443
860
|
}
|
|
444
861
|
else {
|
|
862
|
+
started = true;
|
|
863
|
+
this._isOpening = false;
|
|
445
864
|
this._isOpen = true;
|
|
446
865
|
resolve();
|
|
447
866
|
}
|
|
448
867
|
});
|
|
449
868
|
}
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
869
|
+
_handleMessage(socket, msg, rinfo) {
|
|
870
|
+
if (this._socket !== socket) {
|
|
871
|
+
return;
|
|
872
|
+
}
|
|
873
|
+
if (!this.isServer) {
|
|
874
|
+
if (rinfo.port !== this._port) {
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
if (this._address !== undefined && rinfo.address !== this._address) {
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
const key = `${rinfo.address}:${rinfo.port}`;
|
|
882
|
+
let entry = this._connections.get(key);
|
|
883
|
+
if (!entry) {
|
|
884
|
+
entry = { conn: { id: genConnectionId('udp') } };
|
|
885
|
+
this._connections.set(key, entry);
|
|
886
|
+
}
|
|
887
|
+
if (this.isServer && this._idleTimeout > 0) {
|
|
888
|
+
if (entry.idleTimer) {
|
|
889
|
+
clearTimeout(entry.idleTimer);
|
|
890
|
+
}
|
|
891
|
+
const conn = entry.conn;
|
|
892
|
+
entry.idleTimer = setTimeout(() => {
|
|
893
|
+
const e = this._connections.get(key);
|
|
894
|
+
if (!e || e.conn !== conn) {
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
897
|
+
this._connections.delete(key);
|
|
898
|
+
this.emit('connection-close', conn);
|
|
899
|
+
}, this._idleTimeout);
|
|
900
|
+
}
|
|
901
|
+
const conn = entry.conn;
|
|
902
|
+
const response = (data) => new Promise((resolve, reject) => {
|
|
903
|
+
socket.send(data, rinfo.port, rinfo.address, (error) => {
|
|
904
|
+
if (error) {
|
|
905
|
+
reject(error);
|
|
906
|
+
}
|
|
907
|
+
else {
|
|
908
|
+
resolve();
|
|
909
|
+
}
|
|
910
|
+
});
|
|
911
|
+
});
|
|
912
|
+
this.emit('data', msg, response, conn);
|
|
913
|
+
}
|
|
914
|
+
write(data) {
|
|
915
|
+
return new Promise((resolve, reject) => {
|
|
916
|
+
if (this._isOpen && this._socket) {
|
|
917
|
+
this._socket.send(data, this._port, this._address, (error) => {
|
|
918
|
+
if (error) {
|
|
919
|
+
reject(error);
|
|
920
|
+
}
|
|
921
|
+
else {
|
|
922
|
+
this.emit('write', data);
|
|
923
|
+
resolve();
|
|
924
|
+
}
|
|
461
925
|
});
|
|
462
926
|
}
|
|
463
927
|
else {
|
|
464
|
-
reject(new
|
|
928
|
+
reject(new ModbusError(ModbusErrorCode.PORT_NOT_OPEN, 'Port is not open'));
|
|
465
929
|
}
|
|
466
930
|
});
|
|
467
931
|
}
|
|
468
932
|
close() {
|
|
933
|
+
if (!this._socket) {
|
|
934
|
+
return Promise.resolve();
|
|
935
|
+
}
|
|
936
|
+
const socket = this._socket;
|
|
469
937
|
return new Promise((resolve) => {
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
938
|
+
socket.once('close', () => resolve());
|
|
939
|
+
try {
|
|
940
|
+
socket.close();
|
|
941
|
+
}
|
|
942
|
+
catch (_a) {
|
|
943
|
+
// Socket may already be closed; the registered 'close' listener still fires asynchronously.
|
|
944
|
+
}
|
|
475
945
|
});
|
|
476
946
|
}
|
|
477
947
|
destroy() {
|
|
478
948
|
this._destroyed = true;
|
|
479
|
-
this.
|
|
480
|
-
|
|
949
|
+
return this.close().then(() => {
|
|
950
|
+
this.removeAllListeners();
|
|
951
|
+
});
|
|
481
952
|
}
|
|
482
953
|
}
|
|
483
954
|
|
|
484
955
|
class AbstractApplicationLayer extends EventEmitter {
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
956
|
+
constructor() {
|
|
957
|
+
super(...arguments);
|
|
958
|
+
Object.defineProperty(this, "_role", {
|
|
959
|
+
enumerable: true,
|
|
960
|
+
configurable: true,
|
|
961
|
+
writable: true,
|
|
962
|
+
value: void 0
|
|
963
|
+
});
|
|
964
|
+
}
|
|
965
|
+
get role() {
|
|
966
|
+
if (!this._role) {
|
|
967
|
+
throw new ModbusError(ModbusErrorCode.INVALID_ROLE, 'Application layer role not set');
|
|
493
968
|
}
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
return false;
|
|
969
|
+
return this._role;
|
|
970
|
+
}
|
|
971
|
+
set role(value) {
|
|
972
|
+
if (this._role && this._role !== value) {
|
|
973
|
+
throw new ModbusError(ModbusErrorCode.INVALID_ROLE, `Application layer role already set to ${this._role}`);
|
|
503
974
|
}
|
|
975
|
+
this._role = value;
|
|
504
976
|
}
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
0x1980, 0xd941, 0x1b00, 0xdbc1, 0xda81, 0x1a40, 0x1e00, 0xdec1, 0xdf81, 0x1f40, 0xdd01, 0x1dc0, 0x1c80, 0xdc41, 0x1400, 0xd4c1, 0xd581,
|
|
512
|
-
0x1540, 0xd701, 0x17c0, 0x1680, 0xd641, 0xd201, 0x12c0, 0x1380, 0xd341, 0x1100, 0xd1c1, 0xd081, 0x1040, 0xf001, 0x30c0, 0x3180, 0xf141,
|
|
513
|
-
0x3300, 0xf3c1, 0xf281, 0x3240, 0x3600, 0xf6c1, 0xf781, 0x3740, 0xf501, 0x35c0, 0x3480, 0xf441, 0x3c00, 0xfcc1, 0xfd81, 0x3d40, 0xff01,
|
|
514
|
-
0x3fc0, 0x3e80, 0xfe41, 0xfa01, 0x3ac0, 0x3b80, 0xfb41, 0x3900, 0xf9c1, 0xf881, 0x3840, 0x2800, 0xe8c1, 0xe981, 0x2940, 0xeb01, 0x2bc0,
|
|
515
|
-
0x2a80, 0xea41, 0xee01, 0x2ec0, 0x2f80, 0xef41, 0x2d00, 0xedc1, 0xec81, 0x2c40, 0xe401, 0x24c0, 0x2580, 0xe541, 0x2700, 0xe7c1, 0xe681,
|
|
516
|
-
0x2640, 0x2200, 0xe2c1, 0xe381, 0x2340, 0xe101, 0x21c0, 0x2080, 0xe041, 0xa001, 0x60c0, 0x6180, 0xa141, 0x6300, 0xa3c1, 0xa281, 0x6240,
|
|
517
|
-
0x6600, 0xa6c1, 0xa781, 0x6740, 0xa501, 0x65c0, 0x6480, 0xa441, 0x6c00, 0xacc1, 0xad81, 0x6d40, 0xaf01, 0x6fc0, 0x6e80, 0xae41, 0xaa01,
|
|
518
|
-
0x6ac0, 0x6b80, 0xab41, 0x6900, 0xa9c1, 0xa881, 0x6840, 0x7800, 0xb8c1, 0xb981, 0x7940, 0xbb01, 0x7bc0, 0x7a80, 0xba41, 0xbe01, 0x7ec0,
|
|
519
|
-
0x7f80, 0xbf41, 0x7d00, 0xbdc1, 0xbc81, 0x7c40, 0xb401, 0x74c0, 0x7580, 0xb541, 0x7700, 0xb7c1, 0xb681, 0x7640, 0x7200, 0xb2c1, 0xb381,
|
|
520
|
-
0x7340, 0xb101, 0x71c0, 0x7080, 0xb041, 0x5000, 0x90c1, 0x9181, 0x5140, 0x9301, 0x53c0, 0x5280, 0x9241, 0x9601, 0x56c0, 0x5780, 0x9741,
|
|
521
|
-
0x5500, 0x95c1, 0x9481, 0x5440, 0x9c01, 0x5cc0, 0x5d80, 0x9d41, 0x5f00, 0x9fc1, 0x9e81, 0x5e40, 0x5a00, 0x9ac1, 0x9b81, 0x5b40, 0x9901,
|
|
522
|
-
0x59c0, 0x5880, 0x9841, 0x8801, 0x48c0, 0x4980, 0x8941, 0x4b00, 0x8bc1, 0x8a81, 0x4a40, 0x4e00, 0x8ec1, 0x8f81, 0x4f40, 0x8d01, 0x4dc0,
|
|
523
|
-
0x4c80, 0x8c41, 0x4400, 0x84c1, 0x8581, 0x4540, 0x8701, 0x47c0, 0x4680, 0x8641, 0x8201, 0x42c0, 0x4380, 0x8341, 0x4100, 0x81c1, 0x8081,
|
|
524
|
-
0x4040,
|
|
525
|
-
];
|
|
526
|
-
function crc(data) {
|
|
527
|
-
let crc = 0xffff;
|
|
528
|
-
for (let index = 0; index < data.length; index++) {
|
|
529
|
-
crc = (TABLE[(crc ^ data[index]) & 0xff] ^ (crc >> 8)) & 0xffff;
|
|
977
|
+
flush() {
|
|
978
|
+
// no-op — override in subclasses
|
|
979
|
+
}
|
|
980
|
+
addCustomFunctionCode(cfc) {
|
|
981
|
+
}
|
|
982
|
+
removeCustomFunctionCode(fc) {
|
|
530
983
|
}
|
|
531
|
-
return crc;
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
/**
|
|
535
|
-
* Get time interval between message frames witch well-known as 3.5T.
|
|
536
|
-
* @param baudRate Serial port baud rate.
|
|
537
|
-
* @param {number} [approximation=48] Approximate number of bits corresponding to 3.5T.
|
|
538
|
-
* @returns `ms`.
|
|
539
|
-
*/
|
|
540
|
-
function getThreePointFiveT(baudRate, approximation = 48) {
|
|
541
|
-
return (approximation * 1000) / baudRate;
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
function lrc(data) {
|
|
545
|
-
return (~data.reduce((sum, n) => sum + n, 0) + 1) & 0xff;
|
|
546
984
|
}
|
|
547
985
|
|
|
548
986
|
const MAX_FRAME_LENGTH = 256;
|
|
987
|
+
const MIN_FRAME_LENGTH = 4;
|
|
549
988
|
class RtuApplicationLayer extends AbstractApplicationLayer {
|
|
550
|
-
constructor(physicalLayer,
|
|
551
|
-
/**
|
|
552
|
-
* The time interval between two frames, support two formats:
|
|
553
|
-
* - bit: `48bit` as default
|
|
554
|
-
* - millisecond: `20ms`
|
|
555
|
-
*/
|
|
556
|
-
intervalBetweenFrames) {
|
|
989
|
+
constructor(physicalLayer, options = {}) {
|
|
557
990
|
super();
|
|
558
|
-
Object.defineProperty(this, "
|
|
991
|
+
Object.defineProperty(this, "PROTOCOL", {
|
|
559
992
|
enumerable: true,
|
|
560
993
|
configurable: true,
|
|
561
994
|
writable: true,
|
|
562
|
-
value:
|
|
995
|
+
value: 'RTU'
|
|
563
996
|
});
|
|
564
|
-
Object.defineProperty(this, "
|
|
997
|
+
Object.defineProperty(this, "_states", {
|
|
565
998
|
enumerable: true,
|
|
566
999
|
configurable: true,
|
|
567
1000
|
writable: true,
|
|
568
|
-
value:
|
|
1001
|
+
value: new Map()
|
|
569
1002
|
});
|
|
570
|
-
Object.defineProperty(this, "
|
|
1003
|
+
Object.defineProperty(this, "_customFunctionCodes", {
|
|
571
1004
|
enumerable: true,
|
|
572
1005
|
configurable: true,
|
|
573
1006
|
writable: true,
|
|
574
|
-
value:
|
|
1007
|
+
value: new Map()
|
|
575
1008
|
});
|
|
576
1009
|
Object.defineProperty(this, "_removeAllListeners", {
|
|
577
1010
|
enumerable: true,
|
|
@@ -579,129 +1012,276 @@ class RtuApplicationLayer extends AbstractApplicationLayer {
|
|
|
579
1012
|
writable: true,
|
|
580
1013
|
value: []
|
|
581
1014
|
});
|
|
1015
|
+
Object.defineProperty(this, "_threePointFiveT", {
|
|
1016
|
+
enumerable: true,
|
|
1017
|
+
configurable: true,
|
|
1018
|
+
writable: true,
|
|
1019
|
+
value: void 0
|
|
1020
|
+
});
|
|
1021
|
+
Object.defineProperty(this, "_onePointFiveT", {
|
|
1022
|
+
enumerable: true,
|
|
1023
|
+
configurable: true,
|
|
1024
|
+
writable: true,
|
|
1025
|
+
value: void 0
|
|
1026
|
+
});
|
|
1027
|
+
const { intervalBetweenFrames, interCharTimeout } = options;
|
|
582
1028
|
let threePointFiveT = 0;
|
|
1029
|
+
let onePointFiveT = 0;
|
|
583
1030
|
if (physicalLayer.TYPE === 'SERIAL') {
|
|
584
|
-
|
|
585
|
-
|
|
1031
|
+
const baudRate = physicalLayer.baudRate;
|
|
1032
|
+
if (intervalBetweenFrames && intervalBetweenFrames.unit === 'ms') {
|
|
1033
|
+
threePointFiveT = intervalBetweenFrames.value;
|
|
586
1034
|
}
|
|
587
1035
|
else {
|
|
588
|
-
threePointFiveT =
|
|
589
|
-
? 1.
|
|
590
|
-
: getThreePointFiveT(physicalLayer.baudRate, intervalBetweenFrames ? Number(intervalBetweenFrames.slice(0, -3)) : 48));
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
const handleData = (data, response) => {
|
|
594
|
-
this._bufferRx = Buffer.concat([this._bufferRx, data]);
|
|
595
|
-
clearTimeout(this._timerThreePointFive);
|
|
596
|
-
const handleData = () => {
|
|
597
|
-
this.framing(this._bufferRx, (error, frame) => {
|
|
598
|
-
if (this._waitingResponse) {
|
|
599
|
-
if (error && error.message === 'Insufficient data length') {
|
|
600
|
-
return;
|
|
601
|
-
}
|
|
602
|
-
this._waitingResponse.callback(error, frame);
|
|
603
|
-
this._bufferRx = Buffer.alloc(0);
|
|
604
|
-
}
|
|
605
|
-
else {
|
|
606
|
-
if (!error) {
|
|
607
|
-
this.emit('framing', frame, response);
|
|
608
|
-
}
|
|
609
|
-
this._bufferRx = Buffer.alloc(0);
|
|
610
|
-
}
|
|
611
|
-
});
|
|
612
|
-
};
|
|
613
|
-
if (this._bufferRx.length >= MAX_FRAME_LENGTH) {
|
|
614
|
-
handleData();
|
|
1036
|
+
threePointFiveT =
|
|
1037
|
+
baudRate > 19200 ? 1.75 : Math.ceil(bitsToMs(baudRate, intervalBetweenFrames ? intervalBetweenFrames.value : 38.5));
|
|
615
1038
|
}
|
|
616
|
-
|
|
617
|
-
if (
|
|
618
|
-
|
|
1039
|
+
if (interCharTimeout) {
|
|
1040
|
+
if (interCharTimeout.unit === 'ms') {
|
|
1041
|
+
onePointFiveT = interCharTimeout.value;
|
|
619
1042
|
}
|
|
620
1043
|
else {
|
|
621
|
-
|
|
1044
|
+
onePointFiveT = baudRate > 19200 ? 0.75 : Math.ceil(bitsToMs(baudRate, interCharTimeout.value));
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
this._threePointFiveT = threePointFiveT;
|
|
1049
|
+
this._onePointFiveT = onePointFiveT;
|
|
1050
|
+
const handleData = (data, response, connection) => {
|
|
1051
|
+
let state = this.getState(connection.id);
|
|
1052
|
+
if (state.t15Expired && state.end > state.start) {
|
|
1053
|
+
this.emit('framing-error', new ModbusError(ModbusErrorCode.T1_5_EXCEEDED, 'Inter-character timeout (t1.5) exceeded'));
|
|
1054
|
+
state.start = 0;
|
|
1055
|
+
state.end = 0;
|
|
1056
|
+
}
|
|
1057
|
+
state.t15Expired = false;
|
|
1058
|
+
let dataOffset = 0;
|
|
1059
|
+
while (dataOffset < data.length) {
|
|
1060
|
+
const space = state.pool.length - state.end;
|
|
1061
|
+
if (space === 0) {
|
|
1062
|
+
this.clearStateTimers(state);
|
|
1063
|
+
this.flushBuffer(connection.id, response, connection, this._threePointFiveT > 0);
|
|
1064
|
+
state = this.getState(connection.id);
|
|
1065
|
+
if (state.pool.length - state.end === 0) {
|
|
1066
|
+
this.emit('framing-error', new ModbusError(ModbusErrorCode.INVALID_DATA, 'Frame buffer exhausted before complete frame received'));
|
|
1067
|
+
state.start = 0;
|
|
1068
|
+
state.end = 0;
|
|
1069
|
+
state.t15Expired = false;
|
|
1070
|
+
}
|
|
1071
|
+
continue;
|
|
1072
|
+
}
|
|
1073
|
+
const toCopy = Math.min(space, data.length - dataOffset);
|
|
1074
|
+
data.copy(state.pool, state.end, dataOffset, dataOffset + toCopy);
|
|
1075
|
+
state.end += toCopy;
|
|
1076
|
+
dataOffset += toCopy;
|
|
1077
|
+
}
|
|
1078
|
+
this.clearStateTimers(state);
|
|
1079
|
+
const commitFrame = () => {
|
|
1080
|
+
this.clearStateTimers(state);
|
|
1081
|
+
this.flushBuffer(connection.id, response, connection, this._threePointFiveT > 0);
|
|
1082
|
+
};
|
|
1083
|
+
if (state.end - state.start >= MAX_FRAME_LENGTH) {
|
|
1084
|
+
commitFrame();
|
|
1085
|
+
}
|
|
1086
|
+
else if (this._threePointFiveT) {
|
|
1087
|
+
if (this._onePointFiveT > 0) {
|
|
1088
|
+
state.interCharTimer = setTimeout(() => {
|
|
1089
|
+
state.interCharTimer = undefined;
|
|
1090
|
+
state.t15Expired = true;
|
|
1091
|
+
}, this._onePointFiveT);
|
|
622
1092
|
}
|
|
1093
|
+
state.timer = setTimeout(commitFrame, this._threePointFiveT);
|
|
1094
|
+
}
|
|
1095
|
+
else {
|
|
1096
|
+
commitFrame();
|
|
623
1097
|
}
|
|
624
1098
|
};
|
|
625
1099
|
physicalLayer.on('data', handleData);
|
|
626
|
-
this._removeAllListeners.push(() =>
|
|
627
|
-
|
|
628
|
-
|
|
1100
|
+
this._removeAllListeners.push(() => physicalLayer.removeListener('data', handleData));
|
|
1101
|
+
const handleConnectionClose = (connection) => {
|
|
1102
|
+
this.clearState(connection.id);
|
|
1103
|
+
};
|
|
1104
|
+
physicalLayer.on('connection-close', handleConnectionClose);
|
|
1105
|
+
this._removeAllListeners.push(() => physicalLayer.removeListener('connection-close', handleConnectionClose));
|
|
629
1106
|
const handleClose = () => {
|
|
630
|
-
|
|
631
|
-
|
|
1107
|
+
for (const key of [...this._states.keys()]) {
|
|
1108
|
+
this.clearState(key);
|
|
1109
|
+
}
|
|
632
1110
|
};
|
|
633
1111
|
physicalLayer.on('close', handleClose);
|
|
634
|
-
this._removeAllListeners.push(() =>
|
|
635
|
-
physicalLayer.removeListener('close', handleClose);
|
|
636
|
-
});
|
|
1112
|
+
this._removeAllListeners.push(() => physicalLayer.removeListener('close', handleClose));
|
|
637
1113
|
}
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
1114
|
+
getState(key) {
|
|
1115
|
+
let state = this._states.get(key);
|
|
1116
|
+
if (!state) {
|
|
1117
|
+
state = { pool: Buffer.alloc(MAX_FRAME_LENGTH * 2), start: 0, end: 0 };
|
|
1118
|
+
this._states.set(key, state);
|
|
1119
|
+
}
|
|
1120
|
+
return state;
|
|
1121
|
+
}
|
|
1122
|
+
clearStateTimers(state) {
|
|
1123
|
+
if (state.timer) {
|
|
1124
|
+
clearTimeout(state.timer);
|
|
1125
|
+
state.timer = undefined;
|
|
1126
|
+
}
|
|
1127
|
+
if (state.interCharTimer) {
|
|
1128
|
+
clearTimeout(state.interCharTimer);
|
|
1129
|
+
state.interCharTimer = undefined;
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
clearState(key) {
|
|
1133
|
+
const state = this._states.get(key);
|
|
1134
|
+
if (state) {
|
|
1135
|
+
this.clearStateTimers(state);
|
|
1136
|
+
}
|
|
1137
|
+
this._states.delete(key);
|
|
1138
|
+
}
|
|
1139
|
+
flush() {
|
|
1140
|
+
for (const state of this._states.values()) {
|
|
1141
|
+
this.clearStateTimers(state);
|
|
1142
|
+
state.start = 0;
|
|
1143
|
+
state.end = 0;
|
|
1144
|
+
}
|
|
1145
|
+
this._states.clear();
|
|
1146
|
+
}
|
|
1147
|
+
addCustomFunctionCode(cfc) {
|
|
1148
|
+
if (!isUint8(cfc.fc)) {
|
|
1149
|
+
throw new ModbusError(ModbusErrorCode.RANGE, `fc must be an integer in 0..255, got ${cfc.fc}`);
|
|
1150
|
+
}
|
|
1151
|
+
this._customFunctionCodes.set(cfc.fc, cfc);
|
|
1152
|
+
}
|
|
1153
|
+
removeCustomFunctionCode(fc) {
|
|
1154
|
+
this._customFunctionCodes.delete(fc);
|
|
1155
|
+
}
|
|
1156
|
+
flushBuffer(key, response, connection, strict) {
|
|
1157
|
+
const state = this._states.get(key);
|
|
1158
|
+
if (!state) {
|
|
1159
|
+
return;
|
|
1160
|
+
}
|
|
1161
|
+
while (state.end - state.start > 0) {
|
|
1162
|
+
const buffer = state.pool.subarray(state.start, state.end);
|
|
1163
|
+
const result = this.tryExtract(buffer);
|
|
1164
|
+
if (result.kind === 'frame') {
|
|
1165
|
+
state.start += result.frame.length;
|
|
1166
|
+
this.deliverFrame(result.frame, response, connection);
|
|
1167
|
+
}
|
|
1168
|
+
else if (result.kind === 'skip') {
|
|
1169
|
+
if (strict) {
|
|
1170
|
+
this.emit('framing-error', new ModbusError(ModbusErrorCode.CRC_MISMATCH, 'CRC mismatch'));
|
|
1171
|
+
state.start = 0;
|
|
1172
|
+
state.end = 0;
|
|
1173
|
+
state.t15Expired = false;
|
|
1174
|
+
return;
|
|
667
1175
|
}
|
|
1176
|
+
state.start += 1;
|
|
668
1177
|
}
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
1178
|
+
else if (result.kind === 'insufficient') {
|
|
1179
|
+
if (state.end - state.start >= MAX_FRAME_LENGTH) {
|
|
1180
|
+
state.start += 1;
|
|
1181
|
+
continue;
|
|
1182
|
+
}
|
|
1183
|
+
if (strict) {
|
|
1184
|
+
this.emit('framing-error', new ModbusError(state.t15Expired ? ModbusErrorCode.T1_5_EXCEEDED : ModbusErrorCode.INCOMPLETE_FRAME, state.t15Expired ? 'Inter-character timeout (t1.5) exceeded' : 'Incomplete frame at t3.5'));
|
|
1185
|
+
state.start = 0;
|
|
1186
|
+
state.end = 0;
|
|
1187
|
+
state.t15Expired = false;
|
|
1188
|
+
return;
|
|
1189
|
+
}
|
|
1190
|
+
if (state.t15Expired) {
|
|
1191
|
+
this.emit('framing-error', new ModbusError(ModbusErrorCode.T1_5_EXCEEDED, 'Inter-character timeout (t1.5) exceeded'));
|
|
1192
|
+
state.start = 0;
|
|
1193
|
+
state.end = 0;
|
|
1194
|
+
state.t15Expired = false;
|
|
1195
|
+
}
|
|
1196
|
+
break;
|
|
672
1197
|
}
|
|
673
1198
|
else {
|
|
674
|
-
|
|
1199
|
+
this.emit('framing-error', result.error);
|
|
1200
|
+
state.start = 0;
|
|
1201
|
+
state.end = 0;
|
|
1202
|
+
return;
|
|
675
1203
|
}
|
|
676
1204
|
}
|
|
677
|
-
|
|
678
|
-
|
|
1205
|
+
if (state.start > 0) {
|
|
1206
|
+
if (state.start < state.end) {
|
|
1207
|
+
state.pool.copy(state.pool, 0, state.start, state.end);
|
|
1208
|
+
}
|
|
1209
|
+
state.end -= state.start;
|
|
1210
|
+
state.start = 0;
|
|
1211
|
+
}
|
|
1212
|
+
if (state.end === 0 && !state.timer) {
|
|
1213
|
+
this._states.delete(key);
|
|
679
1214
|
}
|
|
680
1215
|
}
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
1216
|
+
tryExtract(buffer) {
|
|
1217
|
+
if (buffer.length < MIN_FRAME_LENGTH) {
|
|
1218
|
+
return { kind: 'insufficient' };
|
|
1219
|
+
}
|
|
1220
|
+
const isResponse = this.role === 'MASTER';
|
|
1221
|
+
const fc = buffer[1];
|
|
1222
|
+
const cfc = this._customFunctionCodes.get(fc);
|
|
1223
|
+
if (cfc) {
|
|
1224
|
+
const predictor = isResponse ? cfc.predictResponseLength : cfc.predictRequestLength;
|
|
1225
|
+
const expected = predictor(buffer);
|
|
1226
|
+
if (expected === null) {
|
|
1227
|
+
return { kind: 'insufficient' };
|
|
1228
|
+
}
|
|
1229
|
+
return this.checkExpected(buffer, expected);
|
|
1230
|
+
}
|
|
1231
|
+
const result = predictRtuFrameLength(buffer, isResponse);
|
|
1232
|
+
if (result.kind === 'length') {
|
|
1233
|
+
return this.checkExpected(buffer, result.length);
|
|
1234
|
+
}
|
|
1235
|
+
if (result.kind === 'need-more') {
|
|
1236
|
+
return { kind: 'insufficient' };
|
|
1237
|
+
}
|
|
1238
|
+
return {
|
|
1239
|
+
kind: 'error',
|
|
1240
|
+
error: new ModbusError(ModbusErrorCode.UNKNOWN_FC, `Unknown function code 0x${fc.toString(16).padStart(2, '0')} — register a CustomFunctionCode to frame this FC`),
|
|
1241
|
+
};
|
|
685
1242
|
}
|
|
686
|
-
|
|
687
|
-
|
|
1243
|
+
checkExpected(buffer, expected) {
|
|
1244
|
+
if (expected > MAX_FRAME_LENGTH || expected < MIN_FRAME_LENGTH) {
|
|
1245
|
+
return { kind: 'error', error: new ModbusError(ModbusErrorCode.INVALID_DATA, 'Invalid data') };
|
|
1246
|
+
}
|
|
1247
|
+
if (buffer.length < expected) {
|
|
1248
|
+
return { kind: 'insufficient' };
|
|
1249
|
+
}
|
|
1250
|
+
if (this.crcMatches(buffer, expected)) {
|
|
1251
|
+
return { kind: 'frame', frame: buffer.subarray(0, expected), rest: buffer.subarray(expected) };
|
|
1252
|
+
}
|
|
1253
|
+
return { kind: 'skip', rest: buffer.subarray(1) };
|
|
1254
|
+
}
|
|
1255
|
+
crcMatches(buffer, length) {
|
|
1256
|
+
return buffer.readUInt16LE(length - 2) === crc(buffer.subarray(0, length - 2));
|
|
1257
|
+
}
|
|
1258
|
+
deliverFrame(buffer, response, connection) {
|
|
1259
|
+
const frameBuf = Buffer.from(buffer);
|
|
1260
|
+
const frame = {
|
|
1261
|
+
unit: frameBuf[0],
|
|
1262
|
+
fc: frameBuf[1],
|
|
1263
|
+
data: frameBuf.subarray(2, frameBuf.length - 2),
|
|
1264
|
+
buffer: frameBuf,
|
|
1265
|
+
};
|
|
1266
|
+
this.emit('framing', frame, response, connection);
|
|
688
1267
|
}
|
|
689
1268
|
encode(data) {
|
|
690
1269
|
const buffer = Buffer.alloc(data.data.length + 4);
|
|
691
1270
|
buffer.writeUInt8(data.unit, 0);
|
|
692
1271
|
buffer.writeUInt8(data.fc, 1);
|
|
693
|
-
data.data
|
|
694
|
-
buffer.writeUInt8(num, 2 + index);
|
|
695
|
-
});
|
|
1272
|
+
buffer.set(data.data, 2);
|
|
696
1273
|
buffer.writeUInt16LE(crc(buffer.subarray(0, -2)), buffer.length - 2);
|
|
697
1274
|
return buffer;
|
|
698
1275
|
}
|
|
699
1276
|
destroy() {
|
|
700
1277
|
this.removeAllListeners();
|
|
701
|
-
for (const
|
|
702
|
-
|
|
1278
|
+
for (const removeListener of this._removeAllListeners) {
|
|
1279
|
+
removeListener();
|
|
1280
|
+
}
|
|
1281
|
+
for (const state of this._states.values()) {
|
|
1282
|
+
this.clearStateTimers(state);
|
|
703
1283
|
}
|
|
704
|
-
|
|
1284
|
+
this._states.clear();
|
|
705
1285
|
}
|
|
706
1286
|
}
|
|
707
1287
|
|
|
@@ -710,26 +1290,43 @@ const CHAR_CODE = {
|
|
|
710
1290
|
CR: '\r'.charCodeAt(0),
|
|
711
1291
|
LF: '\n'.charCodeAt(0),
|
|
712
1292
|
};
|
|
1293
|
+
// Modbus ASCII frame encodes at most 256 PDU bytes as 512 hex chars between
|
|
1294
|
+
// `:` and `\r`. Cap per-connection buffering so a peer that never sends `\r`
|
|
1295
|
+
// cannot grow `state.frame` without bound.
|
|
1296
|
+
const MAX_ASCII_PAYLOAD = 512;
|
|
1297
|
+
const HEX_DECODE = new Uint8Array(256);
|
|
1298
|
+
HEX_DECODE.fill(0xff);
|
|
1299
|
+
for (let i = 0x30; i <= 0x39; i++) {
|
|
1300
|
+
HEX_DECODE[i] = i - 0x30;
|
|
1301
|
+
}
|
|
1302
|
+
for (let i = 0x41; i <= 0x46; i++) {
|
|
1303
|
+
HEX_DECODE[i] = i - 0x41 + 10;
|
|
1304
|
+
}
|
|
1305
|
+
for (let i = 0x61; i <= 0x66; i++) {
|
|
1306
|
+
HEX_DECODE[i] = i - 0x61 + 10;
|
|
1307
|
+
}
|
|
1308
|
+
const HEX_ENCODE = new Uint8Array('0123456789ABCDEF'.split('').map((c) => c.charCodeAt(0)));
|
|
713
1309
|
class AsciiApplicationLayer extends AbstractApplicationLayer {
|
|
714
|
-
constructor(physicalLayer) {
|
|
1310
|
+
constructor(physicalLayer, options = {}) {
|
|
1311
|
+
var _a;
|
|
715
1312
|
super();
|
|
716
|
-
Object.defineProperty(this, "
|
|
1313
|
+
Object.defineProperty(this, "PROTOCOL", {
|
|
717
1314
|
enumerable: true,
|
|
718
1315
|
configurable: true,
|
|
719
1316
|
writable: true,
|
|
720
|
-
value:
|
|
1317
|
+
value: 'ASCII'
|
|
721
1318
|
});
|
|
722
|
-
Object.defineProperty(this, "
|
|
1319
|
+
Object.defineProperty(this, "lenientHex", {
|
|
723
1320
|
enumerable: true,
|
|
724
1321
|
configurable: true,
|
|
725
1322
|
writable: true,
|
|
726
|
-
value:
|
|
1323
|
+
value: void 0
|
|
727
1324
|
});
|
|
728
|
-
Object.defineProperty(this, "
|
|
1325
|
+
Object.defineProperty(this, "_states", {
|
|
729
1326
|
enumerable: true,
|
|
730
1327
|
configurable: true,
|
|
731
1328
|
writable: true,
|
|
732
|
-
value:
|
|
1329
|
+
value: new Map()
|
|
733
1330
|
});
|
|
734
1331
|
Object.defineProperty(this, "_removeAllListeners", {
|
|
735
1332
|
enumerable: true,
|
|
@@ -737,44 +1334,62 @@ class AsciiApplicationLayer extends AbstractApplicationLayer {
|
|
|
737
1334
|
writable: true,
|
|
738
1335
|
value: []
|
|
739
1336
|
});
|
|
740
|
-
|
|
1337
|
+
this.lenientHex = (_a = options.lenientHex) !== null && _a !== void 0 ? _a : false;
|
|
1338
|
+
const lenientHex = this.lenientHex;
|
|
1339
|
+
const isHexChar = (value) => {
|
|
1340
|
+
if (value >= 0x30 && value <= 0x39) {
|
|
1341
|
+
return true;
|
|
1342
|
+
}
|
|
1343
|
+
if (value >= 0x41 && value <= 0x46) {
|
|
1344
|
+
return true;
|
|
1345
|
+
}
|
|
1346
|
+
if (lenientHex && value >= 0x61 && value <= 0x66) {
|
|
1347
|
+
return true;
|
|
1348
|
+
}
|
|
1349
|
+
return false;
|
|
1350
|
+
};
|
|
1351
|
+
const handleData = (data, response, connection) => {
|
|
1352
|
+
const state = this.getState(connection.id);
|
|
741
1353
|
data.forEach((value) => {
|
|
742
|
-
switch (
|
|
1354
|
+
switch (state.status) {
|
|
743
1355
|
case 'idle': {
|
|
744
1356
|
if (value === CHAR_CODE.COLON) {
|
|
745
|
-
|
|
746
|
-
|
|
1357
|
+
state.status = 'reception';
|
|
1358
|
+
state.frame = [];
|
|
747
1359
|
}
|
|
748
1360
|
break;
|
|
749
1361
|
}
|
|
750
1362
|
case 'reception': {
|
|
751
1363
|
if (value === CHAR_CODE.COLON) {
|
|
752
|
-
|
|
1364
|
+
state.frame = [];
|
|
753
1365
|
}
|
|
754
1366
|
else if (value === CHAR_CODE.CR) {
|
|
755
|
-
|
|
1367
|
+
state.status = 'waiting end';
|
|
1368
|
+
}
|
|
1369
|
+
else if (state.frame.length >= MAX_ASCII_PAYLOAD) {
|
|
1370
|
+
state.status = 'idle';
|
|
1371
|
+
state.frame = [];
|
|
1372
|
+
this.emit('framing-error', new ModbusError(ModbusErrorCode.INVALID_DATA, 'Invalid data'));
|
|
1373
|
+
}
|
|
1374
|
+
else if (!isHexChar(value)) {
|
|
1375
|
+
state.status = 'idle';
|
|
1376
|
+
state.frame = [];
|
|
1377
|
+
this.emit('framing-error', new ModbusError(ModbusErrorCode.INVALID_HEX, 'Invalid hex character'));
|
|
756
1378
|
}
|
|
757
1379
|
else {
|
|
758
|
-
|
|
1380
|
+
state.frame.push(value);
|
|
759
1381
|
}
|
|
760
1382
|
break;
|
|
761
1383
|
}
|
|
762
1384
|
case 'waiting end': {
|
|
763
1385
|
if (value === CHAR_CODE.COLON) {
|
|
764
|
-
|
|
765
|
-
|
|
1386
|
+
state.status = 'reception';
|
|
1387
|
+
state.frame = [];
|
|
766
1388
|
}
|
|
767
1389
|
else {
|
|
768
|
-
|
|
1390
|
+
state.status = 'idle';
|
|
769
1391
|
if (value === CHAR_CODE.LF) {
|
|
770
|
-
this.framing(Buffer.from(
|
|
771
|
-
if (this._waitingResponse) {
|
|
772
|
-
this._waitingResponse.callback(error, frame);
|
|
773
|
-
}
|
|
774
|
-
else if (!error) {
|
|
775
|
-
this.emit('framing', frame, response);
|
|
776
|
-
}
|
|
777
|
-
});
|
|
1392
|
+
this.framing(Buffer.from(state.frame), response, connection);
|
|
778
1393
|
}
|
|
779
1394
|
}
|
|
780
1395
|
break;
|
|
@@ -783,115 +1398,97 @@ class AsciiApplicationLayer extends AbstractApplicationLayer {
|
|
|
783
1398
|
});
|
|
784
1399
|
};
|
|
785
1400
|
physicalLayer.on('data', handleData);
|
|
786
|
-
this._removeAllListeners.push(() =>
|
|
787
|
-
|
|
788
|
-
|
|
1401
|
+
this._removeAllListeners.push(() => physicalLayer.removeListener('data', handleData));
|
|
1402
|
+
const handleConnectionClose = (connection) => {
|
|
1403
|
+
this._states.delete(connection.id);
|
|
1404
|
+
};
|
|
1405
|
+
physicalLayer.on('connection-close', handleConnectionClose);
|
|
1406
|
+
this._removeAllListeners.push(() => physicalLayer.removeListener('connection-close', handleConnectionClose));
|
|
789
1407
|
const handleClose = () => {
|
|
790
|
-
this.
|
|
791
|
-
this._frame = [];
|
|
1408
|
+
this._states.clear();
|
|
792
1409
|
};
|
|
793
1410
|
physicalLayer.on('close', handleClose);
|
|
794
|
-
this._removeAllListeners.push(() =>
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
const ascii = [];
|
|
802
|
-
let num = '';
|
|
803
|
-
for (const value of _buffer) {
|
|
804
|
-
num += String.fromCharCode(value);
|
|
805
|
-
if (num.length === 2) {
|
|
806
|
-
ascii.push(Number('0x' + num));
|
|
807
|
-
num = '';
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
const buffer = Buffer.from(ascii);
|
|
811
|
-
const frame = {
|
|
812
|
-
unit: buffer[0],
|
|
813
|
-
fc: buffer[1],
|
|
814
|
-
data: Array.from(buffer.subarray(2, buffer.length - 1)),
|
|
815
|
-
buffer: _buffer,
|
|
816
|
-
};
|
|
817
|
-
if (this._waitingResponse) {
|
|
818
|
-
for (const check of this._waitingResponse.preCheck) {
|
|
819
|
-
const res = check(frame);
|
|
820
|
-
if (typeof res === 'undefined') {
|
|
821
|
-
callback(new Error('Insufficient data length'));
|
|
822
|
-
return;
|
|
823
|
-
}
|
|
824
|
-
if (typeof res === 'number') {
|
|
825
|
-
if (frame.data.length < res) {
|
|
826
|
-
callback(new Error('Insufficient data length'));
|
|
827
|
-
return;
|
|
828
|
-
}
|
|
829
|
-
if (frame.data.length !== res) {
|
|
830
|
-
callback(new Error('Invalid response'));
|
|
831
|
-
return;
|
|
832
|
-
}
|
|
833
|
-
}
|
|
834
|
-
if (!res) {
|
|
835
|
-
callback(new Error('Invalid response'));
|
|
836
|
-
return;
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
const lrcPassed = buffer[buffer.length - 1] === lrc(buffer.subarray(0, buffer.length - 1));
|
|
841
|
-
if (lrcPassed) {
|
|
842
|
-
callback(null, frame);
|
|
843
|
-
}
|
|
844
|
-
else {
|
|
845
|
-
callback(new Error('LRC check failed'));
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
else {
|
|
849
|
-
callback(new Error('Invalid data'));
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
else {
|
|
853
|
-
callback(new Error('Insufficient data length'));
|
|
1411
|
+
this._removeAllListeners.push(() => physicalLayer.removeListener('close', handleClose));
|
|
1412
|
+
}
|
|
1413
|
+
getState(key) {
|
|
1414
|
+
let state = this._states.get(key);
|
|
1415
|
+
if (!state) {
|
|
1416
|
+
state = { status: 'idle', frame: [] };
|
|
1417
|
+
this._states.set(key, state);
|
|
854
1418
|
}
|
|
1419
|
+
return state;
|
|
855
1420
|
}
|
|
856
|
-
|
|
857
|
-
this.
|
|
858
|
-
this._status = 'idle';
|
|
859
|
-
this._frame = [];
|
|
1421
|
+
flush() {
|
|
1422
|
+
this._states.clear();
|
|
860
1423
|
}
|
|
861
|
-
|
|
862
|
-
|
|
1424
|
+
framing(_buffer, response, connection) {
|
|
1425
|
+
if (_buffer.length < 6) {
|
|
1426
|
+
this.emit('framing-error', new ModbusError(ModbusErrorCode.INSUFFICIENT_DATA, 'Insufficient data length'));
|
|
1427
|
+
return;
|
|
1428
|
+
}
|
|
1429
|
+
if (_buffer.length % 2 !== 0) {
|
|
1430
|
+
this.emit('framing-error', new ModbusError(ModbusErrorCode.INVALID_DATA, 'Invalid data'));
|
|
1431
|
+
return;
|
|
1432
|
+
}
|
|
1433
|
+
const buffer = Buffer.allocUnsafe(_buffer.length / 2);
|
|
1434
|
+
for (let i = 0; i < _buffer.length; i += 2) {
|
|
1435
|
+
const hi = HEX_DECODE[_buffer[i]];
|
|
1436
|
+
const lo = HEX_DECODE[_buffer[i + 1]];
|
|
1437
|
+
if (hi === 0xff || lo === 0xff) {
|
|
1438
|
+
this.emit('framing-error', new ModbusError(ModbusErrorCode.INVALID_HEX, 'Invalid hex character'));
|
|
1439
|
+
return;
|
|
1440
|
+
}
|
|
1441
|
+
buffer[i / 2] = (hi << 4) | lo;
|
|
1442
|
+
}
|
|
1443
|
+
const frame = {
|
|
1444
|
+
unit: buffer[0],
|
|
1445
|
+
fc: buffer[1],
|
|
1446
|
+
data: buffer.subarray(2, buffer.length - 1),
|
|
1447
|
+
buffer: _buffer,
|
|
1448
|
+
};
|
|
1449
|
+
const lrcPassed = buffer[buffer.length - 1] === lrc(buffer.subarray(0, buffer.length - 1));
|
|
1450
|
+
if (!lrcPassed) {
|
|
1451
|
+
this.emit('framing-error', new ModbusError(ModbusErrorCode.LRC_MISMATCH, 'LRC check failed'));
|
|
1452
|
+
return;
|
|
1453
|
+
}
|
|
1454
|
+
this.emit('framing', frame, response, connection);
|
|
863
1455
|
}
|
|
864
1456
|
encode(data) {
|
|
865
1457
|
const buffer = Buffer.alloc(data.data.length + 3);
|
|
866
1458
|
buffer.writeUInt8(data.unit, 0);
|
|
867
1459
|
buffer.writeUInt8(data.fc, 1);
|
|
868
|
-
data.data
|
|
869
|
-
buffer.writeUInt8(num, 2 + index);
|
|
870
|
-
});
|
|
1460
|
+
buffer.set(data.data, 2);
|
|
871
1461
|
buffer.writeUInt8(lrc(buffer.subarray(0, -1)), buffer.length - 1);
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
1462
|
+
const out = Buffer.allocUnsafe(1 + buffer.length * 2 + 2);
|
|
1463
|
+
out[0] = CHAR_CODE.COLON;
|
|
1464
|
+
for (let i = 0; i < buffer.length; i++) {
|
|
1465
|
+
const byte = buffer[i];
|
|
1466
|
+
out[1 + i * 2] = HEX_ENCODE[byte >> 4];
|
|
1467
|
+
out[2 + i * 2] = HEX_ENCODE[byte & 0x0f];
|
|
875
1468
|
}
|
|
876
|
-
|
|
877
|
-
|
|
1469
|
+
out[out.length - 2] = CHAR_CODE.CR;
|
|
1470
|
+
out[out.length - 1] = CHAR_CODE.LF;
|
|
1471
|
+
return out;
|
|
878
1472
|
}
|
|
879
1473
|
destroy() {
|
|
880
1474
|
this.removeAllListeners();
|
|
881
|
-
for (const
|
|
882
|
-
|
|
1475
|
+
for (const removeListener of this._removeAllListeners) {
|
|
1476
|
+
removeListener();
|
|
883
1477
|
}
|
|
1478
|
+
this._states.clear();
|
|
884
1479
|
}
|
|
885
1480
|
}
|
|
886
1481
|
|
|
1482
|
+
const MAX_TCP_FRAME = 260;
|
|
1483
|
+
const EMPTY_BUFFER = Buffer.alloc(0);
|
|
887
1484
|
class TcpApplicationLayer extends AbstractApplicationLayer {
|
|
888
1485
|
constructor(physicalLayer) {
|
|
889
1486
|
super();
|
|
890
|
-
Object.defineProperty(this, "
|
|
1487
|
+
Object.defineProperty(this, "PROTOCOL", {
|
|
891
1488
|
enumerable: true,
|
|
892
1489
|
configurable: true,
|
|
893
1490
|
writable: true,
|
|
894
|
-
value:
|
|
1491
|
+
value: 'TCP'
|
|
895
1492
|
});
|
|
896
1493
|
Object.defineProperty(this, "_transactionId", {
|
|
897
1494
|
enumerable: true,
|
|
@@ -899,106 +1496,218 @@ class TcpApplicationLayer extends AbstractApplicationLayer {
|
|
|
899
1496
|
writable: true,
|
|
900
1497
|
value: 1
|
|
901
1498
|
});
|
|
1499
|
+
Object.defineProperty(this, "_buffers", {
|
|
1500
|
+
enumerable: true,
|
|
1501
|
+
configurable: true,
|
|
1502
|
+
writable: true,
|
|
1503
|
+
value: new Map()
|
|
1504
|
+
});
|
|
902
1505
|
Object.defineProperty(this, "_removeAllListeners", {
|
|
903
1506
|
enumerable: true,
|
|
904
1507
|
configurable: true,
|
|
905
1508
|
writable: true,
|
|
906
1509
|
value: []
|
|
907
1510
|
});
|
|
908
|
-
const handleData = (data, response) => {
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
1511
|
+
const handleData = (data, response, connection) => {
|
|
1512
|
+
var _a;
|
|
1513
|
+
let buffer = (_a = this._buffers.get(connection.id)) !== null && _a !== void 0 ? _a : EMPTY_BUFFER;
|
|
1514
|
+
buffer = buffer.length === 0 ? data : Buffer.concat([buffer, data]);
|
|
1515
|
+
while (buffer.length > 0) {
|
|
1516
|
+
const result = this.tryExtract(buffer);
|
|
1517
|
+
if (result.kind === 'frame') {
|
|
1518
|
+
this.processFrame(result.frame, response, connection);
|
|
1519
|
+
buffer = result.rest;
|
|
912
1520
|
}
|
|
913
|
-
else if (
|
|
914
|
-
|
|
1521
|
+
else if (result.kind === 'insufficient') {
|
|
1522
|
+
break;
|
|
915
1523
|
}
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
physicalLayer.removeListener('data', handleData);
|
|
921
|
-
});
|
|
922
|
-
}
|
|
923
|
-
framing(buffer, callback) {
|
|
924
|
-
if (buffer.length >= 8) {
|
|
925
|
-
if (buffer[2] === 0 && buffer[3] === 0 && buffer.readUInt16BE(4) === buffer.length - 6) {
|
|
926
|
-
const frame = {
|
|
927
|
-
transaction: buffer.readUInt16BE(0),
|
|
928
|
-
unit: buffer[6],
|
|
929
|
-
fc: buffer[7],
|
|
930
|
-
data: Array.from(buffer.subarray(8)),
|
|
931
|
-
buffer,
|
|
932
|
-
};
|
|
933
|
-
if (this._waitingResponse) {
|
|
934
|
-
for (const check of this._waitingResponse.preCheck) {
|
|
935
|
-
const res = check(frame);
|
|
936
|
-
if (typeof res === 'undefined') {
|
|
937
|
-
callback(new Error('Insufficient data length'));
|
|
938
|
-
return;
|
|
939
|
-
}
|
|
940
|
-
if (typeof res === 'number') {
|
|
941
|
-
if (frame.data.length < res) {
|
|
942
|
-
callback(new Error('Insufficient data length'));
|
|
943
|
-
return;
|
|
944
|
-
}
|
|
945
|
-
if (frame.data.length !== res) {
|
|
946
|
-
callback(new Error('Invalid response'));
|
|
947
|
-
return;
|
|
948
|
-
}
|
|
949
|
-
}
|
|
950
|
-
if (!res) {
|
|
951
|
-
callback(new Error('Invalid response'));
|
|
952
|
-
return;
|
|
953
|
-
}
|
|
954
|
-
}
|
|
1524
|
+
else {
|
|
1525
|
+
this.emit('framing-error', result.error);
|
|
1526
|
+
buffer = EMPTY_BUFFER;
|
|
1527
|
+
break;
|
|
955
1528
|
}
|
|
956
|
-
|
|
1529
|
+
}
|
|
1530
|
+
if (buffer.length === 0) {
|
|
1531
|
+
this._buffers.delete(connection.id);
|
|
957
1532
|
}
|
|
958
1533
|
else {
|
|
959
|
-
|
|
1534
|
+
this._buffers.set(connection.id, buffer);
|
|
960
1535
|
}
|
|
1536
|
+
};
|
|
1537
|
+
physicalLayer.on('data', handleData);
|
|
1538
|
+
this._removeAllListeners.push(() => physicalLayer.removeListener('data', handleData));
|
|
1539
|
+
const handleConnectionClose = (connection) => {
|
|
1540
|
+
this._buffers.delete(connection.id);
|
|
1541
|
+
};
|
|
1542
|
+
physicalLayer.on('connection-close', handleConnectionClose);
|
|
1543
|
+
this._removeAllListeners.push(() => physicalLayer.removeListener('connection-close', handleConnectionClose));
|
|
1544
|
+
const handleClose = () => {
|
|
1545
|
+
this._buffers.clear();
|
|
1546
|
+
};
|
|
1547
|
+
physicalLayer.on('close', handleClose);
|
|
1548
|
+
this._removeAllListeners.push(() => physicalLayer.removeListener('close', handleClose));
|
|
1549
|
+
}
|
|
1550
|
+
tryExtract(buffer) {
|
|
1551
|
+
if (buffer.length < 8) {
|
|
1552
|
+
return { kind: 'insufficient' };
|
|
961
1553
|
}
|
|
962
|
-
|
|
963
|
-
|
|
1554
|
+
if (buffer[2] !== 0 || buffer[3] !== 0) {
|
|
1555
|
+
return { kind: 'error', error: new ModbusError(ModbusErrorCode.INVALID_DATA, 'Invalid data') };
|
|
964
1556
|
}
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
1557
|
+
const length = buffer.readUInt16BE(4);
|
|
1558
|
+
const total = 6 + length;
|
|
1559
|
+
if (total > MAX_TCP_FRAME || length < 2) {
|
|
1560
|
+
return { kind: 'error', error: new ModbusError(ModbusErrorCode.INVALID_DATA, 'Invalid data') };
|
|
1561
|
+
}
|
|
1562
|
+
if (buffer.length < total) {
|
|
1563
|
+
return { kind: 'insufficient' };
|
|
1564
|
+
}
|
|
1565
|
+
return { kind: 'frame', frame: buffer.subarray(0, total), rest: buffer.subarray(total) };
|
|
1566
|
+
}
|
|
1567
|
+
processFrame(buffer, response, connection) {
|
|
1568
|
+
const frame = {
|
|
1569
|
+
transaction: buffer.readUInt16BE(0),
|
|
1570
|
+
unit: buffer[6],
|
|
1571
|
+
fc: buffer[7],
|
|
1572
|
+
data: buffer.subarray(8),
|
|
1573
|
+
buffer,
|
|
1574
|
+
};
|
|
1575
|
+
this.emit('framing', frame, response, connection);
|
|
971
1576
|
}
|
|
972
1577
|
encode(data) {
|
|
973
1578
|
var _a;
|
|
1579
|
+
const transaction = (_a = data.transaction) !== null && _a !== void 0 ? _a : this._transactionId;
|
|
974
1580
|
const buffer = Buffer.alloc(data.data.length + 8);
|
|
975
|
-
buffer.writeUInt16BE(
|
|
1581
|
+
buffer.writeUInt16BE(transaction, 0);
|
|
976
1582
|
buffer.writeUInt16BE(0, 2);
|
|
977
1583
|
buffer.writeUInt16BE(data.data.length + 2, 4);
|
|
978
1584
|
buffer.writeUInt8(data.unit, 6);
|
|
979
1585
|
buffer.writeUInt8(data.fc, 7);
|
|
980
|
-
data.data
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
1586
|
+
buffer.set(data.data, 8);
|
|
1587
|
+
if (data.transaction === undefined) {
|
|
1588
|
+
this._transactionId = (this._transactionId + 1) % 65536 || 1;
|
|
1589
|
+
}
|
|
984
1590
|
return buffer;
|
|
985
1591
|
}
|
|
986
1592
|
destroy() {
|
|
987
1593
|
this.removeAllListeners();
|
|
988
|
-
for (const
|
|
989
|
-
|
|
1594
|
+
for (const removeListener of this._removeAllListeners) {
|
|
1595
|
+
removeListener();
|
|
990
1596
|
}
|
|
1597
|
+
this._buffers.clear();
|
|
991
1598
|
}
|
|
992
1599
|
}
|
|
993
1600
|
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
1601
|
+
const FIFO_KEY = 'fifo';
|
|
1602
|
+
class MasterSession {
|
|
1603
|
+
constructor() {
|
|
1604
|
+
Object.defineProperty(this, "_waiters", {
|
|
1605
|
+
enumerable: true,
|
|
1606
|
+
configurable: true,
|
|
1607
|
+
writable: true,
|
|
1608
|
+
value: new Map()
|
|
1609
|
+
});
|
|
1610
|
+
}
|
|
1611
|
+
start(key, preCheck, callback) {
|
|
1612
|
+
this._waiters.set(key, { preCheck, callback });
|
|
1613
|
+
}
|
|
1614
|
+
stop(key) {
|
|
1615
|
+
this._waiters.delete(key);
|
|
1616
|
+
}
|
|
1617
|
+
stopAll(error) {
|
|
1618
|
+
const waiters = [...this._waiters.values()];
|
|
1619
|
+
this._waiters.clear();
|
|
1620
|
+
for (const w of waiters) {
|
|
1621
|
+
w.callback(error);
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
has(key) {
|
|
1625
|
+
return this._waiters.has(key);
|
|
1626
|
+
}
|
|
1627
|
+
handleFrame(frame) {
|
|
1628
|
+
var _a;
|
|
1629
|
+
const key = (_a = frame.transaction) !== null && _a !== void 0 ? _a : FIFO_KEY;
|
|
1630
|
+
const waiter = this._waiters.get(key);
|
|
1631
|
+
if (!waiter) {
|
|
1632
|
+
return;
|
|
1633
|
+
}
|
|
1634
|
+
if (!this.runPreChecks(waiter, frame)) {
|
|
1635
|
+
return;
|
|
1636
|
+
}
|
|
1637
|
+
waiter.callback(null, frame);
|
|
1638
|
+
}
|
|
1639
|
+
handleError(error) {
|
|
1640
|
+
// Framing errors lose transaction context; reject every in-flight waiter.
|
|
1641
|
+
this.stopAll(error);
|
|
1642
|
+
}
|
|
1643
|
+
runPreChecks(waiter, frame) {
|
|
1644
|
+
for (const check of waiter.preCheck) {
|
|
1645
|
+
const res = check(frame);
|
|
1646
|
+
if (typeof res === 'undefined') {
|
|
1647
|
+
waiter.callback(new ModbusError(ModbusErrorCode.INSUFFICIENT_DATA, 'Insufficient data length'));
|
|
1648
|
+
return false;
|
|
1649
|
+
}
|
|
1650
|
+
if (typeof res === 'number') {
|
|
1651
|
+
if (frame.data.length < res) {
|
|
1652
|
+
waiter.callback(new ModbusError(ModbusErrorCode.INSUFFICIENT_DATA, 'Insufficient data length'));
|
|
1653
|
+
return false;
|
|
1654
|
+
}
|
|
1655
|
+
if (frame.data.length !== res) {
|
|
1656
|
+
waiter.callback(new ModbusError(ModbusErrorCode.INVALID_RESPONSE, 'Invalid response'));
|
|
1657
|
+
return false;
|
|
1658
|
+
}
|
|
1659
|
+
continue;
|
|
1660
|
+
}
|
|
1661
|
+
if (!res) {
|
|
1662
|
+
waiter.callback(new ModbusError(ModbusErrorCode.INVALID_RESPONSE, 'Invalid response'));
|
|
1663
|
+
return false;
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
return true;
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
/******************************************************************************
|
|
1671
|
+
Copyright (c) Microsoft Corporation.
|
|
1672
|
+
|
|
1673
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
1674
|
+
purpose with or without fee is hereby granted.
|
|
1675
|
+
|
|
1676
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
1677
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
1678
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
1679
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
1680
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
1681
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
1682
|
+
PERFORMANCE OF THIS SOFTWARE.
|
|
1683
|
+
***************************************************************************** */
|
|
1684
|
+
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
|
|
1685
|
+
|
|
1686
|
+
|
|
1687
|
+
function __awaiter(thisArg, _arguments, P, generator) {
|
|
1688
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
1689
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
1690
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
1691
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
1692
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
1693
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
1694
|
+
});
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
1698
|
+
var e = new Error(message);
|
|
1699
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
1700
|
+
};
|
|
1701
|
+
|
|
1702
|
+
class ModbusMaster extends EventEmitter {
|
|
1703
|
+
get isOpen() {
|
|
1704
|
+
return this.physicalLayer.isOpen;
|
|
1705
|
+
}
|
|
1706
|
+
get destroyed() {
|
|
999
1707
|
return this.physicalLayer.destroyed;
|
|
1000
1708
|
}
|
|
1001
|
-
constructor(applicationLayer, physicalLayer,
|
|
1709
|
+
constructor(applicationLayer, physicalLayer, options = {}) {
|
|
1710
|
+
var _a, _b;
|
|
1002
1711
|
super();
|
|
1003
1712
|
Object.defineProperty(this, "applicationLayer", {
|
|
1004
1713
|
enumerable: true,
|
|
@@ -1012,11 +1721,47 @@ class ModbusMaster extends EventEmitter {
|
|
|
1012
1721
|
writable: true,
|
|
1013
1722
|
value: physicalLayer
|
|
1014
1723
|
});
|
|
1724
|
+
Object.defineProperty(this, "_masterSession", {
|
|
1725
|
+
enumerable: true,
|
|
1726
|
+
configurable: true,
|
|
1727
|
+
writable: true,
|
|
1728
|
+
value: new MasterSession()
|
|
1729
|
+
});
|
|
1730
|
+
Object.defineProperty(this, "_queue", {
|
|
1731
|
+
enumerable: true,
|
|
1732
|
+
configurable: true,
|
|
1733
|
+
writable: true,
|
|
1734
|
+
value: []
|
|
1735
|
+
});
|
|
1736
|
+
Object.defineProperty(this, "_draining", {
|
|
1737
|
+
enumerable: true,
|
|
1738
|
+
configurable: true,
|
|
1739
|
+
writable: true,
|
|
1740
|
+
value: false
|
|
1741
|
+
});
|
|
1742
|
+
Object.defineProperty(this, "_nextTid", {
|
|
1743
|
+
enumerable: true,
|
|
1744
|
+
configurable: true,
|
|
1745
|
+
writable: true,
|
|
1746
|
+
value: 1
|
|
1747
|
+
});
|
|
1748
|
+
Object.defineProperty(this, "_closed", {
|
|
1749
|
+
enumerable: true,
|
|
1750
|
+
configurable: true,
|
|
1751
|
+
writable: true,
|
|
1752
|
+
value: false
|
|
1753
|
+
});
|
|
1015
1754
|
Object.defineProperty(this, "timeout", {
|
|
1016
1755
|
enumerable: true,
|
|
1017
1756
|
configurable: true,
|
|
1018
1757
|
writable: true,
|
|
1019
|
-
value:
|
|
1758
|
+
value: void 0
|
|
1759
|
+
});
|
|
1760
|
+
Object.defineProperty(this, "concurrent", {
|
|
1761
|
+
enumerable: true,
|
|
1762
|
+
configurable: true,
|
|
1763
|
+
writable: true,
|
|
1764
|
+
value: void 0
|
|
1020
1765
|
});
|
|
1021
1766
|
Object.defineProperty(this, "writeFC1", {
|
|
1022
1767
|
enumerable: true,
|
|
@@ -1090,6 +1835,12 @@ class ModbusMaster extends EventEmitter {
|
|
|
1090
1835
|
writable: true,
|
|
1091
1836
|
value: void 0
|
|
1092
1837
|
});
|
|
1838
|
+
this.timeout = (_a = options.timeout) !== null && _a !== void 0 ? _a : 1000;
|
|
1839
|
+
this.concurrent = (_b = options.concurrent) !== null && _b !== void 0 ? _b : false;
|
|
1840
|
+
if (this.concurrent && this.applicationLayer.PROTOCOL !== 'TCP') {
|
|
1841
|
+
throw new ModbusError(ModbusErrorCode.CONCURRENT_NOT_TCP, 'concurrent mode requires a Modbus TCP application layer');
|
|
1842
|
+
}
|
|
1843
|
+
this.applicationLayer.role = 'MASTER';
|
|
1093
1844
|
this.writeFC1 = this.readCoils;
|
|
1094
1845
|
this.writeFC2 = this.readDiscreteInputs;
|
|
1095
1846
|
this.writeFC3 = this.readHoldingRegisters;
|
|
@@ -1102,6 +1853,12 @@ class ModbusMaster extends EventEmitter {
|
|
|
1102
1853
|
this.handleFC22 = this.maskWriteRegister;
|
|
1103
1854
|
this.handleFC23 = this.readAndWriteMultipleRegisters;
|
|
1104
1855
|
this.handleFC43_14 = this.readDeviceIdentification;
|
|
1856
|
+
applicationLayer.on('framing', (frame) => {
|
|
1857
|
+
this._masterSession.handleFrame(frame);
|
|
1858
|
+
});
|
|
1859
|
+
applicationLayer.on('framing-error', (error) => {
|
|
1860
|
+
this._masterSession.handleError(error);
|
|
1861
|
+
});
|
|
1105
1862
|
physicalLayer.on('error', (error) => {
|
|
1106
1863
|
this.emit('error', error);
|
|
1107
1864
|
});
|
|
@@ -1109,34 +1866,90 @@ class ModbusMaster extends EventEmitter {
|
|
|
1109
1866
|
this.emit('close');
|
|
1110
1867
|
});
|
|
1111
1868
|
}
|
|
1112
|
-
|
|
1869
|
+
send(adu, preCheck, timeout, broadcast) {
|
|
1870
|
+
const task = () => this._exchange(adu, preCheck, timeout, broadcast);
|
|
1871
|
+
if (this.concurrent) {
|
|
1872
|
+
return task();
|
|
1873
|
+
}
|
|
1874
|
+
return new Promise((resolve, reject) => {
|
|
1875
|
+
const run = () => __awaiter(this, void 0, void 0, function* () {
|
|
1876
|
+
try {
|
|
1877
|
+
resolve(yield task());
|
|
1878
|
+
}
|
|
1879
|
+
catch (error) {
|
|
1880
|
+
reject(error);
|
|
1881
|
+
}
|
|
1882
|
+
});
|
|
1883
|
+
this._queue.push({ run, cancel: reject });
|
|
1884
|
+
this._drain();
|
|
1885
|
+
});
|
|
1886
|
+
}
|
|
1887
|
+
_drain() {
|
|
1888
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1889
|
+
if (this._draining) {
|
|
1890
|
+
return;
|
|
1891
|
+
}
|
|
1892
|
+
this._draining = true;
|
|
1893
|
+
try {
|
|
1894
|
+
while (this._queue.length > 0) {
|
|
1895
|
+
const item = this._queue.shift();
|
|
1896
|
+
yield item.run();
|
|
1897
|
+
}
|
|
1898
|
+
}
|
|
1899
|
+
finally {
|
|
1900
|
+
this._draining = false;
|
|
1901
|
+
}
|
|
1902
|
+
});
|
|
1903
|
+
}
|
|
1904
|
+
_exchange(adu, preCheck, timeout, broadcast) {
|
|
1113
1905
|
return new Promise((resolve, reject) => {
|
|
1906
|
+
if (this._closed) {
|
|
1907
|
+
reject(new ModbusError(ModbusErrorCode.MASTER_CLOSED, 'Master closed'));
|
|
1908
|
+
return;
|
|
1909
|
+
}
|
|
1910
|
+
const usesTid = this.applicationLayer.PROTOCOL === 'TCP' && !broadcast;
|
|
1911
|
+
let tid;
|
|
1912
|
+
if (usesTid) {
|
|
1913
|
+
tid = this._nextTid;
|
|
1914
|
+
this._nextTid = (this._nextTid + 1) % 65536 || 1;
|
|
1915
|
+
}
|
|
1916
|
+
const key = tid !== null && tid !== void 0 ? tid : FIFO_KEY;
|
|
1917
|
+
const payload = this.applicationLayer.encode(Object.assign(Object.assign({}, adu), { transaction: tid }));
|
|
1918
|
+
let settled = false;
|
|
1919
|
+
const settle = (error, frame) => {
|
|
1920
|
+
if (settled) {
|
|
1921
|
+
return;
|
|
1922
|
+
}
|
|
1923
|
+
settled = true;
|
|
1924
|
+
clearTimeout(timer);
|
|
1925
|
+
this._masterSession.stop(key);
|
|
1926
|
+
if (error) {
|
|
1927
|
+
reject(error);
|
|
1928
|
+
}
|
|
1929
|
+
else {
|
|
1930
|
+
resolve(frame);
|
|
1931
|
+
}
|
|
1932
|
+
};
|
|
1933
|
+
const timer = setTimeout(() => settle(new ModbusError(ModbusErrorCode.TIMEOUT, 'Timeout')), timeout);
|
|
1934
|
+
// FIFO mode: clear stale buffer state before this request.
|
|
1935
|
+
// Concurrent mode: must not flush — other in-flight requests share the buffer.
|
|
1936
|
+
if (!this.concurrent) {
|
|
1937
|
+
this.applicationLayer.flush();
|
|
1938
|
+
}
|
|
1114
1939
|
this.physicalLayer
|
|
1115
|
-
.write(
|
|
1940
|
+
.write(payload)
|
|
1116
1941
|
.then(() => {
|
|
1117
|
-
if (
|
|
1118
|
-
|
|
1942
|
+
if (settled) {
|
|
1943
|
+
return;
|
|
1119
1944
|
}
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
reject(new Error('Timeout'));
|
|
1124
|
-
}, timeout);
|
|
1125
|
-
this.applicationLayer.startWaitingResponse(response.preCheck, (error, frame) => {
|
|
1126
|
-
clearTimeout(tid);
|
|
1127
|
-
this.applicationLayer.stopWaitingResponse();
|
|
1128
|
-
if (error) {
|
|
1129
|
-
reject(error);
|
|
1130
|
-
}
|
|
1131
|
-
else {
|
|
1132
|
-
resolve(frame);
|
|
1133
|
-
}
|
|
1134
|
-
});
|
|
1945
|
+
if (broadcast) {
|
|
1946
|
+
settle(null);
|
|
1947
|
+
return;
|
|
1135
1948
|
}
|
|
1949
|
+
const finalChecks = usesTid ? [(frame) => frame.transaction === tid, ...preCheck] : preCheck;
|
|
1950
|
+
this._masterSession.start(key, finalChecks, (error, frame) => settle(error, frame));
|
|
1136
1951
|
})
|
|
1137
|
-
.catch((error) =>
|
|
1138
|
-
reject(error);
|
|
1139
|
-
});
|
|
1952
|
+
.catch((error) => settle(error));
|
|
1140
1953
|
});
|
|
1141
1954
|
}
|
|
1142
1955
|
writeFC1Or2(unit, fc, address, length, timeout) {
|
|
@@ -1144,104 +1957,60 @@ class ModbusMaster extends EventEmitter {
|
|
|
1144
1957
|
const bufferTx = Buffer.alloc(4);
|
|
1145
1958
|
bufferTx.writeUInt16BE(address, 0);
|
|
1146
1959
|
bufferTx.writeUInt16BE(length, 2);
|
|
1147
|
-
return this.
|
|
1148
|
-
data: this.applicationLayer.encode({
|
|
1149
|
-
unit,
|
|
1150
|
-
fc,
|
|
1151
|
-
data: Array.from(bufferTx),
|
|
1152
|
-
}),
|
|
1153
|
-
broadcast: unit === 0,
|
|
1154
|
-
}, {
|
|
1155
|
-
preCheck: [(frame) => frame.unit === unit && frame.fc === fc, () => 1 + byteCount, (frame) => frame.data[0] === byteCount],
|
|
1156
|
-
}, timeout).then((frame) => {
|
|
1960
|
+
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) => {
|
|
1157
1961
|
if (frame) {
|
|
1158
|
-
return Object.assign(Object.assign({}, frame), { data: Array.from({ length }
|
|
1962
|
+
return Object.assign(Object.assign({}, frame), { data: Array.from({ length }, (_, index) => (frame.data[1 + ~~(index / 8)] & (1 << index % 8)) > 0) });
|
|
1159
1963
|
}
|
|
1160
1964
|
});
|
|
1161
1965
|
}
|
|
1162
1966
|
readCoils(unit, address, length, timeout = this.timeout) {
|
|
1163
|
-
return this.writeFC1Or2(unit,
|
|
1967
|
+
return this.writeFC1Or2(unit, exports.FunctionCode.READ_COILS, address, length, timeout);
|
|
1164
1968
|
}
|
|
1165
1969
|
readDiscreteInputs(unit, address, length, timeout = this.timeout) {
|
|
1166
|
-
return this.writeFC1Or2(unit,
|
|
1970
|
+
return this.writeFC1Or2(unit, exports.FunctionCode.READ_DISCRETE_INPUTS, address, length, timeout);
|
|
1167
1971
|
}
|
|
1168
1972
|
writeFC3Or4(unit, fc, address, length, timeout) {
|
|
1169
1973
|
const byteCount = length * 2;
|
|
1170
1974
|
const bufferTx = Buffer.alloc(4);
|
|
1171
1975
|
bufferTx.writeUInt16BE(address, 0);
|
|
1172
1976
|
bufferTx.writeUInt16BE(length, 2);
|
|
1173
|
-
return this.
|
|
1174
|
-
data: this.applicationLayer.encode({
|
|
1175
|
-
unit,
|
|
1176
|
-
fc,
|
|
1177
|
-
data: Array.from(bufferTx),
|
|
1178
|
-
}),
|
|
1179
|
-
broadcast: unit === 0,
|
|
1180
|
-
}, {
|
|
1181
|
-
preCheck: [(frame) => frame.unit === unit && frame.fc === fc, () => 1 + byteCount, (frame) => frame.data[0] === byteCount],
|
|
1182
|
-
}, timeout).then((frame) => {
|
|
1977
|
+
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) => {
|
|
1183
1978
|
if (frame) {
|
|
1184
|
-
const bufferRx =
|
|
1185
|
-
return Object.assign(Object.assign({}, frame), { data: Array.from({ length }
|
|
1979
|
+
const bufferRx = frame.data.subarray(1);
|
|
1980
|
+
return Object.assign(Object.assign({}, frame), { data: Array.from({ length }, (_, index) => bufferRx.readUInt16BE(index * 2)) });
|
|
1186
1981
|
}
|
|
1187
1982
|
});
|
|
1188
1983
|
}
|
|
1189
1984
|
readHoldingRegisters(unit, address, length, timeout = this.timeout) {
|
|
1190
|
-
return this.writeFC3Or4(unit,
|
|
1985
|
+
return this.writeFC3Or4(unit, exports.FunctionCode.READ_HOLDING_REGISTERS, address, length, timeout);
|
|
1191
1986
|
}
|
|
1192
1987
|
readInputRegisters(unit, address, length, timeout = this.timeout) {
|
|
1193
|
-
return this.writeFC3Or4(unit,
|
|
1988
|
+
return this.writeFC3Or4(unit, exports.FunctionCode.READ_INPUT_REGISTERS, address, length, timeout);
|
|
1194
1989
|
}
|
|
1195
1990
|
writeSingleCoil(unit, address, value, timeout = this.timeout) {
|
|
1196
|
-
const fc =
|
|
1991
|
+
const fc = exports.FunctionCode.WRITE_SINGLE_COIL;
|
|
1197
1992
|
const bufferTx = Buffer.alloc(4);
|
|
1198
1993
|
bufferTx.writeUInt16BE(address, 0);
|
|
1199
|
-
bufferTx.writeUInt16BE(value ?
|
|
1200
|
-
return this.
|
|
1201
|
-
data: this.applicationLayer.encode({
|
|
1202
|
-
unit,
|
|
1203
|
-
fc,
|
|
1204
|
-
data: Array.from(bufferTx),
|
|
1205
|
-
}),
|
|
1206
|
-
broadcast: unit === 0,
|
|
1207
|
-
}, {
|
|
1208
|
-
preCheck: [
|
|
1209
|
-
(frame) => frame.unit === unit && frame.fc === fc,
|
|
1210
|
-
() => bufferTx.length,
|
|
1211
|
-
(frame) => frame.data.every((v, i) => v === bufferTx[i]),
|
|
1212
|
-
],
|
|
1213
|
-
}, timeout).then((frame) => {
|
|
1994
|
+
bufferTx.writeUInt16BE(value ? COIL_ON : COIL_OFF, 2);
|
|
1995
|
+
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) => {
|
|
1214
1996
|
if (frame) {
|
|
1215
1997
|
return Object.assign(Object.assign({}, frame), { data: value });
|
|
1216
1998
|
}
|
|
1217
1999
|
});
|
|
1218
2000
|
}
|
|
1219
2001
|
writeSingleRegister(unit, address, value, timeout = this.timeout) {
|
|
1220
|
-
const fc =
|
|
2002
|
+
const fc = exports.FunctionCode.WRITE_SINGLE_REGISTER;
|
|
1221
2003
|
const bufferTx = Buffer.alloc(4);
|
|
1222
2004
|
bufferTx.writeUInt16BE(address, 0);
|
|
1223
2005
|
bufferTx.writeUInt16BE(value, 2);
|
|
1224
|
-
return this.
|
|
1225
|
-
data: this.applicationLayer.encode({
|
|
1226
|
-
unit,
|
|
1227
|
-
fc,
|
|
1228
|
-
data: Array.from(bufferTx),
|
|
1229
|
-
}),
|
|
1230
|
-
broadcast: unit === 0,
|
|
1231
|
-
}, {
|
|
1232
|
-
preCheck: [
|
|
1233
|
-
(frame) => frame.unit === unit && frame.fc === fc,
|
|
1234
|
-
() => bufferTx.length,
|
|
1235
|
-
(frame) => frame.data.every((v, i) => v === bufferTx[i]),
|
|
1236
|
-
],
|
|
1237
|
-
}, timeout).then((frame) => {
|
|
2006
|
+
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) => {
|
|
1238
2007
|
if (frame) {
|
|
1239
2008
|
return Object.assign(Object.assign({}, frame), { data: value });
|
|
1240
2009
|
}
|
|
1241
2010
|
});
|
|
1242
2011
|
}
|
|
1243
2012
|
writeMultipleCoils(unit, address, value, timeout = this.timeout) {
|
|
1244
|
-
const fc =
|
|
2013
|
+
const fc = exports.FunctionCode.WRITE_MULTIPLE_COILS;
|
|
1245
2014
|
const byteCount = Math.ceil(value.length / 8);
|
|
1246
2015
|
const bufferTx = Buffer.alloc(5 + byteCount);
|
|
1247
2016
|
bufferTx.writeUInt16BE(address, 0);
|
|
@@ -1252,23 +2021,14 @@ class ModbusMaster extends EventEmitter {
|
|
|
1252
2021
|
bufferTx[5 + ~~(i / 8)] |= 1 << i % 8;
|
|
1253
2022
|
}
|
|
1254
2023
|
});
|
|
1255
|
-
return this.
|
|
1256
|
-
data: this.applicationLayer.encode({
|
|
1257
|
-
unit,
|
|
1258
|
-
fc,
|
|
1259
|
-
data: Array.from(bufferTx),
|
|
1260
|
-
}),
|
|
1261
|
-
broadcast: unit === 0,
|
|
1262
|
-
}, {
|
|
1263
|
-
preCheck: [(frame) => frame.unit === unit && frame.fc === fc, () => 4, (frame) => frame.data.every((v, i) => v === bufferTx[i])],
|
|
1264
|
-
}, timeout).then((frame) => {
|
|
2024
|
+
return this.send({ unit, fc, data: bufferTx }, [(frame) => frame.unit === unit && frame.fc === fc, () => 4, (frame) => frame.data.equals(bufferTx.subarray(0, 4))], timeout, unit === 0).then((frame) => {
|
|
1265
2025
|
if (frame) {
|
|
1266
2026
|
return Object.assign(Object.assign({}, frame), { data: value });
|
|
1267
2027
|
}
|
|
1268
2028
|
});
|
|
1269
2029
|
}
|
|
1270
2030
|
writeMultipleRegisters(unit, address, value, timeout = this.timeout) {
|
|
1271
|
-
const fc =
|
|
2031
|
+
const fc = exports.FunctionCode.WRITE_MULTIPLE_REGISTERS;
|
|
1272
2032
|
const byteCount = value.length * 2;
|
|
1273
2033
|
const bufferTx = Buffer.alloc(5 + byteCount);
|
|
1274
2034
|
bufferTx.writeUInt16BE(address, 0);
|
|
@@ -1277,73 +2037,48 @@ class ModbusMaster extends EventEmitter {
|
|
|
1277
2037
|
value.forEach((v, i) => {
|
|
1278
2038
|
bufferTx.writeUInt16BE(v, 5 + i * 2);
|
|
1279
2039
|
});
|
|
1280
|
-
return this.
|
|
1281
|
-
data: this.applicationLayer.encode({
|
|
1282
|
-
unit,
|
|
1283
|
-
fc,
|
|
1284
|
-
data: Array.from(bufferTx),
|
|
1285
|
-
}),
|
|
1286
|
-
broadcast: unit === 0,
|
|
1287
|
-
}, {
|
|
1288
|
-
preCheck: [(frame) => frame.unit === unit && frame.fc === fc, () => 4, (frame) => frame.data.every((v, i) => v === bufferTx[i])],
|
|
1289
|
-
}, timeout).then((frame) => {
|
|
2040
|
+
return this.send({ unit, fc, data: bufferTx }, [(frame) => frame.unit === unit && frame.fc === fc, () => 4, (frame) => frame.data.equals(bufferTx.subarray(0, 4))], timeout, unit === 0).then((frame) => {
|
|
1290
2041
|
if (frame) {
|
|
1291
2042
|
return Object.assign(Object.assign({}, frame), { data: value });
|
|
1292
2043
|
}
|
|
1293
2044
|
});
|
|
1294
2045
|
}
|
|
1295
|
-
reportServerId(unit, timeout = this.timeout) {
|
|
1296
|
-
const fc =
|
|
1297
|
-
return this.
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
preCheck: [
|
|
1306
|
-
(frame) => frame.unit === unit && frame.fc === fc,
|
|
1307
|
-
(frame) => {
|
|
1308
|
-
if (frame.data.length >= 3) {
|
|
1309
|
-
return 1 + frame.data[0];
|
|
1310
|
-
}
|
|
1311
|
-
},
|
|
1312
|
-
],
|
|
1313
|
-
}, timeout).then((frame) => {
|
|
2046
|
+
reportServerId(unit, serverIdLength = 1, timeout = this.timeout) {
|
|
2047
|
+
const fc = exports.FunctionCode.REPORT_SERVER_ID;
|
|
2048
|
+
return this.send({ unit, fc, data: Buffer.alloc(0) }, [
|
|
2049
|
+
(frame) => frame.unit === unit && frame.fc === fc,
|
|
2050
|
+
(frame) => {
|
|
2051
|
+
if (frame.data.length >= 2 + serverIdLength) {
|
|
2052
|
+
return 1 + frame.data[0];
|
|
2053
|
+
}
|
|
2054
|
+
},
|
|
2055
|
+
], timeout, unit === 0).then((frame) => {
|
|
1314
2056
|
if (frame) {
|
|
2057
|
+
const runStatusIndex = 1 + serverIdLength;
|
|
1315
2058
|
return Object.assign(Object.assign({}, frame), { data: {
|
|
1316
|
-
serverId: frame.data[1],
|
|
1317
|
-
runIndicatorStatus: frame.data[
|
|
1318
|
-
additionalData: frame.data.
|
|
2059
|
+
serverId: serverIdLength === 1 ? frame.data[1] : Array.from(frame.data.subarray(1, runStatusIndex)),
|
|
2060
|
+
runIndicatorStatus: frame.data[runStatusIndex] === 0xff,
|
|
2061
|
+
additionalData: Array.from(frame.data.subarray(runStatusIndex + 1)),
|
|
1319
2062
|
} });
|
|
1320
2063
|
}
|
|
1321
2064
|
});
|
|
1322
2065
|
}
|
|
1323
2066
|
maskWriteRegister(unit, address, andMask, orMask, timeout = this.timeout) {
|
|
1324
|
-
const fc =
|
|
2067
|
+
const fc = exports.FunctionCode.MASK_WRITE_REGISTER;
|
|
1325
2068
|
const bufferTx = Buffer.alloc(6);
|
|
1326
2069
|
bufferTx.writeUInt16BE(address, 0);
|
|
1327
2070
|
bufferTx.writeUInt16BE(andMask, 2);
|
|
1328
2071
|
bufferTx.writeUInt16BE(orMask, 4);
|
|
1329
|
-
return this.
|
|
1330
|
-
data: this.applicationLayer.encode({
|
|
1331
|
-
unit,
|
|
1332
|
-
fc,
|
|
1333
|
-
data: Array.from(bufferTx),
|
|
1334
|
-
}),
|
|
1335
|
-
broadcast: unit === 0,
|
|
1336
|
-
}, {
|
|
1337
|
-
preCheck: [(frame) => frame.unit === unit && frame.fc === fc, () => 6, (frame) => frame.data.every((v, i) => v === bufferTx[i])],
|
|
1338
|
-
}, timeout).then((frame) => {
|
|
2072
|
+
return this.send({ unit, fc, data: bufferTx }, [(frame) => frame.unit === unit && frame.fc === fc, () => 6, (frame) => frame.data.equals(bufferTx)], timeout, unit === 0).then((frame) => {
|
|
1339
2073
|
if (frame) {
|
|
1340
2074
|
return Object.assign(Object.assign({}, frame), { data: { andMask, orMask } });
|
|
1341
2075
|
}
|
|
1342
2076
|
});
|
|
1343
2077
|
}
|
|
1344
2078
|
readAndWriteMultipleRegisters(unit, read, write, timeout = this.timeout) {
|
|
1345
|
-
const fc =
|
|
2079
|
+
const fc = exports.FunctionCode.READ_WRITE_MULTIPLE_REGISTERS;
|
|
1346
2080
|
const byteCount = write.value.length * 2;
|
|
2081
|
+
const readByteCount = read.length * 2;
|
|
1347
2082
|
const bufferTx = Buffer.alloc(9 + byteCount);
|
|
1348
2083
|
bufferTx.writeUInt16BE(read.address, 0);
|
|
1349
2084
|
bufferTx.writeUInt16BE(read.length, 2);
|
|
@@ -1353,150 +2088,108 @@ class ModbusMaster extends EventEmitter {
|
|
|
1353
2088
|
write.value.forEach((v, i) => {
|
|
1354
2089
|
bufferTx.writeUInt16BE(v, 9 + i * 2);
|
|
1355
2090
|
});
|
|
1356
|
-
return this.
|
|
1357
|
-
data: this.applicationLayer.encode({
|
|
1358
|
-
unit,
|
|
1359
|
-
fc,
|
|
1360
|
-
data: Array.from(bufferTx),
|
|
1361
|
-
}),
|
|
1362
|
-
broadcast: unit === 0,
|
|
1363
|
-
}, {
|
|
1364
|
-
preCheck: [(frame) => frame.unit === unit && frame.fc === fc, () => 1 + byteCount, (frame) => frame.data[0] === byteCount],
|
|
1365
|
-
}, timeout).then((frame) => {
|
|
2091
|
+
return this.send({ unit, fc, data: bufferTx }, [(frame) => frame.unit === unit && frame.fc === fc, () => 1 + readByteCount, (frame) => frame.data[0] === readByteCount], timeout, unit === 0).then((frame) => {
|
|
1366
2092
|
if (frame) {
|
|
1367
|
-
const bufferRx =
|
|
1368
|
-
return Object.assign(Object.assign({}, frame), { data: Array.from({ length: read.length }
|
|
2093
|
+
const bufferRx = frame.data.subarray(1);
|
|
2094
|
+
return Object.assign(Object.assign({}, frame), { data: Array.from({ length: read.length }, (_, index) => bufferRx.readUInt16BE(index * 2)) });
|
|
1369
2095
|
}
|
|
1370
2096
|
});
|
|
1371
2097
|
}
|
|
1372
2098
|
readDeviceIdentification(unit, readDeviceIDCode, objectId, timeout = this.timeout) {
|
|
1373
|
-
const fc =
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
data
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
case 3: {
|
|
1401
|
-
object[2].push(v);
|
|
1402
|
-
if (object[1] === object[2].length) {
|
|
1403
|
-
objects.push(2 + object[1]);
|
|
1404
|
-
object = [];
|
|
1405
|
-
}
|
|
1406
|
-
break;
|
|
2099
|
+
const fc = exports.FunctionCode.READ_DEVICE_IDENTIFICATION;
|
|
2100
|
+
let parsed;
|
|
2101
|
+
return this.send({ unit, fc, data: Buffer.from([MEI_READ_DEVICE_ID, readDeviceIDCode, objectId]) }, [
|
|
2102
|
+
(frame) => frame.unit === unit && frame.fc === fc,
|
|
2103
|
+
(frame) => {
|
|
2104
|
+
if (frame.data.length >= 6) {
|
|
2105
|
+
if (frame.data[0] === MEI_READ_DEVICE_ID && frame.data[1] === readDeviceIDCode) {
|
|
2106
|
+
const objects = [];
|
|
2107
|
+
let object = [];
|
|
2108
|
+
let totalBytes = 0;
|
|
2109
|
+
for (const v of frame.data.subarray(6)) {
|
|
2110
|
+
switch (object.length) {
|
|
2111
|
+
case 0:
|
|
2112
|
+
case 1: {
|
|
2113
|
+
object.push(v);
|
|
2114
|
+
break;
|
|
2115
|
+
}
|
|
2116
|
+
case 2: {
|
|
2117
|
+
object.push([v]);
|
|
2118
|
+
break;
|
|
2119
|
+
}
|
|
2120
|
+
case 3: {
|
|
2121
|
+
object[2].push(v);
|
|
2122
|
+
if (object[1] === object[2].length) {
|
|
2123
|
+
objects.push({ id: object[0], value: Buffer.from(object[2]).toString() });
|
|
2124
|
+
totalBytes += 2 + object[1];
|
|
2125
|
+
object = [];
|
|
1407
2126
|
}
|
|
2127
|
+
break;
|
|
1408
2128
|
}
|
|
1409
2129
|
}
|
|
1410
|
-
if (objects.length === frame.data[5]) {
|
|
1411
|
-
return 6 + objects.reduce((previous, current) => previous + current, 0);
|
|
1412
|
-
}
|
|
1413
2130
|
}
|
|
1414
|
-
|
|
1415
|
-
|
|
2131
|
+
if (objects.length === frame.data[5]) {
|
|
2132
|
+
parsed = objects;
|
|
2133
|
+
return 6 + totalBytes;
|
|
1416
2134
|
}
|
|
1417
2135
|
}
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
}, timeout).then((frame) => {
|
|
1421
|
-
if (frame) {
|
|
1422
|
-
const objects = [];
|
|
1423
|
-
let object = [];
|
|
1424
|
-
for (const v of frame.data.slice(6)) {
|
|
1425
|
-
switch (object.length) {
|
|
1426
|
-
case 0:
|
|
1427
|
-
case 1: {
|
|
1428
|
-
object.push(v);
|
|
1429
|
-
break;
|
|
1430
|
-
}
|
|
1431
|
-
case 2: {
|
|
1432
|
-
object.push([v]);
|
|
1433
|
-
break;
|
|
1434
|
-
}
|
|
1435
|
-
case 3: {
|
|
1436
|
-
object[2].push(v);
|
|
1437
|
-
if (object[1] === object[2].length) {
|
|
1438
|
-
objects.push({ id: object[0], value: Buffer.from(object[2]).toString() });
|
|
1439
|
-
object = [];
|
|
1440
|
-
}
|
|
1441
|
-
break;
|
|
1442
|
-
}
|
|
2136
|
+
else {
|
|
2137
|
+
return false;
|
|
1443
2138
|
}
|
|
1444
2139
|
}
|
|
2140
|
+
},
|
|
2141
|
+
], timeout, unit === 0).then((frame) => {
|
|
2142
|
+
if (frame && parsed) {
|
|
1445
2143
|
return Object.assign(Object.assign({}, frame), { data: {
|
|
1446
2144
|
readDeviceIDCode,
|
|
1447
2145
|
conformityLevel: frame.data[2],
|
|
1448
2146
|
moreFollows: frame.data[3] === 0xff,
|
|
1449
2147
|
nextObjectId: frame.data[4],
|
|
1450
|
-
objects,
|
|
2148
|
+
objects: parsed,
|
|
1451
2149
|
} });
|
|
1452
2150
|
}
|
|
1453
2151
|
});
|
|
1454
2152
|
}
|
|
2153
|
+
addCustomFunctionCode(cfc) {
|
|
2154
|
+
this.applicationLayer.addCustomFunctionCode(cfc);
|
|
2155
|
+
}
|
|
2156
|
+
removeCustomFunctionCode(fc) {
|
|
2157
|
+
this.applicationLayer.removeCustomFunctionCode(fc);
|
|
2158
|
+
}
|
|
2159
|
+
sendCustomFC(unit, fc, data, timeout = this.timeout) {
|
|
2160
|
+
const payload = Buffer.isBuffer(data) ? data : Buffer.from(data);
|
|
2161
|
+
return this.send({ unit, fc, data: payload }, [(frame) => frame.unit === unit && frame.fc === fc], timeout, unit === 0).then((frame) => {
|
|
2162
|
+
if (frame) {
|
|
2163
|
+
return frame.data;
|
|
2164
|
+
}
|
|
2165
|
+
});
|
|
2166
|
+
}
|
|
1455
2167
|
open(...args) {
|
|
2168
|
+
this._closed = false;
|
|
1456
2169
|
return this.physicalLayer.open(...args);
|
|
1457
2170
|
}
|
|
1458
2171
|
close() {
|
|
2172
|
+
this._closed = true;
|
|
2173
|
+
const queued = this._queue.splice(0);
|
|
2174
|
+
for (const item of queued) {
|
|
2175
|
+
item.cancel(new ModbusError(ModbusErrorCode.MASTER_CLOSED, 'Master closed'));
|
|
2176
|
+
}
|
|
2177
|
+
this._masterSession.stopAll(new ModbusError(ModbusErrorCode.MASTER_CLOSED, 'Master closed'));
|
|
1459
2178
|
return this.physicalLayer.close();
|
|
1460
2179
|
}
|
|
1461
2180
|
destroy() {
|
|
2181
|
+
this._closed = true;
|
|
2182
|
+
const queued = this._queue.splice(0);
|
|
2183
|
+
for (const item of queued) {
|
|
2184
|
+
item.cancel(new ModbusError(ModbusErrorCode.MASTER_DESTROYED, 'Master destroyed'));
|
|
2185
|
+
}
|
|
2186
|
+
this._masterSession.stopAll(new ModbusError(ModbusErrorCode.MASTER_DESTROYED, 'Master destroyed'));
|
|
1462
2187
|
this.removeAllListeners();
|
|
1463
2188
|
this.applicationLayer.destroy();
|
|
1464
2189
|
return this.physicalLayer.destroy();
|
|
1465
2190
|
}
|
|
1466
2191
|
}
|
|
1467
2192
|
|
|
1468
|
-
/******************************************************************************
|
|
1469
|
-
Copyright (c) Microsoft Corporation.
|
|
1470
|
-
|
|
1471
|
-
Permission to use, copy, modify, and/or distribute this software for any
|
|
1472
|
-
purpose with or without fee is hereby granted.
|
|
1473
|
-
|
|
1474
|
-
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
1475
|
-
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
1476
|
-
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
1477
|
-
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
1478
|
-
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
1479
|
-
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
1480
|
-
PERFORMANCE OF THIS SOFTWARE.
|
|
1481
|
-
***************************************************************************** */
|
|
1482
|
-
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
function __awaiter(thisArg, _arguments, P, generator) {
|
|
1486
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
1487
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
1488
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
1489
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
1490
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
1491
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
1492
|
-
});
|
|
1493
|
-
}
|
|
1494
|
-
|
|
1495
|
-
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
1496
|
-
var e = new Error(message);
|
|
1497
|
-
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
1498
|
-
};
|
|
1499
|
-
|
|
1500
2193
|
class ModbusSlave extends EventEmitter {
|
|
1501
2194
|
get isOpen() {
|
|
1502
2195
|
return this.physicalLayer.isOpen;
|
|
@@ -1504,7 +2197,8 @@ class ModbusSlave extends EventEmitter {
|
|
|
1504
2197
|
get destroyed() {
|
|
1505
2198
|
return this.physicalLayer.destroyed;
|
|
1506
2199
|
}
|
|
1507
|
-
constructor(applicationLayer, physicalLayer) {
|
|
2200
|
+
constructor(applicationLayer, physicalLayer, options = {}) {
|
|
2201
|
+
var _a;
|
|
1508
2202
|
super();
|
|
1509
2203
|
Object.defineProperty(this, "applicationLayer", {
|
|
1510
2204
|
enumerable: true,
|
|
@@ -1524,583 +2218,746 @@ class ModbusSlave extends EventEmitter {
|
|
|
1524
2218
|
writable: true,
|
|
1525
2219
|
value: new Map()
|
|
1526
2220
|
});
|
|
1527
|
-
|
|
2221
|
+
Object.defineProperty(this, "concurrent", {
|
|
2222
|
+
enumerable: true,
|
|
2223
|
+
configurable: true,
|
|
2224
|
+
writable: true,
|
|
2225
|
+
value: void 0
|
|
2226
|
+
});
|
|
2227
|
+
Object.defineProperty(this, "_queues", {
|
|
2228
|
+
enumerable: true,
|
|
2229
|
+
configurable: true,
|
|
2230
|
+
writable: true,
|
|
2231
|
+
value: new Map()
|
|
2232
|
+
});
|
|
2233
|
+
Object.defineProperty(this, "_customFunctionCodes", {
|
|
2234
|
+
enumerable: true,
|
|
2235
|
+
configurable: true,
|
|
2236
|
+
writable: true,
|
|
2237
|
+
value: new Map()
|
|
2238
|
+
});
|
|
2239
|
+
Object.defineProperty(this, "_locks", {
|
|
2240
|
+
enumerable: true,
|
|
2241
|
+
configurable: true,
|
|
2242
|
+
writable: true,
|
|
2243
|
+
value: new Map()
|
|
2244
|
+
});
|
|
2245
|
+
this.concurrent = (_a = options.concurrent) !== null && _a !== void 0 ? _a : false;
|
|
2246
|
+
if (this.concurrent && this.applicationLayer.PROTOCOL !== 'TCP') {
|
|
2247
|
+
throw new ModbusError(ModbusErrorCode.CONCURRENT_NOT_TCP, 'concurrent mode requires a Modbus TCP application layer');
|
|
2248
|
+
}
|
|
2249
|
+
this.applicationLayer.role = 'SLAVE';
|
|
2250
|
+
applicationLayer.on('framing', (frame, response, connection) => {
|
|
1528
2251
|
if (!(frame.unit === 0x00 || this.models.has(frame.unit))) {
|
|
1529
2252
|
return;
|
|
1530
2253
|
}
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
}
|
|
1538
|
-
|
|
1539
|
-
}
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
}
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
this.handleFC17(model, frame, response);
|
|
1591
|
-
break;
|
|
1592
|
-
}
|
|
1593
|
-
case 0x16: {
|
|
1594
|
-
this.handleFC22(model, frame, response);
|
|
1595
|
-
break;
|
|
1596
|
-
}
|
|
1597
|
-
case 0x17: {
|
|
1598
|
-
this.handleFC23(model, frame, response);
|
|
1599
|
-
break;
|
|
1600
|
-
}
|
|
1601
|
-
case 0x2b: {
|
|
1602
|
-
this.handleFC43_14(model, frame, response);
|
|
1603
|
-
break;
|
|
1604
|
-
}
|
|
1605
|
-
default: {
|
|
1606
|
-
this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
1607
|
-
break;
|
|
1608
|
-
}
|
|
1609
|
-
}
|
|
1610
|
-
};
|
|
1611
|
-
for (const model of frame.unit === 0x00 ? this.models.values() : [this.models.get(frame.unit)]) {
|
|
1612
|
-
intercept(model).then((res) => {
|
|
1613
|
-
if (res !== 'break') {
|
|
1614
|
-
handleFC(model);
|
|
2254
|
+
if (this.concurrent) {
|
|
2255
|
+
this._processFrame(frame, response).catch((error) => this.emit('error', error));
|
|
2256
|
+
return;
|
|
2257
|
+
}
|
|
2258
|
+
let q = this._queues.get(connection.id);
|
|
2259
|
+
if (!q) {
|
|
2260
|
+
q = { items: [], processing: false };
|
|
2261
|
+
this._queues.set(connection.id, q);
|
|
2262
|
+
}
|
|
2263
|
+
q.items.push({ frame, response });
|
|
2264
|
+
this._drain(connection.id, q);
|
|
2265
|
+
});
|
|
2266
|
+
physicalLayer.on('error', (error) => {
|
|
2267
|
+
this.emit('error', error);
|
|
2268
|
+
});
|
|
2269
|
+
physicalLayer.on('connection-close', (connection) => {
|
|
2270
|
+
const q = this._queues.get(connection.id);
|
|
2271
|
+
if (!q) {
|
|
2272
|
+
return;
|
|
2273
|
+
}
|
|
2274
|
+
q.items.length = 0;
|
|
2275
|
+
if (!q.processing) {
|
|
2276
|
+
this._queues.delete(connection.id);
|
|
2277
|
+
}
|
|
2278
|
+
});
|
|
2279
|
+
physicalLayer.on('close', () => {
|
|
2280
|
+
for (const q of this._queues.values()) {
|
|
2281
|
+
q.items.length = 0;
|
|
2282
|
+
}
|
|
2283
|
+
this._queues.clear();
|
|
2284
|
+
this.emit('close');
|
|
2285
|
+
});
|
|
2286
|
+
}
|
|
2287
|
+
handleFC1(model, frame, response) {
|
|
2288
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
2289
|
+
var _a;
|
|
2290
|
+
if (frame.data.length !== 4) {
|
|
2291
|
+
return;
|
|
2292
|
+
}
|
|
2293
|
+
if (!model.readCoils) {
|
|
2294
|
+
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
2295
|
+
return;
|
|
2296
|
+
}
|
|
2297
|
+
const address = (frame.data[0] << 8) | frame.data[1];
|
|
2298
|
+
const length = (frame.data[2] << 8) | frame.data[3];
|
|
2299
|
+
if (length < LIMITS.READ_COILS_MIN || length > LIMITS.READ_COILS_MAX) {
|
|
2300
|
+
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2301
|
+
return;
|
|
2302
|
+
}
|
|
2303
|
+
if (!checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).coils)) {
|
|
2304
|
+
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
2305
|
+
return;
|
|
2306
|
+
}
|
|
2307
|
+
try {
|
|
2308
|
+
const coils = yield model.readCoils(address, length);
|
|
2309
|
+
const bufferTx = Buffer.alloc(Math.ceil(length / 8));
|
|
2310
|
+
coils.forEach((coil, index) => {
|
|
2311
|
+
if (coil) {
|
|
2312
|
+
bufferTx[Math.floor(index / 8)] |= 1 << index % 8;
|
|
1615
2313
|
}
|
|
1616
2314
|
});
|
|
2315
|
+
yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: Buffer.concat([Buffer.from([bufferTx.length]), bufferTx]) })));
|
|
2316
|
+
}
|
|
2317
|
+
catch (error) {
|
|
2318
|
+
yield this.responseError(frame, response, error);
|
|
1617
2319
|
}
|
|
1618
|
-
});
|
|
1619
|
-
physicalLayer.on('error', (error) => {
|
|
1620
|
-
this.emit('error', error);
|
|
1621
|
-
});
|
|
1622
|
-
physicalLayer.on('close', () => {
|
|
1623
|
-
this.emit('close');
|
|
1624
2320
|
});
|
|
1625
2321
|
}
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
if (
|
|
1630
|
-
|
|
1631
|
-
const address = bufferRx.readUInt16BE(0);
|
|
1632
|
-
const length = bufferRx.readUInt16BE(2);
|
|
1633
|
-
if (length >= 0x0001 && length <= 0x07d0) {
|
|
1634
|
-
if (checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).coils)) {
|
|
1635
|
-
Promise.resolve(model.readCoils(address, length))
|
|
1636
|
-
.then((coils) => {
|
|
1637
|
-
const bufferTx = Buffer.alloc(Math.ceil(length / 8));
|
|
1638
|
-
coils.forEach((coil, index) => {
|
|
1639
|
-
if (coil) {
|
|
1640
|
-
bufferTx[~~(index / 8)] |= 1 << index % 8;
|
|
1641
|
-
}
|
|
1642
|
-
});
|
|
1643
|
-
response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: [bufferTx.length].concat(Array.from(bufferTx)) })));
|
|
1644
|
-
})
|
|
1645
|
-
.catch((error) => {
|
|
1646
|
-
this.responseError(frame, response, error);
|
|
1647
|
-
});
|
|
1648
|
-
}
|
|
1649
|
-
else {
|
|
1650
|
-
this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
1651
|
-
}
|
|
1652
|
-
}
|
|
1653
|
-
else {
|
|
1654
|
-
this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
1655
|
-
}
|
|
2322
|
+
handleFC2(model, frame, response) {
|
|
2323
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
2324
|
+
var _a;
|
|
2325
|
+
if (frame.data.length !== 4) {
|
|
2326
|
+
return;
|
|
1656
2327
|
}
|
|
1657
|
-
|
|
1658
|
-
this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
2328
|
+
if (!model.readDiscreteInputs) {
|
|
2329
|
+
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
2330
|
+
return;
|
|
1659
2331
|
}
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
bufferTx[~~(index / 8)] |= 1 << index % 8;
|
|
1677
|
-
}
|
|
1678
|
-
});
|
|
1679
|
-
response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: [bufferTx.length].concat(Array.from(bufferTx)) })));
|
|
1680
|
-
})
|
|
1681
|
-
.catch((error) => {
|
|
1682
|
-
this.responseError(frame, response, error);
|
|
1683
|
-
});
|
|
1684
|
-
}
|
|
1685
|
-
else {
|
|
1686
|
-
this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
2332
|
+
const address = (frame.data[0] << 8) | frame.data[1];
|
|
2333
|
+
const length = (frame.data[2] << 8) | frame.data[3];
|
|
2334
|
+
if (length < LIMITS.READ_COILS_MIN || length > LIMITS.READ_COILS_MAX) {
|
|
2335
|
+
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2336
|
+
return;
|
|
2337
|
+
}
|
|
2338
|
+
if (!checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).discreteInputs)) {
|
|
2339
|
+
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
2340
|
+
return;
|
|
2341
|
+
}
|
|
2342
|
+
try {
|
|
2343
|
+
const discreteInputs = yield model.readDiscreteInputs(address, length);
|
|
2344
|
+
const bufferTx = Buffer.alloc(Math.ceil(length / 8));
|
|
2345
|
+
discreteInputs.forEach((discreteInput, index) => {
|
|
2346
|
+
if (discreteInput) {
|
|
2347
|
+
bufferTx[Math.floor(index / 8)] |= 1 << index % 8;
|
|
1687
2348
|
}
|
|
1688
|
-
}
|
|
1689
|
-
|
|
1690
|
-
this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
1691
|
-
}
|
|
2349
|
+
});
|
|
2350
|
+
yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: Buffer.concat([Buffer.from([bufferTx.length]), bufferTx]) })));
|
|
1692
2351
|
}
|
|
1693
|
-
|
|
1694
|
-
this.responseError(frame, response,
|
|
2352
|
+
catch (error) {
|
|
2353
|
+
yield this.responseError(frame, response, error);
|
|
1695
2354
|
}
|
|
1696
|
-
}
|
|
2355
|
+
});
|
|
1697
2356
|
}
|
|
1698
2357
|
handleFC3(model, frame, response) {
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
if (
|
|
1702
|
-
|
|
1703
|
-
const address = bufferRx.readUInt16BE(0);
|
|
1704
|
-
const length = bufferRx.readUInt16BE(2);
|
|
1705
|
-
if (length >= 0x0001 && length <= 0x007d) {
|
|
1706
|
-
if (checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).holdingRegisters)) {
|
|
1707
|
-
Promise.resolve(model.readHoldingRegisters(address, length))
|
|
1708
|
-
.then((registers) => {
|
|
1709
|
-
const bufferTx = Buffer.alloc(length * 2);
|
|
1710
|
-
registers.forEach((register, index) => {
|
|
1711
|
-
bufferTx.writeUInt16BE(register, index * 2);
|
|
1712
|
-
});
|
|
1713
|
-
response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: [bufferTx.length].concat(Array.from(bufferTx)) })));
|
|
1714
|
-
})
|
|
1715
|
-
.catch((error) => {
|
|
1716
|
-
this.responseError(frame, response, error);
|
|
1717
|
-
});
|
|
1718
|
-
}
|
|
1719
|
-
else {
|
|
1720
|
-
this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
1721
|
-
}
|
|
1722
|
-
}
|
|
1723
|
-
else {
|
|
1724
|
-
this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
1725
|
-
}
|
|
2358
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
2359
|
+
var _a;
|
|
2360
|
+
if (frame.data.length !== 4) {
|
|
2361
|
+
return;
|
|
1726
2362
|
}
|
|
1727
|
-
|
|
1728
|
-
this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
2363
|
+
if (!model.readHoldingRegisters) {
|
|
2364
|
+
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
2365
|
+
return;
|
|
1729
2366
|
}
|
|
1730
|
-
|
|
2367
|
+
const address = (frame.data[0] << 8) | frame.data[1];
|
|
2368
|
+
const length = (frame.data[2] << 8) | frame.data[3];
|
|
2369
|
+
if (length < LIMITS.READ_REGISTERS_MIN || length > LIMITS.READ_REGISTERS_MAX) {
|
|
2370
|
+
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2371
|
+
return;
|
|
2372
|
+
}
|
|
2373
|
+
if (!checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).holdingRegisters)) {
|
|
2374
|
+
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
2375
|
+
return;
|
|
2376
|
+
}
|
|
2377
|
+
try {
|
|
2378
|
+
const registers = yield model.readHoldingRegisters(address, length);
|
|
2379
|
+
const bufferTx = Buffer.alloc(length * 2);
|
|
2380
|
+
registers.forEach((register, index) => {
|
|
2381
|
+
bufferTx.writeUInt16BE(register, index * 2);
|
|
2382
|
+
});
|
|
2383
|
+
yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: Buffer.concat([Buffer.from([bufferTx.length]), bufferTx]) })));
|
|
2384
|
+
}
|
|
2385
|
+
catch (error) {
|
|
2386
|
+
yield this.responseError(frame, response, error);
|
|
2387
|
+
}
|
|
2388
|
+
});
|
|
1731
2389
|
}
|
|
1732
2390
|
handleFC4(model, frame, response) {
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
if (
|
|
1736
|
-
|
|
1737
|
-
const address = bufferRx.readUInt16BE(0);
|
|
1738
|
-
const length = bufferRx.readUInt16BE(2);
|
|
1739
|
-
if (length >= 0x0001 && length <= 0x007d) {
|
|
1740
|
-
if (checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).inputRegisters)) {
|
|
1741
|
-
Promise.resolve(model.readInputRegisters(address, length))
|
|
1742
|
-
.then((registers) => {
|
|
1743
|
-
const bufferTx = Buffer.alloc(length * 2);
|
|
1744
|
-
registers.forEach((register, index) => {
|
|
1745
|
-
bufferTx.writeUInt16BE(register, index * 2);
|
|
1746
|
-
});
|
|
1747
|
-
response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: [bufferTx.length].concat(Array.from(bufferTx)) })));
|
|
1748
|
-
})
|
|
1749
|
-
.catch((error) => {
|
|
1750
|
-
this.responseError(frame, response, error);
|
|
1751
|
-
});
|
|
1752
|
-
}
|
|
1753
|
-
else {
|
|
1754
|
-
this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
1755
|
-
}
|
|
1756
|
-
}
|
|
1757
|
-
else {
|
|
1758
|
-
this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
1759
|
-
}
|
|
2391
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
2392
|
+
var _a;
|
|
2393
|
+
if (frame.data.length !== 4) {
|
|
2394
|
+
return;
|
|
1760
2395
|
}
|
|
1761
|
-
|
|
1762
|
-
this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
2396
|
+
if (!model.readInputRegisters) {
|
|
2397
|
+
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
2398
|
+
return;
|
|
1763
2399
|
}
|
|
1764
|
-
|
|
2400
|
+
const address = (frame.data[0] << 8) | frame.data[1];
|
|
2401
|
+
const length = (frame.data[2] << 8) | frame.data[3];
|
|
2402
|
+
if (length < LIMITS.READ_REGISTERS_MIN || length > LIMITS.READ_REGISTERS_MAX) {
|
|
2403
|
+
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2404
|
+
return;
|
|
2405
|
+
}
|
|
2406
|
+
if (!checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).inputRegisters)) {
|
|
2407
|
+
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
2408
|
+
return;
|
|
2409
|
+
}
|
|
2410
|
+
try {
|
|
2411
|
+
const registers = yield model.readInputRegisters(address, length);
|
|
2412
|
+
const bufferTx = Buffer.alloc(length * 2);
|
|
2413
|
+
registers.forEach((register, index) => {
|
|
2414
|
+
bufferTx.writeUInt16BE(register, index * 2);
|
|
2415
|
+
});
|
|
2416
|
+
yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: Buffer.concat([Buffer.from([bufferTx.length]), bufferTx]) })));
|
|
2417
|
+
}
|
|
2418
|
+
catch (error) {
|
|
2419
|
+
yield this.responseError(frame, response, error);
|
|
2420
|
+
}
|
|
2421
|
+
});
|
|
1765
2422
|
}
|
|
1766
2423
|
handleFC5(model, frame, response) {
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
if (
|
|
1770
|
-
|
|
1771
|
-
const address = bufferRx.readUInt16BE(0);
|
|
1772
|
-
const value = bufferRx.readUInt16BE(2);
|
|
1773
|
-
if (value === 0x0000 || value === 0xff00) {
|
|
1774
|
-
if (checkRange(address, (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).coils)) {
|
|
1775
|
-
Promise.resolve(model.writeSingleCoil(address, value === 0xff00))
|
|
1776
|
-
.then(() => {
|
|
1777
|
-
response(this.applicationLayer.encode(frame));
|
|
1778
|
-
})
|
|
1779
|
-
.catch((error) => {
|
|
1780
|
-
this.responseError(frame, response, error);
|
|
1781
|
-
});
|
|
1782
|
-
}
|
|
1783
|
-
else {
|
|
1784
|
-
this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
1785
|
-
}
|
|
1786
|
-
}
|
|
1787
|
-
else {
|
|
1788
|
-
this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
1789
|
-
}
|
|
2424
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
2425
|
+
var _a;
|
|
2426
|
+
if (frame.data.length !== 4) {
|
|
2427
|
+
return;
|
|
1790
2428
|
}
|
|
1791
|
-
|
|
1792
|
-
this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
2429
|
+
if (!model.writeSingleCoil) {
|
|
2430
|
+
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
2431
|
+
return;
|
|
1793
2432
|
}
|
|
1794
|
-
|
|
2433
|
+
const address = (frame.data[0] << 8) | frame.data[1];
|
|
2434
|
+
const value = (frame.data[2] << 8) | frame.data[3];
|
|
2435
|
+
if (value !== COIL_OFF && value !== COIL_ON) {
|
|
2436
|
+
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2437
|
+
return;
|
|
2438
|
+
}
|
|
2439
|
+
if (!checkRange(address, (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).coils)) {
|
|
2440
|
+
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
2441
|
+
return;
|
|
2442
|
+
}
|
|
2443
|
+
try {
|
|
2444
|
+
yield model.writeSingleCoil(address, value === COIL_ON);
|
|
2445
|
+
yield response(this.applicationLayer.encode(frame));
|
|
2446
|
+
}
|
|
2447
|
+
catch (error) {
|
|
2448
|
+
yield this.responseError(frame, response, error);
|
|
2449
|
+
}
|
|
2450
|
+
});
|
|
1795
2451
|
}
|
|
1796
2452
|
handleFC6(model, frame, response) {
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
if (
|
|
1800
|
-
|
|
1801
|
-
const address = bufferRx.readUInt16BE(0);
|
|
1802
|
-
const value = bufferRx.readUInt16BE(2);
|
|
1803
|
-
if (value >= 0x0000 && value <= 0xffff) {
|
|
1804
|
-
if (checkRange(address, (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).holdingRegisters)) {
|
|
1805
|
-
Promise.resolve(model.writeSingleRegister(address, value))
|
|
1806
|
-
.then(() => {
|
|
1807
|
-
response(this.applicationLayer.encode(frame));
|
|
1808
|
-
})
|
|
1809
|
-
.catch((error) => {
|
|
1810
|
-
this.responseError(frame, response, error);
|
|
1811
|
-
});
|
|
1812
|
-
}
|
|
1813
|
-
else {
|
|
1814
|
-
this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
1815
|
-
}
|
|
1816
|
-
}
|
|
1817
|
-
else {
|
|
1818
|
-
this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
1819
|
-
}
|
|
2453
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
2454
|
+
var _a;
|
|
2455
|
+
if (frame.data.length !== 4) {
|
|
2456
|
+
return;
|
|
1820
2457
|
}
|
|
1821
|
-
|
|
1822
|
-
this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
2458
|
+
if (!model.writeSingleRegister) {
|
|
2459
|
+
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
2460
|
+
return;
|
|
1823
2461
|
}
|
|
1824
|
-
|
|
2462
|
+
const address = (frame.data[0] << 8) | frame.data[1];
|
|
2463
|
+
const value = (frame.data[2] << 8) | frame.data[3];
|
|
2464
|
+
if (!checkRange(address, (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).holdingRegisters)) {
|
|
2465
|
+
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
2466
|
+
return;
|
|
2467
|
+
}
|
|
2468
|
+
try {
|
|
2469
|
+
yield model.writeSingleRegister(address, value);
|
|
2470
|
+
yield response(this.applicationLayer.encode(frame));
|
|
2471
|
+
}
|
|
2472
|
+
catch (error) {
|
|
2473
|
+
yield this.responseError(frame, response, error);
|
|
2474
|
+
}
|
|
2475
|
+
});
|
|
1825
2476
|
}
|
|
1826
2477
|
handleFC15(model, frame, response) {
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
if (
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
2478
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
2479
|
+
var _a;
|
|
2480
|
+
if (frame.data.length <= 5 || frame.data.length !== 5 + frame.data[4]) {
|
|
2481
|
+
return;
|
|
2482
|
+
}
|
|
2483
|
+
if (!model.writeMultipleCoils && !model.writeSingleCoil) {
|
|
2484
|
+
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
2485
|
+
return;
|
|
2486
|
+
}
|
|
2487
|
+
const address = (frame.data[0] << 8) | frame.data[1];
|
|
2488
|
+
const length = (frame.data[2] << 8) | frame.data[3];
|
|
2489
|
+
const byteCount = frame.data[4];
|
|
2490
|
+
if (length < LIMITS.READ_COILS_MIN || length > LIMITS.WRITE_COILS_MAX || byteCount !== Math.ceil(length / 8)) {
|
|
2491
|
+
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2492
|
+
return;
|
|
2493
|
+
}
|
|
2494
|
+
if (!checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).coils)) {
|
|
2495
|
+
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
2496
|
+
return;
|
|
2497
|
+
}
|
|
2498
|
+
const value = new Array(length);
|
|
2499
|
+
for (let i = 0; i < length; i++) {
|
|
2500
|
+
value[i] = (frame.data[5 + Math.floor(i / 8)] & (1 << i % 8)) > 0;
|
|
2501
|
+
}
|
|
2502
|
+
try {
|
|
2503
|
+
if (model.writeMultipleCoils) {
|
|
2504
|
+
yield model.writeMultipleCoils(address, value);
|
|
1850
2505
|
}
|
|
1851
2506
|
else {
|
|
1852
|
-
|
|
2507
|
+
for (let i = 0; i < value.length; i++) {
|
|
2508
|
+
yield model.writeSingleCoil(address + i, value[i]);
|
|
2509
|
+
}
|
|
1853
2510
|
}
|
|
2511
|
+
yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: frame.data.subarray(0, 4) })));
|
|
1854
2512
|
}
|
|
1855
|
-
|
|
1856
|
-
this.responseError(frame, response,
|
|
2513
|
+
catch (error) {
|
|
2514
|
+
yield this.responseError(frame, response, error);
|
|
1857
2515
|
}
|
|
1858
|
-
}
|
|
2516
|
+
});
|
|
1859
2517
|
}
|
|
1860
2518
|
handleFC16(model, frame, response) {
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
if (
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
2519
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
2520
|
+
var _a;
|
|
2521
|
+
if (frame.data.length <= 5 || frame.data.length !== 5 + frame.data[4]) {
|
|
2522
|
+
return;
|
|
2523
|
+
}
|
|
2524
|
+
if (!model.writeMultipleRegisters && !model.writeSingleRegister) {
|
|
2525
|
+
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
2526
|
+
return;
|
|
2527
|
+
}
|
|
2528
|
+
const address = (frame.data[0] << 8) | frame.data[1];
|
|
2529
|
+
const length = (frame.data[2] << 8) | frame.data[3];
|
|
2530
|
+
const byteCount = frame.data[4];
|
|
2531
|
+
if (length < LIMITS.READ_REGISTERS_MIN || length > LIMITS.WRITE_REGISTERS_MAX || byteCount !== length * 2) {
|
|
2532
|
+
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2533
|
+
return;
|
|
2534
|
+
}
|
|
2535
|
+
if (!checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).holdingRegisters)) {
|
|
2536
|
+
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
2537
|
+
return;
|
|
2538
|
+
}
|
|
2539
|
+
const value = new Array(length);
|
|
2540
|
+
for (let i = 0; i < length; i++) {
|
|
2541
|
+
value[i] = (frame.data[5 + i * 2] << 8) | frame.data[6 + i * 2];
|
|
2542
|
+
}
|
|
2543
|
+
try {
|
|
2544
|
+
if (model.writeMultipleRegisters) {
|
|
2545
|
+
yield model.writeMultipleRegisters(address, value);
|
|
1884
2546
|
}
|
|
1885
2547
|
else {
|
|
1886
|
-
|
|
2548
|
+
for (let i = 0; i < value.length; i++) {
|
|
2549
|
+
yield model.writeSingleRegister(address + i, value[i]);
|
|
2550
|
+
}
|
|
1887
2551
|
}
|
|
2552
|
+
yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: frame.data.subarray(0, 4) })));
|
|
1888
2553
|
}
|
|
1889
|
-
|
|
1890
|
-
this.responseError(frame, response,
|
|
2554
|
+
catch (error) {
|
|
2555
|
+
yield this.responseError(frame, response, error);
|
|
1891
2556
|
}
|
|
1892
|
-
}
|
|
2557
|
+
});
|
|
1893
2558
|
}
|
|
1894
2559
|
handleFC17(model, frame, response) {
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
var _b;
|
|
1900
|
-
var { serverId = (_b = model.unit) !== null && _b !== void 0 ? _b : 1, runIndicatorStatus = true, additionalData = [] } = _a;
|
|
1901
|
-
response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: [2 + additionalData.length, serverId, runIndicatorStatus ? 0xff : 0x00].concat(additionalData) })));
|
|
1902
|
-
})
|
|
1903
|
-
.catch((error) => {
|
|
1904
|
-
this.responseError(frame, response, error);
|
|
1905
|
-
});
|
|
2560
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
2561
|
+
var _a;
|
|
2562
|
+
if (frame.data.length !== 0) {
|
|
2563
|
+
return;
|
|
1906
2564
|
}
|
|
1907
|
-
|
|
1908
|
-
this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
2565
|
+
if (!model.reportServerId) {
|
|
2566
|
+
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
2567
|
+
return;
|
|
1909
2568
|
}
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
const address = bufferRx.readUInt16BE(0);
|
|
1918
|
-
const andMask = bufferRx.readUInt16BE(2);
|
|
1919
|
-
const orMask = bufferRx.readUInt16BE(4);
|
|
1920
|
-
if (checkRange(address, (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).holdingRegisters)) {
|
|
1921
|
-
Promise.resolve(model.maskWriteRegister
|
|
1922
|
-
? model.maskWriteRegister(address, andMask, orMask)
|
|
1923
|
-
: Promise.resolve(model.readHoldingRegisters(address, 1)).then(([value]) => {
|
|
1924
|
-
return Promise.resolve(model.writeSingleRegister(address, (value & andMask) | (orMask & (~andMask & 0xff))));
|
|
1925
|
-
}))
|
|
1926
|
-
.then(() => {
|
|
1927
|
-
response(this.applicationLayer.encode(frame));
|
|
1928
|
-
})
|
|
1929
|
-
.catch((error) => {
|
|
1930
|
-
this.responseError(frame, response, error);
|
|
1931
|
-
});
|
|
2569
|
+
try {
|
|
2570
|
+
const { serverId = (_a = model.unit) !== null && _a !== void 0 ? _a : 1, runIndicatorStatus = true, additionalData = [] } = yield model.reportServerId();
|
|
2571
|
+
const serverIdBytes = Array.isArray(serverId) ? serverId : [serverId];
|
|
2572
|
+
const byteCount = serverIdBytes.length + 1 + additionalData.length;
|
|
2573
|
+
if (byteCount > 255) {
|
|
2574
|
+
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.SERVER_DEVICE_FAILURE));
|
|
2575
|
+
return;
|
|
1932
2576
|
}
|
|
1933
|
-
|
|
1934
|
-
|
|
2577
|
+
const allBytes = [...serverIdBytes, runIndicatorStatus ? 0xff : 0x00, ...additionalData];
|
|
2578
|
+
if (allBytes.some((b) => !isUint8(b))) {
|
|
2579
|
+
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.SERVER_DEVICE_FAILURE));
|
|
2580
|
+
return;
|
|
1935
2581
|
}
|
|
2582
|
+
yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: Buffer.concat([Buffer.from([byteCount]), Buffer.from(allBytes)]) })));
|
|
1936
2583
|
}
|
|
1937
|
-
|
|
1938
|
-
this.responseError(frame, response,
|
|
2584
|
+
catch (error) {
|
|
2585
|
+
yield this.responseError(frame, response, error);
|
|
1939
2586
|
}
|
|
1940
|
-
}
|
|
2587
|
+
});
|
|
1941
2588
|
}
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
if (
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
if (
|
|
1962
|
-
|
|
1963
|
-
Promise.resolve(model.writeMultipleRegisters
|
|
1964
|
-
? model.writeMultipleRegisters(address.write, value)
|
|
1965
|
-
: Promise.all(value.map((v, i) => model.writeSingleRegister(address.write + i, v))))
|
|
1966
|
-
.then(() => Promise.resolve(model.readHoldingRegisters(address.read, length.read)))
|
|
1967
|
-
.then((registers) => {
|
|
1968
|
-
const bufferTx = Buffer.alloc(length.read * 2);
|
|
1969
|
-
registers.forEach((register, index) => {
|
|
1970
|
-
bufferTx.writeUInt16BE(register, index * 2);
|
|
1971
|
-
});
|
|
1972
|
-
response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: [bufferTx.length].concat(Array.from(bufferTx)) })));
|
|
1973
|
-
})
|
|
1974
|
-
.catch((error) => {
|
|
1975
|
-
this.responseError(frame, response, error);
|
|
1976
|
-
});
|
|
2589
|
+
handleFC22(model, frame, response) {
|
|
2590
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
2591
|
+
var _a;
|
|
2592
|
+
if (frame.data.length !== 6) {
|
|
2593
|
+
return;
|
|
2594
|
+
}
|
|
2595
|
+
if (!model.maskWriteRegister && !(model.readHoldingRegisters && model.writeSingleRegister)) {
|
|
2596
|
+
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
2597
|
+
return;
|
|
2598
|
+
}
|
|
2599
|
+
const address = (frame.data[0] << 8) | frame.data[1];
|
|
2600
|
+
const andMask = (frame.data[2] << 8) | frame.data[3];
|
|
2601
|
+
const orMask = (frame.data[4] << 8) | frame.data[5];
|
|
2602
|
+
if (!checkRange(address, (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).holdingRegisters)) {
|
|
2603
|
+
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
2604
|
+
return;
|
|
2605
|
+
}
|
|
2606
|
+
try {
|
|
2607
|
+
yield this.withAddressLock([address], () => __awaiter(this, void 0, void 0, function* () {
|
|
2608
|
+
if (model.maskWriteRegister) {
|
|
2609
|
+
yield model.maskWriteRegister(address, andMask, orMask);
|
|
1977
2610
|
}
|
|
1978
2611
|
else {
|
|
1979
|
-
|
|
2612
|
+
const [value] = yield model.readHoldingRegisters(address, 1);
|
|
2613
|
+
yield model.writeSingleRegister(address, (value & andMask) | (orMask & (~andMask & 0xffff)));
|
|
1980
2614
|
}
|
|
1981
|
-
}
|
|
1982
|
-
|
|
1983
|
-
this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
1984
|
-
}
|
|
2615
|
+
}));
|
|
2616
|
+
yield response(this.applicationLayer.encode(frame));
|
|
1985
2617
|
}
|
|
1986
|
-
|
|
1987
|
-
this.responseError(frame, response,
|
|
2618
|
+
catch (error) {
|
|
2619
|
+
yield this.responseError(frame, response, error);
|
|
1988
2620
|
}
|
|
1989
|
-
}
|
|
2621
|
+
});
|
|
1990
2622
|
}
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2623
|
+
handleFC23(model, frame, response) {
|
|
2624
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
2625
|
+
var _a;
|
|
2626
|
+
if (frame.data.length <= 9 || frame.data.length !== 9 + frame.data[8]) {
|
|
2627
|
+
return;
|
|
2628
|
+
}
|
|
2629
|
+
if (!model.readHoldingRegisters || (!model.writeMultipleRegisters && !model.writeSingleRegister)) {
|
|
2630
|
+
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
2631
|
+
return;
|
|
2632
|
+
}
|
|
2633
|
+
const address = {
|
|
2634
|
+
read: (frame.data[0] << 8) | frame.data[1],
|
|
2635
|
+
write: (frame.data[4] << 8) | frame.data[5],
|
|
2636
|
+
};
|
|
2637
|
+
const length = {
|
|
2638
|
+
read: (frame.data[2] << 8) | frame.data[3],
|
|
2639
|
+
write: (frame.data[6] << 8) | frame.data[7],
|
|
2640
|
+
};
|
|
2641
|
+
const byteCount = frame.data[8];
|
|
2642
|
+
if (length.read < LIMITS.READ_REGISTERS_MIN ||
|
|
2643
|
+
length.read > LIMITS.READ_REGISTERS_MAX ||
|
|
2644
|
+
length.write < LIMITS.READ_REGISTERS_MIN ||
|
|
2645
|
+
length.write > LIMITS.RW_REGISTERS_WRITE_MAX ||
|
|
2646
|
+
byteCount !== length.write * 2) {
|
|
2647
|
+
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2648
|
+
return;
|
|
2649
|
+
}
|
|
2650
|
+
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)) {
|
|
2651
|
+
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
2652
|
+
return;
|
|
2653
|
+
}
|
|
2654
|
+
const value = new Array(length.write);
|
|
2655
|
+
for (let i = 0; i < length.write; i++) {
|
|
2656
|
+
value[i] = (frame.data[9 + i * 2] << 8) | frame.data[10 + i * 2];
|
|
2657
|
+
}
|
|
2658
|
+
try {
|
|
2659
|
+
const writeAddresses = Array.from({ length: length.write }, (_, i) => address.write + i);
|
|
2660
|
+
yield this.withAddressLock(writeAddresses, () => __awaiter(this, void 0, void 0, function* () {
|
|
2661
|
+
if (model.writeMultipleRegisters) {
|
|
2662
|
+
yield model.writeMultipleRegisters(address.write, value);
|
|
2002
2663
|
}
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2664
|
+
else {
|
|
2665
|
+
for (let i = 0; i < value.length; i++) {
|
|
2666
|
+
yield model.writeSingleRegister(address.write + i, value[i]);
|
|
2006
2667
|
}
|
|
2007
|
-
break;
|
|
2008
2668
|
}
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2669
|
+
}));
|
|
2670
|
+
const registers = yield model.readHoldingRegisters(address.read, length.read);
|
|
2671
|
+
const bufferTx = Buffer.alloc(length.read * 2);
|
|
2672
|
+
registers.forEach((register, index) => {
|
|
2673
|
+
bufferTx.writeUInt16BE(register, index * 2);
|
|
2674
|
+
});
|
|
2675
|
+
yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: Buffer.concat([Buffer.from([bufferTx.length]), bufferTx]) })));
|
|
2676
|
+
}
|
|
2677
|
+
catch (error) {
|
|
2678
|
+
yield this.responseError(frame, response, error);
|
|
2679
|
+
}
|
|
2680
|
+
});
|
|
2681
|
+
}
|
|
2682
|
+
handleFC43_14(model, frame, response) {
|
|
2683
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
2684
|
+
if (frame.data.length === 3) {
|
|
2685
|
+
if (frame.data[0] === MEI_READ_DEVICE_ID && model.readDeviceIdentification) {
|
|
2686
|
+
const readDeviceIDCode = frame.data[1];
|
|
2687
|
+
let objectID = frame.data[2];
|
|
2688
|
+
switch (readDeviceIDCode) {
|
|
2689
|
+
case exports.ReadDeviceIDCode.BASIC_STREAM: {
|
|
2690
|
+
if (objectID > 0x02 || (objectID > 0x06 && objectID < 0x80)) {
|
|
2691
|
+
objectID = 0x00;
|
|
2692
|
+
}
|
|
2693
|
+
break;
|
|
2012
2694
|
}
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
return;
|
|
2695
|
+
case exports.ReadDeviceIDCode.REGULAR_STREAM: {
|
|
2696
|
+
if (objectID >= 0x80 || (objectID > 0x06 && objectID < 0x80)) {
|
|
2697
|
+
objectID = 0x00;
|
|
2698
|
+
}
|
|
2699
|
+
break;
|
|
2019
2700
|
}
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
}
|
|
2026
|
-
}
|
|
2027
|
-
Promise.resolve(model.readDeviceIdentification())
|
|
2028
|
-
.then((identification) => {
|
|
2029
|
-
const objects = new Map([
|
|
2030
|
-
[0x00, 'null'],
|
|
2031
|
-
[0x01, 'null'],
|
|
2032
|
-
[0x02, 'null'],
|
|
2033
|
-
]);
|
|
2034
|
-
for (const [key, value] of Object.entries(identification)) {
|
|
2035
|
-
const id = parseInt(key);
|
|
2036
|
-
if (!isNaN(id) && id >= 0 && id <= 255) {
|
|
2037
|
-
objects.set(id, value);
|
|
2701
|
+
case exports.ReadDeviceIDCode.EXTENDED_STREAM: {
|
|
2702
|
+
if (objectID > 0x06 && objectID < 0x80) {
|
|
2703
|
+
objectID = 0x00;
|
|
2704
|
+
}
|
|
2705
|
+
break;
|
|
2038
2706
|
}
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2707
|
+
case exports.ReadDeviceIDCode.SPECIFIC_ACCESS: {
|
|
2708
|
+
if (objectID > 0x06 && objectID < 0x80) {
|
|
2709
|
+
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
2710
|
+
return;
|
|
2711
|
+
}
|
|
2712
|
+
break;
|
|
2044
2713
|
}
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
const ids = [];
|
|
2048
|
-
let totalLength = 10;
|
|
2049
|
-
let lastID = 0;
|
|
2050
|
-
let conformityLevel = 0x81;
|
|
2051
|
-
for (const [id, value] of objects.entries()) {
|
|
2052
|
-
if (id < 0x00 || (id >= 0x07 && id <= 0x7f) || id > 0xff) {
|
|
2053
|
-
this.responseError(frame, response, getErrorByCode(exports.ErrorCode.SERVER_DEVICE_FAILURE));
|
|
2714
|
+
default: {
|
|
2715
|
+
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
|
|
2054
2716
|
return;
|
|
2055
2717
|
}
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2718
|
+
}
|
|
2719
|
+
try {
|
|
2720
|
+
const identification = yield model.readDeviceIdentification();
|
|
2721
|
+
const objects = new Map();
|
|
2722
|
+
for (const key of Object.keys(identification)) {
|
|
2723
|
+
const id = Number(key);
|
|
2724
|
+
if (id >= 0 && id <= 255) {
|
|
2725
|
+
objects.set(id, identification[id]);
|
|
2726
|
+
}
|
|
2064
2727
|
}
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2728
|
+
for (let id = 0x00; id <= 0x02; id++) {
|
|
2729
|
+
if (!objects.has(id)) {
|
|
2730
|
+
objects.set(id, 'null');
|
|
2731
|
+
}
|
|
2068
2732
|
}
|
|
2069
|
-
if (
|
|
2070
|
-
|
|
2733
|
+
if (!objects.has(objectID)) {
|
|
2734
|
+
if (readDeviceIDCode === exports.ReadDeviceIDCode.SPECIFIC_ACCESS) {
|
|
2735
|
+
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
|
|
2736
|
+
return;
|
|
2737
|
+
}
|
|
2738
|
+
objectID = 0x00;
|
|
2071
2739
|
}
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2740
|
+
let maxId = 0;
|
|
2741
|
+
for (const id of objects.keys()) {
|
|
2742
|
+
if ((id >= 0x07 && id <= 0x7f) || id > 0xff) {
|
|
2743
|
+
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.SERVER_DEVICE_FAILURE));
|
|
2744
|
+
return;
|
|
2745
|
+
}
|
|
2746
|
+
if (id > maxId) {
|
|
2747
|
+
maxId = id;
|
|
2075
2748
|
}
|
|
2076
2749
|
}
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2750
|
+
const conformityLevel = maxId >= 0x80 ? exports.ConformityLevel.EXTENDED : maxId > 0x02 ? exports.ConformityLevel.REGULAR : exports.ConformityLevel.BASIC;
|
|
2751
|
+
const ids = [];
|
|
2752
|
+
let totalLength = 10;
|
|
2753
|
+
let lastID = 0;
|
|
2754
|
+
for (const [id, value] of objects.entries()) {
|
|
2755
|
+
if (objectID > id) {
|
|
2756
|
+
continue;
|
|
2757
|
+
}
|
|
2758
|
+
const byteLength = Buffer.byteLength(value);
|
|
2759
|
+
if (byteLength > 245) {
|
|
2760
|
+
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.SERVER_DEVICE_FAILURE));
|
|
2761
|
+
return;
|
|
2762
|
+
}
|
|
2763
|
+
if (lastID !== 0) {
|
|
2764
|
+
continue;
|
|
2765
|
+
}
|
|
2766
|
+
if (byteLength + 2 > 253 - totalLength) {
|
|
2767
|
+
if (lastID === 0) {
|
|
2768
|
+
lastID = id;
|
|
2769
|
+
}
|
|
2770
|
+
}
|
|
2771
|
+
else {
|
|
2772
|
+
totalLength += byteLength + 2;
|
|
2773
|
+
ids.push(id);
|
|
2774
|
+
if (readDeviceIDCode === exports.ReadDeviceIDCode.SPECIFIC_ACCESS) {
|
|
2775
|
+
break;
|
|
2776
|
+
}
|
|
2082
2777
|
}
|
|
2083
2778
|
}
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2779
|
+
ids.sort((a, b) => a - b);
|
|
2780
|
+
const parts = [];
|
|
2781
|
+
parts.push(Buffer.from([MEI_READ_DEVICE_ID, readDeviceIDCode, conformityLevel, lastID === 0 ? 0x00 : 0xff, lastID, ids.length]));
|
|
2782
|
+
for (const id of ids) {
|
|
2088
2783
|
const value = objects.get(id);
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2784
|
+
parts.push(Buffer.from([id, Buffer.byteLength(value)]));
|
|
2785
|
+
parts.push(Buffer.from(value));
|
|
2786
|
+
}
|
|
2787
|
+
yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: Buffer.concat(parts) })));
|
|
2788
|
+
}
|
|
2789
|
+
catch (error) {
|
|
2790
|
+
yield this.responseError(frame, response, error);
|
|
2791
|
+
}
|
|
2792
|
+
}
|
|
2793
|
+
else {
|
|
2794
|
+
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
2795
|
+
}
|
|
2099
2796
|
}
|
|
2100
|
-
}
|
|
2797
|
+
});
|
|
2101
2798
|
}
|
|
2102
2799
|
responseError(frame, response, error) {
|
|
2103
|
-
|
|
2800
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
2801
|
+
yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { fc: frame.fc | EXCEPTION_OFFSET, data: Buffer.from([getCodeByError(error)]) })));
|
|
2802
|
+
});
|
|
2803
|
+
}
|
|
2804
|
+
_drain(key, q) {
|
|
2805
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
2806
|
+
if (q.processing) {
|
|
2807
|
+
return;
|
|
2808
|
+
}
|
|
2809
|
+
q.processing = true;
|
|
2810
|
+
try {
|
|
2811
|
+
while (q.items.length > 0) {
|
|
2812
|
+
const { frame, response } = q.items.shift();
|
|
2813
|
+
try {
|
|
2814
|
+
yield this._processFrame(frame, response);
|
|
2815
|
+
}
|
|
2816
|
+
catch (error) {
|
|
2817
|
+
this.emit('error', error);
|
|
2818
|
+
}
|
|
2819
|
+
}
|
|
2820
|
+
}
|
|
2821
|
+
finally {
|
|
2822
|
+
q.processing = false;
|
|
2823
|
+
// Cleanup empty queue entries so the map doesn't grow unbounded across
|
|
2824
|
+
// ephemeral connections (UDP rinfo, brief TCP clients).
|
|
2825
|
+
if (q.items.length === 0 && this._queues.get(key) === q) {
|
|
2826
|
+
this._queues.delete(key);
|
|
2827
|
+
}
|
|
2828
|
+
}
|
|
2829
|
+
});
|
|
2830
|
+
}
|
|
2831
|
+
_processFrame(frame, _response) {
|
|
2832
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
2833
|
+
const response = (data) => __awaiter(this, void 0, void 0, function* () {
|
|
2834
|
+
if (frame.unit === 0x00) {
|
|
2835
|
+
return;
|
|
2836
|
+
}
|
|
2837
|
+
try {
|
|
2838
|
+
yield _response(data);
|
|
2839
|
+
}
|
|
2840
|
+
catch (error) { }
|
|
2841
|
+
});
|
|
2842
|
+
for (const model of frame.unit === 0x00 ? this.models.values() : [this.models.get(frame.unit)]) {
|
|
2843
|
+
const intercepted = yield this._intercept(model, frame, response);
|
|
2844
|
+
if (!intercepted) {
|
|
2845
|
+
yield this._handleFC(model, frame, response);
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2848
|
+
});
|
|
2849
|
+
}
|
|
2850
|
+
_intercept(model, frame, response) {
|
|
2851
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
2852
|
+
if (!model.interceptor) {
|
|
2853
|
+
return false;
|
|
2854
|
+
}
|
|
2855
|
+
try {
|
|
2856
|
+
const data = yield model.interceptor(frame.fc, frame.data);
|
|
2857
|
+
if (data !== undefined) {
|
|
2858
|
+
yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data })));
|
|
2859
|
+
return true;
|
|
2860
|
+
}
|
|
2861
|
+
return false;
|
|
2862
|
+
}
|
|
2863
|
+
catch (error) {
|
|
2864
|
+
yield this.responseError(frame, response, error);
|
|
2865
|
+
return true;
|
|
2866
|
+
}
|
|
2867
|
+
});
|
|
2868
|
+
}
|
|
2869
|
+
withAddressLock(addresses, fn) {
|
|
2870
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
2871
|
+
const sorted = [...new Set(addresses)].sort((a, b) => a - b);
|
|
2872
|
+
const previous = sorted.map((addr) => { var _a; return (_a = this._locks.get(addr)) !== null && _a !== void 0 ? _a : Promise.resolve(); });
|
|
2873
|
+
const work = Promise.all(previous).then(() => fn());
|
|
2874
|
+
const cleanup = work.catch(() => {
|
|
2875
|
+
/* ignore */
|
|
2876
|
+
});
|
|
2877
|
+
for (const addr of sorted) {
|
|
2878
|
+
this._locks.set(addr, cleanup);
|
|
2879
|
+
}
|
|
2880
|
+
try {
|
|
2881
|
+
return yield work;
|
|
2882
|
+
}
|
|
2883
|
+
finally {
|
|
2884
|
+
for (const addr of sorted) {
|
|
2885
|
+
if (this._locks.get(addr) === cleanup) {
|
|
2886
|
+
this._locks.delete(addr);
|
|
2887
|
+
}
|
|
2888
|
+
}
|
|
2889
|
+
}
|
|
2890
|
+
});
|
|
2891
|
+
}
|
|
2892
|
+
_handleFC(model, frame, response) {
|
|
2893
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
2894
|
+
switch (frame.fc) {
|
|
2895
|
+
case exports.FunctionCode.READ_COILS: {
|
|
2896
|
+
yield this.handleFC1(model, frame, response);
|
|
2897
|
+
break;
|
|
2898
|
+
}
|
|
2899
|
+
case exports.FunctionCode.READ_DISCRETE_INPUTS: {
|
|
2900
|
+
yield this.handleFC2(model, frame, response);
|
|
2901
|
+
break;
|
|
2902
|
+
}
|
|
2903
|
+
case exports.FunctionCode.READ_HOLDING_REGISTERS: {
|
|
2904
|
+
yield this.handleFC3(model, frame, response);
|
|
2905
|
+
break;
|
|
2906
|
+
}
|
|
2907
|
+
case exports.FunctionCode.READ_INPUT_REGISTERS: {
|
|
2908
|
+
yield this.handleFC4(model, frame, response);
|
|
2909
|
+
break;
|
|
2910
|
+
}
|
|
2911
|
+
case exports.FunctionCode.WRITE_SINGLE_COIL: {
|
|
2912
|
+
yield this.handleFC5(model, frame, response);
|
|
2913
|
+
break;
|
|
2914
|
+
}
|
|
2915
|
+
case exports.FunctionCode.WRITE_SINGLE_REGISTER: {
|
|
2916
|
+
yield this.handleFC6(model, frame, response);
|
|
2917
|
+
break;
|
|
2918
|
+
}
|
|
2919
|
+
case exports.FunctionCode.WRITE_MULTIPLE_COILS: {
|
|
2920
|
+
yield this.handleFC15(model, frame, response);
|
|
2921
|
+
break;
|
|
2922
|
+
}
|
|
2923
|
+
case exports.FunctionCode.WRITE_MULTIPLE_REGISTERS: {
|
|
2924
|
+
yield this.handleFC16(model, frame, response);
|
|
2925
|
+
break;
|
|
2926
|
+
}
|
|
2927
|
+
case exports.FunctionCode.REPORT_SERVER_ID: {
|
|
2928
|
+
yield this.handleFC17(model, frame, response);
|
|
2929
|
+
break;
|
|
2930
|
+
}
|
|
2931
|
+
case exports.FunctionCode.MASK_WRITE_REGISTER: {
|
|
2932
|
+
yield this.handleFC22(model, frame, response);
|
|
2933
|
+
break;
|
|
2934
|
+
}
|
|
2935
|
+
case exports.FunctionCode.READ_WRITE_MULTIPLE_REGISTERS: {
|
|
2936
|
+
yield this.handleFC23(model, frame, response);
|
|
2937
|
+
break;
|
|
2938
|
+
}
|
|
2939
|
+
case exports.FunctionCode.READ_DEVICE_IDENTIFICATION: {
|
|
2940
|
+
yield this.handleFC43_14(model, frame, response);
|
|
2941
|
+
break;
|
|
2942
|
+
}
|
|
2943
|
+
default: {
|
|
2944
|
+
const cfc = this._customFunctionCodes.get(frame.fc);
|
|
2945
|
+
if (cfc === null || cfc === void 0 ? void 0 : cfc.handle) {
|
|
2946
|
+
try {
|
|
2947
|
+
const responseData = yield cfc.handle(frame.data, frame.unit);
|
|
2948
|
+
yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: responseData })));
|
|
2949
|
+
}
|
|
2950
|
+
catch (error) {
|
|
2951
|
+
yield this.responseError(frame, response, error);
|
|
2952
|
+
}
|
|
2953
|
+
}
|
|
2954
|
+
else {
|
|
2955
|
+
yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
|
|
2956
|
+
}
|
|
2957
|
+
break;
|
|
2958
|
+
}
|
|
2959
|
+
}
|
|
2960
|
+
});
|
|
2104
2961
|
}
|
|
2105
2962
|
add(model) {
|
|
2106
2963
|
var _a;
|
|
@@ -2109,6 +2966,14 @@ class ModbusSlave extends EventEmitter {
|
|
|
2109
2966
|
remove(unit) {
|
|
2110
2967
|
this.models.delete(unit);
|
|
2111
2968
|
}
|
|
2969
|
+
addCustomFunctionCode(cfc) {
|
|
2970
|
+
this._customFunctionCodes.set(cfc.fc, cfc);
|
|
2971
|
+
this.applicationLayer.addCustomFunctionCode(cfc);
|
|
2972
|
+
}
|
|
2973
|
+
removeCustomFunctionCode(fc) {
|
|
2974
|
+
this._customFunctionCodes.delete(fc);
|
|
2975
|
+
this.applicationLayer.removeCustomFunctionCode(fc);
|
|
2976
|
+
}
|
|
2112
2977
|
open(...args) {
|
|
2113
2978
|
return this.physicalLayer.open(...args);
|
|
2114
2979
|
}
|
|
@@ -2116,6 +2981,7 @@ class ModbusSlave extends EventEmitter {
|
|
|
2116
2981
|
return this.physicalLayer.close();
|
|
2117
2982
|
}
|
|
2118
2983
|
destroy() {
|
|
2984
|
+
this._locks.clear();
|
|
2119
2985
|
this.removeAllListeners();
|
|
2120
2986
|
this.applicationLayer.destroy();
|
|
2121
2987
|
return this.physicalLayer.destroy();
|
|
@@ -2125,6 +2991,14 @@ class ModbusSlave extends EventEmitter {
|
|
|
2125
2991
|
exports.AbstractApplicationLayer = AbstractApplicationLayer;
|
|
2126
2992
|
exports.AbstractPhysicalLayer = AbstractPhysicalLayer;
|
|
2127
2993
|
exports.AsciiApplicationLayer = AsciiApplicationLayer;
|
|
2994
|
+
exports.COIL_OFF = COIL_OFF;
|
|
2995
|
+
exports.COIL_ON = COIL_ON;
|
|
2996
|
+
exports.EXCEPTION_OFFSET = EXCEPTION_OFFSET;
|
|
2997
|
+
exports.LIMITS = LIMITS;
|
|
2998
|
+
exports.MEI_READ_DEVICE_ID = MEI_READ_DEVICE_ID;
|
|
2999
|
+
exports.MasterSession = MasterSession;
|
|
3000
|
+
exports.ModbusError = ModbusError;
|
|
3001
|
+
exports.ModbusErrorCode = ModbusErrorCode;
|
|
2128
3002
|
exports.ModbusMaster = ModbusMaster;
|
|
2129
3003
|
exports.ModbusSlave = ModbusSlave;
|
|
2130
3004
|
exports.RtuApplicationLayer = RtuApplicationLayer;
|