ESPHome 2025.8.0b2
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
39
40static const char *const TAG = "esp32_ble_tracker";
41
42ESP32BLETracker *global_esp32_ble_tracker = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
43
45 switch (state) {
47 return "INIT";
49 return "DISCONNECTING";
51 return "IDLE";
53 return "SEARCHING";
55 return "DISCOVERED";
57 return "READY_TO_CONNECT";
59 return "CONNECTING";
61 return "CONNECTED";
63 return "ESTABLISHED";
64 default:
65 return "UNKNOWN";
66 }
67}
68
70
72 if (this->parent_->is_failed()) {
73 this->mark_failed();
74 ESP_LOGE(TAG, "BLE Tracker was marked failed by ESP32BLE");
75 return;
76 }
77
79
80#ifdef USE_OTA
82 [this](ota::OTAState state, float progress, uint8_t error, ota::OTAComponent *comp) {
83 if (state == ota::OTA_STARTED) {
84 this->stop_scan();
85 for (auto *client : this->clients_) {
86 client->disconnect();
87 }
88 }
89 });
90#endif
91}
92
94 if (!this->parent_->is_active()) {
95 this->ble_was_disabled_ = true;
96 return;
97 } else if (this->ble_was_disabled_) {
98 this->ble_was_disabled_ = false;
99 // If the BLE stack was disabled, we need to start the scan again.
100 if (this->scan_continuous_) {
101 this->start_scan();
102 }
103 }
104
105 // Check for scan timeout - moved here from scheduler to avoid false reboots
106 // when the loop is blocked
108 switch (this->scan_timeout_state_) {
110 uint32_t now = App.get_loop_component_start_time();
111 uint32_t timeout_ms = this->scan_duration_ * 2000;
112 // Robust time comparison that handles rollover correctly
113 // This works because unsigned arithmetic wraps around predictably
114 if ((now - this->scan_start_time_) > timeout_ms) {
115 // First time we've seen the timeout exceeded - wait one more loop iteration
116 // This ensures all components have had a chance to process pending events
117 // This is because esp32_ble may not have run yet and called
118 // gap_scan_event_handler yet when the loop unblocks
119 ESP_LOGW(TAG, "Scan timeout exceeded");
121 }
122 break;
123 }
125 // We've waited at least one full loop iteration, and scan is still running
126 ESP_LOGE(TAG, "Scan never terminated, rebooting");
127 App.reboot();
128 break;
129
131 // This case should be unreachable - scanner and timeout states are always synchronized
132 break;
133 }
134 }
135
137 if (counts != this->client_state_counts_) {
138 this->client_state_counts_ = counts;
139 ESP_LOGD(TAG, "connecting: %d, discovered: %d, searching: %d, disconnecting: %d",
140 this->client_state_counts_.connecting, this->client_state_counts_.discovered,
141 this->client_state_counts_.searching, this->client_state_counts_.disconnecting);
142 }
143
147 }
148 /*
149
150 Avoid starting the scanner if:
151 - we are already scanning
152 - we are connecting to a device
153 - we are disconnecting from a device
154
155 Otherwise the scanner could fail to ever start again
156 and our only way to recover is to reboot.
157
158 https://github.com/espressif/esp-idf/issues/6688
159
160 */
161 bool promote_to_connecting = counts.discovered && !counts.searching && !counts.connecting;
162
163 if (this->scanner_state_ == ScannerState::IDLE && !counts.connecting && !counts.disconnecting &&
164 !promote_to_connecting) {
165#ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE
166 this->update_coex_preference_(false);
167#endif
168 if (this->scan_continuous_) {
169 this->start_scan_(false); // first = false
170 }
171 }
172 // If there is a discovered client and no connecting
173 // clients and no clients using the scanner to search for
174 // devices, then promote the discovered client to ready to connect.
175 // We check both RUNNING and IDLE states because:
176 // - RUNNING: gap_scan_event_handler initiates stop_scan_() but promotion can happen immediately
177 // - IDLE: Scanner has already stopped (naturally or by gap_scan_event_handler)
178 if (promote_to_connecting &&
181 }
182}
183
185
187 ESP_LOGD(TAG, "Stopping scan.");
188 this->scan_continuous_ = false;
189 this->stop_scan_();
190}
191
193
196 ESP_LOGE(TAG, "Cannot stop scan: %s", this->scanner_state_to_string_(this->scanner_state_));
197 return;
198 }
199 // Reset timeout state machine when stopping scan
202 esp_err_t err = esp_ble_gap_stop_scanning();
203 if (err != ESP_OK) {
204 ESP_LOGE(TAG, "esp_ble_gap_stop_scanning failed: %d", err);
205 return;
206 }
207}
208
210 if (!this->parent_->is_active()) {
211 ESP_LOGW(TAG, "Cannot start scan while ESP32BLE is disabled.");
212 return;
213 }
214 if (this->scanner_state_ != ScannerState::IDLE) {
215 this->log_unexpected_state_("start scan", ScannerState::IDLE);
216 return;
217 }
219 ESP_LOGD(TAG, "Starting scan, set scanner state to STARTING.");
220 if (!first) {
221 for (auto *listener : this->listeners_)
222 listener->on_scan_end();
223 }
224#ifdef USE_ESP32_BLE_DEVICE
225 this->already_discovered_.clear();
226#endif
227 this->scan_params_.scan_type = this->scan_active_ ? BLE_SCAN_TYPE_ACTIVE : BLE_SCAN_TYPE_PASSIVE;
228 this->scan_params_.own_addr_type = BLE_ADDR_TYPE_PUBLIC;
229 this->scan_params_.scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL;
230 this->scan_params_.scan_interval = this->scan_interval_;
231 this->scan_params_.scan_window = this->scan_window_;
232
233 // Start timeout monitoring in loop() instead of using scheduler
234 // This prevents false reboots when the loop is blocked
237
238 esp_err_t err = esp_ble_gap_set_scan_params(&this->scan_params_);
239 if (err != ESP_OK) {
240 ESP_LOGE(TAG, "esp_ble_gap_set_scan_params failed: %d", err);
241 return;
242 }
243 err = esp_ble_gap_start_scanning(this->scan_duration_);
244 if (err != ESP_OK) {
245 ESP_LOGE(TAG, "esp_ble_gap_start_scanning failed: %d", err);
246 return;
247 }
248}
249
251 client->app_id = ++this->app_id_;
252 this->clients_.push_back(client);
254}
255
257 listener->set_parent(this);
258 this->listeners_.push_back(listener);
260}
261
263 this->raw_advertisements_ = false;
264 this->parse_advertisements_ = false;
265 for (auto *listener : this->listeners_) {
266 if (listener->get_advertisement_parser_type() == AdvertisementParserType::PARSED_ADVERTISEMENTS) {
267 this->parse_advertisements_ = true;
268 } else {
269 this->raw_advertisements_ = true;
270 }
271 }
272 for (auto *client : this->clients_) {
273 if (client->get_advertisement_parser_type() == AdvertisementParserType::PARSED_ADVERTISEMENTS) {
274 this->parse_advertisements_ = true;
275 } else {
276 this->raw_advertisements_ = true;
277 }
278 }
279}
280
281void ESP32BLETracker::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
282 // Note: This handler is called from the main loop context, not directly from the BT task.
283 // The esp32_ble component queues events via enqueue_ble_event() and processes them in loop().
284 switch (event) {
285 case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT:
286 this->gap_scan_set_param_complete_(param->scan_param_cmpl);
287 break;
288 case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT:
289 this->gap_scan_start_complete_(param->scan_start_cmpl);
290 break;
291 case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT:
292 this->gap_scan_stop_complete_(param->scan_stop_cmpl);
293 break;
294 default:
295 break;
296 }
297 // Forward all events to clients (scan results are handled separately via gap_scan_event_handler)
298 for (auto *client : this->clients_) {
299 client->gap_event_handler(event, param);
300 }
301}
302
303void ESP32BLETracker::gap_scan_event_handler(const BLEScanResult &scan_result) {
304 // Note: This handler is called from the main loop context via esp32_ble's event queue.
305 // We process advertisements immediately instead of buffering them.
306 ESP_LOGV(TAG, "gap_scan_result - event %d", scan_result.search_evt);
307
308 if (scan_result.search_evt == ESP_GAP_SEARCH_INQ_RES_EVT) {
309 // Process the scan result immediately
310 bool found_discovered_client = this->process_scan_result_(scan_result);
311
312 // If we found a discovered client that needs promotion, stop scanning
313 // This replaces the promote_to_connecting logic from loop()
314 if (found_discovered_client && this->scanner_state_ == ScannerState::RUNNING) {
315 ESP_LOGD(TAG, "Found discovered client, stopping scan for connection");
316 this->stop_scan_();
317 }
318 } else if (scan_result.search_evt == ESP_GAP_SEARCH_INQ_CMPL_EVT) {
319 // Scan finished on its own
321 this->log_unexpected_state_("scan complete", ScannerState::RUNNING);
322 }
323 // Scan completed naturally, perform cleanup and transition to IDLE
324 this->cleanup_scan_state_(false);
325 }
326}
327
328void ESP32BLETracker::gap_scan_set_param_complete_(const esp_ble_gap_cb_param_t::ble_scan_param_cmpl_evt_param &param) {
329 // Called from main loop context via gap_event_handler after being queued from BT task
330 ESP_LOGV(TAG, "gap_scan_set_param_complete - status %d", param.status);
331 if (param.status == ESP_BT_STATUS_DONE) {
332 this->scan_set_param_failed_ = ESP_BT_STATUS_SUCCESS;
333 } else {
334 this->scan_set_param_failed_ = param.status;
335 }
336}
337
338void ESP32BLETracker::gap_scan_start_complete_(const esp_ble_gap_cb_param_t::ble_scan_start_cmpl_evt_param &param) {
339 // Called from main loop context via gap_event_handler after being queued from BT task
340 ESP_LOGV(TAG, "gap_scan_start_complete - status %d", param.status);
341 this->scan_start_failed_ = param.status;
343 this->log_unexpected_state_("start complete", ScannerState::STARTING);
344 }
345 if (param.status == ESP_BT_STATUS_SUCCESS) {
346 this->scan_start_fail_count_ = 0;
348 } else {
350 if (this->scan_start_fail_count_ != std::numeric_limits<uint8_t>::max()) {
352 }
353 }
354}
355
356void ESP32BLETracker::gap_scan_stop_complete_(const esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param &param) {
357 // Called from main loop context via gap_event_handler after being queued from BT task
358 // This allows us to safely transition to IDLE state and perform cleanup without race conditions
359 ESP_LOGV(TAG, "gap_scan_stop_complete - status %d", param.status);
361 this->log_unexpected_state_("stop complete", ScannerState::STOPPING);
362 }
363
364 // Perform cleanup and transition to IDLE
365 this->cleanup_scan_state_(true);
366}
367
368void ESP32BLETracker::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
369 esp_ble_gattc_cb_param_t *param) {
370 for (auto *client : this->clients_) {
371 client->gattc_event_handler(event, gattc_if, param);
372 }
373}
374
379
380#ifdef USE_ESP32_BLE_DEVICE
381ESPBLEiBeacon::ESPBLEiBeacon(const uint8_t *data) { memcpy(&this->beacon_data_, data, sizeof(beacon_data_)); }
383 if (!data.uuid.contains(0x4C, 0x00))
384 return {};
385
386 if (data.data.size() != 23)
387 return {};
388 return ESPBLEiBeacon(data.data.data());
389}
390
391void ESPBTDevice::parse_scan_rst(const BLEScanResult &scan_result) {
392 this->scan_result_ = &scan_result;
393 for (uint8_t i = 0; i < ESP_BD_ADDR_LEN; i++)
394 this->address_[i] = scan_result.bda[i];
395 this->address_type_ = static_cast<esp_ble_addr_type_t>(scan_result.ble_addr_type);
396 this->rssi_ = scan_result.rssi;
397
398 // Parse advertisement data directly
399 uint8_t total_len = scan_result.adv_data_len + scan_result.scan_rsp_len;
400 this->parse_adv_(scan_result.ble_adv, total_len);
401
402#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
403 ESP_LOGVV(TAG, "Parse Result:");
404 const char *address_type;
405 switch (this->address_type_) {
406 case BLE_ADDR_TYPE_PUBLIC:
407 address_type = "PUBLIC";
408 break;
409 case BLE_ADDR_TYPE_RANDOM:
410 address_type = "RANDOM";
411 break;
412 case BLE_ADDR_TYPE_RPA_PUBLIC:
413 address_type = "RPA_PUBLIC";
414 break;
415 case BLE_ADDR_TYPE_RPA_RANDOM:
416 address_type = "RPA_RANDOM";
417 break;
418 default:
419 address_type = "UNKNOWN";
420 break;
421 }
422 ESP_LOGVV(TAG, " Address: %02X:%02X:%02X:%02X:%02X:%02X (%s)", this->address_[0], this->address_[1],
423 this->address_[2], this->address_[3], this->address_[4], this->address_[5], address_type);
424
425 ESP_LOGVV(TAG, " RSSI: %d", this->rssi_);
426 ESP_LOGVV(TAG, " Name: '%s'", this->name_.c_str());
427 for (auto &it : this->tx_powers_) {
428 ESP_LOGVV(TAG, " TX Power: %d", it);
429 }
430 if (this->appearance_.has_value()) {
431 ESP_LOGVV(TAG, " Appearance: %u", *this->appearance_);
432 }
433 if (this->ad_flag_.has_value()) {
434 ESP_LOGVV(TAG, " Ad Flag: %u", *this->ad_flag_);
435 }
436 for (auto &uuid : this->service_uuids_) {
437 ESP_LOGVV(TAG, " Service UUID: %s", uuid.to_string().c_str());
438 }
439 for (auto &data : this->manufacturer_datas_) {
440 auto ibeacon = ESPBLEiBeacon::from_manufacturer_data(data);
441 if (ibeacon.has_value()) {
442 ESP_LOGVV(TAG, " Manufacturer iBeacon:");
443 ESP_LOGVV(TAG, " UUID: %s", ibeacon.value().get_uuid().to_string().c_str());
444 ESP_LOGVV(TAG, " Major: %u", ibeacon.value().get_major());
445 ESP_LOGVV(TAG, " Minor: %u", ibeacon.value().get_minor());
446 ESP_LOGVV(TAG, " TXPower: %d", ibeacon.value().get_signal_power());
447 } else {
448 ESP_LOGVV(TAG, " Manufacturer ID: %s, data: %s", data.uuid.to_string().c_str(),
449 format_hex_pretty(data.data).c_str());
450 }
451 }
452 for (auto &data : this->service_datas_) {
453 ESP_LOGVV(TAG, " Service data:");
454 ESP_LOGVV(TAG, " UUID: %s", data.uuid.to_string().c_str());
455 ESP_LOGVV(TAG, " Data: %s", format_hex_pretty(data.data).c_str());
456 }
457
458 ESP_LOGVV(TAG, " Adv data: %s",
459 format_hex_pretty(scan_result.ble_adv, scan_result.adv_data_len + scan_result.scan_rsp_len).c_str());
460#endif
461}
462
463void ESPBTDevice::parse_adv_(const uint8_t *payload, uint8_t len) {
464 size_t offset = 0;
465
466 while (offset + 2 < len) {
467 const uint8_t field_length = payload[offset++]; // First byte is length of adv record
468 if (field_length == 0) {
469 continue; // Possible zero padded advertisement data
470 }
471
472 // first byte of adv record is adv record type
473 const uint8_t record_type = payload[offset++];
474 const uint8_t *record = &payload[offset];
475 const uint8_t record_length = field_length - 1;
476 offset += record_length;
477
478 // See also Generic Access Profile Assigned Numbers:
479 // https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile/ See also ADVERTISING AND SCAN
480 // RESPONSE DATA FORMAT: https://www.bluetooth.com/specifications/bluetooth-core-specification/ (vol 3, part C, 11)
481 // See also Core Specification Supplement: https://www.bluetooth.com/specifications/bluetooth-core-specification/
482 // (called CSS here)
483
484 switch (record_type) {
485 case ESP_BLE_AD_TYPE_NAME_SHORT:
486 case ESP_BLE_AD_TYPE_NAME_CMPL: {
487 // CSS 1.2 LOCAL NAME
488 // "The Local Name data type shall be the same as, or a shortened version of, the local name assigned to the
489 // device." CSS 1: Optional in this context; shall not appear more than once in a block.
490 // SHORTENED LOCAL NAME
491 // "The Shortened Local Name data type defines a shortened version of the Local Name data type. The Shortened
492 // Local Name data type shall not be used to advertise a name that is longer than the Local Name data type."
493 if (record_length > this->name_.length()) {
494 this->name_ = std::string(reinterpret_cast<const char *>(record), record_length);
495 }
496 break;
497 }
498 case ESP_BLE_AD_TYPE_TX_PWR: {
499 // CSS 1.5 TX POWER LEVEL
500 // "The TX Power Level data type indicates the transmitted power level of the packet containing the data type."
501 // CSS 1: Optional in this context (may appear more than once in a block).
502 this->tx_powers_.push_back(*payload);
503 break;
504 }
505 case ESP_BLE_AD_TYPE_APPEARANCE: {
506 // CSS 1.12 APPEARANCE
507 // "The Appearance data type defines the external appearance of the device."
508 // See also https://www.bluetooth.com/specifications/gatt/characteristics/
509 // CSS 1: Optional in this context; shall not appear more than once in a block and shall not appear in both
510 // the AD and SRD of the same extended advertising interval.
511 this->appearance_ = *reinterpret_cast<const uint16_t *>(record);
512 break;
513 }
514 case ESP_BLE_AD_TYPE_FLAG: {
515 // CSS 1.3 FLAGS
516 // "The Flags data type contains one bit Boolean flags. The Flags data type shall be included when any of the
517 // Flag bits are non-zero and the advertising packet is connectable, otherwise the Flags data type may be
518 // omitted."
519 // CSS 1: Optional in this context; shall not appear more than once in a block.
520 this->ad_flag_ = *record;
521 break;
522 }
523 // CSS 1.1 SERVICE UUID
524 // The Service UUID data type is used to include a list of Service or Service Class UUIDs.
525 // There are six data types defined for the three sizes of Service UUIDs that may be returned:
526 // CSS 1: Optional in this context (may appear more than once in a block).
527 case ESP_BLE_AD_TYPE_16SRV_CMPL:
528 case ESP_BLE_AD_TYPE_16SRV_PART: {
529 // • 16-bit Bluetooth Service UUIDs
530 for (uint8_t i = 0; i < record_length / 2; i++) {
531 this->service_uuids_.push_back(ESPBTUUID::from_uint16(*reinterpret_cast<const uint16_t *>(record + 2 * i)));
532 }
533 break;
534 }
535 case ESP_BLE_AD_TYPE_32SRV_CMPL:
536 case ESP_BLE_AD_TYPE_32SRV_PART: {
537 // • 32-bit Bluetooth Service UUIDs
538 for (uint8_t i = 0; i < record_length / 4; i++) {
539 this->service_uuids_.push_back(ESPBTUUID::from_uint32(*reinterpret_cast<const uint32_t *>(record + 4 * i)));
540 }
541 break;
542 }
543 case ESP_BLE_AD_TYPE_128SRV_CMPL:
544 case ESP_BLE_AD_TYPE_128SRV_PART: {
545 // • Global 128-bit Service UUIDs
546 this->service_uuids_.push_back(ESPBTUUID::from_raw(record));
547 break;
548 }
549 case ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE: {
550 // CSS 1.4 MANUFACTURER SPECIFIC DATA
551 // "The Manufacturer Specific data type is used for manufacturer specific data. The first two data octets shall
552 // contain a company identifier from Assigned Numbers. The interpretation of any other octets within the data
553 // shall be defined by the manufacturer specified by the company identifier."
554 // CSS 1: Optional in this context (may appear more than once in a block).
555 if (record_length < 2) {
556 ESP_LOGV(TAG, "Record length too small for ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE");
557 break;
558 }
559 ServiceData data{};
560 data.uuid = ESPBTUUID::from_uint16(*reinterpret_cast<const uint16_t *>(record));
561 data.data.assign(record + 2UL, record + record_length);
562 this->manufacturer_datas_.push_back(data);
563 break;
564 }
565
566 // CSS 1.11 SERVICE DATA
567 // "The Service Data data type consists of a service UUID with the data associated with that service."
568 // CSS 1: Optional in this context (may appear more than once in a block).
569 case ESP_BLE_AD_TYPE_SERVICE_DATA: {
570 // «Service Data - 16 bit UUID»
571 // Size: 2 or more octets
572 // The first 2 octets contain the 16 bit Service UUID fol- lowed by additional service data
573 if (record_length < 2) {
574 ESP_LOGV(TAG, "Record length too small for ESP_BLE_AD_TYPE_SERVICE_DATA");
575 break;
576 }
577 ServiceData data{};
578 data.uuid = ESPBTUUID::from_uint16(*reinterpret_cast<const uint16_t *>(record));
579 data.data.assign(record + 2UL, record + record_length);
580 this->service_datas_.push_back(data);
581 break;
582 }
583 case ESP_BLE_AD_TYPE_32SERVICE_DATA: {
584 // «Service Data - 32 bit UUID»
585 // Size: 4 or more octets
586 // The first 4 octets contain the 32 bit Service UUID fol- lowed by additional service data
587 if (record_length < 4) {
588 ESP_LOGV(TAG, "Record length too small for ESP_BLE_AD_TYPE_32SERVICE_DATA");
589 break;
590 }
591 ServiceData data{};
592 data.uuid = ESPBTUUID::from_uint32(*reinterpret_cast<const uint32_t *>(record));
593 data.data.assign(record + 4UL, record + record_length);
594 this->service_datas_.push_back(data);
595 break;
596 }
597 case ESP_BLE_AD_TYPE_128SERVICE_DATA: {
598 // «Service Data - 128 bit UUID»
599 // Size: 16 or more octets
600 // The first 16 octets contain the 128 bit Service UUID followed by additional service data
601 if (record_length < 16) {
602 ESP_LOGV(TAG, "Record length too small for ESP_BLE_AD_TYPE_128SERVICE_DATA");
603 break;
604 }
605 ServiceData data{};
606 data.uuid = ESPBTUUID::from_raw(record);
607 data.data.assign(record + 16UL, record + record_length);
608 this->service_datas_.push_back(data);
609 break;
610 }
611 case ESP_BLE_AD_TYPE_INT_RANGE:
612 // Avoid logging this as it's very verbose
613 break;
614 default: {
615 ESP_LOGV(TAG, "Unhandled type: advType: 0x%02x", record_type);
616 break;
617 }
618 }
619 }
620}
621
622std::string ESPBTDevice::address_str() const {
623 char mac[24];
624 snprintf(mac, sizeof(mac), "%02X:%02X:%02X:%02X:%02X:%02X", this->address_[0], this->address_[1], this->address_[2],
625 this->address_[3], this->address_[4], this->address_[5]);
626 return mac;
627}
628
630#endif // USE_ESP32_BLE_DEVICE
631
633 ESP_LOGCONFIG(TAG, "BLE Tracker:");
634 ESP_LOGCONFIG(TAG,
635 " Scan Duration: %" PRIu32 " s\n"
636 " Scan Interval: %.1f ms\n"
637 " Scan Window: %.1f ms\n"
638 " Scan Type: %s\n"
639 " Continuous Scanning: %s",
640 this->scan_duration_, this->scan_interval_ * 0.625f, this->scan_window_ * 0.625f,
641 this->scan_active_ ? "ACTIVE" : "PASSIVE", YESNO(this->scan_continuous_));
642 ESP_LOGCONFIG(TAG, " Scanner State: %s", this->scanner_state_to_string_(this->scanner_state_));
643 ESP_LOGCONFIG(TAG, " Connecting: %d, discovered: %d, searching: %d, disconnecting: %d",
644 this->client_state_counts_.connecting, this->client_state_counts_.discovered,
645 this->client_state_counts_.searching, this->client_state_counts_.disconnecting);
646 if (this->scan_start_fail_count_) {
647 ESP_LOGCONFIG(TAG, " Scan Start Fail Count: %d", this->scan_start_fail_count_);
648 }
649}
650
651#ifdef USE_ESP32_BLE_DEVICE
653 const uint64_t address = device.address_uint64();
654 for (auto &disc : this->already_discovered_) {
655 if (disc == address)
656 return;
657 }
658 this->already_discovered_.push_back(address);
659
660 ESP_LOGD(TAG, "Found device %s RSSI=%d", device.address_str().c_str(), device.get_rssi());
661
662 const char *address_type_s;
663 switch (device.get_address_type()) {
664 case BLE_ADDR_TYPE_PUBLIC:
665 address_type_s = "PUBLIC";
666 break;
667 case BLE_ADDR_TYPE_RANDOM:
668 address_type_s = "RANDOM";
669 break;
670 case BLE_ADDR_TYPE_RPA_PUBLIC:
671 address_type_s = "RPA_PUBLIC";
672 break;
673 case BLE_ADDR_TYPE_RPA_RANDOM:
674 address_type_s = "RPA_RANDOM";
675 break;
676 default:
677 address_type_s = "UNKNOWN";
678 break;
679 }
680
681 ESP_LOGD(TAG, " Address Type: %s", address_type_s);
682 if (!device.get_name().empty()) {
683 ESP_LOGD(TAG, " Name: '%s'", device.get_name().c_str());
684 }
685 for (auto &tx_power : device.get_tx_powers()) {
686 ESP_LOGD(TAG, " TX Power: %d", tx_power);
687 }
688}
689
690bool ESPBTDevice::resolve_irk(const uint8_t *irk) const {
691 uint8_t ecb_key[16];
692 uint8_t ecb_plaintext[16];
693 uint8_t ecb_ciphertext[16];
694
695 uint64_t addr64 = esp32_ble::ble_addr_to_uint64(this->address_);
696
697 memcpy(&ecb_key, irk, 16);
698 memset(&ecb_plaintext, 0, 16);
699
700 ecb_plaintext[13] = (addr64 >> 40) & 0xff;
701 ecb_plaintext[14] = (addr64 >> 32) & 0xff;
702 ecb_plaintext[15] = (addr64 >> 24) & 0xff;
703
704 mbedtls_aes_context ctx = {0, 0, {0}};
705 mbedtls_aes_init(&ctx);
706
707 if (mbedtls_aes_setkey_enc(&ctx, ecb_key, 128) != 0) {
708 mbedtls_aes_free(&ctx);
709 return false;
710 }
711
712 if (mbedtls_aes_crypt_ecb(&ctx, ESP_AES_ENCRYPT, ecb_plaintext, ecb_ciphertext) != 0) {
713 mbedtls_aes_free(&ctx);
714 return false;
715 }
716
717 mbedtls_aes_free(&ctx);
718
719 return ecb_ciphertext[15] == (addr64 & 0xff) && ecb_ciphertext[14] == ((addr64 >> 8) & 0xff) &&
720 ecb_ciphertext[13] == ((addr64 >> 16) & 0xff);
721}
722
724 for (auto *client : this->clients_) {
725 auto state = client->state();
727 return true;
728 }
729 }
730 return false;
731}
732#endif // USE_ESP32_BLE_DEVICE
733
734bool ESP32BLETracker::process_scan_result_(const BLEScanResult &scan_result) {
735 bool found_discovered_client = false;
736
737 // Process raw advertisements
738 if (this->raw_advertisements_) {
739 for (auto *listener : this->listeners_) {
740 listener->parse_devices(&scan_result, 1);
741 }
742 for (auto *client : this->clients_) {
743 client->parse_devices(&scan_result, 1);
744 }
745 }
746
747 // Process parsed advertisements
748 if (this->parse_advertisements_) {
749#ifdef USE_ESP32_BLE_DEVICE
750 ESPBTDevice device;
751 device.parse_scan_rst(scan_result);
752
753 bool found = false;
754 for (auto *listener : this->listeners_) {
755 if (listener->parse_device(device))
756 found = true;
757 }
758
759 for (auto *client : this->clients_) {
760 if (client->parse_device(device)) {
761 found = true;
762 // Check if this client is discovered and needs promotion
763 if (client->state() == ClientState::DISCOVERED) {
764 // Only check for connecting clients if we found a discovered client
765 // This matches the original logic: !connecting && client->state() == DISCOVERED
766 if (!this->has_connecting_clients_()) {
767 found_discovered_client = true;
768 }
769 }
770 }
771 }
772
773 if (!found && !this->scan_continuous_) {
774 this->print_bt_device_info(device);
775 }
776#endif // USE_ESP32_BLE_DEVICE
777 }
778
779 return found_discovered_client;
780}
781
782void ESP32BLETracker::cleanup_scan_state_(bool is_stop_complete) {
783 ESP_LOGD(TAG, "Scan %scomplete, set scanner state to IDLE.", is_stop_complete ? "stop " : "");
784#ifdef USE_ESP32_BLE_DEVICE
785 this->already_discovered_.clear();
786#endif
787 // Reset timeout state machine instead of cancelling scheduler timeout
789
790 for (auto *listener : this->listeners_)
791 listener->on_scan_end();
792
794}
795
797 this->stop_scan_();
798 if (this->scan_start_fail_count_ == std::numeric_limits<uint8_t>::max()) {
799 ESP_LOGE(TAG, "Scan could not restart after %d attempts, rebooting to restore stack (IDF)",
800 std::numeric_limits<uint8_t>::max());
801 App.reboot();
802 }
803 if (this->scan_start_failed_) {
804 ESP_LOGE(TAG, "Scan start failed: %d", this->scan_start_failed_);
805 this->scan_start_failed_ = ESP_BT_STATUS_SUCCESS;
806 }
807 if (this->scan_set_param_failed_) {
808 ESP_LOGE(TAG, "Scan set param failed: %d", this->scan_set_param_failed_);
809 this->scan_set_param_failed_ = ESP_BT_STATUS_SUCCESS;
810 }
811}
812
814 // Only promote the first discovered client to avoid multiple simultaneous connections
815 for (auto *client : this->clients_) {
816 if (client->state() != ClientState::DISCOVERED) {
817 continue;
818 }
819
821 ESP_LOGD(TAG, "Stopping scan to make connection");
822 this->stop_scan_();
823 // Don't wait for scan stop complete - promote immediately.
824 // This is safe because ESP-IDF processes BLE commands sequentially through its internal mailbox queue.
825 // This guarantees that the stop scan command will be fully processed before any subsequent connect command,
826 // preventing race conditions or overlapping operations.
827 }
828
829 ESP_LOGD(TAG, "Promoting client to connect");
830#ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE
831 this->update_coex_preference_(true);
832#endif
833 client->set_state(ClientState::READY_TO_CONNECT);
834 break;
835 }
836}
837
839 switch (state) {
841 return "IDLE";
843 return "STARTING";
845 return "RUNNING";
847 return "STOPPING";
849 return "FAILED";
850 default:
851 return "UNKNOWN";
852 }
853}
854
855void ESP32BLETracker::log_unexpected_state_(const char *operation, ScannerState expected_state) const {
856 ESP_LOGE(TAG, "Unexpected state: %s on %s, expected: %s", this->scanner_state_to_string_(this->scanner_state_),
857 operation, this->scanner_state_to_string_(expected_state));
858}
859
860#ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE
862 if (force_ble && !this->coex_prefer_ble_) {
863 ESP_LOGD(TAG, "Setting coexistence to Bluetooth to make connection.");
864 this->coex_prefer_ble_ = true;
865 esp_coex_preference_set(ESP_COEX_PREFER_BT); // Prioritize Bluetooth
866 } else if (!force_ble && this->coex_prefer_ble_) {
867 ESP_LOGD(TAG, "Setting coexistence preference to balanced.");
868 this->coex_prefer_ble_ = false;
869 esp_coex_preference_set(ESP_COEX_PREFER_BALANCE); // Reset to default
870 }
871}
872#endif
873
874} // namespace esphome::esp32_ble_tracker
875
876#endif // USE_ESP32
uint8_t address
Definition bl0906.h:4
uint32_t IRAM_ATTR HOT get_loop_component_start_time() const
Get the cached time in milliseconds from when the current component started its loop execution.
virtual void mark_failed()
Mark this component as failed.
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
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 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.
void log_unexpected_state_(const char *operation, ScannerState expected_state) const
Log an unexpected scanner state.
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)
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:92
void add_on_state_callback(std::function< void(OTAState, float, uint8_t, OTAComponent *)> &&callback)
bool state
Definition fan.h:0
ESP32BLETracker * global_esp32_ble_tracker
const char * client_state_to_string(ClientState state)
uint64_t ble_addr_to_uint64(const esp_bd_addr_t address)
Definition ble.cpp:533
OTAGlobalCallback * get_global_ota_callback()
const float AFTER_BLUETOOTH
Definition component.cpp:53
std::string size_t len
Definition helpers.h:279
std::string format_hex_pretty(const uint8_t *data, size_t length, char separator, bool show_length)
Format a byte array in pretty-printed, human-readable hex format.
Definition helpers.cpp:280
Application App
Global storage of Application pointer - only one Application can exist.