Files
EVCS/HANDOFF.md
2026-09-25 01:42:57 +02:00

38 KiB
Raw Blame History

HANDOFF.md — EVCS / Grid Hysteresis — État au 09/07/2026

⚠️ Les sessions §1 à §72 (09/07 → 17/09/2026) sont ARCHIVÉES SANS PERTE dans HANDOFF_ARCHIVE_2026.md (fichier NON chargé automatiquement — grep/lecture à la demande). Ce fichier ne garde vivantes que les sessions récentes (§73→). Split lossless vérifié par md5 le 24/09/2026.

Index des sessions archivées (§1→§72 — détail dans HANDOFF_ARCHIVE_2026.md)

    1. Contexte Projet
    1. Architecture Data-Flow
    1. Patchs déployés le 09/07/2026 (session opencode)
    1. Root Cause de l'incident « EDF ON à 48 % sans charge Jeep » (nuit 08→09/07)
    1. Sémantique utile (pour ne pas re-chercher)
    1. Méthode de travail validée (à réutiliser)
    1. Backlog / Point du 10/07 au réveil
    1. Historique des sessions
  • ⚙️ CONFIG LOCALE — Qwen3.6-35B sur DGX Spark (≠ gros modèle cloud)
  • 🧩 SKILLS n8n — validées le 16/07/2026 (Qwen3.6 local, PAS gros modèle)
    1. Session du 18/07/2026 (matin)
    1. Session du 04/08/2026 (matin) — Fix grid HC 1h30 + libération takeover
    1. Session du 07/08/2026 — P7 : Charge HC nuit automatique (VW hybride) + outillage push
    1. Session du 07/08/2026 (soir) — R1 : Déduplication du moteur nuit
    1. Session du 07/08/2026 (soir) — R3 : Suite de tests golden
    1. Session du 07/08/2026 (soir) — P8 : Libération EDF quand le véhicule est plein
    1. Session du 07/08/2026 (soir) — R2 : Git = source de vérité du code des nodes
    1. Session du 07/08/2026 (nuit) — Agent IA planificateur en MODE OMBRE
    1. Session du 08/08/2026 — Incident rebond EDF nuit 07-08/08 : fixes A/B/C/D
    1. Session du 09/08/2026 — Incident nuit 08-09/08 : le rebond est revenu par un AUTRE chemin (Fix E)
    1. Session du 09/08/2026 (suite) — Fix F : la mémoire "charge terminée" est prioritaire sur les gardes
    1. Session du 09/08/2026 — PREMIER CÂBLAGE RÉEL de l'advisor : cible SOC matin
    1. Session du 09/08/2026 — P9 : charge réseau avant départ + sélecteur véhicule VW/JEEP
    1. Session du 09/08/2026 — P10 : mémoire "véhicule plein" en journée
    1. Session du 12/08/2026 — P11 : reset du collant au changement de véhicule
    1. Session du 27/08/2026 — L'oscillateur du takeover nuit, et deux acteurs non lus
  • Diagnostic en attente de vérification (Qwen, 22/08)
    1. Session du 22/08/2026 — Fix G : coupure EDF quand l'override EVCS disparaît mi-session (hystérésis)
    1. Session du 24/08/2026 — Fix G retiré + diagnostic des deux oscillateurs
    1. Session du 30/08/2026 — Deux propriétaires GX de la décision réseau, jamais lus
    1. Session du 31/08/2026 — La substitution ne se reproduit plus (batterie preneuse). Cas « batterie pleine » encore ouvert
    1. Session du 31/08/2026 (suite) — Fenêtre d'établissement du réseau
    1. Session du 31/08/2026 (suite) — Séquence d'ouverture du contacteur
    1. Session du 01/09/2026 (nuit) — La consigne réseau appartient désormais au contrôleur
    1. Session du 01/09/2026 (nuit) — État réel de la résilience, faits vérifiés
    1. Session du 01/09/2026 (nuit) — Chien de garde EVCS sur le second Synology
    1. Session du 01/09/2026 (nuit) — Sauvegarde Home Assistant : elle n'existait pas, elle existe
    1. Session du 01/09/2026 — La table des bandes SOC déclarait un temps que personne ne lisait
    1. Session du 03/09/2026 — Un demarrage se decide sur l'excedent, pas sur la production
    1. Session du 03/09/2026 (suite) — La provocation solaire demandee aux chargeurs eux-memes
    1. Session du 03/09/2026 (suite) — Le plancher du soir cesse d'etre un niveau fixe
    1. Session du 03/09/2026 (suite) — La cible du matin devient une energie
    1. Session du 03/09/2026 (suite) — Session reseau UNIQUE de la nuit
    1. Session du 03/09/2026 (suite) — La conversion energie ↔ SOC devient une courbe mesuree
    1. Session du 04/09/2026 — Pourquoi la memoire s'arretait a la semaine
    1. Nuit du 03-04/09 — La session unique tenait le contacteur, pas l'energie
    1. Mise en service d'InfluxDB (04/09/2026 07:28) — la memoire est complete
    1. Session du 04/09/2026 — Grafana : les deux bases se consultent
    1. Session du 04/09/2026 — « Le systeme » : replique locale de la vue VRM
    1. Session du 04/09/2026 (soir) — Connexion InfluxDB retiree du YAML, et un effet de bord du redemarrage
    1. Session du 05/09/2026 — Le bridage de charge est une CELLULE, et il commence a 75 %
    1. Session du 05/09/2026 — Le bridage a un coupable nomme : le module d'adresse 3
    1. Session du 05/09/2026 (midi) — Lecture en direct de la chaine BMS depuis Linux
    1. Session du 05/09/2026 (12h30) — Le parc est sain a cinq modules sur sept, et le hub n'est pas coupable
    1. Session du 05/09/2026 (12h45) — BAT3 lue isolee : UNE cellule, et elle est en meilleur etat que deux modules restes en service
    1. Session du 05/09/2026 (13h05) — Le declenchement saisi en direct, et l'origine des degats
    1. Session du 05/09/2026 (18h30) — Le tampon d'evenements : la cellule 12 est detruite en ce moment, toutes les nuits
    1. Session du 05/09/2026 (19h15) — La cellule 15 de BAT3 ne peut plus debiter, meme pleine
    1. Session du 05/09/2026 (soir) — Dates d'achat, et ce qu'elles eliminent
    1. Session du 06/09/2026 — Le relachement du contacteur se mesurait sur un instant
    1. Session du 07/09/2026 — La borne annonce « charge terminee » apres 0,10 kWh
    1. Session du 07/09/2026 — Fix H : un statut « charged » doit etre credible
    1. Session du 07/09/2026 — Tableau « La journee », et un report de valeur qui fabriquait de l'energie
    1. Session du 08/09/2026 — La prevision se juge sur la date qu'elle decrit
    1. Session du 11/09/2026 — Le credit solaire sur la consigne reseau, valide en reel
    1. Session du 13/09/2026 — Le conseiller ne lisait pas mal : on lui parlait mal
    1. Session du 13/09/2026 — Le parc butait sur NOTRE plafond, pas sur le sien
    1. Session du 13/09/2026 (soir) — Six sessions sur douze etaient mal etiquetees
    1. Session du 13/09/2026 (soir) — Contrat d'etiquetage du concierge, arrete des deux cotes
    1. Session du 14/09/2026 — L'auteur des transitions etait lisible depuis le debut
    1. Nuit du 14 au 15/09/2026 — P12 : la voiture se charge sur le solaire d'hier
    1. Session du 16/09/2026 — Premiere nuit P12, battement de coeur, guichet d'ordres, et la telemetrie BMS qui ment
    1. Nuit du 16 au 17/09/2026 — Le chien de garde arretait la charge qu'il devait proteger
    1. Session du 17/09/2026 — La prevision entre dans les tableaux Grafana
    1. Session du 17/09/2026 (apres-midi) — Le reseau force a une echeance, et la surconsigne n'etait pas le defaut qu'on croyait

