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>
@@ -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),
|
||||
]
|
||||
@@ -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),
|
||||
]
|
||||
@@ -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),
|
||||
]
|
||||
@@ -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")
|
||||
|
||||
|
After Width: | Height: | Size: 73 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 53 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 71 KiB |
|
After Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 75 KiB |
|
After Width: | Height: | Size: 50 KiB |