ts-communication 1.0.0 → 1.1.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/LICENSE +15 -0
- package/README.md +246 -14
- package/dist/Invt/InvtModbusTcp.d.ts +88 -0
- package/dist/Invt/InvtModbusTcp.js +255 -0
- package/dist/XCommunication.d.ts +18 -0
- package/dist/XCommunication.js +29 -0
- package/dist/ab/AbEtherNetIp.d.ts +1 -0
- package/dist/ab/AbEtherNetIp.js +2 -0
- package/dist/core/CommunicationLock.d.ts +88 -0
- package/dist/core/CommunicationLock.js +156 -0
- package/dist/core/Core.d.ts +27 -1
- package/dist/core/Core.js +375 -71
- package/dist/core/DeviceSerialPort.d.ts +64 -0
- package/dist/core/DeviceSerialPort.js +283 -0
- package/dist/core/DeviceUdpNet.d.ts +56 -0
- package/dist/core/DeviceUdpNet.js +307 -0
- package/dist/core/INetMessage.d.ts +145 -0
- package/dist/core/INetMessage.js +240 -0
- package/dist/debug/PingTest.d.ts +11 -0
- package/dist/debug/PingTest.js +59 -0
- package/dist/debug/tcpClient.d.ts +11 -0
- package/dist/debug/tcpClient.js +116 -0
- package/dist/debug/tcpServer.d.ts +12 -0
- package/dist/debug/tcpServer.js +102 -0
- package/dist/delta/DeltaTcpNet.d.ts +65 -0
- package/dist/delta/DeltaTcpNet.js +346 -0
- package/dist/ge/GeSRTPNet.d.ts +49 -0
- package/dist/ge/GeSRTPNet.js +337 -0
- package/dist/inovance/InovanceEasyNet.d.ts +8 -0
- package/dist/inovance/InovanceEasyNet.js +103 -0
- package/dist/inovance/InovanceSerial.d.ts +55 -0
- package/dist/inovance/InovanceSerial.js +145 -0
- package/dist/inovance/InovanceSerialOverTcp.d.ts +39 -0
- package/dist/inovance/InovanceSerialOverTcp.js +172 -0
- package/dist/inovance/InovanceTcpNet.d.ts +2 -4
- package/dist/inovance/InovanceTcpNet.js +40 -13
- package/dist/melsec/MelsecMcAsciiNet.d.ts +49 -0
- package/dist/melsec/MelsecMcAsciiNet.js +196 -0
- package/dist/melsec/MelsecMcAsciiUdp.d.ts +54 -0
- package/dist/melsec/MelsecMcAsciiUdp.js +204 -0
- package/dist/melsec/MelsecMcNet.d.ts +1 -8
- package/dist/melsec/MelsecMcNet.js +4 -14
- package/dist/melsec/MelsecMcUdp.d.ts +52 -0
- package/dist/melsec/MelsecMcUdp.js +191 -0
- package/dist/modbus/ModbusAscii.d.ts +24 -3
- package/dist/modbus/ModbusAscii.js +109 -3
- package/dist/modbus/ModbusAsciiOverTcp.d.ts +21 -3
- package/dist/modbus/ModbusAsciiOverTcp.js +67 -3
- package/dist/modbus/ModbusRtu.d.ts +101 -13
- package/dist/modbus/ModbusRtu.js +355 -21
- package/dist/modbus/ModbusRtuOverTcp.d.ts +23 -3
- package/dist/modbus/ModbusRtuOverTcp.js +68 -3
- package/dist/modbus/ModbusTcpNet.d.ts +16 -3
- package/dist/modbus/ModbusTcpNet.js +192 -60
- package/dist/modbus/ModbusUdpNet.d.ts +26 -3
- package/dist/modbus/ModbusUdpNet.js +125 -2
- package/dist/omron/OmronFinsNet.d.ts +1 -2
- package/dist/omron/OmronFinsNet.js +210 -69
- package/dist/omron/OmronFinsUdp.d.ts +41 -0
- package/dist/omron/OmronFinsUdp.js +146 -0
- package/dist/siemens/SiemensS7Net.d.ts +6 -0
- package/dist/siemens/SiemensS7Net.js +45 -6
- package/dist/xinjie/XinJETcpNet.d.ts +18 -0
- package/dist/xinjie/XinJETcpNet.js +106 -0
- package/package.json +16 -3
package/dist/core/Core.js
CHANGED
|
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.DeviceClient = exports.ByteTransform = exports.OperateResult = exports.PlcString = exports.UInt64 = exports.Int64 = exports.Double = exports.Float = exports.UInt32 = exports.Int32 = exports.UInt16 = exports.Int16 = exports.Byte = exports.PlcValue = exports.DataFormat = void 0;
|
|
37
37
|
exports.wordsFor = wordsFor;
|
|
38
38
|
const net = __importStar(require("node:net"));
|
|
39
|
+
const CommunicationLock_1 = require("./CommunicationLock");
|
|
39
40
|
var DataFormat;
|
|
40
41
|
(function (DataFormat) {
|
|
41
42
|
DataFormat["ABCD"] = "ABCD";
|
|
@@ -55,54 +56,89 @@ class PlcValue {
|
|
|
55
56
|
this.width = width;
|
|
56
57
|
this.signed = signed;
|
|
57
58
|
}
|
|
58
|
-
valueOf() {
|
|
59
|
+
valueOf() {
|
|
60
|
+
return this.value;
|
|
61
|
+
}
|
|
59
62
|
}
|
|
60
63
|
exports.PlcValue = PlcValue;
|
|
61
|
-
function range(name, value, min, max) {
|
|
62
|
-
|
|
64
|
+
function range(name, value, min, max) {
|
|
65
|
+
if (!Number.isInteger(value) || value < min || value > max)
|
|
66
|
+
throw new RangeError(`${name} value out of range: ${value}`);
|
|
67
|
+
}
|
|
63
68
|
class Byte extends PlcValue {
|
|
64
|
-
constructor(value) {
|
|
69
|
+
constructor(value) {
|
|
70
|
+
range('byte', value, 0, 255);
|
|
71
|
+
super(value, 'byte', 1, false);
|
|
72
|
+
}
|
|
65
73
|
}
|
|
66
74
|
exports.Byte = Byte;
|
|
67
75
|
class Int16 extends PlcValue {
|
|
68
|
-
constructor(value) {
|
|
76
|
+
constructor(value) {
|
|
77
|
+
range('short', value, -32768, 32767);
|
|
78
|
+
super(value, 'int16', 2, true);
|
|
79
|
+
}
|
|
69
80
|
}
|
|
70
81
|
exports.Int16 = Int16;
|
|
71
82
|
class UInt16 extends PlcValue {
|
|
72
|
-
constructor(value) {
|
|
83
|
+
constructor(value) {
|
|
84
|
+
range('ushort', value, 0, 65535);
|
|
85
|
+
super(value, 'uint16', 2, false);
|
|
86
|
+
}
|
|
73
87
|
}
|
|
74
88
|
exports.UInt16 = UInt16;
|
|
75
89
|
class Int32 extends PlcValue {
|
|
76
|
-
constructor(value) {
|
|
90
|
+
constructor(value) {
|
|
91
|
+
range('int', value, -2147483648, 2147483647);
|
|
92
|
+
super(value, 'int32', 4, true);
|
|
93
|
+
}
|
|
77
94
|
}
|
|
78
95
|
exports.Int32 = Int32;
|
|
79
96
|
class UInt32 extends PlcValue {
|
|
80
|
-
constructor(value) {
|
|
97
|
+
constructor(value) {
|
|
98
|
+
range('uint', value, 0, 4294967295);
|
|
99
|
+
super(value, 'uint32', 4, false);
|
|
100
|
+
}
|
|
81
101
|
}
|
|
82
102
|
exports.UInt32 = UInt32;
|
|
83
103
|
class Float extends PlcValue {
|
|
84
|
-
constructor(value) {
|
|
85
|
-
|
|
104
|
+
constructor(value) {
|
|
105
|
+
if (!Number.isFinite(value))
|
|
106
|
+
throw new RangeError(`float value is not finite: ${value}`);
|
|
107
|
+
super(value, 'float', 4, true);
|
|
108
|
+
}
|
|
86
109
|
}
|
|
87
110
|
exports.Float = Float;
|
|
88
111
|
class Double extends PlcValue {
|
|
89
|
-
constructor(value) {
|
|
90
|
-
|
|
112
|
+
constructor(value) {
|
|
113
|
+
if (!Number.isFinite(value))
|
|
114
|
+
throw new RangeError(`double value is not finite: ${value}`);
|
|
115
|
+
super(value, 'double', 8, true);
|
|
116
|
+
}
|
|
91
117
|
}
|
|
92
118
|
exports.Double = Double;
|
|
93
119
|
class Int64 extends PlcValue {
|
|
94
|
-
constructor(value) {
|
|
95
|
-
|
|
120
|
+
constructor(value) {
|
|
121
|
+
const n = BigInt(value);
|
|
122
|
+
if (n < -9223372036854775808n || n > 9223372036854775807n)
|
|
123
|
+
throw new RangeError(`long value out of range: ${value}`);
|
|
124
|
+
super(n, 'int64', 8, true);
|
|
125
|
+
}
|
|
96
126
|
}
|
|
97
127
|
exports.Int64 = Int64;
|
|
98
128
|
class UInt64 extends PlcValue {
|
|
99
|
-
constructor(value) {
|
|
100
|
-
|
|
129
|
+
constructor(value) {
|
|
130
|
+
const n = BigInt(value);
|
|
131
|
+
if (n < 0n || n > 18446744073709551615n)
|
|
132
|
+
throw new RangeError(`ulong value out of range: ${value}`);
|
|
133
|
+
super(n, 'uint64', 8, false);
|
|
134
|
+
}
|
|
101
135
|
}
|
|
102
136
|
exports.UInt64 = UInt64;
|
|
103
137
|
class PlcString extends String {
|
|
104
138
|
kind = 'string';
|
|
105
|
-
constructor(value) {
|
|
139
|
+
constructor(value) {
|
|
140
|
+
super(value);
|
|
141
|
+
}
|
|
106
142
|
}
|
|
107
143
|
exports.PlcString = PlcString;
|
|
108
144
|
class OperateResult {
|
|
@@ -124,7 +160,9 @@ class OperateResult {
|
|
|
124
160
|
this.ErrorCode = isSuccessOrErrorCode;
|
|
125
161
|
}
|
|
126
162
|
}
|
|
127
|
-
static CreateSuccessResult(content) {
|
|
163
|
+
static CreateSuccessResult(content) {
|
|
164
|
+
return new OperateResult(content, true, '', 0);
|
|
165
|
+
}
|
|
128
166
|
static CreateFailedResult(resultOrMessage, errorCode = 10000) {
|
|
129
167
|
return typeof resultOrMessage === 'string'
|
|
130
168
|
? new OperateResult(undefined, false, resultOrMessage, errorCode)
|
|
@@ -135,48 +173,146 @@ exports.OperateResult = OperateResult;
|
|
|
135
173
|
class ByteTransform {
|
|
136
174
|
IsStringReverseByteWord = false;
|
|
137
175
|
DataFormat;
|
|
138
|
-
constructor(format = DataFormat.CDAB) {
|
|
176
|
+
constructor(format = DataFormat.CDAB) {
|
|
177
|
+
this.DataFormat = format;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* 按照 DataFormat 重新排序字节
|
|
181
|
+
* 完全匹配 C# RegularByteTransform 的 ByteTransDataFormat* 方法
|
|
182
|
+
* 转换后的字节将用小端序 DataView 读取
|
|
183
|
+
*/
|
|
139
184
|
order(bytes, width) {
|
|
140
|
-
if (width === 2)
|
|
141
|
-
return bytes;
|
|
142
185
|
const out = new Uint8Array(bytes.length);
|
|
143
|
-
const map = {
|
|
144
|
-
[DataFormat.ABCD]: [0, 1, 2, 3], [DataFormat.BADC]: [1, 0, 3, 2],
|
|
145
|
-
[DataFormat.CDAB]: [2, 3, 0, 1], [DataFormat.DCBA]: [3, 2, 1, 0]
|
|
146
|
-
};
|
|
147
|
-
const m = map[this.DataFormat];
|
|
148
186
|
for (let i = 0; i < bytes.length; i += width) {
|
|
149
187
|
const chunk = bytes.subarray(i, i + width);
|
|
150
|
-
if (width ===
|
|
151
|
-
|
|
152
|
-
|
|
188
|
+
if (width === 2) {
|
|
189
|
+
// 对应 C# ByteTransDataFormat2
|
|
190
|
+
switch (this.DataFormat) {
|
|
191
|
+
case DataFormat.ABCD:
|
|
192
|
+
case DataFormat.CDAB:
|
|
193
|
+
out[i] = chunk[1];
|
|
194
|
+
out[i + 1] = chunk[0];
|
|
195
|
+
break;
|
|
196
|
+
case DataFormat.BADC:
|
|
197
|
+
case DataFormat.DCBA:
|
|
198
|
+
out[i] = chunk[0];
|
|
199
|
+
out[i + 1] = chunk[1];
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
else if (width === 4) {
|
|
204
|
+
// 对应 C# ByteTransDataFormat4
|
|
205
|
+
switch (this.DataFormat) {
|
|
206
|
+
case DataFormat.ABCD:
|
|
207
|
+
out[i] = chunk[3];
|
|
208
|
+
out[i + 1] = chunk[2];
|
|
209
|
+
out[i + 2] = chunk[1];
|
|
210
|
+
out[i + 3] = chunk[0];
|
|
211
|
+
break;
|
|
212
|
+
case DataFormat.BADC:
|
|
213
|
+
out[i] = chunk[2];
|
|
214
|
+
out[i + 1] = chunk[3];
|
|
215
|
+
out[i + 2] = chunk[0];
|
|
216
|
+
out[i + 3] = chunk[1];
|
|
217
|
+
break;
|
|
218
|
+
case DataFormat.CDAB:
|
|
219
|
+
out[i] = chunk[1];
|
|
220
|
+
out[i + 1] = chunk[0];
|
|
221
|
+
out[i + 2] = chunk[3];
|
|
222
|
+
out[i + 3] = chunk[2];
|
|
223
|
+
break;
|
|
224
|
+
case DataFormat.DCBA:
|
|
225
|
+
out[i] = chunk[0];
|
|
226
|
+
out[i + 1] = chunk[1];
|
|
227
|
+
out[i + 2] = chunk[2];
|
|
228
|
+
out[i + 3] = chunk[3];
|
|
229
|
+
break;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
153
232
|
else if (width === 8) {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
233
|
+
// 对应 C# ByteTransDataFormat8
|
|
234
|
+
switch (this.DataFormat) {
|
|
235
|
+
case DataFormat.ABCD:
|
|
236
|
+
out[i] = chunk[7];
|
|
237
|
+
out[i + 1] = chunk[6];
|
|
238
|
+
out[i + 2] = chunk[5];
|
|
239
|
+
out[i + 3] = chunk[4];
|
|
240
|
+
out[i + 4] = chunk[3];
|
|
241
|
+
out[i + 5] = chunk[2];
|
|
242
|
+
out[i + 6] = chunk[1];
|
|
243
|
+
out[i + 7] = chunk[0];
|
|
244
|
+
break;
|
|
245
|
+
case DataFormat.BADC:
|
|
246
|
+
out[i] = chunk[6];
|
|
247
|
+
out[i + 1] = chunk[7];
|
|
248
|
+
out[i + 2] = chunk[4];
|
|
249
|
+
out[i + 3] = chunk[5];
|
|
250
|
+
out[i + 4] = chunk[2];
|
|
251
|
+
out[i + 5] = chunk[3];
|
|
252
|
+
out[i + 6] = chunk[0];
|
|
253
|
+
out[i + 7] = chunk[1];
|
|
254
|
+
break;
|
|
255
|
+
case DataFormat.CDAB:
|
|
256
|
+
out[i] = chunk[1];
|
|
257
|
+
out[i + 1] = chunk[0];
|
|
258
|
+
out[i + 2] = chunk[3];
|
|
259
|
+
out[i + 3] = chunk[2];
|
|
260
|
+
out[i + 4] = chunk[5];
|
|
261
|
+
out[i + 5] = chunk[4];
|
|
262
|
+
out[i + 6] = chunk[7];
|
|
263
|
+
out[i + 7] = chunk[6];
|
|
264
|
+
break;
|
|
265
|
+
case DataFormat.DCBA:
|
|
266
|
+
out[i] = chunk[0];
|
|
267
|
+
out[i + 1] = chunk[1];
|
|
268
|
+
out[i + 2] = chunk[2];
|
|
269
|
+
out[i + 3] = chunk[3];
|
|
270
|
+
out[i + 4] = chunk[4];
|
|
271
|
+
out[i + 5] = chunk[5];
|
|
272
|
+
out[i + 6] = chunk[6];
|
|
273
|
+
out[i + 7] = chunk[7];
|
|
274
|
+
break;
|
|
275
|
+
}
|
|
160
276
|
}
|
|
161
|
-
else
|
|
277
|
+
else {
|
|
278
|
+
// 其他宽度,直接复制
|
|
162
279
|
out.set(chunk, i);
|
|
280
|
+
}
|
|
163
281
|
}
|
|
164
282
|
return out;
|
|
165
283
|
}
|
|
166
|
-
view(data, offset, length) {
|
|
284
|
+
view(data, offset, length) {
|
|
285
|
+
return this.order(data.subarray(offset, offset + length), length);
|
|
286
|
+
}
|
|
167
287
|
dataView(data, offset, length) {
|
|
168
288
|
const view = this.view(data, offset, length);
|
|
169
289
|
// Buffer.subarray shares an ArrayBuffer; preserve its byteOffset.
|
|
170
290
|
return new DataView(view.buffer, view.byteOffset, view.byteLength);
|
|
171
291
|
}
|
|
172
|
-
TransInt16(data, offset = 0) {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
292
|
+
TransInt16(data, offset = 0) {
|
|
293
|
+
return this.dataView(data, offset, 2).getInt16(0, true);
|
|
294
|
+
}
|
|
295
|
+
TransUInt16(data, offset = 0) {
|
|
296
|
+
return this.dataView(data, offset, 2).getUint16(0, true);
|
|
297
|
+
}
|
|
298
|
+
TransInt32(data, offset = 0) {
|
|
299
|
+
return this.dataView(data, offset, 4).getInt32(0, true);
|
|
300
|
+
}
|
|
301
|
+
TransUInt32(data, offset = 0) {
|
|
302
|
+
return this.dataView(data, offset, 4).getUint32(0, true);
|
|
303
|
+
}
|
|
304
|
+
TransSingle(data, offset = 0) {
|
|
305
|
+
return this.dataView(data, offset, 4).getFloat32(0, true);
|
|
306
|
+
}
|
|
307
|
+
TransDouble(data, offset = 0) {
|
|
308
|
+
return this.dataView(data, offset, 8).getFloat64(0, true);
|
|
309
|
+
}
|
|
310
|
+
TransInt64(data, offset = 0) {
|
|
311
|
+
return this.dataView(data, offset, 8).getBigInt64(0, true);
|
|
312
|
+
}
|
|
313
|
+
TransUInt64(data, offset = 0) {
|
|
314
|
+
return this.dataView(data, offset, 8).getBigUint64(0, true);
|
|
315
|
+
}
|
|
180
316
|
TransByte(value, width) {
|
|
181
317
|
const values = Array.isArray(value) ? value : [value];
|
|
182
318
|
const first = values[0];
|
|
@@ -189,16 +325,16 @@ class ByteTransform {
|
|
|
189
325
|
if (w === 1)
|
|
190
326
|
raw[0] = Number(scalar);
|
|
191
327
|
else if (w === 2)
|
|
192
|
-
(v instanceof UInt16 ? view.setUint16 : view.setInt16).call(view, 0, Number(scalar),
|
|
328
|
+
(v instanceof UInt16 ? view.setUint16 : view.setInt16).call(view, 0, Number(scalar), true);
|
|
193
329
|
else if (w === 4)
|
|
194
|
-
(v instanceof UInt32 ? view.setUint32 : v instanceof Float ? view.setFloat32 : view.setInt32).call(view, 0, Number(scalar),
|
|
330
|
+
(v instanceof UInt32 ? view.setUint32 : v instanceof Float ? view.setFloat32 : view.setInt32).call(view, 0, Number(scalar), true);
|
|
195
331
|
else if (w === 8)
|
|
196
|
-
(v instanceof UInt64 ? view.setBigUint64 : view.setBigInt64).call(view, 0, BigInt(scalar),
|
|
332
|
+
(v instanceof UInt64 ? view.setBigUint64 : view.setBigInt64).call(view, 0, BigInt(scalar), true);
|
|
197
333
|
out.set(this.order(raw, w), i * w);
|
|
198
334
|
});
|
|
199
335
|
return out;
|
|
200
336
|
}
|
|
201
|
-
TransString(data, encoding = '
|
|
337
|
+
TransString(data, encoding = 'ascii') {
|
|
202
338
|
let raw = Buffer.from(data);
|
|
203
339
|
if (this.IsStringReverseByteWord)
|
|
204
340
|
for (let i = 0; i + 1 < raw.length; i += 2)
|
|
@@ -217,9 +353,28 @@ class DeviceClient {
|
|
|
217
353
|
ConnectionId = '';
|
|
218
354
|
socket;
|
|
219
355
|
connecting;
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
356
|
+
// 通信锁和请求队列
|
|
357
|
+
communicationLock;
|
|
358
|
+
requestQueue;
|
|
359
|
+
// 是否启用通信锁(默认启用)
|
|
360
|
+
EnableCommunicationLock = true;
|
|
361
|
+
constructor(ipAddress, port) {
|
|
362
|
+
if (ipAddress !== undefined)
|
|
363
|
+
this.IpAddress = ipAddress;
|
|
364
|
+
if (port !== undefined)
|
|
365
|
+
this.Port = port;
|
|
366
|
+
this.ConnectionId = `${this.IpAddress}:${this.Port}`;
|
|
367
|
+
// 初始化通信锁和队列
|
|
368
|
+
this.communicationLock = new CommunicationLock_1.CommunicationLockSimple();
|
|
369
|
+
this.requestQueue = new CommunicationLock_1.RequestQueue();
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* 获取当前协议的消息解析器
|
|
373
|
+
* 子类应该重写此方法以提供特定协议的消息解析器
|
|
374
|
+
*/
|
|
375
|
+
GetNewNetMessage() {
|
|
376
|
+
return undefined;
|
|
377
|
+
}
|
|
223
378
|
async ConnectServer() {
|
|
224
379
|
if (this.socket && !this.socket.destroyed)
|
|
225
380
|
return OperateResult.CreateSuccessResult();
|
|
@@ -227,18 +382,131 @@ class DeviceClient {
|
|
|
227
382
|
return this.connecting;
|
|
228
383
|
this.connecting = new Promise(resolve => {
|
|
229
384
|
const socket = net.createConnection({ host: this.IpAddress, port: this.Port });
|
|
230
|
-
const timer = setTimeout(() => {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
385
|
+
const timer = setTimeout(() => {
|
|
386
|
+
socket.destroy();
|
|
387
|
+
resolve(new OperateResult('Connection timeout', -1));
|
|
388
|
+
}, this.ConnectTimeOut);
|
|
389
|
+
socket.once('connect', () => {
|
|
390
|
+
clearTimeout(timer);
|
|
391
|
+
this.socket = socket;
|
|
392
|
+
resolve(OperateResult.CreateSuccessResult());
|
|
393
|
+
});
|
|
394
|
+
socket.once('error', e => {
|
|
395
|
+
clearTimeout(timer);
|
|
396
|
+
resolve(new OperateResult(e.message, -1));
|
|
397
|
+
});
|
|
398
|
+
}).finally(() => {
|
|
399
|
+
this.connecting = undefined;
|
|
400
|
+
});
|
|
234
401
|
return this.connecting;
|
|
235
402
|
}
|
|
236
|
-
async ConnectClose() {
|
|
403
|
+
async ConnectClose() {
|
|
404
|
+
this.socket?.destroy();
|
|
405
|
+
this.socket = undefined;
|
|
406
|
+
this.communicationLock.dispose();
|
|
407
|
+
return OperateResult.CreateSuccessResult();
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* 发送请求并接收响应(带通信锁和队列)
|
|
411
|
+
*/
|
|
237
412
|
async request(frame, parser) {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
413
|
+
// 加入请求队列
|
|
414
|
+
return this.requestQueue.enqueue(async () => {
|
|
415
|
+
// 进入通信锁
|
|
416
|
+
if (this.EnableCommunicationLock) {
|
|
417
|
+
const lockResult = await this.communicationLock.EnterLock(this.ConnectTimeOut);
|
|
418
|
+
if (!lockResult.IsSuccess) {
|
|
419
|
+
return OperateResult.CreateFailedResult(lockResult);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
try {
|
|
423
|
+
const connected = await this.ConnectServer();
|
|
424
|
+
if (!connected.IsSuccess) {
|
|
425
|
+
return OperateResult.CreateFailedResult(connected);
|
|
426
|
+
}
|
|
427
|
+
// 如果有消息解析器,使用它
|
|
428
|
+
const message = this.GetNewNetMessage();
|
|
429
|
+
if (message && !parser) {
|
|
430
|
+
return await this.requestWithMessage(frame, message);
|
|
431
|
+
}
|
|
432
|
+
// 否则使用传统的 parser 方式
|
|
433
|
+
return await this.requestConnected(frame, parser || ((data) => data));
|
|
434
|
+
}
|
|
435
|
+
finally {
|
|
436
|
+
// 离开通信锁
|
|
437
|
+
if (this.EnableCommunicationLock) {
|
|
438
|
+
this.communicationLock.LeaveLock();
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* 使用 INetMessage 解析器发送请求
|
|
445
|
+
*/
|
|
446
|
+
async requestWithMessage(frame, message) {
|
|
447
|
+
return new Promise(resolve => {
|
|
448
|
+
const socket = this.socket;
|
|
449
|
+
let receiveBuffer = Buffer.alloc(0);
|
|
450
|
+
let done = false;
|
|
451
|
+
message.SendBytes = frame;
|
|
452
|
+
const finish = (result) => {
|
|
453
|
+
if (done)
|
|
454
|
+
return;
|
|
455
|
+
done = true;
|
|
456
|
+
clearTimeout(timer);
|
|
457
|
+
socket.off('data', onData);
|
|
458
|
+
socket.off('error', onError);
|
|
459
|
+
resolve(result);
|
|
460
|
+
};
|
|
461
|
+
const onError = (e) => finish(new OperateResult(e.message, -1));
|
|
462
|
+
const onData = (chunk) => {
|
|
463
|
+
receiveBuffer = Buffer.concat([receiveBuffer, chunk]);
|
|
464
|
+
try {
|
|
465
|
+
// 第一步:接收头部
|
|
466
|
+
if (message.HeadBytes.length < message.ProtocolHeadBytesLength) {
|
|
467
|
+
const headLength = message.ProtocolHeadBytesLength;
|
|
468
|
+
if (receiveBuffer.length >= headLength) {
|
|
469
|
+
message.HeadBytes = receiveBuffer.subarray(0, headLength);
|
|
470
|
+
receiveBuffer = receiveBuffer.subarray(headLength);
|
|
471
|
+
// 检查头部合法性
|
|
472
|
+
if (!message.CheckHeadBytesLegal()) {
|
|
473
|
+
finish(new OperateResult('Invalid message head', -3));
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
else {
|
|
478
|
+
return; // 继续等待
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
// 第二步:获取内容长度
|
|
482
|
+
const contentLength = message.GetContentLengthByHeadBytes();
|
|
483
|
+
if (contentLength < 0) {
|
|
484
|
+
// 需要更多数据来确定长度
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
// 第三步:接收内容
|
|
488
|
+
if (receiveBuffer.length >= contentLength) {
|
|
489
|
+
message.ContentBytes = receiveBuffer.subarray(0, contentLength);
|
|
490
|
+
// 检查消息匹配
|
|
491
|
+
const matchResult = message.CheckMessageMatch(frame, Buffer.concat([message.HeadBytes, message.ContentBytes]));
|
|
492
|
+
if (matchResult === 0) {
|
|
493
|
+
finish(new OperateResult('Message match failed', -4));
|
|
494
|
+
}
|
|
495
|
+
else if (matchResult === 1) {
|
|
496
|
+
finish(OperateResult.CreateSuccessResult(message.ContentBytes));
|
|
497
|
+
}
|
|
498
|
+
// matchResult === -1 继续等待
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
catch (e) {
|
|
502
|
+
finish(new OperateResult(`Parse error: ${e.message}`, -5));
|
|
503
|
+
}
|
|
504
|
+
};
|
|
505
|
+
const timer = setTimeout(() => finish(new OperateResult('Receive timeout', -2)), this.ReceiveTimeOut);
|
|
506
|
+
socket.on('data', onData);
|
|
507
|
+
socket.once('error', onError);
|
|
508
|
+
socket.write(frame);
|
|
509
|
+
});
|
|
242
510
|
}
|
|
243
511
|
/** Send one frame on an already connected socket (used by protocol handshakes). */
|
|
244
512
|
async requestConnected(frame, parser) {
|
|
@@ -246,22 +514,58 @@ class DeviceClient {
|
|
|
246
514
|
const socket = this.socket;
|
|
247
515
|
let chunks = [];
|
|
248
516
|
let done = false;
|
|
249
|
-
const finish = (result) => {
|
|
250
|
-
|
|
517
|
+
const finish = (result) => {
|
|
518
|
+
if (done)
|
|
519
|
+
return;
|
|
520
|
+
done = true;
|
|
521
|
+
clearTimeout(timer);
|
|
522
|
+
socket.off('data', onData);
|
|
523
|
+
socket.off('error', onError);
|
|
524
|
+
resolve(result);
|
|
525
|
+
};
|
|
251
526
|
const onError = (e) => finish(new OperateResult(e.message, -1));
|
|
252
|
-
const onData = (chunk) => {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
527
|
+
const onData = (chunk) => {
|
|
528
|
+
chunks.push(chunk);
|
|
529
|
+
try {
|
|
530
|
+
const body = parser(Buffer.concat(chunks));
|
|
531
|
+
if (body)
|
|
532
|
+
finish(OperateResult.CreateSuccessResult(body));
|
|
533
|
+
}
|
|
534
|
+
catch { /* wait for remaining frame */
|
|
535
|
+
}
|
|
536
|
+
};
|
|
258
537
|
const timer = setTimeout(() => finish(new OperateResult('Receive timeout', -2)), this.ReceiveTimeOut);
|
|
259
538
|
socket.on('data', onData);
|
|
260
539
|
socket.once('error', onError);
|
|
261
540
|
socket.write(frame);
|
|
262
541
|
});
|
|
263
542
|
}
|
|
264
|
-
|
|
543
|
+
/**
|
|
544
|
+
* 批量发送请求(自动合并结果)
|
|
545
|
+
*/
|
|
546
|
+
async ReadFromCoreServer(commands) {
|
|
547
|
+
if (commands.length === 0) {
|
|
548
|
+
return OperateResult.CreateSuccessResult(Buffer.alloc(0));
|
|
549
|
+
}
|
|
550
|
+
if (commands.length === 1) {
|
|
551
|
+
return this.request(commands[0]);
|
|
552
|
+
}
|
|
553
|
+
// 批量发送
|
|
554
|
+
const results = [];
|
|
555
|
+
for (const cmd of commands) {
|
|
556
|
+
const result = await this.request(cmd);
|
|
557
|
+
if (!result.IsSuccess) {
|
|
558
|
+
return result;
|
|
559
|
+
}
|
|
560
|
+
results.push(result.Content);
|
|
561
|
+
}
|
|
562
|
+
return OperateResult.CreateSuccessResult(Buffer.concat(results));
|
|
563
|
+
}
|
|
564
|
+
fromResult(result, fn) {
|
|
565
|
+
return result.IsSuccess ? OperateResult.CreateSuccessResult(fn(result.Content)) : OperateResult.CreateFailedResult(result);
|
|
566
|
+
}
|
|
265
567
|
}
|
|
266
568
|
exports.DeviceClient = DeviceClient;
|
|
267
|
-
function wordsFor(type, length) {
|
|
569
|
+
function wordsFor(type, length) {
|
|
570
|
+
return type === 'int32' || type === 'uint32' || type === 'float' ? length * 2 : type === 'int64' || type === 'uint64' || type === 'double' ? length * 4 : length;
|
|
571
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { OperateResult, ByteTransform } from './Core';
|
|
2
|
+
import { ICommunicationLock, RequestQueue } from './CommunicationLock';
|
|
3
|
+
import { INetMessage } from './INetMessage';
|
|
4
|
+
/**
|
|
5
|
+
* 串口设备基类
|
|
6
|
+
* Base class for serial port devices
|
|
7
|
+
*/
|
|
8
|
+
export declare abstract class DeviceSerialPort {
|
|
9
|
+
PortName: string;
|
|
10
|
+
BaudRate: number;
|
|
11
|
+
DataBits: 7 | 8;
|
|
12
|
+
StopBits: 1 | 2;
|
|
13
|
+
Parity: 'none' | 'even' | 'odd' | 'mark' | 'space';
|
|
14
|
+
ReceiveTimeOut: number;
|
|
15
|
+
ConnectTimeOut: number;
|
|
16
|
+
ByteTransform: ByteTransform;
|
|
17
|
+
ConnectionId: string;
|
|
18
|
+
protected communicationLock: ICommunicationLock;
|
|
19
|
+
protected requestQueue: RequestQueue;
|
|
20
|
+
EnableCommunicationLock: boolean;
|
|
21
|
+
private serial?;
|
|
22
|
+
private isOpen;
|
|
23
|
+
ReceiveEmptyDataCount: number;
|
|
24
|
+
constructor();
|
|
25
|
+
/**
|
|
26
|
+
* 获取当前协议的消息解析器
|
|
27
|
+
* 子类应该重写此方法以提供特定协议的消息解析器
|
|
28
|
+
*/
|
|
29
|
+
protected abstract GetNewNetMessage(): INetMessage | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* 打包命令(在发送前添加协议头、CRC等)
|
|
32
|
+
* 子类可以重写此方法
|
|
33
|
+
*/
|
|
34
|
+
protected PackCommandWithHeader(command: Buffer): Buffer;
|
|
35
|
+
/**
|
|
36
|
+
* 解包响应(验证CRC、去除协议头等)
|
|
37
|
+
* 子类可以重写此方法
|
|
38
|
+
*/
|
|
39
|
+
protected UnpackResponseContent(send: Buffer, response: Buffer): OperateResult<Buffer>;
|
|
40
|
+
/**
|
|
41
|
+
* 初始化串口配置
|
|
42
|
+
*/
|
|
43
|
+
SerialPortInni(portName: string, baudRate?: number, dataBits?: 7 | 8, stopBits?: 1 | 2, parity?: 'none' | 'even' | 'odd' | 'mark' | 'space'): this;
|
|
44
|
+
/**
|
|
45
|
+
* 打开串口
|
|
46
|
+
*/
|
|
47
|
+
Open(): Promise<OperateResult<void>>;
|
|
48
|
+
/**
|
|
49
|
+
* 关闭串口
|
|
50
|
+
*/
|
|
51
|
+
Close(): Promise<OperateResult<void>>;
|
|
52
|
+
/**
|
|
53
|
+
* 发送请求并接收响应
|
|
54
|
+
*/
|
|
55
|
+
protected ReadFromCoreServer(send: Buffer, hasResponseData?: boolean, usePackAndUnpack?: boolean): Promise<OperateResult<Buffer>>;
|
|
56
|
+
/**
|
|
57
|
+
* 使用 INetMessage 解析器读取数据
|
|
58
|
+
*/
|
|
59
|
+
private readWithMessage;
|
|
60
|
+
/**
|
|
61
|
+
* 释放资源
|
|
62
|
+
*/
|
|
63
|
+
Dispose(): Promise<void>;
|
|
64
|
+
}
|