73. Nuit du 17 au 18/09/2026 — Dissocier la recharge batterie de la fonction EVCS

Ce qui a declenche le chantier, et une correction de sens

Deux sessions ont passe l'apres-midi a signaler au proprietaire qu'un changement de la consigne reseau reduisait la recharge de la batterie sous force_grid_now. Sa reponse ferme la question : « force_grid_now pour moi c'est pour recharger la voiture, ça me va comme cela ». Nous raisonnions tous les deux comme si cette strategie etait une commande de recharge batterie — parce que l'episode du 17/09 en avait eu l'effet. C'etait un effet de bord, pas l'intention.

⚠️ A retenir : force_grid_now = « charge la voiture sur le reseau maintenant ». Toute consequence sur la batterie solaire est incidente et ne doit pas servir d'argument pour ou contre une evolution de cette strategie.

Et c'est lui qui en tire le chantier : la recharge de la batterie solaire doit devenir une fonction propre, avec son tableau de bord et sa commande — un bouton de charge manuelle en heures creuses pour les cas ou le bilan ne l'autorise pas alors que la meteo ou la brievete du jour d'hiver la justifient.

FAIT — le detail par cellule pendant la charge (Grafana)

Le detail cellule par cellule existait deja. Ce qui manquait etait le « pendant la charge » : rien ne disait QUAND le module chargeait, et un ecart entre cellules ne veut rien dire hors de son courant — le 05/09, meme module et meme tension de parc, la cellule meneuse etait a 3555 mV sous 50 A et a 3496 mV sous 30 A.

Le releve console porte le courant depuis le 17/09 mais l'ingestion ecrivait null. Elle lit desormais la colonne courant_mA, reperee par son NOM : les US5000 et les US2000C n'emettent pas les memes colonnes, et une position ferait lire la mauvaise grandeur des qu'on deplace le cable d'un pack a l'autre.

Deux panneaux : « Sante des modules — tous, d'un coup d'oeil » (le resume qu'on lit en premier, la ou les autres panneaux demandent de choisir un module d'avance) et « Le module chargeait-il ? ». Releve au deploiement, cinq modules vivants, ecarts de 1 a 3 mV — le parc restant est sain.

CONCEPTION ARRETEE, NON ECRITE — la charge HC manuelle

Trois pistes, mesurees sur banc golden par la session voisine.

Piste 2 — un chemin separe qui ne toucherait que la consigne et le contacteur : MORTE. Le controleur reecrit la consigne de repos a chaque cycle des que gridSessionActive est faux : sonde a 5000 W poses de l'exterieur, ramenes a 700 W en trente secondes. Elle ne se fait pas ecraser par accident, elle s'auto-detruit : pour qu'elle tienne il faudrait que le controleur la connaisse, donc qu'elle ne soit plus separee.

