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