upload learn from scratch book, exercises and projects

This commit is contained in:
2025-11-15 14:22:10 +01:00
commit 47ecf2e43c
3 changed files with 3284 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,516 @@
# BLE + ESP32: Síntesis & Roadmap para Edge AI
## Resumen Ejecutivo
Has completado una **masterclass exhaustiva** que cubre:
**Módulo 0**: Fundamentos BLE (PHY, Link Layer, GATT, Seguridad)
**Módulo 1**: Advertising & Discovery (Beacons, Data types)
**Módulo 2**: Connections & GATT (Server, Notificaciones)
**Módulo 3**: Security & Advanced (LESC, Bonding, NVS)
**Debugging**: Logging, GDB+OpenOCD, ASAN, HCI Sniffing
**Ejercicios**: 4 niveles progresivos con código completo
---
## Checklist de Dominio
Antes de avanzar a Edge AI + Blockchain, valida que **entiendes profundamente**:
### Teoría BLE (✓ si respondes sin dudar)
- [ ] Diferencia entre PHY 1M, 2M, Coded. ¿Cuándo usarías cada uno?
- [ ] ¿Por qué BLE usa frequency hopping? ¿Cuál es el parámetro `hop_increment`?
- [ ] ¿Qué es CCCD y por qué es crítico para notificaciones?
- [ ] ¿Cuál es la diferencia entre Notificar e Indicar?
- [ ] ECDH vs Legacy Pairing: ¿Cuáles son los puntos vulnerables?
- [ ] ¿Cómo funciona RPA (Resolvable Private Address)?
### Práctica ESP32 (✓ si compilas sin errors)
- [ ] ¿Puedes implementar un beacon custom en 10 minutos?
- [ ] ¿Puedes crear un GATT server con 3+ características?
- [ ] ¿Puedes hacer pairing LESC + bonding?
- [ ] ¿Puedes debuggear un crash con GDB + stack trace?
- [ ] ¿Puedes optimizar connection parameters para batería?
### NimBLE Internals (✓ si entiendes el flujo)
- [ ] ¿Cómo fluye un evento BLE en NimBLE? (HCI → Host task → app callback)
- [ ] ¿Dónde se almacenan bonded devices? (NVS, qué namespace)
- [ ] ¿Cuál es la diferencia entre `ble_gap_adv_set_fields` y `ble_gap_adv_start`?
- [ ] ¿Por qué `nimble_host_task` es una task FreeRTOS separada?
---
## Integración: Edge AI + BLE + Blockchain
### 1⃣ Scenario: Federated Learning en Wearables
```
┌──────────────────────┐
│ Smartphone Central │ (Orquestador)
│ - Modelo ML global │
│ - Verifica signaturas│
├──────────────────────┤
│ BLE (NimBLE) │
└──────────────────────┘
│ ▲
│ │ (gradientes cuantizados)
▼ │
┌─────────────┐
│ ESP32 #1 │ Reloj (acelerómetro)
│ - TFLite │ → Localdetect caída
│ - Entrena │ → Sube gradientes
└─────────────┘
```
**Implementación BLE:**
- Central (smartphone) se conecta a múltiples periféricos (ESP32).
- ESP32 notifica cambios de modelo cada 10 minutos.
- Central agrega gradientes con CUDA.
- Re-envía modelo actualizado vía BLE.
**Código (conceptual):**
```c
// ESP32: Detecta caída via ML, prepara gradientes
void ml_inference_task(void) {
float *gradients = tflite_get_gradients(); // 4 KB comprimido
// Notificar al central
ble_gatts_notify_custom(conn_handle, grad_handle, gradients, 4096);
}
// Central: Agrega y re-distribuye
void federated_aggregation(std::vector<uint8_t*> all_grads) {
aggregate_on_gpu(all_grads);
broadcast_via_ble(updated_model);
}
```
---
### 2⃣ Scenario: Verificación de Datos en Blockchain
```
┌─────────────────┐
│ ESP32 Sensor │
│ - ADC temp │
│ - Sign with SK │
│ - BLE send │
└─────────────────┘
┌─────────────────┐
│ Smartphone │
│ - Recibe firma │
│ - Verifica PK │
│ - Envía a Dapp│
└─────────────────┘
┌─────────────────┐
│ Blockchain │
│ (Substrate) │
│ - Verifica data │
│ - Almacena hash │
│ - Emite evento │
└─────────────────┘
```
**Criptografía BLE:**
- ESP32 usa SK (private key) para firmar lecturas.
- Central verifica con PK (public key).
- Hash se envía a blockchain para verificación.
---
### 3⃣ Scenario: IoT Mesh con Coordinación
```
Central (Coordinator)
┌──────┼──────┐
│ │ │
Node1 Node2 Node3 (ESP32s con sensores)
│ │ │
└──────┼──────┘
Blockchain
(Consensus)
```
**Protocolo:**
1. Cada nodo es periférico (BLE).
2. Coordinator es central (conecta a todos).
3. Cada 30s, nodos reportan sensores.
4. Coordinator agrega y firma.
5. Envía a blockchain via API.
---
## Próximos Pasos: Roadmap Personalizado
### Fase 1: Validar BLE Mastery (1-2 semanas)
```
[ ] Ejercicio 4 completo: IoT Sensor Sync
[ ] Implementar custom beacon con 128-bit UUID
[ ] Crear GATT server con encriptación LESC
[ ] Debuggear con GDB: set 3+ breakpoints
[ ] Medir consumo de potencia en 3 connection params
```
**Criterio de éxito:** Debuggeas un crash en GATT sin ayuda y optimizas latencia.
---
### Fase 2: TensorFlow Lite Micro + BLE (2-3 semanas)
**Objetivo:** Ejecutar modelo ML en ESP32, actualizarlo via BLE.
**Pasos:**
1. Entrenar modelo pequeño (< 100 KB).
- MobileNetV2 cuantizado.
- DistilBERT para procesamiento de lenguaje.
2. Compilar para ESP32 con TFLite Micro.
```bash
python3 -m tensorflow.lite.python.convert \
--inference_input_type=QUANTIZED_UINT8 \
--inference_output_type=QUANTIZED_UINT8 \
model.h5 model.tflite
xxd -i model.tflite > model.h
```
3. Integrar con BLE para actualizaciones OTA.
```c
void ota_update_model_task(void) {
// Recibe chunks via BLE notification
// Escribe a flash partition
// Reinicia con nuevo modelo
}
```
**Recurso:** TensorFlow Lite for Microcontrollers guide.
---
### Fase 3: Rust + NimBLE Safety (1-2 semanas)
**Objetivo:** Dominar Rust para **seguridad memory-safe**.
**Pasos:**
1. Setups Rust ESP32 toolchain.
```bash
rustup target add xtensa-esp32-espidf
cargo install cargo-espflash
```
2. Reescribir componentes críticos (GAP, GATT).
```rust
pub struct BleDevice {
stack: NimbleStack,
services: Vec<GattService>,
}
impl BleDevice {
pub fn new() -> Result<Self, BleError> {
// Safe initialization
}
}
```
3. Integrar con C via FFI.
**Recurso:** The Embedded Rust Book + esp-idf-hal.
---
### Fase 4: Blockchain Coordination (2-3 semanas)
**Objetivo:** Verificar datos IoT en blockchain (Substrate/Tendermint).
**Pasos:**
1. Setups cadena local.
```bash
substrate-contracts-node --dev
```
2. Escribir pallet Substrate.
```rust
#[pallet::call_index(0)]
pub fn record_iot_reading(
origin: OriginFor<T>,
device_id: u64,
reading: u32,
proof: BoundedVec<u8, ConstU32<64>>,
) -> DispatchResult {
ensure_signed(origin)?;
// Verificar firma BLE
// Almacenar lectura
Ok(())
}
```
3. Conectar ESP32 → Pallet via HTTP/WebSocket.
**Recurso:** Substrate Developer Hub + Ink! (smart contracts).
---
### Fase 5: Federated Learning (3-4 semanas)
**Objetivo:** Entrenar modelo distribuido entre múltiples ESP32s.
**Arquitectura:**
```
Central (Coordinator)
├─ Aggregador ML (PyTorch)
├─ BLE Central
└─ API REST
ESP32 Periféricos
├─ TFLite Micro (inference)
├─ Gradientes locales (4-16 KB)
└─ BLE Periférico
```
**Pasos:**
1. Implementar local SGD en ESP32.
```c
void sgd_step(float *weights, float *gradients, int size) {
for (int i = 0; i < size; i++) {
weights[i] -= LEARNING_RATE * gradients[i];
}
}
```
2. Agregación en Central (PyTorch/Rust).
```python
def aggregate_gradients(esp32_grads):
return torch.stack(esp32_grads).mean(dim=0)
```
3. Broadcasting del modelo nuevo.
**Recurso:** Federated Learning with PyTorch + TensorFlow Federated.
---
## Matriz de Decisión: ¿Qué Aprender Primero?
| Necesidad | Prioridad | Tiempo | Roadmap |
|-----------|-----------|--------|---------|
| **Validar BLE** | 🔴 Ahora | 1w | Fase 1 |
| **ML en Edge** | 🟠 Pronto | 2w | Fase 2 |
| **Memory Safety** | 🟠 Pronto | 2w | Fase 3 |
| **Blockchain** | 🟡 Importante | 3w | Fase 4 |
| **Federated Learning** | 🟢 Después | 4w | Fase 5 |
**Recomendación:** Fases 1 → 2 → 4 (en paralelo 3 si tiempo).
---
## Herramientas Esenciales
### Debugging & Testing
```bash
# Terminal 1: OpenOCD
openocd -f interface/esp32_prog.cfg -f target/esp32.cfg
# Terminal 2: GDB
xtensa-esp32-elf-gdb build/app.elf
(gdb) target remote localhost:3333
# Terminal 3: Monitor
idf.py -p /dev/ttyUSB0 monitor
# Análisis de logs
idf.py monitor | tee session.log
grep "BLE_GATT\|ERROR" session.log | tail -20
```
### Mobile Testing
- **nRF Connect** (Android/iOS): Scan, connect, notif, indicaciones.
- **LightBlue** (iOS): RSSI, timing.
### Hardware Sniffing (Avanzado)
- **Wireshark + Ubertooth One**: Captura OTA de conexiones.
- **Raspberry Pi + OpenOCD**: Debuggeo JTAG local.
---
## Referencias Finales
### Especificaciones (Obligatorios)
1. **Bluetooth Core Spec Vol 4 Part A-H**: https://bluetooth.com
2. **ESP32 Technical Reference Manual**: Espressif docs
3. **Apache NimBLE Architecture**: mynewt.apache.org
### Documentación Técnica
- **ESP-IDF Programming Guide**: docs.espressif.com
- **TensorFlow Lite Micro**: tensorflow.org/lite/microcontrollers
- **Substrate Developer Hub**: substrate.dev
### Repositorios Clave
```bash
# ESP-IDF ejemplos
git clone https://github.com/espressif/esp-idf
cd esp-idf/examples/bluetooth/ble_get_started
# NimBLE
git clone https://github.com/apache/mynewt-nimble
# Raspberry Pi Pico + NimBLE
git clone https://github.com/h2zero/NimBLE-Arduino
# TensorFlow Lite Micro ESP32
git clone https://github.com/tensorflow/tflite-micro
# Substrate template
git clone https://github.com/substrate-developer-hub/substrate-node-template
```
### Papers & Blogs Recomendados
1. **"BLE Security Analysis"** - NoveBits (2024)
2. **"Frequency Hopping in BLE"** - arxiv.org/pdf/... (2019)
3. **"Federated Learning for IoT"** - Google Research (2022)
4. **"Edge AI Inference Optimization"** - TensorFlow Blog (2023)
---
## Ejercicio Final Integrador: Mini-Proyecto Capstone
**Propósito:** Combina BLE + ML + Blockchain.
### Especificación
**Sistema:** "Smart Home Energy Monitor"
```
┌──────────────────┐
│ Smart Meter │
│ (ESP32 + Sensor)│
│ - Mide potencia │
│ - Detecta anomalías (TFLite)
│ - BLE periférico│
└──────────────────┘
│ BLE
┌──────────────────┐
│ Hub (Central) │
│ - Recibe datos │
│ - Agrega stats │
│ - Firma + blockchain
└──────────────────┘
│ HTTP
Blockchain
(Substrate)
```
### Requisitos
**Hardware:**
- 1x ESP32 con sensor ADC (simular consumo).
- 1x Smartphone o PC como central.
**Software:**
- **BLE Periférico (ESP32):**
- Beacon: consumo actual (W).
- GATT Service: historiales (últimas 10 lecturas).
- Notificaciones: cada lectura nueva.
- LESC + Bonding.
- **BLE Central (Smartphone/PC):**
- Conectar múltiples medidores.
- Agregar consumo total.
- Detectar anomalías (> 2σ).
- **Blockchain (Local Substrate):**
- Pallet `EnergyReading` con readings.
- Verificación de firma (public key ESP32).
- Query: consumo por hora/día.
### Entregables
1. Código BLE (periférico + central).
2. Pallet Substrate + tests.
3. Documentación: arquitectura, security, resultados de pruebas.
4. Análisis: latencia BLE, consumo energético, throughput.
### Criterios de Éxito
✅ Periférico con LESC + bonding persistente.
✅ Central agrega datos de 2+ periféricos.
✅ Detección de anomalías (algoritmo simple OK).
✅ Blockchain verifica firma y registra.
✅ Demo: De sensor a blockchain en < 5s.
---
## Último Consejo: Tu Diferenciador Competitivo
Combine **estas 3 áreas** rara vez juntas:
1. **BLE Low-Level** (PHY, Link Layer, Seguridad).
2. **Embedded Rust** (Memory-safety en IoT).
3. **Blockchain Smart Contracts** (Verificación descentralizada).
Esto te posiciona como **experto en Edge AI + Distributed Trust**, muy demandado en:
- **Autonomous vehicles**: Coordinación de sensores.
- **Smart cities**: Redes IoT verificadas.
- **Healthcare wearables**: Datos verificables en cadena.
- **Industria 4.0**: IoT industrial con blockchain.
---
**¡Felicidades! Ya tienes todo el conocimiento. Ahora, a construir.** 🚀
---
## Quick Reference: Comandos Útiles
```bash
# Build & Flash
idf.py set-target esp32
idf.py build
idf.py -p /dev/ttyUSB0 flash monitor
# Menuconfig
idf.py menuconfig
# → Component config → Bluetooth → NimBLE vs Bluedroid
# → Log level → DEBUG
# GDB + OpenOCD
openocd -f interface/ftdi/esp32_devkitj_v1.cfg -f target/esp32.cfg &
xtensa-esp32-elf-gdb build/app.elf
(gdb) target remote localhost:3333
(gdb) thb app_main
(gdb) c
# Monitoring
idf.py monitor
# Dentro del monitor:
# Ctrl+T Ctrl+L → elegir nivel log
# Ctrl+T Ctrl+R → reset
# Ctrl+] → salir
# NVS (bonding)
idf.py erase-flash # Limpia bonding
# Memory profiling
idf.py size-components build/app.elf
```
---
**Documento finalizado. ¡Adelante con el aprendizaje!** 📚

View File

@@ -0,0 +1,986 @@
# 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!**