Files
gilles a8f0d6ccba 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>
2026-05-09 19:25:01 +02:00

6.9 KiB
Raw Permalink Blame History

Debug RS485 — Historique des corrections

Ce document trace les problèmes rencontrés avec la communication RS485 Modbus et les modifications apportées pour la faire fonctionner.


Contexte matériel

Paramètre Valeur
Baudrate réel Epever 115 200 (et non 9 600 par défaut)
UART ESP32 Serial2
RX GPIO 35 (input-only)
TX GPIO 32
Adresse esclave 1
Mode RTU, 8N1, half-duplex

Le baudrate est le premier point de blocage : l'Epever Tracer 4210N peut être configuré à des vitesses non standards. La valeur 115 200 a été découverte grâce à la sonde de boot (probeRegistreBatterie).


Abandon de la librairie ModbusRTU — passage en mode "brut"

Ancienne approche (callbacks, morte mais encore dans le fichier)

Le code utilisait la librairie ModbusRTU avec une chaîne de callbacks asynchrones :

gererModbus()
  └─ mb.readIreg(0x3100, cbPV)
       └─ cbPV → mb.readIreg(0x310C, cbLoad)
            └─ cbLoad → mb.readIreg(0x311A, cbSOC)
                 └─ cbSOC → mb.readIreg(0x3200, cbStatus)
                      └─ cbStatus → mb.readIreg(0x3300, cbEnergie)
                           └─ cbEnergie → mb.readIsts(0x200C, cbJourNuit)
                                └─ cbJourNuit → finaliserLecture()

Problèmes :

  • La lib gérait mal le timing RS485 half-duplex à 115 200 bauds, générant des trames corrompues ou des timeouts
  • Aucun vidage du buffer RX entre requêtes → les octets résiduels polluaient les trames suivantes
  • Pas de diagnostic au boot pour vérifier la communication avant d'entrer dans la boucle

Les callbacks cbPV, cbLoad, cbSOC, cbStatus, cbEnergie, cbJourNuit sont toujours présents dans le fichier mais ne sont plus appelés. Seuls mb.begin() et mb.master() restent actifs (initialisation du Serial2 interne à la lib).

Nouvelle approche (Serial2 direct, synchrone)

gererModbus() appelle effectuerLectureBruteEpever() qui effectue toutes les lectures de façon séquentielle via Serial2 direct + CRC Modbus calculé manuellement :

effectuerLectureBruteEpever()
  1. lireRegistresBruts(0x3100, 8)   → PV + batterie         [FC04, fatal]
  2. lireRegistresBruts(0x310C, 5)   → load + température    [FC04, fatal]
  3. lireRegistresBruts(0x311A, 1)   → SOC %                 [FC04, fatal]
  4. lireRegistresBruts(0x3200, 2)   → statut batterie       [FC04, fatal]
  5. lireHorlogeEpever()             → RTC Epever            [FC03, non-fatal]
  6. lireRegistresBruts(0x3302, 18)  → énergie kWh           [FC04, non-fatal]
  7. lireEntreesDiscretesBrut(0x200C)→ jour/nuit             [FC02, non-fatal]

Corrections des registres

Énergie kWh (base décalée)

Ancienne version Nouvelle version
Base de lecture 0x3300 0x3302
Énergie générée jour offset 0 (0x3300/01) offset 2 (0x3304/05)
Énergie générée total offset 6 (0x3306/07) offset 8 (0x330A/0B)
Énergie consommée jour offset 8 (0x3308/09) offset 10 (0x330C/0D)
Énergie consommée total offset 14 (0x330E/0F) offset 16 (0x3312/13)

Source de référence : MODBUS-Protocol-v25.pdf (tableau des registres input 0x3302 à 0x3313).

Puissance sortie de charge (32 bits)

