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