Réseau — Wi-Fi, mDNS, OSC, ESP-NOW#
Le réseau de l'école isole souvent les appareils entre eux : partagez plutôt une connexion depuis un téléphone, ou passez l'ESP32 en point d'accès. Le Wi-Fi de l'ESP32 est en 2,4 GHz uniquement.
Wi-Fi client, événements et mDNS#
#include <Arduino.h>
#include <WiFi.h>
#include <ESPmDNS.h>
const char* SSID = "MySSID";
const char* PASS = "Enjmin666";
const char* HOSTNAME = "enjmin-01"; // → joignable en http://enjmin-01.local
// Les événements Wi-Fi arrivent dans une tâche système : on garde le traitement court
void onWiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) {
switch (event) {
case ARDUINO_EVENT_WIFI_STA_GOT_IP:
Serial.printf("Connecté : %s (signal %d dBm)\n", WiFi.localIP().toString().c_str(), WiFi.RSSI());
break;
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
Serial.printf("Déconnecté (raison %d), reconnexion automatique…\n", info.wifi_sta_disconnected.reason);
break;
default:
break;
}
}
void setup() {
Serial.begin(115200);
WiFi.onEvent(onWiFiEvent);
WiFi.mode(WIFI_STA);
WiFi.setHostname(HOSTNAME);
WiFi.setAutoReconnect(true);
WiFi.begin(SSID, PASS);
// On attend au plus 10 s, sans bloquer pour toujours
uint32_t start = millis();
while (WiFi.status() != WL_CONNECTED && millis() - start < 10000) delay(100);
if (MDNS.begin(HOSTNAME)) {
MDNS.addService("http", "tcp", 80); // annonce un service (utile avec un serveur web)
}
}
void loop() {
// le reste du programme tourne, connecté ou non
delay(1000);
}
WiFi.onEvent: connexion, perte de connexion, IP obtenue… sans boucle d'attente. La pile Wi-Fi tourne sur le cœur 0, votreloop()sur le cœur 1.- mDNS : l'ESP32 est joignable par son nom (
enjmin-01.local) au lieu d'une IP qui change. Fonctionne directement sur macOS, Linux, iOS, et Windows 10+ ; pas toujours sur Android. WiFi.setSleep(false)réduit la latence (au prix de la consommation) si les messages arrivent par à-coups.
Point d'accès#
#include <Arduino.h>
#include <WiFi.h>
void setup() {
Serial.begin(115200);
// L'ESP32 crée son propre réseau (pas besoin de routeur)
WiFi.mode(WIFI_AP);
WiFi.softAP("ESP32-Enjmin-01", "motdepasse"); // mot de passe : 8 caractères minimum
Serial.printf("Réseau créé, IP de l'ESP32 : %s\n", WiFi.softAPIP().toString().c_str()); // 192.168.4.1
}
void loop() {
Serial.printf("Appareils connectés : %d\n", WiFi.softAPgetStationNum());
delay(2000);
}
WiFi.mode(WIFI_AP_STA) permet d'être à la fois point d'accès et client.
Choisir le réseau sans recompiler : WiFiManager#
Mettre le SSID et le mot de passe dans le code oblige à recompiler à chaque changement de lieu. Avec WiFiManager (lib_deps = tzapu/WiFiManager), l'ESP32 retient le dernier réseau ; s'il ne le trouve pas, il crée un point d'accès avec une page de configuration :
#include <Arduino.h>
#include <WiFiManager.h>
void setup() {
Serial.begin(115200);
WiFiManager wm;
// wm.resetSettings(); // décommenter pour oublier le réseau enregistré
// Ajout d'un champ personnalisé dans la page de configuration
WiFiManagerParameter godotIp("godot", "IP de l'ordinateur Godot", "172.20.10.3", 16);
wm.addParameter(&godotIp);
wm.setConfigPortalTimeout(180); // au bout de 3 min sans configuration : on continue sans Wi-Fi
// Si aucun réseau connu n'est joignable, l'ESP32 crée le point d'accès « ESP32-Setup » :
// s'y connecter avec un téléphone ouvre automatiquement la page de choix du réseau (portail captif)
bool connected = wm.autoConnect("ESP32-Setup", "Enjmin666");
if (connected) {
Serial.printf("Connecté à %s, IP %s\n", WiFi.SSID().c_str(), WiFi.localIP().toString().c_str());
Serial.printf("IP Godot saisie : %s\n", godotIp.getValue()); // à enregistrer avec Preferences
} else {
Serial.println("Pas de réseau configuré");
}
}
void loop() {}
- Au premier démarrage : connectez un téléphone au réseau ESP32-Setup ; la page de choix s'ouvre toute seule (sinon, ouvrez
192.168.4.1). - Le réseau choisi est enregistré dans la NVS : les démarrages suivants se connectent directement.
- Les champs personnalisés (
WiFiManagerParameter) permettent de saisir l'IP de l'ordinateur Godot, un nom d'appareil… à enregistrer ensuite avec Preferences. - Prévoyez un moyen de revenir à la configuration (bouton maintenu au démarrage →
wm.resetSettings()ouwm.startConfigPortal()).
UDP avec l'ESP32#
#include <Arduino.h>
#include "AsyncUDP.h"
#include "WiFi.h"
const char* ssid = "WIFI_NAME";
const char* pass = "WIFI_PASSWORD";
const int relay = 23;
AsyncUDP udp;
void setup() {
Serial.begin(115200);
pinMode(relay, OUTPUT);
WiFi.disconnect(true);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, pass);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
if (udp.listen(1234)) {
Serial.print("WiFi connected. UDP Listening on IP: ");
Serial.println(WiFi.localIP());
udp.onPacket([](AsyncUDPPacket packet) {
Serial.print("UDP Packet Type: ");
Serial.print(packet.isBroadcast() ? "Broadcast"
: packet.isMulticast() ? "Multicast"
: "Unicast");
Serial.print(", From: ");
Serial.print(packet.remoteIP());
Serial.print(":");
Serial.print(packet.remotePort());
Serial.print(", To: ");
Serial.print(packet.localIP());
Serial.print(":");
Serial.print(packet.localPort());
Serial.print(", Length: ");
Serial.print(packet.length()); // dlzka packetu
Serial.print(", Data: ");
Serial.write(packet.data(), packet.length());
Serial.println();
String myString = (const char*)packet.data();
if (myString.startsWith("ON")) {
Serial.println("Turning on relay");
digitalWrite(relay, LOW);
} else if (myString.startsWith("OFF")) {
Serial.println("Turning off relay");
digitalWrite(relay, HIGH);
}
packet.printf("Got %u bytes of data", packet.length());
});
}
}
void loop() {
delay(1000);
udp.broadcast("Anyone here?");
}
OSC avec Godot#
On utilise ArduinoOSC côté ESP32 (lib_deps = hideakitai/ArduinoOSC).
| envoyer un message | OscWiFi.send(ip, port, "/address", v1, v2…) |
| envoyer une variable en continu | OscWiFi.publish(ip, port, "/address", variable)->setFrameRate(hz) |
| recevoir | OscWiFi.subscribe(port, "/address", [](const OscMessage& m) { m.arg<int>(0); … }) |
| faire tourner | OscWiFi.update() à chaque tour de loop() |
Côté ESP32#
Montage : potentiomètre sur GPIO 4, bouton sur GPIO 6.
Ouvrir ce montage dans Plot Circuits ↗
#include <Arduino.h>
#include <WiFi.h>
#include <ArduinoOSCWiFi.h>
const char* SSID = "MySSID";
const char* PASS = "Enjmin666";
const char* COMPUTER_IP = "172.20.10.3"; // l'ordinateur qui fait tourner Godot
const int PORT_GODOT = 9999; // Godot écoute ici
const int PORT_ESP32 = 8888; // l'ESP32 écoute ici
int sensorValue = 0;
bool buttonState = false;
void setup() {
Serial.begin(115200);
pinMode(6, INPUT_PULLUP);
WiFi.begin(SSID, PASS);
while (WiFi.status() != WL_CONNECTED) delay(100);
Serial.println(WiFi.localIP());
// Réception : /led r g b (subscribe APRÈS la connexion Wi-Fi)
OscWiFi.subscribe(PORT_ESP32, "/led", [](const OscMessage& m) {
neopixelWrite(48, m.arg<int>(0), m.arg<int>(1), m.arg<int>(2));
});
// Envoi automatique d'une variable à 30 Hz
OscWiFi.publish(COMPUTER_IP, PORT_GODOT, "/sensor", sensorValue)->setFrameRate(30.f);
}
void loop() {
sensorValue = analogRead(4);
// Envoi ponctuel, seulement quand l'état du bouton change
bool b = digitalRead(6) == LOW;
if (b != buttonState) {
buttonState = b;
OscWiFi.send(COMPUTER_IP, PORT_GODOT, "/button", (int) b);
}
OscWiFi.update(); // envoie et reçoit : à appeler à chaque tour, sans long delay()
}
subscribedoit être appelé après la connexion Wi-Fi : sinon, il est ignoré.
Côté Godot#
Pas besoin d'addon : un message OSC est un petit paquet UDP. Ce script autonome envoie et reçoit des entiers, des flottants et du texte. Enregistrez-le dans le projet sous osc.gd :
extends Node
## OSC minimal pour Godot : envoi et réception de messages (entiers, flottants, texte).
## Les bundles OSC (#bundle) ne sont pas gérés.
signal message_received(address: String, values: Array)
@export var listen_port := 9999
var udp := PacketPeerUDP.new()
func _ready():
udp.bind(listen_port)
func _process(_delta):
while udp.get_available_packet_count() > 0:
var m = decode(udp.get_packet())
if m != null:
message_received.emit(m[0], m[1])
func send(ip: String, port: int, address: String, values: Array = []):
var b := StreamPeerBuffer.new()
b.big_endian = true
var types := ","
for v in values:
types += "i" if v is int else ("f" if v is float else "s")
_write_string(b, address)
_write_string(b, types)
for v in values:
if v is int:
b.put_32(v)
elif v is float:
b.put_float(v)
else:
_write_string(b, str(v))
udp.set_dest_address(ip, port)
udp.put_packet(b.data_array)
func decode(packet: PackedByteArray):
var b := StreamPeerBuffer.new()
b.big_endian = true
b.data_array = packet
var address := _read_string(b)
if not address.begins_with("/"):
return null
var types := _read_string(b)
var values := []
for t in types.substr(1):
match t:
"i": values.append(b.get_32())
"f": values.append(b.get_float())
"s": values.append(_read_string(b))
return [address, values]
# Un texte OSC se termine par 1 à 4 octets nuls (taille multiple de 4)
func _write_string(b: StreamPeerBuffer, text: String):
var bytes := text.to_utf8_buffer()
b.put_data(bytes)
for i in 4 - bytes.size() % 4:
b.put_u8(0)
func _read_string(b: StreamPeerBuffer) -> String:
var data := b.data_array
var start := b.get_position()
var end := start
while end < data.size() and data[end] != 0:
end += 1
b.seek(min(start + ((end - start + 4) & ~3), data.size()))
return data.slice(start, end).get_string_from_utf8()
Et un exemple d'utilisation : le cercle grossit avec le capteur, un clic change la couleur de la LED de l'ESP32 :
extends Node2D
const IP_ESP32 = "172.20.10.5" # ← l'adresse affichée par l'ESP32
const PORT_ESP32 = 8888
var osc
var sensor = 0
var color = Color.ORANGE
func _ready():
RenderingServer.set_default_clear_color(Color("#1d1b33"))
osc = preload("res://osc.gd").new() # port d'écoute : 9999 par défaut
add_child(osc)
osc.message_received.connect(_on_message)
func _on_message(address, values):
match address:
"/sensor":
sensor = values[0]
"/button":
print("bouton : ", values[0])
func _input(event):
if event is InputEventMouseButton and event.pressed:
color = Color(randf(), randf(), randf())
osc.send(IP_ESP32, PORT_ESP32, "/led", [color.r8, color.g8, color.b8])
func _process(_delta):
queue_redraw()
func _draw():
draw_circle(get_viewport_rect().size / 2, remap(sensor, 0, 4095, 10, 250), color)
Alternative : l'addon GodOSC de l'AssetLib (nœuds
OSCServer/OSCClient).
ESP-NOW : ESP32 ↔ ESP32 sans routeur#
ESP-NOW est un protocole d'Espressif : des messages de 250 octets max, envoyés directement d'une carte à l'autre par la radio Wi-Fi, sans réseau ni routeur, avec une latence de l'ordre de la milliseconde et une portée de plusieurs dizaines de mètres. Idéal pour des contrôleurs sans fil : plusieurs ESP32 « manettes » envoient à une ESP32 « réceptrice » branchée en USB à l'ordinateur.
Émetteur#
Ouvrir ce montage dans Plot Circuits ↗
#include <Arduino.h>
#include <WiFi.h>
#include <esp_now.h>
// Adresse du récepteur (affichée par son programme), ou diffusion à tous :
uint8_t receiver[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
// Le message : une simple struct, identique des deux côtés
struct Message {
uint16_t sensor;
bool button;
};
void onSent(const uint8_t* mac, esp_now_send_status_t status) {
if (status != ESP_NOW_SEND_SUCCESS) Serial.println("échec d'envoi");
}
void setup() {
Serial.begin(115200);
pinMode(6, INPUT_PULLUP);
WiFi.mode(WIFI_STA); // ESP-NOW utilise la radio Wi-Fi, sans routeur
if (esp_now_init() != ESP_OK) {
Serial.println("ESP-NOW : erreur d'initialisation");
return;
}
esp_now_register_send_cb(onSent);
esp_now_peer_info_t peer = {};
memcpy(peer.peer_addr, receiver, 6);
peer.channel = 0; // canal Wi-Fi courant
peer.encrypt = false;
esp_now_add_peer(&peer);
}
void loop() {
Message msg;
msg.sensor = analogRead(4);
msg.button = digitalRead(6) == LOW;
esp_now_send(receiver, (uint8_t*) &msg, sizeof(msg));
delay(20); // 50 messages par seconde
}
Récepteur#
#include <Arduino.h>
#include <WiFi.h>
#include <esp_now.h>
struct Message {
uint16_t sensor;
bool button;
};
volatile Message latest; // écrit dans le callback, lu dans loop()
volatile bool hasNew = false;
// Appelé par la tâche Wi-Fi à chaque message reçu : traitement court !
void onReceive(const uint8_t* mac, const uint8_t* data, int len) {
if (len == sizeof(Message)) {
memcpy((void*) &latest, data, sizeof(Message));
hasNew = true;
}
}
void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
Serial.printf("Mon adresse MAC : %s\n", WiFi.macAddress().c_str()); // à copier dans l'émetteur
esp_now_init();
esp_now_register_recv_cb(onReceive);
}
void loop() {
if (hasNew) {
hasNew = false;
Serial.printf("capteur=%4u bouton=%d\n", latest.sensor, latest.button);
neopixelWrite(48, latest.button ? 60 : 0, map(latest.sensor, 0, 4095, 0, 60), 0);
}
}
- La même
structdoit être déclarée des deux côtés (même ordre, mêmes types). FF:FF:FF:FF:FF:FFdiffuse à toutes les cartes à portée ; pour un seul destinataire, mettez son adresse MAC (affichée par le récepteur).- Les callbacks tournent dans la tâche Wi-Fi : on copie les données et on traite dans
loop()(ou on les pousse dans une file FreeRTOS, voir Multitâche). - ESP-NOW et Wi-Fi client peuvent cohabiter, à condition d'être sur le même canal que le routeur.
- Vers Godot : le récepteur peut renvoyer les données en USB HID (manette, voir Jour 3) ou sur le port série.
- En Arduino-ESP32 3.x, le callback de réception devient
onReceive(const esp_now_recv_info_t* info, const uint8_t* data, int len)(adresse dansinfo->src_addr).