Wi-Fi et ADC2
Quand le Wi-Fi est actif, l’ADC2 de l’ESP32 devient inutilisable. C’est pour cette raison que le joystick de l’atelier est câblé sur des broches ADC1 (voir le concept ADC & PWM).
Wi-Fi, HTTP et WebSocket
Comprendre comment un ESP32 crée un réseau Wi-Fi, sert une page web et échange des messages en temps réel avec un navigateur grâce aux WebSockets.
Jusqu’ici, la console ne parlait qu’à ses propres périphériques : boutons, joystick, écran. Les bus de communication relient des composants posés à quelques centimètres les uns des autres. Pour qu’un deuxième joueur interagisse depuis son téléphone, il faut changer d’échelle : passer par le Wi-Fi, et parler la langue des navigateurs.
À la fin de ce concept, tu sauras :
L’ESP32-S3 intègre une radio Wi-Fi 2,4 GHz. Elle fonctionne dans deux modes :
| Mode | Rôle de l’ESP32 | Exemple | Avantage | Limite |
|---|---|---|---|---|
| Station (STA) | Rejoint un réseau existant, comme un téléphone | La box de la maison | Accès à Internet | Dépend du réseau (mot de passe, portail captif, ports bloqués) |
| Point d’accès (AP) | Crée son propre réseau | Un partage de connexion | Fonctionne partout, sans infrastructure | Pas d’Internet, une dizaine d’appareils au plus |
Pour une console de jeu qui doit marcher en salle de TD comme en démonstration, le point d’accès est le choix le plus sûr : la console crée le réseau Snake-Binome3, le téléphone du joueur s’y connecte, et rien ne dépend du réseau de l’établissement (souvent bloqué pour ce genre d’usage).
flowchart LR
subgraph AP["Réseau Wi-Fi créé par la console"]
C["Console XIAO\n192.168.4.1"]
T["Téléphone\n192.168.4.2"]
P["PC\n192.168.4.3"]
end
T <-->|Wi-Fi| C
P <-->|Wi-Fi| C
Sur un réseau, chaque appareil a une adresse IP (quatre nombres, par exemple 192.168.4.1). En mode point d’accès, l’ESP32 prend toujours l’adresse 192.168.4.1 et distribue les suivantes aux appareils qui le rejoignent.
Un même appareil peut faire tourner plusieurs services : chacun écoute sur un port (un numéro de 0 à 65535). Par convention, un serveur web écoute sur le port 80. L’adresse complète d’un service s’écrit donc 192.168.4.1:80, et le navigateur ajoute le :80 tout seul.
L’analogie classique : l’adresse IP est l’adresse d’un immeuble, le port est le numéro de l’appartement.
Quand tu tapes http://192.168.4.1/ dans un navigateur, il envoie une requête HTTP à la console, qui renvoie une réponse : le code de la page web (HTML, CSS, JavaScript). La page s’affiche, et la connexion se termine.
sequenceDiagram
participant N as Navigateur
participant C as Console (serveur web)
N->>C: GET / (requête)
C-->>N: 200 OK + page HTML (réponse)
Note over N,C: la connexion est fermée
HTTP fonctionne toujours dans ce sens : le navigateur demande, le serveur répond. Le serveur ne peut jamais prendre l’initiative.
Dans un jeu, la console doit prévenir le navigateur dès que quelque chose se passe : le serpent a mangé, la partie est perdue. Avec HTTP seul, le navigateur devrait redemander « du nouveau ? » plusieurs fois par seconde (on parle de polling) : chaque requête ouvre une connexion, envoie des en-têtes de plusieurs centaines d’octets, et la réponse arrive au mieux au prochain tour de questions. C’est lent, coûteux, et la console répond surtout « rien de neuf ».
Un WebSocket commence comme une requête HTTP, puis la connexion reste ouverte. Ensuite, chacun des deux côtés peut envoyer un message à tout moment, sans attendre d’être interrogé : c’est un canal bidirectionnel et temps réel, comme une liaison série, mais à travers le Wi-Fi.
sequenceDiagram
participant N as Navigateur
participant C as Console
N->>C: Ouverture du WebSocket (port 81)
C-->>N: Connexion acceptée
C->>N: SCORE:3
N->>C: FOOD:12,7
C->>N: STATE:GAMEOVER
Note over N,C: la connexion reste ouverte jusqu'à ce qu'un côté la ferme
Dans l’atelier, la console fait tourner deux serveurs :
| Serveur | Port | Rôle |
|---|---|---|
| Serveur web (HTTP) | 80 | Envoie la page du joueur, une seule fois, à l’ouverture |
| Serveur WebSocket | 81 | Échange les messages du jeu pendant toute la partie |
Le JavaScript de la page ouvre le WebSocket dès qu’elle est chargée : new WebSocket("ws://192.168.4.1:81/").
Un WebSocket transporte des messages, mais ne dit rien de leur contenu : c’est à toi de définir un protocole, c’est-à-dire les messages possibles, leur format et leur sens. C’est la même démarche que pour l’UART (format 8N1), un cran au-dessus.
Le plus simple est un format texte court, TYPE:valeurs :
| Sens | Message | Sens du message |
|---|---|---|
| Console → navigateur | STATE:PLAYING |
La phase de jeu a changé |
| Console → navigateur | SCORE:12 |
Nouveau score |
| Navigateur → console | FOOD:12,7 |
Le joueur web demande une pomme en case (12, 7) |
Quelques règles évitent la plupart des problèmes :
Le serveur web et le serveur WebSocket ont besoin qu’on leur donne la main régulièrement pour traiter ce qui arrive. On appelle donc leurs fonctions de service à chaque tour de loop(), en dehors de la cadence des images :
void loop() {
server.handleClient(); // HTTP : sert la page si quelqu'un la demande
webSocket.loop(); // WebSocket : reçoit les messages, gère les connexions
// ... puis la game loop habituelle, cadencée par millis()
}
La règle vue dans Du matériel au logiciel n’a jamais été aussi vraie : aucun delay(). Pendant une attente bloquante, les messages s’accumulent, le navigateur croit la console figée, et la connexion finit par tomber.
Les messages reçus arrivent par une fonction de rappel (callback) : une fonction que tu écris, que la bibliothèque appelle à chaque événement (connexion, déconnexion, message). Comme pour une interruption, on y fait le minimum : mémoriser la commande reçue, que la game loop traitera à son prochain tour.
Un joueur peut fermer son navigateur, sortir de la portée du Wi-Fi ou voir son téléphone se mettre en veille. Le jeu doit le prévoir au lieu de le subir, et c’est un travail pour la machine à états :
stateDiagram-v2
[*] --> AttenteJoueur
AttenteJoueur --> Partie : navigateur connecté + START
Partie --> Pause : navigateur déconnecté
Pause --> Partie : navigateur reconnecté
Pause --> AttenteJoueur : délai dépassé
Partie --> FinPartie : serpent mort
FinPartie --> AttenteJoueur : START
L’événement « navigateur déconnecté » est une transition comme une autre. Ce qu’elle déclenche (pause, partie continuée contre l’ordinateur, fin de partie) est un choix de conception, à décider et à documenter.
1. Pourquoi créer un point d’accès plutôt que rejoindre le Wi-Fi de l’école ?
2. Pourquoi HTTP seul est-il mal adapté à un jeu en temps réel ?
3. Le navigateur envoie FOOD:40,2 alors que la grille fait 24 × 30 cases. Que doit faire la console ?
192.168.4.1) : idéal pour une console autonome.loop(), sans delay(), et la déconnexion est une transition de la FSM.