mirror of
https://github.com/esphome/esphome.git
synced 2026-08-22 22:26:21 +00:00
[bk72xx_ble_tracker] Fix scan start retry and stale-millis underflow (#17992)
This commit is contained in:
@@ -48,12 +48,21 @@ void BK72xxBLETracker::on_ota_global_state(ota::OTAState state, float progress,
|
|||||||
ota::OTAComponent *comp) {
|
ota::OTAComponent *comp) {
|
||||||
if (state == ota::OTA_STARTED) {
|
if (state == ota::OTA_STARTED) {
|
||||||
this->scan_continuous_before_ota_ = this->scan_continuous_;
|
this->scan_continuous_before_ota_ = this->scan_continuous_;
|
||||||
|
this->scan_requested_before_ota_ = this->scan_requested_;
|
||||||
this->stop_scan();
|
this->stop_scan();
|
||||||
} else if ((state == ota::OTA_ERROR || state == ota::OTA_ABORT) && this->scan_continuous_before_ota_) {
|
} else if (state == ota::OTA_ERROR || state == ota::OTA_ABORT) {
|
||||||
// On success the device reboots, so restore only on a failed/aborted update;
|
// On success the device reboots, so restore only on a failed/aborted update;
|
||||||
// loop() restarts the scan on its next iteration (continuous idle branch).
|
// loop() restarts the scan on its next iteration (continuous idle branch).
|
||||||
this->scan_continuous_before_ota_ = false;
|
if (this->scan_continuous_before_ota_) {
|
||||||
this->scan_continuous_ = true;
|
this->scan_continuous_before_ota_ = false;
|
||||||
|
this->scan_continuous_ = true;
|
||||||
|
}
|
||||||
|
// A one-shot request that was still pending (latched, retrying) when the
|
||||||
|
// OTA paused scanning is re-latched, not dropped — loop() resumes the retry.
|
||||||
|
if (this->scan_requested_before_ota_) {
|
||||||
|
this->scan_requested_before_ota_ = false;
|
||||||
|
this->scan_requested_ = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif // USE_OTA_STATE_LISTENER
|
#endif // USE_OTA_STATE_LISTENER
|
||||||
@@ -62,25 +71,11 @@ void BK72xxBLETracker::loop() {
|
|||||||
const uint32_t now = millis();
|
const uint32_t now = millis();
|
||||||
if (this->scan_continuous_) {
|
if (this->scan_continuous_) {
|
||||||
if (!this->scan_running_) {
|
if (!this->scan_running_) {
|
||||||
// Rate-limit (re)start attempts. The controller start can fail (no idle activity
|
// A start that succeeded re-anchored the period timer from a later millis(),
|
||||||
// handle, WiFi/BLE coexistence) and leave scan_running_ false; retrying every
|
// so the stale `now` below would underflow the comparison and fire
|
||||||
// main-loop iteration would spin the single-core CPU and starve WiFi (device
|
// on_scan_end() for a scan that just began. Resume next iteration.
|
||||||
// becomes unresponsive). The interval backs off with consecutive failures so a
|
if (this->try_start_with_backoff_(now))
|
||||||
// controller that never comes up polls slowly and quietly.
|
return;
|
||||||
const uint8_t doublings = std::min<uint8_t>(this->failed_start_count_, SCAN_START_RETRY_MAX_DOUBLINGS);
|
|
||||||
if (now - this->last_scan_start_attempt_ >= (SCAN_START_RETRY_MS << doublings)) {
|
|
||||||
this->last_scan_start_attempt_ = now;
|
|
||||||
this->start_scan_();
|
|
||||||
if (this->scan_running_) {
|
|
||||||
this->failed_start_count_ = 0;
|
|
||||||
} else if (this->failed_start_count_ < SCAN_START_RETRY_MAX_DOUBLINGS) {
|
|
||||||
++this->failed_start_count_;
|
|
||||||
if (this->failed_start_count_ == SCAN_START_RETRY_MAX_DOUBLINGS) {
|
|
||||||
ESP_LOGW(TAG, "Scan start keeps failing; retrying every %" PRIu32 " s",
|
|
||||||
(SCAN_START_RETRY_MS << SCAN_START_RETRY_MAX_DOUBLINGS) / 1000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Period timer: fire on_scan_end() once per scan_duration_ window, mirroring
|
// Period timer: fire on_scan_end() once per scan_duration_ window, mirroring
|
||||||
// esp32_ble_tracker::cleanup_scan_state_(). Gated on scan_started_once_ so a scan
|
// esp32_ble_tracker::cleanup_scan_state_(). Gated on scan_started_once_ so a scan
|
||||||
@@ -98,11 +93,51 @@ void BK72xxBLETracker::loop() {
|
|||||||
|
|
||||||
// Non-continuous mode: run for scan_duration_ ms, then stop and fire on_scan_end.
|
// Non-continuous mode: run for scan_duration_ ms, then stop and fire on_scan_end.
|
||||||
// Restart is driven externally (e.g. api: on_client_connected:).
|
// Restart is driven externally (e.g. api: on_client_connected:).
|
||||||
|
//
|
||||||
|
// A requested start that failed (same controller failures the continuous branch
|
||||||
|
// absorbs) is retried with the same backoff — otherwise a failed one-shot start
|
||||||
|
// would be silent: the scan never runs, stop_scan_() is never reached and
|
||||||
|
// on_scan_end() never fires, leaving period-keyed consumers waiting forever.
|
||||||
|
if (this->scan_requested_ && !this->scan_running_) {
|
||||||
|
// Same stale-`now` hazard as the continuous branch: start_scan_() stamps
|
||||||
|
// scan_start_time_ from a later millis(), so the duration check below would
|
||||||
|
// underflow and stop the scan in the iteration that started it.
|
||||||
|
if (this->try_start_with_backoff_(now))
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (this->scan_running_ && now - this->scan_start_time_ >= this->scan_duration_) {
|
if (this->scan_running_ && now - this->scan_start_time_ >= this->scan_duration_) {
|
||||||
this->stop_scan_();
|
this->stop_scan_();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool BK72xxBLETracker::try_start_with_backoff_(uint32_t now, bool force) {
|
||||||
|
// Rate-limit (re)start attempts. The controller start can fail (no idle activity
|
||||||
|
// handle, WiFi/BLE coexistence) and leave scan_running_ false; retrying every
|
||||||
|
// main-loop iteration would spin the single-core CPU and starve WiFi (device
|
||||||
|
// becomes unresponsive). The interval backs off with consecutive failures so a
|
||||||
|
// controller that never comes up polls slowly and quietly.
|
||||||
|
//
|
||||||
|
// force bypasses the gate for an explicit user start (start_scan()) — but
|
||||||
|
// only while the failure streak is clean. Once the controller is failing,
|
||||||
|
// even user-initiated attempts respect the backoff, so a start_scan() action
|
||||||
|
// on a short cadence cannot hammer a failing controller; the attempt stays
|
||||||
|
// inside the failure accounting below either way.
|
||||||
|
const uint8_t doublings = std::min<uint8_t>(this->failed_start_count_, SCAN_START_RETRY_MAX_DOUBLINGS);
|
||||||
|
if ((!force || this->failed_start_count_ != 0) &&
|
||||||
|
now - this->last_scan_start_attempt_ < (SCAN_START_RETRY_MS << doublings))
|
||||||
|
return false;
|
||||||
|
this->last_scan_start_attempt_ = now;
|
||||||
|
this->start_scan_();
|
||||||
|
if (!this->scan_running_ && this->failed_start_count_ < SCAN_START_RETRY_MAX_DOUBLINGS) {
|
||||||
|
++this->failed_start_count_;
|
||||||
|
if (this->failed_start_count_ == SCAN_START_RETRY_MAX_DOUBLINGS) {
|
||||||
|
ESP_LOGW(TAG, "Scan start keeps failing; retrying every %" PRIu32 " s",
|
||||||
|
(SCAN_START_RETRY_MS << SCAN_START_RETRY_MAX_DOUBLINGS) / 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this->scan_running_;
|
||||||
|
}
|
||||||
|
|
||||||
void BK72xxBLETracker::dump_config() {
|
void BK72xxBLETracker::dump_config() {
|
||||||
ESP_LOGCONFIG(TAG,
|
ESP_LOGCONFIG(TAG,
|
||||||
"BK72xx BLE Tracker:\n"
|
"BK72xx BLE Tracker:\n"
|
||||||
@@ -153,13 +188,29 @@ void BK72xxBLETracker::on_scan_report(const bk72xx_ble::BLEScanReport &report) {
|
|||||||
void BK72xxBLETracker::start_scan() {
|
void BK72xxBLETracker::start_scan() {
|
||||||
// Mirrors esp32_ble_tracker::start_scan(): caller sets scan_continuous_ via
|
// Mirrors esp32_ble_tracker::start_scan(): caller sets scan_continuous_ via
|
||||||
// set_scan_continuous() first, then calls start_scan() to begin scanning.
|
// set_scan_continuous() first, then calls start_scan() to begin scanning.
|
||||||
if (!this->scan_running_) {
|
//
|
||||||
this->start_scan_();
|
// Nothing to do while a scan is already running: latching here would leave
|
||||||
}
|
// scan_requested_ set after that scan ends and silently restart a one-shot
|
||||||
|
// scan nobody asked for.
|
||||||
|
if (this->scan_running_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// The request is latched: if this immediate attempt fails (controller busy,
|
||||||
|
// WiFi/BLE coexistence), loop() keeps retrying it with backoff even in
|
||||||
|
// non-continuous mode, so a one-shot start cannot fail silently.
|
||||||
|
//
|
||||||
|
// Routed through the backoff helper (forced: the user asked for an immediate
|
||||||
|
// attempt) so a failure here still counts toward the backoff escalation and
|
||||||
|
// its WARN. The force bypass only applies while the failure streak is clean —
|
||||||
|
// against a failing controller, repeated start_scan() calls are rate-limited
|
||||||
|
// like any other attempt.
|
||||||
|
this->scan_requested_ = true;
|
||||||
|
this->try_start_with_backoff_(millis(), /* force= */ true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BK72xxBLETracker::stop_scan() {
|
void BK72xxBLETracker::stop_scan() {
|
||||||
this->scan_continuous_ = false;
|
this->scan_continuous_ = false;
|
||||||
|
this->scan_requested_ = false; // also cancels a pending (not yet successful) start
|
||||||
this->stop_scan_();
|
this->stop_scan_();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,6 +228,8 @@ void BK72xxBLETracker::start_scan_() {
|
|||||||
|
|
||||||
const uint32_t now = millis();
|
const uint32_t now = millis();
|
||||||
this->scan_running_ = true;
|
this->scan_running_ = true;
|
||||||
|
this->scan_requested_ = false; // the latched one-shot request is satisfied
|
||||||
|
this->failed_start_count_ = 0; // reset here so direct starts clear the backoff too
|
||||||
this->scan_start_time_ = now;
|
this->scan_start_time_ = now;
|
||||||
// Log every explicit start at DEBUG — stop_scan_() logs every stop at DEBUG, and
|
// Log every explicit start at DEBUG — stop_scan_() logs every stop at DEBUG, and
|
||||||
// in non-continuous mode each period is an explicit start, so asymmetric logging
|
// in non-continuous mode each period is an explicit start, so asymmetric logging
|
||||||
|
|||||||
@@ -112,8 +112,15 @@ class BK72xxBLETracker : public Component,
|
|||||||
protected:
|
protected:
|
||||||
void start_scan_();
|
void start_scan_();
|
||||||
void stop_scan_();
|
void stop_scan_();
|
||||||
|
/// Attempt a rate-limited (re)start; returns true when the scan is running,
|
||||||
|
/// which means the caller must not compare its cached millis() against the
|
||||||
|
/// timestamps start_scan_() just refreshed. force bypasses the rate gate for
|
||||||
|
/// an explicit user start only while the failure streak is clean; a failing
|
||||||
|
/// controller rate-limits forced attempts too. Failure accounting always runs.
|
||||||
|
bool try_start_with_backoff_(uint32_t now, bool force = false);
|
||||||
|
|
||||||
bool scan_running_{false};
|
bool scan_running_{false};
|
||||||
|
bool scan_requested_{false}; // latched start_scan() request not yet running; loop() retries with backoff
|
||||||
// Defaults: the BK reference — 30 % duty cycle
|
// Defaults: the BK reference — 30 % duty cycle
|
||||||
// (interval 100 ms / window 30 ms), in 0.625 ms BLE units.
|
// (interval 100 ms / window 30 ms), in 0.625 ms BLE units.
|
||||||
uint32_t scan_interval_{160}; // 160 × 0.625 ms = 100 ms
|
uint32_t scan_interval_{160}; // 160 × 0.625 ms = 100 ms
|
||||||
@@ -122,6 +129,7 @@ class BK72xxBLETracker : public Component,
|
|||||||
bool scan_continuous_{true};
|
bool scan_continuous_{true};
|
||||||
#ifdef USE_OTA_STATE_LISTENER
|
#ifdef USE_OTA_STATE_LISTENER
|
||||||
bool scan_continuous_before_ota_{false}; // continuous mode saved at OTA start, restored on OTA failure
|
bool scan_continuous_before_ota_{false}; // continuous mode saved at OTA start, restored on OTA failure
|
||||||
|
bool scan_requested_before_ota_{false}; // pending one-shot latch saved at OTA start, re-latched on OTA failure
|
||||||
#endif
|
#endif
|
||||||
uint32_t scan_start_time_{0};
|
uint32_t scan_start_time_{0};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user