[bluetooth_proxy] Migrate esp32 onto the neutral GATT backend (#18198)

This commit is contained in:
J. Nick Koston
2026-08-10 10:47:56 -05:00
committed by GitHub
parent c8c929d487
commit f3d1fc0d64
26 changed files with 1518 additions and 976 deletions
@@ -9,11 +9,13 @@ import pytest
from esphome import config_validation as cv
from esphome.components import ble_device_base, bluetooth_connection, bluetooth_proxy
from esphome.config_helpers import frameworks_for_platforms
from esphome.const import (
CONF_ACTIVE,
KEY_CORE,
KEY_TARGET_FRAMEWORK,
KEY_TARGET_PLATFORM,
PLATFORM_ESP32,
PLATFORM_LN882X,
PLATFORM_RP2,
PlatformFramework,
@@ -177,28 +179,49 @@ def test_rp2_rejects_esp32_only_keys_by_name(
bluetooth_proxy.CONFIG_SCHEMA({"connections": [{}]})
def test_hub_source_filter_covers_every_hub_platform() -> None:
# bluetooth_connection cannot import this module to derive the hub.cpp
# framework set, so pin it here: a platform admitted to the proxy but
# missing from the filter would validate, then fail at link.
expected = frameworks_for_platforms(
[*bluetooth_proxy._HUB_PLATFORMS, PLATFORM_ESP32]
)
hub_frameworks = bluetooth_connection.SOURCE_FILE_FRAMEWORKS[
"bluetooth_connection_hub.cpp"
]
assert expected == hub_frameworks
def test_bluetooth_connection_auto_load_covers_its_includes() -> None:
# The esp32 connection header includes esp32_ble_client; the auto load
# must satisfy that closure itself (regression: it once relied on the
# consumer's auto loads).
# The backend registers with its platform BLE stack (and the Bluedroid
# header includes the tracker's), so that closure lives here and
# consumers stay platform-blind; the platform-less arm is the union for
# manifest-resolving tooling.
_set_platform("esp32")
assert "esp32_ble_client" in bluetooth_connection.AUTO_LOAD()
assert bluetooth_connection.AUTO_LOAD() == ["ble_device_base", "esp32_ble_tracker"]
_set_platform("rp2")
assert bluetooth_connection.AUTO_LOAD() == ["ble_device_base", "rp2040_ble"]
_set_platform("ln882x")
assert bluetooth_connection.AUTO_LOAD() == ["ble_device_base"]
# No target platform (tooling resolving the manifest): the union, so
# dependency closures stay complete for build_codeowners and friends.
_set_platform(None)
assert bluetooth_connection.AUTO_LOAD() == ["ble_device_base", "esp32_ble_client"]
assert bluetooth_connection.AUTO_LOAD() == [
"ble_device_base",
"esp32_ble_tracker",
"rp2040_ble",
]
def test_every_registered_hub_platform_has_a_schema_arm() -> None:
# A platform added to HUB_MAX_CONNECTIONS without a schema builder,
# codegen arm, or _HUB_PLATFORMS entry would only fail when a config for
# it is validated (or not even then); pin all three couplings here.
# A platform added to HUB_MAX_CONNECTIONS without a schema builder or
# _HUB_PLATFORMS entry would only fail when a config for it is validated
# (or not even then); pin both couplings here. Connection codegen is
# shared (bluetooth_connection.new_gatt_backend), so it needs no arm.
registered = set(bluetooth_connection.HUB_MAX_CONNECTIONS)
assert registered <= set(bluetooth_proxy._GATT_HUB_SCHEMAS)
assert registered <= set(bluetooth_proxy._GATT_HUB_TO_CODE)
assert registered <= set(bluetooth_proxy._HUB_PLATFORMS)
# Hub platforms must also be in the backend registry the shared codegen
# helpers dispatch on.
assert registered <= set(bluetooth_connection._PLATFORM_BACKENDS)
# The outer walkable schema's bound must stay the loosest platform cap.
assert (
max(bluetooth_connection.HUB_MAX_CONNECTIONS.values())
@@ -220,9 +243,14 @@ def test_defines_h_mirrors_the_rp2_slot_cap() -> None:
assert int(match.group(1)) == cap, (
f"defines.h rp2 arm carries {match.group(1)}, expected {cap}"
)
# The static-analysis client count scales with the same cap.
match = re.search(r"#define ESPHOME_BLE_GATT_CLIENT_COUNT (\d+)", defines)
assert match is not None, "ESPHOME_BLE_GATT_CLIENT_COUNT missing from defines.h"
# The static-analysis client count scales with the same cap. Scoped to
# the USE_RP2 block: the esp32 arm carries its own count.
rp2_block = re.search(r"#ifdef USE_RP2\n((?:#define [^\n]*\n)+)", defines)
assert rp2_block is not None, "no USE_RP2 platform block in defines.h"
match = re.search(
r"#define ESPHOME_BLE_GATT_CLIENT_COUNT (\d+)", rp2_block.group(1)
)
assert match is not None, "ESPHOME_BLE_GATT_CLIENT_COUNT missing from rp2 block"
assert int(match.group(1)) == cap, (
f"ESPHOME_BLE_GATT_CLIENT_COUNT is {match.group(1)}, expected {cap}"
f"rp2 ESPHOME_BLE_GATT_CLIENT_COUNT is {match.group(1)}, expected {cap}"
)