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 +23 -14
- package/nodes/modbus-master.js +9 -9
- package/nodes/modbus-slave-switch.html +22 -2
- package/nodes/modbus-slave-switch.js +23 -15
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -479,7 +479,7 @@ msg.payload = 1; // 或 0
|
|
|
479
479
|
|
|
480
480
|
## 项目信息
|
|
481
481
|
|
|
482
|
-
**版本**: v2.6.
|
|
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.
|
|
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模式:可选接入
|
|
506
|
-
-
|
|
507
|
-
- 状态显示:清晰区分当前运行模式
|
|
508
|
-
- **改进的错误提示**:
|
|
509
|
-
- MQTT未配置时不再报错,提示使用本地模式
|
|
510
|
-
- 友好的配置引导信息
|
|
511
|
-
- 更清晰的节点状态指示
|
|
512
|
-
- **增强的稳定性**:
|
|
513
|
-
- 支持纯本地化部署,不受网络影响
|
|
514
|
-
- 工控机断网环境下持续稳定运行
|
|
515
|
-
- 保持所有原有功能的兼容性
|
|
522
|
+
- 本地模式:纯串口通信
|
|
523
|
+
- MQTT模式:可选接入HA
|
|
524
|
+
- 智能切换和状态显示
|
|
516
525
|
|
|
517
526
|
**性能优化**:
|
|
518
527
|
- 轮询间隔优化:修复间隔计算逻辑,确保每个从站使用正确的轮询间隔
|
package/nodes/modbus-master.js
CHANGED
|
@@ -495,7 +495,7 @@ module.exports = function(RED) {
|
|
|
495
495
|
tryConnect(brokerCandidates[0]);
|
|
496
496
|
}, 5000);
|
|
497
497
|
} else {
|
|
498
|
-
node.
|
|
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.
|
|
869
|
+
node.debug(`从站${slaveId}首次轮询成功,读取${coilCount}个线圈`);
|
|
870
870
|
} else if (publishCount > 0) {
|
|
871
|
-
node.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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="
|
|
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
|
-
//
|
|
83
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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 (!
|
|
518
|
-
//
|
|
518
|
+
if (!mqttEnabled) {
|
|
519
|
+
// 本地模式(未启用MQTT)
|
|
519
520
|
node.status({
|
|
520
521
|
fill: node.currentState ? "green" : "blue",
|
|
521
522
|
shape: "dot",
|
|
522
|
-
text: `本地模式
|
|
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.
|
|
554
|
-
node.
|
|
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.
|
|
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.
|
|
726
|
+
node.debug(`跳过LED反馈(状态未变化,间隔${timeSinceLastChange}ms)`);
|
|
719
727
|
return;
|
|
720
728
|
}
|
|
721
729
|
|
package/package.json
CHANGED