Address review: explicit connections claim slots, re-enable clears backoff

- Explicit `connections:` entries now charge the slot ledger like the
  generated ones (dev let them evade the controller budget); pinned by a
  test
- set_enabled(true) clears the connect backoff - a re-enable is an
  explicit try-again
- The neutral engine's source filter uses the shared
  frameworks_for_platforms helper; ledger tests use the conftest fixture
This commit is contained in:
J. Nick Koston
2026-08-09 10:09:20 -05:00
parent c8e240198d
commit b229bd4d8a
5 changed files with 38 additions and 19 deletions
+7 -7
View File
@@ -12,7 +12,10 @@ from esphome.components.ble_device_base import (
as_reversed_hex_array,
bt_uuid,
)
from esphome.config_helpers import filter_source_files_from_platform
from esphome.config_helpers import (
filter_source_files_from_platform,
frameworks_for_platforms,
)
import esphome.config_validation as cv
from esphome.const import (
CONF_CHARACTERISTIC_UUID,
@@ -59,12 +62,9 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform(
},
# Every framework of every non-esp32 registry platform: a platform
# that validates the neutral arm must also compile the neutral engine.
"ble_client_gatt.cpp": {
pf
for pf in PlatformFramework
if pf.value[0] in bluetooth_connection.GATT_CLIENT_PLATFORMS
and pf.value[0] != PLATFORM_ESP32
},
"ble_client_gatt.cpp": frameworks_for_platforms(
set(bluetooth_connection.GATT_CLIENT_PLATFORMS) - {PLATFORM_ESP32}
),
}
)
@@ -28,8 +28,13 @@ void BLEClient::set_enabled(bool enabled) {
this->enabled = enabled;
if (!enabled) {
this->disconnect();
return;
}
// Enabling does not connect: the next sighting does (legacy parity).
// A re-enable is an explicit "try again": clear the backoff so the next
// sighting connects promptly. Enabling does not itself connect (legacy
// parity).
this->consecutive_failures_ = 0;
this->hold_off_ms_ = 0;
}
bool BLEClient::parse_device(const ble_device_base::ESPBTDevice &device) {
@@ -99,6 +99,12 @@ def _esp32_config_schema() -> cv.All:
raise cv.Invalid(
"Connections can only be used if the proxy is set to active"
)
# Explicit entries claim slots like the generated ones; dev
# historically skipped this, letting an explicit-connections
# config evade the controller budget.
bluetooth_connection.consume_gatt_slot(
"bluetooth_proxy", len(config[CONF_CONNECTIONS])
)(config)
elif config[CONF_ACTIVE]:
connection_slots: int = config[CONF_CONNECTION_SLOTS]
bluetooth_connection.consume_gatt_slot("bluetooth_proxy", connection_slots)(
@@ -9,31 +9,29 @@ from esphome.components import (
bluetooth_connection,
bluetooth_proxy,
)
from esphome.const import (
CONF_MAC_ADDRESS,
KEY_CORE,
KEY_TARGET_PLATFORM,
PLATFORM_RP2,
PlatformFramework,
)
from esphome.const import CONF_MAC_ADDRESS, PlatformFramework
from esphome.core import CORE
from ..types import SetCoreConfigCallable
def test_gatt_slot_ledger_rejects_overcommit_on_rp2() -> None:
def test_gatt_slot_ledger_rejects_overcommit_on_rp2(
set_core_config: SetCoreConfigCallable,
) -> None:
# The cap logic in isolation: two hand charges must trip it.
CORE.data.setdefault(KEY_CORE, {})[KEY_TARGET_PLATFORM] = PLATFORM_RP2
set_core_config(PlatformFramework.RP2_ARDUINO)
bluetooth_connection.consume_gatt_slot("bluetooth_proxy")({})
bluetooth_connection.consume_gatt_slot("ble_client")({})
with pytest.raises(cv.Invalid, match="supports at most 1 GATT client"):
bluetooth_connection.FINAL_VALIDATE_SCHEMA({})
def test_gatt_slot_ledger_skipped_in_testing_mode() -> None:
def test_gatt_slot_ledger_skipped_in_testing_mode(
set_core_config: SetCoreConfigCallable,
) -> None:
# Grouped component builds merge fixtures past the cap; the check defers
# to testing mode like esp32_ble.validate_connection_slots.
CORE.data.setdefault(KEY_CORE, {})[KEY_TARGET_PLATFORM] = PLATFORM_RP2
set_core_config(PlatformFramework.RP2_ARDUINO)
bluetooth_connection.consume_gatt_slot("bluetooth_proxy")({})
bluetooth_connection.consume_gatt_slot("ble_client")({})
CORE.testing_mode = True
@@ -179,6 +179,16 @@ def test_rp2_rejects_esp32_only_keys_by_name(
bluetooth_proxy.CONFIG_SCHEMA({"connections": [{}]})
def test_esp32_explicit_connections_claim_gatt_slots(
set_core_config: SetCoreConfigCallable,
) -> None:
# Explicit `connections:` entries must charge the slot ledger like the
# generated ones; dev historically let them evade the budget.
set_core_config(PlatformFramework.ESP32_IDF)
bluetooth_proxy.CONFIG_SCHEMA({"active": True, "connections": [{}, {}]})
assert "bluetooth_proxy" in bluetooth_connection._ledger().consumers
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