Beacon Advertising Packet Design & Payload Optimization
Every Bluetooth beacon lives or dies by its advertising packet. The packet is the beacon’s voice — a 31-byte whisper on legacy BLE, or a 255-byte shout on extended advertising. How you pack those bytes determines whether a receiver detects your beacon in 50 ms or 500 ms, whether the battery lasts 18 months or 3 years, and whether 200 beacons in the same room produce a clean signal or a collision-ridden mess.
The Legacy 31-Byte Advertising Packet
A legacy BLE advertising packet (BLE 4.x) has exactly 31 bytes of payload space, split into AD (Advertising Data) structures. Each AD structure consumes 2 bytes of overhead (length + type), leaving 29 bytes for actual data — but only if you use a single AD structure. In practice, you need at least two:
| AD Structure | Type | Purpose | Bytes Used |
|---|---|---|---|
| Flags | 0x01 | LE General Discoverable, BR/EDR Not Supported | 3 (2 header + 1 data) |
| Manufacturer Specific | 0xFF | Company ID + custom payload | 2 + 2 + N |
| Complete Local Name | 0x09 | Device name string | 2 + name length |
| TX Power Level | 0x0A | Signed int8, -127 to +127 dBm | 3 |
| Service UUID (128-bit) | 0x07 | Complete list of 128-bit UUIDs | 18 |
After Flags (3 bytes) and the mandatory Manufacturer Specific header (4 bytes for type+length+company ID), you have 24 bytes for your actual beacon payload. That’s the budget for UUID, major, minor, TX power, and any sensor data. The iBeacon format uses all 24 bytes for UUID (16) + Major (2) + Minor (2) + TX Power (1) + reserved (3), leaving zero room for sensor data.
Frame Format Comparison
Three dominant frame formats compete for those 24 bytes. Each makes different tradeoffs:
| Format | Company ID | Payload Layout | Sensor Data? | Ecosystem Lock-in |
|---|---|---|---|---|
| iBeacon | 0x004C (Apple) | UUID(16) + Major(2) + Minor(2) + TX(1) | No — fixed 21 bytes | Apple ecosystem only |
| Eddystone-UID | 0x00AA (Google) | Namespace(10) + Instance(6) + TX(1) | No — fixed 17 bytes | Google Nearby (deprecated) |
| Eddystone-TLM | 0x00AA (Google) | Version(1) + Batt(2) + Temp(2) + AdvCnt(4) + SecCnt(4) | Yes — battery & temp | Google ecosystem |
| Custom (Open) | 0xXXXX (assigned) | Any layout — you define the schema | Yes — flexible | None — your own receiver |
The practical recommendation for most deployments: use a custom frame format. You gain full control of the 24 bytes, can embed sensor data directly in the advertising packet (avoiding the need for a GATT connection), and aren’t locked into an ecosystem that may be deprecated (as Google did with Nearby in 2021).
Designing a Custom Payload: Squeezing Sensors into 24 Bytes
A well-designed custom payload can carry identity, telemetry, and flags in a single 24-byte window — enough to eliminate GATT connections entirely for many use cases:
| Field | Offset | Size | Range / Resolution | Description |
|---|---|---|---|---|
| Frame Version | 0 | 1 byte | 0–255 | Protocol version for forward compatibility |
| Device ID | 1 | 6 bytes | 14 trillion IDs | Unique device identifier (MAC-derived) |
| Battery Level | 7 | 1 byte | 0–100% (1% res) | Unsigned int, 0xFF = not available |
| Temperature | 8 | 1 byte | -40 to +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, packed XYZ |
| Button / Event | 13 | 1 byte | 0–255 events | Counter for button press / tamper |
| Tamper Flag | 14 | 1 byte | 0 = OK, 1 = tampered | Physical tamper status |
| TX Power | 15 | 1 byte | -127 to +127 dBm | Signed int8 for RSSI@1m calibration |
| Sequence Counter | 16 | 4 bytes | 0–4 billion | Anti-replay, uptime estimate |
| Reserved | 20 | 4 bytes | — | Future use / CRC / extension |
Encoding tricks for squeezing more data:
// Temperature: -40 to +85°C in 1 byte (0.5°C resolution)
uint8_t encode_temp(float temp_c) {
int16_t raw = (int16_t)((temp_c + 40.0) * 2.0); // offset + scale
if (raw < 0) raw = 0;
if (raw > 250) raw = 250; // cap at +85°C
return (uint8_t)raw;
}
float decode_temp(uint8_t raw) {
return (raw / 2.0) - 40.0;
}
// Humidity: 0-100% in 1 byte (0.4% resolution)
uint8_t encode_humidity(float rh) {
return (uint8_t)(rh * 2.55);
}
float decode_humidity(uint8_t raw) {
return raw / 2.55;
}
// Accelerometer: ±16g in 3 bytes (0.125g per 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); // center at 128 = 0g
out[1] = (uint8_t)(y_g / 0.125 + 128);
out[2] = (uint8_t)(z_g / 0.125 + 128);
}
Extended Advertising: BLE 5.0 Breaks the 31-Byte Barrier
BLE 5.0 introduced Extended Advertising, expanding the payload from 31 to 255 bytes. This is a game-changer for sensor beacons — you can now broadcast full telemetry, multi-sensor data, encrypted payloads, and even firmware update URLs in a single packet without GATT connections.
| Feature | Legacy (4.x) | Extended (5.0+) |
|---|---|---|
| Max Payload | 31 bytes | 255 bytes |
| PHY Options | 1M only | 1M, 2M, Coded (125k/500k) |
| Advertising Sets | 1 | Up to 10 concurrent |
| TX Power Reporting | Manual | Auto in AUX_SYNC_IND |
| RSSI Stability | ±4 dB typical | ±2 dB with coded PHY |
However, adoption is limited: only ~60% of smartphones in 2025 support BLE 5.0 extended advertising scanning. If your deployment targets consumer phones, you still need a legacy fallback. For infrastructure-grade deployments (dedicated gateways, RTLS anchors), extended advertising is the clear choice.
Advertising Interval: The Battery Life Equation
The advertising interval is the single most impactful parameter on battery life. Each transmitted packet costs energy; the interval determines how many packets per second:
// nRF52-series power consumption per advertising event
// (3 channels × 1M PHY, 0 dBm TX power)
const float TX_CURRENT_MA = 4.6; // TX current at 0 dBm
const float TX_TIME_MS = 0.376; // 376 µs per channel (preamble + header + 31B payload)
const float SLEEP_CURRENT_UA = 1.5; // System OFF + RTC
// Energy per advertising event (3 channels)
float energy_per_event_uC = (TX_CURRENT_MA * 1000) * (TX_TIME_MS * 3 / 1000);
// = 4.6 * 1000 * 0.376 * 3 / 1000 = 5.19 µC per event
// Average current at 100 ms interval (10 events/sec)
float avg_current_100ms_uA = (energy_per_event_uC * 10) / 1.0 + SLEEP_CURRENT_UA;
// = 51.9 + 1.5 = 53.4 µA
// Average current at 1000 ms interval (1 event/sec)
float avg_current_1s_uA = (energy_per_event_uC * 1) / 1.0 + SLEEP_CURRENT_UA;
// = 5.19 + 1.5 = 6.69 µA
// CR2477 battery: 1000 mAh = 3600000 µA·s
// At 100 ms interval: 3600000 / 53.4 / 3600 / 24 = ~781 days
// At 1000 ms interval: 3600000 / 6.69 / 3600 / 24 = ~6235 days (theoretical)
| Interval | Avg Current | CR2477 (1000 mAh) Life | Detection Latency | Use Case |
|---|---|---|---|---|
| 100 ms | 53.4 µA | ~26 months | ≤100 ms | RTLS, real-time tracking |
| 300 ms | 18.8 µA | ~71 months | ≤300 ms | Retail, interactive |
| 1000 ms | 6.7 µA | ~6 years* | ≤1 s | Asset tracking, telemetry |
| 5000 ms | 2.5 µA | ~16 years* | ≤5 s | Cold-chain, low-power |
*Theoretical — self-discharge of CR batteries limits practical life to 5–7 years regardless of current draw.
The key insight: doubling the interval does not halve battery life. Sleep current (1.5 µA) is a fixed floor. At 100 ms, TX dominates (51.9 µA vs 1.5 µA sleep). At 5000 ms, sleep dominates (1.0 µA TX vs 1.5 µA sleep). The crossover point where TX current equals sleep current is around 3.5 seconds — beyond that, further interval extension yields diminishing returns.
Collision Probability: When Too Many Beacons Talk at Once
In a dense deployment (warehouse, hospital, conference hall), hundreds of beacons share the same three advertising channels (37, 38, 39). Each beacon transmits on all three channels simultaneously. When two transmissions overlap in time on the same channel, both packets are corrupted — a collision.
The collision probability follows a modified ALOHA model:
// Slotted ALOHA collision probability (approximation)
// N = number of beacons, T = packet air time, I = interval
//
// P_collision = 1 - exp(-2 * N * T / I)
//
// For 31-byte payload at 1M PHY: T = 376 µs per channel
// Beacons transmit on all 3 channels, so effective T = 376 µs
// (channels are separate frequencies, no inter-channel collision)
import math
def collision_prob(n_beacons, interval_ms, packet_time_us=376):
# Convert to seconds
T = packet_time_us / 1e6
I = interval_ms / 1e3
# Per-channel load (beacons distribute across 3 channels randomly)
# Actually each beacon hits all 3 channels, so load per channel = N * T / I
load = n_beacons * T / I
p_collision = 1 - math.exp(-2 * load)
return p_collision
# Examples:
# 50 beacons, 1000 ms interval: P = 1 - exp(-2 * 50 * 0.000376 / 1.0) = 3.7%
# 200 beacons, 1000 ms interval: P = 1 - exp(-2 * 200 * 0.000376 / 1.0) = 14.0%
# 200 beacons, 300 ms interval: P = 1 - exp(-2 * 200 * 0.000376 / 0.3) = 39.7%
# 500 beacons, 1000 ms interval: P = 1 - exp(-2 * 500 * 0.000376 / 1.0) = 31.3%
| Beacons | Interval | Channel Load | P(collision) | P(receive on 1st scan)* |
|---|---|---|---|---|
| 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% |
*Probability of receiving at least one clean packet during a 1-second scan window, assuming 3 advertising channels and independent collision events.
Mitigation strategies for dense deployments:
- Random jitter: Add ±20% random jitter to each beacon’s interval. This decorrelates transmission timing and breaks periodic collision patterns. Reduces P(collision) by 30–50% in practice.
- Adaptive interval: Beacons detect congestion (by counting received packets from neighbors) and back off. Similar to CSMA/CA but for connectionless advertising.
- Channel preference: Some implementations skip channel 37 (most congested due to Wi-Fi channel 1 overlap) and only advertise on 38+39. Reduces Wi-Fi interference at the cost of detection probability.
- Extended advertising: BLE 5.0 secondary channels (used for AUX_SYNC_IND) operate on any of 37 data channels, spreading the load far more thinly than the 3 primary channels.
Multi-Frame Advertising: Rotating Payloads
When you need to broadcast more data than fits in a single 31-byte packet (legacy) or want to balance detection latency with telemetry freshness, use frame rotation. The beacon cycles through different frame types on successive advertising events:
| Slot | Frame Type | Content | Purpose |
|---|---|---|---|
| 1 (every event) | Identity frame | Device ID + TX power + seq | Fast detection, RSSI ranging |
| 2 (every 3rd) | Telemetry frame | Battery + temp + humidity | Environmental monitoring |
| 3 (every 10th) | Accelerometer frame | XYZ + event counter + tamper | Movement / shock detection |
| 4 (every 30th) | Info frame | Firmware version + config hash | OTA management, fleet audit |
With a 1000 ms base interval, the identity frame transmits every second, telemetry every 3 seconds, accelerometer every 10 seconds, and info every 30 seconds. A scanner that listens for 3 seconds will capture both identity and telemetry; a 10-second scan captures everything except the info frame. This approach effectively multiplexes 4 logical beacons onto a single physical device with minimal battery overhead.
Conclusion
Advertising packet design is the highest-leverage engineering decision in a beacon deployment. A well-designed payload eliminates GATT connections, reduces scanner dwell time, and extends battery life by orders of magnitude. The principles are simple: pack identity and telemetry into the smallest possible footprint, use frame rotation for overflow, add jitter to survive dense deployments, and choose the interval that matches your latency budget — not faster.
For most asset-tracking and environmental monitoring deployments, the sweet spot is a custom 24-byte legacy frame at 1000 ms interval with ±20% jitter and 4-slot frame rotation. This configuration delivers 6-second detection latency (99th percentile), 5+ year battery life on a CR2477, and sub-5% collision rates up to 200 beacons per zone — numbers that no off-the-shelf iBeacon configuration can match.
BLE beacons are only as effective as the bytes they broadcast. Design the packet before designing the deployment.