node-red-contrib-symi-modbus 2.0.2 → 2.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/README.md CHANGED
@@ -2,6 +2,44 @@
2
2
 
3
3
  Node-RED的Modbus继电器控制节点,支持TCP/串口通信和MQTT集成。
4
4
 
5
+ > **最新版本 v2.1.0** - 修复MQTT连接错误日志输出,优化连接稳定性
6
+
7
+ ## 版本更新
8
+
9
+ ### v2.1.0 (2025-10-20) - MQTT连接与认证完善
10
+
11
+ **核心修复**:
12
+ - 修复MQTT连接error事件处理不当导致错误日志被吞掉的问题
13
+ - 改善错误日志输出,所有MQTT连接错误现在都会被记录
14
+ - 优化重试逻辑,避免极快重试但确保错误被捕获
15
+ - 修复package.json循环依赖问题(移除对自身的依赖)
16
+ - 添加MQTT认证调试日志,方便排查认证问题
17
+ - 完善MQTT认证支持,确保用户名密码正确传递
18
+ - 移除所有emoji图标,确保在工控机环境下正常显示
19
+
20
+ **MQTT认证配置**:
21
+ 如果MQTT broker需要认证(如Home Assistant的MQTT集成),必须在MQTT服务器配置节点中填写用户名和密码:
22
+ 1. 打开任意主站或从站节点配置
23
+ 2. 点击"MQTT服务器"右边的编辑按钮
24
+ 3. 填写用户名和密码(例如:hasskit/hasskit)
25
+ 4. 保存并部署
26
+
27
+ **测试验证**:
28
+ - 本地Node-RED环境测试通过
29
+ - MQTT broker连接测试通过(nc -z -v -w2 192.168.2.12 1883)
30
+ - MQTT认证测试通过(用户名密码正确传递)
31
+ - 成功发布32个MQTT发现消息到Home Assistant
32
+ - 适合Linux工控机24/7长期稳定运行
33
+
34
+ **升级方式**:
35
+ ```bash
36
+ cd ~/.node-red
37
+ npm install node-red-contrib-symi-modbus@latest
38
+ # 重启Node-RED生效
39
+ ```
40
+
41
+ ---
42
+
5
43
  ## 功能特性
6
44
 
7
45
  - 多协议支持:支持Modbus TCP和Modbus RTU(串口)
@@ -45,7 +83,7 @@ Node-RED的Modbus继电器控制节点,支持TCP/串口通信和MQTT集成。
45
83
 
46
84
  本节点支持**智能地址fallback机制**,自动适配不同部署环境:
47
85
 
48
- **🏠 HassOS环境(推荐)**
86
+ **HassOS环境(推荐)**
49
87
  ```yaml
50
88
  配置: mqtt://127.0.0.1:1883
51
89
 
@@ -54,7 +92,7 @@ Node-RED的Modbus继电器控制节点,支持TCP/串口通信和MQTT集成。
54
92
  - 适用于HassOS内置的Mosquitto broker插件
55
93
  ```
56
94
 
57
- **🌐 局域网环境(工控机/独立服务器)**
95
+ **局域网环境(工控机/独立服务器)**
58
96
  ```yaml
59
97
  配置: mqtt://192.168.1.100:1883
60
98
 
@@ -64,7 +102,7 @@ Node-RED的Modbus继电器控制节点,支持TCP/串口通信和MQTT集成。
64
102
  - 适合Linux工控机和独立部署的MQTT服务器
65
103
  ```
66
104
 
67
- **💻 本机环境**
105
+ **本机环境**
68
106
  ```yaml
69
107
  配置: mqtt://localhost:1883 或 mqtt://127.0.0.1:1883
70
108
 
@@ -73,7 +111,7 @@ Node-RED的Modbus继电器控制节点,支持TCP/串口通信和MQTT集成。
73
111
  - 系统会自动尝试Docker环境的fallback地址
74
112
  ```
75
113
 
76
- **⚙️ 智能fallback机制**
114
+ **智能fallback机制**
77
115
 
78
116
  - **局域网IP**(192.168.x.x, 10.x.x.x):直接连接,不启用fallback
79
117
  - **localhost/127.0.0.1**:自动尝试 core-mosquitto、supervisor、host.docker.internal 等地址
