ESP32 综合教程:通过 MQTT 自动发现接入 Home Assistant

本文由 AI 协助整理,示例以 Arduino-ESP32、PubSubClient 和 ArduinoJson 为主。Home Assistant 与库的界面、字段会随版本变化,本文依据发布时的官方 MQTT Discovery 规则整理;实际部署请以当前官方文档为准。

ESP32 接入 Home Assistant 的消息路径

这次不是“发出 MQTT 就算完成”

ESP32 能把温湿度发到 MQTT,只说明数据已经进入消息服务器。要在 Home Assistant 里变成有名称、有单位、有设备归属、能画历史曲线的实体,还需要配置这些数据应该如何解释。

最省事的方式是 MQTT Discovery。ESP32 启动后向特定主题发布一份配置,告诉 Home Assistant:“我是一台设备,我有温度、湿度和一个开关,这些实体的状态和命令分别在哪些主题。”Home Assistant 收到后自动创建设备和实体,不需要为每块板手写 YAML。

自动发现很方便,也容易留下“幽灵实体”:设备名改了、唯一编号变了、旧配置仍保留在 Broker 中。本文不仅讲怎样让实体出现,也讲清楚稳定编号、保留消息、在线状态和删除旧实体。

最终功能

完成后,ESP32 会在 Home Assistant 中显示为一台设备,包含:

  • 温度传感器,单位为摄氏度。
  • 湿度传感器,单位为百分比。
  • 一个 LED 开关,可从 Home Assistant 下发开关命令。
  • 在线或离线状态。
  • 设备名称、型号和固件版本。

硬件用普通 ESP32 DevKit 即可。传感器可选 DHT22、SHT30 或 BME280。教程把传感器读取封装成两个变量,便于替换;LED 使用开发板可用的输出引脚,具体 GPIO 要按自己的板型确认。

服务端准备

Home Assistant 需要先配置 MQTT Broker。使用 Home Assistant OS 时,常见做法是安装 Mosquitto Broker 应用,然后在“设置 → 设备与服务”中添加 MQTT 集成。也可以使用自己维护的兼容 Broker。

为 ESP32 创建独立 MQTT 账号,不要让设备使用管理员账号。Broker 不应直接暴露到公网;跨网络访问应使用 VPN、TLS 或经过安全设计的入口。

先用 MQTT 客户端验证以下信息:

配置 示例
Broker 地址 192.168.1.20
端口 1883,仅局域网示例
设备用户名 esp32_node
密码 使用独立强密码
Discovery 前缀 默认 homeassistant

先确认电脑客户端能连上 Broker,再调 ESP32。否则 WiFi、用户名、端口、Broker 防火墙和代码问题会同时站在门口,排查起来很有团队气氛。

主题规划

本项目使用:

home/lab/esp32-ha-01/state
home/lab/esp32-ha-01/set
home/lab/esp32-ha-01/availability
homeassistant/sensor/esp32_ha_01_temperature/config
homeassistant/sensor/esp32_ha_01_humidity/config
homeassistant/switch/esp32_ha_01_led/config

前三个是业务主题,后三个是自动发现配置主题。默认发现主题格式是:

<discovery_prefix>/<component>/<object_id>/config

每个实体还必须有稳定且唯一的 unique_id。不要每次启动都生成新的随机编号,也不要让两块设备使用同一个编号,否则 Home Assistant 会把它们认成同一个实体,像给两户人家发了同一把门牌。

MQTT 主题与消息职责

安装 Arduino 库

在 Arduino IDE 库管理器安装:

  • PubSubClient
  • ArduinoJson
  • 自己传感器所需的库,例如 Adafruit BME280 Library

PubSubClient 默认消息缓冲区可能放不下较长的 Discovery JSON,可以在代码中调用 mqtt.setBufferSize(1024)。若配置继续扩展,应检查序列化后的实际长度,而不是一味把缓冲区调大。

程序配置与主题

下面先准备基础对象:

#include <WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>

const char* WIFI_SSID = "YOUR_WIFI";
const char* WIFI_PASS = "YOUR_PASSWORD";
const char* MQTT_HOST = "192.168.1.20";
const uint16_t MQTT_PORT = 1883;
const char* MQTT_USER = "esp32_node";
const char* MQTT_PASS = "CHANGE_ME";

const char* DEVICE_ID = "esp32_ha_01";
const char* DEVICE_NAME = "书房环境节点";

const char* STATE_TOPIC = "home/lab/esp32-ha-01/state";
const char* COMMAND_TOPIC = "home/lab/esp32-ha-01/set";
const char* AVAIL_TOPIC = "home/lab/esp32-ha-01/availability";

constexpr uint8_t LED_PIN = 2; // 按开发板修改

WiFiClient networkClient;
PubSubClient mqtt(networkClient);

