ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
ble.cpp
Go to the documentation of this file.
1#include "ble.h"
2
3#ifdef USE_ESP32
4
7#include "esphome/core/log.h"
9
10#ifndef CONFIG_BT_CONTROLLER_DISABLED
11#include <esp_bt.h>
12#else
14#include <cinttypes>
15extern "C" {
16#include <esp_hosted.h>
17}
18#include <eh_host_bt.h>
19#include <esp_hosted_bt_host_stack.h>
20#endif
21#include <esp_bt_device.h>
22#include <esp_bt_main.h>
23#include <esp_gap_ble_api.h>
24#include <freertos/FreeRTOS.h>
25#include <freertos/FreeRTOSConfig.h>
26#include <freertos/task.h>
27
28#ifdef USE_ARDUINO
29// Prevent Arduino from releasing BT memory at startup (esp32-hal-misc.c).
30// Without this, esp_bt_controller_init() fails with ESP_ERR_INVALID_STATE.
31extern "C" bool btInUse() { return true; } // NOLINT(readability-identifier-naming)
32#endif
33
35
36ESPHOME_LOG_TAG(TAG, "esp32_ble");
37
38#ifdef CONFIG_BT_CONTROLLER_DISABLED
39// Bringing up the remote BT controller issues synchronous RPCs to the
40// co-processor with 5 second response timeouts, and the default task watchdog
41// is also 5 seconds. If the co-processor firmware does not answer (for example
42// factory firmware without Bluetooth support), the watchdog would reboot the
43// device before the RPC could return an error, causing a boot loop. Raise the
44// watchdog for the duration of the bring-up so failures surface as error
45// returns instead. 60 seconds covers the worst case: transport reconnect
46// (up to ~20s), version preflight (1s), the controller init retry window
47// (5s of 5s RPCs), controller enable (5s) and the bluedroid host bring-up
48// over the hosted HCI transport.
49static constexpr uint32_t HOSTED_BT_WDT_TIMEOUT_MS = 60000;
50#endif
51
52// GAP event groups for deduplication across gap_event_handler and dispatch_gap_event_
53#define GAP_SCAN_COMPLETE_EVENTS \
54 case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: \
55 case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT: \
56 case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT
57
58#define GAP_ADV_COMPLETE_EVENTS \
59 case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT: \
60 case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT: \
61 case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT: \
62 case ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT: \
63 case ESP_GAP_BLE_ADV_START_COMPLETE_EVT: \
64 case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT
65
66#define GAP_SECURITY_EVENTS \
67 case ESP_GAP_BLE_AUTH_CMPL_EVT: \
68 case ESP_GAP_BLE_SEC_REQ_EVT: \
69 case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: \
70 case ESP_GAP_BLE_PASSKEY_REQ_EVT: \
71 case ESP_GAP_BLE_NC_REQ_EVT
72
74 global_ble = this;
75 // Preferences opens NVS in app_main(); a zero handle means it could not be opened, even after an erase
76 if (global_preferences->nvs_handle == 0) {
77 ESP_LOGE(TAG, "NVS unavailable");
78 this->mark_failed();
79 return;
80 }
81
82 this->state_ = BLE_COMPONENT_STATE_DISABLED;
83 if (this->enable_on_boot_) {
84 this->enable();
85 }
86}
87
88// Queue the transition for loop(). A pending transition the other way is
89// cancelled instead, since nothing was torn down or brought up yet; any other
90// state is already there or on its way.
91void ESP32BLE::request_state_(bool enable) {
92 if (enable) {
93 if (this->state_ == BLE_COMPONENT_STATE_DISABLED) {
94 this->state_ = BLE_COMPONENT_STATE_ENABLE;
95 } else if (this->state_ == BLE_COMPONENT_STATE_DISABLE) {
96 this->state_ = BLE_COMPONENT_STATE_ACTIVE;
97 }
98 } else {
99 if (this->state_ == BLE_COMPONENT_STATE_ACTIVE) {
100 this->state_ = BLE_COMPONENT_STATE_DISABLE;
101 } else if (this->state_ == BLE_COMPONENT_STATE_ENABLE) {
102 this->state_ = BLE_COMPONENT_STATE_DISABLED;
103 }
104 }
105}
106
107#ifdef USE_ESP32_BLE_ADVERTISING
109 this->advertising_init_();
110 this->advertising_ref_count_++;
111 this->advertising_refresh();
112}
113
115 if (this->advertising_ref_count_ == 0)
116 return;
117 this->advertising_ref_count_--;
118 this->advertising_refresh();
119}
120
122 if (this->advertising_ == nullptr || !this->is_active())
123 return;
124 // Advertise while any component still needs it, otherwise stop
125 if (this->advertising_ref_count_ == 0) {
126 this->advertising_->stop();
127 } else {
128 this->advertising_->start();
129 }
130}
131
132void ESP32BLE::advertising_set_service_data(const std::vector<uint8_t> &data) {
133 this->advertising_init_();
134 this->advertising_->set_service_data(data);
135 this->advertising_refresh();
136}
137
138void ESP32BLE::advertising_set_manufacturer_data(const std::vector<uint8_t> &data) {
139 this->advertising_init_();
140 this->advertising_->set_manufacturer_data(data);
141 this->advertising_refresh();
142}
143
144void ESP32BLE::advertising_set_service_data_and_name(std::span<const uint8_t> data, bool include_name) {
145 // This method atomically updates both service data and device name inclusion in BLE advertising.
146 // When include_name is true, the device name is included in the advertising packet making it
147 // visible to passive BLE scanners. When false, the name is only visible in scan response
148 // (requires active scanning). This atomic operation ensures we only restart advertising once
149 // when changing both properties, avoiding the brief gap that would occur with separate calls.
150
151 this->advertising_init_();
152
153 if (include_name) {
154 // When including name, clear service data first to avoid packet overflow
155 this->advertising_->set_service_data(std::span<const uint8_t>{});
156 this->advertising_->set_include_name(true);
157 } else {
158 // When including service data, clear name first to avoid packet overflow
159 this->advertising_->set_include_name(false);
160 this->advertising_->set_service_data(data);
161 }
162
163 this->advertising_refresh();
164}
165
166void ESP32BLE::advertising_register_raw_advertisement_callback(std::function<void(bool)> &&callback) {
167 this->advertising_init_();
168 this->advertising_->register_raw_advertisement_callback(std::move(callback));
169}
170
172 this->advertising_init_();
173 this->advertising_->add_service_uuid(uuid);
174 this->advertising_refresh();
175}
176
178 this->advertising_init_();
179 this->advertising_->remove_service_uuid(uuid);
180 this->advertising_refresh();
181}
182#endif
183
184#ifdef USE_ESP32_BLE_ADVERTISING
186 if (this->advertising_ != nullptr)
187 return;
188 this->advertising_ = new BLEAdvertising(this->advertising_cycle_time_); // NOLINT(cppcoreguidelines-owning-memory)
189
190 this->advertising_->set_scan_response(true);
191 this->advertising_->set_min_preferred_interval(0x06);
192 this->advertising_->set_appearance(this->appearance_);
193}
194#endif
195
197 esp_err_t err;
198#ifdef CONFIG_BT_CONTROLLER_DISABLED
199 watchdog::WatchdogManager wdt(HOSTED_BT_WDT_TIMEOUT_MS);
200#endif
201#ifndef CONFIG_BT_CONTROLLER_DISABLED
202 if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_ENABLED) {
203 // start bt controller
204 if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE) {
205 esp_bt_controller_config_t cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
206 err = esp_bt_controller_init(&cfg);
207 if (err != ESP_OK) {
208 ESP_LOGE(TAG, "esp_bt_controller_init failed: %s", esp_err_to_name(err));
209 return false;
210 }
211 while (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE)
212 ;
213 }
214 if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_INITED) {
215 err = esp_bt_controller_enable(ESP_BT_MODE_BLE);
216 if (err != ESP_OK) {
217 ESP_LOGE(TAG, "esp_bt_controller_enable failed: %s", esp_err_to_name(err));
218 return false;
219 }
220 }
221 if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_ENABLED) {
222 ESP_LOGE(TAG, "esp bt controller enable failed");
223 return false;
224 }
225 }
226
227 esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT);
228#else
229 if (esp_hosted_connect_to_slave() != ESP_OK) { // NOLINT
230 ESP_LOGE(TAG, "Co-processor transport failed; BLE disabled");
231 return false;
232 }
233
234 // Fast preflight (1 second RPC timeout): verifies the co-processor answers
235 // RPCs at all before the 5 second timeout BT controller RPCs below.
236 esp_hosted_coprocessor_fwver_t fw_ver{};
237 if (esp_hosted_get_coprocessor_fwversion(&fw_ver) != ESP_OK) {
238 ESP_LOGE(TAG, "Co-processor not responding; BLE disabled. Update its firmware with the esp32_hosted "
239 "update component");
240 return false;
241 }
242 ESP_LOGD(TAG, "Co-processor firmware %" PRIu32 ".%" PRIu32 ".%" PRIu32, fw_ver.major1, fw_ver.minor1, fw_ver.patch1);
243
244 // Not the library's bring-up: its retry loop can block for minutes on a silent co-processor
245 err = eh_host_bt_controller_init();
246 if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
247 ESP_LOGE(TAG,
248 "BT controller init failed; co-processor firmware %" PRIu32 ".%" PRIu32 ".%" PRIu32
249 " may lack BT support. Update it with the esp32_hosted update component; BLE disabled",
250 fw_ver.major1, fw_ver.minor1, fw_ver.patch1);
251 return false;
252 }
253 err = eh_host_bt_controller_enable();
254 if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
255 ESP_LOGE(TAG, "BT controller enable failed: %s; BLE disabled", esp_err_to_name(err));
256 return false;
257 }
258
259 esp_hosted_bt_host_stack_cfg_t bt_cfg{};
260 bt_cfg.stack = ESP_HOSTED_BT_HOST_STACK_BLUEDROID;
261 bt_cfg.bring_up_controller = false;
262 if (esp_hosted_bt_host_stack_setup(&bt_cfg) != ESP_OK) {
263 ESP_LOGE(TAG, "BT host stack setup failed; BLE disabled");
264 return false;
265 }
266#endif
267
268 err = esp_bluedroid_init();
269 if (err != ESP_OK) {
270 ESP_LOGE(TAG, "esp_bluedroid_init failed: %d", err);
271 return false;
272 }
273 err = esp_bluedroid_enable();
274 if (err != ESP_OK) {
275 ESP_LOGE(TAG, "esp_bluedroid_enable failed: %d", err);
276 return false;
277 }
278
279#ifdef ESPHOME_ESP32_BLE_GAP_EVENT_HANDLER_COUNT
280 err = esp_ble_gap_register_callback(ESP32BLE::gap_event_handler);
281 if (err != ESP_OK) {
282 ESP_LOGE(TAG, "esp_ble_gap_register_callback failed: %d", err);
283 return false;
284 }
285#endif
286
287#if defined(USE_ESP32_BLE_SERVER) && defined(ESPHOME_ESP32_BLE_GATTS_EVENT_HANDLER_COUNT)
288 err = esp_ble_gatts_register_callback(ESP32BLE::gatts_event_handler);
289 if (err != ESP_OK) {
290 ESP_LOGE(TAG, "esp_ble_gatts_register_callback failed: %d", err);
291 return false;
292 }
293#endif
294
295#if defined(USE_ESP32_BLE_CLIENT) && defined(ESPHOME_ESP32_BLE_GATTC_EVENT_HANDLER_COUNT)
296 err = esp_ble_gattc_register_callback(ESP32BLE::gattc_event_handler);
297 if (err != ESP_OK) {
298 ESP_LOGE(TAG, "esp_ble_gattc_register_callback failed: %d", err);
299 return false;
300 }
301#endif
302
303 // BLE device names are limited to 20 characters
304 // Buffer: 20 chars + null terminator
305 constexpr size_t ble_name_max_len = 21;
306 char name_buffer[ble_name_max_len];
307 const char *device_name;
308
309 if (this->name_ != nullptr) {
311 // MAC address suffix length (last 6 characters of 12-char MAC address string)
312 constexpr size_t mac_address_suffix_len = 6;
313 char mac_addr[MAC_ADDRESS_BUFFER_SIZE];
315 const char *mac_suffix_ptr = mac_addr + mac_address_suffix_len;
316 make_name_with_suffix_to(name_buffer, sizeof(name_buffer), this->name_, strlen(this->name_), '-', mac_suffix_ptr,
317 mac_address_suffix_len);
318 device_name = name_buffer;
319 } else {
320 device_name = this->name_;
321 }
322 } else {
323 const auto &app_name = App.get_name();
324 truncate_name_to(name_buffer, sizeof(name_buffer), app_name.c_str(), app_name.length(),
325 App.is_name_add_mac_suffix_enabled() ? MAC_ADDRESS_SUFFIX_WITH_SEPARATOR_LEN : 0);
326 device_name = name_buffer;
327 }
328
329 err = esp_ble_gap_set_device_name(device_name);
330 if (err != ESP_OK) {
331 ESP_LOGE(TAG, "esp_ble_gap_set_device_name failed: %d", err);
332 return false;
333 }
334
335 err = esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &(this->io_cap_), sizeof(esp_ble_io_cap_t));
336 if (err != ESP_OK) {
337 ESP_LOGE(TAG, "esp_ble_gap_set_security_param iocap_mode failed: %d", err);
338 return false;
339 }
340
341#ifdef ESPHOME_ESP32_BLE_EXTENDED_AUTH_PARAMS
342 if (this->max_key_size_) {
343 err = esp_ble_gap_set_security_param(ESP_BLE_SM_MAX_KEY_SIZE, &(this->max_key_size_), sizeof(uint8_t));
344 if (err != ESP_OK) {
345 ESP_LOGE(TAG, "esp_ble_gap_set_security_param max_key_size failed: %d", err);
346 return false;
347 }
348 }
349
350 if (this->min_key_size_) {
351 err = esp_ble_gap_set_security_param(ESP_BLE_SM_MIN_KEY_SIZE, &(this->min_key_size_), sizeof(uint8_t));
352 if (err != ESP_OK) {
353 ESP_LOGE(TAG, "esp_ble_gap_set_security_param min_key_size failed: %d", err);
354 return false;
355 }
356 }
357
358 if (this->auth_req_mode_) {
359 err = esp_ble_gap_set_security_param(ESP_BLE_SM_AUTHEN_REQ_MODE, &(this->auth_req_mode_.value()),
360 sizeof(esp_ble_auth_req_t));
361 if (err != ESP_OK) {
362 ESP_LOGE(TAG, "esp_ble_gap_set_security_param authen_req_mode failed: %d", err);
363 return false;
364 }
365 }
366#endif // ESPHOME_ESP32_BLE_EXTENDED_AUTH_PARAMS
367
368 // BLE takes some time to be fully set up, 200ms should be more than enough
369 delay(200); // NOLINT
370
371 return true;
372}
373
375#ifdef CONFIG_BT_CONTROLLER_DISABLED
376 // Same 5 second RPCs as the bring-up path; see HOSTED_BT_WDT_TIMEOUT_MS
377 watchdog::WatchdogManager wdt(HOSTED_BT_WDT_TIMEOUT_MS);
378#endif
379 esp_err_t err = esp_bluedroid_disable();
380 if (err != ESP_OK) {
381 // ESP_ERR_INVALID_STATE means Bluedroid is already disabled, which is fine
382 if (err != ESP_ERR_INVALID_STATE) {
383 ESP_LOGE(TAG, "esp_bluedroid_disable failed: %d", err);
384 return false;
385 }
386 ESP_LOGD(TAG, "Already disabled");
387 }
388 err = esp_bluedroid_deinit();
389 if (err != ESP_OK) {
390 // ESP_ERR_INVALID_STATE means Bluedroid is already deinitialized, which is fine
391 if (err != ESP_ERR_INVALID_STATE) {
392 ESP_LOGE(TAG, "esp_bluedroid_deinit failed: %d", err);
393 return false;
394 }
395 ESP_LOGD(TAG, "Already deinitialized");
396 }
397
398#ifndef CONFIG_BT_CONTROLLER_DISABLED
399 if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_IDLE) {
400 // stop bt controller
401 if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_ENABLED) {
402 err = esp_bt_controller_disable();
403 if (err != ESP_OK) {
404 ESP_LOGE(TAG, "esp_bt_controller_disable failed: %s", esp_err_to_name(err));
405 return false;
406 }
407 while (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_ENABLED)
408 ;
409 }
410 if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_INITED) {
411 err = esp_bt_controller_deinit();
412 if (err != ESP_OK) {
413 ESP_LOGE(TAG, "esp_bt_controller_deinit failed: %s", esp_err_to_name(err));
414 return false;
415 }
416 }
417 if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_IDLE) {
418 ESP_LOGE(TAG, "esp bt controller disable failed");
419 return false;
420 }
421 }
422#else
423 if (esp_hosted_bt_host_stack_teardown() != ESP_OK) {
424 ESP_LOGE(TAG, "esp_hosted_bt_host_stack_teardown failed");
425 return false;
426 }
427#endif
428 return true;
429}
430
432 if (this->state_ != BLE_COMPONENT_STATE_ACTIVE) {
433 this->loop_handle_state_transition_not_active_();
434 return;
435 }
436
437#ifdef USE_ESP32_BLE_ADVERTISING
438 if (this->advertising_ != nullptr) {
439 this->advertising_->loop();
440 }
441#endif
442
443 BLEEvent *ble_event = this->ble_events_.pop();
444 if (ble_event == nullptr)
445 return;
446
447 do {
448 switch (ble_event->type_) {
449#if defined(USE_ESP32_BLE_SERVER) && defined(ESPHOME_ESP32_BLE_GATTS_EVENT_HANDLER_COUNT)
450 case BLEEvent::GATTS: {
451 esp_gatts_cb_event_t event = ble_event->event_.gatts.gatts_event;
452 esp_gatt_if_t gatts_if = ble_event->event_.gatts.gatts_if;
453 esp_ble_gatts_cb_param_t *param = &ble_event->event_.gatts.gatts_param;
454 ESP_LOGV(TAG, "gatts_event [esp_gatt_if: %d] - %d", gatts_if, event);
455 this->gatts_event_callbacks_.call(event, gatts_if, param);
456 break;
457 }
458#endif
459#if defined(USE_ESP32_BLE_CLIENT) && defined(ESPHOME_ESP32_BLE_GATTC_EVENT_HANDLER_COUNT)
460 case BLEEvent::GATTC: {
461 esp_gattc_cb_event_t event = ble_event->event_.gattc.gattc_event;
462 esp_gatt_if_t gattc_if = ble_event->event_.gattc.gattc_if;
463 esp_ble_gattc_cb_param_t *param = &ble_event->event_.gattc.gattc_param;
464 ESP_LOGV(TAG, "gattc_event [esp_gatt_if: %d] - %d", gattc_if, event);
465 this->gattc_event_callbacks_.call(event, gattc_if, param);
466 break;
467 }
468#endif
469 case BLEEvent::GAP: {
470 esp_gap_ble_cb_event_t gap_event = ble_event->event_.gap.gap_event;
471 switch (gap_event) {
472 case ESP_GAP_BLE_SCAN_RESULT_EVT:
473#ifdef ESPHOME_ESP32_BLE_GAP_SCAN_EVENT_HANDLER_COUNT
474 this->gap_scan_event_callbacks_.call(ble_event->scan_result());
475#endif
476 break;
477
478 // Scan complete events
479 GAP_SCAN_COMPLETE_EVENTS:
480 // Advertising complete events
481 GAP_ADV_COMPLETE_EVENTS:
482 // RSSI complete event
483 case ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT:
484 // Security events
485 GAP_SECURITY_EVENTS:
486 ESP_LOGV(TAG, "gap_event_handler - %d", gap_event);
487#ifdef ESPHOME_ESP32_BLE_GAP_EVENT_HANDLER_COUNT
488 {
489 esp_ble_gap_cb_param_t *param = NULL;
490 // clang-format off
491 switch (gap_event) {
492 // All three scan complete events have the same structure with just status
493 // The scan_complete struct matches ESP-IDF's layout exactly, so this reinterpret_cast is safe
494 // This is verified at compile-time by static_assert checks in ble_event.h
495 // The struct already contains our copy of the status (copied in BLEEvent constructor)
496 GAP_SCAN_COMPLETE_EVENTS:
497 param = reinterpret_cast<esp_ble_gap_cb_param_t *>(&ble_event->event_.gap.scan_complete);
498 break;
499
500 // All advertising complete events have the same structure with just status
501 GAP_ADV_COMPLETE_EVENTS:
502 param = reinterpret_cast<esp_ble_gap_cb_param_t *>(&ble_event->event_.gap.adv_complete);
503 break;
504
505 case ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT:
506 param = reinterpret_cast<esp_ble_gap_cb_param_t *>(&ble_event->event_.gap.read_rssi_complete);
507 break;
508
509 GAP_SECURITY_EVENTS:
510 param = reinterpret_cast<esp_ble_gap_cb_param_t *>(&ble_event->event_.gap.security);
511 break;
512
513 default:
514 break;
515 }
516 // clang-format on
517 // Dispatch to all registered handlers
518 this->gap_event_callbacks_.call(gap_event, param);
519 }
520#endif
521 break;
522
523 default:
524 // Unknown/unhandled event
525 ESP_LOGW(TAG, "Unhandled GAP event type in loop: %d", gap_event);
526 break;
527 }
528 break;
529 }
530 default:
531 break;
532 }
533 // Return the event to the pool
534 this->ble_event_pool_.release(ble_event);
535 } while ((ble_event = this->ble_events_.pop()) != nullptr);
536
537 // Log dropped events - only reachable when events were processed.
538 // Drops only occur when the queue is full, and only this loop drains it,
539 // so if pop() returned nullptr above we can skip this check (saves a memw).
540 uint16_t dropped = this->ble_events_.get_and_reset_dropped_count();
541 if (dropped > 0) {
542 ESP_LOGW(TAG, "Dropped %u BLE events due to buffer overflow", dropped);
543 }
544}
545
546void ESP32BLE::loop_handle_state_transition_not_active_() {
547 // Caller ensures state_ != ACTIVE
548 if (this->state_ == BLE_COMPONENT_STATE_DISABLE) {
549 ESP_LOGD(TAG, "Disabling");
550
551#ifdef ESPHOME_ESP32_BLE_BLE_STATUS_EVENT_HANDLER_COUNT
552 this->ble_status_event_callbacks_.call();
553#endif
554
555 if (!ble_dismantle_()) {
556 ESP_LOGE(TAG, "Could not be dismantled");
557 this->mark_failed();
558 return;
559 }
560 this->drain_ble_events_();
561 // A status callback may have asked for BLE back; the stack is down now, so
562 // that request becomes a bring-up.
563 this->state_ =
565 } else if (this->state_ == BLE_COMPONENT_STATE_ENABLE) {
566 ESP_LOGD(TAG, "Enabling");
567 this->state_ = BLE_COMPONENT_STATE_OFF;
568
569 if (!ble_setup_()) {
570 ESP_LOGE(TAG, "Could not be set up");
571 this->mark_failed();
572 return;
573 }
574
575 this->state_ = BLE_COMPONENT_STATE_ACTIVE;
576#ifdef USE_ESP32_BLE_ADVERTISING
577 // Requests made before the stack was up (or before it was re-enabled) take effect now
578 this->advertising_refresh();
579#endif
580 }
581}
582
583// Helper function to load new event data based on type
584void load_ble_event(BLEEvent *event, esp_gap_ble_cb_event_t e, esp_ble_gap_cb_param_t *p) {
585 event->load_gap_event(e, p);
586}
587
588#ifdef USE_ESP32_BLE_CLIENT
589void load_ble_event(BLEEvent *event, esp_gattc_cb_event_t e, esp_gatt_if_t i, esp_ble_gattc_cb_param_t *p) {
590 event->load_gattc_event(e, i, p);
591}
592#endif
593
594#ifdef USE_ESP32_BLE_SERVER
595void load_ble_event(BLEEvent *event, esp_gatts_cb_event_t e, esp_gatt_if_t i, esp_ble_gatts_cb_param_t *p) {
596 event->load_gatts_event(e, i, p);
597}
598#endif
599
600template<typename... Args> void enqueue_ble_event(Args... args) {
601 // Allocate an event from the pool
602 BLEEvent *event = global_ble->ble_event_pool_.allocate();
603 if (event == nullptr) {
604 // No events available - queue is full or we're out of memory
605 global_ble->ble_events_.increment_dropped_count();
606 return;
607 }
608
609 // Load new event data (replaces previous event)
610 load_ble_event(event, args...);
611
612 // Push the event to the queue
613 // Push always succeeds: pool is sized to queue capacity (N-1), so if
614 // allocate() returned non-null, the queue is guaranteed to have room.
615 global_ble->ble_events_.push(event);
616}
617
618// Explicit template instantiations for the friend function
619template void enqueue_ble_event(esp_gap_ble_cb_event_t, esp_ble_gap_cb_param_t *);
620#ifdef USE_ESP32_BLE_SERVER
621template void enqueue_ble_event(esp_gatts_cb_event_t, esp_gatt_if_t, esp_ble_gatts_cb_param_t *);
622#endif
623#ifdef USE_ESP32_BLE_CLIENT
624template void enqueue_ble_event(esp_gattc_cb_event_t, esp_gatt_if_t, esp_ble_gattc_cb_param_t *);
625#endif
626
627void ESP32BLE::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
628 switch (event) {
629 // Queue GAP events that components need to handle
630 // Scanning events - used by esp32_ble_tracker
631 case ESP_GAP_BLE_SCAN_RESULT_EVT:
632 GAP_SCAN_COMPLETE_EVENTS:
633 // Advertising events - used by esp32_ble_beacon and esp32_ble server
634 GAP_ADV_COMPLETE_EVENTS:
635 // Connection events - used by ble_client
636 case ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT:
637 enqueue_ble_event(event, param);
638 return;
639
640 // Security events - used by ble_client and bluetooth_proxy
641 // These are rare but interactive (pairing/bonding), so notify immediately
642 GAP_SECURITY_EVENTS:
643 enqueue_ble_event(event, param);
644 // Wake up main loop to process security event immediately
646 return;
647
648 // Log the result of connection parameter updates: a peer can reject or
649 // never answer an update, and without this the link silently stays on the
650 // old parameters (visible only as unexplained supervision timeouts).
651 case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT: {
652 if (param->update_conn_params.status != ESP_BT_STATUS_SUCCESS) {
653 char mac_s[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
654 format_mac_addr_upper(param->update_conn_params.bda, mac_s);
655 ESP_LOGW(TAG, "[%s] Conn param update failed, status=%d", mac_s, param->update_conn_params.status);
656 }
657#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
658 else {
659 char mac_s[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
660 format_mac_addr_upper(param->update_conn_params.bda, mac_s);
661 ESP_LOGV(TAG, "[%s] Conn params updated: interval=%u (x1.25ms) latency=%u timeout=%u (x10ms)", mac_s,
662 param->update_conn_params.conn_int, param->update_conn_params.latency,
663 param->update_conn_params.timeout);
664 }
665#endif
666 return;
667 }
668
669 // Ignore these GAP events as they are not relevant for our use case
670 case ESP_GAP_BLE_KEY_EVT:
671 case ESP_GAP_BLE_SET_PKT_LENGTH_COMPLETE_EVT:
672 case ESP_GAP_BLE_PHY_UPDATE_COMPLETE_EVT: // BLE 5.0 PHY update complete
673 case ESP_GAP_BLE_CHANNEL_SELECT_ALGORITHM_EVT: // BLE 5.0 channel selection algorithm
674 case ESP_GAP_BLE_LOCAL_IR_EVT: // Local identity root key generated at security init
675 case ESP_GAP_BLE_LOCAL_ER_EVT: // Local encryption root key generated at security init
676 return;
677
678 default:
679 break;
680 }
681 ESP_LOGW(TAG, "Ignoring unexpected GAP event type: %d", event);
682}
683
684#ifdef USE_ESP32_BLE_SERVER
685void ESP32BLE::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
686 esp_ble_gatts_cb_param_t *param) {
687 enqueue_ble_event(event, gatts_if, param);
688 // Wake up main loop to process GATT event immediately
690}
691#endif
692
693#ifdef USE_ESP32_BLE_CLIENT
694void ESP32BLE::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
695 esp_ble_gattc_cb_param_t *param) {
696 enqueue_ble_event(event, gattc_if, param);
697 // Wake up main loop to process GATT event immediately
699}
700#endif
701
702void ESP32BLE::get_mac_msb_first(uint8_t out[MAC_ADDRESS_SIZE]) const {
703 // The running stack owns the address (on hosted controllers it lives in
704 // the remote chip's efuse); null before init becomes all-zero.
705 const uint8_t *mac = esp_bt_dev_get_address();
706 if (mac != nullptr) {
707 memcpy(out, mac, MAC_ADDRESS_SIZE);
708 } else {
709 memset(out, 0, MAC_ADDRESS_SIZE);
710 }
711}
712
714
716 uint8_t mac_address[MAC_ADDRESS_SIZE];
717 this->get_mac_msb_first(mac_address);
718 if (mac_address_is_valid(mac_address)) {
719 const char *io_capability_s;
720 switch (this->io_cap_) {
721 case ESP_IO_CAP_OUT:
722 io_capability_s = "display_only";
723 break;
724 case ESP_IO_CAP_IO:
725 io_capability_s = "display_yes_no";
726 break;
727 case ESP_IO_CAP_IN:
728 io_capability_s = "keyboard_only";
729 break;
730 case ESP_IO_CAP_NONE:
731 io_capability_s = "none";
732 break;
733 case ESP_IO_CAP_KBDISP:
734 io_capability_s = "keyboard_display";
735 break;
736 default:
737 io_capability_s = "invalid";
738 break;
739 }
740
741 char mac_s[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
742 format_mac_addr_upper(mac_address, mac_s);
743 ESP_LOGCONFIG(TAG,
744 "BLE:\n"
745 " MAC address: %s\n"
746 " IO Capability: %s",
747 mac_s, io_capability_s);
748#ifdef USE_ESP32_BLE_PSRAM
749 ESP_LOGCONFIG(TAG, " PSRAM BLE allocation: enabled");
750#endif
751
752#ifdef ESPHOME_ESP32_BLE_EXTENDED_AUTH_PARAMS
753 const char *auth_req_mode_s = "<default>";
754 if (this->auth_req_mode_) {
755 switch (this->auth_req_mode_.value()) {
756 case AUTH_REQ_NO_BOND:
757 auth_req_mode_s = "no_bond";
758 break;
759 case AUTH_REQ_BOND:
760 auth_req_mode_s = "bond";
761 break;
762 case AUTH_REQ_MITM:
763 auth_req_mode_s = "mitm";
764 break;
766 auth_req_mode_s = "bond_mitm";
767 break;
768 case AUTH_REQ_SC_ONLY:
769 auth_req_mode_s = "sc_only";
770 break;
771 case AUTH_REQ_SC_BOND:
772 auth_req_mode_s = "sc_bond";
773 break;
774 case AUTH_REQ_SC_MITM:
775 auth_req_mode_s = "sc_mitm";
776 break;
778 auth_req_mode_s = "sc_mitm_bond";
779 break;
780 }
781 }
782
783 ESP_LOGCONFIG(TAG, " Auth Req Mode: %s", auth_req_mode_s);
784 if (this->max_key_size_ && this->min_key_size_) {
785 ESP_LOGCONFIG(TAG, " Key Size: %u - %u", this->min_key_size_, this->max_key_size_);
786 } else if (this->max_key_size_) {
787 ESP_LOGCONFIG(TAG, " Key Size: <default> - %u", this->max_key_size_);
788 } else if (this->min_key_size_) {
789 ESP_LOGCONFIG(TAG, " Key Size: %u - <default>", this->min_key_size_);
790 }
791#endif // ESPHOME_ESP32_BLE_EXTENDED_AUTH_PARAMS
792
793 } else {
794 ESP_LOGCONFIG(TAG, "Bluetooth stack is not enabled");
795 }
796}
797
798ESP32BLE *global_ble = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
799
800} // namespace esphome::esp32_ble
801
802#endif
bool btInUse()
Definition ble.cpp:31
const StringRef & get_name() const
Get the name of this Application set by pre_setup().
void wake_loop_threadsafe()
Wake the main event loop from another thread or callback.
bool is_name_add_mac_suffix_enabled() const
void mark_failed()
Mark this component as failed.
void set_manufacturer_data(const std::vector< uint8_t > &data)
void set_scan_response(bool scan_response)
void set_include_name(bool include_name)
void set_min_preferred_interval(uint16_t interval)
void set_service_data(const std::vector< uint8_t > &data)
void register_raw_advertisement_callback(std::function< void(bool)> &&callback)
void set_appearance(uint16_t appearance)
struct esphome::esp32_ble::BLEEvent::@79::gap_event gap
struct esphome::esp32_ble::BLEEvent::@79::gatts_event gatts
union esphome::esp32_ble::BLEEvent::@79 event_
struct esphome::esp32_ble::BLEEvent::@79::gattc_event gattc
void advertising_set_manufacturer_data(const std::vector< uint8_t > &data)
Definition ble.cpp:138
void get_mac_msb_first(uint8_t out[MAC_ADDRESS_SIZE]) const
Adapter MAC in printable (MSB-first) order; all-zero until the stack is up.
Definition ble.cpp:702
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
Definition ble.cpp:627
static void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param)
Definition ble.cpp:694
void advertising_start()
Request advertising on behalf of a component.
Definition ble.cpp:108
void advertising_refresh()
Apply the current payload and request count: advertise while requested, otherwise stop.
Definition ble.cpp:121
friend void enqueue_ble_event(Args... args)
Definition ble.cpp:600
void advertising_register_raw_advertisement_callback(std::function< void(bool)> &&callback)
Definition ble.cpp:166
void advertising_set_service_data_and_name(std::span< const uint8_t > data, bool include_name)
Definition ble.cpp:144
void advertising_add_service_uuid(ESPBTUUID uuid)
Definition ble.cpp:171
void dump_config() override
Definition ble.cpp:715
void request_state_(bool enable)
Definition ble.cpp:91
void loop() override
Definition ble.cpp:431
static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
Definition ble.cpp:685
void advertising_set_service_data(const std::vector< uint8_t > &data)
Definition ble.cpp:132
ESPHOME_ALWAYS_INLINE bool is_active()
Definition ble.h:107
float get_setup_priority() const override
Definition ble.cpp:713
void setup() override
Definition ble.cpp:73
void advertising_stop()
Release a request made with advertising_start(); advertising stops at the last release.
Definition ble.cpp:114
void advertising_remove_service_uuid(ESPBTUUID uuid)
Definition ble.cpp:177
std::vector< uint8_t > & out
Definition gif.cpp:49
ESP32BLE * global_ble
Definition ble.cpp:798
void load_ble_event(BLEEvent *event, esp_gap_ble_cb_event_t e, esp_ble_gap_cb_param_t *p)
Definition ble.cpp:584
@ BLE_COMPONENT_STATE_DISABLE
BLE should be disabled on next loop.
Definition ble.h:81
@ BLE_COMPONENT_STATE_OFF
Nothing has been initialized yet.
Definition ble.h:79
@ BLE_COMPONENT_STATE_ENABLE
BLE should be enabled on next loop.
Definition ble.h:85
@ BLE_COMPONENT_STATE_DISABLED
BLE is disabled.
Definition ble.h:83
@ BLE_COMPONENT_STATE_ACTIVE
BLE is active.
Definition ble.h:87
@ AUTH_REQ_SC_MITM_BOND
Definition ble.h:73
@ AUTH_REQ_BOND_MITM
Definition ble.h:69
void enqueue_ble_event(Args... args)
Definition ble.cpp:600
ESPHOME_LOG_TAG(TAG, "esp32_ble")
constexpr float BLUETOOTH
Definition component.h:48
size_t make_name_with_suffix_to(char *buffer, size_t buffer_size, const char *name, size_t name_len, char sep, const char *suffix_ptr, size_t suffix_len)
Format name + separator + suffix directly into buffer without heap allocation.
Definition helpers.cpp:277
size_t truncate_name_to(char *buffer, size_t buffer_size, const char *name, size_t name_len, size_t keep_suffix_len)
Copy a name into a buffer, cutting it when it does not fit.
Definition helpers.cpp:297
bool mac_address_is_valid(const uint8_t *mac)
Check if the MAC address is not all zeros or all ones.
Definition helpers.cpp:865
ESPPreferences * global_preferences
void get_mac_address_into_buffer(std::span< char, MAC_ADDRESS_BUFFER_SIZE > buf)
Get the device MAC address into the given buffer, in lowercase hex notation.
Definition helpers.cpp:848
void HOT delay(uint32_t ms)
Definition hal.cpp:85
Application App
Global storage of Application pointer - only one Application can exist.
char * format_mac_addr_upper(const uint8_t *mac, char *output)
Format MAC address as XX:XX:XX:XX:XX:XX (uppercase, colon separators)
Definition helpers.h:1676
static void uint32_t