diff --git a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp index d78506e067..5783382e55 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp @@ -144,6 +144,16 @@ 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. + if (this->address_ == 0) { + return; + } + this->reset_connection_(ESP_GATT_CONN_TIMEOUT); +} + void BluetoothConnection::reset_connection_(esp_err_t reason) { // Send disconnection notification this->proxy_->send_device_connection(this->address_, false, 0, reason); diff --git a/esphome/components/bluetooth_proxy/bluetooth_connection.h b/esphome/components/bluetooth_proxy/bluetooth_connection.h index b50ea2d6a2..c841ff0a24 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_connection.h +++ b/esphome/components/bluetooth_proxy/bluetooth_connection.h @@ -33,6 +33,8 @@ class BluetoothConnection final : public esp32_ble_client::BLEClientBase { protected: friend class BluetoothProxy; + void on_disconnect_timeout_() override; + bool supports_efficient_uuids_() const; void send_service_for_discovery_(); void reset_connection_(esp_err_t reason); diff --git a/esphome/components/esp32_ble_client/ble_client_base.cpp b/esphome/components/esp32_ble_client/ble_client_base.cpp index 7f0f2c624d..434f989be6 100644 --- a/esphome/components/esp32_ble_client/ble_client_base.cpp +++ b/esphome/components/esp32_ble_client/ble_client_base.cpp @@ -72,6 +72,9 @@ 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_(); } } diff --git a/esphome/components/esp32_ble_client/ble_client_base.h b/esphome/components/esp32_ble_client/ble_client_base.h index 8a491a898e..5b17ed9c6b 100644 --- a/esphome/components/esp32_ble_client/ble_client_base.h +++ b/esphome/components/esp32_ble_client/ble_client_base.h @@ -140,6 +140,10 @@ 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. + /// 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_() {} /// Transition to IDLE and reset conn_id — call when the connection is fully dead. void set_idle_() { this->set_state(espbt::ClientState::IDLE);