Illustre les modules 10 à 12 et la synthèse (7 figures)

M10 : consommation par mode, profil de courant en deep sleep. M11 : publish/subscribe, arborescence des topics. M12 : démarche de projet, structure multi-fichiers. Synthèse : carte du parcours. Figure du chenillard (M2) déplacée après son code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Artemia
2026-10-18 15:24:43 +02:00
co-authored by Claude Opus 5.5
parent f4a80f4d5b
commit 379cd03bc5
23 changed files with 16414 additions and 2 deletions
+2 -2
View File
@@ -64,8 +64,6 @@ Un bouton mécanique génère plusieurs transitions HIGH/LOW parasites en quelqu
Câblez 4 LED (avec résistances de 220 Ω) sur 4 GPIO libres, puis reproduisez un effet de chenillard : Câblez 4 LED (avec résistances de 220 Ω) sur 4 GPIO libres, puis reproduisez un effet de chenillard :
![Câblage du chenillard : une résistance de 220 Ω par LED, masse commune](../schemas/svg/m02-chenillard.svg){#fig-m02-chenillard width=75%}
```cpp ```cpp
const int leds[] = {4, 5, 6, 7}; const int leds[] = {4, 5, 6, 7};
const int nbLeds = 4; const int nbLeds = 4;
@@ -85,6 +83,8 @@ void loop() {
} }
``` ```
![Câblage du chenillard : une résistance de 220 Ω par LED, masse commune](../schemas/svg/m02-chenillard.svg){#fig-m02-chenillard width=75%}
## 4. La LED RGB NeoPixel intégrée ## 4. La LED RGB NeoPixel intégrée
Contrairement à une LED classique qui ne prend que deux états (allumé/éteint) ou une intensité en PWM, la LED intégrée sur la plupart des cartes ESP32-S3 DevKit est une LED adressable de type WS2812 (aussi appelée NeoPixel). Elle intègre son propre contrôleur et se pilote avec un protocole série à un seul fil, où chaque LED reçoit une trame de 24 bits (8 bits rouge, 8 bits vert, 8 bits bleu). Contrairement à une LED classique qui ne prend que deux états (allumé/éteint) ou une intensité en PWM, la LED intégrée sur la plupart des cartes ESP32-S3 DevKit est une LED adressable de type WS2812 (aussi appelée NeoPixel). Elle intègre son propre contrôleur et se pilote avec un protocole série à un seul fil, où chaque LED reçoit une trame de 24 bits (8 bits rouge, 8 bits vert, 8 bits bleu).
+4
View File
@@ -22,6 +22,8 @@ Un ESP32-S3 alimenté en continu (Wi-Fi actif, calcul permanent) consomme couram
| Light sleep | \~0,8 mA | RAM et état conservés | Timer, GPIO, UART... | | Light sleep | \~0,8 mA | RAM et état conservés | Timer, GPIO, UART... |
| Deep sleep | \~10-150 µA | RTC RAM uniquement, redémarrage du programme | Timer, GPIO (EXT0/EXT1) | | Deep sleep | \~10-150 µA | RTC RAM uniquement, redémarrage du programme | Timer, GPIO (EXT0/EXT1) |
![Consommation selon le mode et autonomie correspondante sur une batterie de 2000 mAh (ordres de grandeur)](../schemas/svg/m10-consommation-modes.svg){#fig-m10-consommation-modes}
::: {.callout-note title="Le deep sleep redémarre le programme"} ::: {.callout-note title="Le deep sleep redémarre le programme"}
Contrairement au light sleep, sortir du deep sleep provoque un redémarrage complet du programme (comme après un reset) : `setup()` est ré-exécuté depuis le début. Seule la zone mémoire RTC (déclarée avec `RTC_DATA_ATTR`) est préservée entre deux cycles de deep sleep, ce qui permet de conserver un compteur ou un état minimal. Contrairement au light sleep, sortir du deep sleep provoque un redémarrage complet du programme (comme après un reset) : `setup()` est ré-exécuté depuis le début. Seule la zone mémoire RTC (déclarée avec `RTC_DATA_ATTR`) est préservée entre deux cycles de deep sleep, ce qui permet de conserver un compteur ou un état minimal.
::: :::
@@ -30,6 +32,8 @@ Contrairement au light sleep, sortir du deep sleep provoque un redémarrage comp
Cas d'usage typique : un capteur qui se réveille toutes les X minutes, effectue une mesure, l'envoie, puis se rendort. Cas d'usage typique : un capteur qui se réveille toutes les X minutes, effectue une mesure, l'envoie, puis se rendort.
![Profil de courant d'un capteur en deep sleep réveillé toutes les 60 s (échelle logarithmique)](../schemas/svg/m10-cycle-deep-sleep.svg){#fig-m10-cycle-deep-sleep}
```cpp ```cpp
#define TEMPS_REVEIL_SECONDES 60 #define TEMPS_REVEIL_SECONDES 60
+4
View File
@@ -22,8 +22,12 @@ Le modèle HTTP vu au Module 7 fonctionne bien pour des requêtes ponctuelles, m
| Publish | Envoyer un message sur un topic donné | | Publish | Envoyer un message sur un topic donné |
| Subscribe | S'abonner à un topic pour recevoir tous les messages publiés dessus | | Subscribe | S'abonner à un topic pour recevoir tous les messages publiés dessus |
![Les topics forment une arborescence, comme des chemins de fichiers](../schemas/svg/m11-arborescence-topics.svg){#fig-m11-arborescence-topics width=90%}
Avantage clé : les appareils ne communiquent jamais directement entre eux — tout transite par le broker, ce qui simplifie grandement l'architecture (un capteur ne connaît pas l'adresse du tableau de bord qui affichera ses données, il publie simplement sur un topic). Avantage clé : les appareils ne communiquent jamais directement entre eux — tout transite par le broker, ce qui simplifie grandement l'architecture (un capteur ne connaît pas l'adresse du tableau de bord qui affichera ses données, il publie simplement sur un topic).
![Modèle publish/subscribe : la carte publie ses mesures et reçoit ses commandes via le broker](../schemas/svg/m11-publish-subscribe.svg){#fig-m11-publish-subscribe}
## 3. Choisir un broker MQTT ## 3. Choisir un broker MQTT
- Mosquitto : broker open-source, installable localement (Raspberry Pi, PC, serveur domestique) ou utilisable en test public (test.mosquitto.org). - Mosquitto : broker open-source, installable localement (Raspberry Pi, PC, serveur domestique) ou utilisable en test public (test.mosquitto.org).
+4
View File
@@ -19,6 +19,8 @@ Un projet réussi commence par une phase de cadrage, même brève. Avant d'ouvri
- Le projet doit-il fonctionner de façon autonome (batterie) ou reste-t-il branché en continu ? - Le projet doit-il fonctionner de façon autonome (batterie) ou reste-t-il branché en continu ?
- Quels modules de la formation seront mobilisés ? - Quels modules de la formation seront mobilisés ?
![Démarche de projet : du besoin à la présentation](../schemas/svg/m12-demarche-projet.svg){#fig-m12-demarche-projet}
## 2. Exemples de projets (par ordre de complexité croissante) ## 2. Exemples de projets (par ordre de complexité croissante)
| Projet | Modules mobilisés | Description | | Projet | Modules mobilisés | Description |
@@ -55,6 +57,8 @@ Au-delà d'une centaine de lignes, un seul fichier .ino devient difficile à mai
- affichage.ino : fonctions liées à l'écran OLED et à la LED NeoPixel. - affichage.ino : fonctions liées à l'écran OLED et à la LED NeoPixel.
- reseau.ino : fonctions Wi-Fi/MQTT/BLE. - reseau.ino : fonctions Wi-Fi/MQTT/BLE.
![Organisation d'un croquis en plusieurs onglets, une responsabilité par fichier](../schemas/svg/m12-structure-projet.svg){#fig-m12-structure-projet}
::: {.callout-tip title="Bonne pratique de structuration"} ::: {.callout-tip title="Bonne pratique de structuration"}
Une fonction doit avoir une seule responsabilité claire (ex : `lireTemperature()`, `afficherEcran()`, `publierMQTT()`). Cette organisation facilite grandement le débogage et la relecture, surtout en formation collective où le code peut être partagé et commenté entre apprenants. Une fonction doit avoir une seule responsabilité claire (ex : `lireTemperature()`, `afficherEcran()`, `publierMQTT()`). Cette organisation facilite grandement le débogage et la relecture, surtout en formation collective où le code peut être partagé et commenté entre apprenants.
::: :::
+2
View File
@@ -4,6 +4,8 @@ title: "Synthèse de la formation & liste de matériel"
## 1. Vue d'ensemble des modules ## 1. Vue d'ensemble des modules
![Le parcours de la formation : cinq étapes qui convergent vers le projet final](../schemas/svg/m13-parcours-formation.svg){#fig-m13-parcours-formation}
| Module | Titre | Compétences clés | | Module | Titre | Compétences clés |
|----------|-----------------------------|----------------------------------------| |----------|-----------------------------|----------------------------------------|
| 0 | Prérequis & Installation | Arduino IDE, pilotes USB, ajout du support ESP32, premier Blink | | 0 | Prérequis & Installation | Arduino IDE, pilotes USB, ajout du support ESP32, premier Blink |
+61
View File
@@ -0,0 +1,61 @@
"""Module 10 — Gestion de l'énergie."""
import matplotlib.pyplot as plt
import numpy as np
from style import BLEU, CM, ENCRE, GRIS, ORANGE, ROUGE, VERT, Figure
def consommation_modes():
fig, ax = plt.subplots(figsize=(16 * CM, 6.4 * CM))
modes = [("Actif\n(Wi-Fi, calcul)", 80, 250, ROUGE, "moins d'1 jour"),
("Modem-sleep", 20, 30, ORANGE, "≈ 3 jours"),
("Light sleep", 0.7, 0.9, BLEU, "≈ 3 mois"),
("Deep sleep", 0.010, 0.150, VERT, "plus d'1 an (théorique)")]
for i, (nom, bas, haut, c, autonomie) in enumerate(modes):
ax.barh(i, haut - bas, left=bas, height=0.5, color=c, alpha=0.85)
ax.text(haut * 1.4, i, autonomie, va="center", fontsize=8.5, color=c, fontweight="bold")
ax.set_yticks(range(len(modes)), [m[0] for m in modes])
ax.invert_yaxis()
ax.set_xscale("log")
ax.set_xlim(0.005, 20000)
ax.set_xticks([0.01, 0.1, 1, 10, 100, 1000], ["10 µA", "100 µA", "1 mA", "10 mA", "100 mA", "1 A"])
ax.set_xlabel("courant consommé (échelle logarithmique)")
ax.text(20000, -0.75, "autonomie avec une batterie de 2000 mAh", ha="right", fontsize=7.5,
color=GRIS, style="italic")
ax.grid(axis="y", visible=False)
fig.tight_layout()
return fig
def cycle_deep_sleep():
fig, ax = plt.subplots(figsize=(16 * CM, 6.0 * CM))
t, i = [0], [0.05]
for debut in (0, 60, 120):
t += [debut, debut + 0.4, debut + 1.0, debut + 3.0]
i += [45, 40, 160, 0.05]
t += [150]
i += [0.05]
ax.step(t, i, where="post", color=BLEU, lw=1.6)
ax.fill_between(t, 0.01, i, step="post", color=BLEU, alpha=0.12)
ax.set_yscale("log")
ax.set_ylim(0.01, 600)
ax.set_yticks([0.01, 0.1, 1, 10, 100], ["10 µA", "100 µA", "1 mA", "10 mA", "100 mA"])
ax.set_xlim(-3, 150)
ax.set_xlabel("temps (s)")
ax.set_ylabel("courant")
ax.annotate("réveil : setup() repart du début\nmesure BME280, envoi Wi-Fi (≈ 3 s)", xy=(61.5, 160), xytext=(66, 260),
fontsize=7.8, color=ROUGE, arrowprops=dict(arrowstyle="-|>", color=ROUGE, lw=0.8))
ax.text(30, 0.5, "deep sleep (≈ 57 s)\nseule la RTC reste alimentée", ha="center", fontsize=7.8, color=VERT)
ax.text(31.5, 0.16, "esp_sleep_enable_timer_wakeup(60 s)", ha="center", fontsize=7.3, color=ENCRE,
family="monospace")
ax.text(91, 2.0, "compteurReveils (RTC_DATA_ATTR)\nconservé d'un cycle à l'autre", ha="center", fontsize=7.5,
color=GRIS, style="italic")
ax.grid(axis="x", visible=False)
fig.tight_layout()
return fig
FIGURES = [
Figure("m10-consommation-modes", consommation_modes),
Figure("m10-cycle-deep-sleep", cycle_deep_sleep),
]
+64
View File
@@ -0,0 +1,64 @@
"""Module 11 — IoT & Cloud : le protocole MQTT."""
from outils import boite, fleche, toile
from style import BLEU, ENCRE, GRIS, ORANGE, ROUGE, VERT, Figure
def publish_subscribe():
fig, ax = toile(16, 6.6)
esp = boite(ax, 2.2, 3.3, 3.4, 2.4, couleur=BLEU)
ax.text(2.2, 4.05, "ESP32-S3", ha="center", fontsize=10, fontweight="bold", color=BLEU)
ax.text(2.2, 3.0, "BME280 + LED\nNeoPixel", ha="center", va="center", fontsize=8, color=ENCRE)
broker = boite(ax, 8.0, 3.3, 3.0, 2.4, couleur=ORANGE)
ax.text(8.0, 3.75, "Broker", ha="center", fontsize=10, fontweight="bold", color=ORANGE)
ax.text(8.0, 3.0, "Mosquitto,\nHiveMQ…", ha="center", va="center", fontsize=8, color=ENCRE)
dash = boite(ax, 13.8, 4.8, 3.6, 1.4, "Tableau de bord\n(Node-RED)", couleur=VERT, taille=8.5)
ha = boite(ax, 13.8, 1.8, 3.6, 1.4, "Home Assistant", couleur=VERT, taille=8.5)
# Publication de la température
fleche(ax, (3.9, 3.9), (6.5, 3.9), "publish", couleur=BLEU, taille=8, fond=True)
ax.text(5.3, 5.1, "formation/esp32s3/temperature\n« 21.5 »", ha="center", fontsize=6.8, color=BLEU,
family="monospace")
fleche(ax, (9.5, 3.9), (12.0, 4.6), couleur=BLEU)
fleche(ax, (9.5, 3.7), (12.0, 2.0), couleur=BLEU)
ax.text(12.4, 3.35, "abonnés à …/temperature\n→ reçoivent la valeur", ha="center", fontsize=7.2,
color=GRIS, style="italic")
# Commande en retour
fleche(ax, (12.0, 5.2), (9.5, 4.3), couleur=ROUGE)
fleche(ax, (6.5, 2.7), (3.9, 2.7), "message", couleur=ROUGE, taille=8, fond=True)
ax.text(5.3, 1.45, "formation/esp32s3/commande\n« rouge »", ha="center", fontsize=6.8, color=ROUGE,
family="monospace")
ax.text(10.9, 5.25, "publish « rouge »", ha="center", fontsize=7.5, color=ROUGE, rotation=20)
ax.text(8.0, 0.35, "Les appareils ne se parlent jamais directement : tout passe par le broker.",
ha="center", fontsize=7.8, color=GRIS, style="italic")
return fig
def arborescence_topics():
fig, ax = toile(16, 5.0)
noeuds = {
"maison": (1.6, 3.6), "salon": (4.9, 4.3), "cuisine": (4.9, 2.9),
"temperature ": (8.3, 4.65), "humidite": (8.3, 3.95), "temperature ": (8.3, 2.9),
"formation": (1.6, 1.2), "esp32s3": (4.9, 1.2),
"temperature": (8.3, 1.55), "commande": (8.3, 0.85),
}
liens = [("maison", "salon"), ("maison", "cuisine"), ("salon", "temperature "), ("salon", "humidite"),
("cuisine", "temperature "), ("formation", "esp32s3"), ("esp32s3", "temperature"),
("esp32s3", "commande")]
for a, b in liens:
(x1, y1), (x2, y2) = noeuds[a], noeuds[b]
ax.plot([x1 + 1.15, (x1 + x2) / 2 + 0.3, (x1 + x2) / 2 + 0.3, x2 - 1.15], [y1, y1, y2, y2],
color=GRIS, lw=1.1)
for nom, (x, y) in noeuds.items():
c = BLEU if nom.startswith(("formation", "esp32s3")) or nom in ("temperature", "commande") else GRIS
boite(ax, x, y, 2.3, 0.5, nom.strip(), couleur=c, taille=7.2)
chemins = [(4.65, "maison/salon/temperature", GRIS), (2.9, "maison/cuisine/temperature", GRIS),
(1.55, "formation/esp32s3/temperature", BLEU), (0.85, "formation/esp32s3/commande", BLEU)]
for y, txt, c in chemins:
ax.text(9.75, y, "→ " + txt, va="center", fontsize=7.8, color=c, family="monospace")
ax.text(9.75, 3.95, "→ maison/salon/humidite", va="center", fontsize=7.8, color=GRIS, family="monospace")
return fig
FIGURES = [
Figure("m11-publish-subscribe", publish_subscribe),
Figure("m11-arborescence-topics", arborescence_topics),
]
+43
View File
@@ -0,0 +1,43 @@
"""Module 12 — Projet final."""
from outils import boite, fleche, toile
from style import BLEU, ENCRE, GRIS, ORANGE, VERT, Figure
def demarche_projet():
fig, ax = toile(16, 4.2)
etapes = [("Besoin", "quel\nproblème ?", GRIS), ("Cahier des\ncharges", "matériel,\nmodules", BLEU),
("MVP", "l'indispensable\nd'abord", BLEU), ("Tests", "sur une durée\nréaliste", ORANGE),
("Bonus", "si le temps\nle permet", VERT), ("Présentation", "démo, choix,\ndifficultés", VERT)]
xs = [1.3 + k * 2.68 for k in range(len(etapes))]
for x, (nom, sous, c) in zip(xs, etapes):
boite(ax, x, 2.6, 2.35, 1.1, nom, couleur=c, taille=7.2, gras=True)
ax.text(x, 1.85, sous, ha="center", va="top", fontsize=7, color=GRIS)
for a, b in zip(xs, xs[1:]):
fleche(ax, (a + 1.175, 2.6), (b - 1.175, 2.6))
fleche(ax, (xs[3], 3.15), (xs[2], 3.15), "corriger", couleur=ORANGE, taille=7.3, courbe=0.6)
return fig
def structure_projet():
fig, ax = toile(16, 6.0)
main = boite(ax, 8.0, 4.6, 6.2, 1.6, couleur=BLEU)
ax.text(8.0, 5.05, "MonProjet.ino", ha="center", fontsize=9.5, fontweight="bold", color=BLEU,
family="monospace")
ax.text(8.0, 4.3, "setup() et loop() uniquement :\norchestrent les appels", ha="center", va="center",
fontsize=7.8, color=ENCRE)
onglets = [(2.8, "capteurs.ino", "lireTemperature()\nlireHumidite()", VERT),
(8.0, "affichage.ino", "afficherEcran()\nafficherLed()", ORANGE),
(13.2, "reseau.ino", "connecterWiFi()\npublierMQTT()", GRIS)]
for x, nom, fonctions, c in onglets:
boite(ax, x, 1.4, 4.2, 1.9, couleur=c)
ax.text(x, 1.95, nom, ha="center", fontsize=9, fontweight="bold", color=c, family="monospace")
ax.text(x, 1.1, fonctions, ha="center", va="center", fontsize=7.8, color=ENCRE, family="monospace")
fleche(ax, (8.0 + (x - 8.0) * 0.25, main["b"][1]), (x, 2.35), couleur=c)
ax.text(15.8, 5.6, "une fonction =\nune responsabilité", ha="right", fontsize=7.8, color=GRIS, style="italic")
return fig
FIGURES = [
Figure("m12-demarche-projet", demarche_projet),
Figure("m12-structure-projet", structure_projet),
]
+38
View File
@@ -0,0 +1,38 @@
"""Synthèse de la formation (préfixe m13 : document hors numérotation des modules)."""
from outils import boite, fleche, toile
from style import BLEU, GRIS, ORANGE, ROUGE, VERT, Figure
def parcours_formation():
fig, ax = toile(16, 8.8)
colonnes = [
(1.55, "Prise\nen main", GRIS, [("0", "Installation"), ("1", "Microcontrôleur")]),
(4.775, "Entrées /\nsorties", BLEU, [("2", "GPIO & NeoPixel"), ("3", "PWM & ADC"), ("4", "Série & debug")]),
(8.0, "Bus\n& temps", ORANGE, [("5", "I2C & SPI"), ("6", "Interruptions")]),
(11.225, "Connectivité", VERT, [("7", "Wi-Fi"), ("8", "Serveur web"), ("9", "BLE"), ("11", "MQTT")]),
(14.45, "Autonomie", ROUGE, [("10", "Énergie")]),
]
centres = {}
for x, titre, c, modules in colonnes:
ax.text(x, 8.2, titre, ha="center", va="center", fontsize=8.5, fontweight="bold", color=c)
n = len(modules)
for k, (num, nom) in enumerate(modules):
y = 6.95 - k * 1.25 - (4 - n) * 0.3
boite(ax, x, y, 2.75, 1.0, f"M{num}\n{nom}", couleur=c, taille=7.1)
centres[num] = (x, y)
for a, b in (("1", "2"), ("4", "5"), ("6", "7"), ("11", "10")):
(x1, y1), (x2, y2) = centres[a], centres[b]
fleche(ax, (x1 + 1.375, y1), (x2 - 1.375, y2), couleur=GRIS)
projet = boite(ax, 8.0, 0.8, 9.6, 1.1, "M12 — Projet final : au moins 3 modules combinés", couleur=BLEU,
taille=9, gras=True)
for x, *_ in colonnes:
cible = (min(max(x, 3.6), 12.4), projet["h"][1])
fleche(ax, (x, 2.4), cible, couleur=GRIS, pointille=True)
ax.text(14.45, 4.5, "la LED NeoPixel\nsert de fil rouge\ndans tous\nles modules", ha="center", va="center",
fontsize=7.3, color=GRIS, style="italic")
return fig
FIGURES = [
Figure("m13-parcours-formation", parcours_formation),
]
Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 66 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 75 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 43 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 73 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 40 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 54 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 79 KiB