node-red-contrib-symi-modbus 2.6.2 → 2.6.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
@@ -18,7 +18,10 @@ Node-RED的Modbus继电器控制节点,支持TCP/串口通信和MQTT集成。
18
18
  - **32路继电器**:每台设备支持32个线圈(继电器通道)
19
19
  - **智能轮询**:从站上报时自动暂停轮询,优先处理数据,完成后继续轮询
20
20
  - **灵活配置**:可自定义轮询间隔(100-10000ms,默认200ms)、线圈范围、从站地址
21
- - **MQTT集成**:自动生成Home Assistant兼容的MQTT发现消息
21
+ - **🔥 双模式支持**:
22
+ - **本地模式**:纯串口通信,无需MQTT,断网也能稳定运行
23
+ - **MQTT模式**:可选接入Home Assistant等第三方平台
24
+ - **MQTT集成**(可选):自动生成Home Assistant兼容的MQTT发现消息
22
25
  - **实时状态**:实时监控和控制继电器状态
23
26
  - **主从模式**:提供主站节点和从站控制节点
24
27
  - **稳定可靠**:完整的内存管理和错误处理,适合工控机长期稳定运行
@@ -39,8 +42,36 @@ node-red-restart
39
42
  2. 搜索 `node-red-contrib-symi-modbus`
40
43
  3. 点击安装
41
44
 
42
- ### 2. 配置MQTT服务器
45
+ ### 2. 选择运行模式
43
46
 
47
+ 本节点支持两种运行模式,根据需求选择:
48
+
49
+ #### 模式1:本地模式(推荐用于纯串口控制)
50
+
51
+ **适用场景**:
52
+ - 无需对接第三方平台
53
+ - 断网环境下稳定运行
54
+ - 纯本地化控制,不受网络影响
55
+
56
+ **配置方法**:
57
+ 1. 主站节点:不启用MQTT或不配置MQTT服务器
58
+ 2. 从站开关节点:不配置MQTT服务器
59
+ 3. 将从站开关节点的输出连线到主站节点的输入
60
+
61
+ **优势**:
62
+ - ✅ 断网也能稳定运行
63
+ - ✅ 不依赖外部服务
64
+ - ✅ 响应速度更快
65
+ - ✅ 配置更简单
66
+
67
+ #### 模式2:MQTT模式(推荐用于Home Assistant集成)
68
+
69
+ **适用场景**:
70
+ - 需要对接Home Assistant等平台
71
+ - 需要远程控制
72
+ - 需要状态持久化
73
+
74
+ **配置方法**:
44
75
  1. 拖拽任意节点到流程画布
45
76
  2. 双击节点,找到"MQTT服务器"字段
46
77
  3. 点击编辑按钮,填写MQTT服务器信息:
@@ -48,6 +79,11 @@ node-red-restart
48
79
  - 用户名/密码: 按需填写
49
80
  - 基础主题: `modbus/relay` (默认)
50
81
 
82
+ **优势**:
83
+ - ✅ Home Assistant自动发现
84
+ - ✅ 支持远程控制
85
+ - ✅ 状态持久化存储
86
+
51
87
  ### 3. 配置主站节点
52
88
 
53
89
  1. 拖拽 **Modbus主站** 节点到流程画布
@@ -71,7 +107,9 @@ node-red-restart
71
107
  - 从站地址: `10` (默认,可添加多个,如10、11、12、13)
72
108
  - 线圈范围: `0-31`
73
109
  - 轮询间隔: `200ms` (默认,支持100-10000ms)
74
- 4. 启用MQTT并选择MQTT服务器配置
110
+ 4. MQTT配置(可选):
111
+ - 本地模式:不启用MQTT或留空
112
+ - MQTT模式:启用MQTT并选择MQTT服务器配置
75
113
  5. 部署流程
76
114
 
77
115
  ### 4. 配置RS-485连接配置节点(用于从站开关)
@@ -441,14 +479,15 @@ msg.payload = 1; // 或 0
441
479
 
442
480
  ## 项目信息
443
481
 
444
- **版本**: v2.6.2
482
+ **版本**: v2.6.4
445
483
 
446
484
  **核心功能**:
447
485
  - 支持多种Modbus协议(Telnet ASCII、RTU over TCP、Modbus TCP、Modbus RTU串口)
448
486
  - 多设备轮询(最多10台从站,每台32路继电器,轮询间隔100-10000ms可调)
449
487
  - Symi私有协议自动识别(支持两种485开关控制方式)
