From 4badb90d003a8fc7c094e33f746e4cbdf08b4117 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 22 May 2026 22:34:46 -0500 Subject: [PATCH] [esp32_ble_client] Route CLOSE_EVT and timeout cleanup through a single hook --- .../bluetooth_proxy/bluetooth_connection.cpp | 19 ++++++------------- .../bluetooth_proxy/bluetooth_connection.h | 2 +- .../esp32_ble_client/ble_client_base.cpp | 5 ++--- .../esp32_ble_client/ble_client_base.h | 8 +++++--- 4 files changed, 14 insertions(+), 20 deletions(-) diff --git a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp index 5783382e55..a42fb3544c 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp @@ -144,14 +144,15 @@ void BluetoothConnection::loop() { } } -void BluetoothConnection::on_disconnect_timeout_() { - // The base class forced IDLE because CLOSE_EVT never arrived. Run the same slot - // cleanup the CLOSE_EVT path would have done so the proxy slot is freed, the API - // client is notified, and send_service_ is reset for the next connection. +void BluetoothConnection::on_disconnect_complete_(esp_err_t reason) { + // Called from both the CLOSE_EVT handler and the DISCONNECTING safety timeout in the + // base class. Free the proxy slot, notify the API client, and reset send_service_. + // address_ may already be 0 if reset_connection_ ran earlier on this teardown. if (this->address_ == 0) { return; } - this->reset_connection_(ESP_GATT_CONN_TIMEOUT); + ESP_LOGD(TAG, "[%d] [%s] Close, reason=0x%02x, freeing slot", this->connection_index_, this->address_str_, reason); + this->reset_connection_(reason); } void BluetoothConnection::reset_connection_(esp_err_t reason) { @@ -385,14 +386,6 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga this->proxy_->send_device_connection(this->address_, false, 0, param->disconnect.reason); break; } - case ESP_GATTC_CLOSE_EVT: { - ESP_LOGD(TAG, "[%d] [%s] Close, reason=0x%02x, freeing slot", this->connection_index_, this->address_str_, - param->close.reason); - // Now the GATT connection is fully closed and controller resources are freed - // Safe to mark the connection slot as available - this->reset_connection_(param->close.reason); - break; - } case ESP_GATTC_OPEN_EVT: { if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) { this->reset_connection_(param->open.status); diff --git a/esphome/components/bluetooth_proxy/bluetooth_connection.h b/esphome/components/bluetooth_proxy/bluetooth_connection.h index c841ff0a24..54e7c83fb3 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_connection.h +++ b/esphome/components/bluetooth_proxy/bluetooth_connection.h @@ -33,7 +33,7 @@ class BluetoothConnection final : public esp32_ble_client::BLEClientBase { protected: friend class BluetoothProxy; - void on_disconnect_timeout_() override; + void on_disconnect_complete_(esp_err_t reason) override; bool supports_efficient_uuids_() const; void send_service_for_discovery_(); diff --git a/esphome/components/esp32_ble_client/ble_client_base.cpp b/esphome/components/esp32_ble_client/ble_client_base.cpp index 434f989be6..0c79eecd3a 100644 --- a/esphome/components/esp32_ble_client/ble_client_base.cpp +++ b/esphome/components/esp32_ble_client/ble_client_base.cpp @@ -72,9 +72,7 @@ void BLEClientBase::loop() { // never delivered CLOSE_EVT/DISCONNECT_EVT, services would leak without this call. this->release_services(); this->set_idle_(); - // Notify subclasses so they can release any extra per-connection state (e.g. bluetooth_proxy - // slot accounting) that would normally be cleared by the CLOSE_EVT handler. - this->on_disconnect_timeout_(); + this->on_disconnect_complete_(ESP_GATT_CONN_TIMEOUT); } } @@ -421,6 +419,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ this->log_gattc_lifecycle_event_("CLOSE"); this->release_services(); this->set_idle_(); + this->on_disconnect_complete_(param->close.reason); break; } case ESP_GATTC_SEARCH_RES_EVT: { diff --git a/esphome/components/esp32_ble_client/ble_client_base.h b/esphome/components/esp32_ble_client/ble_client_base.h index 5b17ed9c6b..f7e87e667c 100644 --- a/esphome/components/esp32_ble_client/ble_client_base.h +++ b/esphome/components/esp32_ble_client/ble_client_base.h @@ -140,10 +140,12 @@ class BLEClientBase : public espbt::ESPBTClient, public Component { void log_gattc_warning_(const char *operation, esp_err_t err); void log_connection_params_(const char *param_type); void handle_connection_result_(esp_err_t ret); - /// Hook called from the DISCONNECTING safety timeout after the base class forces IDLE. + /// Hook called once a connection has been fully torn down (after release_services() and + /// set_idle_()), from both the CLOSE_EVT handler and the DISCONNECTING safety timeout. /// Subclasses with extra per-connection accounting (e.g. bluetooth_proxy slot state) - /// override this to perform the same cleanup the normal CLOSE_EVT path would do. - virtual void on_disconnect_timeout_() {} + /// override this to release that state. `reason` is the controller reason code, or + /// ESP_GATT_CONN_TIMEOUT for the safety-timeout path. + virtual void on_disconnect_complete_(esp_err_t reason) {} /// Transition to IDLE and reset conn_id — call when the connection is fully dead. void set_idle_() { this->set_state(espbt::ClientState::IDLE);