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.