450
488
  - 智能轮询暂停机制(从站上报时自动暂停,处理完成后恢复)
451
- - MQTT集成(Home Assistant自动发现,实体唯一性保证,QoS=0高性能发布)
489
+ - 🔥 **双模式支持**(本地模式和MQTT模式可选切换,断网也能稳定运行)
490
+ - MQTT集成(可选启用,Home Assistant自动发现,实体唯一性保证,QoS=0高性能发布)
452
491
  - 物理开关面板双向同步(亖米协议支持,LED反馈同步)
453
492
  - 共享连接架构(多个从站开关节点共享同一个串口/TCP连接,支持500+节点)
454
493
  - 长期稳定运行(内存管理、智能重连、错误日志限流、异步MQTT发布)
@@ -456,30 +495,28 @@ msg.payload = 1; // 或 0
456
495
  **技术栈**:
457
496
  - modbus-serial: ^8.0.23(内部封装serialport,支持TCP和串口)
458
497
  - serialport: ^12.0.0(原生串口通信)
459
- - mqtt: ^5.14.1(最新稳定版)
498
+ - mqtt: ^5.14.1(最新稳定版,可选依赖)
460
499
  - Node.js: >=14.0.0
461
500
  - Node-RED: >=2.0.0
462
501
 
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
- - 用户体验:继电器路数优化、串口配置完善
502
+ **最新更新(v2.6.4)**:
503
+ - **🔥 日志优化**:大幅减少日志输出,保证长期稳定运行
504
+ - 高频操作日志改为debug级别(按键、轮询、写入等)
505
+ - 默认不输出到日志文件,不占用硬盘空间
506
+ - 保留错误日志的10分钟限流机制
507
+ - 完善的内存清理机制,防止内存泄漏
508
+ - **适合工控机长期运行**:
509
+ - 无debug节点时不产生日志
510
+ - 不会因日志增加硬盘占用
511
+ - 不会因日志增加内存占用
512
+ - 断网/MQTT断开也不会持续报错
513
+
514
+ **v2.6.3更新**:
515
+ - **🔥 MQTT可选配置**:完全兼容无MQTT环境
516
+ - 本地模式:纯串口通信,断网也能稳定运行
517
+ - MQTT模式:可选接入Home Assistant等第三方平台
518
+ - 智能切换:MQTT断开时自动切换到本地模式
519
+ - 状态显示:清晰区分当前运行模式
483
520
 
484
521
  **性能优化**:
485
522
  - 轮询间隔优化:修复间隔计算逻辑,确保每个从站使用正确的轮询间隔
@@ -4,7 +4,7 @@
4
4
  color: '#3FADB5',
5
5
  defaults: {
6
6
  name: {value: "Modbus主站"},
7
- modbusServer: {value: "", type: "modbus-server-config"},
7
+ modbusServer: {value: "", type: "modbus-server-config", required: true},
8
8
  // 从站配置列表(数组)
9
9
  slaves: {value: [{
10
10
  address: 10,
@@ -12,9 +12,9 @@
12
12
  coilEnd: 31,
13
13
  pollInterval: 200
14
14
  }]},
15
- // MQTT配置(引用config节点)
16
- enableMqtt: {value: true},
17
- mqttServer: {value: "", type: "mqtt-server-config"}
15
+ // MQTT配置(可选)
16
+ enableMqtt: {value: false}, // 默认不启用MQTT(本地模式)
17
+ mqttServer: {value: "", type: "mqtt-server-config", required: false}
18
18
  },
19
19
  inputs: 1,
20
20
  outputs: 1,
@@ -215,19 +215,25 @@
215
215
  </button>
216
216
  </div>
217
217
 
218
- <!-- MQTT配置 -->
218
+ <!-- MQTT配置(可选) -->
219
219
  <hr style="margin: 15px 0; border: 0; border-top: 2px solid #e0e0e0;">
220
220
  <div class="form-row">
221
221
  <label style="width: 100%; margin-bottom: 8px;">
222
222
  <i class="fa fa-cloud" style="color: #ff9800;"></i>
223
223
  <span style="font-size: 14px; font-weight: 600; color: #333;">MQTT集成配置</span>
224
+ <span style="margin-left: 8px; padding: 2px 8px; background: #e3f2fd; color: #1976d2; border-radius: 3px; font-size: 11px; font-weight: 500;">可选</span>
224
225
  </label>
