ESPHome 2025.8.0b2
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#include <esp_bt.h>
10#include <esp_bt_device.h>
11#include <esp_bt_main.h>
12#include <esp_gap_ble_api.h>
13#include <freertos/FreeRTOS.h>
14#include <freertos/FreeRTOSConfig.h>
15#include <freertos/task.h>
16#include <nvs_flash.h>
17
18#ifdef USE_ARDUINO
19#include <esp32-hal-bt.h>
20#endif
21
23
24static const char *const TAG = "esp32_ble";
25
27 global_ble = this;
28 if (!ble_pre_setup_()) {
29 ESP_LOGE(TAG, "BLE could not be prepared for configuration");
30 this->mark_failed();
31 return;
32 }
33
34 this->state_ = BLE_COMPONENT_STATE_DISABLED;
35 if (this->enable_on_boot_) {
36 this->enable();
37 }
38}
39
41 if (this->state_ != BLE_COMPONENT_STATE_DISABLED)
42 return;
43
44 this->state_ = BLE_COMPONENT_STATE_ENABLE;
45}
46
48 if (this->state_ == BLE_COMPONENT_STATE_DISABLED)
49 return;
50
51 this->state_ = BLE_COMPONENT_STATE_DISABLE;
52}
53
54bool ESP32BLE::is_active() { return this->state_ == BLE_COMPONENT_STATE_ACTIVE; }
55
56#ifdef USE_ESP32_BLE_ADVERTISING
58 this->advertising_init_();
59 if (!this->is_active())
60 return;
61 this->advertising_->start();
62}
63
64void ESP32BLE::advertising_set_service_data(const std::vector<uint8_t> &data) {
65 this->advertising_init_();
66 this->advertising_->set_service_data(data);
67 this->advertising_start();
68}
69
70void ESP32BLE::advertising_set_manufacturer_data(const std::vector<uint8_t> &data) {
71 this->advertising_init_();
72 this->advertising_->set_manufacturer_data(data);
73 this->advertising_start();
74}
75
76void ESP32BLE::advertising_register_raw_advertisement_callback(std::function<void(bool)> &&callback) {
77 this->advertising_init_();
78 this->advertising_->register_raw_advertisement_callback(std::move(callback));
79}
80
82 this->advertising_init_();
83 this->advertising_->add_service_uuid(uuid);
84 this->advertising_start();
85}
86
88 this->advertising_init_();
89 this->advertising_->remove_service_uuid(uuid);
90 this->advertising_start();
91}
92#endif
93
95 esp_err_t err = nvs_flash_init();
96 if (err != ESP_OK) {
97 ESP_LOGE(TAG, "nvs_flash_init failed: %d", err);
98 return false;
99 }
100 return true;
101}
102
103#ifdef USE_ESP32_BLE_ADVERTISING
105 if (this->advertising_ != nullptr)
106 return;
107 this->advertising_ = new BLEAdvertising(this->advertising_cycle_time_); // NOLINT(cppcoreguidelines-owning-memory)
108
109 this->advertising_->set_scan_response(true);
110 this->advertising_->set_min_preferred_interval(0x06);
111 this->advertising_->set_appearance(this->appearance_);
112}
113#endif
114
116 esp_err_t err;
117#ifdef USE_ARDUINO
118 if (!btStart()) {
119 ESP_LOGE(TAG, "btStart failed: %d", esp_bt_controller_get_status());
120 return false;
121 }
122#else
123 if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_ENABLED) {
124 // start bt controller
125 if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE) {
126 esp_bt_controller_config_t cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
127 err = esp_bt_controller_init(&cfg);
128 if (err != ESP_OK) {
129 ESP_LOGE(TAG, "esp_bt_controller_init failed: %s", esp_err_to_name(err));
130 return false;
131 }
132 while (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE)
133 ;
134 }
135 if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_INITED) {
136 err = esp_bt_controller_enable(ESP_BT_MODE_BLE);
137 if (err != ESP_OK) {
138 ESP_LOGE(TAG, "esp_bt_controller_enable failed: %s", esp_err_to_name(err));
139 return false;
140 }
141 }
142 if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_ENABLED) {
143 ESP_LOGE(TAG, "esp bt controller enable failed");
144 return false;
145 }
146 }
147#endif
148
149 esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT);
150
151 err = esp_bluedroid_init();
152 if (err != ESP_OK) {
153 ESP_LOGE(TAG, "esp_bluedroid_init failed: %d", err);
154 return false;
155 }
156 err = esp_bluedroid_enable();
157 if (err != ESP_OK) {
158 ESP_LOGE(TAG, "esp_bluedroid_enable failed: %d", err);
159 return false;
160 }
161
162 if (!this->gap_event_handlers_.empty()) {
163 err = esp_ble_gap_register_callback(ESP32BLE::gap_event_handler);
164 if (err != ESP_OK) {
165 ESP_LOGE(TAG, "esp_ble_gap_register_callback failed: %d", err);
166 return false;
167 }
168 }
169
170 if (!this->gatts_event_handlers_.empty()) {
171 err = esp_ble_gatts_register_callback(ESP32BLE::gatts_event_handler);
172 if (err != ESP_OK) {
173 ESP_LOGE(TAG, "esp_ble_gatts_register_callback failed: %d", err);
174 return false;
175 }
176 }
177
178 if (!this->gattc_event_handlers_.empty()) {
179 err = esp_ble_gattc_register_callback(ESP32BLE::gattc_event_handler);
180 if (err != ESP_OK) {
181 ESP_LOGE(TAG, "esp_ble_gattc_register_callback failed: %d", err);
182 return false;
183 }
184 }
185
186 std::string name;
187 if (this->name_.has_value()) {
188 name = this->name_.value();
190 name += "-" + get_mac_address().substr(6);
191 }
192 } else {
193 name = App.get_name();
194 if (name.length() > 20) {
196 name.erase(name.begin() + 13, name.end() - 7); // Remove characters between 13 and the mac address
197 } else {
198 name = name.substr(0, 20);
199 }
200 }
201 }
202
203 err = esp_ble_gap_set_device_name(name.c_str());
204 if (err != ESP_OK) {
205 ESP_LOGE(TAG, "esp_ble_gap_set_device_name failed: %d", err);
206 return false;
207 }
208
209 err = esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &(this->io_cap_), sizeof(uint8_t));
210 if (err != ESP_OK) {
211 ESP_LOGE(TAG, "esp_ble_gap_set_security_param failed: %d", err);
212 return false;
213 }
214
215 // BLE takes some time to be fully set up, 200ms should be more than enough
216 delay(200); // NOLINT
217
218 return true;
219}
220
222 esp_err_t err = esp_bluedroid_disable();
223 if (err != ESP_OK) {
224 ESP_LOGE(TAG, "esp_bluedroid_disable failed: %d", err);
225 return false;
226 }
227 err = esp_bluedroid_deinit();
228 if (err != ESP_OK) {
229 ESP_LOGE(TAG, "esp_bluedroid_deinit failed: %d", err);
230 return false;
231 }
232
233#ifdef USE_ARDUINO
234 if (!btStop()) {
235 ESP_LOGE(TAG, "btStop failed: %d", esp_bt_controller_get_status());
236 return false;
237 }
238#else
239 if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_IDLE) {
240 // stop bt controller
241 if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_ENABLED) {
242 err = esp_bt_controller_disable();
243 if (err != ESP_OK) {
244 ESP_LOGE(TAG, "esp_bt_controller_disable failed: %s", esp_err_to_name(err));
245 return false;
246 }
247 while (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_ENABLED)
248 ;
249 }
250 if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_INITED) {
251 err = esp_bt_controller_deinit();
252 if (err != ESP_OK) {
253 ESP_LOGE(TAG, "esp_bt_controller_deinit failed: %s", esp_err_to_name(err));
254 return false;
255 }
256 }
257 if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_IDLE) {
258 ESP_LOGE(TAG, "esp bt controller disable failed");
259 return false;
260 }
261 }
262#endif
263 return true;
264}
265
267 switch (this->state_) {
270 return;
272 ESP_LOGD(TAG, "Disabling");
273
274 for (auto *ble_event_handler : this->ble_status_event_handlers_) {
275 ble_event_handler->ble_before_disabled_event_handler();
276 }
277
278 if (!ble_dismantle_()) {
279 ESP_LOGE(TAG, "Could not be dismantled");
280 this->mark_failed();
281 return;
282 }
283 this->state_ = BLE_COMPONENT_STATE_DISABLED;
284 return;
285 }
287 ESP_LOGD(TAG, "Enabling");
288 this->state_ = BLE_COMPONENT_STATE_OFF;
289
290 if (!ble_setup_()) {
291 ESP_LOGE(TAG, "Could not be set up");
292 this->mark_failed();
293 return;
294 }
295
296 this->state_ = BLE_COMPONENT_STATE_ACTIVE;
297 return;
298 }
300 break;
301 }
302
303 BLEEvent *ble_event = this->ble_events_.pop();
304 while (ble_event != nullptr) {
305 switch (ble_event->type_) {
306 case BLEEvent::GATTS: {
307 esp_gatts_cb_event_t event = ble_event->event_.gatts.gatts_event;
308 esp_gatt_if_t gatts_if = ble_event->event_.gatts.gatts_if;
309 esp_ble_gatts_cb_param_t *param = ble_event->event_.gatts.gatts_param;
310 ESP_LOGV(TAG, "gatts_event [esp_gatt_if: %d] - %d", gatts_if, event);
311 for (auto *gatts_handler : this->gatts_event_handlers_) {
312 gatts_handler->gatts_event_handler(event, gatts_if, param);
313 }
314 break;
315 }
316 case BLEEvent::GATTC: {
317 esp_gattc_cb_event_t event = ble_event->event_.gattc.gattc_event;
318 esp_gatt_if_t gattc_if = ble_event->event_.gattc.gattc_if;
319 esp_ble_gattc_cb_param_t *param = ble_event->event_.gattc.gattc_param;
320 ESP_LOGV(TAG, "gattc_event [esp_gatt_if: %d] - %d", gattc_if, event);
321 for (auto *gattc_handler : this->gattc_event_handlers_) {
322 gattc_handler->gattc_event_handler(event, gattc_if, param);
323 }
324 break;
325 }
326 case BLEEvent::GAP: {
327 esp_gap_ble_cb_event_t gap_event = ble_event->event_.gap.gap_event;
328 switch (gap_event) {
329 case ESP_GAP_BLE_SCAN_RESULT_EVT:
330 // Use the new scan event handler - no memcpy!
331 for (auto *scan_handler : this->gap_scan_event_handlers_) {
332 scan_handler->gap_scan_event_handler(ble_event->scan_result());
333 }
334 break;
335
336 // Scan complete events
337 case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT:
338 case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT:
339 case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT:
340 // All three scan complete events have the same structure with just status
341 // The scan_complete struct matches ESP-IDF's layout exactly, so this reinterpret_cast is safe
342 // This is verified at compile-time by static_assert checks in ble_event.h
343 // The struct already contains our copy of the status (copied in BLEEvent constructor)
344 ESP_LOGV(TAG, "gap_event_handler - %d", gap_event);
345 for (auto *gap_handler : this->gap_event_handlers_) {
346 gap_handler->gap_event_handler(
347 gap_event, reinterpret_cast<esp_ble_gap_cb_param_t *>(&ble_event->event_.gap.scan_complete));
348 }
349 break;
350
351 // Advertising complete events
352 case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT:
353 case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT:
354 case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT:
355 case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
356 case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT:
357 // All advertising complete events have the same structure with just status
358 ESP_LOGV(TAG, "gap_event_handler - %d", gap_event);
359 for (auto *gap_handler : this->gap_event_handlers_) {
360 gap_handler->gap_event_handler(
361 gap_event, reinterpret_cast<esp_ble_gap_cb_param_t *>(&ble_event->event_.gap.adv_complete));
362 }
363 break;
364
365 // RSSI complete event
366 case ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT:
367 ESP_LOGV(TAG, "gap_event_handler - %d", gap_event);
368 for (auto *gap_handler : this->gap_event_handlers_) {
369 gap_handler->gap_event_handler(
370 gap_event, reinterpret_cast<esp_ble_gap_cb_param_t *>(&ble_event->event_.gap.read_rssi_complete));
371 }
372 break;
373
374 // Security events
375 case ESP_GAP_BLE_AUTH_CMPL_EVT:
376 case ESP_GAP_BLE_SEC_REQ_EVT:
377 case ESP_GAP_BLE_PASSKEY_NOTIF_EVT:
378 case ESP_GAP_BLE_PASSKEY_REQ_EVT:
379 case ESP_GAP_BLE_NC_REQ_EVT:
380 ESP_LOGV(TAG, "gap_event_handler - %d", gap_event);
381 for (auto *gap_handler : this->gap_event_handlers_) {
382 gap_handler->gap_event_handler(
383 gap_event, reinterpret_cast<esp_ble_gap_cb_param_t *>(&ble_event->event_.gap.security));
384 }
385 break;
386
387 default:
388 // Unknown/unhandled event
389 ESP_LOGW(TAG, "Unhandled GAP event type in loop: %d", gap_event);
390 break;
391 }
392 break;
393 }
394 default:
395 break;
396 }
397 // Return the event to the pool
398 this->ble_event_pool_.release(ble_event);
399 ble_event = this->ble_events_.pop();
400 }
401#ifdef USE_ESP32_BLE_ADVERTISING
402 if (this->advertising_ != nullptr) {
403 this->advertising_->loop();
404 }
405#endif
406
407 // Log dropped events periodically
408 uint16_t dropped = this->ble_events_.get_and_reset_dropped_count();
409 if (dropped > 0) {
410 ESP_LOGW(TAG, "Dropped %u BLE events due to buffer overflow", dropped);
411 }
412}
413
414// Helper function to load new event data based on type
415void load_ble_event(BLEEvent *event, esp_gap_ble_cb_event_t e, esp_ble_gap_cb_param_t *p) {
416 event->load_gap_event(e, p);
417}
418
419void load_ble_event(BLEEvent *event, esp_gattc_cb_event_t e, esp_gatt_if_t i, esp_ble_gattc_cb_param_t *p) {
420 event->load_gattc_event(e, i, p);
421}
422
423void load_ble_event(BLEEvent *event, esp_gatts_cb_event_t e, esp_gatt_if_t i, esp_ble_gatts_cb_param_t *p) {
424 event->load_gatts_event(e, i, p);
425}
426
427template<typename... Args> void enqueue_ble_event(Args... args) {
428 // Allocate an event from the pool
429 BLEEvent *event = global_ble->ble_event_pool_.allocate();
430 if (event == nullptr) {
431 // No events available - queue is full or we're out of memory
432 global_ble->ble_events_.increment_dropped_count();
433 return;
434 }
435
436 // Load new event data (replaces previous event)
437 load_ble_event(event, args...);
438
439 // Push the event to the queue
440 global_ble->ble_events_.push(event);
441 // Push always succeeds because we're the only producer and the pool ensures we never exceed queue size
442}
443
444// Explicit template instantiations for the friend function
445template void enqueue_ble_event(esp_gap_ble_cb_event_t, esp_ble_gap_cb_param_t *);
446template void enqueue_ble_event(esp_gatts_cb_event_t, esp_gatt_if_t, esp_ble_gatts_cb_param_t *);
447template void enqueue_ble_event(esp_gattc_cb_event_t, esp_gatt_if_t, esp_ble_gattc_cb_param_t *);
448
449void ESP32BLE::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
450 switch (event) {
451 // Queue GAP events that components need to handle
452 // Scanning events - used by esp32_ble_tracker
453 case ESP_GAP_BLE_SCAN_RESULT_EVT:
454 case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT:
455 case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT:
456 case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT:
457 // Advertising events - used by esp32_ble_beacon and esp32_ble server
458 case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT:
459 case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT:
460 case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT:
461 case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
462 case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT:
463 // Connection events - used by ble_client
464 case ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT:
465 // Security events - used by ble_client and bluetooth_proxy
466 case ESP_GAP_BLE_AUTH_CMPL_EVT:
467 case ESP_GAP_BLE_SEC_REQ_EVT:
468 case ESP_GAP_BLE_PASSKEY_NOTIF_EVT:
469 case ESP_GAP_BLE_PASSKEY_REQ_EVT:
470 case ESP_GAP_BLE_NC_REQ_EVT:
471 enqueue_ble_event(event, param);
472 return;
473
474 // Ignore these GAP events as they are not relevant for our use case
475 case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT:
476 case ESP_GAP_BLE_SET_PKT_LENGTH_COMPLETE_EVT:
477 case ESP_GAP_BLE_PHY_UPDATE_COMPLETE_EVT: // BLE 5.0 PHY update complete
478 case ESP_GAP_BLE_CHANNEL_SELECT_ALGORITHM_EVT: // BLE 5.0 channel selection algorithm
479 return;
480
481 default:
482 break;
483 }
484 ESP_LOGW(TAG, "Ignoring unexpected GAP event type: %d", event);
485}
486
487void ESP32BLE::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
488 esp_ble_gatts_cb_param_t *param) {
489 enqueue_ble_event(event, gatts_if, param);
490}
491
492void ESP32BLE::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
493 esp_ble_gattc_cb_param_t *param) {
494 enqueue_ble_event(event, gattc_if, param);
495}
496
498
500 const uint8_t *mac_address = esp_bt_dev_get_address();
501 if (mac_address) {
502 const char *io_capability_s;
503 switch (this->io_cap_) {
504 case ESP_IO_CAP_OUT:
505 io_capability_s = "display_only";
506 break;
507 case ESP_IO_CAP_IO:
508 io_capability_s = "display_yes_no";
509 break;
510 case ESP_IO_CAP_IN:
511 io_capability_s = "keyboard_only";
512 break;
513 case ESP_IO_CAP_NONE:
514 io_capability_s = "none";
515 break;
516 case ESP_IO_CAP_KBDISP:
517 io_capability_s = "keyboard_display";
518 break;
519 default:
520 io_capability_s = "invalid";
521 break;
522 }
523 ESP_LOGCONFIG(TAG,
524 "BLE:\n"
525 " MAC address: %s\n"
526 " IO Capability: %s",
527 format_mac_address_pretty(mac_address).c_str(), io_capability_s);
528 } else {
529 ESP_LOGCONFIG(TAG, "Bluetooth stack is not enabled");
530 }
531}
532
533uint64_t ble_addr_to_uint64(const esp_bd_addr_t address) {
534 uint64_t u = 0;
535 u |= uint64_t(address[0] & 0xFF) << 40;
536 u |= uint64_t(address[1] & 0xFF) << 32;
537 u |= uint64_t(address[2] & 0xFF) << 24;
538 u |= uint64_t(address[3] & 0xFF) << 16;
539 u |= uint64_t(address[4] & 0xFF) << 8;
540 u |= uint64_t(address[5] & 0xFF) << 0;
541 return u;
542}
543
544ESP32BLE *global_ble = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
545
546} // namespace esphome::esp32_ble
547
548#endif
uint8_t address
Definition bl0906.h:4
bool is_name_add_mac_suffix_enabled() const
const std::string & get_name() const
Get the name of this Application set by pre_setup().
virtual void mark_failed()
Mark this component as failed.
void set_manufacturer_data(const std::vector< uint8_t > &data)
void set_scan_response(bool scan_response)
void set_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::@77::gattc_event gattc
struct esphome::esp32_ble::BLEEvent::@77::gap_event gap
struct esphome::esp32_ble::BLEEvent::@77::gatts_event gatts
union esphome::esp32_ble::BLEEvent::@77 event_
void advertising_set_manufacturer_data(const std::vector< uint8_t > &data)
Definition ble.cpp:70
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
Definition ble.cpp:449
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:492
friend void enqueue_ble_event(Args... args)
Definition ble.cpp:427
void advertising_register_raw_advertisement_callback(std::function< void(bool)> &&callback)
Definition ble.cpp:76
void advertising_add_service_uuid(ESPBTUUID uuid)
Definition ble.cpp:81
void dump_config() override
Definition ble.cpp:499
void loop() override
Definition ble.cpp:266
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:487
void advertising_set_service_data(const std::vector< uint8_t > &data)
Definition ble.cpp:64
float get_setup_priority() const override
Definition ble.cpp:497
void setup() override
Definition ble.cpp:26
void advertising_remove_service_uuid(ESPBTUUID uuid)
Definition ble.cpp:87
bool has_value() const
Definition optional.h:92
value_type const & value() const
Definition optional.h:94
ESP32BLE * global_ble
Definition ble.cpp:544
void load_ble_event(BLEEvent *event, esp_gap_ble_cb_event_t e, esp_ble_gap_cb_param_t *p)
Definition ble.cpp:415
@ BLE_COMPONENT_STATE_DISABLE
BLE should be disabled on next loop.
Definition ble.h:58
@ BLE_COMPONENT_STATE_OFF
Nothing has been initialized yet.
Definition ble.h:56
@ BLE_COMPONENT_STATE_ENABLE
BLE should be enabled on next loop.
Definition ble.h:62
@ BLE_COMPONENT_STATE_DISABLED
BLE is disabled.
Definition ble.h:60
@ BLE_COMPONENT_STATE_ACTIVE
BLE is active.
Definition ble.h:64
uint64_t ble_addr_to_uint64(const esp_bd_addr_t address)
Definition ble.cpp:533
void enqueue_ble_event(Args... args)
Definition ble.cpp:427
std::string format_mac_address_pretty(const uint8_t *mac)
Definition helpers.cpp:244
void IRAM_ATTR HOT delay(uint32_t ms)
Definition core.cpp:29
std::string get_mac_address()
Get the device MAC address as a string, in lowercase hex notation.
Definition helpers.cpp:578
Application App
Global storage of Application pointer - only one Application can exist.