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