mirror of
https://github.com/esphome/esphome.git
synced 2026-08-22 22:26:21 +00:00
Move BTstack pool ownership into rp2040_ble and count connection slots like esp32_ble
This commit is contained in:
@@ -9,6 +9,7 @@ from collections.abc import Awaitable, Callable
|
||||
from dataclasses import dataclass
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import rp2040_ble
|
||||
from esphome.config_helpers import (
|
||||
filter_source_files_from_platform,
|
||||
frameworks_for_platforms,
|
||||
@@ -37,11 +38,11 @@ CODEOWNERS = ["@bdraco", "@jesserockz"]
|
||||
bluetooth_connection_ns = cg.esphome_ns.namespace("bluetooth_connection")
|
||||
|
||||
# arduino-pico's prebuilt BTstack is compiled with MAX_NR_GATT_CLIENTS 1 and
|
||||
# MAX_NR_HCI_CONNECTIONS 2; for more than one backend, btstack_memory_rp2.cpp
|
||||
# replaces those pools via linker --wrap (emitted by _rp2_register), sized
|
||||
# from ESPHOME_BLE_GATT_CLIENT_COUNT. 3 matches the esp32 default and stays
|
||||
# within the controller's resources (MAX_NR_CONTROLLER_ACL_BUFFERS 3).
|
||||
RP2_MAX_CONNECTIONS = 3
|
||||
# MAX_NR_HCI_CONNECTIONS 2; for more than one backend, rp2040_ble's
|
||||
# btstack_memory.cpp replaces those pools via linker --wrap (requested by
|
||||
# _rp2_register), sized from ESPHOME_BLE_GATT_CLIENT_COUNT. The cap itself
|
||||
# belongs to the platform stack that owns the pools.
|
||||
RP2_MAX_CONNECTIONS = rp2040_ble.MAX_CONNECTIONS
|
||||
|
||||
# Slot limits for the hub platforms running the connection-capable proxy;
|
||||
# the backend registry itself is _PLATFORM_BACKENDS below.
|
||||
@@ -58,16 +59,6 @@ CONF_BACKEND_ID = "backend_id"
|
||||
|
||||
DOMAIN = "bluetooth_connection"
|
||||
|
||||
# The four btstack_memory accessors whose static pools are baked into the
|
||||
# prebuilt liblwip-bt.a; every internal use crosses an object boundary in the
|
||||
# archive, so --wrap intercepts them all (see btstack_memory_rp2.cpp).
|
||||
_RP2_BTSTACK_POOL_SYMBOLS = (
|
||||
"btstack_memory_gatt_client_get",
|
||||
"btstack_memory_gatt_client_free",
|
||||
"btstack_memory_hci_connection_get",
|
||||
"btstack_memory_hci_connection_free",
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class _ConnectionData:
|
||||
@@ -87,8 +78,6 @@ def _esp32_schema_fragment() -> cv.Schema:
|
||||
|
||||
|
||||
def _rp2_schema_fragment() -> cv.Schema:
|
||||
from esphome.components import rp2040_ble
|
||||
|
||||
return cv.Schema(
|
||||
{cv.GenerateID(rp2040_ble.CONF_RP2040_BLE_ID): cv.use_id(rp2040_ble.RP2040BLE)}
|
||||
)
|
||||
@@ -103,18 +92,15 @@ async def _esp32_register(backend: cg.MockObj, config: ConfigType) -> None:
|
||||
|
||||
|
||||
async def _rp2_register(backend: cg.MockObj, config: ConfigType) -> None:
|
||||
from esphome.components import rp2040_ble
|
||||
|
||||
# More than one backend outgrows the prebuilt BTstack pools: swap them for
|
||||
# the ESPHOME_BLE_GATT_CLIENT_COUNT-sized ones in btstack_memory_rp2.cpp.
|
||||
# Keyed to backend registrations (the same event that grows the count that
|
||||
# sizes the pools), so single-backend builds emit no flags and stay
|
||||
# byte-identical to previous releases.
|
||||
# the ESPHOME_BLE_GATT_CLIENT_COUNT-sized ones in rp2040_ble's
|
||||
# btstack_memory.cpp. Keyed to backend registrations (the same event that
|
||||
# grows the count that sizes the pools), so single-backend builds emit no
|
||||
# flags and stay byte-identical to previous releases.
|
||||
data = _get_data()
|
||||
data.rp2_backend_count += 1
|
||||
if data.rp2_backend_count == 2:
|
||||
for symbol in _RP2_BTSTACK_POOL_SYMBOLS:
|
||||
cg.add_build_flag(f"-Wl,--wrap={symbol}")
|
||||
rp2040_ble.add_btstack_pool_overrides()
|
||||
await cg.register_parented(backend, config[rp2040_ble.CONF_RP2040_BLE_ID])
|
||||
|
||||
|
||||
@@ -205,7 +191,6 @@ SOURCE_FILE_FRAMEWORKS: dict[str, set[PlatformFramework]] = {
|
||||
PlatformFramework.ESP32_IDF,
|
||||
},
|
||||
"bluetooth_connection_rp2.cpp": {PlatformFramework.RP2_ARDUINO},
|
||||
"btstack_memory_rp2.cpp": {PlatformFramework.RP2_ARDUINO},
|
||||
}
|
||||
|
||||
FILTER_SOURCE_FILES = filter_source_files_from_platform(SOURCE_FILE_FRAMEWORKS)
|
||||
|
||||
@@ -157,15 +157,17 @@ def _rp2_config_schema() -> cv.All:
|
||||
connection_schema = bluetooth_connection.hub_connection_schema(PLATFORM_RP2)
|
||||
|
||||
def populate_connections(config: ConfigType) -> ConfigType:
|
||||
from esphome.components import rp2040_ble
|
||||
|
||||
# One wrapper + backend pair per slot, declared during validation so
|
||||
# their ids exist for codegen (the esp32 arm's `connections` pattern).
|
||||
if not config[CONF_ACTIVE]:
|
||||
return config
|
||||
connection_slots: int = config[CONF_CONNECTION_SLOTS]
|
||||
rp2040_ble.consume_connection_slots(connection_slots, "bluetooth_proxy")(config)
|
||||
return {
|
||||
**config,
|
||||
CONF_CONNECTIONS: [
|
||||
connection_schema({}) for _ in range(config[CONF_CONNECTION_SLOTS])
|
||||
],
|
||||
CONF_CONNECTIONS: [connection_schema({}) for _ in range(connection_slots)],
|
||||
}
|
||||
|
||||
max_conn = bluetooth_connection.HUB_MAX_CONNECTIONS[PLATFORM_RP2]
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
from collections.abc import Callable, MutableMapping
|
||||
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ENABLE_ON_BOOT, CONF_ID
|
||||
from esphome.core import CORE
|
||||
from esphome.types import ConfigType
|
||||
|
||||
DEPENDENCIES = ["rp2"]
|
||||
@@ -8,6 +11,15 @@ CODEOWNERS = ["@bdraco"]
|
||||
|
||||
CONF_RP2040_BLE_ID = "rp2040_ble_id"
|
||||
|
||||
KEY_RP2040_BLE = "rp2040_ble"
|
||||
KEY_USED_CONNECTION_SLOTS = "used_connection_slots"
|
||||
|
||||
# Hard platform cap on concurrent GATT connections: the BTstack pool overrides
|
||||
# in btstack_memory.cpp are sized from ESPHOME_BLE_GATT_CLIENT_COUNT with this
|
||||
# as the ceiling. 3 matches the esp32 default and stays within the
|
||||
# controller's resources (MAX_NR_CONTROLLER_ACL_BUFFERS 3).
|
||||
MAX_CONNECTIONS = 3
|
||||
|
||||
rp2040_ble_ns = cg.esphome_ns.namespace("rp2040_ble")
|
||||
RP2040BLE = rp2040_ble_ns.class_("RP2040BLE", cg.Component)
|
||||
|
||||
@@ -30,13 +42,61 @@ def _validate_board(config: ConfigType) -> ConfigType:
|
||||
return config
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _validate_board
|
||||
def consume_connection_slots(
|
||||
value: int, consumer: str
|
||||
) -> Callable[[MutableMapping], MutableMapping]:
|
||||
"""Reserve BLE connection slots for a component (the esp32_ble pattern);
|
||||
the total is checked against MAX_CONNECTIONS in final validation."""
|
||||
|
||||
def _consume_connection_slots(config: MutableMapping) -> MutableMapping:
|
||||
data: dict = CORE.data.setdefault(KEY_RP2040_BLE, {})
|
||||
slots: list[str] = data.setdefault(KEY_USED_CONNECTION_SLOTS, [])
|
||||
slots.extend([consumer] * value)
|
||||
return config
|
||||
|
||||
return _consume_connection_slots
|
||||
|
||||
|
||||
def _final_validate(config: ConfigType) -> ConfigType:
|
||||
_validate_board(config)
|
||||
# Skip in testing mode to allow component grouping (esp32_ble parity).
|
||||
if not CORE.testing_mode:
|
||||
used = CORE.data.get(KEY_RP2040_BLE, {}).get(KEY_USED_CONNECTION_SLOTS, [])
|
||||
if len(used) > MAX_CONNECTIONS:
|
||||
raise cv.Invalid(
|
||||
f"BLE components require {len(used)} connection slots but the "
|
||||
f"rp2 maximum is {MAX_CONNECTIONS}. "
|
||||
f"Components: {', '.join(used)}"
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
|
||||
|
||||
# Once per registered scan listener; sizes the controller's StaticVector
|
||||
# listener storage.
|
||||
request_scan_listener_slot = cg.slot_counter("RP2040_BLE_SCAN_LISTENER_COUNT")
|
||||
|
||||
# The four btstack_memory accessors whose static pools are baked into the
|
||||
# prebuilt liblwip-bt.a; every internal use crosses an object boundary in the
|
||||
# archive, so --wrap intercepts them all (see btstack_memory.cpp).
|
||||
_BTSTACK_POOL_SYMBOLS = (
|
||||
"btstack_memory_gatt_client_get",
|
||||
"btstack_memory_gatt_client_free",
|
||||
"btstack_memory_hci_connection_get",
|
||||
"btstack_memory_hci_connection_free",
|
||||
)
|
||||
|
||||
|
||||
def add_btstack_pool_overrides() -> None:
|
||||
"""Emit the --wrap flags that swap the prebuilt BTstack pools for the
|
||||
ESPHOME_BLE_GATT_CLIENT_COUNT-sized ones in btstack_memory.cpp. Called by
|
||||
bluetooth_connection when a second GATT backend registers; idempotent
|
||||
(build flags are a set)."""
|
||||
for symbol in _BTSTACK_POOL_SYMBOLS:
|
||||
cg.add_build_flag(f"-Wl,--wrap={symbol}")
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
|
||||
+9
-8
@@ -1,12 +1,13 @@
|
||||
// Replaces the gatt_client / hci_connection static pools baked into
|
||||
// arduino-pico's prebuilt liblwip-bt.a (built with MAX_NR_GATT_CLIENTS 1,
|
||||
// MAX_NR_HCI_CONNECTIONS 2) with pools sized from ESPHOME_BLE_GATT_CLIENT_COUNT.
|
||||
// bluetooth_connection/__init__.py emits the matching -Wl,--wrap flags only
|
||||
// when more than one backend registers; single-backend builds emit no flags
|
||||
// and this file compiles to nothing, leaving the prebuilt pools in charge.
|
||||
// Layout safety: the framework defines ENABLE_CLASSIC / ENABLE_BLE for
|
||||
// every user TU whenever PIO_FRAMEWORK_ARDUINO_ENABLE_BLUETOOTH is set
|
||||
// (rp2040_ble always sets it), so sizeof() here matches the archive.
|
||||
// add_btstack_pool_overrides() in this component's codegen emits the matching
|
||||
// -Wl,--wrap flags, requested by bluetooth_connection when more than one GATT
|
||||
// backend registers; single-backend builds emit no flags and this file
|
||||
// compiles to nothing, leaving the prebuilt pools in charge. Layout safety:
|
||||
// the framework defines ENABLE_CLASSIC / ENABLE_BLE for every user TU
|
||||
// whenever PIO_FRAMEWORK_ARDUINO_ENABLE_BLUETOOTH is set (this component
|
||||
// always sets it), so sizeof() here matches the archive.
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
@@ -16,7 +17,7 @@
|
||||
|
||||
#include <cstring>
|
||||
|
||||
namespace esphome::bluetooth_connection {
|
||||
namespace esphome::rp2040_ble {
|
||||
namespace {
|
||||
|
||||
// Pinned against arduino-pico 6.0.0's prebuilt archives: a framework bump (or
|
||||
@@ -112,6 +113,6 @@ void __wrap_btstack_memory_hci_connection_free(hci_connection_t *hci_connection)
|
||||
} // extern "C"
|
||||
// NOLINTEND(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp,readability-identifier-naming)
|
||||
|
||||
} // namespace esphome::bluetooth_connection
|
||||
} // namespace esphome::rp2040_ble
|
||||
|
||||
#endif // USE_RP2040_BLE && USE_BLE_GATT_CLIENT && ESPHOME_BLE_GATT_CLIENT_COUNT > 1
|
||||
+2
-2
@@ -7,13 +7,13 @@ from __future__ import annotations
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
|
||||
from esphome.components import bluetooth_connection
|
||||
from esphome.components import rp2040_ble
|
||||
from esphome.core import CORE
|
||||
|
||||
from ..helpers import get_define_value
|
||||
|
||||
WRAP_FLAGS = tuple(
|
||||
f"-Wl,--wrap={symbol}" for symbol in bluetooth_connection._RP2_BTSTACK_POOL_SYMBOLS
|
||||
f"-Wl,--wrap={symbol}" for symbol in rp2040_ble._BTSTACK_POOL_SYMBOLS
|
||||
)
|
||||
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
# 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:
|
||||
Reference in New Issue
Block a user