Routeur solaire intelligent ESP32 pour optimisation autoconsommation photovoltaïque. Route le surplus de production vers un chauffe-eau résistif avec modulation TRIAC 0-100%. Interface web temps réel, écran OLED, 4 capteurs température DS18B20, récupération production SolarEdge API.
Plateforme : ESP32 DEVKIT V1
Framework : Arduino
Version : 1.0.0
- Routage intelligent surplus solaire → chauffe-eau résistif
- Dimmer TRIAC 24A-600V modulation puissance 0-100% (découpage d'onde)
- Serveur web asynchrone (ESPAsyncWebServer, mDNS
routeurSol.local) - API REST complète (
/api/status,/api/command,/api/config) - Server-Sent Events (SSE) mises à jour temps réel
- SolarEdge API récupération production PV HTTPS
- Écran OLED SSD1306 128x64 (U8g2) - affichage status
- 4x DS18B20 températures eau/air/PAC/interne
- Multi-threading FreeRTOS 2 cores ESP32 (dimmer core0, web core1)
- WiFi Manager configuration sans fil (ESPAsyncWiFiManager)
- NTP synchronisation temps automatique
- Authentification sessions utilisateurs avec cookies
- Marche forcée programmable (heures creuses)
- ESP-NOW optionnel communication PAC sans fil
┌─────────────────────────────────────────────────────────────────┐
│ Navigateur Web / Mobile │
│ (http://routeurSol.local ou IP ESP32) │
└────────────────┬────────────────────────────────────────────────┘
│ HTTP/REST API + SSE
▼
┌─────────────────────────────────────────────────────────────────┐
│ ESP32 DEVKIT V1 │
│ (Dual-Core 240MHz, 320KB RAM, 4MB Flash) │
│ │
│ ┌─────────────────────────────────────────────────────────────┤
│ │ Core 1 (loop principal) │
│ │ - AsyncWebServer (port 80) │
│ │ - Routes API REST (/api/*) │
│ │ - SSE (/routeurEvents, /routeurParamsEvents) │
│ │ - NTPClient synchronisation │
│ │ - SolarEdge API polling (5 min) │
│ │ - Écran OLED update (1 sec) │
│ └─────────────────────────────────────────────────────────────┤
│ │ Core 0 (coreTask - temps réel) │
│ │ - TRIAC dimmer control (interruptions 100µs) │
│ │ - Zero-crossing detection (10ms) │
│ │ - DS18B20 acquisition (1 sec) │
│ │ - Calcul puissance à router │
│ └─────────────────────────────────────────────────────────────┘
│ │ │ │
└──────────────────┼─────────────────────────┼─────────────────────┘
GPIO 18/19│ OneWire │ I2C (21/22)
▼ ▼ ▼
┌────────────────────┐ ┌───────────────────┐ ┌──────────────┐
│ Dimmer TRIAC │ │ 4x DS18B20 │ │ OLED SSD1306│
│ 24A - 600V │ │ - Eau ballon │ │ 128x64 I2C │
│ Zero-cross detect │ │ - Air extérieur │ │ │
│ (pin 18) │ │ - PAC │ │ Status + │
│ Gate pulse │ │ - Interne ESP32 │ │ Puissance │
│ (pin 19) │ │ │ │ │
└─────────┬──────────┘ └───────────────────┘ └──────────────┘
│
▼
┌─────────────────────┐
│ Chauffe-eau │
│ Résistif │
│ (1500W - 3000W) │
│ │
│ Routage surplus │
│ production solaire │
└─────────────────────┘
▲
│ Production (HTTPS API)
┌─────────────────────┐
│ SolarEdge API │
│ monitoring. │
│ solaredge.com │
│ │
│ currentPowerFlow │
└─────────────────────┘
routeur solaire/
├── src/
│ ├── main.cpp # Point d'entrée (2101 lignes) ✅ DOCUMENTÉ
│ │ # Setup: WiFi, NTP, OLED, TRIAC, API
│ │ # Loop: core1 (web), core0 (dimmer)
│ │ # 26+ fonctions avec headers @brief
│ ├── routeurWeb.cpp # Serveur web AsyncWebServer (1564 lignes) ✅ DOCUMENTÉ
│ │ # Routes API, SSE, authentification
│ │ # 34 fonctions avec headers @brief
│ └── espnow.cpp # Communication ESP-NOW (377 lignes) ✅ DOCUMENTÉ
│ # 14 fonctions avec headers @brief
│
├── include/
│ ├── globalRouteur.h # Constantes, structures config
│ ├── routeurWeb.h # Classe RouteurSolWebClass
│ ├── espnow.h # Classe EspNowClass (optionnel)
│ ├── solaredge.h # Certificat SSL API SolarEdge
│ └── Triac.h # Classe Triac (dimmer control)
│
├── html/ # Sources HTML/CSS/JS
│ ├── index.html
│ ├── css/
│ └── js/
│
├── data/ # Fichiers compilés Gulp (LittleFS)
│ ├── 404.html.lgz
│ ├── index.html.lgz
│ └── ...
│
├── scripts/
│ ├── add_comments_src.py # Script auto commentaires fonctions
│ └── improve_comments_routeur.py # Script amélioration commentaires (74 fonctions)
│
├── platformio.ini # Configuration PlatformIO
├── gulpfile.js # Build frontend (minify, gzip)
├── package.json # Dépendances Node.js
├── copilote-instructions.md # Guide développeur complet
└── README.md # Ce fichier
## 📚 Documentation Code
Le code source est **intégralement documenté** avec headers Doxygen :
- **main.cpp** : 26 fonctions documentées (WiFi, SolarEdge, Config, Time, Control, Display)
- **routeurWeb.cpp** : 34 fonctions documentées (Routes API, SSE, Auth, Sessions)
- **espnow.cpp** : 14 fonctions documentées (Init, Send, Receive, Callbacks)
**Format headers :**
```cpp
/**
* @brief Description concise de la fonction
* @param paramName Description du paramètre
* @return Description de la valeur retournée
* @note Informations complémentaires
*/
Groupes fonctionnels main.cpp :
- WiFi (6) : wl_status_to_string, WiFiConnect, startWiFi, ConnectWithStoredCredentials...
- SolarEdge/HTTP (3) : getSolarEdgeValues, getSolarEdgeInfos, http_status_to_string
- Config/SPIFFS (8) : loadConfiguration, saveConfiguration, listAllFilesInDir...
- Time (3) : getNTPTime, dstOffset, calculDureeJour
- Routeur Control (5) : marcheForceeSwitch, setRelayPac, handleButtonEvent...
- Display/Web (3) : gestEcran, stopEcran, gestWeb
---
## 🔌 Matériel Requis
### Composants Essentiels
| Composant | Référence | Usage |
|--------------------|---------------------|---------------------------------|
| ESP32 DEVKIT V1 | Dual-core 240MHz | Microcontrôleur principal |
| Dimmer TRIAC | 24A 600V AC | Modulation puissance |
| Zero-cross Detector| Optocoupleur | Détection passage par zéro |
| OLED SSD1306 | 128x64 I2C (0x3C) | Affichage status |
| DS18B20 (x4) | OneWire | Capteurs température |
| Résistances 4.7kΩ | Pull-up OneWire | Bus OneWire |
### Pinout ESP32
| Pin GPIO | Fonction | Description |
|----------|-------------------|--------------------------------------|
| 18 | TRIAC_ZC | Zero-crossing detector (INPUT) |
| 19 | TRIAC_GATE | Gate TRIAC pulse (OUTPUT) |
| 4 | DS18B20_DATA | Bus OneWire (4x capteurs) |
| 21 | I2C_SDA | OLED SSD1306 |
| 22 | I2C_SCL | OLED SSD1306 |
**⚠️ ATTENTION SÉCURITÉ :** Le dimmer TRIAC manipule du 230V AC. **Isolation galvanique obligatoire** entre ESP32 et TRIAC (optocoupleur). Ne jamais modifier le câblage sous tension.
---
## 🔧 Configuration
### `platformio.ini`
```ini
[env:esp32doit-devkit-v1]
platform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip
board = esp32doit-devkit-v1
framework = arduino
monitor_speed = 115200
lib_deps =
ESPAsyncWiFiManager @ ^0.31
AsyncTCP @ ^3.3.5
ESPAsyncWebServer @ ^3.7.1
NTPClient @ ^3.2.1
Dimmable Light for Arduino @ ^2.0.1
DallasTemperature @ ^4.0.4
U8g2 @ ^2.36.5
ArduinoJson @ ^7.3.0
AceButton @ ^1.10.1
// Configuration système
#define MAX_USERS 5 // Nombre max utilisateurs
#define MAX_WIFI 3 // Nombre max réseaux WiFi
#define TZ_OFFSET 1 // Fuseau horaire (GMT+1)
// Configuration routeur (dans struct configuartion_t)
char solarEdge[50]; // API key SolarEdge
int volumeBallon; // Volume ballon (litres)
int puissanceBallon; // Puissance résistance (W)
char heureBackup[10]; // HH:MM marche forcée
int tempEauMin; // Température min (°C)
boolean secondBackup; // Marche forcée secondaire
char heureSecondBackup[10]; // HH:MM seconde marche forcée| Endpoint | Méthode | Description |
|---|---|---|
/api/status |
GET | État routeur (JSON) |
/api/command |
POST | Commande dimmer (power, mode) |
/api/config |
GET/POST | Configuration système |
/api/wifi |
POST | Configuration WiFi |
/routeurEvents |
SSE | Mises à jour temps réel |
/routeurParamsEvents |
SSE | Événements paramètres |
cd "/Users/ludovic1/Documents/PlatformIO/Projects/routeur solaire"
npm install# Minify et gzip des fichiers HTML/CSS/JS
npx gulp
# Les fichiers sont générés dans data/ (.lgz)~/.platformio/penv/bin/platformio run --environment esp32doit-devkit-v1Résultats attendus :
- RAM usage ~17% (55KB / 320KB)
- Flash usage ~67% (1.4MB / 2MB)
~/.platformio/penv/bin/platformio run --target upload --environment esp32doit-devkit-v1~/.platformio/penv/bin/platformio run --target uploadfs --environment esp32doit-devkit-v1Contenu uploadé : fichiers data/*.lgz (HTML/CSS/JS compressés)
~/.platformio/penv/bin/platformio device monitor --environment esp32doit-devkit-v1- Brancher l'ESP32 via USB
- Uploader firmware + filesystem (voir ci-dessus)
- Redémarrer l'ESP32
- L'ESP32 crée un point d'accès :
RouteurSolaire-XXXXXX - Se connecter depuis smartphone/PC
- Naviguer vers
http://192.168.4.1 - Sélectionner réseau WiFi et entrer mot de passe
- Valider → ESP32 redémarre et se connecte au réseau
-
Obtenir API key :
- Connexion https://monitoring.solaredge.com
- Admin → API Access → Generate API Key
- Copier la clé (ex:
8INR9G7TVYP03QAMRMNKJYRNN0MTVJSQ)
-
Configurer dans l'interface web :
/api/config→solarEdge: "VOTRE_CLE_API"- Ou directement dans
globalRouteur.havant compilation
Via interface web /api/config :
{
"volumeBallon": 150, // Volume en litres
"puissanceBallon": 1500, // Puissance résistance en W
"tempEauMin": 50, // Température minimale °C
"heureBackup": "20:00", // Marche forcée heures creuses
"secondBackup": false, // Marche forcée secondaire
"heureSecondBackup": "00:00"
}- Via mDNS :
http://routeurSol.local(macOS/Linux/iOS) - Via IP : Affichée sur OLED ou moniteur série (ex:
http://192.168.1.42)
// Récupération production solaire (API SolarEdge toutes les 5 min)
int productionPV = getSolarEdgeProduction();
// Récupération consommation maison (compteur ou estimation)
int consommationMaison = getConsommation();
// Calcul surplus disponible
int surplus = productionPV - consommationMaison;
// Si surplus > 0 → router vers chauffe-eau
if (surplus > 0) {
// Calcul puissance à injecter (0-100%)
int puissancePourcent = (surplus * 100) / puissanceBallon;
puissancePourcent = constrain(puissancePourcent, 0, 100);
// Commande dimmer TRIAC
triac.setPower(puissancePourcent);
}
// Si température eau < tempEauMin ET heure = heureBackup
// → Marche forcée 100%
if (tempEau < config.tempEauMin && now() == heureBackup) {
triac.setPower(100);
}Le dimmer TRIAC utilise la technique de découpage d'onde sinusoïdale :
- Détection zero-crossing : Interruption GPIO 18 toutes les 10ms (50Hz)
- Timer hardware : Interruption 100µs pour pulse précis
- Pulse gate : Impulsion courte GPIO 19 pour amorcer TRIAC
- Puissance 0% : Pas de pulse (TRIAC bloqué)
- Puissance 50% : Pulse à 5ms (milieu demi-onde)
- Puissance 100% : Pulse immédiat après zero-crossing
Avantages :
- Modulation précise 0-100%
- Pas de harmoniques basses fréquences
- Compatible charges résistives
- Rendement ~99%
Affichage temps réel :
┌──────────────────┐
│ RouteurSol v1.0 │
│ IP: 192.168.1.42 │
├──────────────────┤
│ Prod: 2500 W │
│ Conso: 1200 W │
│ Route: 1300 W │
│ Dimmer: 87% │
├──────────────────┤
│ Eau: 55.2°C │
│ Air: 18.5°C │
│ PAC: 22.1°C │
└──────────────────┘
{
"production": 2500,
"consommation": 1200,
"surplus": 1300,
"dimmerPower": 87,
"temperatures": {
"eau": 55.2,
"air": 18.5,
"pac": 22.1,
"interne": 45.3
},
"wifi": {
"ssid": "MaisonWiFi",
"rssi": -45
},
"uptime": 86400
}Connexion temps réel pour mises à jour instantanées :
// JavaScript côté client
const evtSource = new EventSource('/routeurEvents');
evtSource.addEventListener('power', (event) => {
const data = JSON.parse(event.data);
console.log('Puissance routée:', data.power, 'W');
});Solution :
- Vérifier LED ESP32 clignote (mode WiFiManager)
- Chercher réseau
RouteurSolaire-XXXXXX - Si bloqué : presser bouton RESET physique
- Moniteur série :
[WiFi] Connecting...
Diagnostics :
- Zero-crossing détecté ?
- Moniteur série :
[TRIAC] ZC detected - Si non : vérifier pin 18 → optocoupleur ZC
- Moniteur série :
- Gate pulse envoyé ?
- Oscilloscope sur pin 19 → pulse ~100µs
- TRIAC amorcé ?
- Multimètre sur charge : tension modulée
- Charge résistive ?
- Charges inductives/capacitives non supportées
Vérifications :
- Adresse I2C correcte :
# Scanner I2C ~/.platformio/penv/bin/platformio device monitor # Doit afficher: "I2C device found at 0x3C"
- Pins SDA/SCL (GPIO 21/22) connectés
- Alimentation 3.3V stable (> 100mA)
- Contraste OLED réglé
Checks :
- API key valide (
config.solarEdge) - Certificat SSL à jour (
solaredge.h) - WiFi stable (RSSI > -70 dBm)
- Firewall autorise HTTPS sortant (port 443)
- Moniteur série :
[API] GET https://monitoringapi.solaredge.com/... [API] Response: 200 OK
Causes :
- Sonde déconnectée
- Résistance pull-up 4.7kΩ manquante
- Bus OneWire trop long (> 10m)
- Alimentation insuffisante (parasite mode)
Solution :
// Vérifier adresses sondes
sensors.begin();
Serial.printf("Found %d DS18B20 devices\n", sensors.getDeviceCount());Causes fréquentes :
- Watchdog timeout :
// Dans loop core0 vTaskDelay(pdMS_TO_TICKS(10)); // Éviter boucle infinie
- Stack overflow :
// Augmenter taille stack xTaskCreatePinnedToCore(coreTask, "core0", 20000, NULL, 1, &Task1, 0); // ^^^^^ 10000 → 20000
- Alimentation insuffisante : 500mA min (1A recommandé)
- Température interne > 80°C : Améliorer ventilation
| Ressource | Usage Typique | Max Sécurisé |
|---|---|---|
| RAM | ~17% (55KB) | 70% |
| Flash | ~67% (1.4MB) | 90% |
| Heap libre | ~200KB | > 100KB |
- WiFi : 802.11 b/g/n (2.4 GHz)
- Connexions simultanées : 4-6 (AsyncWebServer)
- SSE clients max : 4
- Latence API : < 100ms
- Précision modulation : ±1% (100 steps)
- Fréquence zero-crossing : 100Hz (détection chaque demi-onde)
- Temps réponse : < 10ms (demi-onde)
- Puissance max : 24A @ 230V = 5.5kW
- Précision DS18B20 : ±0.5°C (-10°C à +85°C)
- Fréquence acquisition : 1 mesure/sec (750ms conversion)
- Résolution : 12 bits (0.0625°C)
- Isolation galvanique obligatoire ESP32 ↔ TRIAC
- Optocoupleur zero-crossing et gate pulse
- Boîtier isolé ESP32 (IP44 min)
- Disjoncteur différentiel 30mA sur circuit
- Câblage normalisé (section 2.5mm² pour 16A)
// Authentification sessions
struct sessions {
char sessID[16];
time_t ttl;
time_t timecreated;
};
// Hash password (à implémenter)
// TODO: bcrypt ou SHA256 pour stockage passwordsRecommandations :
- Changer mot de passe admin par défaut
- Limiter accès WiFi (réseau local isolé)
- Activer HTTPS si exposition internet (certificat Let's Encrypt)
- Mettre à jour firmware régulièrement
ESPAsyncWiFiManager @ ^0.31 # WiFi Manager
AsyncTCP @ ^3.3.5 # TCP asynchrone ESP32
ESPAsyncWebServer @ ^3.7.1 # Serveur web async
NTPClient @ ^3.2.1 # Synchronisation temps
Dimmable Light for Arduino @ ^2.0.1 # Dimmer TRIAC
DallasTemperature @ ^4.0.4 # DS18B20
U8g2 @ ^2.36.5 # OLED SSD1306
ArduinoJson @ ^7.3.0 # Parsing JSON
AceButton @ ^1.10.1 # Gestion boutons{
"devDependencies": {
"gulp": "^4.0.2",
"gulp-htmlmin": "^5.0.1",
"gulp-gzip": "^1.4.2"
}
}| Version | Date | Modifications |
|---|---|---|
| 1.0.0 | 11/2024 | ✅ Documentation complète 74 fonctions (main.cpp, routeurWeb.cpp, espnow.cpp) |
| ✅ Headers @brief Doxygen pour toutes les fonctions | ||
| ✅ Commentaires inline logique PV surplus/TRIAC/PAC | ||
| ✅ Script improve_comments_routeur.py avec dictionnaire descriptions | ||
| 0.9 | 10/2024 | Ajout ESP-NOW, refonte multi-threading |
| 0.8 | 09/2024 | API SolarEdge HTTPS, certificat SSL |
| 0.7 | 08/2024 | Dimmer TRIAC, zero-crossing, interruptions |
| 0.6 | 07/2024 | AsyncWebServer, SSE, interface web |
| 0.5 | 06/2024 | DS18B20 x4, OLED SSD1306 |
Ludovic Sorriaux
Projet : Routeur Solaire Intelligent (ESP32 + TRIAC)
Projet privé - Tous droits réservés
- PlatformIO Documentation
- ESPAsyncWebServer
- U8g2 OLED
- DallasTemperature
- SolarEdge API
- Dimmable Light for Arduino
En cas de problème :
- Consulter section Troubleshooting ci-dessus
- Vérifier logs moniteur série
- Tester avec
debug = truedansglobalRouteur.h - Vérifier isolation TRIAC (sécurité électrique)
⚡ RAPPEL SÉCURITÉ : Ne jamais intervenir sur le circuit TRIAC sous tension. Couper le disjoncteur avant toute manipulation.