# KC868-A2 — Contrôleur Solaire ESP32

Système embarqué autonome de supervision et de pilotage d'une installation solaire.  
Fonctionne sans internet, sans cloud, sans application mobile — tout passe par une interface web hébergée sur l'ESP32.

---

## Matériel

| Composant | Référence |
|-----------|-----------|
| Contrôleur | **Kincony KC868-A2** (ESP32-WROOM-32) |
| Régulateur solaire | **Epever Tracer 4210N** (MPPT 40A) |
| Communication | RS485 Modbus RTU |
| Interface | RJ45 8P8C côté Epever |

### Brochage RS485

```
Epever RJ45 (vue de face, languette bas)   KC868-A2
  Pin 3 / 4  → RS485-B (D-)              → B−
  Pin 5 / 6  → RS485-A (D+)              → A+
  Pin 7 / 8  → GND                       → GND (optionnel)
  Pin 1 / 2  → +7.5V  ⚠ NE PAS CONNECTER
```

**Paramètres Modbus :** esclave 1, 115200 bps, 8N1, RTU, half-duplex.

### GPIO ESP32

| Signal | Pin | Note |
|--------|-----|------|
| Relais 1 | GPIO 15 | |
| Relais 2 | GPIO 2 | Pin de strapping boot — reste HIGH au démarrage |
| RS485 TX | GPIO 32 | |
| RS485 RX | GPIO 35 | Input only |
| Entrée DI1 | GPIO 36 | Input only |
| Entrée DI2 | GPIO 39 | Input only |

---

## Fonctionnalités

### Acquisition de données (Modbus RTU)
- Tension / courant / puissance panneau solaire (PV)
- Tension / courant / puissance sortie de charge (load)
- Tension, SOC (%), température et statut batterie
- Énergie générée/consommée (jour et total, kWh)
- Détection jour/nuit et horloge interne de l'Epever
- Fréquence de lecture configurable (mode jour / mode nuit)

### Commande
- **2 relais** commandés manuellement ou automatiquement
- **2 entrées numériques** (DI1, DI2) — contacts secs
- État des relais sauvegardé en NVS (survit aux coupures)

### Moteur de règles
Règles JSON stockées dans LittleFS. Chaque règle combine :
- Déclencheurs : ensoleillement (jour/nuit), état DI1/DI2
- Conditions : seuil batterie min/max, seuil PV min/max
- Action : relais ON/OFF avec délai optionnel et hystérésis

### Interface web (mobile-first)
Accessible sur `http://192.168.4.1` (AP) ou `http://pv.local` (mDNS) :

| Onglet | Contenu |
|--------|---------|
| Dashboard | Relais, entrées, PV, batterie, load, énergie |
| Règles | Liste, ajout, activation/désactivation, suppression |
| Config | Relais manuels, noms, sleep, OTA, WiFi, WireGuard VPN |
| Historique | Graphes 4h (1 min) et 30h (5 min) exportables CSV |
| Config EPEVER | Lecture/écriture des 18 registres holding du régulateur |
| Debug | Journal série en mémoire |

### Réseau
- **Mode AP permanent** : `kc868-a2` / `soleil12` → `192.168.4.1`
- **Mode STA optionnel** : connexion à un réseau WiFi existant (scan + NVS)
- **Portail captif** : iOS/Android/Windows ouvrent l'UI automatiquement
- **mDNS** : `pv.local` configurable depuis l'interface

### VPN WireGuard *(optionnel, désactivé par défaut)*
Tunnel chiffré vers un serveur WireGuard distant.  
Configuration complète depuis l'onglet Config — clés, endpoint, IP locale, keepalive.  
Actif uniquement quand la STA WiFi est connectée ; l'accès local via AP reste toujours disponible.

### Deep sleep
Mode économie d'énergie configurable : l'ESP32 se rendort entre les cycles de mesure la nuit (PV < seuil), se réveille périodiquement et évalue les règles.

### OTA
Mise à jour firmware et filesystem via `http://192.168.4.1/update` (ElegantOTA).

---

## Architecture logicielle

