Contrôle d'état de LEDs avec un ESP32
2026-01-24
Contexte
Mon patron m'avait offert un ESP32 (un petit microcontrôleur avec Wi-Fi intégré) pour que je puisse le découvrir et m'amuser à tester divers projets dessus. Il m'arrivait aussi souvent de rater des notifications car elles n'étaient pas dans mon champ de vision. J'ai donc eu l'idée de faire un système de leds qui s'allument avec cet esp32.
Étapes:
-> 1 - Achat de tous les éléments manquants
-> 2 - Montage du breadboard
-> 3 - Conception du code
-> 4 - Écriture de scripts exécutés depuis mon PC pour allumer ou éteindre les LEDs de l'ESP
Achat des éléments manquants
Quand j'étais plus petite, j'avais reçu un kit d'essai aux circuits et mes parents m'avaient acheté une boite de LEDs que j'ai gardée depuis.
Je n'ai donc pas eu à acheter les LEDs qui m'auraient coûté 2€.
Il me fallait quand même des résistances pour éviter de griller mes LEDs, des câbles pour tout brancher et un "breadboard" pour faire mes branchements dessus.
Le total était d'environ 10€.
Résistances

Pour choisir les bonnes résistances, il a fallu diviser (Tension d'alimentation - Tension directe de la LED) par le courant souhaité:
- Tension d'alimentation: un ESP32 sort du 3,3V sur ses broches GPIO (broches permettant de brancher les LEDs ici)
- Tension directe de la LED: 2V
- Courant souhaité: 6mA (soit 0,006 A)
-> (3,3 - 2) / 0,006 = 1,3/0,006 = 216,6 ohms
Au moment de l'achat, je prends la valeur la plus proche juste au dessus: 220 ohms.
Câbles et breadboard
Pour les câbles et le breadboard, j'ai acheté des standards sans spécificités particulières.
Montage des éléments
Voilà une image du montage sur le breadboard avec des chemins exemples de courant pour la LED rouge et la LED bleue:

Code de l'ESP
Voilà le code en C++ flashé dans l'esp:
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
// codes Wi-Fi
const char* ssid = "Mon-Wifi";
const char* password = "Mon-mdp";
ESP8266WebServer server(80);
// Pins pour les 5 LEDs
int leds[] = {5, 4, 14, 12, 13}; // D1, D2, D5, D6, D7
void handleControl() {
if (server.hasArg("led") && server.hasArg("state")) {
// On enlève 1 au numéro pour les index de liste
int ledIndex = server.arg("led").toInt() - 1;
String state = server.arg("state");
if (ledIndex >= 0 && ledIndex < 5) {
digitalWrite(leds[ledIndex], (state == "on") ? HIGH : LOW);
server.send(200, "text/plain", "LED " + String(ledIndex + 1) + " is " + state);
} else {
server.send(400, "text/plain", "Index LED invalide");
}
} else {
server.send(400, "text/plain", "Arguments manquants");
}
}
void setup() {
Serial.begin(115200);
// Initialisation des pins
for (int i = 0; i < 5; i++) {
pinMode(leds[i], OUTPUT);
digitalWrite(leds[i], LOW);
}
// Connexion au Wi-Fi
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
Serial.println("");
Serial.println("Connecté ! Adresse IP : ");
Serial.println(WiFi.localIP());
// Routes de l'API
server.on("/set", handleControl);
server.begin();
}
void loop() {
server.handleClient();
}
Ce code permet à l'ESP de servir de mini serveur web auquel on peut envoyer des requêtes pour qu'il allume et éteigne les LEDs.
Les URLs valables sont sous cette forme:
https://<IP>//set?led=1&state=on
Scripts pour contrôler les LEDs
Pour pouvoir allumer les LEDs en fonction des différents évenements, il a fallu que je fasse tourner des scripts sur mon PC debian pour qu'il puisse envoyer les requêtes à l'ESP sur le même réseau.
Script pour allumer les LEDS
Pour ce script, j'ai choisi de ne faire s'allumer les LEDs que pour les notifications de certaines applications.
#!/bin/bash
IP="192.168.1.13"
# Applications pour lesquelles je veux allumer la LED
TARGET_APP="discord|Vesktop|Telegram Desktop|notify-send|Microsoft Teams|Signal"
LAST_APP=""
LAST_TIME=0
echo "--- App Watcher Active ---"
echo "En attente de notifications venant de : $TARGET_APP"
dbus-monitor --session "interface='org.freedesktop.Notifications',member='Notify'" | while read -r line; do
# Dans DBus, le premier 'string' après 'Notify' est souvent le nom de l'application
if [[ "$line" == *"string"* ]]; then
APP_NAME=$(echo "$line" | cut -d '"' -f 2)
# On vérifie si c'est l'app qu'on cherche
if echo "$APP_NAME" | grep -Eiq "$TARGET_APP"; then
CURRENT_TIME=$(date +%s)
# Anti-spam (évite de trigger 2 fois pour la même notif)
if [[ "$APP_NAME" == "$LAST_APP" ]] && (( CURRENT_TIME - LAST_TIME < 2 )); then
continue
fi
LAST_APP="$APP_NAME"
LAST_TIME=$CURRENT_TIME
echo -e "\n📱 App Détectée : $APP_NAME"
# Activation de la LED
curl -s "http://$IP/set?led=2&state=on" > /dev/null
sleep 0.5
curl -s "http://$IP/set?led=2&state=off" > /dev/null
echo "Signal envoyé !"
else
echo "App: $APP_NAME"
echo "$LAST_APP"
fi
fi
done
Script qui détecte ma voix
Pendant mes longues sessions d'appels avec mes amis il arrivait que mon micro ne soit pas détecté correctement.
Sauf que l'application d'appels était rarement au premier plan. Donc une led qui s'allume quand ma voix est détectée est le projet parfait.
import pyaudio
import numpy as np
import requests
import time
# --- CONFIGURATION ---
IP = "192.168.1.13"
ESP_URL_ON = "http://"+IP+"/set?led=1&state=on"
ESP_URL_OFF = "http://"+IP+"/set?led=1&state=off"
THRESHOLD = 400 # Permet de choisir l'intensité à laquelle ma voix est détectée
CHUNK = 1024
RATE = 48000
DELAY_OFF = 0.3
p = pyaudio.PyAudio()
try:
stream = p.open(
format=pyaudio.paInt16,
channels=1,
rate=RATE,
input=True,
frames_per_buffer=CHUNK,
input_device_index=DEVICE_INDEX
)
except Exception as e:
print(f"Erreur ouverture micro : {e}")
exit()
print("Script lancé... ")
is_on = False
last_speech_time = 0
try:
while True:
# Lecture du flux audio
raw_data = stream.read(CHUNK, exception_on_overflow=False)
data = np.frombuffer(raw_data, dtype=np.int16)
peak = np.max(np.abs(data))
# Pour voir le volume :
# print(f"Volume: {peak}", end='\r')
if peak > THRESHOLD:
last_speech_time = time.time()
if not is_on:
try:
requests.get(ESP_URL_ON, timeout=0.5)
is_on = True
print("\n[ON] Voix détectée")
except:
print("\nErreur : Impossible de joindre l'ESP")
elif is_on and (time.time() - last_speech_time > DELAY_OFF):
try:
requests.get(ESP_URL_OFF, timeout=0.5)
is_on = False
print("\n[OFF] Silence")
except:
pass
except KeyboardInterrupt:
print("\nArrêt...")
finally:
stream.stop_stream()
stream.close()
p.terminate()
Page web Pomodoro
J'ai eu des problèmes de concentration où je finissais souvent par procrastiner au lieu de réviser.
Après qu'on m'ait parlé de la méthode pomodoro, j'ai voulu la tester et les LEDs étaient une excellente manière de voir dans quelle "période" j'étais.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>LED Pomodoro Control</title>
<style>
body { font-family: 'Segoe UI', sans-serif; background: #121212; color: white; text-align: center; padding: 50px; }
.timer { font-size: 5rem; font-weight: bold; margin: 20px 0; font-variant-numeric: tabular-nums; }
.status { font-size: 1.5rem; text-transform: uppercase; letter-spacing: 2px; color: #bbb; }
.controls button { padding: 15px 30px; font-size: 1.2rem; cursor: pointer; border: none; border-radius: 5px; margin: 5px; transition: 0.3s; }
.start { background: #27ae60; color: white; }
.stop { background: #c0392b; color: white; }
.start:hover { background: #2ecc71; }
.stop:hover { background: #e74c3c; }
</style>
</head>
<body>
<div class="status" id="phase-name">Ready to Grind</div>
<div class="timer" id="display">20:00</div>
<div class="controls">
<button class="start" onclick="startPomodoro()">START</button>
<button class="stop" onclick="resetTimer()">RESET</button>
</div>
<script>
const IP = "192.168.1.4";
let timeLeft;
let timerId = null;
let currentPhase = 0; // 0: Work, 1: Relax, 2: Prepare
const PHASES = [
{ name: "WORK", duration: 20 * 60, led: 3, color: "#ff4d4d" },
{ name: "RELAX", duration: 4 * 60 + 50, led: 5, color: "#2ecc71" },
{ name: "PREPARE", duration: 10, led: 4, color: "#f1c40f" }
];
async function triggerLed(ledPin, state) {
try {
await fetch(`http://${IP}/set?led=${ledPin}&state=${state}`, { mode: 'no-cors' });
} catch (e) {
console.log("LED Request failed: ", e);
}
}
function updateDisplay() {
const mins = Math.floor(timeLeft / 60).toString().padStart(2, '0');
const secs = (timeLeft % 60).toString().padStart(2, '0');
document.getElementById('display').innerText = `${mins}:${secs}`;
document.getElementById('display').style.color = PHASES[currentPhase].color;
document.getElementById('phase-name').innerText = PHASES[currentPhase].name;
}
async function startPhase(index) {
currentPhase = index;
timeLeft = PHASES[currentPhase].duration;
// Éteindre toutes les lEDs du pomodoro qui ont pu rester allumées.
[3, 4, 5].forEach(pin => triggerLed(pin, "off"));
// Allumer la LED de la bonne "période"
triggerLed(PHASES[currentPhase].led, "on");
updateDisplay();
}
function startPomodoro() {
if (timerId) return;
startPhase(0);
timerId = setInterval(async () => {
timeLeft--;
if (timeLeft < 0) {
currentPhase = (currentPhase + 1) % 3;
await startPhase(currentPhase);
}
updateDisplay();
}, 1000);
}
function resetTimer() {
clearInterval(timerId);
timerId = null;
[3, 4, 5].forEach(pin => triggerLed(pin, "off"));
document.getElementById('display').innerText = "20:00";
document.getElementById('display').style.color = "white";
document.getElementById('phase-name').innerText = "Ready to Grind";
}
</script>
</body>
</html>
Conclusion
Ce projet m'a permis de découvrir le fonctionnement des ESP et d'apprendre comment les "GPIO" fonctionnent.
Il m'a aussi permis de bidouiller des librairies python que je ne connaissais pas et de me servir de tous ces scripts pour me faciliter la vie en ayant toujours mes LEDs visibles sur mon bureau.