Impact sur l'architecture logicielle¶
Les contraintes matérielles ne sont pas des obstacles à contourner — ce sont des paramètres de conception qui dictent des décisions logicielles précises, du choix d'une bibliothèque à la stratégie de synchronisation cloud.
Footprint mémoire : le premier filtre architectural¶
Sur un système Linux avec 16 Go de RAM, la taille d'une bibliothèque est un détail négligeable. Sur un MCU avec 128 Ko de Flash et 32 Ko de SRAM, chaque kilooctet compte et conditionne les décisions d'architecture au sens le plus large.
Impact sur le choix des bibliothèques¶
| Besoin fonctionnel | Bibliothèque Linux/serveur | Taille (Flash) | Bibliothèque embarquée | Taille (Flash) |
|---|---|---|---|---|
| Stack TLS | OpenSSL | ~1–3 Mo | mbedTLS | 60–200 Ko |
| Stack TLS minimale | — | — | wolfSSL | 20–100 Ko |
| Stack MQTT | Eclipse Paho C | ~100 Ko | coreMQTT (FreeRTOS) | 3–8 Ko |
| Stack HTTP | libcurl | ~400 Ko | lwIP HTTP client | 10–30 Ko |
| Sérialisation JSON | cJSON (déjà compact) | ~20 Ko | JSMN (parser) | 1–2 Ko |
| Sérialisation binaire | Protobuf (C++) | ~50 Ko | nanopb | 1–3 Ko |
| Compression | zlib | ~80 Ko | miniz / lz4 embedded | 10–30 Ko |
Règle pratique : sur un MCU Cortex-M4 avec 256 Ko de Flash, le budget est typiquement réparti ainsi :
- Bootloader : 16–32 Ko
- RTOS + HAL : 20–40 Ko
- Stack réseau + TLS : 60–120 Ko
- Application firmware : 60–120 Ko
- Total disponible : 256 Ko — aucune marge pour des bibliothèques "complètes"
Allocation statique vs dynamique¶
L'allocation dynamique (malloc/free) est problématique en embarqué longue durée :
// Mauvaise pratique : malloc en production embarquée
uint8_t *buffer = malloc(payload_size);
// Risque : fragmentation heap sur 5 ans de fonctionnement
// Bonne pratique : pool statique de taille fixe
static uint8_t msg_pool[POOL_SIZE][MAX_MSG_SIZE];
static uint8_t pool_used[POOL_SIZE] = {0};
uint8_t *acquire_buffer(void) {
for (int i = 0; i < POOL_SIZE; i++) {
if (!pool_used[i]) { pool_used[i] = 1; return msg_pool[i]; }
}
return NULL; // Pool épuisé — condition gérée
}
Les mémoires statiques sont déterministes, visibles à la compilation (le linker révèle les dépassements avant le déploiement) et ne se fragmentent pas.
Latence : traitement local vs cloud¶
La latence de bout en bout dans un système IoT dépend de l'endroit où le traitement est effectué. La contrainte matérielle (puissance de calcul disponible sur le nœud) détermine ce qui peut être fait localement.
Taxonomie des latences IoT¶
| Niveau | Traitement | Latence typique | Exemples |
|---|---|---|---|
| On-device | MCU / FPGA | µs – ms | Détection seuil, filtrage FIR |
| Edge gateway | SBC / SoC puissant | ms – 100 ms | FFT, agrégation, ML inference |
| Fog / MEC | Serveur proche | 5–50 ms | Corrélation multi-capteurs |
| Cloud | Datacenter distant | 50–500 ms | Analyse historique, ML cloud |
La règle générale : plus la décision est critique pour la sécurité ou la réactivité, plus elle doit être locale. Un algorithme de détection de chute de pression qui doit couper une vanne en 10 ms ne peut pas attendre un aller-retour cloud.
Impact sur le choix de protocole¶
flowchart TD
A[Contrainte latence\nidentifiée] --> B{Latence requise ?}
B -- "< 1 ms" --> C[Traitement on-device\nBare-metal ou RTOS\nPas de protocole réseau]
B -- "1–100 ms" --> D{Bande passante\ndisponible ?}
D -- "Limitée\n(LoRa, NB-IoT)" --> E[MQTT QoS 0\nou CoAP NON\nPayload binaire compressé]
D -- "Correcte\n(Wi-Fi, Ethernet)" --> F[MQTT QoS 1\nou WebSocket\nJSON / CBOR]
B -- "100 ms–5 s" --> G{Fiabilité\nrequise ?}
G -- "Best effort" --> H[MQTT QoS 0\ncoAP NON\nUDP direct]
G -- "Garanti" --> I[MQTT QoS 1/2\nHTTPS\nTCP avec retry]
B -- "> 5 s" --> J[HTTP REST\nbatch upload\nschedule flexible] Énergie : impact sur les décisions de communication¶
Chaque décision logicielle a un coût énergétique. La fréquence de transmission, le protocole choisi, la taille des payloads — tous ces paramètres impactent directement l'autonomie batterie.
Coût énergétique par protocole de communication¶
| Protocole | Courant TX typique | Durée TX (20 o.) | Énergie / paquet | Portée |
|---|---|---|---|---|
| BLE 5.0 | 5–10 mA | 1 ms | 3–5 µJ | 10–100 m |
| LoRaWAN SF7 | 40–120 mA | 50 ms | 100–300 µJ | 1–10 km |
| LoRaWAN SF12 | 40–120 mA | 2 000 ms | 4 000–8 000 µJ | 5–20 km |
| Zigbee | 30–50 mA | 2 ms | 30–60 µJ | 10–100 m |
| Wi-Fi 802.11n | 80–180 mA | 5 ms | 200–500 µJ | 30–100 m |
| NB-IoT | 70–220 mA | 100 ms–5 s | 1–50 mJ | > 10 km |
| 4G LTE-M | 50–180 mA | 50 ms–2 s | 0,5–20 mJ | > 10 km |
Conséquence directe : passer de LoRaWAN SF12 à SF7 réduit l'énergie par transmission d'un facteur 20, au prix d'une portée réduite. Ce choix logiciel (choix du Spreading Factor) peut doubler l'autonomie batterie.
Duty cycle logiciel¶
Le duty cycle logiciel se contrôle à plusieurs niveaux :
// Niveau 1 : fréquence de collecte
#define SAMPLE_PERIOD_S 60 // 1 mesure par minute (ajustable OTA)
// Niveau 2 : agrégation avant transmission
#define TX_PERIOD_S 300 // Transmission toutes les 5 minutes
#define SAMPLES_PER_TX (TX_PERIOD_S / SAMPLE_PERIOD_S) // = 5
// Niveau 3 : transmission adaptative selon delta
float last_sent_value = 0.0f;
void maybe_transmit(float new_value) {
if (fabsf(new_value - last_sent_value) > DELTA_THRESHOLD) {
transmit(new_value); // Transmission si changement significatif
last_sent_value = new_value;
}
}
La transmission adaptative (delta encoding) peut réduire le nombre de transmissions de 80 à 90 % sur des capteurs dont la valeur change lentement — sans perte d'information significative.
Connectivité intermittente : store-and-forward¶
Un nœud IoT n'est pas toujours en couverture réseau. Les coupures de réseau (maintenance, obstacle temporaire, plan d'épargne réseau LoRa) sont la norme, pas l'exception. L'architecture logicielle doit les anticiper.
Patterns de résilience¶
Store-and-forward : les mesures sont stockées en Flash (ou EEPROM) pendant les périodes hors couverture, puis transmises en lot lors du retour en couverture.
// Circular buffer en Flash NOR (secteur 4 Ko)
typedef struct {
uint32_t timestamp;
float value;
uint8_t sensor_id;
uint8_t quality; // Flag qualité mesure
} sensor_record_t;
#define FLASH_BUFFER_SIZE 256 // 256 enregistrements × 10 octets = 2,5 Ko
void store_reading(float value) {
sensor_record_t rec = {
.timestamp = rtc_get_unix(),
.value = value,
.sensor_id = SENSOR_ID,
.quality = QUALITY_OK
};
flash_write_record(&rec); // Écriture sur Flash avec wear leveling
}
void flush_to_cloud(void) {
sensor_record_t records[FLASH_BUFFER_SIZE];
int count = flash_read_pending(records, FLASH_BUFFER_SIZE);
if (count > 0 && network_available()) {
if (mqtt_publish_batch(records, count) == MQTT_OK) {
flash_mark_sent(count);
}
}
}
Cache local avec synchronisation différée : les lectures récentes sont gardées en RAM pour répondre aux requêtes locales (interface HMI, Modbus master local) sans attendre le cloud.
Horodatage embarqué : sans RTC précis ou GPS, les mesures stockées localement n'ont pas de timestamp fiable. La synchronisation NTP lors de la reconnexion corrige l'horloge et recalibre les timestamps en attente.
Mapping global : contraintes matérielles → décisions logicielles¶
graph LR
subgraph "Contraintes matérielles"
C1["Mémoire\n64–512 Ko Flash\n16–256 Ko SRAM"]
C2["Énergie\nBatterie 1–3 Ah\nDuty cycle < 1 %"]
C3["CPU\nCortex-M4 80 MHz\nPas de FPU double"]
C4["Connectivité\nLoRa / BLE\nBande passante limitée"]
C5["Temps réel\nJitter < 1 ms\nScheduler RTOS"]
end
subgraph "Décisions logicielles"
D1["Bibliothèques légères\n(mbedTLS, nanopb, coreMQTT)\nAllocation statique"]
D2["Duty cycle adaptatif\nDelta encoding\nAgrégation locale"]
D3["Float simple précision\nDSP CMSIS\nPas de stdlib lourde"]
D4["Payload compact (CBOR)\nCompression LZ4\nBatch upload"]
D5["FreeRTOS / Zephyr\nPriorités statiques\nPas de malloc en ISR"]
end
C1 --> D1
C2 --> D2
C3 --> D3
C4 --> D4
C5 --> D5
D2 --> D4
D1 --> D3 Synthèse : les décisions qui ne se corrigent pas facilement¶
Certaines décisions d'architecture logicielle liées aux contraintes matérielles sont très coûteuses à changer en cours de vie du produit :
| Décision | Impact si mal choisie | Coût de correction |
|---|---|---|
| Protocole de sérialisation (JSON vs CBOR) | Payload trop lourd → autonomie réduite | Mise à jour OTA + serveur |
| Stratégie d'allocation mémoire | Fragmentation sur 5 ans → crash aléatoire | Refonte firmware majeure |
| Fréquence de transmission fixe | Trop fréquent → autonomie x3 trop courte | Paramètre OTA si prévu |
| Absence de store-and-forward | Perte de données lors des coupures réseau | Ajout en OTA si flash dispo |
| Pas de mécanisme de rollback OTA | Brick firmware à l'update → terrain inaccessible | Impossible sans accès physique |
| Timestamp sans RTC de secours | Données sans cohérence temporelle | Impossible a posteriori |
Ce qu'il faut retenir¶
- Les contraintes matérielles ne sont pas des obstacles — ce sont des paramètres de conception qui guident vers des solutions logicielles précises et éprouvées industriellement.
- Le footprint mémoire impose des bibliothèques légères, l'allocation statique, et un design de firmware pensé en termes de budget Ko plutôt qu'en fonctionnalités ajoutées.
- La latence requise détermine où le traitement doit s'exécuter : on-device pour le temps critique, edge pour l'analyse locale, cloud pour l'historique et le ML offline.
- Le store-and-forward est non négociable dans tout déploiement IoT industriel — les coupures réseau sont inévitables, la perte de données ne l'est pas.
- Les cinq décisions les plus difficiles à corriger (sérialisation, allocation, transmission, store-and-forward, rollback OTA) doivent être validées en phase de conception, avant la première ligne de code applicatif.
Chapitre suivant : Plateformes & capteurs — choisir le matériel concret pour votre projet IoT.