Estructura General de la Aplicación
El flujo típico de inicialización en una aplicación NimBLE para ESP32 se puede representar de la siguiente manera:
funcion_principal()
│
├── inicializar_memoria_no_volatil() // Paso inicial obligatorio
├── arrancar_nimble() // Configura controlador, HCI y host
├── habilitar_servicio_gap() // Crea servicio estándar 0x1800
├── habilitar_servicio_gatt() // Crea servicio estándar 0x1801
├── establecer_nombre_dispositivo() // Define nombre visible
├── registrar_servicio_personalizado() // Registra tus servicios GATT
├── configurar_callback_sincronizacion() // Acción al estar listo (iniciar anuncios)
├── preparar_gestion_conexiones() // Estructuras para manejar conexiones
└── lanzar_tarea_nimble() // Inicia el loop principal de NimBLE
Consideraciones Críticas en la Secuencia de Inicialización
El orden exacto de las llamadas es fundamental para evitar fallos o errores de registro:
inicializar_memoria_no_volatil()
↓
arrancar_nimble() // En IDF 5.x, maneja controlador e HCI internamente
↓
habilitar_servicios_base() // gap y gatt deben configurarse antes de iniciar tareas
↓
registrar_servicio_personalizado() // Los servicios del usuario se definen aquí
↓
asignar_funcion_sincronizacion() // Punto para iniciar transmisión de anuncios
↓
lanzar_tarea_nimble() // Una vez ejecutado, NimBLE opera activamente
En versiones recientes del IDF, se ha simplificado el proceso; funciones como esp_nimble_hci_and_controller_init() han sido consolidadas. Al migrar código antiguo, es esencial adaptar las llamadas al nuevo enfoque unificado.
Proceso de Registro de Servicios GATT
La definición y registro de servicios personalizados sigue un procedimiento específico:
// Declaración estática de la tabla de servicios
static const struct ble_gatt_svc_def tabla_servicios[] = {
{
.type = BLE_GATT_SVC_TYPE_PRIMARY,
.uuid = &uuid_sensor_ambiental.u,
.characteristics = arreglo_caracteristicas
},
{ 0 } // Marca de fin de la tabla
};
// Configuración y activación de recursos
ble_gatts_calcular_recursos(tabla_servicios);
ble_gatts_registrar_servicios(tabla_servicios);
Tras el registro, NimBLE asigna automáticamente identificadores (handles). Los punteros a los val_handles se actualizan con valores válidos, permitiendo operaciones como notificaciones.
Estructura de Paquetes de Anuncio (Advertising)
Los anuncios en BLE tienen restricciones de tamaño y organización:
- Anuncio Principal (ADV_IND): Máximo 31 bytes.
- Campo de banderas (Flags): Indicador de modo descubrible y compatibilidad.
- UUID de 16 bits: Identifica el tipo de dispositivo (ej. 0x181A para sensores ambientales).
- Respuesta al Escaneo (SCAN_RSP): Adicional, hasta 31 bytes.
- Nombre completo del dispositivo.
- Apariencia (Appearance): Describe la función del dispositivo (ej. 0x0302 para sensor de temperatura).
Separar el UUID y el nombre entre los dos paquetes evita exceder el límite combinado de 62 bytes, optimizando la capacidad de transmisión.
Mecanismo para Enviar Notificaciones (Notify)
Existen patrones correctos e incorrectos para gestionar notificaciones:
// Enfoque inválido: no difunde a todas las conexiones activas
enviar_notificacion_global(handle_conexion_invalido, ...)
// Enfoque recomendado: iterar sobre conexiones establecidas
para_cada_conexion(funcion_notificar, &contexto)
// Dentro de la función, usar:
ble_gatts_notificar(handle_conexion, handle_caracteristica, datos)
Para recibir notificaciones, el cliente debe habilitar el descriptor CCCD (Client Characteristic Configuration Descriptor) escribiendo el valor 0x0001. Herramientas como nRF Connect realizan esta acción automáticamente al subscribirse.
Distribución de Identificadores (Handles) GATT
Los servicios estándar y personalizados se organizan en un mapa de identificadores:
- Servicios Base (0x1800, 0x1801): Creados automáticamente por las funciones de inicialización.
- Incluyen descriptores de capacidad y características como Service Changed.
- Servicios Personalizados (ej. 0x181A): Para aplicaciones específicas.
- Características con descriptores de formato (CPF) que definen unidades y escalas.
- Cada característica notificable posee su propio descriptor CCCD.
Esta jerarquía permite una gestión estructurada de los datos y la intercación con clientes BLE.
Convenciones para el Formato de Datos
Los datos numéricos se trnasmiten como enteros escalados, siguiendo estándares de Bluetooth:
int16_t valor_temperatura = 2500 // Representa 25.00°C
uint16_t valor_humedad = 6000 // Representa 60.00%RH
// Transmisión en formato little-endian: 2500 → 0x09C4 → bytes: C4 09
El descriptor CPF (Characteristic Presentation Format) contiene un exponente (ej. -2), indicando al cliente que debe multiplicar el valor recibido por 10⁻² para obtener el dato real.
Capas del Protocolo BLE
La pila BLE se divide en capas funcionales:
- Aplicación: Servicios y características GATT definidos por el usuario.
- GATT: Gestión de descubrimiento, lectura/escritura y notificaciones.
- ATT: Transmisión de paquetes, con negociación automática de MTU.
- SMP: Gestión de emparejamiento y cifrado (opcional).
- GAP: Control de anuncios, conexión y desconexión.
- HCI: Interfaz entre host y controlador.
- Controlador: Manejo de radio, temporización y capa física.