Sensors, firmware, rooms

dev-smarthome

· MQTT

MQTT discovery for a board I etched myself

Retained birth messages, a stable unique id, and why the device registry stopped duplicating the node.

The board is a small two-layer PCB: an ESP32 module, a BME280, and a header for a door reed. I did not want a custom integration for something this plain. Home Assistant's MQTT discovery is enough if the birth messages are boring and stable.

Identity

The first firmware used the MAC address as unique_id and also as the device name. That is unique and also hostile. After one reflash I had two devices in the registry because I had changed the name string and Home Assistant treated the new discovery payload as a new device with the same MAC hiding in a different field.

The rule I kept: unique_id is the MAC plus the sensor role, and it never changes. The friendly name can change. The device identifier is only the MAC.

{
  "name": "Hall temperature",
  "unique_id": "a4cf12aa01f0-temp",
  "state_topic": "bench/hall/temperature",
  "device_class": "temperature",
  "unit_of_measurement": "°C",
  "device": {
    "identifiers": ["a4cf12aa01f0"],
    "name": "Hall node",
    "model": "bench-rev3",
    "manufacturer": "bench"
  }
}

Retain the birth, not the state

Discovery payloads are retained. State topics are not. A retained temperature from yesterday is worse than a gap. When the node boots it publishes discovery again, then starts periodic state. Home Assistant already has the entity from the retained config and just waits for a value.

Availability is a separate topic, bench/hall/status, with payload online and offline. The offline message is a will, registered before the node says it is up. If the broker connection drops, the will fires and the entities go unavailable without a custom timeout.

The duplicate

I deleted the ghost device in the UI and cleared the old retained discovery topic that still advertised the previous unique id. Until that retain was gone, every restart of Home Assistant recreated the ghost. mosquitto_pub -r -n on the old config topic was the actual fix. Clicking delete was not.