[esp32_ble_tracker] Mark the disable from the hook and trim the transition code

The tracker now learns about a disable from the before-disabled handler
instead of polling is_active(), so a cancelled disable never reaches it and
the scan restart no longer needs an IDLE gate. enable() and disable() are
inline wrappers over a per-direction chain, ble_client skips the cache
clean on a stack that is going down, and a stale comment and doc are
brought up to date.
This commit is contained in:
J. Nick Koston
2026-09-10 08:48:00 -05:00
parent 8b273cc912
commit 7f4de6c5bd
6 changed files with 32 additions and 38 deletions
@@ -70,8 +70,8 @@ void BluedroidGattClient::loop() {
this->listener_->on_connection_state(false, 0, ESP_GATT_CONN_TIMEOUT);
}
} else {
// The loop stays on while a link exists (stack-down watch, pre-started
// search flush); it settles only back at IDLE.
// The loop stays on while a link exists (pre-started search flush); it
// settles only back at IDLE.
this->deliver_pending_search_();
if (this->state() == ClientState::IDLE) {
this->disable_loop();
+12 -22
View File
@@ -83,32 +83,22 @@ void ESP32BLE::setup() {
}
}
void ESP32BLE::enable() { this->request_state_(true); }
void ESP32BLE::disable() { this->request_state_(false); }
// Queue the transition for loop(). A pending transition the other way is
// cancelled instead, since nothing was torn down or brought up yet; any other
// state is already there or on its way.
void ESP32BLE::request_state_(bool enable) {
switch (this->state_) {
case BLE_COMPONENT_STATE_DISABLE:
if (enable)
this->state_ = BLE_COMPONENT_STATE_ACTIVE;
break;
case BLE_COMPONENT_STATE_ENABLE:
if (!enable)
this->state_ = BLE_COMPONENT_STATE_DISABLED;
break;
case BLE_COMPONENT_STATE_DISABLED:
if (enable)
this->state_ = BLE_COMPONENT_STATE_ENABLE;
break;
case BLE_COMPONENT_STATE_ACTIVE:
if (!enable)
this->state_ = BLE_COMPONENT_STATE_DISABLE;
break;
default:
break;
if (enable) {
if (this->state_ == BLE_COMPONENT_STATE_DISABLED) {
this->state_ = BLE_COMPONENT_STATE_ENABLE;
} else if (this->state_ == BLE_COMPONENT_STATE_DISABLE) {
this->state_ = BLE_COMPONENT_STATE_ACTIVE;
}
} else {
if (this->state_ == BLE_COMPONENT_STATE_ACTIVE) {
this->state_ = BLE_COMPONENT_STATE_DISABLE;
} else if (this->state_ == BLE_COMPONENT_STATE_ENABLE) {
this->state_ = BLE_COMPONENT_STATE_DISABLED;
}
}
}
+2 -2
View File
@@ -102,8 +102,8 @@ class ESP32BLE final : public Component {
}
uint32_t get_advertising_cycle_time() const { return this->advertising_cycle_time_; }
void enable();
void disable();
void enable() { this->request_state_(true); }
void disable() { this->request_state_(false); }
ESPHOME_ALWAYS_INLINE bool is_active() { return this->state_ == BLE_COMPONENT_STATE_ACTIVE; }
void setup() override;
void loop() override;
@@ -134,7 +134,7 @@ void BLEClientBase::connect() {
}
if (this->gattc_if_ == ESP_GATT_IF_NONE) {
// Bluedroid drops an open on an unknown interface without any event.
ESP_LOGW(TAG, "[%d] [%s] Connect rejected, GATT app not registered", this->connection_index_, this->address_str_);
this->log_warning_("Connect rejected, GATT app not registered");
this->set_state(espbt::ClientState::IDLE);
return;
}
@@ -220,7 +220,10 @@ void BLEClientBase::release_services() {
#ifndef CONFIG_BT_GATTC_CACHE_NVS_FLASH
// Only the cache clean makes the stack's database unsafe to walk.
this->services_released_ = true;
esp_ble_gattc_cache_clean(this->remote_bda_);
// A stack on its way down frees its own cache.
if (esp32_ble::global_ble->is_active()) {
esp_ble_gattc_cache_clean(this->remote_bda_);
}
#endif
}
@@ -156,10 +156,11 @@ class BLEClientBase : public espbt::ESPBTClient, public Component {
void log_connection_params_(const char *param_type);
void handle_connection_result_(esp_err_t ret);
/// Hook called once a connection has been fully torn down (after release_services() and
/// set_idle_()), from both the CLOSE_EVT handler and the DISCONNECTING safety timeout.
/// set_idle_()): CLOSE_EVT, the DISCONNECTING safety timeout, or the BLE stack going down.
/// Subclasses with extra per-connection accounting (e.g. bluetooth_proxy slot state)
/// override this to release that state. `reason` is the controller reason code, or
/// ESP_GATT_CONN_TIMEOUT for the safety-timeout path.
/// override this to release that state. `reason` is the controller reason code,
/// ESP_GATT_CONN_TIMEOUT for the safety timeout, or ESP_GATT_CONN_TERMINATE_LOCAL_HOST
/// for the stack going down.
virtual void on_disconnect_complete(esp_err_t reason) {}
/// Transition to IDLE and reset conn_id — call when the connection is fully dead.
void set_idle_() {
@@ -74,13 +74,12 @@ void ESP32BLETracker::on_ota_global_state(ota::OTAState state, float progress, u
void ESP32BLETracker::loop() {
if (!this->parent_->is_active()) {
this->ble_was_disabled_ = true;
return;
} else if (this->ble_was_disabled_) {
}
if (this->ble_was_disabled_) {
this->ble_was_disabled_ = false;
// Start the scan again after a disable. A cancelled disable never stopped
// it, so only start from IDLE.
if (this->scan_continuous_ && this->scanner_state_ == ScannerState::IDLE) {
// First start after boot or after the stack came back.
if (this->scan_continuous_) {
this->start_scan();
}
}
@@ -228,12 +227,13 @@ void ESP32BLETracker::ble_before_disabled_event_handler() {
#endif
// 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;
this->skip_next_scan_end_ = false;
#endif
if (this->scanner_state_ != ScannerState::IDLE) {
this->cleanup_scan_state_(true);
}
this->ble_was_disabled_ = true;
}
bool ESP32BLETracker::stop_scan_() {