mirror of
https://github.com/esphome/esphome.git
synced 2026-08-17 10:52:56 +08:00
[bluetooth_proxy] Enable active connections on rp2 (#18132)
This commit is contained in:
@@ -59,15 +59,17 @@ _LOGGER = logging.getLogger(__name__)
|
||||
CONF_CONNECTION_SLOTS = "connection_slots"
|
||||
CONF_CACHE_SERVICES = "cache_services"
|
||||
CONF_CONNECTIONS = "connections"
|
||||
CONF_BACKEND_ID = "backend_id"
|
||||
DEFAULT_CONNECTION_SLOTS = 3
|
||||
|
||||
bluetooth_proxy_ns = cg.esphome_ns.namespace("bluetooth_proxy")
|
||||
|
||||
BluetoothProxy = bluetooth_proxy_ns.class_("BluetoothProxy", cg.Component)
|
||||
|
||||
# Mirrors esp32_ble.IDF_MAX_CONNECTIONS as a literal so the statically walkable
|
||||
# CONFIG_SCHEMA below can state the connection_slots range without importing the
|
||||
# esp32 BLE stack. tests/component_tests/bluetooth_proxy/ pins the two together.
|
||||
# Mirrors esp32_ble.IDF_MAX_CONNECTIONS (the loosest platform cap): the esp32
|
||||
# schema builder asserts the two agree, tests/component_tests/bluetooth_proxy/
|
||||
# pins them together, and the outer walkable schema uses it as the
|
||||
# connection_slots bound (per-platform schemas tighten it).
|
||||
_IDF_MAX_CONNECTIONS = 9
|
||||
|
||||
|
||||
@@ -147,17 +149,99 @@ def _validate_no_active(config: ConfigType) -> ConfigType:
|
||||
return config
|
||||
|
||||
|
||||
# Advertisement-only proxy on a neutral BLE hub: the hub's raw-advertisement
|
||||
# callback feeds the same API batching. GATT/active connections are excluded at
|
||||
# compile time — only the esp32 build compiles the connection stack; nothing
|
||||
# reads HubCapabilities::gatt at runtime for this today.
|
||||
# Keys both platform schemas must declare identically; each arm spreads this
|
||||
# dict so the shared surface cannot drift. CONF_ACTIVE deliberately stays
|
||||
# per-arm: its default differs (esp32 True, hub arms False — no GATT).
|
||||
@functools.cache
|
||||
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."""
|
||||
from esphome.components import rp2040_ble
|
||||
|
||||
connection_schema = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(bluetooth_connection.HubBluetoothConnection),
|
||||
cv.GenerateID(CONF_BACKEND_ID): cv.declare_id(
|
||||
bluetooth_connection.RP2GattClient
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
def populate_connections(config: ConfigType) -> ConfigType:
|
||||
# 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
|
||||
return {
|
||||
**config,
|
||||
CONF_CONNECTIONS: [
|
||||
connection_schema({}) for _ in range(config[CONF_CONNECTION_SLOTS])
|
||||
],
|
||||
}
|
||||
|
||||
max_conn = bluetooth_connection.HUB_MAX_CONNECTIONS[PLATFORM_RP2]
|
||||
schema = (
|
||||
cv.Schema(
|
||||
{
|
||||
**_COMMON_SCHEMA_KEYS,
|
||||
# The GATT backend drives the controller directly (connect, GATT
|
||||
# ops), not through the tracker hub.
|
||||
cv.GenerateID(rp2040_ble.CONF_RP2040_BLE_ID): cv.use_id(
|
||||
rp2040_ble.RP2040BLE
|
||||
),
|
||||
cv.Optional(CONF_ACTIVE, default=True): cv.boolean,
|
||||
cv.Optional(
|
||||
CONF_CONNECTION_SLOTS,
|
||||
default=min(DEFAULT_CONNECTION_SLOTS, max_conn),
|
||||
): cv.All(
|
||||
cv.positive_int,
|
||||
cv.Range(
|
||||
min=1,
|
||||
max=max_conn,
|
||||
msg=f"rp2 supports at most {max_conn} connection slot(s); "
|
||||
"the framework's BTstack library is built with "
|
||||
f"MAX_NR_GATT_CLIENTS {max_conn}",
|
||||
),
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(
|
||||
# ble_hub_id with the friendly no-tracker-configured guard.
|
||||
ble_device_base.BLE_DEVICE_SCHEMA
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
return cv.All(schema, populate_connections)
|
||||
|
||||
|
||||
async def _rp2_connections_to_code(var: cg.MockObj, config: ConfigType) -> None:
|
||||
from esphome.components import rp2040_ble
|
||||
|
||||
# One wrapper + backend pair per slot (the esp32 arm's pattern).
|
||||
for connection_conf in config[CONF_CONNECTIONS]:
|
||||
ble_device_base.request_gatt_client()
|
||||
backend = cg.new_Pvariable(connection_conf[CONF_BACKEND_ID])
|
||||
await cg.register_component(backend, connection_conf)
|
||||
await cg.register_parented(backend, config[rp2040_ble.CONF_RP2040_BLE_ID])
|
||||
connection = cg.new_Pvariable(connection_conf[CONF_ID])
|
||||
cg.add(connection.set_backend(backend))
|
||||
cg.add(var.register_connection(connection))
|
||||
|
||||
|
||||
# Per-platform schema builders and connection codegen; every key of
|
||||
# bluetooth_connection.HUB_MAX_CONNECTIONS needs an entry in both (pinned by
|
||||
# tests/component_tests/bluetooth_proxy/).
|
||||
_GATT_HUB_SCHEMAS = {PLATFORM_RP2: _rp2_config_schema}
|
||||
_GATT_HUB_TO_CODE = {PLATFORM_RP2: _rp2_connections_to_code}
|
||||
|
||||
|
||||
# Keys every platform arm declares identically; each arm spreads this dict so
|
||||
# the shared surface cannot drift. CONF_ACTIVE stays per-arm: its default
|
||||
# differs (esp32 True, rp2 True, advertisement-only False).
|
||||
_COMMON_SCHEMA_KEYS = {
|
||||
cv.GenerateID(): cv.declare_id(BluetoothProxy),
|
||||
}
|
||||
|
||||
# Advertisement-only proxy on a neutral BLE hub: the hub's raw-advertisement
|
||||
# callback feeds the same API batching, no connection stack compiled.
|
||||
_BLE_HUB_CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
@@ -178,9 +262,10 @@ _BLE_HUB_CONFIG_SCHEMA = cv.All(
|
||||
def _validate_platform(config: ConfigType) -> ConfigType:
|
||||
"""Apply the schema for the platform actually being compiled.
|
||||
|
||||
esp32 keeps the full GATT proxy; every other platform gets the
|
||||
advertisement-only shape, which rejects the connection-oriented options
|
||||
above because its schema does not define them.
|
||||
Three-way dispatch: esp32 gets the full GATT proxy, HUB_MAX_CONNECTIONS
|
||||
platforms get their _GATT_HUB_SCHEMAS arm, the remaining hub platforms get
|
||||
the advertisement-only shape; unsupported keys were already rejected by
|
||||
name in _reject_unsupported_connection_keys.
|
||||
"""
|
||||
if config is SCHEMA_EXTRACT:
|
||||
# The language-schema dumper runs without a platform. Expose the esp32
|
||||
@@ -196,18 +281,21 @@ def _validate_platform(config: ConfigType) -> ConfigType:
|
||||
raise cv.Invalid(
|
||||
f"bluetooth_proxy is not supported on {CORE.target_platform}: no "
|
||||
"active-scan-capable BLE tracker hub is available for this "
|
||||
"platform. It runs on esp32 (full proxy), and the ln882x and rp2 "
|
||||
"families (advertisement-only)."
|
||||
"platform. It runs on esp32 and rp2 (full proxy) and the ln882x "
|
||||
"family (advertisement-only)."
|
||||
)
|
||||
if CORE.target_platform in bluetooth_connection.HUB_MAX_CONNECTIONS:
|
||||
return _GATT_HUB_SCHEMAS[CORE.target_platform]()(config)
|
||||
return _BLE_HUB_CONFIG_SCHEMA(config)
|
||||
|
||||
|
||||
def _reject_connection_keys_off_esp32(config: ConfigType) -> ConfigType:
|
||||
"""Reject connection-oriented options by name on hub-only platforms.
|
||||
def _reject_unsupported_connection_keys(config: ConfigType) -> ConfigType:
|
||||
"""Reject connection options a platform does not support, by name.
|
||||
|
||||
Runs before the walkable schema below so the user gets "this option does
|
||||
not exist here" instead of the option's esp32 value range (which would
|
||||
imply a smaller number is accepted).
|
||||
GATT hub platforms keep connection_slots but reject the esp32-only keys;
|
||||
advertisement-only hubs reject all three. Runs before the walkable schema
|
||||
below so the user gets "this option does not exist here" instead of a
|
||||
value-range error implying the option works.
|
||||
"""
|
||||
if not isinstance(config, dict) or CORE.is_esp32 or CORE.target_platform is None:
|
||||
return config
|
||||
@@ -216,14 +304,28 @@ def _reject_connection_keys_off_esp32(config: ConfigType) -> ConfigType:
|
||||
# reports "not supported on {platform}" instead of a key-level message
|
||||
# implying an advertisement-only proxy is available.
|
||||
return config
|
||||
for key in (CONF_CONNECTION_SLOTS, CONF_CACHE_SERVICES, CONF_CONNECTIONS):
|
||||
if CORE.target_platform in bluetooth_connection.HUB_MAX_CONNECTIONS:
|
||||
# Full proxy: connection_slots is real here; the per-connection list
|
||||
# exists internally but carries no user options, and the Bluedroid
|
||||
# NVS service cache is esp32-only.
|
||||
rejected = {
|
||||
CONF_CONNECTIONS: (
|
||||
"has no per-connection options on this platform; use "
|
||||
"'connection_slots' to set the count"
|
||||
),
|
||||
CONF_CACHE_SERVICES: "is esp32-only (Bluedroid NVS service cache)",
|
||||
}
|
||||
else:
|
||||
reason = (
|
||||
"requires active connection support; this platform runs the "
|
||||
"advertisement-only proxy and has no such option"
|
||||
)
|
||||
rejected = dict.fromkeys(
|
||||
(CONF_CONNECTION_SLOTS, CONF_CACHE_SERVICES, CONF_CONNECTIONS), reason
|
||||
)
|
||||
for key, reason in rejected.items():
|
||||
if key in config:
|
||||
raise cv.Invalid(
|
||||
f"'{key}' requires active connection support, which needs the "
|
||||
"esp32 GATT stack; this platform runs the advertisement-only "
|
||||
"proxy and has no such option",
|
||||
path=[key],
|
||||
)
|
||||
raise cv.Invalid(f"'{key}' {reason}", path=[key])
|
||||
return config
|
||||
|
||||
|
||||
@@ -241,11 +343,14 @@ def _reject_connection_keys_off_esp32(config: ConfigType) -> ConfigType:
|
||||
# rejects it as empty. extra=ALLOW_EXTRA passes `connections` through untouched
|
||||
# for _ESP32_CONFIG_SCHEMA to validate exactly once.
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
_reject_connection_keys_off_esp32,
|
||||
_reject_unsupported_connection_keys,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_ACTIVE): cv.boolean,
|
||||
cv.Optional(CONF_CACHE_SERVICES): cv.boolean,
|
||||
# Bounded by the loosest platform cap so range walkers (the
|
||||
# device-builder field-range sync) see a real Range; the
|
||||
# per-platform schemas tighten it (1 on rp2) with their own error.
|
||||
cv.Optional(CONF_CONNECTION_SLOTS): cv.All(
|
||||
cv.positive_int,
|
||||
cv.Range(min=1, max=_IDF_MAX_CONNECTIONS),
|
||||
@@ -295,8 +400,15 @@ async def _to_code_ble_hub(config: ConfigType) -> None:
|
||||
cg.add(var.set_ble_hub(hub))
|
||||
|
||||
# The api component sizes BluetoothConnectionsFreeResponse.allocated with
|
||||
# this define whenever a proxy is present; no connections off-esp32.
|
||||
cg.add_define("BLUETOOTH_PROXY_MAX_CONNECTIONS", 0)
|
||||
# this define whenever a proxy is present. Zero on advertisement-only hubs.
|
||||
# Sized from the instantiated connections so the define can never diverge
|
||||
# from the loop below (the define sizes fixed storage in the proxy).
|
||||
slots = len(config.get(CONF_CONNECTIONS, ()))
|
||||
cg.add_define("BLUETOOTH_PROXY_MAX_CONNECTIONS", slots)
|
||||
if not slots:
|
||||
return
|
||||
|
||||
await _GATT_HUB_TO_CODE[CORE.target_platform](var, config)
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
|
||||
@@ -6,6 +6,8 @@ from esphome.types import ConfigType
|
||||
DEPENDENCIES = ["rp2"]
|
||||
CODEOWNERS = ["@bdraco"]
|
||||
|
||||
CONF_RP2040_BLE_ID = "rp2040_ble_id"
|
||||
|
||||
rp2040_ble_ns = cg.esphome_ns.namespace("rp2040_ble")
|
||||
RP2040BLE = rp2040_ble_ns.class_("RP2040BLE", cg.Component)
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ Scan modes:
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import ble_device_base, ota, rp2040_ble
|
||||
from esphome.components.const import CONF_SCAN_PARAMETERS, CONF_WINDOW
|
||||
from esphome.components.rp2040_ble import CONF_RP2040_BLE_ID
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ACTIVE,
|
||||
@@ -22,8 +23,6 @@ from esphome.const import (
|
||||
)
|
||||
from esphome.types import ConfigType
|
||||
|
||||
CONF_RP2040_BLE_ID = "rp2040_ble_id"
|
||||
|
||||
DEPENDENCIES = ["rp2"]
|
||||
AUTO_LOAD = ["ble_device_base", "rp2040_ble"]
|
||||
CODEOWNERS = ["@bdraco"]
|
||||
|
||||
@@ -253,10 +253,13 @@
|
||||
#if defined(USE_ESP32) || defined(USE_LIBRETINY) || defined(USE_RP2)
|
||||
#define USE_BLUETOOTH_PROXY
|
||||
// Mirror the codegen values per platform: _to_code_esp32() emits the connection
|
||||
// count (default 3), _to_code_ble_hub() emits 0 — so static analysis checks the
|
||||
// same std::array<uint64_t, N> instantiation a real build produces.
|
||||
// count (default 3), _to_code_ble_hub() emits the slot count (1 on rp2, 0 on
|
||||
// advertisement-only hubs) — so static analysis checks the same
|
||||
// std::array<uint64_t, N> instantiation a real build produces.
|
||||
#ifdef USE_ESP32
|
||||
#define BLUETOOTH_PROXY_MAX_CONNECTIONS 3
|
||||
#elif defined(USE_RP2)
|
||||
#define BLUETOOTH_PROXY_MAX_CONNECTIONS 1
|
||||
#else
|
||||
#define BLUETOOTH_PROXY_MAX_CONNECTIONS 0
|
||||
#endif
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
"""bluetooth_proxy mirrors esp32_ble.IDF_MAX_CONNECTIONS; pin them together.
|
||||
|
||||
The mirror exists so the statically walkable CONFIG_SCHEMA can express the
|
||||
connection_slots range without importing the esp32 BLE stack (that import
|
||||
registers esp32-only automations on every platform). The runtime check in
|
||||
_esp32_config_schema() only fires while validating an esp32 config, so this
|
||||
test is what actually catches drift when the upstream constant changes.
|
||||
The mirror doubles as the outer CONFIG_SCHEMA's connection_slots bound, and
|
||||
the esp32 schema builder lazily imports esp32_ble and asserts the two values
|
||||
agree, but that assert only fires while building the esp32 schema. This test
|
||||
catches drift when the upstream constant changes without any esp32 config
|
||||
being validated.
|
||||
"""
|
||||
|
||||
from esphome.components import esp32_ble
|
||||
|
||||
@@ -4,8 +4,10 @@ them without importing the esp32 BLE stack; pin the two declarations together.
|
||||
The outer schema carries no defaults (the per-platform schema applies them), so
|
||||
drift cannot surface in validation output — a key renamed or removed in
|
||||
_esp32_config_schema() but not here would silently vanish from the dashboard's
|
||||
field extractor. This test is what catches that; validator bounds are pinned
|
||||
separately only for connection_slots (test_idf_max_connections_mirror).
|
||||
field extractor. This test is what catches that. The outer schema bounds
|
||||
connection_slots with the loosest platform cap (_IDF_MAX_CONNECTIONS) so range
|
||||
walkers see a real Range; per-platform schemas tighten it, and the cap itself
|
||||
is pinned by test_idf_max_connections_mirror.
|
||||
"""
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
real reason, hub platforms reject GATT-only options by name, and the
|
||||
advertisement-only arm applies its own defaults."""
|
||||
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
import pytest
|
||||
|
||||
from esphome import config_validation as cv
|
||||
@@ -19,9 +22,10 @@ from esphome.core import CORE
|
||||
|
||||
from ..types import SetCoreConfigCallable
|
||||
|
||||
# Advertisement-only hub platforms; rp2 runs the full proxy and has its own
|
||||
# tests below.
|
||||
HUB_PLATFORM_FRAMEWORKS = [
|
||||
PlatformFramework.LN882X_ARDUINO,
|
||||
PlatformFramework.RP2_ARDUINO,
|
||||
]
|
||||
|
||||
HUB_TRACKERS = {
|
||||
@@ -32,9 +36,11 @@ HUB_TRACKERS = {
|
||||
|
||||
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.
|
||||
covered = {pf.value[0] for pf in HUB_PLATFORM_FRAMEWORKS}
|
||||
assert covered == set(bluetooth_proxy._HUB_PLATFORMS)
|
||||
# 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
|
||||
)
|
||||
assert {pf.value[0] for pf in HUB_PLATFORM_FRAMEWORKS} == advertisement_only
|
||||
assert set(HUB_TRACKERS) == set(bluetooth_proxy._HUB_PLATFORMS)
|
||||
|
||||
|
||||
@@ -122,6 +128,55 @@ def test_hub_platform_accepts_the_advertisement_only_shape(
|
||||
assert validated[CONF_ACTIVE] is False
|
||||
|
||||
|
||||
def test_rp2_defaults_to_the_full_proxy(
|
||||
set_core_config: SetCoreConfigCallable,
|
||||
) -> None:
|
||||
# esp32 parity: active defaults to true, with the platform's slot limit,
|
||||
# and one populated connection entry for the codegen to index.
|
||||
set_core_config(PlatformFramework.RP2_ARDUINO)
|
||||
_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
|
||||
|
||||
|
||||
def test_rp2_accepts_explicit_passive(
|
||||
set_core_config: SetCoreConfigCallable,
|
||||
) -> None:
|
||||
set_core_config(PlatformFramework.RP2_ARDUINO)
|
||||
_register_tracker(PLATFORM_RP2)
|
||||
validated = bluetooth_proxy.CONFIG_SCHEMA({CONF_ACTIVE: False})
|
||||
assert validated[CONF_ACTIVE] is False
|
||||
assert bluetooth_proxy.CONF_CONNECTIONS not in validated
|
||||
|
||||
|
||||
def test_rp2_rejects_slots_beyond_the_btstack_limit(
|
||||
set_core_config: SetCoreConfigCallable,
|
||||
) -> None:
|
||||
# The prebuilt BTstack library allows exactly one GATT client connection.
|
||||
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})
|
||||
# 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.
|
||||
with pytest.raises(cv.Invalid, match="at most 9"):
|
||||
bluetooth_proxy.CONFIG_SCHEMA({"connection_slots": 12})
|
||||
|
||||
|
||||
def test_rp2_rejects_esp32_only_keys_by_name(
|
||||
set_core_config: SetCoreConfigCallable,
|
||||
) -> None:
|
||||
set_core_config(PlatformFramework.RP2_ARDUINO)
|
||||
_register_tracker(PLATFORM_RP2)
|
||||
with pytest.raises(cv.Invalid, match="'cache_services' is esp32-only"):
|
||||
bluetooth_proxy.CONFIG_SCHEMA({"cache_services": True})
|
||||
with pytest.raises(cv.Invalid, match="'connections' has no per-connection options"):
|
||||
bluetooth_proxy.CONFIG_SCHEMA({"connections": [{}]})
|
||||
|
||||
|
||||
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
|
||||
@@ -134,3 +189,40 @@ def test_bluetooth_connection_auto_load_covers_its_includes() -> None:
|
||||
# dependency closures stay complete for build_codeowners and friends.
|
||||
_set_platform(None)
|
||||
assert bluetooth_connection.AUTO_LOAD() == ["ble_device_base", "esp32_ble_client"]
|
||||
|
||||
|
||||
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.
|
||||
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)
|
||||
# The outer walkable schema's bound must stay the loosest platform cap.
|
||||
assert (
|
||||
max(bluetooth_connection.HUB_MAX_CONNECTIONS.values())
|
||||
<= bluetooth_proxy._IDF_MAX_CONNECTIONS
|
||||
)
|
||||
|
||||
|
||||
def test_defines_h_mirrors_the_rp2_slot_cap() -> None:
|
||||
# esphome/core/defines.h carries a literal BLUETOOTH_PROXY_MAX_CONNECTIONS
|
||||
# for static analysis; pin it to the real rp2 cap.
|
||||
defines = (Path(__file__).parents[3] / "esphome" / "core" / "defines.h").read_text()
|
||||
cap = bluetooth_connection.RP2_MAX_CONNECTIONS
|
||||
# The rp2 arm's define, tolerating blank/comment lines in between.
|
||||
match = re.search(
|
||||
r"#elif defined\(USE_RP2\)\s*(?:(?://[^\n]*)?\n)+#define BLUETOOTH_PROXY_MAX_CONNECTIONS (\d+)",
|
||||
defines,
|
||||
)
|
||||
assert match is not None, "no USE_RP2 arm defines BLUETOOTH_PROXY_MAX_CONNECTIONS"
|
||||
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"
|
||||
assert int(match.group(1)) == cap, (
|
||||
f"ESPHOME_BLE_GATT_CLIENT_COUNT is {match.group(1)}, expected {cap}"
|
||||
)
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
wifi:
|
||||
ssid: MySSID
|
||||
password: password1
|
||||
|
||||
ota:
|
||||
- platform: esphome
|
||||
|
||||
api:
|
||||
@@ -0,0 +1,11 @@
|
||||
# Distinct shape from bluetooth_proxy's own rp2 fixtures: explicit slot count
|
||||
# on the platform whose backend lives in this component (validate-only, so it
|
||||
# never collides with grouped builds).
|
||||
packages:
|
||||
common: !include common.yaml
|
||||
|
||||
rp2_ble_tracker:
|
||||
|
||||
bluetooth_proxy:
|
||||
active: true
|
||||
connection_slots: 1
|
||||
@@ -0,0 +1,11 @@
|
||||
# Advertisement-only proxy on rp2 by explicit choice. Variant tests compile
|
||||
# as their own builds when this component is tested individually; under CI
|
||||
# batch grouping the active default build is what runs, so this fixture's
|
||||
# guarantee is the individual run plus config validation.
|
||||
packages:
|
||||
common: !include common.yaml
|
||||
|
||||
rp2_ble_tracker:
|
||||
|
||||
bluetooth_proxy:
|
||||
active: false
|
||||
@@ -1,5 +1,5 @@
|
||||
# Advertisement-only proxy on the rp2 BLE hub — the one non-esp32 platform the
|
||||
# proxy admits today (active-scan-capable), and a target CI fully compiles.
|
||||
# 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.
|
||||
# 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
|
||||
|
||||
Reference in New Issue
Block a user