226
+ <div style="font-size: 11px; color: #555; padding: 10px 12px; background: linear-gradient(135deg, #e8f5e9 0%, #f1f8f4 100%); border-left: 4px solid #4caf50; border-radius: 4px; box-shadow: 0 1px 3px rgba(0,0,0,0.08); margin-bottom: 12px;">
227
+ <strong>💡 运行模式选择:</strong><br>
228
+ <strong style="color: #2e7d32;">本地模式</strong>(默认):纯串口通信,断网也能稳定运行,不依赖外部服务<br>
229
+ <strong style="color: #1976d2;">MQTT模式</strong>:可接入Home Assistant等第三方平台,支持远程控制
230
+ </div>
225
231
  </div>
226
232
 
227
233
  <div class="form-row">
228
234
  <label for="node-input-enableMqtt" style="width: 110px;"><i class="fa fa-toggle-on"></i> 启用MQTT</label>
229
235
  <input type="checkbox" id="node-input-enableMqtt" style="width: auto; margin-left: 5px; transform: scale(1.3); cursor: pointer;">
230
- <span style="margin-left: 15px; font-size: 11px; color: #666; font-style: italic;">用于Home Assistant集成和远程控制</span>
236
+ <span style="margin-left: 15px; font-size: 11px; color: #666; font-style: italic;">勾选后切换到MQTT模式</span>
231
237
  </div>
232
238
 
233
239
  <div class="form-row form-row-mqtt">
