node-red-contrib-symi-modbus 2.8.3 → 2.8.4

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 CHANGED
@@ -884,8 +884,39 @@ HomeKit网桥节点无需输入消息,自动同步主站配置和状态。
884
884
 
885
885
  ## 版本信息
886
886
 
887
- **当前版本**: v2.8.3
888
-
887
+ **当前版本**: v2.8.4
888
+
889
+ ### v2.8.4 (2025-11-11)
890
+
891
+ **核心优化**:
892
+ - **移除首次部署LED同步延迟**:部署/重启后立即同步所有LED状态(1秒内完成)
893
+ - 移除固定5秒初始化延迟限制
894
+ - 识别首次轮询(source: 'init'),允许立即发送LED反馈
895
+ - 通过队列机制自动限速(40ms间隔),不会造成总线拥堵
896
+ - 用户体验提升:重启后立即看到正确的LED状态
897
+
898
+ **稳定性增强**:
899
+ - **内存泄漏防护**:每5分钟自动检查监听器数量、队列长度、Buffer大小
900
+ - 监听器数量超过100个时告警
901
+ - 队列积压超过1000个时自动清理(保留最新100个)
902
+ - 帧缓冲区超过10KB时自动清空
903
+ - 确保长期稳定运行,不死机不卡顿
904
+ - **日志防护**:错误日志限流(每5分钟最多输出一次),避免硬盘被日志填满
905
+ - **资源清理**:节点关闭时正确清理所有定时器和监听器,防止内存泄漏
906
+
907
+ **实际使用场景优化**:
908
+ - 主站轮询:200ms/台,5台从站 = 1秒完成一轮
909
+ - 开关面板:支持50个以内,约200个按钮(设计极限500+按钮)
910
+ - Mesh设备发现:仅首次手动触发,已持久化,后续部署不再触发
911
+ - 部署后立即同步:主站首次轮询完成后,所有LED状态立即同步(1秒内)
912
+
913
+ **配置持久化**:
914
+ - 所有配置参数本地持久化存储
915
+ - Node-RED重启后自动恢复,不受重启影响
916
+ - Mesh设备列表持久化,无需重复扫描
917
+ - 断网/通网、断电/通电不影响正常运行
918
+
919
+
889
920
  ### v2.8.3 (2025-11-11)
890
921
 
891
922
  **核心功能**:
