node-red-contrib-symi-modbus 2.6.3 → 2.6.5

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
@@ -479,7 +479,7 @@ msg.payload = 1; // 或 0
479
479
 
480
480
  ## 项目信息
481
481
 
482
- **版本**: v2.6.3
482
+ **版本**: v2.6.5
483
483
 
484
484
  **核心功能**:
485
485
  - 支持多种Modbus协议(Telnet ASCII、RTU over TCP、Modbus TCP、Modbus RTU串口)
@@ -499,20 +499,29 @@ msg.payload = 1; // 或 0
499
499
  - Node.js: >=14.0.0
500
500
  - Node-RED: >=2.0.0
501
501
 
502
- **最新更新(v2.6.3)**:
502
+ **最新更新(v2.6.5)**:
503
+ - **🔥 修复MQTT报错问题**:彻底解决未配置MQTT时的错误日志
504
+ - 从站开关节点新增"启用MQTT"勾选框
505
+ - 默认不启用MQTT,不会尝试连接
506
+ - 勾选后才显示MQTT服务器配置
507
+ - 不会在调试窗口产生任何MQTT错误日志
508
+ - **更好的用户体验**:
509
+ - 节点拖出即可使用(无需配置MQTT)
510
+ - 不再有烦人的错误提示
511
+ - 主站和从站开关均支持MQTT可选
512
+ - 本地模式和MQTT模式自由切换
513
+
514
+ **v2.6.4更新**:
515
+ - **🔥 日志优化**:大幅减少日志输出,保证长期稳定运行
516
+ - 高频操作日志改为debug级别
517
+ - 默认不输出到日志文件
518
+ - 完善的内存清理机制
519
+
520
+ **v2.6.3更新**:
503
521
  - **🔥 MQTT可选配置**:完全兼容无MQTT环境
504
- - 本地模式:纯串口通信,断网也能稳定运行
505
- - MQTT模式:可选接入Home Assistant等第三方平台
506
- - 智能切换:MQTT断开时自动切换到本地模式
507
- - 状态显示:清晰区分当前运行模式
508
- - **改进的错误提示**:
509
- - MQTT未配置时不再报错,提示使用本地模式
510
- - 友好的配置引导信息
511
- - 更清晰的节点状态指示
512
- - **增强的稳定性**:
513
- - 支持纯本地化部署,不受网络影响
514
- - 工控机断网环境下持续稳定运行
515
- - 保持所有原有功能的兼容性
522
+ - 本地模式:纯串口通信
523
+ - MQTT模式:可选接入HA
524
+ - 智能切换和状态显示
516
525
 
517
526
  **性能优化**:
518
527
  - 轮询间隔优化:修复间隔计算逻辑,确保每个从站使用正确的轮询间隔
@@ -495,7 +495,7 @@ module.exports = function(RED) {
495
495
  tryConnect(brokerCandidates[0]);
496
496
  }, 5000);