Piste 3 — forcer la cible du bilan : RETENUE, mais l'ENDROIT du forcage decide de tout.

forcage takeover consigne resultat
en aval (un bouton ecrit balance_required_soc) engage 700 W batterie a ~100 W
en amont (dans BALANCE.js) engage 2333 W le manque reel finance

⚠️ Le cas en aval est le pire des trois parce qu'il A L'AIR DE MARCHER : contacteur ferme, session engagee, et une consigne qui ne finance rien. C'est le defaut du 01/09 et du 04/09 a l'identique, et le seul des trois resultats indiagnosticable depuis le tableau de bord. required_soc ENGAGE la session, grid_energy_needed_kwh la FINANCE : les desolidariser donne une session muette.

Point d'insertion, une ligne. BALANCE.js, fonction coverage(), ligne 226 :

const requiredSocRawPct = socForEnergy(energyToCoverKwh, socFloorPct);

requiredSocPct, socMarginPct, gridNeeded et gridEnergyNeededKwh en derivent tous dans les huit lignes suivantes. Un Math.max(..., plancherManuel) a cet endroit fait donc bouger la cible ET le manque ensemble par construction : le couplage implicite qui fait la fragilite de la piste 3 devient une consequence de la forme du code, et non une discipline a tenir.

Trois effets a connaitre avant d'ecrire

  1. Les deux profils montent ensemble. coverage() est appelee pour p50 ET p80, donc le plancher monte aussi required_soc_prudent, qui est le plancher de P12. Pendant une charge manuelle, la voiture cesserait donc de descendre la batterie. Coherent — on ne remplit pas au reseau pendant que la voiture vide — mais une nuit ou il veut les deux, il n'aura que l'un. C'est structurel, aucune echeance n'y change rien.
  2. ⚠️ Le bilan ne connait pas les heures creuses. grep sur les douze fichiers de src/energy-balance/ : zero occurrence de hc_night / hc_midday. Sans garde de temps, une cible manuelle a 85 % fuit dans la journee et mord sur BATTERY_CLOUD_LOCKS.js:451, eveningSocStartFloorPct = max(eveningSocStartMin, balanceRequiredSocPct) — la voiture ne demarrerait plus le soir. Ce n'est pas theorique, c'est une ligne qui s'applique tous les soirs. Solution retenue : l'echeance, pas les bornes. Meme mecanique que l'expiration de force_grid_now (§72), deja eprouvee en production. Et l'argument architectural : le bilan repond a « de combien la batterie a-t-elle besoin », pas a « quand est-ce bon marche » ; lui apprendre les fenetres tarifaires couplerait deux preoccupations aujourd'hui propres, et ajouterait un endroit de plus a penser le jour ou une borne bouge.
  3. Rien d'autre n'est touche. Le bloc « prochaine fenetre de deficit » n'appelle pas coverage() (il utilise socForEnergy directement, l.287), et un plancher a 85 reste loin sous l'ecretage a 100, donc aucune interaction avec le signal de sous-dimensionnement de §26.

La decision qui reste au proprietaire

Contraindre le bilan (piste 3) ou l'ignorer (piste 1, un sourceMode de plus dans le controleur). Ce n'est pas un choix de technique mais de sens : contraindre garde un seul arbitre, ignorer en cree un second — et ce projet a deja paye cher les automates concurrents (charges programmees de l'ESS §26, consigne a 4200 W §27, plancher SOC de l'ESS §27).

Reste aussi a construire, apres sa reconfiguration du LVHUB : le resume de l'etat des cellules cote Home Assistant, et le bouton lui-meme.


Document mis a jour le : 18/09/2026 (~03:15) — releve par cellule avec le courant, conception de la charge HC manuelle arretee et non ecrite

74. Session du 20/09/2026 — Nuit LVHUB, correctifs swap rapide, et re-mesure du parc assaini

La nuit LVHUB (18-19/09) : le detail batterie est mort depuis le 15/09, pas a cause des packs

Detail complet dans docs/PROTOCOLE_LVHUB_CERBO.md. En resume : le proprietaire a voulu passer le pack US2000C en maitre a la place du pack US5000. Six leviers essayes, aucun ne ramene les trames etendues du Cerbo (nombre de modules, capacite, tensions de cellule, firmware) : rescan, redemarrage Cerbo, MAJ firmware Cerbo, inversion du maitre, reset hub, sequence complete rejouee. Les trames de BASE passent (SOC, limites 250/250 A, alarmes → le BMS protege) ; seules les ETENDUES sont null, et ce depuis le redemarrage HA du 15/09 05:09 (§69), donc AVANT toute manip de packs. Cause = la couche logicielle Venus can-bus-bms v0.71, pas le materiel ni l'ordre des packs. Ne plus toucher au materiel batterie pour ce symptome — demontre inutile. A instruire a froid : firmware du LV-Hub, ou effet de la MAJ Venus, sur doc constructeur. Config a garder : US5000 maitre → cable bleu → hub → Cerbo, cable gris entre packs (seule combinaison qui donne les 3 bips). Le chainage inter-groupes ne marche que dans le sens US5000-tete.

