diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index 48398f1f633..92fd04f77a2 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -250,6 +250,9 @@ void ESP32BLETracker::start_scan_(bool first) { void ESP32BLETracker::register_client(ESPBTClient *client) { #ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT client->app_id = ++this->app_id_; + // Give client a pointer to our state_version_ so it can notify us of state changes. + // This enables loop() fast-path optimization - we skip expensive work when no state changed. + // Safe because ESP32BLETracker (singleton) outlives all registered clients. client->set_tracker_state_version(&this->state_version_); this->clients_.push_back(client); this->recalculate_advertisement_parser_types(); diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h index 55c7ae57faa..fa0cdb6f452 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h @@ -216,6 +216,19 @@ enum class ConnectionType : uint8_t { V3_WITHOUT_CACHE }; +/// Base class for BLE GATT clients that connect to remote devices. +/// +/// State Change Tracking Design: +/// ----------------------------- +/// ESP32BLETracker::loop() needs to know when client states change to avoid +/// expensive polling. Rather than checking all clients every iteration (~7000/min), +/// we use a version counter owned by ESP32BLETracker that clients increment on +/// state changes. The tracker compares versions to skip work when nothing changed. +/// +/// Ownership: ESP32BLETracker owns state_version_. Clients hold a non-owning +/// pointer (tracker_state_version_) set during register_client(). Clients +/// increment the counter through this pointer when their state changes. +/// The pointer may be null if the client is not registered with a tracker. class ESPBTClient : public ESPBTDeviceListener { public: virtual bool gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, @@ -225,6 +238,9 @@ class ESPBTClient : public ESPBTDeviceListener { virtual void disconnect() = 0; bool disconnect_pending() const { return this->want_disconnect_; } void cancel_pending_disconnect() { this->want_disconnect_ = false; } + + /// Set the client state with IDLE handling (clears want_disconnect_). + /// Notifies the tracker of state change for loop optimization. virtual void set_state(ClientState st) { this->set_state_internal_(st); if (st == ClientState::IDLE) { @@ -233,16 +249,21 @@ class ESPBTClient : public ESPBTDeviceListener { } ClientState state() const { return this->state_; } - /// Set the tracker's state version pointer for change notification + /// Called by ESP32BLETracker::register_client() to enable state change notifications. + /// The pointer must remain valid for the lifetime of the client (guaranteed since + /// ESP32BLETracker is a singleton that outlives all clients). void set_tracker_state_version(uint8_t *version) { this->tracker_state_version_ = version; } // Memory optimized layout uint8_t app_id; // App IDs are small integers assigned sequentially protected: - /// Set state without IDLE handling - use for direct state transitions + /// Set state without IDLE handling - use for direct state transitions. + /// Increments the tracker's state version counter to signal that loop() + /// should do full processing on the next iteration. void set_state_internal_(ClientState st) { this->state_ = st; + // Notify tracker that state changed (tracker_state_version_ is owned by ESP32BLETracker) if (this->tracker_state_version_ != nullptr) { (*this->tracker_state_version_)++; } @@ -256,8 +277,9 @@ class ESPBTClient : public ESPBTDeviceListener { private: ClientState state_{ClientState::INIT}; - /// Pointer to tracker's state_version_ counter, incremented on state changes - /// to enable fast-path loop optimization. Set by ESP32BLETracker::register_client(). + /// Non-owning pointer to ESP32BLETracker::state_version_. When this client's + /// state changes, we increment the tracker's counter to signal that loop() + /// should perform full processing. Null if client not registered with tracker. uint8_t *tracker_state_version_{nullptr}; }; @@ -394,10 +416,15 @@ class ESP32BLETracker : public Component, // Group 4: 1-byte types (enums, uint8_t, bool) uint8_t app_id_{0}; uint8_t scan_start_fail_count_{0}; - /// Version counter incremented on any state change (scanner or client) - /// Used for fast-path optimization in loop() to skip work when nothing changed. + /// Version counter for loop() fast-path optimization. Incremented when: + /// - Scanner state changes (via set_scanner_state_()) + /// - Any registered client's state changes (clients hold pointer to this counter) + /// Owned by this class; clients receive non-owning pointer via register_client(). + /// When loop() sees state_version_ == last_processed_version_, it skips expensive + /// client state counting and takes the fast path (just timeout check + return). uint8_t state_version_{0}; - /// Last state version that was fully processed in loop() + /// Last state_version_ value when loop() did full processing. Compared against + /// state_version_ to detect if any state changed since last iteration. uint8_t last_processed_version_{0}; ScannerState scanner_state_{ScannerState::IDLE}; bool scan_continuous_;