node-red-contrib-symi-mesh 1.9.22 → 1.9.23
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
CHANGED
|
@@ -186,8 +186,8 @@ node-red
|
|
|
186
186
|
- **05 - RS485 桥接**:`examples/05-symi-rs485-bridge.json`(`symi-485-bridge` + `symi-485-config`)
|
|
187
187
|
- **06 - RS485 A↔B 同步**:`examples/06-symi-rs485-sync.json`(`symi-rs485-sync` + `symi-485-config`)
|
|
188
188
|
- **07 - RS485 抓包调试**:`examples/07-rs485-debug.json`(`rs485-debug` + `symi-485-config`)
|
|
189
|
-
- **08 - KNX 桥接**:`examples/08-symi-knx-bridge.json`(`symi-knx-bridge`;**导入前必须先安装** `node-red-contrib-knx-ultimate
|
|
190
|
-
- **09 - KNX ↔ HA 桥接**:`examples/09-symi-knx-ha-bridge.json`(`symi-knx-ha-bridge`;**导入前必须先安装** `node-red-contrib-knx-ultimate` 与 Home Assistant
|
|
189
|
+
- **08 - KNX 桥接**:`examples/08-symi-knx-bridge.json`(`symi-knx-bridge`;**导入前必须先安装** `node-red-contrib-knx-ultimate`,否则 knxUltimate 节点会显示为"未知节点")。**已内置 9 条实体模板与 9 条示例映射**(执行器1/2 开关、全开/全关场景、双色调光、窗帘、空调、新风、地暖;覆盖 6 类设备实体,场景与 KNX 地址对齐融合网关 `0/0/1~2/1/24` 实际地址体系;三合一设备 `cd:d2:57:93:46:84` 预置空调+新风+地暖 3 条子实体映射)。导入后只需:①修改网关串口/TCP 与 KNX 网关 IP;②把映射中的 Mesh 设备 MAC 替换为您现场的设备(在映射下拉框重新选择即可)
|
|
190
|
+
- **09 - KNX ↔ HA 桥接**:`examples/09-symi-knx-ha-bridge.json`(`symi-knx-ha-bridge`;**导入前必须先安装** `node-red-contrib-knx-ultimate` 与 Home Assistant 相关节点)。已内置 6 类实体模板与 6 条示例映射(HA 实体 ID 为示例如 `switch.relay_1`,导入后替换为现场实体并选择 HA server 节点)
|
|
191
191
|
- **10 - 云端同步**:`examples/10-symi-cloud-sync.json`(`symi-cloud-sync`)
|
|
192
192
|
|
|
193
193
|
### 6. 多网关配置(可选)
|
|
@@ -703,6 +703,22 @@ RS485通信桥接,支持Modbus协议透传与自定义指令映射。
|
|
|
703
703
|
|
|
704
704
|
## 更新日志
|
|
705
705
|
|
|
706
|
+
### v1.9.23 (2026-08-18)
|
|
707
|
+
|
|
708
|
+
#### KNX 功能地址判定修复(三合一温度/风速无法从 KNX 控制)
|
|
709
|
+
- **修复**:`getKnxAddrFunction` 判定顺序调整——功能地址(温度/模式/风速/当前温度等)判定优先于 status。此前三合一设备把 `statusAddr` 同时用作功能地址(空调 statusAddr=温度、新风 statusAddr=风速)时,KNX 写入该地址会被误判为"状态反馈"而忽略,导致温度/风速无法从 KNX 控制 Mesh。现在仅当地址不匹配任何功能地址时才按独立状态反馈处理。
|
|
710
|
+
- **附带修复**:KNX 读三合一功能地址时,缓存回复从"开关状态(1.001)"修正为正确的功能值(温度 9.001 / 模式 20.102 / 风速 5.001 百分比)。
|
|
711
|
+
|
|
712
|
+
#### KNX→Mesh 场景触发修复(DPT 18.001 场景号解析)
|
|
713
|
+
- **修复**:场景号解析兼容 knxUltimate 对 DPT18 入站消息的三种数据形态——优先取 `msg.knx.rawValue` 原始字节(与融合网关"字节值=场景号"绑定一致),其次 Buffer,回退 `{save_recall, scenenumber}` 对象(scenenumber 1-based 减 1 还原)。此前对对象 `parseInt` 得 NaN→0,导致 KNX 场景触发永远无法匹配 `triggerValue`(1/2)而失效。
|
|
714
|
+
|
|
715
|
+
#### 日志静默优化(生产无噪声)
|
|
716
|
+
- **调整**:状态事件解析失败(0x80 帧 payload 过短等串口/网络噪声帧)与设备列表短帧日志由 warn/error 降为 **debug**(默认不显示;排障时 `SYMI_LOG_LEVEL=debug` 可见)。此类帧为物理层可预期噪声,网关协议层已自动重同步,不影响正常功能。
|
|
717
|
+
|
|
718
|
+
#### 示例流程预置实体模板(快速上手)
|
|
719
|
+
- **examples/08**(KNX 桥接):预置 9 条实体模板与 9 条示例映射(执行器1/2、全开/全关场景、双色调光、窗帘、空调、新风、地暖;含三合一 3 条子实体映射),配置与融合网关实际地址对齐,导入后只需改网关地址与替换 Mesh 设备 MAC 即可运行。
|
|
720
|
+
- **examples/09**(KNX↔HA 桥接):预置 6 类实体模板与 6 条示例 HA 映射,导入后替换 HA 实体 ID 并选择 HA server 即可。
|
|
721
|
+
|
|
706
722
|
### v1.9.22 (2026-08-17)
|
|
707
723
|
|
|
708
724
|
#### 三合一设备 KNX 映射修正(配置层)
|
|
@@ -65,7 +65,11 @@
|
|
|
65
65
|
"periodicSendInterval": 60,
|
|
66
66
|
"x": 120,
|
|
67
67
|
"y": 170,
|
|
68
|
-
"wires": [
|
|
68
|
+
"wires": [
|
|
69
|
+
[
|
|
70
|
+
"n_knx_bridge"
|
|
71
|
+
]
|
|
72
|
+
]
|
|
69
73
|
},
|
|
70
74
|
{
|
|
71
75
|
"id": "n_knx_bridge",
|
|
@@ -73,16 +77,23 @@
|
|
|
73
77
|
"z": "tab_knx_bridge",
|
|
74
78
|
"name": "KNX桥接(配置映射)",
|
|
75
79
|
"gateway": "cfg_symi_gateway_knx",
|
|
76
|
-
"mappings": "[]",
|
|
77
|
-
"knxEntities": "[]",
|
|
80
|
+
"mappings": "[{\"meshMac\":\"4a:8c:46:3d:03:cc\",\"meshChannel\":1,\"sceneId\":0,\"knxEntityId\":\"k_relay_ch1\",\"meshName\":\"零火开关_CH1(执行器1)\",\"deviceType\":\"switch\",\"meshChannels\":4},{\"meshMac\":\"4a:8c:46:3d:03:cc\",\"meshChannel\":2,\"sceneId\":0,\"knxEntityId\":\"k_relay_ch2\",\"meshName\":\"零火开关_CH2(执行器2)\",\"deviceType\":\"switch\",\"meshChannels\":4},{\"meshMac\":\"4a:8c:46:3d:03:cc\",\"meshChannel\":3,\"sceneId\":0,\"knxEntityId\":\"k_scene_on\",\"meshName\":\"零火开关_CH3(全开场景)\",\"deviceType\":\"scene\",\"meshChannels\":4},{\"meshMac\":\"4a:8c:46:3d:03:cc\",\"meshChannel\":4,\"sceneId\":0,\"knxEntityId\":\"k_scene_off\",\"meshName\":\"零火开关_CH4(全关场景)\",\"deviceType\":\"scene\",\"meshChannels\":4},{\"meshMac\":\"cc:d2:57:93:46:84\",\"meshChannel\":1,\"sceneId\":0,\"knxEntityId\":\"k_light_cct\",\"meshName\":\"双色调光灯\",\"deviceType\":\"light_cct\",\"meshChannels\":1},{\"meshMac\":\"ca:d2:57:93:46:84\",\"meshChannel\":1,\"sceneId\":0,\"knxEntityId\":\"k_cover\",\"meshName\":\"智能窗帘\",\"deviceType\":\"cover\",\"meshChannels\":1},{\"meshMac\":\"cd:d2:57:93:46:84\",\"meshChannel\":1,\"sceneId\":0,\"knxEntityId\":\"k_climate\",\"meshName\":\"空调\",\"deviceType\":\"climate\",\"meshChannels\":1},{\"meshMac\":\"cd:d2:57:93:46:84\",\"meshChannel\":1,\"sceneId\":0,\"knxEntityId\":\"k_fresh_air\",\"meshName\":\"新风\",\"deviceType\":\"fresh_air\",\"meshChannels\":1},{\"meshMac\":\"cd:d2:57:93:46:84\",\"meshChannel\":1,\"sceneId\":0,\"knxEntityId\":\"k_floor_heating\",\"meshName\":\"地暖\",\"deviceType\":\"floor_heating\",\"meshChannels\":1}]",
|
|
81
|
+
"knxEntities": "[{\"id\":\"k_relay_ch1\",\"name\":\"执行器1(继电器CH1)\",\"type\":\"switch\",\"cmdAddr\":\"0/0/1\",\"statusAddr\":\"0/1/1\",\"ext1\":\"\",\"ext2\":\"\",\"ext3\":\"\",\"triggerValue\":\"\",\"triggerAction\":\"\",\"invert\":false},{\"id\":\"k_relay_ch2\",\"name\":\"执行器2(继电器CH2)\",\"type\":\"switch\",\"cmdAddr\":\"0/0/2\",\"statusAddr\":\"0/1/2\",\"ext1\":\"\",\"ext2\":\"\",\"ext3\":\"\",\"triggerValue\":\"\",\"triggerAction\":\"\",\"invert\":false},{\"id\":\"k_scene_on\",\"name\":\"全开场景(CH3)\",\"type\":\"scene\",\"cmdAddr\":\"0/0/20\",\"statusAddr\":\"\",\"ext1\":\"\",\"ext2\":\"\",\"ext3\":\"\",\"triggerValue\":\"1\",\"triggerAction\":\"1\",\"invert\":false},{\"id\":\"k_scene_off\",\"name\":\"全关场景(CH4)\",\"type\":\"scene\",\"cmdAddr\":\"0/0/21\",\"statusAddr\":\"\",\"ext1\":\"\",\"ext2\":\"\",\"ext3\":\"\",\"triggerValue\":\"2\",\"triggerAction\":\"1\",\"invert\":false},{\"id\":\"k_light_cct\",\"name\":\"调光灯\",\"type\":\"light_cct\",\"cmdAddr\":\"1/1/80\",\"statusAddr\":\"1/2/80\",\"ext1\":\"1/3/80\",\"ext2\":\"1/5/80\",\"ext3\":\"\",\"triggerValue\":\"\",\"triggerAction\":\"\",\"invert\":false},{\"id\":\"k_cover\",\"name\":\"窗帘\",\"type\":\"cover\",\"cmdAddr\":\"3/1/10\",\"statusAddr\":\"3/1/12\",\"ext1\":\"3/1/11\",\"ext2\":\"\",\"ext3\":\"\",\"triggerValue\":\"\",\"triggerAction\":\"\",\"invert\":false},{\"id\":\"k_climate\",\"name\":\"空调\",\"type\":\"climate\",\"cmdAddr\":\"5/0/1\",\"statusAddr\":\"5/0/8\",\"ext1\":\"5/0/3\",\"ext2\":\"5/0/10\",\"ext3\":\"5/0/7\",\"triggerValue\":\"\",\"triggerAction\":\"\",\"invert\":false},{\"id\":\"k_fresh_air\",\"name\":\"新风\",\"type\":\"fresh_air\",\"cmdAddr\":\"6/6/110\",\"statusAddr\":\"6/6/112\",\"ext1\":\"\",\"ext2\":\"\",\"ext3\":\"\",\"triggerValue\":\"\",\"triggerAction\":\"\",\"invert\":false},{\"id\":\"k_floor_heating\",\"name\":\"地暖\",\"type\":\"floor_heating\",\"cmdAddr\":\"2/1/20\",\"statusAddr\":\"2/1/24\",\"ext1\":\"2/1/9\",\"ext2\":\"\",\"ext3\":\"\",\"triggerValue\":\"\",\"triggerAction\":\"\",\"invert\":false}]",
|
|
78
82
|
"echoWindowEnabled": false,
|
|
79
83
|
"echoWindow": 500,
|
|
80
84
|
"autoSyncEnabled": false,
|
|
81
85
|
"autoSyncDelay": 3,
|
|
82
|
-
"sceneVeilMs":
|
|
86
|
+
"sceneVeilMs": 2000,
|
|
83
87
|
"x": 300,
|
|
84
88
|
"y": 170,
|
|
85
|
-
"wires": [
|
|
89
|
+
"wires": [
|
|
90
|
+
[
|
|
91
|
+
"knx_ultimate_io"
|
|
92
|
+
],
|
|
93
|
+
[
|
|
94
|
+
"dbg_knx_info"
|
|
95
|
+
]
|
|
96
|
+
]
|
|
86
97
|
},
|
|
87
98
|
{
|
|
88
99
|
"id": "dbg_knx_info",
|
|
@@ -119,4 +130,4 @@
|
|
|
119
130
|
"lockTime": 800,
|
|
120
131
|
"dimmerLockTime": 3000
|
|
121
132
|
}
|
|
122
|
-
]
|
|
133
|
+
]
|
|
@@ -65,7 +65,11 @@
|
|
|
65
65
|
"periodicSendInterval": 60,
|
|
66
66
|
"x": 120,
|
|
67
67
|
"y": 170,
|
|
68
|
-
"wires": [
|
|
68
|
+
"wires": [
|
|
69
|
+
[
|
|
70
|
+
"n_knx_ha_bridge"
|
|
71
|
+
]
|
|
72
|
+
]
|
|
69
73
|
},
|
|
70
74
|
{
|
|
71
75
|
"id": "n_knx_ha_bridge",
|
|
@@ -73,11 +77,18 @@
|
|
|
73
77
|
"z": "tab_knx_ha_bridge",
|
|
74
78
|
"name": "KNX-HA桥接(配置映射)",
|
|
75
79
|
"haServer": "",
|
|
76
|
-
"mappings": "[]",
|
|
77
|
-
"knxEntities": "[]",
|
|
80
|
+
"mappings": "[{\"knxEntityId\":\"k1\",\"haEntityId\":\"switch.relay_1\"},{\"knxEntityId\":\"k2\",\"haEntityId\":\"light.living_room_lamp\"},{\"knxEntityId\":\"k3\",\"haEntityId\":\"cover.living_room_curtain\"},{\"knxEntityId\":\"k4\",\"haEntityId\":\"climate.ac_1\"},{\"knxEntityId\":\"k5\",\"haEntityId\":\"fan.fresh_air_1\"},{\"knxEntityId\":\"k6\",\"haEntityId\":\"climate.floor_heating_1\"}]",
|
|
81
|
+
"knxEntities": "[{\"id\":\"k1\",\"name\":\"继电器1\",\"type\":\"switch\",\"cmdAddr\":\"0/0/1\",\"statusAddr\":\"0/1/1\",\"ext1\":\"\",\"ext2\":\"\",\"ext3\":\"\",\"invert\":false},{\"id\":\"k2\",\"name\":\"客厅吊灯\",\"type\":\"light_cct\",\"cmdAddr\":\"0/0/5\",\"statusAddr\":\"0/1/5\",\"ext1\":\"0/0/6\",\"ext2\":\"0/0/7\",\"ext3\":\"\",\"invert\":false},{\"id\":\"k3\",\"name\":\"客厅布帘\",\"type\":\"cover\",\"cmdAddr\":\"0/0/8\",\"statusAddr\":\"0/0/9\",\"ext1\":\"0/0/10\",\"ext2\":\"\",\"ext3\":\"\",\"invert\":false},{\"id\":\"k4\",\"name\":\"温控器\",\"type\":\"climate\",\"cmdAddr\":\"0/0/11\",\"statusAddr\":\"0/0/12\",\"ext1\":\"0/0/13\",\"ext2\":\"0/0/14\",\"ext3\":\"0/1/14\",\"invert\":false},{\"id\":\"k5\",\"name\":\"新风\",\"type\":\"fresh_air\",\"cmdAddr\":\"0/0/15\",\"statusAddr\":\"0/0/16\",\"ext1\":\"\",\"ext2\":\"\",\"ext3\":\"\",\"invert\":false},{\"id\":\"k6\",\"name\":\"地暖\",\"type\":\"floor_heating\",\"cmdAddr\":\"0/0/17\",\"statusAddr\":\"0/0/18\",\"ext1\":\"0/1/18\",\"ext2\":\"\",\"ext3\":\"\",\"invert\":false}]",
|
|
78
82
|
"x": 300,
|
|
79
83
|
"y": 170,
|
|
80
|
-
"wires": [
|
|
84
|
+
"wires": [
|
|
85
|
+
[
|
|
86
|
+
"knx_ultimate_ha_io"
|
|
87
|
+
],
|
|
88
|
+
[
|
|
89
|
+
"dbg_knx_ha_info"
|
|
90
|
+
]
|
|
91
|
+
]
|
|
81
92
|
},
|
|
82
93
|
{
|
|
83
94
|
"id": "dbg_knx_ha_info",
|
|
@@ -96,4 +107,4 @@
|
|
|
96
107
|
"y": 210,
|
|
97
108
|
"wires": []
|
|
98
109
|
}
|
|
99
|
-
]
|
|
110
|
+
]
|
package/nodes/symi-gateway.js
CHANGED
|
@@ -346,8 +346,8 @@ module.exports = function(RED) {
|
|
|
346
346
|
}
|
|
347
347
|
this.parseDeviceListFrame(frame);
|
|
348
348
|
} else {
|
|
349
|
-
//
|
|
350
|
-
this._symiLog.
|
|
349
|
+
// 可预期的噪声帧/粘包/半包:吞掉并限流诊断,生产默认静默(debug),避免刷屏
|
|
350
|
+
this._symiLog.debugThrottled("deviceListShort", `[DeviceList] payload过短(${frame.payload.length}),忽略本帧`);
|
|
351
351
|
this._symiLog.debugThrottled("deviceListShortHex", `[DeviceList] payload(hex): ${Array.from(frame.payload).map(b => b.toString(16).padStart(2, "0")).join(" ").toUpperCase()}`);
|
|
352
352
|
}
|
|
353
353
|
} else if (frame.isDeviceStatusEvent()) {
|
|
@@ -657,9 +657,10 @@ module.exports = function(RED) {
|
|
|
657
657
|
await new Promise(resolve => setImmediate(resolve));
|
|
658
658
|
}
|
|
659
659
|
} catch (error) {
|
|
660
|
-
//
|
|
661
|
-
//
|
|
662
|
-
|
|
660
|
+
// 解析失败会导致同步事件丢失;但根因是串口/网络噪声与网关异常帧
|
|
661
|
+
// (如 0x80 帧 payload 不足 3 字节),属物理层可预期噪声,无法根治。
|
|
662
|
+
// 生产默认静默(降为 debug),排障时可开 SYMI_LOG_LEVEL=debug 查看。
|
|
663
|
+
this._symiLog.debugThrottled("state-event-parse", `[状态事件] 解析失败(该帧的同步事件已丢弃): ${error.message}, frame=${frame.header.toString(16)}${frame.opcode.toString(16)}${frame.status !== null ? frame.status.toString(16) : ""}${Array.from(frame.payload.slice(0, 12)).map(b => b.toString(16).padStart(2, "0")).join("")}`);
|
|
663
664
|
}
|
|
664
665
|
}
|
|
665
666
|
|
package/nodes/symi-knx-bridge.js
CHANGED
|
@@ -711,9 +711,12 @@ module.exports = function(RED) {
|
|
|
711
711
|
};
|
|
712
712
|
|
|
713
713
|
// 判断KNX地址的功能类型
|
|
714
|
+
// 【修复 v1.9.23】功能地址判定必须优先于 status:
|
|
715
|
+
// 三合一等设备常把 statusAddr 同时用作功能地址(如空调 statusAddr=温度地址、新风 statusAddr=风速地址),
|
|
716
|
+
// 若 status 判定在前,KNX 写入该地址会被误判为“状态反馈”而忽略,导致温度/风速无法从 KNX 控制 Mesh。
|
|
717
|
+
// 仅当地址不匹配任何功能地址时才按 status(独立状态反馈)处理。
|
|
714
718
|
node.getKnxAddrFunction = function(mapping, groupAddr) {
|
|
715
719
|
if (groupAddr === mapping.knxAddrCmd) return "cmd";
|
|
716
|
-
if (groupAddr === mapping.knxAddrStatus) return "status";
|
|
717
720
|
if (groupAddr === mapping.knxAddrBrightness) return "brightness";
|
|
718
721
|
if (groupAddr === mapping.knxAddrColorTemp) return "colorTemp";
|
|
719
722
|
if (groupAddr === mapping.knxAddrRGB) return "rgb";
|
|
@@ -723,6 +726,7 @@ module.exports = function(RED) {
|
|
|
723
726
|
if (groupAddr === mapping.knxAddrMode) return "mode";
|
|
724
727
|
if (groupAddr === mapping.knxAddrFanSpeed) return "fanSpeed";
|
|
725
728
|
if (groupAddr === mapping.knxAddrCurrentTemp) return "currentTemp";
|
|
729
|
+
if (groupAddr === mapping.knxAddrStatus) return "status";
|
|
726
730
|
return "unknown";
|
|
727
731
|
};
|
|
728
732
|
|
|
@@ -3746,8 +3750,20 @@ module.exports = function(RED) {
|
|
|
3746
3750
|
else if (mapping.deviceType === "scene") {
|
|
3747
3751
|
if (addrFunc === "cmd" || addrFunc === "status") {
|
|
3748
3752
|
let receivedScene = 0;
|
|
3749
|
-
|
|
3750
|
-
|
|
3753
|
+
// 【场景号解析兼容三种形态】knxUltimate 对 DPT18 入站消息:
|
|
3754
|
+
// 1. msg.knx.rawValue = 原始字节(最可靠,融合网关按"字节值=场景号"绑定)
|
|
3755
|
+
// 2. msg.payload 为 Buffer(部分配置下)
|
|
3756
|
+
// 3. msg.payload 为 {save_recall, scenenumber} 对象(scenenumber 1-based,字节=scenenumber-1)
|
|
3757
|
+
// 此前仅 parseInt(value) 对对象返回 NaN→0,导致 KNX 场景永远无法匹配 triggerValue。
|
|
3758
|
+
if (msg && msg.knx && Buffer.isBuffer(msg.knx.rawValue)) {
|
|
3759
|
+
receivedScene = msg.knx.rawValue[0];
|
|
3760
|
+
} else if (Buffer.isBuffer(value)) {
|
|
3761
|
+
receivedScene = value[0];
|
|
3762
|
+
} else if (value && typeof value === "object" && value.scenenumber !== undefined) {
|
|
3763
|
+
receivedScene = (parseInt(value.scenenumber, 10) || 1) - 1;
|
|
3764
|
+
} else {
|
|
3765
|
+
receivedScene = parseInt(value, 10) || 0;
|
|
3766
|
+
}
|
|
3751
3767
|
|
|
3752
3768
|
// 【关键修复】多个场景共享同一组地址时,必须遍历所有映射找到triggerValue匹配的那个
|
|
3753
3769
|
const allSceneMappings = node.findAllKnxMappings(groupAddr);
|