Initiation à l’ESP32 — Zéro câblage
9 manipulations progressives pour découvrir ce qu’est un microcontrôleur. Du premier dialogue avec la machine jusqu’au contrôle d’une LED depuis ton smartphone — sans brancher quoi que ce soit.
3h
Ce que tu vas créer
À la fin de cet atelier, ton ESP32 sera un objet connecté autonome, piloté depuis ton téléphone :
- Une LED que tu allumes, éteins et dont tu règles l’intensité à distance
- La température du processeur affichée en direct, rafraîchie toutes les 3 secondes
- Le tout servi par un réseau WiFi créé par la carte elle-même — sans box, sans internet
Et pour y arriver : 9 manipulations qui partent vraiment de zéro, sans rien brancher.
Matériel nécessaire
Rien à brancher sur breadboard
Toutes les manipulations utilisent uniquement la LED intégrée (GPIO 2) et le bouton BOOT (GPIO 0) déjà présents sur la carte.
| Matériel | Quantité | Notes |
|---|---|---|
| ESP32 | 1 | Fourni en atelier — tu repars avec |
| Câble USB | 1 | Micro-USB ou USB-C selon la carte |
| Ordinateur | 1 | Avec Thonny installé |
| Smartphone | 1 | Pour la manipulation finale |
Les 9 manipulations
| # | Manipulation | Concept clé |
|---|---|---|
| 01 | Le REPL et la LED | Console interactive, variables, GPIO sortie |
| 02 | LED qui clignote | Boucle infinie, while True |
| 03 | LED qui “respire” | PWM, luminosité variable |
| 04 | Lire le bouton BOOT | GPIO en entrée, logique active bas |
| 05 | LED commandée par le bouton | Capteur → actionneur |
| 06 | Temporisateur d’éclairage | Compteur, minuterie d’escalier |
| 07 | Température du processeur | Capteur interne, f-strings |
| 08 | Scanner les réseaux WiFi | Module réseau |
| 09 | Tableau de bord web + LED + température | WiFi + HTTP + PWM + capteur |
Manip 01 — Le REPL et la LED
Tout se fait dans le Shell de Thonny (la console en bas). On tape une instruction, l’ESP32 l’exécute et affiche le résultat.
Découverte : calculs et variables
>>> 2 + 3 * 4 # la multiplication est prioritaire
14
>>> 15 / 3 - 1 # la division retourne un "float"
4.0
>>> 13 // 4 # division entière
3
>>> 13 % 4 # modulo (reste de la division)
1
>>> 2 ** 8 # puissance : 2 exposant 8
256>>> vitesse1 = 36
>>> vitesse2 = 42
>>> print("Moyenne :", (vitesse1 + vitesse2) / 2)
Moyenne : 39.0
>>> type(vitesse1) # int = nombre entier
<class 'int'>
>>> type("Bonjour") # str = chaîne de caractères
<class 'str'>Contrôler la LED intégrée
La LED bleue sur la carte est connectée au GPIO 2. On la commande depuis le Shell :
>>> from machine import Pin
>>> led = Pin(2, Pin.OUT) # GPIO 2 en sortie
>>> led.value(1) # la LED s'allume !
>>> led.value(0) # la LED s'éteint
>>> led.on() # raccourci pour allumer
>>> led.off() # raccourci pour éteindre
>>> Pin(2, Pin.OUT).value(1) # tout en une seule ligneCtrl+C : arrête le programme en cours ·
Ctrl+D : redémarre l’ESP32 ·
Flèche Haut : rappelle la dernière commande ·
Pin(n, Pin.OUT) = broche en sortie ·
value(1) = 3,3V → allumée ·
value(0) = 0V → éteinte
🤔 Avant de lancer Que renvoie `type(15 / 3)` — un `int` ou un `float` ? voir la réponse
Un float. En Python, la division / renvoie toujours un nombre à virgule, même quand le résultat tombe juste : 15 / 3 donne 5.0, pas 5.
Pour obtenir un entier, il faut la division entière : 15 // 3 renvoie bien 5.
- Combien de secondes dans une année ? Tape
365 * 24 * 3600 - Compare les trois divisions :
10 / 3, puis10 // 3, puis10 % 3 - Crée une variable :
prenom = "Vincent"puisprint("Bonjour", prenom) - Fais clignoter la LED à la main : alterne
led.on()etled.off()en rappelant les commandes avec la flèche Haut - Quel type renvoie
type(3.14)?
Manip 02 — Faire clignoter la LED
Le “Hello World” de l’électronique embarquée : une boucle infinie avec des pauses.
Ctrl+C dans Thonny pour arrêter.
manip02_led_blink.py .python atelier0-initiation/manip02_led_blink.py 43 lignes
GitHub
# Manipulation 03 — Faire clignoter la LED (boucle infinie)
# Fablab Ardèche — Atelier d'initiation
# -----------------------------------------------------------------------
# On combine la LED (manip 02) avec une boucle infinie et des pauses.
# C'est le "Hello World" de l'électronique embarquée.
#
# La fonction time.sleep() met le programme en pause un certain nombre
# de secondes. Pendant cette pause, l'ESP32 ne fait rien d'autre.
#
# Rien à brancher — LED intégrée sur GPIO 2.
# -----------------------------------------------------------------------
from machine import Pin # pour contrôler les broches
import time # pour les fonctions de pause (sleep)
# Initialisation de la LED sur GPIO 2 en sortie
led = Pin(2, Pin.OUT)
print("LED qui clignote — appuyer Ctrl+C pour arrêter")
# Boucle infinie : while True tourne indéfiniment jusqu'à Ctrl+C
while True:
led.value(1) # allumer la LED
time.sleep(0.5) # pause 0,5 seconde (500 ms)
led.value(0) # éteindre la LED
time.sleep(0.5) # pause 0,5 seconde
# -----------------------------------------------------------------------
# EXPÉRIENCES À TESTER :
# - Changer 0.5 en 0.1 → clignotement rapide
# - Changer 0.5 en 2.0 → clignotement lent
# - Mettre des durées différentes : 0.1 allumé, 0.9 éteint
# (comme un phare ou un signal morse)
#
# POUR ARRÊTER :
# - Appuyer Ctrl+C dans Thonny → KeyboardInterrupt
#
# VERSION COMPACTE (même résultat, une seule ligne dans la boucle) :
# while True:
# led.value(not led.value()) # inverse l'état actuel
# time.sleep(0.5)
# -----------------------------------------------------------------------
🤔 Avant de lancer Si tu remplaces les deux `time.sleep(0.5)` par `time.sleep(0.01)`, que verras-tu ? voir la réponse
La LED semblera allumée en permanence, mais moins lumineuse qu’à pleine puissance.
À 50 clignotements par seconde, l’œil ne distingue plus les alternances : au-delà d’environ 25 Hz, la persistance rétinienne les fusionne. Comme la LED n’est allumée que la moitié du temps, on perçoit une luminosité moyenne.
C’est exactement le principe du PWM — la manipulation suivante.
- Remplace
0.5par0.1→ clignotement rapide - Remplace
0.5par2.0→ clignotement lent - Mets des durées différentes :
0.1allumé,0.9éteint — comme un phare - Essaie la version compacte :
led.value(not led.value())inverse l’état à chaque tour
Manip 03 — La LED qui “respire” (PWM)
Le PWM allume et éteint la LED des centaines de fois par seconde.
L’œil perçoit une luminosité intermédiaire. duty va de 0 (éteint) à 1023 (pleine puissance).
manip03_led_pwm.py .python atelier0-initiation/manip03_led_pwm.py 55 lignes
GitHub
# Manipulation 04 — LED qui "respire" avec le PWM
# Fablab Ardèche — Atelier d'initiation
# -----------------------------------------------------------------------
# PWM = Pulse Width Modulation = Modulation de Largeur d'Impulsion
#
# Une broche numérique ne peut faire que 0V ou 3,3V — rien entre les deux.
# Le PWM simule un niveau intermédiaire en allumant/éteignant très vite
# la LED (des centaines de fois par seconde).
#
# Si la LED est allumée 50% du temps et éteinte 50% du temps,
# l'œil voit une luminosité à 50%. C'est le "duty cycle" (rapport cyclique).
#
# duty = 0 → LED éteinte (0% du temps allumée)
# duty = 512 → LED mi-luminosité (50% du temps allumée)
# duty = 1023 → LED à pleine luminosité (100% du temps allumée)
#
# Rien à brancher — LED intégrée sur GPIO 2.
# -----------------------------------------------------------------------
from machine import Pin, PWM # PWM est dans le même module que Pin
import time # pour les pauses
# Créer un objet PWM sur la broche GPIO 2
# freq=1000 signifie que la LED clignote 1000 fois par seconde
# (trop rapide pour que l'œil le voit — il perçoit juste la luminosité moyenne)
led = PWM(Pin(2), freq=1000)
print("LED qui respire — appuyer Ctrl+C pour arrêter")
# Boucle infinie : montée puis descente progressive
while True:
# Montée : de 0 à 1023 par pas de 5
for luminosite in range(0, 1023, 5):
led.duty(luminosite) # régler la luminosité
time.sleep_ms(5) # pause de 5 millisecondes entre chaque pas
# Descente : de 1023 à 0 par pas de 5
for luminosite in range(1023, 0, -5):
led.duty(luminosite)
time.sleep_ms(5)
# -----------------------------------------------------------------------
# EXPÉRIENCES À TESTER DANS LE SHELL (après Ctrl+C) :
# >>> led.duty(0) # éteinte
# >>> led.duty(100) # très faible
# >>> led.duty(512) # moitié
# >>> led.duty(1023) # pleine puissance
#
# À RETENIR :
# - PWM permet de contrôler l'intensité lumineuse d'une LED
# - PWM sert aussi à contrôler la vitesse d'un moteur ou la position
# d'un servo-moteur (manips futures)
# - sleep_ms(5) = pause de 5 millisecondes (plus précis que sleep(0.005))
# -----------------------------------------------------------------------
Après un Ctrl+C, règle la luminosité à la main dans le Shell :
led.duty(0)éteinte ·led.duty(100)très faible ·led.duty(512)moitié ·led.duty(1023)maximum- Change
freq=1000enfreq=1→ tu vois le clignotement qui se cachait derrière le PWM - Change le pas :
range(0, 1023, 50)au lieu de5→ la montée devient saccadée - Passe
sleep_ms(5)àsleep_ms(20)→ respiration plus lente
À retenir : le PWM ne sert pas qu’aux LED. C’est aussi lui qui règle la vitesse d’un moteur et la position d’un servomoteur.
Manip 04 — Lire le bouton BOOT
Le bouton BOOT (GPIO 0) est sur toutes les cartes ESP32. Attention à la logique inversée : relâché = 1, appuyé = 0.
manip04_bouton_boot.py .python atelier0-initiation/manip04_bouton_boot.py 48 lignes
GitHub
# Manipulation 05 — Lire le bouton BOOT (entrée numérique)
# Fablab Ardèche — Atelier d'initiation
# -----------------------------------------------------------------------
# Toutes les cartes ESP32 ont un bouton physique appelé "BOOT" (ou "IO0"),
# connecté au GPIO 0. On va lire son état.
#
# Logique inversée (pull-up) :
# - Bouton RELÂCHÉ → la broche lit 1 (3,3V)
# - Bouton APPUYÉ → la broche lit 0 (0V, reliée à la masse)
#
# C'est la logique "active bas" : l'action donne 0, le repos donne 1.
# Elle est très courante en électronique.
#
# Rien à brancher — le bouton BOOT est déjà sur la carte.
# -----------------------------------------------------------------------
from machine import Pin # pour contrôler les broches
import time # pour les pauses
# Initialisation du bouton BOOT sur GPIO 0 en ENTRÉE avec résistance pull-up
# Pin.PULL_UP active une résistance interne qui maintient la broche à 3,3V
# quand rien n'est connecté (évite les lectures parasites)
bouton = Pin(0, Pin.IN, Pin.PULL_UP)
print("Lecture du bouton BOOT — appuyer Ctrl+C pour arrêter")
print("Appuyez sur le bouton BOOT pour voir la valeur changer...")
while True:
etat = bouton.value() # lire l'état : 1 = relâché, 0 = appuyé
if etat == 0: # bouton appuyé (logique inversée)
print("APPUYÉ → valeur =", etat)
else: # bouton relâché
print("relâché → valeur =", etat)
time.sleep(0.2) # pause 200 ms pour ne pas inonder la console
# -----------------------------------------------------------------------
# À RETENIR :
# - Pin.IN = broche configurée en entrée (on lit)
# - Pin.OUT = broche configurée en sortie (on écrit)
# - Pin.PULL_UP = résistance interne vers 3,3V (évite les flottements)
# - Logique active bas : 0 = action, 1 = repos
#
# DANS LE SHELL :
# >>> bouton.value() # lire l'état instantané
# -----------------------------------------------------------------------
🤔 Avant de lancer Pourquoi le bouton vaut-il `1` au repos et `0` quand on appuie ? voir la réponse
À cause du pull-up interne, activé par Pin.PULL_UP.
Au repos, une petite résistance interne tire la broche vers 3,3 V → elle lit 1. Appuyer sur le bouton relie la broche directement à la masse (GND) → elle lit 0.
Sans ce pull-up, la broche serait « flottante » : elle capterait le bruit ambiant et renverrait des valeurs au hasard. On appelle ça une logique active bas, et elle est très courante en électronique.
- Enlève
Pin.PULL_UP(gardePin(0, Pin.IN)) → observe les valeurs devenir erratiques, puis remets-le - Passe
sleep(0.2)àsleep(0.01)→ la console est inondée. Pourquoi 200 ms est-il un bon compromis ? - Ajoute un compteur qui affiche le nombre total d’appuis
Manip 05 — La LED obéit au bouton
Premier automatisme : une entrée (bouton) commande une sortie (LED) en temps réel.
manip05_led_bouton.py .python atelier0-initiation/manip05_led_bouton.py 48 lignes
GitHub
# Manipulation 06 — La LED obéit au bouton (entrée → sortie)
# Fablab Ardèche — Atelier d'initiation
# -----------------------------------------------------------------------
# On combine les deux manips précédentes :
# - Bouton BOOT sur GPIO 0 (entrée)
# - LED intégrée sur GPIO 2 (sortie)
#
# Le programme lit en permanence l'état du bouton et allume ou éteint
# la LED en conséquence. C'est la base de tout automatisme :
# un CAPTEUR commande un ACTIONNEUR.
#
# Attention à la logique inversée du bouton :
# bouton.value() == 0 → bouton APPUYÉ → LED ALLUMÉE
# bouton.value() == 1 → bouton RELÂCHÉ → LED ÉTEINTE
#
# Rien à brancher — bouton et LED sont déjà sur la carte.
# -----------------------------------------------------------------------
from machine import Pin # pour contrôler les broches
import time # pour les pauses
# Initialisation des broches
led = Pin(2, Pin.OUT) # LED en sortie sur GPIO 2
bouton = Pin(0, Pin.IN, Pin.PULL_UP) # bouton BOOT en entrée sur GPIO 0
print("Maintenez le bouton BOOT appuyé pour allumer la LED")
print("Appuyer Ctrl+C pour arrêter")
while True:
if bouton.value() == 0: # bouton appuyé (logique active bas)
led.value(1) # allumer la LED
else: # bouton relâché
led.value(0) # éteindre la LED
time.sleep(0.02) # pause 20 ms — anti-rebond simple
# -----------------------------------------------------------------------
# VERSION UNE LIGNE (même résultat, plus compact) :
# while True:
# led.value(not bouton.value()) # not inverse : 0→1, 1→0
# time.sleep(0.02)
#
# POUR ALLER PLUS LOIN :
# Modifier le code pour que le bouton BASCULE la LED à chaque appui
# (appui 1 → allume, appui 2 → éteint, appui 3 → allume...)
# C'est ce qu'on appelle un "bascule" ou "flip-flop".
# -----------------------------------------------------------------------
- Inverse la logique : LED allumée au repos, éteinte quand on appuie
- Supprime le
sleep(0.02)→ tu vois apparaître les rebonds du contact mécanique - Plus dur : passe en mode bascule — chaque appui inverse l’état, au lieu de maintenir
Manip 06 — Temporisateur d’éclairage
On appuie sur le bouton → la LED s’allume 2 secondes puis s’éteint seule. Si on réappuie pendant que c’est allumé, le compteur repart — exactement comme une minuterie d’escalier.
Le principe : un compteur démarre à 20 à chaque appui. À chaque tour de boucle (0,1 s), le compteur descend de 1. La LED est allumée tant que le compteur est positif.
manip06_temporisateur.py .python atelier0-initiation/manip06_temporisateur.py 71 lignes
GitHub
# Manipulation 06 — Temporisateur d'éclairage (minuterie d'escalier)
# Fablab Ardèche — Atelier d'initiation
# -----------------------------------------------------------------------
# Principe : on appuie sur le bouton → la LED s'allume pendant 2 secondes
# puis s'éteint toute seule. Si on réappuie pendant que c'est allumé,
# le compteur repart à zéro (la durée est prolongée).
#
# Méthode : un COMPTEUR qui démarre à 20 à chaque appui.
# À chaque tour de boucle (toutes les 0,1 s), le compteur descend de 1.
# La LED est allumée tant que le compteur est positif.
# → 20 × 0,1 s = 2 secondes d'éclairage.
#
# C'est exactement le principe d'une minuterie d'escalier :
# on appuie → la lumière reste allumée un certain temps → elle s'éteint.
#
# Rien à brancher — LED GPIO 2, bouton BOOT GPIO 0.
# -----------------------------------------------------------------------
from machine import Pin
from time import sleep
led = Pin(2, Pin.OUT)
bp = Pin(0, Pin.IN, Pin.PULL_UP) # bouton BOOT, actif bas (0 = appuyé)
compt = 0 # compteur de temporisation
print("Temporisateur — appuyer sur BOOT pour allumer 2 secondes")
print("Ctrl+C pour arrêter")
while True:
# Si le bouton est appuyé (actif bas → value() == 0), relancer le compteur
if bp.value() == 0:
compt = 20 # 20 itérations × 0,1 s = 2 secondes
# La LED est allumée tant que le compteur est positif
led.value(compt > 0) # compt > 0 → True (1) → LED ON
sleep(0.1) # chaque tour dure 0,1 seconde
# Décrémenter le compteur, sans jamais passer en négatif
if compt > 0:
compt -= 1
# -----------------------------------------------------------------------
# COMMENT ÇA MARCHE :
#
# compt = 20 → led ON → sleep 0.1 → compt = 19
# compt = 19 → led ON → sleep 0.1 → compt = 18
# ...
# compt = 1 → led ON → sleep 0.1 → compt = 0
# compt = 0 → led OFF → sleep 0.1 → compt reste 0
#
# Si on appuie à compt = 5, le compteur remonte à 20 → relance le cycle.
#
# POURQUOI LE TEST "if compt > 0" :
# Sans lui, le compteur continuerait à descendre : -1, -2, -3...
# La LED resterait éteinte (le test compt > 0 serait faux), mais il
# faudrait ensuite remonter depuis très loin pour la rallumer.
# Le test bloque donc le compteur à 0.
#
# Variante compacte que tu croiseras parfois :
# compt -= (compt > 0)
# Elle fonctionne car en Python True vaut 1 et False vaut 0.
# C'est plus court, mais nettement moins lisible.
#
# VARIANTES À ESSAYER :
# compt = 50 → 5 secondes d'éclairage
# compt = 100 → 10 secondes
# sleep(0.05) → plus de précision (doubler compt en conséquence)
# -----------------------------------------------------------------------
compt = 50→ 5 secondes d’éclairage ·compt = 100→ 10 secondes- Passe
sleep(0.1)àsleep(0.05): la minuterie devient plus précise. Que faut-il changer àcomptpour garder 2 secondes ? - Fais clignoter la LED pendant la dernière seconde, pour prévenir de l’extinction
Manip 07 — Température interne du processeur
L’ESP32 a un capteur de température intégré dans le chip. Il mesure la chaleur du processeur (pas la température ambiante). Approcher le doigt près de la puce fait monter la valeur.
manip07_temperature_interne.py .python atelier0-initiation/manip07_temperature_interne.py 53 lignes
GitHub
# Manipulation 07 — Lire la température interne du processeur
# Fablab Ardèche — Atelier d'initiation
# -----------------------------------------------------------------------
# L'ESP32 intègre un capteur de température à l'intérieur même du chip.
# Il mesure la température du processeur (pas la température ambiante).
#
# À quoi ça sert ?
# - Surveiller que le processeur ne surchauffe pas
# - Démontrer qu'un microcontrôleur peut avoir des capteurs intégrés
#
# ATTENTION : la valeur est en degrés Fahrenheit dans MicroPython.
# On la convertit en Celsius avec la formule : (F - 32) / 1,8
#
# Astuce de démonstration : approcher le doigt (ou un objet chaud)
# près de la puce ESP32 pendant quelques secondes → la valeur monte.
#
# Rien à brancher — capteur interne au chip.
# -----------------------------------------------------------------------
import esp32 # module spécifique à l'ESP32, contient les fonctions internes
import time # pour les pauses
print("Température interne du processeur ESP32")
print("Approchez le doigt près de la puce pour voir la valeur monter...")
print("Appuyer Ctrl+C pour arrêter")
print()
while True:
# Lecture de la température brute (en Fahrenheit)
temp_fahrenheit = esp32.raw_temperature()
# Conversion en Celsius
temp_celsius = (temp_fahrenheit - 32) / 1.8
# Affichage avec une décimale
print(f"Température : {temp_celsius:.1f} °C ({temp_fahrenheit} °F)")
time.sleep(1) # une mesure par seconde
# -----------------------------------------------------------------------
# À RETENIR :
# - Le module "esp32" donne accès aux fonctions spéciales de la puce
# - raw_temperature() retourne des degrés Fahrenheit (héritage américain)
# - Formule de conversion : °C = (°F - 32) / 1.8
# - f"..." = f-string : façon moderne d'insérer des variables dans du texte
# - :.1f = formater un nombre avec 1 décimale
#
# DANS LE SHELL :
# >>> import esp32
# >>> esp32.raw_temperature()
# >>> (esp32.raw_temperature() - 32) / 1.8
# -----------------------------------------------------------------------
- Pose le doigt sur la puce métallique : la valeur monte en quelques secondes
- Fais travailler le processeur pour le chauffer :
for i in range(1000000): x = i ** 2 - Change
{temp_celsius:.1f}en:.3f→ trois décimales. La mesure est-elle vraiment si précise ? - Affiche l’écart avec la température de la pièce
Manip 08 — Scanner les réseaux WiFi
L’ESP32 détecte tous les réseaux WiFi environnants et affiche leur nom et niveau de signal. Dans une salle remplie d’ESP32, l’effet est garanti.
manip08_scan_wifi.py .python atelier0-initiation/manip08_scan_wifi.py 59 lignes
GitHub
# Manipulation 08 — Scanner les réseaux WiFi environnants
# Fablab Ardèche — Atelier d'initiation
# -----------------------------------------------------------------------
# L'ESP32 est équipé d'une antenne WiFi intégrée.
# Il peut se connecter à un réseau existant (mode Station / STA)
# ou créer son propre réseau (mode Access Point / AP).
#
# Dans cette manip, on utilise le mode Station juste pour écouter :
# on cherche les réseaux WiFi disponibles dans la salle.
# Chaque réseau trouvé affiche son nom (SSID) et son niveau de signal.
#
# Le signal WiFi se mesure en dBm (décibels milliwatts) :
# -30 dBm → signal excellent (très près du routeur)
# -70 dBm → signal faible
# -90 dBm → signal limite
#
# Rien à brancher.
# -----------------------------------------------------------------------
import network # module pour tout ce qui concerne le WiFi et les réseaux
import time # pour les pauses
# Créer une interface WiFi en mode "Station" (client, pas serveur)
wifi = network.WLAN(network.STA_IF)
wifi.active(True) # activer le module WiFi
print("Scan des réseaux WiFi en cours...")
print()
# scan() retourne une liste de tuples :
# (ssid, bssid, canal, signal, securite, cache)
reseaux = wifi.scan()
print(f"{len(reseaux)} réseaux trouvés :")
print("-" * 40)
for reseau in reseaux:
nom = reseau[0].decode("utf-8") # nom du réseau (bytes → texte)
signal = reseau[3] # niveau de signal en dBm
canal = reseau[2] # numéro de canal WiFi (1 à 13)
# Barre de signal visuelle (de 0 à 5 barres)
force = max(0, min(5, (signal + 100) // 10))
barres = "█" * force + "░" * (5 - force)
print(f" {barres} {signal:4d} dBm canal {canal:2d} {nom}")
print()
print("Scan terminé.")
# -----------------------------------------------------------------------
# À RETENIR :
# - network.WLAN() crée une interface WiFi
# - network.STA_IF = mode Station (se connecte à un réseau existant)
# - network.AP_IF = mode Access Point (crée son propre réseau)
# - scan() retourne une liste de tuples avec les infos de chaque réseau
# - .decode("utf-8") convertit des bytes en texte lisible
# -----------------------------------------------------------------------
- Relance le scan en te déplaçant dans la pièce : les barres de signal changent
- Affiche seulement les réseaux dont le signal dépasse −70 dBm
- Trie la liste du signal le plus fort au plus faible
- Compte combien de réseaux occupent le même canal — c’est la cause n°1 d’un WiFi lent
Manip 09 — Tableau de bord web ✨
Le grand final. L’ESP32 crée son réseau WiFi et sert un vrai tableau de bord avec :
- Boutons Allumer / Éteindre la LED
- Slider de luminosité (PWM) — glisser le doigt = LED qui varie en direct
- Température du processeur rafraîchie toutes les 3 secondes
Comment faire :
- Lancer ce programme dans Thonny
- Smartphone : réglages WiFi → se connecter à
ESP32-Polinno(mot de passe :micropython) - Navigateur → taper
192.168.4.1
manip09_serveur_web.py .python atelier0-initiation/manip09_serveur_web.py 385 lignes
GitHub
# Manipulation 09 — Tableau de bord ESP32 (serveur web)
# Fablab Ardèche — Atelier d'initiation
# -----------------------------------------------------------------------
# Le grand final : l'ESP32 crée son réseau WiFi et sert un tableau de bord :
# - Contrôle ON/OFF de la LED intégrée
# - Slider de luminosité (PWM) — glisser le doigt = LED qui varie
# - Température du processeur en temps réel (toutes les 3s)
#
# COMMENT UTILISER :
# 1. Lancer ce programme dans Thonny
# 2. Smartphone : WiFi → "ESP32-Polinno" (mot de passe : micropython)
# 3. Navigateur → 192.168.4.1
#
# Rien à brancher — LED intégrée sur GPIO 2.
#
# Routes :
# GET / → page tableau de bord
# GET /on → LED pleine puissance, redirect /
# GET /off → LED éteinte, redirect /
# GET /dim?v=N → LED à N% de puissance (0-100), pas de redirect
# GET /temp → température en texte brut (pour le JavaScript)
import network
import socket
import esp32
from machine import Pin, PWM
# --- Configuration ---
SSID = "ESP32-Polinno"
PASSWORD = "micropython"
# --- LED en mode PWM (permet de varier la luminosité) ---
# PWM sur GPIO2, fréquence 1000 Hz
# duty : 0 = éteinte, 1023 = pleine puissance
led = PWM(Pin(2), freq=1000)
led.duty(0)
luminosite = 100 # luminosité mémorisée en % (pour le bouton "Allumer")
led_allumee = False
# --- Point d'accès WiFi ---
ap = network.WLAN(network.AP_IF)
ap.active(True)
ap.config(essid=SSID, password=PASSWORD, authmode=network.AUTH_WPA_WPA2_PSK)
print("Réseau WiFi :", SSID)
print("Adresse IP :", ap.ifconfig()[0])
def pct_to_duty(pct):
"""Convertit un pourcentage (0-100) en valeur PWM (0-1023) avec correction gamma.
Sans correction : à 50% le slider la LED paraît déjà très lumineuse
car l'œil humain perçoit la luminosité de façon logarithmique.
La correction gamma 2.2 rend la progression du slider linéaire
pour la perception visuelle.
Formule : duty = (pct / 100) ^ 2.2 × 1023
"""
if pct <= 0:
return 0
if pct >= 100:
return 1023
return int((pct / 100) ** 2 * 1023)
def get_temp():
"""Retourne la température du processeur en °C."""
return f"{(esp32.raw_temperature() - 32) / 1.8:.1f}"
def page_html(led_allumee, luminosite):
"""Génère la page tableau de bord."""
etat = "ALLUMÉE" if led_allumee else "ÉTEINTE"
couleur = "#22c55e" if led_allumee else "#374151"
glow = "0 0 20px #22c55e88, 0 0 40px #22c55e33" if led_allumee else "none"
temp = get_temp()
return f"""<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="data:,">
<title>ESP32 Fablab</title>
<style>
* {{ box-sizing: border-box; margin: 0; padding: 0; }}
body {{
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
background: #0f0f1a;
color: #e2e8f0;
padding: 20px 16px 40px;
max-width: 400px;
margin: 0 auto;
}}
header {{
text-align: center;
padding: 20px 0 24px;
border-bottom: 1px solid #1e293b;
margin-bottom: 20px;
}}
header h1 {{
font-size: 0.9rem;
color: #38bdf8;
letter-spacing: 3px;
font-weight: 700;
text-transform: uppercase;
}}
header p {{ color: #475569; font-size: 0.75rem; margin-top: 4px; }}
.card {{
background: #1e293b;
border: 1px solid #334155;
border-radius: 18px;
padding: 22px 20px;
margin-bottom: 14px;
}}
.card-label {{
font-size: 0.65rem;
color: #64748b;
text-transform: uppercase;
letter-spacing: 1.5px;
margin-bottom: 16px;
}}
/* --- Carte LED --- */
.led-row {{
display: flex;
align-items: center;
gap: 16px;
margin-bottom: 18px;
}}
.led-orb {{
width: 48px;
height: 48px;
border-radius: 50%;
background: {couleur};
box-shadow: {glow};
flex-shrink: 0;
transition: background 0.3s, box-shadow 0.3s;
}}
.led-info {{ flex: 1; }}
.led-etat {{
font-size: 1.1rem;
font-weight: 700;
color: {'#22c55e' if led_allumee else '#64748b'};
}}
.led-sub {{ font-size: 0.72rem; color: #475569; margin-top: 2px; }}
.btns {{ display: flex; gap: 10px; }}
.btn {{
flex: 1;
display: block;
padding: 12px;
border-radius: 12px;
font-size: 0.95rem;
font-weight: 700;
text-decoration: none;
text-align: center;
}}
.btn:active {{ opacity: 0.75; }}
.btn-on {{ background: #22c55e; color: #fff; }}
.btn-off {{ background: #ef4444; color: #fff; }}
/* --- Slider luminosité --- */
.slider-row {{
display: flex;
align-items: center;
gap: 14px;
margin-bottom: 6px;
}}
.slider-row input {{
flex: 1;
accent-color: #f59e0b;
height: 36px;
cursor: pointer;
-webkit-appearance: none;
appearance: none;
background: transparent;
}}
.slider-row input::-webkit-slider-runnable-track {{
height: 12px;
background: #334155;
border-radius: 6px;
}}
.slider-row input::-webkit-slider-thumb {{
-webkit-appearance: none;
width: 44px;
height: 44px;
border-radius: 50%;
background: #f59e0b;
margin-top: -16px;
box-shadow: 0 2px 8px #0008;
}}
.slider-val {{
font-size: 1.3rem;
font-weight: 700;
color: #f59e0b;
width: 52px;
text-align: right;
}}
.slider-labels {{
display: flex;
justify-content: space-between;
font-size: 0.65rem;
color: #475569;
margin-top: 4px;
}}
/* --- Carte température --- */
.temp-row {{
display: flex;
align-items: baseline;
gap: 6px;
}}
.temp-val {{
font-size: 2.4rem;
font-weight: 800;
color: #38bdf8;
line-height: 1;
}}
.temp-unit {{ font-size: 1rem; color: #64748b; }}
.temp-sub {{ font-size: 0.72rem; color: #475569; margin-top: 6px; }}
footer {{
text-align: center;
color: #1e293b;
font-size: 0.7rem;
margin-top: 24px;
font-family: monospace;
}}
</style>
</head>
<body>
<header>
<h1>ESP32 · Fablab Polinno</h1>
<p>Serveur web embarqué · 192.168.4.1</p>
</header>
<!-- Contrôle LED -->
<div class="card">
<div class="card-label">Contrôle LED · GPIO 2</div>
<div class="led-row">
<div class="led-orb" id="orb"></div>
<div class="led-info">
<div class="led-etat" id="etat">LED {etat}</div>
<div class="led-sub">LED intégrée</div>
</div>
</div>
<div class="btns">
<a href="/on" class="btn btn-on" >Allumer</a>
<a href="/off" class="btn btn-off">Éteindre</a>
</div>
</div>
<!-- Luminosité (PWM) -->
<div class="card">
<div class="card-label">Luminosité · PWM</div>
<div class="slider-row">
<input type="range" id="slider" min="0" max="100" value="{luminosite}">
<div class="slider-val"><span id="pct">{luminosite}</span>%</div>
</div>
<div class="slider-labels">
<span>Éteint</span>
<span>Pleine puissance</span>
</div>
</div>
<!-- Température -->
<div class="card">
<div class="card-label">Température · Processeur interne</div>
<div class="temp-row">
<div class="temp-val" id="temp">{temp}</div>
<div class="temp-unit">°C</div>
</div>
<div class="temp-sub">Mise à jour toutes les 3 secondes</div>
</div>
<footer>MicroPython · ESP32 · Fablab Polinno</footer>
<script>
var slider = document.getElementById('slider');
var pct = document.getElementById('pct');
// Mise à jour de l'affichage en temps réel pendant le glissement
slider.oninput = function() {{
pct.textContent = this.value;
}};
// Envoi de la valeur à l'ESP32 quand le doigt se lève
slider.onchange = function() {{
var v = this.value;
pct.textContent = v;
fetch('/dim?v=' + v).catch(function() {{}});
}};
// Température live toutes les 3 secondes
setInterval(function() {{
fetch('/temp')
.then(function(r) {{ return r.text(); }})
.then(function(v) {{ document.getElementById('temp').textContent = v; }})
.catch(function() {{}});
}}, 3000);
</script>
</body>
</html>"""
# --- Serveur web ---
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # libère le port si déjà utilisé
s.bind(("", 80))
s.listen(5)
print("Serveur démarré — http://192.168.4.1")
while True:
conn, addr = s.accept()
try:
request = conn.recv(1024).decode()
ligne = request.split('\r\n')[0] # première ligne uniquement
if "GET /on" in ligne:
led.duty(pct_to_duty(luminosite))
led_allumee = True
print("LED allumée")
conn.send("HTTP/1.1 303 See Other\r\nLocation: /\r\nConnection: close\r\n\r\n")
elif "GET /off" in ligne:
led.duty(0)
led_allumee = False
print("LED éteinte")
conn.send("HTTP/1.1 303 See Other\r\nLocation: /\r\nConnection: close\r\n\r\n")
elif "GET /dim" in ligne:
# Extraire la valeur : "GET /dim?v=75 HTTP/1.1" → 75
try:
v = int(ligne.split("v=")[1].split(" ")[0])
v = max(0, min(100, v))
luminosite = v
led_allumee = (v > 0)
led.duty(pct_to_duty(v))
print(f"Luminosité : {v}%")
except Exception:
pass
conn.send("HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n")
elif "GET /temp" in ligne:
t = get_temp()
conn.send(f"HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\n{t}")
elif "GET / " in ligne:
html = page_html(led_allumee, luminosite)
conn.send("HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nConnection: close\r\n\r\n")
# Envoi par morceaux de 512 octets (la page est trop grande pour un seul send)
for i in range(0, len(html), 512):
conn.send(html[i:i + 512])
else:
conn.send("HTTP/1.1 404 Not Found\r\nConnection: close\r\n\r\n")
except OSError:
pass # connexion coupée par le client — normal sur mobile
finally:
conn.close()
# -----------------------------------------------------------------------
# VARIANTE — LED EXTERNE (montage sur breadboard)
#
# La LED intégrée du GPIO 2 est petite et peu visible en plein jour.
# Pour un rendu bien plus net, câbler une LED externe sur le même GPIO :
#
# GPIO 2 ──── résistance 220 Ω ──── LED (patte longue = +)
# LED (patte courte) ──── GND
#
# Aucune modification du code n'est nécessaire : le GPIO 2 pilote
# les deux en même temps.
#
# EXPÉRIENCES À TESTER :
# - Changer le nom du réseau : SSID = "ESP32-TonPrenom"
# - Modifier le titre de la page dans page_html()
# - Passer le rafraîchissement de la température de 3 s à 1 s
# - Ajouter un bouton "clignoter" qui fait clignoter la LED 5 fois
# -----------------------------------------------------------------------
- Personnalise le réseau :
SSID = "ESP32-TonPrenom" - Change le titre affiché sur la page dans
page_html() - Passe le rafraîchissement de la température de 3 s à 1 s
- Ajoute un bouton « clignoter » qui fait clignoter la LED 5 fois
- Branche une LED externe sur le GPIO 2 (
GPIO 2 → 220 Ω → LED → GND) : bien plus visible, et aucune ligne de code à changer
Pour aller plus loin
LED externe
Brancher une LED sur breadboard avec une résistance 220Ω pour une lumière plus visible.
Ajouter un capteur
Affiche la température d'un DHT22 directement sur le tableau de bord.
Plusieurs LEDs
Contrôle 3 LEDs de couleurs différentes depuis la même interface web.