Per-entity availability, deferred discovery publish, suggested precision

- Add HAAvailabilityConfig for per-entity availability_topic and payloads
- Defer discovery until MQTT connected; publish availability after discovery
- HASensor/HANumber: optional suggested_display_precision in discovery
- HASerializer/HADictionary: extend for new keys; update device types and mocks
- Docs and examples reflect availability and precision usage
- Tests updated for discovery and availability behavior
- Ignore .cursor/ in repo root
This commit is contained in:
2026-05-05 17:47:55 +10:00
parent 8ba5482892
commit b9f4fd7f4b
62 changed files with 2705 additions and 283 deletions
+22
View File
@@ -13,6 +13,8 @@ Represents the physical board in Home Assistant: one device can expose multiple
**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.
@@ -41,8 +43,28 @@ mqtt.setDataPrefix("myDataPrefix");
- **Default:** one retained discovery topic per entity (single-component discovery).
- **Optional:** call `HAMqtt::enableDeviceDiscovery()` to publish a single **device** discovery payload with components under `cmps` (see project README for migration notes).
Device discovery can also publish richer origin/device metadata, for example:
```cpp
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()`**.
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:
+58
View File
@@ -46,12 +46,70 @@ mqtt.publish("customTopic", "payload", true); // retained
```cpp
device.enableSharedAvailability();
device.setPayloadAvailable("up");
device.setPayloadNotAvailable("down");
device.enableLastWill(); // broker publishes offline when TCP drops
// device.setAvailability(false); // optional: start as offline
```
**Per-entity availability:** call `someEntity.setAvailability(true/false)` on each type. Does not use LWT the same way as shared mode; see examples under `examples/availability/`.
Custom per-entity payloads are supported:
```cpp
sensor.setPayloadAvailable("ready");
sensor.setPayloadNotAvailable("lost");
```
For multi-topic availability discovery, add full MQTT topics and a mode:
```cpp
sensor.setAvailabilityMode("all");
sensor.addAvailabilityEntry("bridge/status");
sensor.addAvailabilityEntry("sensor/status", "{{ value_json.state }}");
```
## Discovery helpers by entity
Common entity discovery metadata is available on most entity classes:
```cpp
entity.setEnabledByDefault(false);
entity.setEntityPicture("https://example.com/entity.png");
entity.setQos(1);
entity.setEncoding("utf-8");
entity.setEntityCategory("diagnostic");
```
Read-only sensor presentation/template helpers:
```cpp
sensor.setSuggestedDisplayPrecision(2);
sensor.setValueTemplate("{{ value_json.temperature }}");
sensor.setJsonAttributesTemplate("{{ value_json.attrs | tojson }}");
sensor.setLastResetValueTemplate("{{ value_json.last_reset }}");
sensor.setDeviceClass("enum");
sensor.setOptions("idle;charging;discharging;fault");
```
Writable entity template/payload helpers:
```cpp
mySwitch.setPayloadOn("ENABLE");
mySwitch.setPayloadOff("DISABLE");
mySwitch.setStateOn("running");
mySwitch.setStateOff("stopped");
mySwitch.setValueTemplate("{{ value_json.state }}");
mySwitch.setCommandTemplate("{{ value_json.command }}");
myNumber.setPayloadReset("RESET");
myNumber.setCommandTemplate("{{ value | float | round(1) }}");
mySelect.setCommandTemplate("{{ value_json.choice }}");
myText.setCommandTemplate("{{ value_json.text }}");
myButton.setPayloadPress("PRESS");
```
## Compiler macros
Defined in `ArduinoHADefines.h` or via build flags.