ESPHome 2025.9.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 <string>
10#include <vector>
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
24
26
27using namespace esp32_ble;
28
29using adv_data_t = std::vector<uint8_t>;
30
35
36#ifdef USE_ESP32_BLE_UUID
41#endif
42
43#ifdef USE_ESP32_BLE_DEVICE
45 public:
46 ESPBLEiBeacon() { memset(&this->beacon_data_, 0, sizeof(this->beacon_data_)); }
47 ESPBLEiBeacon(const uint8_t *data);
49
50 uint16_t get_major() { return byteswap(this->beacon_data_.major); }
51 uint16_t get_minor() { return byteswap(this->beacon_data_.minor); }
52 int8_t get_signal_power() { return this->beacon_data_.signal_power; }
53 ESPBTUUID get_uuid() { return ESPBTUUID::from_raw_reversed(this->beacon_data_.proximity_uuid); }
54
55 protected:
56 struct {
57 uint8_t sub_type;
58 uint8_t length;
59 uint8_t proximity_uuid[16];
60 uint16_t major;
61 uint16_t minor;
63 } PACKED beacon_data_;
64};
65
67 public:
68 void parse_scan_rst(const BLEScanResult &scan_result);
69
70 std::string address_str() const;
71
72 uint64_t address_uint64() const;
73
74 const uint8_t *address() const { return address_; }
75
76 esp_ble_addr_type_t get_address_type() const { return this->address_type_; }
77 int get_rssi() const { return rssi_; }
78 const std::string &get_name() const { return this->name_; }
79
80 const std::vector<int8_t> &get_tx_powers() const { return tx_powers_; }
81
83 const optional<uint8_t> &get_ad_flag() const { return ad_flag_; }
84 const std::vector<ESPBTUUID> &get_service_uuids() const { return service_uuids_; }
85
86 const std::vector<ServiceData> &get_manufacturer_datas() const { return manufacturer_datas_; }
87
88 const std::vector<ServiceData> &get_service_datas() const { return service_datas_; }
89
90 // Exposed through a function for use in lambdas
91 const BLEScanResult &get_scan_result() const { return *scan_result_; }
92
93 bool resolve_irk(const uint8_t *irk) const;
94
96 for (auto &it : this->manufacturer_datas_) {
98 if (res.has_value())
99 return *res;
100 }
101 return {};
102 }
103
104 protected:
105 void parse_adv_(const uint8_t *payload, uint8_t len);
106
107 esp_bd_addr_t address_{
108 0,
109 };
110 esp_ble_addr_type_t address_type_{BLE_ADDR_TYPE_PUBLIC};
111 int rssi_{0};
112 std::string name_{};
113 std::vector<int8_t> tx_powers_{};
116 std::vector<ESPBTUUID> service_uuids_{};
117 std::vector<ServiceData> manufacturer_datas_{};
118 std::vector<ServiceData> service_datas_{};
119 const BLEScanResult *scan_result_{nullptr};
120};
121#endif // USE_ESP32_BLE_DEVICE
122
123class ESP32BLETracker;
124
126 public:
127 virtual void on_scan_end() {}
128#ifdef USE_ESP32_BLE_DEVICE
129 virtual bool parse_device(const ESPBTDevice &device) = 0;
130#endif
131 virtual bool parse_devices(const BLEScanResult *scan_results, size_t count) { return false; };
135 void set_parent(ESP32BLETracker *parent) { parent_ = parent; }
136
137 protected:
139};
140
142 uint8_t connecting = 0;
143 uint8_t discovered = 0;
144 uint8_t searching = 0;
145 uint8_t disconnecting = 0;
146
147 bool operator==(const ClientStateCounts &other) const {
148 return connecting == other.connecting && discovered == other.discovered && searching == other.searching &&
150 }
151
152 bool operator!=(const ClientStateCounts &other) const { return !(*this == other); }
153};
154
155enum class ClientState : uint8_t {
156 // Connection is allocated
157 INIT,
158 // Client is disconnecting
160 // Connection is idle, no device detected.
161 IDLE,
162 // Searching for device.
163 SEARCHING,
164 // Device advertisement found.
166 // Device is discovered and the scanner is stopped
168 // Connection in progress.
170 // Initial connection established.
171 CONNECTED,
172 // The client and sub-clients have completed setup.
174};
175
176enum class ScannerState {
177 // Scanner is idle, init state
178 IDLE,
179 // Scanner is starting
180 STARTING,
181 // Scanner is running
182 RUNNING,
183 // Scanner failed to start
184 FAILED,
185 // Scanner is stopping
186 STOPPING,
187};
188
189// Helper function to convert ClientState to string
191
192enum class ConnectionType : uint8_t {
193 // The default connection type, we hold all the services in ram
194 // for the duration of the connection.
195 V1,
196 // The client has a cache of the services and mtu so we should not
197 // fetch them again
199 // The client does not need the services and mtu once we send them
200 // so we should wipe them from memory as soon as we send them
202};
203
205 public:
206 virtual bool gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
207 esp_ble_gattc_cb_param_t *param) = 0;
208 virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) = 0;
209 virtual void connect() = 0;
210 virtual void disconnect() = 0;
211 bool disconnect_pending() const { return this->want_disconnect_; }
213 virtual void set_state(ClientState st) {
214 this->state_ = st;
215 if (st == ClientState::IDLE) {
216 this->want_disconnect_ = false;
217 }
218 }
219 ClientState state() const { return state_; }
220
221 // Memory optimized layout
222 uint8_t app_id; // App IDs are small integers assigned sequentially
223
224 protected:
225 // Group 1: 1-byte types
227 // want_disconnect_ is set to true when a disconnect is requested
228 // while the client is connecting. This is used to disconnect the
229 // client as soon as we get the connection id (conn_id_) from the
230 // ESP_GATTC_OPEN_EVT event.
231 bool want_disconnect_{false};
232 // 2 bytes used, 2 bytes padding
233};
234
236 public GAPEventHandler,
237 public GAPScanEventHandler,
238 public GATTcEventHandler,
240 public Parented<ESP32BLE> {
241 public:
242 void set_scan_duration(uint32_t scan_duration) { scan_duration_ = scan_duration; }
243 void set_scan_interval(uint32_t scan_interval) { scan_interval_ = scan_interval; }
244 void set_scan_window(uint32_t scan_window) { scan_window_ = scan_window; }
245 void set_scan_active(bool scan_active) { scan_active_ = scan_active; }
246 bool get_scan_active() const { return scan_active_; }
247 void set_scan_continuous(bool scan_continuous) { scan_continuous_ = scan_continuous; }
248
250 void setup() override;
251 void dump_config() override;
252 float get_setup_priority() const override;
253
254 void loop() override;
255
257 void register_client(ESPBTClient *client);
259
260#ifdef USE_ESP32_BLE_DEVICE
261 void print_bt_device_info(const ESPBTDevice &device);
262#endif
263
264 void start_scan();
265 void stop_scan();
266
267 void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
268 esp_ble_gattc_cb_param_t *param) override;
269 void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override;
270 void gap_scan_event_handler(const BLEScanResult &scan_result) override;
271 void ble_before_disabled_event_handler() override;
272
273 void add_scanner_state_callback(std::function<void(ScannerState)> &&callback) {
274 this->scanner_state_callbacks_.add(std::move(callback));
275 }
277
278 protected:
279 void stop_scan_();
281 void start_scan_(bool first);
283 void gap_scan_result_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param &param);
285 void gap_scan_set_param_complete_(const esp_ble_gap_cb_param_t::ble_scan_param_cmpl_evt_param &param);
287 void gap_scan_start_complete_(const esp_ble_gap_cb_param_t::ble_scan_start_cmpl_evt_param &param);
289 void gap_scan_stop_complete_(const esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param &param);
293 void cleanup_scan_state_(bool is_stop_complete);
296 bool process_scan_result_(const BLEScanResult &scan_result);
297#ifdef USE_ESP32_BLE_DEVICE
299 bool has_connecting_clients_() const;
300#endif
308 void log_unexpected_state_(const char *operation, ScannerState expected_state) const;
309#ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE
311 void update_coex_preference_(bool force_ble);
312#endif
315 ClientStateCounts counts;
316 for (auto *client : this->clients_) {
317 switch (client->state()) {
319 counts.disconnecting++;
320 break;
322 counts.discovered++;
323 break;
325 counts.searching++;
326 break;
329 counts.connecting++;
330 break;
331 default:
332 break;
333 }
334 }
335 return counts;
336 }
337
338 // Group 1: Large objects (12+ bytes) - vectors and callback manager
339 std::vector<ESPBTDeviceListener *> listeners_;
340 std::vector<ESPBTClient *> clients_;
342#ifdef USE_ESP32_BLE_DEVICE
344 std::vector<uint64_t> already_discovered_;
345#endif
346
347 // Group 2: Structs (aligned to 4 bytes)
349 esp_ble_scan_params_t scan_params_;
351
352 // Group 3: 4-byte types
356 uint32_t scan_window_;
357 esp_bt_status_t scan_start_failed_{ESP_BT_STATUS_SUCCESS};
358 esp_bt_status_t scan_set_param_failed_{ESP_BT_STATUS_SUCCESS};
359
360 // Group 4: 1-byte types (enums, uint8_t, bool)
361 uint8_t app_id_{0};
369#ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE
370 bool coex_prefer_ble_{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 };
378 uint32_t scan_start_time_{0};
380};
381
382// NOLINTNEXTLINE
383extern ESP32BLETracker *global_esp32_ble_tracker;
384
385} // namespace esphome::esp32_ble_tracker
386
387#endif
Helper class to easily give an object a parent of type T.
Definition helpers.h:656
static ESPBTUUID from_raw_reversed(const uint8_t *data)
Definition ble_uuid.cpp:34
void try_promote_discovered_clients_()
Try to promote discovered clients to ready to connect.
std::vector< uint64_t > already_discovered_
Vector of addresses that have already been printed in print_bt_device_info.
bool process_scan_result_(const BLEScanResult &scan_result)
Process a single scan result immediately Returns true if a discovered client needs promotion to READY...
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.
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) override
ClientStateCounts count_client_states_() const
Count clients in each state.
esp_ble_scan_params_t scan_params_
A structure holding the ESP BLE scan parameters.
void register_listener(ESPBTDeviceListener *listener)
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.
CallbackManager< void(ScannerState)> scanner_state_callbacks_
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.
bool has_connecting_clients_() const
Check if any clients are in connecting or ready to connect state.
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 gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override
void print_bt_device_info(const ESPBTDevice &device)
void set_scan_duration(uint32_t scan_duration)
void gap_scan_event_handler(const BLEScanResult &scan_result) override
void set_scan_interval(uint32_t scan_interval)
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 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 add_scanner_state_callback(std::function< void(ScannerState)> &&callback)
std::vector< ESPBTDeviceListener * > listeners_
void start_scan_(bool first)
Start a single scan by setting up the parameters and doing some esp-idf calls.
struct esphome::esp32_ble_tracker::ESPBLEiBeacon::@80 beacon_data_
static optional< ESPBLEiBeacon > from_manufacturer_data(const ServiceData &data)
virtual void set_state(ClientState st)
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
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 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)
bool state
Definition fan.h:0
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:279
bool operator==(const ClientStateCounts &other) const
bool operator!=(const ClientStateCounts &other) const
void byteswap()