Skip to content

Home Assistant MQTT 自动发现实战:一条消息就能上线一个设备

如果你手里有十几块 ESP32、几个智能插座、一堆自己焊的传感器,最省心的接法不是一个个在 configuration.yaml 里手敲 mqtt 配置,而是让设备"自己报到"。Home Assistant 的 MQTT 自动发现(MQTT Discovery)就是干这个的:设备连上 MQTT broker 后,往一个约定的主题发一条配置消息,HA 自己就把灯光、传感器、开关这些实体建好,连重启都不用。

这篇文章把自动发现的机制讲透,给你一套能直接跑的消息结构和避坑清单,让"加一台设备 = 发一条消息"成为现实。

1. 自动发现到底省了什么

先说清楚它解决什么痛点。没有自动发现时,每接一个设备你都要:

  • 在 YAML 里写一段 mqtt: 配置,指定实体类型、状态主题、命令主题、唯一 ID。
  • 改完重启 Home Assistant 才能生效。
  • 设备改名、换功能、下架,又得回去改 YAML。

设备一多,这份 YAML 就成了谁都不敢动的"祖传配置"。自动发现把"实体定义"变成一条 MQTT 消息:设备自己把定义发到 discovery 主题,HA 订阅前缀后自动建实体。增删改全靠消息,YAML 几乎不动,重启也免了。这是大规模物联网项目唯一可维护的路子。

2. discovery 主题结构与前缀

自动发现的核心是"约定主题"。默认 discovery 前缀是 homeassistant,可在 HA 的 MQTT 集成里改。一条实体配置消息必须发到:

<前缀>/<组件>/<对象id>/config

例如一个厨房灯:

homeassistant/light/kitchen_led/config
  • 前缀:HA 订阅 homeassistant/#,所有以它开头的 config 主题都会被扫描。
  • 组件:light、sensor、switch、binary_sensor、climate 等,决定建什么类型实体。
  • 对象 id:这台设备该实体的唯一标识,HA 用它拼出实体 ID。

注意:前缀、组件、对象 id 三层都不能错,否则 HA 收得到消息却建不出实体。

3. 一条完整的配置消息长什么样

以灯光为例,向 homeassistant/light/kitchen_led/config 发一条 JSON(记得 retained=true):

{
  "name": "厨房灯",
  "unique_id": "kitchen_led_001",
  "object_id": "kitchen_led",
  "state_topic": "home/kitchen/led/state",
  "command_topic": "home/kitchen/led/set",
  "payload_on": "ON",
  "payload_off": "OFF",
  "availability": {
    "topic": "homeassistant/light/kitchen_led/availability"
  },
  "device": {
    "identifiers": ["esp32_kitchen_001"],
    "name": "厨房 ESP32",
    "manufacturer": "DIY",
    "model": "ESP32"
  }
}

关键点:unique_id 全局唯一;state_topic/command_topic 是设备实际收发状态的业务主题(和 discovery 主题无关);device 块把同设备的多个实体聚成一个"设备"展示;availability 告诉 HA 去哪看在线状态。

4. 状态、命令与保留消息

三个容易踩的点:

  • 状态主题(state_topic):设备把自己的实时状态(ON/OFF、温度值)发到这里。必须 retained,否则 HA 重启后读不到最后状态,会显示未知。
  • 命令主题(command_topic):HA 下发控制指令(如"开灯")走这里,设备订阅它执行动作。
  • 保留标志(retained):config、state、availability 三类消息都要设 retained=true。保留消息让 broker 替你"记住"最后一条,新订阅者(包括刚重启的 HA)立刻拿到,不会出现"设备在线但状态空白"。

很多"设备时有时无"的怪问题,根因就是漏了 retained。

5. 出生与遗愿:让设备会"报备"和"道别"

设备不能只报上线不报下线,否则断电了 HA 还显示"开"。标准做法是:

  • 出生消息(Birth):设备连上 broker 后,立刻向自己的 availability 主题发一条 online(retained)。
  • 遗愿消息(LWT / Last Will):连接时告诉 broker,如果我不告而别,请代我向 availability 主题发 offline(retained)。

于是 HA 监听 availability 主题,收到 offline 自动把实体标离线。这样即便设备突然断电,HA 也能在几秒内把状态翻成"离线",不会一直显示过期的"开"。

6. 一个可跑的端到端示例

假设厨房 ESP32 要暴露一盏灯 + 一个温度传感器,设备连上 MQTT 后做三件事:

  1. 发 discovery config(灯光 + 传感器两条,retained)。
  2. 发 birth:homeassistant/light/kitchen_led/availability → online(retained)。
  3. 订阅 home/kitchen/led/set 收命令;定时向 home/kitchen/temp/state 发温度(retained)。

HA 这边不用写任何 YAML,刷新实体列表就能看到"厨房灯"和"厨房温度"两个实体,且断电后自动离线。新增第二台设备,只需换对象 id 和主题前缀重复这个过程,零配置侵入。

