Files
org-hub/learn/BLE_ESP32_Fundamentals.md

1783 lines
52 KiB
Markdown
Raw Permalink Normal View History

# BLE + ESP32: Masterclass Teórico-Práctico
## De Cero a Experto en Bluetooth Low Energy y Desarrollo Embedded
---
## TABLA DE CONTENIDOS
1. [Módulo 0: Fundamentos de BLE](#módulo-0-fundamentos-de-ble)
2. [Módulo 1: Advertising & Discovery](#módulo-1-advertising--discovery)
3. [Módulo 2: Connections & GATT](#módulo-2-connections--gatt)
4. [Módulo 3: Security & Advanced Topics](#módulo-3-security--advanced-topics)
5. [Debugging Masterclass](#debugging-masterclass)
6. [Ejercicios Prácticos Progresivos](#ejercicios-prácticos-progresivos)
7. [Referencias & Recursos](#referencias--recursos)
---
# MÓDULO 0: FUNDAMENTOS DE BLE
## 0.1 ¿Qué es Bluetooth Low Energy (BLE)?
BLE es una tecnología inalámbrica diseñada para **baja potencia, corto alcance y bajo consumo de energía**. Se diferencia del **Classic Bluetooth** (BR/EDR) en:
| Característica | Classic BT | BLE |
|---|---|---|
| **Potencia** | ~100 mW | ~10 mW promedio |
| **Alcance** | ~100 m | ~100 m (BLE 5.x) |
| **Latencia** | 100 ms | <10 ms |
| **Ancho de banda** | 720 kbps | 1-2 Mbps |
| **Caso de uso** | Audio, streaming | Sensores, IoT, wearables |
| **Stack** | Complejo (Bluedroid+BR/EDR) | Simplificado (NimBLE BLE-only) |
**¿Por qué BLE importa en IoT/Edge AI?**
- Actualizaciones de modelos ML en dispositivos wearables sin descargar batería.
- Comunicación entre edge devices (sensores + nodos) con bajo overhead.
- Integración con blockchain: coordinación de múltiples IoT nodes.
---
## 0.2 Arquitectura del Stack BLE
El stack BLE se organiza en **capas**, de forma similar a OSI:
```
┌─────────────────────────────────────┐
│ APLICACIÓN (Application) │ ← Tu código
├─────────────────────────────────────┤
│ GATT (Generic Attribute Profile) │ ← Servicios y características
├─────────────────────────────────────┤
│ GAP (Generic Access Profile) │ ← Advertising, conexiones
├─────────────────────────────────────┤
│ ATT (Attribute Protocol) │ ← Lectura/escritura de atributos
├─────────────────────────────────────┤
│ L2CAP (Logical Link Control) │ ← Multiplexación de canales
├─────────────────────────────────────┤
│ Link Layer (Enlace de datos) │ ← Control de conexión, hop freq
├─────────────────────────────────────┤
│ PHY (Física) │ ← Radio 2.4 GHz, modulación
└─────────────────────────────────────┘
├─── CONTROLLER ───────────────────── (Firmware en el chip)
│ (HCI: Host Controller Interface)
└─── HOST ─────────────────────────── (Software en tu aplicación)
(Bluedroid o NimBLE en ESP32)
```
### Conceptos Clave:
**1. Controller vs Host**
- **Controller**: Firmware bajo nivel en el chip (RF, Link Layer). No lo tocas en ESP32; está pre-compilado.
- **Host**: Software que interpreta comandos del aplicación y genera HCI commands para el controller.
- **HCI (Host Controller Interface)**: Protocolo de comunicación entre host y controller. En ESP32 usa transporte RAM (no UART).
**2. Roles BLE**
- **Peripheral (Esclavo)**: Anuncia datos, acepta conexiones. Ej: reloj inteligente.
- **Central (Maestro)**: Escanea, inicia conexiones. Ej: smartphone.
**3. Estados GAP**
- **Standby**: Inactivo.
- **Advertising**: Transmite paquetes de publicidad.
- **Scanning**: Escucha paquetes de publicidad.
- **Connected**: Vinculado con otra dispositivo.
---
## 0.3 Physical Layer (PHY): La Radio
### Banda de Frecuencia
BLE opera en la banda ISM (Industrial, Scientific, Medical) de **2.4 GHz**, compartida con WiFi, Zigbee, etc.
```
┌─────────────────────────────────────────────────────────────┐
│ 2.4 GHz ISM Band (2400 - 2483.5 MHz) │
├─────────────────────────────────────────────────────────────┤
│ BLE: 37 canales de datos + 3 canales de advertising │
│ Espaciados cada 2 MHz │
│ │
│ Ch 37: 2402 MHz (Adv) │
│ Ch 38: 2426 MHz (Adv) │
│ Ch 39: 2480 MHz (Adv) │
│ │
│ Ch 0-36: 2404 - 2480 MHz (Datos + algunas adv ext) │
└─────────────────────────────────────────────────────────────┘
```
### Modulación y Velocidades (PHYs)
| PHY | Velocidad | Modulación | Uso |
|---|---|---|---|
| **LE 1M** (default) | 1 Mbps | GFSK | Compatibilidad, bajo costo |
| **LE 2M** (BLE 5.0+) | 2 Mbps | GFSK | Mayor throughput, menos batería en tx |
| **LE Coded** (BLE 5.0+) | 125/500 kbps | GFSK + FEC | Largo alcance, menor SNR |
**Nota**: Todos los chips ESP32 modernos soportan LE 1M. LE 2M y LE Coded dependen de la variante (C3, S3, etc.).
### Formato de Paquete BLE (Uncoded PHY)
```
┌───────┬──────────────┬───────────┬─────┐
│Preamble│Access Address│ PDU │ CRC │
│ 1 byte │ 4 bytes │ 2-257B │3 byte│
└───────┴──────────────┴───────────┴─────┘
└────────────────────────────────┘
Link Layer Packet (LL PDU)
```
- **Preamble** (0xAA/0x55): Sincronización de frecuencia.
- **Access Address** (4B): ID del dispositivo o conexión.
- **PDU**: Datos; puede ser advertising PDU o data channel PDU.
- **CRC** (3B): Cyclic Redundancy Check para detectar errores.
---
## 0.4 Link Layer: La Inteligencia
### Frequency Hopping (AFH)
BLE evita colisiones e interferencias usando **Adaptive Frequency Hopping (AFH)**:
1. **Secuencia pseudo-aleatoria**: Los dispositivos emparejados comparten un `Access Address` y un `hop increment` (derivado de direcciones MAC).
2. **Salto por conexión**: Salta entre los 37 canales de datos cada `connection interval` (7.5 ms - 4 s).
3. **Channel map**: Si ciertos canales tienen mucho ruido, se excluyen dinámicamente.
**Fórmula**: Nuevo canal = (Anterior + hop_increment) % 37
```python
# Pseudocódigo del hop
def next_channel(current_ch, hop_inc, channel_map):
"""Calcula el próximo canal en el hop."""
unmapped_channels = channel_map # Máscara de 37 bits
next_unmapped = (current_ch + hop_inc) % 37
# Si está excluido, reintenta con el siguiente
while not test_bit(unmapped_channels, next_unmapped):
next_unmapped = (next_unmapped + 1) % 37
return next_unmapped
```
### Paquetes Advertising PDU
Formato de un paquete de publicidad:
```
┌──────────────────┬─────────────────────────┐
│ Advertising Hdr │ Advertising Payload │
├──────────────────┼─────────────────────────┤
│ Tipo | Tx/Rx Adr │ MAC + Datos Payload │
│ 1B │ 1B │ ≤ 255 bytes │
└──────────────────┴─────────────────────────┘
```
**Tipos de Advertising PDU:**
- `ADV_IND` (0x00): Connectable undirected (periférico abierto).
- `ADV_DIRECT_IND`: Connectable directed (a un central específico).
- `ADV_NONCONN_IND`: Non-connectable (beacon).
- `ADV_SCAN_IND`: Scannable undirected (periférico + scan response).
- `SCAN_RSP`: Respuesta a scan request.
---
## 0.5 ATT & GATT: Estructura de Datos
### ATT (Attribute Protocol)
ATT es el protocolo **crudo** de lectura/escritura de **atributos** (bloques de datos con UUID).
**Operaciones ATT:**
```
Cliente (Central) Servidor (Peripheral)
│ │
├─ Read Request ────────────────> │
│ Read Response │
<────────────────────────────────┤
│ │
├─ Write Request ───────────────> │
│ Write Response │
<────────────────────────────────┤
│ │
├─ Notify (unsolicited) <───────── │
│ (sin confirmación) │
│ │
├─ Indicate (unsolicited) <────── │
│ (con confirmación) │
│ ─── Confirmation ───────────> │
```
### GATT (Generic Attribute Profile)
GATT es una **capa de semántica** sobre ATT que organiza atributos en:
1. **Servicios**: Colecciones de funcionalidad. Ej: Heart Rate Service (HRS).
2. **Características**: Propiedades dentro de un servicio. Ej: Heart Rate Measurement.
3. **Descriptores**: Metadatos de una característica. Ej: CCCD (Client Characteristic Configuration).
**Estructura GATT:**
```
Service (UUID 0x180D = Heart Rate Service)
├─ Characteristic 1 (UUID 0x2A37 = Heart Rate Measurement)
│ ├─ Value: [BPM value]
│ ├─ Properties: Read, Notify
│ └─ Descriptor (CCCD): [0x01, 0x00] = Notificaciones enabled
└─ Characteristic 2 (UUID 0x2A38 = Body Sensor Location)
├─ Value: [0x01 = Chest]
└─ Properties: Read
```
**CCCD (Client Characteristic Configuration Descriptor)**:
- Bitmask que el cliente escribe para activar/desactivar notificaciones.
- `0x0001`: Notificaciones.
- `0x0002`: Indicaciones.
---
## 0.6 Bluedroid vs NimBLE: Comparativa Técnica
### ESP-Bluedroid
**Arquitectura:**
```
┌─────────────────┐
│ esp_* APIs │ ← Tu código
├─────────────────┤
│ BTC (Bluetooth │ ← Profile manager, HCI handlers
│ Transport Ctrl) │
├─────────────────┤
│ BTU (Bluetooth │ ← L2CAP, GATT, SMP, ATT
│ Upper Layer) │
├─────────────────┤
│ HCI Interface │ ← Comandos al controller
└─────────────────┘
```
**Características:**
- Soporta Classic BT (BR/EDR) + BLE.
- Memora RAM: **~200 KB heap** típicamente.
- Velocidad init: ~631 ms (ver benchmark).
- Mayor footprint de código.
**Cuándo usar Bluedroid:**
- Necesitas Classic BT (audio Bluetooth clásico).
- Compatibilidad máxima con código Android.
### Apache NimBLE
**Arquitectura:**
```
┌─────────────────┐
│ ble_* APIs │ ← Tu código
├─────────────────┤
│ NimBLE Host │ ← Task separado en FreeRTOS
│ (event-driven) │
├─────────────────┤
│ HCI Interface │ ← Comandos al controller
└─────────────────┘
```
**Características:**
- **BLE-only** (no Classic BT).
- RAM: **~70-80 KB heap**, mucho más eficiente.
- Velocidad init: **~209 ms** (3x más rápido).
- Open-source Apache license.
- Mejor para deep sleep / battery-powered devices.
**Cuándo usar NimBLE:**
- Solo necesitas BLE (99% de IoT moderno).
- Quieres optimizar potencia y memoria.
- Prefieres open-source.
### Recomendación
**🎯 Para tu roadmap (Edge AI + IoT + Blockchain):**
Usa **NimBLE** porque:
1. Menor footprint → más espacio para ML models.
2. Mejor para federated learning (múltiples nodos IoT).
3. Comunidad open-source muy activa (Apache).
4. Por defecto moderno en nuevos proyectos ESP32.
---
## 0.7 Seguridad BLE: Introducción Conceptual
### Legacy Pairing (BLE 4.1 y anteriores)
1. **Intercambio de claves Temporary Link Key (TK)** basado en IO capabilities.
2. **Generación STK** (Short Term Key) via algoritmo simple (vulnerable).
3. **Encriptación** de la conexión con STK.
**Vulnerabilidades conocidas:**
- KNOB attack (Key Negotiation Of Bluetooth).
- Downgrade attacks.
### LE Secure Connections (LESC, BLE 4.2+)
1. **Generación de pares ECDH** (Elliptic Curve Diffie-Hellman) en cada dispositivo.
2. **Intercambio de claves públicas** (seguro matemáticamente).
3. **Generación LTK** (Long Term Key) determinista y fuerte.
4. **Variantes de autenticación:**
- **Numeric Comparison**: Ambos muestran 6 dígitos; usuario confirma si coinciden.
- **Passkey Entry**: Uno muestra código, otro lo digita.
- **Just Works**: Sin confirmación (sin MITM protection).
- **Out Of Band (OOB)**: Intercambio de claves por canal alternativo (NFC, etc.).
**Matemática simplificada:**
```
Dispositivo A: d_a = random(); Q_a = d_a * G (ECDH público)
Dispositivo B: d_b = random(); Q_b = d_b * G
Intercambio: Q_a <-> Q_b
Shared secret: DHKey = d_a * Q_b = d_b * Q_a = (d_a * d_b) * G
LTK = PRES(DHKey, other_params) [derivado del shared secret]
```
**En ESP32:**
- LESC es el default en NimBLE.
- Generación ECDH es **blocking ~160 ms** (limitación ROM).
- Requiere supervision_timeout >= 165 ms durante pairing.
---
## 0.8 Consumo de Potencia y Parámetros Críticos
### Ecuación General
```
Consumo promedio = (T_on * I_active + T_sleep * I_sleep) / T_total
```
### Parámetros de Conexión
| Parámetro | Rango | Impacto |
|---|---|---|
| **Connection Interval** | 7.5 ms - 4 s | Menor = mayor latencia pero más potencia |
| **Peripheral Latency** | 0 - 499 | Nodo puede saltear N intervalos (reduce despertares) |
| **Supervision Timeout** | 100 ms - 32 s | Si no recibe dato en este tiempo, desconecta |
| **MTU (Max Transfer Unit)** | 23 - 251 bytes | Mayor MTU = menos fragmentación, menos paquetes |
**Ejemplo optimizado para batería:**
```c
// Connection interval 1 segundo, permite saltar 199 de cada 200 eventos
ble_gap_upd_params conn_params = {
.itvl_min = 800, // 1000 ms
.itvl_max = 800,
.latency = 199, // Puede saltear hasta 199 intervalos
.supervision_timeout = 320, // 3.2 segundos
.min_ce_len = 0x00,
.max_ce_len = 0x00
};
```
---
# MÓDULO 1: ADVERTISING & DISCOVERY
## 1.1 Ciclo de Vida del Advertising
### Estados del Periférico
```
┌─────────────┐
│ STANDBY │
│ (inactivo) │
└──────┬──────┘
│ esp_ble_gap_start_advertising()
┌─────────────┐
│ ADVERTISING │◄──────────────────────┐
│ (cada ~100 │ │
│ eventos, │ │
│ hopping │ │
│ canales) │ │
└──────┬──────┘ │
│ (remoto) Connection request │
▼ │
┌─────────────┐ │
│ CONNECTED │────► Disconnect ──────┘
│ │
└─────────────┘
```
### Advertising Event
**Cada N milisegundos:**
1. ESP32 transmite paquete de publicidad en **Ch 37**.
2. Espera ~120 μs por **SCAN_REQ** (si scannable).
3. Si recibe SCAN_REQ, transmite **SCAN_RSP** en Ch 37.
4. Salta a **Ch 38**, repite.
5. Salta a **Ch 39**, repite.
6. Espera hasta el próximo `advertising interval` (ej: 100 ms).
---
## 1.2 Estructura de Advertising Data
Los datos de publicidad se empaquetan en **TLV (Type-Length-Value)** blocks:
```
┌──────┬────────┬─────────────────┐
│ Type │ Length │ Value │
│ 1B │ 1B │ Length bytes │
└──────┴────────┴─────────────────┘
^ ^ ^
│ │ └─ Contenido real (flags, UUID, etc.)
│ └─ Tamaño en bytes
└─ Tipo de dato (0x01 = Flags, 0x08 = Name, etc.)
```
### Tipos Comunes (AD Types)
| AD Type | Valor | Significado |
|---------|-------|-----------|
| `0x01` | Flags | Propiedades del dispositivo |
| `0x08` | Shortened Local Name | Nombre corto |
| `0x09` | Complete Local Name | Nombre completo |
| `0x0A` | TX Power Level | Potencia de transmisión |
| `0x16` | Service Data (16-bit UUID) | Datos asociados a servicio |
| `0x20` | Service Data (32-bit UUID) | Idem pero UUID 32-bit |
| `0x21` | Service Data (128-bit UUID) | Idem pero UUID 128-bit |
### Flags Comunes
```c
#define ESP_BLE_ADV_FLAG_LIMIT_DISC 0x01 // Limited Discoverable Mode
#define ESP_BLE_ADV_FLAG_GEN_DISC 0x02 // General Discoverable Mode
#define ESP_BLE_ADV_FLAG_BREDR_NOT_SPT 0x04 // BR/EDR Not Supported
#define ESP_BLE_ADV_FLAG_DMR_CONTROLLER 0x08 // Dual Mode Controller
#define ESP_BLE_ADV_FLAG_DMR_HOST 0x10 // Dual Mode Host
```
**Ejemplo de payload de advertising completo:**
```
01 01 06 → Flags: 0x06 (General Discoverable, BR/EDR Not Supported)
09 09 45 53 50 33 32 5F 42 4C 45 → Name: "ESP32_BLE" (9 bytes)
02 0A F4 → TX Power: -12 dBm (0xF4 = -12 en two's complement)
Total: 15 bytes de 31 disponibles
```
---
## 1.3 Beacons: Tipos y Formatos
Los **beacons** son dispositivos que no necesitan conexión; solo transmiten datos periódicamente.
### iBeacon (Apple)
**Formato:**
```
Company ID (Apple 0x004C) + Type + Major + Minor + TX Power
2B + 1B + 2B + 2B + 1B
```
**Ejemplo en Service Data:**
```c
uint8_t adv_data[] = {
0x02, 0x01, 0x06, // Flags
0x1A, 0xFF, // Service Data type
0x4C, 0x00, // Company ID: 0x004C (Apple)
0x02, 0x15, // iBeacon type (0x02) + version (0x15)
// UUID: 12345678-1234-5678-1234-56789ABCDEF0
0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78,
0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0,
0x12, 0x34, // Major: 0x1234
0x56, 0x78, // Minor: 0x5678
0xC3 // TX Power: -61 dBm
};
```
### Eddystone (Google/open)
Más flexible que iBeacon. Formatos:
- **Eddystone-URL**: URL corta (prefijo comprimido).
- **Eddystone-UID**: UUID 16B + instancia (sensores).
- **Eddystone-TLM**: Telemetría (batería, temp).
- **Eddystone-EID**: Ephemeral ID (privacidad).
---
## 1.4 API: Bluedroid Beacon
### Código Ejemplo (Conceptual)
```c
#include "esp_gatt_defs.h"
#include "esp_gap_ble_api.h"
#include "esp_bt.h"
// Advertising data: Name + TX Power
static uint8_t adv_data[] = {
0x02, 0x01, 0x06, // Flags
0x09, 0x09, 'B', 'L', 'U', 'E', 'D', 'R', 'O', 'I', 'D', // Name
0x02, 0x0A, 0xF4 // TX Power: -12 dBm
};
static uint8_t scan_rsp_data[] = {
0x02, 0x0A, 0xF4, // TX Power
0x0B, 0x09, 'h', 't', 't', 'p', 's', ':', '/', '/', 'e', 's', 'p'
};
// GAP Callback
static void esp_gap_cb(esp_gap_ble_cb_event_t event,
esp_ble_gap_cb_param_t *param) {
switch (event) {
case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT:
ESP_LOGI("GAP", "Advertising data set");
esp_ble_gap_start_advertising(&adv_params);
break;
case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
if (param->adv_start_cmpl.status == ESP_BT_STATUS_SUCCESS) {
ESP_LOGI("GAP", "Advertising started");
}
break;
// ... más eventos
}
}
void app_main(void) {
// Inicializar NVS
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES) {
nvs_flash_erase();
nvs_flash_init();
}
// Liberar memoria de Classic BT
esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT);
// Init Bluetooth
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 Bluedroid
esp_bluedroid_init();
esp_bluedroid_enable();
// Registrar callback GAP
esp_ble_gap_register_callback(esp_gap_cb);
// Configurar parámetros de advertising
esp_ble_adv_params_t adv_params = {
.adv_int_min = 0x0020, // 20 ms
.adv_int_max = 0x0040, // 40 ms
.adv_type = ADV_TYPE_NONCONN_IND, // Non-connectable
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
.channel_map = ADV_CHNL_ALL,
.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
};
// Establecer datos de advertising
esp_ble_gap_config_adv_data_raw(adv_data, sizeof(adv_data));
esp_ble_gap_config_scan_rsp_data_raw(scan_rsp_data, sizeof(scan_rsp_data));
}
```
---
## 1.5 API: NimBLE Beacon
### Código Ejemplo (Conceptual)
```c
#include "host/ble_hs.h"
#include "host/util/util.h"
#include "nimble/nimble_port.h"
static uint8_t own_addr_type;
// Inicializar GAP
static int adv_init(void) {
struct ble_svc_gap_params gap_params;
struct ble_hs_cfg cfg;
struct ble_gap_adv_params adv_params;
struct ble_gap_adv_set_fields adv_fields;
// Asegurar dirección válida
ble_hs_util_ensure_addr(0);
ble_hs_id_infer_auto(0, &own_addr_type);
// Formato de datos de advertising
memset(&adv_fields, 0, sizeof(adv_fields));
// Flags
adv_fields.flags = BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP;
// Nombre
adv_fields.name = (uint8_t *)"NimBLE_Beacon";
adv_fields.name_len = strlen("NimBLE_Beacon");
adv_fields.name_is_complete = 1;
// TX Power
adv_fields.tx_pwr_lvl_is_present = 1;
adv_fields.tx_pwr_lvl = -12; // dBm
rc = ble_gap_adv_set_fields(&adv_fields);
if (rc != 0) {
ESP_LOGE("ADV", "ble_gap_adv_set_fields failed: %d", rc);
return rc;
}
// Configurar parámetros de advertising
memset(&adv_params, 0, sizeof(adv_params));
adv_params.conn_mode = BLE_GAP_CONN_MODE_NON; // Non-connectable
adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN; // General discoverable
adv_params.itvl_min = BLE_GAP_ADV_ITVL_MS(20); // 20 ms
adv_params.itvl_max = BLE_GAP_ADV_ITVL_MS(40); // 40 ms
rc = ble_gap_adv_start(own_addr_type, NULL, BLE_HS_FOREVER, &adv_params,
NULL, NULL);
if (rc != 0) {
ESP_LOGE("ADV", "ble_gap_adv_start failed: %d", rc);
return rc;
}
return 0;
}
// Sync callback (cuando NimBLE está listo)
static void ble_app_on_sync(void) {
int rc = adv_init();
assert(rc == 0);
}
void app_main(void) {
// Inicializar NVS
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES) {
nvs_flash_erase();
nvs_flash_init();
}
// Init Bluetooth
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();
// Registrar callback sync
ble_hs_cfg.sync_cb = ble_app_on_sync;
// Iniciar stack NimBLE
nimble_port_freertos_init(BLE_HS_TASK_PRIORITY);
}
```
---
## 1.6 Ejercicio Práctico 1: Beacon Custom
**Objetivo**: Crear un beacon que transmita datos custom (temperatura, estado sensor).
**Requisitos:**
1. Usa NimBLE.
2. Implementa Service Data con UUID 16-bit custom.
3. Transmite 2 bytes (temperatura + humedad mocked).
4. Advertising interval: 100 ms.
5. TX Power: -10 dBm.
**Pasos:**
1. Crea un proyecto nuevo en `examples/bluetooth/ble_beacon_custom/`.
2. Incluye lectura ADC simulada (temperatura mocked).
3. Actualiza datos cada 500 ms (sin reiniciar advertising).
4. Verifica en nRF Connect que los datos cambian.
---
# MÓDULO 2: CONNECTIONS & GATT
## 2.1 Ciclo de Vida de Conexión
### Central (Smartphone, Tuya App, etc.)
```
SCAN ──> DISCOVER ──> CONNECT ──> GATT ──> DISCONNECT
│ │ │ │
│ Busca │ Envía │ Espera │ Lee/
│ beacons │ LE_CONN │ CONNECT │ Escribe/
│ que se │ _REQ en │ _IND │ Notif
│ anuncian│ Ch 37/38/ │ │
│ │ 39 │ │
```
### Periférico (ESP32)
```
ADVERTISING ──> CONNECT_IND ──> CONNECTED ──> [GATT] ──> DISCONNECT
│ │ │
│ Transmite │ Central envia │ Intercambia
│ en 37/38/39 │ LE_CONN_REQ en │ atributos y
│ │ cualquier canal │ datos
│ │ (parámetros) │
```
---
## 2.2 Connection Request: Parámetros Clave
Cuando un central inicia conexión, negocia:
```c
struct ble_gap_conn_params {
uint16_t scan_itvl; // Scan interval (central)
uint16_t scan_window; // Scan window (central)
uint16_t itvl_min; // Min connection interval (periférico)
uint16_t itvl_max; // Max connection interval
uint16_t latency; // Peripheral latency
uint16_t supervision_timeout; // Supervision timeout
uint16_t min_ce_len; // Min connection event length
uint16_t max_ce_len; // Max connection event length
};
```
**Connection Interval**: Tiempo entre dos eventos de conexión.
- Rango: 7.5 ms - 4 s (en múltiplos de 1.25 ms).
- **Valores típicos:**
- Low latency: 7.5 - 30 ms (gaming, streaming).
- Normal: 30 - 100 ms (sensores, actuadores).
- Battery: 1 - 4 s (wearables, medidores).
**Peripheral Latency**: Nodo puede ignorar N connection events.
- Ej: latency=199 en 1s interval = nodo responde cada 200s (5 minutos).
- **Trade-off**: Menor consumo vs. menor responsividad.
**Supervision Timeout**: Máximo tiempo sin recibir datos antes de desconexión.
- Debe ser > (1 + latency) * interval_max * 2.
- Ej: interval=100ms, latency=199 → timeout >= 40 segundos.
---
## 2.3 MTU Negotiation
**MTU (Maximum Transmission Unit)**: Máximo tamaño de datos útil en ATT.
```
┌──────────────┬────────────┬─────────────────┐
│ Link Layer │ L2CAP HDR │ ATT Data │
│ (4 bytes) │ (4 bytes) │ (= MTU - 3) │
└──────────────┴────────────┴─────────────────┘
Total ≤ 251 bytes (BLE packet limit)
```
- **Default MTU**: 23 bytes → 20 bytes de datos útil.
- **Max MTU**: 251 bytes → 248 bytes de datos útil.
**Negociación:**
```
Client → Server: MTU Exchange Request (MTU_desired = 128)
Server → Client: MTU Exchange Response (MTU_server = 251)
Negociado: MTU = min(128, 251) = 128
```
**En NimBLE:**
```c
// Configurar MTU esperado durante init
ble_hs_cfg.att_max_mtu = 251;
// En runtime, solicitar MTU desde central
ble_gattc_exchange_mtu(conn_handle);
```
---
## 2.4 GATT Services y Characteristics
### Estructura Jerárquica
```
┌────────────────────────────┐
│ SERVICE (HRS 0x180D) │
│ ├─ Start Handle: 1 │
│ └─ End Handle: 10 │
├────────────────────────────┤
│ CHARACTERISTIC 1 (0x2A37) │
│ ├─ Handle: 2 │
│ ├─ Value Handle: 3 │
│ ├─ Properties: R,N │
│ │ │
│ └─ DESCRIPTOR CCCD │
│ ├─ Handle: 4 │
│ └─ Value: [0x00, 0x00] │
├────────────────────────────┤
│ CHARACTERISTIC 2 (0x2A38) │
│ ├─ Handle: 5 │
│ ├─ Value Handle: 6 │
│ └─ Properties: R │
└────────────────────────────┘
```
### Declaración de Servicio (Bluedroid)
```c
// Tabla de atributos
static const esp_gatts_attr_db_t gatt_db[GATTS_CHAR_IDX_NB] = {
// Declaración de servicio
[0] = {
{ESP_GATT_AUTO_RSP},
{
ESP_UUID_LEN_16,
(uint8_t *)&primary_service_uuid, // 0x2800
ESP_GATT_PERM_READ,
2, // 16-bit UUID de servicio
(uint8_t *)&service_uuid // 0x180D (HRS)
}
},
// Declaración de característica
[1] = {
{ESP_GATT_AUTO_RSP},
{
ESP_UUID_LEN_16,
(uint8_t *)&character_declaration_uuid, // 0x2803
ESP_GATT_PERM_READ,
1, // Properties
(uint8_t *)&char_prop_read_notify // 0x12
}
},
// Valor de característica
[2] = {
{ESP_GATT_AUTO_RSP},
{
ESP_UUID_LEN_16,
(uint8_t *)&heart_rate_char_uuid, // 0x2A37
ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE,
8,
(uint8_t *)&heart_rate_value // [BPM]
}
},
// CCCD Descriptor
[3] = {
{ESP_GATT_AUTO_RSP},
{
ESP_UUID_LEN_16,
(uint8_t *)&character_client_config_uuid, // 0x2902
ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE,
2,
(uint8_t *)&cccd_value // [0x00, 0x00]
}
}
};
```
### Declaración de Servicio (NimBLE)
```c
static const struct ble_gatt_svc_def gatt_svr_svcs[] = {
{
.type = BLE_GATT_SVC_TYPE_PRIMARY,
.uuid = BLE_UUID16_DECLARE(0x180D), // Heart Rate Service
.characteristics = (struct ble_gatt_chr_def[]) {
{
.uuid = BLE_UUID16_DECLARE(0x2A37), // HRM
.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,
},
{0}
},
},
{0}
},
},
{0}
};
```
---
## 2.5 Notificaciones vs Indicaciones
| Propiedad | Notificación | Indicación |
|-----------|--------------|-----------|
| **Confirmación** | No | Sí |
| **Velocidad** | Rápida | Más lenta |
| **Fiabilidad** | Sin garantía | Garantizada |
| **Caso de uso** | Streaming datos | Eventos críticos |
| **Evento NimBLE** | `BLE_GAP_EVENT_NOTIFY_TX` | `BLE_GAP_EVENT_CONN_UPDATE` |
**Flujo de Indicación:**
```
Peripheral → INDICATE → Central
▼ Procesa dato
Peripheral ← CONFIRM ← Central
Evento: BLE_GAP_EVENT_INDICATE_TX
```
**Flujo de Notificación:**
```
Peripheral → NOTIFY → Central
(sin confirmación)
```
---
## 2.6 Ejercicio Práctico 2: GATT Server + LED Control
**Objetivo**: Crear un periférico que expone dos características:
1. **LED Control** (write): Escribe 0x00 = OFF, 0x01 = ON.
2. **Button Status** (read + notify): Lee estado de botón cada 1 segundo.
**Requisitos:**
1. Usa NimBLE.
2. Servicios:
- Automation IO service (custom UUID 128-bit).
- LED write characteristic.
- Button read characteristic (con CCCD).
3. Connection parameters: interval 30 ms, latency 0, timeout 3 s.
4. MTU: 128 bytes.
**Pasos:**
1. Configura GPIO (LED en Pin 2, Button en Pin 0).
2. Implementa `gatt_svr_chr_access` para manejar reads/writes.
3. Crea task que envíe notificaciones cada 1 segundo.
4. Verifica en nRF Connect.
---
# MÓDULO 3: SECURITY & ADVANCED TOPICS
## 3.1 Flujo de Pairing (LESC)
### Fase 1: Exchange Public Keys
```
Central (Smartphone) Peripheral (ESP32)
│ │
├─ Pairing Request ────────> │
│ (IO cap, auth req, etc.) │
│ │
<──── Pairing Response ────┤
│ (IO cap, bonding flag) │
│ │
├─ Public Key (ECDH) ──────> │ [~160 ms blocking]
│ │
<── Public Key (ECDH) ─────┤
```
### Fase 2: Authentication
Depende de IO capabilities:
```
Numeric Comparison:
Ambos generan random: 0-999999
Ambos muestran 6 dígitos
User confirma: "¿Coinciden?" → Yes/No
Passkey Entry:
Central muestra: 123456
Periférico: Entrada en UART → 123456
Just Works:
Sin confirmación (sin MITM)
```
### Fase 3: Key Generation
```
Shared Secret (DHKey) = d_central * Public_Key_periférico
= d_periférico * Public_Key_central
LTK (Long Term Key):
= PRES(DHKey, 256-bit salt, identifiers)
[Derivación de 128 bits]
CSRK (Connection Signature Resolving Key):
= Generado para firmar datos [opcionalidad]
IRK (Identity Resolving Key):
= Permite resolver direcciones privadas
```
---
## 3.2 NVS: Almacenamiento de Bonding
**NVS (Non-Volatile Storage)**: Partición en flash donde persisten pares.
**Estructura en ESP32:**
```
Flash
├─ Partition: nvs
│ ├─ Namespace: peer_0
│ │ ├─ ltk (16 bytes)
│ │ ├─ ediv (2 bytes)
│ │ ├─ rand (8 bytes)
│ │ ├─ csrk (16 bytes)
│ │ ├─ irk (16 bytes)
│ │ └─ identity_address (6 bytes)
│ │
│ ├─ Namespace: peer_1
│ └─ ...
```
**API NimBLE:**
```c
// Almacenar automáticamente al parear
ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
// Restaurar bonded devices al reconnectar
static void ble_app_on_sync(void) {
// NimBLE automáticamente busca pares en NVS
// Si encontrada identidad, hace "reconnect with encryption"
}
// Limpiar bonding
ble_store_util_delete_all();
// Listar pares
ble_store_util_status_rr(); // Imprime en logs
```
---
## 3.3 LE Secure Connections vs Legacy
### Legacy Pairing (BLE 4.0-4.1)
```
┌─────────────────────────────────────────┐
│ Vulnerabilidad: TK débil, algoritmo simple│
│ → KNOB attack: downgrade a 3-DES débil │
└─────────────────────────────────────────┘
```
### LE Secure Connections (BLE 4.2+)
```
┌──────────────────────────────────────────────┐
│ ✓ ECDH robusta (P-256 curve) │
│ ✓ Immune a KNOB (key length siempre 128 bit)│
│ ✓ Numeric Comparison sin vulnerabilidades │
│ ✗ Bloquea ~160 ms durante pairing (ROM) │
└──────────────────────────────────────────────┘
```
**En ESP32, LESC es default. No la deshabilites a menos que requieras legacy.**
---
## 3.4 Privacy: Dirección Privada Resolvible (RPA)
### Problema
Un central puede **trackear** un periférico por su MAC fijo.
### Solución: RPA (Resolvable Private Address)
Dirección de 48 bits generada dinámicamente:
```
┌─────────────┬──────────────────────┐
│ Hash (16 b) │ Random (32 b) │
│ 2 MSB = 01 │ (lower 2 bits = 01) │
└─────────────┴──────────────────────┘
```
**Hash = AES(IRK, Random_part)[0:15]**
- Cada conexión genera RPA nueva.
- Central con IRK bonded puede resolver dirección.
- Sin IRK, imposible trackear.
**En NimBLE:**
```c
// Forzar RPA (privacidad máxima)
ble_hs_cfg.rpa_timeout = 60; // Regenerar cada 60 s
ble_hs_cfg.sm_use_rpa = 1; // Use RPA en lugar de dirección fija
// En periférico
ble_gap_set_addr_type(BLE_ADDR_TYPE_RPA_RANDOM_DEFAULT);
```
---
## 3.5 Herramientas de Debugging de Seguridad
### Extraer LTK de iOS (post-pairing)
1. **Xcode Bluetooth Packet Logger** (Mac):
```bash
# Busca: "LE Start Encryption"
# En la expansión, observa: LTK = [16 bytes]
```
2. **Android HCI Snoop** (developer options):
```bash
adb pull /sdcard/btsnoop_hci.log
# Abre en Wireshark, filtra por "Start Encryption"
```
### Monitoreo en Tiempo Real (ESP32)
```c
// Log pairing state
static void gatt_svr_subscribe_cb(struct ble_gap_event *event, void *arg) {
if (event->type == BLE_GAP_EVENT_SUBSCRIBE) {
ESP_LOGI("SECURITY", "Subscribe flags: 0x%04x",
event->subscribe.cur_notify);
}
}
// Log encryption state
case BLE_GAP_EVENT_ENC_CHANGE:
ESP_LOGI("SECURITY", "Encrypted: %d",
event->enc_change.encrypted);
break;
```
---
## 3.6 Ejercicio Práctico 3: Periférico Seguro con Bonding
**Objetivo**: Crear un dispositivo que requiere LESC + Passkey, almacena bonding y re-encripta automáticamente.
**Requisitos:**
1. NimBLE + LESC activado (default).
2. Passkey Entry (periférico muestra código).
3. Almacenamiento bonding en NVS.
4. Característica protegida: requiere encriptación.
5. Desconectar si intenta acceso sin encripción.
**Pasos:**
1. Configura Security Manager:
```c
ble_hs_cfg.sm_mitm = 1;
ble_hs_cfg.sm_bonding = 1;
ble_hs_cfg.sm_io_cap = BLE_SM_IO_CAP_DISP_ONLY;
```
2. Implementa callback `BLE_GAP_EVENT_PASSKEY_ACTION`.
3. En `gatt_svr_chr_access`, valida `event->access.op` y rechaza si no cifrado.
4. Test: Parear, desconectar, reconectar sin re-introducir código.
---
## 3.7 Coexistencia WiFi-BLE y PSRAM
### El Problema
ESP32 tiene un **controller compartido** para WiFi y BLE. Ambos usan la banda 2.4 GHz.
**Collision mitigation:**
- Algoritmo de frequency hopping separa canales.
- BLE tiene prioridad sobre WiFi en ciertos eventos críticos.
- Aumenta latencia WiFi durante transferencias BLE.
### Estrategia de Optimización
```c
// Config: Asignar más tiempo a BLE si es crítico
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
bt_cfg.ble_ll_resolv_list_size = 0; // Liberar RAM si no usas privacidad
esp_bt_controller_init(&bt_cfg);
// O usar PSRAM para buffers del stack
ble_hs_cfg.ble_l2cap_mtu = 251; // Reduce MTU default si hay presión RAM
```
### PSRAM (Pseudo SRAM)
ESP32 puede tener hasta 4 MB de PSRAM externo. **Acceso es lento (2-5x que IRAM)**, pero útil para:
- Buffers grandes.
- Modelos ML pequeños.
**En contexto de BLE:**
- No recomendable para buffers de paquetes (latencia).
- Sí para almacenamiento de bonding LUTs si muchos pares.
---
## 3.8 ESP32 Memory Deep Dive
### Layout de Memoria
```
IRAM (Instruction RAM): 192 KB total
├─ 64 KB: PRO/APP cache MMU
├─ 128 KB: Ejecutable + stack tasks + heap crítico
│ ├─ NimBLE task: ~8 KB stack
│ ├─ Application task: ~4 KB
│ └─ Heap: Resto
DRAM (Data RAM): 320 KB total
├─ 160 KB: Estáticos (.data + .bss)
├─ 160 KB: Heap runtime (rest)
FLASH (External): 4 MB típico
├─ Partition: app (1.2 MB código)
├─ Partition: nvs (64 KB bonding)
└─ Partition: rest (available)
```
### Impacto en BLE
| Stack | IRAM | DRAM Estático | Heap Init |
|-------|------|--------------|-----------|
| **Bluedroid** | ~60 KB | ~40 KB | ~200 KB |
| **NimBLE** | ~40 KB | ~20 KB | ~70 KB |
**Conclusión**: NimBLE deja 130 KB más para ML models. Crítico para Edge AI.
---
# DEBUGGING MASTERCLASS
## Debugging 1: Logging Básico (Siempre Disponible)
### ESP-IDF Monitor
```bash
idf.py -p /dev/ttyUSB0 monitor
```
**Dentro del monitor:**
```
# Cambiar nivel de log
Ctrl+T Ctrl+L → Choose log level → ble (default DEBUG)
# Reset
Ctrl+T Ctrl+R
# Salir
Ctrl+]
```
### Filtrar Logs por Módulo
```c
// En código
esp_log_level_set("BLE_GATT", ESP_LOG_DEBUG); // Solo GATT debug
esp_log_level_set("*", ESP_LOG_INFO); // Todo a INFO
// En menuconfig
idf.py menuconfig
→ Component config → Log output → Default log level → DEBUG
→ Bluetooth → Log level → Debug
```
### Ejemplo de Logging Custom
```c
#include "esp_log.h"
static const char *TAG = "MY_BLE_APP";
void my_function() {
ESP_LOGD(TAG, "Debug: valor=%d", 42);
ESP_LOGI(TAG, "Info: evento=%s", "connected");
ESP_LOGW(TAG, "Warning: mtu bajo");
ESP_LOGE(TAG, "Error: code=%d", error_code);
}
```
---
## Debugging 2: GDB + OpenOCD (JTAG Hardware Required)
### Hardware Setup
**Requerimientos:**
- **JTAG Adapter**: ESP-PROG, Jlink, ST-Link, o Raspberry Pi.
- **5 Cables**: TDI, TDO, TCK, TMS, GND (+ SRST opcional).
**ESP32 JTAG Pins:**
```
GPIO12 → TDI
GPIO13 → TCK
GPIO14 → TDO
GPIO15 → TMS
GND → GND
```
### Instalación OpenOCD
```bash
# Linux (Debian/Ubuntu)
sudo apt-get install openocd
# macOS
brew install openocd
# Verify
openocd --version
```
### Iniciar OpenOCD
```bash
# Para ESP-PROG en Linux
openocd -f interface/ftdi/esp32_devkitj_v1.cfg \
-f target/esp32.cfg
# Para ST-Link en Windows
openocd -f interface/stlink.cfg \
-f target/esp32.cfg
# Verás algo como:
# Info : Listening on port 3333 for gdb connections
# Info : Listening on port 6666 for telnet connections
```
### Conectar GDB
**Terminal 1 (OpenOCD ejecutándose):**
```bash
openocd -f interface/esp32_prog.cfg -f target/esp32.cfg
```
**Terminal 2 (GDB):**
```bash
cd your_project_dir
# Opción 1: Automático (desde ESP-IDF)
idf.py gdb
# Opción 2: Manual
xtensa-esp32-elf-gdb build/your_app.elf
# En GDB console:
(gdb) target remote localhost:3333
(gdb) mon reset halt # Halt on reset
(gdb) thb app_main # Breakpoint temporal en app_main
(gdb) c # Continue
```
### Comandos GDB Útiles
```bash
# Breakpoints
(gdb) b function_name
(gdb) b file.c:123
(gdb) info breakpoints
(gdb) delete 1
# Stepping
(gdb) n # Next (step over)
(gdb) s # Step into function
(gdb) c # Continue
(gdb) finish # Run until return
# Inspection
(gdb) p variable # Print value
(gdb) p /x 0x1234 # Hex format
(gdb) x/32x 0x3ffb0000 # Examine memory (32 words hex)
(gdb) info registers # All registers
(gdb) bt # Backtrace (stack)
# FreeRTOS-aware (custom GDB macros)
(gdb) info tasks # List FreeRTOS tasks
(gdb) task 2 # Switch to task 2
```
### Debugging BLE Specific Issues
**Breakpoints en callbacks:**
```bash
(gdb) b esp_gap_cb # Breakpoint GAP callback
(gdb) b gatt_svr_chr_access # Breakpoint GATT access
# Cuando se para, inspecciona evento
(gdb) p event->type # Tipo de evento
(gdb) p event->connect.conn_handle # Handle conexión
```
---
## Debugging 3: Análisis de Paquetes (Wireshark + HCI Sniffing)
### Opción A: Wireshark en Linux (sin hardware sniffer)
**Limitación**: Captura solo HCI local (advertiser), no conexiones activas.
```bash
# Instalar Wireshark + plugin Bluetooth
sudo apt-get install wireshark wireshark-plugin-btsnoop
# Ejecutar como root
sudo wireshark
# Capturar:
# 1. Capture → Interfaces
# 2. Selecciona "Bluetooth"
# 3. Start capturing
```
**Filtros útiles:**
```
btatt # Filter: Attribute Protocol
btle # Filter: BLE link layer
btatt.handle == 0x0003 # Characteristic handle
btatt.opcode == 0x0b # Write command
```
### Opción B: HCI Sniffing en Android
```bash
# Activar developer options
# → Bluetooth HCI snoop log: ON
# Ejecutar comando
adb pull /sdcard/btsnoop_hci.log
# Abrir en Wireshark
wireshark btsnoop_hci.log
```
### Opción C: Captura de Logs de Sincronización (ESP32 Built-in)
```bash
# Compilar con debug habilitado
idf.py menuconfig
# → Component config → Bluetooth → Enable debug logs
# → Log Level → Debug
# En monitor, buscar:
# [HCI] LE Create Connection Complete
# [GATT] Write characteristic
```
---
## Debugging 4: Profiling y Análisis de Performance
### Task CPU Usage
```c
#include "freertos/task.h"
void print_task_stats(void) {
char* buffer = malloc(1024);
vTaskList(buffer);
printf("%s\n", buffer);
free(buffer);
}
// Output:
// Task Name State Priority Stack Num
// nimble_host_task X 100 4096 123
// IDLE R 0 1024 0
```
### Heap Usage
```c
#include "esp_heap_caps.h"
void print_heap_stats(void) {
printf("Internal DRAM: %d bytes free\n",
heap_caps_get_free_size(MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT));
printf("Internal IRAM: %d bytes free\n",
heap_caps_get_free_size(MALLOC_CAP_INTERNAL | MALLOC_CAP_32BIT));
}
```
### Timing Analysis
```c
#include "esp_timer.h"
void measure_operation(void) {
int64_t start = esp_timer_get_time(); // microseconds
// [Operation]
int64_t elapsed_us = esp_timer_get_time() - start;
ESP_LOGI(TAG, "Operation took %.2f ms", elapsed_us / 1000.0);
}
```
---
## Debugging 5: Clang + AddressSanitizer (ASAN)
### Enable AddressSanitizer
```bash
idf.py menuconfig
# → Component config → Compiler options → Enable AddressSanitizer
```
### Build & Run
```bash
idf.py build flash monitor
```
**Output de memory error:**
```
=================================================================
==12345==ERROR: AddressSanitizer: heap-buffer-overflow on unknown address
Read of size 4 at 0x... Thread T1
#0 in some_function ...
```
### Instrumentación Manual
```c
#include "sanitizer/asan_interface.h"
void unsafe_access(void) {
uint8_t buffer[10];
// Marcar rango válido
ASAN_POISON_MEMORY_REGION(buffer + 10, 1);
// Esto triggea ASAN error si accede
buffer[15] = 0xFF; // Out of bounds!
}
```
---
# EJERCICIOS PRÁCTICOS PROGRESIVOS
## Ejercicio 1: Beacon Personalizado (Nivel: Básico)
**Objetivo**: Dominar advertising y formatos de datos.
**Requisitos:**
- Transmitir temperatura simulada cada 100 ms.
- Usar Service Data con UUID 16-bit personalizado (0xAABB).
- Formato: `[temp_byte] [humidity_byte]`.
- Verificable en nRF Connect.
**Template:**
```c
static uint8_t service_data[] = {
0x02, 0x0A, 0xF4, // TX Power
0x07, 0x16, // Service Data 16-bit
0xBB, 0xAA, // UUID 0xAABB
25, // Temperature
60 // Humidity
};
// Update cada 100ms en timer
void update_adv_data(void) {
service_data[4] = read_temp();
service_data[5] = read_humidity();
ble_gap_adv_set_fields(&adv_fields);
}
```
**Verificación:**
1. Conecta a nRF Connect.
2. Abre Raw Data en advertising.
3. Confirma que hex cambia cada 100 ms.
---
## Ejercicio 2: GATT Server Interactivo (Nivel: Intermedio)
**Objetivo**: Implementar lectura/escritura de características + notificaciones.
**Requisitos:**
- Servicio custom (UUID 128-bit).
- 3 características:
1. **LED Control** (write): 0x00=OFF, 0x01=ON.
2. **Button State** (read+notify): 0x00/0x01.
3. **BPM Mock** (read+notify): Valor 60-100 BPM.
- BPM actualiza cada 2 segundos.
- Button emula presión cada 5 segundos.
**Pasos:**
1. Define UUID 128-bit de servicio:
```c
#define CUSTOM_SERVICE_UUID \
{0x12, 0x34, 0x56, 0x78, ...} // 16 bytes
```
2. Implementa task que actualiza BPM y Button.
3. Maneja `GATT_WRITE_EVT` para LED control.
4. En nRF Connect, verifica:
- Write LED → GPIO on/off.
- Read BPM → valor entre 60-100.
- Suscribirse a BPM → recibe notificaciones cada 2s.
---
## Ejercicio 3: Bonding + Security (Nivel: Avanzado)
**Objetivo**: Dominar seguridad LESC con persistencia.
**Requisitos:**
- LESC + Numeric Comparison.
- Almacenamiento bonding en NVS.
- Reinicio sin re-pairing.
- Rechazo de acceso no encriptado.
**Pasos:**
1. Configura SM:
```c
ble_hs_cfg.sm_mitm = 1;
ble_hs_cfg.sm_bonding = 1;
ble_hs_cfg.sm_sc = 1; // Secure Connections
```
2. Implementa passkey callback:
```c
case BLE_GAP_EVENT_PASSKEY_ACTION:
ESP_LOGI("SEC", "Passkey: %06d",
event->passkey.params.action_type);
break;
```
3. Valida encriptación en GATT access:
```c
if (!ble_gap_conn_find(conn_handle, &desc)) {
if (!desc.sec_state.encrypted) {
return BLE_ATT_ERR_INSUFFICIENT_AUTHEN;
}
}
```
4. Test:
- Parear, confirmar passkey.
- Reboot, reconectar → sin passkey.
- Borrar bonding en ESP32 (`ble_store_util_delete_all()`).
- Reconectar → pide passkey nuevamente.
---
## Ejercicio 4: IoT Sensor Sync (Nivel: Avanzado+)
**Objetivo**: Integrar múltiples sensores + WiFi + BLE.
**Requisitos:**
- 1x Beacon BLE (temperatura).
- 1x GATT Server (BLE) con:
- Lectura ADC (temperatura real).
- Lectura RSSI de WiFi.
- Reset button.
- WiFi conectado a broker MQTT.
- Publicar datos por MQTT cada 10s.
- Recibir comandos por GATT + aplicar.
**Pasos:**
1. Setup ADC + WiFi + MQTT.
2. Crea beacon con temp actualizada.
3. Crea GATT server con:
- Service: Environmental Sensing (0x181A).
- Characteristic: Temperature (0x2A6E).
- Characteristic: Humidity (mock, 0x2A6F).
4. Escribe datos en NVS bajo etiqueta `iot_config`.
5. Implementa: Write characteristic para actualizar MQTT topic.
6. Test:
- BLE beacon muestra temp en tiempo real.
- GATT server lee sensores.
- MQTT publica cada 10s.
- Comando BLE actualiza MQTT topic.
---
# REFERENCIAS & RECURSOS
## Especificaciones Oficiales
- **Bluetooth Core Specification Vol 4 (BLE)**
- Part A: Architecture
- Part B: Link Layer
- Part D: ATT/GATT
- Part E: HCI
- Part H: Security
- **ESP32 Technical Reference Manual**
- Wireless, Memory, Timers
## Documentación
- **ESP-IDF Official Docs**: https://docs.espressif.com
- Bluetooth Low Energy API
- NimBLE Host
- JTAG Debugging
- **Apache NimBLE Project**: https://mynewt.apache.org
- API Reference
- Architecture Guide
## Herramientas de Desarrollo
- **nRF Connect** (mobile app): Escanear, conectar, debug BLE
- **Wireshark**: Análisis de paquetes BLE/HCI
- **OpenOCD**: JTAG debugger
- **GDB**: GNU Debugger
- **Ubertooth One**: Hardware BLE sniffer (avanzado)
## Repositorios Open-Source
- **esp-idf/examples/bluetooth**: Ejemplos oficiales
- **apache/mynewt-nimble**: Stack NimBLE
- **espressif/esp-idf**: Full ESP-IDF
- **h2zero/NimBLE-Arduino**: NimBLE para Arduino IDE
## Artículos Técnicos Recomendados
1. **"The Bluetooth Low Energy Protocol Stack: Understanding the Fundamentals"** - NoveBits
2. **"A Practical Guide to Debugging BLE Communication on iOS"** - NoveBits
3. **"Understanding the Architecture of the Bluetooth Low Energy Protocol"** - Analog Devices
4. **"BLE Frequency Hopping Algorithm Analysis"** - arxiv.org (si te interesa sniffing avanzado)
## Comunidades
- **ESP32 Community Forum**: esp32.com
- **Apache NimBLE Mailing List**: dev@mynewt.apache.org
- **Bluetooth SIG**: bluetooth.com (especificaciones)
---
## ROADMAP: Próximos Pasos para Edge AI + IoT + Blockchain
Con BLE + ESP32 dominados, el siguiente paso en tu roadmap:
1. **Quantización de Modelos ML**
- TensorFlow Lite Micro (< 1 MB, ~10 ms inferencia).
- Desplegar en ESP32 con BLE para actualizaciones federadas.
2. **Federated Learning sobre BLE**
- Múltiples ESP32 (sensores) entrenando modelo compartido.
- Coordinación por NimBLE + Bluetooth Mesh.
3. **Blockchain + BLE**
- Verificación de datos IoT en cadena (Substrate).
- Provenance de sensor data.
4. **Edge Inference Optimization**
- ONNX Runtime en ESP32.
- Comparación: nanoGPT vs DistilBERT en hardware limitado.
5. **Rust + CUDA para kernels críticos**
- Compilar NimBLE components en Rust (safety).
- CUDA kernels para training federated en nodos GPU (coordinadores).
---
**¡Felicidades por completar esta masterclass! Ahora eres experto en BLE + ESP32.** 🎓
Para profundizar, revisa los repos sugeridos e implementa los mini-proyectos. Cada iteración refuerza internalizacion de conceptos.