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>
@@ -44,3 +44,6 @@
|
|||||||
|
|
||||||
// Texte des tableaux aligné à gauche (la justification crée des blancs dans les colonnes étroites)
|
// Texte des tableaux aligné à gauche (la justification crée des blancs dans les colonnes étroites)
|
||||||
#show table: set par(justify: false)
|
#show table: set par(justify: false)
|
||||||
|
|
||||||
|
// Figures : pleine largeur par défaut (une largeur explicite dans le .qmd reste prioritaire)
|
||||||
|
#set image(width: 100%)
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ title: "Module 7 — Wi-Fi : station, point d'accès et client HTTP"
|
|||||||
| `WIFI_AP` (Point d'accès) | La carte crée son propre réseau Wi-Fi | Configuration sans routeur, projet autonome |
|
| `WIFI_AP` (Point d'accès) | La carte crée son propre réseau Wi-Fi | Configuration sans routeur, projet autonome |
|
||||||
| `WIFI_AP_STA` | Les deux simultanément | Portail de configuration puis bascule en client |
|
| `WIFI_AP_STA` | Les deux simultanément | Portail de configuration puis bascule en client |
|
||||||
|
|
||||||
|
{#fig-m07-modes-wifi}
|
||||||
|
|
||||||
## 2. Mode station : se connecter à un réseau existant
|
## 2. Mode station : se connecter à un réseau existant
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
@@ -74,6 +76,8 @@ void loop() {
|
|||||||
|
|
||||||
Une fois connectée en mode station, la carte peut effectuer des requêtes HTTP vers des services web, par exemple pour récupérer la météo actuelle.
|
Une fois connectée en mode station, la carte peut effectuer des requêtes HTTP vers des services web, par exemple pour récupérer la météo actuelle.
|
||||||
|
|
||||||
|
{#fig-m07-requete-http}
|
||||||
|
|
||||||
### 4.1 Bibliothèque nécessaire
|
### 4.1 Bibliothèque nécessaire
|
||||||
|
|
||||||
HTTPClient est incluse nativement dans le core Arduino-ESP32. Pour analyser une réponse JSON, installez la bibliothèque « ArduinoJson » (Benoit Blanchon) depuis le gestionnaire de bibliothèques.
|
HTTPClient est incluse nativement dans le core Arduino-ESP32. Pour analyser une réponse JSON, installez la bibliothèque « ArduinoJson » (Benoit Blanchon) depuis le gestionnaire de bibliothèques.
|
||||||
|
|||||||
@@ -55,6 +55,8 @@ void loop() {
|
|||||||
|
|
||||||
Construisons une page avec des boutons permettant de changer la couleur de la LED NeoPixel intégrée, directement depuis un navigateur.
|
Construisons une page avec des boutons permettant de changer la couleur de la LED NeoPixel intégrée, directement depuis un navigateur.
|
||||||
|
|
||||||
|
{#fig-m08-echanges-serveur}
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
#include <WebServer.h>
|
#include <WebServer.h>
|
||||||
@@ -107,6 +109,8 @@ Le mot-clé PROGMEM place la chaîne de caractères HTML directement en mémoire
|
|||||||
|
|
||||||
Le modèle utilisé ci-dessus (rechargement de page à chaque clic) fonctionne mais reste rudimentaire. Le WebSocket établit une connexion persistante entre le navigateur et la carte, permettant un échange de données en temps réel sans recharger la page — idéal pour afficher en continu une mesure de capteur ou réagir instantanément à une action utilisateur.
|
Le modèle utilisé ci-dessus (rechargement de page à chaque clic) fonctionne mais reste rudimentaire. Le WebSocket établit une connexion persistante entre le navigateur et la carte, permettant un échange de données en temps réel sans recharger la page — idéal pour afficher en continu une mesure de capteur ou réagir instantanément à une action utilisateur.
|
||||||
|
|
||||||
|
{#fig-m08-http-vs-websocket}
|
||||||
|
|
||||||
Cette technique nécessite la bibliothèque tierce « ESPAsyncWebServer » couplée à « AsyncTCP », plus avancée que WebServer.h. Elle sera abordée en détail dans le cadre du projet final (Module 12) si le projet choisi le nécessite (ex : dashboard de supervision en direct).
|
Cette technique nécessite la bibliothèque tierce « ESPAsyncWebServer » couplée à « AsyncTCP », plus avancée que WebServer.h. Elle sera abordée en détail dans le cadre du projet final (Module 12) si le projet choisi le nécessite (ex : dashboard de supervision en direct).
|
||||||
|
|
||||||
## 5. Exercice : dashboard de supervision
|
## 5. Exercice : dashboard de supervision
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ Contrairement à l'ESP32 « original », l'ESP32-S3 ne supporte pas le Bluetooth
|
|||||||
| Caractéristique | Une donnée précise à l'intérieur d'un service (ex : une couleur, une température), également identifiée par un UUID |
|
| Caractéristique | Une donnée précise à l'intérieur d'un service (ex : une couleur, une température), également identifiée par un UUID |
|
||||||
| Notification | Mécanisme permettant au serveur d'envoyer une mise à jour au client sans que celui-ci ait à demander (poll) |
|
| Notification | Mécanisme permettant au serveur d'envoyer une mise à jour au client sans que celui-ci ait à demander (poll) |
|
||||||
|
|
||||||
|
{#fig-m09-hierarchie-gatt}
|
||||||
|
|
||||||
::: {.callout-note title="Qu'est-ce qu'un UUID ?"}
|
::: {.callout-note title="Qu'est-ce qu'un UUID ?"}
|
||||||
Un UUID (Universally Unique Identifier) est un identifiant sur 128 bits garantissant qu'aucun autre service ou caractéristique dans le monde n'utilisera accidentellement le même identifiant. Pour vos projets personnels, générez vos propres UUID aléatoires via un générateur en ligne plutôt que de réutiliser des exemples trouvés sur Internet.
|
Un UUID (Universally Unique Identifier) est un identifiant sur 128 bits garantissant qu'aucun autre service ou caractéristique dans le monde n'utilisera accidentellement le même identifiant. Pour vos projets personnels, générez vos propres UUID aléatoires via un générateur en ligne plutôt que de réutiliser des exemples trouvés sur Internet.
|
||||||
:::
|
:::
|
||||||
@@ -137,6 +139,8 @@ Sans développer d'application dédiée, on peut tester un serveur BLE avec une
|
|||||||
|
|
||||||
À l'inverse de l'écriture (client → serveur), les notifications permettent au serveur d'envoyer spontanément une donnée au client sans attendre une requête (par exemple, la valeur d'un capteur mise à jour toutes les secondes).
|
À l'inverse de l'écriture (client → serveur), les notifications permettent au serveur d'envoyer spontanément une donnée au client sans attendre une requête (par exemple, la valeur d'un capteur mise à jour toutes les secondes).
|
||||||
|
|
||||||
|
{#fig-m09-sequence-ble width=85%}
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
// A ajouter a la caracteristique lors de sa creation :
|
// A ajouter a la caracteristique lors de sa creation :
|
||||||
// BLECharacteristic::PROPERTY_NOTIFY en plus de READ/WRITE
|
// BLECharacteristic::PROPERTY_NOTIFY en plus de READ/WRITE
|
||||||
|
|||||||
@@ -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,
|
def fleche(ax, depart, arrivee, texte="", couleur=ENCRE, taille=8, double=False, pointille=False,
|
||||||
cote="haut", decalage=0.18, courbe=0):
|
cote="haut", decalage=0.18, courbe=0, fond=False):
|
||||||
"""Flèche entre deux points, avec étiquette optionnelle au milieu."""
|
"""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,
|
ax.annotate("", xy=arrivee, xytext=depart,
|
||||||
arrowprops=dict(arrowstyle="<|-|>" if double else "-|>", color=couleur, lw=1.3,
|
arrowprops=dict(arrowstyle="<|-|>" if double else "-|>", color=couleur, lw=1.3,
|
||||||
linestyle="--" if pointille else "-", shrinkA=0, shrinkB=0,
|
linestyle="--" if pointille else "-", shrinkA=0, shrinkB=0,
|
||||||
mutation_scale=11, connectionstyle=f"arc3,rad={courbe}"))
|
mutation_scale=11, connectionstyle=f"arc3,rad={courbe}"))
|
||||||
if texte:
|
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
|
mx, my = (depart[0] + arrivee[0]) / 2, (depart[1] + arrivee[1]) / 2
|
||||||
vertical = abs(arrivee[0] - depart[0]) < abs(arrivee[1] - depart[1])
|
vertical = abs(arrivee[0] - depart[0]) < abs(arrivee[1] - depart[1])
|
||||||
if vertical:
|
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:
|
else:
|
||||||
dy = decalage if cote == "haut" else -decalage
|
dy = decalage if cote == "haut" else -decalage
|
||||||
ax.text(mx, my + dy, texte, ha="center", va="bottom" if cote == "haut" else "top",
|
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="",
|
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
|
haut = hauteur - 0.6
|
||||||
for a, x in xs.items():
|
for a, x in xs.items():
|
||||||
c = (couleurs or {}).get(a, BLEU)
|
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)
|
largeur = min(largeur_cm / len(acteurs) - 0.3, 0.2 * len(a) + 0.9)
|
||||||
ax.plot([x, x], [0.3, haut - 0.4], color=GRIS, lw=1, ls=(0, (3, 3)))
|
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):
|
for k, msg in enumerate(messages):
|
||||||
src, dst, texte = msg[:3]
|
src, dst, texte = msg[:3]
|
||||||
pointille = len(msg) > 3 and msg[3]
|
pointille = len(msg) > 3 and msg[3]
|
||||||
y = haut - 1.2 - k * pas_cm
|
y = haut - 1.2 - k * pas_cm
|
||||||
if src == dst:
|
if src == dst:
|
||||||
ax.annotate("", xy=(xs[src] + 0.05, y - 0.3), xytext=(xs[src] + 0.05, y),
|
# Boucle vers soi-même : petit rectangle ouvert à droite de la ligne de vie
|
||||||
arrowprops=dict(arrowstyle="-|>", color=ENCRE, lw=1.1,
|
x = xs[src]
|
||||||
connectionstyle="arc,angleA=0,angleB=0,armA=18,armB=18,rad=4",
|
ax.plot([x, x + 0.45, x + 0.45], [y + 0.12, y + 0.12, y - 0.22], color=ENCRE, lw=1.1)
|
||||||
mutation_scale=9))
|
fleche(ax, (x + 0.45, y - 0.22), (x + 0.02, y - 0.22))
|
||||||
ax.text(xs[src] + 0.75, y - 0.15, texte, fontsize=7.8, va="center", color=ENCRE)
|
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
|
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:
|
for k, acteur, texte in notes:
|
||||||
y = haut - 1.2 - k * pas_cm
|
y = haut - 1.2 - k * pas_cm
|
||||||
ax.text(xs[acteur] + 0.2, y, texte, fontsize=7.5, color=GRIS, style="italic", va="center")
|
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 |