diff --git a/esphome/components/ethernet/__init__.py b/esphome/components/ethernet/__init__.py index a44a609d3c..0454440f14 100644 --- a/esphome/components/ethernet/__init__.py +++ b/esphome/components/ethernet/__init__.py @@ -4,6 +4,7 @@ import logging from esphome import automation, pins from esphome.automation import Condition import esphome.codegen as cg +from esphome.components import spi from esphome.components.network import ( add_use_address, get_network_priority, @@ -39,6 +40,7 @@ from esphome.const import ( CONF_POLLING_INTERVAL, CONF_RESET_PIN, CONF_SPI, + CONF_SPI_ID, CONF_STATIC_IP, CONF_SUBNET, CONF_TYPE, @@ -263,10 +265,42 @@ def _is_framework_spi_polling_mode_supported() -> bool: return False +# Options that come from the referenced spi bus when spi_id is set +_SPI_BUS_PROVIDED_OPTIONS = ( + CONF_CLK_PIN, + CONF_MOSI_PIN, + CONF_MISO_PIN, + CONF_INTERFACE, +) + + +def _validate_spi_bus(config: ConfigType) -> ConfigType: + """Cross-validate spi_id against the options the referenced bus provides.""" + if CONF_SPI_ID in config: + for key in _SPI_BUS_PROVIDED_OPTIONS: + if key in config: + raise cv.Invalid( + f"'{key}' cannot be used together with '{CONF_SPI_ID}'; " + f"it comes from the referenced 'spi:' bus.", + path=[key], + ) + else: + for key in (CONF_CLK_PIN, CONF_MOSI_PIN, CONF_MISO_PIN): + if key not in config: + raise cv.Invalid( + f"'{key}' is a required option when '{CONF_SPI_ID}' is not set.", + path=[key], + ) + return config + + def _validate_spi_interface(config: ConfigType) -> ConfigType: """Set default SPI interface or validate user choice against the variant.""" if not CORE.is_esp32: return config + if CONF_SPI_ID in config: + # The interface comes from the referenced spi bus; don't set a default. + return config from esphome.components.esp32 import VARIANT_ESP32, get_esp32_variant from esphome.components.spi import get_hw_interface_list @@ -451,9 +485,14 @@ def _spi_schema(default_clock: str = "26.67MHz", max_clock: int = int(80e6)) -> BASE_SCHEMA.extend( cv.Schema( { - cv.Required(CONF_CLK_PIN): pins.internal_gpio_output_pin_number, - cv.Required(CONF_MISO_PIN): pins.internal_gpio_input_pin_number, - cv.Required(CONF_MOSI_PIN): pins.internal_gpio_output_pin_number, + # clk/mosi/miso are required unless spi_id is set; enforced + # by _validate_spi_bus below. + cv.Optional(CONF_CLK_PIN): pins.internal_gpio_output_pin_number, + cv.Optional(CONF_MISO_PIN): pins.internal_gpio_input_pin_number, + cv.Optional(CONF_MOSI_PIN): pins.internal_gpio_output_pin_number, + cv.Optional(CONF_SPI_ID): cv.All( + cv.only_on_esp32, cv.use_id(spi.SPIComponent) + ), cv.Required(CONF_CS_PIN): pins.internal_gpio_output_pin_number, cv.Optional( CONF_INTERRUPT_PIN @@ -478,6 +517,7 @@ def _spi_schema(default_clock: str = "26.67MHz", max_clock: int = int(80e6)) -> ), ), cv.only_on([Platform.ESP32, Platform.RP2]), + _validate_spi_bus, _validate_spi_interface, ) @@ -529,6 +569,30 @@ def _final_validate_spi(config: ConfigType) -> None: return from esphome.components.spi import CONF_INTERFACE_INDEX, get_spi_interface + if CONF_SPI_ID in config: + # Sharing the bus: the standard spi device schema enforces that the + # referenced bus declares both data lines. The IDF ethernet drivers + # additionally need a hardware host, which shows as an interface index + # on the validated bus config. + spi.final_validate_device_schema( + "ethernet", require_mosi=True, require_miso=True + )(config) + cv.Schema( + { + cv.Required(CONF_SPI_ID): fv.id_declaration_match_schema( + { + cv.Required( + CONF_INTERFACE_INDEX, + msg="Component ethernet requires this spi bus to use " + "a hardware interface", + ): cv.valid + } + ) + }, + extra=cv.ALLOW_EXTRA, + )(config) + return + if spi_configs := fv.full_config.get().get(CONF_SPI): # get_spi_interface() returns strings like "SPI2_HOST" spi_host = f"{config[CONF_INTERFACE].upper()}_HOST" @@ -625,9 +689,15 @@ async def _to_code_esp32(var: cg.MockObj, config: ConfigType) -> None: ) if config[CONF_TYPE] in SPI_ETHERNET_TYPES: - 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])) + if (spi_id := config.get(CONF_SPI_ID)) is not None: + # Pins and host come from the shared spi bus. + spi_parent = await cg.get_variable(spi_id) + cg.add(var.set_spi_parent(spi_parent)) + else: + 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]])) 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])) @@ -641,7 +711,6 @@ async def _to_code_esp32(var: cg.MockObj, config: ConfigType) -> None: cg.add_define("USE_ETHERNET_SPI") - cg.add(var.set_interface(SPI_INTERFACE_MAP[config[CONF_INTERFACE]])) add_idf_sdkconfig_option("CONFIG_ETH_USE_SPI_ETHERNET", True) # CONFIG_ETH_SPI_ETHERNET_{TYPE} Kconfig options were removed in IDF 6.0 # Types that are never built into IDF ship no Kconfig option at all diff --git a/esphome/components/ethernet/ethernet_component.h b/esphome/components/ethernet/ethernet_component.h index 1482e7a828..2b67b9093b 100644 --- a/esphome/components/ethernet/ethernet_component.h +++ b/esphome/components/ethernet/ethernet_component.h @@ -13,6 +13,9 @@ #include "esp_eth.h" #ifdef USE_ETHERNET_SPI #include "hal/spi_types.h" +#ifdef USE_SPI +#include "esphome/components/spi/spi.h" +#endif #endif #include "esp_eth_mac.h" #include "esp_eth_mac_esp.h" @@ -176,6 +179,9 @@ class EthernetComponent final : public Component { void set_reset_pin(uint8_t reset_pin) { this->reset_pin_ = reset_pin; } void set_clock_speed(int clock_speed) { this->clock_speed_ = clock_speed; } void set_interface(spi_host_device_t interface) { this->interface_ = interface; } +#ifdef USE_SPI + void set_spi_parent(spi::SPIComponent *parent) { this->spi_parent_ = parent; } +#endif #ifdef USE_ETHERNET_SPI_POLLING_SUPPORT void set_polling_interval(uint32_t polling_interval) { this->polling_interval_ = polling_interval; } #endif @@ -258,6 +264,11 @@ class EthernetComponent final : public Component { int phy_addr_spi_{-1}; int clock_speed_; spi_host_device_t interface_{SPI2_HOST}; +#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. + spi::SPIComponent *spi_parent_{nullptr}; +#endif #ifdef USE_ETHERNET_SPI_POLLING_SUPPORT uint32_t polling_interval_{0}; #endif diff --git a/esphome/components/ethernet/ethernet_component_esp32.cpp b/esphome/components/ethernet/ethernet_component_esp32.cpp index 069478e70c..1d9903271e 100644 --- a/esphome/components/ethernet/ethernet_component_esp32.cpp +++ b/esphome/components/ethernet/ethernet_component_esp32.cpp @@ -59,6 +59,9 @@ #ifdef USE_ETHERNET_SPI #include #include +#ifdef USE_SPI +#include "esphome/components/spi/spi.h" +#endif #endif namespace esphome::ethernet { @@ -168,25 +171,34 @@ void EthernetComponent::ethernet_lazy_init_() { // Install GPIO ISR handler to be able to service SPI Eth modules interrupts gpio_install_isr_service(0); - spi_bus_config_t buscfg = { - .mosi_io_num = this->mosi_pin_, - .miso_io_num = this->miso_pin_, - .sclk_io_num = this->clk_pin_, - .quadwp_io_num = -1, - .quadhd_io_num = -1, - .data4_io_num = -1, - .data5_io_num = -1, - .data6_io_num = -1, - .data7_io_num = -1, - .max_transfer_sz = 0, - .flags = 0, - .intr_flags = 0, - }; + spi_host_device_t host; +#ifdef USE_SPI + if (this->spi_parent_ != nullptr) { + // The bus is owned and already initialized by the spi component; share its host. + host = this->spi_parent_->get_interface(); + } else +#endif + { + spi_bus_config_t buscfg = { + .mosi_io_num = this->mosi_pin_, + .miso_io_num = this->miso_pin_, + .sclk_io_num = this->clk_pin_, + .quadwp_io_num = -1, + .quadhd_io_num = -1, + .data4_io_num = -1, + .data5_io_num = -1, + .data6_io_num = -1, + .data7_io_num = -1, + .max_transfer_sz = 0, + .flags = 0, + .intr_flags = 0, + }; - auto host = this->interface_; + host = this->interface_; - err = spi_bus_initialize(host, &buscfg, SPI_DMA_CH_AUTO); - ESPHL_ERROR_CHECK(err, "SPI bus initialize error"); + err = spi_bus_initialize(host, &buscfg, SPI_DMA_CH_AUTO); + ESPHL_ERROR_CHECK(err, "SPI bus initialize error"); + } #endif // Network interface setup handled by network component @@ -575,17 +587,25 @@ void EthernetComponent::dump_config() { YESNO(this->is_connected())); this->dump_connect_params_(); #ifdef USE_ETHERNET_SPI - ESP_LOGCONFIG(TAG, - " CLK Pin: %u\n" - " MISO Pin: %u\n" - " MOSI Pin: %u\n" - " CS Pin: %u", - this->clk_pin_, this->miso_pin_, this->mosi_pin_, this->cs_pin_); - const char *spi_interface = "spi3"; - if (this->interface_ == SPI2_HOST) { - spi_interface = "spi2"; +#ifdef USE_SPI + if (this->spi_parent_ != nullptr) { + // Pins and interface come from the shared spi bus; only CS is ours. + ESP_LOGCONFIG(TAG, " CS Pin: %u", this->cs_pin_); + } else +#endif + { + ESP_LOGCONFIG(TAG, + " CLK Pin: %u\n" + " MISO Pin: %u\n" + " MOSI Pin: %u\n" + " CS Pin: %u", + this->clk_pin_, this->miso_pin_, this->mosi_pin_, this->cs_pin_); + const char *spi_interface = "spi3"; + if (this->interface_ == SPI2_HOST) { + spi_interface = "spi2"; + } + ESP_LOGCONFIG(TAG, " Interface: %s", spi_interface); } - ESP_LOGCONFIG(TAG, " Interface: %s", spi_interface); #ifdef USE_ETHERNET_SPI_POLLING_SUPPORT if (this->polling_interval_ != 0) { ESP_LOGCONFIG(TAG, " Polling Interval: %" PRIu32 " ms", this->polling_interval_); diff --git a/esphome/components/spi/spi.h b/esphome/components/spi/spi.h index 0358ed278f..17c59c895a 100644 --- a/esphome/components/spi/spi.h +++ b/esphome/components/spi/spi.h @@ -352,6 +352,8 @@ class SPIComponent final : public Component { this->using_hw_ = true; } + SPIInterface get_interface() const { return this->interface_; } + void set_interface_name(const char *name) { this->interface_name_ = name; } float get_setup_priority() const override { return setup_priority::BUS; } diff --git a/tests/component_tests/conftest.py b/tests/component_tests/conftest.py index 3730978ec3..4f0b786cc2 100644 --- a/tests/component_tests/conftest.py +++ b/tests/component_tests/conftest.py @@ -57,6 +57,14 @@ def reset_core() -> Generator[None]: CORE.reset() +@pytest.fixture(autouse=True) +def reset_full_config() -> Generator[None]: + """Give each test a clean final-validate config and restore it after.""" + token = final_validate.full_config.set({}) + yield + final_validate.full_config.reset(token) + + @pytest.fixture def set_core_config() -> Generator[SetCoreConfigCallable]: """Fixture to set up the core configuration for tests.""" diff --git a/tests/component_tests/ethernet/config/spi_id_shared_bus.yaml b/tests/component_tests/ethernet/config/spi_id_shared_bus.yaml new file mode 100644 index 0000000000..ffcd6a59f3 --- /dev/null +++ b/tests/component_tests/ethernet/config/spi_id_shared_bus.yaml @@ -0,0 +1,21 @@ +esphome: + name: test + +esp32: + board: esp32dev + +spi: + - id: spi_bus + interface: spi2 + clk_pin: GPIO18 + mosi_pin: GPIO23 + miso_pin: GPIO19 + +ethernet: + id: eth_component + type: W5500 + spi_id: spi_bus + cs_pin: GPIO5 + interrupt_pin: GPIO36 + reset_pin: GPIO22 + clock_speed: 20MHz diff --git a/tests/component_tests/ethernet/config/spi_own_bus.yaml b/tests/component_tests/ethernet/config/spi_own_bus.yaml new file mode 100644 index 0000000000..3169e6a39a --- /dev/null +++ b/tests/component_tests/ethernet/config/spi_own_bus.yaml @@ -0,0 +1,16 @@ +esphome: + name: test + +esp32: + board: esp32dev + +ethernet: + id: eth_component + type: W5500 + clk_pin: GPIO18 + mosi_pin: GPIO23 + miso_pin: GPIO19 + cs_pin: GPIO5 + interrupt_pin: GPIO36 + reset_pin: GPIO22 + clock_speed: 20MHz diff --git a/tests/component_tests/ethernet/test_ethernet.py b/tests/component_tests/ethernet/test_ethernet.py index 9308d0b099..522357f972 100644 --- a/tests/component_tests/ethernet/test_ethernet.py +++ b/tests/component_tests/ethernet/test_ethernet.py @@ -27,14 +27,6 @@ _CH390_CONFIG = { } -@pytest.fixture(autouse=True) -def _reset_full_config(): - """Reset fv.full_config so each test starts with a clean slate.""" - token = fv.full_config.set({}) - yield - fv.full_config.reset(token) - - def test_rejects_wifi_and_ethernet_without_priority() -> None: """Wi-Fi + ethernet without a network: priority: list must be rejected.""" fv.full_config.set({"wifi": {}, "ethernet": {}}) diff --git a/tests/component_tests/ethernet/test_spi_id.py b/tests/component_tests/ethernet/test_spi_id.py new file mode 100644 index 0000000000..e6dc9249e6 --- /dev/null +++ b/tests/component_tests/ethernet/test_spi_id.py @@ -0,0 +1,235 @@ +"""Tests for the ethernet `spi_id:` option (attach to a shared spi bus).""" + +from collections.abc import Callable +from pathlib import Path + +import pytest +from voluptuous import Invalid + +from esphome import config_validation as cv +from esphome.components.esp32 import ( + KEY_BOARD, + KEY_IDF_VERSION, + KEY_VARIANT, + VARIANT_ESP32S3, +) +from esphome.components.ethernet import CONF_INTERFACE, CONFIG_SCHEMA, _final_validate +from esphome.components.rp2.const import KEY_BOARD as RP2_KEY_BOARD + +# Registers the rp2 pin schema so RP2 configs can validate pins. +import esphome.components.rp2.gpio # noqa: F401 +from esphome.components.spi import CONF_INTERFACE_INDEX +from esphome.const import ( + CONF_CLK_PIN, + CONF_ID, + CONF_MISO_PIN, + CONF_MOSI_PIN, + CONF_SPI, + CONF_SPI_ID, + CONF_TYPE, + PlatformFramework, +) +from esphome.core import CORE, ID +import esphome.final_validate as fv + +from ..types import SetCoreConfigCallable + +_W5500_PIN_CONFIG = { + "type": "W5500", + "clk_pin": 47, + "mosi_pin": 48, + "miso_pin": 14, + "cs_pin": 21, +} + +_W5500_SPI_ID_CONFIG = { + "type": "W5500", + "spi_id": "spi_bus", + "cs_pin": 21, +} + + +def _set_esp32_s3(set_core_config: SetCoreConfigCallable) -> None: + set_core_config( + PlatformFramework.ESP32_IDF, + platform_data={ + KEY_BOARD: "esp32-s3-devkitc-1", + KEY_VARIANT: VARIANT_ESP32S3, + KEY_IDF_VERSION: cv.Version(5, 3, 2), + }, + ) + # _validate derives use_address from the node name, which has no default here. + CORE.name = "spi-id-test" + + +def test_spi_id_accepted_without_pins_or_interface( + set_core_config: SetCoreConfigCallable, +) -> None: + """With spi_id set, the pin options are not required and no interface is defaulted.""" + _set_esp32_s3(set_core_config) + config = CONFIG_SCHEMA(dict(_W5500_SPI_ID_CONFIG)) + assert config[CONF_SPI_ID] == ID("spi_bus") + # The interface comes from the referenced bus; no default may be injected. + assert CONF_INTERFACE not in config + + +@pytest.mark.parametrize( + ("key", "value"), + [ + (CONF_CLK_PIN, 47), + (CONF_MOSI_PIN, 48), + (CONF_MISO_PIN, 14), + (CONF_INTERFACE, "spi2"), + ], +) +def test_spi_id_rejects_bus_options( + set_core_config: SetCoreConfigCallable, key: str, value: int | str +) -> None: + """Options provided by the referenced bus must be rejected alongside spi_id.""" + _set_esp32_s3(set_core_config) + with pytest.raises(Invalid, match=f"'{key}' cannot be used together with 'spi_id'"): + CONFIG_SCHEMA({**_W5500_SPI_ID_CONFIG, key: value}) + + +@pytest.mark.parametrize("key", [CONF_CLK_PIN, CONF_MOSI_PIN, CONF_MISO_PIN]) +def test_bus_pins_still_required_without_spi_id( + set_core_config: SetCoreConfigCallable, key: str +) -> None: + """Without spi_id, the bus pin options stay required.""" + _set_esp32_s3(set_core_config) + config = {k: v for k, v in _W5500_PIN_CONFIG.items() if k != key} + with pytest.raises( + Invalid, match=f"'{key}' is a required option when 'spi_id' is not set" + ): + CONFIG_SCHEMA(config) + + +def test_spi_id_rejected_on_rp2(set_core_config: SetCoreConfigCallable) -> None: + """spi_id is ESP32-only; the RP2 path is unchanged.""" + set_core_config( + PlatformFramework.RP2_ARDUINO, platform_data={RP2_KEY_BOARD: "rpipicow"} + ) + CORE.name = "spi-id-test" + config = { + "type": "W5500", + "spi_id": "spi_bus", + "clk_pin": 18, + "mosi_pin": 19, + "miso_pin": 16, + "cs_pin": 17, + } + with pytest.raises(Invalid, match="only available on"): + CONFIG_SCHEMA(config) + + +def _eth_spi_id_final_config() -> dict: + return {CONF_TYPE: "W5500", CONF_SPI_ID: ID("spi_bus")} + + +class _FakeFinalConfig(dict): + """Dict-backed FinalValidateConfig with just enough ID resolution for + fv.id_declaration_match_schema to find an spi bus fragment.""" + + def get_path_for_id(self, id: ID) -> list: + for index, conf in enumerate(self[CONF_SPI]): + if conf[CONF_ID] == id: + return [CONF_SPI, index, CONF_ID] + raise KeyError(id) + + def get_config_for_path(self, path: list) -> dict: + return self[path[0]][path[1]] + + +def _set_spi_buses(*buses: dict) -> None: + fv.full_config.set(_FakeFinalConfig({CONF_SPI: list(buses)})) + + +_SHAREABLE_BUS = { + CONF_ID: ID("spi_bus"), + CONF_INTERFACE_INDEX: 0, + CONF_MISO_PIN: {}, + CONF_MOSI_PIN: {}, +} + + +def test_final_validate_accepts_hardware_bus_with_data_pins( + set_core_config: SetCoreConfigCallable, +) -> None: + """A hardware spi bus that declares miso_pin and mosi_pin may be shared.""" + _set_esp32_s3(set_core_config) + # An unrelated bus first: the ID lookup must skip past it. + _set_spi_buses({CONF_ID: ID("other_bus"), CONF_INTERFACE_INDEX: 1}, _SHAREABLE_BUS) + _final_validate(_eth_spi_id_final_config()) + + +def test_final_validate_rejects_software_bus( + set_core_config: SetCoreConfigCallable, +) -> None: + """A software spi bus (no hardware interface index) cannot be shared.""" + _set_esp32_s3(set_core_config) + bus = {k: v for k, v in _SHAREABLE_BUS.items() if k != CONF_INTERFACE_INDEX} + _set_spi_buses(bus) + with pytest.raises(Invalid, match="requires this spi bus to use a hardware"): + _final_validate(_eth_spi_id_final_config()) + + +@pytest.mark.parametrize("pin_key", [CONF_MISO_PIN, CONF_MOSI_PIN]) +def test_final_validate_rejects_bus_without_data_pin( + set_core_config: SetCoreConfigCallable, pin_key: str +) -> None: + """The shared bus must declare both data pins to drive the ethernet chip.""" + _set_esp32_s3(set_core_config) + bus = {k: v for k, v in _SHAREABLE_BUS.items() if k != pin_key} + _set_spi_buses(bus) + with pytest.raises(Invalid, match=f"requires this spi bus to declare a {pin_key}"): + _final_validate(_eth_spi_id_final_config()) + + +def test_final_validate_rejects_colliding_host_without_spi_id( + set_core_config: SetCoreConfigCallable, +) -> None: + """Without spi_id, claiming the same host as an spi bus stays an error.""" + _set_esp32_s3(set_core_config) + fv.full_config.set({CONF_SPI: [{CONF_ID: ID("spi_bus"), CONF_INTERFACE_INDEX: 0}]}) + config = {CONF_TYPE: "W5500", CONF_INTERFACE: "spi2"} + with pytest.raises(Invalid, match="both using interface 'SPI2_HOST'"): + _final_validate(config) + + +def test_final_validate_accepts_distinct_host_without_spi_id( + set_core_config: SetCoreConfigCallable, +) -> None: + """Without spi_id, a different host than the spi bus is accepted.""" + _set_esp32_s3(set_core_config) + fv.full_config.set({CONF_SPI: [{CONF_ID: ID("spi_bus"), CONF_INTERFACE_INDEX: 0}]}) + _final_validate({CONF_TYPE: "W5500", CONF_INTERFACE: "spi3"}) + + +def test_generated_code_uses_spi_parent( + generate_main: Callable[[str | Path], str], + component_config_path: Callable[[str], Path], +) -> None: + """With spi_id, codegen wires the spi parent and skips the bus options.""" + main_cpp = generate_main(component_config_path("spi_id_shared_bus.yaml")) + + assert "eth_component->set_spi_parent(spi_bus);" in main_cpp + assert "eth_component->set_cs_pin(5);" in main_cpp + assert "eth_component->set_clk_pin(" not in main_cpp + assert "eth_component->set_miso_pin(" not in main_cpp + assert "eth_component->set_mosi_pin(" not in main_cpp + assert "eth_component->set_interface(" not in main_cpp + + +def test_generated_code_without_spi_id_initializes_own_bus( + generate_main: Callable[[str | Path], str], + component_config_path: Callable[[str], Path], +) -> None: + """Without spi_id, codegen still emits the pin and interface setters.""" + main_cpp = generate_main(component_config_path("spi_own_bus.yaml")) + + assert "eth_component->set_spi_parent(" not in main_cpp + assert "eth_component->set_clk_pin(18);" in main_cpp + assert "eth_component->set_miso_pin(19);" in main_cpp + assert "eth_component->set_mosi_pin(23);" in main_cpp + assert "eth_component->set_cs_pin(5);" in main_cpp + assert "eth_component->set_interface(::SPI3_HOST);" in main_cpp diff --git a/tests/component_tests/network/test_priority.py b/tests/component_tests/network/test_priority.py index 017f0711a3..041b358dda 100644 --- a/tests/component_tests/network/test_priority.py +++ b/tests/component_tests/network/test_priority.py @@ -24,11 +24,9 @@ from tests.component_tests.types import SetCoreConfigCallable @pytest.fixture(autouse=True) def _clear_core_data(): - """Wipe CORE.data and reset fv.full_config so each test starts clean.""" + """Wipe CORE.data so each test starts clean.""" CORE.data.clear() - token = fv.full_config.set({}) yield - fv.full_config.reset(token) CORE.data.clear() diff --git a/tests/components/ethernet/common-w5500-spi-id.yaml b/tests/components/ethernet/common-w5500-spi-id.yaml new file mode 100644 index 0000000000..14c01842f0 --- /dev/null +++ b/tests/components/ethernet/common-w5500-spi-id.yaml @@ -0,0 +1,17 @@ +ethernet: + type: W5500 + spi_id: spi_bus + cs_pin: 5 + interrupt_pin: 36 + reset_pin: 22 + clock_speed: 10Mhz + 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" + on_connect: + - logger.log: "Ethernet connected!" + on_disconnect: + - logger.log: "Ethernet disconnected!" diff --git a/tests/components/ethernet/test-w5500-spi-id.esp32-idf.yaml b/tests/components/ethernet/test-w5500-spi-id.esp32-idf.yaml new file mode 100644 index 0000000000..16a25ced2a --- /dev/null +++ b/tests/components/ethernet/test-w5500-spi-id.esp32-idf.yaml @@ -0,0 +1,3 @@ +packages: + spi: !include ../../test_build_components/common/spi/esp32-idf.yaml + ethernet: !include common-w5500-spi-id.yaml