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.