node-red-contrib-symi-modbus 1.6.0 → 1.6.1
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 +150 -1
- package/nodes/modbus-master.js +41 -18
- package/package.json +2 -2
- package/node-red-contrib-symi-modbus-1.6.0.tgz +0 -0
package/README.md
CHANGED
|
@@ -1235,7 +1235,156 @@ python -m pymodbus.server tcp --port 502
|
|
|
1235
1235
|
|
|
1236
1236
|
## 更新日志
|
|
1237
1237
|
|
|
1238
|
-
### v1.6.
|
|
1238
|
+
### v1.6.1 (2025-10-18) - 增强日志和故障诊断 ✅最新稳定版
|
|
1239
|
+
|
|
1240
|
+
#### 核心改进
|
|
1241
|
+
|
|
1242
|
+
**1. 详细的连接状态日志**
|
|
1243
|
+
- ✅ 使用emoji图标(✅❌)清晰显示连接状态
|
|
1244
|
+
- ✅ Modbus连接成功/失败日志:`✅ Modbus已连接` 或 `❌ Modbus连接失败`
|
|
1245
|
+
- ✅ MQTT连接成功日志:`✅ MQTT已连接`
|
|
1246
|
+
- ✅ 节点状态栏显示:`Modbus✓ MQTT✓`(绿色圆点)或 `Modbus✗ MQTT✗`(红色圆环)
|
|
1247
|
+
|
|
1248
|
+
**2. 完整的状态发布日志**
|
|
1249
|
+
- ✅ 每次发布状态到MQTT都显示日志:`📤 发布状态: modbus/relay/10/0/state = ON`
|
|
1250
|
+
- ✅ 首次轮询日志:`📊 从站10首次轮询成功,发布32个状态到MQTT`
|
|
1251
|
+
- ✅ 状态变化日志:`📊 从站10状态变化,发布5个更新到MQTT`
|
|
1252
|
+
- ✅ MQTT未连接警告:`无法发布状态: MQTT未连接 (从站10 线圈0)`
|
|
1253
|
+
|
|
1254
|
+
**3. 轮询详情日志**
|
|
1255
|
+
- ✅ 开始轮询日志:`🔄 开始轮询 1 个从站设备: 从站10(线圈0-31)`
|
|
1256
|
+
- ✅ 连接状态检查:`📡 Modbus连接: 已连接✅, MQTT连接: 已连接✅`
|
|
1257
|
+
- ✅ 帮助用户快速定位问题(Modbus连接失败?MQTT连接失败?轮询失败?)
|
|
1258
|
+
|
|
1259
|
+
**4. 故障诊断指南**
|
|
1260
|
+
|
|
1261
|
+
#### HA实体不可用问题排查步骤
|
|
1262
|
+
|
|
1263
|
+
**步骤1:检查Node-RED日志(最重要!)**
|
|
1264
|
+
|
|
1265
|
+
```
|
|
1266
|
+
✅ 正常日志(一切正常):
|
|
1267
|
+
✅ Modbus已连接: 127.0.0.1:502
|
|
1268
|
+
✅ MQTT已连接: mqtt://localhost:1883
|
|
1269
|
+
🔄 开始轮询 1 个从站设备: 从站10(线圈0-31)
|
|
1270
|
+
📡 Modbus连接: 已连接✅, MQTT连接: 已连接✅
|
|
1271
|
+
📊 从站10首次轮询成功,发布32个状态到MQTT
|
|
1272
|
+
📤 发布状态: modbus/relay/10/0/state = OFF
|
|
1273
|
+
📤 发布状态: modbus/relay/10/1/state = OFF
|
|
1274
|
+
... (共32个)
|
|
1275
|
+
|
|
1276
|
+
❌ 问题1:Modbus未连接
|
|
1277
|
+
❌ Modbus连接失败: connect ECONNREFUSED 127.0.0.1:502
|
|
1278
|
+
解决:检查Modbus设备是否运行,地址端口是否正确
|
|
1279
|
+
|
|
1280
|
+
❌ 问题2:MQTT未连接
|
|
1281
|
+
MQTT错误: connect ECONNREFUSED 127.0.0.1:1883
|
|
1282
|
+
所有MQTT broker候选地址都无法连接
|
|
1283
|
+
解决:启动MQTT broker (mosquitto)
|
|
1284
|
+
|
|
1285
|
+
❌ 问题3:Modbus已连接,但无状态发布
|
|
1286
|
+
✅ Modbus已连接
|
|
1287
|
+
✅ MQTT已连接
|
|
1288
|
+
🔄 开始轮询...
|
|
1289
|
+
轮询从站10失败: Timed out
|
|
1290
|
+
解决:检查从站地址是否正确,设备是否在线
|
|
1291
|
+
```
|
|
1292
|
+
|
|
1293
|
+
**步骤2:检查mosquitto日志**
|
|
1294
|
+
|
|
1295
|
+
```bash
|
|
1296
|
+
# 查看mosquitto日志,确认是否收到状态消息
|
|
1297
|
+
tail -f /var/log/mosquitto/mosquitto.log
|
|
1298
|
+
|
|
1299
|
+
# 应该看到类似输出:
|
|
1300
|
+
Received PUBLISH from modbus_master_xxx (d0, q1, r1, ..., 'modbus/relay/10/0/state', ... (2 bytes))
|
|
1301
|
+
Received PUBLISH from modbus_master_xxx (d0, q1, r1, ..., 'modbus/relay/10/1/state', ... (3 bytes))
|
|
1302
|
+
```
|
|
1303
|
+
|
|
1304
|
+
如果**只看到discovery消息,没有state消息**,说明Modbus轮询失败或状态未发布。
|
|
1305
|
+
|
|
1306
|
+
**步骤3:使用MQTT客户端测试**
|
|
1307
|
+
|
|
1308
|
+
```bash
|
|
1309
|
+
# 订阅所有状态主题
|
|
1310
|
+
mosquitto_sub -h localhost -t 'modbus/relay/#' -v
|
|
1311
|
+
|
|
1312
|
+
# 应该看到:
|
|
1313
|
+
modbus/relay/10/availability online
|
|
1314
|
+
modbus/relay/10/0/state OFF
|
|
1315
|
+
modbus/relay/10/1/state OFF
|
|
1316
|
+
...
|
|
1317
|
+
```
|
|
1318
|
+
|
|
1319
|
+
如果**只看到availability,没有state**,说明状态未发布。
|
|
1320
|
+
|
|
1321
|
+
**步骤4:检查Home Assistant日志**
|
|
1322
|
+
|
|
1323
|
+
在HA中查看日志,如果看到类似错误:
|
|
1324
|
+
```
|
|
1325
|
+
Payload is not supported: {...}
|
|
1326
|
+
Invalid fan_modes mode: 0
|
|
1327
|
+
```
|
|
1328
|
+
|
|
1329
|
+
**这些错误与本节点无关**,这是其他MQTT设备的问题。本节点只发布switch类型实体。
|
|
1330
|
+
|
|
1331
|
+
#### 常见问题和解决方案
|
|
1332
|
+
|
|
1333
|
+
| 症状 | 原因 | 解决方案 |
|
|
1334
|
+
|-----|------|---------|
|
|
1335
|
+
| HA实体不可用(unavailable) | 1) Modbus未连接<br>2) MQTT未连接<br>3) 未发布状态 | 查看Node-RED日志,按上述步骤排查 |
|
|
1336
|
+
| Node-RED日志无Modbus连接 | Modbus设备未运行或地址错误 | 检查TCP地址/端口或串口配置 |
|
|
1337
|
+
| Node-RED日志无MQTT连接 | MQTT broker未运行 | 启动mosquitto服务 |
|
|
1338
|
+
| Modbus和MQTT都已连接,但无状态发布 | 1) 轮询失败<br>2) 从站地址错误 | 检查从站地址配置(默认10) |
|
|
1339
|
+
| mosquitto日志只有discovery | 状态未发布到MQTT | 检查Modbus轮询是否成功 |
|
|
1340
|
+
|
|
1341
|
+
#### 技术改进
|
|
1342
|
+
|
|
1343
|
+
**节点状态显示优化**:
|
|
1344
|
+
```javascript
|
|
1345
|
+
// 新增状态更新函数
|
|
1346
|
+
node.updateNodeStatus = function() {
|
|
1347
|
+
const modbusStatus = node.isConnected ? "Modbus✓" : "Modbus✗";
|
|
1348
|
+
const mqttStatus = node.mqttConnected ? "MQTT✓" : "MQTT✗";
|
|
1349
|
+
|
|
1350
|
+
if (node.isConnected && node.mqttConnected) {
|
|
1351
|
+
node.status({fill: "green", shape: "dot", text: `${modbusStatus} ${mqttStatus}`});
|
|
1352
|
+
} else if (node.isConnected || node.mqttConnected) {
|
|
1353
|
+
node.status({fill: "yellow", shape: "ring", text: `${modbusStatus} ${mqttStatus}`});
|
|
1354
|
+
} else {
|
|
1355
|
+
node.status({fill: "red", shape: "ring", text: `${modbusStatus} ${mqttStatus}`});
|
|
1356
|
+
}
|
|
1357
|
+
};
|
|
1358
|
+
```
|
|
1359
|
+
|
|
1360
|
+
**状态发布日志**:
|
|
1361
|
+
```javascript
|
|
1362
|
+
node.mqttClient.publish(stateTopic, payload, { qos: 1, retain: true }, (err) => {
|
|
1363
|
+
if (err) {
|
|
1364
|
+
node.warn(`❌ 发布状态失败: ${stateTopic} - ${err.message}`);
|
|
1365
|
+
} else {
|
|
1366
|
+
node.log(`📤 发布状态: ${stateTopic} = ${payload}`);
|
|
1367
|
+
}
|
|
1368
|
+
});
|
|
1369
|
+
```
|
|
1370
|
+
|
|
1371
|
+
#### 升级建议
|
|
1372
|
+
|
|
1373
|
+
**从v1.6.0升级到v1.6.1**:
|
|
1374
|
+
```bash
|
|
1375
|
+
cd ~/.node-red
|
|
1376
|
+
npm install node-red-contrib-symi-modbus@latest
|
|
1377
|
+
# 重启Node-RED
|
|
1378
|
+
```
|
|
1379
|
+
|
|
1380
|
+
**新功能**:
|
|
1381
|
+
- 无需修改配置,升级后自动获得详细日志
|
|
1382
|
+
- 在Node-RED界面中查看节点状态栏,一目了然
|
|
1383
|
+
- 出现问题时,日志会清晰指出问题所在
|
|
1384
|
+
|
|
1385
|
+
---
|
|
1386
|
+
|
|
1387
|
+
### v1.6.0 (2025-10-18) - 智能MQTT连接和Docker/容器环境完美兼容
|
|
1239
1388
|
|
|
1240
1389
|
#### 核心功能
|
|
1241
1390
|
|
package/nodes/modbus-master.js
CHANGED
|
@@ -94,11 +94,26 @@ module.exports = function(RED) {
|
|
|
94
94
|
node.currentSlaveIndex = 0;
|
|
95
95
|
node.deviceStates = {}; // 存储每个设备的状态
|
|
96
96
|
node.mqttClient = null;
|
|
97
|
+
node.mqttConnected = false; // MQTT连接状态
|
|
97
98
|
node.isClosing = false;
|
|
98
99
|
node.lastErrorLog = {}; // 记录每个从站的最后错误日志时间
|
|
99
100
|
node.lastMqttErrorLog = 0; // MQTT错误日志时间
|
|
100
101
|
node.errorLogInterval = 10 * 60 * 1000; // 错误日志间隔:10分钟
|
|
101
102
|
|
|
103
|
+
// 更新节点状态显示
|
|
104
|
+
node.updateNodeStatus = function() {
|
|
105
|
+
const modbusStatus = node.isConnected ? "Modbus✓" : "Modbus✗";
|
|
106
|
+
const mqttStatus = node.mqttConnected ? "MQTT✓" : "MQTT✗";
|
|
107
|
+
|
|
108
|
+
if (node.isConnected && node.mqttConnected) {
|
|
109
|
+
node.status({fill: "green", shape: "dot", text: `${modbusStatus} ${mqttStatus}`});
|
|
110
|
+
} else if (node.isConnected || node.mqttConnected) {
|
|
111
|
+
node.status({fill: "yellow", shape: "ring", text: `${modbusStatus} ${mqttStatus}`});
|
|
112
|
+
} else {
|
|
113
|
+
node.status({fill: "red", shape: "ring", text: `${modbusStatus} ${mqttStatus}`});
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
|
|
102
117
|
// 初始化设备状态(基于从站配置列表)
|
|
103
118
|
node.config.slaves.forEach((slave) => {
|
|
104
119
|
const slaveId = slave.address;
|
|
@@ -131,7 +146,8 @@ module.exports = function(RED) {
|
|
|
131
146
|
|
|
132
147
|
node.client.setTimeout(5000);
|
|
133
148
|
node.isConnected = true;
|
|
134
|
-
node.
|
|
149
|
+
node.log(`✅ Modbus已连接: ${node.config.connectionType === "tcp" ? `${node.config.tcpHost}:${node.config.tcpPort}` : node.config.serialPort}`);
|
|
150
|
+
node.updateNodeStatus();
|
|
135
151
|
|
|
136
152
|
// 清除错误日志记录(重新部署或重连时允许再次显示错误)
|
|
137
153
|
node.lastErrorLog = {};
|
|
@@ -141,9 +157,9 @@ module.exports = function(RED) {
|
|
|
141
157
|
node.startPolling();
|
|
142
158
|
|
|
143
159
|
} catch (err) {
|
|
144
|
-
node.error(
|
|
145
|
-
node.status({fill: "red", shape: "ring", text: "连接失败"});
|
|
160
|
+
node.error(`❌ Modbus连接失败: ${err.message}`);
|
|
146
161
|
node.isConnected = false;
|
|
162
|
+
node.updateNodeStatus();
|
|
147
163
|
|
|
148
164
|
// 5秒后重试连接(使用定时器避免递归)
|
|
149
165
|
if (!node.isClosing && !node.reconnectTimer) {
|
|
@@ -262,7 +278,8 @@ module.exports = function(RED) {
|
|
|
262
278
|
lastConnectAttempt = Date.now();
|
|
263
279
|
|
|
264
280
|
node.mqttClient.on('connect', () => {
|
|
265
|
-
node.
|
|
281
|
+
node.mqttConnected = true;
|
|
282
|
+
node.log(`✅ MQTT已连接: ${brokerUrl}`);
|
|
266
283
|
|
|
267
284
|
// 成功连接后,更新配置的broker地址(下次优先使用成功的地址)
|
|
268
285
|
if (brokerUrl !== brokerCandidates[0]) {
|
|
@@ -274,6 +291,9 @@ module.exports = function(RED) {
|
|
|
274
291
|
|
|
275
292
|
// 订阅命令主题
|
|
276
293
|
node.subscribeCommands();
|
|
294
|
+
|
|
295
|
+
// 更新状态显示
|
|
296
|
+
node.updateNodeStatus();
|
|
277
297
|
});
|
|
278
298
|
|
|
279
299
|
node.mqttClient.on('error', (err) => {
|
|
@@ -469,6 +489,7 @@ module.exports = function(RED) {
|
|
|
469
489
|
// 发布MQTT状态
|
|
470
490
|
node.publishMqttState = function(slaveId, coil, value) {
|
|
471
491
|
if (!node.mqttClient || !node.mqttClient.connected) {
|
|
492
|
+
node.warn(`无法发布状态: MQTT未连接 (从站${slaveId} 线圈${coil})`);
|
|
472
493
|
return;
|
|
473
494
|
}
|
|
474
495
|
|
|
@@ -478,7 +499,9 @@ module.exports = function(RED) {
|
|
|
478
499
|
// 使用QoS=1确保消息送达,retain=true确保断线重连后可获取最新状态
|
|
479
500
|
node.mqttClient.publish(stateTopic, payload, { qos: 1, retain: true }, (err) => {
|
|
480
501
|
if (err) {
|
|
481
|
-
node.warn(
|
|
502
|
+
node.warn(`❌ 发布状态失败: ${stateTopic} - ${err.message}`);
|
|
503
|
+
} else {
|
|
504
|
+
node.log(`📤 发布状态: ${stateTopic} = ${payload}`);
|
|
482
505
|
}
|
|
483
506
|
});
|
|
484
507
|
};
|
|
@@ -493,7 +516,9 @@ module.exports = function(RED) {
|
|
|
493
516
|
node.lastErrorLog = {};
|
|
494
517
|
node.lastMqttErrorLog = 0;
|
|
495
518
|
|
|
496
|
-
node.
|
|
519
|
+
const slaveList = node.config.slaves.map(s => `从站${s.address}(线圈${s.coilStart}-${s.coilEnd})`).join(', ');
|
|
520
|
+
node.log(`🔄 开始轮询 ${node.config.slaves.length} 个从站设备: ${slaveList}`);
|
|
521
|
+
node.log(`📡 Modbus连接: ${node.isConnected ? '已连接✅' : '未连接❌'}, MQTT连接: ${node.mqttConnected ? '已连接✅' : '未连接❌'}`);
|
|
497
522
|
node.currentSlaveIndex = 0;
|
|
498
523
|
|
|
499
524
|
// 使用最小的轮询间隔
|
|
@@ -540,6 +565,7 @@ module.exports = function(RED) {
|
|
|
540
565
|
|
|
541
566
|
// 更新设备状态
|
|
542
567
|
const isFirstPoll = !node.deviceStates[slaveId].initialPublished;
|
|
568
|
+
let publishCount = 0;
|
|
543
569
|
|
|
544
570
|
for (let i = 0; i < coilCount; i++) {
|
|
545
571
|
const coilIndex = slave.coilStart + i;
|
|
@@ -551,6 +577,7 @@ module.exports = function(RED) {
|
|
|
551
577
|
// 第一次轮询或状态改变时,发布到MQTT和触发事件
|
|
552
578
|
if (isFirstPoll || oldValue !== newValue) {
|
|
553
579
|
node.publishMqttState(slaveId, coilIndex, newValue);
|
|
580
|
+
publishCount++;
|
|
554
581
|
node.emit('stateUpdate', {
|
|
555
582
|
slave: slaveId,
|
|
556
583
|
coil: coilIndex,
|
|
@@ -559,6 +586,12 @@ module.exports = function(RED) {
|
|
|
559
586
|
}
|
|
560
587
|
}
|
|
561
588
|
|
|
589
|
+
if (isFirstPoll) {
|
|
590
|
+
node.log(`📊 从站${slaveId}首次轮询成功,发布${publishCount}个状态到MQTT`);
|
|
591
|
+
} else if (publishCount > 0) {
|
|
592
|
+
node.log(`📊 从站${slaveId}状态变化,发布${publishCount}个更新到MQTT`);
|
|
593
|
+
}
|
|
594
|
+
|
|
562
595
|
node.deviceStates[slaveId].lastUpdate = Date.now();
|
|
563
596
|
node.deviceStates[slaveId].error = null;
|
|
564
597
|
node.deviceStates[slaveId].initialPublished = true; // 标记已发布初始状态
|
|
@@ -578,11 +611,7 @@ module.exports = function(RED) {
|
|
|
578
611
|
node.send(output);
|
|
579
612
|
|
|
580
613
|
// 更新状态显示
|
|
581
|
-
node.
|
|
582
|
-
fill: "green",
|
|
583
|
-
shape: "dot",
|
|
584
|
-
text: `从站${slaveId} 正常`
|
|
585
|
-
});
|
|
614
|
+
node.updateNodeStatus();
|
|
586
615
|
|
|
587
616
|
} catch (err) {
|
|
588
617
|
node.deviceStates[slaveId].error = err.message;
|
|
@@ -598,13 +627,7 @@ module.exports = function(RED) {
|
|
|
598
627
|
}
|
|
599
628
|
|
|
600
629
|
// 更新状态显示
|
|
601
|
-
|
|
602
|
-
const totalCount = node.config.slaves.length;
|
|
603
|
-
node.status({
|
|
604
|
-
fill: failedCount === totalCount ? "red" : "yellow",
|
|
605
|
-
shape: "ring",
|
|
606
|
-
text: `正常: ${totalCount - failedCount}/${totalCount}`
|
|
607
|
-
});
|
|
630
|
+
node.updateNodeStatus();
|
|
608
631
|
|
|
609
632
|
// 仅当全部从站都失败时,检测连接是否断开
|
|
610
633
|
if (failedCount === totalCount && err.message &&
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-red-contrib-symi-modbus",
|
|
3
|
-
"version": "1.6.
|
|
4
|
-
"description": "Node-RED Modbus节点,支持TCP/串口通信、串口自动搜索、多设备轮询、智能MQTT连接(自动fallback到host.docker.internal等)、Home Assistant
|
|
3
|
+
"version": "1.6.1",
|
|
4
|
+
"description": "Node-RED Modbus节点,支持TCP/串口通信、串口自动搜索、多设备轮询、智能MQTT连接(自动fallback到host.docker.internal等)、Home Assistant自动发现和多品牌开关面板,增强日志和状态显示",
|
|
5
5
|
"main": "nodes/modbus-master.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
Binary file
|