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 集成里改。一条实体配置消息必须发到:
例如一个厨房灯:
- 前缀: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 后做三件事:
- 发 discovery config(灯光 + 传感器两条,retained)。
- 发 birth:
homeassistant/light/kitchen_led/availability→online(retained)。 - 订阅
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
homeassistant,HA 在"MQTT 集成"里配置;实体消息必须发到 <前缀>/<组件>/<对象id>/config,比如 homeassistant/light/kitchen_led/config。组件名(light/sensor/switch…)决定 HA 建什么类型实体,<对象id> 是这台设备该实体的唯一标识。前缀可以改(比如改成 hass),改了之后所有设备都要用新前缀发消息,且老的保留消息还挂在旧前缀下,要清掉避免重复。<前缀>/<组件>/<对象id>/availability 发一条"offline"保留消息。同时设备正常时定期发"online"。HA 的实体配 availability 主题监听这个,一旦收不到 online 就自动把实体标离线,避免显示过期的"开"状态。这是物联网设备可用性的标准做法。<对象id> 或相同的 unique_id,HA 会把它们当成同一个实体,后到的覆盖先到的,轻则状态串台,重则设备互相打架。所以对象 id 和消息里的 unique_id 必须全局唯一,常见做法是 <设备名>_<功能>,比如 bedroom_esp32_temp。同一个物理设备的多个实体(温度、湿度、开关)用同一个 device 信息块关联,HA 会在设备面板里把它们聚成一个设备。