Conception des Paquets d’Advertising et Optimisation du Payload des Balises
Chaque balise Bluetooth vit ou meurt par son paquet d’advertising. Le paquet est la voix de la balise — un murmure de 31 octets en BLE hérité, ou un cri de 255 octets en advertising étendu. La façon dont ces octets sont conditionnés détermine si un récepteur détecte votre balise en 50 ms ou 500 ms, si la batterie dure 18 mois ou 3 ans, et si 200 balises dans la même pièce produisent un signal propre ou un chaos de collisions.
Le Paquet d’Advertising Hérité de 31 Octets
Un paquet d’advertising BLE hérité (BLE 4.x) dispose d’exactement 31 octets d’espace de payload, divisé en structures AD (Advertising Data). Chaque structure AD consomme 2 octets de surcharge (longueur + type), laissant 29 octets pour les données réelles — mais uniquement si une seule structure AD est utilisée. En pratique, il en faut au moins deux :
| Structure AD | Type | Objectif | Octets Utilisés |
|---|---|---|---|
| Flags | 0x01 | LE General Discoverable, BR/EDR Non Pris en Charge | 3 (2 en-tête + 1 donnée) |
| Manufacturer Specific | 0xFF | Company ID + payload personnalisé | 2 + 2 + N |
| Complete Local Name | 0x09 | Chaîne de nom d’appareil | 2 + longueur du nom |
| TX Power Level | 0x0A | Int8 signé, -127 à +127 dBm | 3 |
| Service UUID (128-bit) | 0x07 | Liste complète des UUIDs 128 bits | 18 |
Après Flags (3 octets) et l’en-tête obligatoire Manufacturer Specific (4 octets pour type+longueur+Company ID), il reste 24 octets pour le payload réel de la balise. C’est le budget pour UUID, major, minor, puissance TX et toute donnée de capteur. Le format iBeacon utilise les 24 octets pour UUID (16) + Major (2) + Minor (2) + TX Power (1) + réservé (3), ne laissant aucun espace pour les données de capteur.
Comparaison des Formats de Trame
| Format | Company ID | Layout du Payload | Données Capteur ? | Dépendance Écosystème |
|---|---|---|---|---|
| iBeacon | 0x004C (Apple) | UUID(16) + Major(2) + Minor(2) + TX(1) | Non — 21 octets fixes | Écosystème Apple uniquement |
| Eddystone-UID | 0x00AA (Google) | Namespace(10) + Instance(6) + TX(1) | Non — 17 octets fixes | Google Nearby (obsolète) |
| Eddystone-TLM | 0x00AA (Google) | Version(1) + Batt(2) + Temp(2) + AdvCnt(4) + SecCnt(4) | Oui — batterie et temp | Écosystème Google |
| Personnalisé (Ouvert) | 0xXXXX (attribué) | Layout quelconque — vous définissez le schéma | Oui — flexible | Aucune — votre propre récepteur |
La recommandation pratique pour la plupart des déploiements : utiliser un format de trame personnalisé. On obtient le contrôle total des 24 octets, peut intégrer des données de capteur directement dans le paquet d’advertising (évitant les connexions GATT), et n’est pas lié à un écosystème qui peut être déprécié (comme Google l’a fait avec Nearby en 2021).
Concevoir un Payload Personnalisé : Des Capteurs en 24 Octets
| Champ | Offset | Taille | Plage / Résolution | Description |
|---|---|---|---|---|
| Frame Version | 0 | 1 octet | 0–255 | Version de protocole pour compatibilité |
| Device ID | 1 | 6 octets | 14 billions d’IDs | Identifiant unique (dérivé MAC) |
| Battery Level | 7 | 1 octet | 0–100% (1% rés.) | Non signé, 0xFF = non disponible |
| Temperature | 8 | 1 octet | -40 à +85°C (0,5°C) | Int8, raw = temp × 2 + 40 |
| Humidity | 9 | 1 octet | 0–100% (0,4% rés.) | Non signé, raw = humidity × 2,55 |
| Accelerometer | 10 | 3 octets | ±16g (0,125g) | 3 × int8, XYZ empaqueté |
| Button / Event | 13 | 1 octet | 0–255 événements | Compteur de pression / manipulation |
| Tamper Flag | 14 | 1 octet | 0 = OK, 1 = manipulé | Statut de manipulation physique |
| TX Power | 15 | 1 octet | -127 à +127 dBm | Int8 signé pour calibration RSSI@1m |
| Sequence Counter | 16 | 4 octets | 0–4 milliards | Anti-rejeu, estimation d’uptime |
| Reserved | 20 | 4 octets | — | Usage futur / CRC / extension |
Astuces d’encodage pour compresser les données :
// Température : -40 à +85°C en 1 octet (0,5°C de résolution)
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;
}
// Humidité : 0-100% en 1 octet (0,4% de résolution)
uint8_t encode_humidity(float rh) {
return (uint8_t)(rh * 2.55);
}
// Accéléromètre : ±16g en 3 octets (0,125g par 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 Étendu : BLE 5.0 Brise la Barrière des 31 Octets
BLE 5.0 a introduit l’Extended Advertising, étendant le payload de 31 à 255 octets. C’est un changement décisif pour les balises capteurs — on peut désormais diffuser la télémétrie complète, les données multi-capteurs, les payloads chiffrés et même les URLs de mise à jour firmware dans un seul paquet sans connexions GATT.
| Caractéristique | Hérité (4.x) | Étendu (5.0+) |
|---|---|---|
| Payload Max | 31 octets | 255 octets |
| Options PHY | 1M uniquement | 1M, 2M, Coded (125k/500k) |
| Sets d’Advertising | 1 | Jusqu’à 10 simultanés |
| Reporting TX Power | Manuel | Auto dans AUX_SYNC_IND |
| Stabilité RSSI | ±4 dB typique | ±2 dB avec Coded PHY |
Cependant, l’adoption est limitée : seulement ~60 % des smartphones en 2025 prennent en charge le scan d’advertising étendu BLE 5.0. Si votre déploiement cible des smartphones grand public, vous avez toujours besoin d’un fallback hérité. Pour les déploiements de qualité infrastructure (passerelles dédiées, ancres RTLS), l’advertising étendu est le choix évident.
Intervalle d’Advertising : L’Équation de Durée de Vie de la Batterie
L’intervalle d’advertising est le paramètre le plus impactant sur la durée de vie de la batterie. Chaque paquet transmis coûte de l’énergie ; l’intervalle détermine combien de paquets par seconde :
// Consommation nRF52 par événement d'advertising
// (3 canaux × 1M PHY, 0 dBm TX)
const float TX_CURRENT_MA = 4.6;
const float TX_TIME_MS = 0.376; // 376 µs par canal
const float SLEEP_CURRENT_UA = 1.5;
// Énergie par événement (3 canaux)
float energy_per_event_uC = (TX_CURRENT_MA * 1000) * (TX_TIME_MS * 3 / 1000);
// = 5.19 µC par événement
// Courant moyen à 100 ms (10 événements/s)
float avg_current_100ms_uA = (5.19 * 10) + 1.5;
// = 53.4 µA
// Courant moyen à 1000 ms (1 événement/s)
float avg_current_1s_uA = (5.19 * 1) + 1.5;
// = 6.69 µA
// Batterie CR2477 : 1000 mAh
// 100 ms : ~781 jours
// 1000 ms : ~6235 jours (théorique)
| Intervalle | Courant Moy. | Durée CR2477 (1000 mAh) | Latence Détection | Cas d’Usage |
|---|---|---|---|---|
| 100 ms | 53,4 µA | ~26 mois | ≤100 ms | RTLS, suivi temps réel |
| 300 ms | 18,8 µA | ~71 mois | ≤300 ms | Commerce, interactif |
| 1000 ms | 6,7 µA | ~6 ans* | ≤1 s | Suivi d’actifs, télémétrie |
| 5000 ms | 2,5 µA | ~16 ans* | ≤5 s | Chaîne du froid, basse conso |
*Théorique — l’autodécharge des batteries CR limite la durée de vie pratique à 5–7 ans indépendamment du courant consommé.
L’insight clé : doubler l’intervalle ne réduit pas de moitié la durée de vie de la batterie. Le courant de veille (1,5 µA) est un plancher fixe. À 100 ms, le TX domine (51,9 µA vs 1,5 µA veille). À 5000 ms, la veille domine (1,0 µA TX vs 1,5 µA veille). Le point de croisement où le courant TX égale le courant de veille se situe vers 3,5 secondes — au-delà, l’extension de l’intervalle produit des rendements décroissants.
Probabilité de Collision : Quand Trop de Balises Parlent à la Fois
Dans un déploiement dense (entrepôt, hôpital, salle de conférence), des centaines de balises partagent les trois mêmes canaux d’advertising (37, 38, 39). Chaque balise émet sur les trois canaux simultanément. Quand deux transmissions se chevauchent dans le temps sur le même canal, les deux paquets sont corrompus — une collision.
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
# Exemples :
# 50 balises, 1000 ms : P = 3,7%
# 200 balises, 1000 ms : P = 14,0%
# 200 balises, 300 ms : P = 39,7%
# 500 balises, 1000 ms : P = 31,3%
| Balises | Intervalle | Charge Canal | P(collision) | P(réception au 1er 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% |
*Probabilité de recevoir au moins un paquet propre pendant une fenêtre de scan de 1 seconde.
Stratégies d’atténuation pour les déploiements denses :
- Jitter aléatoire : Ajouter ±20% de jitter aléatoire à l’intervalle de chaque balise. Cela décorèlle la temporisation d’émission et brise les motifs de collision périodiques. Réduit P(collision) de 30–50% en pratique.
- Intervalle adaptatif : Les balises détectent la congestion (en comptant les paquets reçus des voisins) et reculent. Similaire à CSMA/CA mais pour l’advertising sans connexion.
- Préférence de canal : Certaines implémentations ignorent le canal 37 (le plus encombré par le chevauchement Wi-Fi canal 1) et n’advertissent que sur 38+39. Réduit l’interférence Wi-Fi au détriment de la probabilité de détection.
- Advertising étendu : Les canaux secondaires BLE 5.0 opèrent sur n’importe lequel des 37 canaux de données, répartissant la charge bien plus finement que les 3 canaux primaires.
Advertising Multi-Trame : Rotation de Payloads
Quand on doit diffuser plus de données que ce qui tient dans un seul paquet de 31 octets (hérité) ou équilibrer latence de détection avec fraîcheur de télémétrie, on utilise la rotation de trames. La balise cycle entre différents types de trames lors d’événements d’advertising successifs :
| Slot | Type de Trame | Contenu | Objectif |
|---|---|---|---|
| 1 (chaque événement) | Identité | Device ID + TX + seq | Détection rapide, ranging RSSI |
| 2 (chaque 3e) | Télémétrie | Batterie + temp + humidité | Surveillance environnementale |
| 3 (chaque 10e) | Accéléromètre | XYZ + compteur + manipulation | Détection de mouvement/choc |
| 4 (chaque 30e) | Info | Version FW + hash config | Gestion OTA, audit de flotte |
Avec un intervalle de base de 1000 ms, la trame d’identité est transmise chaque seconde, télémétrie toutes les 3 secondes, accéléromètre toutes les 10 secondes, et info toutes les 30 secondes. Un scanner qui écoute 3 secondes capturera identité et télémétrie ; un scan de 10 secondes capture tout sauf la trame d’info. Cette approche multiplexe efficacement 4 balises logiques sur un seul appareil physique avec une surcharge de batterie minimale.
Conclusion
La conception du paquet d’advertising est la décision d’ingénierie à plus fort levier dans un déploiement de balises. Un payload bien conçu élimine les connexions GATT, réduit le temps de présence du scanner et prolonge la durée de vie de la batterie de plusieurs ordres de grandeur. Les principes sont simples : conditionner l’identité et la télémétrie dans le plus petit espace possible, utiliser la rotation de trames pour le débordement, ajouter du jitter pour survivre aux déploiements denses, et choisir l’intervalle qui correspond au budget de latence — pas plus rapide.
Pour la plupart des déploiements de suivi d’actifs et de surveillance environnementale, le point optimal est une trame héritée personnalisée de 24 octets à 1000 ms d’intervalle avec jitter ±20% et rotation de 4 slots. Cette configuration offre une latence de détection de 6 secondes (99e percentile), une durée de vie de batterie de 5+ ans avec CR2477, et des taux de collision inférieurs à 5% jusqu’à 200 balises par zone — des chiffres qu’aucune configuration iBeacon standard ne peut égaler.
Les balises BLE ne sont aussi efficaces que les octets qu’elles diffusent. Concevez le paquet avant de concevoir le déploiement.