mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +00:00 
			
		
		
		
	[esp32_ble_tracker] Fix false reboots when event loop is blocked (#10144)
This commit is contained in:
		| @@ -101,6 +101,38 @@ void ESP32BLETracker::loop() { | |||||||
|       this->start_scan(); |       this->start_scan(); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   // Check for scan timeout - moved here from scheduler to avoid false reboots | ||||||
|  |   // when the loop is blocked | ||||||
|  |   if (this->scanner_state_ == ScannerState::RUNNING) { | ||||||
|  |     switch (this->scan_timeout_state_) { | ||||||
|  |       case ScanTimeoutState::MONITORING: { | ||||||
|  |         uint32_t now = App.get_loop_component_start_time(); | ||||||
|  |         uint32_t timeout_ms = this->scan_duration_ * 2000; | ||||||
|  |         // Robust time comparison that handles rollover correctly | ||||||
|  |         // This works because unsigned arithmetic wraps around predictably | ||||||
|  |         if ((now - this->scan_start_time_) > timeout_ms) { | ||||||
|  |           // First time we've seen the timeout exceeded - wait one more loop iteration | ||||||
|  |           // This ensures all components have had a chance to process pending events | ||||||
|  |           // This is because esp32_ble may not have run yet and called | ||||||
|  |           // gap_scan_event_handler yet when the loop unblocks | ||||||
|  |           ESP_LOGW(TAG, "Scan timeout exceeded"); | ||||||
|  |           this->scan_timeout_state_ = ScanTimeoutState::EXCEEDED_WAIT; | ||||||
|  |         } | ||||||
|  |         break; | ||||||
|  |       } | ||||||
|  |       case ScanTimeoutState::EXCEEDED_WAIT: | ||||||
|  |         // We've waited at least one full loop iteration, and scan is still running | ||||||
|  |         ESP_LOGE(TAG, "Scan never terminated, rebooting"); | ||||||
|  |         App.reboot(); | ||||||
|  |         break; | ||||||
|  |  | ||||||
|  |       case ScanTimeoutState::INACTIVE: | ||||||
|  |         // This case should be unreachable - scanner and timeout states are always synchronized | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|   ClientStateCounts counts = this->count_client_states_(); |   ClientStateCounts counts = this->count_client_states_(); | ||||||
|   if (counts != this->client_state_counts_) { |   if (counts != this->client_state_counts_) { | ||||||
|     this->client_state_counts_ = counts; |     this->client_state_counts_ = counts; | ||||||
| @@ -164,7 +196,8 @@ void ESP32BLETracker::stop_scan_() { | |||||||
|     ESP_LOGE(TAG, "Cannot stop scan: %s", this->scanner_state_to_string_(this->scanner_state_)); |     ESP_LOGE(TAG, "Cannot stop scan: %s", this->scanner_state_to_string_(this->scanner_state_)); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   this->cancel_timeout("scan"); |   // Reset timeout state machine when stopping scan | ||||||
|  |   this->scan_timeout_state_ = ScanTimeoutState::INACTIVE; | ||||||
|   this->set_scanner_state_(ScannerState::STOPPING); |   this->set_scanner_state_(ScannerState::STOPPING); | ||||||
|   esp_err_t err = esp_ble_gap_stop_scanning(); |   esp_err_t err = esp_ble_gap_stop_scanning(); | ||||||
|   if (err != ESP_OK) { |   if (err != ESP_OK) { | ||||||
| @@ -197,11 +230,10 @@ void ESP32BLETracker::start_scan_(bool first) { | |||||||
|   this->scan_params_.scan_interval = this->scan_interval_; |   this->scan_params_.scan_interval = this->scan_interval_; | ||||||
|   this->scan_params_.scan_window = this->scan_window_; |   this->scan_params_.scan_window = this->scan_window_; | ||||||
|  |  | ||||||
|   // Start timeout before scan is started. Otherwise scan never starts if any error. |   // Start timeout monitoring in loop() instead of using scheduler | ||||||
|   this->set_timeout("scan", this->scan_duration_ * 2000, []() { |   // This prevents false reboots when the loop is blocked | ||||||
|     ESP_LOGE(TAG, "Scan never terminated, rebooting to restore stack (IDF)"); |   this->scan_start_time_ = App.get_loop_component_start_time(); | ||||||
|     App.reboot(); |   this->scan_timeout_state_ = ScanTimeoutState::MONITORING; | ||||||
|   }); |  | ||||||
|  |  | ||||||
|   esp_err_t err = esp_ble_gap_set_scan_params(&this->scan_params_); |   esp_err_t err = esp_ble_gap_set_scan_params(&this->scan_params_); | ||||||
|   if (err != ESP_OK) { |   if (err != ESP_OK) { | ||||||
| @@ -752,7 +784,8 @@ void ESP32BLETracker::cleanup_scan_state_(bool is_stop_complete) { | |||||||
| #ifdef USE_ESP32_BLE_DEVICE | #ifdef USE_ESP32_BLE_DEVICE | ||||||
|   this->already_discovered_.clear(); |   this->already_discovered_.clear(); | ||||||
| #endif | #endif | ||||||
|   this->cancel_timeout("scan"); |   // Reset timeout state machine instead of cancelling scheduler timeout | ||||||
|  |   this->scan_timeout_state_ = ScanTimeoutState::INACTIVE; | ||||||
|  |  | ||||||
|   for (auto *listener : this->listeners_) |   for (auto *listener : this->listeners_) | ||||||
|     listener->on_scan_end(); |     listener->on_scan_end(); | ||||||
|   | |||||||
| @@ -367,6 +367,14 @@ class ESP32BLETracker : public Component, | |||||||
| #ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE | #ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE | ||||||
|   bool coex_prefer_ble_{false}; |   bool coex_prefer_ble_{false}; | ||||||
| #endif | #endif | ||||||
|  |   // Scan timeout state machine | ||||||
|  |   enum class ScanTimeoutState : uint8_t { | ||||||
|  |     INACTIVE,       // No timeout monitoring | ||||||
|  |     MONITORING,     // Actively monitoring for timeout | ||||||
|  |     EXCEEDED_WAIT,  // Timeout exceeded, waiting one loop before reboot | ||||||
|  |   }; | ||||||
|  |   uint32_t scan_start_time_{0}; | ||||||
|  |   ScanTimeoutState scan_timeout_state_{ScanTimeoutState::INACTIVE}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| // NOLINTNEXTLINE | // NOLINTNEXTLINE | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user