Compare commits
7 Commits
a8f0d6ccba
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| c8ea5deb3d | |||
| 14b3967590 | |||
| 11559de21e | |||
| a53cb124d1 | |||
| 17bb1d3aab | |||
| 6315da85e4 | |||
| df350c10f5 |
+1
-1
@@ -15,5 +15,5 @@ backup/firmware/
|
||||
emulator/firmware/
|
||||
|
||||
# WireGuard — fichier de configuration contenant la clé privée
|
||||
kc868-a2.conf
|
||||
#kc868-a2.conf
|
||||
*.conf
|
||||
|
||||
@@ -0,0 +1,419 @@
|
||||
# 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
|
||||
```
|
||||
|
||||
| Vue d'ensemble | Port RS485 | Port Ethernet |
|
||||
|:-:|:-:|:-:|
|
||||
|  |  |  |
|
||||
|
||||

|
||||
|
||||
**Documentation technique :**
|
||||
- [KC868-A2 Schematic](KC868-A2-schematic.pdf) — schéma électronique complet de la carte
|
||||
- [Epever Modbus Protocol v2.5](MODBUS-Protocol-v25.pdf) — registres et protocole de communication du régulateur
|
||||
|
||||
**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`).
|
||||
|
||||
#### Exemple de fichier `.conf` (généré par wg-easy ou `wg` CLI)
|
||||
|
||||
```ini
|
||||
[Interface]
|
||||
PrivateKey = <clé privée ESP32 — générer avec : wg genkey>
|
||||
Address = 10.8.0.x/24
|
||||
MTU = 1420
|
||||
|
||||
[Peer]
|
||||
PublicKey = <clé publique du serveur WireGuard>
|
||||
PresharedKey = <optionnel — générer avec : wg genpsk>
|
||||
AllowedIPs = 0.0.0.0/0
|
||||
PersistentKeepalive = 25
|
||||
Endpoint = mon.domaine.org:51820
|
||||
```
|
||||
|
||||
**Générer une paire de clés :**
|
||||
```bash
|
||||
# Clé privée
|
||||
wg genkey | tee privatekey
|
||||
|
||||
# Clé publique correspondante
|
||||
cat privatekey | wg pubkey
|
||||
|
||||
# Clé pré-partagée (optionnel)
|
||||
wg genpsk
|
||||
```
|
||||
|
||||
> **Note :** la bibliothèque `WireGuard-ESP32-Arduino` n'expose pas encore la `PresharedKey` dans son API Arduino. Le tunnel fonctionne sans PSK — configurer le peer serveur en conséquence ou attendre un patch de la lib.
|
||||
|
||||
### 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 |
|
||||
|
||||
---
|
||||
|
||||
## Résolution des problèmes RS485
|
||||
|
||||
La communication avec l'Epever a nécessité plusieurs corrections non documentées dans les projets existants. Ce résumé évite de retomber dans les mêmes pièges.
|
||||
|
||||
### Problème 1 — Baudrate incorrect (bloquant)
|
||||
|
||||
L'Epever Tracer 4210N communique à **115 200 bps** et non à 9 600 bps comme indiqué par défaut dans la plupart des bibliothèques et exemples en ligne. Aucune réponse RS485 n'est obtenue tant que le baudrate est incorrect.
|
||||
|
||||
**Correction :** `#define MODBUS_BAUDRATE 115200` et une sonde de démarrage (`probeRegistreBatterie()`) qui tente 9 600 puis 115 200 et confirme la réponse par CRC.
|
||||
|
||||
### Problème 2 — Bibliothèque ModbusRTU inadaptée
|
||||
|
||||
La bibliothèque `modbus-esp8266` avec ses callbacks asynchrones gérait mal le timing half-duplex à 115 200 bds : trames corrompues, échos non consommés, timeouts aléatoires.
|
||||
|
||||
**Correction :** abandon du mode callback, passage en **Serial2 direct** avec CRC Modbus calculé manuellement. La bibliothèque reste uniquement pour initialiser l'UART (`mb.begin()` / `mb.master()`).
|
||||
|
||||
### Problème 3 — Buffer RX pollué entre requêtes
|
||||
|
||||
Sans purge, les octets résiduels d'une réponse précédente (ou du bruit de ligne) polluaient la trame suivante et généraient des CRC invalides en cascade.
|
||||
|
||||
**Correction :** appel systématique de `viderRx()` avant chaque envoi — lit et journalise tous les octets présents dans le FIFO Serial2.
|
||||
|
||||
### Problème 4 — Registres énergie mal adressés
|
||||
|
||||
La plupart des projets open source lisent l'énergie à partir de `0x3300`. Le protocole Modbus v2.5 d'Epever indique que les compteurs kWh démarrent à **`0x3302`** (génération du jour) — décalage de 2 registres sur tous les offsets.
|
||||
|
||||
### Problème 5 — Puissance charge sur 32 bits
|
||||
|
||||
Le registre `0x310E` (puissance sortie load) est sur **32 bits** (word L + word H en `0x310F`). Une lecture 16 bits donnait des valeurs erronées dès que la puissance dépassait 655 W.
|
||||
|
||||
### Problème 6 — Détection jour/nuit incohérente
|
||||
|
||||
Le registre FC02 `0x200C` retourne parfois `Nuit` alors que le panneau produit clairement (transition rapide matin/soir).
|
||||
|
||||
**Correction :** logique hybride — `sun = !nuit_registre || pv > 2.0 V`. Si le panneau produit plus de 2 V, on considère qu'il fait jour quel que soit le registre.
|
||||
|
||||
### Tableau récapitulatif
|
||||
|
||||
| Problème | Cause racine | Correction appliquée |
|
||||
|----------|-------------|---------------------|
|
||||
| Aucune réponse | Baudrate 9 600 au lieu de 115 200 | `MODBUS_BAUDRATE 115200` + sonde boot |
|
||||
| Trames corrompues | Buffer RX non purgé | `viderRx()` avant chaque trame |
|
||||
| Timeouts aléatoires | Lib asynchrone inadaptée | Serial2 direct + CRC manuel |
|
||||
| Énergie kWh = 0 | Base registre `0x3300` au lieu de `0x3302` | Correction offsets (doc v2.5) |
|
||||
| Puissance load erronée | Lecture 16 bits au lieu de 32 bits | `(reg[3] << 16 \| reg[2]) × 0.01` |
|
||||
| Soleil incohérent | FC02 seul, pas de fallback | Hybride : registre + tension PV |
|
||||
|
||||
> Le détail complet avec les extraits de code est dans [`debug_rs485.md`](debug_rs485.md).
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
## Ressources et liens utiles
|
||||
|
||||
### Matériel — KC868-A2
|
||||
|
||||
| Ressource | Lien |
|
||||
|-----------|------|
|
||||
| Page produit officielle | [kincony.com — KC868-A2](https://www.kincony.com/esp32-4g-relay.html) |
|
||||
| Forum KinCony | [forum.kincony.com](https://www.kincony.com/forum/) |
|
||||
| GitHub KinCony (officiel) | [github.com/hzkincony](https://github.com/hzkincony) |
|
||||
| Profil ESPHome | [devices.esphome.io — KC868-A2](https://devices.esphome.io/devices/kincony-kc868-a2/) |
|
||||
| Template Tasmota | [templates.blakadder.com — KC868-A2](https://templates.blakadder.com/kincony_KC868-A2.html) |
|
||||
| Scripts démo Arduino | [github.com/playfultechnology/kincony](https://github.com/playfultechnology/kincony) |
|
||||
|
||||
### Matériel — Epever Tracer
|
||||
|
||||
| Ressource | Lien |
|
||||
|-----------|------|
|
||||
| Documentation Modbus v2.5 | [MODBUS-Protocol-v25.pdf](MODBUS-Protocol-v25.pdf) *(inclus dans ce repo)* |
|
||||
| Documentation Tracer-AN Series | [epever.com — Tracer-AN v1.0](https://www.epever.com/upload/cert/file/1811/Tracer-AN-SMS-EL-V1.0.pdf) |
|
||||
| Registres Modbus — discussion avancée | [diysolarforum.com — Epever registers](https://diysolarforum.com/threads/epever-tracer-modbus-registers-digging-deeper.108305/) |
|
||||
| Carte complète des registres (Python) | [github.com/kasbert/epsolar-tracer](https://github.com/kasbert/epsolar-tracer/blob/master/pyepsolartracer/registers.py) |
|
||||
| Intégration Home Assistant | [community.home-assistant.io — Epever Modbus](https://community.home-assistant.io/t/epever-modbus-rs-485-config/214397) |
|
||||
| Topics GitHub `epever` | [github.com/topics/epever](https://github.com/topics/epever) |
|
||||
|
||||
### Projets similaires (ESP32 + Epever + Modbus)
|
||||
|
||||
| Projet | Description |
|
||||
|--------|-------------|
|
||||
| [Solar-Tracer-Blynk-V3](https://github.com/Bettapro/Solar-Tracer-Blynk-V3) | ESP32 + Epever RS485 → Blynk / Home Assistant / MQTT |
|
||||
| [Tracer-RS485-Modbus-Blynk-V2](https://github.com/tekk/Tracer-RS485-Modbus-Blynk-V2) | ESP8266 + Epever RS485 → Blynk (version remaniée) |
|
||||
| [Tracer-RS485-Modbus-CustomAPI](https://github.com/DeltaLima/Tracer-RS485-Modbus-CustomAPI) | ESP8266 + Epever RS485 → API personnalisée |
|
||||
| [EPSolar_Tracer](https://github.com/alexnathanson/EPSolar_Tracer) | Plusieurs implémentations de communication avec les Tracer |
|
||||
|
||||
### WireGuard sur ESP32
|
||||
|
||||
| Ressource | Lien |
|
||||
|-----------|------|
|
||||
| Bibliothèque utilisée | [github.com/ciniml/WireGuard-ESP32-Arduino](https://github.com/ciniml/WireGuard-ESP32-Arduino) |
|
||||
| Référence Arduino | [arduino.cc — WireGuard-ESP32](https://www.arduino.cc/reference/en/libraries/wireguard-esp32/) |
|
||||
| Guide démarrage (DeepWiki) | [deepwiki.com — Getting Started](https://deepwiki.com/ciniml/WireGuard-ESP32-Arduino/2-getting-started) |
|
||||
| Exemple avec interface web | [github.com/andr13/ESP32-Web-WireGuard](https://github.com/andr13/ESP32-Web-WireGuard) |
|
||||
|
||||
### Bibliothèques et outils
|
||||
|
||||
| Ressource | Lien |
|
||||
|-----------|------|
|
||||
| PlatformIO | [platformio.org](https://platformio.org/) |
|
||||
| ESPAsyncWebServer | [github.com/me-no-dev/ESPAsyncWebServer](https://github.com/me-no-dev/ESPAsyncWebServer) |
|
||||
| ElegantOTA | [github.com/ayushsharma82/ElegantOTA](https://github.com/ayushsharma82/ElegantOTA) |
|
||||
| ArduinoJson | [arduinojson.org](https://arduinojson.org/) |
|
||||
| modbus-esp8266 | [github.com/emelianov/modbus-esp8266](https://github.com/emelianov/modbus-esp8266) |
|
||||
|
||||
---
|
||||
|
||||
## Licence
|
||||
|
||||
Usage personnel — aucune licence définie.
|
||||
@@ -0,0 +1,10 @@
|
||||
- sauvegarde du dev de l app sur mon serveur gitea : donne moi les consigne a taper dans le terminal pour uploader ce dossier dans mon serveur gitea (ensuite : https://gitea.maison43.duckdns.org/gilles/kc868_a2)
|
||||
- analyse les dossier et fichier et parametre le gitignore
|
||||
- le code actuel est fonctionnel, analyse le et remet a jours le readme.md
|
||||
|
||||
ensuite:
|
||||
- ajout la possibilite d 'envoyer les info des sensor sur un serveur mqtt (default: 192.168.1.36 port 1883)
|
||||
- dans config ajouter les option de parametrage d envoie et de reception mqtt : adresse serveur mqtt, port , user passward, si vide pas de user et de passwaord ( reglage par defaut) topic d envoie des sensor, topic d abonnement pour piloter les relai,
|
||||
- il y a ausssi les 2 input aussi a integrer
|
||||
- utilise un topic et des value clair base sur le nom des sensor
|
||||
- option pour regler la frequence d'envoir de message
|
||||
+82
-1
@@ -51,7 +51,7 @@ function afficherOnglet(nom, bouton) {
|
||||
document.getElementById(nom).classList.add('actif');
|
||||
bouton.classList.add('active');
|
||||
if (nom === 'regles') chargerRegles();
|
||||
if (nom === 'config') { chargerSleep(); chargerWifi(); chargerPrefsUI(); chargerModbus(); chargerWireGuard(); }
|
||||
if (nom === 'config') { chargerSleep(); chargerWifi(); chargerPrefsUI(); chargerModbus(); chargerMqtt(); chargerWireGuard(); }
|
||||
if (nom === 'historique') chargerHistorique();
|
||||
if (nom === 'debug') chargerDebug();
|
||||
if (nom === 'epever-config') lireConfigEpever();
|
||||
@@ -848,6 +848,87 @@ function fermerSunPopup() {
|
||||
if (modal) modal.classList.add('hidden');
|
||||
}
|
||||
|
||||
// --- MQTT ---
|
||||
|
||||
function mqttAfficherTopics(base) {
|
||||
const info = document.getElementById('mqtt-topics-info');
|
||||
const pub = document.getElementById('mqtt-topics-list');
|
||||
const cmd = document.getElementById('mqtt-cmd-list');
|
||||
if (!info || !pub || !cmd) return;
|
||||
const b = base || 'solar';
|
||||
const pubTopics = [
|
||||
'pv/voltage', 'pv/current',
|
||||
'battery/voltage', 'battery/soc', 'battery/temperature', 'battery/status',
|
||||
'load/voltage', 'load/current', 'load/power',
|
||||
'energy/generated/today', 'energy/generated/total',
|
||||
'energy/consumed/today', 'energy/consumed/total',
|
||||
'sun', 'rs485/ok', 'relay/1', 'relay/2', 'input/1', 'input/2'
|
||||
];
|
||||
pub.innerHTML = pubTopics.map(t => `<code>${b}/${t}</code>`).join(' ');
|
||||
cmd.innerHTML = `<code>${b}/relay/1/set</code> <code>${b}/relay/2/set</code>` +
|
||||
`<br><small style="color:var(--muted)">Valeurs acceptées : ON / OFF</small>`;
|
||||
info.classList.remove('hidden');
|
||||
}
|
||||
|
||||
async function chargerMqtt() {
|
||||
try {
|
||||
const d = await (await fetch('/api/mqtt')).json();
|
||||
const bar = document.getElementById('mqtt-status-bar');
|
||||
if (bar) {
|
||||
if (d.enabled && d.connected) {
|
||||
bar.textContent = '✓ Connecté — ' + d.server + ':' + d.port;
|
||||
bar.className = 'ec-statusbar ec-ok';
|
||||
} else if (d.enabled) {
|
||||
bar.textContent = '⏳ Activé — en attente de connexion WiFi ou broker';
|
||||
bar.className = 'ec-statusbar';
|
||||
} else {
|
||||
bar.textContent = 'Désactivé';
|
||||
bar.className = 'ec-statusbar';
|
||||
}
|
||||
}
|
||||
const s = id => document.getElementById(id);
|
||||
s('mqtt-enabled').value = String(!!d.enabled);
|
||||
if (s('mqtt-server')) s('mqtt-server').value = d.server || '192.168.1.36';
|
||||
if (s('mqtt-port')) s('mqtt-port').value = d.port || 1883;
|
||||
if (s('mqtt-user')) s('mqtt-user').value = d.user || '';
|
||||
if (s('mqtt-pass')) s('mqtt-pass').value = d.pass || '';
|
||||
if (s('mqtt-base')) s('mqtt-base').value = d.base || 'solar';
|
||||
if (s('mqtt-interval')) s('mqtt-interval').value = d.interval || 30;
|
||||
mqttAfficherTopics(d.base || 'solar');
|
||||
} catch { /* silencieux */ }
|
||||
}
|
||||
|
||||
async function sauvegarderMqtt() {
|
||||
const g = id => (document.getElementById(id)?.value || '').trim();
|
||||
const enabled = g('mqtt-enabled') === 'true';
|
||||
const server = g('mqtt-server') || '192.168.1.36';
|
||||
const port = parseInt(g('mqtt-port')) || 1883;
|
||||
const user = g('mqtt-user');
|
||||
const pass = g('mqtt-pass');
|
||||
const base = g('mqtt-base') || 'solar';
|
||||
const interval = parseInt(g('mqtt-interval')) || 30;
|
||||
|
||||
if (enabled && !server) { afficherToast('⚠ Adresse serveur requise'); return; }
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/mqtt', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ enabled, server, port, user, pass, base, interval })
|
||||
});
|
||||
const d = await res.json();
|
||||
if (d.ok) {
|
||||
afficherToast(enabled ? '✓ MQTT activé — connexion en cours' : '✓ MQTT désactivé');
|
||||
mqttAfficherTopics(base);
|
||||
setTimeout(chargerMqtt, 3000);
|
||||
} else {
|
||||
afficherToast('⚠ Erreur sauvegarde MQTT');
|
||||
}
|
||||
} catch(e) {
|
||||
afficherToast('Erreur : ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
// --- WireGuard ---
|
||||
|
||||
async function chargerWireGuard() {
|
||||
|
||||
@@ -425,6 +425,57 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="regle-form">
|
||||
<div class="form-titre">Publication MQTT</div>
|
||||
<p class="aide">Envoie les données des capteurs vers un broker MQTT (Home Assistant, Mosquitto…). Les relais sont pilotables via les topics de commande.</p>
|
||||
|
||||
<div id="mqtt-status-bar" class="ec-statusbar">Chargement…</div>
|
||||
|
||||
<div class="form-ligne">
|
||||
<label>Activé</label>
|
||||
<select id="mqtt-enabled">
|
||||
<option value="false">Non</option>
|
||||
<option value="true">Oui</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-ligne">
|
||||
<label>Serveur</label>
|
||||
<input type="text" id="mqtt-server" placeholder="192.168.1.36" autocomplete="off">
|
||||
</div>
|
||||
<div class="form-ligne">
|
||||
<label>Port</label>
|
||||
<input type="number" id="mqtt-port" min="1" max="65535" value="1883">
|
||||
</div>
|
||||
<div class="form-ligne">
|
||||
<label>Utilisateur</label>
|
||||
<input type="text" id="mqtt-user" placeholder="Optionnel" autocomplete="off">
|
||||
</div>
|
||||
<div class="form-ligne">
|
||||
<label>Mot de passe</label>
|
||||
<input type="password" id="mqtt-pass" placeholder="Optionnel" autocomplete="new-password">
|
||||
</div>
|
||||
<div class="form-ligne">
|
||||
<label>Topic de base <span class="ec-aide" title="Tous les topics seront préfixés par cette valeur. Ex: solar → solar/battery/voltage">ℹ</span></label>
|
||||
<input type="text" id="mqtt-base" placeholder="solar" autocomplete="off">
|
||||
</div>
|
||||
<div class="form-ligne">
|
||||
<label>Intervalle</label>
|
||||
<div class="ec-field-unit">
|
||||
<input type="number" id="mqtt-interval" min="5" max="3600" value="30">
|
||||
<span class="ec-unit">s</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="mqtt-topics-info" class="hidden">
|
||||
<div class="form-section-label" style="margin-top:0.5rem">Topics publiés</div>
|
||||
<div id="mqtt-topics-list" class="aide" style="font-size:0.72rem;line-height:1.7"></div>
|
||||
<div class="form-section-label" style="margin-top:0.4rem">Topics de commande</div>
|
||||
<div id="mqtt-cmd-list" class="aide" style="font-size:0.72rem;line-height:1.7"></div>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primaire btn-plein" onclick="sauvegarderMqtt()">Enregistrer et appliquer</button>
|
||||
</div>
|
||||
|
||||
<div class="regle-form">
|
||||
<div class="form-titre">VPN WireGuard</div>
|
||||
<p class="aide">Tunnel chiffré vers votre serveur WireGuard. Nécessite une connexion WiFi (mode STA). Désactivé par défaut — la configuration locale reste accessible même si le VPN est coupé.</p>
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
#include <Arduino.h>
|
||||
|
||||
void initMqtt();
|
||||
void gererMqtt();
|
||||
void getMqttJson(String &out);
|
||||
bool setMqttConfig(bool enabled, const char* server, uint16_t port,
|
||||
const char* user, const char* pass,
|
||||
const char* topicBase, uint32_t intervalMs);
|
||||
void mqttPublierEtat(); // publication immédiate (ex: après changement relais)
|
||||
@@ -13,6 +13,7 @@ lib_deps =
|
||||
https://github.com/me-no-dev/ESPAsyncWebServer.git
|
||||
ayushsharma82/ElegantOTA @ ^3.1.0
|
||||
emelianov/modbus-esp8266 @ ^4.1.0
|
||||
knolleary/PubSubClient @ ^2.8
|
||||
|
||||
; --- Cible physique KC868-A2 ---
|
||||
[env:kc868_a2]
|
||||
@@ -21,6 +22,7 @@ build_flags = -D ELEGANTOTA_USE_ASYNC_WEBSERVER=1
|
||||
lib_deps =
|
||||
${common.lib_deps}
|
||||
https://github.com/ciniml/WireGuard-ESP32-Arduino.git
|
||||
knolleary/PubSubClient @ ^2.8
|
||||
|
||||
; --- Build QEMU : WiFi/sleep désactivés, Modbus + règles actifs ---
|
||||
; Compiler : pio run -e qemu
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "debug_log.h"
|
||||
#include "epever_config.h"
|
||||
#include "wireguard_vpn.h"
|
||||
#include "mqtt_client.h"
|
||||
|
||||
// Instance globale partagée entre tous les modules
|
||||
SystemState state;
|
||||
@@ -43,6 +44,7 @@ void setup() {
|
||||
initHistorique();
|
||||
initConfigEpever();
|
||||
initWireGuard();
|
||||
initMqtt();
|
||||
|
||||
debugLogf("Système prêt.");
|
||||
}
|
||||
@@ -50,6 +52,7 @@ void setup() {
|
||||
void loop() {
|
||||
gererWifi();
|
||||
gererWireGuard();
|
||||
gererMqtt();
|
||||
gererOTA();
|
||||
gererBoutons();
|
||||
gererModbus();
|
||||
|
||||
@@ -0,0 +1,249 @@
|
||||
#include "mqtt_client.h"
|
||||
|
||||
#ifndef QEMU_BUILD
|
||||
|
||||
#include <WiFi.h>
|
||||
#include <PubSubClient.h>
|
||||
#include <Preferences.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include "state.h"
|
||||
#include "config.h"
|
||||
|
||||
// --- Config ---
|
||||
static bool mqttActif = false;
|
||||
static String mqttServer = "192.168.1.36";
|
||||
static uint16_t mqttPort = 1883;
|
||||
static String mqttUser;
|
||||
static String mqttPass;
|
||||
static String mqttBase = "solar"; // topic de base
|
||||
static uint32_t mqttInterval = 30000; // ms entre publications
|
||||
|
||||
// --- État runtime ---
|
||||
static WiFiClient wifiClient;
|
||||
static PubSubClient mqttClient(wifiClient);
|
||||
static bool mqttConnecte = false;
|
||||
static unsigned long tDernierePubli = 0;
|
||||
static unsigned long tDerniereConnex = 0;
|
||||
static const unsigned long RECONNECT_INTERVAL = 15000UL;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// NVS
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static void chargerNVS() {
|
||||
Preferences p;
|
||||
p.begin("mqtt", true);
|
||||
mqttActif = p.getBool("enabled", false);
|
||||
mqttServer = p.getString("server", "192.168.1.36");
|
||||
mqttPort = p.getUShort("port", 1883);
|
||||
mqttUser = p.getString("user", "");
|
||||
mqttPass = p.getString("pass", "");
|
||||
mqttBase = p.getString("base", "solar");
|
||||
mqttInterval = p.getULong("interval", 30000);
|
||||
p.end();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Topics dérivés du topic de base
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static String t(const char* suffixe) { return mqttBase + "/" + suffixe; }
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Callback réception (commande relais / entrées)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static void mqttCallback(char* topic, byte* payload, unsigned int len) {
|
||||
String msg;
|
||||
for (unsigned int i = 0; i < len; i++) msg += (char)payload[i];
|
||||
msg.trim();
|
||||
|
||||
bool etat = (msg == "ON" || msg == "1" || msg == "true");
|
||||
|
||||
String top = String(topic);
|
||||
if (top == t("relay/1/set")) {
|
||||
state.relay1 = etat;
|
||||
digitalWrite(PIN_RELAY1, etat ? HIGH : LOW);
|
||||
Serial.printf("[MQTT] Relais 1 → %s\n", etat ? "ON" : "OFF");
|
||||
} else if (top == t("relay/2/set")) {
|
||||
state.relay2 = etat;
|
||||
digitalWrite(PIN_RELAY2, etat ? HIGH : LOW);
|
||||
Serial.printf("[MQTT] Relais 2 → %s\n", etat ? "ON" : "OFF");
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Connexion / reconnexion
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static void connecterMqtt() {
|
||||
if (WiFi.status() != WL_CONNECTED) return;
|
||||
|
||||
mqttClient.setServer(mqttServer.c_str(), mqttPort);
|
||||
mqttClient.setCallback(mqttCallback);
|
||||
mqttClient.setBufferSize(512);
|
||||
|
||||
String clientId = "kc868-" + WiFi.macAddress();
|
||||
clientId.replace(":", "");
|
||||
|
||||
bool ok;
|
||||
if (mqttUser.length() > 0) {
|
||||
ok = mqttClient.connect(clientId.c_str(), mqttUser.c_str(), mqttPass.c_str());
|
||||
} else {
|
||||
ok = mqttClient.connect(clientId.c_str());
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
mqttConnecte = true;
|
||||
Serial.printf("[MQTT] Connecté — %s:%u base: %s intervalle: %us\n",
|
||||
mqttServer.c_str(), mqttPort,
|
||||
mqttBase.c_str(), mqttInterval / 1000);
|
||||
// Abonnements commandes
|
||||
mqttClient.subscribe(t("relay/1/set").c_str());
|
||||
mqttClient.subscribe(t("relay/2/set").c_str());
|
||||
// Publication immédiate après connexion
|
||||
mqttPublierEtat();
|
||||
} else {
|
||||
mqttConnecte = false;
|
||||
Serial.printf("[MQTT] Échec connexion (rc=%d) — retry dans %lus\n",
|
||||
mqttClient.state(), RECONNECT_INTERVAL / 1000);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Publication état complet
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static void pub(const char* suffixe, float val, int decimales = 2) {
|
||||
char buf[16];
|
||||
dtostrf(val, 1, decimales, buf);
|
||||
mqttClient.publish(t(suffixe).c_str(), buf, true);
|
||||
}
|
||||
|
||||
static void pub(const char* suffixe, bool val) {
|
||||
mqttClient.publish(t(suffixe).c_str(), val ? "ON" : "OFF", true);
|
||||
}
|
||||
|
||||
static void pub(const char* suffixe, int val) {
|
||||
mqttClient.publish(t(suffixe).c_str(), String(val).c_str(), true);
|
||||
}
|
||||
|
||||
void mqttPublierEtat() {
|
||||
if (!mqttConnecte || !mqttClient.connected()) return;
|
||||
|
||||
// Panneau solaire
|
||||
pub("pv/voltage", state.pv);
|
||||
pub("pv/current", state.pvCurrent);
|
||||
|
||||
// Batterie
|
||||
pub("battery/voltage", state.battery);
|
||||
pub("battery/soc", (int)state.batSOC);
|
||||
pub("battery/temperature", state.batTemperature, 1);
|
||||
pub("battery/status", (int)state.batStatut);
|
||||
|
||||
// Sortie de charge
|
||||
pub("load/voltage", state.loadVoltage);
|
||||
pub("load/current", state.loadCurrent);
|
||||
pub("load/power", state.loadPower, 1);
|
||||
|
||||
// Énergie
|
||||
pub("energy/generated/today", state.energieGenJour);
|
||||
pub("energy/generated/total", state.energieGenTotal);
|
||||
pub("energy/consumed/today", state.energieConJour);
|
||||
pub("energy/consumed/total", state.energieConTotal);
|
||||
|
||||
// État général
|
||||
pub("sun", state.sun);
|
||||
pub("rs485/ok", state.rs485_ok);
|
||||
|
||||
// Relais
|
||||
pub("relay/1", state.relay1);
|
||||
pub("relay/2", state.relay2);
|
||||
|
||||
// Entrées numériques
|
||||
pub("input/1", state.di1);
|
||||
pub("input/2", state.di2);
|
||||
|
||||
tDernierePubli = millis();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// API publique
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void initMqtt() {
|
||||
chargerNVS();
|
||||
Serial.printf("[MQTT] %s — %s:%u base: %s intervalle: %us\n",
|
||||
mqttActif ? "Activé" : "Désactivé",
|
||||
mqttServer.c_str(), mqttPort,
|
||||
mqttBase.c_str(), mqttInterval / 1000);
|
||||
}
|
||||
|
||||
void gererMqtt() {
|
||||
if (!mqttActif) return;
|
||||
if (WiFi.status() != WL_CONNECTED) {
|
||||
if (mqttConnecte) { mqttConnecte = false; }
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mqttClient.connected()) {
|
||||
mqttConnecte = false;
|
||||
unsigned long maintenant = millis();
|
||||
if ((maintenant - tDerniereConnex) >= RECONNECT_INTERVAL) {
|
||||
tDerniereConnex = maintenant;
|
||||
connecterMqtt();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
mqttClient.loop();
|
||||
|
||||
// Publication périodique
|
||||
if ((millis() - tDernierePubli) >= mqttInterval) {
|
||||
mqttPublierEtat();
|
||||
}
|
||||
}
|
||||
|
||||
bool setMqttConfig(bool enabled, const char* server, uint16_t port,
|
||||
const char* user, const char* pass,
|
||||
const char* topicBase, uint32_t intervalMs) {
|
||||
Preferences p;
|
||||
p.begin("mqtt", false);
|
||||
p.putBool("enabled", enabled);
|
||||
p.putString("server", server);
|
||||
p.putUShort("port", port);
|
||||
p.putString("user", user);
|
||||
p.putString("pass", pass);
|
||||
p.putString("base", topicBase);
|
||||
p.putULong("interval", intervalMs);
|
||||
p.end();
|
||||
|
||||
if (mqttConnecte) { mqttClient.disconnect(); mqttConnecte = false; }
|
||||
tDerniereConnex = 0;
|
||||
chargerNVS();
|
||||
Serial.printf("[MQTT] Config mise à jour — %s\n", mqttActif ? "activé" : "désactivé");
|
||||
return true;
|
||||
}
|
||||
|
||||
void getMqttJson(String &out) {
|
||||
JsonDocument doc;
|
||||
doc["enabled"] = mqttActif;
|
||||
doc["connected"] = mqttConnecte;
|
||||
doc["server"] = mqttServer;
|
||||
doc["port"] = mqttPort;
|
||||
doc["user"] = mqttUser;
|
||||
doc["pass"] = mqttPass;
|
||||
doc["base"] = mqttBase;
|
||||
doc["interval"] = mqttInterval / 1000; // en secondes pour l'UI
|
||||
serializeJson(doc, out);
|
||||
}
|
||||
|
||||
#else
|
||||
// --- Stubs QEMU ---
|
||||
void initMqtt() { Serial.println("[MQTT] Désactivé (build QEMU)"); }
|
||||
void gererMqtt() {}
|
||||
void mqttPublierEtat() {}
|
||||
void getMqttJson(String &out) { out = "{\"enabled\":false,\"connected\":false}"; }
|
||||
bool setMqttConfig(bool, const char*, uint16_t, const char*, const char*,
|
||||
const char*, uint32_t) { return false; }
|
||||
#endif
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "epever_config.h"
|
||||
#include "wifi_ap.h"
|
||||
#include "wireguard_vpn.h"
|
||||
#include "mqtt_client.h"
|
||||
#include "debug_log.h"
|
||||
|
||||
AsyncWebServer server(80);
|
||||
@@ -440,6 +441,30 @@ void demarrerWebserveur() {
|
||||
});
|
||||
server.addHandler(handlerMdns);
|
||||
|
||||
// --- API MQTT ---
|
||||
|
||||
server.on("/api/mqtt", HTTP_GET, [](AsyncWebServerRequest *r) {
|
||||
String json;
|
||||
getMqttJson(json);
|
||||
r->send(200, "application/json", json);
|
||||
});
|
||||
|
||||
auto *handlerMqtt = new AsyncCallbackJsonWebHandler("/api/mqtt",
|
||||
[](AsyncWebServerRequest *r, JsonVariant &json) {
|
||||
JsonObject obj = json.as<JsonObject>();
|
||||
bool enabled = obj["enabled"] | false;
|
||||
const char* srv = obj["server"] | "192.168.1.36";
|
||||
uint16_t port = obj["port"] | 1883u;
|
||||
const char* user = obj["user"] | "";
|
||||
const char* pass = obj["pass"] | "";
|
||||
const char* base = obj["base"] | "solar";
|
||||
uint32_t interval = (uint32_t)((obj["interval"] | 30u)) * 1000u;
|
||||
interval = constrain(interval, 5000u, 3600000u);
|
||||
bool ok = setMqttConfig(enabled, srv, port, user, pass, base, interval);
|
||||
r->send(ok ? 200 : 500, "application/json", ok ? "{\"ok\":true}" : "{\"ok\":false}");
|
||||
});
|
||||
server.addHandler(handlerMqtt);
|
||||
|
||||
// --- API WireGuard ---
|
||||
|
||||
server.on("/api/wireguard", HTTP_GET, [](AsyncWebServerRequest *r) {
|
||||
|
||||
Reference in New Issue
Block a user