Incident du swap rapide (19/09) et les correctifs — P11-bis, bouton reset, alerte

VW debranchee ~14:50, JEEP branchee, mais le swap n'a dure que 14 s → la borne a GELE son etat (charging_time reste a 1h25, jamais reinitialise). P11 (base uniquement sur charging_time ≤ 90s) restait aveugle → le collant "vehicule plein" de la VW a bloque la JEEP, qui n'a pas charge de l'apres-midi. Un debranchement FRANC de 30s a tout debloque.

Correctifs deployes (golden 113 → 116) :

  • P11-bis : sessionFresh gagne un SECOND signal, l'age du dernier (re)branchement (as_timestamp du last_changed de vehicle_connected), independant du charging_time de la borne. Le swap de 14s avait bien fait bouger ce last_changed. Meme fenetre 90s. Gabarit + INPUT_PARSE (evcsVehicleConnectedAgeSec) + WINDOWS.
  • Bouton reset borne (input_boolean.evcs_reset_borne, carte EVCS Master) : echappatoire manuelle, force une session fraiche un cycle, momentane (le controleur le rebascule off).
  • Detection anomalie "branchee mais ne charge pas" (historiseur) : helper input_text.evcs_alerte_charge (SPARK/OpenClaw envoie le WhatsApp), toggle maitre input_boolean.evcs_alerte_charge_enabled (OFF, opt-in, seuils 500W/10min A CALIBRER). Critere robuste par la censure sans injection.
  • Cote OpenClaw (SPARK) : le concierge ecrit desormais AUSSI le selecteur input_select.victron_evcs_vehicle sur confirmation WhatsApp (plus besoin de le regler a la main), + message spontane utilisable (une reponse VW/JEEP sans question posee est appliquee) + menage gateway (la saturation qui avalait les messages).

⚠️ RE-MESURE DE LA COURBE BATTERIE SUR LE PARC ASSAINI (le point majeur)

