mirror of
https://github.com/esphome/esphome.git
synced 2026-07-10 17:05:36 +00:00
[mdns] Drop fallback paths, collapse everything under USE_MDNS_EVENT_DRIVEN_POLLING
mDNS on ESP8266/RP2040 always runs over a network interface (WiFi on ESP8266; WiFi or W5500/etc. ethernet on RP2040), and every such interface already publishes an IP state listener in tree. USE_MDNS_EVENT_DRIVEN_POLLING is therefore always defined in production, so the fallback set_interval() paths in both platform files and the was_connected_ bookkeeping are dead code. Also drop the ethernet-specific test — the existing wifi and ethernet+mdns combos are already covered by the mdns test fixtures paired with their network component tests. Trim redundant comments throughout: the header now documents the ~9s probe/announce window and why update() can stop afterward in a few lines instead of a full essay; platform files keep only the non-obvious bits (why RP2040 needs to drive begin()/notifyAPChange() itself, why re-arming on any listener notification is correct).
This commit is contained in:
@@ -169,15 +169,9 @@ async def to_code(config):
|
||||
elif CORE.is_rp2040:
|
||||
cg.add_library("LEAmDNS", None)
|
||||
|
||||
# ESP8266 and RP2040 use a network 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.
|
||||
#
|
||||
# We subscribe to any listener interface that's configured (WiFi and/or
|
||||
# Ethernet); the same on_ip_state() override satisfies both because the
|
||||
# listener signatures match. If neither is available the component falls
|
||||
# back to the legacy polling loop at MDNS_UPDATE_INTERVAL_MS.
|
||||
# Subscribe to the network IP state listener(s) so MDNS.update() is only
|
||||
# scheduled during the probe+announce phase. Same on_ip_state() override
|
||||
# serves both WiFi and Ethernet (signatures match).
|
||||
if CORE.is_esp8266 or CORE.is_rp2040:
|
||||
if "wifi" in CORE.config:
|
||||
from esphome.components import wifi
|
||||
|
||||
@@ -192,9 +192,8 @@ void MDNSComponent::compile_records_(StaticVector<MDNSService, MDNS_SERVICE_COUN
|
||||
|
||||
#ifdef USE_MDNS_EVENT_DRIVEN_POLLING
|
||||
void MDNSComponent::start_polling_window_() {
|
||||
// Re-arming replaces the previous window. The scheduler's set_interval/set_timeout
|
||||
// with a uint32_t ID already does atomic cancel-and-add for items sharing that ID
|
||||
// (see Scheduler::set_timer_common_), so no explicit cancel is needed.
|
||||
// uint32_t-ID set_interval/set_timeout already does atomic cancel-and-add, so
|
||||
// re-arming replaces the previous window implicitly.
|
||||
this->set_interval(MDNS_POLL_ID, MDNS_UPDATE_INTERVAL_MS, mdns_pump_update);
|
||||
this->set_timeout(MDNS_POLL_STOP_ID, MDNS_POLL_WINDOW_MS, [this]() { this->cancel_interval(MDNS_POLL_ID); });
|
||||
}
|
||||
|
||||
@@ -5,12 +5,8 @@
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
// Event-driven polling replaces the legacy set_interval() loop on platforms that need
|
||||
// scheduler-backed MDNS.update() (ESP8266, RP2040). It's enabled when at least one
|
||||
// compatible network IP state listener slot is available — the mdns Python to_code()
|
||||
// requests a WiFi slot when WiFi is in the config and an Ethernet slot when Ethernet
|
||||
// is in the config. If neither is available (e.g. clang-tidy running without full
|
||||
// codegen), the component falls back to the legacy polling loop.
|
||||
// On ESP8266 and RP2040 the scheduler-backed MDNS.update() polling window is armed by
|
||||
// IP state listener events on whichever network interface is configured.
|
||||
#if (defined(USE_ESP8266) || defined(USE_RP2040)) && \
|
||||
((defined(USE_WIFI) && defined(USE_WIFI_IP_STATE_LISTENERS)) || \
|
||||
(defined(USE_ETHERNET) && defined(USE_ETHERNET_IP_STATE_LISTENERS)))
|
||||
@@ -29,9 +25,8 @@
|
||||
namespace esphome::mdns {
|
||||
|
||||
#ifdef USE_MDNS_EVENT_DRIVEN_POLLING
|
||||
/// Call MDNS.update() on the target platform. Defined in the per-platform cpp file so
|
||||
/// the shared component code can drive the polling window without pulling in
|
||||
/// platform-specific mDNS headers.
|
||||
/// Platform-specific MDNS.update() trampoline. Defined in mdns_<platform>.cpp so the
|
||||
/// shared code can schedule it without including the platform's mDNS header.
|
||||
void mdns_pump_update();
|
||||
#endif
|
||||
|
||||
@@ -81,28 +76,13 @@ class MDNSComponent final : public Component
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
// 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
|
||||
// are NOT affected by how often update() is called.
|
||||
//
|
||||
// The work has a bounded lifetime: after MDNS.begin() (or _restart() triggered by a
|
||||
// network interface change) the library sends 3 probes 250ms apart followed by 8
|
||||
// announcements 1000ms apart, after which all internal timeouts are set to
|
||||
// resetToNeverExpires(). ESPHome does not issue mDNS service queries, so the service
|
||||
// query cache is always empty. Every subsequent update() call is pure overhead.
|
||||
//
|
||||
// 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.
|
||||
static constexpr uint32_t MDNS_POLL_WINDOW_MS = 12000;
|
||||
// Scheduler IDs (uint32_t variants avoid name hashing/strcmp on cancel paths)
|
||||
// LEAmDNS has meaningful work only during the probe+announce phase (3×250ms probes +
|
||||
// 8×1000ms announces, ~9s). Afterwards every internal timer is resetToNeverExpires()
|
||||
// and update() becomes pure overhead. We arm a bounded polling window from IP state
|
||||
// listener events so update() runs only during that phase.
|
||||
static constexpr uint32_t MDNS_UPDATE_INTERVAL_MS = 50;
|
||||
static constexpr uint32_t MDNS_POLL_WINDOW_MS = 12000; // ~9s phase + jitter/restart margin
|
||||
static constexpr uint32_t MDNS_POLL_ID = 0;
|
||||
static constexpr uint32_t MDNS_POLL_STOP_ID = 1;
|
||||
#endif
|
||||
@@ -133,8 +113,8 @@ class MDNSComponent final : public Component
|
||||
|
||||
protected:
|
||||
#ifdef USE_MDNS_EVENT_DRIVEN_POLLING
|
||||
/// Arm a bounded polling window so MDNS.update() runs at MDNS_UPDATE_INTERVAL_MS
|
||||
/// for MDNS_POLL_WINDOW_MS. A subsequent call replaces the previous window.
|
||||
/// Arm a fresh MDNS_POLL_WINDOW_MS polling window. Idempotent — re-arming replaces
|
||||
/// the previous window via the scheduler's atomic cancel-and-add on matching IDs.
|
||||
void start_polling_window_();
|
||||
#endif
|
||||
/// Helper to set up services and MAC buffers, then call platform-specific registration
|
||||
@@ -181,9 +161,6 @@ class MDNSComponent final : public Component
|
||||
#endif
|
||||
#ifdef USE_RP2040
|
||||
bool initialized_{false};
|
||||
#if !defined(USE_MDNS_EVENT_DRIVEN_POLLING)
|
||||
bool was_connected_{false};
|
||||
#endif
|
||||
#endif
|
||||
void compile_records_(StaticVector<MDNSService, MDNS_SERVICE_COUNT> &services, char *mac_address_buf);
|
||||
};
|
||||
|
||||
@@ -8,9 +8,7 @@
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "mdns_component.h"
|
||||
#ifdef USE_MDNS_WIFI_LISTENER
|
||||
#include "esphome/components/wifi/wifi_component.h"
|
||||
#endif
|
||||
|
||||
namespace esphome::mdns {
|
||||
|
||||
@@ -39,41 +37,24 @@ 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.
|
||||
// ESP8266 has no ethernet driver in the Arduino build so only the WiFi listener
|
||||
// branch is reachable here.
|
||||
// LEAmDNS's own LwipIntf::statusChangeCB drives _restart() on netif changes; we only
|
||||
// need to arm the polling window around the initial probe/announce and each reconnect.
|
||||
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
|
||||
// ESP8266 — see wifi_component_esp8266.cpp), not on IP loss, so every notification
|
||||
// represents a fresh IP that the LEAmDNS library's lwIP callback will trigger a
|
||||
// _restart() for. Always re-arm the polling window — start_polling_window_() is
|
||||
// idempotent (scheduler does atomic cancel-and-add on matching IDs).
|
||||
// IP listener only fires on acquisition (not loss), so any notification is a fresh
|
||||
// IP worth re-arming for. start_polling_window_() is idempotent.
|
||||
if (ips[0].is_set()) {
|
||||
this->start_polling_window_();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void MDNSComponent::on_shutdown() {
|
||||
MDNS.close();
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
#endif
|
||||
|
||||
// Arduino-Pico's PolledTimeout.h (pulled in by ESP8266mDNS.h) redefines IRAM_ATTR to empty.
|
||||
// Save and restore our definition around the include to avoid a redefinition warning.
|
||||
#pragma push_macro("IRAM_ATTR")
|
||||
#undef IRAM_ATTR
|
||||
#include <ESP8266mDNS.h>
|
||||
@@ -26,10 +25,7 @@ static void register_rp2040(MDNSComponent *, StaticVector<MDNSService, MDNS_SERV
|
||||
MDNS.begin(App.get_name().c_str());
|
||||
|
||||
for (const auto &service : services) {
|
||||
// Strip the leading underscore from the proto and service_type. While it is
|
||||
// part of the wire protocol to have an underscore, and for example ESP-IDF
|
||||
// expects the underscore to be there, the ESP8266 implementation always adds
|
||||
// the underscore itself.
|
||||
// ESP8266mDNS always adds the leading underscore itself, so strip it here.
|
||||
auto *proto = MDNS_STR_ARG(service.proto);
|
||||
while (*proto == '_') {
|
||||
proto++;
|
||||
@@ -46,28 +42,17 @@ static void register_rp2040(MDNSComponent *, StaticVector<MDNSService, MDNS_SERV
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_MDNS_EVENT_DRIVEN_POLLING
|
||||
void mdns_pump_update() { MDNS.update(); }
|
||||
#endif
|
||||
|
||||
void MDNSComponent::setup() {
|
||||
// RP2040's LEAmDNS library registers a LwipIntf::stateUpCB() callback to restart
|
||||
// mDNS when the network interface reconnects. However, stateUpCB() is stubbed out
|
||||
// in arduino-pico's LwipIntfCB.cpp because the original ESP8266 implementation used
|
||||
// schedule_function() which doesn't exist in arduino-pico, and the callback can't
|
||||
// safely run directly since netif status callbacks fire from IRQ context
|
||||
// (PICO_CYW43_ARCH_THREADSAFE_BACKGROUND) while _restart() allocates UDP sockets.
|
||||
//
|
||||
// Workaround: defer MDNS.begin() and service registration until the network has an IP,
|
||||
// then call notifyAPChange() on subsequent reconnects to restart mDNS probing and
|
||||
// announcing — all from main loop context via the IP state listener callback(s) so
|
||||
// it's thread-safe. We subscribe to any listener interface that's active (WiFi and/or
|
||||
// Ethernet); the same on_ip_state() override serves both because the signatures match.
|
||||
#ifdef USE_MDNS_EVENT_DRIVEN_POLLING
|
||||
// arduino-pico stubs out LwipIntf::stateUpCB (the netif status callback LEAmDNS uses
|
||||
// on ESP8266 for auto-restart), so we must drive begin()/notifyAPChange() from our
|
||||
// own IP state listener. Both WiFi and Ethernet have the same listener signature —
|
||||
// one on_ip_state() override serves both.
|
||||
#ifdef USE_MDNS_WIFI_LISTENER
|
||||
wifi::global_wifi_component->add_ip_state_listener(this);
|
||||
// AFTER_CONNECTION priority means the network may already be up when setup() runs;
|
||||
// the listener only fires on subsequent state changes, so seed the current state.
|
||||
// AFTER_CONNECTION priority means the network may already be up; the listener only
|
||||
// fires on subsequent changes, so seed the current state.
|
||||
{
|
||||
const auto ips = wifi::global_wifi_component->wifi_sta_ip_addresses();
|
||||
if (ips[0].is_set()) {
|
||||
@@ -78,8 +63,6 @@ void MDNSComponent::setup() {
|
||||
#endif
|
||||
#ifdef USE_MDNS_ETHERNET_LISTENER
|
||||
ethernet::global_eth_component->add_ip_state_listener(this);
|
||||
// Seed current Ethernet state for the same reason — if the interface is already up
|
||||
// when mdns setup() runs, we need to kick off begin() + polling here.
|
||||
if (ethernet::global_eth_component->is_connected()) {
|
||||
const auto ips = ethernet::global_eth_component->get_ip_addresses();
|
||||
if (ips[0].is_set()) {
|
||||
@@ -87,35 +70,11 @@ void MDNSComponent::setup() {
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#else
|
||||
// Fallback (no IP state listener available): poll forever, checking connection state
|
||||
// each tick.
|
||||
this->set_interval(MDNS_UPDATE_INTERVAL_MS, [this]() {
|
||||
bool connected = network::is_connected();
|
||||
if (connected && !this->was_connected_) {
|
||||
if (!this->initialized_) {
|
||||
this->setup_buffers_and_register_(register_rp2040);
|
||||
this->initialized_ = true;
|
||||
} else {
|
||||
MDNS.notifyAPChange();
|
||||
}
|
||||
}
|
||||
this->was_connected_ = connected;
|
||||
if (this->initialized_) {
|
||||
MDNS.update();
|
||||
}
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef USE_MDNS_EVENT_DRIVEN_POLLING
|
||||
void MDNSComponent::on_ip_state(const network::IPAddresses &ips, const network::IPAddress &,
|
||||
const network::IPAddress &) {
|
||||
// Both WiFi and Ethernet IP state listeners only notify on IP acquisition (see
|
||||
// wifi_component_pico_w.cpp and ethernet_component.cpp), not on IP loss, so every
|
||||
// notification represents a fresh IP that needs a probe/announce cycle. The library's
|
||||
// internal LwipIntf::stateUpCB is stubbed out on arduino-pico (see setup()), so we
|
||||
// drive begin/restart ourselves from this callback.
|
||||
// Listener only fires on IP acquisition (not loss); every event is a fresh IP.
|
||||
if (!ips[0].is_set()) {
|
||||
return;
|
||||
}
|
||||
@@ -127,7 +86,6 @@ void MDNSComponent::on_ip_state(const network::IPAddresses &ips, const network::
|
||||
}
|
||||
this->start_polling_window_();
|
||||
}
|
||||
#endif
|
||||
|
||||
void MDNSComponent::on_shutdown() {
|
||||
MDNS.close();
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
ethernet:
|
||||
type: W5500
|
||||
clk_pin: 18
|
||||
mosi_pin: 19
|
||||
miso_pin: 16
|
||||
cs_pin: 17
|
||||
interrupt_pin: 21
|
||||
reset_pin: 20
|
||||
manual_ip:
|
||||
static_ip: 192.168.178.56
|
||||
gateway: 192.168.178.1
|
||||
subnet: 255.255.255.0
|
||||
domain: .local
|
||||
mac_address: "02:AA:BB:CC:DD:01"
|
||||
|
||||
mdns:
|
||||
disabled: false
|
||||
services:
|
||||
- service: _test_service
|
||||
protocol: _tcp
|
||||
port: 8888
|
||||
txt:
|
||||
static_string: Anything
|
||||
@@ -1 +0,0 @@
|
||||
<<: !include common-enabled-ethernet.yaml
|
||||
Reference in New Issue
Block a user