[mdns] Fall back to legacy polling when WiFi IP state listener isn't available

clang-tidy CI compiles the source with the esp8266-arduino-tidy env's raw build
flags (-DUSE_ESP8266 only) without running the Python codegen that adds
USE_WIFI_IP_STATE_LISTENERS. The previous guard assumed USE_WIFI_IP_STATE_LISTENERS
would always be defined on ESP8266, so clang-tidy failed with 'no member named
add_ip_state_listener in wifi::WiFiComponent'.

Gate USE_MDNS_EVENT_DRIVEN_POLLING on USE_WIFI + USE_WIFI_IP_STATE_LISTENERS for
both ESP8266 and RP2040. When either is absent, fall back to the pre-PR behaviour:
set_interval(MDNS_UPDATE_INTERVAL_MS, MDNS.update) running forever. Python side
already only requests the listener slot when WiFi is in the config, so real
production builds on ESP8266 (which always have WiFi) continue to use the
event-driven path — only the clang-tidy static-analysis build takes the fallback.
This commit is contained in:
J. Nick Koston
2026-04-23 19:36:11 -05:00
parent 25601434d9
commit 5cb258034b
3 changed files with 27 additions and 18 deletions
+4 -7
View File
@@ -172,13 +172,10 @@ async def to_code(config):
# ESP8266 and RP2040 use a WiFi IP state listener to arm a bounded MDNS.update()
# polling window only while the library is in its probe+announce phase. This
# eliminates the steady-state 50ms interval that ran forever (1200+ dispatches
# per minute) and its scheduler overhead.
#
# ESP8266 has no ethernet driver in the Arduino build, so it's always a WiFi
# device — the listener is unconditional. RP2040 supports the W5500 ethernet
# shield without WiFi, so the listener is requested only when WiFi is present;
# ethernet-only RP2040 builds fall back to the legacy polling loop.
if CORE.is_esp8266 or (CORE.is_rp2040 and "wifi" in CORE.config):
# per minute) and its scheduler overhead. When WiFi is absent (e.g. an
# ethernet-only RP2040 build) the component falls back to the legacy polling
# loop at MDNS_UPDATE_INTERVAL_MS.
if (CORE.is_esp8266 or CORE.is_rp2040) and "wifi" in CORE.config:
from esphome.components import wifi
wifi.request_wifi_ip_state_listener()
+11 -11
View File
@@ -5,13 +5,12 @@
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
// Event-driven polling is used whenever the scheduler-backed MDNS.update() interval
// needs to be gated on network state (ESP8266, or RP2040 with WiFi). ESP8266 mDNS
// always runs over WiFi — there is no ethernet driver for ESP8266 in the Arduino
// build — so this path is unconditional on ESP8266. RP2040 can run mDNS over the
// W5500 ethernet shield without WiFi, so it falls back to the legacy polling loop
// when WiFi is absent.
#if defined(USE_ESP8266) || (defined(USE_RP2040) && defined(USE_WIFI) && defined(USE_WIFI_IP_STATE_LISTENERS))
// Event-driven polling replaces the legacy set_interval() loop on platforms that need
// scheduler-backed MDNS.update() (ESP8266, RP2040). It's enabled when a WiFi IP state
// listener slot is available — the mdns Python to_code() requests one when WiFi is in
// the config. If it's not (e.g. clang-tidy running without full codegen, or an
// ethernet-only RP2040 build), the component falls back to the legacy polling loop.
#if (defined(USE_ESP8266) || defined(USE_RP2040)) && defined(USE_WIFI) && defined(USE_WIFI_IP_STATE_LISTENERS)
#include "esphome/components/network/ip_address.h"
#include "esphome/components/wifi/wifi_component.h"
#define USE_MDNS_EVENT_DRIVEN_POLLING
@@ -68,7 +67,6 @@ class MDNSComponent final : public Component
void setup() override;
void dump_config() override;
#ifdef USE_MDNS_EVENT_DRIVEN_POLLING
// On ESP8266 and RP2040, MDNS.update() calls _process(true) which only manages
// timer-driven state machines (probe/announce timeouts and service query cache TTLs).
// Incoming mDNS packets are handled independently via the lwIP onRx UDP callback and
@@ -80,10 +78,12 @@ class MDNSComponent final : public Component
// resetToNeverExpires(). ESPHome does not issue mDNS service queries, so the service
// query cache is always empty. Every subsequent update() call is pure overhead.
//
// Instead of polling forever, we arm a bounded polling window driven by
// WiFiIPStateListener events. A fresh window covers each probe/announce cycle that
// follows initial connect or reconnect; outside the window no update() calls occur.
// When USE_MDNS_EVENT_DRIVEN_POLLING is defined we arm a bounded polling window from
// WiFiIPStateListener events so update() only runs during the probe+announce phase;
// outside that window no update() calls occur. Otherwise (fallback), we poll at
// MDNS_UPDATE_INTERVAL_MS forever.
static constexpr uint32_t MDNS_UPDATE_INTERVAL_MS = 50;
#ifdef USE_MDNS_EVENT_DRIVEN_POLLING
// Boot probe+announce phase is ~9.0s (3*250ms probes + 8*1000ms announces). Window
// includes margin for the initial `rand() % MDNS_PROBE_DELAY` jitter and for the
// debounced internal restart triggered by netif status changes on ESP8266.
+12
View File
@@ -7,7 +7,9 @@
#include "esphome/core/application.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#ifdef USE_MDNS_EVENT_DRIVEN_POLLING
#include "esphome/components/wifi/wifi_component.h"
#endif
#include "mdns_component.h"
namespace esphome::mdns {
@@ -37,18 +39,27 @@ static void register_esp8266(MDNSComponent *, StaticVector<MDNSService, MDNS_SER
}
}
#ifdef USE_MDNS_EVENT_DRIVEN_POLLING
void mdns_pump_update() { MDNS.update(); }
#endif
void MDNSComponent::setup() {
this->setup_buffers_and_register_(register_esp8266);
#ifdef USE_MDNS_EVENT_DRIVEN_POLLING
// Arduino LEAmDNS registers its own LwipIntf::statusChangeCB that calls _restart()
// on every netif status change (link up, IP up, etc.), so we don't trigger begin()
// or restart here — we just cover the probe+announce window with a bounded polling
// schedule. The listener catches subsequent reconnects and re-arms the window.
wifi::global_wifi_component->add_ip_state_listener(this);
this->start_polling_window_();
#else
// Fallback for builds without a WiFi IP state listener (e.g. clang-tidy without
// codegen defines). Matches the pre-PR behaviour: poll forever at 50ms.
this->set_interval(MDNS_UPDATE_INTERVAL_MS, []() { MDNS.update(); });
#endif
}
#ifdef USE_MDNS_EVENT_DRIVEN_POLLING
void MDNSComponent::on_ip_state(const network::IPAddresses &ips, const network::IPAddress &,
const network::IPAddress &) {
// ESPHome's WiFiIPStateListener only notifies on IP acquisition (GOT_IP events on
@@ -60,6 +71,7 @@ void MDNSComponent::on_ip_state(const network::IPAddresses &ips, const network::
this->start_polling_window_();
}
}
#endif
void MDNSComponent::on_shutdown() {
MDNS.close();