[rp2040_ble][bluetooth_connection] 3 connection slots on rp2 with esp32 parity (#18247)

This commit is contained in:
J. Nick Koston
2026-08-11 08:43:08 -05:00
committed by GitHub
parent f3a5a9fbd5
commit aee41d64c2
20 changed files with 688 additions and 123 deletions
@@ -142,8 +142,8 @@ def test_rp2_defaults_to_the_full_proxy(
_register_tracker(PLATFORM_RP2)
validated = bluetooth_proxy.CONFIG_SCHEMA({})
assert validated[CONF_ACTIVE] is True
assert validated[bluetooth_proxy.CONF_CONNECTION_SLOTS] == 1
assert len(validated[bluetooth_proxy.CONF_CONNECTIONS]) == 1
assert validated[bluetooth_proxy.CONF_CONNECTION_SLOTS] == 3
assert len(validated[bluetooth_proxy.CONF_CONNECTIONS]) == 3
def test_rp2_accepts_explicit_passive(
@@ -159,11 +159,15 @@ def test_rp2_accepts_explicit_passive(
def test_rp2_rejects_slots_beyond_the_btstack_limit(
set_core_config: SetCoreConfigCallable,
) -> None:
# The prebuilt BTstack library allows exactly one GATT client connection.
# The BTstack pool overrides are sized for RP2_MAX_CONNECTIONS slots.
set_core_config(PlatformFramework.RP2_ARDUINO)
_register_tracker(PLATFORM_RP2)
with pytest.raises(cv.Invalid, match="at most 1 connection slot"):
bluetooth_proxy.CONFIG_SCHEMA({"connection_slots": 2})
with pytest.raises(cv.Invalid, match="at most 3 connection slot"):
bluetooth_proxy.CONFIG_SCHEMA({"connection_slots": 4})
# Fewer slots than the cap stay accepted (the prebuilt single-client pool
# path for 1, the wrap path for 2).
validated = bluetooth_proxy.CONFIG_SCHEMA({"connection_slots": 1})
assert len(validated[bluetooth_proxy.CONF_CONNECTIONS]) == 1
# Values past even the loosest platform cap stop at the outer walkable
# schema, which stays bounded for range walkers (device-builder sync);
# in-range values get the platform message above.
@@ -0,0 +1,15 @@
esphome:
name: poolwrap-rp2-default
rp2:
board: rpipicow
wifi:
ssid: MySSID
password: password1
api:
rp2_ble_tracker:
bluetooth_proxy:
@@ -0,0 +1,16 @@
esphome:
name: poolwrap-rp2-single
rp2:
board: rpipicow
wifi:
ssid: MySSID
password: password1
api:
rp2_ble_tracker:
bluetooth_proxy:
connection_slots: 1
@@ -0,0 +1,16 @@
esphome:
name: poolwrap-rp2-two
rp2:
board: rpipicow
wifi:
ssid: MySSID
password: password1
api:
rp2_ble_tracker:
bluetooth_proxy:
connection_slots: 2
@@ -0,0 +1,41 @@
"""Connection-slot accounting: consumers claim against MAX_CONNECTIONS and
final validation rejects over-subscription with the consumer list."""
from __future__ import annotations
from collections.abc import Callable
from pathlib import Path
import pytest
from esphome import config_validation as cv
from esphome.components import rp2040_ble
from esphome.core import CORE
def test_proxy_claims_its_slots_through_the_shared_accounting(
generate_main: Callable[[str | Path], str],
component_config_path: Callable[[str], Path],
) -> None:
# A default (3-slot) proxy build records one claim per slot, attributed
# to the consumer, and passes final validation.
generate_main(component_config_path("rp2_proxy_default.yaml"))
used = CORE.data[rp2040_ble.KEY_RP2040_BLE][rp2040_ble.KEY_USED_CONNECTION_SLOTS]
assert used == ["bluetooth_proxy"] * 3
def test_oversubscription_is_rejected_with_the_consumer_list() -> None:
# No YAML shape reaches this today (the proxy schema caps at the same
# limit); the guard exists for a second consumer such as ble_client.
rp2040_ble.consume_connection_slots(3, "bluetooth_proxy")({})
rp2040_ble.consume_connection_slots(1, "ble_client")({})
with pytest.raises(
cv.Invalid,
match=r"4 connection slots.*maximum is 3.*bluetooth_proxy.*ble_client",
):
rp2040_ble.validate_connection_slots()
def test_at_cap_passes() -> None:
rp2040_ble.consume_connection_slots(3, "bluetooth_proxy")({})
rp2040_ble.validate_connection_slots()
@@ -0,0 +1,52 @@
"""The rp2 BTstack pool overrides: multi-slot builds emit the --wrap flags
that swap the prebuilt single-client pools for the codegen-sized ones;
single-slot builds emit none and stay byte-identical to previous releases."""
from __future__ import annotations
from collections.abc import Callable
from pathlib import Path
from esphome.core import CORE
from ..helpers import get_define_value
# Spelled out rather than derived from rp2040_ble's symbol tuple, so a typo
# in the component's list fails here instead of mirroring into the test.
WRAP_FLAGS = (
"-Wl,--wrap=btstack_memory_gatt_client_get",
"-Wl,--wrap=btstack_memory_gatt_client_free",
"-Wl,--wrap=btstack_memory_hci_connection_get",
"-Wl,--wrap=btstack_memory_hci_connection_free",
)
def test_default_slots_emit_the_pool_wrap(
generate_main: Callable[[str | Path], str],
component_config_path: Callable[[str], Path],
) -> None:
generate_main(component_config_path("rp2_proxy_default.yaml"))
assert all(flag in CORE.build_flags for flag in WRAP_FLAGS)
assert get_define_value("ESPHOME_BLE_GATT_CLIENT_COUNT") == "3"
assert get_define_value("BLUETOOTH_PROXY_MAX_CONNECTIONS") == "3"
def test_two_slots_emit_the_pool_wrap(
generate_main: Callable[[str | Path], str],
component_config_path: Callable[[str], Path],
) -> None:
# Two slots: the wrap pools are smaller than the cap, sized from the count.
generate_main(component_config_path("rp2_proxy_two_slots.yaml"))
assert all(flag in CORE.build_flags for flag in WRAP_FLAGS)
assert get_define_value("ESPHOME_BLE_GATT_CLIENT_COUNT") == "2"
assert get_define_value("BLUETOOTH_PROXY_MAX_CONNECTIONS") == "2"
def test_single_slot_keeps_the_prebuilt_pools(
generate_main: Callable[[str | Path], str],
component_config_path: Callable[[str], Path],
) -> None:
generate_main(component_config_path("rp2_proxy_single_slot.yaml"))
assert not any(flag in CORE.build_flags for flag in WRAP_FLAGS)
assert get_define_value("ESPHOME_BLE_GATT_CLIENT_COUNT") == "1"
assert get_define_value("BLUETOOTH_PROXY_MAX_CONNECTIONS") == "1"
@@ -6,6 +6,7 @@ packages:
rp2_ble_tracker:
# Two slots: the one shape where the wrap pools are smaller than the cap.
bluetooth_proxy:
active: true
connection_slots: 1
connection_slots: 2
@@ -1,5 +1,7 @@
# Full proxy on the rp2 BLE hub: active defaults to true here (esp32 parity),
# so this compiles the BTstack GATT client backend and one connection slot.
# so this compiles the BTstack GATT client backend with the default three
# connection slots, exercising the rp2040_ble/btstack_memory.cpp pool --wrap
# link.
# No explicit ble_hub_id: the generated binding resolves the single declared
# hub, and an inline id here would collide with rp2_ble_tracker's own fixture
# once CI merges both components into one grouped rp2040-ard build (grouped
@@ -0,0 +1,9 @@
# Pico 2 W build of the full proxy: links the rp2350 framework archive, so
# the pool --wrap overrides and their per-architecture layout asserts are
# exercised for this chip too (see test.rp2040-ard.yaml for the slot shape).
packages:
common: !include common.yaml
rp2_ble_tracker:
bluetooth_proxy:
@@ -2,8 +2,10 @@ esphome:
name: componenttestrp2040pico2ard
friendly_name: $component_name
# rpipico2w: superset of rpipico2 with the CYW43 radio, so wireless
# components (wifi, BLE) can share this target too.
rp2:
board: rpipico2
board: rpipico2w
logger:
level: VERY_VERBOSE