52 KiB
BLE + ESP32: Masterclass Teórico-Práctico
De Cero a Experto en Bluetooth Low Energy y Desarrollo Embedded
TABLA DE CONTENIDOS
- Módulo 0: Fundamentos de BLE
- Módulo 1: Advertising & Discovery
- Módulo 2: Connections & GATT
- Módulo 3: Security & Advanced Topics
- Debugging Masterclass
- Ejercicios Prácticos Progresivos
- 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):
- Secuencia pseudo-aleatoria: Los dispositivos emparejados comparten un
Access Addressy unhop increment(derivado de direcciones MAC). - Salto por conexión: Salta entre los 37 canales de datos cada
connection interval(7.5 ms - 4 s). - Channel map: Si ciertos canales tienen mucho ruido, se excluyen dinámicamente.
Fórmula: Nuevo canal = (Anterior + hop_increment) % 37
# 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:
- Servicios: Colecciones de funcionalidad. Ej: Heart Rate Service (HRS).
- Características: Propiedades dentro de un servicio. Ej: Heart Rate Measurement.
- 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:
- Menor footprint → más espacio para ML models.
- Mejor para federated learning (múltiples nodos IoT).
- Comunidad open-source muy activa (Apache).
- Por defecto moderno en nuevos proyectos ESP32.
0.7 Seguridad BLE: Introducción Conceptual
Legacy Pairing (BLE 4.1 y anteriores)
- Intercambio de claves Temporary Link Key (TK) basado en IO capabilities.
- Generación STK (Short Term Key) via algoritmo simple (vulnerable).
- 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+)
- Generación de pares ECDH (Elliptic Curve Diffie-Hellman) en cada dispositivo.
- Intercambio de claves públicas (seguro matemáticamente).
- Generación LTK (Long Term Key) determinista y fuerte.
- 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:
// 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:
- ESP32 transmite paquete de publicidad en Ch 37.
- Espera ~120 μs por SCAN_REQ (si scannable).
- Si recibe SCAN_REQ, transmite SCAN_RSP en Ch 37.
- Salta a Ch 38, repite.
- Salta a Ch 39, repite.
- 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
#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:
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)
#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)
#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:
- Usa NimBLE.
- Implementa Service Data con UUID 16-bit custom.
- Transmite 2 bytes (temperatura + humedad mocked).
- Advertising interval: 100 ms.
- TX Power: -10 dBm.
Pasos:
- Crea un proyecto nuevo en
examples/bluetooth/ble_beacon_custom/. - Incluye lectura ADC simulada (temperatura mocked).
- Actualiza datos cada 500 ms (sin reiniciar advertising).
- 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:
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:
// 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)
// 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)
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:
- LED Control (write): Escribe 0x00 = OFF, 0x01 = ON.
- Button Status (read + notify): Lee estado de botón cada 1 segundo.
Requisitos:
- Usa NimBLE.
- Servicios:
- Automation IO service (custom UUID 128-bit).
- LED write characteristic.
- Button read characteristic (con CCCD).
- Connection parameters: interval 30 ms, latency 0, timeout 3 s.
- MTU: 128 bytes.
Pasos:
- Configura GPIO (LED en Pin 2, Button en Pin 0).
- Implementa
gatt_svr_chr_accesspara manejar reads/writes. - Crea task que envíe notificaciones cada 1 segundo.
- 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:
// 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:
// 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)
-
Xcode Bluetooth Packet Logger (Mac):
# Busca: "LE Start Encryption" # En la expansión, observa: LTK = [16 bytes] -
Android HCI Snoop (developer options):
adb pull /sdcard/btsnoop_hci.log # Abre en Wireshark, filtra por "Start Encryption"
Monitoreo en Tiempo Real (ESP32)
// 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:
- NimBLE + LESC activado (default).
- Passkey Entry (periférico muestra código).
- Almacenamiento bonding en NVS.
- Característica protegida: requiere encriptación.
- Desconectar si intenta acceso sin encripción.
Pasos:
-
Configura Security Manager:
ble_hs_cfg.sm_mitm = 1; ble_hs_cfg.sm_bonding = 1; ble_hs_cfg.sm_io_cap = BLE_SM_IO_CAP_DISP_ONLY; -
Implementa callback
BLE_GAP_EVENT_PASSKEY_ACTION. -
En
gatt_svr_chr_access, validaevent->access.opy rechaza si no cifrado. -
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
// 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
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
// 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
#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
# Linux (Debian/Ubuntu)
sudo apt-get install openocd
# macOS
brew install openocd
# Verify
openocd --version
Iniciar OpenOCD
# 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):
openocd -f interface/esp32_prog.cfg -f target/esp32.cfg
Terminal 2 (GDB):
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
# 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:
(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.
# 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
# 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)
# 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
#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
#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
#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
idf.py menuconfig
# → Component config → Compiler options → Enable AddressSanitizer
Build & Run
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
#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:
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:
- Conecta a nRF Connect.
- Abre Raw Data en advertising.
- 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:
- LED Control (write): 0x00=OFF, 0x01=ON.
- Button State (read+notify): 0x00/0x01.
- BPM Mock (read+notify): Valor 60-100 BPM.
- BPM actualiza cada 2 segundos.
- Button emula presión cada 5 segundos.
Pasos:
-
Define UUID 128-bit de servicio:
#define CUSTOM_SERVICE_UUID \ {0x12, 0x34, 0x56, 0x78, ...} // 16 bytes -
Implementa task que actualiza BPM y Button.
-
Maneja
GATT_WRITE_EVTpara LED control. -
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:
-
Configura SM:
ble_hs_cfg.sm_mitm = 1; ble_hs_cfg.sm_bonding = 1; ble_hs_cfg.sm_sc = 1; // Secure Connections -
Implementa passkey callback:
case BLE_GAP_EVENT_PASSKEY_ACTION: ESP_LOGI("SEC", "Passkey: %06d", event->passkey.params.action_type); break; -
Valida encriptación en GATT access:
if (!ble_gap_conn_find(conn_handle, &desc)) { if (!desc.sec_state.encrypted) { return BLE_ATT_ERR_INSUFFICIENT_AUTHEN; } } -
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:
-
Setup ADC + WiFi + MQTT.
-
Crea beacon con temp actualizada.
-
Crea GATT server con:
- Service: Environmental Sensing (0x181A).
- Characteristic: Temperature (0x2A6E).
- Characteristic: Humidity (mock, 0x2A6F).
-
Escribe datos en NVS bajo etiqueta
iot_config. -
Implementa: Write characteristic para actualizar MQTT topic.
-
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
- "The Bluetooth Low Energy Protocol Stack: Understanding the Fundamentals" - NoveBits
- "A Practical Guide to Debugging BLE Communication on iOS" - NoveBits
- "Understanding the Architecture of the Bluetooth Low Energy Protocol" - Analog Devices
- "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:
-
Quantización de Modelos ML
- TensorFlow Lite Micro (< 1 MB, ~10 ms inferencia).
- Desplegar en ESP32 con BLE para actualizaciones federadas.
-
Federated Learning sobre BLE
- Múltiples ESP32 (sensores) entrenando modelo compartido.
- Coordinación por NimBLE + Bluetooth Mesh.
-
Blockchain + BLE
- Verificación de datos IoT en cadena (Substrate).
- Provenance de sensor data.
-
Edge Inference Optimization
- ONNX Runtime en ESP32.
- Comparación: nanoGPT vs DistilBERT en hardware limitado.
-
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.