Merge branch 'dev' into rp2-3-connection-slots

This commit is contained in:
J. Nick Koston
2026-08-10 16:38:18 -05:00
committed by GitHub
106 changed files with 3934 additions and 533 deletions
@@ -15,6 +15,7 @@ bk72xx:
bk72xx_ble_tracker:
scan_parameters:
continuous: false
active: false
on_ble_advertise:
- mac_address:
- AC:37:43:77:5F:4C
@@ -48,6 +48,8 @@ def test_trigger_codegen(
# scan_parameters continuous: false reaches the YAML-mode setter, not the
# runtime override.
assert "->set_configured_continuous(false)" in main_cpp
# active: false (non-default) flows through to the setter.
assert "->set_scan_active(false)" in main_cpp
# Constructor call, not just the declaration: the parent argument is what
# registers the trigger as a listener.
assert re.search(
@@ -16,8 +16,8 @@ from esphome.components.ln882h_ble_tracker import (
from esphome.components.rp2_ble_tracker import SCAN_PARAMETERS_SCHEMA as RP2_SCHEMA
def _validate(**kwargs: str) -> dict:
"""Run a scan_parameters config through a passive tracker's real schema."""
def _validate(**kwargs: str | bool) -> dict:
"""Run a scan_parameters config through the bk72xx tracker's real schema."""
return BK72XX_SCHEMA(kwargs)
@@ -48,11 +48,12 @@ def test_to_ble_units_truncates() -> None:
def test_bk72xx_defaults_are_valid() -> None:
"""bk72xx pins the BK reference rate: 100 ms interval, shared 30 ms window."""
"""bk72xx pins the BK reference rate — 100 ms interval, shared 30 ms window —
and exposes active (default on, like every active-capable tracker)."""
config = _validate()
assert to_ble_units(config["interval"]) == 160
assert to_ble_units(config["window"]) == 48
assert "active" not in config
assert config["active"] is True
def test_esp32_defaults_are_valid() -> None:
@@ -86,10 +87,9 @@ def test_esp32_active_can_disable() -> None:
assert config["active"] is False
def test_passive_schema_rejects_active_key() -> None:
"""Trackers without active scan support must not silently accept the option."""
with pytest.raises(cv.Invalid):
_validate(active="true")
def test_bk72xx_active_can_disable() -> None:
config = _validate(active=False)
assert config["active"] is False
# --- accepted configurations ---
@@ -15,6 +15,7 @@ from esphome.const import (
KEY_CORE,
KEY_TARGET_FRAMEWORK,
KEY_TARGET_PLATFORM,
PLATFORM_BK72XX,
PLATFORM_ESP32,
PLATFORM_LN882X,
PLATFORM_RP2,
@@ -27,18 +28,20 @@ from ..types import SetCoreConfigCallable
# Advertisement-only hub platforms; rp2 runs the full proxy and has its own
# tests below.
HUB_PLATFORM_FRAMEWORKS = [
PlatformFramework.BK72XX_ARDUINO,
PlatformFramework.LN882X_ARDUINO,
]
HUB_TRACKERS = {
PLATFORM_BK72XX: "bk72xx_ble_tracker",
PLATFORM_LN882X: "ln882h_ble_tracker",
PLATFORM_RP2: "rp2_ble_tracker",
}
def test_hub_platform_list_covers_every_hub_platform() -> None:
# A platform added to _HUB_PLATFORMS (bk72xx is planned) would otherwise
# get no gate coverage at all; GATT platforms have their own tests.
# A platform added to _HUB_PLATFORMS would otherwise get no gate coverage
# at all; GATT platforms have their own tests.
advertisement_only = set(bluetooth_proxy._HUB_PLATFORMS) - set(
bluetooth_connection.HUB_MAX_CONNECTIONS
)
@@ -1,13 +1,31 @@
"""Tests for the ethernet final-validation coexistence gate."""
"""Tests for the ethernet final-validation coexistence gate and schema bounds."""
import pytest
from voluptuous import Invalid
from esphome.components.ethernet import _final_validate
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_CLOCK_SPEED, CONFIG_SCHEMA, _final_validate
from esphome.components.network import _validate_priority_list
from esphome.const import CONF_PRIORITY
from esphome.const import 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,
}
@pytest.fixture(autouse=True)
def _reset_full_config():
@@ -35,3 +53,40 @@ def test_rejects_wifi_and_ethernet_with_incomplete_priority() -> None:
)
with pytest.raises(Invalid, match=r"must.*list both interfaces; missing: wifi"):
_final_validate({})
@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"})
@@ -0,0 +1,10 @@
mitsubishi_cn105:
id: ac_hub
climate:
- platform: mitsubishi_cn105
mitsubishi_cn105_id: ac_hub
name: AC
current_temperature_min_interval: 30s
uart_id: uart_bus
update_interval: 10s
@@ -0,0 +1,30 @@
"""Tests for Mitsubishi CN105 climate configuration migration diagnostics."""
from collections.abc import Callable
from pathlib import Path
import pytest
from esphome.components.mitsubishi_cn105 import climate
import esphome.config_validation as cv
from esphome.core import CORE
from esphome.yaml_util import load_yaml
def test_top_level_hub_rejects_leftover_legacy_climate_keys(
component_fixture_path: Callable[[str], Path],
) -> None:
config = load_yaml(
component_fixture_path("top_level_hub_with_legacy_climate_keys.yaml")
)
CORE.raw_config = config
with pytest.raises(cv.Invalid) as exc_info:
climate.CONFIG_SCHEMA(config["climate"][0])
message = str(exc_info.value)
assert "'current_temperature_min_interval'" in message
assert "'uart_id'" in message
assert "'update_interval'" in message
assert "top-level 'mitsubishi_cn105:' block" in message
assert "'telemetry_request_min_interval'" in message
@@ -0,0 +1,5 @@
esphome:
name: preftest
bk72xx:
board: generic-bk7252
@@ -0,0 +1,5 @@
esphome:
name: preftest
esp32:
board: esp32dev
@@ -0,0 +1,5 @@
esphome:
name: preftest
esp8266:
board: esp01_1m
@@ -0,0 +1,4 @@
esphome:
name: preftest
host:
@@ -0,0 +1,6 @@
esphome:
name: preftest
nrf52:
board: adafruit_itsybitsy_nrf52840
bootloader: adafruit_nrf52_sd140_v6
@@ -0,0 +1,5 @@
esphome:
name: preftest
rp2:
board: rpipicow
@@ -0,0 +1,39 @@
"""Every preferences platform either emits USE_PREFERENCE_KEY_LOOKUP from
codegen (key-lookup backends) or must not (slot-based backends, whose managers
have no load_from_key()). Run each platform's real codegen and assert the
emission, mirroring the split the deny-list in esphome/core/defines.h assumes
for static analysis.
The fixtures cover every distinct preferences backend today: ln882x and
rtl87xx route through libretiny (bk72xx stands in for the family), rp2040 is
an alias of rp2, and nrf52 exercises zephyr. A seventh backend needs a new
fixture here."""
from collections.abc import Callable
from pathlib import Path
import pytest
from esphome.core import CORE
@pytest.mark.parametrize(
("fixture", "emits"),
[
("esp32.yaml", True),
("bk72xx.yaml", True), # libretiny
("host.yaml", True),
("nrf52.yaml", True), # zephyr
("esp8266.yaml", False),
("rp2.yaml", False),
],
)
def test_key_lookup_define_matches_the_platform_backend(
fixture: str,
emits: bool,
generate_main: Callable[[str | Path], str],
component_config_path: Callable[[str], Path],
) -> None:
generate_main(component_config_path(fixture))
defines = {define.name for define in CORE.defines}
assert ("USE_PREFERENCE_KEY_LOOKUP" in defines) is emits
@@ -33,14 +33,49 @@ def test_web_server_auth_explicit_basic_no_warning(
generate_main: Callable[[str], str],
caplog: pytest.LogCaptureFixture,
) -> None:
"""Auth type basic builds Basic and does not warn."""
generate_main("tests/component_tests/web_server/web_server_auth_basic.yaml")
"""Auth type basic on ESP32 uses plaintext credentials and does not warn."""
main_cpp = generate_main(
"tests/component_tests/web_server/web_server_auth_basic.yaml"
)
assert '->set_auth_username("admin");' in main_cpp
assert '->set_auth_password("password");' in main_cpp
assert "set_auth_basic_hash" not in main_cpp
assert _has_define("USE_WEBSERVER_AUTH")
assert not _has_define("USE_WEBSERVER_AUTH_DIGEST")
assert _DEFAULT_CHANGE_WARNING not in caplog.text
def test_web_server_auth_basic_esp8266_uses_precomputed_hash(
generate_main: Callable[[str], str],
) -> None:
"""Auth type basic on ESP8266 emits the precomputed base64 hash, not the credentials."""
main_cpp = generate_main(
"tests/component_tests/web_server/web_server_auth_basic_esp8266.yaml"
)
assert '->set_auth_basic_hash("YWRtaW46cGFzc3dvcmQ=");' in main_cpp
assert "set_auth_username" not in main_cpp
assert "set_auth_password" not in main_cpp
assert _has_define("USE_WEBSERVER_AUTH")
assert not _has_define("USE_WEBSERVER_AUTH_DIGEST")
def test_web_server_auth_digest_esp8266_uses_plaintext_credentials(
generate_main: Callable[[str], str],
) -> None:
"""Auth type digest on ESP8266 uses plaintext credentials, not the basic hash."""
main_cpp = generate_main(
"tests/component_tests/web_server/web_server_auth_digest_esp8266.yaml"
)
assert '->set_auth_username("admin");' in main_cpp
assert '->set_auth_password("password");' in main_cpp
assert "set_auth_basic_hash" not in main_cpp
assert _has_define("USE_WEBSERVER_AUTH")
assert _has_define("USE_WEBSERVER_AUTH_DIGEST")
def test_web_server_auth_explicit_digest(
generate_main: Callable[[str], str],
caplog: pytest.LogCaptureFixture,
@@ -0,0 +1,16 @@
---
esphome:
name: test
esp8266:
board: esp01_1m
wifi:
ssid: MySSID
password: password1
web_server:
auth:
username: admin
password: password
type: basic
@@ -0,0 +1,16 @@
---
esphome:
name: test
esp8266:
board: esp01_1m
wifi:
ssid: MySSID
password: password1
web_server:
auth:
username: admin
password: password
type: digest