# 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.