node-red-contrib-symi-mesh 1.9.22 → 1.9.42
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 +143 -82
- package/examples/08-symi-knx-bridge.json +18 -5
- package/examples/09-symi-knx-ha-bridge.json +16 -5
- package/lib/device-manager.js +4 -96
- package/lib/knx-entity-mapper.js +144 -0
- package/lib/knx-scene.js +97 -0
- package/lib/mqtt-helper.js +2 -8
- package/lib/sync-utils.js +0 -128
- package/nodes/rs485-debug.html +20 -5
- package/nodes/symi-485-bridge.html +11 -3
- package/nodes/symi-485-bridge.js +8 -26
- package/nodes/symi-485-config.html +24 -5
- package/nodes/symi-485-config.js +11 -2
- package/nodes/symi-cloud-sync.html +22 -5
- package/nodes/symi-cloud-sync.js +13 -4
- package/nodes/symi-device.html +36 -24
- package/nodes/symi-device.js +5 -3
- package/nodes/symi-gateway.html +24 -5
- package/nodes/symi-gateway.js +56 -17
- package/nodes/symi-ha-sync.html +11 -3
- package/nodes/symi-knx-bridge.html +832 -734
- package/nodes/symi-knx-bridge.js +911 -237
- package/nodes/symi-knx-ha-bridge.html +13 -4
- package/nodes/symi-mqtt-brand.html +20 -3
- package/nodes/symi-mqtt-sync.html +11 -3
- package/nodes/symi-mqtt-sync.js +51 -9
- package/nodes/symi-mqtt.html +11 -3
- package/nodes/symi-mqtt.js +7 -2
- package/nodes/symi-rs485-sync.html +13 -4
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -95,6 +95,19 @@ cd ~/.node-red
|
|
|
95
95
|
npm install /ABSOLUTE/PATH/node-red-contrib-symi-mesh-<version>.tgz
|
|
96
96
|
```
|
|
97
97
|
|
|
98
|
+
**npm 登录与发布(维护者)**:
|
|
99
|
+
|
|
100
|
+
本仓库根目录 `.npmrc` 已指定官方源 `https://registry.npmjs.org/`。若全局 `~/.npmrc` 配置了 `registry=https://registry.npmmirror.com/`,在本目录执行 `npm login` 会跳到 npmmirror,**无法登录 npm 官方**。请在本仓库目录使用:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
cd /path/to/node-red-contrib-symi-mesh
|
|
104
|
+
npm login --registry https://registry.npmjs.org
|
|
105
|
+
# 或依赖 ~/.npmrc 中已有的 //registry.npmjs.org/:_authToken 直接发布
|
|
106
|
+
npm publish
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
客户安装仍可使用任意镜像:`npm install node-red-contrib-symi-mesh`(镜像会同步 npm 官方包)。
|
|
110
|
+
|
|
98
111
|
安装后重启Node-RED:
|
|
99
112
|
```bash
|
|
100
113
|
node-red-restart
|
|
@@ -186,8 +199,8 @@ node-red
|
|
|
186
199
|
- **05 - RS485 桥接**:`examples/05-symi-rs485-bridge.json`(`symi-485-bridge` + `symi-485-config`)
|
|
187
200
|
- **06 - RS485 A↔B 同步**:`examples/06-symi-rs485-sync.json`(`symi-rs485-sync` + `symi-485-config`)
|
|
188
201
|
- **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
|
|
202
|
+
- **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 替换为您现场的设备(在映射下拉框重新选择即可)
|
|
203
|
+
- **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
204
|
- **10 - 云端同步**:`examples/10-symi-cloud-sync.json`(`symi-cloud-sync`)
|
|
192
205
|
|
|
193
206
|
### 6. 多网关配置(可选)
|
|
@@ -483,6 +496,7 @@ node-red
|
|
|
483
496
|
- **Mesh→KNX批量处理**:当Mesh同时改变同一设备的多个开关通道时,批量发送到不同的KNX地址,按通道顺序排序确保一致性
|
|
484
497
|
- 批量处理时间窗口:100ms内收到的同一设备的开关命令会自动合并
|
|
485
498
|
- **防死循环**:统一使用800ms防死循环时间窗口,增强型状态回传过滤逻辑
|
|
499
|
+
- **组地址合法性**:本节点将 KNX 组地址(GA)以标准字符串透传,合法性校验由上游 `node-red-contrib-knx-ultimate` 节点负责(其在接入总线时已过滤非法组地址)。本节点不重复实现地址解析,避免与 knxUltimate 的循环引用保护冲突。
|
|
486
500
|
|
|
487
501
|
**配置步骤**:
|
|
488
502
|
1. **安装KNX节点**
|
|
@@ -528,6 +542,15 @@ npm install node-red-contrib-knx-ultimate
|
|
|
528
542
|
- **开启自动状态校准**:勾选此项开启全局校准。
|
|
529
543
|
- **校准延迟(ms)**:建议设置在 2000ms - 5000ms 之间。设置太短可能在设备还在动作中就读取了旧状态,设置太长则响应不够及时。
|
|
530
544
|
|
|
545
|
+
**同步设置(KNX 桥接节点 →「同步设置」内联面板)**:
|
|
546
|
+
|
|
547
|
+
| 参数 | 默认值 | 说明 |
|
|
548
|
+
|------|--------|------|
|
|
549
|
+
| 场景遮蔽 (sceneVeilMs) | 2000ms | Mesh 场景触发后短时遮蔽,避免互斥场景同帧双发 |
|
|
550
|
+
| 反馈抑制 (meshFeedbackSuppressMs) | **3000ms** | KNX→Mesh 后 Mesh 中间帧反写 KNX 的抑制窗口;单路开关通常 3 秒内放行用户反馈,**不建议超过 5000ms**(现场 8000ms 会导致单路控制等待过久) |
|
|
551
|
+
| 场景结算 (sceneSettleMs) | 3000ms | KNX 场景批次展开结束后,0x32 刷新 + KNX Read 纠偏的等待时间 |
|
|
552
|
+
| 自动同步延迟 | 3000ms | AutoSync / DelayedSync 触发前的延迟 |
|
|
553
|
+
|
|
531
554
|
**防死循环机制**:
|
|
532
555
|
系统会自动识别校准产生的回复消息,仅更新内部状态,**绝不会**再次触发反向控制发送给KNX,请放心使用。
|
|
533
556
|
|
|
@@ -565,7 +588,7 @@ npm install node-red-contrib-knx-ultimate
|
|
|
565
588
|
|
|
566
589
|
Tab分隔,每行一个实体:
|
|
567
590
|
```
|
|
568
|
-
名称 类型 命令地址
|
|
591
|
+
名称 类型 命令地址 反馈组地址 扩展1 扩展2 扩展3 触发值(可选)
|
|
569
592
|
```
|
|
570
593
|
|
|
571
594
|
支持的类型和地址字段:
|
|
@@ -616,6 +639,42 @@ Tab分隔,每行一个实体:
|
|
|
616
639
|
- `2`:在 Mesh 当前状态基础上反转(对应导入模板动作 2=翻转)
|
|
617
640
|
- 留空:跟随 KNX 数值(默认)
|
|
618
641
|
|
|
642
|
+
**四键零火开关场景键(CH3/CH4)与 KNX 场景双向同步(v1.9.42)**:
|
|
643
|
+
|
|
644
|
+
适用于米家四键零火开关:**CH1/CH2 为负载继电器**,**CH3/CH4 为点动场景键**(按下自动复位,不上报 OFF)。需在 KNX 桥接节点完成以下配置:
|
|
645
|
+
|
|
646
|
+
1. **KNX 实体库** — 新增 2 条 `scene` 实体(可共用同一组地址 `0/0/20`):
|
|
647
|
+
```
|
|
648
|
+
全开场景 scene 0/0/20 1 1
|
|
649
|
+
全关场景 scene 0/0/20 2 1
|
|
650
|
+
```
|
|
651
|
+
- **场景号 (triggerValue)**:填 ETS/面板场景号(1=全开、2=全关),**配置几发几**,系统内部自动换算总线字节,**不要手动 +1**。
|
|
652
|
+
- **Mesh动作 (triggerAction)**:点动场景键填 `1`(按下=触发开/激活)。
|
|
653
|
+
|
|
654
|
+
2. **设备映射** — 同一 Mesh 四键开关建 4 行(CH1/CH2 负载 + CH3/CH4 场景):
|
|
655
|
+
|
|
656
|
+
| Mesh 设备 | 通道 | 场景ID | KNX 实体 |
|
|
657
|
+
|-----------|------|--------|----------|
|
|
658
|
+
| 四键开关 MAC | 1 | - | 执行器1 (switch) |
|
|
659
|
+
| 四键开关 MAC | 2 | - | 执行器2 (switch) |
|
|
660
|
+
| 四键开关 MAC | 3 | - | 全开场景 (scene) |
|
|
661
|
+
| 四键开关 MAC | 4 | - | 全关场景 (scene) |
|
|
662
|
+
|
|
663
|
+
3. **Flow 接线**:
|
|
664
|
+
```
|
|
665
|
+
[knxUltimate-in] → [symi-knx-bridge 输入] → [symi-knx-bridge KNX输出] → [knxUltimate-out]
|
|
666
|
+
[symi-gateway] ──(配置引用)──→ [symi-knx-bridge]
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
4. **行为说明(与日志关键字对照)**:
|
|
670
|
+
- **Mesh 按 CH3/CH4** → KNX 收到 DPT18 场景字节(日志 `[Mesh->KNX] 场景触发` + `scenenumber:1/2`)。
|
|
671
|
+
- **KNX 触发场景 1/2** → Mesh 仅翻转对应场景键,**CH1/CH2 负载状态保持**(日志 `[KNX->Mesh] 场景触发 ... 精确状态 0x..., 负载通道保持`)。
|
|
672
|
+
- 总线回显会被拦截(`跳过场景触发(总线回显)`),避免死循环。
|
|
673
|
+
|
|
674
|
+
5. **可选**:导入 `examples/08-symi-knx-bridge.json` 作为模板(已含 9 实体 / 9 映射及 `meshFeedbackSuppressMs=3000`)。
|
|
675
|
+
|
|
676
|
+
> **注意**:四键面板 CH3/CH4 走 KNX `scene` 实体映射 + 精确状态组包;与下方「按键X场景 / Mesh场景 / 0x34」是不同链路——后者用于 HA/MQTT/RS485 同步节点或映射通道选 `mesh_scene` 时触发 Mesh 内置场景。
|
|
677
|
+
|
|
619
678
|
### Symi RS485 Bridge 桥接节点
|
|
620
679
|
|
|
621
680
|
RS485通信桥接,支持Modbus协议透传与自定义指令映射。
|
|
@@ -682,115 +741,117 @@ RS485通信桥接,支持Modbus协议透传与自定义指令映射。
|
|
|
682
741
|
4. 配置对应的外部系统实体(KNX组地址、HA实体、MQTT设备等)
|
|
683
742
|
5. 部署后即可实现场景联动
|
|
684
743
|
|
|
685
|
-
###
|
|
744
|
+
### 按键场景触发(Mesh 内置场景 / 0x34)
|
|
686
745
|
|
|
687
|
-
|
|
746
|
+
在 **HA / MQTT / RS485 同步节点**(以及 KNX 桥接映射通道选 **`mesh_scene`** 时)可用「按键X场景」或「Mesh场景」通道。
|
|
688
747
|
|
|
689
748
|
**功能说明**:
|
|
690
|
-
-
|
|
691
|
-
- **场景ID
|
|
692
|
-
-
|
|
693
|
-
- 当Mesh开关按键被触发时,不仅能控制继电器,还能触发本地场景,满足设备上报状态的需求
|
|
749
|
+
- 选择后通过 `gateway.sendScene(sceneId)` 发送串口协议 **0x34** Mesh 内置场景控制帧,替代继电器直控
|
|
750
|
+
- **场景ID 默认 0(不触发)**;有效范围 **0-31**(0=全关、1=全开为内置默认)及 **64-95**(自定义)
|
|
751
|
+
- 与 **KNX DPT18 场景实体**相互独立:KNX 四键 CH3/CH4 请按上文「四键零火开关场景键」用 `scene` 实体 + 精确状态组包,不要与 0x34 混用
|
|
694
752
|
|
|
695
753
|
**使用方式**:
|
|
696
|
-
1. 在同步节点的设备选择中,选择Mesh开关设备
|
|
697
|
-
2.
|
|
698
|
-
3. 输入场景ID
|
|
754
|
+
1. 在同步节点的设备选择中,选择 Mesh 开关设备
|
|
755
|
+
2. 通道选择「按键X场景」或「Mesh场景」
|
|
756
|
+
3. 输入场景 ID(0-31 或 64-95,默认 0=不触发)
|
|
699
757
|
4. 配置对应的外部系统实体
|
|
700
|
-
5.
|
|
758
|
+
5. 部署后,按键触发时将执行 Mesh 内置场景
|
|
701
759
|
|
|
702
760
|
---
|
|
703
761
|
|
|
704
762
|
## 更新日志
|
|
705
763
|
|
|
706
|
-
### v1.9.
|
|
764
|
+
### v1.9.42 场景键双向同步修复:4 键零火开关 CH3/CH4 + KNX→Mesh 精确状态 (2026-08-24)
|
|
765
|
+
|
|
766
|
+
> 排查 mesh 4 键零火开关 CH3/CH4 在双向同步中表现异常的两条根因链:
|
|
767
|
+
>
|
|
768
|
+
> **Mesh→KNX 链**:CH3/CH4 点动按键时 mesh 上报 0x02 整包状态帧(含 CH1/CH2/CH3/CH4 全部位),且厂商老固件 `NODE_STATUS` 不携带 `msg_type=0x0D` 字段;按 v1.9.40 的 `isUserControl` 严格判定整包都被判为非用户控制 → bridge.js `deviceType="scene"` 分支 `if (!eventData.isUserControl) continue` 把整个场景触发判定拦住 → 按 CH3/CH4 KNX 端 0/0/20 永远收不到场景字节。**修复**:在 scene 分支加 4 键零火开关点动场景键精确放行(`isFourKeyMomentary`)—— `deviceType="scene"` 且 `meshChannel` 为数字 1-4 时直接放行(不依赖 isUserControl),其他场景键仍受 isUserControl 拦截网关回写。
|
|
769
|
+
>
|
|
770
|
+
> **KNX→Mesh 链**:CH3/CH4 触发 KNX 场景后,若用 `TYPE_ON_OFF` 整包模拟按键或 pulse 全关基态,1 字节同时定义按键 1-4 位,会把其它已真实打开的按键位写成关,重复触发全开场景时 CH1/CH2 被"关闭又打开"。**修复**:KNX 场景触发主路径改用 `buildSwitchState` **精确状态组包**——只翻转目标场景键(CH3/CH4),保留 CH1/CH2 负载通道当前状态,直接发 `TYPE_ON_OFF` 帧(日志关键字 `精确状态`);不再用 pulse 全关基态,也不再对四键场景键走 mesh 内置 0x34。队列中仍保留 `type="mesh_scene"` 分支供映射表 `meshChannel="mesh_scene"` 等显式配置场景 ID 时使用。
|
|
771
|
+
>
|
|
772
|
+
> 配套修复:(a) `isValidSceneId` 场景ID 范围 2-31 → 0-31(mesh 协议 0=全关 1=全开内置默认,2-31/64-95=自定义),与 `gateway.sendScene` 一致;(b) `isSceneChannel=true` 路径增加 `armSceneLoadSuppressForDevice(macNormalized, ...)` 给同 MAC 负载通道打 5s 抑制标记(与 scene 分支 L1482 对齐),避免点动 CH3/CH4 触发的整包副作用误反写 KNX;(c) Mesh→KNX 出站写帧统一带 `_internal: true`,输入 handler 早返回逻辑与 v1.9.40 设计一致;(d) 默认 `meshFeedbackSuppressMs` 保持 **3000ms**(v1.9.31 现场 8000ms 过长,单路开关控制需等 8 秒才放行反馈,3000ms 为合理上限)。验证:本机 Node-RED v1.9.42 重复按 CH3/CH4 场景键各 2 次 + 重复开/重复关各 1 次,KNX 端 0/0/20 收到 DPT18 场景字节(1=全开 / 2=全关),CH1/CH2 无误反写,无重复误触发,symi 节点 0 Error/Warning。
|
|
773
|
+
|
|
774
|
+
- **scene 分支加 4 键零火开关点动场景键精确放行**:`symi-knx-bridge.js` `deviceType="scene"` 分支在 `switchKey` 判定前增加 `isFourKeyMomentary` 条件——`deviceType="scene"` 且 `meshChannel` 为数字 1-4 时直接放行(mesh 物理按键即用户意图),其他场景键仍受 `if (!eventData.isUserControl) continue` 拦截网关回写/查询响应。现场 v1.9.31 同行为(gateway 内 `[按键场景]` 检测 `lastChangedButtons` 直接发场景,不依赖 isUserControl)。
|
|
775
|
+
|
|
776
|
+
- **KNX→Mesh 场景触发改用精确状态组包(主路径)**:KNX 场景字节匹配 `deviceType="scene"` 映射后,用 `protocolHandler.buildSwitchState` 构造仅翻转目标 CH3/CH4 的 `TYPE_ON_OFF` 帧,负载通道 CH1/CH2 保持当前缓存状态;日志 `[KNX->Mesh] 场景触发 ... (精确状态 0x..., 负载通道保持)`。替代 v1.9.40 的 pulse 全关基态与整包误动方案。映射表 `meshChannel="mesh_scene"` 且配置了 `sceneId` 时,队列消费仍可走 `gateway.sendScene`(0x34)备用路径。
|
|
777
|
+
|
|
778
|
+
- **`isValidSceneId` 场景ID 范围扩展**:bridge.js 场景ID 校验由 2-31/64-95 → 0-31/64-95(mesh 协议 MESH_SCENE_NO_ALL_OFF=0 / ALL_ON=1 内置默认),与 `gateway.sendScene` 范围校验一致;gateway.js `sendScene` 同步扩展。
|
|
779
|
+
|
|
780
|
+
- **`isSceneChannel` 路径加 `sceneLoadSuppress` 整包抑制**:bridge.js `mapping.deviceType="switch"` 且 `isSceneChannel=true`(meshChannel 以 `scene`/`mesh_scene` 开头)分支触发场景后调用 `node.armSceneLoadSuppressForDevice(macNormalized, ...)`,给同 MAC 负载通道打 5s 抑制标记,与 `deviceType="scene"` 分支 L1482 对齐;避免点动 CH3/CH4 触发的整包副作用误反写 KNX。
|
|
781
|
+
|
|
782
|
+
- **Mesh→KNX 出站 `_internal` 标记补全**:Mesh→KNX 写帧(含反馈重试)统一设置 `_internal: true`,与输入 handler `msg._internal === true` 早返回配对,避免 flow 回环时误触发 KNX→Mesh 控制逻辑。
|
|
783
|
+
|
|
784
|
+
- **默认反馈抑制 3000ms**:`meshFeedbackSuppressMs` 默认 3000ms(500–30000 可调);示例 `examples/08` 同步为 3000ms。相对 v1.9.31 现场 8000ms,单路开关控制反馈放行更快,仍保留批次/reconcile 兜底。
|
|
707
785
|
|
|
708
|
-
|
|
709
|
-
- **修复**:三合一设备(空调+新风+地暖)此前只建了空调(climate)一条映射。现正确拆分为 3 条映射(`climate`/`fresh_air`/`floor_heating`),并存指向同一 Mesh 设备,三者自动按上报字段分流同步到各自 KNX 地址。
|
|
710
|
-
- **修复**:4 键零火面板的按键 3/4 原设计为场景键(全开/全关),此前被误复用为普通开关映射导致冲突。已删除冲突的普通开关映射,恢复 2 按键 + 2 场景的正确结构。
|
|
711
|
-
- **验证**(本地融合网关实测):三合一 Mesh 主动上报时日志显示 `找到3个映射`,空调温度/模式→0/0/12、0/0/13,新风开关/风速→0/0/15、0/0/16,地暖开关/温度→0/0/17、0/0/18,全部正确;AutoSync 成功读回各状态地址。9 条映射全部生效。
|
|
786
|
+
- **不动行为**(用户业务约束):mesh 4 键零火开关**只支持开关模式**(米家限制,不能用无线开关/场景模式);CH3/CH4 仍是**点动按键**,按下自动复位不上报 OFF 帧(`symi-gateway.js` `点动按键自动复位不上报`),与现场 v1.9.31 同行为。设备映射中 CH3/CH4 的 **通道填 3/4**,**KNX 实体类型选 scene**(不要与下方 0x34「Mesh场景」通道混用)。
|
|
712
787
|
|
|
713
|
-
|
|
714
|
-
- **问题**:连续操作几次后某次点按无反应,需等 3 秒"锁"结束后才恢复。根因是防回环窗口按最坏情况设 3 秒,但设备执行回报实测仅 100-115ms(KNX→Mesh)、1-10ms(Mesh→KNX),窗口过度导致用户连续操作被误锁。
|
|
715
|
-
- **修改**(数据驱动收窄,`symi-knx-bridge.js`):
|
|
716
|
-
- 单设备 KNX→Mesh 防反写窗口:3s → **800ms**(覆盖回报+尾帧,满足实体 <1 秒响应)
|
|
717
|
-
- Mesh→KNX 命令回环窗口:2s → **600ms**(覆盖 1-10ms 回报)
|
|
718
|
-
- 用户操作宽限期(值反转判定):800ms → **300ms**
|
|
719
|
-
- **效果**:实体控制锁 <1 秒,场景后 0.8 秒内即可正常操作实体,无感知延迟;防回环仍由回报时间窗口 + 值语义 + SyncUtils 防死循环三重保障,不会死循环。
|
|
788
|
+
- **验证**:`npm test` 52 specs passing、`npm run lint` 0 errors;本机 Node-RED v1.9.42 启动 + 现场控制验证:日志 `[Mesh->KNX] 场景触发` / `[KNX->Mesh] 场景触发 ... 精确状态` / `SceneExecGuard` / `场景批次 ... 3000ms 结算` 均正常,DPT18 场景字节正确(1=全开 / 2=全关),CH1/CH2 无误反写,symi 节点 0 Error/Warning。
|
|
720
789
|
|
|
721
|
-
|
|
790
|
+
> **版本说明**:npm 无 v1.9.41 包;v1.9.40 中规划的 `_internal` 补全、四键场景精确状态等 hotfix 已在 **v1.9.42** 一并交付。
|
|
722
791
|
|
|
723
|
-
|
|
724
|
-
- **修复**:场景触发后一段时间内单独控制实体不生效的问题。三处协同优化:
|
|
725
|
-
- **移除全局 KNX 活动窗口拦截**:场景暴风期间每个总线 Write 都会持续刷新该窗口,导致场景结束后数秒内所有 Mesh→KNX 用户操作被误拦。防回环改由"命令回环窗口 + 单设备控制窗口"(均带值语义)精确承担。
|
|
726
|
-
- **命令回环窗口 6s→2s 并引入值语义**:窗口内值相同视为命令回执(拦),值反转且超过 800ms 宽限期视为用户快速反操作(放行)。场景把灯全开后,用户 0.8 秒起即可反向单独关灯,立即生效。
|
|
727
|
-
- **场景遮蔽(SceneVeil)默认 3000→2000ms**:Mesh 设备执行回报通常 1 秒内到齐,2 秒足够总线平息,契合"场景 1-2 秒可接受"的标准体验(节点属性 500-30000ms 可调)。
|
|
728
|
-
- **新增**:命令队列实时优先——场景批量命令积压(串口 50ms/帧逐一排空)时,用户单独操作的实体命令自动插队到队首,下一个调度周期立即执行,保证实体控制 <1 秒响应;同一设备的命令仍按 Last-Write-Wins 原地合并,不重复发送。
|
|
792
|
+
### v1.9.40 P0/P1 修复:首次对账 + 场景批次 reconcile + 测试覆盖 (2026-08-22)
|
|
729
793
|
|
|
730
|
-
|
|
731
|
-
- 总线涌入大量组地址时:仅匹配已映射的有效地址,未映射地址零开销忽略;有效命令进入串行队列按 Mesh 硬件节拍(50ms/帧)逐一处理。
|
|
732
|
-
- 同一开关设备的多个通道自动合并为一行协议发送(100ms 批量窗口);其他实体一行协议对应一个实体状态。
|
|
794
|
+
> v1.9.39 现场审查后三项修复 + 单元测试扩至 52 spec。v1.9.42 在此基础上完成 `_internal` 出站标记补全与四键场景精确状态组包。
|
|
733
795
|
|
|
734
|
-
|
|
796
|
+
- **【P0 #1】输入 handler 早返回**:由 topic 字符串匹配改为 `msg._internal === true`(v1.9.42 已补全 Mesh→KNX 写帧设置 `_internal`)。
|
|
797
|
+
- **【P0 #2】首次状态 KNX 对账**:首次 Mesh 状态仅缓存并标记 `pendingFirstReconcile`,待 KNX 反馈到达后轻量纠偏。
|
|
798
|
+
- **【P1 #3】场景批次 reconcile**:结算前不清空反馈缓存,0x32 刷新完成后再比对,避免批次期间误反写。
|
|
799
|
+
- **测试**:新增 `test/sync-knx-to-mesh_spec.js`、`test/handle-mesh-state_spec.js` 等,共 **52 passing**。
|
|
735
800
|
|
|
736
|
-
|
|
737
|
-
- **修复**:KNX 执行器从状态地址(statusAddr)回报状态时,被误判为"KNX 总线控制活动"刷新了全局 3 秒活动窗口,导致 Mesh→KNX 写入后用户 3 秒内的 Mesh 物理操作被静默拦截(表现为"开灯后再按关灯无反应")。现在只有控制地址(cmdAddr)的 GroupValue_Write 才刷新全局窗口。
|
|
738
|
-
- **修复**:三处防回环拦截点(全局 KNX 活动窗口、单设备控制窗口)原先静默跳过且无任何日志,现恢复 debug 级别诊断输出,排障时可开启 SYMI_LOG_LEVEL=debug 查看,生产环境默认静默。
|
|
801
|
+
### v1.9.33 反馈组地址透明化:实体面板统一字段 (2026-08-21)
|
|
739
802
|
|
|
740
|
-
|
|
741
|
-
- **修复**:KNX 桥接节点移除运行时统计面板(RuntimeStats),避免其每 5 秒覆盖"未配置网关/请添加映射/网关断开"等关键状态提示;网关节点保留该统计(显示连接状态、收发计数、运行时长)。
|
|
742
|
-
- **修复**:状态事件解析失败警告增加限流(同 key 默认 60 秒一条),避免 Mesh 网络噪音刷屏警告。
|
|
743
|
-
- **修复**:KNX 桥接"场景映射匹配"诊断日志降为 debug 级别,避免高频刷屏。
|
|
744
|
-
- **合规**:移除 package.json 中虚报覆盖率的占位测试脚本,改为诚实的测试占位。
|
|
745
|
-
- **清理**:删除临时调试脚本、npm 冗余版本副本、重复/一次性文档,项目根目录仅保留发布必要文件。
|
|
803
|
+
> KNX 实体「添加/编辑」面板第二个地址字段统一命名为「反馈组地址」(复杂类型括号内保留功能语义,如位置/温度/风速)。**反馈组地址仅在 KNX 实体库录入**(v1.9.39 起设备映射表不再展示反馈列,保持列表简洁)。
|
|
746
804
|
|
|
747
|
-
|
|
805
|
+
- **实体面板**:switch/light 等为独立反馈地址;cover/climate 等标签注明「同时承担反馈」。
|
|
806
|
+
- **实体列表「反馈」列**:展示各实体 feedback 组地址,便于核对。
|
|
807
|
+
- **导入模板**:Tab 分隔字段名统一为「反馈组地址」。
|
|
748
808
|
|
|
749
|
-
|
|
809
|
+
### v1.9.32 全节点编辑托盘默认宽度加宽 (2026-08-21)
|
|
750
810
|
|
|
751
|
-
|
|
752
|
-
-
|
|
753
|
-
- **命令回环窗口(v1.9.17)**:部分设备固件不在状态帧携带控制源标记,仅凭 isUserControl 会永久阻断 Mesh→KNX 同步。改用"命令回环窗口"识别:窗口内视为命令反馈回环跳过反写,窗口外视为本地按键/App 操作放行同步。
|
|
754
|
-
- **用户操作豁免(v1.9.17)**:补齐两处 isUserControl=true 豁免实现,用户物理操作不受 KNX 控制窗口阻挡,与注释声明保持一致。
|
|
755
|
-
- **状态反馈地址简化(v1.9.17)**:KNX 执行器状态回执仅更新缓存,不再触发立即校准,避免回执先于 Mesh 设备执行到位导致的同一命令重复下发。
|
|
811
|
+
- 全部 13 个节点编辑托盘默认约 **950px**(小屏自动收窄);修复 symi-knx-bridge jQuery `tray[0].getBoundingClientRect()` bug。
|
|
812
|
+
- 同步设置单行不换行;表单 flex 填满宽度;默认值与运行时常量一致(场景遮蔽 2000ms、反馈抑制 **3000ms**、场景结算 3000ms)。
|
|
756
813
|
|
|
757
|
-
|
|
758
|
-
- 场景控制 DPT 由 17.001 修正为 **18.001(Scene Control)**,与 KNX 标准及融合网关场景绑定完全对齐。
|
|
759
|
-
- 修复多个场景共享同一 KNX 组地址时的映射匹配:遍历该组地址下全部场景映射,按触发值精确命中。
|
|
760
|
-
- 场景遮蔽窗口(SceneVeil)默认 2000ms,开放节点属性配置(500-30000ms),阻止 KNX 场景触发后的总线回环。
|
|
814
|
+
### v1.9.31 代码审查 + 编辑器面板优化 (2026-08-20)
|
|
761
815
|
|
|
762
|
-
|
|
763
|
-
-
|
|
764
|
-
-
|
|
765
|
-
-
|
|
816
|
+
- **同步设置内联展开**:自动同步/场景遮蔽/反馈抑制/场景结算收纳到「同步设置」,主面板仅保留网关、实体库、映射。
|
|
817
|
+
- **移除孤儿模块** `lib/knx-bridge-guards.js`;485 断线不再重放过期队列。
|
|
818
|
+
- **温度钳制**:空调 16–30°C、地暖 18–32°C(Mesh 模组规格)。
|
|
819
|
+
- **Mesh 内置场景 (0x34)** 与 **KNX DPT18 场景**语义分离;KNX 四键场景键配置见上文「四键零火开关场景键」(v1.9.42 起 KNX→Mesh 主路径为精确状态组包)。
|
|
766
820
|
|
|
767
|
-
|
|
768
|
-
- 网关节点新增运行时统计(连接状态、收发计数、错误计数、重连次数、运行时长)。
|
|
769
|
-
- 用户物理操作输出 `[状态上报] 用户操作` 可见日志,控制回执/状态上报/场景事件帧输出 `[设备帧]` 日志,便于现场确认同步链路。
|
|
770
|
-
- 示例流程 08/09 内置 knxUltimate 配置与接线,导入后仅需改两处地址即可完成 Mesh↔KNX 双向同步。
|
|
821
|
+
### v1.9.29 - v1.9.39 迭代汇总 (2026-08-20 ~ 2026-08-21)
|
|
771
822
|
|
|
772
|
-
|
|
823
|
+
> 中间版本要点合并如下;**v1.9.41 未单独发包**,相关 hotfix 已并入 v1.9.42。
|
|
773
824
|
|
|
774
|
-
|
|
775
|
-
-
|
|
825
|
+
- **v1.9.39**:设备映射表移除「反馈地址」列;反馈组地址**只在 KNX 实体库**录入(`showEntityPanel`)。
|
|
826
|
+
- **v1.9.38**:回退实体列表中的反馈地址字面量展示;保留 v1.9.37 数据模型与 `lib/knx-entity-mapper.js`。
|
|
827
|
+
- **v1.9.37**:`statusExt1/2/3` 独立反馈地址;`translateKnxEntityAddresses` + `getKnxAddrFunction`;9 实体迁移兼容(statusExt 空则回退 statusAddr)。
|
|
828
|
+
- **v1.9.36**:编辑面板标签按设备类型注明真实用途(如 cover「位置地址(同时承担反馈)」)。
|
|
829
|
+
- **v1.9.35**:light_cct / cover 的 Mesh→KNX 路径补第 5 层拦截(`lastKnxToMeshTime` + `meshFeedbackSuppressMs` 默认 3000ms)。
|
|
830
|
+
- **v1.9.34**:examples/08 全开/全关同地址 `0/0/20`、场景号 1/2;与 v1.9.33 UI 命名同步。
|
|
831
|
+
- **v1.9.30**:场景/同步拦截点增加 `isUserControl` 前置判断——用户主动操作 5 秒内立刻放行。
|
|
832
|
+
- **v1.9.29**:新增 `lib/knx-scene.js`(ETS 1–64 ↔ 总线 byte 0–63);sceneEchoGuard / sceneExecGuard / sceneLoadSuppress;场景批次 + `sceneSettleMs`;首批 mocha 测试。**注**:v1.9.29 的 KNX→Mesh「场景脉冲」路径已被 **v1.9.42 精确状态组包**取代;Mesh 内置 0x34(`mesh_scene` 通道)仍保留。
|
|
776
833
|
|
|
777
|
-
|
|
778
|
-
- 修复 `symi-rs485-sync` 节点 `gateway` 字段未初始化导致 `registerSyncDevice` 逻辑永远不执行的问题。
|
|
834
|
+
### v1.9.20 - v1.9.23 (2026-08-17/18) KNX/Mesh 同步链路迭代汇总
|
|
779
835
|
|
|
780
|
-
|
|
836
|
+
> **注**:以下弱随 v1.9.23 发布。核心要点如下,详细改动见 Git 历史。
|
|
781
837
|
|
|
782
|
-
|
|
783
|
-
-
|
|
784
|
-
-
|
|
838
|
+
- **v1.9.23 (2026-08-18)**:
|
|
839
|
+
- **KNX 功能地址判定修复**:`getKnxAddrFunction` 判定顺序调整,功能地址(温度/模式/风速等)优先于 status,修复三合一设备温度/风速无法从 KNX 控制的问题。
|
|
840
|
+
- **KNX→Mesh 场景触发修复(DPT 18.001)**:场景号解析兼容 knxUltimate 三种入站形态(优先 `rawValue` 原始字节),修复 `parseInt` 得 NaN 导致场景匹配失效。
|
|
841
|
+
- **日志静默优化**:状态事件解析失败、设备列表短帧日志降为 debug(生产无噪声)。
|
|
842
|
+
- **示例预置**:examples/08(KNX 桥接 9 实体/9 映射)、examples/09(KNX↔HA 6 实体)预置模板。
|
|
843
|
+
- **v1.9.22 (2026-08-17)**:三合一设备 KNX 映射拆分为 climate/fresh_air/floor_heating 3 条映射;4 键面板按键 3/4 恢复为场景键;按实测回报时长收窄防回环窗口(实体控制锁 <1 秒)。
|
|
844
|
+
- **v1.9.21 (2026-08-17)**:移除全局 KNX 活动窗口拦截、命令回环窗口 6s→2s 并引入值语义(0.8s 起反向操作立即生效)、场景遮蔽默认 3000→2000ms、命令队列实时优先(用户实体命令插队)。
|
|
845
|
+
- **v1.9.20 (2026-08-17)**:修复 KNX 状态地址回报误刷新全局活动窗口导致用户 Mesh 操作被静默拦截;三处防回环拦截点恢复 debug 诊断日志;移除 KNX 桥接运行时统计面板避免覆盖关键状态提示。
|
|
785
846
|
|
|
786
|
-
|
|
787
|
-
- 增大 MQTT `connectTimeout`(握手阶段超时上限)以降低 DelayedSync/高并发下的握手误判导致 `connack timeout`。
|
|
788
|
-
- 对 `connack timeout` 采用“握手超时非致命”处理:改为黄色状态并触发重连兜底,避免它被当作致命错误影响节点长期稳定性。
|
|
847
|
+
### v1.9.0 - v1.9.19 历史迭代汇总
|
|
789
848
|
|
|
790
|
-
|
|
849
|
+
> 较早迭代详见 Git 历史。核心方向:KNX/Mesh 双向同步可靠性、单一事件源、防死循环/防回弹、协议兼容、稳定性与合规、安装兼容性修复等。要点如下:
|
|
791
850
|
|
|
792
|
-
- **v1.9.
|
|
793
|
-
- **v1.9.
|
|
794
|
-
- **v1.9.
|
|
795
|
-
- **v1.9.
|
|
796
|
-
- **v1.9.
|
|
851
|
+
- **v1.9.12 (2026-04-10)**:安装兼容性修复(必须升级)——降低 `engines` 至 node>=18 / npm>=8 / node-red>=3,修复 Node.js 20.x/Node-RED 3.x 下 `EBADENGINE` 安装失败;修复 `symi-rs485-sync` 节点 `gateway` 字段未初始化导致同步永不执行。
|
|
852
|
+
- **v1.9.11 (2026-03-30)**:关闭 KNX→Mesh 后的 Mesh→KNX 反写回(`knxControlTimestamps` 窗口内无条件阻止);MQTT 节点 `connack timeout` 握手超时非致命化并触发重连兜底。
|
|
853
|
+
- **v1.9.10 (2026-03-30)**:发布包安装可靠性(必须)——修复 lock 文件中误将自身 `.tgz` 写为 `file:` 本地依赖,避免客户侧 `tarball corrupted / ENOENT` 安装失败。
|
|
854
|
+
- **v1.9.9 (2026-03-28)**:KNX 桥同步可靠性(推荐 KNX 项目升级)——Mesh 写 KNX 对称主控、KNX 面板优先、状态反馈去重、场景遮蔽(SceneVeil)、默认关闭 LWW;DelayedSync 与 Mesh 查询回包、MQTT connack timeout 防崩、`autoSyncEnabled` 健壮解析。
|
|
855
|
+
- **v1.9.8 (2026-03-23)**:稳定性、协议兼容性与合规性增强——引擎升级(Node>=22/NPM>=10/Node-RED>=4)、高并发与抗丢包测试策略、双向防死循环击穿修复、修复依赖 CVE、eslint 通过。
|
|
856
|
+
- **v1.9.7 (2026-02-28)**:自动状态校准(AutoSync)与状态查询逻辑——自动同步状态、查询不改变总线状态、Read/Response/Write 严格区分、DelayedSync 读响应不误触 HA、误关灯防护、界面布局优化与日志分级。
|
|
857
|
+
- **v1.9.0 - v1.9.6**:防反向控制与回显保护(3 秒主控窗口、回显检测窗口)、双向批量处理与网关限流、窗帘/调光同步增强、反馈确认闭环、多协议联动映射修复、首次启动状态快照与设备发现增强、虚拟化与场景支持(虚拟场景实体、按键场景触发 0x34)、工程化改进(静默日志与配置/内存泄漏修复)。
|
|
@@ -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,25 @@
|
|
|
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/20\", \"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
|
+
],
|
|
97
|
+
"meshFeedbackSuppressMs": 3000,
|
|
98
|
+
"sceneSettleMs": 3000
|
|
86
99
|
},
|
|
87
100
|
{
|
|
88
101
|
"id": "dbg_knx_info",
|
|
@@ -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/lib/device-manager.js
CHANGED
|
@@ -88,20 +88,7 @@ class DeviceInfo {
|
|
|
88
88
|
// 53 80 06 03 E3 01 03 37 - 低风(03)
|
|
89
89
|
// 53 80 06 03 E3 01 04 30 - 自动(04)
|
|
90
90
|
if (this.deviceType === 10 && parameters.length === 0) {
|
|
91
|
-
const oldFanMode = this.state.fanMode;
|
|
92
91
|
this.state.fanMode = attrType;
|
|
93
|
-
if (oldFanMode !== this.state.fanMode && this.manager) {
|
|
94
|
-
this.manager.emit("device-state-changed", {
|
|
95
|
-
device: this,
|
|
96
|
-
state: { fanMode: this.state.fanMode, acFanSpeed: this.state.fanMode },
|
|
97
|
-
attrType: 0x1C,
|
|
98
|
-
parameters: [],
|
|
99
|
-
subOpcode: 0x06,
|
|
100
|
-
// updateFromEvent 无法获知控制源,这里默认视为用户触发(避免引用未定义变量导致崩溃)
|
|
101
|
-
isUserControl: true,
|
|
102
|
-
isSceneExecution: false
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
92
|
return; // 已处理,直接返回
|
|
106
93
|
}
|
|
107
94
|
// 非温控器设备,继续处理其他逻辑
|
|
@@ -160,20 +147,6 @@ class DeviceInfo {
|
|
|
160
147
|
this.state.curtainAction = "closing";
|
|
161
148
|
}
|
|
162
149
|
}
|
|
163
|
-
|
|
164
|
-
// 触发状态变化事件
|
|
165
|
-
if ((oldStatus !== this.state.curtainStatus || oldAction !== this.state.curtainAction) && this.manager) {
|
|
166
|
-
this.manager.emit("device-state-changed", {
|
|
167
|
-
device: this,
|
|
168
|
-
state: { curtainStatus: this.state.curtainStatus, curtainAction: this.state.curtainAction },
|
|
169
|
-
attrType: attrType,
|
|
170
|
-
parameters: parameters,
|
|
171
|
-
subOpcode: 0x06,
|
|
172
|
-
// updateFromEvent 无法获知控制源,这里默认视为用户触发(避免引用未定义变量导致崩溃)
|
|
173
|
-
isUserControl: true,
|
|
174
|
-
isSceneExecution: false
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
150
|
}
|
|
178
151
|
break;
|
|
179
152
|
case 0x06:
|
|
@@ -241,20 +214,7 @@ class DeviceInfo {
|
|
|
241
214
|
case 0x1C:
|
|
242
215
|
// 风速设置(温控器和三合一空调都使用)
|
|
243
216
|
if (parameters.length > 0) {
|
|
244
|
-
const oldFanMode = this.state.fanMode;
|
|
245
217
|
this.state.fanMode = parameters[0];
|
|
246
|
-
// 触发状态变化事件(通过DeviceManager)
|
|
247
|
-
if (oldFanMode !== this.state.fanMode && this.manager) {
|
|
248
|
-
this.manager.emit("device-state-changed", {
|
|
249
|
-
device: this,
|
|
250
|
-
state: { fanMode: this.state.fanMode, acFanSpeed: this.state.fanMode },
|
|
251
|
-
attrType: attrType,
|
|
252
|
-
parameters: [],
|
|
253
|
-
subOpcode: 0x06,
|
|
254
|
-
isUserControl: true,
|
|
255
|
-
isSceneExecution: false
|
|
256
|
-
});
|
|
257
|
-
}
|
|
258
218
|
}
|
|
259
219
|
break;
|
|
260
220
|
case 0x1D:
|
|
@@ -336,7 +296,7 @@ class DeviceInfo {
|
|
|
336
296
|
}
|
|
337
297
|
break;
|
|
338
298
|
case 0x6C:
|
|
339
|
-
// 地暖温度 (1字节,
|
|
299
|
+
// 地暖温度 (1字节,15-35°C, 空参数=查询无结果)
|
|
340
300
|
if (this.isThreeInOne && parameters.length > 0) {
|
|
341
301
|
this.state.floorHeatingTemp = parameters[0];
|
|
342
302
|
this.state.floorHeatingSwitch = true;
|
|
@@ -425,8 +385,8 @@ class DeviceInfo {
|
|
|
425
385
|
const floorHeatingEn = (byte5 & 0x01) !== 0;
|
|
426
386
|
this.state.floorHeatingSwitch = floorHeatingOn;
|
|
427
387
|
|
|
428
|
-
// byte6: 地暖温度 (
|
|
429
|
-
if (byte6 >=
|
|
388
|
+
// byte6: 地暖温度 (15-35°C)
|
|
389
|
+
if (byte6 >= 15 && byte6 <= 35) {
|
|
430
390
|
this.state.floorHeatingTemp = byte6;
|
|
431
391
|
}
|
|
432
392
|
|
|
@@ -464,12 +424,8 @@ class DeviceInfo {
|
|
|
464
424
|
handleSwitchState(parameters, attrType) {
|
|
465
425
|
if (parameters.length === 0) return;
|
|
466
426
|
|
|
467
|
-
// 解析控制源 (0x0D)
|
|
468
|
-
// 默认为true(用户控制),只有明确检测到源为Mesh(2)时才设为false
|
|
469
|
-
let isUserControl = true;
|
|
470
|
-
let stateBytes = 1;
|
|
471
|
-
|
|
472
427
|
// 确定状态字节数
|
|
428
|
+
let stateBytes = 1;
|
|
473
429
|
if (this.channels <= 4) {
|
|
474
430
|
// 1-4路: 0x02通常1字节,0x45通常2字节
|
|
475
431
|
if (attrType === 0x45) stateBytes = 2;
|
|
@@ -479,20 +435,6 @@ class DeviceInfo {
|
|
|
479
435
|
stateBytes = 2;
|
|
480
436
|
}
|
|
481
437
|
|
|
482
|
-
// 检查是否有控制源字段 (0x0D)
|
|
483
|
-
// 格式: [State...] [AttrType] [Value]
|
|
484
|
-
// 例如: 01 0D 01 (State=1, Source=Local)
|
|
485
|
-
if (parameters.length >= stateBytes + 2) {
|
|
486
|
-
if (parameters[stateBytes] === 0x0D) {
|
|
487
|
-
const source = parameters[stateBytes + 1];
|
|
488
|
-
// 0:未知 1:本地 2:Mesh 3:双控
|
|
489
|
-
// Mesh(2)表示由网关或其他Mesh设备控制,非本地物理按键
|
|
490
|
-
if (source === 2) {
|
|
491
|
-
isUserControl = false;
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
|
|
496
438
|
// 严格按协议 V1.0 3.5.1.2 TYPE_ON_OFF:每2位表示1路,b01=关 b10=开,低2位为第1路
|
|
497
439
|
// 1-4路:1字节;6-8路:2字节小端序。解析后 switch_N 为 true=开 false=关
|
|
498
440
|
|
|
@@ -511,20 +453,7 @@ class DeviceInfo {
|
|
|
511
453
|
// 这里只处理开关控制,风速控制由 0x1C 消息处理
|
|
512
454
|
if (this.deviceType === 10) {
|
|
513
455
|
// 温控器开关:0x01=关, 0x02=开
|
|
514
|
-
const oldSwitch = this.state.switch;
|
|
515
456
|
this.state.switch = value === 0x02;
|
|
516
|
-
// 触发开关状态变化事件
|
|
517
|
-
if (oldSwitch !== this.state.switch && this.manager) {
|
|
518
|
-
this.manager.emit("device-state-changed", {
|
|
519
|
-
device: this,
|
|
520
|
-
state: { switch: this.state.switch, acSwitch: this.state.switch },
|
|
521
|
-
attrType: 0x02,
|
|
522
|
-
parameters: [],
|
|
523
|
-
subOpcode: 0x06,
|
|
524
|
-
isUserControl: isUserControl,
|
|
525
|
-
isSceneExecution: false
|
|
526
|
-
});
|
|
527
|
-
}
|
|
528
457
|
} else {
|
|
529
458
|
this.state.switch = value === 0x02;
|
|
530
459
|
}
|
|
@@ -591,27 +520,6 @@ class DeviceInfo {
|
|
|
591
520
|
// 存储变化的按键信息,供外部使用
|
|
592
521
|
this.lastChangedButtons = changedButtons;
|
|
593
522
|
|
|
594
|
-
// 触发状态变化事件(多路开关)
|
|
595
|
-
// 注意:这个事件会被symi-gateway.js转发给symi-485-bridge.js
|
|
596
|
-
if (changedButtons.length > 0 && this.manager) {
|
|
597
|
-
const changedState = {};
|
|
598
|
-
for (const btn of changedButtons) {
|
|
599
|
-
changedState[`switch_${btn.button}`] = btn.newState;
|
|
600
|
-
}
|
|
601
|
-
// 使用与symi-gateway.js相同的事件格式
|
|
602
|
-
// 【修复】使用计算好的isUserControl值,而不是硬编码为true
|
|
603
|
-
// 这样6键按键等设备可以正确识别控制源(本地按键 vs Mesh网络控制)
|
|
604
|
-
this.manager.emit("device-state-changed", {
|
|
605
|
-
device: this,
|
|
606
|
-
state: changedState,
|
|
607
|
-
attrType: 0x02,
|
|
608
|
-
parameters: [],
|
|
609
|
-
subOpcode: 0x06,
|
|
610
|
-
isUserControl: isUserControl,
|
|
611
|
-
isSceneExecution: false
|
|
612
|
-
});
|
|
613
|
-
}
|
|
614
|
-
|
|
615
523
|
return changedButtons;
|
|
616
524
|
}
|
|
617
525
|
|