```
src/
├── main.cpp            — Init + boucle principale (non bloquante)
├── modbus_epever.cpp   — Lecture RS485 Modbus RTU (FC02/03/04/16), CRC manuel
├── epever_config.cpp   — Lecture/écriture des 18 registres holding de configuration
├── webserver.cpp       — Serveur HTTP async + 30+ routes REST
├── wifi_ap.cpp         — WiFi AP+STA, mDNS, portail captif, reconnexion auto
├── wireguard_vpn.cpp   — Tunnel WireGuard (optionnel)
├── rules.cpp           — Moteur de règles JSON
├── historique.cpp      — Historique RAM (hires 4h) + LittleFS (lores 30h)
├── sleep.cpp           — Deep sleep + restauration état relais
├── buttons.cpp         — Entrées numériques DI1/DI2 avec anti-rebond
├── ota.cpp             — ElegantOTA async
└── debug_log.cpp       — Journal en mémoire (ring buffer)

include/                — Headers correspondants + config.h + state.h

data/                   — Assets web (LittleFS)
├── index.html          — SPA 6 onglets
├── app.js              — Logique JS (~1100 lignes)
└── style.css           — Thème sombre mobile-first
```

**Principe fondamental :** aucun `delay()` ni boucle bloquante.  
Tout le timing passe par `millis()`. Les lectures RS485 ont un timeout court ; en cas d'erreur, le dernier état valide est conservé.

### API REST (sélection)

| Méthode | Endpoint | Description |
|---------|----------|-------------|
| GET | `/api/state` | État système complet (JSON) |
| POST | `/api/relay/1/on` | Relais 1 ON |
| POST | `/api/relay/1/toggle` | Toggle + sauvegarde NVS |
| GET | `/api/rules` | Liste des règles |
| POST | `/api/rules` | Ajout de règle (JSON) |
| GET | `/api/epever/config` | Lecture config EPEVER depuis le régulateur |
| POST | `/api/epever/config` | Écriture config EPEVER |
| GET | `/api/wifi/status` | Statut AP + STA |
| GET | `/api/wifi/scan` | Scan réseaux disponibles |
| POST | `/api/wifi/sta` | Connexion à un réseau WiFi |
| GET | `/api/mdns` | Hostname mDNS courant |
| POST | `/api/mdns` | Modification hostname mDNS |
| GET | `/api/wireguard` | Config et statut WireGuard |
| POST | `/api/wireguard` | Sauvegarde config WireGuard |
| GET | `/api/history/hires` | Historique 4h (1 min/point) |
| GET | `/api/history/csv` | Export CSV (30h) |
| GET | `/api/sleep` | Config deep sleep |
| POST | `/api/modbus` | Intervalles de lecture Modbus |

---

## Bibliothèques utilisées

