ESPHome 2026.10.0
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
12#ifdef USE_ESP32
13
14#include <esp_bt_defs.h>
15#include <esp_gap_ble_api.h>
16#include <esp_gattc_api.h>
17
18#include <freertos/FreeRTOS.h>
19#include <freertos/semphr.h>
20
27
28#ifdef USE_OTA_STATE_LISTENER
30#endif
31
33
34using namespace esp32_ble;
35
37
38#ifdef USE_ESP32_BLE_UUID
40#endif
41
42#ifdef USE_ESP32_BLE_DEVICE
43// The advertisement device types are owned by the platform-neutral
44// ble_device_base layer; re-exported here (esp32 only) for backward
45// compatibility. ESPBTDevice::parse_scan_rst() (esp32-only) adapts BLEScanResult.
48#endif // USE_ESP32_BLE_DEVICE
49
50class ESP32BLETracker;
51
52// esp32-flavored listener: the neutral parse_device/on_scan_end come from
53// ble_device_base; this subclass adds the esp32-only raw-advertisement path
54// (BLEScanResult batches) and the tracker back-pointer.
56 public:
57#ifndef USE_ESP32_BLE_DEVICE
58 // Raw-only build: no parsed-device support is compiled in.
59 bool parse_device(const ble_device_base::ESPBTDevice &device) override { return false; }
60#endif
61 void set_parent(ESP32BLETracker *parent) { parent_ = parent; }
62
63 protected:
65};
66
68 uint8_t connecting = 0;
69 uint8_t discovered = 0;
70 uint8_t disconnecting = 0;
71 // CONNECTED + ESTABLISHED clients. Tracked so coex stays at PREFER_BT
72 // while active connections may still need to send/receive GATT traffic.
73 uint8_t active = 0;
74
75 bool operator==(const ClientStateCounts &other) const {
76 return connecting == other.connecting && discovered == other.discovered && disconnecting == other.disconnecting &&
77 active == other.active;
78 }
79
80 bool operator!=(const ClientStateCounts &other) const { return !(*this == other); }
81};
82
83// The client connection state types are owned by the platform-neutral
84// ble_device_base layer; re-exported here for backward compatibility.
85using ClientState = ble_device_base::ClientState;
86using ConnectionType = ble_device_base::ConnectionType;
88
89// Neutral scanner lifecycle re-exported for backward compatibility.
91
106 public:
109 virtual bool wants_parsed_advertisements() { return true; }
110
111 virtual bool gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
112 esp_ble_gattc_cb_param_t *param) = 0;
113 virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) = 0;
114 virtual void connect() = 0;
115 virtual void disconnect() = 0;
119 bool disconnect_pending() const { return this->want_disconnect_; }
121
124 virtual void set_state(ClientState st) {
125 this->set_state_internal_(st);
126 if (st == ClientState::IDLE) {
127 this->want_disconnect_ = false;
128 }
129 }
130 ClientState state() const { return this->state_; }
131
135 void set_tracker_state_version(uint8_t *version) { this->tracker_state_version_ = version; }
136
137 // Memory optimized layout
138 uint8_t app_id{0}; // App IDs are small integers assigned sequentially
139
140 protected:
144 void set_state_internal_(ClientState st) {
145 this->state_ = st;
146 // Notify tracker that state changed (tracker_state_version_ is owned by ESP32BLETracker)
147 if (this->tracker_state_version_ != nullptr) {
148 (*this->tracker_state_version_)++;
149 }
150 }
151
152 // want_disconnect_ is set to true when a disconnect is requested
153 // while the client is connecting. This is used to disconnect the
154 // client as soon as we get the connection id (conn_id_) from the
155 // ESP_GATTC_OPEN_EVT event.
156 bool want_disconnect_{false};
157
158 private:
159 ClientState state_{ClientState::INIT};
163 uint8_t *tracker_state_version_{nullptr};
164};
165
166class ESP32BLETracker final : public Component,
167#ifdef USE_OTA_STATE_LISTENER
169#endif
170 public Parented<ESP32BLE> {
171 public:
172 void set_scan_duration(uint32_t scan_duration) { scan_duration_ = scan_duration; }
173 void set_scan_interval(uint32_t scan_interval) { scan_interval_ = scan_interval; }
174 void set_scan_window(uint32_t scan_window) { scan_window_ = scan_window; }
175#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT
177#endif
179 bool get_scan_active() const { return scan_active_; }
180 void set_scan_continuous(bool scan_continuous) { scan_continuous_ = scan_continuous; }
181
183 void setup() override;
184 void dump_config() override;
185 float get_setup_priority() const override;
186
187 void loop() override;
188
189 // esp32-flavored path (unmigrated esp32 sensors; sets the tracker back-pointer).
191 void register_client(ESPBTClient *client);
192
193 // ---- ble_device_base::BLEHub (the platform-neutral tracker contract) ----
198#ifdef USE_BLE_SCANNER_STATE_CALLBACK
202#endif
204 // scan_mode_switch is false: the mode is driven through this tracker's own
205 // API (set_scan_active + restart), not the neutral request_scan_mode().
206 return {/* active_scan = */ true, /* merges_scan_response = */ true, /* gatt = */ true,
207 /* scan_mode_switch = */ false};
208 }
209 void get_adapter_mac(uint8_t out[MAC_ADDRESS_SIZE]) { this->parent_->get_mac_msb_first(out); }
210 bool scan_running() { return this->scanner_state_ == ScannerState::RUNNING; }
211 bool scan_active() { return this->scan_active_; }
212 // The mode is driven through this tracker's own API (see get_capabilities);
213 // the neutral request refuses without changing any state.
214 bool request_scan_mode(bool active) { return false; }
215
216#ifdef USE_ESP32_BLE_DEVICE
217 void print_bt_device_info(const ESPBTDevice &device);
218#endif
219
220 void start_scan();
221 // For the start_scan action: in any other state the state machine returns to IDLE on its own
222 // and loop() restarts scanning when scan_continuous_ is set, so only an idle scanner starts here.
224 if (this->scanner_state_ == ScannerState::IDLE)
225 this->start_scan();
226 }
227 void stop_scan();
228
229 void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param);
230 void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param);
231 void gap_scan_event_handler(const BLEScanResult &scan_result);
233
234#ifdef USE_OTA_STATE_LISTENER
235 void on_ota_global_state(ota::OTAState state, float progress, uint8_t error, ota::OTAComponent *comp) override;
236#endif
237
239
240 protected:
242 bool stop_scan_();
244 void notify_scan_end_();
246 void start_scan_(bool first);
248 void gap_scan_result_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param &param);
250 void gap_scan_set_param_complete_(const esp_ble_gap_cb_param_t::ble_scan_param_cmpl_evt_param &param);
252 void gap_scan_start_complete_(const esp_ble_gap_cb_param_t::ble_scan_start_cmpl_evt_param &param);
254 void gap_scan_stop_complete_(const esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param &param);
258 void cleanup_scan_state_(bool is_stop_complete);
260 void process_scan_result_(const BLEScanResult &scan_result);
268 void log_unexpected_state_(const char *operation, ScannerState expected_state) const;
269#ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE
271 void update_coex_preference_(bool force_ble);
272#endif
275 ClientStateCounts counts;
276#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT
277 for (auto *client : this->clients_) {
278 switch (client->state()) {
279 case ClientState::DISCONNECTING:
280 counts.disconnecting++;
281 break;
282 case ClientState::DISCOVERED:
283 counts.discovered++;
284 break;
285 case ClientState::CONNECTING:
286 counts.connecting++;
287 break;
288 case ClientState::CONNECTED:
289 case ClientState::ESTABLISHED:
290 counts.active++;
291 break;
292 default:
293 break;
294 }
295 }
296#endif
297 return counts;
298 }
299
300 // Group 1: Large objects (12+ bytes) - vectors
301#ifdef ESPHOME_ESP32_BLE_TRACKER_LISTENER_COUNT
303#endif
304#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT
306#endif
307 // Parsed listeners registered through the neutral BLEHub contract (migrated
308 // sensors); dispatched alongside listeners_.
309#ifdef ESPHOME_BLE_DEVICE_BASE_LISTENER_COUNT
311#endif
313#ifdef USE_BLE_SCANNER_STATE_CALLBACK
315#endif
316#ifdef USE_ESP32_BLE_DEVICE
319#endif
320
321 // Group 2: Structs (aligned to 4 bytes)
323 esp_ble_scan_params_t scan_params_;
325
326 // Group 3: 4-byte types
331#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT
336 uint32_t desired_scan_window_(uint8_t active) const {
337 return (this->connection_scan_window_ != 0 && active > 0) ? this->connection_scan_window_ : this->scan_window_;
338 }
339#endif
340 esp_bt_status_t scan_start_failed_{ESP_BT_STATUS_SUCCESS};
341 esp_bt_status_t scan_set_param_failed_{ESP_BT_STATUS_SUCCESS};
342
343 // Group 4: 1-byte types (enums, uint8_t, bool)
344 uint8_t app_id_{0};
352 uint8_t state_version_{0};
356 ScannerState scanner_state_{ScannerState::IDLE};
357 // Packed 1-bit flags.
359 bool scan_active_ : 1;
360#ifdef USE_OTA_STATE_LISTENER
362#endif
363 bool ble_was_disabled_ : 1 {true};
364 bool parse_advertisements_ : 1 {false};
365#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT
367 bool skip_next_scan_end_ : 1 {false};
368#endif
369#ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE
370 bool coex_prefer_ble_ : 1 {false};
371#endif
372 // Scan timeout state machine
373 enum class ScanTimeoutState : uint8_t {
374 INACTIVE, // No timeout monitoring
375 MONITORING, // Actively monitoring for timeout
376 EXCEEDED_WAIT, // Timeout exceeded, waiting one loop before reboot
377 };
382};
383
384// NOLINTNEXTLINE
385extern ESP32BLETracker *global_esp32_ble_tracker;
386
387} // namespace esphome::esp32_ble_tracker
388
389#endif
Helper class to easily give an object a parent of type T.
Definition helpers.h:2004
Minimal static vector - saves memory by avoiding std::vector overhead.
Definition helpers.h:291
Per-scan-period "Found device" DEBUG logger, deduplicated by MAC address.
Definition ble_device.h:269
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)
uint8_t state_version_
Version counter for loop() fast-path optimization.
StaticVector< ESPBTClient *, ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT > clients_
void set_raw_advertisement_callback(ble_device_base::RawAdvertisementCallback callback)
void notify_scan_end_()
Fire on_scan_end on every listener unless a window-change restart suppressed it.
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.
ble_device_base::ScannerStateCallback scanner_state_callback_
uint8_t last_processed_version_
Last state_version_ value when loop() did full processing.
void gap_scan_event_handler(const BLEScanResult &scan_result)
bool skip_next_scan_end_
Suppress the window-change restart's on_scan_end sweeps (stop and start).
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_
uint32_t desired_scan_window_(uint8_t active) const
The window to scan at for the given number of active GATT connections.
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.
static constexpr ble_device_base::HubCapabilities get_capabilities()
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.
uint32_t connection_scan_window_
Window used while a GATT connection is active; set by the user, or defaulted when the window was rais...
void set_connection_scan_window(uint32_t scan_window)
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.
ble_device_base::DiscoveredDeviceLog discovered_log_
Per-period "Found device" DEBUG log with MAC dedup (shared ble_device_base impl)
void set_scanner_state_(ScannerState state)
Called to set the scanner state. Will also call callbacks to let listeners know when state is changed...
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 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.
bool stop_scan_()
Returns true when a stop was issued to the controller.
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 get_adapter_mac(uint8_t out[MAC_ADDRESS_SIZE])
void start_scan_(bool first)
Start a single scan by setting up the parameters and doing some esp-idf calls.
void set_scanner_state_callback(ble_device_base::ScannerStateCallback callback)
Base class for BLE GATT clients that connect to remote devices.
virtual void ble_before_disabled_event_handler()
Called right before the BLE stack is dismantled.
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 wants_parsed_advertisements()
False keeps the tracker from building parsed ESPBTDevice objects on this client's account (raw consum...
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.
bool parse_device(const ble_device_base::ESPBTDevice &device) override
Listener interface for global OTA state changes (includes OTA component pointer).
bool state
Definition fan.h:2
std::vector< uint8_t > & out
Definition gif.cpp:49
const char * client_state_to_string(ClientState state)
std::vector< uint8_t > adv_data_t
Definition ble_device.h:38
ScannerState
Scanner lifecycle, wire-value aligned with the api enum so consumers cast directly (pinned by static_...
Definition ble_hub.h:53
ble_device_base::adv_data_t adv_data_t
ESP32BLETracker * global_esp32_ble_tracker
static void uint32_t
What a tracker's controller/SDK can do — consumers branch on data, not #ifdefs.
Definition ble_hub.h:73
Subscriber slot for the raw-advertisement stream (the bluetooth_proxy path).
Definition ble_hub.h:43
Subscriber slot for scanner-state transitions; same shape as RawAdvertisementCallback,...
Definition ble_hub.h:65
bool operator==(const ClientStateCounts &other) const
bool operator!=(const ClientStateCounts &other) const