返回文章列表

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 重启或集成重载时,所有实体的状态都会经历一个从 unknownunavailable实际状态 的恢复过程。对于米家蓝牙开关这类设备,它的 action 属性在恢复时会被系统”重放”最后一次上报的值(比如 double)。

而”状态触发器”看到的是:
action 属性从 unavailable 变成了 double
这个变化完全满足触发条件 to: double,于是自动化就被执行了。

解决方案

这个问题有三种解决思路,按推荐程度从高到低排列:


方案一:在触发器里排除无效状态(推荐,一劳永逸)

在 YAML 模式下编辑自动化,为触发器添加 not_fromnot_to 选项,显式忽略 unknownunavailable 状态:

trigger:
  - platform: state
    entity_id: event.mijia_ble_switch
    attribute: action
    to: double
    not_from:
      - unknown
      - unavailable
    not_to:
      - unknown
      - unavailable

原理
只有 action 属性在两个有效状态之间变化时才会触发,系统初始化时的”虚假变化”被直接拦截在触发器层面。

优点:配置简单,一次设置永久有效,不影响自动化其他逻辑。


方案二:在条件中过滤无效状态(备选,灵活度高)

如果你不想修改触发器,也可以在自动化的”且如果”条件中添加一个模板条件,判断触发前的状态是否有效:

  1. 在自动化编辑界面,点击 “且如果” 添加条件
  2. 选择 “模板” 条件
  3. 输入以下内容:
{{ 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 获取触发前的状态。如果触发前的状态是 unknownunavailable,则判定为系统初始化导致的假触发,条件不通过,自动化不会继续执行。

优点

  • 触发器保持原样,只在条件层加一道防线
  • 适合触发器配置复杂、不方便修改的场景
  • 可以和其他条件组合使用,逻辑清晰

缺点

  • 比方案一多一层条件判断,稍显冗余
  • 触发器的触发次数依然会统计(虽然动作不执行)

方案三:改用事件监听(更彻底,从根源规避)

如果状态过滤依然不稳定,可以改用”事件监听”的方式来触发自动化。因为 HA 重启时不会重放历史事件,只会恢复实体的状态。

  1. 进入 开发者工具事件
  2. 监听 xiaomi_ble_event(或对应的集成事件类型)
  3. 按下开关,观察事件数据中的 action
  4. 新建自动化,触发类型选择 事件,填入对应的事件类型和条件
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),而不是 unknownunavailable。所以坑一中的”排除无效状态”方法对它无效——因为回放的状态本身就是”有效”的。

解决方案


方案一:主动”置空”虚拟事件(治本之法,强烈推荐)

这个方案的核心思路是:在每次有效事件触发并执行完毕后,立即用一个无害的空事件把虚拟事件的状态覆盖掉。这样下次 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 状态机行为理解不够深入导致的。

简单总结一下核心思路:

  1. 对于普通设备的误触发:在触发器或条件中排除 unknown/unavailable 状态即可解决
  2. 对于小米中枢虚拟事件:这个问题比较特殊,推荐用”主动置空”的方式从根源解决

希望这篇文章能帮你少走一些弯路。如果你也遇到了类似的问题,或者有更好的解决方案,欢迎在评论区交流讨论。


参考链接