L'ancien callback lisait bufLoad[2] * 0.01f (16 bits uniquement, registre 0x310E seul).
La puissance est en réalité sur 32 bits (0x310E = word L, 0x310F = word H) :

// Avant
state.loadPower = bufLoad[2] * 0.01f;

// Après
state.loadPower = u32x100(load, 2);  // (reg[3] << 16 | reg[2]) * 0.01f

Détection jour/nuit (logique hybride)

Le registre FC02 0x200C retourne 1 = Nuit, 0 = Jour. Mais si l'Epever renvoie nuit alors que le panneau produit clairement (PV > 2 V), l'état est incohérent côté UI.

// Avant (simple inversion)
state.sun = !bufJourNuit[0];

// Après (hybride : registre + tension PV)
state.sun = !nuit || state.pv > 2.0f;

Mécanismes de robustesse ajoutés

viderRx() — purge systématique du buffer avant chaque trame

Entre deux requêtes Modbus, des octets parasites peuvent s'accumuler dans le FIFO Serial2 (echos, bruit, réponse tardive). Sans purge, ces octets polluent la réponse suivante et génèrent des CRC invalides.

static void viderRx(const char *raison) {
    // lit et logue tous les octets résiduels présents avant l'envoi
}

Appelé en tête de chaque fonction de lecture brute.

probeRegistreBatterie() — sonde de boot RS485

Au démarrage, avant d'entrer dans la boucle normale, le code envoie une trame Modbus manuelle FC04 sur le registre 0x3104 (tension batterie) et vérifie :

  • réception d'octets
  • adresse esclave correcte
  • CRC valide
  • valeur de tension cohérente

Si le baudrate principal échoue, la sonde retente à 9 600 et 115 200.

CRC Modbus calculé et vérifié manuellement

static uint16_t crc16Modbus(const uint8_t *buf, size_t len);

Vérifié sur chaque trame reçue avant d'extraire les valeurs. En cas d'échec CRC, la trame est rejetée, l'erreur est comptée, et le cycle suivant repart proprement.

Lectures non-fatales (énergie, RTC, jour/nuit)

Certaines lectures peuvent légitimement échouer (registres optionnels, timing serré) sans invalider les données essentielles. Le cycle continue et conserve les dernières valeurs valides :

  • Énergie (0x3302) : non-fatal, conserve le dernier kWh connu
  • Jour/nuit (FC02 0x200C) : non-fatal, fallback sur state.pv > 2.0f
  • Horloge Epever (0x9013) : non-fatal, epeverClockOk = false

Synchronisation RTC ESP32 depuis l'Epever

static void calerHorlogeEspDepuisEpever();

Après lecture des holding registers 0x90130x9015 (secondes/minutes/heures/jour/mois/année), l'heure système ESP32 est calée via settimeofday(). Rafraîchissement toutes les 6h (INTERVALLE_SYNC_RTC).


Fonctions FC couvertes

Fonction Modbus Usage
FC02 (Read Discrete Inputs) Registre 0x200C — état jour/nuit
FC03 (Read Holding Registers) Registres 0x90130x9015 — horloge RTC
FC04 (Read Input Registers) PV, batterie, load, SOC, statut, énergie
FC16 (Write Multiple Holding Registers) Réglage horloge RTC Epever

Résumé des points de blocage résolus

Problème Cause Correction
Aucune réponse RS485 Baudrate 9600 au lieu de 115200 MODBUS_BAUDRATE 115200 + sonde boot
Trames corrompues Buffer RX pollué entre requêtes viderRx() systématique
Timeouts aléatoires Lib ModbusRTU inadaptée au timing Passage en Serial2 direct
Énergie kWh à zéro Mauvaise base de registre (0x3300 vs 0x3302) Correction offsets selon PDF
Puissance load erronée Lecture 16 bits au lieu de 32 bits u32x100() sur 0x310E/0F
État soleil incohérent FC02 seul, pas de fallback PV Logique hybride !nuit || pv > 2V