Skip to content

Commit db73db1

Browse files
fhirschmannclaude
andcommitted
feat: Home Assistant MQTT discovery
Publishes retained HA discovery configs on every MQTT connect so all entities auto-register under one HA device: track/status/firmware/sw-rev/ WiFi (+battery) sensors, volume & LED-brightness numbers, lock & ambient switches, equalizer select, and play-pause/next/prev/update/shutdown buttons. Reuses the existing state/command topics; lock-controls now publishes ON/OFF on connect for a consistent switch state. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent a873641 commit db73db1

1 file changed

Lines changed: 65 additions & 1 deletion

File tree

‎src/Mqtt.cpp‎

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,67 @@ static NumberType toNumber(const std::string str) {
314314
return 0;
315315
}
316316

317+
#ifdef MQTT_ENABLE
318+
// --- Home Assistant MQTT discovery -------------------------------------------------------
319+
// Publishes retained discovery configs so every entity shows up automatically under one HA
320+
// device. Reuses the existing state/command topics; called once on each MQTT connect.
321+
static String gHassDeviceBlock; // shared "device" + availability JSON (rebuilt per connect)
322+
323+
static void mqttHassPublish(const char *component, const char *objectId, const String &fields) {
324+
if (mqtt_client == NULL) {
325+
return;
326+
}
327+
String topic = "homeassistant/" + String(component) + "/" + gDeviceId + "/" + String(objectId) + "/config";
328+
String payload = "{\"unique_id\":\"" + gDeviceId + "_" + String(objectId) + "\"," + fields + gHassDeviceBlock + "}";
329+
esp_mqtt_client_publish(mqtt_client, topic.c_str(), payload.c_str(), 0, 0, true);
330+
}
331+
332+
static void Mqtt_PublishHassDiscovery(void) {
333+
if (mqtt_client == NULL) {
334+
return;
335+
}
336+
const String tState = Mqtt_GetStateTopic(topicState);
337+
gHassDeviceBlock = ",\"device\":{\"identifiers\":[\"" + gDeviceId + "\"],\"name\":\"" + gDeviceId
338+
+ "\",\"manufacturer\":\"Leo Industries\",\"model\":\"ESPuino AT-1\",\"sw_version\":\"" + String(buildRevision)
339+
+ "\"},\"availability_topic\":\"" + tState + "\",\"payload_available\":\"Online\",\"payload_not_available\":\"Offline\"";
340+
341+
// sensors
342+
mqttHassPublish("sensor", "track", "\"name\":\"Track\",\"icon\":\"mdi:music-note\",\"state_topic\":\"" + String(Mqtt_GetStateTopic(topicTrack)) + "\"");
343+
mqttHassPublish("sensor", "status", "\"name\":\"Status\",\"icon\":\"mdi:play-pause\",\"state_topic\":\"" + String(Mqtt_GetStateTopic(topicPausePlay)) + "\"");
344+
mqttHassPublish("sensor", "wifi", "\"name\":\"WiFi signal\",\"device_class\":\"signal_strength\",\"unit_of_measurement\":\"dBm\",\"entity_category\":\"diagnostic\",\"state_topic\":\"" + String(Mqtt_GetStateTopic(topicWiFiRssi)) + "\"");
345+
mqttHassPublish("sensor", "firmware", "\"name\":\"Firmware update\",\"icon\":\"mdi:package-up\",\"entity_category\":\"diagnostic\",\"state_topic\":\"" + String(Mqtt_GetStateTopic(topicFirmwareUpdate)) + "\"");
346+
mqttHassPublish("sensor", "swrev", "\"name\":\"Software revision\",\"icon\":\"mdi:tag\",\"entity_category\":\"diagnostic\",\"state_topic\":\"" + String(Mqtt_GetStateTopic(topicSRevision)) + "\"");
347+
348+
// numbers
349+
const String loudCmd = Mqtt_GetCommandTopic(topicLoudness);
350+
mqttHassPublish("number", "volume", "\"name\":\"Volume\",\"icon\":\"mdi:volume-high\",\"min\":" + String(AudioPlayer_GetMinVolume()) + ",\"max\":" + String(AudioPlayer_GetMaxVolume()) + ",\"command_topic\":\"" + loudCmd + "\",\"state_topic\":\"" + String(Mqtt_GetStateTopic(topicLoudness)) + "\"");
351+
const String ledCmd = Mqtt_GetCommandTopic(topicLedBrightness);
352+
mqttHassPublish("number", "ledbrightness", "\"name\":\"LED brightness\",\"icon\":\"mdi:brightness-6\",\"entity_category\":\"config\",\"min\":0,\"max\":255,\"command_topic\":\"" + ledCmd + "\",\"state_topic\":\"" + String(Mqtt_GetStateTopic(topicLedBrightness)) + "\"");
353+
354+
// switches
355+
const String lockCmd = Mqtt_GetCommandTopic(topicLockControls);
356+
mqttHassPublish("switch", "lock", "\"name\":\"Lock controls\",\"icon\":\"mdi:lock\",\"payload_on\":\"ON\",\"payload_off\":\"OFF\",\"state_on\":\"ON\",\"state_off\":\"OFF\",\"command_topic\":\"" + lockCmd + "\",\"state_topic\":\"" + String(Mqtt_GetStateTopic(topicLockControls)) + "\"");
357+
mqttHassPublish("switch", "ambient", "\"name\":\"Ambient light\",\"icon\":\"mdi:lightbulb\",\"payload_on\":\"ON\",\"payload_off\":\"OFF\",\"optimistic\":true,\"command_topic\":\"" + String(Mqtt_GetCommandTopic(topicAmbientLight)) + "\"");
358+
359+
// select
360+
const String eqCmd = Mqtt_GetCommandTopic(topicEqualizer);
361+
mqttHassPublish("select", "equalizer", "\"name\":\"Equalizer\",\"icon\":\"mdi:equalizer\",\"options\":[\"flat\",\"music\",\"speech\",\"voiceBoost\"],\"command_topic\":\"" + eqCmd + "\",\"state_topic\":\"" + String(Mqtt_GetStateTopic(topicEqualizer)) + "\"");
362+
363+
// buttons
364+
const String trackCmd = Mqtt_GetCommandTopic(topicTrackControl);
365+
mqttHassPublish("button", "playpause", "\"name\":\"Play/Pause\",\"icon\":\"mdi:play-pause\",\"payload_press\":\"3\",\"command_topic\":\"" + trackCmd + "\"");
366+
mqttHassPublish("button", "next", "\"name\":\"Next track\",\"icon\":\"mdi:skip-next\",\"payload_press\":\"4\",\"command_topic\":\"" + trackCmd + "\"");
367+
mqttHassPublish("button", "prev", "\"name\":\"Previous track\",\"icon\":\"mdi:skip-previous\",\"payload_press\":\"5\",\"command_topic\":\"" + trackCmd + "\"");
368+
mqttHassPublish("button", "update", "\"name\":\"Update firmware\",\"icon\":\"mdi:package-up\",\"entity_category\":\"config\",\"payload_press\":\"update\",\"command_topic\":\"" + String(Mqtt_GetCommandTopic(topicFirmwareUpdate)) + "\"");
369+
mqttHassPublish("button", "shutdown", "\"name\":\"Shutdown\",\"icon\":\"mdi:power\",\"payload_press\":\"OFF\",\"command_topic\":\"" + String(Mqtt_GetCommandTopic(topicSleep)) + "\"");
370+
371+
#ifdef BATTERY_MEASURE_ENABLE
372+
mqttHassPublish("sensor", "battery_voltage", "\"name\":\"Battery voltage\",\"device_class\":\"voltage\",\"unit_of_measurement\":\"V\",\"entity_category\":\"diagnostic\",\"state_topic\":\"" + String(Mqtt_GetStateTopic(topicBatteryVoltage)) + "\"");
373+
mqttHassPublish("sensor", "battery", "\"name\":\"Battery\",\"device_class\":\"battery\",\"unit_of_measurement\":\"%\",\"entity_category\":\"diagnostic\",\"state_topic\":\"" + String(Mqtt_GetStateTopic(topicBatterySOC)) + "\"");
374+
#endif
375+
}
376+
#endif
377+
317378
// Is called if there's a new MQTT-message for us
318379
#ifdef MQTT_ENABLE
319380
void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) {
@@ -358,13 +419,16 @@ void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event
358419
// Firmware-update (GitHub OTA)
359420
esp_mqtt_client_subscribe(client, Mqtt_GetCommandTopic(topicFirmwareUpdate), qos);
360421

422+
// Home Assistant MQTT discovery (entities auto-register under one device)
423+
Mqtt_PublishHassDiscovery();
424+
361425
// Publish current state
362426
publishMqtt(topicState, "Online", false);
363427
publishMqtt(topicTrack, gPlayProperties.title, false);
364428
publishMqtt(topicCoverChanged, "", false);
365429
publishMqtt(topicLoudness, static_cast<uint32_t>(AudioPlayer_GetCurrentVolume()), false);
366430
publishMqtt(topicSleepTimer, System_GetSleepTimerTimeStamp(), false);
367-
publishMqtt(topicLockControls, static_cast<uint32_t>(System_AreControlsLocked()), false);
431+
publishMqtt(topicLockControls, System_AreControlsLocked() ? "ON" : "OFF", false);
368432
publishMqtt(topicPlaymode, static_cast<uint32_t>(gPlayProperties.playMode), false);
369433
if (gPlayProperties.playMode == NO_PLAYLIST) { // idle
370434
publishMqtt(topicPausePlay, "idle", false);

0 commit comments

Comments
 (0)