From 81216da8e7dad6495f143a308f840a01a749330b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 9 Aug 2026 04:02:45 -0500 Subject: [PATCH] Apply review: honest slot docstring, cap-coverage pin, write breadcrumb --- esphome/components/ble_client/ble_client_gatt.cpp | 4 ++++ esphome/components/bluetooth_connection/__init__.py | 8 ++++---- .../bluetooth_proxy/test_platform_gates.py | 6 ++++++ 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/esphome/components/ble_client/ble_client_gatt.cpp b/esphome/components/ble_client/ble_client_gatt.cpp index 41a647cf20..9232e09ae9 100644 --- a/esphome/components/ble_client/ble_client_gatt.cpp +++ b/esphome/components/ble_client/ble_client_gatt.cpp @@ -149,6 +149,10 @@ void BLEClient::on_service_discovery_done(int error) { } void BLEClient::on_write_result(uint16_t handle, int error) { + if (error != 0) { + // Breadcrumb even when no node claims the handle. + ESP_LOGD(TAG, "[%s] Write on handle 0x%04x completed with status %d", this->address_str_, handle, error); + } for (auto *node : this->nodes_) { node->on_write_result(handle, error); } diff --git a/esphome/components/bluetooth_connection/__init__.py b/esphome/components/bluetooth_connection/__init__.py index 858dfa91f5..0ee316a6e6 100644 --- a/esphome/components/bluetooth_connection/__init__.py +++ b/esphome/components/bluetooth_connection/__init__.py @@ -157,10 +157,10 @@ def _ledger() -> _SlotLedger: def consume_gatt_slot( consumer: str, count: int = 1 ) -> Callable[[ConfigType], ConfigType]: - """Validator claiming GATT connection slots for the neutral ledger (the - platform cap check in FINAL_VALIDATE_SCHEMA); esp32 additionally charges - the controller's connection budget. The proxy's esp32 arm still charges - the controller directly - route it through here when it migrates.""" + """Validator claiming GATT connection slots - the one spelling for every + claimant. The neutral ledger feeds the hub-platform cap check in + FINAL_VALIDATE_SCHEMA; esp32 additionally charges the controller's + connection budget (its cap lives there, not in HUB_MAX_CONNECTIONS).""" def validator(config: ConfigType) -> ConfigType: _ledger().consumers.extend([consumer] * count) diff --git a/tests/component_tests/bluetooth_proxy/test_platform_gates.py b/tests/component_tests/bluetooth_proxy/test_platform_gates.py index 17992d25ab..2155157d78 100644 --- a/tests/component_tests/bluetooth_proxy/test_platform_gates.py +++ b/tests/component_tests/bluetooth_proxy/test_platform_gates.py @@ -207,6 +207,12 @@ def test_every_registered_hub_platform_has_a_schema_arm() -> None: # Hub platforms must also be in the backend registry the shared codegen # helpers dispatch on. assert registered <= set(bluetooth_connection._PLATFORM_BACKENDS) + # Every non-esp32 backend platform must carry a slot cap: without one the + # ledger's FINAL_VALIDATE accepts unlimited claims silently (esp32's cap + # is the controller budget in esp32_ble). + assert set(bluetooth_connection._PLATFORM_BACKENDS) - {"esp32"} <= set( + bluetooth_connection.HUB_MAX_CONNECTIONS + ) # The outer walkable schema's bound must stay the loosest platform cap. assert ( max(bluetooth_connection.HUB_MAX_CONNECTIONS.values())