Illustre les modules 7 à 9 (6 figures)

M7 : modes Wi-Fi, requête HTTP. M8 : échanges navigateur/serveur, HTTP vs WebSocket. M9 : modèle GATT, session BLE. Diagrammes de séquence plus lisibles (étiquettes sur fond blanc, boîtes à la largeur du nom). Gabarit : figures pleine largeur par défaut.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Artemia
2026-10-18 15:20:12 +02:00
co-authored by Claude Opus 5.5
parent 372c5a3e1e
commit f4a80f4d5b
20 changed files with 13937 additions and 12 deletions
+54
View File
@@ -0,0 +1,54 @@
"""Module 7 — Wi-Fi : station, point d'accès et client HTTP."""
from outils import boite, fleche, sequence, toile
from style import BLEU, ENCRE, GRIS, ORANGE, VERT, Figure
def modes_wifi():
fig, ax = toile(16, 6.4)
panneaux = [(2.7, "WIFI_STA\nstation"), (8.0, "WIFI_AP\npoint d'accès"), (13.3, "WIFI_AP_STA\nles deux")]
for x, titre in panneaux:
boite(ax, x, 3.1, 5.0, 5.6, couleur=GRIS, fond="#fafafa")
ax.text(x, 5.4, titre, ha="center", va="center", fontsize=8.5, fontweight="bold", color=ENCRE)
# STA
box = boite(ax, 2.7, 3.9, 2.2, 0.8, "Box / routeur", couleur=GRIS, taille=8)
esp = boite(ax, 1.5, 2.0, 2.0, 0.8, "ESP32-S3", couleur=BLEU, taille=8, gras=True)
tel = boite(ax, 3.9, 2.0, 1.8, 0.8, "PC / mobile", couleur=GRIS, taille=8)
fleche(ax, esp["h"], (2.3, box["b"][1]), double=True, couleur=BLEU)
fleche(ax, tel["h"], (3.1, box["b"][1]), double=True, couleur=GRIS)
ax.text(2.7, 4.5, "↑ Internet", ha="center", fontsize=7.5, color=GRIS)
ax.text(2.7, 0.7, "IP donnée par la box\nWiFi.localIP()", ha="center", fontsize=7.2, color=BLEU)
# AP
esp2 = boite(ax, 8.0, 3.9, 2.2, 0.9, "ESP32-S3\n192.168.4.1", couleur=BLEU, taille=8, gras=True)
for dx in (-1.3, 1.3):
t = boite(ax, 8.0 + dx, 2.0, 1.8, 0.8, "mobile", couleur=GRIS, taille=8)
fleche(ax, t["h"], (8.0 + dx * 0.4, esp2["b"][1]), double=True, couleur=ORANGE)
ax.text(8.0, 0.7, "réseau « ESP32-Formation »\nsans box ni Internet", ha="center", fontsize=7.2, color=ORANGE)
# AP_STA
box3 = boite(ax, 13.3, 4.3, 2.2, 0.7, "Box / routeur", couleur=GRIS, taille=8)
esp3 = boite(ax, 13.3, 2.95, 2.2, 0.8, "ESP32-S3", couleur=BLEU, taille=8, gras=True)
t3 = boite(ax, 13.3, 1.5, 1.8, 0.7, "mobile", couleur=GRIS, taille=8)
fleche(ax, esp3["h"], box3["b"], "client", double=True, couleur=BLEU, taille=7)
fleche(ax, t3["h"], esp3["b"], "point d'accès", double=True, couleur=ORANGE, taille=7)
ax.text(13.3, 0.7, "ex. portail de configuration", ha="center", fontsize=7.2, color=GRIS)
return fig
def requete_http():
acteurs = ["ESP32-S3", "Box Wi-Fi", "api.open-meteo.com"]
messages = [
("ESP32-S3", "Box Wi-Fi", "WiFi.begin(ssid, motDePasse)"),
("Box Wi-Fi", "ESP32-S3", "connecté : adresse IP attribuée", True),
("ESP32-S3", "api.open-meteo.com", "http.GET() → GET /v1/forecast?latitude=…"),
("api.open-meteo.com", "ESP32-S3", "200 OK + {\"current_weather\": {\"temperature\": 14.2, …}}", True),
("ESP32-S3", "ESP32-S3", "deserializeJson() puis affichage OLED"),
("ESP32-S3", "ESP32-S3", "http.end()"),
]
fig, ax = sequence(acteurs, messages, largeur_cm=16, pas_cm=0.95,
couleurs={"Box Wi-Fi": GRIS, "api.open-meteo.com": VERT})
return fig
FIGURES = [
Figure("m07-modes-wifi", modes_wifi),
Figure("m07-requete-http", requete_http),
]
+58
View File
@@ -0,0 +1,58 @@
"""Module 8 — Serveur web embarqué."""
from matplotlib.patches import Rectangle
from outils import boite, fleche, sequence, toile
from style import BLEU, GRIS, ORANGE, ROUGE, VERT, Figure
def echanges_serveur():
acteurs = ["Navigateur (mobile)", "ESP32-S3 — WebServer", "LED NeoPixel"]
messages = [
("Navigateur (mobile)", "ESP32-S3 — WebServer", "GET / (saisie de http://192.168.1.42)"),
("ESP32-S3 — WebServer", "Navigateur (mobile)", "200 OK + page HTML (4 boutons)", True),
("Navigateur (mobile)", "ESP32-S3 — WebServer", "clic « Rouge » → GET /rouge"),
("ESP32-S3 — WebServer", "LED NeoPixel", "changerCouleur(255, 0, 0)"),
("ESP32-S3 — WebServer", "Navigateur (mobile)", "200 OK + page HTML (rechargée)", True),
]
fig, ax = sequence(acteurs, messages, largeur_cm=16, pas_cm=0.95,
couleurs={"Navigateur (mobile)": GRIS, "LED NeoPixel": ROUGE})
ax.text(8.0, 0.15, "serveur.on(\"/rouge\", …) associe chaque URL (route) à une fonction ; "
"handleClient() traite les requêtes dans loop()", ha="center", fontsize=7.2, color=GRIS, style="italic")
return fig
def http_vs_websocket():
fig, ax = toile(16, 7.2)
for x0, titre, c in ((0.3, "HTTP classique", ORANGE), (8.3, "WebSocket", VERT)):
ax.text(x0 + 3.7, 6.85, titre, ha="center", fontsize=10, fontweight="bold", color=c)
for dx, nom in ((0.9, "Navigateur"), (6.5, "ESP32-S3")):
boite(ax, x0 + dx, 6.2, 1.9, 0.6, nom, couleur=GRIS if nom == "Navigateur" else BLEU, taille=8)
ax.plot([x0 + dx] * 2, [0.85, 5.85], color=GRIS, lw=1, ls=(0, (3, 3)))
# HTTP : une requête par échange
g, d = 1.2, 6.8
y = 5.4
for req, rep in (("GET /mesure", "200 OK « 21,5 °C »"), ("GET /mesure", "200 OK « 21,6 °C »"),
("GET /mesure", "200 OK « 21,6 °C »")):
fleche(ax, (g, y), (d, y), req, taille=7.3, decalage=0.05)
fleche(ax, (d, y - 0.55), (g, y - 0.55), rep, taille=7.3, pointille=True, decalage=0.05)
y -= 1.5
ax.text(4.0, 0.15, "le navigateur doit redemander\n(nouvelle requête à chaque fois)", ha="center",
fontsize=7.3, color=ORANGE)
# WebSocket : une connexion persistante
g, d = 9.2, 14.8
fleche(ax, (g, 5.4), (d, 5.4), "ouverture (handshake)", taille=7.3, decalage=0.05)
fleche(ax, (d, 4.85), (g, 4.85), "connexion acceptée", taille=7.3, pointille=True, decalage=0.05)
ax.add_patch(Rectangle((g, 0.75), d - g, 3.75, color=VERT, alpha=0.08))
for k, (sens, txt) in enumerate(((1, "21,5 °C"), (1, "21,6 °C"), (-1, "« rouge »"), (1, "21,7 °C"), (1, "21,7 °C"))):
yy = 4.1 - k * 0.7
a, b = (d, g) if sens == 1 else (g, d)
fleche(ax, (a, yy), (b, yy), txt, taille=7.3, couleur=VERT if sens == 1 else ROUGE, decalage=0.05)
ax.text(12.0, 0.15, "connexion ouverte en permanence :\nla carte envoie dès qu'une valeur change",
ha="center", fontsize=7.3, color=VERT)
return fig
FIGURES = [
Figure("m08-echanges-serveur", echanges_serveur),
Figure("m08-http-vs-websocket", http_vs_websocket),
]
+48
View File
@@ -0,0 +1,48 @@
"""Module 9 — Bluetooth Low Energy (BLE)."""
from outils import boite, fleche, sequence, toile
from style import BLEU, ENCRE, GRIS, ORANGE, VERT, Figure
def hierarchie_gatt():
fig, ax = toile(16, 7.0)
boite(ax, 5.6, 3.5, 10.8, 6.6, couleur=BLEU, fond="#f5f8fe")
ax.text(0.5, 6.4, "Serveur BLE — l'ESP32-S3", fontsize=9.5, fontweight="bold", color=BLEU)
ax.text(0.5, 6.0, 'BLEDevice::init("ESP32-S3-Formation")', fontsize=7.5, color=GRIS, family="monospace")
boite(ax, 5.6, 3.15, 10.0, 5.1, couleur=ORANGE)
ax.text(0.9, 5.25, "Service", fontsize=9, fontweight="bold", color=ORANGE)
ax.text(0.9, 4.9, "UUID 4fafc201-1fb5-459e-8fcc-c5c9c331914b", fontsize=7.5, color=ENCRE, family="monospace")
boite(ax, 5.6, 2.55, 9.2, 3.1, couleur=VERT)
ax.text(1.3, 3.75, "Caractéristique", fontsize=9, fontweight="bold", color=VERT)
ax.text(1.3, 3.4, "UUID beb5483e-36e1-4688-b7f5-ea07361b26a8", fontsize=7.5, color=ENCRE, family="monospace")
for i, (p, c) in enumerate((("READ", BLEU), ("WRITE", ORANGE), ("NOTIFY", VERT))):
boite(ax, 2.0 + i * 1.75, 2.6, 1.5, 0.55, p, couleur=c, taille=7.5, gras=True)
boite(ax, 8.4, 2.6, 2.6, 0.8, 'valeur : "rouge"', couleur=GRIS, taille=8)
ax.text(5.6, 1.4, "(un service peut contenir plusieurs caractéristiques)", ha="center",
fontsize=7.3, color=GRIS, style="italic")
boite(ax, 14.1, 3.5, 2.8, 2.0, "Client BLE\nsmartphone\n(nRF Connect)", couleur=GRIS, taille=8.5)
fleche(ax, (12.7, 3.0), (11.0, 3.0), "écrit", couleur=ORANGE, taille=7.5, decalage=0.08)
fleche(ax, (11.0, 2.2), (12.7, 2.2), "notifie", couleur=VERT, taille=7.5, decalage=0.08)
fleche(ax, (12.7, 4.1), (11.0, 4.1), "lit", couleur=BLEU, taille=7.5, decalage=0.08)
return fig
def sequence_ble():
acteurs = ["ESP32-S3 (serveur)", "Smartphone (client)"]
messages = [
("ESP32-S3 (serveur)", "Smartphone (client)", "annonce (advertising) « ESP32-S3-Formation »", True),
("Smartphone (client)", "ESP32-S3 (serveur)", "connexion"),
("Smartphone (client)", "ESP32-S3 (serveur)", "découverte des services et caractéristiques"),
("Smartphone (client)", "ESP32-S3 (serveur)", "WRITE « rouge »"),
("ESP32-S3 (serveur)", "ESP32-S3 (serveur)", "onWrite() → LED rouge"),
("ESP32-S3 (serveur)", "Smartphone (client)", "NOTIFY « 21.5 » (température)", True),
("ESP32-S3 (serveur)", "Smartphone (client)", "NOTIFY « 21.6 » … toutes les 5 s", True),
]
fig, ax = sequence(acteurs, messages, largeur_cm=15, pas_cm=0.85,
couleurs={"Smartphone (client)": GRIS})
return fig
FIGURES = [
Figure("m09-hierarchie-gatt", hierarchie_gatt),
Figure("m09-sequence-ble", sequence_ble),
]
+20 -12
View File
@@ -34,21 +34,26 @@ def boite(ax, x, y, l, h, texte="", couleur=BLEU, taille=9, gras=False, fond=Non
def fleche(ax, depart, arrivee, texte="", couleur=ENCRE, taille=8, double=False, pointille=False,
cote="haut", decalage=0.18, courbe=0):
"""Flèche entre deux points, avec étiquette optionnelle au milieu."""
cote="haut", decalage=0.18, courbe=0, fond=False):
"""Flèche entre deux points, avec étiquette optionnelle au milieu.
fond=True place l'étiquette sur fond blanc (utile quand elle croise des traits).
"""
ax.annotate("", xy=arrivee, xytext=depart,
arrowprops=dict(arrowstyle="<|-|>" if double else "-|>", color=couleur, lw=1.3,
linestyle="--" if pointille else "-", shrinkA=0, shrinkB=0,
mutation_scale=11, connectionstyle=f"arc3,rad={courbe}"))
if texte:
cadre = dict(boxstyle="square,pad=0.1", fc="white", ec="none") if fond else None
mx, my = (depart[0] + arrivee[0]) / 2, (depart[1] + arrivee[1]) / 2
vertical = abs(arrivee[0] - depart[0]) < abs(arrivee[1] - depart[1])
if vertical:
ax.text(mx + decalage, my, texte, ha="left", va="center", fontsize=taille, color=couleur)
ax.text(mx + decalage, my, texte, ha="left", va="center", fontsize=taille, color=couleur,
bbox=cadre)
else:
dy = decalage if cote == "haut" else -decalage
ax.text(mx, my + dy, texte, ha="center", va="bottom" if cote == "haut" else "top",
fontsize=taille, color=couleur)
fontsize=taille, color=couleur, bbox=cadre)
def chronogramme(voies, duree, largeur_cm=16, hauteur_voie_cm=1.5, graduations=None, unite="",
@@ -98,20 +103,23 @@ def sequence(acteurs, messages, largeur_cm=16, pas_cm=0.85, couleurs=None, notes
haut = hauteur - 0.6
for a, x in xs.items():
c = (couleurs or {}).get(a, BLEU)
boite(ax, x, haut, min(3.6, largeur_cm / len(acteurs) - 0.4), 0.8, a, couleur=c, taille=8.5, gras=True)
ax.plot([x, x], [0.3, haut - 0.4], color=GRIS, lw=1, ls=(0, (3, 3)))
largeur = min(largeur_cm / len(acteurs) - 0.3, 0.2 * len(a) + 0.9)
boite(ax, x, haut, largeur, 0.8, a, couleur=c, taille=8.5, gras=True)
ax.plot([x, x], [0.55, haut - 0.4], color=GRIS, lw=1, ls=(0, (3, 3)), zorder=0)
for k, msg in enumerate(messages):
src, dst, texte = msg[:3]
pointille = len(msg) > 3 and msg[3]
y = haut - 1.2 - k * pas_cm
if src == dst:
ax.annotate("", xy=(xs[src] + 0.05, y - 0.3), xytext=(xs[src] + 0.05, y),
arrowprops=dict(arrowstyle="-|>", color=ENCRE, lw=1.1,
connectionstyle="arc,angleA=0,angleB=0,armA=18,armB=18,rad=4",
mutation_scale=9))
ax.text(xs[src] + 0.75, y - 0.15, texte, fontsize=7.8, va="center", color=ENCRE)
# Boucle vers soi-même : petit rectangle ouvert à droite de la ligne de vie
x = xs[src]
ax.plot([x, x + 0.45, x + 0.45], [y + 0.12, y + 0.12, y - 0.22], color=ENCRE, lw=1.1)
fleche(ax, (x + 0.45, y - 0.22), (x + 0.02, y - 0.22))
ax.text(x + 0.6, y - 0.05, texte, fontsize=7.8, va="center", color=ENCRE,
bbox=dict(boxstyle="square,pad=0.1", fc="white", ec="none"))
continue
fleche(ax, (xs[src], y), (xs[dst], y), texte, pointille=pointille, taille=7.8, decalage=0.08)
fleche(ax, (xs[src], y), (xs[dst], y), texte, pointille=pointille, taille=7.8, decalage=0.08,
fond=True)
for k, acteur, texte in notes:
y = haut - 1.2 - k * pas_cm
ax.text(xs[acteur] + 0.2, y, texte, fontsize=7.5, color=GRIS, style="italic", va="center")
Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 60 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 52 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 71 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 64 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: 50 KiB