Article
Home Assistant 自动化避坑指南:解决米家设备与小米中枢虚拟事件的误触发问题
详细分析米家蓝牙无线开关和小米中枢网关虚拟事件在 Home Assistant 重启时的误触发问题,提供三种解决方案包括状态过滤、条件判断和事件置空等方法
前言
在使用 Home Assistant 搭建智能家居自动化的过程中,我遇到了两个非常恼人的问题:米家蓝牙无线开关的误触发 和 小米中枢网关虚拟事件的回放触发。这两个问题有一个共同的症状——每当 Home Assistant 重启、网络波动、或者集成重新加载时,自动化就会被无缘无故地触发一次。
经过一番摸索和社区求助,我终于找到了问题的根源和解决方案。本文把这两个坑的来龙去脉整理出来,希望能帮到遇到同样问题的朋友。
坑一:米家蓝牙无线开关,HA 重启后自动触发
问题描述
我有一个米家蓝牙无线开关,通过 Xiaomi Home 官方集成接入 Home Assistant。在自动化里,我用”状态触发器”监听开关实体的”动作”属性变化,当检测到”双击”时执行某个场景。
配置大致如下:
trigger:
- platform: state
entity_id: event.mijia_ble_switch
attribute: action
to: double
平时用着很正常,但每次 Home Assistant 重启、或者网络短暂断连后恢复时,这个自动化就会自动执行一次,仿佛有人真的按了一下开关。
问题根源
这个问题的本质在于 Home Assistant 状态机的初始化过程。
当 HA 重启或集成重载时,所有实体的状态都会经历一个从 unknown → unavailable → 实际状态 的恢复过程。对于米家蓝牙开关这类设备,它的 action 属性在恢复时会被系统”重放”最后一次上报的值(比如 double)。
而”状态触发器”看到的是:
action 属性从 unavailable 变成了 double。
这个变化完全满足触发条件 to: double,于是自动化就被执行了。
解决方案
这个问题有三种解决思路,按推荐程度从高到低排列:
方案一:在触发器里排除无效状态(推荐,一劳永逸)
在 YAML 模式下编辑自动化,为触发器添加 not_from 和 not_to 选项,显式忽略 unknown 和 unavailable 状态:
trigger:
- platform: state
entity_id: event.mijia_ble_switch
attribute: action
to: double
not_from:
- unknown
- unavailable
not_to:
- unknown
- unavailable
原理:
只有 action 属性在两个有效状态之间变化时才会触发,系统初始化时的”虚假变化”被直接拦截在触发器层面。
优点:配置简单,一次设置永久有效,不影响自动化其他逻辑。
方案二:在条件中过滤无效状态(备选,灵活度高)
如果你不想修改触发器,也可以在自动化的”且如果”条件中添加一个模板条件,判断触发前的状态是否有效:
- 在自动化编辑界面,点击 “且如果” 添加条件
- 选择 “模板” 条件
- 输入以下内容:
{{ trigger.from_state.state not in ['unknown', 'unavailable'] }}
完整的 YAML 如下:
trigger:
- platform: state
entity_id: event.mijia_ble_switch
attribute: action
to: double
condition:
- condition: template
value_template: "{{ trigger.from_state.state not in ['unknown', 'unavailable'] }}"
action:
# 你的动作
原理:
利用 trigger.from_state 获取触发前的状态。如果触发前的状态是 unknown 或 unavailable,则判定为系统初始化导致的假触发,条件不通过,自动化不会继续执行。
优点:
- 触发器保持原样,只在条件层加一道防线
- 适合触发器配置复杂、不方便修改的场景
- 可以和其他条件组合使用,逻辑清晰
缺点:
- 比方案一多一层条件判断,稍显冗余
- 触发器的触发次数依然会统计(虽然动作不执行)
方案三:改用事件监听(更彻底,从根源规避)
如果状态过滤依然不稳定,可以改用”事件监听”的方式来触发自动化。因为 HA 重启时不会重放历史事件,只会恢复实体的状态。
- 进入 开发者工具 → 事件
- 监听
xiaomi_ble_event(或对应的集成事件类型) - 按下开关,观察事件数据中的
action值 - 新建自动化,触发类型选择 事件,填入对应的事件类型和条件
trigger:
- platform: event
event_type: xiaomi_ble_event
event_data:
action: double
原理:
事件是”一次性”的,触发后即消失。HA 重启时不会重新发送历史事件,因此从根本上规避了状态恢复带来的误判。
优点:最彻底,完全不受状态机影响
缺点:需要额外学习事件类型,配置稍繁琐
坑二:小米中枢网关虚拟事件,HA 重启时回放触发
问题描述
我在小米中枢网关里创建了一个虚拟事件(比如 haier_hot_water_turn_on),用于触发热水器循环。在 HA 里用状态触发器监听这个虚拟事件实体:
trigger:
- platform: state
entity_id: event.xiaomi_cn_1148896473_hub1_virtual_event_e_4_1
attribute: 事件名称
to: haier_hot_water_turn_on
日常使用正常,但每次 HA 重启后,这个自动化同样会被执行一次。
问题根源
这是 Xiaomi Home 集成的一个已知 Bug。当 Home Assistant 重新连接小米中枢网关时,网关会把最后一次发送过的虚拟事件重新上报给 HA。HA 的实体状态随之更新,状态触发器检测到变化,自动化被触发。
这个问题的特殊性在于:回放的事件是一个完全有效的事件内容(比如 haier_hot_water_turn_on),而不是 unknown 或 unavailable。所以坑一中的”排除无效状态”方法对它无效——因为回放的状态本身就是”有效”的。
解决方案
方案一:主动”置空”虚拟事件(治本之法,强烈推荐)
这个方案的核心思路是:在每次有效事件触发并执行完毕后,立即用一个无害的空事件把虚拟事件的状态覆盖掉。这样下次 HA 重启时,网关回放的就是这个无害的空事件,不会触发你的业务自动化。
具体做法是在自动化的末尾添加以下步骤:
# 在原有动作全部执行完毕后
- delay: "00:00:05" # 延时几秒,确保核心逻辑执行完毕
- action: notify.send_message
metadata: {}
data:
message: "[\"nop\"]" # 发送一个无害的空事件
target:
entity_id: notify.xiaomi_cn_1148896473_hub1_virtual_event_a_4_1 # 中枢网关的发送虚拟事件实体
完整示例(以热水器循环自动化为例):
alias: 调整海尔热水器温度并执行零冷水循环
description: 触发后直接执行零冷水单次循环
triggers:
- trigger: state
entity_id: event.xiaomi_cn_1148896473_hub1_virtual_event_e_4_1
attribute: 事件名称
to: haier_hot_water_turn_on
actions:
# ===== 你的业务逻辑 =====
- action: select.select_option
target:
entity_id: select.145790acf35a_zerocoldwaterstatus
data:
option: 单次循环
- delay: "00:00:35"
- action: notify.send_message
target:
entity_id: notify.xiaomi_cn_566538160_lx06_play_text_a_5_1
data:
message: 热水循环完毕
# ===== 以下为置空逻辑 =====
- delay: "00:00:05" # 等 5 秒,确保通知说完
- action: notify.send_message
metadata: {}
data:
message: "[\"nop\"]"
target:
entity_id: notify.xiaomi_cn_1148896473_hub1_virtual_event_a_4_1
注意事项:
message必须是 JSON 数组格式,["nop"]是一个无害的占位符,你也可以换成["_"]或["clear"]notify实体需要在 Xiaomi Home 集成中提前启用(在中枢网关设备页找到”发送虚拟事件”实体)- 延时时间建议不少于 3 秒,避免覆盖动作太早影响其他依赖此事件的自动化
原理:
每次有效事件执行完后,用空事件覆盖虚拟事件的”当前值”。HA 重启时,网关回放的是 ["nop"],状态变化是 unavailable → ["nop"],这个值和你的业务触发值(haier_hot_water_turn_on)不匹配,自动化自然不会触发。
方案二:校验”事件新鲜度”(时间戳过滤,备选)
如果不方便添加置空逻辑(比如有多个自动化共用同一个虚拟事件),也可以在自动化条件中加入时间校验,只有”刚刚发生”的事件才被认为是有效的:
condition:
- condition: template
value_template: >
{{ (as_timestamp(now()) - as_timestamp(state_attr('event.xiaomi_cn_1148896473_hub1_virtual_event_e_4_1', 'last_triggered'))) | float < 5 }}
原理:
- 人为触发的事件,时间戳和当前时间相差通常不到 1 秒
- HA 重启回放的事件,时间戳是重启前的历史时间,差值可能长达几分钟甚至几小时
- 通过判断时间差小于 5 秒,可以精准过滤掉系统回放
优点:不需要修改自动化末尾,适合多个自动化共享虚拟事件的场景
缺点:依赖实体正确维护 last_triggered 属性,配置稍复杂
对比总结
| 问题类型 | 根本原因 | 推荐方案 | 适用场景 |
|---|---|---|---|
| 蓝牙开关误触发 | 状态恢复时从 unavailable 变为有效值 | 触发器添加 not_from/not_to 过滤 | 所有通过状态触发的设备 |
| 蓝牙开关误触发 | 同上 | 条件中过滤 trigger.from_state | 触发器不便修改时 |
| 中枢虚拟事件回放 | 网关重连时重放最后一次事件 | 事件执行后主动置空 | 小米中枢网关虚拟事件专用 |
| 中枢虚拟事件回放 | 同上 | 时间戳新鲜度校验 | 多个自动化共用虚拟事件时 |
| 方案 | 优点 | 缺点 |
|---|---|---|
| 触发器过滤无效状态 | 配置简单,一劳永逸 | 对回放有效事件的情况无效 |
| 条件过滤无效状态 | 触发器保持原样,灵活 | 多一层判断,稍显冗余 |
| 事件监听替代状态 | 从根源规避,不受状态恢复影响 | 需要额外学习事件类型 |
| 主动置空虚拟事件 | 彻底解决问题,无需额外条件 | 需要增加自动化步骤 |
| 时间戳新鲜度校验 | 逻辑严谨,精准区分 | 配置稍复杂,依赖实体属性 |
结语
Home Assistant 的自动化和状态系统虽然强大,但也暗藏了不少”玄学”问题。这两个关于误触发的坑,本质上都是对 HA 状态机行为理解不够深入导致的。
简单总结一下核心思路:
- 对于普通设备的误触发:在触发器或条件中排除
unknown/unavailable状态即可解决 - 对于小米中枢虚拟事件:这个问题比较特殊,推荐用”主动置空”的方式从根源解决
希望这篇文章能帮你少走一些弯路。如果你也遇到了类似的问题,或者有更好的解决方案,欢迎在评论区交流讨论。
参考链接: