Files
org-hub/learn/BLE_example_exercises.md

987 lines
26 KiB
Markdown
Raw Normal View History

# 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 <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`
```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<uint8_t *> 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<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!**