987 lines
26 KiB
Markdown
987 lines
26 KiB
Markdown
|
|
# 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!**
|