From 5f11c430cc176662d90726f49ea0e806f043325b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 19 Aug 2026 10:30:43 -0500 Subject: [PATCH] Emit the materializer define only for the backend that has one The registry entry now carries its materializer gate, so rp2 builds stop receiving a Bluedroid define. Also: parent before the capacity check in register_gatt_node (neutral parity) and BluedroidServiceTable is non-copyable (owning raw pointer). --- esphome/components/ble_client/ble_client.cpp | 3 +++ esphome/components/bluetooth_connection/__init__.py | 7 +++++-- .../bluetooth_connection/gatt_service_table_bluedroid.h | 4 ++++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/esphome/components/ble_client/ble_client.cpp b/esphome/components/ble_client/ble_client.cpp index abb53a4c8a..e00e0fd0b1 100644 --- a/esphome/components/ble_client/ble_client.cpp +++ b/esphome/components/ble_client/ble_client.cpp @@ -141,6 +141,9 @@ bool BLEClient::all_nodes_established_() { #ifdef USE_BLE_CLIENT_GATT_NODES void BLEClient::register_gatt_node(BLEClientNode *node) { + // Parent before the capacity check so a dropped node still has a usable + // parent() (neutral-engine parity). + node->set_ble_client_parent(this); if (this->gatt_nodes_.size() == ESPHOME_BLE_CLIENT_MAX_NODES) { // push_back past capacity is a silent no-op; an undersized slot count // must be loud at boot, not an unresolvable node at runtime. diff --git a/esphome/components/bluetooth_connection/__init__.py b/esphome/components/bluetooth_connection/__init__.py index 69a250f809..d52badb623 100644 --- a/esphome/components/bluetooth_connection/__init__.py +++ b/esphome/components/bluetooth_connection/__init__.py @@ -126,6 +126,8 @@ class _PlatformBackend: register: Callable[[cg.MockObj, ConfigType], Awaitable[None]] # Selects the backend's alias-ladder arm (order-independent arms). define: str + # The backend's on-demand materializer gate, when it has one. + materializer_define: str | None = None # The single registry of platforms with a GATT client backend; a platform @@ -137,6 +139,7 @@ _PLATFORM_BACKENDS: dict[str, _PlatformBackend] = { _esp32_schema_fragment, _esp32_register, "USE_BLE_GATT_BACKEND_BLUEDROID", + materializer_define="USE_BLUEDROID_GATT_SERVICE_TABLE", ), PLATFORM_RP2: _PlatformBackend( RP2GattClient, _rp2_schema_fragment, _rp2_register, "USE_BLE_GATT_BACKEND_RP2" @@ -254,8 +257,8 @@ async def new_gatt_backend( entry = _backend_entry() ble_device_base.request_gatt_client() cg.add_define(entry.define) - if service_table: - cg.add_define("USE_BLUEDROID_GATT_SERVICE_TABLE") + if service_table and entry.materializer_define is not None: + cg.add_define(entry.materializer_define) backend = cg.new_Pvariable(config[CONF_BACKEND_ID]) # The backend is the slot's real Component: component keys from the # connection entry (setup_priority, ...) apply to it. Consumers whose own diff --git a/esphome/components/bluetooth_connection/gatt_service_table_bluedroid.h b/esphome/components/bluetooth_connection/gatt_service_table_bluedroid.h index a125c010ff..95b04b597a 100644 --- a/esphome/components/bluetooth_connection/gatt_service_table_bluedroid.h +++ b/esphome/components/bluetooth_connection/gatt_service_table_bluedroid.h @@ -18,6 +18,10 @@ namespace esphome::bluetooth_connection { class BluedroidServiceTable { public: ~BluedroidServiceTable() { this->free(); } + // Owns storage_; a copy would double-free. + BluedroidServiceTable() = default; + BluedroidServiceTable(const BluedroidServiceTable &) = delete; + BluedroidServiceTable &operator=(const BluedroidServiceTable &) = delete; /// The service count build() requires: the stack's PRIMARY+SECONDARY /// attribute totals, never the SEARCH_RES event count.