node-red-contrib-symi-modbus 2.6.0 → 2.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -26
- package/nodes/modbus-master.js +54 -10
- package/nodes/serial-port-config.js +151 -21
- package/package.json +1 -2
package/README.md
CHANGED
|
@@ -231,8 +231,14 @@ node-red-restart
|
|
|
231
231
|
- **内存管理**:自动清理缓存,释放无用对象
|
|
232
232
|
- **事件监听器清理**:关闭时移除所有监听器,防止内存泄漏
|
|
233
233
|
- **智能日志限流**:错误日志10分钟输出一次,避免日志刷屏
|
|
234
|
-
-
|
|
234
|
+
- **智能重连机制**:
|
|
235
|
+
- Modbus连接断开自动重连(指数退避:5秒→10秒→20秒...最大60秒)
|
|
236
|
+
- MQTT连接断开自动重连(支持多地址fallback)
|
|
237
|
+
- 串口拔插自动检测并重连
|
|
238
|
+
- TCP网络故障自动恢复
|
|
239
|
+
- 连接前彻底清理旧实例,避免资源泄漏
|
|
235
240
|
- **互斥锁机制**:防止读写冲突导致的数据异常
|
|
241
|
+
- **Keep-Alive心跳**:TCP连接启用30秒心跳检测
|
|
236
242
|
|
|
237
243
|
## 技术规格
|
|
238
244
|
|
|
@@ -435,7 +441,7 @@ msg.payload = 1; // 或 0
|
|
|
435
441
|
|
|
436
442
|
## 项目信息
|
|
437
443
|
|
|
438
|
-
**版本**: v2.6.
|
|
444
|
+
**版本**: v2.6.2
|
|
439
445
|
|
|
440
446
|
**核心功能**:
|
|
441
447
|
- 支持多种Modbus协议(Telnet ASCII、RTU over TCP、Modbus TCP、Modbus RTU串口)
|
|
@@ -445,7 +451,7 @@ msg.payload = 1; // 或 0
|
|
|
445
451
|
- MQTT集成(Home Assistant自动发现,实体唯一性保证,QoS=0高性能发布)
|
|
446
452
|
- 物理开关面板双向同步(亖米协议支持,LED反馈同步)
|
|
447
453
|
- 共享连接架构(多个从站开关节点共享同一个串口/TCP连接,支持500+节点)
|
|
448
|
-
-
|
|
454
|
+
- 长期稳定运行(内存管理、智能重连、错误日志限流、异步MQTT发布)
|
|
449
455
|
|
|
450
456
|
**技术栈**:
|
|
451
457
|
- modbus-serial: ^8.0.23(内部封装serialport,支持TCP和串口)
|
|
@@ -454,29 +460,26 @@ msg.payload = 1; // 或 0
|
|
|
454
460
|
- Node.js: >=14.0.0
|
|
455
461
|
- Node-RED: >=2.0.0
|
|
456
462
|
|
|
457
|
-
**最新更新(v2.6.
|
|
458
|
-
-
|
|
459
|
-
-
|
|
460
|
-
-
|
|
461
|
-
-
|
|
462
|
-
-
|
|
463
|
-
-
|
|
464
|
-
-
|
|
465
|
-
|
|
466
|
-
-
|
|
467
|
-
-
|
|
468
|
-
-
|
|
469
|
-
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
-
|
|
476
|
-
|
|
477
|
-
- 完善串口配置:支持所有串口参数
|
|
478
|
-
- 智能日志输出:减少系统负担
|
|
479
|
-
- 长期稳定运行:工控机7x24小时验证
|
|
463
|
+
**最新更新(v2.6.2)**:
|
|
464
|
+
- **🔥 智能重连机制**:全面优化的连接恢复系统
|
|
465
|
+
- 串口拔插自动重连:检测到串口断开后自动尝试重连,支持热插拔
|
|
466
|
+
- TCP断网自动重连:网络故障恢复后自动重新建立连接
|
|
467
|
+
- 指数退避策略:重连间隔智能调整(5秒→10秒→20秒→40秒→最大60秒)
|
|
468
|
+
- 连接状态完全释放:重连前彻底清理旧连接,避免资源泄漏
|
|
469
|
+
- Keep-Alive心跳:TCP连接启用30秒心跳检测,及时发现网络故障
|
|
470
|
+
- 错误类型识别:准确识别8种连接错误类型,精准触发重连
|
|
471
|
+
- **长期稳定性保障**:
|
|
472
|
+
- 完善的连接生命周期管理,避免资源泄漏
|
|
473
|
+
- 节点关闭时自动清除所有定时器和监听器
|
|
474
|
+
- 重连定时器严格管理,避免重复创建
|
|
475
|
+
- 支持长期运行(测试验证:串口拔插后自动重连成功)
|
|
476
|
+
|
|
477
|
+
**v2.6.0-v2.6.1更新**:
|
|
478
|
+
- 双模式支持:完整支持开关按钮和场景按钮两种模式
|
|
479
|
+
- 精确LED反馈:使用原始设备地址和通道,确保LED反馈到正确按键
|
|
480
|
+
- 完美状态同步:物理按键和HA远程控制双向同步
|
|
481
|
+
- 性能提升:全局防抖、智能日志、互斥锁机制
|
|
482
|
+
- 用户体验:继电器路数优化、串口配置完善
|
|
480
483
|
|
|
481
484
|
**性能优化**:
|
|
482
485
|
- 轮询间隔优化:修复间隔计算逻辑,确保每个从站使用正确的轮询间隔
|
package/nodes/modbus-master.js
CHANGED
|
@@ -131,6 +131,7 @@ module.exports = function(RED) {
|
|
|
131
131
|
node.isConnected = false;
|
|
132
132
|
node.pollTimer = null;
|
|
133
133
|
node.reconnectTimer = null;
|
|
134
|
+
node.reconnectAttempts = 0; // 重连尝试次数
|
|
134
135
|
node.currentSlaveIndex = 0;
|
|
135
136
|
node.deviceStates = {}; // 存储每个设备的状态
|
|
136
137
|
node.mqttClient = null;
|
|
@@ -176,6 +177,29 @@ module.exports = function(RED) {
|
|
|
176
177
|
|
|
177
178
|
// 连接Modbus
|
|
178
179
|
node.connectModbus = async function() {
|
|
180
|
+
// 清除旧的重连定时器
|
|
181
|
+
if (node.reconnectTimer) {
|
|
182
|
+
clearTimeout(node.reconnectTimer);
|
|
183
|
+
node.reconnectTimer = null;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// 关闭旧连接(确保完全释放)
|
|
187
|
+
if (node.client && node.client.isOpen) {
|
|
188
|
+
try {
|
|
189
|
+
node.log('关闭旧的Modbus连接...');
|
|
190
|
+
await new Promise((resolve) => {
|
|
191
|
+
node.client.close(() => {
|
|
192
|
+
resolve();
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
} catch (err) {
|
|
196
|
+
node.warn(`关闭旧连接时出错: ${err.message}`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// 创建新的Modbus客户端实例
|
|
201
|
+
node.client = new ModbusRTU();
|
|
202
|
+
|
|
179
203
|
try {
|
|
180
204
|
if (node.config.connectionType === "tcp") {
|
|
181
205
|
// 验证TCP主机地址
|
|
@@ -208,12 +232,15 @@ module.exports = function(RED) {
|
|
|
208
232
|
|
|
209
233
|
node.log(`TCP网关连接成功(${modeNames[tcpMode]}): ${node.config.tcpHost}:${node.config.tcpPort}`);
|
|
210
234
|
} else {
|
|
235
|
+
node.log(`正在连接串口: ${node.config.serialPort} @ ${node.config.serialBaudRate || 9600}bps...`);
|
|
236
|
+
|
|
211
237
|
await node.client.connectRTUBuffered(node.config.serialPort, {
|
|
212
238
|
baudRate: node.config.serialBaudRate || 9600,
|
|
213
239
|
dataBits: node.config.serialDataBits || 8,
|
|
214
240
|
stopBits: node.config.serialStopBits || 1,
|
|
215
241
|
parity: node.config.serialParity || 'none'
|
|
216
242
|
});
|
|
243
|
+
|
|
217
244
|
node.log(`串口Modbus连接成功: ${node.config.serialPort} @ ${node.config.serialBaudRate || 9600}bps`);
|
|
218
245
|
}
|
|
219
246
|
|
|
@@ -221,7 +248,9 @@ module.exports = function(RED) {
|
|
|
221
248
|
const timeout = node.config.connectionType === "serial" ? 10000 : 5000;
|
|
222
249
|
node.client.setTimeout(timeout);
|
|
223
250
|
node.log(`Modbus超时设置: ${timeout}ms`);
|
|
251
|
+
|
|
224
252
|
node.isConnected = true;
|
|
253
|
+
node.reconnectAttempts = 0; // 重置重连计数
|
|
225
254
|
node.log(`Modbus已连接: ${node.config.connectionType === "tcp" ? `${node.config.tcpHost}:${node.config.tcpPort}` : node.config.serialPort}`);
|
|
226
255
|
node.updateNodeStatus();
|
|
227
256
|
|
|
@@ -239,7 +268,6 @@ module.exports = function(RED) {
|
|
|
239
268
|
}
|
|
240
269
|
|
|
241
270
|
// 立即启动轮询(不等待MQTT连接)
|
|
242
|
-
// 修复:确保轮询在Modbus连接成功后立即启动,不依赖MQTT状态
|
|
243
271
|
if (!node.pollTimer) {
|
|
244
272
|
node.log('Modbus连接成功,立即启动轮询...');
|
|
245
273
|
node.startPolling();
|
|
@@ -252,12 +280,17 @@ module.exports = function(RED) {
|
|
|
252
280
|
node.isConnected = false;
|
|
253
281
|
node.updateNodeStatus();
|
|
254
282
|
|
|
255
|
-
// 5
|
|
283
|
+
// 使用指数退避策略重试(5秒、10秒、20秒...最大60秒)
|
|
256
284
|
if (!node.isClosing && !node.reconnectTimer) {
|
|
285
|
+
const delay = Math.min(5000 * Math.pow(2, node.reconnectAttempts), 60000);
|
|
286
|
+
node.reconnectAttempts++;
|
|
287
|
+
|
|
288
|
+
node.log(`${delay/1000}秒后重试Modbus连接(第${node.reconnectAttempts}次)...`);
|
|
289
|
+
|
|
257
290
|
node.reconnectTimer = setTimeout(() => {
|
|
258
291
|
node.reconnectTimer = null;
|
|
259
292
|
node.connectModbus();
|
|
260
|
-
},
|
|
293
|
+
}, delay);
|
|
261
294
|
}
|
|
262
295
|
}
|
|
263
296
|
};
|
|
@@ -913,12 +946,18 @@ module.exports = function(RED) {
|
|
|
913
946
|
node.updateNodeStatus();
|
|
914
947
|
|
|
915
948
|
// 检测是否是连接断开错误
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
949
|
+
const isConnectionError = err.message && (
|
|
950
|
+
err.message.includes('ECONNRESET') ||
|
|
951
|
+
err.message.includes('ETIMEDOUT') ||
|
|
952
|
+
err.message.includes('ENOTCONN') ||
|
|
953
|
+
err.message.includes('ECONNREFUSED') ||
|
|
954
|
+
err.message.includes('EHOSTUNREACH') ||
|
|
955
|
+
err.message.includes('Port Not Open') ||
|
|
956
|
+
err.message.includes('Port is not open') ||
|
|
957
|
+
err.message.includes('Cannot call write')
|
|
958
|
+
);
|
|
921
959
|
|
|
960
|
+
if (isConnectionError) {
|
|
922
961
|
// 连接断开,尝试重连
|
|
923
962
|
if (shouldLog) {
|
|
924
963
|
node.warn('检测到连接断开,尝试重连...');
|
|
@@ -936,12 +975,17 @@ module.exports = function(RED) {
|
|
|
936
975
|
}
|
|
937
976
|
}
|
|
938
977
|
|
|
939
|
-
//
|
|
978
|
+
// 使用指数退避策略重连
|
|
940
979
|
if (!node.isClosing && !node.reconnectTimer) {
|
|
980
|
+
const delay = Math.min(5000 * Math.pow(2, node.reconnectAttempts), 60000);
|
|
981
|
+
node.reconnectAttempts++;
|
|
982
|
+
|
|
983
|
+
node.log(`${delay/1000}秒后重试Modbus连接(第${node.reconnectAttempts}次)...`);
|
|
984
|
+
|
|
941
985
|
node.reconnectTimer = setTimeout(() => {
|
|
942
986
|
node.reconnectTimer = null;
|
|
943
987
|
node.connectModbus();
|
|
944
|
-
},
|
|
988
|
+
}, delay);
|
|
945
989
|
}
|
|
946
990
|
}
|
|
947
991
|
}
|
|
@@ -25,6 +25,9 @@ module.exports = function(RED) {
|
|
|
25
25
|
// 共享的连接实例
|
|
26
26
|
node.connection = null; // TCP socket 或 SerialPort
|
|
27
27
|
node.isOpening = false; // 标记是否正在打开连接
|
|
28
|
+
node.isClosing = false; // 标记节点是否正在关闭
|
|
29
|
+
node.reconnectTimer = null; // 重连定时器
|
|
30
|
+
node.reconnectAttempts = 0; // 重连尝试次数
|
|
28
31
|
|
|
29
32
|
// 数据监听器列表(每个从站开关节点注册一个)
|
|
30
33
|
node.dataListeners = [];
|
|
@@ -36,11 +39,20 @@ module.exports = function(RED) {
|
|
|
36
39
|
return;
|
|
37
40
|
}
|
|
38
41
|
|
|
42
|
+
// 清除旧的重连定时器
|
|
43
|
+
if (node.reconnectTimer) {
|
|
44
|
+
clearTimeout(node.reconnectTimer);
|
|
45
|
+
node.reconnectTimer = null;
|
|
46
|
+
}
|
|
47
|
+
|
|
39
48
|
try {
|
|
40
49
|
node.connection = new net.Socket();
|
|
50
|
+
node.connection.setKeepAlive(true, 30000); // 启用TCP Keep-Alive,30秒间隔
|
|
51
|
+
node.connection.setTimeout(60000); // 60秒超时
|
|
41
52
|
|
|
42
53
|
node.connection.connect(node.tcpPort, node.tcpHost, () => {
|
|
43
54
|
node.log(`TCP连接已建立: ${node.tcpHost}:${node.tcpPort}`);
|
|
55
|
+
node.reconnectAttempts = 0; // 重置重连计数
|
|
44
56
|
});
|
|
45
57
|
|
|
46
58
|
// 监听数据(分发给所有注册的监听器)
|
|
@@ -55,21 +67,43 @@ module.exports = function(RED) {
|
|
|
55
67
|
});
|
|
56
68
|
});
|
|
57
69
|
|
|
70
|
+
// 监听超时
|
|
71
|
+
node.connection.on('timeout', () => {
|
|
72
|
+
node.warn('TCP连接超时,尝试重连...');
|
|
73
|
+
if (node.connection) {
|
|
74
|
+
node.connection.destroy();
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
58
78
|
// 监听错误
|
|
59
79
|
node.connection.on('error', (err) => {
|
|
60
80
|
node.error(`TCP连接错误: ${err.message}`);
|
|
81
|
+
// 不在这里重连,在close事件中统一处理
|
|
61
82
|
});
|
|
62
83
|
|
|
63
|
-
//
|
|
64
|
-
node.connection.on('close', () => {
|
|
65
|
-
node.log(
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
84
|
+
// 监听关闭(统一的重连入口)
|
|
85
|
+
node.connection.on('close', (hadError) => {
|
|
86
|
+
node.log(`TCP连接已关闭${hadError ? '(有错误)' : ''}`);
|
|
87
|
+
|
|
88
|
+
// 清理连接对象
|
|
89
|
+
if (node.connection) {
|
|
90
|
+
node.connection.removeAllListeners();
|
|
91
|
+
node.connection.destroy();
|
|
92
|
+
node.connection = null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// 自动重连(如果不是主动关闭且有监听器在使用)
|
|
96
|
+
if (!node.isClosing && node.dataListeners.length > 0) {
|
|
97
|
+
const delay = Math.min(5000 * Math.pow(2, node.reconnectAttempts), 60000); // 指数退避,最大60秒
|
|
98
|
+
node.reconnectAttempts++;
|
|
99
|
+
|
|
100
|
+
node.log(`${delay/1000}秒后尝试重新连接TCP(第${node.reconnectAttempts}次)...`);
|
|
101
|
+
|
|
102
|
+
node.reconnectTimer = setTimeout(() => {
|
|
103
|
+
node.reconnectTimer = null;
|
|
70
104
|
node.openTcpConnection();
|
|
71
|
-
}
|
|
72
|
-
}
|
|
105
|
+
}, delay);
|
|
106
|
+
}
|
|
73
107
|
});
|
|
74
108
|
} catch (err) {
|
|
75
109
|
node.error(`TCP连接初始化失败: ${err.message}`);
|
|
@@ -88,6 +122,25 @@ module.exports = function(RED) {
|
|
|
88
122
|
return;
|
|
89
123
|
}
|
|
90
124
|
|
|
125
|
+
// 清除旧的重连定时器
|
|
126
|
+
if (node.reconnectTimer) {
|
|
127
|
+
clearTimeout(node.reconnectTimer);
|
|
128
|
+
node.reconnectTimer = null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// 清理旧的串口实例(确保完全释放)
|
|
132
|
+
if (node.connection) {
|
|
133
|
+
try {
|
|
134
|
+
node.connection.removeAllListeners();
|
|
135
|
+
if (node.connection.isOpen) {
|
|
136
|
+
node.connection.close();
|
|
137
|
+
}
|
|
138
|
+
} catch (err) {
|
|
139
|
+
// 忽略清理错误
|
|
140
|
+
}
|
|
141
|
+
node.connection = null;
|
|
142
|
+
}
|
|
143
|
+
|
|
91
144
|
node.isOpening = true;
|
|
92
145
|
|
|
93
146
|
try {
|
|
@@ -106,10 +159,24 @@ module.exports = function(RED) {
|
|
|
106
159
|
|
|
107
160
|
if (err) {
|
|
108
161
|
node.error(`串口打开失败: ${err.message}`);
|
|
162
|
+
|
|
163
|
+
// 打开失败时也要触发重连(如果有监听器在使用)
|
|
164
|
+
if (!node.isClosing && node.dataListeners.length > 0) {
|
|
165
|
+
const delay = Math.min(5000 * Math.pow(2, node.reconnectAttempts), 60000);
|
|
166
|
+
node.reconnectAttempts++;
|
|
167
|
+
|
|
168
|
+
node.log(`${delay/1000}秒后尝试重新打开串口(第${node.reconnectAttempts}次)...`);
|
|
169
|
+
|
|
170
|
+
node.reconnectTimer = setTimeout(() => {
|
|
171
|
+
node.reconnectTimer = null;
|
|
172
|
+
node.openSerialConnection();
|
|
173
|
+
}, delay);
|
|
174
|
+
}
|
|
109
175
|
return;
|
|
110
176
|
}
|
|
111
177
|
|
|
112
178
|
node.log(`串口已打开: ${node.serialPort} @ ${node.baudRate}bps ${node.dataBits}${node.parity.charAt(0).toUpperCase()}${node.stopBits}`);
|
|
179
|
+
node.reconnectAttempts = 0; // 重置重连计数
|
|
113
180
|
|
|
114
181
|
// 监听数据(分发给所有注册的监听器)
|
|
115
182
|
node.connection.on('data', (data) => {
|
|
@@ -126,15 +193,55 @@ module.exports = function(RED) {
|
|
|
126
193
|
// 监听错误
|
|
127
194
|
node.connection.on('error', (err) => {
|
|
128
195
|
node.error(`串口错误: ${err.message}`);
|
|
196
|
+
// 不在这里重连,在close事件中统一处理
|
|
129
197
|
});
|
|
130
198
|
|
|
131
|
-
//
|
|
132
|
-
node.connection.on('close', () => {
|
|
133
|
-
|
|
199
|
+
// 监听关闭(统一的重连入口)
|
|
200
|
+
node.connection.on('close', (err) => {
|
|
201
|
+
if (err) {
|
|
202
|
+
node.log(`串口已关闭(错误: ${err.message})`);
|
|
203
|
+
} else {
|
|
204
|
+
node.log('串口已关闭');
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// 清理连接对象
|
|
208
|
+
if (node.connection) {
|
|
209
|
+
try {
|
|
210
|
+
node.connection.removeAllListeners();
|
|
211
|
+
} catch (e) {
|
|
212
|
+
// 忽略清理错误
|
|
213
|
+
}
|
|
214
|
+
node.connection = null;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// 自动重连(如果不是主动关闭且有监听器在使用)
|
|
218
|
+
if (!node.isClosing && node.dataListeners.length > 0) {
|
|
219
|
+
const delay = Math.min(5000 * Math.pow(2, node.reconnectAttempts), 60000); // 指数退避,最大60秒
|
|
220
|
+
node.reconnectAttempts++;
|
|
221
|
+
|
|
222
|
+
node.log(`检测到串口断开,${delay/1000}秒后尝试重新连接(第${node.reconnectAttempts}次)...`);
|
|
223
|
+
|
|
224
|
+
node.reconnectTimer = setTimeout(() => {
|
|
225
|
+
node.reconnectTimer = null;
|
|
226
|
+
node.openSerialConnection();
|
|
227
|
+
}, delay);
|
|
228
|
+
}
|
|
134
229
|
});
|
|
135
230
|
});
|
|
136
231
|
} catch (err) {
|
|
232
|
+
node.isOpening = false;
|
|
137
233
|
node.error(`串口初始化失败: ${err.message}`);
|
|
234
|
+
|
|
235
|
+
// 初始化失败时也要触发重连
|
|
236
|
+
if (!node.isClosing && node.dataListeners.length > 0) {
|
|
237
|
+
const delay = Math.min(5000 * Math.pow(2, node.reconnectAttempts), 60000);
|
|
238
|
+
node.reconnectAttempts++;
|
|
239
|
+
|
|
240
|
+
node.reconnectTimer = setTimeout(() => {
|
|
241
|
+
node.reconnectTimer = null;
|
|
242
|
+
node.openSerialConnection();
|
|
243
|
+
}, delay);
|
|
244
|
+
}
|
|
138
245
|
}
|
|
139
246
|
};
|
|
140
247
|
|
|
@@ -226,25 +333,48 @@ module.exports = function(RED) {
|
|
|
226
333
|
|
|
227
334
|
// 节点关闭时清理
|
|
228
335
|
node.on('close', function(done) {
|
|
336
|
+
node.isClosing = true; // 标记正在关闭,防止自动重连
|
|
337
|
+
|
|
338
|
+
// 清除重连定时器
|
|
339
|
+
if (node.reconnectTimer) {
|
|
340
|
+
clearTimeout(node.reconnectTimer);
|
|
341
|
+
node.reconnectTimer = null;
|
|
342
|
+
}
|
|
343
|
+
|
|
229
344
|
// 清空所有监听器
|
|
230
345
|
node.dataListeners = [];
|
|
231
346
|
|
|
232
347
|
// 关闭连接
|
|
233
348
|
if (node.connection) {
|
|
234
349
|
if (node.connectionType === 'tcp') {
|
|
235
|
-
|
|
236
|
-
node.connection.
|
|
350
|
+
try {
|
|
351
|
+
node.connection.removeAllListeners();
|
|
352
|
+
if (!node.connection.destroyed) {
|
|
353
|
+
node.connection.destroy();
|
|
354
|
+
}
|
|
355
|
+
} catch (err) {
|
|
356
|
+
node.warn(`关闭TCP连接时出错: ${err.message}`);
|
|
237
357
|
}
|
|
358
|
+
node.connection = null;
|
|
238
359
|
done();
|
|
239
360
|
} else {
|
|
240
|
-
|
|
241
|
-
node.connection.
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
361
|
+
try {
|
|
362
|
+
node.connection.removeAllListeners();
|
|
363
|
+
if (node.connection.isOpen) {
|
|
364
|
+
node.connection.close((err) => {
|
|
365
|
+
if (err) {
|
|
366
|
+
node.error(`串口关闭失败: ${err.message}`);
|
|
367
|
+
}
|
|
368
|
+
node.connection = null;
|
|
369
|
+
done();
|
|
370
|
+
});
|
|
371
|
+
} else {
|
|
372
|
+
node.connection = null;
|
|
245
373
|
done();
|
|
246
|
-
}
|
|
247
|
-
}
|
|
374
|
+
}
|
|
375
|
+
} catch (err) {
|
|
376
|
+
node.warn(`关闭串口连接时出错: ${err.message}`);
|
|
377
|
+
node.connection = null;
|
|
248
378
|
done();
|
|
249
379
|
}
|
|
250
380
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-red-contrib-symi-modbus",
|
|
3
|
-
"version": "2.6.
|
|
3
|
+
"version": "2.6.2",
|
|
4
4
|
"description": "Node-RED Modbus节点,支持TCP/串口通信、串口自动搜索、多设备轮询、智能MQTT连接(自动fallback HassOS/Docker环境)、Home Assistant自动发现和物理开关面板双向同步,工控机长期稳定运行",
|
|
5
5
|
"main": "nodes/modbus-master.js",
|
|
6
6
|
"scripts": {
|
|
@@ -42,7 +42,6 @@
|
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"modbus-serial": "^8.0.23",
|
|
44
44
|
"mqtt": "^5.14.1",
|
|
45
|
-
"node-red-contrib-symi-modbus": "file:node-red-contrib-symi-modbus-2.6.0.tgz",
|
|
46
45
|
"serialport": "^12.0.0"
|
|
47
46
|
},
|
|
48
47
|
"repository": {
|