Initial commit — KC868-A2 contrôleur solaire ESP32
Fonctionnalités : - Lecture RS485 Modbus Epever Tracer 4210N (115200 bps, FC03/FC04/FC16) - Moteur de règles JSON (LittleFS) — commande automatique des relais - Interface web mobile-first (dashboard, règles, config, historique, EPEVER, debug) - WiFi AP+STA simultanés avec reconnexion automatique et portail captif - mDNS configurable (pv.local par défaut) - Configuration registres EPEVER depuis l'UI (18 registres holding) - Historique basse/haute résolution avec graphes canvas - VPN WireGuard optionnel (désactivé par défaut, config via UI) - OTA firmware + filesystem via ElegantOTA - Deep sleep / économie d'énergie Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+572
@@ -0,0 +1,572 @@
|
||||
# consigne.md
|
||||
|
||||
# Projet : Contrôleur solaire autonome KC868-A2 + Epever 4210N
|
||||
|
||||
---
|
||||
|
||||
## Rôle de l'assistant
|
||||
|
||||
Tu es un expert en développement d'objets IoT ESP32 avec PlatformIO.
|
||||
Tu emploieras le français pour la discussion. Les commentaires de code seront en français.
|
||||
|
||||
---
|
||||
|
||||
# 1. Objectif du projet
|
||||
|
||||
Développer un système autonome basé sur une carte Kincony KC868-A2 (ESP32) permettant :
|
||||
|
||||
* Lecture des données d'un régulateur solaire Epever 4210N via RS485 Modbus
|
||||
* Pilotage de 2 relais
|
||||
* Gestion de 2 boutons via contacts secs DI1 / DI2
|
||||
* Hébergement d'une interface web responsive adaptée smartphone
|
||||
* Fonctionnement autonome via point d'accès WiFi ESP32
|
||||
* Mise à jour firmware OTA via interface web
|
||||
* Moteur de règles programmable depuis l'interface web
|
||||
* Gestion d'un mode économie d'énergie / sleep
|
||||
* Système robuste tolérant aux erreurs RS485
|
||||
|
||||
Le système doit fonctionner sans internet.
|
||||
|
||||
---
|
||||
|
||||
# 2. Hardware utilisé
|
||||
|
||||
## Carte principale
|
||||
|
||||
* Kincony KC868-A2
|
||||
* ESP32-WROOM-32E intégré
|
||||
* 2 relais
|
||||
* RS485 intégré (MAX13487 — direction automatique, pas de pin DE/RE à gérer)
|
||||
* Entrées contacts secs DI1 / DI2
|
||||
* Alimentation 12V
|
||||
|
||||
## Régulateur solaire
|
||||
|
||||
* Epever Tracer 4210N
|
||||
* Communication Modbus RS485 via RJ45
|
||||
|
||||
---
|
||||
|
||||
# 3. GPIO KC868-A2
|
||||
|
||||
Extraits du schéma bloc officiel de la carte :
|
||||
|
||||
| Fonction | GPIO | Notes |
|
||||
| -------------- | ----- | ---------------------------------------------- |
|
||||
| Relay 1 | GPIO15 | Actif haut |
|
||||
| Relay 2 | GPIO2 | ⚠️ Pin de boot — doit être HIGH au démarrage |
|
||||
| RS485 TXD | GPIO32 | Vers MAX13487 |
|
||||
| RS485 RXD | GPIO35 | Depuis MAX13487 (input only) |
|
||||
| DI1 | GPIO36 | Input only, pull-up interne |
|
||||
| DI2 | GPIO39 | Input only, pull-up interne |
|
||||
| SDA (I2C) | GPIO4 | Disponible si besoin |
|
||||
| SCL (I2C) | GPIO5 | Disponible si besoin |
|
||||
| 1-Wire / DTH1 | GPIO27 | Disponible si besoin |
|
||||
| DTH2 / LED | GPIO26 | Disponible si besoin |
|
||||
|
||||
⚠️ **GPIO2** : relais 2 est sur ce pin de boot. Le relais ne doit pas forcer GPIO2 à LOW au démarrage sous peine de bloquer l'ESP32 en mode flash.
|
||||
|
||||
⚠️ **GPIO35 et GPIO36 / GPIO39** : ces pins sont en entrée uniquement (input-only) sur l'ESP32 — pas de sortie possible.
|
||||
|
||||
Tous les GPIO sont en logique 3,3V.
|
||||
|
||||
---
|
||||
|
||||
# 4. Développement logiciel
|
||||
|
||||
## Environnement
|
||||
|
||||
* Visual Studio Code
|
||||
* PlatformIO
|
||||
* Framework Arduino ESP32
|
||||
|
||||
## Langages
|
||||
|
||||
* C++
|
||||
* HTML / CSS / JavaScript
|
||||
|
||||
---
|
||||
|
||||
# 5. Configuration PlatformIO
|
||||
|
||||
## platformio.ini
|
||||
|
||||
```ini
|
||||
[env:kc868_a2]
|
||||
platform = espressif32
|
||||
board = esp32dev
|
||||
framework = arduino
|
||||
monitor_speed = 115200
|
||||
board_build.filesystem = littlefs
|
||||
|
||||
lib_deps =
|
||||
bblanchon/ArduinoJson
|
||||
emelianov/modbus-esp8266
|
||||
me-no-dev/ESPAsyncWebServer
|
||||
me-no-dev/AsyncTCP
|
||||
ayushsharma82/AsyncElegantOTA
|
||||
```
|
||||
|
||||
## Commandes utiles
|
||||
|
||||
```bash
|
||||
pio run # Compiler
|
||||
pio run --target upload # Compiler et flasher
|
||||
pio run --target monitor # Moniteur série
|
||||
pio run --target uploadfs # Uploader le filesystem LittleFS (/data)
|
||||
pio run --target clean # Nettoyer le build
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 6. Architecture générale
|
||||
|
||||
```text
|
||||
Epever 4210N
|
||||
│
|
||||
└── RS485 (Modbus RTU) ──► ESP32 KC868-A2
|
||||
│
|
||||
┌───────────┼───────────┐
|
||||
▼ ▼ ▼
|
||||
Moteur règles Relais 1/2 Boutons DI1/DI2
|
||||
│
|
||||
▼
|
||||
Interface Web (WiFi AP 192.168.4.1)
|
||||
│
|
||||
▼
|
||||
Smartphone utilisateur
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 7. Ordre de développement validé
|
||||
|
||||
## Étape 1 — WiFi + Interface Web + OTA
|
||||
|
||||
* Point d'accès WiFi actif
|
||||
* Serveur web async avec pages statiques depuis LittleFS
|
||||
* OTA via `/update`
|
||||
|
||||
## Étape 2 — Pilotage relais
|
||||
|
||||
* API REST ON/OFF relais 1 et 2
|
||||
* Retour état relais dans le dashboard
|
||||
|
||||
## Étape 3 — Gestion boutons DI1 / DI2
|
||||
|
||||
* Lecture non-bloquante avec anti-rebond
|
||||
* DI1 : bascule mode auto / manuel
|
||||
* DI2 : commande manuelle relais
|
||||
|
||||
## Étape 4 — Lecture RS485 Epever
|
||||
|
||||
* Initialisation Serial2 (GPIO32 TX, GPIO35 RX)
|
||||
* Lecture périodique non-bloquante des registres Epever
|
||||
* Mise à jour du `SystemState` global
|
||||
|
||||
## Étape 5 — Moteur de règles
|
||||
|
||||
* Chargement règles depuis LittleFS (`rules.json`)
|
||||
* Évaluation périodique des conditions
|
||||
* Application des actions sur les relais
|
||||
|
||||
## Étape 6 — Gestion sleep / économie d'énergie
|
||||
|
||||
* Détection mode JOUR / NUIT via état Epever
|
||||
* Deep sleep la nuit, réveil périodique
|
||||
|
||||
---
|
||||
|
||||
# 8. Câblage
|
||||
|
||||
## 8.1 RS485
|
||||
|
||||
```text
|
||||
EPEVER RS485 A (D+) ───── KC868-A2 A1 / RXI
|
||||
EPEVER RS485 B (D-) ───── KC868-A2 B1 / TXO
|
||||
EPEVER GND ───── KC868-A2 GND (recommandé)
|
||||
```
|
||||
|
||||
⚠️ Ne jamais connecter une alimentation provenant du RJ45 Epever.
|
||||
|
||||
## 8.2 Boutons (contacts secs)
|
||||
|
||||
```text
|
||||
DI1 ----[Bouton]---- GND
|
||||
DI2 ----[Bouton]---- GND
|
||||
```
|
||||
|
||||
* DI1 : mode auto / manuel
|
||||
* DI2 : commande manuelle relais
|
||||
|
||||
## 8.3 Relais
|
||||
|
||||
* Relay 1 (GPIO15) → charge 1
|
||||
* Relay 2 (GPIO2) → charge 2
|
||||
|
||||
---
|
||||
|
||||
# 9. WiFi
|
||||
|
||||
## Mode point d'accès (AP)
|
||||
|
||||
```text
|
||||
SSID : KC868_SOLAR
|
||||
IP : 192.168.4.1
|
||||
```
|
||||
|
||||
Le smartphone se connecte directement à la carte, sans routeur ni internet.
|
||||
|
||||
---
|
||||
|
||||
# 10. Interface Web
|
||||
|
||||
## Objectifs
|
||||
|
||||
* Responsive smartphone (mobile-first)
|
||||
* Interface légère — pas de framework lourd (pas de React, Vue, etc.)
|
||||
* Mise à jour dynamique par fetch JSON sans rechargement de page
|
||||
* Vanilla JS + CSS simple
|
||||
|
||||
## Sections
|
||||
|
||||
```text
|
||||
[ Dashboard ]
|
||||
- Tension batterie (V)
|
||||
- Tension PV (V)
|
||||
- Courant PV (A)
|
||||
- Etat jour / nuit
|
||||
- Etat relais 1 et 2
|
||||
- Etat RS485 (OK / erreur)
|
||||
|
||||
[ Commandes ]
|
||||
- ON/OFF relais 1 et 2
|
||||
- Bascule mode auto / manuel
|
||||
|
||||
[ Programmation ]
|
||||
- Liste des règles actives
|
||||
- Ajout / suppression règle
|
||||
|
||||
[ Configuration ]
|
||||
- Activation sleep
|
||||
- Seuils batterie
|
||||
- Intervalle de lecture RS485
|
||||
- Temporisations règles
|
||||
|
||||
[ Firmware ]
|
||||
- OTA update (upload .bin)
|
||||
|
||||
[ Debug ]
|
||||
- Logs série
|
||||
- Erreurs RS485
|
||||
- Etats DI1 / DI2
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 11. OTA (mise à jour firmware)
|
||||
|
||||
```text
|
||||
http://192.168.4.1/update
|
||||
```
|
||||
|
||||
* Upload fichier `.bin` depuis l'interface web
|
||||
* Redémarrage automatique après mise à jour
|
||||
* Protection par mot de passe
|
||||
|
||||
---
|
||||
|
||||
# 12. Communication Modbus Epever
|
||||
|
||||
## Paramètres
|
||||
|
||||
* Baudrate : 9600
|
||||
* Adresse esclave : 1
|
||||
* Modbus RTU (half-duplex)
|
||||
* Serial2 : TX=GPIO32, RX=GPIO35
|
||||
|
||||
## Registres utiles
|
||||
|
||||
| Donnée | Registre | Unité | Facteur |
|
||||
| --------------------- | -------- | ----------- | ------- |
|
||||
| Tension PV | 0x3100 | V | ÷100 |
|
||||
| Courant PV | 0x3101 | A | ÷100 |
|
||||
| Puissance PV | 0x3102 | W | ÷100 |
|
||||
| Tension batterie | 0x3104 | V | ÷100 |
|
||||
| Courant de charge | 0x3106 | A | ÷100 |
|
||||
| Etat jour/nuit | 0x200C | bit 0 = nuit | — |
|
||||
| Etat charge batterie | 0x3201 | bits | — |
|
||||
|
||||
## Timing de lecture
|
||||
|
||||
* Intervalle recommandé : toutes les 5 secondes
|
||||
* Timeout réponse : 200 ms maximum
|
||||
* Retry : 2 tentatives maximum
|
||||
|
||||
---
|
||||
|
||||
# 13. Gestion des erreurs RS485
|
||||
|
||||
## Règle critique
|
||||
|
||||
Une erreur RS485 ne doit **jamais** bloquer :
|
||||
|
||||
* le serveur web
|
||||
* le pilotage relais
|
||||
* les boutons
|
||||
* le moteur de règles
|
||||
|
||||
## Interdit
|
||||
|
||||
```cpp
|
||||
// JAMAIS de boucle bloquante
|
||||
while (!response) { }
|
||||
delay(x);
|
||||
```
|
||||
|
||||
## Comportement attendu
|
||||
|
||||
* Lecture périodique pilotée par `millis()`
|
||||
* Timeout court (200 ms)
|
||||
* Maximum 2 retries
|
||||
* En cas d'échec : conserver la dernière valeur valide, passer `rs485_ok = false`
|
||||
* L'interface web affiche l'état RS485
|
||||
|
||||
---
|
||||
|
||||
# 14. Etat système global
|
||||
|
||||
```cpp
|
||||
struct SystemState {
|
||||
// Données Epever
|
||||
float battery; // Tension batterie (V)
|
||||
float pv; // Tension PV (V)
|
||||
float pvCurrent; // Courant PV (A)
|
||||
bool sun; // true = jour
|
||||
|
||||
// Relais
|
||||
bool relay1;
|
||||
bool relay2;
|
||||
|
||||
// Boutons
|
||||
bool di1;
|
||||
bool di2;
|
||||
|
||||
// Santé RS485
|
||||
bool rs485_ok;
|
||||
unsigned long last_update; // millis() de la dernière lecture OK
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 15. Pilotage relais
|
||||
|
||||
* ON/OFF manuel depuis l'interface web
|
||||
* Pilotage automatique via moteur de règles
|
||||
* Pilotage manuel via boutons DI1/DI2
|
||||
* État affiché en temps réel dans le dashboard
|
||||
|
||||
---
|
||||
|
||||
# 16. Boutons DI1 / DI2
|
||||
|
||||
* Anti-rebond logiciel (50 ms)
|
||||
* DI1 : bascule mode auto ↔ manuel
|
||||
* DI2 : en mode manuel, toggle relais 1
|
||||
* En mode auto : les règles reprennent le contrôle
|
||||
|
||||
---
|
||||
|
||||
# 17. Moteur de règles
|
||||
|
||||
## Structure logique
|
||||
|
||||
```text
|
||||
SI (conditions)
|
||||
ALORS (action)
|
||||
AVEC (délai optionnel en secondes)
|
||||
```
|
||||
|
||||
## Conditions possibles
|
||||
|
||||
* Soleil (jour/nuit)
|
||||
* Batterie > seuil
|
||||
* Batterie < seuil
|
||||
* Etat relais
|
||||
* Etat DI
|
||||
|
||||
## Actions possibles
|
||||
|
||||
* Activer relais 1 ou 2
|
||||
* Désactiver relais 1 ou 2
|
||||
|
||||
## Exemple règle
|
||||
|
||||
```text
|
||||
SI soleil ET batterie > 13V → ALORS relais1 ON
|
||||
SI soleil ET batterie > 13V → ATTENDRE 3600s → ALORS relais2 ON
|
||||
```
|
||||
|
||||
## Format JSON (stocké dans LittleFS `rules.json`)
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"enabled": true,
|
||||
"sun": true,
|
||||
"battery_min": 13.0,
|
||||
"battery_max": 0,
|
||||
"relay": 1,
|
||||
"state": true,
|
||||
"delay": 0
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
| Champ | Type | Description |
|
||||
| ------------- | ------- | ----------------------------------------- |
|
||||
| `id` | int | Identifiant unique |
|
||||
| `enabled` | bool | Règle active ou non |
|
||||
| `sun` | bool | Condition soleil (true=jour, false=nuit) |
|
||||
| `battery_min` | float | Seuil min batterie en V (0 = ignoré) |
|
||||
| `battery_max` | float | Seuil max batterie en V (0 = ignoré) |
|
||||
| `relay` | int | Numéro relais (1 ou 2) |
|
||||
| `state` | bool | true = ON, false = OFF |
|
||||
| `delay` | int | Délai avant action (secondes) |
|
||||
|
||||
## Timing des règles
|
||||
|
||||
```cpp
|
||||
// Évaluation pilotée par millis(), jamais par delay()
|
||||
if (millis() - lastRuleEval > RULE_INTERVAL_MS) {
|
||||
evaluerRegles();
|
||||
lastRuleEval = millis();
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 18. Gestion de l'énergie
|
||||
|
||||
## Mode JOUR
|
||||
|
||||
```text
|
||||
WiFi ON
|
||||
Serveur web ON
|
||||
Lecture Epever active (toutes les 5s)
|
||||
Règles actives
|
||||
```
|
||||
|
||||
## Mode NUIT
|
||||
|
||||
```text
|
||||
WiFi OFF
|
||||
Serveur web OFF
|
||||
Deep sleep
|
||||
Réveil périodique (configurable, ex: 10 min)
|
||||
Lecture Epever au réveil
|
||||
Si toujours nuit → retour deep sleep
|
||||
```
|
||||
|
||||
## Paramètres configurables
|
||||
|
||||
* Activation / désactivation du sleep
|
||||
* Intervalle de réveil (secondes)
|
||||
* Seuil de détection soleil (valeur registre 0x200C)
|
||||
* Mode : deep sleep ou light sleep
|
||||
|
||||
## Contraintes deep sleep
|
||||
|
||||
* Deep sleep = reboot complet de l'ESP32
|
||||
* UART indisponible pendant le sleep
|
||||
* Les relais conservent leur état (verrouillage mécanique)
|
||||
* Variables RAM perdues au réveil → sauvegarder en RTC memory si besoin
|
||||
|
||||
---
|
||||
|
||||
# 19. Consommation estimée
|
||||
|
||||
| Mode | Consommation |
|
||||
| ------ | ------------ |
|
||||
| Normal | 1.8 à 4 W |
|
||||
| Sleep | 0.1 à 0.6 W |
|
||||
|
||||
---
|
||||
|
||||
# 20. Structure projet PlatformIO
|
||||
|
||||
```text
|
||||
/src
|
||||
main.cpp ← boucle principale, init, dispatcher
|
||||
wifi.cpp ← AP WiFi
|
||||
webserver.cpp ← serveur HTTP async, endpoints REST
|
||||
ota.cpp ← mise à jour OTA
|
||||
modbus.cpp ← lecture RS485 Epever non-bloquante
|
||||
relais.cpp ← contrôle GPIO relais
|
||||
rules.cpp ← évaluation moteur de règles
|
||||
sleep.cpp ← gestion deep sleep
|
||||
buttons.cpp ← lecture DI1/DI2 avec anti-rebond
|
||||
|
||||
/include
|
||||
config.h ← constantes GPIO, SSID, intervalles
|
||||
state.h ← déclaration SystemState
|
||||
|
||||
/data
|
||||
index.html ← interface web principale
|
||||
style.css
|
||||
app.js ← fetch JSON, mise à jour dynamique
|
||||
rules.json ← règles persistées (LittleFS)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 21. Contraintes de conception
|
||||
|
||||
## Non-bloquant partout
|
||||
|
||||
Toute la logique doit utiliser `millis()` et des machines à états. Jamais de `delay()` ni de boucle d'attente.
|
||||
|
||||
## RS485 half-duplex
|
||||
|
||||
* Le MAX13487 gère automatiquement la direction (pas de pin DE)
|
||||
* Ne pas lire trop fréquemment : respecter l'intervalle de 5 secondes
|
||||
* Bien gérer le timeout de 200 ms par requête Modbus
|
||||
|
||||
## GPIO2 (Relay 2)
|
||||
|
||||
* GPIO2 est un pin de strapping de boot sur ESP32
|
||||
* S'assurer que le relais ne tire pas GPIO2 à LOW au démarrage
|
||||
|
||||
## LittleFS
|
||||
|
||||
* Le filesystem doit être uploadé séparément : `pio run --target uploadfs`
|
||||
* La partition LittleFS doit être configurée dans `platformio.ini`
|
||||
|
||||
---
|
||||
|
||||
# 22. Evolutions possibles
|
||||
|
||||
* MQTT / Home Assistant
|
||||
* Historique graphique (Chart.js)
|
||||
* Mode STA (connexion au routeur existant)
|
||||
* Accès distant VPN
|
||||
* Conditions avancées ET / OU dans les règles
|
||||
* Gestion des priorités entre règles
|
||||
* Notifications push
|
||||
|
||||
---
|
||||
|
||||
# 23. Objectif final
|
||||
|
||||
Le système final doit être :
|
||||
|
||||
* **autonome** — fonctionne sans internet ni serveur externe
|
||||
* **robuste** — tolérant aux erreurs RS485 et aux redémarrages
|
||||
* **configurable** — réglable depuis un smartphone
|
||||
* **optimisé énergie** — mode sleep la nuit
|
||||
* **extensible** — architecture modulaire facilitant les ajouts
|
||||
* **maintenable** — code commenté en français, structure claire
|
||||
|
||||
---
|
||||
Reference in New Issue
Block a user