7. 常见坑与排查清单

  • 实体不出现:检查前缀对不对、组件名拼错没(比如 lights 应为 light)、对象 id 是否含非法字符(用下划线别用空格)。
  • 删不掉:旧保留消息还在 broker 上。向那个 config topic 发一条空 payload 并 retained,覆盖掉旧消息即可让 HA 删除实体。
  • 状态串台:两个设备 unique_id 或对象 id 撞了。全局唯一,建议 <设备>_<功能>。
  • 显示离线:availability 主题没收到 online,或 birth/LWT 没设 retained。
  • 改了不生效:config 消息没 retained,HA 重启后丢失;或你改了消息但 broker 上还是旧保留,先发空消息清掉再发新的。

8. 与 ESPHome / 其他系统的配合

ESPHome 的 mqtt 组件可以自动发 discovery 消息,你几乎不用手写 JSON,指定 discovery_prefix 和实体即可。自己写的固件(Arduino/MicroPython)则手动按上面的结构发。无论哪种,只要遵循同一套前缀和主题约定,HA 都能统一发现。建议所有 DIY 设备统一一个 discovery 前缀和命名规范,后期排查和迁移都轻松。

9. 上线后的巡检与防退化纪律

自动发现是"靠消息自治"的机制,broker 上的保留消息就是"真相来源",它也会被悄悄搞乱:一次设备固件改了对象 id 却没清旧消息、一次 broker 被清空导致所有保留丢失、一次手滑把两设备配成同 ID。建立纪律:

  • 每周:在 HA 实体列表抽查 2-3 个自动发现设备,确认实体在、状态对、离线判定正常。
  • 每月:用 MQTT 客户端订阅 <前缀>/#,核对保留消息数量与预期一致,发现孤儿(设备已下线但消息还在)就清掉。
  • 每次固件变更前:先向旧 config topic 发空消息清保留,再发新消息,避免新旧叠加。
  • broker 重建/迁移:保留消息会随 broker 清空而丢失,迁移后必须让所有设备重新上报 discovery;把这步写进迁移 runbook,别等用户报"设备没了"才发现。
  • 红线:禁止"为了图快在 YAML 里硬编码和自动发现重复的同 ID 实体"。两套来源抢同一个实体 ID,状态会互相覆盖、极难排查。要么全自动发现,要么全 YAML,别混。

FAQ

MQTT 自动发现和手动在 configuration.yaml 里写 mqtt 有什么区别? 手动方式要求你每次改设备都去编辑 YAML、重启 HA 才能生效,设备多了极难维护。自动发现把"实体定义"本身作为一条 MQTT 消息发到 discovery 主题,HA 收到就自动建好实体,设备上下线、增删实体都靠发消息完成,无需动 YAML、无需重启。代价是你要理解消息结构和保留标志,但它才是大规模物联网项目可维护的做法。 discovery 前缀和实体 topic 是什么关系,能不能改? discovery 前缀默认是 homeassistant,HA 在"MQTT 集成"里配置;实体消息必须发到 <前缀>/<组件>/<对象id>/config,比如 homeassistant/light/kitchen_led/config。组件名(light/sensor/switch…)决定 HA 建什么类型实体,<对象id> 是这台设备该实体的唯一标识。前缀可以改(比如改成 hass),改了之后所有设备都要用新前缀发消息,且老的保留消息还挂在旧前缀下,要清掉避免重复。 为什么我的自动发现设备一会儿在线一会儿离线,或者删不掉? 两个典型原因。第一,没用保留消息(retained):消息没打保留标志,HA 订阅时如果设备已经发过又断开了,就收不到,表现为时有时无;正确做法是 config 和状态消息都设 retained=true,让 broker 替你"记住"。第二,删不掉是因为保留消息一直躺在 broker 上,HA 重启又读到了;要彻底删除,得向那个 config topic 发一条空消息(payload 为空)并保留,把旧保留覆盖掉。 设备断电后状态显示错乱,怎么让它自动标"离线"? 用遗愿消息(LWT / Last Will and Testament)。设备连接时告诉 broker:如果我不告而别(断网/断电),请你代我向 <前缀>/<组件>/<对象id>/availability 发一条"offline"保留消息。同时设备正常时定期发"online"。HA 的实体配 availability 主题监听这个,一旦收不到 online 就自动把实体标离线,避免显示过期的"开"状态。这是物联网设备可用性的标准做法。 多个设备共用一个实体 ID 会怎样,唯一 ID 怎么定? 如果两个设备发了相同的 <对象id> 或相同的 unique_id,HA 会把它们当成同一个实体,后到的覆盖先到的,轻则状态串台,重则设备互相打架。所以对象 id 和消息里的 unique_id 必须全局唯一,常见做法是 <设备名>_<功能>,比如 bedroom_esp32_temp。同一个物理设备的多个实体(温度、湿度、开关)用同一个 device 信息块关联,HA 会在设备面板里把它们聚成一个设备。

相关文章