mirror of
https://github.com/esphome/esphome.git
synced 2026-07-10 17:05:36 +00:00
[mdns] Drive event-driven polling from Ethernet IP state events on RP2040
Extends the WiFi-only listener pattern from the previous commit to also subscribe to EthernetIPStateListener when Ethernet is configured. RP2040 can run mDNS over a W5500 ethernet shield without WiFi, and mDNS and WiFi are mutually exclusive on RP2040 (the framework doesn't support both simultaneously on the CYW43/PIO paths), so this adds the ethernet-only path without touching the WiFi path. ESPHome's wifi and ethernet components already publish compatible IP state listener APIs (`WiFiIPStateListener::on_ip_state` and `EthernetIPStateListener::on_ip_state` with identical signatures). MDNSComponent multiply-inherits both when available; a single on_ip_state() override satisfies both vtable entries. - New `USE_MDNS_WIFI_LISTENER` / `USE_MDNS_ETHERNET_LISTENER` gates control per- interface subscription. `USE_MDNS_EVENT_DRIVEN_POLLING` fires if either is available. - Python side now calls `ethernet.request_ethernet_ip_state_listener()` when ethernet is in the config (RP2040 only — ESP8266 has no ethernet driver). - setup() seeds current state for each registered listener so an already-up interface still triggers MDNS.begin() + polling window under AFTER_CONNECTION priority. Tests: adds `test-enabled-ethernet.rp2040-ard.yaml` covering the ethernet-only path. Existing `test-enabled.rp2040-ard.yaml` (WiFi-only) and ESP8266 tests continue to pass.
This commit is contained in:
@@ -169,16 +169,24 @@ async def to_code(config):
|
||||
elif CORE.is_rp2040:
|
||||
cg.add_library("LEAmDNS", None)
|
||||
|
||||
# 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. 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
|
||||
# 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.
|
||||
if CORE.is_esp8266 or CORE.is_rp2040:
|
||||
if "wifi" in CORE.config:
|
||||
from esphome.components import wifi
|
||||
|
||||
wifi.request_wifi_ip_state_listener()
|
||||
wifi.request_wifi_ip_state_listener()
|
||||
if CORE.is_rp2040 and "ethernet" in CORE.config:
|
||||
from esphome.components import ethernet
|
||||
|
||||
ethernet.request_ethernet_ip_state_listener()
|
||||
|
||||
if CORE.is_esp32:
|
||||
add_idf_component(name="espressif/mdns", ref="1.11.0")
|
||||
|
||||
@@ -6,14 +6,24 @@
|
||||
#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 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)
|
||||
// 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.
|
||||
#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)))
|
||||
#include "esphome/components/network/ip_address.h"
|
||||
#include "esphome/components/wifi/wifi_component.h"
|
||||
#define USE_MDNS_EVENT_DRIVEN_POLLING
|
||||
#if defined(USE_WIFI) && defined(USE_WIFI_IP_STATE_LISTENERS)
|
||||
#include "esphome/components/wifi/wifi_component.h"
|
||||
#define USE_MDNS_WIFI_LISTENER
|
||||
#endif
|
||||
#if defined(USE_ETHERNET) && defined(USE_ETHERNET_IP_STATE_LISTENERS)
|
||||
#include "esphome/components/ethernet/ethernet_component.h"
|
||||
#define USE_MDNS_ETHERNET_LISTENER
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace esphome::mdns {
|
||||
@@ -58,10 +68,14 @@ struct MDNSService {
|
||||
};
|
||||
|
||||
class MDNSComponent final : public Component
|
||||
#ifdef USE_MDNS_EVENT_DRIVEN_POLLING
|
||||
#ifdef USE_MDNS_WIFI_LISTENER
|
||||
,
|
||||
public wifi::WiFiIPStateListener
|
||||
#endif
|
||||
#ifdef USE_MDNS_ETHERNET_LISTENER
|
||||
,
|
||||
public ethernet::EthernetIPStateListener
|
||||
#endif
|
||||
{
|
||||
public:
|
||||
void setup() override;
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
#ifdef USE_MDNS_EVENT_DRIVEN_POLLING
|
||||
#include "mdns_component.h"
|
||||
#ifdef USE_MDNS_WIFI_LISTENER
|
||||
#include "esphome/components/wifi/wifi_component.h"
|
||||
#endif
|
||||
#include "mdns_component.h"
|
||||
|
||||
namespace esphome::mdns {
|
||||
|
||||
@@ -50,6 +50,8 @@ void MDNSComponent::setup() {
|
||||
// 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.
|
||||
wifi::global_wifi_component->add_ip_state_listener(this);
|
||||
this->start_polling_window_();
|
||||
#else
|
||||
|
||||
@@ -6,9 +6,12 @@
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "mdns_component.h"
|
||||
#ifdef USE_MDNS_EVENT_DRIVEN_POLLING
|
||||
#ifdef USE_MDNS_WIFI_LISTENER
|
||||
#include "esphome/components/wifi/wifi_component.h"
|
||||
#endif
|
||||
#ifdef USE_MDNS_ETHERNET_LISTENER
|
||||
#include "esphome/components/ethernet/ethernet_component.h"
|
||||
#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.
|
||||
@@ -57,19 +60,36 @@ void MDNSComponent::setup() {
|
||||
//
|
||||
// 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 WiFiIPStateListener callback so it's
|
||||
// thread-safe.
|
||||
// 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
|
||||
#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.
|
||||
const auto ips = wifi::global_wifi_component->wifi_sta_ip_addresses();
|
||||
if (ips[0].is_set()) {
|
||||
this->on_ip_state(ips, wifi::global_wifi_component->get_dns_address(0),
|
||||
wifi::global_wifi_component->get_dns_address(1));
|
||||
{
|
||||
const auto ips = wifi::global_wifi_component->wifi_sta_ip_addresses();
|
||||
if (ips[0].is_set()) {
|
||||
this->on_ip_state(ips, wifi::global_wifi_component->get_dns_address(0),
|
||||
wifi::global_wifi_component->get_dns_address(1));
|
||||
}
|
||||
}
|
||||
#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()) {
|
||||
this->on_ip_state(ips, network::IPAddress{}, network::IPAddress{});
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#else
|
||||
// Fallback (non-WiFi build): poll forever, checking connection state each tick.
|
||||
// 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_) {
|
||||
@@ -91,11 +111,11 @@ void MDNSComponent::setup() {
|
||||
#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 (see
|
||||
// wifi_component_pico_w.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.
|
||||
// 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.
|
||||
if (!ips[0].is_set()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
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
|
||||
@@ -0,0 +1 @@
|
||||
<<: !include common-enabled-ethernet.yaml
|
||||
Reference in New Issue
Block a user