1515
1616#ifdef ESP_PLATFORM
1717#include < esp_wifi.h>
18+ #include < esp_netif.h>
1819#include < esp_heap_caps.h>
1920#include < freertos/FreeRTOS.h>
2021#include < freertos/task.h>
@@ -179,6 +180,10 @@ static unsigned long s_wifi_connected_at = 0;
179180static uint8_t s_wifi_disconnect_reason = 0 ;
180181static unsigned long s_wifi_disconnect_time = 0 ;
181182
183+ // Most recent global/unique-local IPv6 address (SLAAC), as a string; empty when none.
184+ // Populated from the ARDUINO_EVENT_WIFI_STA_GOT_IP6 handler; cleared on WiFi disconnect.
185+ static char s_global_ipv6[46 ] = " " ;
186+
182187#ifdef MQTT_MEMORY_DEBUG
183188// #region agent log
184189static void agentLogHeap (const char * location, const char * message, const char * hypothesisId,
@@ -201,6 +206,17 @@ unsigned long MQTTBridge::getWifiConnectedAtMillis() {
201206 return s_wifi_connected_at;
202207}
203208
209+ bool MQTTBridge::getGlobalIPv6 (char * buf, size_t len) {
210+ if (buf == nullptr || len == 0 ) return false ;
211+ if (s_global_ipv6[0 ] == ' \0 ' ) {
212+ buf[0 ] = ' \0 ' ;
213+ return false ;
214+ }
215+ strncpy (buf, s_global_ipv6, len - 1 );
216+ buf[len - 1 ] = ' \0 ' ;
217+ return true ;
218+ }
219+
204220void MQTTBridge::formatMqttStatusReply (char * buf, size_t bufsize, const MQTTPrefs* obs) {
205221 if (buf == nullptr || bufsize == 0 ) return ;
206222 const char * msgs = (obs && obs->mqtt_status_enabled ) ? " on" : " off" ;
@@ -829,11 +845,23 @@ void MQTTBridge::initializeWiFiInTask() {
829845 switch (event) {
830846 case ARDUINO_EVENT_WIFI_STA_GOT_IP :
831847 MQTT_DEBUG_PRINTLN (" WiFi connected: %s" , IPAddress (info.got_ip .ip_info .ip .addr ).toString ().c_str ());
848+ // Kick off IPv6 link-local + RA/SLAAC (additive; IPv4 path unchanged). Idempotent.
849+ WiFi.enableIpV6 ();
832850 // Set flag to trigger NTP sync from loop() instead of doing it here
833851 if (!_ntp_synced && !_ntp_sync_pending) {
834852 _ntp_sync_pending = true ;
835853 }
836854 break ;
855+ case ARDUINO_EVENT_WIFI_STA_GOT_IP6 : {
856+ // Store only global/unique-local addresses; link-local isn't useful for diagnostics.
857+ esp_ip6_addr_t ip6 = info.got_ip6 .ip6_info .ip ;
858+ esp_ip6_addr_type_t type = esp_netif_ip6_get_addr_type (&ip6);
859+ if (type == ESP_IP6_ADDR_IS_GLOBAL || type == ESP_IP6_ADDR_IS_UNIQUE_LOCAL ) {
860+ snprintf (s_global_ipv6, sizeof (s_global_ipv6), IPV6STR , IPV62STR (ip6));
861+ MQTT_DEBUG_PRINTLN (" WiFi IPv6: %s" , s_global_ipv6);
862+ }
863+ break ;
864+ }
837865 case ARDUINO_EVENT_WIFI_STA_DISCONNECTED :
838866 s_wifi_disconnect_reason = info.wifi_sta_disconnected .reason ;
839867 s_wifi_disconnect_time = millis ();
@@ -1325,7 +1353,16 @@ void MQTTBridge::setupSlot(int index) {
13251353 } else if (slot.port == 443 ) {
13261354 proto = " wss" ;
13271355 }
1328- snprintf (slot.broker_uri , sizeof (slot.broker_uri ), " %s://%s:%d" , proto, slot.host , slot.port );
1356+ // Bare IPv6 literals (contain ':' but no '.', not already bracketed) must be wrapped
1357+ // in [..] so the ":port" suffix isn't mistaken for part of the address.
1358+ bool bare_ipv6 = (slot.host [0 ] != ' [' ) &&
1359+ (strchr (slot.host , ' :' ) != nullptr ) &&
1360+ (strchr (slot.host , ' .' ) == nullptr );
1361+ if (bare_ipv6) {
1362+ snprintf (slot.broker_uri , sizeof (slot.broker_uri ), " %s://[%s]:%d" , proto, slot.host , slot.port );
1363+ } else {
1364+ snprintf (slot.broker_uri , sizeof (slot.broker_uri ), " %s://%s:%d" , proto, slot.host , slot.port );
1365+ }
13291366 }
13301367 slot.client ->setServer (slot.broker_uri );
13311368 MQTT_DEBUG_PRINTLN (" MQTT%d custom broker URI: %s (host='%s', port=%u)" ,
@@ -2080,6 +2117,9 @@ bool MQTTBridge::handleWiFiConnection(unsigned long now) {
20802117 _wifi_disconnected_time = 0 ;
20812118 s_wifi_connected_at = now;
20822119 _wifi_reconnect_backoff_attempt = 0 ;
2120+ // Re-arm IPv6 link-local after a reconnect (covers paths that re-run WiFi.begin
2121+ // and skip the GOT_IP event ordering). Idempotent.
2122+ WiFi.enableIpV6 ();
20832123 #ifdef ESP_PLATFORM
20842124 wifi_ps_type_t ps_mode;
20852125 uint8_t ps_pref = _obs->wifi_power_save ;
@@ -2106,6 +2146,7 @@ bool MQTTBridge::handleWiFiConnection(unsigned long now) {
21062146 if (_last_wifi_status == WL_CONNECTED ) {
21072147 _wifi_disconnected_time = now;
21082148 s_wifi_connected_at = 0 ;
2149+ s_global_ipv6[0 ] = ' \0 ' ; // drop stale IPv6 so get wifi.status doesn't report it
21092150 // Disconnect all slot clients when WiFi drops
21102151 for (int i = 0 ; i < RUNTIME_MQTT_SLOTS ; i++) {
21112152 if (_slots[i].client && _slots[i].connected ) {
0 commit comments