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