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
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DeviceSerialPort = void 0;
|
|
4
|
+
const Core_1 = require("./Core");
|
|
5
|
+
const CommunicationLock_1 = require("./CommunicationLock");
|
|
6
|
+
/**
|
|
7
|
+
* 串口设备基类
|
|
8
|
+
* Base class for serial port devices
|
|
9
|
+
*/
|
|
10
|
+
class DeviceSerialPort {
|
|
11
|
+
// 串口配置
|
|
12
|
+
PortName = '';
|
|
13
|
+
BaudRate = 9600;
|
|
14
|
+
DataBits = 8;
|
|
15
|
+
StopBits = 1;
|
|
16
|
+
Parity = 'none';
|
|
17
|
+
// 超时设置
|
|
18
|
+
ReceiveTimeOut = 5000;
|
|
19
|
+
ConnectTimeOut = 5000;
|
|
20
|
+
// 字节转换器
|
|
21
|
+
ByteTransform = new Core_1.ByteTransform();
|
|
22
|
+
// 连接ID
|
|
23
|
+
ConnectionId = '';
|
|
24
|
+
// 通信锁和请求队列
|
|
25
|
+
communicationLock;
|
|
26
|
+
requestQueue;
|
|
27
|
+
// 是否启用通信锁(默认启用)
|
|
28
|
+
EnableCommunicationLock = true;
|
|
29
|
+
// 串口对象
|
|
30
|
+
serial;
|
|
31
|
+
isOpen = false;
|
|
32
|
+
// 接收空数据计数(用于某些协议判断帧结束)
|
|
33
|
+
ReceiveEmptyDataCount = 0;
|
|
34
|
+
constructor() {
|
|
35
|
+
this.communicationLock = new CommunicationLock_1.CommunicationLockSimple();
|
|
36
|
+
this.requestQueue = new CommunicationLock_1.RequestQueue();
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* 打包命令(在发送前添加协议头、CRC等)
|
|
40
|
+
* 子类可以重写此方法
|
|
41
|
+
*/
|
|
42
|
+
PackCommandWithHeader(command) {
|
|
43
|
+
return command;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* 解包响应(验证CRC、去除协议头等)
|
|
47
|
+
* 子类可以重写此方法
|
|
48
|
+
*/
|
|
49
|
+
UnpackResponseContent(send, response) {
|
|
50
|
+
return Core_1.OperateResult.CreateSuccessResult(response);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* 初始化串口配置
|
|
54
|
+
*/
|
|
55
|
+
SerialPortInni(portName, baudRate = 9600, dataBits = 8, stopBits = 1, parity = 'none') {
|
|
56
|
+
this.PortName = portName;
|
|
57
|
+
this.BaudRate = baudRate;
|
|
58
|
+
this.DataBits = dataBits;
|
|
59
|
+
this.StopBits = stopBits;
|
|
60
|
+
this.Parity = parity;
|
|
61
|
+
this.ConnectionId = `Serial:${portName}`;
|
|
62
|
+
return this;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* 打开串口
|
|
66
|
+
*/
|
|
67
|
+
async Open() {
|
|
68
|
+
if (this.isOpen && this.serial) {
|
|
69
|
+
return Core_1.OperateResult.CreateSuccessResult();
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
// 动态导入 serialport
|
|
73
|
+
const mod = await import('serialport');
|
|
74
|
+
const SerialPort = mod.SerialPort;
|
|
75
|
+
this.serial = new SerialPort({
|
|
76
|
+
path: this.PortName,
|
|
77
|
+
baudRate: this.BaudRate,
|
|
78
|
+
dataBits: this.DataBits,
|
|
79
|
+
stopBits: this.StopBits,
|
|
80
|
+
parity: this.Parity,
|
|
81
|
+
autoOpen: false
|
|
82
|
+
});
|
|
83
|
+
// 打开串口
|
|
84
|
+
return new Promise((resolve) => {
|
|
85
|
+
const timer = setTimeout(() => {
|
|
86
|
+
resolve(new Core_1.OperateResult('Serial port open timeout', -1));
|
|
87
|
+
}, this.ConnectTimeOut);
|
|
88
|
+
this.serial.open((err) => {
|
|
89
|
+
clearTimeout(timer);
|
|
90
|
+
if (err) {
|
|
91
|
+
resolve(new Core_1.OperateResult(`Unable to open serial port: ${err.message}`, -1));
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
this.isOpen = true;
|
|
95
|
+
resolve(Core_1.OperateResult.CreateSuccessResult());
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
catch (e) {
|
|
101
|
+
return new Core_1.OperateResult(`Unable to load serialport module: ${e.message}. Please install: npm install serialport`, -1);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* 关闭串口
|
|
106
|
+
*/
|
|
107
|
+
async Close() {
|
|
108
|
+
if (!this.serial || !this.isOpen) {
|
|
109
|
+
return Core_1.OperateResult.CreateSuccessResult();
|
|
110
|
+
}
|
|
111
|
+
return new Promise((resolve) => {
|
|
112
|
+
this.serial.close((err) => {
|
|
113
|
+
if (err) {
|
|
114
|
+
resolve(new Core_1.OperateResult(`Unable to close serial port: ${err.message}`, -1));
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
this.isOpen = false;
|
|
118
|
+
this.serial = undefined;
|
|
119
|
+
resolve(Core_1.OperateResult.CreateSuccessResult());
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* 发送请求并接收响应
|
|
126
|
+
*/
|
|
127
|
+
async ReadFromCoreServer(send, hasResponseData = true, usePackAndUnpack = true) {
|
|
128
|
+
// 加入请求队列
|
|
129
|
+
return this.requestQueue.enqueue(async () => {
|
|
130
|
+
// 进入通信锁
|
|
131
|
+
if (this.EnableCommunicationLock) {
|
|
132
|
+
const lockResult = await this.communicationLock.EnterLock(this.ConnectTimeOut);
|
|
133
|
+
if (!lockResult.IsSuccess) {
|
|
134
|
+
return Core_1.OperateResult.CreateFailedResult(lockResult);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
try {
|
|
138
|
+
// 确保串口已打开
|
|
139
|
+
const openResult = await this.Open();
|
|
140
|
+
if (!openResult.IsSuccess) {
|
|
141
|
+
return Core_1.OperateResult.CreateFailedResult(openResult);
|
|
142
|
+
}
|
|
143
|
+
// 打包命令
|
|
144
|
+
const sendData = usePackAndUnpack ? this.PackCommandWithHeader(send) : send;
|
|
145
|
+
// 如果不需要响应数据,直接发送并返回
|
|
146
|
+
if (!hasResponseData) {
|
|
147
|
+
return new Promise((resolve) => {
|
|
148
|
+
this.serial.write(sendData, (err) => {
|
|
149
|
+
if (err) {
|
|
150
|
+
resolve(new Core_1.OperateResult(`Serial write error: ${err.message}`, -1));
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
resolve(Core_1.OperateResult.CreateSuccessResult(Buffer.alloc(0)));
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
// 获取消息解析器
|
|
159
|
+
const message = this.GetNewNetMessage();
|
|
160
|
+
if (message) {
|
|
161
|
+
return await this.readWithMessage(sendData, send, message, usePackAndUnpack);
|
|
162
|
+
}
|
|
163
|
+
// 如果没有消息解析器,返回错误
|
|
164
|
+
return new Core_1.OperateResult('No message parser available', -1);
|
|
165
|
+
}
|
|
166
|
+
finally {
|
|
167
|
+
// 离开通信锁
|
|
168
|
+
if (this.EnableCommunicationLock) {
|
|
169
|
+
this.communicationLock.LeaveLock();
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* 使用 INetMessage 解析器读取数据
|
|
176
|
+
*/
|
|
177
|
+
async readWithMessage(sendData, originalSend, message, usePackAndUnpack) {
|
|
178
|
+
return new Promise((resolve) => {
|
|
179
|
+
let receiveBuffer = Buffer.alloc(0);
|
|
180
|
+
let done = false;
|
|
181
|
+
let emptyCount = 0;
|
|
182
|
+
message.SendBytes = sendData;
|
|
183
|
+
const finish = (result) => {
|
|
184
|
+
if (done)
|
|
185
|
+
return;
|
|
186
|
+
done = true;
|
|
187
|
+
clearTimeout(timer);
|
|
188
|
+
this.serial.removeListener('data', onData);
|
|
189
|
+
resolve(result);
|
|
190
|
+
};
|
|
191
|
+
const onData = (chunk) => {
|
|
192
|
+
if (chunk.length === 0) {
|
|
193
|
+
emptyCount++;
|
|
194
|
+
if (this.ReceiveEmptyDataCount > 0 && emptyCount >= this.ReceiveEmptyDataCount) {
|
|
195
|
+
// 连续接收到空数据,认为帧结束
|
|
196
|
+
if (receiveBuffer.length > 0) {
|
|
197
|
+
const unpackResult = usePackAndUnpack
|
|
198
|
+
? this.UnpackResponseContent(originalSend, receiveBuffer)
|
|
199
|
+
: Core_1.OperateResult.CreateSuccessResult(receiveBuffer);
|
|
200
|
+
finish(unpackResult);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
emptyCount = 0;
|
|
206
|
+
receiveBuffer = Buffer.concat([receiveBuffer, chunk]);
|
|
207
|
+
try {
|
|
208
|
+
// 第一步:接收头部
|
|
209
|
+
if (message.HeadBytes.length < message.ProtocolHeadBytesLength) {
|
|
210
|
+
// 检查并跳过无效前缀
|
|
211
|
+
const uselessLength = message.PependedUselesByteLength(receiveBuffer);
|
|
212
|
+
if (uselessLength > 0) {
|
|
213
|
+
receiveBuffer = receiveBuffer.subarray(uselessLength);
|
|
214
|
+
}
|
|
215
|
+
const headLength = message.ProtocolHeadBytesLength;
|
|
216
|
+
if (headLength > 0 && receiveBuffer.length >= headLength) {
|
|
217
|
+
message.HeadBytes = receiveBuffer.subarray(0, headLength);
|
|
218
|
+
receiveBuffer = receiveBuffer.subarray(headLength);
|
|
219
|
+
// 检查头部合法性
|
|
220
|
+
if (!message.CheckHeadBytesLegal()) {
|
|
221
|
+
finish(new Core_1.OperateResult('Invalid message head', -3));
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
else if (headLength < 0) {
|
|
226
|
+
// 动态长度消息(如 Modbus ASCII),需要特殊处理
|
|
227
|
+
// 继续接收直到遇到结束符
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
return; // 继续等待
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
// 第二步:获取内容长度
|
|
234
|
+
const contentLength = message.GetContentLengthByHeadBytes();
|
|
235
|
+
if (contentLength < 0) {
|
|
236
|
+
// 对于动态长度消息,继续等待
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
// 第三步:接收内容
|
|
240
|
+
if (receiveBuffer.length >= contentLength) {
|
|
241
|
+
message.ContentBytes = receiveBuffer.subarray(0, contentLength);
|
|
242
|
+
const fullMessage = Buffer.concat([message.HeadBytes, message.ContentBytes]);
|
|
243
|
+
// 检查消息匹配
|
|
244
|
+
const matchResult = message.CheckMessageMatch(sendData, fullMessage);
|
|
245
|
+
if (matchResult === 0) {
|
|
246
|
+
finish(new Core_1.OperateResult('Message match failed', -4));
|
|
247
|
+
}
|
|
248
|
+
else if (matchResult === 1) {
|
|
249
|
+
// 解包响应
|
|
250
|
+
const unpackResult = usePackAndUnpack
|
|
251
|
+
? this.UnpackResponseContent(originalSend, fullMessage)
|
|
252
|
+
: Core_1.OperateResult.CreateSuccessResult(message.ContentBytes);
|
|
253
|
+
finish(unpackResult);
|
|
254
|
+
}
|
|
255
|
+
// matchResult === -1 继续等待
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
catch (e) {
|
|
259
|
+
finish(new Core_1.OperateResult(`Parse error: ${e.message}`, -5));
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
const timer = setTimeout(() => {
|
|
263
|
+
finish(new Core_1.OperateResult('Receive timeout', -2));
|
|
264
|
+
}, this.ReceiveTimeOut);
|
|
265
|
+
this.serial.on('data', onData);
|
|
266
|
+
// 发送数据
|
|
267
|
+
this.serial.write(sendData, (err) => {
|
|
268
|
+
if (err) {
|
|
269
|
+
finish(new Core_1.OperateResult(`Serial write error: ${err.message}`, -1));
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* 释放资源
|
|
276
|
+
*/
|
|
277
|
+
async Dispose() {
|
|
278
|
+
await this.Close();
|
|
279
|
+
this.communicationLock.dispose();
|
|
280
|
+
this.requestQueue.clear();
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
exports.DeviceSerialPort = DeviceSerialPort;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { OperateResult, ByteTransform } from './Core';
|
|
2
|
+
import { ICommunicationLock, RequestQueue } from './CommunicationLock';
|
|
3
|
+
import { INetMessage } from './INetMessage';
|
|
4
|
+
/**
|
|
5
|
+
* UDP 设备基类
|
|
6
|
+
* Base class for UDP devices
|
|
7
|
+
*/
|
|
8
|
+
export declare abstract class DeviceUdpNet {
|
|
9
|
+
IpAddress: string;
|
|
10
|
+
Port: number;
|
|
11
|
+
LocalPort: number;
|
|
12
|
+
ConnectTimeOut: number;
|
|
13
|
+
ReceiveTimeOut: number;
|
|
14
|
+
ByteTransform: ByteTransform;
|
|
15
|
+
ConnectionId: string;
|
|
16
|
+
protected communicationLock: ICommunicationLock;
|
|
17
|
+
protected requestQueue: RequestQueue;
|
|
18
|
+
EnableCommunicationLock: boolean;
|
|
19
|
+
private socket?;
|
|
20
|
+
private isOpen;
|
|
21
|
+
constructor(ipAddress?: string, port?: number);
|
|
22
|
+
/**
|
|
23
|
+
* 获取当前协议的消息解析器
|
|
24
|
+
* 子类应该重写此方法以提供特定协议的消息解析器
|
|
25
|
+
*/
|
|
26
|
+
protected GetNewNetMessage(): INetMessage | undefined;
|
|
27
|
+
/**
|
|
28
|
+
* 打开 UDP socket
|
|
29
|
+
*/
|
|
30
|
+
ConnectServer(): Promise<OperateResult<void>>;
|
|
31
|
+
/**
|
|
32
|
+
* 关闭 UDP socket
|
|
33
|
+
*/
|
|
34
|
+
ConnectClose(): Promise<OperateResult<void>>;
|
|
35
|
+
/**
|
|
36
|
+
* 发送请求并接收响应
|
|
37
|
+
*/
|
|
38
|
+
protected request(frame: Buffer, parser?: (data: Buffer) => Buffer): Promise<OperateResult<Buffer>>;
|
|
39
|
+
/**
|
|
40
|
+
* 使用 INetMessage 解析器发送请求
|
|
41
|
+
*/
|
|
42
|
+
private requestWithMessage;
|
|
43
|
+
/**
|
|
44
|
+
* 简单的请求响应(不使用 INetMessage)
|
|
45
|
+
*/
|
|
46
|
+
private requestSimple;
|
|
47
|
+
/**
|
|
48
|
+
* 批量发送请求(自动合并结果)
|
|
49
|
+
*/
|
|
50
|
+
protected ReadFromCoreServer(commands: Buffer[]): Promise<OperateResult<Buffer>>;
|
|
51
|
+
protected fromResult<T>(result: OperateResult<Buffer>, fn: (data: Buffer) => T): OperateResult<T>;
|
|
52
|
+
/**
|
|
53
|
+
* 释放资源
|
|
54
|
+
*/
|
|
55
|
+
Dispose(): Promise<void>;
|
|
56
|
+
}
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.DeviceUdpNet = void 0;
|
|
37
|
+
const dgram = __importStar(require("node:dgram"));
|
|
38
|
+
const Core_1 = require("./Core");
|
|
39
|
+
const CommunicationLock_1 = require("./CommunicationLock");
|
|
40
|
+
/**
|
|
41
|
+
* UDP 设备基类
|
|
42
|
+
* Base class for UDP devices
|
|
43
|
+
*/
|
|
44
|
+
class DeviceUdpNet {
|
|
45
|
+
IpAddress = '127.0.0.1';
|
|
46
|
+
Port = 5000;
|
|
47
|
+
LocalPort = 0; // 0 表示随机端口
|
|
48
|
+
ConnectTimeOut = 5000;
|
|
49
|
+
ReceiveTimeOut = 5000;
|
|
50
|
+
ByteTransform = new Core_1.ByteTransform();
|
|
51
|
+
ConnectionId = '';
|
|
52
|
+
// 通信锁和请求队列
|
|
53
|
+
communicationLock;
|
|
54
|
+
requestQueue;
|
|
55
|
+
// 是否启用通信锁(默认启用)
|
|
56
|
+
EnableCommunicationLock = true;
|
|
57
|
+
// UDP socket
|
|
58
|
+
socket;
|
|
59
|
+
isOpen = false;
|
|
60
|
+
constructor(ipAddress, port) {
|
|
61
|
+
if (ipAddress !== undefined)
|
|
62
|
+
this.IpAddress = ipAddress;
|
|
63
|
+
if (port !== undefined)
|
|
64
|
+
this.Port = port;
|
|
65
|
+
this.ConnectionId = `UDP:${this.IpAddress}:${this.Port}`;
|
|
66
|
+
// 初始化通信锁和队列
|
|
67
|
+
this.communicationLock = new CommunicationLock_1.CommunicationLockSimple();
|
|
68
|
+
this.requestQueue = new CommunicationLock_1.RequestQueue();
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* 获取当前协议的消息解析器
|
|
72
|
+
* 子类应该重写此方法以提供特定协议的消息解析器
|
|
73
|
+
*/
|
|
74
|
+
GetNewNetMessage() {
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* 打开 UDP socket
|
|
79
|
+
*/
|
|
80
|
+
async ConnectServer() {
|
|
81
|
+
if (this.isOpen && this.socket) {
|
|
82
|
+
return Core_1.OperateResult.CreateSuccessResult();
|
|
83
|
+
}
|
|
84
|
+
return new Promise((resolve) => {
|
|
85
|
+
try {
|
|
86
|
+
this.socket = dgram.createSocket('udp4');
|
|
87
|
+
const timer = setTimeout(() => {
|
|
88
|
+
resolve(new Core_1.OperateResult('UDP socket bind timeout', -1));
|
|
89
|
+
}, this.ConnectTimeOut);
|
|
90
|
+
this.socket.once('listening', () => {
|
|
91
|
+
clearTimeout(timer);
|
|
92
|
+
this.isOpen = true;
|
|
93
|
+
resolve(Core_1.OperateResult.CreateSuccessResult());
|
|
94
|
+
});
|
|
95
|
+
this.socket.once('error', (err) => {
|
|
96
|
+
clearTimeout(timer);
|
|
97
|
+
resolve(new Core_1.OperateResult(`UDP socket error: ${err.message}`, -1));
|
|
98
|
+
});
|
|
99
|
+
// 绑定本地端口(0 表示随机)
|
|
100
|
+
this.socket.bind(this.LocalPort);
|
|
101
|
+
}
|
|
102
|
+
catch (e) {
|
|
103
|
+
resolve(new Core_1.OperateResult(`Unable to create UDP socket: ${e.message}`, -1));
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* 关闭 UDP socket
|
|
109
|
+
*/
|
|
110
|
+
async ConnectClose() {
|
|
111
|
+
if (!this.socket || !this.isOpen) {
|
|
112
|
+
return Core_1.OperateResult.CreateSuccessResult();
|
|
113
|
+
}
|
|
114
|
+
return new Promise((resolve) => {
|
|
115
|
+
this.socket.close(() => {
|
|
116
|
+
this.isOpen = false;
|
|
117
|
+
this.socket = undefined;
|
|
118
|
+
resolve(Core_1.OperateResult.CreateSuccessResult());
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* 发送请求并接收响应
|
|
124
|
+
*/
|
|
125
|
+
async request(frame, parser) {
|
|
126
|
+
// 加入请求队列
|
|
127
|
+
return this.requestQueue.enqueue(async () => {
|
|
128
|
+
// 进入通信锁
|
|
129
|
+
if (this.EnableCommunicationLock) {
|
|
130
|
+
const lockResult = await this.communicationLock.EnterLock(this.ConnectTimeOut);
|
|
131
|
+
if (!lockResult.IsSuccess) {
|
|
132
|
+
return Core_1.OperateResult.CreateFailedResult(lockResult);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
try {
|
|
136
|
+
const connected = await this.ConnectServer();
|
|
137
|
+
if (!connected.IsSuccess) {
|
|
138
|
+
return Core_1.OperateResult.CreateFailedResult(connected);
|
|
139
|
+
}
|
|
140
|
+
// 如果有消息解析器,使用它
|
|
141
|
+
const message = this.GetNewNetMessage();
|
|
142
|
+
if (message && !parser) {
|
|
143
|
+
return await this.requestWithMessage(frame, message);
|
|
144
|
+
}
|
|
145
|
+
// 否则使用传统的 parser 方式
|
|
146
|
+
return await this.requestSimple(frame, parser || ((data) => data));
|
|
147
|
+
}
|
|
148
|
+
finally {
|
|
149
|
+
// 离开通信锁
|
|
150
|
+
if (this.EnableCommunicationLock) {
|
|
151
|
+
this.communicationLock.LeaveLock();
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* 使用 INetMessage 解析器发送请求
|
|
158
|
+
*/
|
|
159
|
+
async requestWithMessage(frame, message) {
|
|
160
|
+
return new Promise((resolve) => {
|
|
161
|
+
const socket = this.socket;
|
|
162
|
+
let receiveBuffer = Buffer.alloc(0);
|
|
163
|
+
let done = false;
|
|
164
|
+
message.SendBytes = frame;
|
|
165
|
+
const finish = (result) => {
|
|
166
|
+
if (done)
|
|
167
|
+
return;
|
|
168
|
+
done = true;
|
|
169
|
+
clearTimeout(timer);
|
|
170
|
+
socket.removeListener('message', onMessage);
|
|
171
|
+
resolve(result);
|
|
172
|
+
};
|
|
173
|
+
const onMessage = (msg, rinfo) => {
|
|
174
|
+
// 验证响应来自目标服务器
|
|
175
|
+
if (rinfo.address !== this.IpAddress || rinfo.port !== this.Port) {
|
|
176
|
+
return; // 忽略来自其他地址的消息
|
|
177
|
+
}
|
|
178
|
+
receiveBuffer = Buffer.concat([receiveBuffer, msg]);
|
|
179
|
+
try {
|
|
180
|
+
// 第一步:接收头部
|
|
181
|
+
if (message.HeadBytes.length < message.ProtocolHeadBytesLength) {
|
|
182
|
+
const headLength = message.ProtocolHeadBytesLength;
|
|
183
|
+
if (headLength > 0 && receiveBuffer.length >= headLength) {
|
|
184
|
+
message.HeadBytes = receiveBuffer.subarray(0, headLength);
|
|
185
|
+
receiveBuffer = receiveBuffer.subarray(headLength);
|
|
186
|
+
// 检查头部合法性
|
|
187
|
+
if (!message.CheckHeadBytesLegal()) {
|
|
188
|
+
finish(new Core_1.OperateResult('Invalid message head', -3));
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
return; // 继续等待
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
// 第二步:获取内容长度
|
|
197
|
+
const contentLength = message.GetContentLengthByHeadBytes();
|
|
198
|
+
if (contentLength < 0) {
|
|
199
|
+
// 需要更多数据来确定长度
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
// 第三步:接收内容
|
|
203
|
+
if (receiveBuffer.length >= contentLength) {
|
|
204
|
+
message.ContentBytes = receiveBuffer.subarray(0, contentLength);
|
|
205
|
+
// 检查消息匹配
|
|
206
|
+
const matchResult = message.CheckMessageMatch(frame, Buffer.concat([message.HeadBytes, message.ContentBytes]));
|
|
207
|
+
if (matchResult === 0) {
|
|
208
|
+
finish(new Core_1.OperateResult('Message match failed', -4));
|
|
209
|
+
}
|
|
210
|
+
else if (matchResult === 1) {
|
|
211
|
+
finish(Core_1.OperateResult.CreateSuccessResult(message.ContentBytes));
|
|
212
|
+
}
|
|
213
|
+
// matchResult === -1 继续等待
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
catch (e) {
|
|
217
|
+
finish(new Core_1.OperateResult(`Parse error: ${e.message}`, -5));
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
const timer = setTimeout(() => {
|
|
221
|
+
finish(new Core_1.OperateResult('Receive timeout', -2));
|
|
222
|
+
}, this.ReceiveTimeOut);
|
|
223
|
+
socket.on('message', onMessage);
|
|
224
|
+
// 发送数据
|
|
225
|
+
socket.send(frame, this.Port, this.IpAddress, (err) => {
|
|
226
|
+
if (err) {
|
|
227
|
+
finish(new Core_1.OperateResult(`UDP send error: ${err.message}`, -1));
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* 简单的请求响应(不使用 INetMessage)
|
|
234
|
+
*/
|
|
235
|
+
async requestSimple(frame, parser) {
|
|
236
|
+
return new Promise((resolve) => {
|
|
237
|
+
const socket = this.socket;
|
|
238
|
+
let chunks = [];
|
|
239
|
+
let done = false;
|
|
240
|
+
const finish = (result) => {
|
|
241
|
+
if (done)
|
|
242
|
+
return;
|
|
243
|
+
done = true;
|
|
244
|
+
clearTimeout(timer);
|
|
245
|
+
socket.removeListener('message', onMessage);
|
|
246
|
+
resolve(result);
|
|
247
|
+
};
|
|
248
|
+
const onMessage = (msg, rinfo) => {
|
|
249
|
+
// 验证响应来自目标服务器
|
|
250
|
+
if (rinfo.address !== this.IpAddress || rinfo.port !== this.Port) {
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
chunks.push(msg);
|
|
254
|
+
try {
|
|
255
|
+
const body = parser(Buffer.concat(chunks));
|
|
256
|
+
if (body)
|
|
257
|
+
finish(Core_1.OperateResult.CreateSuccessResult(body));
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
// 继续等待
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
const timer = setTimeout(() => {
|
|
264
|
+
finish(new Core_1.OperateResult('Receive timeout', -2));
|
|
265
|
+
}, this.ReceiveTimeOut);
|
|
266
|
+
socket.on('message', onMessage);
|
|
267
|
+
socket.send(frame, this.Port, this.IpAddress, (err) => {
|
|
268
|
+
if (err) {
|
|
269
|
+
finish(new Core_1.OperateResult(`UDP send error: ${err.message}`, -1));
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* 批量发送请求(自动合并结果)
|
|
276
|
+
*/
|
|
277
|
+
async ReadFromCoreServer(commands) {
|
|
278
|
+
if (commands.length === 0) {
|
|
279
|
+
return Core_1.OperateResult.CreateSuccessResult(Buffer.alloc(0));
|
|
280
|
+
}
|
|
281
|
+
if (commands.length === 1) {
|
|
282
|
+
return this.request(commands[0]);
|
|
283
|
+
}
|
|
284
|
+
// 批量发送
|
|
285
|
+
const results = [];
|
|
286
|
+
for (const cmd of commands) {
|
|
287
|
+
const result = await this.request(cmd);
|
|
288
|
+
if (!result.IsSuccess) {
|
|
289
|
+
return result;
|
|
290
|
+
}
|
|
291
|
+
results.push(result.Content);
|
|
292
|
+
}
|
|
293
|
+
return Core_1.OperateResult.CreateSuccessResult(Buffer.concat(results));
|
|
294
|
+
}
|
|
295
|
+
fromResult(result, fn) {
|
|
296
|
+
return result.IsSuccess ? Core_1.OperateResult.CreateSuccessResult(fn(result.Content)) : Core_1.OperateResult.CreateFailedResult(result);
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* 释放资源
|
|
300
|
+
*/
|
|
301
|
+
async Dispose() {
|
|
302
|
+
await this.ConnectClose();
|
|
303
|
+
this.communicationLock.dispose();
|
|
304
|
+
this.requestQueue.clear();
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
exports.DeviceUdpNet = DeviceUdpNet;
|