Bonjour,
j'ai modifié le code et me voici avec un rafraichissement des données toutes les 0,5sec.
Maintenant, la régulation semble beaucoup plus stable et la navigation entre les pages est bien plus fluide.
Cela pourrait aider pour tout le monde.
Si André veut ajouter tout cela dans son programme, ca serait super.
Voici les fichiers:
https://www.grosfichiers.com/X5Kfp7tF4Y7
Claude m'a résumé les modification du code :
# Adaptation du routeur solaire F1ATB au compteur Meross/Refoss EM06P
Résumé des évolutions et corrections apportées, dans l'ordre chronologique.
## 1. Contexte et objectif de départ
Le programme gérait initialement un compteur **Shelly Pro EM**. L'objectif était
de l'adapter pour fonctionner avec un compteur **Meross/Refoss EM06P**, qui
utilise le même style de protocole JSON-RPC en HTTP, mais avec :
- une structure JSON différente (tableau plat de 6 voies indépendantes,
identifiées par `"id":1` à `"id":6`, plutôt que des objets nommés
`"em1:0"`, `"em1:1"`... comme chez Shelly) ;
- des noms de champs différents (`power` au lieu de `act_power`, etc.) ;
- pas de compteur d'énergie cumulé "depuis toujours" (pas de
`total_act_energy`), seulement des compteurs `day_energy` / `week_energy`
/ `month_energy` / `year_energy` (et leurs équivalents `_ret_energy` pour
le sens inverse), tous remis à zéro périodiquement par l'appareil
lui-même.
## 2. Découverte de l'API réelle
La documentation officielle (`docs.refoss.net`) est un site JavaScript non
lisible automatiquement. La structure réelle de l'API a été déterminée à
partir d'un relevé JSON fourni par l'utilisateur, puis confirmée par un test
direct :
```
GET http://<IP>/rpc/Em.Status.Get?id=65535
```
`id=65535` est un identifiant spécial qui renvoie le statut des 6 voies en
une seule requête (pas besoin d'interroger voie par voie).
## 3. Écriture de la fonction `LectureEM06P()`
Nouvelle fonction créée sur le modèle de `LectureShellyProEm()`, avec :
- une fonction `ExtraitVoieEM06P(id, data)` qui isole le sous-objet JSON
d'une voie donnée dans le tableau `"status":[...]` ;
- lecture de `power`, `voltage`, `pf`, `year_energy`/`year_ret_energy` et
`day_energy`/`day_ret_energy` par voie.
## 4. Bugs corrigés dans le code
### 4.1 Lecture réseau peu robuste
La boucle de lecture HTTP initiale s'arrêtait dès qu'il n'y avait
momentanément plus de données disponibles, alors que la réponse peut arriver
en plusieurs paquets réseau séparés (en-têtes puis corps). Corrigée avec une
boucle qui relance le délai d'attente à chaque octet reçu.
### 4.2 Bug dans `ValJson()` (fonction générique de parsing JSON)
`ValJson()` retournait **0** lorsque le champ demandé était le **dernier**
d'un objet JSON (donc suivi directement de `}` sans virgule après) : le code
faisait `min(position_virgule, position_accolade)`, et quand la virgule est
absente (`indexOf` renvoie -1), `min(x, -1)` vaut toujours -1, ce qui faisait
échouer l'extraction. Ce bug touchait spécifiquement `day_ret_energy`
(toujours en dernière position dans chaque objet voie de l'EM06P), d'où
"Energie Active du jour → Injectée" bloquée à 0. Corrigé, et potentiellement
utile aussi pour d'autres sources utilisant cette même fonction.
### 4.3 Calcul de l'énergie du jour inadapté
`EnergieQuotidienne()` calculait l'énergie du jour par **delta** par rapport
à une baseline mémorisée à minuit (`EAS_M_J0` etc.), ce qui suppose un
compteur strictement croissant. L'EM06P n'ayant pas ce type de compteur,
cette logique produisait des valeurs incohérentes. Corrigé en excluant
`Source == "EM06P"` de ce calcul par delta, et en utilisant directement les
champs `day_energy`/`day_ret_energy` fournis par l'appareil (déjà remis à
zéro par l'appareil lui-même chaque minuit).
### 4.4 Convention de signe puissance/énergie
Une inversion de signe avait été appliquée par erreur suite à une déduction
indirecte, puis annulée après confirmation terrain : sur ce compteur,
**puissance positive = soutirage réel, puissance négative = injection
réelle** (convention standard, pas d'inversion nécessaire). Confirmé par
observation directe en situation d'injection puis de soutirage réel.
### 4.5 Voie secondaire (T) mal choisie, lue une fois sur cinq
- La voie secondaire était calculée comme "voie principale + 1", sans lien
avec l'installation réelle (PV câblé sur la voie 4). Fixée en dur sur
`VOIE_SECONDAIRE_EM06P_ID = 4`.
- Le code alternait la lecture entre voie M et voie T sur plusieurs cycles
(`ShEm_comptage_appels`), hérité du Shelly qui nécessitait une requête HTTP
séparée par voie. Or l'EM06P renvoie déjà les 6 voies en une seule requête
: le code a été restructuré pour extraire M **et** T de la même réponse à
**chaque** appel, supprimant l'alternance et améliorant la réactivité de
la régulation.
### 4.6 Sources d'affichage oubliant `"EM06P"`
Plusieurs endroits du code testaient explicitement `Source_data ==
"ShellyEm" || Source_data == "ShellyPro"` sans inclure `"EM06P"`, empêchant
l'affichage correct de la voie secondaire :
- `AdaptationSource()` (page Paramètres, JS) : champs de nom de la seconde
sonde restaient masqués.
- `PageBruteJS2` (switch de la page Données Brutes) : aucune donnée EM06P
affichée du tout (tombait dans le `default`).
- `handleAjaxData()` (serveur, deux occurrences) : envoyait `"Fake"` au lieu
des vraies données de la voie T vers la page d'accueil.
- `handleMainJS1()` (serveur) : variable `biSonde` restait à `false`,
masquant la colonne entière sur la page d'accueil même avec de bonnes
données reçues.
Toutes ces conditions ont été complétées avec `Source_data == "EM06P"` /
`Source == "EM06P"`.
### 4.7 Titre de la page "Données Brutes"
Le titre "Données Shelly Em" était en dur dans le HTML (`PageBrute.h`),
partagé par Shelly et EM06P. Ajout d'un `id="titreShellyEm"` sur ce `<div>`,
mis à jour dynamiquement en JS selon la source (`case "EM06P":` distinct de
`case "ShellyEm"/"ShellyPro":`).
### 4.8 Fuite mémoire / fragmentation heap
La lecture réseau construisait la réponse **caractère par caractère** avec
un `String` Arduino (`EM06P_Data += (char)...`), ce qui peut déclencher une
réallocation complète à chaque ajout. Répété toutes les 300-600ms en continu,
ça fragmentait sévèrement la mémoire (RAM libre minimum tombée à ~200
octets, causant des pages web inaccessibles / timeouts). Corrigé avec
`EM06P_Data.reserve(2000)` en début de fonction, pour allouer le buffer une
seule fois.
### 4.9 Délai artificiel de ~5,5 secondes par lecture (cause principale de l'instabilité de régulation)
Un diagnostic de timing (ajouté avec compteur d'appels lents, moyenne et
écart-type calculés sur des fenêtres de 200 lectures) a révélé que **100 %**
des lectures dépassaient 1 seconde, avec une moyenne très stable autour de
5 500ms (écart-type faible, donc un phénomène régulier, pas aléatoire). Des
tests `curl` directs depuis un PC sur le même réseau ont montré que le
compteur répondait en réalité en 50-400ms — innocentant le matériel.
**Cause identifiée** : la boucle de lecture attendait que
`WiFiClient::connected()` devienne `false` pour considérer la lecture
terminée. Sur ESP32, cette fonction est connue pour mettre plusieurs
secondes à détecter la fermeture réelle d'une connexion TCP par le serveur,
même quand toutes les données ont déjà été reçues.
**Correction** : lecture désormais basée sur l'en-tête HTTP
`Content-Length` — la fonction s'arrête dès que le nombre d'octets annoncé a
été reçu, sans attendre la fermeture formelle de la connexion. Résultat
observé : temps de lecture moyen passé de ~5 500ms à **~1-460ms** (mesuré
via `Charge coeur 0` sur la page Données ESP32 et les messages `EM06P
DIAG`).
## 5. Outil de diagnostic ajouté
Un système de journalisation périodique (`EM06P DIAG`, toutes les 200
lectures) a été ajouté dans `LectureEM06P()`, donnant :
- nombre de lectures dépassant 1 seconde sur la fenêtre ;
- durée moyenne ;
- écart-type ;
- durée maximale observée.
Les compteurs sont remis à zéro à chaque rapport (fenêtre glissante), pour
rester représentatifs de l'état récent plutôt que d'une moyenne diluée
depuis le démarrage.
## 6. Correctif watchdog sur données invalides
Dans le cas où aucune donnée JSON valide n'est reçue (pas d'accolade `{`
trouvée), le code appelait auparavant quand même `filtre_puissance()` et
positionnait `PuissanceRecue = true` — ce qui réinitialisait à tort le
watchdog de sécurité (`WDT_TIMEOUT`, 180s) même en cas d'échec de lecture
réel et prolongé. Corrigé : ces deux appels sont retirés dans cette branche
d'erreur spécifique, pour que le reset de sécurité fonctionne correctement
en cas de panne durable du compteur.
## 7. État actuel
- Lecture HTTP rapide et fiable (~1-460ms au lieu de ~5 500ms).
- Mémoire stable dans le temps (plus de fuite/fragmentation).
- Voies M (routeur) et T (PV, voie 4) lues à chaque cycle, sans alternance.
- Affichage correct sur toutes les pages (Accueil, Données Brutes,
Paramètres) pour la source EM06P.
- Énergies jour/total cohérentes avec les données réelles de l'appareil.
- Tests de stabilité de la régulation solaire en cours sur plusieurs jours,
suite à la correction du délai de lecture (point 4.9), qui était très
probablement la cause principale de l'instabilité observée initialement.