Aller au contenu

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.