ESPHome 2026.5.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_idf_version.h>
16#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0)
17// mbedtls 4.0 (IDF 6.0) removed the legacy mbedtls AES API.
18// Use the PSA Crypto API instead.
19#define USE_BLE_TRACKER_PSA_AES
20#endif
21
22#include <esp_bt_defs.h>
23#include <esp_gap_ble_api.h>
24#include <esp_gattc_api.h>
25
26#include <freertos/FreeRTOS.h>
27#include <freertos/semphr.h>
28
32
33#ifdef USE_OTA_STATE_LISTENER
35#endif
36
38
39using namespace esp32_ble;
40
41using adv_data_t = std::vector<uint8_t>;
42
47
48#ifdef USE_ESP32_BLE_UUID
53#endif
54
55#ifdef USE_ESP32_BLE_DEVICE
57 public:
58 ESPBLEiBeacon() { memset(&this->beacon_data_, 0, sizeof(this->beacon_data_)); }
59 ESPBLEiBeacon(const uint8_t *data);
60 static optional<ESPBLEiBeacon> from_manufacturer_data(const ServiceData &data);
61
62 uint16_t get_major() { return byteswap(this->beacon_data_.major); }
63 uint16_t get_minor() { return byteswap(this->beacon_data_.minor); }
64 int8_t get_signal_power() { return this->beacon_data_.signal_power; }
65 ESPBTUUID get_uuid() { return ESPBTUUID::from_raw_reversed(this->beacon_data_.proximity_uuid); }
66
67 protected:
68 struct {
69 uint8_t sub_type;
70 uint8_t length;
71 uint8_t proximity_uuid[16];
72 uint16_t major;
73 uint16_t minor;
75 } PACKED beacon_data_;
76};
77
79 public:
80 void parse_scan_rst(const BLEScanResult &scan_result);
81
82 std::string address_str() const;
83
85 const char *address_str_to(std::span<char, MAC_ADDRESS_PRETTY_BUFFER_SIZE> buf) const {
86 format_mac_addr_upper(this->address_, buf.data());
87 return buf.data();
88 }
89
90 uint64_t address_uint64() const;
91
92 const uint8_t *address() const { return address_; }
93
94 esp_ble_addr_type_t get_address_type() const { return this->address_type_; }
95 int get_rssi() const { return rssi_; }
96 const std::string &get_name() const { return this->name_; }
97
98 const std::vector<int8_t> &get_tx_powers() const { return tx_powers_; }
99
100 const optional<uint16_t> &get_appearance() const { return appearance_; }
101 const optional<uint8_t> &get_ad_flag() const { return ad_flag_; }
102 const std::vector<ESPBTUUID> &get_service_uuids() const { return service_uuids_; }
103
104 const std::vector<ServiceData> &get_manufacturer_datas() const { return manufacturer_datas_; }
105
106 const std::vector<ServiceData> &get_service_datas() const { return service_datas_; }
107
108 // Exposed through a function for use in lambdas
109 const BLEScanResult &get_scan_result() const { return *scan_result_; }
110
111 bool resolve_irk(const uint8_t *irk) const;
112
113 optional<ESPBLEiBeacon> get_ibeacon() const {
114 for (auto &it : this->manufacturer_datas_) {
116 if (res.has_value())
117 return res;
118 }
119 return {};
120 }
121
122 protected:
123 void parse_adv_(const uint8_t *payload, uint8_t len);
124
125 esp_bd_addr_t address_{
126 0,
127 };
128 esp_ble_addr_type_t address_type_{BLE_ADDR_TYPE_PUBLIC};
129 int rssi_{0};
130 std::string name_{};
131 std::vector<int8_t> tx_powers_{};
132 optional<uint16_t> appearance_{};
133 optional<uint8_t> ad_flag_{};
134 std::vector<ESPBTUUID> service_uuids_{};
135 std::vector<ServiceData> manufacturer_datas_{};
136 std::vector<ServiceData> service_datas_{};
137 const BLEScanResult *scan_result_{nullptr};
138};
139#endif // USE_ESP32_BLE_DEVICE
140
141class ESP32BLETracker;
142
144 public:
145 virtual void on_scan_end() {}
146#ifdef USE_ESP32_BLE_DEVICE
147 virtual bool parse_device(const ESPBTDevice &device) = 0;
148#endif
149 virtual bool parse_devices(const BLEScanResult *scan_results, size_t count) { return false; };
153 void set_parent(ESP32BLETracker *parent) { parent_ = parent; }
154
155 protected:
157};
158
160 uint8_t connecting = 0;
161 uint8_t discovered = 0;
162 uint8_t disconnecting = 0;
163
164 bool operator==(const ClientStateCounts &other) const {
165 return connecting == other.connecting && discovered == other.discovered && disconnecting == other.disconnecting;
166 }
167
168 bool operator!=(const ClientStateCounts &other) const { return !(*this == other); }
169};
170
171enum class ClientState : uint8_t {
172 // Connection is allocated
173 INIT,
174 // Client is disconnecting
176 // Connection is idle, no device detected.
177 IDLE,
178 // Device advertisement found.
180 // Connection in progress.
182 // Initial connection established.
183 CONNECTED,
184 // The client and sub-clients have completed setup.
186};
187
188enum class ScannerState {
189 // Scanner is idle, init state
190 IDLE,
191 // Scanner is starting
192 STARTING,
193 // Scanner is running
194 RUNNING,
195 // Scanner failed to start
196 FAILED,
197 // Scanner is stopping
198 STOPPING,
199};
200
207 public:
209};
210
211// Helper function to convert ClientState to string
213
214enum class ConnectionType : uint8_t {
215 // The default connection type, we hold all the services in ram
216 // for the duration of the connection.
217 V1,
218 // The client has a cache of the services and mtu so we should not
219 // fetch them again
221 // The client does not need the services and mtu once we send them
222 // so we should wipe them from memory as soon as we send them
224};
225
240 public:
241 virtual bool gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
242 esp_ble_gattc_cb_param_t *param) = 0;
243 virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) = 0;
244 virtual void connect() = 0;
245 virtual void disconnect() = 0;
246 bool disconnect_pending() const { return this->want_disconnect_; }
248
251 virtual void set_state(ClientState st) {
252 this->set_state_internal_(st);
253 if (st == ClientState::IDLE) {
254 this->want_disconnect_ = false;
255 }
256 }
257 ClientState state() const { return this->state_; }
258
262 void set_tracker_state_version(uint8_t *version) { this->tracker_state_version_ = version; }
263
264 // Memory optimized layout
265 uint8_t app_id; // App IDs are small integers assigned sequentially
266
267 protected:
272 this->state_ = st;
273 // Notify tracker that state changed (tracker_state_version_ is owned by ESP32BLETracker)
274 if (this->tracker_state_version_ != nullptr) {
275 (*this->tracker_state_version_)++;
276 }
277 }
278
279 // want_disconnect_ is set to true when a disconnect is requested
280 // while the client is connecting. This is used to disconnect the
281 // client as soon as we get the connection id (conn_id_) from the
282 // ESP_GATTC_OPEN_EVT event.
283 bool want_disconnect_{false};
284
285 private:
290 uint8_t *tracker_state_version_{nullptr};
291};
292
294#ifdef USE_OTA_STATE_LISTENER
296#endif
297 public Parented<ESP32BLE> {
298 public:
299 void set_scan_duration(uint32_t scan_duration) { scan_duration_ = scan_duration; }
300 void set_scan_interval(uint32_t scan_interval) { scan_interval_ = scan_interval; }
301 void set_scan_window(uint32_t scan_window) { scan_window_ = scan_window; }
302 void set_scan_active(bool scan_active) { scan_active_ = scan_active; }
303 bool get_scan_active() const { return scan_active_; }
304 void set_scan_continuous(bool scan_continuous) { scan_continuous_ = scan_continuous; }
305
307 void setup() override;
308 void dump_config() override;
309 float get_setup_priority() const override;
310
311 void loop() override;
312
314 void register_client(ESPBTClient *client);
316
317#ifdef USE_ESP32_BLE_DEVICE
318 void print_bt_device_info(const ESPBTDevice &device);
319#endif
320
321 void start_scan();
322 void stop_scan();
323
324 void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param);
325 void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param);
326 void gap_scan_event_handler(const BLEScanResult &scan_result);
328
329#ifdef USE_OTA_STATE_LISTENER
330 void on_ota_global_state(ota::OTAState state, float progress, uint8_t error, ota::OTAComponent *comp) override;
331#endif
332
335 this->scanner_state_listeners_.push_back(listener);
336 }
338
339 protected:
340 void stop_scan_();
342 void start_scan_(bool first);
344 void gap_scan_result_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param &param);
346 void gap_scan_set_param_complete_(const esp_ble_gap_cb_param_t::ble_scan_param_cmpl_evt_param &param);
348 void gap_scan_start_complete_(const esp_ble_gap_cb_param_t::ble_scan_start_cmpl_evt_param &param);
350 void gap_scan_stop_complete_(const esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param &param);
354 void cleanup_scan_state_(bool is_stop_complete);
356 void process_scan_result_(const BLEScanResult &scan_result);
364 void log_unexpected_state_(const char *operation, ScannerState expected_state) const;
365#ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE
367 void update_coex_preference_(bool force_ble);
368#endif
371 ClientStateCounts counts;
372#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT
373 for (auto *client : this->clients_) {
374 switch (client->state()) {
376 counts.disconnecting++;
377 break;
379 counts.discovered++;
380 break;
382 counts.connecting++;
383 break;
384 default:
385 break;
386 }
387 }
388#endif
389 return counts;
390 }
391
392 // Group 1: Large objects (12+ bytes) - vectors
393#ifdef ESPHOME_ESP32_BLE_TRACKER_LISTENER_COUNT
395#endif
396#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT
398#endif
399 std::vector<BLEScannerStateListener *> scanner_state_listeners_;
400#ifdef USE_ESP32_BLE_DEVICE
402 std::vector<uint64_t> already_discovered_;
403#endif
404
405 // Group 2: Structs (aligned to 4 bytes)
407 esp_ble_scan_params_t scan_params_;
409
410 // Group 3: 4-byte types
415 esp_bt_status_t scan_start_failed_{ESP_BT_STATUS_SUCCESS};
416 esp_bt_status_t scan_set_param_failed_{ESP_BT_STATUS_SUCCESS};
417
418 // Group 4: 1-byte types (enums, uint8_t, bool)
419 uint8_t app_id_{0};
427 uint8_t state_version_{0};
434#ifdef USE_OTA_STATE_LISTENER
436#endif
440#ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE
441 bool coex_prefer_ble_{false};
442#endif
443 // Scan timeout state machine
444 enum class ScanTimeoutState : uint8_t {
445 INACTIVE, // No timeout monitoring
446 MONITORING, // Actively monitoring for timeout
447 EXCEEDED_WAIT, // Timeout exceeded, waiting one loop before reboot
448 };
453};
454
455// NOLINTNEXTLINE
456extern ESP32BLETracker *global_esp32_ble_tracker;
457
458} // namespace esphome::esp32_ble_tracker
459
460#endif
Helper class to easily give an object a parent of type T.
Definition helpers.h:2013
Minimal static vector - saves memory by avoiding std::vector overhead.
Definition helpers.h:210
static ESPBTUUID from_raw_reversed(const uint8_t *data)
Definition ble_uuid.cpp:35
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.
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)
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...
void print_bt_device_info(const ESPBTDevice &device)
void set_scan_duration(uint32_t scan_duration)
void set_scan_interval(uint32_t scan_interval)
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.
static optional< ESPBLEiBeacon > from_manufacturer_data(const ServiceData &data)
struct esphome::esp32_ble_tracker::ESPBLEiBeacon::@83 beacon_data_
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.
const BLEScanResult & get_scan_result() const
esp_ble_addr_type_t get_address_type() const
void parse_adv_(const uint8_t *payload, uint8_t len)
void parse_scan_rst(const BLEScanResult &scan_result)
std::vector< ServiceData > manufacturer_datas_
const char * address_str_to(std::span< char, MAC_ADDRESS_PRETTY_BUFFER_SIZE > buf) const
Format MAC address into provided buffer, returns pointer to buffer for convenience.
const optional< uint8_t > & get_ad_flag() const
const std::vector< int8_t > & get_tx_powers() const
const std::vector< ServiceData > & get_service_datas() const
const optional< uint16_t > & get_appearance() const
const std::vector< ServiceData > & get_manufacturer_datas() const
bool resolve_irk(const uint8_t *irk) const
const std::vector< ESPBTUUID > & get_service_uuids() const
std::vector< ServiceData > service_datas_
optional< ESPBLEiBeacon > get_ibeacon() const
virtual AdvertisementParserType get_advertisement_parser_type()
virtual bool parse_device(const ESPBTDevice &device)=0
virtual bool parse_devices(const BLEScanResult *scan_results, size_t count)
Listener interface for global OTA state changes (includes OTA component pointer).
Definition ota_backend.h:88
bool state
Definition fan.h:2
ESP32BLETracker * global_esp32_ble_tracker
std::vector< uint8_t > adv_data_t
const char * client_state_to_string(ClientState state)
std::string size_t len
Definition helpers.h:1045
char * format_mac_addr_upper(const uint8_t *mac, char *output)
Format MAC address as XX:XX:XX:XX:XX:XX (uppercase, colon separators)
Definition helpers.h:1435
static void uint32_t
bool operator==(const ClientStateCounts &other) const
bool operator!=(const ClientStateCounts &other) const
void byteswap()