正式项目不要把真实密码提交到公开代码仓库。可以放入未纳入版本控制的 secrets.h,或者通过配置页面写入 NVS。

发布自动发现配置

为了便于理解,下面采用“每个实体一条配置”的方式。官方文档也支持把多组件放在一个设备发现消息中;项目实体很多时,可以再升级成设备级发现。

先写公共设备信息:

void addDeviceInfo(JsonDocument& doc) {
  JsonObject device = doc["device"].to<JsonObject>();
  device["identifiers"][0] = DEVICE_ID;
  device["name"] = DEVICE_NAME;
  device["manufacturer"] = "DIY";
  device["model"] = "ESP32 DevKit";
  device["sw_version"] = "1.0.0";
}

温度实体:

void publishTemperatureDiscovery() {
  JsonDocument doc;
  doc["name"] = "温度";
  doc["unique_id"] = "esp32_ha_01_temperature";
  doc["state_topic"] = STATE_TOPIC;
  doc["value_template"] = "{{ value_json.temperature }}";
  doc["device_class"] = "temperature";
  doc["unit_of_measurement"] = "°C";
  doc["state_class"] = "measurement";
  doc["availability_topic"] = AVAIL_TOPIC;
  addDeviceInfo(doc);

  char payload[768];
  size_t len = serializeJson(doc, payload, sizeof(payload));
  mqtt.publish(
      "homeassistant/sensor/esp32_ha_01_temperature/config",
      reinterpret_cast<const uint8_t*>(payload), len, true);
}

湿度实体只需调整名称、唯一编号、模板、设备类型和单位:

void publishHumidityDiscovery() {
  JsonDocument doc;
  doc["name"] = "湿度";
  doc["unique_id"] = "esp32_ha_01_humidity";
  doc["state_topic"] = STATE_TOPIC;
  doc["value_template"] = "{{ value_json.humidity }}";
  doc["device_class"] = "humidity";
  doc["unit_of_measurement"] = "%";
  doc["state_class"] = "measurement";
  doc["availability_topic"] = AVAIL_TOPIC;
  addDeviceInfo(doc);

  char payload[768];
  size_t len = serializeJson(doc, payload, sizeof(payload));
  mqtt.publish(
      "homeassistant/sensor/esp32_ha_01_humidity/config",
      reinterpret_cast<const uint8_t*>(payload), len, true);
}

最后一个参数 true 表示保留 Discovery 配置。这样 Home Assistant 或 Broker 重启后仍能收到配置。保留消息也意味着旧配置不会自动消失,修改 unique_id 前要计划如何清理。

增加可控制的 LED 开关

开关既有命令主题,也有状态字段:

void publishLedDiscovery() {
  JsonDocument doc;
  doc["name"] = "状态灯";
  doc["unique_id"] = "esp32_ha_01_led";
  doc["command_topic"] = COMMAND_TOPIC;
  doc["state_topic"] = STATE_TOPIC;
  doc["value_template"] = "{{ value_json.led }}";
  doc["payload_on"] = "ON";
  doc["payload_off"] = "OFF";
  doc["availability_topic"] = AVAIL_TOPIC;
  addDeviceInfo(doc);

  char payload[768];
  size_t len = serializeJson(doc, payload, sizeof(payload));
  mqtt.publish(
      "homeassistant/switch/esp32_ha_01_led/config",
      reinterpret_cast<const uint8_t*>(payload), len, true);
}

接收命令时必须验证主题和内容:

bool ledOn = false;

void onMqttMessage(char* topic, byte* payload, unsigned int length) {
  if (strcmp(topic, COMMAND_TOPIC) != 0) return;

  String command;
  for (unsigned int i = 0; i < length; ++i) {
    command += static_cast<char>(payload[i]);
  }

  if (command == "ON") {
    ledOn = true;
    digitalWrite(LED_PIN, HIGH);
  } else if (command == "OFF") {
    ledOn = false;
    digitalWrite(LED_PIN, LOW);
  } else {
    Serial.printf("Unknown command: %s\n", command.c_str());
    return;
  }

  publishState();
}

有些开发板板载 LED 是低电平点亮,HIGH 与 LOW 需要反过来。接继电器时还要考虑上电默认状态,不要让设备启动时产生危险动作。

发布状态数据

温度、湿度和 LED 状态共用一个 JSON:

void publishState() {
  float temperature = 24.3f; // 替换为真实读数
  float humidity = 52.6f;

  if (!isfinite(temperature) || !isfinite(humidity)) {
    Serial.println("Invalid sensor value");
    return;
  }

  JsonDocument doc;
  doc["temperature"] = temperature;
  doc["humidity"] = humidity;
  doc["led"] = ledOn ? "ON" : "OFF";

  char payload[256];
  size_t len = serializeJson(doc, payload, sizeof(payload));
  mqtt.publish(STATE_TOPIC,
               reinterpret_cast<const uint8_t*>(payload), len, false);
}

