From 68cbe019c61fe7a01ea2da23f7c8e96bbe7702b4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 10 Sep 2026 07:51:57 -0500 Subject: [PATCH] [bluetooth_connection] Keep the stack-down reset in one place The loop backstop only ran one pass earlier than the tracker hook and forced the reset into a helper with extra branches; the hook alone now does the reset. The unregistered-interface check moves to connect(), where it covers both the INIT state and the window before REG_EVT with one predicate. --- .../bluetooth_connection_bluedroid.cpp | 37 +++++-------------- .../bluetooth_connection_bluedroid.h | 1 - esphome/components/esp32_ble/ble.cpp | 4 +- .../esp32_ble_client/ble_client_base.cpp | 5 +-- .../esp32_ble_tracker/esp32_ble_tracker.cpp | 5 +-- 5 files changed, 14 insertions(+), 38 deletions(-) diff --git a/esphome/components/bluetooth_connection/bluetooth_connection_bluedroid.cpp b/esphome/components/bluetooth_connection/bluetooth_connection_bluedroid.cpp index 27d6899971..a65bb58359 100644 --- a/esphome/components/bluetooth_connection/bluetooth_connection_bluedroid.cpp +++ b/esphome/components/bluetooth_connection/bluetooth_connection_bluedroid.cpp @@ -45,9 +45,7 @@ void BluedroidGattClient::setup() { void BluedroidGattClient::loop() { if (!esp32_ble::global_ble->is_active()) { - // Backstop for the window between disable() and the teardown; the - // tracker's before-disabled hook is the primary path. - this->reset_for_stack_down_(); + // ble_before_disabled_event_handler() settles the slot. return; } auto st = this->state(); @@ -82,25 +80,17 @@ void BluedroidGattClient::loop() { } // Stack down: no CLOSE_EVT will come. Settle a live link so the consumer -// frees its slot, then re-register the app on the next enable. -void BluedroidGattClient::reset_for_stack_down_() { +// frees its slot, then register the app again on the next enable. +void BluedroidGattClient::ble_before_disabled_event_handler() { auto st = this->state(); - if (st == ClientState::INIT) { - return; - } - if (st != ClientState::IDLE) { + if (st != ClientState::IDLE && st != ClientState::INIT) { this->release_services(); this->set_idle_(); this->listener_->on_connection_state(false, 0, ble_device_base::GATT_ERR_NOT_CONNECTED); } - // The interface belongs to the torn-down stack; Bluedroid drops an open - // issued with it without any event. + // The interface belongs to the torn-down stack. this->gattc_if_ = ESP_GATT_IF_NONE; this->set_state(ClientState::INIT); -} - -void BluedroidGattClient::ble_before_disabled_event_handler() { - this->reset_for_stack_down_(); // An idle slot runs no loop; the INIT branch must run to register again. this->enable_loop(); } @@ -115,15 +105,14 @@ void BluedroidGattClient::dump_config() { // ---- contract ops ---- int BluedroidGattClient::connect(uint64_t address, uint8_t addr_type) { - auto st = this->state(); - if (st == ClientState::INIT) { - // Nothing can be opened until the app is registered on a running stack. - ESP_LOGW(TAG, "[%d] Connect rejected, BLE stack not ready", this->connection_index_); + if (this->gattc_if_ == ESP_GATT_IF_NONE) { + // Bluedroid drops an open on an unknown interface without any event. + ESP_LOGW(TAG, "[%d] Connect rejected, GATT app not registered", this->connection_index_); return ble_device_base::GATT_ERR_NOT_CONNECTED; } // Only from idle: clobbering DISCONNECTING would open a new link the // stale CLOSE_EVT then tears down. - if (st != ClientState::IDLE) { + if (this->state() != ClientState::IDLE) { ESP_LOGW(TAG, "[%d] Connect rejected, slot busy", this->connection_index_); return ESP_GATT_BUSY; } @@ -145,14 +134,6 @@ void BluedroidGattClient::tracker_connect_() { ESP_LOGW(TAG, "[%d] Cannot connect, still waiting for CLOSE_EVT", this->connection_index_); return; } - if (this->gattc_if_ == ESP_GATT_IF_NONE) { - // REG_EVT has not landed on this stack; Bluedroid drops an open on an - // unknown interface without any event, which would wedge the slot. - ESP_LOGW(TAG, "[%d] Connect failed, GATT app not registered", this->connection_index_); - this->set_idle_(); - this->listener_->on_connection_state(false, 0, ble_device_base::GATT_ERR_NOT_CONNECTED); - return; - } ESP_LOGI(TAG, "[%d] 0x%02x Connecting", this->connection_index_, this->remote_addr_type_); // Per-attempt latches; the search machine is reset by set_idle_(), the // one door back to IDLE. diff --git a/esphome/components/bluetooth_connection/bluetooth_connection_bluedroid.h b/esphome/components/bluetooth_connection/bluetooth_connection_bluedroid.h index 55fc06b17a..f285260e76 100644 --- a/esphome/components/bluetooth_connection/bluetooth_connection_bluedroid.h +++ b/esphome/components/bluetooth_connection/bluetooth_connection_bluedroid.h @@ -99,7 +99,6 @@ class BluedroidGattClient final : public esp32_ble_tracker::ESPBTClient, public void unconditional_disconnect_(); void set_idle_(); void set_disconnecting_(); - void reset_for_stack_down_(); esp_err_t update_conn_params_(uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t timeout, const char *param_type); int check_and_log_error_(const char *operation, esp_err_t err); diff --git a/esphome/components/esp32_ble/ble.cpp b/esphome/components/esp32_ble/ble.cpp index 15c370d3e2..387a16263c 100644 --- a/esphome/components/esp32_ble/ble.cpp +++ b/esphome/components/esp32_ble/ble.cpp @@ -580,9 +580,7 @@ void ESP32BLE::loop_handle_state_transition_not_active_() { this->mark_failed(); return; } - // Whatever the old stack queued while going down (close events for the - // links it tore down, scan completions) must not replay against the next - // stack, which hands out the same interface ids. + // Drop what the old stack queued; the next stack reuses the same interface ids. BLEEvent *ble_event; while ((ble_event = this->ble_events_.pop()) != nullptr) { this->ble_event_pool_.release(ble_event); diff --git a/esphome/components/esp32_ble_client/ble_client_base.cpp b/esphome/components/esp32_ble_client/ble_client_base.cpp index 1c49187b23..8b4e4348b0 100644 --- a/esphome/components/esp32_ble_client/ble_client_base.cpp +++ b/esphome/components/esp32_ble_client/ble_client_base.cpp @@ -42,7 +42,7 @@ void BLEClientBase::set_state(espbt::ClientState st) { void BLEClientBase::loop() { if (!esp32_ble::global_ble->is_active()) { - this->set_state(espbt::ClientState::INIT); + // ble_before_disabled_event_handler() resets the client. return; } if (this->state() == espbt::ClientState::INIT) { @@ -73,9 +73,8 @@ void BLEClientBase::loop() { float BLEClientBase::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; } void BLEClientBase::ble_before_disabled_event_handler() { - // Same reset as the stack-down branch of loop(); an idle client has its - // loop disabled, so enable it for the INIT registration on the next enable. this->set_state(espbt::ClientState::INIT); + // An idle client runs no loop; the INIT branch must run to register again. this->enable_loop(); } diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index 7ae2ce568d..cbf3378723 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -225,9 +225,8 @@ void ESP32BLETracker::ble_before_disabled_event_handler() { client->ble_before_disabled_event_handler(); } #endif - // The stop above never completes: the stack is torn down and its queued - // events are dropped, so settle the scanner here. start_scan_() refuses - // anything but IDLE once the stack is back. + // The stop above never completes (stack torn down, events dropped); settle + // here so start_scan_() sees IDLE once the stack is back. if (this->scanner_state_ != ScannerState::IDLE) { #ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT this->skip_next_scan_end_ = false;