node-red-contrib-symi-mesh 1.8.10 → 1.8.11
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 +10 -0
- package/nodes/symi-gateway.js +10 -0
- package/nodes/symi-knx-bridge.html +1 -1
- package/nodes/symi-knx-bridge.js +20 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -704,6 +704,16 @@ node-red-contrib-symi-mesh/
|
|
|
704
704
|
|
|
705
705
|
## 更新日志
|
|
706
706
|
|
|
707
|
+
### v1.8.11 (2026-01-16)
|
|
708
|
+
|
|
709
|
+
**核心稳定性修复**:
|
|
710
|
+
- **僵尸节点彻底清除**:修复了在删除或修改 KNX 场景映射并重新部署后,旧的配置逻辑仍在后台运行的问题。
|
|
711
|
+
- **原因分析**:旧节点实例销毁时,未能完全解绑网关事件监听器,导致“僵尸节点”继续响应事件。
|
|
712
|
+
- **解决方案**:引入 `node.isClosed` 标志位,强制拦截销毁后的所有逻辑执行;同时修复了网关连接状态监听器的内存泄漏问题。
|
|
713
|
+
- **内存泄漏修复**:将所有匿名事件监听器改为具名函数,确保在节点关闭时能被正确移除,防止多次部署后的内存累积。
|
|
714
|
+
- **空指针异常防护**:在 `symi-gateway` 中增加了多处连接状态检查,防止网关断开后后台任务访问已销毁的客户端对象,彻底消除 `Cannot read properties of null (reading 'sendFrame')` 报错刷屏。
|
|
715
|
+
- **UI 显示优化**:修复了 KNX 开关类型在配置列表中错误显示“扩展”列数据的问题,现在开关类型的扩展列将正确显示为“-”。
|
|
716
|
+
|
|
707
717
|
### v1.8.10 (2026-01-16)
|
|
708
718
|
|
|
709
719
|
**新增功能**:
|
package/nodes/symi-gateway.js
CHANGED
|
@@ -495,6 +495,10 @@ module.exports = function(RED) {
|
|
|
495
495
|
}
|
|
496
496
|
|
|
497
497
|
for (const attr of queryAttrs) {
|
|
498
|
+
if (!this.client) {
|
|
499
|
+
this.error(`查询设备${device.name}失败: 网关未连接 (Client is null)`);
|
|
500
|
+
break;
|
|
501
|
+
}
|
|
498
502
|
const frame = this.protocolHandler.buildDeviceStatusQueryFrame(device.networkAddress, attr);
|
|
499
503
|
await this.client.sendFrame(frame, 2);
|
|
500
504
|
await this.sleep(150); // 增加延迟确保设备有时间响应
|
|
@@ -522,6 +526,7 @@ module.exports = function(RED) {
|
|
|
522
526
|
this.log(`[三合一检测] 查询 ${device.name} 完整状态...`);
|
|
523
527
|
const threeInOneAttrs = [0x02, 0x16, 0x1B, 0x1C, 0x1D, 0x6A, 0x6C];
|
|
524
528
|
for (const attr of threeInOneAttrs) {
|
|
529
|
+
if (!this.client) break;
|
|
525
530
|
const frame = this.protocolHandler.buildDeviceStatusQueryFrame(device.networkAddress, attr);
|
|
526
531
|
await this.client.sendFrame(frame, 2);
|
|
527
532
|
await this.sleep(150);
|
|
@@ -545,6 +550,7 @@ module.exports = function(RED) {
|
|
|
545
550
|
// 查询温控器状态
|
|
546
551
|
const tempCtrlAttrs = [0x02, 0x16, 0x1B, 0x1C, 0x1D];
|
|
547
552
|
for (const attr of tempCtrlAttrs) {
|
|
553
|
+
if (!this.client) break;
|
|
548
554
|
const frame = this.protocolHandler.buildDeviceStatusQueryFrame(device.networkAddress, attr);
|
|
549
555
|
await this.client.sendFrame(frame, 2);
|
|
550
556
|
await this.sleep(150);
|
|
@@ -567,6 +573,10 @@ module.exports = function(RED) {
|
|
|
567
573
|
this.log('启用设备状态上报功能...');
|
|
568
574
|
for (const device of devices) {
|
|
569
575
|
try {
|
|
576
|
+
if (!this.client) {
|
|
577
|
+
this.error('网关连接断开,停止启用状态上报');
|
|
578
|
+
break;
|
|
579
|
+
}
|
|
570
580
|
// 启用状态上报:msg_type=0x10, param=0x01
|
|
571
581
|
const frame = this.protocolHandler.buildDeviceControlFrame(device.networkAddress, 0x10, Buffer.from([0x01]));
|
|
572
582
|
await this.client.sendFrame(frame, 2);
|
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
if (!knxEntities.length) { c.html('<div class="tips">点击"导入"或"添加"管理KNX实体</div>'); return; }
|
|
80
80
|
let h = '<table class="tbl"><tr><th>名称</th><th>类型</th><th>命令</th><th>状态</th><th>扩展</th><th style="width:60px">操作</th></tr>';
|
|
81
81
|
knxEntities.forEach((e,i) => {
|
|
82
|
-
const ext = [e.ext1,e.ext2,e.ext3].filter(x=>x).join(',');
|
|
82
|
+
const ext = (e.type === 'switch') ? '-' : [e.ext1,e.ext2,e.ext3].filter(x=>x).join(',');
|
|
83
83
|
const inv = e.type==='cover' ? '<input type="checkbox" class="e-inv" title="位置反转"'+(e.invert?' checked':'')+'>' : '';
|
|
84
84
|
h += '<tr data-ei="'+i+'"><td>'+e.name+'</td><td>'+(typeLabels[e.type]||e.type)+inv+'</td><td>'+e.cmdAddr+'</td><td>'+(e.statusAddr||'-')+'</td><td>'+(ext||'-')+'</td><td><button class="red-ui-button red-ui-button-small e-edit" title="编辑"><i class="fa fa-pencil"></i></button> <button class="red-ui-button red-ui-button-small e-del" title="删除"><i class="fa fa-times"></i></button></td></tr>';
|
|
85
85
|
});
|
package/nodes/symi-knx-bridge.js
CHANGED
|
@@ -178,6 +178,7 @@ module.exports = function(RED) {
|
|
|
178
178
|
|
|
179
179
|
// 初始化标记
|
|
180
180
|
node.initializing = true;
|
|
181
|
+
node.isClosed = false; // 防止僵尸节点执行逻辑
|
|
181
182
|
node.initTimer = setTimeout(() => {
|
|
182
183
|
node.initializing = false;
|
|
183
184
|
node.log('[KNX Bridge] 初始化完成,开始同步');
|
|
@@ -261,6 +262,9 @@ module.exports = function(RED) {
|
|
|
261
262
|
|
|
262
263
|
// ========== Mesh设备状态变化处理 ==========
|
|
263
264
|
const handleMeshStateChange = (eventData) => {
|
|
265
|
+
// 防止僵尸节点执行
|
|
266
|
+
if (node.isClosed) return;
|
|
267
|
+
|
|
264
268
|
// 只检查initializing,不检查syncLock
|
|
265
269
|
// syncLock会导致队列处理期间丢失事件,改用per-device时间戳防回环
|
|
266
270
|
if (node.initializing) return;
|
|
@@ -1198,13 +1202,17 @@ module.exports = function(RED) {
|
|
|
1198
1202
|
// ========== 事件监听 ==========
|
|
1199
1203
|
|
|
1200
1204
|
// 监听网关连接状态
|
|
1201
|
-
|
|
1205
|
+
const handleGatewayConnected = () => {
|
|
1206
|
+
if (node.isClosed) return;
|
|
1202
1207
|
node.status({ fill: 'green', shape: 'ring', text: `网关已连接 ${node.mappings.length}个映射` });
|
|
1203
|
-
}
|
|
1208
|
+
};
|
|
1209
|
+
node.gateway.on('gateway-connected', handleGatewayConnected);
|
|
1204
1210
|
|
|
1205
|
-
|
|
1211
|
+
const handleGatewayDisconnected = () => {
|
|
1212
|
+
if (node.isClosed) return;
|
|
1206
1213
|
node.status({ fill: 'red', shape: 'ring', text: '网关断开' });
|
|
1207
|
-
}
|
|
1214
|
+
};
|
|
1215
|
+
node.gateway.on('gateway-disconnected', handleGatewayDisconnected);
|
|
1208
1216
|
|
|
1209
1217
|
// 监听Mesh设备状态变化
|
|
1210
1218
|
node.gateway.on('device-state-changed', handleMeshStateChange);
|
|
@@ -1212,6 +1220,9 @@ module.exports = function(RED) {
|
|
|
1212
1220
|
// ========== 场景执行事件处理 ==========
|
|
1213
1221
|
// 当收到场景执行通知时,查询所有已映射设备的状态
|
|
1214
1222
|
const handleSceneExecuted = (eventData) => {
|
|
1223
|
+
// 防止僵尸节点执行
|
|
1224
|
+
if (node.isClosed) return;
|
|
1225
|
+
|
|
1215
1226
|
if (node.initializing) return;
|
|
1216
1227
|
|
|
1217
1228
|
node.log(`[场景执行] 检测到场景${eventData.sceneId}执行,延迟查询设备状态`);
|
|
@@ -1253,6 +1264,9 @@ module.exports = function(RED) {
|
|
|
1253
1264
|
|
|
1254
1265
|
// ========== 清理 ==========
|
|
1255
1266
|
node.on('close', function(done) {
|
|
1267
|
+
// 标记为已关闭,阻止任何后续事件处理
|
|
1268
|
+
node.isClosed = true;
|
|
1269
|
+
|
|
1256
1270
|
// 清除初始化定时器
|
|
1257
1271
|
if (node.initTimer) {
|
|
1258
1272
|
clearTimeout(node.initTimer);
|
|
@@ -1262,6 +1276,8 @@ module.exports = function(RED) {
|
|
|
1262
1276
|
if (node.gateway) {
|
|
1263
1277
|
node.gateway.removeListener('device-state-changed', handleMeshStateChange);
|
|
1264
1278
|
node.gateway.removeListener('scene-executed', handleSceneExecuted);
|
|
1279
|
+
node.gateway.removeListener('gateway-connected', handleGatewayConnected);
|
|
1280
|
+
node.gateway.removeListener('gateway-disconnected', handleGatewayDisconnected);
|
|
1265
1281
|
}
|
|
1266
1282
|
|
|
1267
1283
|
// 销毁 SyncUtils 实例,清理资源
|