状态数据通常不必保留。Home Assistant 会保存历史,设备离线后也不应让 Broker 用一条很旧的读数假装它刚刚还在测量。若业务确实需要 retain,要同时正确维护 availability 和数据过期逻辑。

在线、离线与遗嘱消息

连接 MQTT 时设置 Last Will:

bool connectMqtt() {
  bool ok = mqtt.connect(
      DEVICE_ID,
      MQTT_USER,
      MQTT_PASS,
      AVAIL_TOPIC,
      1,
      true,
      "offline");

  if (!ok) return false;

  mqtt.publish(AVAIL_TOPIC, "online", true);
  mqtt.subscribe(COMMAND_TOPIC);
  publishTemperatureDiscovery();
  publishHumidityDiscovery();
  publishLedDiscovery();
  publishState();
  return true;
}

设备异常断电或网络丢失后,Broker 会发布 offline;正常连接时设备发布 online。Home Assistant 便能把实体显示为不可用,而不是永久保留最后一个温度,看起来仿佛拔掉电源以后房间气候也被冻结了。

官方 MQTT 集成默认还会在 homeassistant/status 发布 Birth 消息。更完善的设备可以订阅它,收到 online 后重新发送发现配置。本文已经使用保留发现消息,入门阶段能够应对常见重启场景。

主循环与重连节流

不要在 loop() 中无间隔疯狂重连:

unsigned long lastReconnectAttempt = 0;
unsigned long lastPublishAt = 0;

void loop() {
  if (!mqtt.connected()) {
    if (millis() - lastReconnectAttempt >= 5000) {
      lastReconnectAttempt = millis();
      connectMqtt();
    }
  } else {
    mqtt.loop();
  }

  if (mqtt.connected() && millis() - lastPublishAt >= 30000) {
    lastPublishAt = millis();
    publishState();
  }
}

WiFi 也需要独立处理断线恢复。正式项目可以用状态机管理 WiFi 与 MQTT,确保传感器采样不被网络重连阻塞。

验证顺序

  1. 用桌面 MQTT 客户端确认 Broker 用户名和密码可用。
  2. ESP32 只连接 WiFi,打印 IP。
  3. 连接 MQTT,查看 Broker 日志或订阅 home/#。
  4. 发布温度发现配置,确认 Home Assistant 出现一个实体。
  5. 增加湿度与 LED,检查它们是否归到同一设备。
  6. 从 Home Assistant 切换 LED,确认 ESP32 收到命令并回报状态。
  7. 直接拔掉 ESP32,观察实体是否变成不可用。
  8. 重启 Home Assistant,确认发现配置仍可恢复。

常见问题

现象 排查方向
MQTT 已连接但没有实体 Discovery 前缀、配置主题格式、JSON 和 retain
出现多个重复设备 unique_id 或设备 identifiers 不稳定
实体存在但没有数值 state_topic、JSON 字段和 value_template 不一致
控制按钮能点但设备不动作 命令主题、订阅、payload 大小写和引脚逻辑
设备断电仍显示在线 Last Will 或 availability 配置不正确
配置发布失败 PubSubClient 缓冲区太小,或序列化数组容量不足

删除旧的自动发现实体

如果之前发布了错误配置,向旧的配置主题发布空消息并保留,即可清除对应保留配置。随后在 Home Assistant 中检查设备和实体是否仍有其他引用。

不要只改 unique_id 重新发布。那会创建新实体,旧实体仍然留着,几轮测试后界面里便会出现“温度”“温度 2”“温度 3”,很像一个没人愿意认领的家族群。

安全和工程化建议

为每台设备使用独立账号与最小主题权限;生产网络启用 TLS,并正确验证 Broker 证书;不要把管理界面和 1883 端口直接暴露到公网;密码和 WiFi 凭据不要提交到公开仓库。

设备端还可以增加 NVS 配置、Web 配网、OTA、看门狗和错误计数。Home Assistant 端则可以基于温度、湿度和在线状态创建自动化,例如节点离线十分钟后提醒,而不是只把数值摆在仪表盘上欣赏。

总结

ESP32 接入 Home Assistant 的关键不是“MQTT 能发消息”,而是为设备建立稳定身份,并把配置、状态、命令和可用性分开。unique_id 决定实体身份,Discovery 配置告诉平台如何解释数据,availability 让平台知道设备是否真的在线。

把这些基础打牢后,增加光照、空气质量、继电器或门磁只是继续添加实体。设备会从一个能发 JSON 的开发板,变成 Home Assistant 中可管理、可自动化、能明确显示在线状态的智能家居节点。

参考资料

打赏

如果这篇学习记录对你有帮助,欢迎扫码支持后续整理。

打赏码

打赏支持:王庆岩

posted on 2026-09-19 15:19  潇洒石头  阅读(27)  评论(0)    收藏  举报

导航