Anatomie d'un système IoT
Jour 1 · Comprendre et faire parler les objets · 25 min
Avant de brancher quoi que ce soit, il faut savoir où l'on met les mains. Un système IoT, quelle que soit sa taille, s'organise toujours en quatre couches. Un capteur de température dans un bureau ou un réseau de mille sondes industrielles : même découpage.
Ce que fait chaque couche
| Couche | Rôle | Exemples |
|---|---|---|
| Perception | Mesurer le monde physique, ou agir dessus | Sonde de température, détecteur d'ouverture, relais de chauffage |
| Réseau | Acheminer la donnée du capteur vers le système | MQTT, CoAP, LoRaWAN, Wi-Fi, 4G |
| Traitement | Recevoir, stocker, déclencher des règles | Broker MQTT, base time-series, moteur d'alertes |
| Application | Rendre la donnée utile à quelqu'un | Tableau de bord, alerte SMS, rapport mensuel |
C'est celle qui décide de tout le reste : mal choisie, elle vide les batteries, sature le serveur et laisse passer les pannes sans les signaler. Les autres couches viendront aux modules M2 à M5.
Le cas qui va nous servir de fil rouge
Thermeo supervise le chauffage de bâtiments tertiaires. Une centaine de capteurs de température et d'humidité, répartis sur plusieurs sites. Aujourd'hui, chaque capteur interroge un serveur HTTP toutes les trente secondes.
Trois problèmes en découlent, et ils sont typiques :
- Les batteries se vident. Ouvrir une connexion HTTP coûte cher en énergie, et le capteur le fait 2 880 fois par jour.
- Le serveur travaille pour rien. La température d'un bureau ne change pas toutes les trente secondes ; l'essentiel des requêtes ne rapporte aucune information nouvelle.
- Une panne passe inaperçue. Si un capteur cesse d'émettre, personne ne le sait — le serveur attend simplement une requête qui n'arrive jamais.
Ces trois problèmes ont la même origine : le capteur demande au lieu de signaler. C'est ce que la leçon suivante va renverser.
Pourquoi MQTT plutôt que HTTP
Jour 1 · Comprendre et faire parler les objets · 25 min
HTTP fonctionne sur un principe simple : un client demande, un serveur répond. C'est parfait pour consulter une page web. Pour un capteur sur batterie, c'est un contresens.
Le renversement : publication et abonnement
MQTT inverse la logique. Le capteur ne demande rien : il publie quand il a quelque chose à dire. Les applications intéressées s'abonnent. Entre les deux, un broker fait le tri et la distribution.
Le point important, et il est structurant : le capteur ne sait pas qui l'écoute, et l'application ne sait pas qui publie. On appelle cela le découplage. Ajouter un second tableau de bord ne demande aucune modification côté capteurs — on branche un abonné de plus.
Ce que cela change concrètement
| HTTP (situation actuelle) | MQTT (cible) | |
|---|---|---|
| Qui déclenche | Le capteur interroge en boucle | Le capteur émet quand il a une valeur |
| Connexion | Ouverte et fermée à chaque appel | Une seule, maintenue et légère |
| En-tête d'un message | Plusieurs centaines d'octets | 2 octets au minimum |
| Ajouter un consommateur | Modifier le serveur | S'abonner, rien d'autre |
| Capteur en panne | Silence indistinct d'une absence de mesure | Détecté automatiquement |
Un en-tête MQTT tient en 2 octets là où un en-tête HTTP en consomme plusieurs centaines. Sur un capteur qui émet toutes les dix minutes pendant cinq ans, cet écart décide de l'autonomie de la pile.
Et CoAP ?
Il existe un troisième larron, CoAP, qui transpose le modèle REST sur UDP pour des équipements encore plus contraints. Nous le manipulerons au chapitre 3. Retenez pour l'instant que MQTT maintient une connexion vers un broker, là où CoAP interroge une ressource sans intermédiaire.
MQTT transporte des messages, rien de plus. Il ne les stocke pas durablement, ne les met pas en base et n'impose aucun format de contenu — la charge utile est une suite d'octets, à vous de décider si c'est du JSON, du texte ou du binaire.
Topics : nommer pour retrouver
Jour 1 · Comprendre et faire parler les objets · 20 min
Un topic est l'adresse d'un message. Le publieur y dépose, l'abonné y puise. C'est une simple chaîne de caractères découpée par des barres obliques, du plus général au plus précis :
thermeo/lyon/etage1/capt-01/temperature
└──┬───┘ └─┬─┘ └──┬──┘ └──┬──┘ └────┬────┘
parc site étage capteur grandeurRien n'impose cette structure : le broker accepte n'importe quel nom. Mais une convention cohérente est ce qui rendra le système exploitable dans six mois, quand il y aura cent capteurs et trois personnes différentes pour les administrer.
Les deux jokers
| Joker | Portée | Exemple | Ce qu'il capte |
|---|---|---|---|
+ | Exactement un niveau | thermeo/lyon/+/+/temperature | Toutes les températures de Lyon, quel que soit l'étage et le capteur |
# | Tout ce qui suit | thermeo/lyon/# | Absolument tout ce qui concerne le site de Lyon |
thermeo/lyon/+/+/temperature ne captera jamais thermeo/paris/etage1/capt-12/temperature : le deuxième niveau est figé sur lyon. Le joker + remplace un niveau, il n'en saute aucun.
Autre règle : # ne peut apparaître qu'en dernière position. thermeo/#/temperature est invalide.
Bien concevoir sa convention
Trois principes suffisent :
- Du général au précis. C'est ce qui rend les jokers utiles : on peut s'abonner à un site entier, puis affiner.
- Pas d'accent, pas d'espace, pas de majuscule. Les topics circulent entre des systèmes hétérogènes ; restez en minuscules et tirets.
- La grandeur en dernier. Si vous placez
temperatureavant le site, vous ne pourrez plus vous abonner à « tout ce qui concerne le capteur 01 ».
Quiz — MQTT et les topics
Jour 1 · Comprendre et faire parler les objets · 10 min
1. Un capteur publie sur thermeo/lyon/etage2/capt-07/temperature. Un abonné écoute thermeo/lyon/+/+/temperature. Reçoit-il le message ?
2. Quel abonnement capte à la fois les températures et les humidités de tout le parc ?
3. Dans le modèle publication-abonnement, que connaît le capteur ?
4. Pourquoi MQTT convient mieux qu'HTTP à un capteur sur batterie ?
5. Lequel de ces topics est invalide ?
TP 1 — Démarrer votre broker
Jour 1 · Comprendre et faire parler les objets · 45 min
Faire tourner un broker MQTT en local et vérifier qu'il accepte les connexions.
Docker uniquement. Vérifiez avec docker --version — version 24 ou supérieure. Aucun matériel.
- Créez un dossier de travail
tp-iot-mqttet placez-vous dedans. Tout le module s'y déroulera. - Créez
mosquitto.conf. Sans ce fichier, le broker n'écoute que sur la boucle interne du conteneur et vous ne pourrez pas l'atteindre :listener 1883 allow_anonymous true # Journalisation détaillée : indispensable pour comprendre ce qui se passe log_type all connection_messages true - Démarrez le broker en montant votre configuration :
docker run -it --name broker-thermeo \ -p 1883:1883 \ -v "$(pwd)/mosquitto.conf":/mosquitto/config/mosquitto.conf \ eclipse-mosquitto:2 - Lisez les journaux : vous devez voir
Opening ipv4 listen socket on port 1883. Laissez ce terminal ouvert — il sera votre console de supervision tout au long du module. - Dans un second terminal, vérifiez que le port répond :
docker exec broker-thermeo mosquitto_sub -h localhost -t 'test' -C 1 -W 3 - La commande attend trois secondes puis rend la main sans erreur : le broker écoute. Un
Connection refusedsignifie que la configuration n'a pas été montée — reprenez l'étape b et vérifiez le chemin.
- Pourquoi
allow_anonymous trueest-il acceptable ici, et pourquoi sera-t-il inacceptable en production ? - Le broker est le point de passage unique de tous les messages. Quelle conséquence sur la disponibilité du système ?
TP 2 — Publier, souscrire, filtrer
Jour 1 · Comprendre et faire parler les objets · 1 h 15
Mettre en œuvre la convention de nommage de Thermeo et la manipuler avec les jokers.
- Abonnez-vous à toutes les températures du site de Lyon :
docker exec broker-thermeo mosquitto_sub -h localhost -v \ -t 'thermeo/lyon/+/+/temperature' - Dans un troisième terminal, publiez trois relevés :
docker exec broker-thermeo mosquitto_pub -h localhost \ -t 'thermeo/lyon/etage1/capt-01/temperature' -m '21.4' docker exec broker-thermeo mosquitto_pub -h localhost \ -t 'thermeo/lyon/etage2/capt-07/temperature' -m '22.9' docker exec broker-thermeo mosquitto_pub -h localhost \ -t 'thermeo/paris/etage1/capt-12/temperature' -m '19.8' - L'abonné n'a reçu que deux messages sur trois. Identifiez lequel manque et pourquoi avant de continuer — la réponse tient au deuxième niveau du topic.
- Relancez avec le joker multi-niveaux :
docker exec broker-thermeo mosquitto_sub -h localhost -v -t 'thermeo/#' - Republiez les trois relevés : tout arrive cette fois. Ajoutez une publication d'humidité et vérifiez qu'elle est également captée.
- Écrivez
capteur.py, qui simule un capteur émettant toutes les deux secondes :import time, random, json import paho.mqtt.client as mqtt SITE, ETAGE, CAPTEUR = "lyon", "etage1", "capt-01" TOPIC = f"thermeo/{SITE}/{ETAGE}/{CAPTEUR}/temperature" c = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2) c.connect("localhost", 1883, 60) c.loop_start() try: while True: valeur = round(random.uniform(19.0, 24.0), 1) charge = json.dumps({"valeur": valeur, "unite": "C"}) c.publish(TOPIC, charge) print("publie", charge) time.sleep(2) except KeyboardInterrupt: c.loop_stop(); c.disconnect() - Lancez
python capteur.pyet vérifiez dans l'abonnéthermeo/#que les relevés arrivent bien toutes les deux secondes.
- Pourquoi publier une charge utile JSON plutôt que la valeur brute
21.4? - La convention place le site avant l'étage. Que faciliterait ou compliquerait le fait de placer la grandeur en premier ?
Qualité de service : que garantit-on ?
Jour 1 · Comprendre et faire parler les objets · 20 min
Un message peut se perdre. Le réseau coupe, le capteur redémarre, l'abonné plante. MQTT propose trois niveaux de garantie, appelés QoS, et le choix n'est pas anodin : chaque niveau supplémentaire coûte des échanges réseau, donc de l'énergie.
| Niveau | Promesse | Échanges | Conséquence |
|---|---|---|---|
| QoS 0 | Au plus une fois | 1 | Le message part et on n'en parle plus. Il peut se perdre sans que personne le sache. |
| QoS 1 | Au moins une fois | 2 | Le broker accuse réception. Si l'accusé se perd, le message est renvoyé — il peut donc arriver en double. |
| QoS 2 | Exactement une fois | 4 | Poignée de main en quatre temps. Aucune perte, aucun doublon, mais quatre fois plus de trafic. |
Prendre QoS 2 « pour être tranquille ». Sur un capteur sur pile qui émet toutes les dix minutes, c'est quadrupler le trafic pour garantir l'unicité d'un relevé de température qui sera de toute façon remplacé dix minutes plus tard.
Comment choisir
- QoS 0 — une mesure périodique, vite remplacée par la suivante. Perdre un relevé de température sur cent n'a aucune conséquence.
- QoS 1 — une alerte, un événement. Mieux vaut la recevoir deux fois que pas du tout. Il faut alors gérer les doublons côté application.
- QoS 2 — un ordre, une commande, une transaction. Arrêter deux fois une chaudière n'est pas neutre.
Le niveau effectif est le minimum des deux. Un abonné en QoS 2 recevra en QoS 0 ce qu'un publieur a émis en QoS 0 : la garantie ne se crée pas après coup, elle se transporte.
La session persistante
Un abonné déconnecté ne reçoit rien — sauf s'il a demandé une session persistante. Le broker met alors ses messages en file d'attente et les lui livre à la reconnexion. Cela suppose deux choses : un identifiant client stable, et un QoS d'au moins 1. Vous le vérifierez dans le TP qui suit.
Quiz — Qualité de service
Jour 1 · Comprendre et faire parler les objets · 8 min
1. Un relevé de température toutes les deux secondes. Quel QoS ?
2. Une commande d'arrêt d'urgence d'une chaudière. Quel QoS ?
3. Le publieur émet en QoS 0, l'abonné a souscrit en QoS 2. Que se passe-t-il ?
4. Que faut-il pour qu'un abonné retrouve les messages émis pendant sa coupure ?
TP 3 — Éprouver les trois niveaux
Jour 1 · Comprendre et faire parler les objets · 1 h
Constater par vous-même ce que chaque niveau garantit, et ce qu'il coûte.
Notez par écrit ce que vous attendez de chaque niveau. Vous comparerez ensuite avec ce que vous observez : c'est là que la notion s'installe.
- Abonnez-vous en QoS 0, puis coupez l'abonné pendant que le capteur publie. Relancez-le : les messages émis pendant la coupure sont définitivement perdus.
docker exec broker-thermeo mosquitto_sub -h localhost -v -t 'thermeo/#' -q 0 - Recommencez avec une session persistante et QoS 1. L'identifiant fixe et l'option
-cdemandent au broker de conserver la file :docker exec broker-thermeo mosquitto_sub -h localhost -v \ -t 'thermeo/#' -q 1 -c -i 'superviseur-thermeo' - Coupez l'abonné, laissez publier une dizaine de messages, relancez exactement la même commande. Les messages manqués sont livrés : c'est toute la différence.
- Passez le publieur en QoS 2 et observez les journaux du broker :
c.publish(TOPIC, charge, qos=2) - Comptez les échanges réseau visibles dans les journaux pour un seul message en QoS 2, puis en QoS 0. Consignez l'écart : c'est le prix de la garantie.
- Remplissez le tableau comparatif dans votre compte rendu : pour chaque niveau, le nombre d'échanges observés, ce qui est garanti, et un cas d'usage Thermeo qui le justifie.
- Vos observations correspondent-elles à ce que vous aviez noté avant de commencer ?
- Si un abonné en QoS 1 reçoit deux fois le même relevé, quelle conséquence côté application, et comment s'en prémunir ?
Connaître l'état du parc à tout instant
Jour 1 · Comprendre et faire parler les objets · 20 min
Reste le troisième problème de Thermeo : une panne de capteur passe inaperçue. MQTT y répond par deux mécanismes complémentaires, qu'on confond souvent.
Le message retenu
Un message publié avec le drapeau retain est conservé par le broker comme « dernière valeur connue » du topic. Tout nouvel abonné le reçoit immédiatement, sans attendre la prochaine publication.
C'est ce qui permet à un tableau de bord de s'afficher rempli dès son ouverture, au lieu de montrer des cases vides pendant dix minutes. Un seul message retenu par topic : chaque nouveau remplace le précédent. Pour l'effacer, on publie une charge utile vide.
Le testament
Le Last Will — le testament — est un message que le client confie au broker au moment de se connecter, avec cette consigne : « si je disparais sans prévenir, publie ceci à ma place ».
Le broker le déclenche lorsque le client cesse de donner signe de vie, à l'expiration du délai de keep-alive. Le capteur signale donc sa propre panne, sans que personne ait à le surveiller.
Le message retenu est attaché à un topic : il conserve la dernière valeur, pour tout nouvel abonné.
La session persistante est attachée à un client : elle lui conserve les messages manqués pendant son absence.
Les deux survivent à une déconnexion, mais ils ne répondent pas à la même question. C'est le point sur lequel votre encadrant vous interrogera.
Les deux ensemble
Un capteur bien conçu publie en-ligne en retenu à sa connexion, et confie un
testament hors-ligne, retenu lui aussi. Résultat : le topic de statut porte en
permanence l'état réel du capteur, disponible instantanément pour qui s'y abonne. C'est
exactement ce qui manquait à Thermeo, et vous allez le mettre en place maintenant.
TP 4 — Détecter un capteur muet
Jour 1 · Comprendre et faire parler les objets · 1 h
Mettre en place les deux mécanismes qui donnent l'état du parc en temps réel.
- Publiez une valeur en message retenu :
docker exec broker-thermeo mosquitto_pub -h localhost -r \ -t 'thermeo/lyon/etage1/capt-01/temperature' -m '21.4' - Abonnez-vous ensuite à ce topic : vous recevez la valeur immédiatement, sans attendre de nouvelle publication.
- Vérifiez qu'un retenu écrase le précédent : publiez
22.0, réabonnez-vous, constatez que seule la dernière valeur revient. Puis effacez-le avec une charge vide :docker exec broker-thermeo mosquitto_pub -h localhost -r \ -t 'thermeo/lyon/etage1/capt-01/temperature' -m '' - Mettez en place le testament. Il se déclare avant la connexion :
STATUT = f"thermeo/{SITE}/{ETAGE}/{CAPTEUR}/statut" c = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2) c.will_set(STATUT, "hors-ligne", qos=1, retain=True) c.connect("localhost", 1883, 60) c.publish(STATUT, "en-ligne", qos=1, retain=True) - Surveillez les statuts du parc dans un terminal :
docker exec broker-thermeo mosquitto_sub -h localhost -v -t 'thermeo/+/+/+/statut' - Lancez le capteur :
en-ligneapparaît. Interrompez-le brutalement par Ctrl+C — le broker publiehors-ligneà sa place. C'est la détection de panne qui manquait à Thermeo. - Testez la déconnexion propre : publiez
hors-ligneavantdisconnect()et vérifiez que le testament n'est alors pas déclenché.
- Pourquoi le testament doit-il être déclaré avant la connexion, et pas après ?
- Que verrait un tableau de bord ouvert après coup si le statut n'était pas retenu ?
- Quel keep-alive retiendriez-vous pour un capteur sur pile qui émet toutes les dix minutes ?
Point de contrôle — fin du jour 1
Jour 1 · Comprendre et faire parler les objets · 15 min
hors-ligneVotre encadrant vous demandera de montrer, pas de raconter : il coupera lui-même un capteur pour voir le statut basculer. Préparez vos terminaux.
Utilisez le bouton Exporter mon avancement en haut de page : il copie le récapitulatif de votre progression, que vous lui transmettez.
Ce qu'un broker ouvert laisse passer
Jour 2 · Sécuriser les échanges · 20 min
La maquette du jour 1 fonctionne, mais elle est ouverte à tous vents. N'importe qui sur le réseau peut lire les relevés — et surtout en injecter de faux.
Un attaquant publie -10 sur le topic de température d'un bâtiment. La régulation croit qu'il gèle et pousse le chauffage au maximum. Ou l'inverse : il publie 22 en plein hiver sur un capteur réel à 3 °C, et les canalisations gèlent.
Chez Thermeo, une consigne falsifiée n'est pas une fuite de données : c'est un dégât matériel.
Trois questions, trois réponses distinctes
On confond souvent ces trois notions. Elles répondent pourtant à des questions différentes, et chacune appelle son propre mécanisme.
| Question | Notion | Mécanisme MQTT |
|---|---|---|
| Qui es-tu ? | Authentification | Mot de passe, puis certificat client |
| Qui peut lire ce qu'on s'échange ? | Confidentialité | TLS |
| Qu'as-tu le droit de faire ? | Autorisation | Listes de contrôle d'accès (ACL) |
Le chiffrement n'autorise rien. Un capteur légitime, authentifié et en TLS, peut parfaitement écrire sur le topic d'un autre capteur si aucune ACL ne l'en empêche. C'est l'erreur de conception la plus fréquente, et votre encadrant vous interrogera dessus.
Ce que le responsable sécurité exige
Trois conditions avant tout déploiement, que vous allez remplir aujourd'hui :
- Plus aucun accès anonyme.
- Tout le trafic chiffré.
- Chaque capteur limité à ses propres topics — celui de Lyon ne doit pas pouvoir écrire sur ceux de Paris.
Certificats X.509 et chaîne de confiance
Jour 2 · Sécuriser les échanges · 25 min
Le mot de passe règle la question « qui es-tu ? », mais il circule en clair tant que la connexion ne l'est pas. Pour chiffrer, il faut des certificats — et pour des certificats, il faut une autorité.
Le principe
Une autorité de certification (CA) est simplement une paire de clés dont on décide qu'elle fait foi. Elle signe les certificats du broker et des capteurs. Chacun d'eux, possédant le certificat de l'autorité, peut alors vérifier que l'autre a bien été signé par elle.
Pour un réseau interne, vous êtes votre propre autorité : c'est ce qu'on appelle une CA privée. Aucun organisme extérieur n'intervient, et c'est parfaitement légitime tant que les certificats ne servent qu'entre vos machines.
| Fichier | Contenu | Qui le détient |
|---|---|---|
ca.key | Clé privée de l'autorité | Personne d'autre que vous. Qui l'a peut signer n'importe quoi |
ca.crt | Certificat public de l'autorité | Tout le monde — c'est lui qui permet de vérifier |
broker.key | Clé privée du broker | Le broker seul |
broker.crt | Certificat du broker, signé par la CA | Public |
capt-01.key/.crt | Idem pour un capteur | Le capteur |
Le Common Name du certificat du broker doit correspondre au nom par lequel les clients le joignent. Si le certificat dit localhost et que le client se connecte à 192.168.1.10, la vérification échoue — et c'est normal : c'est exactement le mécanisme qui protège d'un serveur usurpé.
Deux usages du certificat client
Un capteur peut présenter son propre certificat. Cela apporte deux choses d'un coup :
- Il prouve son identité sans mot de passe, donc sans secret à stocker en clair dans le firmware.
- Son
CNpeut servir directement d'identifiant — c'est l'optionuse_identity_as_username, qui relie le certificat aux ACL.
825 jours pour les certificats de service, 3650 pour l'autorité. L'écart n'est pas arbitraire : renouveler un certificat de service est une opération de routine, alors que changer l'autorité oblige à redéployer la confiance sur tout le parc.
Quiz — Authentification et chiffrement
Jour 2 · Sécuriser les échanges · 8 min
1. Un capteur est authentifié et communique en TLS. Peut-il publier sur le topic d'un autre capteur ?
2. Quel fichier ne doit jamais quitter la machine qui l'a généré ?
3. Le certificat du broker porte CN = localhost, mais le client se connecte à 10.0.0.5. Que se passe-t-il ?
4. À quoi sert use_identity_as_username ?
TP 1 — Fermer la porte
Jour 2 · Sécuriser les échanges · 40 min
Supprimer l'accès anonyme et imposer une authentification.
- Créez le fichier de mots de passe avec un premier compte. La commande demande le mot de passe deux fois :
docker exec -it broker-thermeo \ mosquitto_passwd -c /mosquitto/config/passwd capt-01 - Ajoutez deux comptes de plus. Attention : sans
-c, sinon vous écrasez le fichier :docker exec -it broker-thermeo \ mosquitto_passwd /mosquitto/config/passwd capt-07 docker exec -it broker-thermeo \ mosquitto_passwd /mosquitto/config/passwd superviseur - Modifiez
mosquitto.conf:listener 1883 allow_anonymous false password_file /mosquitto/config/passwd log_type all connection_messages true - Redémarrez avec
docker restart broker-thermeo, puis tentez une publication sans identifiants. Elle échoue — c'est le résultat attendu. Notez le code d'erreur exact, il vous servira à diagnostiquer plus tard. - Republiez avec les identifiants :
docker exec broker-thermeo mosquitto_pub -h localhost \ -u capt-01 -P '<votre-mot-de-passe>' \ -t 'thermeo/lyon/etage1/capt-01/temperature' -m '21.4' - Adaptez
capteur.py, avant l'appel àconnect:c.username_pw_set("capt-01", "<votre-mot-de-passe>")
- Le mot de passe circule encore en clair sur le réseau. Qu'apporte malgré tout l'authentification à ce stade ?
- Que se passe-t-il si vous oubliez
-cà la création, et pourquoi est-ce dangereux sur un broker en service ?
TP 2 — Devenir sa propre autorité
Jour 2 · Sécuriser les échanges · 50 min
Produire la chaîne de confiance dont le broker et les capteurs auront besoin.
Écrivez ces commandes dans un script generer-certs.sh au fur et à mesure. Vous le rejouerez plusieurs fois — et c'est un livrable attendu.
- Créez un dossier
certsdans votre répertoire de travail. - Générez la clé et le certificat de votre autorité, valable dix ans :
openssl genrsa -out certs/ca.key 2048 openssl req -new -x509 -days 3650 -key certs/ca.key \ -out certs/ca.crt \ -subj "/C=FR/O=Thermeo/CN=Thermeo Root CA" - Générez la clé et la demande de signature du broker. Le
CNdoit être le nom par lequel les clients le joindront :openssl genrsa -out certs/broker.key 2048 openssl req -new -key certs/broker.key -out certs/broker.csr \ -subj "/C=FR/O=Thermeo/CN=localhost" - Faites signer le certificat du broker par votre autorité :
openssl x509 -req -in certs/broker.csr -CA certs/ca.crt \ -CAkey certs/ca.key -CAcreateserial \ -out certs/broker.crt -days 825 -sha256 - Faites de même pour le capteur — il servira au TP 4 :
openssl genrsa -out certs/capt-01.key 2048 openssl req -new -key certs/capt-01.key -out certs/capt-01.csr \ -subj "/C=FR/O=Thermeo/CN=capt-01" openssl x509 -req -in certs/capt-01.csr -CA certs/ca.crt \ -CAkey certs/ca.key -CAcreateserial \ -out certs/capt-01.crt -days 825 -sha256 - Inspectez le résultat et relevez l'émetteur, le sujet et les dates de validité :
openssl x509 -in certs/broker.crt -noout -text | head -20
- Où
ca.keydevrait-il être conservé dans une vraie installation ? - Que faudra-t-il faire dans 825 jours, et comment l'anticiper sur cent capteurs ?
TP 3 — Chiffrer le trafic
Jour 2 · Sécuriser les échanges · 1 h
Passer le broker en TLS et vérifier de vos yeux que le chiffrement est effectif.
- Ouvrez un écouteur TLS sur le port 8883, en gardant provisoirement le 1883 pour comparer :
listener 1883 allow_anonymous false password_file /mosquitto/config/passwd listener 8883 allow_anonymous false password_file /mosquitto/config/passwd cafile /mosquitto/config/certs/ca.crt certfile /mosquitto/config/certs/broker.crt keyfile /mosquitto/config/certs/broker.key tls_version tlsv1.2 log_type all - Recréez le conteneur en montant aussi les certificats :
docker rm -f broker-thermeo docker run -it --name broker-thermeo \ -p 1883:1883 -p 8883:8883 \ -v "$(pwd)/mosquitto.conf":/mosquitto/config/mosquitto.conf \ -v "$(pwd)/certs":/mosquitto/config/certs \ -v "$(pwd)/passwd":/mosquitto/config/passwd \ eclipse-mosquitto:2 - Si le broker refuse de démarrer, lisez le message : une erreur de permission sur les clés est le cas le plus fréquent. Consignez le message et sa résolution — c'est un livrable.
- Publiez en TLS en fournissant le certificat de l'autorité, pour que le client puisse vérifier le broker :
mosquitto_pub -h localhost -p 8883 \ --cafile certs/ca.crt \ -u capt-01 -P '<mot-de-passe>' \ -t 'thermeo/lyon/etage1/capt-01/temperature' -m '21.4' - Retentez sans
--cafile: la connexion échoue, le client refusant un certificat qu'il ne peut pas valider. C'est le comportement attendu. - Adaptez
capteur.py:import ssl c.username_pw_set("capt-01", "<mot-de-passe>") c.tls_set(ca_certs="certs/ca.crt", tls_version=ssl.PROTOCOL_TLSv1_2) c.connect("localhost", 8883, 60) - Si vous avez Wireshark, capturez sur la boucle locale et comparez 1883 et 8883 : sur le premier, la valeur
21.4se lit en clair ; sur le second, non. C'est la démonstration que le chiffrement fonctionne. - Une fois la vérification faite, supprimez l'écouteur 1883 et redémarrez : plus aucun trafic en clair.
- Pourquoi une erreur de certificat doit-elle faire échouer la connexion plutôt que produire un simple avertissement ?
Autoriser : les listes de contrôle d'accès
Jour 2 · Sécuriser les échanges · 15 min
Il reste la troisième exigence : qu'un capteur ne puisse écrire que sur ses propres topics. C'est le rôle des ACL, déclarées dans un fichier que Mosquitto relit à chaque connexion.
La syntaxe
Une ACL se lit comme une suite de blocs : un utilisateur, puis ses droits.
user superviseur
topic read thermeo/#
user capt-01
topic write thermeo/lyon/etage1/capt-01/#
topic read thermeo/lyon/etage1/capt-01/consigneTrois verbes existent : read, write, et readwrite. Les
jokers + et # fonctionnent comme dans les abonnements.
Chaque capteur n'écrit que sous sa propre branche, et ne lit que sa consigne. Le superviseur lit tout mais n'écrit rien. Si un capteur est compromis, le dégât reste circonscrit à sa branche — c'est exactement l'objectif.
Ce que cela implique
Notez la conséquence du dernier point : dans cette configuration, le superviseur ne peut pas publier de consigne. Si Thermeo veut plus tard piloter les chaudières depuis la supervision, il faudra un compte distinct, avec des droits d'écriture explicites et limités aux topics de consigne. C'est une décision d'architecture, pas un détail de configuration.
Que faire si un capteur disparaît du site ? Son certificat reste valide. Il faut pouvoir le révoquer — soit en retirant son bloc du fichier ACL, soit par une liste de révocation. Vérifiez que votre maquette permet la première option : c'est la question que posera votre encadrant.
Quiz — Autorisation
Jour 2 · Sécuriser les échanges · 6 min
1. Une ACL déclare user capt-01 puis topic write thermeo/lyon/etage1/capt-01/#. capt-01 peut-il lire les relevés de capt-07 ?
2. Un capteur est volé sur site. Quelle est la première action ?
3. Dans la configuration proposée, le superviseur peut-il publier une consigne ?
TP 4 — Identité par certificat et cloisonnement
Jour 2 · Sécuriser les échanges · 1 h
Donner à chaque capteur une identité propre et le restreindre à son périmètre.
- Exigez un certificat client et utilisez son CN comme identité :
require_certificate true use_identity_as_username true - Le capteur présente désormais son certificat, sans mot de passe :
c.tls_set(ca_certs="certs/ca.crt", certfile="certs/capt-01.crt", keyfile="certs/capt-01.key", tls_version=ssl.PROTOCOL_TLSv1_2) - Vérifiez dans les journaux que le client est identifié comme
capt-01: c'est le CN du certificat qui fait foi. - Créez le fichier
acl:# Le superviseur lit l'ensemble du parc user superviseur topic read thermeo/# # Chaque capteur n'écrit que sur sa propre branche user capt-01 topic write thermeo/lyon/etage1/capt-01/# topic read thermeo/lyon/etage1/capt-01/consigne user capt-07 topic write thermeo/lyon/etage2/capt-07/# topic read thermeo/lyon/etage2/capt-07/consigne - Déclarez-le et redémarrez :
acl_file /mosquitto/config/acl - Depuis
capt-01, tentez de publier sur le topic decapt-07. La publication est rejetée — c'est précisément l'exigence du responsable sécurité. - Vérifiez le pendant : le superviseur lit tout le parc, mais sa tentative de publier une consigne est refusée.
- Trois mécanismes se superposent maintenant : mot de passe, certificat, ACL. Que protège chacun, exactement ?
- Quelle discipline
use_identity_as_usernameimpose-t-il à la génération des certificats sur un parc de cent capteurs ?
Point de contrôle — fin du jour 2
Jour 2 · Sécuriser les échanges · 15 min
opensslDes clés privées présentes dans le dépôt Git font échouer la validation du module, sans discussion. Sur un parcours qui traite de sécurité, il ne peut y avoir aucune tolérance là-dessus.
Vérifiez avec git status et ajoutez un .gitignore avant de livrer.
CoAP : l'autre voie
Jour 3 · Élargir et assembler · 20 min
MQTT n'est pas la seule réponse. CoAP transpose le modèle REST sur UDP, pour des équipements encore plus contraints que ceux que nous avons manipulés.
Deux philosophies
| MQTT | CoAP | |
|---|---|---|
| Modèle | Publication / abonnement | Requête / réponse, comme HTTP |
| Transport | TCP — connexion maintenue | UDP — pas de connexion |
| Intermédiaire | Un broker, obligatoire | Aucun : on interroge l'objet directement |
| Qui déclenche | L'objet, quand il a une valeur | Le client, quand il veut savoir |
| En-tête | 2 octets | 4 octets |
| Sécurité | TLS | DTLS |
Ce n'est pas la taille de l'en-tête. C'est la connexion : MQTT en maintient une en permanence, ce qui consomme, mais permet à l'objet de signaler un événement immédiatement. CoAP ne maintient rien, mais l'objet doit alors être joignable au moment où on l'interroge — impossible s'il dort pour économiser sa pile.
L'observation
CoAP propose un mécanisme d'observe : le client s'inscrit sur une ressource et le serveur le notifie des changements. C'est un rapprochement du modèle MQTT, mais sans broker — donc sans point de collecte central, ce qui complique la supervision de cent capteurs.
Un capteur alimenté secteur qui doit remonter des alertes : MQTT. Un capteur sur pile qui se réveille toutes les heures et qu'on interroge à la demande : CoAP mérite l'examen. Les deux peuvent cohabiter derrière une passerelle.
Les réseaux LPWAN
Jour 3 · Élargir et assembler · 25 min
Reste la question radio. Pour les sept bâtiments de Thermeo sans connexion exploitable, il faut un réseau longue portée et basse consommation — c'est ce que désigne LPWAN.
Les trois candidats
| LoRaWAN | Sigfox | NB-IoT | |
|---|---|---|---|
| Portée urbaine | 2 à 5 km | 3 à 10 km | 1 à 5 km |
| Débit | 0,3 à 50 kbit/s | 100 bit/s | 20 à 250 kbit/s |
| Messages par jour | limité par le cycle d'utilisation | 140 en émission | pas de quota fixe |
| Taille utile | 51 à 242 octets | 12 octets | jusqu'à 1 600 octets |
| Infrastructure | vos propres passerelles | opérateur | réseau mobile |
| Coût annuel par objet | faible, mais passerelles à financer | quelques euros | abonnement mobile |
Thermeo veut un relevé toutes les quinze minutes, soit 96 messages par jour. Regardez la ligne « messages par jour » : une des trois technologies est éliminée par ce seul chiffre. Laquelle, et pourquoi ? C'est le premier travail du TP.
Trois critères qu'on oublie souvent
- Le cycle d'utilisation. Sur les bandes libres, la réglementation limite le temps d'émission — typiquement 1 % du temps. Cela contraint la conception applicative bien plus que le débit théorique.
- Qui exploite l'infrastructure. LoRaWAN vous rend indépendant d'un opérateur, mais vous devenez responsable des passerelles, de leur alimentation et de leur maintenance.
- La pérennité. Un opérateur peut cesser son service — c'est déjà arrivé. Sur un choix qui engage dix ans, ce risque pèse autant que les caractéristiques techniques.
Quiz — CoAP et LPWAN
Jour 3 · Élargir et assembler · 8 min
1. Quelle différence structurelle entre MQTT et CoAP ?
2. Thermeo veut 96 messages par jour et 12 octets de charge utile. Quelle technologie est éliminée ?
3. Un capteur sur pile qui dort et se réveille toutes les heures. MQTT ou CoAP ?
4. Qu'est-ce que le cycle d'utilisation contraint ?
TP 1 — Mettre CoAP en œuvre
Jour 3 · Élargir et assembler · 50 min
Faire fonctionner un échange CoAP et mesurer ce qui le distingue de MQTT.
Installez la bibliothèque : pip install aiocoap
- Écrivez le serveur
serveur_coap.py:import asyncio, random import aiocoap.resource as resource import aiocoap class Temperature(resource.Resource): async def render_get(self, request): valeur = round(random.uniform(19.0, 24.0), 1) return aiocoap.Message(payload=str(valeur).encode()) async def main(): racine = resource.Site() racine.add_resource(['temperature'], Temperature()) await aiocoap.Context.create_server_context(racine, bind=('127.0.0.1', 5683)) await asyncio.get_running_loop().create_future() asyncio.run(main()) - Écrivez le client
client_coap.py:import asyncio from aiocoap import Context, Message, GET async def main(): ctx = await Context.create_client_context() req = Message(code=GET, uri='coap://127.0.0.1/temperature') rep = await ctx.request(req).response print('reponse :', rep.payload.decode()) asyncio.run(main()) - Lancez le serveur puis le client. Notez la différence de posture avec MQTT : ici c'est le client qui demande, le capteur ne pousse rien de lui-même.
- Ajoutez une seconde ressource
/humiditeet vérifiez que le même serveur les expose toutes les deux. - Comparez la taille d'un échange CoAP et d'un échange MQTT, et consignez ce que cela change pour un capteur sur pile.
- CoAP fonctionne sur UDP, qui ne garantit pas la livraison. Comment le protocole compense-t-il, et à quel coût ?
- CoAP n'a pas de broker. Avantage ou inconvénient pour superviser cent capteurs ?
TP 2 — Un ESP32 sans ESP32
Jour 3 · Élargir et assembler · 1 h
Exécuter du vrai firmware embarqué, sans aucune carte physique.
Ce TP publie sur test.mosquitto.org, un broker public, parce que le simulateur tourne dans le cloud et n'atteint pas votre poste. N'y publiez jamais de donnée réelle : c'est un réflexe à installer maintenant.
- Ouvrez wokwi.com et créez un projet ESP32. Le simulateur exécute réellement le firmware et dispose d'une pile réseau.
- Ajoutez un capteur DHT22 par le bouton +. Reliez sa broche de données à la GPIO 15, son alimentation au 3V3 et sa masse au GND.
- Saisissez le firmware dans
sketch.ino. Le SSIDWokwi-GUESTest le réseau du simulateur, sans mot de passe :#include <WiFi.h> #include <PubSubClient.h> #include "DHTesp.h" const char* SSID = "Wokwi-GUEST"; const char* BROKER = "test.mosquitto.org"; const char* TOPIC = "thermeo/demo/etage1/esp32-01/temperature"; WiFiClient net; PubSubClient mqtt(net); DHTesp dht; void setup() { Serial.begin(115200); dht.setup(15, DHTesp::DHT22); WiFi.begin(SSID, ""); while (WiFi.status() != WL_CONNECTED) { delay(250); Serial.print("."); } Serial.println(" wifi ok"); mqtt.setServer(BROKER, 1883); } void loop() { if (!mqtt.connected()) mqtt.connect("esp32-thermeo-01"); TempAndHumidity d = dht.getTempAndHumidity(); char buf[16]; dtostrf(d.temperature, 4, 1, buf); mqtt.publish(TOPIC, buf); Serial.printf("publie %s\n", buf); delay(5000); } - Déclarez les bibliothèques dans
libraries.txt:PubSubClient DHT sensor library for ESPx - Démarrez la simulation. Le moniteur série affiche la connexion puis les publications. Cliquez sur le DHT22 pour faire varier la température : la valeur publiée suit.
- Depuis votre poste, recevez ce que publie l'ESP32 simulé :
mosquitto_sub -h test.mosquitto.org -v \ -t 'thermeo/demo/etage1/esp32-01/#' - Vous recevez les relevés d'un microcontrôleur qui n'existe pas physiquement. Mesurez le délai entre l'affichage sur le moniteur série et la réception.
- Ajoutez un testament au firmware, comme au jour 1, en utilisant la signature complète de
mqtt.connect. Coupez la simulation et vérifiez la bascule du statut.
- Le simulateur exécute le firmware mais pas le matériel. Qu'est-ce qu'il ne permettra jamais de tester ?
- Le firmware se reconnecte à chaque boucle si nécessaire. Quel problème sur un capteur alimenté par pile ?
TP 3 — Instruire le choix radio
Jour 3 · Élargir et assembler · 1 h 15
Recommander une technologie LPWAN pour les sept bâtiments isolés, sur critères.
Ce n'est pas un exercice technique mais un travail d'ingénieur : une valeur sans source ne sera pas retenue. C'est le point sur lequel votre encadrant vous interrogera.
- Documentez-vous sur LoRaWAN, Sigfox et NB-IoT. Sources attendues : LoRa Alliance, documentation The Things Network, spécifications 3GPP.
- Construisez un tableau comparatif portant sur : portée urbaine et rurale, débit utile, taille et nombre de messages autorisés par jour, consommation et autonomie estimée, coût annuel par objet, nécessité de passerelles, couverture en France.
- Renseignez chaque case avec sa source.
- Appliquez les contraintes de Thermeo : un relevé toutes les quinze minutes, 12 octets de charge utile, cinq ans d'autonomie visée, sept bâtiments dont trois en zone rurale, 15 € par objet et par an.
- Calculez le nombre de messages quotidiens qu'impose ce rythme et confrontez-le aux quotas. Une technologie est éliminée par ce seul calcul : identifiez-la et démontrez-le.
- Rédigez une recommandation d'une page : la technologie retenue, les deux raisons principales, et ce qui vous ferait changer d'avis si une contrainte évoluait.
- LoRaWAN impose d'exploiter ses propres passerelles. Dans quel cas est-ce un avantage plutôt qu'une charge ?
- Un opérateur peut cesser son service. Comment ce risque pèse-t-il sur un choix engageant dix ans ?
TP 4 — Assembler le tout
Jour 3 · Élargir et assembler · 1 h 30
Réunir les acquis des trois jours en une maquette qui tient debout seule.
- Repartez du broker sécurisé du jour 2 : TLS, certificats clients, ACL actives.
- Générez des certificats pour trois capteurs supplémentaires avec votre script
generer-certs.sh, et étendez le fichier ACL en conséquence. - Généralisez
capteur.pypour qu'il prenne son identité en paramètre :import sys SITE, ETAGE, CAPTEUR = sys.argv[1], sys.argv[2], sys.argv[3] # le reste du code reprend ces trois variables # lancement : python capteur.py lyon etage1 capt-01 - Lancez quatre capteurs simultanément, sur deux sites et deux étages différents.
- Écrivez
superviseur.py, qui s'abonne à tout le parc, tient à jour le dernier relevé de chacun et signale tout capteur muet depuis plus de dix secondes :# Ossature attendue — a completer # 1. connexion TLS avec le certificat du superviseur # 2. abonnement a thermeo/# en QoS 1, session persistante # 3. dictionnaire {topic: (valeur, horodatage)} # 4. boucle d'affichage toutes les 5 s : etat du parc # 5. alerte si horodatage plus vieux que 10 s - Coupez brutalement un capteur : le superviseur doit le signaler, à la fois par son horodatage et par le testament reçu sur le topic de statut.
- Vérifiez enfin que l'ACL tient : tentez depuis un capteur de publier sur la branche d'un autre, et confirmez le refus dans les journaux.
- Votre maquette tourne sur un poste. Qu'est-ce qui casserait en premier avec cent capteurs sur un vrai réseau ?
- Le superviseur détecte un capteur muet par horodatage et par testament. Pourquoi conserver les deux ?
- Quelle brique manque encore pour exploiter ces données au-delà du temps réel ?
Point de contrôle — fin du module M1
Jour 3 · Élargir et assembler · 20 min
Elle a lieu mercredi à 15 h 00 et dure deux heures. Votre encadrant passera en revue vos livrables, vous fera expliquer les notions et validera — ou non — le module.
Ce qui n'est pas attendu : la perfection du code Python. Un script qui fonctionne et que vous savez expliquer vaut mieux qu'un script élégant recopié.