| Bibliothèque | Version | Rôle |
|-------------|---------|------|
| [ArduinoJson](https://arduinojson.org/) | ^7.0 | Sérialisation JSON |
| [AsyncTCP](https://github.com/me-no-dev/AsyncTCP) | git | TCP non bloquant |
| [ESPAsyncWebServer](https://github.com/me-no-dev/ESPAsyncWebServer) | git | Serveur HTTP async |
| [ElegantOTA](https://github.com/ayushsharma82/ElegantOTA) | ^3.1 | OTA via navigateur |
| [modbus-esp8266](https://github.com/emelianov/modbus-esp8266) | ^4.1 | Modbus RTU (utilisé pour les fonctions bas niveau) |
| [WireGuard-ESP32-Arduino](https://github.com/ciniml/WireGuard-ESP32-Arduino) | git | VPN WireGuard (kc868_a2 uniquement) |

Intégrées dans le framework ESP32 Arduino : `WiFi`, `DNSServer`, `ESPmDNS`, `Preferences` (NVS), `LittleFS`.

---

## Outils

- **[PlatformIO](https://platformio.org/)** — build, flash, monitor
- **Framework** : Arduino pour ESP32 (espressif32)
- **Filesystem** : LittleFS

---

## Build et flash

### Prérequis
- PlatformIO Core ou PlatformIO IDE (VS Code)
- Câble USB pour le premier flash, WiFi pour les suivants (OTA)

### Commandes

```bash
pio run                            # Compiler
pio run -e kc868_a2 --target upload       # Flasher par USB
pio run -e kc868_a2 --target uploadfs     # Flasher le filesystem (LittleFS)
pio run --target monitor           # Moniteur série 115200 bps
pio run -e qemu                    # Build émulateur (sans WireGuard ni WiFi)
```

### OTA (après premier flash)

Ouvrir `http://192.168.4.1/update` (ou `http://pv.local/update`) :
1. Sélectionner **Firmware** → uploader `.pio/build/kc868_a2/firmware.bin`
2. Sélectionner **Filesystem** → uploader `.pio/build/kc868_a2/littlefs.bin`

---

## Configuration

### WiFi AP (compile-time)
Dans `include/config.h` :
```cpp
#define WIFI_SSID     "kc868-a2"
#define WIFI_PASSWORD "soleil12"
```

### WiFi STA (runtime)
Onglet Config → section Connexion WiFi → scanner, choisir le réseau, saisir le mot de passe.

### Connexion WiFi STA + nom mDNS (runtime)
Onglet Config → section Connexion WiFi.

### VPN WireGuard (runtime)
Onglet Config → section VPN WireGuard :
- Coller la clé privée de l'interface ESP32
- Coller la clé publique du serveur
- Renseigner l'endpoint (`mon.domaine.org`) et le port (`51820`)
- IP locale WireGuard (ex. `10.8.0.16`)
- Keepalive recommandé : `25` s pour maintenir le NAT actif
- Activer et sauvegarder

> **Sécurité :** les clés sont stockées dans la NVS flash de l'ESP32. Ne jamais committer le fichier `.conf` WireGuard dans git (il est dans `.gitignore`).

### Deep sleep
Onglet Config → Mode économie d'énergie : activer, régler l'intervalle de réveil et le seuil PV.

---

## Registres Modbus lus

| Registre | Données | Format |
|----------|---------|--------|
| 0x3100 | Tension PV | U16 × 0.01 V |
| 0x3101 | Courant PV | U16 × 0.01 A |
| 0x3104 | Tension batterie | U16 × 0.01 V |
| 0x3105 | Courant batterie | S16 × 0.01 A |
| 0x3106 | Tension load | U16 × 0.01 V |
| 0x3107 | Courant load | U16 × 0.01 A |
| 0x310C | Température batterie | S16 × 0.01 °C |
| 0x311A | SOC batterie | U16 % |
| 0x3201 | Statut batterie | bitfield |
| 0x3302 | Énergie générée jour | U32 × 0.01 kWh |
| 0x3304 | Énergie générée total | U32 × 0.01 kWh |
| 0x3306 | Énergie consommée jour | U32 × 0.01 kWh |
| 0x3308 | Énergie consommée total | U32 × 0.01 kWh |
| 0x200C | État jour/nuit | bitfield |
| 0x9013 | Horloge RTC | 3 × U16 |

---

## TODO

### Fonctionnalités

- [ ] **Publication MQTT** — envoi périodique de l'état système vers un broker MQTT (Home Assistant, Mosquitto…). Topics suggérés : `solar/state`, `solar/relay/1`, etc.
- [ ] **Version firmware dans l'UI** — afficher le numéro de version (défini dans `config.h`) sur le dashboard et dans l'en-tête de l'API `/api/state`
- [ ] **Améliorations interface web** — graphes plein écran, export PDF, notifications push navigateur (PWA), mode nuit/jour automatique de l'UI
- [ ] **Support PSK WireGuard** — la bibliothèque `WireGuard-ESP32-Arduino` n'expose pas encore la preshared key dans son API Arduino ; patcher la lib ou utiliser un fork qui le supporte
- [ ] **Alertes** — notification (email, webhook, MQTT) quand la batterie est basse, le RS485 déconnecté, ou un relais en défaut
- [ ] **Gestion multi-règles avancée** — priorité entre règles, règles avec plage horaire

### Code / qualité

- [ ] **Audit code mort** — vérifier si `modbus-esp8266` est encore utile (le code Modbus utilise du Serial2 raw + CRC manuel depuis la correction du RS485 ; les fonctions de la lib pourraient être inutilisées)
- [ ] **`backup/`** — le dossier est un instantané manuel ; le supprimer du repo une fois que l'historique git remplit ce rôle
- [ ] **Secrets compile-time** — `WIFI_PASSWORD` et `OTA_PASSWORD` dans `config.h` en clair ; les déplacer dans un fichier `secrets.h` exclu du repo
- [ ] **Tests unitaires** — couvrir le moteur de règles et le décodage Modbus avec des tests PlatformIO (dossier `test/`)
- [ ] **Emulateur QEMU** — vérifier que le build `qemu` et les scripts Python du dossier `emulator/` sont encore synchronisés avec l'état actuel du firmware

---

## Licence

Usage personnel — aucune licence définie.