497
497
  } else {
498
- node.log(`尝试备用MQTT broker: ${nextBroker}`);
498
+ node.debug(`尝试备用MQTT broker: ${nextBroker}`);
499
499
  setTimeout(() => {
500
500
  tryConnect(nextBroker);
501
501
  }, 500); // 快速尝试下一个地址
@@ -866,9 +866,9 @@ module.exports = function(RED) {
866
866
  }
867
867
 
868
868
  if (isFirstPoll) {
869
- node.log(`从站${slaveId}首次轮询成功,读取${coilCount}个线圈`);
869
+ node.debug(`从站${slaveId}首次轮询成功,读取${coilCount}个线圈`);
870
870
  } else if (publishCount > 0) {
871
- node.log(`从站${slaveId}状态变化: ${publishCount}个线圈`);
871
+ node.debug(`从站${slaveId}状态变化: ${publishCount}个线圈`);
872
872
  }
873
873
 
874
874
  node.deviceStates[slaveId].lastUpdate = Date.now();
@@ -1021,7 +1021,7 @@ module.exports = function(RED) {
1021
1021
  const channel = frame.channel; // 通道号(1-8)
1022
1022
  const state = frame.opInfo[0] === 0x01; // 状态(1=开,0=关)
1023
1023
 
1024
- node.log(`Symi按键事件: 设备${deviceAddr} 通道${channel} 状态=${state ? 'ON' : 'OFF'}`);
1024
+ node.debug(`Symi按键事件: 设备${deviceAddr} 通道${channel} 状态=${state ? 'ON' : 'OFF'}`);
1025
1025
 
1026
1026
  // 查找对应的从站和线圈
1027
1027
  // 假设:设备地址1对应从站10,设备地址2对应从站11,以此类推
@@ -1042,7 +1042,7 @@ module.exports = function(RED) {
1042
1042
  return;
1043
1043
  }
1044
1044
 
1045
- node.log(`Symi按键映射: 设备${deviceAddr}通道${channel} → 从站${slaveId}线圈${coilNumber}`);
1045
+ node.debug(`Symi按键映射: 设备${deviceAddr}通道${channel} → 从站${slaveId}线圈${coilNumber}`);
1046
1046
 
1047
1047
  // 写入线圈(异步,不阻塞)
1048
1048
  node.writeSingleCoil(slaveId, coilNumber, state).catch(err => {
@@ -1102,7 +1102,7 @@ module.exports = function(RED) {
1102
1102
  // 更新本地状态
1103
1103
  node.deviceStates[slaveId].coils[coil] = value;
1104
1104
 
1105
- node.log(`写入成功: 从站${slaveId} 线圈${coil} = ${value}`);
1105
+ node.debug(`写入成功: 从站${slaveId} 线圈${coil} = ${value}`);
1106
1106
 
1107
1107
  // 发布到MQTT和触发事件
1108
1108
  node.publishMqttState(slaveId, coil, value);
@@ -1120,7 +1120,7 @@ module.exports = function(RED) {
1120
1120
  node.pausePolling = false;
1121
1121
  const pauseDuration = Date.now() - pauseStartTime;
1122
1122
  if (node.pollingPausedCount > 0) {
1123
- node.log(`轮询暂停 ${pauseDuration}ms,跳过 ${node.pollingPausedCount} 次轮询`);
1123
+ node.debug(`轮询暂停 ${pauseDuration}ms,跳过 ${node.pollingPausedCount} 次轮询`);
1124
1124
  node.pollingPausedCount = 0;
1125
1125
  }
1126
1126
  }, 100);
@@ -1185,7 +1185,7 @@ module.exports = function(RED) {
1185
1185
  });
1186
1186
  }
1187
1187
 
1188
- node.log(`批量写入成功: 从站${slaveId} 起始线圈${startCoil} 共${values.length}个线圈`);
1188
+ node.debug(`批量写入成功: 从站${slaveId} 起始线圈${startCoil} 共${values.length}个线圈`);
1189
1189
 
1190
1190
  // 释放锁
1191
1191
  node.modbusLock = false;
@@ -1195,7 +1195,7 @@ module.exports = function(RED) {
1195
1195
  node.pausePolling = false;
1196
1196
  const pauseDuration = Date.now() - pauseStartTime;
1197
1197
  if (node.pollingPausedCount > 0) {
1198
- node.log(`轮询暂停 ${pauseDuration}ms,跳过 ${node.pollingPausedCount} 次轮询`);
1198
+ node.debug(`轮询暂停 ${pauseDuration}ms,跳过 ${node.pollingPausedCount} 次轮询`);
1199
1199
  node.pollingPausedCount = 0;
1200
1200
  }
1201
1201
  }, 100);
@@ -7,6 +7,7 @@
7
7
  // RS-485连接配置(共享配置节点)
8
8
  serialPortConfig: {value: "", type: "serial-port-config", required: true},
9
9
  // MQTT配置(可选)
10
+ enableMqtt: {value: false}, // 默认不启用MQTT
10
11
  mqttServer: {value: "", type: "mqtt-server-config", required: false},
11
12
  // 开关面板配置
12
13
  switchBrand: {value: "symi"}, // 品牌选择
@@ -24,6 +25,19 @@
24
25
  // 显示时直接使用用户输入的路数(1-32)
25
26
  const coilDisplay = this.targetCoilNumber || 1;
26
27
  return this.name || `开关${this.switchId}-按钮${this.buttonNumber} → 继电器${this.targetSlaveAddress}-${coilDisplay}路`;
28
+ },
29
+ oneditprepare: function() {
30
+ // MQTT开关控制显示/隐藏
31
+ $("#node-input-enableMqtt").on("change", function() {
32
+ if ($(this).is(":checked")) {
33
+ $(".form-row-mqtt-slave").show();
34
+ } else {
35
+ $(".form-row-mqtt-slave").hide();
36
+ }
37
+ });
38
+
39
+ // 初始化显示状态
40
+ $("#node-input-enableMqtt").trigger("change");
27
41
  }
28
42
  });
29
43
  </script>
