diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index e0029ad15b..254eddd1d9 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -185,9 +185,6 @@ void ESP32BLETracker::loop() { ESP_LOGW(TAG, "Dropped %zu BLE scan results due to buffer overflow", dropped); } } - if (this->scanner_state_ == ScannerState::STOPPED) { - this->end_of_scan_(); // Change state to IDLE - } if (this->scanner_state_ == ScannerState::FAILED || (this->scan_set_param_failed_ && this->scanner_state_ == ScannerState::RUNNING)) { this->stop_scan_(); @@ -278,8 +275,6 @@ void ESP32BLETracker::stop_scan_() { ESP_LOGE(TAG, "Scan is starting while trying to stop."); } else if (this->scanner_state_ == ScannerState::STOPPING) { ESP_LOGE(TAG, "Scan is already stopping while trying to stop."); - } else if (this->scanner_state_ == ScannerState::STOPPED) { - ESP_LOGE(TAG, "Scan is already stopped while trying to stop."); } return; } @@ -306,8 +301,6 @@ void ESP32BLETracker::start_scan_(bool first) { ESP_LOGE(TAG, "Cannot start scan while already stopping."); } else if (this->scanner_state_ == ScannerState::FAILED) { ESP_LOGE(TAG, "Cannot start scan while already failed."); - } else if (this->scanner_state_ == ScannerState::STOPPED) { - ESP_LOGE(TAG, "Cannot start scan while already stopped."); } return; } @@ -342,21 +335,6 @@ void ESP32BLETracker::start_scan_(bool first) { } } -void ESP32BLETracker::end_of_scan_() { - // The lock must be held when calling this function. - if (this->scanner_state_ != ScannerState::STOPPED) { - ESP_LOGE(TAG, "end_of_scan_ called while scanner is not stopped."); - return; - } - ESP_LOGD(TAG, "End of scan, set scanner state to IDLE."); - this->already_discovered_.clear(); - this->cancel_timeout("scan"); - - for (auto *listener : this->listeners_) - listener->on_scan_end(); - this->set_scanner_state_(ScannerState::IDLE); -} - void ESP32BLETracker::register_client(ESPBTClient *client) { client->app_id = ++this->app_id_; this->clients_.push_back(client); @@ -389,6 +367,8 @@ void ESP32BLETracker::recalculate_advertisement_parser_types() { } void ESP32BLETracker::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { + // Note: This handler is called from the main loop context, not directly from the BT task. + // The esp32_ble component queues events via enqueue_ble_event() and processes them in loop(). switch (event) { case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: this->gap_scan_set_param_complete_(param->scan_param_cmpl); @@ -409,11 +389,13 @@ void ESP32BLETracker::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_ga } void ESP32BLETracker::gap_scan_event_handler(const BLEScanResult &scan_result) { + // Note: This handler is called from the main loop context via esp32_ble's event queue. + // However, we still use a lock-free ring buffer to batch results efficiently. ESP_LOGV(TAG, "gap_scan_result - event %d", scan_result.search_evt); if (scan_result.search_evt == ESP_GAP_SEARCH_INQ_RES_EVT) { - // Lock-free SPSC ring buffer write (Producer side) - // This runs in the ESP-IDF Bluetooth stack callback thread + // Ring buffer write (Producer side) + // Even though we're in the main loop, the ring buffer design allows efficient batching // IMPORTANT: Only this thread writes to ring_write_index_ // Load our own index with relaxed ordering (we're the only writer) @@ -445,15 +427,15 @@ void ESP32BLETracker::gap_scan_event_handler(const BLEScanResult &scan_result) { ESP_LOGE(TAG, "Scan was in failed state when scan completed."); } else if (this->scanner_state_ == ScannerState::IDLE) { ESP_LOGE(TAG, "Scan was idle when scan completed."); - } else if (this->scanner_state_ == ScannerState::STOPPED) { - ESP_LOGE(TAG, "Scan was stopped when scan completed."); } } - this->set_scanner_state_(ScannerState::STOPPED); + // Scan completed naturally, perform cleanup and transition to IDLE + this->cleanup_scan_state_(false); } } void ESP32BLETracker::gap_scan_set_param_complete_(const esp_ble_gap_cb_param_t::ble_scan_param_cmpl_evt_param ¶m) { + // Called from main loop context via gap_event_handler after being queued from BT task ESP_LOGV(TAG, "gap_scan_set_param_complete - status %d", param.status); if (param.status == ESP_BT_STATUS_DONE) { this->scan_set_param_failed_ = ESP_BT_STATUS_SUCCESS; @@ -463,6 +445,7 @@ void ESP32BLETracker::gap_scan_set_param_complete_(const esp_ble_gap_cb_param_t: } void ESP32BLETracker::gap_scan_start_complete_(const esp_ble_gap_cb_param_t::ble_scan_start_cmpl_evt_param ¶m) { + // Called from main loop context via gap_event_handler after being queued from BT task ESP_LOGV(TAG, "gap_scan_start_complete - status %d", param.status); this->scan_start_failed_ = param.status; if (this->scanner_state_ != ScannerState::STARTING) { @@ -474,8 +457,6 @@ void ESP32BLETracker::gap_scan_start_complete_(const esp_ble_gap_cb_param_t::ble ESP_LOGE(TAG, "Scan was in failed state when start complete."); } else if (this->scanner_state_ == ScannerState::IDLE) { ESP_LOGE(TAG, "Scan was idle when start complete."); - } else if (this->scanner_state_ == ScannerState::STOPPED) { - ESP_LOGE(TAG, "Scan was stopped when start complete."); } } if (param.status == ESP_BT_STATUS_SUCCESS) { @@ -490,6 +471,8 @@ void ESP32BLETracker::gap_scan_start_complete_(const esp_ble_gap_cb_param_t::ble } void ESP32BLETracker::gap_scan_stop_complete_(const esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param ¶m) { + // Called from main loop context via gap_event_handler after being queued from BT task + // This allows us to safely transition to IDLE state and perform cleanup without race conditions ESP_LOGV(TAG, "gap_scan_stop_complete - status %d", param.status); if (this->scanner_state_ != ScannerState::STOPPING) { if (this->scanner_state_ == ScannerState::RUNNING) { @@ -500,11 +483,11 @@ void ESP32BLETracker::gap_scan_stop_complete_(const esp_ble_gap_cb_param_t::ble_ ESP_LOGE(TAG, "Scan was in failed state when stop complete."); } else if (this->scanner_state_ == ScannerState::IDLE) { ESP_LOGE(TAG, "Scan was idle when stop complete."); - } else if (this->scanner_state_ == ScannerState::STOPPED) { - ESP_LOGE(TAG, "Scan was stopped when stop complete."); } } - this->set_scanner_state_(ScannerState::STOPPED); + + // Perform cleanup and transition to IDLE + this->cleanup_scan_state_(true); } void ESP32BLETracker::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, @@ -794,9 +777,6 @@ void ESP32BLETracker::dump_config() { case ScannerState::STOPPING: ESP_LOGCONFIG(TAG, " Scanner State: STOPPING"); break; - case ScannerState::STOPPED: - ESP_LOGCONFIG(TAG, " Scanner State: STOPPED"); - break; case ScannerState::FAILED: ESP_LOGCONFIG(TAG, " Scanner State: FAILED"); break; @@ -881,6 +861,17 @@ bool ESPBTDevice::resolve_irk(const uint8_t *irk) const { } #endif // USE_ESP32_BLE_DEVICE +void ESP32BLETracker::cleanup_scan_state_(bool is_stop_complete) { + ESP_LOGD(TAG, "Scan %scomplete, set scanner state to IDLE.", is_stop_complete ? "stop " : ""); + this->already_discovered_.clear(); + this->cancel_timeout("scan"); + + for (auto *listener : this->listeners_) + listener->on_scan_end(); + + this->set_scanner_state_(ScannerState::IDLE); +} + } // namespace esphome::esp32_ble_tracker #endif // USE_ESP32 diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h index e1119c0e18..c274e64b12 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h @@ -158,18 +158,16 @@ enum class ClientState : uint8_t { }; enum class ScannerState { - // Scanner is idle, init state, set from the main loop when processing STOPPED + // Scanner is idle, init state IDLE, - // Scanner is starting, set from the main loop only + // Scanner is starting STARTING, - // Scanner is running, set from the ESP callback only + // Scanner is running RUNNING, - // Scanner failed to start, set from the ESP callback only + // Scanner failed to start FAILED, - // Scanner is stopping, set from the main loop only + // Scanner is stopping STOPPING, - // Scanner is stopped, set from the ESP callback only - STOPPED, }; enum class ConnectionType : uint8_t { @@ -262,8 +260,6 @@ class ESP32BLETracker : public Component, void stop_scan_(); /// Start a single scan by setting up the parameters and doing some esp-idf calls. void start_scan_(bool first); - /// Called when a scan ends - void end_of_scan_(); /// Called when a `ESP_GAP_BLE_SCAN_RESULT_EVT` event is received. void gap_scan_result_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param ¶m); /// Called when a `ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT` event is received. @@ -274,6 +270,8 @@ class ESP32BLETracker : public Component, void gap_scan_stop_complete_(const esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param ¶m); /// Called to set the scanner state. Will also call callbacks to let listeners know when state is changed. void set_scanner_state_(ScannerState state); + /// Common cleanup logic when transitioning scanner to IDLE state + void cleanup_scan_state_(bool is_stop_complete); uint8_t app_id_{0};