Files
org-hub/learn/BLE_example_exercises.md

26 KiB

BLE + ESP32: Ejercicios Avanzados & Debugging en Detalle

Parte 1: Ejercicios Codificados (Estructura Completa)


EJERCICIO 1: Beacon Personalizado (Básico)

Proyecto: ble_sensor_beacon

// main/main.c
#include <stdio.h>
#include <string.h>
#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

// 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
// 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}
};
// 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)

// 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

{
    "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

{
    "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

{
    "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

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

openocd -f interface/ftdi/esp32_devkitj_v1.cfg -f target/esp32.cfg

Terminal 2: VSCode Debug

# 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:

# 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

idf.py menuconfig
# → Component config → Compiler options → Enable AddressSanitizer → YES
idf.py build flash monitor

Código con bug

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

// 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

// 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

# 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

# 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++

// 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

// 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:

// 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:

// 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<uint8_t *> gradients) {
    // Convert uint8_t (quantized) to float
    // Aggregate on GPU
    // Send back to ESP32s
}

4. Verificación en Blockchain (Substrate)

// substrate_pallet/src/lib.rs (pseudo)

#[pallet::call_index(0)]
pub fn register_iot_reading(
    origin: OriginFor<T>,
    device_id: u64,
    temperature: u16,
    signature: BoundedVec<u8, ConstU32<64>>,
) -> DispatchResult {
    let caller = ensure_signed(origin)?;
    
    // Verificar BLE-signed data
    ensure!(
        verify_signature(&device_id.to_le_bytes(), &signature),
        Error::<T>::InvalidSignature
    );
    
    // Almacenar en blockchain
    <Readings<T>>::insert((caller, device_id), temperature);
    
    Ok(())
}

¡Con esto tienes debugging profundo y profesional para BLE + ESP32!