@@ -64,13 +78,19 @@
64
78
  <span style="margin-left: 8px; padding: 2px 8px; background: #e3f2fd; color: #1976d2; border-radius: 3px; font-size: 11px; font-weight: 500;">可选</span>
65
79
  </label>
66
80
  <div style="font-size: 11px; color: #555; padding: 10px 12px; background: linear-gradient(135deg, #e3f2fd 0%, #f0f7ff 100%); border-left: 4px solid #2196f3; border-radius: 4px; box-shadow: 0 1px 3px rgba(0,0,0,0.08);">
67
- <strong>💡 提示:</strong>不配置MQTT则使用<strong>本地模式</strong>(纯串口通信),配置MQTT则使用<strong>MQTT模式</strong>(可接入Home Assistant等平台)
81
+ <strong>💡 提示:</strong>不启用MQTT则使用<strong>本地模式</strong>(通过连线控制),启用MQTT则使用<strong>MQTT模式</strong>(可接入Home Assistant
68
82
  </div>
69
83
  </div>
70
84
 
71
85
  <div class="form-row">
86
+ <label for="node-input-enableMqtt" style="width: 110px;"><i class="fa fa-toggle-on"></i> 启用MQTT</label>
87
+ <input type="checkbox" id="node-input-enableMqtt" style="width: auto; margin-left: 5px; transform: scale(1.3); cursor: pointer;">
88
+ <span style="margin-left: 15px; font-size: 11px; color: #666; font-style: italic;">勾选后切换到MQTT模式</span>
89
+ </div>
90
+
91
+ <div class="form-row form-row-mqtt-slave">
72
92
  <label for="node-input-mqttServer" style="width: 110px;"><i class="fa fa-server"></i> MQTT服务器</label>
73
- <input type="text" id="node-input-mqttServer" placeholder="" style="width: calc(70% - 110px);">
93
+ <input type="text" id="node-input-mqttServer" placeholder="选择MQTT服务器配置" style="width: calc(70% - 110px);">
74
94
  <div style="font-size: 11px; color: #888; margin-left: 110px; margin-top: 3px;">
75
95
  选择已配置的MQTT服务器(需与主站节点使用同一配置)
76
96
  </div>
@@ -79,8 +79,9 @@ module.exports = function(RED) {
79
79
 
80
80
  // 配置参数
81
81
  node.config = {
82
- // MQTT配置节点读取MQTT配置
83
- mqttBroker: node.mqttServerConfig ? node.mqttServerConfig.broker : "mqtt://localhost:1883",
82
+ // MQTT配置
83
+ enableMqtt: config.enableMqtt !== undefined ? config.enableMqtt : false, // 是否启用MQTT(默认false)
84
+ mqttBroker: node.mqttServerConfig ? node.mqttServerConfig.broker : "",
84
85
  mqttUsername: node.mqttServerConfig ? node.mqttServerConfig.username : "",
85
86
  mqttPassword: node.mqttServerConfig ? (node.mqttServerConfig.credentials ? node.mqttServerConfig.credentials.password : "") : "",
86
87
  mqttBaseTopic: node.mqttServerConfig ? node.mqttServerConfig.baseTopic : "modbus/relay",
@@ -292,14 +293,14 @@ module.exports = function(RED) {
292
293
  globalDebounceCache.set(debounceKey, now);
293
294
 
294
295
  if (isSceneMode) {
295
- node.log(`场景触发: 面板${node.config.switchId} 按键${node.config.buttonNumber}`);
296
+ node.debug(`场景触发: 面板${node.config.switchId} 按键${node.config.buttonNumber}`);
296
297
  // 场景模式:切换状态(每次触发时翻转)
297
298
  node.currentState = !node.currentState;
298
299
  node.sendMqttCommand(node.currentState);
299
300
  } else {
300
301
 
301
302
  // 开关模式:根据状态发送ON/OFF
302
- node.log(`开关${buttonEvent.state ? 'ON' : 'OFF'}: 面板${node.config.switchId} 按键${node.config.buttonNumber}`);
303
+ node.debug(`开关${buttonEvent.state ? 'ON' : 'OFF'}: 面板${node.config.switchId} 按键${node.config.buttonNumber}`);
303
304
  node.sendMqttCommand(buttonEvent.state);
304
305
  }
305
306
  }
@@ -340,7 +341,7 @@ module.exports = function(RED) {
340
341
  };
341
342
 
342
343
  node.send(msg);
343
- node.log(`本地模式:发送控制命令到从站${node.config.targetSlaveAddress} 线圈${node.config.targetCoilNumber} = ${state ? 'ON' : 'OFF'}`);
344
+ node.debug(`本地模式:发送控制命令到从站${node.config.targetSlaveAddress} 线圈${node.config.targetCoilNumber} = ${state ? 'ON' : 'OFF'}`);
344
345
  };
345
346
 
346
347
  // 处理命令队列(防止多个按键同时按下造成冲突)
@@ -509,27 +510,27 @@ module.exports = function(RED) {
509
510
  node.updateStatus = function() {
510
511
  const rs485Status = node.isRs485Connected ? 'OK' : 'ERR';
511
512
  const state = node.currentState ? 'ON' : 'OFF';
512
- const hasMqttConfig = node.config.mqttBroker && node.config.mqttBroker.trim() !== '';
513
+ const mqttEnabled = node.config.enableMqtt === true;
513
514
  const mqttConnected = node.mqttClient && node.mqttClient.connected;
514
515
 
515
516
  // RS485连接正常
516
517
  if (node.isRs485Connected) {
517
- if (!hasMqttConfig) {
518
- // 本地模式(未配置MQTT)
518
+ if (!mqttEnabled) {
519
+ // 本地模式(未启用MQTT)
519
520
  node.status({
520
521
  fill: node.currentState ? "green" : "blue",
521
522
  shape: "dot",
522
- text: `本地模式 RS485-${rs485Status} ${state}`
523
+ text: `本地模式 ${state}`
523
524
  });
524
525
  } else if (mqttConnected) {
525
- // MQTT模式(已配置且已连接)
526
+ // MQTT模式(已启用且已连接)
526
527
  node.status({
527
528
  fill: node.currentState ? "green" : "grey",
528
529
  shape: "dot",
529
530
  text: `MQTT模式 ${state}`
530
531
  });
531
532
  } else {
532
- // MQTT配置但未连接(使用本地模式)
533
+ // MQTT已启用但未连接(使用本地模式)
533
534
  node.status({
534
535
  fill: node.currentState ? "green" : "blue",
535
536
  shape: "ring",
@@ -548,10 +549,17 @@ module.exports = function(RED) {
548
549
 
549
550
  // 连接MQTT(带智能重试和fallback)
550
551
  node.connectMqtt = function() {
552
+ // 检查是否启用MQTT
553
+ if (!node.config.enableMqtt) {
554
+ node.log('MQTT未启用 - 使用本地模式(通过Node-RED连线控制)');
555
+ node.log('提示:将此节点连线到主站节点,即可实现本地控制');
556
+ return;
557
+ }
558
+
551
559
  // 验证MQTT broker配置
552
560
  if (!node.config.mqttBroker || node.config.mqttBroker.trim() === '') {
553
- node.log('MQTT未配置 - 使用本地模式(通过Node-RED连线控制)');
554
- node.log('提示:将此节点连线到主站节点,即可实现本地控制');
561
+ node.warn('MQTT已启用但broker地址未配置 - 使用本地模式');
562
+ node.warn('提示:请在MQTT服务器配置节点中设置broker地址,或禁用MQTT功能');
555
563
  return;
556
564
  }
557
565
 
@@ -666,7 +674,7 @@ module.exports = function(RED) {
666
674
  tryConnect(brokerCandidates[0]);
667
675
  }, 5000);
668
676
  } else {
669
- node.log(`尝试备用MQTT broker: ${nextBroker}`);
677
+ node.debug(`尝试备用MQTT broker: ${nextBroker}`);
670
678
  setTimeout(() => {
671
679
  tryConnect(nextBroker);
672
680
  }, 500); // 快速尝试下一个地址
@@ -715,7 +723,7 @@ module.exports = function(RED) {
715
723
 
716
724
  if (!stateChanged && timeSinceLastChange < 100) {
717
725
  // 状态未变化且时间间隔太短,跳过LED反馈(避免死循环)
718
- node.log(`跳过LED反馈(状态未变化,间隔${timeSinceLastChange}ms)`);
726
+ node.debug(`跳过LED反馈(状态未变化,间隔${timeSinceLastChange}ms)`);
719
727
  return;
720
728
  }
721
729
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-symi-modbus",
3
- "version": "2.6.3",
3
+ "version": "2.6.5",
4
4
  "description": "Node-RED Modbus节点,支持TCP/串口通信、串口自动搜索、多设备轮询、可选MQTT集成(支持纯本地模式和MQTT模式)、Home Assistant自动发现和物理开关面板双向同步,工控机长期稳定运行",
5
5
  "main": "nodes/modbus-master.js",
6
6
  "scripts": {