mirror of
https://github.com/esphome/esphome.git
synced 2026-11-10 23:25:58 +00:00
174 lines
5.7 KiB
Python
174 lines
5.7 KiB
Python
"""Tests for the ethernet final-validation coexistence gate and schema bounds."""
|
|
|
|
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_ESP32,
|
|
VARIANT_ESP32S3,
|
|
VARIANT_ESP32S31,
|
|
)
|
|
from esphome.components.ethernet import (
|
|
CONF_CLOCK_SPEED,
|
|
CONFIG_SCHEMA,
|
|
_final_validate_wifi,
|
|
)
|
|
from esphome.components.network import _validate_priority_list
|
|
from esphome.const import CONF_AP, CONF_NETWORKS, CONF_PRIORITY, PlatformFramework
|
|
from esphome.core import CORE
|
|
import esphome.final_validate as fv
|
|
|
|
from ..types import SetCoreConfigCallable
|
|
|
|
_CH390_CONFIG = {
|
|
"type": "CH390",
|
|
"clk_pin": 47,
|
|
"mosi_pin": 48,
|
|
"miso_pin": 14,
|
|
"cs_pin": 21,
|
|
}
|
|
|
|
|
|
_WIFI_STA_CONFIG = {CONF_NETWORKS: [{"ssid": "MySSID", "password": "password1"}]}
|
|
_WIFI_AP_CONFIG = {CONF_AP: {"ssid": "espap", "password": "mypassword"}}
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("wifi", "priority", "extra", "match"),
|
|
[
|
|
(_WIFI_STA_CONFIG, None, {}, "missing: ethernet, wifi"),
|
|
(_WIFI_STA_CONFIG, ["ethernet"], {}, "missing: wifi"),
|
|
(_WIFI_STA_CONFIG, ["wifi", "ethernet"], {}, None),
|
|
(_WIFI_AP_CONFIG, None, {}, None),
|
|
# A station can still appear at run time: DPP or a provisioner saves credentials
|
|
({"dpp": {}}, None, {}, "missing: ethernet, wifi"),
|
|
({"dpp": {}, **_WIFI_AP_CONFIG}, None, {}, "missing: ethernet, wifi"),
|
|
(_WIFI_AP_CONFIG, None, {"captive_portal": {}}, "missing: ethernet, wifi"),
|
|
(_WIFI_AP_CONFIG, None, {"improv_serial": {}}, "missing: ethernet, wifi"),
|
|
],
|
|
)
|
|
def test_wifi_station_needs_a_full_priority_list_but_an_access_point_does_not(
|
|
wifi: dict, priority: list[str] | None, extra: dict, match: str | None
|
|
) -> None:
|
|
"""A station, configured or provisioned later, competes with ethernet; an access point does not."""
|
|
full = {"wifi": wifi, "ethernet": {}, **extra}
|
|
if priority:
|
|
full["network"] = {CONF_PRIORITY: _validate_priority_list(priority)}
|
|
fv.full_config.set(full)
|
|
if match is None:
|
|
_final_validate_wifi({})
|
|
else:
|
|
with pytest.raises(Invalid, match=match):
|
|
_final_validate_wifi({})
|
|
|
|
|
|
@pytest.mark.parametrize("clock_speed", ["26.67MHz", "72MHz"])
|
|
def test_ch390_accepts_clock_speed_up_to_the_datasheet_maximum(
|
|
set_core_config: SetCoreConfigCallable, clock_speed: str
|
|
) -> None:
|
|
"""CH390 SCK is rated to 72MHz, so the schema must accept the whole range."""
|
|
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 = "ch390-test"
|
|
config = CONFIG_SCHEMA({**_CH390_CONFIG, CONF_CLOCK_SPEED: clock_speed})
|
|
assert config[CONF_CLOCK_SPEED] == cv.frequency(clock_speed)
|
|
|
|
|
|
def test_ch390_rejects_clock_speed_above_the_datasheet_maximum(
|
|
set_core_config: SetCoreConfigCallable,
|
|
) -> None:
|
|
"""The shared 80MHz ceiling is out of spec for this part."""
|
|
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 = "ch390-test"
|
|
with pytest.raises(Invalid, match="value must be at most 72000000"):
|
|
CONFIG_SCHEMA({**_CH390_CONFIG, CONF_CLOCK_SPEED: "80MHz"})
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("idf_version", "match"),
|
|
[
|
|
(
|
|
cv.Version(5, 5, 3),
|
|
r"\(RGMII\) PHY requires ESP-IDF 6\.0\.0 or newer, got 5\.5\.3",
|
|
),
|
|
(cv.Version(6, 0, 0), None),
|
|
],
|
|
)
|
|
def test_rgmii_phy_needs_idf_6(
|
|
set_core_config: SetCoreConfigCallable,
|
|
idf_version: cv.Version,
|
|
match: str | None,
|
|
) -> None:
|
|
"""The internal EMAC only drives RGMII from ESP-IDF 6.0 on."""
|
|
set_core_config(
|
|
PlatformFramework.ESP32_IDF,
|
|
platform_data={
|
|
KEY_BOARD: "esp32-s31-devkitc-1",
|
|
KEY_VARIANT: VARIANT_ESP32S31,
|
|
KEY_IDF_VERSION: idf_version,
|
|
},
|
|
)
|
|
CORE.name = "rgmii-test"
|
|
config = {"type": "GENERIC", "mdc_pin": 23, "mdio_pin": 18}
|
|
if match is None:
|
|
CONFIG_SCHEMA(config)
|
|
else:
|
|
with pytest.raises(Invalid, match=match):
|
|
CONFIG_SCHEMA(config)
|
|
|
|
|
|
_LAN8720_CONFIG = {"type": "LAN8720", "mdc_pin": 23, "mdio_pin": 18}
|
|
|
|
|
|
def _set_esp32_core(set_core_config: SetCoreConfigCallable) -> None:
|
|
set_core_config(
|
|
PlatformFramework.ESP32_IDF,
|
|
platform_data={
|
|
KEY_BOARD: "esp32dev",
|
|
KEY_VARIANT: VARIANT_ESP32,
|
|
KEY_IDF_VERSION: cv.Version(5, 3, 2),
|
|
},
|
|
)
|
|
CORE.name = "rmii-test"
|
|
|
|
|
|
def test_rmii_rejects_removed_clk_mode(
|
|
set_core_config: SetCoreConfigCallable,
|
|
) -> None:
|
|
"""The removed clk_mode option points at the clk: replacement."""
|
|
_set_esp32_core(set_core_config)
|
|
with pytest.raises(Invalid, match="GPIO17_OUT -> mode: CLK_OUT, pin: GPIO17"):
|
|
CONFIG_SCHEMA(
|
|
{
|
|
**_LAN8720_CONFIG,
|
|
"clk_mode": "GPIO17_OUT",
|
|
"clk": {"mode": "CLK_OUT", "pin": 17},
|
|
}
|
|
)
|
|
|
|
|
|
def test_rmii_requires_clk(set_core_config: SetCoreConfigCallable) -> None:
|
|
"""An RMII PHY has no default clock, so clk must be given."""
|
|
_set_esp32_core(set_core_config)
|
|
with pytest.raises(Invalid, match=r"required key not provided @ data\['clk'\]"):
|
|
CONFIG_SCHEMA(_LAN8720_CONFIG)
|