AccueilAteliersInitiation ESP32
ATELIER DÉCOUVERTE Débutant

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

ESP32MicroPythonWiFiLEDREPL

Ce que tu vas créer

À la fin de cet atelier, ton ESP32 sera un objet connecté autonome, piloté depuis ton téléphone :

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érielQuantitéNotes
ESP321Fourni en atelier — tu repars avec
Câble USB1Micro-USB ou USB-C selon la carte
Ordinateur1Avec Thonny installé
Smartphone1Pour la manipulation finale

Les 9 manipulations

#ManipulationConcept clé
01Le REPL et la LEDConsole interactive, variables, GPIO sortie
02LED qui clignoteBoucle infinie, while True
03LED qui “respire”PWM, luminosité variable
04Lire le bouton BOOTGPIO en entrée, logique active bas
05LED commandée par le boutonCapteur → actionneur
06Temporisateur d’éclairageCompteur, minuterie d’escalier
07Température du processeurCapteur interne, f-strings
08Scanner les réseaux WiFiModule réseau
09Tableau de bord web + LED + températureWiFi + 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 ligne

Ctrl+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.

🔧 À toi de jouer
  • Combien de secondes dans une année ? Tape 365 * 24 * 3600
  • Compare les trois divisions : 10 / 3, puis 10 // 3, puis 10 % 3
  • Crée une variable : prenom = "Vincent" puis print("Bonjour", prenom)
  • Fais clignoter la LED à la main : alterne led.on() et led.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.

🔧 À toi de jouer
  • Remplace 0.5 par 0.1 → clignotement rapide
  • Remplace 0.5 par 2.0 → clignotement lent
  • Mets des durées différentes : 0.1 allumé, 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))
# -----------------------------------------------------------------------
🔧 À toi de jouer

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=1000 en freq=1 → tu vois le clignotement qui se cachait derrière le PWM
  • Change le pas : range(0, 1023, 50) au lieu de 5 → 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.

🔧 À toi de jouer
  • Enlève Pin.PULL_UP (garde Pin(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".
# -----------------------------------------------------------------------
🔧 À toi de jouer
  • 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)
# -----------------------------------------------------------------------
🔧 À toi de jouer
  • 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 à compt pour 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
# -----------------------------------------------------------------------
🔧 À toi de jouer
  • 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
# -----------------------------------------------------------------------
🔧 À toi de jouer
  • 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 :

Comment faire :

  1. Lancer ce programme dans Thonny
  2. Smartphone : réglages WiFi → se connecter à ESP32-Polinno (mot de passe : micropython)
  3. 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
# -----------------------------------------------------------------------
🔧 À toi de jouer
  • 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.