mirror of
https://github.com/alexhopeoconnor/arduino-home-assistant.git
synced 2026-10-04 02:48:13 +10:00
153 lines
7.2 KiB
Markdown
153 lines
7.2 KiB
Markdown
# 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:
|
|
|
|
```cpp
|
|
void configureTopicPrefixes() {
|
|
// Set both before mqtt.begin(...) publishes any discovery data.
|
|
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.
|
|
|
|
Choose the mode before connecting a device:
|
|
|
|
- **New device:** call `enableDeviceDiscovery()` before connecting.
|
|
- **Existing device using single-component discovery:** follow the migration
|
|
procedure below.
|
|
- **No need for device discovery:** keep the default behavior.
|
|
|
|
### 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 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`.
|
|
|
|
```cpp
|
|
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:
|
|
|
|
```cpp
|
|
void configureDeviceDiscovery() {
|
|
// These strings are borrowed, so keep literals or other long-lived storage.
|
|
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](mqtt-usage.md#compiler-macros)).
|