From 246d455dc34ba24df0b9adacdc7094f48ae0958e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 23 Feb 2026 09:28:50 -0600 Subject: [PATCH] [esp32_ble_client] Complete disconnection on failed OPEN_EVT in DISCONNECTING state When a connection fails to establish, the ESP-IDF stack sends DISCONNECT_EVT followed by OPEN_EVT with a failure status (e.g. 133). No CLOSE_EVT follows since no GATT connection was established. Previously the OPEN_EVT in DISCONNECTING state was silently ignored, leaving the client stuck in DISCONNECTING forever. Now we treat this failed OPEN_EVT as the terminal event and transition to IDLE. --- .../esp32_ble_client/ble_client_base.cpp | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/esphome/components/esp32_ble_client/ble_client_base.cpp b/esphome/components/esp32_ble_client/ble_client_base.cpp index 9370343c9c..3b35420d7b 100644 --- a/esphome/components/esp32_ble_client/ble_client_base.cpp +++ b/esphome/components/esp32_ble_client/ble_client_base.cpp @@ -295,10 +295,19 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ // ESP-IDF's BLE stack may send ESP_GATTC_OPEN_EVT after esp_ble_gattc_open() returns an // error, if the error occurred at the BTA/GATT layer. This can result in the event // arriving after we've already transitioned to IDLE state. - // It may also arrive during DISCONNECTING if the controller is still cleaning up. - if (this->state() == espbt::ClientState::IDLE || this->state() == espbt::ClientState::DISCONNECTING) { - ESP_LOGD(TAG, "[%d] [%s] ESP_GATTC_OPEN_EVT in %s state (status=%d), ignoring", this->connection_index_, - this->address_str_, espbt::client_state_to_string(this->state()), param->open.status); + if (this->state() == espbt::ClientState::IDLE) { + ESP_LOGD(TAG, "[%d] [%s] ESP_GATTC_OPEN_EVT in IDLE state (status=%d), ignoring", this->connection_index_, + this->address_str_, param->open.status); + break; + } else if (this->state() == espbt::ClientState::DISCONNECTING) { + // When a connection fails to establish, the ESP-IDF stack sends DISCONNECT_EVT + // (which we now transition to DISCONNECTING) followed by OPEN_EVT with a failure status. + // No CLOSE_EVT will follow since no GATT connection was established, so this + // failed OPEN_EVT is the terminal event — transition to IDLE here. + ESP_LOGD(TAG, "[%d] [%s] ESP_GATTC_OPEN_EVT in DISCONNECTING state (status=%d), completing disconnection", + this->connection_index_, this->address_str_, param->open.status); + this->set_state(espbt::ClientState::IDLE); + this->conn_id_ = UNSET_CONN_ID; break; }