ESPHome 2026.8.0-dev
Loading...
Searching...
No Matches
esp32_ble_tracker.h
Go to the documentation of this file.
1#pragma once
2
7
8#include <array>
9#include <span>
10#include <string>
11#include <vector>
12
13#ifdef USE_ESP32
14
15#include <esp_bt_defs.h>
16#include <esp_gap_ble_api.h>
17#include <esp_gattc_api.h>
18
19#include <freertos/FreeRTOS.h>
20#include <freertos/semphr.h>
21
27
28#ifdef USE_OTA_STATE_LISTENER
30#endif
31
33
34using namespace esp32_ble;
35
37
42
43#ifdef USE_ESP32_BLE_UUID
45#endif
46
47#ifdef USE_ESP32_BLE_DEVICE
48// The advertisement device types are owned by the platform-neutral
49// ble_device_base layer; re-exported here (esp32 only) for backward
50// compatibility. ESPBTDevice::parse_scan_rst() (esp32-only) adapts BLEScanResult.
53#endif // USE_ESP32_BLE_DEVICE
54
55class ESP32BLETracker;
56
57// esp32-flavored listener: the neutral parse_device/on_scan_end come from
58// ble_device_base; this subclass adds the esp32-only raw-advertisement path
59// (BLEScanResult batches) and the tracker back-pointer.
61 public:
62#ifndef USE_ESP32_BLE_DEVICE
63 // Raw-only build: no parsed-device support is compiled in.
64 bool parse_device(const ble_device_base::ESPBTDevice &device) override { return false; }
65#endif
66 virtual bool parse_devices(const BLEScanResult *scan_results, size_t count) { return false; };
70 void set_parent(ESP32BLETracker *parent) { parent_ = parent; }
71
72 protected:
74};
75
77 uint8_t connecting = 0;
78 uint8_t discovered = 0;
79 uint8_t disconnecting = 0;
80 // CONNECTED + ESTABLISHED clients. Tracked so coex stays at PREFER_BT
81 // while active connections may still need to send/receive GATT traffic.
82 uint8_t active = 0;
83
84 bool operator==(const ClientStateCounts &other) const {
85 return connecting == other.connecting && discovered == other.discovered && disconnecting == other.disconnecting &&
86 active == other.active;
87 }
88
89 bool operator!=(const ClientStateCounts &other) const { return !(*this == other); }
90};
91
92enum class ClientState : uint8_t {
93 // Connection is allocated
94 INIT,
95 // Client is disconnecting
97 // Connection is idle, no device detected.
98 IDLE,
99 // Device advertisement found.
101 // Connection in progress.
103 // Initial connection established.
104 CONNECTED,
105 // The client and sub-clients have completed setup.
107};
108
109enum class ScannerState {
110 // Scanner is idle, init state
111 IDLE,
112 // Scanner is starting
113 STARTING,
114 // Scanner is running
115 RUNNING,
116 // Scanner failed to start
117 FAILED,
118 // Scanner is stopping
119 STOPPING,
120};
121
128 public:
130};
131
132// Helper function to convert ClientState to string
134
135enum class ConnectionType : uint8_t {
136 // The default connection type, we hold all the services in ram
137 // for the duration of the connection.
138 V1,
139 // The client has a cache of the services and mtu so we should not
140 // fetch them again
142 // The client does not need the services and mtu once we send them
143 // so we should wipe them from memory as soon as we send them
145};
146
161 public:
162 virtual bool gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
163 esp_ble_gattc_cb_param_t *param) = 0;
164 virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) = 0;
165 virtual void connect() = 0;
166 virtual void disconnect() = 0;
167 bool disconnect_pending() const { return this->want_disconnect_; }
169
172 virtual void set_state(ClientState st) {
173 this->set_state_internal_(st);
174 if (st == ClientState::IDLE) {
175 this->want_disconnect_ = false;
176 }
177 }
178 ClientState state() const { return this->state_; }
179
183 void set_tracker_state_version(uint8_t *version) { this->tracker_state_version_ = version; }
184
185 // Memory optimized layout
186 uint8_t app_id; // App IDs are small integers assigned sequentially
187
188 protected:
193 this->state_ = st;
194 // Notify tracker that state changed (tracker_state_version_ is owned by ESP32BLETracker)
195 if (this->tracker_state_version_ != nullptr) {
196 (*this->tracker_state_version_)++;
197 }
198 }
199
200 // want_disconnect_ is set to true when a disconnect is requested
201 // while the client is connecting. This is used to disconnect the
202 // client as soon as we get the connection id (conn_id_) from the
203 // ESP_GATTC_OPEN_EVT event.
204 bool want_disconnect_{false};
205
206 private:
211 uint8_t *tracker_state_version_{nullptr};
212};
213
214class ESP32BLETracker final : public Component,
216#ifdef USE_OTA_STATE_LISTENER
218#endif
219 public Parented<ESP32BLE> {
220 public:
221 void set_scan_duration(uint32_t scan_duration) { scan_duration_ = scan_duration; }
222 void set_scan_interval(uint32_t scan_interval) { scan_interval_ = scan_interval; }
223 void set_scan_window(uint32_t scan_window) { scan_window_ = scan_window; }
225 bool get_scan_active() const { return scan_active_; }
226 void set_scan_continuous(bool scan_continuous) { scan_continuous_ = scan_continuous; }
227
229 void setup() override;
230 void dump_config() override;
231 float get_setup_priority() const override;
232
233 void loop() override;
234
235 // esp32-flavored path (unmigrated esp32 sensors; sets the tracker back-pointer).
237 void register_client(ESPBTClient *client);
239
240 // ---- ble_device_base::BLEHub (the platform-neutral tracker contract) ----
246 return {/* active_scan = */ true, /* merges_scan_response = */ true, /* gatt = */ true};
247 }
248 void get_adapter_mac(uint8_t out[6]) override;
249 bool scan_running() override { return this->scanner_state_ == ScannerState::RUNNING; }
250 bool scan_active() override { return this->scan_active_; }
251
252#ifdef USE_ESP32_BLE_DEVICE
253 void print_bt_device_info(const ESPBTDevice &device);
254#endif
255
256 void start_scan();
257 void stop_scan();
258
259 void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param);
260 void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param);
261 void gap_scan_event_handler(const BLEScanResult &scan_result);
263
264#ifdef USE_OTA_STATE_LISTENER
265 void on_ota_global_state(ota::OTAState state, float progress, uint8_t error, ota::OTAComponent *comp) override;
266#endif
267
270 this->scanner_state_listeners_.push_back(listener);
271 }
273
274 protected:
275 void stop_scan_();
277 void start_scan_(bool first);
279 void gap_scan_result_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param &param);
281 void gap_scan_set_param_complete_(const esp_ble_gap_cb_param_t::ble_scan_param_cmpl_evt_param &param);
283 void gap_scan_start_complete_(const esp_ble_gap_cb_param_t::ble_scan_start_cmpl_evt_param &param);
285 void gap_scan_stop_complete_(const esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param &param);
289 void cleanup_scan_state_(bool is_stop_complete);
291 void process_scan_result_(const BLEScanResult &scan_result);
299 void log_unexpected_state_(const char *operation, ScannerState expected_state) const;
300#ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE
302 void update_coex_preference_(bool force_ble);
303#endif
306 ClientStateCounts counts;
307#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT
308 for (auto *client : this->clients_) {
309 switch (client->state()) {
311 counts.disconnecting++;
312 break;
314 counts.discovered++;
315 break;
317 counts.connecting++;
318 break;
321 counts.active++;
322 break;
323 default:
324 break;
325 }
326 }
327#endif
328 return counts;
329 }
330
331 // Group 1: Large objects (12+ bytes) - vectors
332#ifdef ESPHOME_ESP32_BLE_TRACKER_LISTENER_COUNT
334#endif
335#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT
337#endif
338 std::vector<BLEScannerStateListener *> scanner_state_listeners_;
339 // Parsed listeners registered through the neutral BLEHub contract (migrated
340 // sensors); dispatched alongside listeners_.
341#ifdef ESPHOME_BLE_DEVICE_BASE_LISTENER_COUNT
343#endif
345#ifdef USE_ESP32_BLE_DEVICE
347 std::vector<uint64_t> already_discovered_;
348#endif
349
350 // Group 2: Structs (aligned to 4 bytes)
352 esp_ble_scan_params_t scan_params_;
354
355 // Group 3: 4-byte types
360 esp_bt_status_t scan_start_failed_{ESP_BT_STATUS_SUCCESS};
361 esp_bt_status_t scan_set_param_failed_{ESP_BT_STATUS_SUCCESS};
362
363 // Group 4: 1-byte types (enums, uint8_t, bool)
364 uint8_t app_id_{0};
372 uint8_t state_version_{0};
379#ifdef USE_OTA_STATE_LISTENER
381#endif
385#ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE
386 bool coex_prefer_ble_{false};
387#endif
388 // Scan timeout state machine
389 enum class ScanTimeoutState : uint8_t {
390 INACTIVE, // No timeout monitoring
391 MONITORING, // Actively monitoring for timeout
392 EXCEEDED_WAIT, // Timeout exceeded, waiting one loop before reboot
393 };
398};
399
400// NOLINTNEXTLINE
401extern ESP32BLETracker *global_esp32_ble_tracker;
402
403} // namespace esphome::esp32_ble_tracker
404
405#endif
Helper class to easily give an object a parent of type T.
Definition helpers.h:1881
Minimal static vector - saves memory by avoiding std::vector overhead.
Definition helpers.h:222
Listener interface for BLE scanner state changes.
virtual void on_scanner_state(ScannerState state)=0
void try_promote_discovered_clients_()
Try to promote discovered clients to ready to connect.
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
std::vector< uint64_t > already_discovered_
Vector of addresses that have already been printed in print_bt_device_info.
uint8_t state_version_
Version counter for loop() fast-path optimization.
void set_raw_advertisement_callback(ble_device_base::RawAdvertisementCallback cb) override
StaticVector< ESPBTClient *, ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT > clients_
void gap_scan_stop_complete_(const esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param &param)
Called when a ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT event is received.
ClientStateCounts count_client_states_() const
Count clients in each state.
uint8_t last_processed_version_
Last state_version_ value when loop() did full processing.
void gap_scan_event_handler(const BLEScanResult &scan_result)
std::vector< BLEScannerStateListener * > scanner_state_listeners_
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param)
ble_device_base::RawAdvertisementCallback raw_advertisement_callback_
esp_ble_scan_params_t scan_params_
A structure holding the ESP BLE scan parameters.
StaticVector< ESPBTDeviceListener *, ESPHOME_ESP32_BLE_TRACKER_LISTENER_COUNT > listeners_
void register_listener(ESPBTDeviceListener *listener)
uint32_t scan_timeout_ms_
Precomputed timeout value: scan_duration_ * 2000.
void update_coex_preference_(bool force_ble)
Update BLE coexistence preference.
const char * scanner_state_to_string_(ScannerState state) const
Convert scanner state enum to string for logging.
void gap_scan_set_param_complete_(const esp_ble_gap_cb_param_t::ble_scan_param_cmpl_evt_param &param)
Called when a ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT event is received.
uint32_t scan_duration_
The interval in seconds to perform scans.
void setup() override
Setup the FreeRTOS task and the Bluetooth stack.
void handle_scanner_failure_()
Handle scanner failure states.
void cleanup_scan_state_(bool is_stop_complete)
Common cleanup logic when transitioning scanner to IDLE state.
void set_scanner_state_(ScannerState state)
Called to set the scanner state. Will also call callbacks to let listeners know when state is changed...
ble_device_base::HubCapabilities get_capabilities() const override
void print_bt_device_info(const ESPBTDevice &device)
void set_scan_duration(uint32_t scan_duration)
void set_scan_interval(uint32_t scan_interval)
StaticVector< ble_device_base::ESPBTDeviceListener *, ESPHOME_BLE_DEVICE_BASE_LISTENER_COUNT > neutral_listeners_
void add_scanner_state_listener(BLEScannerStateListener *listener)
Add a listener for scanner state changes.
void process_scan_result_(const BLEScanResult &scan_result)
Process a single scan result immediately.
void gap_scan_start_complete_(const esp_ble_gap_cb_param_t::ble_scan_start_cmpl_evt_param &param)
Called when a ESP_GAP_BLE_SCAN_START_COMPLETE_EVT event is received.
void log_unexpected_state_(const char *operation, ScannerState expected_state) const
Log an unexpected scanner state.
void on_ota_global_state(ota::OTAState state, float progress, uint8_t error, ota::OTAComponent *comp) override
void gap_scan_result_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param &param)
Called when a ESP_GAP_BLE_SCAN_RESULT_EVT event is received.
void start_scan_(bool first)
Start a single scan by setting up the parameters and doing some esp-idf calls.
Base class for BLE GATT clients that connect to remote devices.
void set_tracker_state_version(uint8_t *version)
Called by ESP32BLETracker::register_client() to enable state change notifications.
virtual void set_state(ClientState st)
Set the client state with IDLE handling (clears want_disconnect_).
virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)=0
virtual bool gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param)=0
void set_state_internal_(ClientState st)
Set state without IDLE handling - use for direct state transitions.
virtual AdvertisementParserType get_advertisement_parser_type()
bool parse_device(const ble_device_base::ESPBTDevice &device) override
virtual bool parse_devices(const BLEScanResult *scan_results, size_t count)
Listener interface for global OTA state changes (includes OTA component pointer).
bool state
Definition fan.h:2
std::vector< uint8_t > adv_data_t
Definition ble_device.h:37
std::function< void(const uint8_t *mac, int rssi, uint8_t addr_type, const uint8_t *data, uint16_t data_len)> RawAdvertisementCallback
Callback for raw advertisements (the bluetooth_proxy path).
Definition ble_hub.h:27
ble_device_base::adv_data_t adv_data_t
ESP32BLETracker * global_esp32_ble_tracker
const char * client_state_to_string(ClientState state)
static void uint32_t
What a tracker's controller/SDK can do — consumers branch on data, not #ifdefs.
Definition ble_hub.h:31
bool operator==(const ClientStateCounts &other) const
bool operator!=(const ClientStateCounts &other) const