ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Home Assistant MQTT Discovery 自动发现与实体配置

2026/10/1 1:15:26 拓冰建站 浏览量
Home Assistant MQTT Discovery 自动发现与实体配置 1. 为什么我最后放弃了在HA里手写实体配置先说个真实经历。早期做智能家居接入我的习惯是每个设备都在configuration.yaml里老老实实写一遍实体定义。一个带温度、湿度、电量、开关状态的四合一传感器就要写四段配置十个设备写下来配置文件三百多行改一个设备名得全局搜替换漏一处就报错重启。最要命的是设备一多我根本记不清哪个unique_id对应哪个物理设备排查起来全靠翻日志。后来接触了MQTT Discovery设备自发现整个思路变了。简单说它是 Home Assistant 和 MQTT 之间的一套约定设备或网关不需要你手动在配置里声明实体只要往约定的 MQTT 主题上发一条符合规范的 JSON 消息HA 就会自动把实体创建出来并绑定好状态主题、命令主题、单位、设备分类等所有元信息。设备离线后你还可以再发一条空消息把这个实体删掉。这套机制解决的核心问题是规模化接入。一台自制 ESP 传感器、一块 STM32 加 4G 模块的远程采集板、一个用脚本跑起来的虚拟设备只要它能发 MQTT就能被 HA 自动识别。对于做批量设备接入、做二次开发、做网关中间件的人来说这几乎是绕不开的一环。这篇文章适合三类人看一是刚上手 Home Assistant、还在被 yaml 配置折磨的人二是想给自己做的硬件设备加上能被 HA 自动发现能力的嵌入式开发者三是做物联网平台或网关、需要把第三方设备统一映射到 HA 的工程师。我会把 Discovery 的主题结构、发现消息的字段逻辑、设备分组的坑、上线和下线流程、以及实际调试中怎么定位消息发了但实体不出来这类问题一条条讲清楚。需要提前说明的是HA 的 Discovery 规范细节较多不同版本间偶有字段调整我下面给的都是长期稳定、实际在用的写法涉及版本差异的地方会单独标注。凡是我补全的、原始规范里没写得太细的部分都是基于我自己的实践总结你按自己环境微调即可。2. Discovery 的通信骨架主题命名与消息流向2.1 发现主题的层级到底怎么拼HA 的 MQTT Discovery 默认使用homeassistant作为发现前缀。一条完整的发现主题长这样discovery_prefix/component/[node_id/]object_id/config拆开看每一段discovery_prefix默认homeassistant可以在configuration.yaml的mqtt段里改。除非有特殊隔离需求否则建议保持默认因为很多现成的固件和中间件都硬编码了这个前缀。component实体类型比如sensor、binary_sensor、switch、light、climate、cover、number、select等。它决定了 HA 用哪个平台去解析这条消息。node_id可选用来做设备分组。同一个物理设备下的多个实体放同一个node_idHA 会把它们聚合到一张设备卡片下。object_id这个实体在 HA 内部的唯一标识也是实体的entity_id基础部分。config固定后缀标记这是一条配置/发现消息。举个例子一个节点叫living_room_sensor里面有个温度实体那么发现主题就是homeassistant/sensor/living_room_sensor/temperature/config对应的实体 ID 大致会生成sensor.living_room_sensor_temperature。这里有个细节很多人踩过object_id里不要用大写、空格和特殊符号统一用小写字母加下划线否则生成的 entity_id 会很难看甚至出现转义问题。提示node_id和object_id的拼接顺序不是随意的。如果你把两者写反HA 会把它当成不同设备聚合效果就没了。判断方法很简单——看 HA 里生成的实体前缀是不是符合你的预期。2.2 发现消息发出去之后HA 做了什么理解消息流向调试时能省一半时间。整个过程其实是这样设备或网关连上 MQTT Broker往prefix/component/.../config发布一条 JSON通常设置retain true。HA 的 MQTT 集成在订阅了homeassistant/#收到这条 retained 消息后解析 JSON注册实体。HA 根据消息里的state_topic去订阅状态如果配了command_topic则向该主题发布控制指令。Broker 重启或 HA 重启后因为消息是 retained 的HA 重新订阅时立刻又能拿到实体自动恢复不需要设备重新上线。为什么一定要用 retain这是新手最容易忽略的点。如果不用 retained 消息设备发完发现消息就完事了HA 那一刻如果没在线比如正在重启这条消息就丢了实体永远不会出现。而 retain 让 Broker 替你保存最后一条消息任何新订阅者包括重启后的 HA都能立刻收到。代价是消息会一直存在 Broker 上设备彻底不用了记得发空消息清理否则会留下一堆僵尸实体。清理方法往同一个 config 主题发一个空 payload空字符串并保持 retainHA 收到后会删除该实体。# 用 mosquitto_pub 发一条空消息retain 保持 mosquitto_pub -h 192.168.1.10 -t homeassistant/sensor/living_room_sensor/temperature/config -r -n-n表示发送空 payload-r表示 retained。这条命令我经常用来手动清理测试产生的垃圾实体。2.3 发现消息里那几个必填字段一条能成功注册实体的消息最少要包含这些字段{ name: 客厅温度, state_topic: home/sensor/living_room/state, unit_of_measurement: °C, device_class: temperature, value_template: {{ value_json.temperature }}, unique_id: living_room_sensor_temperature, device: { identifiers: [living_room_sensor_01], name: 客厅环境传感器, model: DIY-ESP32-Sensor, manufacturer: selfmade } }逐个解释为什么这么写name显示名。如果不写device每个实体就是孤立的一张卡片写了device并带上identifiersHA 会把多个实体聚合成一个设备。state_topic状态来源。HA 只是订阅者不会主动去问设备。unit_of_measurement和device_class决定 HA 界面的图标、单位显示以及能否参与统计图表。device_class填对了历史数据可以自动换算和长期统计。value_template从原始 payload 里提取字段。如果设备直接发纯数字这一项可以省略如果发的是 JSON就必须用模板取值。unique_id实体在 HA 注册表里的唯一键。强烈建议每个实体都写不写的话你无法在界面上重命名实体实体 ID 也不稳定。device设备分组信息。identifiers是设备唯一标识必须全局唯一通常用设备序列号或 MAC。这里说个我踩过的坑unique_id一旦用了就不能随便改。改了之后 HA 会认为是一个全新实体旧的实体变成不可用历史数据断掉。所以命名要提前规划好最好和设备物理编号绑定。3. 不同实体类型的 Discovery 配置差异3.1 传感器与二进制传感器component为sensor时HA 期望收到可读的数值或文本为binary_sensor时只认ON/OFF两种状态。传感器的典型配置重点是device_class和state_class{ name: 电量, state_topic: home/sensor/living_room/state, value_template: {{ value_json.battery }}, unit_of_measurement: %, device_class: battery, state_class: measurement, entity_category: diagnostic }state_class有三个常用值measurement瞬时值如温度、湿度、total累计值如总电量、total_increasing只增不减的累计值如用电量。填错了HA 的统计功能会报错或算错。entity_category设为diagnostic后这个实体不会出现在主控面板而是折叠到设备详情的诊断区像电量、信号强度这类就该这么处理。二进制传感器我常用在门窗磁、人体感应上{ name: 门窗状态, state_topic: home/sensor/door/state, payload_on: OPEN, payload_off: CLOSED, device_class: door, value_template: {{ value_json.contact }} }payload_on/payload_off是重点。很多硬件上报的是1/0或true/false和 HA 默认期待的ON/OFF不一致必须在发现消息里显式声明否则状态显示是反的或者一直是未知。注意device_class为door、window、motion等安全类时HA 界面会用不同的图标和颜色标识选对了体验好很多。别偷懒一律不写。3.2 开关、灯与可控实体可控实体和只读传感器最大的区别是多了一个command_topic。HA 向这个主题发指令设备订阅后执行。{ name: 补光灯, state_topic: home/light/grow/state, command_topic: home/light/grow/set, payload_on: ON, payload_off: OFF, state_on: ON, state_off: OFF, brightness_state_topic: home/light/grow/brightness, brightness_command_topic: home/light/grow/brightness/set, brightness_scale: 255, unique_id: grow_light_01 }几个容易混的字段字段作用常见错误payload_on/offHA 发出的指令内容与设备约定的值不匹配state_on/off判断设备状态用的值和 payload 混用导致状态不刷新brightness_scale亮度上限默认 255设备用 0-100 时没改导致亮度算错我最早接一个支持 0-100 亮度调节的灯带忘了设brightness_scale: 100结果滑条拉到最大HA 发的是 255灯带直接按最大值处理中间全乱。这类字段一定要和设备的实际协议对齐。3.3 设备分组与命名冲突处理设备分组靠device.identifiers。同一个identifiers下的所有实体HA 会合并到一张卡片。这里有两个坑一是identifiers重复。如果你有两个不同设备用了同一个 identifierHA 会把它们当成同一台设备实体混在一起日志里会提示冲突。我的做法是直接用设备的 MAC 或芯片唯一 ID 拼在 identifier 里例如dev_a1b2c3d4。二是unique_id重复。写入 HA 注册表的unique_id全局唯一重名会导致后来者注册失败。建议格式统一为设备ID_功能生成时就带上别靠肉眼保证不重复。当设备固件升级、发现消息内容变化时只要unique_id不变HA 会更新实体属性而不是新建实体。所以unique_id是整个 Discovery 体系里最需要稳定的字段。4. 从零跑通一次完整的自发现流程4.1 环境准备与最小验证在动设备之前先用命令行把 Broker 和 HA 的通路验证一遍。准备一个 MQTT BrokerMosquitto 就行。Home Assistant已配置 MQTT 集成并连上 Broker。一个 MQTT 客户端工具命令行用mosquitto_pub/mosquitto_sub图形化用 MQTTX。先在 HA 的 MQTT 集成里确认连接正常。然后手动发一条发现消息mosquitto_pub -h 192.168.1.10 \ -t homeassistant/sensor/test_node/test_temp/config \ -r \ -m { name: 测试温度, state_topic: home/test_node/state, unit_of_measurement: °C, device_class: temperature, value_template: {{ value_json.temp }}, unique_id: test_node_temp, device: { identifiers: [test_node_01], name: 测试节点 } }发完之后去 HA 的实体列表里搜测试温度。能出现说明链路通了。这一步我用得很多属于排查问题的第一道验证先排除设备和固件的问题确认 HA 端一切正常再往设备侧查。接着发一条状态数据mosquitto_pub -h 192.168.1.10 \ -t home/test_node/state \ -m {temp: 24.5}HA 界面上这个实体应该显示 24.5。4.2 嵌入式设备侧的实现思路如果你是用 ESP32、ESP8266 这类设备思路是上电连上 WiFi 和 MQTT 后先把发现消息以 retained 方式发出去然后周期性发状态。以 Arduino 框架的 PubSubClient 为例核心逻辑是这样// 连接成功后发布发现消息 void publishDiscovery() { const char* topic homeassistant/sensor/esp_node/esp_temp/config; const char* payload R({ name: 节点温度, state_topic: home/esp_node/state, unit_of_measurement: °C, device_class: temperature, value_template: {{ value_json.temp }}, unique_id: esp_node_temp, device: { identifiers: [esp_node_01], name: ESP测试节点, model: ESP32, manufacturer: selfmade } }); mqttClient.publish(topic, payload, true); // true retain }几个实践要点发现消息只在连接成功后发一次即可不需要每次上报状态都发。但如果 HA 重启retained 消息会帮它恢复设备无需干预。判断是否需要重发可以监听 HA 的状态主题或者在设备上做一次重连后重发。我的经验是 retained 足够重发多了反而产生大量重复消息。如果是 STM32 加 4G 模块这类资源受限设备发现消息 JSON 可以预先拼成字符串常量减少内存拼接开销。TLS 加密连接时注意发现消息体积别太大分段发送在某些模块上会出问题。4.3 上线、下线与失效处理设备正常上线就是发 retained 发现消息。设备要下线或彻底停用时有两种做法一是发空配置清实体mosquitto_pub -h 192.168.1.10 -t homeassistant/sensor/esp_node/esp_temp/config -r -n二是配合 HA 的availability可用性机制。在发现消息里加{ availability_topic: home/esp_node/status, payload_available: online, payload_not_available: offline }设备连上后往availability_topic发online断开前发offline最好用 MQTT 遗嘱消息 Last Will 实现设备异常掉线时 Broker 自动代发offline。这样 HA 界面上的实体状态会正确显示为不可用而不是一直卡在最后上报的数值上。提示遗嘱消息要在 MQTT 连接时就指定。ESP 上用mqttClient.connect(clientId, user, pass, willTopic, willQos, willRetain, willMessage)的重载版本把离线主题和消息带上。这是让设备状态诚实反映现实的关键很多人忘了配结果设备拔电了 HA 还显示在线。5. 排查消息发了实体却没出来5.1 分层定位法这个问题我遇到过无数次总结了一套自下而上的排查顺序Broker 层用mosquitto_sub -t homeassistant/# -v订阅确认消息真的发出去了主题和 payload 都对。这一步能过滤掉一大半设备根本没发的情况。HA 订阅层确认 HA 的 MQTT 集成连的是同一个 Broker、同一个端口、同一套账号密码。我见过有人设备连的是 1883HA 配的是别的端口一直不通。消息格式层JSON 必须是合法 JSON不能有注释、不能有尾逗号。用在线工具格式化一下或者用jq校验echo payload | jq .。格式错 HA 会静默丢弃日志里可能只有一行不易察觉的警告。字段语义层检查component和字段是否匹配。比如把带command_topic的配置发到了sensor类型下HA 会报字段不识别。5.2 常见错误对照表现象最可能原因处理方式实体完全不出现发现消息没 retainHA 未收到加-r重发实体出现但一直未知state_topic没收到数据或模板取值错误用mosquitto_sub核对状态主题和字段状态显示反了payload_on/off与设备值不符显式声明 payload 或改 value_template实体重复出现多个unique_id重复改为全局唯一清理旧实体删除后发现消息还在空消息没 retain发空 payload 时带上-r重启后实体丢失消息未 retain全程 retained 发布5.3 用 value_template 处理复杂 payload设备上报的数据常常是一个大 JSON包含多个字段。这时每个实体的发现消息都订阅同一状态主题各自用模板取字段是最高效的做法{ state_topic: home/node1/state, value_template: {{ value_json.data.temperature }}, unit_of_measurement: °C, device_class: temperature }如果字段是嵌套的模板里用点号逐层取。若值是字符串要先转数字用{{ value_json.temp | float }}。我遇到过一个设备把温度上报成24.5字符串HA 图形化统计里没数据加上| float过滤器就好了。这类小过滤器在实际项目里能救不少急。另外要留意模板的容错。设备刚上线还没发状态时模板对空字符串求值会报错。可以在模板里加默认值{{ value_json.temp | default(0) }}避免日志刷满警告。6. 规模化接入时的工程化建议6.1 主题规划要提前做设备少时怎么写都行一旦上百个设备、上千个实体主题规划就是生死线。我现在的习惯是homeassistant/component/device_id/entity/config 发现主题 home/device_id/state 设备状态 home/device_id/entity/set 控制命令 home/device_id/status 在线状态device_id用物理编号和device.identifiers保持一致。这样一眼就能看出消息属于哪个设备日志排查、批量脚本操作都方便。切忌用设备名字随机数这种不可追溯的命名。6.2 网关中间件统一翻译如果设备本身不会发 Discovery 消息很多厂家设备只发自己的私有协议常见做法是做一个网关中间件订阅设备原始数据转换成 HA 的发现消息和状态消息再转发。用 Python 的paho-mqtt写一个这样的网关其实不复杂import paho.mqtt.client as mqtt import json DISCOVERY_PREFIX homeassistant def publish_discovery(client, device_id, device_name, entity_key, entity_name, unit, dev_class): topic f{DISCOVERY_PREFIX}/sensor/{device_id}/{entity_key}/config payload { name: entity_name, state_topic: fhome/{device_id}/state, unit_of_measurement: unit, device_class: dev_class, value_template: {{ value_json.%s }} % entity_key, unique_id: f{device_id}_{entity_key}, device: { identifiers: [device_id], name: device_name } } client.publish(topic, json.dumps(payload), retainTrue) client mqtt.Client() client.connect(192.168.1.10, 1883, 60) publish_discovery(client, sensor01, 网关温度计, temperature, 温度, °C, temperature) client.loop_forever()这个模式的好处是设备端零改动所有转换逻辑集中在中间件改起来只动一处。生产环境里我会把设备清单做成配置文件启动时批量注册发现消息新增设备只加一行配置。6.3 版本兼容与字段演进HA 对 Discovery 的字段做过若干次调整比如状态类、设备分组的相关字段。我的应对策略尽量只用长期稳定的核心字段name、state_topic、command_topic、unique_id、device、value_template。这些几乎不会变。对新增的可选字段如entity_category、suggested_display_precision按需使用但要留意 HA 版本说明。升级 HA 前先在测试实例上导入一份线上发现消息验证实体能否正常注册和历史数据是否保留。字段演进不是大问题真正麻烦的是升级后某些实体变成恢复状态。这通常是因为unique_id或device.identifiers变了导致的重新注册。只要这两项不动升级一般无感。7. 我在实际项目里踩过的几个具体坑第一个坑发现消息的device_class拼错。当时把temperature写成了tempHA 没有报错但实体被当作普通文本传感器单位也能显示可就是进不了统计和历史图表。找了两小时才定位到字符串拼错。现在的习惯是发现消息发给 HA 后先在开发者工具里看一眼实体的属性确认device_class被正确识别。第二个坑多个实体共用状态主题时模板取值失败。有次一个设备上报的是数组而非对象所有value_template全都取不到值。解决方法是先把原始 payload 打印出来看一眼再决定模板怎么写。别凭想象写模板这是铁律。第三个坑retain 和遗嘱消息一起用时的顺序问题。设备异常重启后Broker 代发的遗嘱offline是 retained 的如果设备上线后没及时更新为onlineHA 会一直显示离线。我现在都是设备一连接上就立即发online并且把发现消息和状态主题都设为 retained确保状态一致。第四个坑清理不彻底留下僵尸实体。测试阶段发过大量发现消息后来设备删了但 retained 消息还在HA 每次启动都恢复一堆无效实体。解决办法就是前面说的删除时发空 retained 消息或者干脆把测试用的前缀和其他设备隔离。这些坑的共同点是它们在日志里几乎都是静默的。HA 不会主动告诉你你的 JSON 少了个字段或者这个 device_class 不认识绝大多数问题得靠你自己去对比规范、去订阅主题看原始消息。所以养成发消息前先在订阅端看一眼的习惯能省掉大量返工。最后分享一个我常用的调试动作把发现消息、状态主题、命令主题都订阅在一个终端里用mosquitto_sub -t homeassistant/# -v -t home/# -v操作设备时全程盯着消息流。消息怎么流的、字段长什么样、retain 标志有没有一眼全清楚比翻日志快得多。