Le parc compte maintenant 13 equivalents US2000 sains : 4 US5000 (pack 1, neufs 03/2026) + 5 US2000C (pack 2, dont l'ancien module sain …0323). Les modules abimes (cellules 12 et 15 mortes) sont sortis. Cellules du pack 2 mesurees a 3-6 mV d'ecart (contre 168 mV sur l'ancien parc), SOH 99.

node tools/mesure-courbe-batterie.mjs 8 sur ce parc donne une courbe PLATE a 0,30 kWh/point de 25 a 97 %, PLUS DE GENOU. Le genou a 33 % (§41) etait la signature de la cellule 12 morte : il a disparu avec elle. La reserve utile a quasiment double : 12,4 → ~23 kWh (100 → 22 %).

Recalage deploye (rec 1) :

  • src/energy-balance/BALANCE.js : kneePct 33 → 0, pentes 0,169/0,097 → 0,1615 plat (× echelleParc 13/7 = 0,30 au parc actuel).
  • src/nodes/INPUT_PARSE.js : batteryUsableCapacityKwh 2,414 → 2,308 kWh/module (0,30/13 × 100).
  • helper evcs_balance_usable_capacity_kwh 15 → 23 (inerte mais coherent).
  • Tests bilan 50/50 (10 attendus re-figes, verifies a la main), golden 116/116, golden-acm 20/20. Mutation deux sens sur la courbe.

⚠️ La courbe est un RESULTAT DE MESURE, a remesurer apres toute evolution du parc. Les constantes 0,1615 et 2,308 valent pour 13 modules ; le scaling modules/7 (helper evcs_balance_modules_en_service = 13) adapte au nombre.

Rec 2 : top-off ACM 52,4 → 52,8 V

Le 52,4 V avait ete pose le 06/09 pour menager la cellule 12 faible du pack ancien. Ce pack est sorti ; on releve le top-off pour atteindre une charge plus pleine et recalibrer la jauge SOC Pylontech (qui derive tant qu'on n'atteint jamais 100 %). 52,8 V = 3,52 V/cellule, sous les 53,2 V autorises par le BMS, et la cellule haute du pack 2 est a 3356 mV. patch rejouable tools/patch-20260920-topoff-52v8.mjs.

⚠️ ANGLE MORT A LEVER : cellules du pack 1 (US5000) hors surveillance

Le releve serie continu est sur le pack 2 (US2000C) ; les trames etendues du Cerbo sont mortes (voir ci-dessus). Les cellules du pack 1 (US5000) ne sont donc PAS surveillees en continu. Lues saines le 19/09 (pwr : Vlow 3330 / Vhigh 3331, ~1-2 mV), et les US5000 sont neufs. Le proprietaire a commande un 2e cable console pour surveiller les deux packs. La re-mesure de courbe et la capacite, elles, couvrent BIEN le parc entier (agregat SOC/puissance du Cerbo). Seule la sante CELLULE du pack 1 manque — d'ou la prudence sur le top-off, applique sur autorisation explicite du proprietaire.

Production vs consommation (7 jours, 13-19/09)

Maison a ~40 kWh/jour, conso qui DEPASSE souvent la production (15/09 : 29 vs 39 ; 17/09 : 27 vs 42). Deja tendu en septembre ; l'hiver sera nettement deficitaire. (PV mesuree = censuree sans injection, ressource reelle plus haute les jours ou la batterie s'est remplie tot.)


Document mis a jour le : 20/09/2026 — nuit LVHUB (trames etendues mortes cote Venus), P11-bis + bouton reset + alerte, courbe batterie remesuree (plate 0,30, ~23 kWh utiles), top-off 52,8 V

75. Session du 24/09/2026 — Le releve des cellules migre du portable vers le Synology .17

Le declencheur

Le releve serie des cellules Pylontech tournait sur le PC PORTABLE (boucle deploy/releve-cellules/lancer.sh, @reboot crontab). Il est mort le 20/09 a 17:29 : le portable a redemarre a 18:13, son garde anti-doublon a vu un pgrep transitoire et a refuse de relancer -> 4 jours de trou dans la table cellule, Grafana « No data » (§74 finissait juste avant ce constat). Le portable n'a pas vocation a rester allume ; le proprietaire a propose d'utiliser le .17 (ARCHIVE3, DS920+), toujours allume, avec le cable FTDI sur un port USB du NAS.

Ce qui a rendu la migration possible (verifie, pas suppose)

  • DSM embarque les pilotes usbserial.ko + ftdi_sio.ko (presents dans /lib/modules/), simplement pas charges d'office. Verrou decisif leve.
  • Le .17 monte deja le depot en NFS : 10.10.10.22:/volume2/projects -> /volume1/homes/TMZF5455/projects. Le conteneur ecrit donc les CSV dans le meme docs/mesures/batteries/ que lit le service d'historisation du .22. Un seul maillon bouge : le lecteur serie. Toute la chaine aval est inchangee.
  • ⚠️ Le montage NFS est noexec : node LIT le .mjs (OK), mais un script ne peut pas etre EXECUTE directement depuis ce montage.

Ce qui a ete construit (deploy/releve-cellules/)

  • compose.yaml : conteneur node:20-bookworm-slim, restart: unless-stopped, device /dev/ttyUSB0 mappe, depot monte en /depot. Tourne en user: 1026:100 (PAS root : le NFS peut ecraser root via root_squash ; le dossier CSV est g+w pour users). TMZF5455 est dans le groupe docker -> compose sans sudo. Remplace la boucle lancer.sh.
  • charger-pilotes.sh : charge les modules (verifie via lsmod, ne fait PAS confiance au code retour de modprobe) et chmod 666 /dev/ttyUSB0 (le conteneur n'est pas root). A lancer au boot en root.
  • README.md : deploiement, bascule depuis le portable, verification.

Garde anti-blocage ajoute a pylontech-console.mjs (--suivi)

Cause racine de la panne du 20/09 identifiee : lireJusqua resout au timeout au lieu de rejeter, donc un port fige renvoie du vide en boucle sans jamais crasher — restart: unless-stopped ne l'aurait pas rattrape. Un controle du mtime du CSV force process.exit(1) apres 3x le pas sans ecriture, pour que le superviseur (conteneur ou lancer.sh) rouvre le port. Protege les deux hotes.

L'EPREUVE DU FEU : reboot + saut majeur DSM 7.3.2 -> 7.4.1 le meme coup

Le proprietaire a tout tente d'un coup. Le premier boot a echoue, revelant deux pieges que l'update majeur cree — desormais corriges et documentes dans le README :

  1. noexec : la tache DSM appelait le CHEMIN NU du script -> « Permission denied ». Corrige : commande sh <chemin> (lit au lieu d'executer).
  2. L'update majeur EFFACE le sudo passwordless : DSM 7.4.1 a supprime le NOPASSWD de TMZF5455, donc l'auto-elevation sudo -n du script est morte. Parade durable : la tache de boot tourne en root (aucun sudo requis). Un update majeur peut aussi DESACTIVER les taches declenchees — a reverifier.

Autres faits etablis : le noyau est inchange (4.4.302+ en 7.4.1, donc pas de souci de vermagic) ; les .ko existent toujours ; le cable FTDI est bien enumere (0403:6001 FT232R USB UART). insmod en root fonctionne sans erreur.

Preuve du dernier maillon (reboot propre, ZERO intervention)

Second reboot, sans rien charger a la main :

uptime 14 min  ·  /dev/ttyUSB0 cree a 14:14:11 en crw-rw-rw- (666)
conteneur Up  ·  base .22 ingere les 5 modules (dernier point 14:24)

Le 666 est la preuve decisive : seul charger-pilotes.sh fait ce chmod, et il n'a pas ete lance a la main -> la tache de boot (root, sh) s'est executee seule. Chaine complete automatique validee.

Config finale de la tache DSM (a ne pas casser)

Planificateur de taches DSM, tache declenchee « Au demarrage », utilisateur = root, commande :

sh /volume1/homes/TMZF5455/projects/EVCS/EVCS/deploy/releve-cellules/charger-pilotes.sh

Le @reboot lancer.sh du portable est retire de son crontab (remplace par une note) : ne jamais le reactiver, il ferait un second lecteur sur la meme chaine (relevés faux, silencieux).

Limite inchangee

Le cable est sur le pack 2 (5x US2000C) ; le pack 1 (US5000) reste hors surveillance cellule jusqu'au 2e cable en commande. La courbe de capacite et le SOC couvrent tout le parc (agregat Cerbo).


Document mis a jour le : 24/09/2026 — releve migre sur le Synology .17 (conteneur + tache boot root/sh), survie prouvee sur reboot + update DSM 7.4.1

76. Session du 24/09/2026 — Le rapport de nuit extrapolait l'import sur les heures futures

Le bug (signale par la session SPARK via les compteurs d'energie)

Rapport du 24/09 : « Au total 4,2 kilowattheures importes » pour la nuit, contre ~1,6 kWh vus par deux compteurs d'ENERGIE independants (le compteur EDF de facturation edf_general_ok_energie_totale : 1,53 ; les compteurs AC-in Victron airtria_mpii_v1_energy_from_ac_in_1_to_* : 1,60). Deux compteurs concordent : c'est le 4,2 qui est faux.

La cause — et ce n'etait PAS celle proposee d'abord

SPARK diagnostiquait une surestimation par la cadence d'echantillonnage (sum/60000 supposant 1 point/min alors qu'il y en aurait 2,7). Faux, et verifie en base : le SQL ne sommait pas les points bruts, il construisait une grille generate_series a la minute + derniere valeur connue, donc 1 point/min par construction. Donnees d'ailleurs eparses : 130 points sur 961 minutes. Le 2,7 (= 4,2/1,53) etait une coincidence numerique, pas un mecanisme.

La vraie cause : le rapport tourne a 06:47 mais sa fenetre va jusqu'a 10:00. Le generate_series remplit les ~3 h futures, et la derniere puissance connue (le reseau qui importait encore a 06:47) est reportee sur 193 minutes fantomes. Preuve : rejeu de l'ancienne methode tronquee a 06:47 -> 3,79 kWh, cohcerent avec le 4,2 ; la meme methode sur la fenetre COMPLETE (donnees presentes cet apres-midi) redonne 1,60. Elle n'etait donc fausse QUE parce qu'elle tournait avant la cloture de sa propre fenetre.

⚠️ Lecon de methode : cette cause ne se trouve qu'en REJOUANT la requete sur les donnees, pas en raisonnant sur un ratio. J'ai rejoue plutot que de croire le diagnostic du ratio sur parole — c'est ce qui a evite de « corriger » la mauvaise chose.

Le correctif (deploye 24/09, ec5f43e)

tools/rapport-nuit.mjs : integration ponderee par l'intervalle reel entre points (lead(ts), cap 300 s pour qu'un trou n'extrapole rien ; aucun point apres le dernier echantillon donc aucune extrapolation) et fenetre plafonnee a now(). Applique aux QUATRE grandeurs integrees (importe, injecte, voiture, batterie_chargee) qui partageaient la formule. Verifie : 1,60 kWh sur fenetre complete (= les compteurs), 1,19 en simulation 06:47 (import reel jusque-la).

⚠️ Option ecartee : lire le delta d'un compteur d'ENERGIE cumule (le plus robuste en principe) est inapplicable ici — ces compteurs (edf_general_ok_energie_totale, AC-in Victron) ne sont PAS dans la table PostgreSQL mesure (seul sensor.victron_grid_power_l1 y est) ; SPARK les lisait direct sur HA (recorder SQLite). Si un jour on veut la methode compteur, il faut d'abord ajouter ces entites a l'historisation.

⚠️ Portee : tous les rapports depuis le 08/09 surestimaient ces kWh (ils tournent toujours avant 10 h). Le RESTE du rapport (SOC, heures de fermeture, vehicule, injection) etait juste — ces faits ne sont pas integres.

Decalage de l'heure d'envoi 06:47 -> 07:30 (proposition du proprietaire)

Le fix stoppe la SURestimation, mais a 06:47 le rapport reste INCOMPLET : il rate l'import 06:47->07:24. Raisonnement du proprietaire : la fenetre HC ferme a 07:24, et si l'ajustement de la cible du matin fait son travail il n'y a pas d'autre fermeture EDF ensuite — donc un rapport apres 07:24 capture l'import COMPLET de la nuit. RAPPORT_HEURE/MINUTE passe a 07:30 (historise-evcs.mjs).

Reserve consignee : les jours ou la cible du matin est trop basse, l'hysteresis peut refermer EDF apres 07:24 (cas plein tarif §39) et un rapport a 07:30 le raterait. C'est l'exception ; 07:30 est strictement meilleur que 06:47 (toujours incomplet). La fenetre de calcul restant plafonnee a now(), l'heure d'envoi peut etre avancee/reculee sans risque de fantome.


Document mis a jour le : 24/09/2026 — rapport de nuit : integration ponderee + fenetre plafonnee (fin de l'extrapolation), envoi decale a 07:30 pour un total complet

77. Session du 24/09/2026 — Banc de test PV pour l'agent vocal OpenClaw

Contexte

Le proprietaire pilote OpenClaw en vocal depuis son iPhone et veut le faire travailler la gestion PV. Coordination avec la session SPARK (qui tient OpenClaw) : moi la source de verite et les attendus (j'ai PostgreSQL/InfluxDB et l'historique HA), SPARK le harnais et la fiche de l'agent. Deux etages, comme pour le concierge (§65/§66) : je ne touche pas a OpenClaw, SPARK ne fabrique pas d'attendu.

La source de verite d'abord (consignee MANUEL §0)

Production PV reelle du jour = SOMME des trois *_yield_today par chargeur (airtria_mppt1 + mppt_450_200_hq2449yr9pt + v1), remis a zero chaque nuit. Verifie le 24/09 (45,1 kWh) et recoupe par l'integrale de victron_pv_power_system (44,0). Trois pieges ecartes : energy_production_today = PREVISION (Forecast. Solar), gx_device_pv_energy = compteur PARTIEL et gelable, *_d_hier = la veille.

Le banc — 9 gammes, snapshots reels figes + attendus

Modele : campagne-conseiller.mjs (§63). Chaque gamme = fiche de l'agent + snapshot capteurs fige + question, note sur >=3 passages. Snapshots batis sur mes chiffres reels (jamais inventes), tires de la table mesure (puissances, SOC, courant, import) et de l'historique HA (yield_today, modes MPPT, prevision). Les 4 competences :

  • Lire la bonne source : G1 production du jour (45,1 = somme yields, pas la prevision) · G2 reste a produire (26,8 = energy_production_today_remaining, le SEUL cas ou la prevision est juste).
  • Discriminer : G3 bridage batterie pleine (SOC 97, +1 A, 7 MPPT voltage_current_limited, import nul -> ecretage normal) · G4 vraie baisse (PV quasi identique a G3 mais SOC 69, +25 A, 7 MPPT mppt_active -> ressource limitee, rien de gache) · G5 substitution anormale (MPPT bridés MAIS import ~ conso -> le reseau remplace le solaire, SIGNALER).
  • Decider : G6 charge voiture refusee sur excedent negatif (-1854 W, batterie en decharge) · G7 reste-t-il du solaire a capter (un MPPT limited -> oui, ressource refusee dispo).
  • Hygiene donnees : G8 compteur GX gele (last_changed vieux -> ne pas citer) · G9 yield_today unavailable (production partielle, ne pas sommer un trou).

Ce que le banc a TROUVE (sa raison d'etre)

G5 a echoue 0/3 au premier tour. La regle de substitution etait pourtant dans la fiche, mais l'agent concluait « bridage normal » sur batterie pleine + MPPT bridés SANS verifier l'import reseau. SPARK a reordonne la fiche pour forcer l'import en controle nº1 (« jamais conclure bridage sans avoir verifie que l'import ~ 0 ») -> G5 = 3/3, G3/G4 sans regression. C'est le cas qui verrouille la distinction bridage/substitution — celle qui a coute des mois d'observation au proprietaire (§26-§31). Banc complet : 9/9 sur 3 passages.

Statut des snapshots (pour un futur re-figeage)

Reels a figer : G1, G2, G3, G4, G6, G7 (dates/valeurs dans les messages a SPARK du 24/09). G5 = valeurs reelles du 27/08 (hors table mesure, anterieures au 04/09) mais comportement CORRIGE depuis le 31/08 -> cas d'apprentissage, ne se reproduit plus. G8/G9 = synthetiques d'hygiene. Le banc extrait la fiche live d'AGENTS.md d'OpenClaw a chaque run : toute evolution de la fiche est re-testee.

Capteurs de discrimination (rappel, tous lus par l'agent)

Les 7 *_operation_mode (v1, airtria_mppt1, mppt_450_200 device + 4 trackers), victron_battery_soc, victron_battery_current (signe = sens), victron_consumption_power_l1, victron_grid_power_l1, victron_pv_power_system. Regle-cle : bridage NORMAL ⇒ import nul ; substitution ANORMALE ⇒ import ~ conso.


Document mis a jour le : 24/09/2026 — banc PV 9/9 pour OpenClaw (source de verite + discrimination bridage/vraie baisse/substitution), la source vit dans MANUEL §0

78. Session du 24-25/09/2026 — Détail cellule exposé en helpers HA pour l'agent vocal

Les trames BMS cellule de HA sont mortes depuis le 15/09 (§69) ; le relevé série (table cellule, sur le NAS depuis §75) est la seule source vivante. Pour que l'agent vocal OpenClaw lise le détail cellule en direct sans taper PostgreSQL, le service d'historisation publie désormais trois helpers HA à chaque cycle, depuis le dernier relevé série :

  • input_number.evcs_cell_spread_max_mv — écart max du pack (mV)
  • input_text.evcs_cell_weakest_module — code-barres du module le plus sensible
  • input_number.evcs_cell_releve_age_min — âge du dernier relevé (min)

L'âge permet à l'agent la même discipline que pour le gel GX : frais → il donne la valeur, vieux → il dit « muet ». publierCellulesHelpers() dans tools/historise-evcs.mjs (SQL sur les modules du dernier lot, à ±5 min du max(ts)), appelée après l'ingestion. Package evcs_cellules.yaml (sans initial: — réécrits chaque cycle), rechargé sans redémarrage. Vérifié en réel le 25/09 : écart 21 mV, âge 1 min, module K220803C30160323 (le plus ancien du pack 2). SPARK bascule l'agent dessus (fiche + gamme G13 live).


Document mis a jour le : 25/09/2026 — détail cellule publié en helpers HA (source série vivante), agent vocal câblé

79. Session du 25/09/2026 — Les jugements sortent de la fiche de l'agent vocal

SPARK a restructure l'AGENTS.md d'OpenClaw (28 247 / 30 000 car, plafond par fichier du gateway) : permutation pure des sections, l'energie en tete (dans les 45 % gardes mot pour mot si le fichier tronque), md5 des 10 sections identiques. Le code du gateway ne charge que AGENTS.md et USER.md : pas de split possible.

⚠️ Decouverte : la fiche est sur le fil du rasoir. 96 car ajoutes dans le bloc CELLULE (pour G13b) ont fait tomber G5 (substitution) de 7/7 a 1/7, sans lien semantique. Plutot que de rediger mieux, les deux jugements sont sortis du modele et calcules par le service d'historisation (tools/diagnostics-agent.mjs, fonctions pures, node tests/diagnostics-agent/run.mjs 15/15, deux mutations qui mordent) :

  • sensor.evcs_pv_diagnostic : bridage_normal / substitution_edf / ressource_limitee / reseau_en_appoint / pas_de_production / indetermine, avec explication et grandeurs en attributs. L'import est teste AVANT de conclure au bridage, par l'ordre du code (regle du 31/08). Bruit compteur 100 W.
  • sensor.evcs_cellules : ecart max (mV) si releve frais ; perime au-dela de 30 min (3 pas de 10 min, comme la garde anti-blocage §75) SANS ecart ni module en attribut ; muet si base injoignable ou cycle en echec. L'agent ne peut plus citer une valeur perimee : il ne la voit pas. Les deux sont republies meme quand le cycle echoue. Les anciens helpers evcs_cell_* (§78) sont RETIRES le 25/09 apres bascule de la fiche par SPARK (package evcs_cellules.yaml supprime, sauvegarde .bak-20260925 a cote). Banc SPARK apres migration : 13/13, G5 7/7, G13b 7/7 ; fiche 28 247 -> 27 129 car.

Suite du meme jour, deux capteurs de plus (tests 26/26, mutations qui mordent) :

  • sensor.evcs_production_jour_kwh (G1/G9) : somme des trois yield_today device, statut complet/partiel/indisponible, chargeurs muets nommes. ⚠️ Les trois chargeurs se remettent a zero a des heures DIFFERENTES (00:37, 00:41, 00:45 le 25/09) : un rendement non nul modifie avant minuit compte pour 0.
  • sensor.evcs_charge_solaire (G6/G7) : RELAIS de la decision du controleur (non_branchee / en_attente / en_charge_solaire / en_charge_reseau / indetermine), raison du controleur et excedent en attributs. Delibrement PAS un second arbitre : l'agent explique, il ne decide pas. Defaut corrige dans la fiche par SPARK : « off » ne prouve pas une ressource refusee (la nuit les 7 sont off) — seul limited compte. Multi-agents (code du gateway lu par SPARK) : possible, routage par point d'entree (binding canal/compte/fil), outils et memoire isoles par agent ; UN seul point d'entree vocal => le vocal reste a l'agent par defaut (maison). Serveur Spark max-batch-size 1 : requetes serialisees, aucun gain de debit. Retenu : pas d'agent d'accueil (double la latence, aiguillage faillible), un fil par agent.

Bilan final de la migration (25/09, ~02:00)

La partie energie de la fiche OpenClaw ne CALCULE plus rien : elle relaie des etats et des explications. Fiche 28 247 -> 27 006 car (marge ~3 000 sous le plafond de 30 000), backup conserve cote SPARK. Banc complet 15/15, et 7 passages sur chaque cas migre : G5, G6, G7, G7b, G7c, G9, G13b = 7/7.

question de l'agent il lit avant
bridage ou substitution ? (G3-G5) sensor.evcs_pv_diagnostic (etat) jugeait l'import et les modes
reste-t-il du solaire a capter ? (G7) sensor.evcs_pv_diagnostic (etat) comptait limited/off (faux la nuit)
production du jour (G1/G9) sensor.evcs_production_jour_kwh sommait trois capteurs
la voiture peut-elle charger ? (G6) sensor.evcs_charge_solaire calculait PV − conso
reste a produire (G2) sensor.energy_production_today_remaining soustrayait prevision − realise
detail cellule (G13) sensor.evcs_cellules lisait des helpers qui gardaient la valeur perimee

Correspondance « reste a capter », a ne pas remplacer par l'attribut brut mppt_bridés (qui vaut 0 quand les modes sont illisibles et ferait dire « non » avec assurance) : bridage_normal -> oui · substitution_edf -> oui + anomalie a signaler · ressource_limitee / reseau_en_appoint / pas_de_production -> non · indetermine -> « je ne peux pas le dire ». Verrouille par G7c.

Reste dans la fiche, volontairement : G8 (ne jamais lire un compteur GX comme un delta) — une interdiction, pas un jugement ; P1 et P3 retirent a l'agent toute raison de lire ces compteurs.

⚠️ Doctrine a garder pour toute evolution de l'agent : quand une reponse depend d'un raisonnement sur des mesures, le calcul va dans tools/diagnostics-agent.mjs (fonction pure + test + mutation) et l'agent relaie l'etat. Une regle en prose est fragile : un ajout sans rapport ailleurs dans la fiche l'a fait basculer (G5, 7/7 -> 1/7). Et quand une decision existe deja ailleurs (controleur EVCS), l'agent la RELAIE, il ne la recalcule jamais. Limite connue : si le service d'historisation meurt, les quatre capteurs se figent ; l'agent verifie sensor.evcs_historisation_heartbeat.


Document mis a jour le : 25/09/2026 — agent vocal : la partie energie relaie des capteurs-verdict (banc 15/15), cadrage multi-agents