@@ -372,13 +372,15 @@ module.exports = function(RED) {
372
372
  // 连接MQTT(带智能重试和fallback)
373
373
  node.connectMqtt = function() {
374
374
  if (!node.config.enableMqtt) {
375
- node.log('MQTT未启用,跳过MQTT连接');
375
+ node.log('MQTT未启用 - 使用纯本地模式(仅串口通信)');
376
+ node.log('提示:如需Home Assistant集成,请在节点配置中启用MQTT');
376
377
  return;
377
378
  }
378
379
 
379
380
  // 验证MQTT broker配置
380
381
  if (!node.config.mqttBroker || node.config.mqttBroker.trim() === '') {
381
- node.warn('MQTT已启用但broker地址未配置,跳过MQTT连接');
382
+ node.warn('MQTT已启用但broker地址未配置 - 使用纯本地模式');
383
+ node.warn('提示:请在MQTT服务器配置节点中设置broker地址,或禁用MQTT功能');
382
384
  return;
383
385
  }
384
386
 
@@ -493,7 +495,7 @@ module.exports = function(RED) {
493
495
  tryConnect(brokerCandidates[0]);
494
496
  }, 5000);
495
497
  } else {
496
- node.log(`尝试备用MQTT broker: ${nextBroker}`);
498
+ node.debug(`尝试备用MQTT broker: ${nextBroker}`);
497
499
  setTimeout(() => {
498
500
  tryConnect(nextBroker);
499
501
  }, 500); // 快速尝试下一个地址
@@ -864,9 +866,9 @@ module.exports = function(RED) {
864
866
  }
865
867
 
866
868
  if (isFirstPoll) {
867
- node.log(`从站${slaveId}首次轮询成功,读取${coilCount}个线圈`);
869
+ node.debug(`从站${slaveId}首次轮询成功,读取${coilCount}个线圈`);
868
870
  } else if (publishCount > 0) {
869
- node.log(`从站${slaveId}状态变化: ${publishCount}个线圈`);
871
+ node.debug(`从站${slaveId}状态变化: ${publishCount}个线圈`);
870
872
  }
871
873
 
872
874
  node.deviceStates[slaveId].lastUpdate = Date.now();
@@ -1019,7 +1021,7 @@ module.exports = function(RED) {
1019
1021
  const channel = frame.channel; // 通道号(1-8)
1020
1022
  const state = frame.opInfo[0] === 0x01; // 状态(1=开,0=关)
1021
1023
 
1022
- node.log(`Symi按键事件: 设备${deviceAddr} 通道${channel} 状态=${state ? 'ON' : 'OFF'}`);
1024
+ node.debug(`Symi按键事件: 设备${deviceAddr} 通道${channel} 状态=${state ? 'ON' : 'OFF'}`);
1023
1025
 
1024
1026
  // 查找对应的从站和线圈
1025
1027
  // 假设:设备地址1对应从站10,设备地址2对应从站11,以此类推
@@ -1040,7 +1042,7 @@ module.exports = function(RED) {
1040
1042
  return;
1041
1043
  }
1042
1044
 
1043
- node.log(`Symi按键映射: 设备${deviceAddr}通道${channel} → 从站${slaveId}线圈${coilNumber}`);
1045
+ node.debug(`Symi按键映射: 设备${deviceAddr}通道${channel} → 从站${slaveId}线圈${coilNumber}`);
1044
1046
 
1045
1047
  // 写入线圈(异步,不阻塞)
1046
1048
  node.writeSingleCoil(slaveId, coilNumber, state).catch(err => {
@@ -1100,7 +1102,7 @@ module.exports = function(RED) {
1100
1102
  // 更新本地状态
1101
1103
  node.deviceStates[slaveId].coils[coil] = value;
1102
1104
 
1103
- node.log(`写入成功: 从站${slaveId} 线圈${coil} = ${value}`);
1105
+ node.debug(`写入成功: 从站${slaveId} 线圈${coil} = ${value}`);
1104
1106
 
1105
1107
  // 发布到MQTT和触发事件
1106
1108
  node.publishMqttState(slaveId, coil, value);
@@ -1118,7 +1120,7 @@ module.exports = function(RED) {
1118
1120
  node.pausePolling = false;
1119
1121
  const pauseDuration = Date.now() - pauseStartTime;
1120
1122
  if (node.pollingPausedCount > 0) {
1121
- node.log(`轮询暂停 ${pauseDuration}ms,跳过 ${node.pollingPausedCount} 次轮询`);
1123
+ node.debug(`轮询暂停 ${pauseDuration}ms,跳过 ${node.pollingPausedCount} 次轮询`);
1122
1124
  node.pollingPausedCount = 0;
1123
1125
  }
1124
1126
  }, 100);
@@ -1183,7 +1185,7 @@ module.exports = function(RED) {
1183
1185
  });
1184
1186
  }
1185
1187
 
1186
- node.log(`批量写入成功: 从站${slaveId} 起始线圈${startCoil} 共${values.length}个线圈`);
1188
+ node.debug(`批量写入成功: 从站${slaveId} 起始线圈${startCoil} 共${values.length}个线圈`);
1187
1189
 
1188
1190
  // 释放锁
1189
1191
  node.modbusLock = false;
@@ -1193,7 +1195,7 @@ module.exports = function(RED) {
1193
1195
  node.pausePolling = false;
1194
1196
  const pauseDuration = Date.now() - pauseStartTime;
1195
1197
  if (node.pollingPausedCount > 0) {
1196
- node.log(`轮询暂停 ${pauseDuration}ms,跳过 ${node.pollingPausedCount} 次轮询`);
1198
+ node.debug(`轮询暂停 ${pauseDuration}ms,跳过 ${node.pollingPausedCount} 次轮询`);
1197
1199
  node.pollingPausedCount = 0;
1198
1200
  }
1199
1201
  }, 100);
@@ -5,9 +5,9 @@
5
5
  defaults: {
6
6
  name: {value: "从站开关"},
7
7
  // RS-485连接配置(共享配置节点)
8
- serialPortConfig: {value: "", type: "serial-port-config"},
9
- // MQTT配置
10
- mqttServer: {value: "", type: "mqtt-server-config"},
8
+ serialPortConfig: {value: "", type: "serial-port-config", required: true},
9
+ // MQTT配置(可选)
10
+ mqttServer: {value: "", type: "mqtt-server-config", required: false},
11
11
  // 开关面板配置
12
12
  switchBrand: {value: "symi"}, // 品牌选择
13
13
  buttonType: {value: "switch"}, // 按钮类型:switch=开关模式,scene=场景模式
@@ -55,18 +55,22 @@
55
55
  </div>
56
56
  </div>
57
57
 
58
- <!-- MQTT配置 -->
58
+ <!-- MQTT配置(可选) -->
59
59
  <hr style="margin: 15px 0; border: 0; border-top: 2px solid #e0e0e0;">
60
60
  <div class="form-row">
61
61
  <label style="width: 100%; margin-bottom: 8px;">
62
62
  <i class="fa fa-cloud" style="color: #9c27b0;"></i>
63
63
  <span style="font-size: 14px; font-weight: 600; color: #333;">MQTT服务器配置</span>
64
+ <span style="margin-left: 8px; padding: 2px 8px; background: #e3f2fd; color: #1976d2; border-radius: 3px; font-size: 11px; font-weight: 500;">可选</span>
64
65
  </label>
66
+ <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等平台)
68
+ </div>
65
69
  </div>
66
70
 
67
71
  <div class="form-row">
68
72
  <label for="node-input-mqttServer" style="width: 110px;"><i class="fa fa-server"></i> MQTT服务器</label>
69
- <input type="text" id="node-input-mqttServer" placeholder="选择或添加MQTT服务器配置" style="width: calc(70% - 110px);">
73
+ <input type="text" id="node-input-mqttServer" placeholder="" style="width: calc(70% - 110px);">
70
74
  <div style="font-size: 11px; color: #888; margin-left: 110px; margin-top: 3px;">
71
75
  选择已配置的MQTT服务器(需与主站节点使用同一配置)
72
76
  </div>
@@ -292,14 +292,14 @@ module.exports = function(RED) {
292
292
  globalDebounceCache.set(debounceKey, now);
293
293
 
294
294
  if (isSceneMode) {
295
- node.log(`场景触发: 面板${node.config.switchId} 按键${node.config.buttonNumber}`);
295
+ node.debug(`场景触发: 面板${node.config.switchId} 按键${node.config.buttonNumber}`);
296
296
  // 场景模式:切换状态(每次触发时翻转)
297
297
  node.currentState = !node.currentState;
298
298
  node.sendMqttCommand(node.currentState);
299
299
  } else {
300
300
 
301
301
  // 开关模式:根据状态发送ON/OFF
302
- node.log(`开关${buttonEvent.state ? 'ON' : 'OFF'}: 面板${node.config.switchId} 按键${node.config.buttonNumber}`);
302
+ node.debug(`开关${buttonEvent.state ? 'ON' : 'OFF'}: 面板${node.config.switchId} 按键${node.config.buttonNumber}`);
303
303
  node.sendMqttCommand(buttonEvent.state);
304
304
  }
305
305
  }
@@ -309,24 +309,38 @@ module.exports = function(RED) {
309
309
  }
310
310
  };
