@@ -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
319380void 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