node-red-contrib-symi-modbus 2.7.2 → 2.7.3
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 +38 -70
- package/nodes/custom-protocol.js +12 -9
- package/nodes/modbus-slave-switch.js +36 -32
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -737,7 +737,7 @@ HomeKit网桥节点无需输入消息,自动同步主站配置和状态。
|
|
|
737
737
|
|
|
738
738
|
**设备类型说明**:
|
|
739
739
|
- **开关模式**:接收`true`发送打开指令,接收`false`发送关闭指令
|
|
740
|
-
-
|
|
740
|
+
- **窗帘模式**:无论收到`true`还是`false`,都触发下一个指令,循环顺序:打开 → 暂停 → 关闭 → 暂停 → 打开...
|
|
741
741
|
- **其他模式**:与开关模式相同,接收`true/false`发送对应指令
|
|
742
742
|
|
|
743
743
|
**功能特性**:
|
|
@@ -758,18 +758,23 @@ HomeKit网桥节点无需输入消息,自动同步主站配置和状态。
|
|
|
758
758
|
|
|
759
759
|
**窗帘模式示例**:
|
|
760
760
|
```
|
|
761
|
-
第1次收到true
|
|
762
|
-
第2次收到false → 发送"
|
|
763
|
-
第3次收到true
|
|
764
|
-
第4次收到false →
|
|
761
|
+
第1次收到true/false → 发送"打开"指令(例如:01 05 00 00 FF 00 8C 3A)
|
|
762
|
+
第2次收到true/false → 发送"暂停"指令(例如:01 05 00 01 FF 00 DD FA)
|
|
763
|
+
第3次收到true/false → 发送"关闭"指令(例如:01 05 00 00 00 00 CD CA)
|
|
764
|
+
第4次收到true/false → 发送"暂停"指令(例如:01 05 00 01 FF 00 DD FA)
|
|
765
|
+
第5次收到true/false → 循环回到"打开"指令
|
|
765
766
|
```
|
|
766
767
|
|
|
768
|
+
**适配两种开关模式**:
|
|
769
|
+
- **有状态开关**(开关模式):从站开关关联实际线圈,输出`true/false`交替
|
|
770
|
+
- **无状态开关**(场景模式):从站开关关联虚拟线圈(如线圈32),每次点击只发`true`或`false`
|
|
771
|
+
|
|
767
772
|
**技术细节**:
|
|
768
773
|
- 输入消息:`msg.payload = true/false`(从从站开关节点接收)
|
|
769
774
|
- 输出消息:`msg.payload = Buffer`(16进制数据,可连线到debug节点)
|
|
770
775
|
- 16进制字符串自动转换为Buffer格式
|
|
771
776
|
- 支持空格、大小写混合输入,自动格式化
|
|
772
|
-
-
|
|
777
|
+
- 窗帘模式内部维护状态索引,自动循环(4个状态:打开→暂停→关闭→暂停)
|
|
773
778
|
|
|
774
779
|
**注意事项**:
|
|
775
780
|
- 16进制指令最多48字节
|
|
@@ -857,42 +862,18 @@ HomeKit网桥节点无需输入消息,自动同步主站配置和状态。
|
|
|
857
862
|
- Node.js: >=14.0.0
|
|
858
863
|
- Node-RED: >=2.0.0
|
|
859
864
|
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
- 配置持久化保存,重启后自动恢复
|
|
868
|
-
- 连线方式:从站开关 → 自定义协议 → debug节点
|
|
869
|
-
|
|
870
|
-
**历史更新**:
|
|
871
|
-
- **v2.7.1**:新增可视化控制看板节点,实时显示和控制所有继电器状态
|
|
872
|
-
- **v2.7.0**:智能写入队列机制,解决HomeKit群控锁竞争问题,支持160个继电器同时群控
|
|
873
|
-
- **v2.6.8**:新增HomeKit网桥节点,一键桥接到Apple HomeKit,支持Siri语音控制
|
|
874
|
-
- **v2.6.7**:修复LED反馈功能,TCP连接稳定性优化,日志优化
|
|
875
|
-
- **v2.6.6**:解决MQTT日志刷屏问题,局域网IP检测优化
|
|
876
|
-
- **v2.6.5**:修复MQTT报错问题,新增"启用MQTT"勾选框
|
|
877
|
-
- **v2.6.4**:日志优化,大幅减少日志输出
|
|
878
|
-
- **v2.6.3**:MQTT可选配置,完全兼容无MQTT环境
|
|
879
|
-
|
|
880
|
-
**性能优化**:
|
|
881
|
-
- 轮询间隔优化:修复间隔计算逻辑,确保每个从站使用正确的轮询间隔
|
|
882
|
-
- MQTT发布使用QoS=0,避免阻塞轮询
|
|
883
|
-
- 异步发布状态更新,不影响Modbus读取性能
|
|
884
|
-
- 减少调试日志输出,降低CPU占用
|
|
885
|
-
- 互斥锁机制确保读写操作不冲突
|
|
886
|
-
- 共享连接配置节点,避免串口资源冲突
|
|
887
|
-
- 写入队列20ms间隔,快速响应,总线稳定
|
|
888
|
-
|
|
889
|
-
**许可证**: MIT License
|
|
890
|
-
|
|
891
|
-
**作者**: symi-daguo
|
|
865
|
+
## 许可证
|
|
866
|
+
|
|
867
|
+
MIT License
|
|
868
|
+
|
|
869
|
+
## 作者
|
|
870
|
+
|
|
871
|
+
symi-daguo
|
|
892
872
|
- NPM: https://www.npmjs.com/~symi-daguo
|
|
893
873
|
- GitHub: https://github.com/symi-daguo
|
|
894
874
|
|
|
895
|
-
|
|
875
|
+
## 支持
|
|
876
|
+
|
|
896
877
|
- Issues: https://github.com/symi-daguo/node-red-contrib-symi-modbus/issues
|
|
897
878
|
- NPM: https://www.npmjs.com/package/node-red-contrib-symi-modbus
|
|
898
879
|
|
|
@@ -919,16 +900,6 @@ HomeKit网桥节点无需输入消息,自动同步主站配置和状态。
|
|
|
919
900
|
- HEX显示:可选大写、可选时间戳、`maxBytes` 控制显示长度
|
|
920
901
|
- 输出:`msg.payload`(格式化HEX)、`msg.buffer`(原始Buffer)、`msg.meta`(来源信息)
|
|
921
902
|
|
|
922
|
-
### v2.6.6 重要更新
|
|
923
|
-
|
|
924
|
-
- 调试节点配置验证修复:根据所选数据来源类型(`serial`/`modbus`)动态校验,避免误报为“配置不正确”
|
|
925
|
-
- 统一侧边栏分类:所有节点统一归类到 `SYMI-MODBUS`
|
|
926
|
-
- 文档更新:补充节点分类、调试节点要点与常见问题处理
|
|
927
|
-
|
|
928
|
-
典型用法:
|
|
929
|
-
- 联调TCP转RS485网关时,观察上行/下行原始数据帧是否完整。
|
|
930
|
-
- 排查波特率/数据位/校验位配置是否正确(串口模式)。
|
|
931
|
-
- 与 `modbus-master` 或 `modbus-slave-switch` 同时使用,定位现场设备通信异常。
|
|
932
903
|
|
|
933
904
|
### MQTT自动发现
|
|
934
905
|
|
|
@@ -1167,29 +1138,26 @@ msg.payload = 1; // 或 0
|
|
|
1167
1138
|
|
|
1168
1139
|
## 项目信息
|
|
1169
1140
|
|
|
1170
|
-
|
|
1141
|
+
**当前版本**: v2.7.3
|
|
1171
1142
|
|
|
1172
|
-
|
|
1173
|
-
-
|
|
1174
|
-
-
|
|
1175
|
-
-
|
|
1176
|
-
-
|
|
1177
|
-
- 🔥 **双模式支持**(本地模式和MQTT模式可选切换,断网也能稳定运行)
|
|
1178
|
-
- 🔥 **免连线通信**(主站和从站通过内部事件通信,无需连线,支持本地模式和MQTT模式)
|
|
1179
|
-
- 🔥 **HomeKit网桥**(一键桥接到Apple HomeKit,支持Siri语音控制,名称可自定义)
|
|
1180
|
-
- 🔥 **智能写入队列**(所有写入操作串行执行,避免锁竞争,支持HomeKit群控)
|
|
1181
|
-
- 🔥 **可视化控制看板**(实时显示和控制所有继电器状态,美观易用,客户友好)
|
|
1182
|
-
- 🔥 **自定义协议转换**(支持非标准485协议设备,窗帘循环控制,配置界面可测试)
|
|
1183
|
-
- MQTT集成(可选启用,Home Assistant自动发现,实体唯一性保证,QoS=0高性能发布)
|
|
1184
|
-
- 物理开关面板双向同步(亖米协议支持,LED反馈同步,支持开关模式和场景模式)
|
|
1185
|
-
- 共享连接架构(多个从站开关节点共享同一个串口/TCP连接,支持500+节点)
|
|
1186
|
-
- 长期稳定运行(内存管理、智能重连、错误日志限流、异步MQTT发布、TCP永久连接)
|
|
1143
|
+
**最新更新**(v2.7.3):
|
|
1144
|
+
- 优化窗帘控制逻辑:打开 → 暂停 → 关闭 → 暂停 → 打开(循环),更符合实际使用场景
|
|
1145
|
+
- 优化MQTT断网日志:长期断网时减少日志输出,避免垃圾日志影响性能和硬盘空间
|
|
1146
|
+
- 重试间隔优化:从5秒改为30秒,减少重试频率,降低系统负担
|
|
1147
|
+
- 日志级别优化:MQTT错误从error改为debug,不写入日志文件
|
|
1187
1148
|
|
|
1188
1149
|
**技术栈**:
|
|
1189
|
-
- modbus-serial: ^8.0.23(内部封装serialport,支持TCP和串口)
|
|
1190
|
-
- serialport: ^12.0.0(原生串口通信)
|
|
1191
|
-
- mqtt: ^5.14.1(最新稳定版,可选依赖)
|
|
1192
|
-
- hap-nodejs: ^1.2.0(HomeKit桥接)
|
|
1193
|
-
- node-persist: ^4.0.4(持久化存储)
|
|
1194
1150
|
- Node.js: >=14.0.0
|
|
1195
1151
|
- Node-RED: >=2.0.0
|
|
1152
|
+
- modbus-serial: ^8.0.23
|
|
1153
|
+
- serialport: ^12.0.0
|
|
1154
|
+
- mqtt: ^5.14.1(可选)
|
|
1155
|
+
- hap-nodejs: ^1.2.0
|
|
1156
|
+
- node-persist: ^4.0.4
|
|
1157
|
+
|
|
1158
|
+
**历史版本**:
|
|
1159
|
+
- v2.7.2: 新增自定义协议节点,支持非标准485协议设备
|
|
1160
|
+
- v2.7.1: 新增可视化控制看板节点
|
|
1161
|
+
- v2.7.0: 智能写入队列机制,支持HomeKit群控
|
|
1162
|
+
- v2.6.8: 新增HomeKit网桥节点
|
|
1163
|
+
- v2.6.7及更早: 基础功能实现
|
package/nodes/custom-protocol.js
CHANGED
|
@@ -83,7 +83,7 @@ module.exports = function(RED) {
|
|
|
83
83
|
// 处理输入消息
|
|
84
84
|
node.on('input', function(msg) {
|
|
85
85
|
var value = msg.payload;
|
|
86
|
-
|
|
86
|
+
|
|
87
87
|
// 只接受布尔值
|
|
88
88
|
if (typeof value !== 'boolean') {
|
|
89
89
|
node.warn('输入必须为true/false,当前类型: ' + typeof value);
|
|
@@ -91,38 +91,41 @@ module.exports = function(RED) {
|
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
if (node.config.deviceType === 'curtain') {
|
|
94
|
-
//
|
|
94
|
+
// 窗帘模式:无论收到true还是false,都触发下一个指令
|
|
95
|
+
// 适配有状态开关(true/false交替)和无状态开关(只发true或false)
|
|
96
|
+
// 循环顺序:打开 → 暂停 → 关闭 → 暂停 → 打开...
|
|
95
97
|
var commands = [
|
|
96
98
|
{name: '打开', hex: node.config.openCmd},
|
|
99
|
+
{name: '暂停', hex: node.config.pauseCmd},
|
|
97
100
|
{name: '关闭', hex: node.config.closeCmd},
|
|
98
101
|
{name: '暂停', hex: node.config.pauseCmd}
|
|
99
102
|
];
|
|
100
|
-
|
|
103
|
+
|
|
101
104
|
// 获取当前指令
|
|
102
105
|
var currentCmd = commands[node.curtainStateIndex];
|
|
103
106
|
if (!currentCmd || !currentCmd.hex) {
|
|
104
107
|
node.warn('窗帘模式缺少' + currentCmd.name + '指令配置');
|
|
105
108
|
return;
|
|
106
109
|
}
|
|
107
|
-
|
|
110
|
+
|
|
108
111
|
// 转换并发送
|
|
109
112
|
var buffer = hexStringToBuffer(currentCmd.hex);
|
|
110
113
|
if (buffer) {
|
|
111
114
|
sendCommand(buffer, currentCmd.name);
|
|
112
|
-
|
|
113
|
-
//
|
|
114
|
-
node.curtainStateIndex = (node.curtainStateIndex + 1) %
|
|
115
|
+
|
|
116
|
+
// 移动到下一个状态(无论收到true还是false)
|
|
117
|
+
node.curtainStateIndex = (node.curtainStateIndex + 1) % 4;
|
|
115
118
|
}
|
|
116
119
|
} else {
|
|
117
120
|
// 开关/其他模式:true发送打开,false发送关闭
|
|
118
121
|
var cmdName = value ? '打开' : '关闭';
|
|
119
122
|
var hexString = value ? node.config.openCmd : node.config.closeCmd;
|
|
120
|
-
|
|
123
|
+
|
|
121
124
|
if (!hexString) {
|
|
122
125
|
node.warn(cmdName + '指令未配置');
|
|
123
126
|
return;
|
|
124
127
|
}
|
|
125
|
-
|
|
128
|
+
|
|
126
129
|
var buffer = hexStringToBuffer(hexString);
|
|
127
130
|
if (buffer) {
|
|
128
131
|
sendCommand(buffer, cmdName);
|
|
@@ -691,55 +691,59 @@ module.exports = function(RED) {
|
|
|
691
691
|
node.mqttClient.on('error', (err) => {
|
|
692
692
|
// 连接失败,尝试下一个候选地址
|
|
693
693
|
const errorMsg = err.message || err.code || '连接失败';
|
|
694
|
-
node.warn(`MQTT连接错误: ${errorMsg} (broker: ${brokerUrl})`);
|
|
695
|
-
|
|
696
694
|
const now = Date.now();
|
|
695
|
+
|
|
696
|
+
// 使用日志限流,避免长期断网时产生垃圾日志
|
|
697
|
+
const shouldLogError = (now - node.lastMqttErrorLog) > node.errorLogInterval;
|
|
698
|
+
if (shouldLogError) {
|
|
699
|
+
node.debug(`MQTT连接错误: ${errorMsg} (broker: ${brokerUrl})`);
|
|
700
|
+
}
|
|
701
|
+
|
|
697
702
|
const timeSinceLastAttempt = now - lastConnectAttempt;
|
|
698
|
-
|
|
699
|
-
// 避免频繁重试(至少等待1
|
|
703
|
+
|
|
704
|
+
// 避免频繁重试(至少等待1秒)
|
|
700
705
|
if (timeSinceLastAttempt < 1000) {
|
|
701
706
|
setTimeout(() => {
|
|
702
707
|
tryNextBroker();
|
|
703
708
|
}, 1000);
|
|
704
709
|
return;
|
|
705
710
|
}
|
|
706
|
-
|
|
711
|
+
|
|
707
712
|
tryNextBroker();
|
|
708
|
-
|
|
713
|
+
|
|
709
714
|
function tryNextBroker() {
|
|
710
715
|
// 尝试下一个候选地址
|
|
711
716
|
currentCandidateIndex = (currentCandidateIndex + 1) % brokerCandidates.length;
|
|
712
717
|
const nextBroker = brokerCandidates[currentCandidateIndex];
|
|
713
|
-
|
|
718
|
+
|
|
714
719
|
// 如果回到第一个地址,说明所有地址都试过了
|
|
715
720
|
if (currentCandidateIndex === 0) {
|
|
716
721
|
// 判断是否是局域网IP配置(只有一个候选地址)
|
|
717
722
|
const isSingleIpConfig = brokerCandidates.length === 1;
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
node.
|
|
731
|
-
node.
|
|
732
|
-
node.
|
|
733
|
-
node.error('提示:如果Node-RED运行在Docker容器中,可能需要使用host.docker.internal或容器IP [此错误将在10分钟后再次显示]');
|
|
734
|
-
node.lastMqttErrorLog = now;
|
|
723
|
+
|
|
724
|
+
// 使用日志限流,避免长期断网时产生垃圾日志
|
|
725
|
+
const shouldLog = (now - node.lastMqttErrorLog) > node.errorLogInterval;
|
|
726
|
+
|
|
727
|
+
if (shouldLog) {
|
|
728
|
+
if (isSingleIpConfig) {
|
|
729
|
+
// 局域网IP配置失败,使用debug级别(不写入日志文件)
|
|
730
|
+
node.debug(`MQTT连接失败: ${errorMsg}`);
|
|
731
|
+
node.debug(`无法连接到MQTT broker: ${brokerCandidates[0]}`);
|
|
732
|
+
node.debug('请检查:1) MQTT broker是否在该地址运行 2) 网络是否连通 3) 端口是否正确');
|
|
733
|
+
} else {
|
|
734
|
+
// 多个fallback地址都失败,使用debug级别
|
|
735
|
+
node.debug(`MQTT错误: ${errorMsg}`);
|
|
736
|
+
node.debug(`所有MQTT broker候选地址都无法连接: ${brokerCandidates.join(', ')}`);
|
|
737
|
+
node.debug('请检查:1) MQTT broker是否运行 2) 网络连接是否正常 3) broker地址是否正确');
|
|
735
738
|
}
|
|
739
|
+
node.lastMqttErrorLog = now;
|
|
736
740
|
}
|
|
737
|
-
|
|
738
|
-
// 5
|
|
741
|
+
|
|
742
|
+
// 30秒后重试第一个地址(从5秒改为30秒,减少重试频率)
|
|
739
743
|
setTimeout(() => {
|
|
740
744
|
node.debug('重试连接MQTT broker...');
|
|
741
745
|
tryConnect(brokerCandidates[0]);
|
|
742
|
-
},
|
|
746
|
+
}, 30000);
|
|
743
747
|
} else {
|
|
744
748
|
node.debug(`尝试备用MQTT broker: ${nextBroker}`);
|
|
745
749
|
setTimeout(() => {
|
|
@@ -747,7 +751,7 @@ module.exports = function(RED) {
|
|
|
747
751
|
}, 500); // 快速尝试下一个地址
|
|
748
752
|
}
|
|
749
753
|
}
|
|
750
|
-
|
|
754
|
+
|
|
751
755
|
node.updateStatus();
|
|
752
756
|
});
|
|
753
757
|
|
|
@@ -758,16 +762,16 @@ module.exports = function(RED) {
|
|
|
758
762
|
node.mqttClient.on('offline', () => {
|
|
759
763
|
const now = Date.now();
|
|
760
764
|
const shouldLog = (now - node.lastMqttErrorLog) > node.errorLogInterval;
|
|
761
|
-
|
|
765
|
+
|
|
762
766
|
if (shouldLog) {
|
|
763
|
-
node.
|
|
767
|
+
node.debug('MQTT离线,正在尝试重连...');
|
|
764
768
|
node.lastMqttErrorLog = now;
|
|
765
769
|
}
|
|
766
|
-
|
|
770
|
+
|
|
767
771
|
// 尝试下一个候选地址
|
|
768
772
|
currentCandidateIndex = (currentCandidateIndex + 1) % brokerCandidates.length;
|
|
769
773
|
const nextBroker = brokerCandidates[currentCandidateIndex];
|
|
770
|
-
|
|
774
|
+
|
|
771
775
|
setTimeout(() => {
|
|
772
776
|
tryConnect(nextBroker);
|
|
773
777
|
}, 2000);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-red-contrib-symi-modbus",
|
|
3
|
-
"version": "2.7.
|
|
3
|
+
"version": "2.7.3",
|
|
4
4
|
"description": "Node-RED Modbus节点,支持TCP/串口通信、串口自动搜索、多设备轮询、可选MQTT集成(支持纯本地模式和MQTT模式)、Home Assistant自动发现、HomeKit网桥、可视化控制看板、自定义协议转换和物理开关面板双向同步,工控机长期稳定运行",
|
|
5
5
|
"main": "nodes/modbus-master.js",
|
|
6
6
|
"scripts": {
|