ESPHome 2026.11.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_BT_CONTROLLER_DISABLED
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 <cinttypes>
20
21#ifdef USE_OTA
23#endif
24
25#ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE
26#include <esp_coexist.h>
27#endif
28
29// bt_trace.h
30#undef TAG
31
33
34ESPHOME_LOG_TAG(TAG, "esp32_ble_tracker");
35
36ESP32BLETracker *global_esp32_ble_tracker = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
37
39
41 if (this->parent_->is_failed()) {
42 this->mark_failed();
43 ESP_LOGE(TAG, "BLE Tracker was marked failed by ESP32BLE");
44 return;
45 }
46
48
49#ifdef USE_OTA_STATE_LISTENER
51#endif
52}
53
54#ifdef USE_OTA_STATE_LISTENER
56 if (state == ota::OTA_STARTED) {
57 ESP_LOGD(TAG, "Stopping scan for OTA");
59 this->stop_scan();
60#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT
61 for (auto *client : this->clients_) {
62 client->disconnect();
63 }
64#ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE
65 // The OTA transfer blocks the main loop, so the revert in loop() cannot run. No
66 // active-connection gate here: every client was just told to disconnect.
67 this->update_coex_preference_(false);
68#endif
69#endif
70 } else if ((state == ota::OTA_ERROR || state == ota::OTA_ABORT) && this->scan_continuous_before_ota_) {
71 this->scan_continuous_before_ota_ = false;
72 this->scan_continuous_ = true;
73 // Do not restart scanning immediately here; allow loop() to
74 // safely restart scanning once the scanner and all clients are idle.
75 }
76}
77#endif
78
80 if (!this->parent_->is_active()) {
81 return;
82 }
83 if (this->ble_was_disabled_) {
84 this->ble_was_disabled_ = false;
85 // First start after boot or after the stack came back.
86 if (this->scan_continuous_) {
87 this->start_scan();
88 }
89 }
90
91 // Check for scan timeout - moved here from scheduler to avoid false reboots
92 // when the loop is blocked. This must run every iteration for safety.
94 switch (this->scan_timeout_state_) {
96 // Robust time comparison that handles rollover correctly
97 // This works because unsigned arithmetic wraps around predictably
98 if ((App.get_loop_component_start_time() - this->scan_start_time_) > this->scan_timeout_ms_) {
99 // First time we've seen the timeout exceeded - wait one more loop iteration
100 // This ensures all components have had a chance to process pending events
101 // This is because esp32_ble may not have run yet and called
102 // gap_scan_event_handler yet when the loop unblocks
103 ESP_LOGW(TAG, "Scan timeout exceeded");
105 }
106 break;
107 }
109 // We've waited at least one full loop iteration, and scan is still running
110 ESP_LOGE(TAG, "Scan never terminated, rebooting");
111 App.reboot();
112 break;
114 break;
115 }
116 }
117
118 // Fast path: skip expensive client state counting and processing
119 // if no state has changed since last loop iteration.
120 //
121 // How state changes ensure we reach the code below:
122 // - handle_scanner_failure_(): scanner_state_ becomes FAILED via set_scanner_state_(), or
123 // scan_set_param_failed_ requires scanner_state_==RUNNING which can only be reached via
124 // set_scanner_state_(RUNNING) in gap_scan_start_complete_() (scan params are set during
125 // STARTING, not RUNNING, so version is always incremented before this condition is true)
126 // - start_scan_(): scanner_state_ becomes IDLE via set_scanner_state_() in cleanup_scan_state_()
127 // - try_promote_discovered_clients_(): client enters DISCOVERED via set_state(), or
128 // connecting client finishes (state change), or scanner reaches RUNNING/IDLE
129 // - connection-window restart: scan_params_ is only written in start_scan_()
130 // (which changes scanner state via set_scanner_state_()), and
131 // counts.active/disconnecting only change on client state changes
132 //
133 // All conditions that affect the logic below are tied to state changes that increment
134 // state_version_, so the fast path is safe.
135 if (this->state_version_ == this->last_processed_version_) {
136 return;
137 }
139
140 // State changed - do full processing
142 if (counts != this->client_state_counts_) {
143 this->client_state_counts_ = counts;
144 ESP_LOGD(TAG, "connecting: %d, discovered: %d, disconnecting: %d, active: %d",
145 this->client_state_counts_.connecting, this->client_state_counts_.discovered,
146 this->client_state_counts_.disconnecting, this->client_state_counts_.active);
147 }
148
149 // Scanner failure: reached when set_scanner_state_(FAILED) or scan_set_param_failed_ set
153 }
154
155#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT
156 // The programmed window no longer matches the connection state (typically
157 // the last connection dropped): restart so the right window applies now
158 // instead of at the end of the scan period. Continuous only (a user-started
159 // scan would not restart); !disconnecting matches the restart gate below.
160 if (this->scanner_state_ == ScannerState::RUNNING && this->scan_continuous_ && !counts.disconnecting &&
161 this->scan_params_.scan_window != this->desired_scan_window_(counts.active)) {
162 // Same logical scan period continues: no on_scan_end sweeps for this
163 // restart. Only armed when the stop was issued.
164 this->skip_next_scan_end_ = this->stop_scan_();
165 }
166#endif
167 /*
168
169 Avoid starting the scanner if:
170 - we are already scanning
171 - we are connecting to a device
172 - we are disconnecting from a device
173
174 Otherwise the scanner could fail to ever start again
175 and our only way to recover is to reboot.
176
177 https://github.com/espressif/esp-idf/issues/6688
178
179 */
180
181 // Start scan: reached when scanner_state_ becomes IDLE (via set_scanner_state_()) and
182 // no clients are in the transient CONNECTING / DISCOVERED / DISCONNECTING states
183 // (their state changes increment version when they finish). CONNECTED / ESTABLISHED
184 // clients do NOT block this branch — the coex revert below has its own active-count gate.
185 if (this->scanner_state_ == ScannerState::IDLE && !counts.connecting && !counts.disconnecting && !counts.discovered) {
186#ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE
187 // Only revert to BALANCE when no connections are active. Established connections
188 // continue to need PREFER_BT so peer GATT responses can reach us while WiFi traffic
189 // (advertisement upload, log streaming) competes for the shared radio. Reverting too
190 // early causes Bluedroid to time out at ~20s and synthesize status=133.
191 if (!counts.active) {
192 this->update_coex_preference_(false);
193 }
194#endif
195 if (this->scan_continuous_) {
196 this->start_scan_(false); // first = false
197 }
198 }
199 // Promote discovered clients: reached when a client's state becomes DISCOVERED (via set_state()),
200 // or when a blocking condition clears (connecting client finishes, scanner reaches RUNNING/IDLE).
201 // All these trigger state_version_ increment, so we'll process and check promotion eligibility.
202 // We check both RUNNING and IDLE states because:
203 // - RUNNING: gap_scan_event_handler initiates stop_scan_() but promotion can happen immediately
204 // - IDLE: Scanner has already stopped (naturally or by gap_scan_event_handler)
205 if (counts.discovered && !counts.connecting &&
206 (this->scanner_state_ == ScannerState::RUNNING || this->scanner_state_ == ScannerState::IDLE)) {
208 }
209}
210
212
214 // V to match the start log: the mode-switch and OTA callers narrate their
215 // reason at D themselves, and the user-facing stop action is deliberate.
216 ESP_LOGV(TAG, "Stopping scan.");
217 this->scan_continuous_ = false;
218#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT
219 // The window-change restart is abandoned with continuous scanning.
220 this->skip_next_scan_end_ = false;
221#endif
222 this->stop_scan_();
223}
224
226 // Tell the controller to stop; a scan still starting has nothing to stop yet.
228 this->stop_scan_();
229 }
230#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT
231 for (auto *client : this->clients_) {
232 client->ble_before_disabled_event_handler();
233 }
234 this->skip_next_scan_end_ = false;
235#endif
236 // The stop above never completes (stack torn down, events dropped); settle
237 // here so start_scan_() sees IDLE once the stack is back.
238 if (this->scanner_state_ != ScannerState::IDLE) {
239 this->cleanup_scan_state_(true);
240 }
241 // A failure latched by the old stack must not be handled against the next.
242 this->scan_start_failed_ = ESP_BT_STATUS_SUCCESS;
243 this->scan_set_param_failed_ = ESP_BT_STATUS_SUCCESS;
244 this->ble_was_disabled_ = true;
245}
246
249 // IDLE means there is nothing to stop; STOPPING means a stop is already in
250 // flight and will finish on its own. Neither is an error.
252 ESP_LOGE(TAG, "Cannot stop scan: %s", this->scanner_state_to_string_(this->scanner_state_));
253 }
254 return false;
255 }
256 // Reset timeout state machine when stopping scan
259 esp_err_t err = esp_ble_gap_stop_scanning();
260 if (err != ESP_OK) {
261 ESP_LOGE(TAG, "esp_ble_gap_stop_scanning failed: %d", err);
262 return false;
263 }
264 return true;
265}
266
268 if (!this->parent_->is_active()) {
269 ESP_LOGW(TAG, "Cannot start scan while ESP32BLE is disabled.");
270 return;
271 }
272 if (this->scanner_state_ != ScannerState::IDLE) {
273 this->log_unexpected_state_("start scan", ScannerState::IDLE);
274 return;
275 }
277 ESP_LOGV(TAG, "Starting scan, set scanner state to STARTING.");
278 if (!first)
279 this->notify_scan_end_();
280#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT
281 this->skip_next_scan_end_ = false;
282#endif
283#ifdef USE_ESP32_BLE_DEVICE
284 this->discovered_log_.clear();
285#endif
286 this->scan_params_.scan_type = this->scan_active_ ? BLE_SCAN_TYPE_ACTIVE : BLE_SCAN_TYPE_PASSIVE;
287 this->scan_params_.own_addr_type = BLE_ADDR_TYPE_PUBLIC;
288 this->scan_params_.scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL;
289 this->scan_params_.scan_interval = this->scan_interval_;
290#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT
291 // Count fresh: an automation can start a scan before loop() refreshes the counts.
292 const uint32_t window = this->desired_scan_window_(this->count_client_states_().active);
293 if (window != this->scan_window_) {
294 // Guarantee the connection airtime instead of scanning wall to wall.
295 ESP_LOGV(TAG, "Connection active, using %" PRIu32 " unit scan window", window);
296 }
297#else
298 const uint32_t window = this->scan_window_;
299#endif
300 this->scan_params_.scan_window = window;
301
302 // Start timeout monitoring in loop() instead of using scheduler
303 // This prevents false reboots when the loop is blocked
305 this->scan_timeout_ms_ = this->scan_duration_ * 2000;
307
308 esp_err_t err = esp_ble_gap_set_scan_params(&this->scan_params_);
309 if (err != ESP_OK) {
310 ESP_LOGE(TAG, "esp_ble_gap_set_scan_params failed: %d", err);
311 return;
312 }
313 err = esp_ble_gap_start_scanning(this->scan_duration_);
314 if (err != ESP_OK) {
315 ESP_LOGE(TAG, "esp_ble_gap_start_scanning failed: %d", err);
316 return;
317 }
318}
319
321#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT
322 client->app_id = ++this->app_id_;
323 // Give client a pointer to our state_version_ so it can notify us of state changes.
324 // This enables loop() fast-path optimization - we skip expensive work when no state changed.
325 // Safe because ESP32BLETracker (singleton) outlives all registered clients.
327 this->clients_.push_back(client);
328 // Registration is add-only, so the flag is a monotonic OR.
329 if (client->wants_parsed_advertisements())
330 this->parse_advertisements_ = true;
331#endif
332}
333
335#ifdef ESPHOME_BLE_DEVICE_BASE_LISTENER_COUNT
336 // Neutral BLEHub path (migrated sensors): parsed-advertisement consumers only.
337 this->neutral_listeners_.push_back(listener);
338 this->parse_advertisements_ = true;
339#endif
340}
341
343#ifdef ESPHOME_ESP32_BLE_TRACKER_LISTENER_COUNT
344 listener->set_parent(this);
345 this->listeners_.push_back(listener);
346 this->parse_advertisements_ = true;
347#endif
348}
349
350void ESP32BLETracker::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
351 // Note: This handler is called from the main loop context, not directly from the BT task.
352 // The esp32_ble component queues events via enqueue_ble_event() and processes them in loop().
353 switch (event) {
354 case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT:
355 this->gap_scan_set_param_complete_(param->scan_param_cmpl);
356 break;
357 case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT:
358 this->gap_scan_start_complete_(param->scan_start_cmpl);
359 break;
360 case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT:
361 this->gap_scan_stop_complete_(param->scan_stop_cmpl);
362 break;
363 default:
364 break;
365 }
366 // Forward all events to clients (scan results are handled separately via gap_scan_event_handler)
367#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT
368 for (auto *client : this->clients_) {
369 client->gap_event_handler(event, param);
370 }
371#endif
372}
373
374void ESP32BLETracker::gap_scan_event_handler(const BLEScanResult &scan_result) {
375 // Note: This handler is called from the main loop context via esp32_ble's event queue.
376 // We process advertisements immediately instead of buffering them.
377 ESP_LOGVV(TAG, "gap_scan_result - event %d", scan_result.search_evt);
378
379 if (scan_result.search_evt == ESP_GAP_SEARCH_INQ_RES_EVT) {
380 // Process the scan result immediately
381 this->process_scan_result_(scan_result);
382 } else if (scan_result.search_evt == ESP_GAP_SEARCH_INQ_CMPL_EVT) {
383 // Scan finished on its own
385 this->log_unexpected_state_("scan complete", ScannerState::RUNNING);
386 }
387 // Scan completed naturally, perform cleanup and transition to IDLE
388 this->cleanup_scan_state_(false);
389 }
390}
391
392void ESP32BLETracker::gap_scan_set_param_complete_(const esp_ble_gap_cb_param_t::ble_scan_param_cmpl_evt_param &param) {
393 // Called from main loop context via gap_event_handler after being queued from BT task
394 ESP_LOGV(TAG, "gap_scan_set_param_complete - status %d", param.status);
395 if (param.status == ESP_BT_STATUS_DONE) {
396 this->scan_set_param_failed_ = ESP_BT_STATUS_SUCCESS;
397 } else {
398 this->scan_set_param_failed_ = param.status;
399 }
400}
401
402void ESP32BLETracker::gap_scan_start_complete_(const esp_ble_gap_cb_param_t::ble_scan_start_cmpl_evt_param &param) {
403 // Called from main loop context via gap_event_handler after being queued from BT task
404 ESP_LOGV(TAG, "gap_scan_start_complete - status %d", param.status);
405 this->scan_start_failed_ = param.status;
407 this->log_unexpected_state_("start complete", ScannerState::STARTING);
408 }
409 if (param.status == ESP_BT_STATUS_SUCCESS) {
410 this->scan_start_fail_count_ = 0;
412 } else {
414 if (this->scan_start_fail_count_ != std::numeric_limits<uint8_t>::max()) {
416 }
417 }
418}
419
420void ESP32BLETracker::gap_scan_stop_complete_(const esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param &param) {
421 // Called from main loop context via gap_event_handler after being queued from BT task
422 // This allows us to safely transition to IDLE state and perform cleanup without race conditions
423 ESP_LOGV(TAG, "gap_scan_stop_complete - status %d", param.status);
425 this->log_unexpected_state_("stop complete", ScannerState::STOPPING);
426 }
427
428 // Perform cleanup and transition to IDLE
429 this->cleanup_scan_state_(true);
430}
431
432void ESP32BLETracker::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
433 esp_ble_gattc_cb_param_t *param) {
434#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT
435 for (auto *client : this->clients_) {
436 client->gattc_event_handler(event, gattc_if, param);
437 }
438#endif
439}
440
442 this->scanner_state_ = state;
443 this->state_version_++;
444#ifdef USE_BLE_SCANNER_STATE_CALLBACK
445 if (this->scanner_state_callback_.is_set()) {
446 this->scanner_state_callback_.invoke(state);
447 }
448#endif
449}
450
452 ESP_LOGCONFIG(TAG, "BLE Tracker:");
453 ESP_LOGCONFIG(TAG,
454 " Scan Duration: %" PRIu32 " s\n"
455 " Scan Interval: %.1f ms\n"
456 " Scan Window: %.1f ms\n"
457 " Scan Type: %s\n"
458 " Continuous Scanning: %s",
459 this->scan_duration_, this->scan_interval_ * 0.625f, this->scan_window_ * 0.625f,
460 this->scan_active_ ? "ACTIVE" : "PASSIVE", YESNO(this->scan_continuous_));
461#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT
462 if (this->connection_scan_window_ != 0) {
463 ESP_LOGCONFIG(TAG, " Connection Scan Window: %.1f ms", this->connection_scan_window_ * 0.625f);
464 }
465#endif
466 ESP_LOGCONFIG(TAG,
467 " Scanner State: %s\n"
468 " Connecting: %d, discovered: %d, disconnecting: %d, active: %d",
470 this->client_state_counts_.discovered, this->client_state_counts_.disconnecting,
471 this->client_state_counts_.active);
472 if (this->scan_start_fail_count_) {
473 ESP_LOGCONFIG(TAG, " Scan Start Fail Count: %d", this->scan_start_fail_count_);
474 }
475}
476
477#ifdef USE_ESP32_BLE_DEVICE
479 // Shared implementation in ble_device_base — identical output and per-period
480 // MAC dedup on every tracker backend.
481 this->discovered_log_.log_device(TAG, device);
482}
483
484// resolve_irk() is provided by ble_device_base (portable software AES).
485
486#endif // USE_ESP32_BLE_DEVICE
487
488void ESP32BLETracker::process_scan_result_(const BLEScanResult &scan_result) {
489 // Neutral raw-advertisement subscriber (the bluetooth_proxy path).
492 adv.address = esp32_ble::ble_addr_to_uint64(scan_result.bda);
493 adv.data = scan_result.ble_adv;
494 adv.data_len = static_cast<uint16_t>(scan_result.adv_data_len) + scan_result.scan_rsp_len;
495 adv.rssi = scan_result.rssi;
496 adv.addr_type = scan_result.ble_addr_type;
498 }
499
500 // Process parsed advertisements
501 if (this->parse_advertisements_) {
502#ifdef USE_ESP32_BLE_DEVICE
503 ESPBTDevice device;
504 // The historical ingest keeps the raw scan-result fields populated for
505 // external components.
506 device.parse_scan_rst(scan_result);
507
508 bool found = false;
509#ifdef ESPHOME_ESP32_BLE_TRACKER_LISTENER_COUNT
510 for (auto *listener : this->listeners_) {
511 if (listener->parse_device(device))
512 found = true;
513 }
514#endif
515#ifdef ESPHOME_BLE_DEVICE_BASE_LISTENER_COUNT
516 for (auto *listener : this->neutral_listeners_) {
517 if (listener->parse_device(device))
518 found = true;
519 }
520#endif
521
522#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT
523 for (auto *client : this->clients_) {
524 if (client->parse_device(device)) {
525 found = true;
526 }
527 }
528#endif
529
530 if (!found && !this->scan_continuous_) {
531 this->print_bt_device_info(device);
532 }
533#endif // USE_ESP32_BLE_DEVICE
534 }
535}
536
537void ESP32BLETracker::cleanup_scan_state_(bool is_stop_complete) {
538 ESP_LOGV(TAG, "Scan %scomplete, set scanner state to IDLE.", is_stop_complete ? "stop " : "");
539#ifdef USE_ESP32_BLE_DEVICE
540 this->discovered_log_.clear();
541#endif
542 // Reset timeout state machine instead of cancelling scheduler timeout
544
545 this->notify_scan_end_();
546
548}
549
551#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT
552 // Window-change restart continues the same scan period; the flag stays set
553 // across the stop and is cleared by the restart in start_scan_.
554 if (this->skip_next_scan_end_)
555 return;
556#endif
557#ifdef ESPHOME_ESP32_BLE_TRACKER_LISTENER_COUNT
558 for (auto *listener : this->listeners_)
559 listener->on_scan_end();
560#endif
561#ifdef ESPHOME_BLE_DEVICE_BASE_LISTENER_COUNT
562 for (auto *listener : this->neutral_listeners_)
563 listener->on_scan_end();
564#endif
565}
566
568 this->stop_scan_();
569 if (this->scan_start_fail_count_ == std::numeric_limits<uint8_t>::max()) {
570 ESP_LOGE(TAG, "Scan could not restart after %d attempts, rebooting to restore stack (IDF)",
571 std::numeric_limits<uint8_t>::max());
572 App.reboot();
573 }
574 if (this->scan_start_failed_) {
575 ESP_LOGE(TAG, "Scan start failed: %d", this->scan_start_failed_);
576 this->scan_start_failed_ = ESP_BT_STATUS_SUCCESS;
577 }
578 if (this->scan_set_param_failed_) {
579 ESP_LOGE(TAG, "Scan set param failed: %d", this->scan_set_param_failed_);
580 this->scan_set_param_failed_ = ESP_BT_STATUS_SUCCESS;
581 }
582}
583
585 // Only promote the first discovered client to avoid multiple simultaneous connections
586#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT
587 for (auto *client : this->clients_) {
588 if (client->state() != ClientState::DISCOVERED) {
589 continue;
590 }
591
593 ESP_LOGD(TAG, "Stopping scan to make connection");
594 this->stop_scan_();
595 // Don't wait for scan stop complete - promote immediately.
596 // This is safe because ESP-IDF processes BLE commands sequentially through its internal mailbox queue.
597 // This guarantees that the stop scan command will be fully processed before any subsequent connect command,
598 // preventing race conditions or overlapping operations.
599 }
600
601 ESP_LOGD(TAG, "Promoting client to connect");
602 // A connect ends the scan period a window-change restart was continuing.
603 this->skip_next_scan_end_ = false;
604#ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE
605 this->update_coex_preference_(true);
606#endif
607 client->connect();
608 break;
609 }
610#endif
611}
612
614 switch (state) {
616 return "IDLE";
618 return "STARTING";
620 return "RUNNING";
622 return "STOPPING";
624 return "FAILED";
625 default:
626 return "UNKNOWN";
627 }
628}
629
630void ESP32BLETracker::log_unexpected_state_(const char *operation, ScannerState expected_state) const {
631 ESP_LOGE(TAG, "Unexpected state: %s on %s, expected: %s", this->scanner_state_to_string_(this->scanner_state_),
632 operation, this->scanner_state_to_string_(expected_state));
633}
634
635#ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE
637#ifndef CONFIG_BT_CONTROLLER_DISABLED
638 if (force_ble && !this->coex_prefer_ble_) {
639 ESP_LOGD(TAG, "Setting coexistence to Bluetooth to make connection.");
640 this->coex_prefer_ble_ = true;
641 esp_coex_preference_set(ESP_COEX_PREFER_BT); // Prioritize Bluetooth
642 } else if (!force_ble && this->coex_prefer_ble_) {
643 ESP_LOGD(TAG, "Setting coexistence preference to balanced.");
644 this->coex_prefer_ble_ = false;
645 esp_coex_preference_set(ESP_COEX_PREFER_BALANCE); // Reset to default
646 }
647#endif // CONFIG_BT_CONTROLLER_DISABLED
648}
649#endif
650
651} // namespace esphome::esp32_ble_tracker
652
653#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
ESPHOME_LOG_TAG(TAG, "esp32_ble")
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