node-red-contrib-symi-modbus 2.9.3 → 2.9.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 +9 -21
- package/nodes/modbus-slave-switch.html +16 -1
- package/nodes/modbus-slave-switch.js +28 -31
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -888,29 +888,17 @@ HomeKit网桥节点无需输入消息,自动同步主站配置和状态。
|
|
|
888
888
|
|
|
889
889
|
## 版本信息
|
|
890
890
|
|
|
891
|
-
**当前版本**: v2.9.
|
|
891
|
+
**当前版本**: v2.9.4 (2025-12-15)
|
|
892
892
|
|
|
893
|
-
**v2.9.
|
|
893
|
+
**v2.9.4 更新内容**:
|
|
894
894
|
- **重要修复**:场景按钮LED反馈不同步问题
|
|
895
|
-
-
|
|
896
|
-
-
|
|
897
|
-
-
|
|
898
|
-
-
|
|
899
|
-
-
|
|
900
|
-
-
|
|
901
|
-
|
|
902
|
-
**v2.9.2 更新内容**:
|
|
903
|
-
- **稳定性增强**:全局防抖缓存定期清理机制
|
|
904
|
-
- 每10分钟自动清理超过1分钟未使用的防抖记录
|
|
905
|
-
- 防止长期运行时内存缓慢增长
|
|
906
|
-
- **稳定性增强**:Mesh无线模式按键注册清理
|
|
907
|
-
- 节点关闭时正确清理meshWirelessButtons全局Map
|
|
908
|
-
- 避免重新部署时残留无效注册信息
|
|
909
|
-
- **代码审查**:全面检查所有节点代码
|
|
910
|
-
- 确认所有定时器在节点关闭时正确清理
|
|
911
|
-
- 确认所有事件监听器正确注销
|
|
912
|
-
- 确认所有连接资源正确释放
|
|
913
|
-
- 支持断电断网后自动恢复运行
|
|
895
|
+
- 修复场景按钮按下后背光灯不跟随继电器状态变化的bug
|
|
896
|
+
- 问题根因:场景模式下currentState提前更新,导致后续状态变化事件被认为"未变化"而跳过LED反馈
|
|
897
|
+
- 解决方案:场景按钮触发时立即发送LED反馈,不等待状态变化事件
|
|
898
|
+
- **新功能**:按键背光灯选项
|
|
899
|
+
- 在按钮编号下拉框中新增"按键背光灯"选项(通道0x0F)
|
|
900
|
+
- 用于红外感应触发背光灯的联动控制
|
|
901
|
+
- 未选择此选项时,红外感应帧会被正确忽略,避免误触发
|
|
914
902
|
|
|
915
903
|
**核心特性**:
|
|
916
904
|
- 支持Modbus RTU/TCP协议,兼容标准Modbus设备和TCP转RS485网关
|
|
@@ -37,7 +37,8 @@
|
|
|
37
37
|
} else {
|
|
38
38
|
// 开关模式和场景模式显示
|
|
39
39
|
const coilDisplay = this.targetCoilNumber || 1;
|
|
40
|
-
|
|
40
|
+
const btnLabel = this.buttonNumber == 15 ? '背光灯' : `按钮${this.buttonNumber}`;
|
|
41
|
+
return this.name || `开关${this.switchId}-${btnLabel} → 继电器${this.targetSlaveAddress}-${coilDisplay}路`;
|
|
41
42
|
}
|
|
42
43
|
},
|
|
43
44
|
oneditprepare: function() {
|
|
@@ -68,6 +69,16 @@
|
|
|
68
69
|
}
|
|
69
70
|
});
|
|
70
71
|
|
|
72
|
+
// 按钮编号切换时显示/隐藏背光灯提示
|
|
73
|
+
$("#node-input-buttonNumber").on("change", function() {
|
|
74
|
+
const buttonNumber = $(this).val();
|
|
75
|
+
if (buttonNumber === '15') {
|
|
76
|
+
$("#backlight-hint").show();
|
|
77
|
+
} else {
|
|
78
|
+
$("#backlight-hint").hide();
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
71
82
|
// Mesh设备发现按钮
|
|
72
83
|
$("#btn-discover-mesh").on("click", function() {
|
|
73
84
|
const serialPortConfig = $("#node-input-serialPortConfig").val();
|
|
@@ -297,8 +308,12 @@
|
|
|
297
308
|
<option value="6">按钮 6</option>
|
|
298
309
|
<option value="7">按钮 7</option>
|
|
299
310
|
<option value="8">按钮 8</option>
|
|
311
|
+
<option value="15">按键背光灯</option>
|
|
300
312
|
</select>
|
|
301
313
|
<span style="margin-left: 10px; font-size: 11px; color: #666; font-style: italic;">面板物理按键</span>
|
|
314
|
+
<div id="backlight-hint" style="font-size: 11px; color: #888; margin-left: 110px; margin-top: 3px; display: none;">
|
|
315
|
+
<i class="fa fa-info-circle" style="color: #2196f3;"></i> 按键背光灯用于红外感应触发,通道0x0F
|
|
316
|
+
</div>
|
|
302
317
|
</div>
|
|
303
318
|
|
|
304
319
|
<!-- Mesh模式配置 -->
|
|
@@ -442,11 +442,11 @@ module.exports = function(RED) {
|
|
|
442
442
|
node.hasReceivedInitialState = false;
|
|
443
443
|
|
|
444
444
|
// 根据按钮编号计算deviceAddr和channel(用于LED反馈)
|
|
445
|
-
//
|
|
446
|
-
//
|
|
447
|
-
//
|
|
448
|
-
node.buttonDeviceAddr =
|
|
449
|
-
node.buttonChannel =
|
|
445
|
+
// 8键面板:deviceAddr固定为1,channel直接使用按键编号1-8
|
|
446
|
+
// 这是因为8键面板的帧格式是 deviceAddr=1, channel=1-8,不是按4通道分组
|
|
447
|
+
// 例如:按键7的LED反馈帧是 7E 01 04 0F 01 00 01 07 ...(deviceAddr=1, channel=7)
|
|
448
|
+
node.buttonDeviceAddr = 1;
|
|
449
|
+
node.buttonChannel = node.config.buttonNumber;
|
|
450
450
|
|
|
451
451
|
// MQTT主题(映射到继电器设备)
|
|
452
452
|
node.stateTopic = `${node.config.mqttBaseTopic}/${node.config.targetSlaveAddress}/${node.config.targetCoilNumber}/state`;
|
|
@@ -729,14 +729,18 @@ module.exports = function(RED) {
|
|
|
729
729
|
// RS-485模式或非Mesh模式:只处理自己绑定的线圈
|
|
730
730
|
// 检查状态是否真正变化
|
|
731
731
|
const stateChanged = (node.currentState !== value);
|
|
732
|
+
|
|
733
|
+
// 区分触发源:按键触发时LED反馈已在handleRs485Data中发送
|
|
734
|
+
const isButtonPress = (triggerSource === 'button-press' || triggerSource === 'scene-trigger');
|
|
732
735
|
|
|
733
736
|
// 更新当前状态
|
|
734
737
|
node.currentState = value;
|
|
735
738
|
node.lastStateChange.timestamp = Date.now();
|
|
736
739
|
node.lastStateChange.value = value;
|
|
737
740
|
|
|
738
|
-
if (stateChanged) {
|
|
739
|
-
// RS-485
|
|
741
|
+
if (stateChanged && !isButtonPress) {
|
|
742
|
+
// RS-485模式:状态变化且非按键触发时发送LED反馈
|
|
743
|
+
// 按键触发的LED反馈已在handleRs485Data中发送,避免重复
|
|
740
744
|
node.sendCommandToPanel(value);
|
|
741
745
|
}
|
|
742
746
|
|
|
@@ -788,35 +792,24 @@ module.exports = function(RED) {
|
|
|
788
792
|
continue; // 静默忽略非按键事件
|
|
789
793
|
}
|
|
790
794
|
|
|
791
|
-
//
|
|
792
|
-
//
|
|
793
|
-
|
|
795
|
+
// 计算实际按键编号
|
|
796
|
+
// 8键面板:deviceAddr=1时,channel直接就是按键编号1-8
|
|
797
|
+
// 例如:devAddr=1,channel=7→按键7
|
|
798
|
+
// 特殊:channel=0x0F(15)是红外感应触发背光灯
|
|
799
|
+
const actualButtonNumber = (buttonEvent.deviceAddr === 1) ? buttonEvent.channel : (buttonEvent.deviceAddr * 4 - 4 + buttonEvent.channel);
|
|
794
800
|
|
|
795
801
|
// 检查是否是我们监听的开关面板和按钮
|
|
796
802
|
// switchId对应本地地址(物理面板地址)
|
|
797
|
-
// buttonNumber对应实际按键编号(1-8
|
|
803
|
+
// buttonNumber对应实际按键编号(1-8,或15表示背光灯)
|
|
798
804
|
//
|
|
799
|
-
//
|
|
800
|
-
//
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
805
|
+
// 注意:通道0x0F是红外感应触发背光灯
|
|
806
|
+
// 只有当用户配置了buttonNumber=15(按键背光灯)时才处理该帧
|
|
807
|
+
// 否则忽略红外感应帧(避免误触发普通按键事件)
|
|
808
|
+
if (buttonEvent.channel === 0x0F && node.config.buttonNumber !== 15) {
|
|
809
|
+
continue; // 忽略红外感应帧(仅当未配置为背光灯模式时)
|
|
810
|
+
}
|
|
804
811
|
|
|
805
|
-
if (
|
|
806
|
-
// 只有开关模式才更新deviceAddr和channel为实际值
|
|
807
|
-
// 场景模式保持使用配置计算的值,因为场景帧的通道(如0x0F)是特殊标识,不是LED反馈通道
|
|
808
|
-
if (!isSceneModeFrame && node.config.buttonType !== 'scene') {
|
|
809
|
-
const oldDeviceAddr = node.buttonDeviceAddr;
|
|
810
|
-
const oldChannel = node.buttonChannel;
|
|
811
|
-
node.buttonDeviceAddr = buttonEvent.deviceAddr;
|
|
812
|
-
node.buttonChannel = buttonEvent.channel;
|
|
813
|
-
|
|
814
|
-
// 输出调试日志,对比计算值和实际值
|
|
815
|
-
if (oldDeviceAddr !== buttonEvent.deviceAddr || oldChannel !== buttonEvent.channel) {
|
|
816
|
-
node.log(`按键事件更新LED反馈地址:计算值(设备${oldDeviceAddr} 通道${oldChannel}) → 实际值(设备${buttonEvent.deviceAddr} 通道${buttonEvent.channel})`);
|
|
817
|
-
}
|
|
818
|
-
}
|
|
819
|
-
|
|
812
|
+
if (buttonEvent.raw.localAddr === node.config.switchId && actualButtonNumber === node.config.buttonNumber) {
|
|
820
813
|
// 判断按钮类型:优先使用协议解析结果,其次使用配置
|
|
821
814
|
const isSceneMode = buttonEvent.isSceneMode ||
|
|
822
815
|
node.config.buttonType === 'scene' ||
|
|
@@ -844,6 +837,10 @@ module.exports = function(RED) {
|
|
|
844
837
|
// 场景模式:切换状态(每次触发时翻转)
|
|
845
838
|
node.currentState = !node.currentState;
|
|
846
839
|
node.sendMqttCommand(node.currentState);
|
|
840
|
+
|
|
841
|
+
// 场景模式:立即发送LED反馈(修复:不等待状态变化事件)
|
|
842
|
+
// 因为currentState已经更新,后续的coilStateChanged事件会被认为"状态未变化"而跳过
|
|
843
|
+
node.sendCommandToPanel(node.currentState);
|
|
847
844
|
} else {
|
|
848
845
|
// 开关模式:根据状态发送ON/OFF
|
|
849
846
|
node.debug(`开关${buttonEvent.state ? 'ON' : 'OFF'}: 面板${node.config.switchId} 按键${node.config.buttonNumber}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-red-contrib-symi-modbus",
|
|
3
|
-
"version": "2.9.
|
|
3
|
+
"version": "2.9.4",
|
|
4
4
|
"description": "Node-RED Modbus节点,支持TCP/串口通信、串口自动搜索、多设备轮询、可选MQTT集成(支持纯本地模式和MQTT模式)、Home Assistant自动发现、HomeKit网桥、可视化控制看板、自定义协议转换和物理开关面板双向同步,工控机长期稳定运行",
|
|
5
5
|
"main": "nodes/modbus-master.js",
|
|
6
6
|
"scripts": {
|