diff --git a/esphome/components/bluetooth_connection/__init__.py b/esphome/components/bluetooth_connection/__init__.py index 9dd6ac7099..25aea88812 100644 --- a/esphome/components/bluetooth_connection/__init__.py +++ b/esphome/components/bluetooth_connection/__init__.py @@ -117,31 +117,31 @@ _PLATFORM_BACKENDS: dict[str, _PlatformBackend] = { GATT_CLIENT_PLATFORMS = list(_PLATFORM_BACKENDS) -def _backend_entry() -> _PlatformBackend: - if (entry := _PLATFORM_BACKENDS.get(CORE.target_platform)) is None: - raise cv.Invalid( - f"no GATT client backend is registered for {CORE.target_platform}" - ) +def _backend_entry(platform: str | None = None) -> _PlatformBackend: + key = platform if platform is not None else CORE.target_platform + if (entry := _PLATFORM_BACKENDS.get(key)) is None: + raise cv.Invalid(f"no GATT client backend is registered for {key}") return entry -def gatt_client_schema() -> cv.Schema: +def gatt_client_schema(platform: str | None = None) -> cv.Schema: """Schema fragment for one GATT backend instance: its generated id plus - the platform-stack reference new_gatt_backend() resolves. Platform - dispatch happens at call time, so call this from inside a validator or a - per-platform schema builder, never at module import. + the platform-stack reference new_gatt_backend() resolves. + + Defaults to the platform being validated; pass `platform` explicitly when + building a schema outside validation (the language-schema dumper calls + per-platform builders under arbitrary CORE platforms). """ - entry = _backend_entry() + entry = _backend_entry(platform) return entry.schema_fragment().extend( {cv.GenerateID(CONF_BACKEND_ID): cv.declare_id(entry.backend_class)} ) -def hub_connection_schema() -> cv.Schema: +def hub_connection_schema(platform: str | None = None) -> cv.Schema: """Per-slot schema for the proxy's connection wrappers: the wrapper id on - top of the backend fragment. Same call-time constraint as - gatt_client_schema().""" - return gatt_client_schema().extend( + top of the backend fragment. Same platform rules as gatt_client_schema().""" + return gatt_client_schema(platform).extend( {cv.GenerateID(): cv.declare_id(HubBluetoothConnection)} ) diff --git a/esphome/components/bluetooth_proxy/__init__.py b/esphome/components/bluetooth_proxy/__init__.py index b30ea275c4..8f8a0da1bc 100644 --- a/esphome/components/bluetooth_proxy/__init__.py +++ b/esphome/components/bluetooth_proxy/__init__.py @@ -4,7 +4,13 @@ import logging import esphome.codegen as cg from esphome.components import ble_device_base, bluetooth_connection import esphome.config_validation as cv -from esphome.const import CONF_ACTIVE, CONF_ID, PLATFORM_LN882X, PLATFORM_RP2 +from esphome.const import ( + CONF_ACTIVE, + CONF_ID, + PLATFORM_ESP32, + PLATFORM_LN882X, + PLATFORM_RP2, +) from esphome.core import CORE from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor from esphome.types import ConfigType @@ -84,7 +90,7 @@ def _esp32_config_schema() -> cv.All: f"update _IDF_MAX_CONNECTIONS in bluetooth_proxy/__init__.py" ) - CONNECTION_SCHEMA = bluetooth_connection.hub_connection_schema() + CONNECTION_SCHEMA = bluetooth_connection.hub_connection_schema(PLATFORM_ESP32) def validate_connections(config): if CONF_CONNECTIONS in config: @@ -147,7 +153,7 @@ def _rp2_config_schema() -> cv.All: """Full proxy on the rp2 BLE hub: active connections through the BTstack GATT client backend in bluetooth_connection. The slot limit comes from the prebuilt BTstack library (one connection today); the code is built for N.""" - connection_schema = bluetooth_connection.hub_connection_schema() + connection_schema = bluetooth_connection.hub_connection_schema(PLATFORM_RP2) def populate_connections(config: ConfigType) -> ConfigType: # One wrapper + backend pair per slot, declared during validation so diff --git a/tests/component_tests/bluetooth_proxy/test_outer_schema_mirror.py b/tests/component_tests/bluetooth_proxy/test_outer_schema_mirror.py index 0054b2226c..765b2e48d4 100644 --- a/tests/component_tests/bluetooth_proxy/test_outer_schema_mirror.py +++ b/tests/component_tests/bluetooth_proxy/test_outer_schema_mirror.py @@ -14,15 +14,11 @@ import voluptuous as vol from esphome import config_validation as cv from esphome.components.bluetooth_proxy import CONFIG_SCHEMA, _esp32_config_schema -from esphome.const import KEY_CORE, KEY_TARGET_PLATFORM, PLATFORM_ESP32 -from esphome.core import CORE def _esp32_schema_keys() -> dict[str, object]: - # The builder resolves the backend schema through the platform-dispatched - # bluetooth_connection.gatt_client_schema(), so the platform must be set - # (conftest's autouse reset restores CORE after each test). - CORE.data.setdefault(KEY_CORE, {})[KEY_TARGET_PLATFORM] = PLATFORM_ESP32 + # The builder names its platform explicitly, so no CORE state is needed + # (this also mirrors how the language-schema dumper calls it). return _keys(_schema_of(_esp32_config_schema()))