node-red-contrib-symi-modbus 2.9.8 → 2.9.10
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 +29 -3
- package/nodes/custom-protocol.js +11 -11
- package/nodes/homekit-bridge.js +8 -8
- package/nodes/modbus-dashboard.js +1 -1
- package/nodes/modbus-debug.js +5 -5
- package/nodes/modbus-master.html +2 -2
- package/nodes/modbus-master.js +153 -62
- package/nodes/modbus-server-config.js +2 -1
- package/nodes/modbus-slave-switch.html +23 -1
- package/nodes/modbus-slave-switch.js +43 -25
- package/nodes/serial-port-config.js +60 -30
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -471,8 +471,8 @@ node-red-restart
|
|
|
471
471
|
- 连接前彻底清理旧实例,避免资源泄漏
|
|
472
472
|
- **互斥锁机制**:防止读写冲突导致的数据异常
|
|
473
473
|
- **TCP永久连接**:
|
|
474
|
-
|
|
475
|
-
|
|
474
|
+
- 禁用TCP超时(永久连接),避免无数据时超时断开
|
|
475
|
+
- Keep-Alive心跳10秒间隔,确保连接活跃
|
|
476
476
|
- 适应客户长期不在家、总线无数据的场景
|
|
477
477
|
- 网络故障自动重连,恢复后立即恢复通信
|
|
478
478
|
|
|
@@ -952,7 +952,33 @@ HomeKit网桥节点无需输入消息,自动同步主站配置和状态。
|
|
|
952
952
|
|
|
953
953
|
## 版本信息
|
|
954
954
|
|
|
955
|
-
**当前版本**: v2.9.
|
|
955
|
+
**当前版本**: v2.9.10 (2026-01-08)
|
|
956
|
+
|
|
957
|
+
**v2.9.10 更新内容**:
|
|
958
|
+
- **日志系统优化(解决日志占用问题)**:
|
|
959
|
+
- **彻底静默重连日志**:将 TCP/串口连接过程中的 `node.error` 降级为 `node.log` 或 `node.debug`,不再发送到 Node-RED 调试面板(Debug Tab),彻底解决重连期间日志刷屏问题。
|
|
960
|
+
- **智能过滤**:相同的连接错误在重试期间不再重复输出日志(每 10 分钟仅后台提醒一次)。
|
|
961
|
+
- **状态栏增强**:将具体错误信息(如“拒绝连接”、“串口不存在”)直接显示在节点状态文字中,无需查看日志即可掌握连接状况。
|
|
962
|
+
- **多主站配置隔离修复**:
|
|
963
|
+
- 修复了多个Modbus主站节点在编辑时配置互相干扰的问题(通过Scoped Event Handler实现)
|
|
964
|
+
- 确保每个主站节点的从站列表配置完全独立,互不影响
|
|
965
|
+
- **从站开关主站关联**:
|
|
966
|
+
- **新增关联主站功能**:从站开关节点新增"关联主站"配置项
|
|
967
|
+
- **多主站支持**:支持选择特定的主站节点,解决多主站环境下相同从站地址冲突的问题
|
|
968
|
+
- **智能过滤**:从站开关只响应关联主站的状态更新,避免误触发
|
|
969
|
+
- **事件机制优化**:
|
|
970
|
+
- 优化内部事件总线,携带主站ID信息,实现精确的消息路由
|
|
971
|
+
- 增强`modbus-master`和`modbus-slave-switch`之间的免连线通信稳定性
|
|
972
|
+
|
|
973
|
+
**v2.9.9 更新内容**:
|
|
974
|
+
- **深度优化多 TCP 主站并发**:
|
|
975
|
+
- **独立主站标识**:为每个 TCP 主站生成唯一标识,支持多个 TCP 连接独立运行,互不干扰
|
|
976
|
+
- **全局实例管理**:建立全局实例表,解决多节点并发时的资源抢占和卡顿问题
|
|
977
|
+
- **TCP 参数调优**:
|
|
978
|
+
- 降低 TCP 超时时间至 1000ms,提升异常响应速度
|
|
979
|
+
- 启用 Keep-Alive(10秒心跳),确保长连接稳定性
|
|
980
|
+
- 开启 NoDelay,禁用 Nagle 算法,显著降低指令发送延迟
|
|
981
|
+
- **稳定性增强**:完善节点关闭时的资源释放逻辑,防止内存泄漏和连接残留
|
|
956
982
|
|
|
957
983
|
**v2.9.8 更新内容**:
|
|
958
984
|
- **新增品牌支持**:Clowire(克伦威尔)智能面板
|
package/nodes/custom-protocol.js
CHANGED
|
@@ -21,7 +21,7 @@ module.exports = function(RED) {
|
|
|
21
21
|
// 获取串口配置节点
|
|
22
22
|
var serialNode = RED.nodes.getNode(node.config.serialConfig);
|
|
23
23
|
if (!serialNode) {
|
|
24
|
-
node.
|
|
24
|
+
node.log('未找到串口配置节点');
|
|
25
25
|
node.status({fill: "red", shape: "ring", text: "未配置串口"});
|
|
26
26
|
return;
|
|
27
27
|
}
|
|
@@ -38,21 +38,21 @@ module.exports = function(RED) {
|
|
|
38
38
|
|
|
39
39
|
// 确保是偶数长度
|
|
40
40
|
if (hex.length % 2 !== 0) {
|
|
41
|
-
node.
|
|
41
|
+
node.log('16进制字符串长度必须为偶数: ' + hexString);
|
|
42
42
|
return null;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
// 限制48字节
|
|
46
46
|
if (hex.length > 96) {
|
|
47
47
|
hex = hex.substring(0, 96);
|
|
48
|
-
node.
|
|
48
|
+
node.log('指令长度超过48字节,已截断');
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
// 转换为Buffer
|
|
52
52
|
var buffer = Buffer.from(hex, 'hex');
|
|
53
53
|
return buffer;
|
|
54
54
|
} catch (err) {
|
|
55
|
-
node.
|
|
55
|
+
node.log('16进制字符串转换失败: ' + err.message);
|
|
56
56
|
return null;
|
|
57
57
|
}
|
|
58
58
|
}
|
|
@@ -60,13 +60,13 @@ module.exports = function(RED) {
|
|
|
60
60
|
// 发送指令到串口
|
|
61
61
|
function sendCommand(buffer, cmdName) {
|
|
62
62
|
if (!buffer) {
|
|
63
|
-
node.
|
|
63
|
+
node.log('指令为空,跳过发送');
|
|
64
64
|
return;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
// 直接通过串口配置节点发送数据
|
|
68
68
|
if (!serialNode || !serialNode.connection) {
|
|
69
|
-
node.
|
|
69
|
+
node.log('串口连接未建立');
|
|
70
70
|
node.status({fill: "red", shape: "ring", text: "连接未建立"});
|
|
71
71
|
return;
|
|
72
72
|
}
|
|
@@ -80,7 +80,7 @@ module.exports = function(RED) {
|
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
if (!isConnected) {
|
|
83
|
-
node.
|
|
83
|
+
node.log('串口/TCP连接未打开');
|
|
84
84
|
node.status({fill: "red", shape: "ring", text: "连接未打开"});
|
|
85
85
|
return;
|
|
86
86
|
}
|
|
@@ -88,7 +88,7 @@ module.exports = function(RED) {
|
|
|
88
88
|
// 使用串口配置节点的write方法(带队列机制)
|
|
89
89
|
serialNode.write(buffer, function(err) {
|
|
90
90
|
if (err) {
|
|
91
|
-
node.
|
|
91
|
+
node.log('发送失败: ' + err.message);
|
|
92
92
|
node.status({fill: "red", shape: "ring", text: "发送失败"});
|
|
93
93
|
} else {
|
|
94
94
|
node.log(cmdName + '指令已发送: ' + buffer.toString('hex').toUpperCase());
|
|
@@ -108,7 +108,7 @@ module.exports = function(RED) {
|
|
|
108
108
|
|
|
109
109
|
// 只接受布尔值
|
|
110
110
|
if (typeof value !== 'boolean') {
|
|
111
|
-
node.
|
|
111
|
+
node.log('输入必须为true/false,当前类型: ' + typeof value);
|
|
112
112
|
return;
|
|
113
113
|
}
|
|
114
114
|
|
|
@@ -126,7 +126,7 @@ module.exports = function(RED) {
|
|
|
126
126
|
// 获取当前指令
|
|
127
127
|
var currentCmd = commands[node.curtainStateIndex];
|
|
128
128
|
if (!currentCmd || !currentCmd.hex) {
|
|
129
|
-
node.
|
|
129
|
+
node.log('窗帘模式缺少' + currentCmd.name + '指令配置');
|
|
130
130
|
return;
|
|
131
131
|
}
|
|
132
132
|
|
|
@@ -144,7 +144,7 @@ module.exports = function(RED) {
|
|
|
144
144
|
var hexString = value ? node.config.openCmd : node.config.closeCmd;
|
|
145
145
|
|
|
146
146
|
if (!hexString) {
|
|
147
|
-
node.
|
|
147
|
+
node.log(cmdName + '指令未配置');
|
|
148
148
|
return;
|
|
149
149
|
}
|
|
150
150
|
|
package/nodes/homekit-bridge.js
CHANGED
|
@@ -54,7 +54,7 @@ module.exports = function(RED) {
|
|
|
54
54
|
// 获取主站节点
|
|
55
55
|
node.masterNode = RED.nodes.getNode(node.config.masterNodeId);
|
|
56
56
|
if (!node.masterNode) {
|
|
57
|
-
node.
|
|
57
|
+
node.log("未找到主站节点");
|
|
58
58
|
node.status({fill: "red", shape: "dot", text: "主站节点未找到"});
|
|
59
59
|
return;
|
|
60
60
|
}
|
|
@@ -62,7 +62,7 @@ module.exports = function(RED) {
|
|
|
62
62
|
// 从主站节点的 config.slaves 获取从站配置
|
|
63
63
|
const slaves = node.masterNode.config ? node.masterNode.config.slaves : null;
|
|
64
64
|
if (!slaves || slaves.length === 0) {
|
|
65
|
-
node.
|
|
65
|
+
node.log("主站节点未配置从站");
|
|
66
66
|
node.status({fill: "red", shape: "dot", text: "主站未配置从站"});
|
|
67
67
|
return;
|
|
68
68
|
}
|
|
@@ -82,8 +82,8 @@ module.exports = function(RED) {
|
|
|
82
82
|
node.log(`端口: ${node.config.port}`);
|
|
83
83
|
|
|
84
84
|
} catch (err) {
|
|
85
|
-
node.
|
|
86
|
-
node.status({fill: "red", shape: "
|
|
85
|
+
node.log(`初始化异常: ${err.message}`);
|
|
86
|
+
node.status({fill: "red", shape: "ring", text: `初始化失败: ${err.message}`});
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
|
|
@@ -198,7 +198,7 @@ module.exports = function(RED) {
|
|
|
198
198
|
node.log(`配件名称已更新: ${accessory._slaveAddr}_${accessory._coil} -> ${newName}`);
|
|
199
199
|
}
|
|
200
200
|
} catch (err) {
|
|
201
|
-
node.
|
|
201
|
+
node.log(`更新配件名称失败: ${err.message}`);
|
|
202
202
|
}
|
|
203
203
|
}
|
|
204
204
|
|
|
@@ -259,7 +259,7 @@ module.exports = function(RED) {
|
|
|
259
259
|
node.log(`状态同步到HomeKit: 从站${slave} 线圈${coil} = ${value ? 'ON' : 'OFF'}`);
|
|
260
260
|
}
|
|
261
261
|
} catch (err) {
|
|
262
|
-
node.
|
|
262
|
+
node.log(`状态变化监听器错误: ${err.message}`);
|
|
263
263
|
}
|
|
264
264
|
};
|
|
265
265
|
|
|
@@ -287,7 +287,7 @@ module.exports = function(RED) {
|
|
|
287
287
|
});
|
|
288
288
|
node.log(`已更新 ${updateCount} 个配件名称`);
|
|
289
289
|
} catch (err) {
|
|
290
|
-
node.
|
|
290
|
+
node.log(`批量更新配件名称失败: ${err.message}`);
|
|
291
291
|
}
|
|
292
292
|
}
|
|
293
293
|
|
|
@@ -307,7 +307,7 @@ module.exports = function(RED) {
|
|
|
307
307
|
node.bridge.unpublish();
|
|
308
308
|
node.log("HomeKit网桥已停止");
|
|
309
309
|
} catch (err) {
|
|
310
|
-
node.
|
|
310
|
+
node.log(`停止网桥时出错: ${err.message}`);
|
|
311
311
|
}
|
|
312
312
|
}
|
|
313
313
|
|
package/nodes/modbus-debug.js
CHANGED
|
@@ -92,7 +92,7 @@ module.exports = function(RED) {
|
|
|
92
92
|
if (node.sourceType === "serial") {
|
|
93
93
|
if (!node.serialPortConfig) {
|
|
94
94
|
node.status({ fill: "red", shape: "ring", text: "未选择串口配置" });
|
|
95
|
-
node.
|
|
95
|
+
node.log("未配置RS-485连接,请在节点中选择 serial-port-config 配置节点");
|
|
96
96
|
return;
|
|
97
97
|
}
|
|
98
98
|
// 使用共享连接
|
|
@@ -104,14 +104,14 @@ module.exports = function(RED) {
|
|
|
104
104
|
node.log(`modbus-debug 监听共享连接:${desc}`);
|
|
105
105
|
node.status({ fill: "blue", shape: "ring", text: "监听中" });
|
|
106
106
|
} catch (err) {
|
|
107
|
-
node.
|
|
107
|
+
node.log(`注册数据监听器失败: ${err.message}`);
|
|
108
108
|
node.status({ fill: "red", shape: "ring", text: "监听失败" });
|
|
109
109
|
}
|
|
110
110
|
} else if (node.sourceType === "modbus") {
|
|
111
111
|
// 已废弃:不再支持独立连接到Modbus服务器(会导致串口冲突)
|
|
112
112
|
// 请使用 "serial" 源类型并选择 serial-port-config 配置节点
|
|
113
113
|
node.status({ fill: "red", shape: "ring", text: "请使用serial源类型" });
|
|
114
|
-
node.
|
|
114
|
+
node.log("不再支持独立连接到Modbus服务器(会导致串口冲突),请将源类型改为 'serial' 并选择 serial-port-config 配置节点");
|
|
115
115
|
return;
|
|
116
116
|
}
|
|
117
117
|
};
|
|
@@ -128,7 +128,7 @@ module.exports = function(RED) {
|
|
|
128
128
|
node.serialPortConfig.unregisterDataListener(sendHexMsg);
|
|
129
129
|
}
|
|
130
130
|
} catch (e) {
|
|
131
|
-
node.
|
|
131
|
+
node.log(`注销监听器时出错: ${e.message}`);
|
|
132
132
|
}
|
|
133
133
|
try {
|
|
134
134
|
if (node.localConnection) {
|
|
@@ -140,7 +140,7 @@ module.exports = function(RED) {
|
|
|
140
140
|
node.localConnection = null;
|
|
141
141
|
}
|
|
142
142
|
} catch (e) {
|
|
143
|
-
node.
|
|
143
|
+
node.log(`关闭本地连接时出错: ${e.message}`);
|
|
144
144
|
}
|
|
145
145
|
done();
|
|
146
146
|
});
|
package/nodes/modbus-master.html
CHANGED
|
@@ -122,7 +122,7 @@
|
|
|
122
122
|
renderSlaveList();
|
|
123
123
|
});
|
|
124
124
|
|
|
125
|
-
$(
|
|
125
|
+
$("#slave-list-container").on("click", ".btn-delete-slave", function() {
|
|
126
126
|
var index = parseInt($(this).data("index"));
|
|
127
127
|
if (node.slaves.length > 1) {
|
|
128
128
|
node.slaves.splice(index, 1);
|
|
@@ -132,7 +132,7 @@
|
|
|
132
132
|
}
|
|
133
133
|
});
|
|
134
134
|
|
|
135
|
-
$(
|
|
135
|
+
$("#slave-list-container").on("change", ".slave-address, .slave-coil-start, .slave-coil-end, .slave-poll-interval", function() {
|
|
136
136
|
var index = parseInt($(this).data("index"));
|
|
137
137
|
var className = $(this).attr("class").split(" ")[0];
|
|
138
138
|
var value = parseInt($(this).val());
|