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
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
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)
# -----------------------------------------------------------------------
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))
# -----------------------------------------------------------------------
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é
# -----------------------------------------------------------------------
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".
# -----------------------------------------------------------------------
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 64 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 passer en négatif)
compt -= (compt > 0) # astuce : (compt > 0) vaut 1 ou 0
# -----------------------------------------------------------------------
# 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.
#
# L'astuce "compt -= (compt > 0)" :
# Si compt > 0, l'expression vaut True (= 1), on soustrait 1.
# Si compt == 0, l'expression vaut False (= 0), on soustrait 0.
# → Le compteur ne descend jamais en dessous de 0.
#
# VARIANTES À ESSAYER :
# compt = 50 → 5 secondes d'éclairage
# compt = 100 → 10 secondes
# sleep(0.05) → plus de précision (doubler compt en conséquence)
# -----------------------------------------------------------------------
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
# -----------------------------------------------------------------------
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
# -----------------------------------------------------------------------
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 361 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)
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()
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")
conn.close()
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.