ESPHome 2026.10.0-dev
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#ifndef CONFIG_ESP_HOSTED_ENABLE_BT_BLUEDROID
11#include <esp_bt.h>
12#endif
13#include <esp_bt_defs.h>
14#include <esp_bt_main.h>
15#include <esp_gap_ble_api.h>
16#include <freertos/FreeRTOS.h>
17#include <freertos/FreeRTOSConfig.h>
18#include <freertos/task.h>
19#include <nvs_flash.h>
20#include <cinttypes>
21
22#ifdef USE_OTA
24#endif
25
26#ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE
27#include <esp_coexist.h>
28#endif
29
30// bt_trace.h
31#undef TAG
32
34
35static const char *const TAG = "esp32_ble_tracker";
36
37ESP32BLETracker *global_esp32_ble_tracker = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
38
40
42 if (this->parent_->is_failed()) {
43 this->mark_failed();
44 ESP_LOGE(TAG, "BLE Tracker was marked failed by ESP32BLE");
45 return;
46 }
47
49
50#ifdef USE_OTA_STATE_LISTENER
52#endif
53}
54
55#ifdef USE_OTA_STATE_LISTENER
57 if (state == ota::OTA_STARTED) {
58 ESP_LOGD(TAG, "Stopping scan for OTA");
60 this->stop_scan();
61#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT
62 for (auto *client : this->clients_) {
63 client->disconnect();
64 }
65#endif
67 this->scan_continuous_before_ota_ = false;
68 this->scan_continuous_ = true;
69 // Do not restart scanning immediately here; allow loop() to
70 // safely restart scanning once the scanner and all clients are idle.
71 }
72}
73#endif
74
76 if (!this->parent_->is_active()) {
77 this->ble_was_disabled_ = true;
78 return;
79 } else if (this->ble_was_disabled_) {
80 this->ble_was_disabled_ = false;
81 // If the BLE stack was disabled, we need to start the scan again.
82 if (this->scan_continuous_) {
83 this->start_scan();
84 }
85 }
86
87 // Check for scan timeout - moved here from scheduler to avoid false reboots
88 // when the loop is blocked. This must run every iteration for safety.
89 if (this->scanner_state_ == ScannerState::RUNNING) {
90 switch (this->scan_timeout_state_) {
92 // Robust time comparison that handles rollover correctly
93 // This works because unsigned arithmetic wraps around predictably
94 if ((App.get_loop_component_start_time() - this->scan_start_time_) > this->scan_timeout_ms_) {
95 // First time we've seen the timeout exceeded - wait one more loop iteration
96 // This ensures all components have had a chance to process pending events
97 // This is because esp32_ble may not have run yet and called
98 // gap_scan_event_handler yet when the loop unblocks
99 ESP_LOGW(TAG, "Scan timeout exceeded");
101 }
102 break;
103 }
105 // We've waited at least one full loop iteration, and scan is still running
106 ESP_LOGE(TAG, "Scan never terminated, rebooting");
107 App.reboot();
108 break;
110 break;
111 }
112 }
113
114 // Fast path: skip expensive client state counting and processing
115 // if no state has changed since last loop iteration.
116 //
117 // How state changes ensure we reach the code below:
118 // - handle_scanner_failure_(): scanner_state_ becomes FAILED via set_scanner_state_(), or
119 // scan_set_param_failed_ requires scanner_state_==RUNNING which can only be reached via
120 // set_scanner_state_(RUNNING) in gap_scan_start_complete_() (scan params are set during
121 // STARTING, not RUNNING, so version is always incremented before this condition is true)
122 // - start_scan_(): scanner_state_ becomes IDLE via set_scanner_state_() in cleanup_scan_state_()
123 // - try_promote_discovered_clients_(): client enters DISCOVERED via set_state(), or
124 // connecting client finishes (state change), or scanner reaches RUNNING/IDLE
125 // - connection-window restart: scan_params_ is only written in start_scan_()
126 // (which changes scanner state via set_scanner_state_()), and
127 // counts.active/disconnecting only change on client state changes
128 //
129 // All conditions that affect the logic below are tied to state changes that increment
130 // state_version_, so the fast path is safe.
131 if (this->state_version_ == this->last_processed_version_) {
132 return;
133 }
135
136 // State changed - do full processing
138 if (counts != this->client_state_counts_) {
139 this->client_state_counts_ = counts;
140 ESP_LOGD(TAG, "connecting: %d, discovered: %d, disconnecting: %d, active: %d",
141 this->client_state_counts_.connecting, this->client_state_counts_.discovered,
142 this->client_state_counts_.disconnecting, this->client_state_counts_.active);
143 }
144
145 // Scanner failure: reached when set_scanner_state_(FAILED) or scan_set_param_failed_ set
146 if (this->scanner_state_ == ScannerState::FAILED ||
147 (this->scan_set_param_failed_ && this->scanner_state_ == ScannerState::RUNNING)) {
149 }
150
151#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT
152 // The programmed window no longer matches the connection state (typically
153 // the last connection dropped): restart so the right window applies now
154 // instead of at the end of the scan period. Continuous only (a user-started
155 // scan would not restart); !disconnecting matches the restart gate below.
156 if (this->scanner_state_ == ScannerState::RUNNING && this->scan_continuous_ && !counts.disconnecting &&
157 this->scan_params_.scan_window != this->desired_scan_window_(counts.active)) {
158 // Same logical scan period continues: no on_scan_end sweeps for this
159 // restart. Only armed when the stop was issued.
160 this->skip_next_scan_end_ = this->stop_scan_();
161 }
162#endif
163 /*
164
165 Avoid starting the scanner if:
166 - we are already scanning
167 - we are connecting to a device
168 - we are disconnecting from a device
169
170 Otherwise the scanner could fail to ever start again
171 and our only way to recover is to reboot.
172
173 https://github.com/espressif/esp-idf/issues/6688
174
175 */
176
177 // Start scan: reached when scanner_state_ becomes IDLE (via set_scanner_state_()) and
178 // no clients are in the transient CONNECTING / DISCOVERED / DISCONNECTING states
179 // (their state changes increment version when they finish). CONNECTED / ESTABLISHED
180 // clients do NOT block this branch — the coex revert below has its own active-count gate.
181 if (this->scanner_state_ == ScannerState::IDLE && !counts.connecting && !counts.disconnecting && !counts.discovered) {
182#ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE
183 // Only revert to BALANCE when no connections are active. Established connections
184 // continue to need PREFER_BT so peer GATT responses can reach us while WiFi traffic
185 // (advertisement upload, log streaming) competes for the shared radio. Reverting too
186 // early causes Bluedroid to time out at ~20s and synthesize status=133.
187 if (!counts.active) {
188 this->update_coex_preference_(false);
189 }
190#endif
191 if (this->scan_continuous_) {
192 this->start_scan_(false); // first = false
193 }
194 }
195 // Promote discovered clients: reached when a client's state becomes DISCOVERED (via set_state()),
196 // or when a blocking condition clears (connecting client finishes, scanner reaches RUNNING/IDLE).
197 // All these trigger state_version_ increment, so we'll process and check promotion eligibility.
198 // We check both RUNNING and IDLE states because:
199 // - RUNNING: gap_scan_event_handler initiates stop_scan_() but promotion can happen immediately
200 // - IDLE: Scanner has already stopped (naturally or by gap_scan_event_handler)
201 if (counts.discovered && !counts.connecting &&
202 (this->scanner_state_ == ScannerState::RUNNING || this->scanner_state_ == ScannerState::IDLE)) {
204 }
205}
206
208
210 // V to match the start log: the mode-switch and OTA callers narrate their
211 // reason at D themselves, and the user-facing stop action is deliberate.
212 ESP_LOGV(TAG, "Stopping scan.");
213 this->scan_continuous_ = false;
214#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT
215 // The window-change restart is abandoned with continuous scanning.
216 this->skip_next_scan_end_ = false;
217#endif
218 this->stop_scan_();
219}
220
222
224 if (this->scanner_state_ != ScannerState::RUNNING && this->scanner_state_ != ScannerState::FAILED) {
225 // IDLE means there is nothing to stop; STOPPING means a stop is already in
226 // flight and will finish on its own. Neither is an error.
227 if (this->scanner_state_ != ScannerState::IDLE && this->scanner_state_ != ScannerState::STOPPING) {
228 ESP_LOGE(TAG, "Cannot stop scan: %s", this->scanner_state_to_string_(this->scanner_state_));
229 }
230 return false;
231 }
232 // Reset timeout state machine when stopping scan
234 this->set_scanner_state_(ScannerState::STOPPING);
235 esp_err_t err = esp_ble_gap_stop_scanning();
236 if (err != ESP_OK) {
237 ESP_LOGE(TAG, "esp_ble_gap_stop_scanning failed: %d", err);
238 return false;
239 }
240 return true;
241}
242
244 if (!this->parent_->is_active()) {
245 ESP_LOGW(TAG, "Cannot start scan while ESP32BLE is disabled.");
246 return;
247 }
248 if (this->scanner_state_ != ScannerState::IDLE) {
249 this->log_unexpected_state_("start scan", ScannerState::IDLE);
250 return;
251 }
252 this->set_scanner_state_(ScannerState::STARTING);
253 ESP_LOGV(TAG, "Starting scan, set scanner state to STARTING.");
254 if (!first)
255 this->notify_scan_end_();
256#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT
257 this->skip_next_scan_end_ = false;
258#endif
259#ifdef USE_ESP32_BLE_DEVICE
260 this->discovered_log_.clear();
261#endif
262 this->scan_params_.scan_type = this->scan_active_ ? BLE_SCAN_TYPE_ACTIVE : BLE_SCAN_TYPE_PASSIVE;
263 this->scan_params_.own_addr_type = BLE_ADDR_TYPE_PUBLIC;
264 this->scan_params_.scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL;
265 this->scan_params_.scan_interval = this->scan_interval_;
266#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT
267 // Count fresh: an automation can start a scan before loop() refreshes the counts.
268 const uint32_t window = this->desired_scan_window_(this->count_client_states_().active);
269 if (window != this->scan_window_) {
270 // Guarantee the connection airtime instead of scanning wall to wall.
271 ESP_LOGV(TAG, "Connection active, using %" PRIu32 " unit scan window", window);
272 }
273#else
274 const uint32_t window = this->scan_window_;
275#endif
276 this->scan_params_.scan_window = window;
277
278 // Start timeout monitoring in loop() instead of using scheduler
279 // This prevents false reboots when the loop is blocked
281 this->scan_timeout_ms_ = this->scan_duration_ * 2000;
283
284 esp_err_t err = esp_ble_gap_set_scan_params(&this->scan_params_);
285 if (err != ESP_OK) {
286 ESP_LOGE(TAG, "esp_ble_gap_set_scan_params failed: %d", err);
287 return;
288 }
289 err = esp_ble_gap_start_scanning(this->scan_duration_);
290 if (err != ESP_OK) {
291 ESP_LOGE(TAG, "esp_ble_gap_start_scanning failed: %d", err);
292 return;
293 }
294}
295
297#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT
298 client->app_id = ++this->app_id_;
299 // Give client a pointer to our state_version_ so it can notify us of state changes.
300 // This enables loop() fast-path optimization - we skip expensive work when no state changed.
301 // Safe because ESP32BLETracker (singleton) outlives all registered clients.
303 this->clients_.push_back(client);
304 // Registration is add-only, so the flag is a monotonic OR.
305 if (client->wants_parsed_advertisements())
306 this->parse_advertisements_ = true;
307#endif
308}
309
311#ifdef ESPHOME_BLE_DEVICE_BASE_LISTENER_COUNT
312 // Neutral BLEHub path (migrated sensors): parsed-advertisement consumers only.
313 this->neutral_listeners_.push_back(listener);
314 this->parse_advertisements_ = true;
315#endif
316}
317
319#ifdef ESPHOME_ESP32_BLE_TRACKER_LISTENER_COUNT
320 listener->set_parent(this);
321 this->listeners_.push_back(listener);
322 this->parse_advertisements_ = true;
323#endif
324}
325
326void ESP32BLETracker::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
327 // Note: This handler is called from the main loop context, not directly from the BT task.
328 // The esp32_ble component queues events via enqueue_ble_event() and processes them in loop().
329 switch (event) {
330 case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT:
331 this->gap_scan_set_param_complete_(param->scan_param_cmpl);
332 break;
333 case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT:
334 this->gap_scan_start_complete_(param->scan_start_cmpl);
335 break;
336 case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT:
337 this->gap_scan_stop_complete_(param->scan_stop_cmpl);
338 break;
339 default:
340 break;
341 }
342 // Forward all events to clients (scan results are handled separately via gap_scan_event_handler)
343#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT
344 for (auto *client : this->clients_) {
345 client->gap_event_handler(event, param);
346 }
347#endif
348}
349
350void ESP32BLETracker::gap_scan_event_handler(const BLEScanResult &scan_result) {
351 // Note: This handler is called from the main loop context via esp32_ble's event queue.
352 // We process advertisements immediately instead of buffering them.
353 ESP_LOGVV(TAG, "gap_scan_result - event %d", scan_result.search_evt);
354
355 if (scan_result.search_evt == ESP_GAP_SEARCH_INQ_RES_EVT) {
356 // Process the scan result immediately
357 this->process_scan_result_(scan_result);
358 } else if (scan_result.search_evt == ESP_GAP_SEARCH_INQ_CMPL_EVT) {
359 // Scan finished on its own
360 if (this->scanner_state_ != ScannerState::RUNNING) {
361 this->log_unexpected_state_("scan complete", ScannerState::RUNNING);
362 }
363 // Scan completed naturally, perform cleanup and transition to IDLE
364 this->cleanup_scan_state_(false);
365 }
366}
367
368void ESP32BLETracker::gap_scan_set_param_complete_(const esp_ble_gap_cb_param_t::ble_scan_param_cmpl_evt_param &param) {
369 // Called from main loop context via gap_event_handler after being queued from BT task
370 ESP_LOGV(TAG, "gap_scan_set_param_complete - status %d", param.status);
371 if (param.status == ESP_BT_STATUS_DONE) {
372 this->scan_set_param_failed_ = ESP_BT_STATUS_SUCCESS;
373 } else {
374 this->scan_set_param_failed_ = param.status;
375 }
376}
377
378void ESP32BLETracker::gap_scan_start_complete_(const esp_ble_gap_cb_param_t::ble_scan_start_cmpl_evt_param &param) {
379 // Called from main loop context via gap_event_handler after being queued from BT task
380 ESP_LOGV(TAG, "gap_scan_start_complete - status %d", param.status);
381 this->scan_start_failed_ = param.status;
382 if (this->scanner_state_ != ScannerState::STARTING) {
383 this->log_unexpected_state_("start complete", ScannerState::STARTING);
384 }
385 if (param.status == ESP_BT_STATUS_SUCCESS) {
386 this->scan_start_fail_count_ = 0;
387 this->set_scanner_state_(ScannerState::RUNNING);
388 } else {
389 this->set_scanner_state_(ScannerState::FAILED);
390 if (this->scan_start_fail_count_ != std::numeric_limits<uint8_t>::max()) {
392 }
393 }
394}
395
396void ESP32BLETracker::gap_scan_stop_complete_(const esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param &param) {
397 // Called from main loop context via gap_event_handler after being queued from BT task
398 // This allows us to safely transition to IDLE state and perform cleanup without race conditions
399 ESP_LOGV(TAG, "gap_scan_stop_complete - status %d", param.status);
400 if (this->scanner_state_ != ScannerState::STOPPING) {
401 this->log_unexpected_state_("stop complete", ScannerState::STOPPING);
402 }
403
404 // Perform cleanup and transition to IDLE
405 this->cleanup_scan_state_(true);
406}
407
408void ESP32BLETracker::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
409 esp_ble_gattc_cb_param_t *param) {
410#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT
411 for (auto *client : this->clients_) {
412 client->gattc_event_handler(event, gattc_if, param);
413 }
414#endif
415}
416
418 this->scanner_state_ = state;
419 this->state_version_++;
420#ifdef USE_BLE_SCANNER_STATE_CALLBACK
421 if (this->scanner_state_callback_.is_set()) {
422 this->scanner_state_callback_.invoke(state);
423 }
424#endif
425}
426
428 ESP_LOGCONFIG(TAG, "BLE Tracker:");
429 ESP_LOGCONFIG(TAG,
430 " Scan Duration: %" PRIu32 " s\n"
431 " Scan Interval: %.1f ms\n"
432 " Scan Window: %.1f ms\n"
433 " Scan Type: %s\n"
434 " Continuous Scanning: %s",
435 this->scan_duration_, this->scan_interval_ * 0.625f, this->scan_window_ * 0.625f,
436 this->scan_active_ ? "ACTIVE" : "PASSIVE", YESNO(this->scan_continuous_));
437#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT
438 if (this->connection_scan_window_ != 0) {
439 ESP_LOGCONFIG(TAG, " Connection Scan Window: %.1f ms", this->connection_scan_window_ * 0.625f);
440 }
441#endif
442 ESP_LOGCONFIG(TAG,
443 " Scanner State: %s\n"
444 " Connecting: %d, discovered: %d, disconnecting: %d, active: %d",
446 this->client_state_counts_.discovered, this->client_state_counts_.disconnecting,
447 this->client_state_counts_.active);
448 if (this->scan_start_fail_count_) {
449 ESP_LOGCONFIG(TAG, " Scan Start Fail Count: %d", this->scan_start_fail_count_);
450 }
451}
452
453#ifdef USE_ESP32_BLE_DEVICE
455 // Shared implementation in ble_device_base — identical output and per-period
456 // MAC dedup on every tracker backend.
457 this->discovered_log_.log_device(TAG, device);
458}
459
460// resolve_irk() is provided by ble_device_base (portable software AES).
461
462#endif // USE_ESP32_BLE_DEVICE
463
464void ESP32BLETracker::process_scan_result_(const BLEScanResult &scan_result) {
465 // Neutral raw-advertisement subscriber (the bluetooth_proxy path).
468 adv.address = esp32_ble::ble_addr_to_uint64(scan_result.bda);
469 adv.data = scan_result.ble_adv;
470 adv.data_len = static_cast<uint16_t>(scan_result.adv_data_len) + scan_result.scan_rsp_len;
471 adv.rssi = scan_result.rssi;
472 adv.addr_type = scan_result.ble_addr_type;
474 }
475
476 // Process parsed advertisements
477 if (this->parse_advertisements_) {
478#ifdef USE_ESP32_BLE_DEVICE
479 ESPBTDevice device;
480 // The historical ingest keeps the raw scan-result fields populated for
481 // external components.
482 device.parse_scan_rst(scan_result);
483
484 bool found = false;
485#ifdef ESPHOME_ESP32_BLE_TRACKER_LISTENER_COUNT
486 for (auto *listener : this->listeners_) {
487 if (listener->parse_device(device))
488 found = true;
489 }
490#endif
491#ifdef ESPHOME_BLE_DEVICE_BASE_LISTENER_COUNT
492 for (auto *listener : this->neutral_listeners_) {
493 if (listener->parse_device(device))
494 found = true;
495 }
496#endif
497
498#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT
499 for (auto *client : this->clients_) {
500 if (client->parse_device(device)) {
501 found = true;
502 }
503 }
504#endif
505
506 if (!found && !this->scan_continuous_) {
507 this->print_bt_device_info(device);
508 }
509#endif // USE_ESP32_BLE_DEVICE
510 }
511}
512
513void ESP32BLETracker::cleanup_scan_state_(bool is_stop_complete) {
514 ESP_LOGV(TAG, "Scan %scomplete, set scanner state to IDLE.", is_stop_complete ? "stop " : "");
515#ifdef USE_ESP32_BLE_DEVICE
516 this->discovered_log_.clear();
517#endif
518 // Reset timeout state machine instead of cancelling scheduler timeout
520
521 this->notify_scan_end_();
522
523 this->set_scanner_state_(ScannerState::IDLE);
524}
525
527#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT
528 // Window-change restart continues the same scan period; the flag stays set
529 // across the stop and is cleared by the restart in start_scan_.
530 if (this->skip_next_scan_end_)
531 return;
532#endif
533#ifdef ESPHOME_ESP32_BLE_TRACKER_LISTENER_COUNT
534 for (auto *listener : this->listeners_)
535 listener->on_scan_end();
536#endif
537#ifdef ESPHOME_BLE_DEVICE_BASE_LISTENER_COUNT
538 for (auto *listener : this->neutral_listeners_)
539 listener->on_scan_end();
540#endif
541}
542
544 this->stop_scan_();
545 if (this->scan_start_fail_count_ == std::numeric_limits<uint8_t>::max()) {
546 ESP_LOGE(TAG, "Scan could not restart after %d attempts, rebooting to restore stack (IDF)",
547 std::numeric_limits<uint8_t>::max());
548 App.reboot();
549 }
550 if (this->scan_start_failed_) {
551 ESP_LOGE(TAG, "Scan start failed: %d", this->scan_start_failed_);
552 this->scan_start_failed_ = ESP_BT_STATUS_SUCCESS;
553 }
554 if (this->scan_set_param_failed_) {
555 ESP_LOGE(TAG, "Scan set param failed: %d", this->scan_set_param_failed_);
556 this->scan_set_param_failed_ = ESP_BT_STATUS_SUCCESS;
557 }
558}
559
561 // Only promote the first discovered client to avoid multiple simultaneous connections
562#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT
563 for (auto *client : this->clients_) {
564 if (client->state() != ClientState::DISCOVERED) {
565 continue;
566 }
567
568 if (this->scanner_state_ == ScannerState::RUNNING) {
569 ESP_LOGD(TAG, "Stopping scan to make connection");
570 this->stop_scan_();
571 // Don't wait for scan stop complete - promote immediately.
572 // This is safe because ESP-IDF processes BLE commands sequentially through its internal mailbox queue.
573 // This guarantees that the stop scan command will be fully processed before any subsequent connect command,
574 // preventing race conditions or overlapping operations.
575 }
576
577 ESP_LOGD(TAG, "Promoting client to connect");
578 // A connect ends the scan period a window-change restart was continuing.
579 this->skip_next_scan_end_ = false;
580#ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE
581 this->update_coex_preference_(true);
582#endif
583 client->connect();
584 break;
585 }
586#endif
587}
588
590 switch (state) {
591 case ScannerState::IDLE:
592 return "IDLE";
593 case ScannerState::STARTING:
594 return "STARTING";
595 case ScannerState::RUNNING:
596 return "RUNNING";
597 case ScannerState::STOPPING:
598 return "STOPPING";
599 case ScannerState::FAILED:
600 return "FAILED";
601 default:
602 return "UNKNOWN";
603 }
604}
605
606void ESP32BLETracker::log_unexpected_state_(const char *operation, ScannerState expected_state) const {
607 ESP_LOGE(TAG, "Unexpected state: %s on %s, expected: %s", this->scanner_state_to_string_(this->scanner_state_),
608 operation, this->scanner_state_to_string_(expected_state));
609}
610
611#ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE
613#ifndef CONFIG_ESP_HOSTED_ENABLE_BT_BLUEDROID
614 if (force_ble && !this->coex_prefer_ble_) {
615 ESP_LOGD(TAG, "Setting coexistence to Bluetooth to make connection.");
616 this->coex_prefer_ble_ = true;
617 esp_coex_preference_set(ESP_COEX_PREFER_BT); // Prioritize Bluetooth
618 } else if (!force_ble && this->coex_prefer_ble_) {
619 ESP_LOGD(TAG, "Setting coexistence preference to balanced.");
620 this->coex_prefer_ble_ = false;
621 esp_coex_preference_set(ESP_COEX_PREFER_BALANCE); // Reset to default
622 }
623#endif // CONFIG_ESP_HOSTED_ENABLE_BT_BLUEDROID
624}
625#endif
626
627} // namespace esphome::esp32_ble_tracker
628
629#endif // USE_ESP32
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.
void mark_failed()
Mark this component as failed.
void clear()
Reset the per-period dedup list (call when a scan period ends).
Definition ble_device.h:274
void log_device(const char *tag, const ESPBTDevice &device)
Log the device at DEBUG the first time its MAC is seen this scan period.
void parse_scan_rst(const esp32_ble::BLEScanResult &scan_result)
Historical esp32 ingest (esp32 builds only): parse an ESP-IDF scan result.
void try_promote_discovered_clients_()
Try to promote discovered clients to ready to connect.
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
uint8_t state_version_
Version counter for loop() fast-path optimization.
StaticVector< ESPBTClient *, ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT > clients_
void notify_scan_end_()
Fire on_scan_end on every listener unless a window-change restart suppressed it.
void gap_scan_stop_complete_(const esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param &param)
Called when a ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT event is received.
ClientStateCounts count_client_states_() const
Count clients in each state.
ble_device_base::ScannerStateCallback scanner_state_callback_
uint8_t last_processed_version_
Last state_version_ value when loop() did full processing.
void gap_scan_event_handler(const BLEScanResult &scan_result)
bool skip_next_scan_end_
Suppress the window-change restart's on_scan_end sweeps (stop and start).
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param)
ble_device_base::RawAdvertisementCallback raw_advertisement_callback_
uint32_t desired_scan_window_(uint8_t active) const
The window to scan at for the given number of active GATT connections.
esp_ble_scan_params_t scan_params_
A structure holding the ESP BLE scan parameters.
StaticVector< ESPBTDeviceListener *, ESPHOME_ESP32_BLE_TRACKER_LISTENER_COUNT > listeners_
void register_listener(ESPBTDeviceListener *listener)
uint32_t scan_timeout_ms_
Precomputed timeout value: scan_duration_ * 2000.
void update_coex_preference_(bool force_ble)
Update BLE coexistence preference.
const char * scanner_state_to_string_(ScannerState state) const
Convert scanner state enum to string for logging.
void gap_scan_set_param_complete_(const esp_ble_gap_cb_param_t::ble_scan_param_cmpl_evt_param &param)
Called when a ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT event is received.
uint32_t scan_duration_
The interval in seconds to perform scans.
uint32_t connection_scan_window_
Window used while a GATT connection is active; set by the user, or defaulted when the window was rais...
void setup() override
Setup the FreeRTOS task and the Bluetooth stack.
void handle_scanner_failure_()
Handle scanner failure states.
void cleanup_scan_state_(bool is_stop_complete)
Common cleanup logic when transitioning scanner to IDLE state.
ble_device_base::DiscoveredDeviceLog discovered_log_
Per-period "Found device" DEBUG log with MAC dedup (shared ble_device_base impl)
void set_scanner_state_(ScannerState state)
Called to set the scanner state. Will also call callbacks to let listeners know when state is changed...
void print_bt_device_info(const ESPBTDevice &device)
StaticVector< ble_device_base::ESPBTDeviceListener *, ESPHOME_BLE_DEVICE_BASE_LISTENER_COUNT > neutral_listeners_
void process_scan_result_(const BLEScanResult &scan_result)
Process a single scan result immediately.
void gap_scan_start_complete_(const esp_ble_gap_cb_param_t::ble_scan_start_cmpl_evt_param &param)
Called when a ESP_GAP_BLE_SCAN_START_COMPLETE_EVT event is received.
void log_unexpected_state_(const char *operation, ScannerState expected_state) const
Log an unexpected scanner state.
bool stop_scan_()
Returns true when a stop was issued to the controller.
void on_ota_global_state(ota::OTAState state, float progress, uint8_t error, ota::OTAComponent *comp) override
void start_scan_(bool first)
Start a single scan by setting up the parameters and doing some esp-idf calls.
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 bool wants_parsed_advertisements()
False keeps the tracker from building parsed ESPBTDevice objects on this client's account (raw consum...
void add_global_state_listener(OTAGlobalStateListener *listener)
bool state
Definition fan.h:2
ScannerState
Scanner lifecycle, wire-value aligned with the api enum so consumers cast directly (pinned by static_...
Definition ble_hub.h:53
ESP32BLETracker * global_esp32_ble_tracker
uint64_t ble_addr_to_uint64(const esp_bd_addr_t address)
Definition ble.h:38
OTAGlobalCallback * get_global_ota_callback()
constexpr float AFTER_BLUETOOTH
Definition component.h:49
Application App
Global storage of Application pointer - only one Application can exist.
static void uint32_t
bool is_set() const
A default-constructed slot is "no subscriber"; hubs must guard on this.
Definition ble_hub.h:47
void invoke(const RawAdvertisement &adv) const
Definition ble_hub.h:48
One raw advertisement as delivered by the controller — a borrowed view, valid only for the duration o...
Definition ble_hub.h:24
uint64_t address
Producers convert their native byte order at the emit site, so no byte-order convention crosses this ...
Definition ble_hub.h:27
void invoke(ScannerState state) const
Definition ble_hub.h:69