ESPHome 2026.7.0
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"
8
9#ifndef CONFIG_ESP_HOSTED_ENABLE_BT_BLUEDROID
10#include <esp_bt.h>
11#else
13#include <cinttypes>
14extern "C" {
15#include <esp_hosted.h>
16#include <esp_hosted_misc.h>
17#include <esp_hosted_bluedroid.h>
18}
19#endif
20#include <esp_bt_device.h>
21#include <esp_bt_main.h>
22#include <esp_gap_ble_api.h>
23#include <freertos/FreeRTOS.h>
24#include <freertos/FreeRTOSConfig.h>
25#include <freertos/task.h>
26#include <nvs_flash.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
36static const char *const TAG = "esp32_ble";
37
38#ifdef CONFIG_ESP_HOSTED_ENABLE_BT_BLUEDROID
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), controller init/enable (5s each) and
47// the bluedroid host bring-up over the hosted HCI transport.
48static constexpr uint32_t HOSTED_BT_WDT_TIMEOUT_MS = 60000;
49#endif
50
51// GAP event groups for deduplication across gap_event_handler and dispatch_gap_event_
52#define GAP_SCAN_COMPLETE_EVENTS \
53 case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: \
54 case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT: \
55 case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT
56
57#define GAP_ADV_COMPLETE_EVENTS \
58 case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT: \
59 case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT: \
60 case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT: \
61 case ESP_GAP_BLE_ADV_START_COMPLETE_EVT: \
62 case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT
63
64#define GAP_SECURITY_EVENTS \
65 case ESP_GAP_BLE_AUTH_CMPL_EVT: \
66 case ESP_GAP_BLE_SEC_REQ_EVT: \
67 case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: \
68 case ESP_GAP_BLE_PASSKEY_REQ_EVT: \
69 case ESP_GAP_BLE_NC_REQ_EVT
70
72 global_ble = this;
73 if (!ble_pre_setup_()) {
74 ESP_LOGE(TAG, "BLE could not be prepared for configuration");
75 this->mark_failed();
76 return;
77 }
78
79 this->state_ = BLE_COMPONENT_STATE_DISABLED;
80 if (this->enable_on_boot_) {
81 this->enable();
82 }
83}
84
86 if (this->state_ != BLE_COMPONENT_STATE_DISABLED)
87 return;
88
89 this->state_ = BLE_COMPONENT_STATE_ENABLE;
90}
91
93 if (this->state_ == BLE_COMPONENT_STATE_DISABLED)
94 return;
95
96 this->state_ = BLE_COMPONENT_STATE_DISABLE;
97}
98
99#ifdef USE_ESP32_BLE_ADVERTISING
101 this->advertising_init_();
102 if (!this->is_active())
103 return;
104 this->advertising_->start();
105}
106
107void ESP32BLE::advertising_set_service_data(const std::vector<uint8_t> &data) {
108 this->advertising_init_();
109 this->advertising_->set_service_data(data);
110 this->advertising_start();
111}
112
113void ESP32BLE::advertising_set_manufacturer_data(const std::vector<uint8_t> &data) {
114 this->advertising_init_();
115 this->advertising_->set_manufacturer_data(data);
116 this->advertising_start();
117}
118
119void ESP32BLE::advertising_set_service_data_and_name(std::span<const uint8_t> data, bool include_name) {
120 // This method atomically updates both service data and device name inclusion in BLE advertising.
121 // When include_name is true, the device name is included in the advertising packet making it
122 // visible to passive BLE scanners. When false, the name is only visible in scan response
123 // (requires active scanning). This atomic operation ensures we only restart advertising once
124 // when changing both properties, avoiding the brief gap that would occur with separate calls.
125
126 this->advertising_init_();
127
128 if (include_name) {
129 // When including name, clear service data first to avoid packet overflow
130 this->advertising_->set_service_data(std::span<const uint8_t>{});
131 this->advertising_->set_include_name(true);
132 } else {
133 // When including service data, clear name first to avoid packet overflow
134 this->advertising_->set_include_name(false);
135 this->advertising_->set_service_data(data);
136 }
137
138 this->advertising_start();
139}
140
141void ESP32BLE::advertising_register_raw_advertisement_callback(std::function<void(bool)> &&callback) {
142 this->advertising_init_();
143 this->advertising_->register_raw_advertisement_callback(std::move(callback));
144}
145
147 this->advertising_init_();
148 this->advertising_->add_service_uuid(uuid);
149 this->advertising_start();
150}
151
153 this->advertising_init_();
154 this->advertising_->remove_service_uuid(uuid);
155 this->advertising_start();
156}
157#endif
158
160 esp_err_t err = nvs_flash_init();
161 if (err != ESP_OK) {
162 ESP_LOGE(TAG, "nvs_flash_init failed: %d", err);
163 return false;
164 }
165 return true;
166}
167
168#ifdef USE_ESP32_BLE_ADVERTISING
170 if (this->advertising_ != nullptr)
171 return;
172 this->advertising_ = new BLEAdvertising(this->advertising_cycle_time_); // NOLINT(cppcoreguidelines-owning-memory)
173
174 this->advertising_->set_scan_response(true);
175 this->advertising_->set_min_preferred_interval(0x06);
176 this->advertising_->set_appearance(this->appearance_);
177}
178#endif
179
181 esp_err_t err;
182#ifdef CONFIG_ESP_HOSTED_ENABLE_BT_BLUEDROID
183 watchdog::WatchdogManager wdt(HOSTED_BT_WDT_TIMEOUT_MS);
184#endif
185#ifndef CONFIG_ESP_HOSTED_ENABLE_BT_BLUEDROID
186 if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_ENABLED) {
187 // start bt controller
188 if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE) {
189 esp_bt_controller_config_t cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
190 err = esp_bt_controller_init(&cfg);
191 if (err != ESP_OK) {
192 ESP_LOGE(TAG, "esp_bt_controller_init failed: %s", esp_err_to_name(err));
193 return false;
194 }
195 while (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE)
196 ;
197 }
198 if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_INITED) {
199 err = esp_bt_controller_enable(ESP_BT_MODE_BLE);
200 if (err != ESP_OK) {
201 ESP_LOGE(TAG, "esp_bt_controller_enable failed: %s", esp_err_to_name(err));
202 return false;
203 }
204 }
205 if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_ENABLED) {
206 ESP_LOGE(TAG, "esp bt controller enable failed");
207 return false;
208 }
209 }
210
211 esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT);
212#else
213 if (esp_hosted_connect_to_slave() != ESP_OK) { // NOLINT
214 ESP_LOGE(TAG, "Co-processor transport failed; BLE disabled");
215 return false;
216 }
217
218 // Fast preflight (1 second RPC timeout): verifies the co-processor answers
219 // RPCs at all before the 5 second timeout BT controller RPCs below, and
220 // before hosted_hci_bluedroid_open(), which aborts if the transport is down.
221 esp_hosted_coprocessor_fwver_t fw_ver{};
222 if (esp_hosted_get_coprocessor_fwversion(&fw_ver) != ESP_OK) {
223 ESP_LOGE(TAG, "Co-processor not responding; BLE disabled. Update its firmware with the esp32_hosted "
224 "update component");
225 return false;
226 }
227 ESP_LOGD(TAG, "Co-processor firmware %" PRIu32 ".%" PRIu32 ".%" PRIu32, fw_ver.major1, fw_ver.minor1, fw_ver.patch1);
228
229 if (esp_hosted_bt_controller_init() != ESP_OK) {
230 ESP_LOGE(TAG,
231 "BT controller init failed; co-processor firmware %" PRIu32 ".%" PRIu32 ".%" PRIu32
232 " may lack BT support. Update it with the esp32_hosted update component; BLE disabled",
233 fw_ver.major1, fw_ver.minor1, fw_ver.patch1);
234 return false;
235 }
236
237 if (esp_hosted_bt_controller_enable() != ESP_OK) {
238 ESP_LOGE(TAG,
239 "BT controller enable failed; co-processor firmware %" PRIu32 ".%" PRIu32 ".%" PRIu32
240 " may lack BT support. Update it with the esp32_hosted update component; BLE disabled",
241 fw_ver.major1, fw_ver.minor1, fw_ver.patch1);
242 return false;
243 }
244
245 hosted_hci_bluedroid_open();
246
247 esp_bluedroid_hci_driver_operations_t operations = {
248 .send = hosted_hci_bluedroid_send,
249 .check_send_available = hosted_hci_bluedroid_check_send_available,
250 .register_host_callback = hosted_hci_bluedroid_register_host_callback,
251 };
252 esp_bluedroid_attach_hci_driver(&operations);
253#endif
254
255 err = esp_bluedroid_init();
256 if (err != ESP_OK) {
257 ESP_LOGE(TAG, "esp_bluedroid_init failed: %d", err);
258 return false;
259 }
260 err = esp_bluedroid_enable();
261 if (err != ESP_OK) {
262 ESP_LOGE(TAG, "esp_bluedroid_enable failed: %d", err);
263 return false;
264 }
265
266#ifdef ESPHOME_ESP32_BLE_GAP_EVENT_HANDLER_COUNT
267 err = esp_ble_gap_register_callback(ESP32BLE::gap_event_handler);
268 if (err != ESP_OK) {
269 ESP_LOGE(TAG, "esp_ble_gap_register_callback failed: %d", err);
270 return false;
271 }
272#endif
273
274#if defined(USE_ESP32_BLE_SERVER) && defined(ESPHOME_ESP32_BLE_GATTS_EVENT_HANDLER_COUNT)
275 err = esp_ble_gatts_register_callback(ESP32BLE::gatts_event_handler);
276 if (err != ESP_OK) {
277 ESP_LOGE(TAG, "esp_ble_gatts_register_callback failed: %d", err);
278 return false;
279 }
280#endif
281
282#if defined(USE_ESP32_BLE_CLIENT) && defined(ESPHOME_ESP32_BLE_GATTC_EVENT_HANDLER_COUNT)
283 err = esp_ble_gattc_register_callback(ESP32BLE::gattc_event_handler);
284 if (err != ESP_OK) {
285 ESP_LOGE(TAG, "esp_ble_gattc_register_callback failed: %d", err);
286 return false;
287 }
288#endif
289
290 // BLE device names are limited to 20 characters
291 // Buffer: 20 chars + null terminator
292 constexpr size_t ble_name_max_len = 21;
293 char name_buffer[ble_name_max_len];
294 const char *device_name;
295
296 if (this->name_ != nullptr) {
298 // MAC address suffix length (last 6 characters of 12-char MAC address string)
299 constexpr size_t mac_address_suffix_len = 6;
300 char mac_addr[MAC_ADDRESS_BUFFER_SIZE];
302 const char *mac_suffix_ptr = mac_addr + mac_address_suffix_len;
303 make_name_with_suffix_to(name_buffer, sizeof(name_buffer), this->name_, strlen(this->name_), '-', mac_suffix_ptr,
304 mac_address_suffix_len);
305 device_name = name_buffer;
306 } else {
307 device_name = this->name_;
308 }
309 } else {
310 const auto &app_name = App.get_name();
311 size_t name_len = app_name.length();
312 if (name_len > 20) {
314 // Keep first 13 chars and last 7 chars (MAC suffix), remove middle
315 memcpy(name_buffer, app_name.c_str(), 13);
316 memcpy(name_buffer + 13, app_name.c_str() + name_len - 7, 7);
317 } else {
318 memcpy(name_buffer, app_name.c_str(), 20);
319 }
320 name_buffer[20] = '\0';
321 } else {
322 memcpy(name_buffer, app_name.c_str(), name_len + 1); // Include null terminator
323 }
324 device_name = name_buffer;
325 }
326
327 err = esp_ble_gap_set_device_name(device_name);
328 if (err != ESP_OK) {
329 ESP_LOGE(TAG, "esp_ble_gap_set_device_name failed: %d", err);
330 return false;
331 }
332
333 err = esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &(this->io_cap_), sizeof(esp_ble_io_cap_t));
334 if (err != ESP_OK) {
335 ESP_LOGE(TAG, "esp_ble_gap_set_security_param iocap_mode failed: %d", err);
336 return false;
337 }
338
339#ifdef ESPHOME_ESP32_BLE_EXTENDED_AUTH_PARAMS
340 if (this->max_key_size_) {
341 err = esp_ble_gap_set_security_param(ESP_BLE_SM_MAX_KEY_SIZE, &(this->max_key_size_), sizeof(uint8_t));
342 if (err != ESP_OK) {
343 ESP_LOGE(TAG, "esp_ble_gap_set_security_param max_key_size failed: %d", err);
344 return false;
345 }
346 }
347
348 if (this->min_key_size_) {
349 err = esp_ble_gap_set_security_param(ESP_BLE_SM_MIN_KEY_SIZE, &(this->min_key_size_), sizeof(uint8_t));
350 if (err != ESP_OK) {
351 ESP_LOGE(TAG, "esp_ble_gap_set_security_param min_key_size failed: %d", err);
352 return false;
353 }
354 }
355
356 if (this->auth_req_mode_) {
357 err = esp_ble_gap_set_security_param(ESP_BLE_SM_AUTHEN_REQ_MODE, &(this->auth_req_mode_.value()),
358 sizeof(esp_ble_auth_req_t));
359 if (err != ESP_OK) {
360 ESP_LOGE(TAG, "esp_ble_gap_set_security_param authen_req_mode failed: %d", err);
361 return false;
362 }
363 }
364#endif // ESPHOME_ESP32_BLE_EXTENDED_AUTH_PARAMS
365
366 // BLE takes some time to be fully set up, 200ms should be more than enough
367 delay(200); // NOLINT
368
369 return true;
370}
371
373#ifdef CONFIG_ESP_HOSTED_ENABLE_BT_BLUEDROID
374 // Same 5 second RPCs as the bring-up path; see HOSTED_BT_WDT_TIMEOUT_MS
375 watchdog::WatchdogManager wdt(HOSTED_BT_WDT_TIMEOUT_MS);
376#endif
377 esp_err_t err = esp_bluedroid_disable();
378 if (err != ESP_OK) {
379 // ESP_ERR_INVALID_STATE means Bluedroid is already disabled, which is fine
380 if (err != ESP_ERR_INVALID_STATE) {
381 ESP_LOGE(TAG, "esp_bluedroid_disable failed: %d", err);
382 return false;
383 }
384 ESP_LOGD(TAG, "Already disabled");
385 }
386 err = esp_bluedroid_deinit();
387 if (err != ESP_OK) {
388 // ESP_ERR_INVALID_STATE means Bluedroid is already deinitialized, which is fine
389 if (err != ESP_ERR_INVALID_STATE) {
390 ESP_LOGE(TAG, "esp_bluedroid_deinit failed: %d", err);
391 return false;
392 }
393 ESP_LOGD(TAG, "Already deinitialized");
394 }
395
396#ifndef CONFIG_ESP_HOSTED_ENABLE_BT_BLUEDROID
397 if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_IDLE) {
398 // stop bt controller
399 if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_ENABLED) {
400 err = esp_bt_controller_disable();
401 if (err != ESP_OK) {
402 ESP_LOGE(TAG, "esp_bt_controller_disable failed: %s", esp_err_to_name(err));
403 return false;
404 }
405 while (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_ENABLED)
406 ;
407 }
408 if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_INITED) {
409 err = esp_bt_controller_deinit();
410 if (err != ESP_OK) {
411 ESP_LOGE(TAG, "esp_bt_controller_deinit failed: %s", esp_err_to_name(err));
412 return false;
413 }
414 }
415 if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_IDLE) {
416 ESP_LOGE(TAG, "esp bt controller disable failed");
417 return false;
418 }
419 }
420#else
421 if (esp_hosted_bt_controller_disable() != ESP_OK) {
422 ESP_LOGE(TAG, "esp_hosted_bt_controller_disable failed");
423 return false;
424 }
425
426 if (esp_hosted_bt_controller_deinit(false) != ESP_OK) {
427 ESP_LOGE(TAG, "esp_hosted_bt_controller_deinit failed");
428 return false;
429 }
430
431 hosted_hci_bluedroid_close();
432#endif
433 return true;
434}
435
437 if (this->state_ != BLE_COMPONENT_STATE_ACTIVE) {
438 this->loop_handle_state_transition_not_active_();
439 return;
440 }
441
442#ifdef USE_ESP32_BLE_ADVERTISING
443 if (this->advertising_ != nullptr) {
444 this->advertising_->loop();
445 }
446#endif
447
448 BLEEvent *ble_event = this->ble_events_.pop();
449 if (ble_event == nullptr)
450 return;
451
452 do {
453 switch (ble_event->type_) {
454#if defined(USE_ESP32_BLE_SERVER) && defined(ESPHOME_ESP32_BLE_GATTS_EVENT_HANDLER_COUNT)
455 case BLEEvent::GATTS: {
456 esp_gatts_cb_event_t event = ble_event->event_.gatts.gatts_event;
457 esp_gatt_if_t gatts_if = ble_event->event_.gatts.gatts_if;
458 esp_ble_gatts_cb_param_t *param = &ble_event->event_.gatts.gatts_param;
459 ESP_LOGV(TAG, "gatts_event [esp_gatt_if: %d] - %d", gatts_if, event);
460 this->gatts_event_callbacks_.call(event, gatts_if, param);
461 break;
462 }
463#endif
464#if defined(USE_ESP32_BLE_CLIENT) && defined(ESPHOME_ESP32_BLE_GATTC_EVENT_HANDLER_COUNT)
465 case BLEEvent::GATTC: {
466 esp_gattc_cb_event_t event = ble_event->event_.gattc.gattc_event;
467 esp_gatt_if_t gattc_if = ble_event->event_.gattc.gattc_if;
468 esp_ble_gattc_cb_param_t *param = &ble_event->event_.gattc.gattc_param;
469 ESP_LOGV(TAG, "gattc_event [esp_gatt_if: %d] - %d", gattc_if, event);
470 this->gattc_event_callbacks_.call(event, gattc_if, param);
471 break;
472 }
473#endif
474 case BLEEvent::GAP: {
475 esp_gap_ble_cb_event_t gap_event = ble_event->event_.gap.gap_event;
476 switch (gap_event) {
477 case ESP_GAP_BLE_SCAN_RESULT_EVT:
478#ifdef ESPHOME_ESP32_BLE_GAP_SCAN_EVENT_HANDLER_COUNT
479 this->gap_scan_event_callbacks_.call(ble_event->scan_result());
480#endif
481 break;
482
483 // Scan complete events
484 GAP_SCAN_COMPLETE_EVENTS:
485 // Advertising complete events
486 GAP_ADV_COMPLETE_EVENTS:
487 // RSSI complete event
488 case ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT:
489 // Security events
490 GAP_SECURITY_EVENTS:
491 ESP_LOGV(TAG, "gap_event_handler - %d", gap_event);
492#ifdef ESPHOME_ESP32_BLE_GAP_EVENT_HANDLER_COUNT
493 {
494 esp_ble_gap_cb_param_t *param = NULL;
495 // clang-format off
496 switch (gap_event) {
497 // All three scan complete events have the same structure with just status
498 // The scan_complete struct matches ESP-IDF's layout exactly, so this reinterpret_cast is safe
499 // This is verified at compile-time by static_assert checks in ble_event.h
500 // The struct already contains our copy of the status (copied in BLEEvent constructor)
501 GAP_SCAN_COMPLETE_EVENTS:
502 param = reinterpret_cast<esp_ble_gap_cb_param_t *>(&ble_event->event_.gap.scan_complete);
503 break;
504
505 // All advertising complete events have the same structure with just status
506 GAP_ADV_COMPLETE_EVENTS:
507 param = reinterpret_cast<esp_ble_gap_cb_param_t *>(&ble_event->event_.gap.adv_complete);
508 break;
509
510 case ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT:
511 param = reinterpret_cast<esp_ble_gap_cb_param_t *>(&ble_event->event_.gap.read_rssi_complete);
512 break;
513
514 GAP_SECURITY_EVENTS:
515 param = reinterpret_cast<esp_ble_gap_cb_param_t *>(&ble_event->event_.gap.security);
516 break;
517
518 default:
519 break;
520 }
521 // clang-format on
522 // Dispatch to all registered handlers
523 this->gap_event_callbacks_.call(gap_event, param);
524 }
525#endif
526 break;
527
528 default:
529 // Unknown/unhandled event
530 ESP_LOGW(TAG, "Unhandled GAP event type in loop: %d", gap_event);
531 break;
532 }
533 break;
534 }
535 default:
536 break;
537 }
538 // Return the event to the pool
539 this->ble_event_pool_.release(ble_event);
540 } while ((ble_event = this->ble_events_.pop()) != nullptr);
541
542 // Log dropped events - only reachable when events were processed.
543 // Drops only occur when the queue is full, and only this loop drains it,
544 // so if pop() returned nullptr above we can skip this check (saves a memw).
545 uint16_t dropped = this->ble_events_.get_and_reset_dropped_count();
546 if (dropped > 0) {
547 ESP_LOGW(TAG, "Dropped %u BLE events due to buffer overflow", dropped);
548 }
549}
550
551void ESP32BLE::loop_handle_state_transition_not_active_() {
552 // Caller ensures state_ != ACTIVE
553 if (this->state_ == BLE_COMPONENT_STATE_DISABLE) {
554 ESP_LOGD(TAG, "Disabling");
555
556#ifdef ESPHOME_ESP32_BLE_BLE_STATUS_EVENT_HANDLER_COUNT
557 this->ble_status_event_callbacks_.call();
558#endif
559
560 if (!ble_dismantle_()) {
561 ESP_LOGE(TAG, "Could not be dismantled");
562 this->mark_failed();
563 return;
564 }
565 this->state_ = BLE_COMPONENT_STATE_DISABLED;
566 } else if (this->state_ == BLE_COMPONENT_STATE_ENABLE) {
567 ESP_LOGD(TAG, "Enabling");
568 this->state_ = BLE_COMPONENT_STATE_OFF;
569
570 if (!ble_setup_()) {
571 ESP_LOGE(TAG, "Could not be set up");
572 this->mark_failed();
573 return;
574 }
575
576 this->state_ = BLE_COMPONENT_STATE_ACTIVE;
577 }
578}
579
580// Helper function to load new event data based on type
581void load_ble_event(BLEEvent *event, esp_gap_ble_cb_event_t e, esp_ble_gap_cb_param_t *p) {
582 event->load_gap_event(e, p);
583}
584
585#ifdef USE_ESP32_BLE_CLIENT
586void load_ble_event(BLEEvent *event, esp_gattc_cb_event_t e, esp_gatt_if_t i, esp_ble_gattc_cb_param_t *p) {
587 event->load_gattc_event(e, i, p);
588}
589#endif
590
591#ifdef USE_ESP32_BLE_SERVER
592void load_ble_event(BLEEvent *event, esp_gatts_cb_event_t e, esp_gatt_if_t i, esp_ble_gatts_cb_param_t *p) {
593 event->load_gatts_event(e, i, p);
594}
595#endif
596
597template<typename... Args> void enqueue_ble_event(Args... args) {
598 // Allocate an event from the pool
599 BLEEvent *event = global_ble->ble_event_pool_.allocate();
600 if (event == nullptr) {
601 // No events available - queue is full or we're out of memory
602 global_ble->ble_events_.increment_dropped_count();
603 return;
604 }
605
606 // Load new event data (replaces previous event)
607 load_ble_event(event, args...);
608
609 // Push the event to the queue
610 // Push always succeeds: pool is sized to queue capacity (N-1), so if
611 // allocate() returned non-null, the queue is guaranteed to have room.
612 global_ble->ble_events_.push(event);
613}
614
615// Explicit template instantiations for the friend function
616template void enqueue_ble_event(esp_gap_ble_cb_event_t, esp_ble_gap_cb_param_t *);
617#ifdef USE_ESP32_BLE_SERVER
618template void enqueue_ble_event(esp_gatts_cb_event_t, esp_gatt_if_t, esp_ble_gatts_cb_param_t *);
619#endif
620#ifdef USE_ESP32_BLE_CLIENT
621template void enqueue_ble_event(esp_gattc_cb_event_t, esp_gatt_if_t, esp_ble_gattc_cb_param_t *);
622#endif
623
624void ESP32BLE::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
625 switch (event) {
626 // Queue GAP events that components need to handle
627 // Scanning events - used by esp32_ble_tracker
628 case ESP_GAP_BLE_SCAN_RESULT_EVT:
629 GAP_SCAN_COMPLETE_EVENTS:
630 // Advertising events - used by esp32_ble_beacon and esp32_ble server
631 GAP_ADV_COMPLETE_EVENTS:
632 // Connection events - used by ble_client
633 case ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT:
634 enqueue_ble_event(event, param);
635 return;
636
637 // Security events - used by ble_client and bluetooth_proxy
638 // These are rare but interactive (pairing/bonding), so notify immediately
639 GAP_SECURITY_EVENTS:
640 enqueue_ble_event(event, param);
641 // Wake up main loop to process security event immediately
643 return;
644
645 // Ignore these GAP events as they are not relevant for our use case
646 case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT:
647 case ESP_GAP_BLE_SET_PKT_LENGTH_COMPLETE_EVT:
648 case ESP_GAP_BLE_PHY_UPDATE_COMPLETE_EVT: // BLE 5.0 PHY update complete
649 case ESP_GAP_BLE_CHANNEL_SELECT_ALGORITHM_EVT: // BLE 5.0 channel selection algorithm
650 return;
651
652 default:
653 break;
654 }
655 ESP_LOGW(TAG, "Ignoring unexpected GAP event type: %d", event);
656}
657
658#ifdef USE_ESP32_BLE_SERVER
659void ESP32BLE::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
660 esp_ble_gatts_cb_param_t *param) {
661 enqueue_ble_event(event, gatts_if, param);
662 // Wake up main loop to process GATT event immediately
664}
665#endif
666
667#ifdef USE_ESP32_BLE_CLIENT
668void ESP32BLE::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
669 esp_ble_gattc_cb_param_t *param) {
670 enqueue_ble_event(event, gattc_if, param);
671 // Wake up main loop to process GATT event immediately
673}
674#endif
675
677
679 const uint8_t *mac_address = esp_bt_dev_get_address();
680 if (mac_address) {
681 const char *io_capability_s;
682 switch (this->io_cap_) {
683 case ESP_IO_CAP_OUT:
684 io_capability_s = "display_only";
685 break;
686 case ESP_IO_CAP_IO:
687 io_capability_s = "display_yes_no";
688 break;
689 case ESP_IO_CAP_IN:
690 io_capability_s = "keyboard_only";
691 break;
692 case ESP_IO_CAP_NONE:
693 io_capability_s = "none";
694 break;
695 case ESP_IO_CAP_KBDISP:
696 io_capability_s = "keyboard_display";
697 break;
698 default:
699 io_capability_s = "invalid";
700 break;
701 }
702
703 char mac_s[18];
704 format_mac_addr_upper(mac_address, mac_s);
705 ESP_LOGCONFIG(TAG,
706 "BLE:\n"
707 " MAC address: %s\n"
708 " IO Capability: %s",
709 mac_s, io_capability_s);
710#ifdef USE_ESP32_BLE_PSRAM
711 ESP_LOGCONFIG(TAG, " PSRAM BLE allocation: enabled");
712#endif
713
714#ifdef ESPHOME_ESP32_BLE_EXTENDED_AUTH_PARAMS
715 const char *auth_req_mode_s = "<default>";
716 if (this->auth_req_mode_) {
717 switch (this->auth_req_mode_.value()) {
718 case AUTH_REQ_NO_BOND:
719 auth_req_mode_s = "no_bond";
720 break;
721 case AUTH_REQ_BOND:
722 auth_req_mode_s = "bond";
723 break;
724 case AUTH_REQ_MITM:
725 auth_req_mode_s = "mitm";
726 break;
728 auth_req_mode_s = "bond_mitm";
729 break;
730 case AUTH_REQ_SC_ONLY:
731 auth_req_mode_s = "sc_only";
732 break;
733 case AUTH_REQ_SC_BOND:
734 auth_req_mode_s = "sc_bond";
735 break;
736 case AUTH_REQ_SC_MITM:
737 auth_req_mode_s = "sc_mitm";
738 break;
740 auth_req_mode_s = "sc_mitm_bond";
741 break;
742 }
743 }
744
745 ESP_LOGCONFIG(TAG, " Auth Req Mode: %s", auth_req_mode_s);
746 if (this->max_key_size_ && this->min_key_size_) {
747 ESP_LOGCONFIG(TAG, " Key Size: %u - %u", this->min_key_size_, this->max_key_size_);
748 } else if (this->max_key_size_) {
749 ESP_LOGCONFIG(TAG, " Key Size: <default> - %u", this->max_key_size_);
750 } else if (this->min_key_size_) {
751 ESP_LOGCONFIG(TAG, " Key Size: %u - <default>", this->min_key_size_);
752 }
753#endif // ESPHOME_ESP32_BLE_EXTENDED_AUTH_PARAMS
754
755 } else {
756 ESP_LOGCONFIG(TAG, "Bluetooth stack is not enabled");
757 }
758}
759
760ESP32BLE *global_ble = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
761
762} // namespace esphome::esp32_ble
763
764#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.
constexpr size_type length() const
Definition string_ref.h:75
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)
union esphome::esp32_ble::BLEEvent::@78 event_
struct esphome::esp32_ble::BLEEvent::@78::gatts_event gatts
struct esphome::esp32_ble::BLEEvent::@78::gap_event gap
struct esphome::esp32_ble::BLEEvent::@78::gattc_event gattc
void advertising_set_manufacturer_data(const std::vector< uint8_t > &data)
Definition ble.cpp:113
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
Definition ble.cpp:624
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:668
friend void enqueue_ble_event(Args... args)
Definition ble.cpp:597
void advertising_register_raw_advertisement_callback(std::function< void(bool)> &&callback)
Definition ble.cpp:141
void advertising_set_service_data_and_name(std::span< const uint8_t > data, bool include_name)
Definition ble.cpp:119
void advertising_add_service_uuid(ESPBTUUID uuid)
Definition ble.cpp:146
void dump_config() override
Definition ble.cpp:678
void loop() override
Definition ble.cpp:436
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:659
void advertising_set_service_data(const std::vector< uint8_t > &data)
Definition ble.cpp:107
ESPHOME_ALWAYS_INLINE bool is_active()
Definition ble.h:107
float get_setup_priority() const override
Definition ble.cpp:676
void setup() override
Definition ble.cpp:71
void advertising_remove_service_uuid(ESPBTUUID uuid)
Definition ble.cpp:152
ESP32BLE * global_ble
Definition ble.cpp:760
void load_ble_event(BLEEvent *event, esp_gap_ble_cb_event_t e, esp_ble_gap_cb_param_t *p)
Definition ble.cpp:581
@ 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:597
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)
Zero-allocation version: format name + separator + suffix directly into buffer.
Definition helpers.cpp:241
const char int const __FlashStringHelper va_list args
Definition log.h:74
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:744
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:1467
static void uint32_t