@@ -400,6 +400,9 @@ module.exports = function(RED) {
400
400
 
401
401
  // 节点关闭标志(用于静默关闭期间的警告)
402
402
  node.isClosing = false;
403
+
404
+ // 首次状态同步标志(用于识别部署/重启后的首次轮询)
405
+ node.hasReceivedInitialState = false;
403
406
 
404
407
  // 根据按钮编号计算deviceAddr和channel(用于LED反馈)
405
408
  // Symi协议公式:按键编号 = deviceAddr * 4 - 4 + channel
@@ -496,12 +499,12 @@ module.exports = function(RED) {
496
499
  node.log(`监听Symi开关${node.config.switchId}的按钮${node.config.buttonNumber}按键事件...`);
497
500
 
498
501
  node.isRs485Connected = true;
502
+
503
+ // 立即结束初始化阶段(允许Mesh按键控制继电器)
504
+ // LED反馈已通过队列机制自动限速,无需延迟
505
+ node.isInitializing = false;
499
506
  node.updateStatus();
500
507
 
501
- // 结束初始化阶段(5秒后)- 避免部署时大量LED反馈同时发送
502
- setTimeout(() => {
503
- node.isInitializing = false;
504
- }, 5000);
505
508
 
506
509
  // 监听物理开关面板的按键事件
507
510
  node.startListeningButtonEvents();
@@ -545,6 +548,12 @@ module.exports = function(RED) {
545
548
  const value = Boolean(data.value);
546
549
 
547
550
  // 检查是否是我们关注的从站和线圈
551
+ // 识别首次轮询(source: 'init'),标记已接收初始状态
552
+ if (data.source === 'init' && !node.hasReceivedInitialState) {
553
+ node.hasReceivedInitialState = true;
554
+ node.debug(`收到首次轮询状态同步:从站${slave} 线圈${coil} = ${value ? 'ON' : 'OFF'}`);
555
+ }
556
+
548
557
  if (slave === node.config.targetSlaveAddress && coil === node.config.targetCoilNumber) {
549
558
  node.log(`收到状态变化事件:从站${slave} 线圈${coil} = ${value ? 'ON' : 'OFF'}`);
550
559
 
@@ -838,11 +847,9 @@ module.exports = function(RED) {
838
847
  return;
839
848
  }
840
849
 
841
- // 初始化期间不发送LED反馈(避免部署时大量LED同时发送)
842
- if (node.isInitializing) {
843
- node.debug(`[sendCommandToPanel] 初始化期间,跳过LED反馈`);
844
- return;
845
- }
850
+ // 智能LED反馈控制:首次轮询时允许发送,通过队列自动限速
851
+ // 移除固定5秒延迟,部署/重启后立即同步LED状态(1秒内完成)
852
+ // 队列机制已有40ms间隔,不会造成总线拥堵
846
853
 
847
854
  const now = Date.now();
848
855
 
@@ -47,6 +47,34 @@ module.exports = function(RED) {
47
47
  // 延迟关闭定时器(避免Mesh设备发现等临时操作导致串口频繁开关)
48
48
  node.closeTimer = null; // 延迟关闭定时器
49
49
 
50
+ // 内存泄漏防护:定期检查监听器数量、队列长度、Buffer大小
51
+ node.memoryCheckInterval = setInterval(() => {
52
+ try {
53
+ // 检查监听器数量(正常情况下不应超过100个)
54
+ if (node.dataListeners.length > 100) {
55
+ node.warn(`监听器数量异常: ${node.dataListeners.length},可能存在内存泄漏`);
56
+ }
57
+
58
+ // 检查写入队列长度(正常情况下不应超过1000个)
59
+ if (node.writeQueue.length > 1000) {
60
+ node.warn(`写入队列积压严重: ${node.writeQueue.length},自动清理旧数据`);
61
+ // 只保留最新的100个写入请求
62
+ const removed = node.writeQueue.length - 100;
63
+ node.writeQueue = node.writeQueue.slice(-100);
64
+ node.warn(`已清理${removed}个积压的写入请求`);
65
+ }
66
+
67
+ // 检查帧缓冲区大小(正常情况下不应超过10KB)
68
+ if (node.frameBuffer && node.frameBuffer.length > 10000) {
69
+ node.warn(`帧缓冲区过大: ${node.frameBuffer.length}字节,自动清空`);
70
+ node.frameBuffer = Buffer.alloc(0);
71
+ }
72
+ } catch (err) {
73
+ node.error(`内存检查失败: ${err.message}`);
74
+ }
75
+ }, 5 * 60 * 1000); // 每5分钟检查一次
76
+
77
+
50
78
  // 打开TCP连接
51
79
  node.openTcpConnection = function() {
52
80
  if (node.connection && !node.connection.destroyed) {
@@ -179,6 +207,12 @@ module.exports = function(RED) {
179
207
  const openDelay = needDelay ? 500 : 0;
180
208
 
181
209
  setTimeout(() => {
210
+ // 检查节点是否正在关闭
211
+ if (node.isClosing) {
212
+ node.debug('节点正在关闭,取消串口打开操作');
213
+ return;
214
+ }
215
+
182
216
  node.isOpening = true;
183
217
 
184
218
  try {
@@ -512,6 +546,12 @@ module.exports = function(RED) {
512
546
  // 清除延迟关闭定时器
513
547
  if (node.closeTimer) {
514
548
  clearTimeout(node.closeTimer);
549
+
550
+ // 清除内存检查定时器
551
+ if (node.memoryCheckInterval) {
552
+ clearInterval(node.memoryCheckInterval);
553
+ node.memoryCheckInterval = null;
554
+ }
515
555
  node.closeTimer = null;
516
556
  }
517
557
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-symi-modbus",
3
- "version": "2.8.3",
3
+ "version": "2.8.4",
4
4
  "description": "Node-RED Modbus节点,支持TCP/串口通信、串口自动搜索、多设备轮询、可选MQTT集成(支持纯本地模式和MQTT模式)、Home Assistant自动发现、HomeKit网桥、可视化控制看板、自定义协议转换和物理开关面板双向同步,工控机长期稳定运行",
5
5
  "main": "nodes/modbus-master.js",
6
6
  "scripts": {