[esp32_ble_tracker] Remove unnecessary STOPPED scanner state to reduce latency (#10055)

This commit is contained in:
J. Nick Koston 2025-08-03 18:57:59 -10:00 committed by GitHub
parent bb3ebaf955
commit 7c297366c7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 33 additions and 44 deletions

View File

@ -185,9 +185,6 @@ void ESP32BLETracker::loop() {
ESP_LOGW(TAG, "Dropped %zu BLE scan results due to buffer overflow", dropped); 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 || if (this->scanner_state_ == ScannerState::FAILED ||
(this->scan_set_param_failed_ && this->scanner_state_ == ScannerState::RUNNING)) { (this->scan_set_param_failed_ && this->scanner_state_ == ScannerState::RUNNING)) {
this->stop_scan_(); this->stop_scan_();
@ -278,8 +275,6 @@ void ESP32BLETracker::stop_scan_() {
ESP_LOGE(TAG, "Scan is starting while trying to stop."); ESP_LOGE(TAG, "Scan is starting while trying to stop.");
} else if (this->scanner_state_ == ScannerState::STOPPING) { } else if (this->scanner_state_ == ScannerState::STOPPING) {
ESP_LOGE(TAG, "Scan is already stopping while trying to stop."); 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; return;
} }
@ -306,8 +301,6 @@ void ESP32BLETracker::start_scan_(bool first) {
ESP_LOGE(TAG, "Cannot start scan while already stopping."); ESP_LOGE(TAG, "Cannot start scan while already stopping.");
} else if (this->scanner_state_ == ScannerState::FAILED) { } else if (this->scanner_state_ == ScannerState::FAILED) {
ESP_LOGE(TAG, "Cannot start scan while already 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; 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) { void ESP32BLETracker::register_client(ESPBTClient *client) {
client->app_id = ++this->app_id_; client->app_id = ++this->app_id_;
this->clients_.push_back(client); 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) { 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) { switch (event) {
case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT:
this->gap_scan_set_param_complete_(param->scan_param_cmpl); 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) { 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); ESP_LOGV(TAG, "gap_scan_result - event %d", scan_result.search_evt);
if (scan_result.search_evt == ESP_GAP_SEARCH_INQ_RES_EVT) { if (scan_result.search_evt == ESP_GAP_SEARCH_INQ_RES_EVT) {
// Lock-free SPSC ring buffer write (Producer side) // Ring buffer write (Producer side)
// This runs in the ESP-IDF Bluetooth stack callback thread // Even though we're in the main loop, the ring buffer design allows efficient batching
// IMPORTANT: Only this thread writes to ring_write_index_ // IMPORTANT: Only this thread writes to ring_write_index_
// Load our own index with relaxed ordering (we're the only writer) // 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."); ESP_LOGE(TAG, "Scan was in failed state when scan completed.");
} else if (this->scanner_state_ == ScannerState::IDLE) { } else if (this->scanner_state_ == ScannerState::IDLE) {
ESP_LOGE(TAG, "Scan was idle when scan completed."); 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 &param) { void ESP32BLETracker::gap_scan_set_param_complete_(const esp_ble_gap_cb_param_t::ble_scan_param_cmpl_evt_param &param) {
// 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); ESP_LOGV(TAG, "gap_scan_set_param_complete - status %d", param.status);
if (param.status == ESP_BT_STATUS_DONE) { if (param.status == ESP_BT_STATUS_DONE) {
this->scan_set_param_failed_ = ESP_BT_STATUS_SUCCESS; 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 &param) { void ESP32BLETracker::gap_scan_start_complete_(const esp_ble_gap_cb_param_t::ble_scan_start_cmpl_evt_param &param) {
// 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); ESP_LOGV(TAG, "gap_scan_start_complete - status %d", param.status);
this->scan_start_failed_ = param.status; this->scan_start_failed_ = param.status;
if (this->scanner_state_ != ScannerState::STARTING) { 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."); ESP_LOGE(TAG, "Scan was in failed state when start complete.");
} else if (this->scanner_state_ == ScannerState::IDLE) { } else if (this->scanner_state_ == ScannerState::IDLE) {
ESP_LOGE(TAG, "Scan was idle when start complete."); 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) { 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 &param) { void ESP32BLETracker::gap_scan_stop_complete_(const esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param &param) {
// 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); ESP_LOGV(TAG, "gap_scan_stop_complete - status %d", param.status);
if (this->scanner_state_ != ScannerState::STOPPING) { if (this->scanner_state_ != ScannerState::STOPPING) {
if (this->scanner_state_ == ScannerState::RUNNING) { 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."); ESP_LOGE(TAG, "Scan was in failed state when stop complete.");
} else if (this->scanner_state_ == ScannerState::IDLE) { } else if (this->scanner_state_ == ScannerState::IDLE) {
ESP_LOGE(TAG, "Scan was idle when stop complete."); 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, 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: case ScannerState::STOPPING:
ESP_LOGCONFIG(TAG, " Scanner State: STOPPING"); ESP_LOGCONFIG(TAG, " Scanner State: STOPPING");
break; break;
case ScannerState::STOPPED:
ESP_LOGCONFIG(TAG, " Scanner State: STOPPED");
break;
case ScannerState::FAILED: case ScannerState::FAILED:
ESP_LOGCONFIG(TAG, " Scanner State: FAILED"); ESP_LOGCONFIG(TAG, " Scanner State: FAILED");
break; break;
@ -881,6 +861,17 @@ bool ESPBTDevice::resolve_irk(const uint8_t *irk) const {
} }
#endif // USE_ESP32_BLE_DEVICE #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 } // namespace esphome::esp32_ble_tracker
#endif // USE_ESP32 #endif // USE_ESP32

View File

@ -158,18 +158,16 @@ enum class ClientState : uint8_t {
}; };
enum class ScannerState { enum class ScannerState {
// Scanner is idle, init state, set from the main loop when processing STOPPED // Scanner is idle, init state
IDLE, IDLE,
// Scanner is starting, set from the main loop only // Scanner is starting
STARTING, STARTING,
// Scanner is running, set from the ESP callback only // Scanner is running
RUNNING, RUNNING,
// Scanner failed to start, set from the ESP callback only // Scanner failed to start
FAILED, FAILED,
// Scanner is stopping, set from the main loop only // Scanner is stopping
STOPPING, STOPPING,
// Scanner is stopped, set from the ESP callback only
STOPPED,
}; };
enum class ConnectionType : uint8_t { enum class ConnectionType : uint8_t {
@ -262,8 +260,6 @@ class ESP32BLETracker : public Component,
void stop_scan_(); void stop_scan_();
/// Start a single scan by setting up the parameters and doing some esp-idf calls. /// Start a single scan by setting up the parameters and doing some esp-idf calls.
void start_scan_(bool first); 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. /// 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 &param); void gap_scan_result_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param &param);
/// Called when a `ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT` event is received. /// 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 &param); void gap_scan_stop_complete_(const esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param &param);
/// Called to set the scanner state. Will also call callbacks to let listeners know when state is changed. /// Called to set the scanner state. Will also call callbacks to let listeners know when state is changed.
void set_scanner_state_(ScannerState state); 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}; uint8_t app_id_{0};