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 +42 -4
- package/nodes/modbus-master.js +48 -34
- package/nodes/modbus-slave-switch.js +48 -34
- package/nodes/mqtt-server-config.html +2 -2
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
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 等地址
|
package/nodes/modbus-master.js
CHANGED
|
@@ -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
|
-
// 避免频繁重试(至少等待
|
|
352
|
-
if (timeSinceLastAttempt <
|
|
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
|
-
|
|
362
|
-
|
|
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
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
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 (
|
|
378
|
-
|
|
379
|
-
node.error(
|
|
380
|
-
node.error(
|
|
381
|
-
node.error('
|
|
382
|
-
node.
|
|
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
|
-
// 避免频繁重试(至少等待
|
|
439
|
-
if (timeSinceLastAttempt <
|
|
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
|
-
|
|
449
|
-
|
|
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
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
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 (
|
|
465
|
-
|
|
466
|
-
node.error(
|
|
467
|
-
node.error(
|
|
468
|
-
node.error('
|
|
469
|
-
node.
|
|
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
|
|
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
|
|
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": {
|