From 00872d433696fe775b40f2b01883c32fdccff4c1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 12 Oct 2026 03:56:53 -1000 Subject: [PATCH] [ethernet] Skip setters that pass the default PHY address, MDC, MDIO, clock and SPI interface (#20070) --- esphome/components/ethernet/__init__.py | 43 +++++++++++----- .../components/ethernet/ethernet_component.h | 12 ++--- .../ethernet/config/rmii_custom.yaml | 14 ++++++ .../ethernet/config/rmii_defaults.yaml | 13 +++++ .../ethernet/config/spi_s3_own_bus.yaml | 14 ++++++ .../ethernet/test_default_setters.py | 49 +++++++++++++++++++ 6 files changed, 128 insertions(+), 17 deletions(-) create mode 100644 tests/component_tests/ethernet/config/rmii_custom.yaml create mode 100644 tests/component_tests/ethernet/config/rmii_defaults.yaml create mode 100644 tests/component_tests/ethernet/config/spi_s3_own_bus.yaml create mode 100644 tests/component_tests/ethernet/test_default_setters.py diff --git a/esphome/components/ethernet/__init__.py b/esphome/components/ethernet/__init__.py index 58915ff96db..31e4952d8e0 100644 --- a/esphome/components/ethernet/__init__.py +++ b/esphome/components/ethernet/__init__.py @@ -214,6 +214,15 @@ CLK_MODES = { spi_host_device_t = cg.global_ns.enum("spi_host_device_t") +# Must match the member initializers in ethernet_component.h; setters are skipped +# when the config already equals them. +DEFAULT_SPI_INTERFACE = "spi2" +DEFAULT_PHY_ADDR = 0 +DEFAULT_MDC_PIN = 23 +DEFAULT_MDIO_PIN = 18 +DEFAULT_CLK_MODE = "CLK_EXT_IN" +DEFAULT_CLK_PIN = 0 + SPI_INTERFACE_MAP = { "spi2": spi_host_device_t.SPI2_HOST, "spi3": spi_host_device_t.SPI3_HOST, @@ -437,7 +446,9 @@ RMII_SCHEMA = cv.All( " GPIO17_OUT -> mode: CLK_OUT, pin: GPIO17" ), cv.Required(CONF_CLK): CLK_SCHEMA, - cv.Optional(CONF_PHY_ADDR, default=0): cv.int_range(min=0, max=31), + cv.Optional(CONF_PHY_ADDR, default=DEFAULT_PHY_ADDR): cv.int_range( + min=0, max=31 + ), cv.Optional(CONF_POWER_PIN): pins.internal_gpio_output_pin_number, cv.Optional(CONF_PHY_REGISTERS): cv.ensure_list(PHY_REGISTER_SCHEMA), } @@ -454,7 +465,9 @@ GENERIC_SCHEMA = cv.All( { cv.Required(CONF_MDC_PIN): pins.internal_gpio_output_pin_number, cv.Required(CONF_MDIO_PIN): pins.internal_gpio_output_pin_number, - cv.Optional(CONF_PHY_ADDR, default=0): cv.int_range(min=0, max=31), + cv.Optional(CONF_PHY_ADDR, default=DEFAULT_PHY_ADDR): cv.int_range( + min=0, max=31 + ), cv.Optional(CONF_POWER_PIN): pins.internal_gpio_output_pin_number, cv.Optional(CONF_PHY_REGISTERS): cv.ensure_list(PHY_REGISTER_SCHEMA), } @@ -683,6 +696,15 @@ async def to_code(config: ConfigType) -> None: CORE.add_job(final_step) +def _add_mdio_setters(var: cg.MockObj, config: ConfigType) -> None: + if (phy_addr := config[CONF_PHY_ADDR]) != DEFAULT_PHY_ADDR: + cg.add(var.set_phy_addr(phy_addr)) + if (mdc_pin := config[CONF_MDC_PIN]) != DEFAULT_MDC_PIN: + cg.add(var.set_mdc_pin(mdc_pin)) + if (mdio_pin := config[CONF_MDIO_PIN]) != DEFAULT_MDIO_PIN: + cg.add(var.set_mdio_pin(mdio_pin)) + + async def _to_code_esp32(var: cg.MockObj, config: ConfigType) -> None: from esphome.components.esp32 import ( add_idf_component, @@ -701,7 +723,8 @@ async def _to_code_esp32(var: cg.MockObj, config: ConfigType) -> None: cg.add(var.set_clk_pin(config[CONF_CLK_PIN])) cg.add(var.set_miso_pin(config[CONF_MISO_PIN])) cg.add(var.set_mosi_pin(config[CONF_MOSI_PIN])) - cg.add(var.set_interface(SPI_INTERFACE_MAP[config[CONF_INTERFACE]])) + if (interface := config[CONF_INTERFACE]) != DEFAULT_SPI_INTERFACE: + cg.add(var.set_interface(SPI_INTERFACE_MAP[interface])) cg.add(var.set_cs_pin(config[CONF_CS_PIN])) if CONF_INTERRUPT_PIN in config: cg.add(var.set_interrupt_pin(config[CONF_INTERRUPT_PIN])) @@ -730,18 +753,16 @@ async def _to_code_esp32(var: cg.MockObj, config: ConfigType) -> None: add_idf_sdkconfig_option("CONFIG_ETH_USE_OPENETH", True) elif config[CONF_TYPE] in ("GENERIC", "YT8531"): # RGMII data pins come from the IDF default config; set MDC/MDIO + PHY addr. - cg.add(var.set_phy_addr(config[CONF_PHY_ADDR])) - cg.add(var.set_mdc_pin(config[CONF_MDC_PIN])) - cg.add(var.set_mdio_pin(config[CONF_MDIO_PIN])) + _add_mdio_setters(var, config) if CONF_POWER_PIN in config: cg.add(var.set_power_pin(config[CONF_POWER_PIN])) _add_phy_registers(var, config) else: - cg.add(var.set_phy_addr(config[CONF_PHY_ADDR])) - cg.add(var.set_mdc_pin(config[CONF_MDC_PIN])) - cg.add(var.set_mdio_pin(config[CONF_MDIO_PIN])) - cg.add(var.set_clk_mode(config[CONF_CLK][CONF_MODE])) - cg.add(var.set_clk_pin(config[CONF_CLK][CONF_PIN])) + _add_mdio_setters(var, config) + if (clk_mode := config[CONF_CLK][CONF_MODE]) != DEFAULT_CLK_MODE: + cg.add(var.set_clk_mode(clk_mode)) + if (clk_pin := config[CONF_CLK][CONF_PIN]) != DEFAULT_CLK_PIN: + cg.add(var.set_clk_pin(clk_pin)) if CONF_POWER_PIN in config: cg.add(var.set_power_pin(config[CONF_POWER_PIN])) _add_phy_registers(var, config) diff --git a/esphome/components/ethernet/ethernet_component.h b/esphome/components/ethernet/ethernet_component.h index 44c4d6733d3..8d5552c6cca 100644 --- a/esphome/components/ethernet/ethernet_component.h +++ b/esphome/components/ethernet/ethernet_component.h @@ -271,7 +271,7 @@ class EthernetComponent final : public Component { int reset_pin_{-1}; int phy_addr_spi_{-1}; int clock_speed_; - spi_host_device_t interface_{SPI2_HOST}; + spi_host_device_t interface_{SPI2_HOST}; // Must match DEFAULT_SPI_INTERFACE in __init__.py #ifdef USE_SPI // When set, the SPI bus is owned and initialized by this spi component // and the ethernet chip only adds a device to it. @@ -283,16 +283,16 @@ class EthernetComponent final : public Component { #else // Group all 32-bit members first int power_pin_{-1}; - emac_rmii_clock_mode_t clk_mode_{EMAC_CLK_EXT_IN}; + emac_rmii_clock_mode_t clk_mode_{EMAC_CLK_EXT_IN}; // Must match DEFAULT_CLK_MODE in __init__.py #ifdef ESPHOME_ETHERNET_PHY_REGISTER_COUNT StaticVector phy_registers_{}; #endif // Group all 8-bit members together - uint8_t clk_pin_{0}; - uint8_t phy_addr_{0}; - uint8_t mdc_pin_{23}; - uint8_t mdio_pin_{18}; + uint8_t clk_pin_{0}; // Must match DEFAULT_CLK_PIN in __init__.py + uint8_t phy_addr_{0}; // Must match DEFAULT_PHY_ADDR in __init__.py + uint8_t mdc_pin_{23}; // Must match DEFAULT_MDC_PIN in __init__.py + uint8_t mdio_pin_{18}; // Must match DEFAULT_MDIO_PIN in __init__.py #endif // USE_ETHERNET_SPI // ESP32 pointers diff --git a/tests/component_tests/ethernet/config/rmii_custom.yaml b/tests/component_tests/ethernet/config/rmii_custom.yaml new file mode 100644 index 00000000000..291753592c4 --- /dev/null +++ b/tests/component_tests/ethernet/config/rmii_custom.yaml @@ -0,0 +1,14 @@ +esphome: + name: test + +esp32: + board: esp32dev + +ethernet: + type: LAN8720 + mdc_pin: GPIO4 + mdio_pin: GPIO5 + phy_addr: 1 + clk: + mode: CLK_OUT + pin: GPIO17 diff --git a/tests/component_tests/ethernet/config/rmii_defaults.yaml b/tests/component_tests/ethernet/config/rmii_defaults.yaml new file mode 100644 index 00000000000..7f39b31dfdb --- /dev/null +++ b/tests/component_tests/ethernet/config/rmii_defaults.yaml @@ -0,0 +1,13 @@ +esphome: + name: test + +esp32: + board: esp32dev + +ethernet: + type: LAN8720 + mdc_pin: GPIO23 + mdio_pin: GPIO18 + clk: + mode: CLK_EXT_IN + pin: GPIO0 diff --git a/tests/component_tests/ethernet/config/spi_s3_own_bus.yaml b/tests/component_tests/ethernet/config/spi_s3_own_bus.yaml new file mode 100644 index 00000000000..3f21fcd2c63 --- /dev/null +++ b/tests/component_tests/ethernet/config/spi_s3_own_bus.yaml @@ -0,0 +1,14 @@ +esphome: + name: test + +esp32: + board: esp32-s3-devkitc-1 + +ethernet: + type: W5500 + clk_pin: GPIO12 + mosi_pin: GPIO11 + miso_pin: GPIO13 + cs_pin: GPIO10 + interrupt_pin: GPIO4 + clock_speed: 20MHz diff --git a/tests/component_tests/ethernet/test_default_setters.py b/tests/component_tests/ethernet/test_default_setters.py new file mode 100644 index 00000000000..4658e8a4fbf --- /dev/null +++ b/tests/component_tests/ethernet/test_default_setters.py @@ -0,0 +1,49 @@ +"""Ethernet setters are skipped when the config matches the C++ initializers.""" + +from collections.abc import Callable +from pathlib import Path + +_MDIO_SETTERS = ("set_phy_addr(", "set_mdc_pin(", "set_mdio_pin(") + + +def test_rmii_defaults_are_not_emitted( + generate_main: Callable[[str | Path], str], + component_config_path: Callable[[str], Path], +) -> None: + main_cpp = generate_main(component_config_path("rmii_defaults.yaml")) + + for setter in (*_MDIO_SETTERS, "set_clk_mode(", "set_clk_pin("): + assert setter not in main_cpp + + +def test_rmii_custom_values_are_emitted( + generate_main: Callable[[str | Path], str], + component_config_path: Callable[[str], Path], +) -> None: + main_cpp = generate_main(component_config_path("rmii_custom.yaml")) + + assert "set_phy_addr(1);" in main_cpp + assert "set_mdc_pin(4);" in main_cpp + assert "set_mdio_pin(5);" in main_cpp + assert "set_clk_mode(::EMAC_CLK_OUT);" in main_cpp + assert "set_clk_pin(17);" in main_cpp + + +def test_spi2_interface_is_not_emitted( + generate_main: Callable[[str | Path], str], + component_config_path: Callable[[str], Path], +) -> None: + """ESP32-S3 defaults to spi2, which is the C++ initializer.""" + main_cpp = generate_main(component_config_path("spi_s3_own_bus.yaml")) + + assert "set_interface(" not in main_cpp + + +def test_spi3_interface_is_emitted( + generate_main: Callable[[str | Path], str], + component_config_path: Callable[[str], Path], +) -> None: + """Classic ESP32 defaults to spi3, so the setter is still needed.""" + main_cpp = generate_main(component_config_path("spi_own_bus.yaml")) + + assert "set_interface(::SPI3_HOST);" in main_cpp