# BLE + ESP32: Ejercicios Avanzados & Debugging en Detalle ## Parte 1: Ejercicios Codificados (Estructura Completa) --- ## EJERCICIO 1: Beacon Personalizado (Básico) ### Proyecto: `ble_sensor_beacon` ```c // main/main.c #include #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_log.h" #include "esp_err.h" #include "nimble/nimble_port.h" #include "nimble/nimble_port_freertos.h" #include "host/ble_hs.h" #include "host/util/util.h" #include "esp_bt.h" static const char *TAG = "BLE_BEACON"; // Variables globales static uint8_t own_addr_type; static uint8_t adv_data[] = { 0x02, 0x01, 0x06, // Flags 0x02, 0x0A, 0xF4, // TX Power: -12 dBm 0x07, 0x16, // Service Data 16-bit 0xBB, 0xAA, // UUID 0xAABB (custom) 25, // Temperature: 25°C 60 // Humidity: 60% }; // Task para actualizar advertising data void update_beacon_task(void *pvParameters) { uint8_t temp = 20; uint8_t hum = 50; while (1) { // Simular sensor temp = 20 + (esp_random() % 10); // 20-30°C hum = 50 + (esp_random() % 20); // 50-70% // Actualizar payload adv_data[8] = temp; adv_data[9] = hum; ESP_LOGI(TAG, "Updated: Temp=%d°C, Humidity=%d%%", temp, hum); // Re-set advertising data (sin reiniciar advertising) struct ble_gap_adv_set_fields adv_fields = {0}; adv_fields.flags = BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP; adv_fields.mfg_data = adv_data; adv_fields.mfg_data_len = sizeof(adv_data); ble_gap_adv_set_fields(&adv_fields); vTaskDelay(100 / portTICK_PERIOD_MS); // 100 ms } } // Inicializar advertising static int adv_init(void) { struct ble_gap_adv_params adv_params = {0}; struct ble_gap_adv_set_fields adv_fields = {0}; // Configurar campos adv_fields.flags = BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP; adv_fields.mfg_data = adv_data; adv_fields.mfg_data_len = sizeof(adv_data); int rc = ble_gap_adv_set_fields(&adv_fields); if (rc != 0) { ESP_LOGE(TAG, "ble_gap_adv_set_fields failed: %d", rc); return rc; } // Parámetros advertising: 100 ms interval, non-connectable adv_params.conn_mode = BLE_GAP_CONN_MODE_NON; adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN; adv_params.itvl_min = BLE_GAP_ADV_ITVL_MS(100); adv_params.itvl_max = BLE_GAP_ADV_ITVL_MS(100); rc = ble_gap_adv_start(own_addr_type, NULL, BLE_HS_FOREVER, &adv_params, NULL, NULL); if (rc != 0) { ESP_LOGE(TAG, "ble_gap_adv_start failed: %d", rc); return rc; } ESP_LOGI(TAG, "Advertising started"); return 0; } // Callback sync de NimBLE static void ble_app_on_sync(void) { // Asegurar dirección válida ble_hs_util_ensure_addr(0); ble_hs_id_infer_auto(0, &own_addr_type); if (adv_init() != 0) { ESP_LOGE(TAG, "Failed to initialize advertising"); } // Iniciar task de actualización xTaskCreate(update_beacon_task, "update_beacon", 2048, NULL, 5, NULL); } void app_main(void) { esp_err_t ret = nvs_flash_init(); if (ret == ESP_ERR_NVS_NO_FREE_PAGES) { nvs_flash_erase(); nvs_flash_init(); } // Init Bluetooth controller esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); esp_bt_controller_init(&bt_cfg); esp_bt_controller_enable(ESP_BT_MODE_BLE); // Init NimBLE nimble_port_init(); ble_hs_cfg.sync_cb = ble_app_on_sync; // Iniciar NimBLE stack nimble_port_freertos_init(BLE_HS_TASK_PRIORITY); } ``` ### Verificación en nRF Connect 1. **Scan**: Busca dispositivo `NimBLE_Beacon` o MAC. 2. **Raw Data**: Observa advertising packet: ``` 02 01 06 (Flags) 02 0A F4 (TX Power) 07 16 BB AA 19 3C ... (Service Data: 25°C, 60% humidity) ``` 3. **Repetición**: Datos actualizan cada 100 ms. --- ## EJERCICIO 2: GATT Server Interactivo (Intermedio) ### Proyecto: `ble_gatt_interactive` ```c // main/include/gatt_services.h #ifndef GATT_SERVICES_H #define GATT_SERVICES_H #include "host/ble_hs.h" // UUIDs del servicio custom (128-bit) #define CUSTOM_SERVICE_UUID \ {0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0, \ 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0} // Características extern uint8_t led_state; // Write extern uint8_t button_state; // Read + Notify extern uint8_t heart_rate; // Read + Notify // Declaración de servicios GATT extern const struct ble_gatt_svc_def gatt_svr_svcs[]; #endif // GATT_SERVICES_H ``` ```c // main/src/gatt_services.c #include "gatt_services.h" #include "esp_log.h" static const char *TAG = "GATT_SERVICES"; // Estado de características uint8_t led_state = 0; uint8_t button_state = 0; uint8_t heart_rate = 70; // Callback de acceso a características static int gatt_svr_chr_access(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg) { uint16_t uuid16 = ble_uuid_u16(ctxt->chr->uuid); switch (uuid16) { case 0x0001: // LED Control (write) if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) { if (ctxt->om->om_len > 0) { led_state = ctxt->om->om_data[0]; ESP_LOGI(TAG, "LED state: %d", led_state); if (led_state) { // Encender LED (GPIO) } else { // Apagar LED } } } break; case 0x0002: // Button State (read + notify) if (ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR) { os_mbuf_append(ctxt->om, &button_state, sizeof(button_state)); } break; case 0x0003: // Heart Rate (read + notify) if (ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR) { os_mbuf_append(ctxt->om, &heart_rate, sizeof(heart_rate)); } break; } return 0; } // Callback para CCCD (notificaciones) static int gatt_svr_dsc_access(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg) { uint16_t uuid16 = ble_uuid_u16(ctxt->dsc->uuid); if (uuid16 == BLE_GATT_DSC_CLT_CFG_UUID16) { if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_DSC) { uint16_t notify_flags = os_mbuf_read_u16(ctxt->om, 0); ESP_LOGI(TAG, "Notify flags for %s: 0x%04x", (char *)arg, notify_flags); } } return 0; } // Tabla de servicios GATT const struct ble_gatt_svc_def gatt_svr_svcs[] = { { .type = BLE_GATT_SVC_TYPE_PRIMARY, .uuid = BLE_UUID128_DECLARE(CUSTOM_SERVICE_UUID), .characteristics = (struct ble_gatt_chr_def[]) { // LED Control { .uuid = BLE_UUID16_DECLARE(0x0001), .access_cb = gatt_svr_chr_access, .flags = BLE_GATT_CHR_F_WRITE, }, // Button State { .uuid = BLE_UUID16_DECLARE(0x0002), .access_cb = gatt_svr_chr_access, .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY, .descriptors = (struct ble_gatt_dsc_def[]) { { .uuid = BLE_UUID16_DECLARE(BLE_GATT_DSC_CLT_CFG_UUID16), .access_cb = gatt_svr_dsc_access, .flags = BLE_GATT_DSC_F_WRITE, .arg = "Button" }, {0} }, }, // Heart Rate { .uuid = BLE_UUID16_DECLARE(0x0003), .access_cb = gatt_svr_chr_access, .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY, .descriptors = (struct ble_gatt_dsc_def[]) { { .uuid = BLE_UUID16_DECLARE(BLE_GATT_DSC_CLT_CFG_UUID16), .access_cb = gatt_svr_dsc_access, .flags = BLE_GATT_DSC_F_WRITE, .arg = "HeartRate" }, {0} }, }, {0} }, }, {0} }; ``` ```c // main/main.c #include "host/ble_hs.h" #include "nimble/nimble_port.h" #include "nimble/nimble_port_freertos.h" #include "gatt_services.h" static const char *TAG = "BLE_GATT_SERVER"; static uint8_t own_addr_type; // GAP event handler static int gap_event_handler(struct ble_gap_event *event, void *arg) { switch (event->type) { case BLE_GAP_EVENT_CONNECT: ESP_LOGI(TAG, "Connected: conn_handle=%d", event->connect.conn_handle); break; case BLE_GAP_EVENT_DISCONNECT: ESP_LOGI(TAG, "Disconnected: conn_handle=%d, reason=%d", event->disconnect.conn.conn_handle, event->disconnect.reason); adv_init(); // Re-advertise break; case BLE_GAP_EVENT_CONN_UPDATE_REQ: ESP_LOGI(TAG, "Connection update request"); break; case BLE_GAP_EVENT_ADV_COMPLETE: ESP_LOGD(TAG, "Advertising complete"); break; } return 0; } static int adv_init(void) { struct ble_gap_adv_params adv_params = {0}; struct ble_gap_adv_set_fields adv_fields = {0}; adv_fields.flags = BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP; adv_fields.name = (uint8_t *)"BLE_GATT_TEST"; adv_fields.name_len = strlen("BLE_GATT_TEST"); adv_fields.name_is_complete = 1; adv_fields.tx_pwr_lvl_is_present = 1; adv_fields.tx_pwr_lvl = -10; ble_gap_adv_set_fields(&adv_fields); adv_params.conn_mode = BLE_GAP_CONN_MODE_UND; // Connectable adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN; adv_params.itvl_min = BLE_GAP_ADV_ITVL_MS(100); adv_params.itvl_max = BLE_GAP_ADV_ITVL_MS(200); return ble_gap_adv_start(own_addr_type, NULL, BLE_HS_FOREVER, &adv_params, gap_event_handler, NULL); } // Task para actualizar BPM y Button void sensor_update_task(void *pvParameters) { uint16_t notify_handles[] = {0, 0}; // Button y HR while (1) { // Simular sensor heart_rate = 60 + (esp_random() % 40); button_state = (esp_random() % 100) > 80 ? 1 : 0; ESP_LOGI(TAG, "HR=%d, Button=%d", heart_rate, button_state); // TODO: Send notifications aquí // ble_gatts_notify_custom(conn_handle, handle, om); vTaskDelay(2000 / portTICK_PERIOD_MS); } } static void ble_app_on_sync(void) { ble_hs_util_ensure_addr(0); ble_hs_id_infer_auto(0, &own_addr_type); // Registrar servicios GATT ble_gatts_count_cfg(gatt_svr_svcs); ble_gatts_add_svcs(gatt_svr_svcs); adv_init(); xTaskCreate(sensor_update_task, "sensor_update", 2048, NULL, 5, NULL); } void app_main(void) { esp_err_t ret = nvs_flash_init(); if (ret == ESP_ERR_NVS_NO_FREE_PAGES) { nvs_flash_erase(); nvs_flash_init(); } esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); esp_bt_controller_init(&bt_cfg); esp_bt_controller_enable(ESP_BT_MODE_BLE); nimble_port_init(); ble_hs_cfg.sync_cb = ble_app_on_sync; ble_hs_cfg.att_max_mtu = 256; nimble_port_freertos_init(BLE_HS_TASK_PRIORITY); } ``` --- ## EJERCICIO 3: Security + Bonding (Avanzado) ```c // main/main.c (Security enabled) #include "host/ble_sm.h" #include "host/ble_gap.h" static const char *TAG = "BLE_SECURITY"; // Pairing complete callback static int pairing_cb(uint16_t conn_handle, struct ble_gap_event *event, void *arg) { struct ble_gap_conn_desc desc; switch (event->type) { case BLE_GAP_EVENT_ENC_CHANGE: if (event->enc_change.status == 0) { ESP_LOGI(TAG, "Encryption enabled on conn %d", conn_handle); } else { ESP_LOGE(TAG, "Encryption failed: %d", event->enc_change.status); } break; case BLE_GAP_EVENT_PASSKEY_ACTION: ESP_LOGI(TAG, "Passkey action: %d", event->passkey.params.action); // En periférico (display), imprime passkey if (event->passkey.params.action == BLE_SM_IOACT_DISP) { ESP_LOGI(TAG, "Passkey to show: %06d", event->passkey.params.numcmp); } break; case BLE_GAP_EVENT_CONN_UPDATE: if (ble_gap_conn_find(conn_handle, &desc) == 0) { ESP_LOGI(TAG, "Connection updated: itvl=%d, latency=%d, timeout=%d", desc.conn_itvl, desc.conn_latency, desc.supervision_tout); } break; } return 0; } // Configurar Security Manager static void ble_app_on_sync(void) { ble_hs_util_ensure_addr(0); ble_hs_id_infer_auto(0, &own_addr_type); // Security Manager config ble_hs_cfg.sm_mitm = 1; // MITM protection ble_hs_cfg.sm_bonding = 1; // Bonding ble_hs_cfg.sm_sc = 1; // Secure Connections ble_hs_cfg.sm_io_cap = BLE_SM_IO_CAP_DISP_ONLY; // Display-only // Registrar GATT services ble_gatts_count_cfg(gatt_svr_svcs); ble_gatts_add_svcs(gatt_svr_svcs); // Iniciar advertising adv_init(); } // Char access con encryptión requerida static int gatt_svr_chr_access_secure(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg) { struct ble_gap_conn_desc desc; // Validar encriptación if (ble_gap_conn_find(conn_handle, &desc) == 0) { if (!desc.sec_state.encrypted) { ESP_LOGE(TAG, "Write denied: not encrypted"); return BLE_ATT_ERR_INSUFFICIENT_AUTHEN; } } // Proceder con la operación... return gatt_svr_chr_access(conn_handle, attr_handle, ctxt, arg); } ``` --- # Parte 2: Debugging Avanzado en C++ con Clang --- ## Debugging Setup Completo ### 1. Configurar VSCode + Clang + GDB #### File: `.vscode/settings.json` ```json { "editor.formatOnSave": true, "editor.defaultFormatter": "xaver.clang-format", "[c]": { "editor.defaultFormatter": "xaver.clang-format", "editor.formatOnSave": true }, "C_Cpp.intelliSenseEngine": "disabled", "C_Cpp.codeAnalysisRunOnSave": true, "clang.executable": "/usr/bin/clang", "clang.fallbackFlags": ["-I${workspaceFolder}/components", "-I${workspaceFolder}/main"] } ``` #### File: `.vscode/launch.json` ```json { "version": "0.2.0", "configurations": [ { "name": "ESP32 GDB", "type": "cppdbg", "request": "launch", "program": "${workspaceFolder}/build/your_app.elf", "args": [], "stopAtEntry": true, "cwd": "${workspaceFolder}", "environment": [], "externalConsole": false, "MIMode": "gdb", "miDebuggerPath": "/opt/esp/tools/xtensa-esp32-elf/esp-12.2.0_20230208/xtensa-esp32-elf/bin/xtensa-esp32-elf-gdb", "miDebuggerServerAddress": "localhost:3333", "setupCommands": [ { "description": "Enable pretty-printing for gdb", "text": "-enable-pretty-printing", "ignoreFailures": true }, { "description": "Connect to OpenOCD", "text": "-target-select remote localhost:3333" }, { "description": "Set FreeRTOS awareness", "text": "source ${workspaceFolder}/.gdbinit" } ], "preLaunchTask": "openocd" } ] } ``` #### File: `.vscode/tasks.json` ```json { "version": "2.0.0", "tasks": [ { "label": "openocd", "type": "shell", "command": "openocd", "args": [ "-f", "interface/ftdi/esp32_devkitj_v1.cfg", "-f", "target/esp32.cfg" ], "isBackground": true, "problemMatcher": { "pattern": { "regexp": "^.*Open On-Chip Debugger.*$", "file": 1, "location": 2, "message": 3 }, "background": { "activeOnStart": true, "beginsPattern": "^.*listening on port.*$", "endsPattern": "^.*$" } } }, { "label": "build", "type": "shell", "command": "idf.py", "args": ["build"], "problemMatcher": [] }, { "label": "flash", "type": "shell", "command": "idf.py", "args": ["-p", "/dev/ttyUSB0", "flash"], "dependsOn": "build" } ] } ``` #### File: `.gdbinit` ```gdb set print pretty on set print array on set print array-indexes on # FreeRTOS-aware debugging source /opt/esp/tools/esp-gdb-init # Breakpoints útiles break app_main break esp_app_on_sync break gatt_svr_chr_access # Print stack en caso de crash define print_stack bt full end # Macros útiles define inspect_heap printf "DRAM free: %d bytes\n", heap_caps_get_free_size(2) printf "IRAM free: %d bytes\n", heap_caps_get_free_size(4) end define inspect_tasks info tasks end ``` --- ### 2. Debugging Práctico: Session Ejemplo #### Escenario: Crash en GATT access durante notificación **Terminal 1: OpenOCD** ```bash openocd -f interface/ftdi/esp32_devkitj_v1.cfg -f target/esp32.cfg ``` **Terminal 2: VSCode Debug** ```bash # Presionar F5 en VSCode (ejecuta launch.json) # O manual: xtensa-esp32-elf-gdb build/your_app.elf (gdb) target remote localhost:3333 (gdb) thb app_main (gdb) c ``` **En GDB:** ```bash # Cuando paras en app_main (gdb) b gatt_svr_chr_access (gdb) c # Si crashea en gatt_svr_chr_access (gdb) bt full # Ver stack trace completo (gdb) frame 1 (gdb) p ctxt # Inspeccionar parámetros (gdb) p ctxt->om # mbuf (gdb) x/32x ctxt->om->om_data # Ver datos en memory # Ver heap en crash (gdb) p heap_caps_get_free_size(2) # Ver registros (gdb) info registers ``` --- ### 3. AddressSanitizer (ASAN) #### Configuración ```bash idf.py menuconfig # → Component config → Compiler options → Enable AddressSanitizer → YES idf.py build flash monitor ``` #### Código con bug ```c void buggy_notify_task(void *arg) { uint8_t *buffer = malloc(10); while (1) { // BUG: Escribir fuera de límites buffer[15] = 0xFF; // Out of bounds! vTaskDelay(1000 / portTICK_PERIOD_MS); } } ``` #### Output ASAN ``` ================================================================= ==12345==ERROR: AddressSanitizer: heap-buffer-overflow on unknown address Write of size 1 at 0x3ffb2050 (pc 0x40084567 T0) Freed by thread T0 here: #0 0x400... in malloc (in your_app.elf) #1 0x400... in buggy_notify_task at main.c:42 SUMMARY: AddressSanitizer: heap-buffer-overflow main.c:42 ``` --- ### 4. Memory Profiling ```c // main/monitoring.c #include "esp_heap_caps.h" #include "esp_system.h" void print_heap_status(void) { printf("\n=== HEAP STATUS ===\n"); printf("Internal DRAM: %d / %d bytes free (%.1f%%)\n", heap_caps_get_free_size(MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT), heap_caps_get_total_size(MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT), 100.0 * heap_caps_get_free_size(MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT) / heap_caps_get_total_size(MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT)); printf("Internal IRAM: %d / %d bytes free (%.1f%%)\n", heap_caps_get_free_size(MALLOC_CAP_INTERNAL | MALLOC_CAP_32BIT), heap_caps_get_total_size(MALLOC_CAP_INTERNAL | MALLOC_CAP_32BIT), 100.0 * heap_caps_get_free_size(MALLOC_CAP_INTERNAL | MALLOC_CAP_32BIT) / heap_caps_get_total_size(MALLOC_CAP_INTERNAL | MALLOC_CAP_32BIT)); printf("External RAM: %d bytes free\n", heap_caps_get_free_size(MALLOC_CAP_SPIRAM)); } void print_task_stack_usage(void) { char *buffer = malloc(1024); vTaskList(buffer); printf("\n=== TASK STATUS ===\n%s\n", buffer); free(buffer); } // Registrar en timer void monitor_timer_callback(TimerHandle_t xTimer) { print_heap_status(); print_task_stack_usage(); } ``` --- ### 5. HCI Packet Sniffing en Software ```c // main/hci_monitor.c - Captura HCI comandos/eventos #include "esp_hci_transport.h" static void hci_packet_logger(uint8_t type, const uint8_t *data, uint16_t len) { printf("[HCI %s] ", type == 0x01 ? "CMD" : type == 0x04 ? "EVT" : "DATA"); for (int i = 0; i < (len < 20 ? len : 20); i++) { printf("%02X ", data[i]); } if (len > 20) printf("..."); printf(" (%d bytes)\n", len); } // Hookear en HCI initialization void app_main(void) { // ... init code ... // TODO: No hay hook público en ESP-IDF para HCI logging en runtime // Alternativa: Usar btmon en host Linux } ``` --- ### 6. GDB Macros para BLE ```gdb # File: ~/.gdbinit (o incluido en .vscode/.gdbinit) define ble_info printf "=== BLE INFO ===\n" printf "GAP State: %d\n", ble_hs_pvcy_set_gap_state printf "GATT Handle: %d\n", gatt_conn_handle end define ble_breakpoints break ble_gap_event_fn break ble_gattc_notification_cb break ble_gatts_register_svc_cb printf "BLE Breakpoints set\n" end define ble_stack_trace printf "Stack trace para BLE:\n" bt 10 end define inspect_conn_desc p *(struct ble_gap_conn_desc *)$arg0 end ``` --- ### 7. Análisis de Logs Filtrados ```bash # Capturar logs durante debugging idf.py -p /dev/ttyUSB0 monitor | tee debug_session.log # Post-análisis en bash grep "BLE_GATT" debug_session.log grep "ERROR" debug_session.log grep "time to.*ms" debug_session.log ``` --- ### 8. Testing Automatizado en C++ ```cpp // main/test_ble_suite.cpp (con Catch2 framework) #define CATCH_CONFIG_MAIN #include "catch.hpp" #include "host/ble_hs.h" TEST_CASE("BLE Stack Initialization", "[ble]") { int rc = ble_hs_init(); REQUIRE(rc == 0); } TEST_CASE("GATT Characteristic Access", "[gatt]") { // Setup uint8_t test_value = 42; // Test write REQUIRE(gatt_test_write(test_value) == 0); // Verify REQUIRE(test_value == 42); } TEST_CASE("Security Pairing Flow", "[security]") { // Mock central connect REQUIRE(mock_central_connect() == 0); // Trigger pairing REQUIRE(ble_sm_alg_p256() != NULL); } ``` --- # Parte 3: Tips Avanzados --- ## 1. Optimización de Consumo de Potencia ```c // Configurar connection parameters para batería static struct ble_gap_upd_params battery_opt_params = { .itvl_min = 800, // 1000 ms .itvl_max = 800, .latency = 199, // Skip 199 de cada 200 events .supervision_timeout = 320, // 3.2 s .min_ce_len = 0, .max_ce_len = 0 }; // Aplicar durante conexión ble_gap_update_params(conn_handle, &battery_opt_params); // Medición en runtime void measure_power_consumption(void) { // Usar INA219 (power monitor) vía I2C si disponible // O leer current consumption via ADC mock uint32_t t_start = esp_timer_get_time(); vTaskDelay(10000 / portTICK_PERIOD_MS); // 10 s uint32_t t_elapsed = (esp_timer_get_time() - t_start) / 1000000; ESP_LOGI("PWR", "10 seconds elapsed, check device power meter"); } ``` --- ## 2. Rust + BLE (Interop) Si quieres **Rust para seguridad** + C para BLE: ```rust // main/src/lib.rs (bindgen-generated bindings) extern "C" { pub fn ble_gap_adv_start( own_addr_type: u8, direct_addr: *const libc::c_void, duration_ms: i32, adv_params: *const libc::c_void, cb: extern "C" fn(event: *mut libc::c_void) -> i32, cb_arg: *mut libc::c_void, ) -> i32; } pub fn start_advertising_safe() -> Result<(), String> { unsafe { match ble_gap_adv_start( 0, // own_addr_type std::ptr::null(), -1, // BLE_HS_FOREVER std::ptr::null(), gap_callback, std::ptr::null_mut(), ) { 0 => Ok(()), code => Err(format!("BLE error: {}", code)), } } } ``` --- ## 3. CUDA para Federated Learning Coordinator Si coordinas múltiples ESP32s con modelo ML: ```cpp // coordinator/src/federated_aggregator.cu (pseudo-code) __global__ void aggregate_gradients(float *global_model, float **local_gradients, int num_devices, int model_size) { int idx = blockIdx.x * blockDim.x + threadIdx.x; if (idx < model_size) { float sum = 0.0f; for (int i = 0; i < num_devices; i++) { sum += local_gradients[i][idx]; } global_model[idx] += (sum / num_devices) * LEARNING_RATE; } } void aggregate_from_ble_devices(std::vector gradients) { // Convert uint8_t (quantized) to float // Aggregate on GPU // Send back to ESP32s } ``` --- ## 4. Verificación en Blockchain (Substrate) ```rust // substrate_pallet/src/lib.rs (pseudo) #[pallet::call_index(0)] pub fn register_iot_reading( origin: OriginFor, device_id: u64, temperature: u16, signature: BoundedVec>, ) -> DispatchResult { let caller = ensure_signed(origin)?; // Verificar BLE-signed data ensure!( verify_signature(&device_id.to_le_bytes(), &signature), Error::::InvalidSignature ); // Almacenar en blockchain >::insert((caller, device_id), temperature); Ok(()) } ``` --- **¡Con esto tienes debugging profundo y profesional para BLE + ESP32!**