Files
arduino-home-assistant/docs/device-and-discovery.md
T
2026-09-02 07:14:20 +10:00

6.7 KiB

Device & discovery

HADevice

Represents the physical board in Home Assistant: one device can expose multiple entities (sensors, switches, lights, …).

Unique ID (required): must be unique in your Home Assistant instance. Common choices:

  • MAC address as bytes: HADevice device(mac, sizeof(mac));
  • String: HADevice device("myId"); — keep it short and alphanumeric.
  • Or default-construct and call setUniqueId(bytes, length) in setup().

Optional metadata (each costs some RAM/flash; skip on tiny MCUs unless needed):

  • setName, setSoftwareVersion, setManufacturer, setModel, setConfigurationUrl
  • setModelId, setHardwareVersion, setSerialNumber, setSuggestedArea, setViaDevice
  • addConnection("mac", "aa:bb:cc:dd:ee:ff") or setConnectionsJson("[[\"mac\",\"aa:bb:cc:dd:ee:ff\"]]")

String setters take pointers whose contents are not copied — use literals or storage that outlives the call.

Discovery

When MQTT connects, the library publishes Home Assistant MQTT discovery payloads so entities appear automatically.

Entities can be constructed either before or after HAMqtt. Entities which already exist when HAMqtt is constructed are registered then; entities constructed later register immediately. Both the device, MQTT object, and entities must have a lifetime that outlasts mqtt.loop().

HAMqtt has a configurable entity limit (24 by default). Check getRegisteredDeviceTypeCount(), getDeviceTypeLimit(), and getDeviceTypeRegistrationFailures() in firmware diagnostics: registrations above the limit are rejected and logged rather than silently disappearing from discovery.

Topic prefixes

Defaults:

  • Discovery prefix: homeassistant
  • Data prefix (states, commands): aha

Override before begin() if needed:

mqtt.setDiscoveryPrefix("myHaPrefix");
mqtt.setDataPrefix("myDataPrefix");

Single-component vs device discovery

  • Default: one retained discovery topic per entity (single-component discovery).
  • Device discovery: HAMqtt::enableDeviceDiscovery() publishes one retained device payload with components under cmps.

Both formats remain supported by Home Assistant. Device discovery requires Home Assistant 2024.11.0 or newer. Use enableDeviceDiscovery() only for a new device which has never published this library's single-component discovery topics.

Migrating an existing device to device discovery

Do not switch an existing device by calling enableDeviceDiscovery() alone. Home Assistant derives an entity's discovery identity from the topic, and a direct switch causes retained-topic conflicts. The migration deliberately publishes, in order:

  1. {"migrate_discovery":true} to every old retained component config topic;
  2. the retained /device/<deviceId>/config payload; and only then
  3. an empty retained payload to every old component config topic.

Keep the device ID and each entity ID unchanged. They preserve the mapping from the old <component>/<deviceId>/<entityId>/config topic to cmps[entityId] in the new device payload.

Start migration once during setup, then advance one stage per loop iteration after the MQTT connection is available. Each method is retry-safe: do not advance if it returns false.

bool migrationStarted = false;

void setup() {
    // Create device, mqtt, and entities; keep their IDs unchanged.
    migrationStarted = mqtt.beginDeviceDiscoveryMigration();
    mqtt.begin("192.168.1.50", "user", "password");
}

void loop() {
    mqtt.loop();

    switch (mqtt.getDeviceDiscoveryMigrationState()) {
    case HAMqtt::DeviceDiscoveryMigrationMarkersPending:
        mqtt.publishDeviceDiscoveryMigrationMarkers();
        break;
    case HAMqtt::DeviceDiscoveryMigrationMarkersPublished:
        mqtt.publishDeviceDiscoveryMigrationConfig();
        break;
    case HAMqtt::DeviceDiscoveryMigrationDevicePublished:
        mqtt.completeDeviceDiscoveryMigration();
        break;
    default:
        break;
    }
}

The migration stage is held in RAM. If the board reboots before completion, start the same migration again; the retained marker/config/cleanup publishes are idempotent. If you must abandon a started migration while connected, call rollbackDeviceDiscoveryMigration(). Once a device config has been published, it first writes {"migrate_discovery":true} to the device discovery topic, restores the legacy configs, then clears the device config and returns to single-component mode. A failed rollback remains pending and suppresses automatic device-bundle publication until retried successfully. Run all publishing stages from loop(), not inside an inbound MQTT callback.

Device discovery can also publish richer origin/device metadata, for example:

device.setModelId("esp32-s3-devkit");
device.setHardwareVersion("rev-b");
device.setSerialNumber("SN-00042");
device.setSuggestedArea("Garage");
device.setViaDevice("main_gateway");
device.addConnection("mac", "AA:BB:CC:DD:EE:FF");
mqtt.setOriginSupportUrl("https://example.com/device-help");

For entity identifiers in Home Assistant, prefer setDefaultEntityId() over legacy setObjectId(). setObjectId() remains source-compatible but no longer serializes the removed MQTT obj_id property. setDefaultEntityId() influences Home Assistant's entity ID only when it first creates the entity; existing users can retain a customized entity ID in the entity registry.

Common entity discovery metadata can be configured on most entity types via:

  • setEnabledByDefault(bool)
  • setEntityPicture(const char*)
  • setQos(uint8_t)
  • setEncoding(const char*)
  • setEntityCategory(const char*)

Runtime discovery changes

After changing discovery-related settings at runtime:

  • HABaseDeviceType::republishDiscovery() to refresh discovery.
  • HABaseDeviceType::removeFromDiscovery() to remove one entity from discovery.

In single-component mode, removal clears that entity's retained config topic. In device discovery mode, Home Assistant requires a platform-only component marker ({"p":"sensor"}, for example), followed by a compacted device bundle that omits the component. ArduinoHA performs both publishes. Call republishDiscovery() on that entity to add it back.

Identifier and lifetime checklist

  • Give HADevice a stable, non-empty unique ID and each entity a stable, topic-safe ID (A-Z, a-z, 0-9, _, -).
  • Use enableExtendedUniqueIds() when multiple devices could otherwise reuse the same entity IDs.
  • Metadata setters store pointers; retain their backing strings. HAText copies the current state and command text it receives, so its state does not borrow a transient MQTT buffer.

Trimming flash (optional)

You can exclude unused entity implementations with macros (see MQTT usage).