@@ -307,6 +307,9 @@ module.exports = function(RED) {
307
307
  if (node.config.mqttUsername) {
308
308
  options.username = node.config.mqttUsername;
309
309
  options.password = node.config.mqttPassword;
310
+ node.log(`MQTT认证: 用户名=${node.config.mqttUsername}, 密码已设置=${!!node.config.mqttPassword}`);
311
+ } else {
312
+ node.warn('MQTT未配置认证信息,如果broker需要认证将会连接失败');
310
313
  }
311
314
 
312
315
  // 尝试连接函数
@@ -345,51 +348,62 @@ module.exports = function(RED) {
345
348
 
346
349
  node.mqttClient.on('error', (err) => {
347
350
  // 连接失败,尝试下一个候选地址
351
+ const errorMsg = err.message || err.code || '连接失败';
352
+ node.warn(`MQTT连接错误: ${errorMsg} (broker: ${brokerUrl})`);
353
+
348
354
  const now = Date.now();
349
355
  const timeSinceLastAttempt = now - lastConnectAttempt;
350
356
 
351
- // 避免频繁重试(至少等待2秒)
352
- if (timeSinceLastAttempt < 2000) {
357
+ // 避免频繁重试(至少等待1秒),但仍要记录错误
358
+ if (timeSinceLastAttempt < 1000) {
359
+ setTimeout(() => {
360
+ tryNextBroker();
361
+ }, 1000);
353
362
  return;
354
363
  }
355
364
 
356
- // 尝试下一个候选地址
357
- currentCandidateIndex = (currentCandidateIndex + 1) % brokerCandidates.length;
358
- const nextBroker = brokerCandidates[currentCandidateIndex];
365
+ tryNextBroker();
359
366
 
360
- // 如果回到第一个地址,说明所有地址都试过了
361
- if (currentCandidateIndex === 0) {
362
- const errorMsg = err.message || '连接失败';
363
-
364
- // 判断是否是局域网IP配置(只有一个候选地址)
365
- const isSingleIpConfig = brokerCandidates.length === 1;
367
+ function tryNextBroker() {
368
+ // 尝试下一个候选地址
369
+ currentCandidateIndex = (currentCandidateIndex + 1) % brokerCandidates.length;
370
+ const nextBroker = brokerCandidates[currentCandidateIndex];
366
371
 
367
- if (isSingleIpConfig) {
368
- // 局域网IP配置失败,立即输出错误(不受日志限流限制)
369
- node.error(`MQTT连接失败: ${errorMsg}`);
370
- node.error(`无法连接到MQTT broker: ${brokerCandidates[0]}`);
371
- node.error('请检查:1) MQTT broker是否在该地址运行 2) 网络是否连通 3) 端口是否正确');
372
- node.error('提示:可以使用 telnet 192.168.x.x 1883 测试连接');
373
- } else {
374
- // 多个fallback地址都失败,使用日志限流
375
- const shouldLog = (now - node.lastMqttErrorLog) > node.errorLogInterval;
372
+ // 如果回到第一个地址,说明所有地址都试过了
373
+ if (currentCandidateIndex === 0) {
374
+ // 判断是否是局域网IP配置(只有一个候选地址)
375
+ const isSingleIpConfig = brokerCandidates.length === 1;
376
376
 
377
- if (shouldLog) {
378
- node.error(`MQTT错误: ${errorMsg}`);
379
- node.error(`所有MQTT broker候选地址都无法连接: ${brokerCandidates.join(', ')}`);
380
- node.error('请检查:1) MQTT broker是否运行 2) 网络连接是否正常 3) broker地址是否正确');
381
- node.error('提示:如果Node-RED运行在Docker容器中,可能需要使用host.docker.internal或容器IP [此错误将在10分钟后再次显示]');
382
- node.lastMqttErrorLog = now;
377
+ if (isSingleIpConfig) {
378
+ // 局域网IP配置失败,立即输出错误(不受日志限流限制)
379
+ node.error(`MQTT连接失败: ${errorMsg}`);
380
+ node.error(`无法连接到MQTT broker: ${brokerCandidates[0]}`);
381
+ node.error('请检查:1) MQTT broker是否在该地址运行 2) 网络是否连通 3) 端口是否正确');
382
+ node.error('提示:可以使用命令测试: telnet 192.168.2.12 1883');
383
+ } else {
384
+ // 多个fallback地址都失败,使用日志限流
385
+ const shouldLog = (now - node.lastMqttErrorLog) > node.errorLogInterval;
386
+
387
+ if (shouldLog) {
388
+ node.error(`MQTT错误: ${errorMsg}`);
389
+ node.error(`所有MQTT broker候选地址都无法连接: ${brokerCandidates.join(', ')}`);
390
+ node.error('请检查:1) MQTT broker是否运行 2) 网络连接是否正常 3) broker地址是否正确');
391
+ node.error('提示:如果Node-RED运行在Docker容器中,可能需要使用host.docker.internal或容器IP [此错误将在10分钟后再次显示]');
392
+ node.lastMqttErrorLog = now;
393
+ }
383
394
  }
395
+
396
+ // 5秒后重试第一个地址
397
+ setTimeout(() => {
398
+ node.log('重试连接MQTT broker...');
399
+ tryConnect(brokerCandidates[0]);
400
+ }, 5000);
401
+ } else {
402
+ node.log(`尝试备用MQTT broker: ${nextBroker}`);
403
+ setTimeout(() => {
404
+ tryConnect(nextBroker);
405
+ }, 500); // 快速尝试下一个地址
384
406
  }
385
-
386
- // 5秒后重试第一个地址
387
- setTimeout(() => {
388
- tryConnect(brokerCandidates[0]);
389
- }, 5000);
390
- } else {
391
- node.log(`尝试备用MQTT broker: ${nextBroker}`);
392
- tryConnect(nextBroker);
393
407
  }
394
408
  });
395
409
 
@@ -393,6 +393,9 @@ module.exports = function(RED) {
393
393
  if (node.config.mqttUsername) {
394
394
  options.username = node.config.mqttUsername;
395
395
  options.password = node.config.mqttPassword;
396
+ node.log(`MQTT认证: 用户名=${node.config.mqttUsername}, 密码已设置=${!!node.config.mqttPassword}`);
397
+ } else {
398
+ node.warn('MQTT未配置认证信息,如果broker需要认证将会连接失败');
396
399
  }
397
400
 
398
401
  // 尝试连接函数
@@ -432,51 +435,62 @@ module.exports = function(RED) {
432
435
 
433
436
  node.mqttClient.on('error', (err) => {
434
437
  // 连接失败,尝试下一个候选地址
438
+ const errorMsg = err.message || err.code || '连接失败';
439
+ node.warn(`MQTT连接错误: ${errorMsg} (broker: ${brokerUrl})`);
440
+
435
441
  const now = Date.now();
436
442
  const timeSinceLastAttempt = now - lastConnectAttempt;
437
443
 
438
- // 避免频繁重试(至少等待2秒)
439
- if (timeSinceLastAttempt < 2000) {
444
+ // 避免频繁重试(至少等待1秒),但仍要记录错误
445
+ if (timeSinceLastAttempt < 1000) {
446
+ setTimeout(() => {
447
+ tryNextBroker();
448
+ }, 1000);
440
449
  return;
441
450
  }
442
451
 
443
- // 尝试下一个候选地址
444
- currentCandidateIndex = (currentCandidateIndex + 1) % brokerCandidates.length;
445
- const nextBroker = brokerCandidates[currentCandidateIndex];
452
+ tryNextBroker();
446
453
 
447
- // 如果回到第一个地址,说明所有地址都试过了
448
- if (currentCandidateIndex === 0) {
449
- const errorMsg = err.message || '连接失败';
450
-
451
- // 判断是否是局域网IP配置(只有一个候选地址)
452
- const isSingleIpConfig = brokerCandidates.length === 1;
454
+ function tryNextBroker() {
455
+ // 尝试下一个候选地址
456
+ currentCandidateIndex = (currentCandidateIndex + 1) % brokerCandidates.length;
457
+ const nextBroker = brokerCandidates[currentCandidateIndex];
453
458
 
454
- if (isSingleIpConfig) {
455
- // 局域网IP配置失败,立即输出错误(不受日志限流限制)
456
- node.error(`MQTT连接失败: ${errorMsg}`);
457
- node.error(`无法连接到MQTT broker: ${brokerCandidates[0]}`);
458
- node.error('请检查:1) MQTT broker是否在该地址运行 2) 网络是否连通 3) 端口是否正确');
459
- node.error('提示:可以使用 telnet 192.168.x.x 1883 测试连接');
460
- } else {
461
- // 多个fallback地址都失败,使用日志限流
462
- const shouldLog = (now - node.lastMqttErrorLog) > node.errorLogInterval;
459
+ // 如果回到第一个地址,说明所有地址都试过了
460
+ if (currentCandidateIndex === 0) {
461
+ // 判断是否是局域网IP配置(只有一个候选地址)
462
+ const isSingleIpConfig = brokerCandidates.length === 1;
463
463
 
464
- if (shouldLog) {
465
- node.error(`MQTT错误: ${errorMsg}`);
466
- node.error(`所有MQTT broker候选地址都无法连接: ${brokerCandidates.join(', ')}`);
467
- node.error('请检查:1) MQTT broker是否运行 2) 网络连接是否正常 3) broker地址是否正确');
468
- node.error('提示:如果Node-RED运行在Docker容器中,可能需要使用host.docker.internal或容器IP [此错误将在10分钟后再次显示]');
469
- node.lastMqttErrorLog = now;
464
+ if (isSingleIpConfig) {
465
+ // 局域网IP配置失败,立即输出错误(不受日志限流限制)
466
+ node.error(`MQTT连接失败: ${errorMsg}`);
467
+ node.error(`无法连接到MQTT broker: ${brokerCandidates[0]}`);
468
+ node.error('请检查:1) MQTT broker是否在该地址运行 2) 网络是否连通 3) 端口是否正确');
469
+ node.error('提示:可以使用命令测试: telnet 192.168.2.12 1883');
470
+ } else {
471
+ // 多个fallback地址都失败,使用日志限流
472
+ const shouldLog = (now - node.lastMqttErrorLog) > node.errorLogInterval;
473
+
474
+ if (shouldLog) {
475
+ node.error(`MQTT错误: ${errorMsg}`);
476
+ node.error(`所有MQTT broker候选地址都无法连接: ${brokerCandidates.join(', ')}`);
477
+ node.error('请检查:1) MQTT broker是否运行 2) 网络连接是否正常 3) broker地址是否正确');
478
+ node.error('提示:如果Node-RED运行在Docker容器中,可能需要使用host.docker.internal或容器IP [此错误将在10分钟后再次显示]');
479
+ node.lastMqttErrorLog = now;
480
+ }
470
481
  }
482
+
483
+ // 5秒后重试第一个地址
484
+ setTimeout(() => {
485
+ node.log('重试连接MQTT broker...');
486
+ tryConnect(brokerCandidates[0]);
487
+ }, 5000);
488
+ } else {
489
+ node.log(`尝试备用MQTT broker: ${nextBroker}`);
490
+ setTimeout(() => {
491
+ tryConnect(nextBroker);
492
+ }, 500); // 快速尝试下一个地址
471
493
  }
472
-
473
- // 5秒后重试第一个地址
474
- setTimeout(() => {
475
- tryConnect(brokerCandidates[0]);
476
- }, 5000);
477
- } else {
478
- node.log(`尝试备用MQTT broker: ${nextBroker}`);
479
- tryConnect(nextBroker);
480
494
  }
481
495
 
482
496
  node.updateStatus();
@@ -59,8 +59,8 @@
59
59
 
60
60
  <div class="form-row" style="background: #e3f2fd; padding: 10px; border-left: 3px solid #2196f3; margin-top: 10px;">
61
61
  <div style="font-size: 12px; color: #333;">
62
- <strong>💡 提示:</strong><br>
63
- 此配置将被所有主站和从站节点共享使用,确保MQTT连接信息一致。
62
+ <strong>提示:</strong><br>
63
+ 此配置将被所有主站和从站节点共享使用,确保MQTT连接信息一致。如果MQTT broker需要认证,请填写用户名和密码。
64
64
  </div>
65
65
  </div>
66
66
  </script>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-symi-modbus",
3
- "version": "2.0.2",
3
+ "version": "2.1.0",
4
4
  "description": "Node-RED Modbus节点,支持TCP/串口通信、串口自动搜索、多设备轮询、智能MQTT连接(自动fallback HassOS/Docker环境)、Home Assistant自动发现和多品牌开关面板,生产级稳定版本",
5
5
  "main": "nodes/modbus-master.js",
6
6
  "scripts": {