Diseño de Paquetes de Advertising y Optimización de Payload en Beacons
Cada baliza Bluetooth vive o muere por su paquete de advertising. El paquete es la voz de la baliza — un susurro de 31 bytes en BLE heredado, o un grito de 255 bytes en advertising extendido. Cómo se empaquetan esos bytes determina si un receptor detecta su baliza en 50 ms o 500 ms, si la batería dura 18 meses o 3 años, y si 200 balizas en la misma sala producen una señal limpia o un caos de colisiones.
El Paquete de Advertising Heredado de 31 Bytes
Un paquete de advertising BLE heredado (BLE 4.x) tiene exactamente 31 bytes de espacio de payload, dividido en estructuras AD (Advertising Data). Cada estructura AD consume 2 bytes de sobrecarga (longitud + tipo), dejando 29 bytes para datos reales — pero solo si se usa una única estructura AD. En la práctica, se necesitan al menos dos:
| Estructura AD | Tipo | Propósito | Bytes Usados |
|---|---|---|---|
| Flags | 0x01 | LE General Discoverable, BR/EDR No Soportado | 3 (2 cabecera + 1 dato) |
| Manufacturer Specific | 0xFF | Company ID + payload personalizado | 2 + 2 + N |
| Complete Local Name | 0x09 | Cadena de nombre del dispositivo | 2 + longitud del nombre |
| TX Power Level | 0x0A | Int8 con signo, -127 a +127 dBm | 3 |
| Service UUID (128-bit) | 0x07 | Lista completa de UUIDs de 128 bits | 18 |
Después de Flags (3 bytes) y la cabecera obligatoria Manufacturer Specific (4 bytes para tipo+longitud+company ID), quedan 24 bytes para el payload real de la baliza. Ese es el presupuesto para UUID, major, minor, TX power y cualquier dato de sensor. El formato iBeacon usa los 24 bytes para UUID (16) + Major (2) + Minor (2) + TX Power (1) + reservado (3), dejando cero espacio para datos de sensor.
Comparación de Formatos de Trama
| Formato | Company ID | Layout del Payload | ¿Datos de Sensor? | Dependencia de Ecosistema |
|---|---|---|---|---|
| iBeacon | 0x004C (Apple) | UUID(16) + Major(2) + Minor(2) + TX(1) | No — 21 bytes fijos | Solo ecosistema Apple |
| Eddystone-UID | 0x00AA (Google) | Namespace(10) + Instance(6) + TX(1) | No — 17 bytes fijos | Google Nearby (obsoleto) |
| Eddystone-TLM | 0x00AA (Google) | Version(1) + Batt(2) + Temp(2) + AdvCnt(4) + SecCnt(4) | Sí — batería y temp | Ecosistema Google |
| Personalizado (Abierto) | 0xXXXX (asignado) | Cualquier layout — usted define el esquema | Sí — flexible | Ninguno — su propio receptor |
La recomendación práctica para la mayoría de despliegues: usar un formato de trama personalizado. Se obtiene control total de los 24 bytes, se pueden incrustar datos de sensor directamente en el paquete de advertising (evitando conexiones GATT), y no se queda atado a un ecosistema que puede ser obsoleto (como Google hizo con Nearby en 2021).
Diseño de Payload Personalizado: Sensores en 24 Bytes
| Campo | Offset | Tamaño | Rango / Resolución | Descripción |
|---|---|---|---|---|
| Frame Version | 0 | 1 byte | 0–255 | Versión de protocolo para compatibilidad |
| Device ID | 1 | 6 bytes | 14 billones de IDs | Identificador único (derivado de MAC) |
| Battery Level | 7 | 1 byte | 0–100% (1% res) | Unsigned, 0xFF = no disponible |
| Temperature | 8 | 1 byte | -40 a +85°C (0,5°C) | Int8, raw = temp × 2 + 40 |
| Humidity | 9 | 1 byte | 0–100% (0,4% res) | Unsigned, raw = humidity × 2,55 |
| Accelerometer | 10 | 3 bytes | ±16g (0,125g) | 3 × int8, XYZ empaquetado |
| Button / Event | 13 | 1 byte | 0–255 eventos | Contador de pulsación / manipulación |
| Tamper Flag | 14 | 1 byte | 0 = OK, 1 = manipulado | Estado de manipulación física |
| TX Power | 15 | 1 byte | -127 a +127 dBm | Int8 con signo para calibración RSSI@1m |
| Sequence Counter | 16 | 4 bytes | 0–4 mil millones | Anti-replay, estimación de uptime |
| Reserved | 20 | 4 bytes | — | Uso futuro / CRC / extensión |
Trucos de codificación para comprimir más datos:
// Temperatura: -40 a +85°C en 1 byte (0,5°C de resolución)
uint8_t encode_temp(float temp_c) {
int16_t raw = (int16_t)((temp_c + 40.0) * 2.0);
if (raw < 0) raw = 0;
if (raw > 250) raw = 250;
return (uint8_t)raw;
}
float decode_temp(uint8_t raw) {
return (raw / 2.0) - 40.0;
}
// Humedad: 0-100% en 1 byte (0,4% de resolución)
uint8_t encode_humidity(float rh) {
return (uint8_t)(rh * 2.55);
}
// Acelerómetro: ±16g en 3 bytes (0,125g por LSB)
void encode_accel(float x_g, float y_g, float z_g, uint8_t out[3]) {
out[0] = (uint8_t)(x_g / 0.125 + 128);
out[1] = (uint8_t)(y_g / 0.125 + 128);
out[2] = (uint8_t)(z_g / 0.125 + 128);
}
Advertising Extendido: BLE 5.0 Rompe la Barrera de 31 Bytes
BLE 5.0 introdujo Extended Advertising, expandiendo el payload de 31 a 255 bytes. Esto es un cambio radical para balizas de sensor — ahora se puede difundir telemetría completa, datos multi-sensor, payloads cifrados e incluso URLs de actualización de firmware en un solo paquete sin conexiones GATT.
| Característica | Heredado (4.x) | Extendido (5.0+) |
|---|---|---|
| Payload Máximo | 31 bytes | 255 bytes |
| Opciones PHY | 1M solo | 1M, 2M, Coded (125k/500k) |
| Sets de Advertising | 1 | Hasta 10 simultáneos |
| Reporte de TX Power | Manual | Auto en AUX_SYNC_IND |
| Estabilidad RSSI | ±4 dB típico | ±2 dB con Coded PHY |
Sin embargo, la adopción es limitada: solo ~60% de los smartphones en 2025 soportan escaneo de advertising extendido BLE 5.0. Si su despliegue apunta a teléfonos de consumidor, aún necesita un respaldo heredado. Para despliegues de grado infraestructura (gateways dedicados, anclas RTLS), el advertising extendido es la opción clara.
Intervalo de Advertising: La Ecuación de Vida Útil de Batería
El intervalo de advertising es el parámetro de mayor impacto en la vida útil de la batería. Cada paquete transmitido cuesta energía; el intervalo determina cuántos paquetes por segundo:
// Consumo de nRF52 por evento de advertising
// (3 canales × 1M PHY, 0 dBm TX)
const float TX_CURRENT_MA = 4.6;
const float TX_TIME_MS = 0.376; // 376 µs por canal
const float SLEEP_CURRENT_UA = 1.5;
// Energía por evento (3 canales)
float energy_per_event_uC = (TX_CURRENT_MA * 1000) * (TX_TIME_MS * 3 / 1000);
// = 5.19 µC por evento
// Corriente promedio a 100 ms (10 eventos/seg)
float avg_current_100ms_uA = (5.19 * 10) + 1.5;
// = 53.4 µA
// Corriente promedio a 1000 ms (1 evento/seg)
float avg_current_1s_uA = (5.19 * 1) + 1.5;
// = 6.69 µA
// Batería CR2477: 1000 mAh
// 100 ms: ~781 días
// 1000 ms: ~6235 días (teórico)
| Intervalo | Corriente Prom. | Vida CR2477 (1000 mAh) | Latencia Detección | Caso de Uso |
|---|---|---|---|---|
| 100 ms | 53,4 µA | ~26 meses | ≤100 ms | RTLS, tracking en tiempo real |
| 300 ms | 18,8 µA | ~71 meses | ≤300 ms | Retail, interactivo |
| 1000 ms | 6,7 µA | ~6 años* | ≤1 s | Tracking de activos, telemetría |
| 5000 ms | 2,5 µA | ~16 años* | ≤5 s | Cadena de frío, bajo consumo |
*Teórico — la autodescarga de baterías CR limita la vida útil práctica a 5–7 años independientemente del consumo de corriente.
El insight clave: duplicar el intervalo no reduce a la mitad la vida útil de la batería. La corriente de reposo (1,5 µA) es un suelo fijo. A 100 ms, el TX domina (51,9 µA vs 1,5 µA reposo). A 5000 ms, el reposo domina (1,0 µA TX vs 1,5 µA reposo). El punto de cruce donde la corriente TX iguala a la de reposo está alrededor de 3,5 segundos — más allá de eso, extender el intervalo produce rendimientos decrecientes.
Probabilidad de Colisión: Cuando Demasiadas Balizas Hablan a la Vez
En un despliegue denso (almacén, hospital, salón de conferencias), cientos de balizas comparten los mismos tres canales de advertising (37, 38, 39). Cada baliza transmite en los tres canales simultáneamente. Cuando dos transmisiones se solapan en el tiempo en el mismo canal, ambos paquetes se corrompen — una colisión.
import math
def collision_prob(n_beacons, interval_ms, packet_time_us=376):
T = packet_time_us / 1e6
I = interval_ms / 1e3
load = n_beacons * T / I
p_collision = 1 - math.exp(-2 * load)
return p_collision
# Ejemplos:
# 50 balizas, 1000 ms: P = 3,7%
# 200 balizas, 1000 ms: P = 14,0%
# 200 balizas, 300 ms: P = 39,7%
# 500 balizas, 1000 ms: P = 31,3%
| Balizas | Intervalo | Carga Canal | P(colisión) | P(recibir en 1er escaneo)* |
|---|---|---|---|---|
| 50 | 1000 ms | 1,9% | 3,7% | 96,3% |
| 200 | 1000 ms | 7,5% | 14,0% | 86,0% |
| 200 | 300 ms | 25,1% | 39,7% | 60,3% |
| 500 | 1000 ms | 18,8% | 31,3% | 68,7% |
| 500 | 300 ms | 62,7% | 71,5% | 28,5% |
*Probabilidad de recibir al menos un paquete limpio durante una ventana de escaneo de 1 segundo.
Estrategias de mitigación para despliegues densos:
- Jitter aleatorio: Añadir ±20% de jitter aleatorio al intervalo de cada baliza. Esto descorrelaciona la temporización de transmisión y rompe patrones de colisión periódicos. Reduce P(colisión) en 30–50% en la práctica.
- Intervalo adaptativo: Las balizas detectan congestión (contando paquetes recibidos de vecinos) y retroceden. Similar a CSMA/CA pero para advertising sin conexión.
- Preferencia de canal: Algunas implementaciones omiten el canal 37 (más congestionado por solapamiento con Wi-Fi canal 1) y solo anuncian en 38+39. Reduce interferencia Wi-Fi a costa de la probabilidad de detección.
- Advertising extendido: Los canales secundarios BLE 5.0 operan en cualquiera de 37 canales de datos, distribuyendo la carga mucho más que los 3 canales primarios.
Advertising Multi-Trama: Rotación de Payloads
Cuando se necesita difundir más datos de los que caben en un solo paquete de 31 bytes (heredado) o equilibrar latencia de detección con frescura de telemetría, se usa rotación de tramas. La baliza cicla entre diferentes tipos de trama en eventos sucesivos:
| Slot | Tipo de Trama | Contenido | Propósito |
|---|---|---|---|
| 1 (cada evento) | Identidad | Device ID + TX + seq | Detección rápida, ranging RSSI |
| 2 (cada 3) | Telemetría | Batería + temp + humedad | Monitoreo ambiental |
| 3 (cada 10) | Acelerómetro | XYZ + contador + manipulación | Detección de movimiento/impacto |
| 4 (cada 30) | Información | Versión FW + hash config | Gestión OTA, auditoría de flota |
Con un intervalo base de 1000 ms, la trama de identidad se transmite cada segundo, telemetría cada 3 segundos, acelerómetro cada 10 segundos, e información cada 30 segundos. Un escáner que escucha 3 segundos capturará identidad y telemetría; un escaneo de 10 segundos captura todo excepto la trama de información. Este enfoque multiplexa efectivamente 4 balizas lógicas en un solo dispositivo físico con mínima sobrecarga de batería.
Conclusión
El diseño del paquete de advertising es la decisión de ingeniería de mayor apalancamiento en un despliegue de balizas. Un payload bien diseñado elimina conexiones GATT, reduce el tiempo de permanencia del escáner y extiende la vida útil de la batería por órdenes de magnitud. Los principios son simples: empaquetar identidad y telemetría en el menor espacio posible, usar rotación de tramas para el desbordamiento, añadir jitter para sobrevivir despliegues densos, y elegir el intervalo que coincida con su presupuesto de latencia — no más rápido.
Para la mayoría de despliegues de tracking de activos y monitoreo ambiental, el punto óptimo es una trama heredada personalizada de 24 bytes a 1000 ms con jitter ±20% y rotación de 4 slots. Esta configuración entrega latencia de detección de 6 segundos (percentil 99), vida útil de batería de 5+ años con CR2477, y tasas de colisión inferiores al 5% hasta 200 balizas por zona — números que ninguna configuración iBeacon de estante puede igualar.
Las balizas BLE son tan efectivas como los bytes que difunden. Diseñe el paquete antes de diseñar el despliegue.