# 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

---
