ESPHome 2025.6.0
Loading...
Searching...
No Matches
esp32_ble_tracker.cpp
Go to the documentation of this file.
1#ifdef USE_ESP32
2
3#include "esp32_ble_tracker.h"
6#include "esphome/core/hal.h"
8#include "esphome/core/log.h"
9
10#include <esp_bt.h>
11#include <esp_bt_defs.h>
12#include <esp_bt_main.h>
13#include <esp_gap_ble_api.h>
14#include <freertos/FreeRTOS.h>
15#include <freertos/FreeRTOSConfig.h>
16#include <freertos/task.h>
17#include <nvs_flash.h>
18#include <cinttypes>
19
20#ifdef USE_OTA
22#endif
23
24#ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE
25#include <esp_coexist.h>
26#endif
27
28#ifdef USE_ARDUINO
29#include <esp32-hal-bt.h>
30#endif
31
32#define MBEDTLS_AES_ALT
33#include <aes_alt.h>
34
35// bt_trace.h
36#undef TAG
37
38namespace esphome {
39namespace esp32_ble_tracker {
40
41static const char *const TAG = "esp32_ble_tracker";
42
43ESP32BLETracker *global_esp32_ble_tracker = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
44
46
48 if (this->parent_->is_failed()) {
49 this->mark_failed();
50 ESP_LOGE(TAG, "BLE Tracker was marked failed by ESP32BLE");
51 return;
52 }
54 this->scan_ring_buffer_ = allocator.allocate(SCAN_RESULT_BUFFER_SIZE);
55
56 if (this->scan_ring_buffer_ == nullptr) {
57 ESP_LOGE(TAG, "Could not allocate ring buffer for BLE Tracker!");
58 this->mark_failed();
59 }
60
61 global_esp32_ble_tracker = this;
62
63#ifdef USE_OTA
65 [this](ota::OTAState state, float progress, uint8_t error, ota::OTAComponent *comp) {
66 if (state == ota::OTA_STARTED) {
67 this->stop_scan();
68 for (auto *client : this->clients_) {
69 client->disconnect();
70 }
71 }
72 });
73#endif
74}
75
77 if (!this->parent_->is_active()) {
78 this->ble_was_disabled_ = true;
79 return;
80 } else if (this->ble_was_disabled_) {
81 this->ble_was_disabled_ = false;
82 // If the BLE stack was disabled, we need to start the scan again.
83 if (this->scan_continuous_) {
84 this->start_scan();
85 }
86 }
87 int connecting = 0;
88 int discovered = 0;
89 int searching = 0;
90 int disconnecting = 0;
91 for (auto *client : this->clients_) {
92 switch (client->state()) {
94 disconnecting++;
95 break;
97 discovered++;
98 break;
100 searching++;
101 break;
104 connecting++;
105 break;
106 default:
107 break;
108 }
109 }
110 if (connecting != connecting_ || discovered != discovered_ || searching != searching_ ||
111 disconnecting != disconnecting_) {
112 connecting_ = connecting;
113 discovered_ = discovered;
114 searching_ = searching;
115 disconnecting_ = disconnecting;
116 ESP_LOGD(TAG, "connecting: %d, discovered: %d, searching: %d, disconnecting: %d", connecting_, discovered_,
118 }
119 bool promote_to_connecting = discovered && !searching && !connecting;
120
121 // Process scan results from lock-free SPSC ring buffer
122 // Consumer side: This runs in the main loop thread
124 // Load our own index with relaxed ordering (we're the only writer)
125 size_t read_idx = this->ring_read_index_.load(std::memory_order_relaxed);
126
127 // Load producer's index with acquire to see their latest writes
128 size_t write_idx = this->ring_write_index_.load(std::memory_order_acquire);
129
130 while (read_idx != write_idx) {
131 // Process one result at a time directly from ring buffer
132 BLEScanResult &scan_result = this->scan_ring_buffer_[read_idx];
133
134 if (this->raw_advertisements_) {
135 for (auto *listener : this->listeners_) {
136 listener->parse_devices(&scan_result, 1);
137 }
138 for (auto *client : this->clients_) {
139 client->parse_devices(&scan_result, 1);
140 }
141 }
142
143 if (this->parse_advertisements_) {
144 ESPBTDevice device;
145 device.parse_scan_rst(scan_result);
146
147 bool found = false;
148 for (auto *listener : this->listeners_) {
149 if (listener->parse_device(device))
150 found = true;
151 }
152
153 for (auto *client : this->clients_) {
154 if (client->parse_device(device)) {
155 found = true;
156 if (!connecting && client->state() == ClientState::DISCOVERED) {
157 promote_to_connecting = true;
158 }
159 }
160 }
161
162 if (!found && !this->scan_continuous_) {
163 this->print_bt_device_info(device);
164 }
165 }
166
167 // Move to next entry in ring buffer
168 read_idx = (read_idx + 1) % SCAN_RESULT_BUFFER_SIZE;
169
170 // Store with release to ensure reads complete before index update
171 this->ring_read_index_.store(read_idx, std::memory_order_release);
172 }
173
174 // Log dropped results periodically
175 size_t dropped = this->scan_results_dropped_.exchange(0, std::memory_order_relaxed);
176 if (dropped > 0) {
177 ESP_LOGW(TAG, "Dropped %zu BLE scan results due to buffer overflow", dropped);
178 }
179 }
181 this->end_of_scan_(); // Change state to IDLE
182 }
185 this->stop_scan_();
186 if (this->scan_start_fail_count_ == std::numeric_limits<uint8_t>::max()) {
187 ESP_LOGE(TAG, "Scan could not restart after %d attempts, rebooting to restore stack (IDF)",
188 std::numeric_limits<uint8_t>::max());
189 App.reboot();
190 }
191 if (this->scan_start_failed_) {
192 ESP_LOGE(TAG, "Scan start failed: %d", this->scan_start_failed_);
193 this->scan_start_failed_ = ESP_BT_STATUS_SUCCESS;
194 }
195 if (this->scan_set_param_failed_) {
196 ESP_LOGE(TAG, "Scan set param failed: %d", this->scan_set_param_failed_);
197 this->scan_set_param_failed_ = ESP_BT_STATUS_SUCCESS;
198 }
199 }
200 /*
201
202 Avoid starting the scanner if:
203 - we are already scanning
204 - we are connecting to a device
205 - we are disconnecting from a device
206
207 Otherwise the scanner could fail to ever start again
208 and our only way to recover is to reboot.
209
210 https://github.com/espressif/esp-idf/issues/6688
211
212 */
213 if (this->scanner_state_ == ScannerState::IDLE && !connecting && !disconnecting && !promote_to_connecting) {
214#ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE
215 if (this->coex_prefer_ble_) {
216 this->coex_prefer_ble_ = false;
217 ESP_LOGD(TAG, "Setting coexistence preference to balanced.");
218 esp_coex_preference_set(ESP_COEX_PREFER_BALANCE); // Reset to default
219 }
220#endif
221 if (this->scan_continuous_) {
222 this->start_scan_(false); // first = false
223 }
224 }
225 // If there is a discovered client and no connecting
226 // clients and no clients using the scanner to search for
227 // devices, then stop scanning and promote the discovered
228 // client to ready to connect.
229 if (promote_to_connecting &&
231 for (auto *client : this->clients_) {
232 if (client->state() == ClientState::DISCOVERED) {
234 ESP_LOGD(TAG, "Stopping scan to make connection");
235 this->stop_scan_();
236 } else if (this->scanner_state_ == ScannerState::IDLE) {
237 ESP_LOGD(TAG, "Promoting client to connect");
238 // We only want to promote one client at a time.
239 // once the scanner is fully stopped.
240#ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE
241 ESP_LOGD(TAG, "Setting coexistence to Bluetooth to make connection.");
242 if (!this->coex_prefer_ble_) {
243 this->coex_prefer_ble_ = true;
244 esp_coex_preference_set(ESP_COEX_PREFER_BT); // Prioritize Bluetooth
245 }
246#endif
247 client->set_state(ClientState::READY_TO_CONNECT);
248 }
249 break;
250 }
251 }
252 }
253}
254
256
258 ESP_LOGD(TAG, "Stopping scan.");
259 this->scan_continuous_ = false;
260 this->stop_scan_();
261}
262
264
267 if (this->scanner_state_ == ScannerState::IDLE) {
268 ESP_LOGE(TAG, "Scan is already stopped while trying to stop.");
269 } else if (this->scanner_state_ == ScannerState::STARTING) {
270 ESP_LOGE(TAG, "Scan is starting while trying to stop.");
271 } else if (this->scanner_state_ == ScannerState::STOPPING) {
272 ESP_LOGE(TAG, "Scan is already stopping while trying to stop.");
273 } else if (this->scanner_state_ == ScannerState::STOPPED) {
274 ESP_LOGE(TAG, "Scan is already stopped while trying to stop.");
275 }
276 return;
277 }
278 this->cancel_timeout("scan");
280 esp_err_t err = esp_ble_gap_stop_scanning();
281 if (err != ESP_OK) {
282 ESP_LOGE(TAG, "esp_ble_gap_stop_scanning failed: %d", err);
283 return;
284 }
285}
286
288 if (!this->parent_->is_active()) {
289 ESP_LOGW(TAG, "Cannot start scan while ESP32BLE is disabled.");
290 return;
291 }
292 if (this->scanner_state_ != ScannerState::IDLE) {
294 ESP_LOGE(TAG, "Cannot start scan while already starting.");
295 } else if (this->scanner_state_ == ScannerState::RUNNING) {
296 ESP_LOGE(TAG, "Cannot start scan while already running.");
297 } else if (this->scanner_state_ == ScannerState::STOPPING) {
298 ESP_LOGE(TAG, "Cannot start scan while already stopping.");
299 } else if (this->scanner_state_ == ScannerState::FAILED) {
300 ESP_LOGE(TAG, "Cannot start scan while already failed.");
301 } else if (this->scanner_state_ == ScannerState::STOPPED) {
302 ESP_LOGE(TAG, "Cannot start scan while already stopped.");
303 }
304 return;
305 }
307 ESP_LOGD(TAG, "Starting scan, set scanner state to STARTING.");
308 if (!first) {
309 for (auto *listener : this->listeners_)
310 listener->on_scan_end();
311 }
312 this->already_discovered_.clear();
313 this->scan_params_.scan_type = this->scan_active_ ? BLE_SCAN_TYPE_ACTIVE : BLE_SCAN_TYPE_PASSIVE;
314 this->scan_params_.own_addr_type = BLE_ADDR_TYPE_PUBLIC;
315 this->scan_params_.scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL;
316 this->scan_params_.scan_interval = this->scan_interval_;
317 this->scan_params_.scan_window = this->scan_window_;
318
319 // Start timeout before scan is started. Otherwise scan never starts if any error.
320 this->set_timeout("scan", this->scan_duration_ * 2000, []() {
321 ESP_LOGE(TAG, "Scan never terminated, rebooting to restore stack (IDF)");
322 App.reboot();
323 });
324
325 esp_err_t err = esp_ble_gap_set_scan_params(&this->scan_params_);
326 if (err != ESP_OK) {
327 ESP_LOGE(TAG, "esp_ble_gap_set_scan_params failed: %d", err);
328 return;
329 }
330 err = esp_ble_gap_start_scanning(this->scan_duration_);
331 if (err != ESP_OK) {
332 ESP_LOGE(TAG, "esp_ble_gap_start_scanning failed: %d", err);
333 return;
334 }
335}
336
338 // The lock must be held when calling this function.
340 ESP_LOGE(TAG, "end_of_scan_ called while scanner is not stopped.");
341 return;
342 }
343 ESP_LOGD(TAG, "End of scan, set scanner state to IDLE.");
344 this->already_discovered_.clear();
345 this->cancel_timeout("scan");
346
347 for (auto *listener : this->listeners_)
348 listener->on_scan_end();
350}
351
353 client->app_id = ++this->app_id_;
354 this->clients_.push_back(client);
356}
357
359 listener->set_parent(this);
360 this->listeners_.push_back(listener);
362}
363
365 this->raw_advertisements_ = false;
366 this->parse_advertisements_ = false;
367 for (auto *listener : this->listeners_) {
368 if (listener->get_advertisement_parser_type() == AdvertisementParserType::PARSED_ADVERTISEMENTS) {
369 this->parse_advertisements_ = true;
370 } else {
371 this->raw_advertisements_ = true;
372 }
373 }
374 for (auto *client : this->clients_) {
375 if (client->get_advertisement_parser_type() == AdvertisementParserType::PARSED_ADVERTISEMENTS) {
376 this->parse_advertisements_ = true;
377 } else {
378 this->raw_advertisements_ = true;
379 }
380 }
381}
382
383void ESP32BLETracker::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
384 switch (event) {
385 case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT:
386 this->gap_scan_set_param_complete_(param->scan_param_cmpl);
387 break;
388 case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT:
389 this->gap_scan_start_complete_(param->scan_start_cmpl);
390 break;
391 case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT:
392 this->gap_scan_stop_complete_(param->scan_stop_cmpl);
393 break;
394 default:
395 break;
396 }
397 // Forward all events to clients (scan results are handled separately via gap_scan_event_handler)
398 for (auto *client : this->clients_) {
399 client->gap_event_handler(event, param);
400 }
401}
402
403void ESP32BLETracker::gap_scan_event_handler(const BLEScanResult &scan_result) {
404 ESP_LOGV(TAG, "gap_scan_result - event %d", scan_result.search_evt);
405
406 if (scan_result.search_evt == ESP_GAP_SEARCH_INQ_RES_EVT) {
407 // Lock-free SPSC ring buffer write (Producer side)
408 // This runs in the ESP-IDF Bluetooth stack callback thread
409 // IMPORTANT: Only this thread writes to ring_write_index_
410
411 // Load our own index with relaxed ordering (we're the only writer)
412 size_t write_idx = this->ring_write_index_.load(std::memory_order_relaxed);
413 size_t next_write_idx = (write_idx + 1) % SCAN_RESULT_BUFFER_SIZE;
414
415 // Load consumer's index with acquire to see their latest updates
416 size_t read_idx = this->ring_read_index_.load(std::memory_order_acquire);
417
418 // Check if buffer is full
419 if (next_write_idx != read_idx) {
420 // Write to ring buffer
421 this->scan_ring_buffer_[write_idx] = scan_result;
422
423 // Store with release to ensure the write is visible before index update
424 this->ring_write_index_.store(next_write_idx, std::memory_order_release);
425 } else {
426 // Buffer full, track dropped results
427 this->scan_results_dropped_.fetch_add(1, std::memory_order_relaxed);
428 }
429 } else if (scan_result.search_evt == ESP_GAP_SEARCH_INQ_CMPL_EVT) {
430 // Scan finished on its own
433 ESP_LOGE(TAG, "Scan was not running when scan completed.");
434 } else if (this->scanner_state_ == ScannerState::STARTING) {
435 ESP_LOGE(TAG, "Scan was not started when scan completed.");
436 } else if (this->scanner_state_ == ScannerState::FAILED) {
437 ESP_LOGE(TAG, "Scan was in failed state when scan completed.");
438 } else if (this->scanner_state_ == ScannerState::IDLE) {
439 ESP_LOGE(TAG, "Scan was idle when scan completed.");
440 } else if (this->scanner_state_ == ScannerState::STOPPED) {
441 ESP_LOGE(TAG, "Scan was stopped when scan completed.");
442 }
443 }
445 }
446}
447
448void ESP32BLETracker::gap_scan_set_param_complete_(const esp_ble_gap_cb_param_t::ble_scan_param_cmpl_evt_param &param) {
449 ESP_LOGV(TAG, "gap_scan_set_param_complete - status %d", param.status);
450 if (param.status == ESP_BT_STATUS_DONE) {
451 this->scan_set_param_failed_ = ESP_BT_STATUS_SUCCESS;
452 } else {
453 this->scan_set_param_failed_ = param.status;
454 }
455}
456
457void ESP32BLETracker::gap_scan_start_complete_(const esp_ble_gap_cb_param_t::ble_scan_start_cmpl_evt_param &param) {
458 ESP_LOGV(TAG, "gap_scan_start_complete - status %d", param.status);
459 this->scan_start_failed_ = param.status;
462 ESP_LOGE(TAG, "Scan was already running when start complete.");
463 } else if (this->scanner_state_ == ScannerState::STOPPING) {
464 ESP_LOGE(TAG, "Scan was stopping when start complete.");
465 } else if (this->scanner_state_ == ScannerState::FAILED) {
466 ESP_LOGE(TAG, "Scan was in failed state when start complete.");
467 } else if (this->scanner_state_ == ScannerState::IDLE) {
468 ESP_LOGE(TAG, "Scan was idle when start complete.");
469 } else if (this->scanner_state_ == ScannerState::STOPPED) {
470 ESP_LOGE(TAG, "Scan was stopped when start complete.");
471 }
472 }
473 if (param.status == ESP_BT_STATUS_SUCCESS) {
474 this->scan_start_fail_count_ = 0;
476 } else {
478 if (this->scan_start_fail_count_ != std::numeric_limits<uint8_t>::max()) {
480 }
481 }
482}
483
484void ESP32BLETracker::gap_scan_stop_complete_(const esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param &param) {
485 ESP_LOGV(TAG, "gap_scan_stop_complete - status %d", param.status);
488 ESP_LOGE(TAG, "Scan was not running when stop complete.");
489 } else if (this->scanner_state_ == ScannerState::STARTING) {
490 ESP_LOGE(TAG, "Scan was not started when stop complete.");
491 } else if (this->scanner_state_ == ScannerState::FAILED) {
492 ESP_LOGE(TAG, "Scan was in failed state when stop complete.");
493 } else if (this->scanner_state_ == ScannerState::IDLE) {
494 ESP_LOGE(TAG, "Scan was idle when stop complete.");
495 } else if (this->scanner_state_ == ScannerState::STOPPED) {
496 ESP_LOGE(TAG, "Scan was stopped when stop complete.");
497 }
498 }
500}
501
502void ESP32BLETracker::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
503 esp_ble_gattc_cb_param_t *param) {
504 for (auto *client : this->clients_) {
505 client->gattc_event_handler(event, gattc_if, param);
506 }
507}
508
513
514ESPBLEiBeacon::ESPBLEiBeacon(const uint8_t *data) { memcpy(&this->beacon_data_, data, sizeof(beacon_data_)); }
516 if (!data.uuid.contains(0x4C, 0x00))
517 return {};
518
519 if (data.data.size() != 23)
520 return {};
521 return ESPBLEiBeacon(data.data.data());
522}
523
524void ESPBTDevice::parse_scan_rst(const BLEScanResult &scan_result) {
525 for (uint8_t i = 0; i < ESP_BD_ADDR_LEN; i++)
526 this->address_[i] = scan_result.bda[i];
527 this->address_type_ = static_cast<esp_ble_addr_type_t>(scan_result.ble_addr_type);
528 this->rssi_ = scan_result.rssi;
529
530 // Parse advertisement data directly
531 uint8_t total_len = scan_result.adv_data_len + scan_result.scan_rsp_len;
532 this->parse_adv_(scan_result.ble_adv, total_len);
533
534#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
535 ESP_LOGVV(TAG, "Parse Result:");
536 const char *address_type;
537 switch (this->address_type_) {
538 case BLE_ADDR_TYPE_PUBLIC:
539 address_type = "PUBLIC";
540 break;
541 case BLE_ADDR_TYPE_RANDOM:
542 address_type = "RANDOM";
543 break;
544 case BLE_ADDR_TYPE_RPA_PUBLIC:
545 address_type = "RPA_PUBLIC";
546 break;
547 case BLE_ADDR_TYPE_RPA_RANDOM:
548 address_type = "RPA_RANDOM";
549 break;
550 default:
551 address_type = "UNKNOWN";
552 break;
553 }
554 ESP_LOGVV(TAG, " Address: %02X:%02X:%02X:%02X:%02X:%02X (%s)", this->address_[0], this->address_[1],
555 this->address_[2], this->address_[3], this->address_[4], this->address_[5], address_type);
556
557 ESP_LOGVV(TAG, " RSSI: %d", this->rssi_);
558 ESP_LOGVV(TAG, " Name: '%s'", this->name_.c_str());
559 for (auto &it : this->tx_powers_) {
560 ESP_LOGVV(TAG, " TX Power: %d", it);
561 }
562 if (this->appearance_.has_value()) {
563 ESP_LOGVV(TAG, " Appearance: %u", *this->appearance_);
564 }
565 if (this->ad_flag_.has_value()) {
566 ESP_LOGVV(TAG, " Ad Flag: %u", *this->ad_flag_);
567 }
568 for (auto &uuid : this->service_uuids_) {
569 ESP_LOGVV(TAG, " Service UUID: %s", uuid.to_string().c_str());
570 }
571 for (auto &data : this->manufacturer_datas_) {
572 auto ibeacon = ESPBLEiBeacon::from_manufacturer_data(data);
573 if (ibeacon.has_value()) {
574 ESP_LOGVV(TAG, " Manufacturer iBeacon:");
575 ESP_LOGVV(TAG, " UUID: %s", ibeacon.value().get_uuid().to_string().c_str());
576 ESP_LOGVV(TAG, " Major: %u", ibeacon.value().get_major());
577 ESP_LOGVV(TAG, " Minor: %u", ibeacon.value().get_minor());
578 ESP_LOGVV(TAG, " TXPower: %d", ibeacon.value().get_signal_power());
579 } else {
580 ESP_LOGVV(TAG, " Manufacturer ID: %s, data: %s", data.uuid.to_string().c_str(),
581 format_hex_pretty(data.data).c_str());
582 }
583 }
584 for (auto &data : this->service_datas_) {
585 ESP_LOGVV(TAG, " Service data:");
586 ESP_LOGVV(TAG, " UUID: %s", data.uuid.to_string().c_str());
587 ESP_LOGVV(TAG, " Data: %s", format_hex_pretty(data.data).c_str());
588 }
589
590 ESP_LOGVV(TAG, " Adv data: %s",
591 format_hex_pretty(scan_result.ble_adv, scan_result.adv_data_len + scan_result.scan_rsp_len).c_str());
592#endif
593}
594
595void ESPBTDevice::parse_adv_(const uint8_t *payload, uint8_t len) {
596 size_t offset = 0;
597
598 while (offset + 2 < len) {
599 const uint8_t field_length = payload[offset++]; // First byte is length of adv record
600 if (field_length == 0) {
601 continue; // Possible zero padded advertisement data
602 }
603
604 // first byte of adv record is adv record type
605 const uint8_t record_type = payload[offset++];
606 const uint8_t *record = &payload[offset];
607 const uint8_t record_length = field_length - 1;
608 offset += record_length;
609
610 // See also Generic Access Profile Assigned Numbers:
611 // https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile/ See also ADVERTISING AND SCAN
612 // RESPONSE DATA FORMAT: https://www.bluetooth.com/specifications/bluetooth-core-specification/ (vol 3, part C, 11)
613 // See also Core Specification Supplement: https://www.bluetooth.com/specifications/bluetooth-core-specification/
614 // (called CSS here)
615
616 switch (record_type) {
617 case ESP_BLE_AD_TYPE_NAME_SHORT:
618 case ESP_BLE_AD_TYPE_NAME_CMPL: {
619 // CSS 1.2 LOCAL NAME
620 // "The Local Name data type shall be the same as, or a shortened version of, the local name assigned to the
621 // device." CSS 1: Optional in this context; shall not appear more than once in a block.
622 // SHORTENED LOCAL NAME
623 // "The Shortened Local Name data type defines a shortened version of the Local Name data type. The Shortened
624 // Local Name data type shall not be used to advertise a name that is longer than the Local Name data type."
625 if (record_length > this->name_.length()) {
626 this->name_ = std::string(reinterpret_cast<const char *>(record), record_length);
627 }
628 break;
629 }
630 case ESP_BLE_AD_TYPE_TX_PWR: {
631 // CSS 1.5 TX POWER LEVEL
632 // "The TX Power Level data type indicates the transmitted power level of the packet containing the data type."
633 // CSS 1: Optional in this context (may appear more than once in a block).
634 this->tx_powers_.push_back(*payload);
635 break;
636 }
637 case ESP_BLE_AD_TYPE_APPEARANCE: {
638 // CSS 1.12 APPEARANCE
639 // "The Appearance data type defines the external appearance of the device."
640 // See also https://www.bluetooth.com/specifications/gatt/characteristics/
641 // CSS 1: Optional in this context; shall not appear more than once in a block and shall not appear in both
642 // the AD and SRD of the same extended advertising interval.
643 this->appearance_ = *reinterpret_cast<const uint16_t *>(record);
644 break;
645 }
646 case ESP_BLE_AD_TYPE_FLAG: {
647 // CSS 1.3 FLAGS
648 // "The Flags data type contains one bit Boolean flags. The Flags data type shall be included when any of the
649 // Flag bits are non-zero and the advertising packet is connectable, otherwise the Flags data type may be
650 // omitted."
651 // CSS 1: Optional in this context; shall not appear more than once in a block.
652 this->ad_flag_ = *record;
653 break;
654 }
655 // CSS 1.1 SERVICE UUID
656 // The Service UUID data type is used to include a list of Service or Service Class UUIDs.
657 // There are six data types defined for the three sizes of Service UUIDs that may be returned:
658 // CSS 1: Optional in this context (may appear more than once in a block).
659 case ESP_BLE_AD_TYPE_16SRV_CMPL:
660 case ESP_BLE_AD_TYPE_16SRV_PART: {
661 // • 16-bit Bluetooth Service UUIDs
662 for (uint8_t i = 0; i < record_length / 2; i++) {
663 this->service_uuids_.push_back(ESPBTUUID::from_uint16(*reinterpret_cast<const uint16_t *>(record + 2 * i)));
664 }
665 break;
666 }
667 case ESP_BLE_AD_TYPE_32SRV_CMPL:
668 case ESP_BLE_AD_TYPE_32SRV_PART: {
669 // • 32-bit Bluetooth Service UUIDs
670 for (uint8_t i = 0; i < record_length / 4; i++) {
671 this->service_uuids_.push_back(ESPBTUUID::from_uint32(*reinterpret_cast<const uint32_t *>(record + 4 * i)));
672 }
673 break;
674 }
675 case ESP_BLE_AD_TYPE_128SRV_CMPL:
676 case ESP_BLE_AD_TYPE_128SRV_PART: {
677 // • Global 128-bit Service UUIDs
678 this->service_uuids_.push_back(ESPBTUUID::from_raw(record));
679 break;
680 }
681 case ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE: {
682 // CSS 1.4 MANUFACTURER SPECIFIC DATA
683 // "The Manufacturer Specific data type is used for manufacturer specific data. The first two data octets shall
684 // contain a company identifier from Assigned Numbers. The interpretation of any other octets within the data
685 // shall be defined by the manufacturer specified by the company identifier."
686 // CSS 1: Optional in this context (may appear more than once in a block).
687 if (record_length < 2) {
688 ESP_LOGV(TAG, "Record length too small for ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE");
689 break;
690 }
691 ServiceData data{};
692 data.uuid = ESPBTUUID::from_uint16(*reinterpret_cast<const uint16_t *>(record));
693 data.data.assign(record + 2UL, record + record_length);
694 this->manufacturer_datas_.push_back(data);
695 break;
696 }
697
698 // CSS 1.11 SERVICE DATA
699 // "The Service Data data type consists of a service UUID with the data associated with that service."
700 // CSS 1: Optional in this context (may appear more than once in a block).
701 case ESP_BLE_AD_TYPE_SERVICE_DATA: {
702 // «Service Data - 16 bit UUID»
703 // Size: 2 or more octets
704 // The first 2 octets contain the 16 bit Service UUID fol- lowed by additional service data
705 if (record_length < 2) {
706 ESP_LOGV(TAG, "Record length too small for ESP_BLE_AD_TYPE_SERVICE_DATA");
707 break;
708 }
709 ServiceData data{};
710 data.uuid = ESPBTUUID::from_uint16(*reinterpret_cast<const uint16_t *>(record));
711 data.data.assign(record + 2UL, record + record_length);
712 this->service_datas_.push_back(data);
713 break;
714 }
715 case ESP_BLE_AD_TYPE_32SERVICE_DATA: {
716 // «Service Data - 32 bit UUID»
717 // Size: 4 or more octets
718 // The first 4 octets contain the 32 bit Service UUID fol- lowed by additional service data
719 if (record_length < 4) {
720 ESP_LOGV(TAG, "Record length too small for ESP_BLE_AD_TYPE_32SERVICE_DATA");
721 break;
722 }
723 ServiceData data{};
724 data.uuid = ESPBTUUID::from_uint32(*reinterpret_cast<const uint32_t *>(record));
725 data.data.assign(record + 4UL, record + record_length);
726 this->service_datas_.push_back(data);
727 break;
728 }
729 case ESP_BLE_AD_TYPE_128SERVICE_DATA: {
730 // «Service Data - 128 bit UUID»
731 // Size: 16 or more octets
732 // The first 16 octets contain the 128 bit Service UUID followed by additional service data
733 if (record_length < 16) {
734 ESP_LOGV(TAG, "Record length too small for ESP_BLE_AD_TYPE_128SERVICE_DATA");
735 break;
736 }
737 ServiceData data{};
738 data.uuid = ESPBTUUID::from_raw(record);
739 data.data.assign(record + 16UL, record + record_length);
740 this->service_datas_.push_back(data);
741 break;
742 }
743 case ESP_BLE_AD_TYPE_INT_RANGE:
744 // Avoid logging this as it's very verbose
745 break;
746 default: {
747 ESP_LOGV(TAG, "Unhandled type: advType: 0x%02x", record_type);
748 break;
749 }
750 }
751 }
752}
753std::string ESPBTDevice::address_str() const {
754 char mac[24];
755 snprintf(mac, sizeof(mac), "%02X:%02X:%02X:%02X:%02X:%02X", this->address_[0], this->address_[1], this->address_[2],
756 this->address_[3], this->address_[4], this->address_[5]);
757 return mac;
758}
760
762 ESP_LOGCONFIG(TAG, "BLE Tracker:");
763 ESP_LOGCONFIG(TAG,
764 " Scan Duration: %" PRIu32 " s\n"
765 " Scan Interval: %.1f ms\n"
766 " Scan Window: %.1f ms\n"
767 " Scan Type: %s\n"
768 " Continuous Scanning: %s",
769 this->scan_duration_, this->scan_interval_ * 0.625f, this->scan_window_ * 0.625f,
770 this->scan_active_ ? "ACTIVE" : "PASSIVE", YESNO(this->scan_continuous_));
771 switch (this->scanner_state_) {
773 ESP_LOGCONFIG(TAG, " Scanner State: IDLE");
774 break;
776 ESP_LOGCONFIG(TAG, " Scanner State: STARTING");
777 break;
779 ESP_LOGCONFIG(TAG, " Scanner State: RUNNING");
780 break;
782 ESP_LOGCONFIG(TAG, " Scanner State: STOPPING");
783 break;
785 ESP_LOGCONFIG(TAG, " Scanner State: STOPPED");
786 break;
788 ESP_LOGCONFIG(TAG, " Scanner State: FAILED");
789 break;
790 }
791 ESP_LOGCONFIG(TAG, " Connecting: %d, discovered: %d, searching: %d, disconnecting: %d", connecting_, discovered_,
793 if (this->scan_start_fail_count_) {
794 ESP_LOGCONFIG(TAG, " Scan Start Fail Count: %d", this->scan_start_fail_count_);
795 }
796}
797
799 const uint64_t address = device.address_uint64();
800 for (auto &disc : this->already_discovered_) {
801 if (disc == address)
802 return;
803 }
804 this->already_discovered_.push_back(address);
805
806 ESP_LOGD(TAG, "Found device %s RSSI=%d", device.address_str().c_str(), device.get_rssi());
807
808 const char *address_type_s;
809 switch (device.get_address_type()) {
810 case BLE_ADDR_TYPE_PUBLIC:
811 address_type_s = "PUBLIC";
812 break;
813 case BLE_ADDR_TYPE_RANDOM:
814 address_type_s = "RANDOM";
815 break;
816 case BLE_ADDR_TYPE_RPA_PUBLIC:
817 address_type_s = "RPA_PUBLIC";
818 break;
819 case BLE_ADDR_TYPE_RPA_RANDOM:
820 address_type_s = "RPA_RANDOM";
821 break;
822 default:
823 address_type_s = "UNKNOWN";
824 break;
825 }
826
827 ESP_LOGD(TAG, " Address Type: %s", address_type_s);
828 if (!device.get_name().empty()) {
829 ESP_LOGD(TAG, " Name: '%s'", device.get_name().c_str());
830 }
831 for (auto &tx_power : device.get_tx_powers()) {
832 ESP_LOGD(TAG, " TX Power: %d", tx_power);
833 }
834}
835
836bool ESPBTDevice::resolve_irk(const uint8_t *irk) const {
837 uint8_t ecb_key[16];
838 uint8_t ecb_plaintext[16];
839 uint8_t ecb_ciphertext[16];
840
841 uint64_t addr64 = esp32_ble::ble_addr_to_uint64(this->address_);
842
843 memcpy(&ecb_key, irk, 16);
844 memset(&ecb_plaintext, 0, 16);
845
846 ecb_plaintext[13] = (addr64 >> 40) & 0xff;
847 ecb_plaintext[14] = (addr64 >> 32) & 0xff;
848 ecb_plaintext[15] = (addr64 >> 24) & 0xff;
849
850 mbedtls_aes_context ctx = {0, 0, {0}};
851 mbedtls_aes_init(&ctx);
852
853 if (mbedtls_aes_setkey_enc(&ctx, ecb_key, 128) != 0) {
854 mbedtls_aes_free(&ctx);
855 return false;
856 }
857
858 if (mbedtls_aes_crypt_ecb(&ctx, ESP_AES_ENCRYPT, ecb_plaintext, ecb_ciphertext) != 0) {
859 mbedtls_aes_free(&ctx);
860 return false;
861 }
862
863 mbedtls_aes_free(&ctx);
864
865 return ecb_ciphertext[15] == (addr64 & 0xff) && ecb_ciphertext[14] == ((addr64 >> 8) & 0xff) &&
866 ecb_ciphertext[13] == ((addr64 >> 16) & 0xff);
867}
868
869} // namespace esp32_ble_tracker
870} // namespace esphome
871
872#endif
uint8_t address
Definition bl0906.h:4
virtual void mark_failed()
Mark this component as failed.
bool cancel_timeout(const std::string &name)
Cancel a timeout function.
Definition component.cpp:79
void set_timeout(const std::string &name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a unique name.
Definition component.cpp:75
An STL allocator that uses SPI or internal RAM.
Definition helpers.h:684
T * allocate(size_t n)
Definition helpers.h:704
static ESPBTUUID from_uint32(uint32_t uuid)
Definition ble_uuid.cpp:22
static ESPBTUUID from_uint16(uint16_t uuid)
Definition ble_uuid.cpp:16
static ESPBTUUID from_raw(const uint8_t *data)
Definition ble_uuid.cpp:28
bool contains(uint8_t data1, uint8_t data2) const
Definition ble_uuid.cpp:125
std::vector< uint64_t > already_discovered_
Vector of addresses that have already been printed in print_bt_device_info.
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
esp_ble_scan_params_t scan_params_
A structure holding the ESP BLE scan parameters.
void register_listener(ESPBTDeviceListener *listener)
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.
void setup() override
Setup the FreeRTOS task and the Bluetooth stack.
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 gap_scan_event_handler(const BLEScanResult &scan_result) override
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.
std::vector< ESPBTClient * > clients_
Client parameters.
std::vector< ESPBTDeviceListener * > listeners_
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::@81 beacon_data_
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 std::vector< int8_t > & get_tx_powers() const
bool resolve_irk(const uint8_t *irk) const
std::vector< ServiceData > service_datas_
bool has_value() const
Definition optional.h:87
void add_on_state_callback(std::function< void(OTAState, float, uint8_t, OTAComponent *)> &&callback)
Definition ota_backend.h:82
bool state
Definition fan.h:0
ESP32BLETracker * global_esp32_ble_tracker
uint64_t ble_addr_to_uint64(const esp_bd_addr_t address)
Definition ble.cpp:467
OTAGlobalCallback * get_global_ota_callback()
const float AFTER_BLUETOOTH
Definition component.cpp:23
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
std::string size_t len
Definition helpers.h:302
Application App
Global storage of Application pointer - only one Application can exist.
std::string format_hex_pretty(const uint8_t *data, size_t length)
Format the byte array data of length len in pretty-printed, human-readable hex.
Definition helpers.cpp:372