[esp32_ble_client] Add 10s safety timeout for DISCONNECTING state

If CLOSE_EVT never arrives after DISCONNECT_EVT, the client gets
stuck in DISCONNECTING forever, blocking reconnection and scanner
restart. Add a 10s timeout watchdog in loop() that forces IDLE
as a recovery path.

Introduce set_disconnecting_() helper to ensure the timeout
timestamp is always set when entering DISCONNECTING state.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
J. Nick Koston
2026-02-23 11:53:44 -06:00
parent 60944a6d8b
commit 5c86bad64b
2 changed files with 17 additions and 3 deletions

View File

@@ -27,6 +27,7 @@ static constexpr uint16_t MEDIUM_CONN_TIMEOUT = 800; // 800 * 10ms = 8s
static constexpr uint16_t FAST_MIN_CONN_INTERVAL = 0x06; // 6 * 1.25ms = 7.5ms (BLE minimum)
static constexpr uint16_t FAST_MAX_CONN_INTERVAL = 0x06; // 6 * 1.25ms = 7.5ms
static constexpr uint16_t FAST_CONN_TIMEOUT = 1000; // 1000 * 10ms = 10s
static constexpr uint32_t DISCONNECTING_TIMEOUT = 10000; // 10s
static const esp_bt_uuid_t NOTIFY_DESC_UUID = {
.len = ESP_UUID_LEN_16,
.uuid =
@@ -62,6 +63,11 @@ void BLEClientBase::loop() {
// will enable it again when a connection is needed.
else if (this->state() == espbt::ClientState::IDLE) {
this->disable_loop();
} else if (this->state() == espbt::ClientState::DISCONNECTING &&
(millis() - this->disconnecting_started_) > DISCONNECTING_TIMEOUT) {
ESP_LOGE(TAG, "[%d] [%s] Timeout waiting for CLOSE_EVT after disconnect, forcing IDLE", this->connection_index_,
this->address_str_);
this->set_idle_();
}
}
@@ -178,7 +184,7 @@ void BLEClientBase::unconditional_disconnect() {
this->set_address(0);
this->set_state(espbt::ClientState::IDLE);
} else {
this->set_state(espbt::ClientState::DISCONNECTING);
this->set_disconnecting_();
}
}
@@ -373,7 +379,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
// causing the controller to reject the new connection (status=133) or crash
// with ASSERT_PARAM in lld_evt.c.
this->release_services();
this->set_state(espbt::ClientState::DISCONNECTING);
this->set_disconnecting_();
break;
}

View File

@@ -113,7 +113,10 @@ class BLEClientBase : public espbt::ESPBTClient, public Component {
char address_str_[MAC_ADDRESS_PRETTY_BUFFER_SIZE]{};
esp_bd_addr_t remote_bda_; // 6 bytes
// Group 5: 2-byte types
// Group 5: 4-byte types
uint32_t disconnecting_started_{0};
// Group 6: 2-byte types
uint16_t conn_id_{UNSET_CONN_ID};
uint16_t mtu_{23};
@@ -142,6 +145,11 @@ class BLEClientBase : public espbt::ESPBTClient, public Component {
this->set_state(espbt::ClientState::IDLE);
this->conn_id_ = UNSET_CONN_ID;
}
/// Transition to DISCONNECTING and start the safety timeout.
void set_disconnecting_() {
this->disconnecting_started_ = millis();
this->set_state(espbt::ClientState::DISCONNECTING);
}
// Compact error logging helpers to reduce flash usage
void log_error_(const char *message);
void log_error_(const char *message, int code);