311
311
 
312
- // 发送命令到继电器(通过MQTT,由主站节点统一处理)
312
+ // 发送命令到继电器(支持两种模式:MQTT模式和本地模式)
313
313
  node.sendMqttCommand = function(state) {
314
- if (!node.mqttClient || !node.mqttClient.connected) {
315
- node.warn('MQTT未连接,无法发送命令');
314
+ // 模式1:MQTT模式(通过MQTT发送命令,由主站节点统一处理)
315
+ if (node.mqttClient && node.mqttClient.connected) {
316
+ // 直接发送MQTT命令(不使用队列,立即发送)
317
+ // 主站节点会处理去重和防抖
318
+ const commandTopic = `${node.config.mqttBaseTopic}/${node.config.targetSlaveAddress}/${node.config.targetCoilNumber}/set`;
319
+ const payload = state ? 'ON' : 'OFF';
320
+
321
+ node.mqttClient.publish(commandTopic, payload, { qos: 1 }, (err) => {
322
+ if (err) {
323
+ node.error(`MQTT发送失败: ${err.message}`);
324
+ }
325
+ // 成功时不输出日志,减少总线负担
326
+ });
316
327
  return;
317
328
  }
318
329
 
319
- // 直接发送MQTT命令(不使用队列,立即发送)
320
- // 主站节点会处理去重和防抖
321
- const commandTopic = `${node.config.mqttBaseTopic}/${node.config.targetSlaveAddress}/${node.config.targetCoilNumber}/set`;
322
- const payload = state ? 'ON' : 'OFF';
323
-
324
- node.mqttClient.publish(commandTopic, payload, { qos: 1 }, (err) => {
325
- if (err) {
326
- node.error(`MQTT发送失败: ${err.message}`);
327
- }
328
- // 成功时不输出日志,减少总线负担
329
- });
330
+ // 模式2:本地模式(通过Node-RED消息输出,直接连线到主站节点)
331
+ // 当MQTT未连接时,通过节点输出发送控制命令
332
+ const msg = {
333
+ payload: {
334
+ cmd: "writeCoil",
335
+ slave: node.config.targetSlaveAddress,
336
+ coil: node.config.targetCoilNumber,
337
+ value: state
338
+ },
339
+ topic: `modbus/write/${node.config.targetSlaveAddress}/${node.config.targetCoilNumber}`
340
+ };
341
+
342
+ node.send(msg);
343
+ node.debug(`本地模式:发送控制命令到从站${node.config.targetSlaveAddress} 线圈${node.config.targetCoilNumber} = ${state ? 'ON' : 'OFF'}`);
330
344
  };
331
345
 
332
346
  // 处理命令队列(防止多个按键同时按下造成冲突)
@@ -494,20 +508,40 @@ module.exports = function(RED) {
494
508
  // 更新节点状态显示
495
509
  node.updateStatus = function() {
496
510
  const rs485Status = node.isRs485Connected ? 'OK' : 'ERR';
497
- const mqttStatus = node.mqttClient && node.mqttClient.connected ? 'OK' : 'ERR';
498
511
  const state = node.currentState ? 'ON' : 'OFF';
499
-
500
- if (node.isRs485Connected && node.mqttClient && node.mqttClient.connected) {
501
- node.status({
502
- fill: node.currentState ? "green" : "grey",
503
- shape: "dot",
504
- text: `RS485-${rs485Status} MQTT-${mqttStatus} ${state}`
505
- });
512
+ const hasMqttConfig = node.config.mqttBroker && node.config.mqttBroker.trim() !== '';
513
+ const mqttConnected = node.mqttClient && node.mqttClient.connected;
514
+
515
+ // RS485连接正常
516
+ if (node.isRs485Connected) {
517
+ if (!hasMqttConfig) {
518
+ // 本地模式(未配置MQTT)
519
+ node.status({
520
+ fill: node.currentState ? "green" : "blue",
521
+ shape: "dot",
522
+ text: `本地模式 RS485-${rs485Status} ${state}`
523
+ });
524
+ } else if (mqttConnected) {
525
+ // MQTT模式(已配置且已连接)
526
+ node.status({
527
+ fill: node.currentState ? "green" : "grey",
528
+ shape: "dot",
529
+ text: `MQTT模式 ${state}`
530
+ });
531
+ } else {
532
+ // MQTT配置但未连接(使用本地模式)
533
+ node.status({
534
+ fill: node.currentState ? "green" : "blue",
535
+ shape: "ring",
536
+ text: `本地模式(MQTT断开) ${state}`
537
+ });
538
+ }
506
539
  } else {
540
+ // RS485连接失败
507
541
  node.status({
508
542
  fill: "red",
509
543
  shape: "ring",
510
- text: `RS485-${rs485Status} MQTT-${mqttStatus}`
544
+ text: `RS485连接失败`
511
545
  });
512
546
  }
513
547
  };
@@ -516,7 +550,8 @@ module.exports = function(RED) {
516
550
  node.connectMqtt = function() {
517
551
  // 验证MQTT broker配置
518
552
  if (!node.config.mqttBroker || node.config.mqttBroker.trim() === '') {
519
- node.warn('MQTT broker地址未配置,跳过MQTT连接');
553
+ node.log('MQTT未配置 - 使用本地模式(通过Node-RED连线控制)');
554
+ node.log('提示:将此节点连线到主站节点,即可实现本地控制');
520
555
  return;
521
556
  }
522
557
 
@@ -631,7 +666,7 @@ module.exports = function(RED) {
631
666
  tryConnect(brokerCandidates[0]);
632
667
  }, 5000);
633
668
  } else {
634
- node.log(`尝试备用MQTT broker: ${nextBroker}`);
669
+ node.debug(`尝试备用MQTT broker: ${nextBroker}`);
635
670
  setTimeout(() => {
636
671
  tryConnect(nextBroker);
637
672
  }, 500); // 快速尝试下一个地址
@@ -680,7 +715,7 @@ module.exports = function(RED) {
680
715
 
681
716
  if (!stateChanged && timeSinceLastChange < 100) {
682
717
  // 状态未变化且时间间隔太短,跳过LED反馈(避免死循环)
683
- node.log(`跳过LED反馈(状态未变化,间隔${timeSinceLastChange}ms)`);
718
+ node.debug(`跳过LED反馈(状态未变化,间隔${timeSinceLastChange}ms)`);
684
719
  return;
685
720
  }
686
721
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "node-red-contrib-symi-modbus",
3
- "version": "2.6.2",
4
- "description": "Node-RED Modbus节点,支持TCP/串口通信、串口自动搜索、多设备轮询、智能MQTT连接(自动fallback HassOS/Docker环境)、Home Assistant自动发现和物理开关面板双向同步,工控机长期稳定运行",
3
+ "version": "2.6.4",
4
+ "description": "Node-RED Modbus节点,支持TCP/串口通信、串口自动搜索、多设备轮询、可选MQTT集成(支持纯本地模式和MQTT模式)、Home Assistant自动发现和物理开关面板双向同步,工控机长期稳定运行",
5
5
  "main": "nodes/modbus-master.js",
6
6
  "scripts": {
7
7
  "test": "echo \"Error: no test specified\" && exit 1"