Le releve serie tournait sur le portable et est mort 4 jours au reboot du 20/09. Migre sur le .17 (conteneur node + tache de boot root/sh chargeant les pilotes). Survie prouvee de bout en bout sur reboot + saut majeur DSM 7.3.2 -> 7.4.1, qui a revele et fait corriger deux pieges (NFS noexec, sudo passwordless efface par l'update). @reboot du portable retire. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GwpFpy2aQ2rJok6tNQSbVj
406 KiB
HANDOFF.md — EVCS / Grid Hysteresis — État au 09/07/2026
1. Contexte Projet
- Installation : Victron ESS sans injection réseau. Batterie 17,5 kWh utile.
- Workflows n8n actifs :
HA Snapshot EVCS Surplus Controller V2.6(idATswvGfG4KJUls0K) — pilote la borne EVCS (30 s)Victron - Battery / Grid Hysteresis Fix V1(id7iIlu7LLTW7TZA8o) — pilote le switch EDF (1 min)
- Accès :
- HA :
http://10.10.10.17:8123— jeton dansjeton_EVCS.txt(⚠️ en clair, à révoquer si dépôt partagé) - n8n :
https://n8n.airtria.pro— clé API dans~/.config/opencode/opencode.json(mcp.n8n-mcp) - MCP opencode (option 1 déployée) : 5 serveurs distants dans
~/.config/opencode/opencode.json(ha-get-state,ha-set-helper,grid-set-state,victron-set-charge-limits,pv-get-forecast) → endpoints SSEhttps://n8n.airtria.pro/mcp/<path>/sse. Les 4 outils paramétrés utilisent$fromAI(...)(corrigé le 09/07). ⚠️ Endpoints MCP n8n sans authentification — à sécuriser (Bearer sur les nodes MCP Server Trigger).
- HA :
- Fichiers de référence locaux :
WINDOWS/WINDOW_SOURCEV2.js,BATTERY/BATTERY_CLOUD_LOCKS.js,HYSTERESIS/COMPUTE.js— copies de lecture (le code qui fait foi est dans n8n)modified/GRID_HYSTERESIS_backup_20260709-*.json— rollback avant P1modified/EVCS_V2.6_backup_20260709-*.json— rollbacks avant P2 puis P3 (2 fichiers)
2. Architecture Data-Flow
EVCS V2.6 (30s) :
HA Snapshot (template) → INPUT_PARSE → WINDOWS_SOURCEV2
→ BATTERY_CLOUD_LOCKS → DECISION_ENGINEV2 → HA_SERVICE_CALLS_OUTPUT
→ Build Actions → Execute HA Service
Grid Hysteresis (1min) :
HA Snapshot → Compute Grid Hysteresis → Build Actions → Execute HA Service
Communication inter-workflows via helpers HA :
input_text.victron_evcs_source_mode (labels FR), input_boolean.victron_evcs_night_grid_takeover_needed,
input_text.victron_evcs_surplus_state, input_select.victron_evcs_strategy.
3. Patchs déployés le 09/07/2026 (session opencode)
P1 — Grid Hysteresis (déployé, vérifié)
- Entité fantôme corrigée : le snapshot lisait
input_select.victron_evcs_user_strategy(inexistante → toujoursunknown). Corrigé eninput_select.victron_evcs_strategy. Conséquence avant fix : les overridesforce_grid_now/force_charge_nowet la coupure « voiture pleine » ne fonctionnaient JAMAIS dans l'hystérésis. - Switch EDF
unavailable: nouvel étatswitch_unavailable= aucun ordre, aucun timestamp, réévaluation complète au retour du switch. Avant : spamturn_onchaque minute + rallumage aveugle à la reconnexion (incident du 08-09/07 : disjoncteur physique coupé par l'utilisateur, EDF rallumé à 00:41 au retour, batterie chargée sur réseau à SOC 48 %). last_on_at: réécrit uniquement sur vraie transition (latch OFF→ON), plus à chaque retry. Avant :hold_on_min_runtimepouvait durer éternellement.- Bug latent corrigé : le bloc
turn_off_force_grid_vehicle_full(V2.5) était écrasé par la chaîne principale (code mort). FlagforcedCutajouté.
P2 — EVCS V2.6 : continuité takeover nuit / anti-créneaux (déployé, vérifié en réel)
- Le snapshot relit
night_takeover_prev(= helper takeover du cycle précédent). nightTakeoverLatcheddansWINDOWS_SOURCEV2etBATTERY_CLOUD_LOCKS: une fois le réseau HC engagé en session nuit, on y reste jusqu'à fin de session (fenêtre/débranchement) ou SOC ≥ 90 (NIGHT_TAKEOVER_RELEASE_SOC_PCT).- Supprime le cycle « château fort » : batterie→jeep→seuil 20 %→EDF ON→30 %→EDF OFF→batterie...
- Vérifié en production dans la nuit du 09/07 : takeover engagé à SOC 35 %, EDF resté ON,
Jeep en charge continue (
hold_min_run).
P3 — EVCS V2.6 : provocation PV batteries pleines (déployé, dry-run complet)
- Problème (ancien sujet de ce HANDOFF) : sans injection, batteries pleines ⇒ MPPT bridé à la
conso maison ⇒ aucun seuil PV observable ; en plus
forecastBufferKwh(10,5 kWh véhicule soustraits) toujours ≤ 0 l'après-midi, etcloudDebten deadlock (batterie pleine = courant nul = dette jamais remboursée). Sonde afternoon inopérante (probe_enabled = off+ seuil PV 1000 W). - Fix :
pvProvocationEligible = SOC ≥ 90 && batterie non déchargée→solar_potential(intégré àisHighBatteryForPotential, donc actif sur les 4 profils jour). Gardes aval intactes. - Attention réglage :
soc_start_min(HA) = 94 ⇒ plancher de démarrage hors fenêtre. Le déclenchement pratique est donc SOC ≥ 94 %. Baisser ce helper à 90 pour aligner.
P4 — Maintien SOC adapté au forecast pendant le takeover nuit (déployé 09/07 après-midi)
- Découverte clé :
Victron - Auto Charge Manager V1.2(idcTeeHy4ePiVeELLh) est le propriétaire des limites DVCC (MaxChargeCurrent/MaxChargeVoltagevia MQTT, feedback :sensor.victron_max_charge_current_setting). Son mode « nuit idle » laissait 30 A de charge batterie dès que EDF était ON → batterie rechargée à fond pendant un takeover. - ACM patché : nouvelle branche prioritaire
evcs_takeover_hold/evcs_takeover_recover: sinight_grid_takeover_neededON + EDF ON → SOC < cible-1 : recharge 30 A ; sinon maintien 2 A / 50.1 V (maison + VE sur HC, batterie en pause). Cible lue depuisinput_number.victron_evcs_night_target_soc(écrite par le workflow EVCS : forecast fort→35 %, moyen→55 %, faible→85 % — donc été ~35, hiver ~85 comme demandé). - EVCS patché : avant minuit (20h→8h), la cible matin utilise
max(today_remaining, tomorrow_kwh)(sinon today_remaining≈0 à 23h → cible 85 % à tort en été). Ajouttomorrow_kwhau snapshot +parsed.tomorrowKwh. - Dry-runs : 8 scénarios ACM + 2 scénarios cible EVCS validés (voir session 09/07).
Autres réalisations du 09/07 (après-midi)
- Borne fantôme résolue : firmware EVCS 2.05 → 2.08 (session repartie de zéro, détection véhicule correcte). Les 19 kWh affichés étaient un cumul de session non réinitialisé.
- Dashboard « EDF Urgence » créé via API HA (sidebar) : scripts
script.edf_urgence_off(hystérésis OFF + latch OFF + stratégie stop + switch OFF — rien ne rallume),script.edf_force_on,script.edf_rendre_la_main+ cartes d'état et « pourquoi ». - Incident Synology : NAS éteint dans la nuit (cause à chercher dans les logs DSM) → n8n + HA morts → EDF resté ON toute la nuit, batterie chargée à 96 % sur réseau. ⚠️ SPOF structurel : tout le contrôle vit sur le Synology.
P5 — Plafond courant EVCS configurable quand EDF est ON (déployé 09/07 soir)
- Le plafond 20 A venait de
cfg.evcsDedicatedMaxW(4900−300 W / 230 V) codé en dur dans INPUT_PARSE. - Nouveau helper
input_number.evcs_plafond_courant_edf_on(6–32 A, défaut 20) : quand AC-IN est physiquement présent (acin1_available, pas juste le switch), le plafond = helper ; hors réseau, retour au plafond câblé ~20 A. Dry-run 5 scénarios OK (dont disjoncteur coupé). - Subtilité :
victron_evcs_current_max_auto_a(22) plafonne EN PLUS les modes auto ; en force_grid/force_charge seul le nouveau helper compte. - Boutons dashboards (EVCS Master = fichier YAML
/mnt/ARCH3/docker/HA/dashboards/evcs_master.yamlédité via NFS ; EDF Urgence = API) : carte « Plafonds courant EVCS » + boutons ⚡ BOOST EDF 28A (script.evcs_boost_edf: plafonds 28/28 + force_grid_now + consigne 28) et Retour normal (script.evcs_boost_off: 20/22 + auto_solar_plus). - Sémantique clarifiée : « Courant demandé » = « Consigne envoyée » =
number.victron_evcs_set_current_command(réécrit toutes les 30 s par le workflow) ; « Consigne lue » = readback borne. En force_charge_now, PV ≥ 700 W ⇒ source « Solaire » ⇒ gardes nuages/batterie freinent : pour une grosse charge, force_grid_now.
P6 — Override « abondance » du plancher SOC de démarrage (déployé 12/07 midi)
- Symptôme : 12/07 midi, PV 8,9 kW, batterie en charge +125 A, Jeep branchée → EVCS bloqué
idle_wait_conditions. Cause unique (dry-run) : plancher SOC fenêtre midi (input_number.victron_evcs_midday_soc_min= 75) > SOC 66. - Fix (
BATTERY_CLOUD_LOCKS) : sipvPowerAvg10m ≥ 4000 WetbatteryCurrentA ≥ +20 A(abondance physique : la batterie monte même avec l'EVCS), plancher de démarrage abaissé à 40 % (effectiveSocStartFloor). Jours nuageux : planchers d'origine intacts. Debug exporté :solarAbundance,effectiveSocStartFloor(safeguards). - Vérifié en réel : déploiement 12:43 →
start_min_currentimmédiat → charge 12 A. - Observation borne : après des heures branchée en statut 6, la Jeep a mis ~2,5 min à
s'engager ; le workflow renvoie
turn_ontoutes les 30 s (retry intégré suffisant). - Planchers de référence : matin 35, midi 75, soir ?, hors fenêtre 94 (
soc_start_min).
Maintenance du 09/07 soir (analyse des messages d'erreurs)
- W-105 borne (surcharge) : survenu en charge 20 A sur onduleur seul (disjoncteur EDF coupé). Déjà couvert : gardes vebus_overload + P5 (plafond retombe à 20 A si AC-IN absent).
saveDataErrorExecution: allactivé sur les 8 workflows critiques (diagnostic des prochaines erreurs avec données des nodes ; Water Heater V2.3 avait erré 4× le 09/07 20:20-20:35, cause inconnue faute de données, rétabli seul).- Garage Thermal : la prise
switch.clim_hygro_socket_1est débranchée volontairement par moments → l'appel au switch est maintenant conditionné à sa disponibilité (execute: switchAvailable). Plus de warnings « missing entity » toutes les 10 min. Backupmodified/GARAGE_backup_*. - Templates MQTT durcis (33 occurrences,
configuration.yaml+packages/victron_vebus_mqtt.yaml) : gardevalue_json is defined→ plus d'erreursvalue_json is undefinedaux reconnexions MQTT. BackupsSAVE_CONFIG/*.bak.20260709-234800,check_configvalid,mqtt.reloadfait. ⚠️ Après unmqtt.reload, les sensors restentunknownjusqu'à republication Venus : publierR/c0619ab34ac5/keepalive(payload vide) force la republication complète.
4. Root Cause de l'incident « EDF ON à 48 % sans charge Jeep » (nuit 08→09/07)
Chaîne complète reconstituée via l'historique HA :
- Borne fantôme au matin du 08/07 (« véhicule en charge » sans véhicule) +
force_grid_nowde la veille ⇒ EDF maintenu ON (la coupure voiture-pleine était du code mort, cf. P1.1/P1.4). - L'utilisateur coupe le disjoncteur physique ⇒ switch
unavailablependant ~5 h. - Le workflow reste coincé en
hold_on_min_runtime(P1.3) en spammantturn_on(P1.2). - 00:40:54 : le switch revient ⇒ 00:41:03 EDF ON sans cause actuelle ⇒ Multiplus charge la batterie sur réseau (2980 W) ⇒ coupé à 01:06 (min_on 25 min, SOC ≥ 30). Tous les maillons sont corrigés par P1.
5. Sémantique utile (pour ne pas re-chercher)
- Latch (
input_boolean.victron_grid_hysteresis_latch) : « c'est l'automate qui a allumé EDF et il défend cet état ». Reprise en main manuelle : stratégiestop+ hystérésis OFF + latch OFF. - Fenêtres EVCS (helpers HA, corrigées/vérifiées le 09/07) :
night_hc01:24→07:24 (HC réelles),morning_solar07:30→11:00,midday_hc_or_solar12:24→14:24 (HC réelles),evening_high_soc17:00→20:30, sinonoutside_window. L'Auto Charge Manager utilisait des fenêtres codées en dur légèrement fausses (01:30-07:15 / 12:25-14:25) — alignées sur 01:24-07:24 / 12:24-14:24 le 09/07. - Seuils hystérésis réseau (HA, valeurs réelles) : ON ≤ 20 %, OFF ≥ 30 %, min_on 25 min.
- Cibles nuit : hard floor 22 % ; cible matin selon forecast : ≥18 kWh→35 %, ≥10→55 %, sinon 85 %.
nightSessionActive = fenêtre nuit && véhicule branché(⚠️ pas besoin de charge active). - Statut borne 6 = branchée en attente de démarrage (« Inconnu (6) » sur le dashboard).
- Labels FR dans
source_mode:Réseau forcé,Réseau HC autorisé,Assistance batterie nuit,Bloqué,Solaire,Potentiel solaire— l'hystérésis matche ces labels.
6. Méthode de travail validée (à réutiliser)
- Backup :
GET /api/v1/workflows/<id>→modified/*_backup_<stamp>.json. - Patch des
jsCodepar remplacement exact (script node),node --checksur chaque node. - Dry-run local de la chaîne complète : snapshot réel via
POST /api/templateHA, exécution séquentielle des 5 nodes avec shim$input, horloge simulable en surchargeantglobal.Date(voir session du 09/07). PUT /api/v1/workflows/<id>(⚠️ filtrersettings: seulsexecutionOrder/timezone/save*/executionTimeout/errorWorkflowsont acceptés).- Vérification live : helpers HA 40-70 s après.
7. Backlog / Point du 10/07 au réveil
- Vérifier la nuit de test P2+P4 (fenêtre HC 01:24→07:24, forecast fort → cible 35 %) :
- takeover engagé quand SOC ≤ 35 → EDF ON continu (pas de créneaux) ;
input_text.victron_charge_mode=evcs_takeover_recoverpuisevcs_takeover_hold;sensor.victron_max_charge_current_setting= 2 pendant le hold (batterie tenue ~35 %) ;- 07:24 : fin de fenêtre → EDF coupé (
turn_off_recovered), ACM repasse en profil jour. Historique utile :switch.edf_general_ok_switch,sensor.victron_battery_soc,input_text.victron_charge_modeentre 01:00 et 08:00.
- Cause de l'extinction du Synology (DSM → Centre des journaux) + réflexion SPOF : le fail-safe physique actuel est « EDF ON + charge batterie » quand le NAS meurt.
- Baisser
input_number.victron_evcs_soc_start_min94 → 90 (aligner avec P3) si souhaité. - Sécuriser : auth Bearer sur les 5 MCP Server Triggers n8n + révoquer/déplacer le jeton HA.
- À investiguer : « conditions de démarrage nuit batterie non réunies » quand la batterie
suffit pour la nuit (conditions de départ
battery_assist_night). - Optionnel : brancher un keepalive/republish Venus automatique après
mqtt.reload(cf. note maintenance §3).
8. Historique des sessions
-
12/06/2026 : analyse blocage outside_window, déploiement afternoon_probe (V2.6), fix clignotement EDF (
edf_general_ok_onconditionné au sourceMode). -
09/07/2026 (nuit) : incident EDF nocturne analysé (root cause complète), MCP option 1 en place, P1 (hystérésis robuste + entité stratégie), P2 (anti-créneaux nuit), P3 (provocation PV).
-
09/07/2026 (journée/soir) : incident Synology (NAS éteint → EDF ON toute la nuit, batterie 96 %), firmware borne 2.08 (fantôme résolu), dashboard EDF Urgence + boost 28 A, P4 (maintien SOC au forecast pendant takeover, via Auto Charge Manager), P5 (plafond EDF ON configurable), fenêtres HC corrigées (01:24-07:24 / 12:24-14:24), maintenance erreurs (save error executions, prise garage conditionnelle, 33 templates MQTT durcis).
-
12/07/2026 : P6 (override abondance du plancher SOC midi — PV fort + batterie en charge ⇒ démarrage dès SOC 40) ; validation en réel à 12:43, charge Jeep engagée.
-
17/07/2026 (après-midi) : Session de débogage majeure — résolution d'un enchaînement de bugs bloquants empêchant la charge solaire de la Jeep :
Bug 1 — Typo critique dans DECISION_ENGINEV2 :
- Ligne ~333 :
noDemandnightSessionNeedsClearScoreOff(variable inexistante) au lieu denoDemandScoreOff - Conséquence :
evcsVehicleNoDemandSemanticrestaitfalsemême quand la voiture était pleine - Le filtre "véhicule plein/pause" ne fonctionnait jamais → blocage du système
Bug 2 — Artefacts de corruption dans WINDOWS_SOURCEV2 :
- 3 occurrences de
tool_f6d3316d2001k4aAfEjbKGNUX2(résidu de copier-coller ou debug) - Provoquaient des erreurs
is not definedà l'exécution - Workflow en ERROR continu depuis 14:49
Bug 3 — Session nuit jamais clearée :
input_boolean.victron_evcs_night_session_activerestaitondepuis 23:24 la veille- Même après la fenêtre HC (01:24-07:24), le profil restait
night_hc→battery_assist_night - En plein jour avec grand soleil, le système pensait être en "assistance batterie nuit"
- Clearé manuellement à 14:45
Bug 4 — EDF rebranché automatiquement en journée :
- Condition
edf_general_ok_ondéclenchée dèssourceMode=grid_forcedougrid_hc_allowed - Même en plein jour avec du soleil, EDF se rebranchait si le workflow passait en ces modes
- Correction : ajout de
context.profile === 'night_hc'pour ne rebrancher que la nuit
Configuration ajustée :
battery_discharge_soft_a: 0A (au lieu de -8A) — permet le démarrage sans décharge batteriebattery_neutral_band_a: 0A (au lieu de -2A) — tolérance nullesoc_start_min: 80% (au lieu de 94%) — seuil de démarrage plus réalistenight_target_soc: 70% (au lieu de 85%) — laisse plus de marge pour le PV
Mode sonde activé :
probe_enabled: ON — permet le démarrage à 6A pour "provoquer" la production PV- Particulièrement utile en autoconsommation sans injection où le PV ne produit que si chargé
Résultat :
- Workflow réparé et fonctionnel
- Jeep en charge à 17A avec le soleil (16:58)
- EDF OFF (ne se rebranche que la nuit)
- Système 100% auto en
auto_solar_plus - DeliveryRatio à 99.9% — charge stable et efficace
À noter : Le courant de sonde (
probeCurrentA) est codé en dur dans le workflow n8n (nœud SETUP/PARAMS), pas dans un helper HA. Pour le changer de 6A à 10A, modifier directement dans l'interface n8n. Mais le workflow augmente automatiquement le courant (17A atteints), donc ce n'est pas critique. - Ligne ~333 :
⚙️ CONFIG LOCALE — Qwen3.6-35B sur DGX Spark (≠ gros modèle cloud)
⚠️ Cette config est CALIBRÉE pour le modèle LOCAL Qwen3.6-35B-A3B (3B actifs, contexte limité). NE PAS appliquer telle quelle à un gros modèle cloud (FABLE/Claude), qui n'a besoin ni du bornage strict ni du découpage par nœud.
Infra (validée le 16/07/2026)
- DGX Spark = 10.10.10.8, endpoint vLLM unique sur port 8000.
- UN SEUL modèle à la fois (contrainte NVRAM 1 Spark) → swap via scripts :
use-code: docker compose down (VL) + sparkrun run qwen3.6-35b sur :8000use-vl: sparkrun stop --all + docker compose up (Qwen3-VL, vision)
- Recette : @official/qwen3.6-35b-a3b-fp8-mtp-vllm --tp 1 --port 8000 --gpu-mem 0.8 --served-model-name qwen3.6-35b (MTP + flashinfer + tool-parser qwen3_coder déjà inclus dans la recette).
- Qwen3-VL (docker /opt/vllm) réservé à l'analyse de captures d'écran.
opencode.json (poste Ubuntu, PAS la Spark)
- baseURL: http://10.10.10.8:8000/v1, apiKey "EMPTY".
- Modèle "qwen3.6-35b" : options { temperature: 0.15, reasoningEffort: "none" }, cost tout à 0 (sinon compteur fantôme FABLE), limit context 65536.
- permission.bash : "curl *": "deny", "wget *": "deny" (bloque hallucinations HTTP).
- Les 6 MCP (n8n-mcp, ha-get-state, ha-set-helper, grid-set-state, victron-set-charge-limits, pv-get-forecast) : NE JAMAIS toucher ce bloc.
RÈGLES DE TRAVAIL spécifiques au local (dans AGENTS.md)
- Contexte trop petit pour un workflow entier (~357K = ~102K tokens > 65K). => JAMAIS charger le workflow complet. UN nœud / UNE tâche à la fois.
- n8n-mcp_get_workflow renvoie le workflow entier => opencode l'écrit dans ~/.local/share/opencode/tool-output/tool_* (tronqué). Le fichier commence par "Retrieved workflow: " AVANT le JSON => json.load ÉCHOUE. Extraction validée : grep -n '"name":"NOEUD"' puis sed -n 'X,Yp'. (ripgrep/rg N'EST PAS installé sur la Spark, utiliser grep.)
- reasoningEffort none coupe le thinking (le tag /no_think en tête de chat NE marche pas : opencode l'intercepte comme slash-command).
- Skill n8n installée dans .opencode/skills/ — surveiller les compactions.
PÉRIMÈTRE (le plus important)
- LOCAL Qwen3.6 = tâches CIBLÉES : lire 1 nœud, repérer une faille logique, patch simple, dry-run. Rapide, gratuit, fiable SI bien borné.
- CLOUD (FABLE/gros modèle) = refontes multi-nœuds, analyse d'un workflow ENTIER, orchestration MCP longue. Ne pas forcer le local dessus (il boucle).
🧩 SKILLS n8n — validées le 16/07/2026 (Qwen3.6 local, PAS gros modèle)
⚠️ Config calibrée pour Qwen3.6-35B-A3B local (contexte 65K). Les skills aident au tool-calling n8n. Sur gros modèle cloud, elles sont facultatives.
Installation
- Dépôt : github.com/viliamvolosv/n8n-opencode-skill
- Emplacement OBLIGATOIRE : .opencode/skills//SKILL.md (dossier "skills" PLURIEL ; le repo clone dans un sous-dossier "skill" SINGULIER → il FAUT remonter : mv n8n-opencode-skill/skill/* .)
- opencode doit être lancé DEPUIS le projet : cd /mnt/projects/EVCS/EVCS && opencode (sinon .opencode/skills/ n'est pas découvert → skills invisibles).
- Chargement PROGRESSIF : au repos les 8 skills ne coûtent que ~12K tokens (18%). Le SKILL.md complet (~16K) ne se charge QUE si la skill est invoquée.
Skills ACTIVES (5, validées par test réel sur DECISION_ENGINEV2)
- n8n-mcp-tools-expert → récup config nœud via MCP (fiabilise les 6 MCP)
- n8n-code-javascript → analyse/patch du jsCode (vars in/out, utilitaires)
- n8n-expression-syntax → vérif syntaxe {{ }}
- n8n-validation-expert → refs de nœuds, cohérence types, conditions
- n8n-node-configuration → conformité params/mode/position
Skills DÉSACTIVÉES (déplacées dans .opencode/skills-off/)
- customize-opencode (méta, hors sujet EVCS)
- n8n-code-python (on fait du JS)
- n8n-workflow-patterns (exige le workflow ENTIER → risque contexte) → réactivables en les remettant dans skills/ , ou à réserver au cloud.
Résultat du test de validation (5 skills, DECISION_ENGINEV2)
- Les 5 skills invoquées et nommées correctement, analyse d'ingénieur juste (16 vars, 7 derived, 22 conditions, pipeline complet reconstitué).
- UNE SEULE compaction (pic 88% au parsing du workflow 357K, reprise propre).
- 2 AttributeError Python transitoires (structure connexions n8n) → auto-corrigés.
- Coût : $0.00.
RÈGLE contexte associée (dans AGENTS.md)
- Analyse pipeline multi-nœuds : grep -n "name"/"jsCode" + sed sur la plage, JAMAIS le workflow JSON entier en mémoire Python. Traite 1 nœud, résume, libère.
- Le fichier tool-output commence par "Retrieved workflow: " AVANT le JSON → parade validée : json.loads(text[text.index('{'):]).
PÉRIMÈTRE (rappel)
- Local Qwen3.6 + skills = analyse ciblée 1 nœud, validation, patch simple. ✅
- Cloud (FABLE/gros modèle) = refonte multi-nœuds massive, workflow entier.
9. Session du 18/07/2026 (matin)
Corrections documentation (MANUEL_COMPLET.md)
- outside_window : décrit comme "Mode probe (sonde) pour relancer le PV si besoin" (pas bloquant)
- Cloud Debt & Probe : nouvelle section 11.4 complète avec explication dette nuageuse, mode probe 10A, bypass en auto_solar_plus / force_charge_now
- Stratégies : clarifié
force_grid_now= préservation batterie (SOC<22% → recover 35%/50%/75%) vsforce_charge_now= secours manuel - Heure de départ : nouvelle section 11.5 "Logique Heure de départ (Night EVCS)"
Modification workflow
- probeCurrentA : augmenté de 6A à 10A dans le nœud INPUT_PARSE (fallback)
- Rationale : 6A trop faible pour "provoquer" la production PV, 10A plus efficace
Versionnage Git
- Export du workflow dans
workflows/evcs-surplus-controller-v2.6.json - Commit & push effectués
10. Session du 04/08/2026 (matin) — Fix grid HC 1h30 + libération takeover
Problèmes诊断qués
- Charge HC ne démarre pas à 1h30 : Le grid ne se mettait pas en route à l'ouverture de la fenêtre HC
- Grid ne rend pas la main après charge : EDF restait ON après la fin de charge (SOC 71% < 90%)
Root Causes identifiées
gridAllowedBySoc(BATTERY_CLOUD_LOCKS) : autorise le grid seulement sisoc <= 35%ouforceGridRequested. En profilenight_hcavec SOC > 35%, le grid était bloqué.nightTakeoverLatched(WINDOWS_SOURCEV2) : reste latché tant quesoc < 90%(hardcodé). Pas de condition "véhicule chargé" → le takeover ne se libérait jamais.
Corrections appliquées
Fix 1: gridAllowedBySoc — Autoriser le grid en profile night_hc
Node: BATTERY_CLOUD_LOCKS
// AVANT:
const gridAllowedBySoc = parsed.soc <= 35 || strategy.forceGridRequested;
// APRÈS:
const gridAllowedBySoc =
strategy.forceGridRequested ||
context.profile === 'night_hc' ||
parsed.soc <= 35;
Fix 2: nightTakeoverLatched — Libérer si charge terminée
Node: WINDOWS_SOURCEV2
// AVANT:
const nightTakeoverLatched =
nightSessionActive &&
nightTakeoverPrev &&
parsed.soc < NIGHT_TAKEOVER_RELEASE_SOC_PCT;
// APRÈS:
const vehicleChargeCompleted =
!parsed.evcsChargingSwitchOn &&
parsed.soc >= 50;
const nightTakeoverLatched =
nightSessionActive &&
nightTakeoverPrev &&
(
parsed.soc < NIGHT_TAKEOVER_RELEASE_SOC_PCT ||
vehicleChargeCompleted
);
Méthode de déploiement
- MCP n8n-mcp_update_workflow : Erreur 1003 (request/body must NOT have additional properties) — bug du MCP
- Bypass : Script
tools/n8n-rest-update-test.mjscréé, utilise REST API n8n directement avec token - PUT réussi avec body minimal :
{name, nodes, connections, settings, staticData} - Settings doit être minimal :
{"executionOrder":"v1","callerPolicy":"workflowsFromSameOwner"}
Backup
backup-workflow-ATswvGfG4KJUls0K-1785849621547.json
Vérification
- Les 2 patches sont dans le workflow en production
- À surveiller : prochaine nuit HC (01:24-07:24) pour valider le comportement
Document mis à jour le : 04/08/2026 (~10:00) — Fix gridAllowedBySoc + nightTakeoverLatched via REST API État live à la clôture : Workflow patché, à tester en conditions réelles
11. Session du 07/08/2026 — P7 : Charge HC nuit automatique (VW hybride) + outillage push
Problème (nuits du 05-07/08, observé sur l'historique HA)
- VW hybride branchée le soir + stratégie
auto_solar_plus→ aucune charge en HC. 01:24 :idle_wait_conditions/ « Conditions de démarrage nuit batterie non réunies » (SOC 53 %, target=trigger=35 %). L'utilisateur devait intervenir manuellement (force_charge_now, ou BOOST EDF 28A à 05:50 le 07/08).
Root causes
battery_assist_nightne démarre jamais :batteryAssistNightStartOkexigebatteryCurrentA >= profileBatteryDischargeFloorA(=battery_discharge_soft_a= 0 A). La nuit, la maison tire sur la batterie (~ −14 A) → condition impossible. → le système attendait que le SOC dérive jusqu'au trigger 35 % (des heures) avant le grid. (= backlog §7.5 « conditions de démarrage nuit batterie non réunies », résolu.)allow_grid_hc_night(toggle « Réseau HC nuit ») était décoratif : parsé dans INPUT_PARSE (params.allowGridHcNight) mais utilisé nulle part (contrairement àallowGridHcMidday).
Fix P7 (déployé 07/08 ~15:45, dry-run 7 scénarios validés)
Dans WINDOWS_SOURCEV2 et BATTERY_CLOUD_LOCKS (logique nuit dupliquée) :
const nightGridHcPreferred =
nightSessionActive && // fenêtre HC nuit + véhicule branché
params.allowGridHcNight; // toggle "Réseau HC nuit" ON
batteryAssistAllowed: ajout&& !nightGridHcPreferrednightGridTakeoverNeeded: ajoutnightGridHcPreferred ||dans le OR
Effet : dès l'ouverture de la fenêtre HC (01:24) avec véhicule branché,
nightPreferredSourceMode = grid_hc_allowed (ou grid_forced si SOC < cible matin)
→ edf_general_ok_on allume EDF → takeover helper ON → ACM evcs_takeover_hold
(batterie tenue 2 A) → gridStartOk → start_grid_hc_min_current puis step-up.
- Toggle « Réseau HC nuit » OFF → comportement historique (battery assist) intact.
- Jour / pas de véhicule / force_charge_now : aucun changement (dry-run vérifié).
- Garde-fous intacts : hard floor 22 %, recovery urgent, latch anti-créneaux P2, P4/P5.
Outillage (résout le blocage minimax « impossible de pousser dans n8n »)
tools/n8n-pull.mjs <id> <fichier>: GET workflow → JSON local (clé API lue automatiquement dans~/.config/opencode/opencode.json).tools/n8n-push.mjs <fichier> [id]: PUT REST (body minimal validé 04/08) avec backup serveur auto pré-push (modified/prepush-*.json) etnode --checksur chaque jsCode (abandon si erreur de syntaxe).tools/patch-20260807-night-hc-grid.mjs: le patch P7 (remplacements exacts, rejoue-able).tools/dryrun-20260807-night-hc.mjs: harnais dry-run WINDOWS→BCL avec snapshot simulé.- Cycle git : commit backup AVANT (
1debfe3) → patch → push → commit APRÈS. Rollback :git checkout 1debfe3 -- workflows/evcs-surplus-controller-v2.6.json && node tools/n8n-push.mjs workflows/evcs-surplus-controller-v2.6.json.
À surveiller (prochaine nuit HC 01:24→07:24, VW branchée)
- 01:24-01:25 :
source_mode→Réseau HC autorisé, EDF ON, takeover helper ON ; start_grid_hc_min_current→ montée en courant (plafond EDF ON = helper 20 A / boost 28 A) ;- ACM en
evcs_takeover_hold(batterie ~2 A, SOC stable) ; - 07:24 : fin de fenêtre → EDF OFF, retour profil jour.
Document mis à jour le : 07/08/2026 (~15:50) — P7 charge HC nuit auto + tools n8n-pull/n8n-push
12. Session du 07/08/2026 (soir) — R1 : Déduplication du moteur nuit
Contexte
Refactor n°1 du plan de maintenabilité (conseil validé avec l'utilisateur) : le moteur nuit complet (~161 lignes : nightSessionActive, batteryAssistAllowed, nightGridTakeoverNeeded, nightPreferredSourceMode, cibles/triggers, latch...) était calculé DEUX FOIS, dans WINDOWS_SOURCEV2 ET BATTERY_CLOUD_LOCKS. Chaque patch nuit devait être appliqué 2× (P2, fix 04/08, P7) → risque de divergence silencieuse entre sourceMode (WINDOWS) et conditions (BCL).
Fix R1 (déployé 07/08 ~19:03, A/B validé)
- WINDOWS_SOURCEV2 = unique calculateur. Reprend les 4 éléments que seul BCL
calculait :
nightSessionStartMs,nightSessionNeedsCaptureversion stricte (critère startMs inclus),batteryRecoveryAllowedNow,batteryTargetReached. ExportnightEvcsPreviewétendu (+nightTakeoverLatched,nightGridHcPreferred). - BATTERY_CLOUD_LOCKS consomme
prev.nightEvcsPreview(bloc de 161 lignes remplacé par ~50 lignes de lecture avec fallbacks sûrs : preview absent → session inactive / 'blocked'). −1,7K chars. - Le shape de l'export
nightEvcsde BCL (consommé par DECISION_ENGINEV2 et HA_SERVICE_CALLS_OUTPUT) est strictement inchangé. - RÈGLE DÉSORMAIS : toute évolution de la logique nuit se fait UNIQUEMENT dans WINDOWS_SOURCEV2. BCL ne contient plus aucune logique nuit.
Validation A/B (tools/ab-20260807-r1.mjs)
- Pipeline complet WINDOWS→BCL exécuté avant (git HEAD) / après (patché) sur les 7 scénarios golden + 2 cas limites (capture session history vide, clear résiduel).
- Sorties BCL strictement identiques (nightEvcs, conditions, safeguards, context).
- Fallback preview absent : pas de crash, retombe en 'blocked'.
- Piège rencontré : Date.now() dans le harnais → faux diffs de quelques ms ; corrigé en construisant le snapshot une fois puis clone JSON par exécution.
Outillage ajouté
tools/patch-20260807-r1-dedup-night.mjs: le refactor (rejouable).tools/ab-20260807-r1.mjs: harnais A/B git HEAD vs working tree — réutilisable pour tout futur refactor (modèle pour R3, la suite de tests golden).
Prochaines étapes du plan validé
- R3 : suite de tests golden (~15 scénarios, exécutée avant chaque push).
- R2 : git source de vérité (src/nodes/*.js + assemblage dans n8n-push.mjs).
- Puis agent IA planificateur en mode ombre (jamais dans la boucle 30 s).
Document mis à jour le : 07/08/2026 (~19:10) — R1 déployé, comportement inchangé (A/B), production stable
13. Session du 07/08/2026 (soir) — R3 : Suite de tests golden
Ce qui existe désormais (tests/golden/)
run.mjs: exécute le VRAI code des nodes en chaîne complète (INPUT_PARSE → WINDOWS_SOURCEV2 → BATTERY_CLOUD_LOCKS → DECISION_ENGINEV2 → HA_SERVICE_CALLS_OUTPUT) dans une sandbox vm avec horloge simulée (Date épinglée par scénario) sur un snapshot HA réel modifié par overrides.fixture-snapshot.json: snapshot production capturé depuis l'exécution n8n 975833 (173 champs, re-capturable viatools/fetch-snapshot-fixture.mjs).scenarios.mjs: 16 scénarios avec attendus FIGÉS après revue manuelle : 6 nuit HC (P7 ON/OFF, sous-cible, hard floor, sans véhicule, EDF ON→start), 4 solaire (matin fort, matin nuageux, abondance P6, provocation P3), garde SOC midi HC, force_grid, force_charge nuit, stop, overload, débranché.
Usage
node tests/golden/run.mjs→ 16/16 ✅ ou liste des diffs + exit 1--print: affiche les résumés obtenus (pour autoriser une évolution)--json: sortie machine (utilisée pour figer les attendus)--only=<motif>: filtre par id de scénario
Intégration au déploiement (le point clé)
tools/n8n-push.mjs exécute la suite AVANT tout PUT du workflow EVCS :
golden rouge = rien n'est poussé. Échappatoire consciente : --skip-golden.
Évolution volontaire du comportement → mettre à jour les attendus dans
scenarios.mjs DANS LE MÊME COMMIT que le patch.
Validation par mutation
P7 volontairement cassé (nightGridHcPreferred = false) → la suite détecte
2 échecs avec diffs précis (sourceMode, takeover, EDF) et bloque le push ;
restauration → 16/16. Le filet fonctionne.
Résumé testé par scénario
profile, sourceMode, state, decision, desiredCharging, currentA, takeoverNeeded, action EDF ON, service helper takeover.
Notes de comportement figées (découvertes en écrivant les attendus)
- SOC 20 la nuit avec maison sur batterie (−14 A) →
battery_current_hard_stop(bande lt25 : hard −8 A) + demande réseau simultanée. Protection cohérente. force_charge_nowla nuit (toggle HC ON) →start_manual_local_min_currentET takeover helper ON (session nuit active) : EDF peut venir en renfort. Voulu.- Garde SOC>35 midi : sourceMode
grid_hc_allowedmaisidle_wait_conditions(le grid HC midi ne s'active que SOC ≤ 35) — comportement épinglé par le test.
Prochaines étapes du plan
- R2 : git source de vérité (src/nodes/*.js + assemblage dans n8n-push).
- Agent IA planificateur en mode ombre (après R2).
- Enrichir la suite au fil des incidents : chaque bug corrigé = +1 scénario.
Document mis à jour le : 07/08/2026 (~19:20) — R3 en place, 16/16, push bloquant
14. Session du 07/08/2026 (soir) — P8 : Libération EDF quand le véhicule est plein
Spécification utilisateur
La nuit : charge dès 01:24 (P7) ✓, puis quand la charge est finie → EDF OFF, sauf si la batterie solaire est sous le seuil (alors EDF reste pour la recharger).
Bugs découverts en traçant la spec
- Fix 04/08 logiquement inversé :
(soc < 90 || vehicleChargeCompleted)GARDAIT le latch takeover après la charge au lieu de le libérer. Jamais corrigé en réalité. - P7 ne relâchait pas :
nightGridHcPreferredrestait vrai tant que véhicule branché → l'EVCS redemandait EDF ON toutes les 30 s jusqu'à 07:24. - Signal "voiture pleine" instable : le VehicleDemandFilter retombe à false dès que le relais s'ouvre (session désarmée) → impossible de s'appuyer dessus seul.
Solution : mémoire AUTO-ENTRETENUE via l'état surplus (P8)
Chaîne (nouveau champ snapshot prev_surplus_state ← input_text.victron_evcs_surplus_state) :
charge → VDF détecte voiture pleine → state 'vehicle_full_or_paused'
cycle N+1 : WINDOWS voit ce state → vehicleChargeCompleted=true
→ DECISION émet 'night_session_done_idle'
cycles suivants : 'night_session_done_idle' s'auto-entretient
fin : débranchement (state vehicle_not_connected) ou fin de fenêtre 07:24
Effets quand vehicleChargeCompleted :
- latch libéré (fix inversion 04/08 :
soc < 90 && !completed) - préférence P7 relâchée ; sourceMode nuit → 'blocked' (zéro tentative de charge)
- SOC > cible matin : takeover helper OFF → hystérésis coupe EDF (turn_off_recovered)
- SOC ≤ cible : takeover helper ON → hystérésis maintient EDF (override) + ACM recharge la batterie à la cible → puis relâche. ⚠️ Le nom 'night_session_done_idle' ne doit matcher AUCUN pattern no-demand de l'hystérésis (full_or_paused / not_requesting_power / charge_complete / no_demand) — c'est ce qui permet à l'override batterie-basse de fonctionner. NE PAS RENOMMER sans vérifier.
Nœuds modifiés (tools/patch-20260807-p8-release-full.mjs, rejouable)
- Snapshot template : +prev_surplus_state
- WINDOWS_SOURCEV2 : vehicleChargeCompleted collant, latch corrigé, P7 conditionné, preferred 'blocked' si plein, export preview
- BATTERY_CLOUD_LOCKS : passthrough du flag dans nightEvcs
- DECISION_ENGINEV2 : branche 'night_session_done_idle' (avant sourceMode blocked)
Validation
- 3 nouveaux scénarios golden (19 au total) : transition vehicle_full→done_idle (takeover turn_off), auto-entretien, batterie basse (takeover turn_on maintenu).
- Les 16 scénarios existants inchangés (aucune régression).
- Push via pipeline bloquant (golden 19/19), production stable.
Comportement attendu cette nuit (VW branchée, checklist complète)
- 01:24-01:25 : grid_hc_allowed → EDF ON → charge 6 A puis montée (P7)
- VW pleine : vehicle_full_or_paused → night_session_done_idle → takeover OFF → EDF coupé par l'hystérésis ≤ 1 min après (si min_on 25 écoulé et SOC ≥ 30)
- Batterie sous cible matin (35 été) : EDF maintenu + recharge ACM jusqu'à cible
- 07:24 : fin de fenêtre → retour profil jour
15. Session du 07/08/2026 (soir) — R2 : Git = source de vérité du code des nodes
Nouveau layout
src/nodes/= SOURCE DE VÉRITÉ : INPUT_PARSE.js, WINDOWS_SOURCEV2.js, BATTERY_CLOUD_LOCKS.js, DECISION_ENGINEV2.js, HA_SERVICE_CALLS_OUTPUT.js, BUILD_ACTIONS.js, HA_SNAPSHOT_TEMPLATE.json.tpl.workflows/evcs-surplus-controller-v2.6.json= artefact déployable, reconstruit automatiquement depuis src/ à chaque push (src GAGNE toujours).tools/lib-node-map.mjs(cartographie),tools/n8n-extract.mjs(JSON→src, uniquement pour resynchroniser après une édition UI n8n — à éviter),tools/n8n-build.mjs [--check](src→JSON / vérif synchro).archive/: toutes les copies historiques (decoupesurplus/, WINDOWS/, BATTERY/, DECISION/, HYSTERESIS/, EVCSV2.2.json, surplus.js...) — copies mortes, dérivées..gitignore: backup-workflow-.json + modified/prepush-.json (rollbacks locaux).
WORKFLOW DE MODIFICATION DÉSORMAIS (règle inscrite dans AGENTS.md)
éditer src/nodes/<NODE>.js
node tools/n8n-push.mjs workflows/evcs-surplus-controller-v2.6.json
→ build src→JSON → node --check → GOLDEN 19/19 (bloquant) → backup serveur → PUT
git commit (src + json + scénarios si évolution voulue) && git push
JAMAIS d'édition directe du JSON ou de l'UI n8n (écrasée au prochain push).
Validations effectuées
- Extract/build idempotents (extract → build → --check : zéro diff).
- Test mutation : édition src → build injecte 1 node → restauration git+extract OK.
- Pipeline complet de push exécuté en réel : build (no-op) → golden 19/19 → PUT OK.
- Nota : tailles
ls≠ longueur de code (octets UTF-8 vs caractères, accents FR).
Plan de maintenabilité : état
- R1 ✅ déduplication moteur nuit (07/08)
- R3 ✅ suite golden 19 scénarios bloquante (07/08)
- R2 ✅ git source de vérité + build/push (07/08)
- Suivant : agent IA planificateur en MODE OMBRE (jamais dans la boucle 30 s) — helpers advisor_*, validation bornée des sorties, kill-switch, cf. HANDOFF §13/§14.
16. Session du 07/08/2026 (nuit) — Agent IA planificateur en MODE OMBRE
Ce qui tourne désormais
Workflow n8n "EVCS Advisor Shadow V1" (id tdfInZns7cqo8fU2, actif, 15 min) :
Schedule 15min → HA Snapshot compact (template Jinja {{ {...} | tojson }})
→ BUILD_PROMPT (kill-switch input_boolean.evcs_advisor_enabled, prompt FR ~4K chars)
→ IF_ENABLED → LLM_LOCAL_MODELS (GET 10.10.10.8:8000/v1/models, 5s)
→ LLM_LOCAL (modèle DYNAMIQUE = celui chargé sur la Spark, max_tokens 6000)
→ IF_LOCAL_OK → sinon LLM_CLOUD (OpenRouter anthropic/claude-haiku-4.5,
credential chiffré "OpenRouter Advisor" id eOM9xofDHT0KMoeY — clé JAMAIS dans git)
→ VALIDATE_PARSE (strip <think>, JSON strict, enums+bornes, invalide = REJET)
→ BUILD_HA_CALLS (helpers advisor_* + divergence + compteurs + logbook)
→ Execute HA Service
GARANTIE MODE OMBRE (structurelle, testée)
BUILD_HA_CALLS n'écrit QUE vers input_text/input_number.evcs_advisor_* et
logbook. Le test tests/advisor/run.mjs ÉCHOUE si une écriture sort de ce
périmètre (16 tests : kill-switch, réponses valides/hors-bornes/non-JSON/
/offline, périmètre ombre, compteurs accord/divergence).
Suite intégrée au push : n8n-push.mjs workflows/evcs-advisor-shadow-v1.json.
Helpers HA (package NFS /mnt/ARCH3/docker/HA/packages/evcs_advisor_shadow.yaml)
evcs_advisor_enabled (kill-switch) · advisor_strategy · advisor_night_target_soc
(22-90) · advisor_current_max_a (6-28) · advisor_ev_charge_tonight · advisor_reason
· advisor_status (ok/invalid/llm_offline) · advisor_divergence · advisor_last_run
· advisor_model · advisor_agree_count / advisor_diverge_count.
Carte dashboard « 🤖 Conseiller IA (ombre) » ajoutée à evcs_master.yaml (backup dans SAVE/).
Première recommandation réelle (07/08 22:40, via qwen3-vl local)
auto_solar_plus | cible 35% | charge VE cette nuit: oui | confiance 90% |
« Prévision solaire forte (40.7 kWh), cible SOC matin basse adaptée. Véhicule
chargé en HC avec réseau EDF. » → accord avec le réel (et avec P7/P8 !).
Pièges rencontrés (pour mémoire)
- Template /api/template DOIT être du Jinja :
{{ { ... } | tojson }}— sans wrapper, HA renvoie le texte brut non rendu. - Modèle local = qwen3-vl Thinking : max_tokens 2500 → JSON tronqué par le
raisonnement → rejeté proprement (
invalid: JSON non terminé). Fix : 6000. (La validation stricte a fait exactement son travail dès le 1er run réel.) - MCP activate_workflow → erreur 415 : activer via REST POST .../activate body {}.
- n8n-push : le fallback d'id par défaut (EVCS) était DANGEREUX avec un 2e
workflow → supprimé, l'id vient du champ
iddu JSON ou de l'argument.
Outillage multi-workflows (extension R2)
lib-node-map.mjs : WORKFLOWS[] (evcs + advisor) · build/extract/push acceptent
un fichier cible · n8n-create.mjs (POST création) · gen-advisor-workflow.mjs
(squelette advisor rejouable, credentials/ids inclus).
Note LLM local (analyse du 07/08, décision : statu quo surveillé)
qwen3-vl-Thinking "fait le job" mais est sous-optimal pour l'advisor : variante Thinking (raisonnement non désactivable, 40-60 s/run, risque marginal de JSON tronqué — rejeté proprement par la validation). Le bon modèle local est qwen3.6-35b quand il est chargé (use-code). Option prête si les compteurs se dégradent (décision utilisateur : ne rien changer pour l'instant) : routage intelligent = 1 IF après LLM_LOCAL_MODELS (local seulement si l'id du modèle chargé n'est pas vl/thinking, sinon cloud direct) + éventuellement cloud moins cher (gemini-2.5-flash-lite ~1€/mois vs haiku-4.5 ~9€/mois plein temps). Implémentation ~30 min, tests advisor inchangés.
Phase 3 — critères de bascule (dans 2-3 semaines d'observation)
- Taux d'accord stratégie ≥ 80% (compteurs agree/diverge sur la carte) ;
- Zéro sortie invalide NON rejetée (advisor_status) ;
- Divergences analysées = soit l'agent a raison (améliorer les règles),
soit il a tort (améliorer le prompt) — les deux font progresser le système.
→ Alors premier câblage réel LIMITÉ : night_target_soc uniquement, derrière un
nouveau toggle
advisor_apply_night_target, le reste inchangé.
17. Session du 08/08/2026 — Incident rebond EDF nuit 07-08/08 : fixes A/B/C/D
Incident (captures HA analysées, faits dans docs/vision-notes.md)
P7 a chargé la VW dès 01:24 ✓ (session 1h32m). Ensuite :
- Fin de charge détectée très tardivement (~04:15 au lieu de ~02:56) ;
- Rebond EDF ON/OFF période ~10 min autour de SOC=35 (= cible matin) + flapping ACM Charging/passthru 2-4 s (03:58→04:16) ;
- Débranchement VW (04:17) → takeover bien relâché mais EDF maintenu (hold_on_min_runtime 25 min) → arrêt manuel utilisateur 04:25 (stopped_user).
Root causes
- A — point dégénéré SOC==cible (WINDOWS_SOURCEV2) : engage takeover
soc <= cible, releasesoc > cible, recovery ACMsoc < cible(strict), et l'ACM recharge PILE à la cible → aucune position stable, oscillation. - B — snapshot incomplet :
evcs_night_session_start_soc/_atjamais relus (absents du template) →nightSessionNeedsCapturevrai à CHAQUE cycle → "Night Session Start At" réécrit toutes les 30 s toute la nuit (visible logs), budget décharge nuit faussé (start SOC glissant). - C — hystérésis grid : pas de coupure anticipée au débranchement (min_on 25 min s'applique même véhicule parti et SOC ≥ seuil OFF).
- D — détection fin de charge fragile : VDF armé uniquement session pleinement active ; stop par garde (ex battery_current_hard_stop) → filtre désarmé → 'vehicle_full_or_paused' inatteignable pendant longtemps.
Fixes déployés (production 08/08 ~14:00, golden 23/23)
- Fix A (WINDOWS_SOURCEV2 + BCL passthrough + DECISION) : nouvel état
auto-entretenu
night_recovery_done_idleémis quand vehicleChargeCompleted && SOC ≥ cible. Takeover relâché DÉFINITIVEMENT ; ré-engagement uniquement si SOC ≤ cible − 2 (bande NIGHT_RECOVERY_REARM_BAND_PCT). ⚠ Le nom ne matche aucun pattern no-demand de l'hystérésis (comme done_idle). Structure takeover : completed ? !nightRecoveryDone : (trigger || urgent || latch). - Fix B (HA_SNAPSHOT_TEMPLATE) : +
evcs_night_session_start_soc, +evcs_night_session_start_at(helpers input_text existants, vérifiés via MCP). - Fix C (workflow hystérésis 7iIlu7LLTW7TZA8o, patch
tools/patch-20260808-c-unplug-cut.mjs rejouable) : nouvel état
turn_off_vehicle_unplugged= véhicule débranché (es=vehicle_not_connected)- latch ON + grid ON + pas d'override + SOC ≥ seuil OFF → coupure immédiate (bypass min_on) + notification. Dry-run 4 cas validé (incident/SOC bas/ takeover ON/recovery_done) AVANT push. Le fichier local du workflow est désormais suivi : workflows/victron-grid-hysteresis-v1.json.
- Fix D (WINDOWS_SOURCEV2) :
evcs_status_raw == 3(Victron "charged") vaut fin de charge immédiate, sans attendre le VDF.
Golden : 19 → 23 scénarios
- 2 attendus mis à jour (transition/auto-entretien : SOC ≥ cible → état
night_recovery_done_idledésormais) ; - 4 nouveaux :
night-recovery-atteinte-soc-cible(LE cas du rebond),night-recovery-done-bande-collante(34 % dans la bande),night-recovery-rearm-sous-bande(32 % → ré-engagement),night-vw-statut-charged(Fix D). - Piège outillage :
tests/golden/run.mjsexécute le JSON BUILDÉ (workflows/*.json), pas src/ → toujoursnode tools/n8n-build.mjsavant un run golden manuel (n8n-push le fait automatiquement).
Documentation
- MANUEL_COMPLET.md §11.6 : GRAFCET complet du cycle nuit (G1 EVCS / G2 hystérésis) + états surplus fin de nuit + résumé incident.
- docs/vision-notes.md : OCR/faits des 9 captures de l'incident.
À surveiller (prochaine nuit HC, VW branchée)
- Fin de charge : passage à vehicle_full_or_paused OU night_recovery_done_idle rapide (Fix D si la VW publie le statut 3) ;
- SOC à la cible → UNE coupure EDF, puis plus aucun ON/OFF (sauf SOC ≤ cible−2) ;
- "Night Session Start At" écrit UNE fois en début de session (Fix B) ;
- Débranchement → EDF OFF ≤ 1 min (turn_off_vehicle_unplugged, Fix C).
Document mis à jour le : 08/08/2026 (~14:15) — fixes A/B/C/D déployés, golden 23/23
18. Session du 09/08/2026 — Incident nuit 08-09/08 : le rebond est revenu par un AUTRE chemin (Fix E)
Incident (captures HA, VW branchée, HC, VW pleine vers ~03:00)
Fix A/D du 08/08 ont bien fonctionné CÔTÉ WINDOWS (status 3 → completed →
takeover Désactivé, source blocked). MAIS l'état EVCS observé était
hold_min_run_source_lost / stop_source_lost — jamais night_recovery_done_idle.
Cycles ~30 min de 03:00 à 05:40 : EDF ON (2531 W réseau, SOC 51 % > cible 35)
→ arrêt urgence utilisateur 05:43 (EDF URGENCE OFF + stratégie stop) → débranchement.
Root cause (ordre des branches DECISION)
- Status 3 arrive PENDANT la session armée (relais fermé, chg=1/perm=1/l=1).
- WINDOWS → sourceMode 'blocked' (Fix D) ; mais dans DECISION la branche
session ACTIVE traite 'blocked' comme « source perdue » :
hold_min_run_source_lost(25 min de session zombie !) puisstop_source_lost. La branche P8 (état collant) est dans la chaîne NON-active → jamais atteinte. - Au stop, le relais s'ouvre → le statut EVCS RETOMBE (3 → 1/6, même piège que
le VDF documenté en P8) → completed=false → re-P7 → takeover ON 1 cycle →
edf_general_ok_onrallume EDF + restart → status 3 → goto 2. Rebond ~30 min. - L'hystérésis adoptait l'EDF ON du cycle de flap puis
hold_on_min_runtime25 min (ov=0/eg=0 ensuite, mais latch pris) → EDF ON quasi permanent.
Fix E (déployé 09/08 ~06:25, golden 25/25)
- DECISION : nouvelle PREMIÈRE branche du bloc session-active (avant VDF) :
night_hc && vehicleChargeCompleted && !force*→ setOff immédiat avec l'état collant (night_recovery_done_idlesi SOC ≥ cible, sinonnight_session_done_idle), decisionstop_vehicle_full_or_paused. → l'état collant est écrit AU PREMIER cycle où status=3, relais encore fermé ; les retombées de statut ensuite sont sans effet (prev state dans la liste). - WINDOWS : garde puissance sur Fix D (
status==3 && evcsAcPowerW <= 300 W) pour ne jamais latcher sur un glitch en pleine charge. - Hystérésis (patch tools/patch-20260809-e2-recovery-done-cut.mjs) :
turn_off_recovery_done= es=night_recovery_done_idle + pas d'override + SOC ≥ seuil OFF → coupure EDF immédiate (bypass min_on 25). Sûr car l'état est collant (le takeover ne peut pas revenir, sauf SOC ≤ cible−2 → override). Dry-run 5 cas validé avant push.
Golden : 23 → 25
night-charged-en-session-active: status 3 en session ARMÉE → arrêt propre- latch immédiat (LE chemin de l'incident) ;
night-flap-statut-apres-latch: relais ouvert, statut retombé à 1, prev=night_recovery_done_idle → collant, aucun re-P7/EDF.
Comportement attendu au REBRANCHEMENT d'une VW pleine en HC
- Session nuit → P7 → EDF ON → tentative de charge (inévitable : le système ne peut pas savoir que la VW est pleine sans fermer le relais) ;
- VW refuse (0 W, status 3) → ≤ 1 min :
night_recovery_done_idlecollant, takeover OFF, session stoppée proprement ; - Hystérésis :
turn_off_recovery_done→ EDF OFF ≤ 1 min (plus de min_on 25) ; - Plus RIEN jusqu'au débranchement / fin de fenêtre (flaps de statut ignorés). Total attendu : EDF ON ~2-3 min UNE seule fois, puis silence.
Document mis à jour le : 09/08/2026 (~06:35) — Fix E déployé (EVCS + hystérésis), golden 25/25
19. Session du 09/08/2026 (suite) — Fix F : la mémoire "charge terminée" est prioritaire sur les gardes
Test de rebranchement utilisateur (~06:33, VW rebranchée en HC)
- ✅ Fix E validé en réel : 06:51:00
night_recovery_done_idlelatché, 06:51:03turn_off_recovery_done→ EDF coupé immédiatement (bypass min_on). - ❌ 06:53-06:56 : la VW a relancé une charge (status 2) qui, EDF OFF, tirait
sur la batterie (−47 A) → garde
mustStopHard→ étatbattery_current_hard_stopÉCRIT PAR-DESSUS le collant dans le helper → completed perdu → re-P7 → EDF rallumé (hold_on_evcs_override, re-min_on 25). → Le collant ne survivait à AUCUN état émis par une branche plus prioritaire.
Fix F (déployé 09/08 ~07:00, golden 26/26)
- DECISION : nouvelle branche dans la cascade PRINCIPALE, entre
vehicle_not_connectedetmustStopHard:night_hc && vehicleChargeCompleted && !force*→ setOff avec l'état collant (recovery_done/done_idle),mustStopsi la borne est encore armée. → l'état collant est réécrit à CHAQUE cycle tant que VW branchée + fenêtre, il survit aux micro-charges/gardes. Aucune perte de protection (le setOff coupe chg/perm/latch comme un hard stop). - La branche Fix E interne (session active) supprimée : remplacée par la branche haute. La branche P8 historique (chaîne non-active) reste en filet.
- Golden 26 :
night-hard-stop-necrase-pas-done(micro-charge −60 A sur batterie avec prev=recovery_done → l'état reste recovery_done, stop ferme).
Leçon d'architecture (pour le futur)
La "mémoire" portée par input_text.victron_evcs_surplus_state est fragile : tout état émis l'écrase. Fix F la protège en réécrivant chaque cycle, mais la solution PROPRE à terme est un helper dédié (input_boolean victron_evcs_night_charge_done, package YAML + reload HA) si un nouveau cas d'écrasement apparaît.
Relecture de la nuit 08-09/08 (après coup)
La VW n'a pris que 5 kWh cette nuit, morcelés par les stops zombies (§18) : elle n'était probablement PAS pleine. Les "micro-charges" du matin étaient de vraies reprises. À 07:00 (post-Fix F) : charging_step_up_grid_hc = charge HC nominale jusqu'à la fin de fenêtre 07:24. Le collant ne se posera que sur un VRAI signal de fin (VDF no-demand ou status 3) — et tiendra désormais.
Document mis à jour le : 09/08/2026 (~07:05) — Fix F déployé, golden 26/26
20. Session du 09/08/2026 — PREMIER CÂBLAGE RÉEL de l'advisor : cible SOC matin
Décision utilisateur
Météo Bretagne au beau fixe (47-58 kWh/j prévus) = conditions idéales pour
tester le câblage avec risque minimal. Périmètre STRICTEMENT limité au plan
§16 : night_target_soc uniquement, derrière un toggle, rollback 1 clic.
Ce qui est câblé
batteryMorningTargetPct (WINDOWS_SOURCEV2) suit
input_number.evcs_advisor_night_target_soc SI ET SEULEMENT SI :
input_boolean.evcs_advisor_apply_night_target= ON (nouveau toggle, initial OFF, package evcs_advisor_shadow.yaml + carte dashboard) ;input_text.evcs_advisor_status= 'ok' (exact) ;- recommandation fraîche ≤ 120 min (parse
DD/MM HH:mmde advisor_last_run, année courante, garde passage d'année — parseAdvisorRunToMs INPUT_PARSE) ; - valeur clampée [max(22, hard floor), 90].
Sinon → règles forecast historiques (
batteryMorningTargetRulePct), aucun changement de comportement. Le toggle OFF = état d'origine.
Chaîne technique
- Snapshot : +advisor_apply_night_target, +advisor_night_target_soc, +advisor_status, +advisor_last_run (states() d'une entité absente → 'unknown' → toggle vu OFF → sûr AVANT le reload HA).
- INPUT_PARSE : params.advisorApplyNightTarget / advisorNightTargetSocPct / advisorStatusOk / advisorLastRunMs.
- WINDOWS : override borné + export advisorNightTargetApplied +
batteryMorningTargetRulePct (preview) ; BCL passthrough ; DECISION : la
reason affiche
target=X% (ADVISOR)quand appliqué → visible logbook/carte. - Impact aval automatique : gridTakeoverTriggerPct, grid_forced vs hc_allowed, recovery post-charge, bande anti-rebond — tous dérivés de la cible.
Golden 26 → 29
night-advisor-target-applique: SOC 30, advisor 22 ON/ok/frais → grid_hc_allowed (au lieu de grid_forced par la règle 35) ;night-advisor-target-stale: > 120 min → fallback règles (grid_forced) ;night-advisor-toggle-off: toggle OFF → règles inchangées.
Sécurités NON modifiées
Hard floor 22 %, hysteresis grid ON ≤ 20 %, recovery urgent départ, latch anti-créneaux, collant fin de charge (Fix A/E2/F). L'advisor ne peut RIEN piloter d'autre (VALIDATE_PARSE borne night_target_soc 22-90 côté advisor, clamp identique côté contrôleur).
Rollback (3 niveaux)
- Toggle « APPLIQUER cible SOC matin » OFF → effet au cycle suivant (30 s) ;
- Kill-switch advisor OFF → plus de runs → fraîcheur > 120 min → fallback auto en ≤ 2 h même si le toggle reste ON ;
git revertdu commit +node tools/n8n-push.mjs workflows/evcs-surplus-controller-v2.6.json.
Surveillance (2-3 nuits)
- Carte Conseiller IA : « Cible SOC matin (agent) » vs « (réel) » doivent converger quand le toggle est ON ;
- Logbook « Victron EVCS - Reason » la nuit :
target=X% (ADVISOR); - Le matin : SOC batterie au lever du soleil ≈ cible advisor, remontée solaire OK ;
- Si SOC matin < 22 % ou EDF nuit anormal → toggle OFF + capture + analyse.
Action utilisateur requise après ce déploiement
Recharger HA pour créer le toggle : Outils développeur → YAML → « ENTRÉES BOOLÉENNES (input_boolean) » Recharger (ou redémarrage HA), puis activer le toggle sur la carte Conseiller IA (dashboard EVCS Master rechargé).
Document mis à jour le : 09/08/2026 (~07:35) — câblage advisor night_target déployé (toggle OFF par défaut), golden 29/29
21. Session du 09/08/2026 — P9 : charge réseau avant départ + sélecteur véhicule VW/JEEP
Contexte clarifié par l'utilisateur (IMPORTANT pour comprendre les incidents)
DEUX véhicules alternent sur la borne :
- VW (hybride) : départ ~08:50, ne publie PAS franchement "charged" (statut 3 rare/fugace) → détection fin de charge = VDF surtout ;
- JEEP : départ ~06:30, publie "charged" franchement. Relecture des incidents : nuit 08-09/08 = JEEP (rebonds §18) ; matin 09/08 = VW déjà pleine (chargée la veille en solaire) rebranchée à la place de la JEEP. L'heure de départ unique (08:50) était un réglage VW ; la JEEP part à 06:30 (dans la fenêtre HC → couverte par la mécanique nuit existante).
Nouveaux helpers (package /mnt/ARCH3/docker/HA/packages/victron_evcs_vehicles.yaml)
input_select.victron_evcs_vehicle(VW/JEEP, défaut VW) — à sélectionner au branchement. NB : l'ancieninput_select.victron_evcs_target_vehicle(None/Jeep/Zero, créé UI) n'était câblé nulle part → retiré de la carte, remplacé par le nouveau. À supprimer un jour via l'UI.input_datetime.victron_evcs_departure_time_jeep(06:30). L'existantvictron_evcs_departure_time(08:50) devient l'heure VW.input_boolean.victron_evcs_allow_grid_departure_urgent(toggle P9, OFF). Carte "Paramètres utilisateur" (evcs_master.yaml) mise à jour. ⚠ RELOAD HA requis (input_boolean + input_datetime + input_select).
P9 (golden 33/33, déployé 09/08 ~08:27)
- INPUT_PARSE :
departureTimeMinsuit le véhicule sélectionné (JEEP → departure_time_jeep, fallback VW) ; impacte aussi departurePriorityActive et batteryRecoveryUrgent nuit (donc la JEEP à 06:30 est mieux servie). - WINDOWS : fenêtre
departureWindowActive= toggle ON + véhicule branché + HORS fenêtre HC + départ dans ≤ 120 min. Si charge non terminée →departureUrgentActive→ sourceModegrid_hc_allowed(prend le pas sur blocked/solar_potential/hybrid_probe, JAMAIS sur solar_only ni stop/force*). Fin de charge (VDF vehicle_full_or_paused relu, ou statut 3) →departureChargeDone→ sourceMode blocked + état collant. - BCL :
gridAllowedBySocinclut departureUrgentActive (sinon SOC>35 bloquait). - DECISION : branche collante
departure_done_idle(même principe que Fix F, au-dessus des gardes, mustStop si borne armée). - HA_SERVICE_CALLS : helper takeover tenu ON pendant departureUrgentActive (override hystérésis + maintien batterie ACM pendant la charge pré-départ).
- Hystérésis (patch tools/patch-20260809-p9-departure-cut.mjs) : coupure
immédiate E2 étendue à
departure_done_idle.
Golden 29 → 33
p9-depart-vw-grid (fenêtre active, SOC 45 > 35 → réseau quand même) · p9-depart-jeep-passe (sélecteur : départ JEEP passé → rien) · p9-done-idle-collant · p9-toggle-off (comportement historique).
Mode d'emploi utilisateur
- Recharger HA (YAML : input_boolean, input_datetime, input_select) ;
- Sur la carte : sélectionner le véhicule au branchement (VW ou JEEP) ;
- Activer « ⚡ Réseau avant départ (P9) » ;
- La JEEP profite de sa vraie heure 06:30 dès cette nuit (recovery urgent nuit) ; la VW aura sa fenêtre 06:50-08:50 hors HC si sous-chargée. Rollback : toggle P9 OFF (30 s) ; sélecteur sans effet si P9 OFF (sauf heure de départ pour la priorité/urgence nuit — voulu).
Document mis à jour le : 09/08/2026 (~08:35) — P9 + sélecteur véhicule déployés, golden 33/33
22. Session du 09/08/2026 — P10 : mémoire "véhicule plein" en journée
Incident (captures utilisateur 09:49-09:57)
VW pleine rebranchée ~07:00. À 09:49, PV fort (5,5 kW) → le surplus solaire lance une tentative (morning_solar, solar_only) : 6 A commandés, 0 W pris, VRM affiche "Charged". Sans mémoire jour, le cycle se répète toutes les ~15 min TOUTE la journée (VDF stop → block → re-start). Zéro coût énergie (solaire, EDF OFF) mais usure relais borne/VW + bruit. Les états collants P8/P9 étaient scopés nuit / fenêtre départ.
Fix P10 (déployé 09/08 10:04, golden 36/36, validé live à 10:04:30)
- WINDOWS :
dayVehicleFullIdlecollant = véhicule branché + hors fenêtre HC- (prev ∈ {day_vehicle_full_idle, vehicle_full_or_paused, night_recovery_done_idle, night_session_done_idle, departure_done_idle} OU statut 3 ≤ 300 W). sourceMode → 'blocked' (y compris face à solar_only : charger un véhicule plein ne sert à rien).
- HÉRITAGES bidirectionnels : le jour hérite des états done de la nuit (07:24 → aucune re-tentative matinale) ; la nuit hérite du jour (vehicleChargeCompleted inclut day_vehicle_full_idle → pas de tentative 01:24 si resté branché plein).
- DECISION : branche collante 'day_vehicle_full_idle' au-dessus des gardes (principe Fix F), stop propre si session armée.
- Libération : DÉBRANCHEMENT (vehicle_not_connected) — ou échappatoires force_charge_now / force_grid_now (testé golden).
- Hystérésis : nom sans pattern no-demand ✓, aucun changement nécessaire (EDF pas impliqué dans ce chemin).
Golden 33 → 36
day-vw-pleine-statut3 (session armée 0 W statut 3 → collant) · day-full-heritage-nuit (prev=night_recovery_done_idle → pas de solaire) · day-full-echappatoire-force-charge (force_charge_now relance).
Validation live
10:04:14 PUT → 10:04:30 : day_vehicle_full_idle latché sur la VW pleine
en cours de tentative ; statut retombé à 6 (relais ouvert) sans relance.
Cycle de vie complet d'un véhicule branché (résumé)
branché → tentatives légitimes (nuit P7 / jour solaire / P9 départ)
→ plein détecté (statut 3 ou VDF)
→ collant : night_*_done_idle / departure_done_idle / day_vehicle_full_idle
(héritages entre périodes, réécrit chaque cycle au-dessus des gardes)
→ DÉBRANCHEMENT = seule remise à zéro (+ force_* en échappatoire)
Document mis à jour le : 09/08/2026 (~10:10) — P10 déployé et validé live, golden 36/36
23. Session du 12/08/2026 — P11 : reset du collant au changement de véhicule
Incident (nuit 12/08, captures utilisateur)
JEEP branchée 01:24, chargée en HC (cible 35% ADVISOR), fin de charge franche
→ état collant night_recovery_done_idle. Utilisateur débranche la JEEP et
branche la VW à sa place. L'état collant a SURVÉCU (le collant n'était
remis à zéro que par vehicle_not_connected, non vu de façon franche lors du
swap) → la VW était bloquée « charge véhicule terminée » alors que c'était
celle de la JEEP. La VW (déchargée) ne chargeait pas.
Cause de conception
Tous les états collants "plein" (P8 night_, P9 departure_, P10 day_*) se libéraient UNIQUEMENT au débranchement (vehicle_not_connected). Un remplacement de véhicule rapide (ou un statut EVCS qui ne repasse pas franchement "déconnecté") laissait le collant en place → nouveau véhicule bloqué.
Fix P11 (déployé 12/08 05:00, golden 39/39, validé live à 05:00:30)
Signal fiable : sensor.victron_evcs_charging_time repart de 0 à CHAQUE
nouvelle connexion physique.
- Snapshot : +
evcs_charging_time; INPUT_PARSE :parseChargingTimeToSec("1h 49m 53s"/"8m 27s"/"0s" → secondes),parsed.evcsChargingTimeSec. - WINDOWS :
sessionFresh= véhicule branché ET charging_time ≤ 90 s. NeutralisevehicleChargeCompleted,dayVehicleFullIdle,departureChargeDoneETevcsStatusCharged(statut 3 résiduel ignoré le temps que la borne renégocie) → le système RETESTE le véhicule neuf. - Export
sessionFresh(traçabilité).
Golden 36 → 39
p11-changement-vehicule-nuit (prev=night_recovery_done_idle + charging_time 15s → night_wait_grid_takeover, retest) · p11-changement-vehicule-jour (day_vehicle_full_idle + 20s → solaire repart) · p11-session-etablie-reste-collant (charging_time 2h05 → reste collant, pas de reset abusif).
Validation live
05:00:00 PUT → 05:00:30 : la VW est passée de night_recovery_done_idle
(bloqué) à night_wait_grid_takeover / source « Réseau HC autorisé ».
Récapitulatif des remises à zéro du collant "plein"
- DÉBRANCHEMENT (vehicle_not_connected) ;
- CHANGEMENT DE VÉHICULE / nouvelle connexion (charging_time ≤ 90 s, P11) ;
- force_charge_now / force_grid_now (échappatoires manuelles).
26. Session du 27/08/2026 — L'oscillateur du takeover nuit, et deux acteurs non lus
L'oscillateur (corrigé, golden 40 → 42, déployé 08:32)
Nuit du 26-27/08, Jeep pleine restée branchée, aucun besoin de charge : trois
sessions EDF (04:24→05:16, 05:31→06:50, 07:05→07:30), 2,5 kWh importés un
matin annoncé à 43 kWh de solaire. La nuit du 20-21/08 en comptait six. Le
helper night_grid_takeover_needed précède chaque bascule du contacteur de 3 s :
la cause est entièrement dans le moteur de nuit EVCS, pas dans l'hystérésis
(grid_soc_on/grid_soc_off valent toujours 20/30, jamais approchés).
Cause, dans WINDOWS_SOURCEV2.js :
gridTakeoverTriggerPct = max(plancher 22, cible 35) = 35 → engage si SOC <= 35
nightRecoveryDone : SOC >= 35 → relâche
NIGHT_RECOVERY_REARM_BAND_PCT = 2 → réengage à 33
Engagement et relâchement sur le même nombre, la bande de 2 points du Fix A ne faisant que retarder le réarmement. Or 2 points de SOC ≈ 0,34 kWh ≈ 31 min à la charge nocturne (~650 W, parfaitement plate) : ce n'est pas une hystérésis, c'est la période d'une horloge. C'est le propriétaire qui a mis le doigt dessus en observant que le phénomène était « super stable car pas de variation de consommation » — la platitude de la charge est ce qui rend le cycle régulier.
Correctif : la recovery batterie post-charge est UNE SEULE passe par session
de nuit (nightRecoveryDone = ... || nightRecoveryDonePrev, sans bande). C'était
déjà l'intention affichée du Fix A (« relâché DÉFINITIVEMENT »). Aucun seuil de
ré-engagement n'est ajouté : batteryBelowHardFloor reste un terme indépendant
de nightGridTakeoverNeeded, et l'hystérésis garde sa bande 20/30. Le vrai
critère de ré-engagement est un budget d'énergie restant à couvrir — il
appartient au bilan énergétique, pas à une constante de plus ici.
Golden : night-recovery-rearm-sous-bande (qui épinglait l'oscillateur) devient
night-recovery-done-pas-de-reengagement ; +night-oscillateur-nuit-2627
(le relevé exact de la nuit) ; +night-recovery-done-plancher-dur (le filet
de sécurité doit rester : SOC 21 → ré-engagement). Preuve par mutation : bande
restaurée → échec du nouveau scénario ; retirée → 42/42.
D'où venait le 35 %, et pourquoi ce n'est pas une réserve
batteryMorningTargetPct vient des règles de prévision (≥ 18 kWh → 35 %,
≥ 10 → 55 %, sinon 85 %). C'est une optimisation économique — « demain il
fera beau, la batterie peut descendre » — et non une réserve de sécurité, ce que
le propriétaire croyait. Point de conception qui en découle : une réserve
contre la perte du réseau ne peut pas être constituée avec le réseau ; elle se
charge sur le solaire et se décide le soir. Voir §27 (à écrire) pour la réserve
d'orage.
Acteur non lu n°1 — les charges programmées de l'ESS (IMPORTANT)
Le GX a trois fenêtres de charge réseau programmées, actives tous les jours, que rien dans nos workflows ne lit :
| # | Départ | Durée | Cible SOC | Actif |
|---|---|---|---|---|
| 0 | 01:35 | 365 min (→ 07:40) | 45 % | on |
| 1 | 07:30 | 298 min (→ 12:28) | 45 % | on |
| 2 | 12:24 | 120 min (→ 14:24) | 55 % | on |
| 3 | 14:24 | 120 min | 85 % | off |
| 4 | 23:08 | 60 min | 50 % | off |
Entités : switch.gx_device_ess_batterylife_schedule_charge_N_enabled,
time..._start, number..._duration, number..._soc.
sensor.gx_device_ess_batterylife_state = self_consumption hors réseau.
Le GX veut donc 45 % la nuit quand EDF est présent, quand notre cible dit 35 % :
deux automates avec deux consignes différentes sur la même batterie.
Origine (donnée par le propriétaire) et décision du 27/08 : ces trois
fenêtres étaient un contournement manuel, posé d'abord quand le système
n'arrivait plus à tenir un seuil de 20 %, puis réutilisé l'hiver pour garantir
un minimum de charge en heures creuses et éviter de rallumer EDF en heures
pleines. Inutiles l'été. Les cinq programmations sont désormais désactivées
(vérifié : ..._charge_0..4_enabled = off). Le contrôleur EVCS est le seul
propriétaire de la décision réseau.
⚠️ Le besoin qu'elles couvraient l'hiver reste entier. Il doit être repris par le bilan énergétique, pas par une reprogrammation manuelle — voir la règle saisonnière ci-dessous.
Règle saisonnière de la cible du matin (propriétaire, 27/08)
« 20 % ou 35 % minimum c'est bien l'été mais l'hiver c'est idiot, ça va plutôt être 80 % le matin ». Le raisonnement est en heure solaire : en été 9 h locales valent 7 h TU, en hiver 10 h locales valent 9 h TU, soit ~2 h de consommation supplémentaires à tenir sur batterie avant le relais du solaire, et une montée hivernale plus lente et plus basse.
Conséquence : la cible du matin ne doit être ni une constante ni un choix entre trois paliers. Elle se déduit de l'heure réelle du croisement production/consommation du lendemain et de l'énergie à couvrir d'ici là — ce que le bilan sait déjà calculer. C'est le chantier qui remplace à la fois les paliers 35/55/85 et les charges programmées de l'ESS.
Acteur non lu n°2 — l'export d'excédent PV
switch.gx_device_dc_coupled_pv_feed_in_excess = off ·
switch.gx_device_ac_coupled_pv_feed_in_excess = off · ess_max_feed_in_power
= −1. Confirme par le proprietaire le 08/09/2026 : « je n'injecte pas c'est imperatif pas negociable ». Ce n'est donc pas une piste, et l'ecretage de la production a batterie pleine est le corollaire assume de ce choix. La
reponse au bridage est du STOCKAGE supplementaire, pas du pilotage.
Ce qui n'est PAS démontré : le bridage du MPPT par la présence d'EDF
Tentative de preuve sur la seule fenêtre EDF-ON diurne conservée en base (21/08, 07:24→08:07) : PV moyenne 91 W (max 218) contre 907 W (max 1464) sur les 43 min suivantes, EDF coupé, à consommation identique (668 vs 637 W). Écart spectaculaire — mais l'enveloppe solaire passe elle-même de 0,17 kWh à 07 h à 1,03 kWh à 08 h, soit un facteur 6 qui explique l'essentiel. La mesure est donc confondue par le lever du soleil et ne prouve rien. Ne pas décider d'une mise à jour firmware sur cette base. Test propre à faire : fermer EDF 10 min en milieu de journée, batterie non pleine, et comparer.
Test contrôlé EDF↔MPPT du 27/08 10:47-10:56 (relevé : docs/mesures/20260827-test-edf-mppt.log)
Protocole : 2 min de référence, hystérésis désactivée, contacteur EDF fermé 5 min, puis restauration complète (EDF ouvert, latch remis à zéro, hystérésis réactivée) et 2 min d'observation. Échantillon toutes les 15 s de PV, courant batterie, conso, import réseau, limite DVCC et mode ACM. Conditions : SOC 71 %, batterie absorbant +129 A, DVCC 130 A — donc MPPT avec un preneur.
Ce que le test établit :
- La limite DVCC est restée à 130 A d'un bout à l'autre et le mode ACM à
day_pv_boost. Le bridage éventuel ne vient donc PAS de l'Auto Charge Manager — hypothèse écartée. - Aucun bridage du MPPT observé dans ces conditions : PV moyenne 3260 W pendant la fenêtre EDF ON contre 2568 W après la coupure. Le solaire déclinait de lui-même (la référence est passée de 7708 à 4460 W en 105 s AVANT toute action — passage nuageux). La comparaison avant/pendant est donc inexploitable ; seule pendant/après l'est, et elle ne montre pas de bridage.
- Découverte non cherchée, celle-là sans ambiguïté : EDF fermé fait importer
du réseau pour charger la batterie, en rampe continue 0 → 983 → 2139 →
3335 → 4113 W en 3 min 30, alors que la consommation maison n'était que
de ~880 W. Preuve directe : à la coupure, le courant batterie s'effondre de
104,6 A à 25,7 A en 15 s — ~80 A (≈ 4 kW) venaient donc du réseau, pas
du soleil. Les cinq programmations ESS étaient déjà désactivées depuis
~10 min : ce n'est pas elles. Le propriétaire de cet ordre reste à
identifier (
minimum SOCde l'ESS n'est pas exposé côté HA).
Ce que le test N'établit PAS. Le propriétaire dispose de plusieurs mois
d'observation concordante d'un bridage du MPPT par la présence d'EDF. Ce test
ne le contredit pas : il ne reproduit pas sa condition. Avec la batterie à
71-75 % absorbant 130 A, le MPPT a toujours eu un preneur, donc rien ne pouvait
le censurer — c'est exactement l'équation de censure
PV_mesurée = min(ressource, conso + absorption batterie). Le cas à tester est
batterie pleine ou quasi pleine (SOC ≥ 90 %) avec EDF fermé : là, le réseau
sert les charges, la batterie ne peut plus absorber, et la PV n'a plus aucun
débouché. Test à refaire dans ces conditions, 3 min suffisent.
Conséquence immédiate, indépendante de la question du bridage : chaque minute d'EDF fermé coûte de l'énergie réseau qui part dans la batterie à plusieurs kW, sans que personne ne l'ait demandé. C'est l'explication mécanique des nuits où la batterie finit à 96 % sur réseau (incident du 09/07), et un argument de plus pour la « session réseau unique et dimensionnée ».
Aucune mise à jour firmware n'est justifiée par ces mesures.
PREUVE (27/08 11:38-11:51) : la présence d'EDF substitue le réseau au solaire
Relevé : docs/mesures/20260827-test-edf-mppt-alterne.log. Protocole alterné
OFF 3 min / ON 3 min / OFF 3 min / ON 3 min, échantillon 15 s, avec cette
fois la tension batterie et l'état des chargeurs MPPT
(sensor.*_mppt_operation_mode, *_pv_tracker_solar_N_operation_mode) — des
capteurs qu'aucun workflow ne lisait et qui disent explicitement si un chargeur
cherche son point de puissance maximale (mppt_active) ou se retient
(voltage_current_limited).
| Phase | PV moyen | Réseau | Conso |
|---|---|---|---|
| OFF_1 | 3 858 W | 0 | 2 223 W |
| ON_1 (établi) | 1 112 W | 2 287 W | 2 258 W |
| OFF_2 | 4 025 W | 0 | 2 226 W |
| ON_2 (établi) | 1 043 W | 2 717 W | 2 590 W |
| FIN (OFF) | 4 358 W | 0 | 2 249 W |
Transitions : bascule ~75-90 s après la fermeture du contacteur, retour en ~32 s après l'ouverture, quatre fois de suite. DVCC constant (110 A / 51,2 V), SOC 86→88, batterie loin de la saturation. La PV moyenne est plus HAUTE à la fin du test qu'au début (4 358 contre 3 858) : aucune tendance météo ne peut expliquer les creux. L'hypothèse « nuages » du test précédent est morte.
Perte : ~2 900 W, soit 73 % de la production, reprise par le réseau au watt près, à consommation inchangée. Ce n'est PAS la censure normale par manque de preneur (il y avait 2 250 W de charges et une batterie autorisée à 110 A, que le solaire alimentait intégralement 90 s plus tôt) : le réseau ne comble pas un déficit, il prend la place. Résidu pendant les phases ON remarquablement constant (~1 040 W), ce qui ressemble à un plancher et non à une réduction proportionnelle.
Cadrage donné par le propriétaire, à respecter : le bridage quand la batterie se remplit — et même avant, pour limiter le courant de charge — est le comportement NORMAL d'une installation en autoconsommation sans injection, il le connaît, ce n'est pas le sujet. L'anomalie est la substitution ci-dessus, apparue depuis le passage des deux MultiPlus II en maître/esclave (parallèle), avec plusieurs mois de recul.
Piste (configuration avant firmware) : un système parallèle ayant perdu son comportement ESS retombe en passthrough + chargeur — le réseau alimente les charges et charge la batterie, le solaire ne faisant plus que compléter, ce qui est exactement le symptôme. À vérifier côté VEConfigure/console GX, non visible depuis HA : (1) assistant ESS présent et identiquement configuré sur les deux unités ; (2) point de consigne réseau (l'import se cale exactement sur les charges) ; (3) affectation du compteur réseau après passage en parallèle.
Conséquence chiffrée pour l'autonomie : chaque heure de contacteur fermé coûte ~2,3 kW de solaire gratuit remplacé par du payant, et cela quel que soit le niveau de la batterie — à cumuler avec l'import de ~4 kW vers la batterie observé au test de 10:47. Le dimensionnement des batteries prévu cet hiver doit se faire APRÈS ce correctif, sous peine de dimensionner sur une installation qui perd les trois quarts de sa production dès que le réseau est présent.
Réserve d'orage (déployé 27/08 12:04, mode ombre, tests 42/42)
Besoin : les coupures réseau d'orage sont fréquentes sur ce site, et le propriétaire veut garder de quoi tenir jusqu'au retour du solaire.
Point de départ, souvent mal posé : une coupure n'arrête PAS l'installation. Le solaire et la batterie continuent, seul le filet de secours disparaît. La question n'est donc pas « combien stocker pour survivre » mais « la batterie traverse-t-elle la prochaine fenêtre de déficit SANS réseau ». C'est exactement la couverture prudente (p80) que le bilan calcule déjà : aucune constante nouvelle. Le profil prudent est le bon choix ici précisément parce qu'une coupure retire la possibilité de rattraper une sous-estimation.
Chaîne : HA_SNAPSHOT_TEMPLATE lit la vigilance sans connaître la commune
(states.sensor | selectattr('entity_id','search','_weather_alert')) →
PREPARE_BALANCE passe-plat → BALANCE calcule le bloc storm →
BUILD_HA_CALLS publie 5 helpers evcs_balance_storm_*.
Fenêtre retenue : en déficit, celle qu'on traverse (couverture prudente) ; en
surplus, la prochaine (nextDeficit) — car à 15 h la réponse « rien à couvrir »
est exacte et inutile, le risque étant la nuit qui vient.
Seuil d'activation : vigilance orange, c'est-à-dire l'échelle officielle de Météo-France (vert/jaune/orange/rouge) et non un nombre choisi ici.
Helpers (package evcs_energy_balance.yaml, sauvegarde
*.bak.20260827, rechargés et vérifiés) : evcs_balance_storm_level ·
_storm_active · _storm_reserve_kwh · _storm_reserve_soc (borné à 100 pour
la plage de l'entrée) · _storm_reason.
Tests 38 → 42. Deux résultats notables figés :
- 24/08 06:43, SOC 21 % : réserve 2,42 kWh (SOC 38,1 %) → manque 2,57 kWh pour tenir la matinée sans réseau ;
- 24/08 15:30, SOC 95 %, vigilance rouge : la fenêtre qui compte devient celle de 19 h, 12,77 kWh, SOC requis 107,1 % — même pleine, la batterie actuelle ne passe pas la nuit sans réseau. Valeur volontairement NON écrêtée : c'est le signal de dimensionnement pour l'achat de batteries de cet hiver.
Garantie mode ombre étendue : le fixture synthétique BALANCE_OK de
scenarios-shadow.mjs ne contenait pas storm, donc les nouvelles écritures
n'étaient PAS couvertes par le test de périmètre. Corrigé, plus une assertion
explicite wroteStorm: 5. Preuve par mutation : une écriture détournée vers
input_boolean.victron_evcs_night_grid_takeover_needed fait échouer la suite
(outOfScope la nomme).
RESTE À FAIRE — action utilisateur : l'intégration Météo-France n'est pas
installée (HA n'a que met / Met.no). Elle est gratuite, sans clé d'API,
intégrée au cœur de HA. Le flux de configuration a été testé par API : il
réclame un nom de commune et refuse les coordonnées ({"city":"empty"} sur
48.7732657 / -3.5187179) — donc à ajouter par l'interface, ou à me donner la
commune. Tant qu'elle est absente, le bilan publie indisponible, active
reste faux, et la réserve est quand même chiffrée : rien ne casse.
Historique HA plus court que configuré
configuration.yaml déclare purge_keep_days: 90, mais l'historique brut
s'arrête au 20/08 (~7 jours) : testé vide au 01/06, 29/07, 10/08, 17/08 ;
1 point au 20/08, 7 au 24/08. Base recréée, ou purge qui ne respecte pas la
consigne — à investiguer. Les statistiques long terme (horaires, conservées
indéfiniment) sont un stock distinct et probablement intactes.
Diagnostic en attente de vérification (Qwen, 22/08)
Voici mon analyse et plan de correction : Diagnostic Le bug est dans HA_SERVICE_CALLS_OUTPUT.js:987-996 : edf_general_ok_on: { execute: ['grid_forced', 'grid_hc_allowed'].includes(context.sourceMode) && !parsed.gridAvailable, ... } Il n'existe pas de edf_general_ok_off. Le workflow ne coupe JAMAIS activement EDF. Il ne fait que le rallumer quand sourceMode est grid ET que le réseau n'est pas encore disponible. Quand sourceMode revient à solar_only/blocked, le switch reste ON dans l'état qu'il avait. Conséquence : après la fenêtre HC nocturne (~06:00), EDF reste allumé. La présence du réseau bloque la production PV (Victron plafonne à ~100 W). Le débranchement du véhicule résout parce que c'est le seul mécanisme qui coupe physiquement le switch. Plan de correction Fichier : src/nodes/HA_SERVICE_CALLS_OUTPUT.js Ajouter un call edf_general_ok_off juste après edf_general_ok_on (ligne 996) : edf_general_ok_off: { execute: !['grid_forced', 'grid_hc_allowed'].includes(context.sourceMode) && parsed.gridAvailable &parsed.evcsVehicle
Verdict (Claude, 22/08) : diagnostic ÉCARTÉ, voir §24 ci-dessous. Le
symptôme est correctement identifié (EDF ne se coupe jamais activement après
la fenêtre HC) mais la cause et le correctif proposés sont faux : le bug n'est
pas dans HA_SERVICE_CALLS_OUTPUT.js (qui n'a jamais eu vocation à couper EDF
— la coupure est le rôle exclusif du workflow séparé "Victron - Battery / Grid
Hysteresis Fix V1"), et ajouter un edf_general_ok_off côté EVCS créerait un
deuxième écrivain non coordonné sur switch.edf_general_ok_switch (courses
30s/1min entre les deux workflows, le hysteresis reprenant la main au cycle
suivant via son propre latchIsOn et rallumant probablement EDF quand même).
Le vrai bug et son correctif (Fix G, côté hystérésis) sont documentés en §24.
Document mis à jour le : 22/08/2026 (~18:00) — Fix G déployé (hystérésis), diagnostic Qwen écarté, golden-hysteresis 7/7
24. Session du 22/08/2026 — Fix G : coupure EDF quand l'override EVCS disparaît mi-session (hystérésis)
Incident (nuit 21-22/08)
Le takeover nuit (P7) a correctement rechargé la batterie à ~35 % via EDF. Quand la fenêtre HC s'est fermée (~06:00-07:24) avec le véhicule encore branché en session active (pas encore flag "charge terminée"), EDF est resté allumé — le réseau a plafonné le MPPT Victron à ~100 W, privant la maison de 600-4000 W de solaire disponible (elle tirait ~1000 W d'EDF). Seul le débranchement du véhicule a libéré EDF (2179 W de PV immédiatement après).
Root cause
Côté EVCS (WINDOWS_SOURCEV2.js/DECISION_ENGINEV2.js) : pas le bug.
Chaque cycle, nightSessionActive et tout le moteur nuit sont activement
recalculés à false/'blocked' dès la fermeture de fenêtre — helper night_grid_takeover_needed passe à off et source_mode à 'Bloqué'
correctement et immédiatement, même mi-session. MAIS les deux branches
"collantes" de fin de session (Fix F, exige profile === 'night_hc' ; P10
day_vehicle_full_idle, exige que le surplus_state précédent soit déjà un
pattern "terminé") ne se déclenchent PAS sur le cycle exact où la fenêtre se
ferme mi-session non-terminée. Le système retombe sur des états génériques
transitoires (stop_source_lost puis idle_wait_source).
Côté Grid Hysteresis (workflows/victron-grid-hysteresis-v1.json, node
"Compute Grid Hysteresis") : c'est là le bug. Les règles de coupure
immédiate existantes (Fix C turn_off_vehicle_unplugged, Fix E2
turn_off_recovery_done) ne matchent que des noms exacts de surplus_state
(vehicle_not_connected, night_recovery_done_idle, departure_done_idle) —
aucune ne matche stop_source_lost/idle_wait_source (ni day_vehicle_full_idle
lui-même). Sans matche, le code retombe sur la boucle générique qui maintient
EDF jusqu'au seuil statique grid_soc_off (35 %, input_number.victron_grid_soc_off),
totalement découplé de la cible dynamique de l'EVCS — potentiellement des
heures, d'autant qu'EDF-ON lui-même bride la recharge solaire organique.
Fix G (déployé 22/08 ~18:00, golden-hysteresis 7/7)
Au lieu d'ajouter encore un nom d'état à une liste blanche fragile, nouvelle
règle générique dans "Compute Grid Hysteresis", basée sur evcsGridOverride
(déjà fiable) plutôt que sur le nom de surplus_state — couvre donc aussi
automatiquement tout futur nom d'état non énuméré :
if (
!forcedCut && latchIsOn && gridIsOn &&
!evcsGridOverride &&
soc > gridSocOn && // 22%, PAS gridSocOff 35% — protège le cas
// indépendant "SOC bas" qui doit tenir
// jusqu'à gridSocOff
evcsSourceModeLooksValid // garde anti-glitch (lecture HA vide/
// unavailable/unknown → pas d'action)
) {
desiredGrid = 'off'; desiredLatch = 'off';
state = 'turn_off_evcs_override_released';
forcedCut = true;
}
Coupure immédiate, bypass min_on (25 min), comme Fix C/E2. Décision validée avec l'utilisateur : vitesse de libération prioritaire sur le risque résiduel (un glitch de lecture HA sur un seul cycle pourrait causer une coupure/ rallumage isolé, auto-diagnosticable dans le logbook — pas de chatter soutenu car il faudrait une lecture fausse à chaque cycle).
Nouvelle suite de tests : tests/golden-hysteresis/ (7 scénarios, première
suite pour ce workflow — 0 → 7)
g-incident-gap-mid-session/g-incident-gap-idle-wait-source (les deux noms
d'état zombies de l'incident, min_on non écoulé) · g-incident-replay-hc-boundary
(rejeu complet, min_on déjà écoulé aussi — prouve que ce n'est pas seulement un
bypass min_on) · g-baseline-low-soc-no-override (garde non-régression : SOC
bas indépendant doit continuer à tenir) · g-regression-fixc-unplugged /
g-regression-fixe2-recovery-done (Fix C/E2 toujours prioritaires, pas
absorbés par Fix G) · g-glitch-guard-unavailable (garde anti-glitch).
Lancée AVANT patch : 3 échecs (les scénarios d'incident, stuck sur
hold_on_min_runtime/hold_on_until_soc_off) + 4 succès (gardes déjà bonnes)
— confirme que le harnais détecte vraiment le bug. Après patch : 7/7.
Câblée dans tools/n8n-push.mjs (nouveau isHysteresisWorkflow), bloquante
au push comme les 2 autres workflows.
Outillage
tools/patch-20260822-g-override-released-cut.mjs (rejouable, même patron que
patch-20260809-e2-recovery-done-cut.mjs) · tests/golden-hysteresis/{fixture-snapshot.json,run.mjs,scenarios.mjs}.
Déploiement
node tools/n8n-push.mjs workflows/victron-grid-hysteresis-v1.json → backup
serveur auto (modified/prepush-7iIlu7LLTW7TZA8o-*.json) → syntaxe OK →
golden-hysteresis 7/7 → PUT OK. grid_min_on_minutes réel vérifié via MCP
ha-get-state avant de figer le fixture : 25 (pas le fallback 45 du code).
Hors périmètre (volontairement)
Aucun changement côté src/nodes/* (EVCS V2.6) — le fix est entièrement côté
hystérésis. Amélioration cosmétique future possible, non incluse ici :
remplacer les noms zombies stop_source_lost/idle_wait_source par un état de
transition dédié pour la lisibilité dashboard/logbook.
À surveiller (prochaine fermeture de fenêtre HC avec véhicule encore
branché mi-session)
- Logbook doit montrer
turn_off_evcs_override_releasedpeu après la fermeture de fenêtre si le véhicule n'est pas encore flag "terminé" ; - La PV doit remonter (MPPT non-plafonné) immédiatement après ;
- Aucune coupure/rallumage répété (
turn_off_evcs_override_releasedsuivi de près parturn_on_evcs_overriderépétitivement) — signe d'un glitch de lecture HA à investiguer si observé.
*Document mis à jour le : 22/08/2026 (~18:05) — Fix G déployé et validé (syntaxe + golden-hysteresis 7/7 + PUT OK), à confirmer en conditions réelles à la prochaine fermeture de fenêtre HC mi-session
25. Session du 24/08/2026 — Fix G retiré + diagnostic des deux oscillateurs
Fix G retiré de la production (déployé 24/08 12:18, golden-hysteresis 7/7)
Fix G (§24) a produit en production un yoyo EDF de 2-4 min la nuit du 23-24/08,
véhicule débranché : coupure à SOC 21 % (Fix G, bypass min_on) puis rallumage à
SOC 20 % (branche hold_on_low_soc, bypass cooldown), MPPT qui flappe à chaque
bascule. Logbook : turn_off_evcs_override_released 06:43:03 → hold_on_low_soc
06:45:03 → turn_on_low_soc_cooldown_bypassed 06:52:03.
Erreur de conception : le garde soc > gridSocOn ne protégeait que l'INSTANT
de l'engagement sur SOC bas (soc <= 20), pas la PHASE DE REMONTÉE 20 → 30 qui
est 100 % de la durée de vie de ce cas. Fix G a donc remplacé l'hystérésis 20/30
par une hystérésis 20/20,1.
Erreur de test : le fixture tests/golden-hysteresis/ portait grid_soc_on: "22" / grid_soc_off: "35" — les valeurs de repli du code, pas la production
(vérifiée via MCP : 20 / 30, inchangées depuis le 07/08). Et le scénario de
non-régression g-baseline-low-soc-no-override utilisait SOC 18 %, c'est-à-dire
le seul point que Fix G épargnait. → Règle : figer un fixture de test sur des
valeurs lues en live via MCP, jamais sur les fallbacks du code.
Retrait : tools/patch-20260824-revert-g.mjs (rejouable) — le jsCode résultant
est byte-identique à la version pré-Fix-G (commit 0419c74), vérifié.
Suite réécrite (7 scénarios, seuils réels 20/30) avec les garde-fous manquants
h-recovery-band-no-cut-21 / -25 : la remontée batterie sur réseau ne doit
JAMAIS être interrompue avant grid_soc_off. Test de mutation : Fix G réappliqué
→ 4/7 échecs, retiré → 7/7. Le filet attrape désormais la régression.
Diagnostic : deux oscillateurs, même pathologie
Détail complet dans docs/MANUEL_COMPLET.md §11.8. Résumé :
A — Yoyo EDF. Le problème de fond n'est pas le réglage de grid_soc_off
mais le fait qu'il n'a aucun sens physique : le 24/08, la PV ne couvre la conso
maison qu'à partir de ~09:00 (0,07 → 0,63 → 2,74 kWh/h contre 0,75 kWh/h de
conso). Couper EDF à 06:43 à 21 % rend le yoyo inévitable quels que soient les
seuils. Le workflow hystérésis lit 25 champs et aucune prévision solaire.
Cible : session réseau unique dimensionnée jusqu'au croisement PV/conso,
arbitrée par le conseiller IA. NB : mesurer la vraie capacité PV impose de
couper EDF (sinon le MPPT est bridé et on ne voit rien).
B — Oscillation charge EVCS en plein soleil (24/08 12:25 → 12:28).
Ce n'est PAS une limite 5 kVA (aucun ve_bus_overload depuis le 20/08,
ac_loads_max_safe_w = 9000, AC-out L1 à 5335 W sans overload, plafond calculé
~20 A) et pas non plus une bande morte batterie trop étroite : le courant
batterie était à +127 A au moment de l'arrêt. Les deux atténuations que
j'avais d'abord proposées (battery_discharge_soft_a à −12 A,
battery_neutral_band_a à 3-5 A) n'avaient donc aucun effet sur ce cas —
l'utilisateur l'a signalé, à raison, avant que je les applique.
Cause réelle : inversion du plancher de démarrage et du plancher de maintien.
À 12:24:00 le profil bascule vers midday_hc_or_solar ; avec SOC 58 % le
plancher de démarrage vaut min(midday_soc_min 75, abondance P6 40) = 40
et le plancher de maintien max(soc_stop_min 29, 75-15) = 60. Le système a
le droit de DÉMARRER ce qu'il n'a pas le droit de MAINTENIR → cycle limite à la
période de min_run_minutes (3 min) sur toute la bande [40 %, 60 %], et ~2,6 kW
de PV écrêtés à chaque creux (PV 10,24 kW → 7,64 kW quand la borne s'arrête).
Décisions de conception validées avec l'utilisateur
- La conso se lit, elle ne se devine pas ; filtrage numérique dynamique des pointes (improbables la nuit hors charge EVCS ; pas de moyenne quand aucun véhicule n'est branché).
- SOC : lissage pour les décisions de confort, valeur instantanée conservée pour la sécurité (le risque prime).
- Les seuils fixes doivent devenir dynamiques (saison, prévision) et être arbitrés par le conseiller IA : « un automatisme c'est trop rigide pour traiter tous ces cas tordus ». Ne plus empiler de branches codées en dur.
- Exploiter les messages d'état réels du Victron et de la borne au lieu de s'auto-limiter à l'aveugle sur un proxy.
Correctif déployé 24/08 12:45 — invariant maintien <= démarrage
src/nodes/BATTERY_CLOUD_LOCKS.js, deux volets indissociables :
effectiveSocHoldFloor = min(règle du profil, plancher de démarrage effectif)— on ne peut plus être autorisé à démarrer ce qu'on n'est pas autorisé à maintenir. Sans abondance, comportement inchangé.- L'abondance P6 se PROUVE au démarrage (
solarAbundanceStart: pvAvg >= 4000 ET batterie >= +20 A) mais se MAINTIENT en charge (solarAbundanceHold: pvAvg >= 4000 ET batterie >= 0). Sans ce dédoublement, la charge du véhicule détourne le surplus, fait tomber le courant batterie sous +20 A et détruit sa propre condition d'autorisation — l'oscillation reviendrait dès que le PV n'est que moyennement fort.
Golden 39 → 40 : midday-inversion-start-hold (relevé VRM du 24/08 12:25 :
PV 10,24 kW, charges 3815 W dont EVCS 3502 W, batterie +127 A, SOC 58 %).
Preuve par mutation : code d'avant → stop / « Conditions de maintien perdues » ;
après → charging_step_up_battery à 19 A. Les 39 scénarios existants inchangés.
Leçon transverse : trois oscillateurs identifiés le même jour (Fix G, abondance P6 auto-destructrice, inversion démarrage/maintien) — même pathologie à chaque fois, des seuils empilés par couches successives, chacun raisonnable isolément, dont personne ne vérifie la cohérence d'ensemble. D'où le chantier du bilan énergétique partagé plutôt qu'un garde de plus.
Document mis à jour le : 24/08/2026 (~12:50) — Fix G retiré (golden-hysteresis 7/7, seuils réels 20/30), inversion démarrage/maintien corrigée (golden 40/40), diagnostic des deux oscillateurs
27. Session du 30/08/2026 — Deux propriétaires GX de la décision réseau, jamais lus
Le propriétaire demandait une procédure détaillée avant d'intervenir sur les deux MultiPlus-II en parallèle (piste VEConfigure de la §26). Les lectures préalables côté GX déplacent la priorité : il est probable qu'aucune intervention VEConfigure ne soit nécessaire.
Procédure complète : docs/PROCEDURE_ESS_PARALLELE.md.
Trouvaille 1 — Consigne de puissance réseau à 4200 W (défaut : 50 W)
number.gx_device_ac_power_setpoint = 4200 W, miroir per-phase
number.airtria_mpii_v1_ac_power_setpoint_l1 = 4200 W. C'est le Grid setpoint
ESS (/Settings/CGwacs/AcPowerSetPoint) : la puissance que le système
s'efforce en permanence de prélever sur le réseau quand AC-IN est présent.
L'ESS régulant la puissance au point de raccordement, le seul levier pour faire place à cet import quand maison et batterie sont déjà servies est de brider les MPPT via DVCC. C'est le mécanisme exact de la substitution prouvée le 27/08, et l'explication du résidu constant (~1040 W).
Les deux campagnes se réconcilient arithmétiquement :
- 10:47, SOC 71 %, conso 877 W → import monte à 4113 W ≈ consigne ;
- 11:38, SOC 86 %, batterie en absorption n'acceptant que ~1070 W →
conso 2258 + batterie 1070 − PV 1043 = 2285 Wcontre 2287 W mesurés.
Valeur inchangée sur tout l'historique disponible (7 j) ; user_id: null sur le
dernier changement = simple recréation d'entité au redémarrage HA du 24/08.
Aucun code du dépôt n'écrit cette entité (grep). Hypothèse la plus probable :
ancien contournement manuel, même famille que les charges programmées ESS
désactivées le 27/08. À confirmer sur l'écran du GX avant de conclure — ne
pas se fier au seul nom de l'entité HA.
Correction proposée : 50 W (défaut Victron, légèrement positif pour ne jamais injecter). Réversible en dix secondes, sans VEConfigure.
Trouvaille 2 — SOC minimal ESS à 40 %, au-dessus de notre cible de nuit
number.gx_device_ess_min_soc_limit = 40 %, sensor.gx_device_active_soc_limit
= 40 % (BatteryLife ne fait donc pas dériver la limite au-dessus).
Sous 40 %, l'ESS recharge la batterie sur le réseau de sa propre initiative.
Ce n'est pas théorique : sensor.gx_device_ess_batterylife_state passe à
recharge les 29/08 04:08 et 30/08 01:21, précédé de
soc_below_battery_life_dynamic_soc_limit.
Or le contrôleur vise 35 % le matin en été : le nôtre relâche à 35, le GX reprend à 40. Troisième automate contradictoire sur la même batterie, après les charges programmées. Arbitrage à rendre avec le propriétaire — piste préférée : laisser ce plancher comme filet de sécurité bas (~20 %) et confier la vraie cible au bilan énergétique. Rien changé sans son accord.
Trouvaille 3 — L'assistant ESS tourne, aucune erreur VE.Bus
États BatteryLife vivants et cohérents → la boucle ESS fonctionne, elle n'est
pas absente. Toutes les alarmes VE.Bus à no_alarm, aucune erreur
d'installation. L'hypothèse « assistant perdu ou dépareillé entre les deux
unités après le passage en parallèle » est affaiblie sans être éliminée (un
assistant présent mais mal paramétré ne lève aucune alarme) — d'où le maintien
de la partie VEConfigure en seconde intention dans la procédure.
Méthode retenue dans la procédure
Étapes classées par risque croissant : lectures gratuites → réglages GX
réversibles → rejeu du protocole alterné du 27/08 comme critère objectif →
VEConfigure seulement en cas d'échec, avec sauvegarde .rvms obligatoire, une
correction à la fois, et interdiction absolue de relancer le VE.Bus System
Configurator. Firmware : toujours pas justifié par les mesures, et à ne pas
mêler au diagnostic.
Document mis à jour le : 30/08/2026 — consigne réseau 4200 W et SOC minimal ESS 40 % identifiés comme propriétaires non lus de la décision réseau ; procédure d'intervention rédigée
28. Session du 31/08/2026 — La substitution ne se reproduit plus (batterie preneuse). Cas « batterie pleine » encore ouvert
Première vérification de la correction de la consigne réseau (4200 → 50 W, §27), sur une nuit complète puis deux campagnes alternées.
La nuit 30-31/08 : l'import colle à la consommation
Une seule session réseau, switch.edf_general_ok_switch ON à 05:04:10
(hystérésis, SOC 21 ≤ seuil 20) et OFF à 10:08:04 (SOC 30 ≥ seuil OFF).
Aucun rebond, aucun créneau.
| heure | import réseau | conso maison | courant batterie |
|---|---|---|---|
| 05:15 | 780 W | 732 W | 0 A |
| 06:30 | 507 W | 477 W | −1 A |
| 07:45 | 781 W | 751 W | +4 A |
À comparer au test du 27/08 10:47 où, pour 877 W de consommation, l'import montait en rampe jusqu'à 4113 W pour gaver la batterie. Le comportement a disparu. Détail de la fermeture : de 05:04 à 05:13 l'import est resté à 24-56 W (la consigne) pendant que la batterie continuait de servir la maison à −15 A ; à 05:14, SOC atteignant le plancher ESS de 20 %, l'ESS a basculé — import = charge maison, batterie à 0 A, SOC plat à 20 % jusqu'à 08:00. Manuel.
Le matin : réseau présent, production libre
De 08:40 à la coupure de 10:08, contacteur toujours fermé :
| heure | PV | import | conso | I batterie |
|---|---|---|---|---|
| 08:45 | 1 303 W | 18 W | 1 109 W | +2 A |
| 09:25 | 3 425 W | 39 W | 805 W | +50 A |
| 10:05 | 5 244 W | 47 W | 952 W | +83 A |
sensor.mppt_450_200_hq2449yr9pt_mppt_operation_mode est resté mppt_active
sur toute la fenêtre : le chargeur déclare lui-même chercher son point de
puissance maximale pendant que le réseau est présent.
Campagnes alternées (tools/test-edf-alterne.mjs)
Journaux : docs/mesures/20260831-1043-test-edf-alterne.log (phases 180 s) et
20260831-1101-test-edf-alterne.log (phases 240 s).
Transitions exploitables, chaque phase fermée contre ses voisines ouvertes :
| phase | PV fermée | PV voisines ouvertes | écart | trackers bridés |
|---|---|---|---|---|
| 10:47 ON_1 | 7 168 W | 7 131 W | +0,5 % | 2,7/7 (DVCC, I=124 A) |
| 11:13 ON_2 | 7 920 W (max 8 759) | 7 002 W | +13,1 % | 4,5/7 (DVCC, I=125 A) |
Le 27/08, la même manœuvre faisait tomber la PV de 3 885 à 1 045 W en 90 s avec
six trackers sur sept en voltage_current_limited. La substitution ne se
reproduit plus à batterie preneuse. Le bridage résiduel observé est le bridage
NORMAL : il apparaît quand le courant batterie approche la limite DVCC (125 sur
130 A) et il s'accompagne d'un import réseau nul — critère de discrimination
utilisable : bridage normal ⇒ import nul ; bridage anormal ⇒ l'import remplace
la production.
Mécanisme identifié : un transitoire de reprise, qui retombe à la consigne
À chaque fermeture du contacteur, le réseau reprend d'un coup toute la charge de la maison et la totalité du solaire bascule vers la batterie. Bilan vérifié au watt près (10:48:15 : PV 7222 + grid 2120 − conso 2249 = 7093 W contre 7107 W de charge batterie mesurée). Puis l'import décroît jusqu'à la consigne :
| campagne | import à la fermeture | après ~2 min 15 |
|---|---|---|
| 11:05 ON_1 | 1 985 W | 25 W, stable |
| 11:13 ON_2 | 2 600 W | 52 W puis −41 W |
Coût ≈ 50-60 Wh par fermeture. Avec l'ancienne consigne à 4200 W, cette reprise n'avait aucune raison de redescendre : elle se stabilisait sur « le réseau alimente tout, on bride le solaire pour faire de la place ». C'est vraisemblablement le même mécanisme, dont la correction de consigne a rétabli l'extinction. Corollaire : chaque fermeture coûte, ce qui plaide pour une session réseau unique et dimensionnée plutôt que des créneaux répétés. Note : aucun transitoire à la fermeture de 05:04 (PV nulle) — il n'y a rien à rediriger quand il n'y a pas de solaire.
CE QUI N'EST TOUJOURS PAS TESTÉ — batterie pleine (à faire)
Tout ce qui précède s'est mesuré à SOC 40-54 %, batterie absorbant 60 à 125 A. Le propriétaire maintient, sur plusieurs mois d'observation, que le cas décisif est batterie pleine : plus de tampon, donc le solaire qui alimentait la maison n'a plus de destination quand le réseau la reprend. Discrimination attendue, contacteur fermé, maison ~2 kW, SOC ≥ 90 % :
| observation | conclusion |
|---|---|
| PV ≈ conso (~2 kW), import ≈ 50 W | corrigé — écrêtage normal, le solaire sert la maison |
| PV ≈ 0-1 kW, import ≈ conso | non corrigé — éviction ; VEConfigure (§27, docs/PROCEDURE_ESS_PARALLELE.md étape 1) |
Le 27/08 à 11:38, SOC 86-88 %, batterie n'acceptant plus que ~1 kW, on avait PV 1043 W pour une maison à 2258 W et un import de 2287 W : le solaire SOUS la consommation et le réseau AU-DESSUS. C'est le point le plus proche du cas décisif dont on dispose, et il est du mauvais côté. Trois minutes de protocole alterné à SOC ≥ 90 suffiront à trancher.
Outillage ajouté
tools/test-edf-alterne.mjs— protocole alterné OFF/ON/OFF/ON/OFF, phases et pas d'échantillonnage paramétrables. Mesure PV, tension et courant batterie, conso, import, SOC, limite DVCC, présence physique AC-IN et le mode des 7 chargeurs solaires. Désactive l'hystérésis pendant le test et restaure tout à la fin, sur Ctrl-C et sur erreur. Journal horodaté dansdocs/mesures/.tools/analyse-test-alterne.mjs— résumé par phase et verdict.
Deux pièges d'instrumentation corrigés en cours de route, à ne pas réintroduire :
- Garde météo. Les phases OUVERTES voient toutes la même ressource : si elles divergent entre elles (33 % et 42 % sur les deux campagnes du 31/08), la moyenne ON contre OFF est ininterprétable. L'outil refuse désormais de conclure dans ce cas et compare chaque phase fermée à ses voisines immédiates. C'est ce piège qui avait pollué le premier test du 27/08. Contrôle interne utile : si la PV chute AVANT l'apparition de l'import, ce sont des nuages (cas de la phase ON_2 du 31/08 10:53, chute 6995 → 3607 W avec l'import encore à zéro).
- Biais du parseur. La lecture des modes MPPT était ancrée sur
MPPT, ce qui faisait tomber en silence toute ligne dont le PREMIER tracker était bridé — précisément les échantillons à mesurer — et biaisait le résultat vers « aucun bridage ». Le bloc de modes est le dernier champ : l'ancrer suredf on|off, jamais sur le motMPPT.
Signal indépendant : la jauge de SOC s'effondre en bas d'échelle
Entre 04:00 et 04:20, le SOC est passé de 30 à 22 % alors que la batterie ne débitait que ~11 A (≈ 550 W), tension stable à 49 V. Huit points sur un parc de ~15 kWh représentent 1,2 kWh, soit plus de deux heures à ce débit — pas vingt minutes. La jauge ne mesure plus, elle réestime. Comportement classique d'un parc vieillissant près du genou de décharge, cohérent avec le module en panne connu (7 modules actifs sur 8, cf. MANUEL §0). Conséquence : tous les seuils des deux workflows (20, 30, 22, 35) sont exprimés dans une grandeur non linéaire en bas d'échelle. À verser au chantier du bilan énergétique ; ne pas « corriger » par un seuil de plus.
LE CAS DÉCISIF, TESTÉ — batterie pleine (campagne 12:58, SOC 90-92 %)
Journal : docs/mesures/20260831-1319-test-edf-alterne.log. Conditions de
départ exactement celles que le propriétaire désignait : SOC 90 %, courant
batterie +22,8 A, DVCC retombée à 80 A, production déjà écrêtée à
3 691 W (2 301 W de maison + ~1,1 kW de batterie) alors que la même
installation produisait 9 112 W le matin. Plus de tampon.
Le phénomène qu'il décrit se produit — et il se résorbe. ON_2, le cas le plus net :
| heure | PV | import | conso |
|---|---|---|---|
| 13:11:57 | 2 663 W | 0 | 1 899 W |
| 13:12:12 | 559 W | 1 777 W | 1 886 W |
| 13:12:57 | 1 405 W | 1 136 W | 1 877 W |
| 13:13:27 | 1 912 W | 683 W | 1 891 W |
| 13:14:27 | 3 008 W | 258 W | 2 362 W |
| 13:14:42 | 3 158 W | 112 W | 2 373 W |
Effondrement de 79 % en un seul pas de mesure, simultané à l'apparition de l'import (et non avant, contrairement aux passages nuageux du matin : le critère de discrimination fonctionne). Puis récupération en miroir, watt pour watt, sur ~2 min 30 : l'import redescend à 112 W et la production revient à 3 158 W, au-dessus de son niveau d'avant fermeture. Même séquence en ON_1 (3 369 → 1 890 W avec import 2 083 W, retour à 3 315 W avec import 8 W).
Une fois le transitoire exclu (165 s), l'état établi ne montre aucune substitution : −7,0 % et +2,0 % contre les phases ouvertes voisines, import résiduel 443 et 479 W et toujours décroissant. Les phases ouvertes déclinent d'elles-mêmes (3 505 → 2 808 → 2 488 W, milieu d'après-midi), ce qui explique le −7 %.
Conclusion. L'observation de plusieurs mois du propriétaire est exacte : à batterie pleine, fermer le contacteur tue la production. Ce qui était faux, c'est la permanence. Le mécanisme est le même transitoire de reprise qu'à batterie preneuse — le réseau reprend la maison, le solaire cherche une destination — sauf que sans tampon la seule variable d'ajustement est le bridage des MPPT, donc l'effet devient spectaculaire au lieu d'être invisible. Avec l'ancienne consigne à 4200 W, ce transitoire n'avait aucune raison de se terminer : le régulateur ne pouvait atteindre sa cible qu'en maintenant le solaire bridé. C'est l'explication complète de « pas de grid sinon le solaire ne sert à rien », et la correction de consigne y met fin.
Coût par fermeture du contacteur, à batterie pleine : ~50 Wh d'import transitoire plus ~100 Wh de production perdue pendant les 2 min 30 de bridage, soit environ 150 Wh. À batterie preneuse, seulement les ~50 Wh. C'est l'argument chiffré en faveur d'une session réseau unique et dimensionnée plutôt que de créneaux répétés : chaque ouverture-fermeture se paie, et se paie trois fois plus cher quand la batterie est pleine.
Ce qui reste vrai et inchangé : à batterie pleine et régime établi, la
production est écrêtée à la consommation (6 à 7 trackers sur 7 en
voltage_current_limited dans TOUTES les phases, ouvertes comme fermées).
C'est le bridage normal d'une installation sans injection, il ne dépend pas du
réseau, et il ne doit pas être confondu avec l'éviction.
Aucune intervention VEConfigure ni firmware n'est justifiée par ces mesures.
L'étape 1 de docs/PROCEDURE_ESS_PARALLELE.md reste en réserve, sans objet
actuel.
Document mis à jour le : 31/08/2026 (~13:30) — cas batterie pleine testé : l'effondrement existe mais dure 2 min 30 et se résorbe entierement ; la substitution permanente est morte avec la consigne a 4200 W ; ni VEConfigure ni firmware justifies
29. Session du 31/08/2026 (suite) — Fenêtre d'établissement du réseau
Le défaut
Mesure du 31/08 (§28) : à la fermeture du contacteur, le réseau reprend d'un coup toute la charge de la maison et le solaire bascule vers la batterie — ou se fait brider s'il n'y a plus de tampon (2 663 → 559 W). L'import redescend ensuite à la consigne en ~150 s, deux fois mesuré (1 985 → 25 W, 2 600 → 52 W).
Pendant cette phase, PV, import et courant batterie ne décrivent aucun régime
permanent. Or la boucle EVCS tourne toutes les 30 s : elle y prend cinq
décisions. Pire, INPUT_PARSE lit pv_power_10m_avg_w, une moyenne glissante
de dix minutes : un creux de 2,5 kW pendant 2 min 30 la tire d'environ 600 W et
l'effet persiste une dizaine de minutes après le retour à la normale. Les seuils
de démarrage et de maintien solaire se jouent dans cette plage.
Boucle de rattrapage qui en résulte : réseau présent → le système lit « plus de soleil » → il conclut qu'il a besoin du réseau → il le garde. La conclusion confirme sa prémisse. Quatrième oscillateur, même famille que les trois du 24/08.
Le correctif
Aucun seuil nouveau : le système publiait déjà l'information et personne ne la lisait — l'horodatage de la dernière transition du réseau.
- Gabarit snapshot :
acin1_last_changed_ts,grid_switch_last_changed_ts(as_timestamp(...last_changed, 0), avec gardeis not nonepour une entité absente), plusgrid_settle_seconds(helper optionnelinput_number.evcs_grid_settle_seconds; absent → 180 s). ⚠️ Un commentaire Jinja{# #}est interdit à l'intérieur de l'expression{{ {...} | tojson }}:TemplateSyntaxError: unexpected char '#'. - INPUT_PARSE :
parsed.gridTransitionAgeSec= âge de la plus récente des deux transitions (AC-IN physique et contacteur). Horodatage absent ou nul →Infinity→ aucune fenêtre, comportement historique. On ne devine jamais une transition qu'on n'a pas observée.params.gridSettleSecondsborné [0,900]. - WINDOWS_SOURCEV2 :
derived.gridSettling = gridAvailable && age < seuil. - BATTERY_CLOUD_LOCKS : pendant la fenêtre,
rawSolarStartOkest faux (on ne démarre pas sur des mesures fausses), les deux termes de mesure derawSolarHoldOk(plancher de décharge, seuil PV) sont neutralisés (on ne laisse pas une charge en cours s'arrêter à cause d'elles),probeFailedNowest suspendu (le réseau masque la production, la sonde n'échoue pas) etsolarShouldStepUpest bloqué. Les step-down ne sont pas bridés : c'est la direction sûre. - HA_SERVICE_CALLS_OUTPUT : trace
ETABLISSEMENT reseau Xs/Ysdans la raison, sinon une lecture du logbook pendant le transitoire donne l'impression que le système décide n'importe quoi — alors qu'il décide de ne pas décider.
ASYMÉTRIE VOULUE (le point à ne pas casser)
La fenêtre ne s'ouvre qu'à l'arrivée du réseau. À sa disparition, les
onduleurs reprennent instantanément toute la charge : les gardes doivent agir
plus vite, jamais moins vite. gridSettling exige donc parsed.gridAvailable.
De même, la fenêtre suspend le confort, jamais le risque :
commonSafety*, planchers SOC, surcharge et arrêt dur batterie restent évalués
normalement — application directe de la règle « lissage pour le confort,
instantané pour la sécurité ».
Golden 42 → 48
Chaque cas est apparié à son contrefactuel hors fenêtre ; sans cet appariement le test ne prouverait rien (première rédaction : les trois scénarios donnaient le même résultat, le maintien passant par la tolérance nuageuse).
| scénario | attendu |
|---|---|
etablissement-maintien-charge (30 s, PV 600 W, batt −15 A) |
charging_step_down_battery 14 A — continue |
etablissement-termine-arret-normal (400 s, idem) |
stop_conditions_lost |
etablissement-securite-non-suspendue (30 s, batt −40 A) |
battery_current_hard_stop |
etablissement-ouverture-aucune-fenetre (réseau disparu 30 s) |
stop_conditions_lost |
etablissement-pas-de-demarrage (30 s, PV 4,5 kW) |
idle_wait_conditions |
etablissement-demarrage-hors-fenetre (400 s, idem) |
start_min_current |
Les 42 scénarios existants sont inchangés : leur fixture ne porte pas les
horodatages, donc Infinity → aucune fenêtre. Le repli est sûr par construction.
Preuve par mutation : gridSettling = false → échec des deux scénarios qui
testent le correctif (maintien-charge, pas-de-demarrage), les quatre autres
restant verts puisqu'ils n'en dépendent pas. Restauration → 48/48.
Suite prévue (ordre convenu avec le propriétaire)
- ✅ fenêtre d'établissement ;
- séquence d'ouverture — baisser la consigne de la borne avant d'ouvrir le contacteur, jamais après. Aujourd'hui l'hystérésis ouvre et le contrôleur EVCS l'apprend jusqu'à 30 s plus tard, pendant lesquelles les deux MultiPlus portent la voiture au courant majoré (jusqu'à 28 A en boost) plus la maison : c'est le mécanisme du W-105 déjà consigné ;
- priorité solaire pour la voiture à batterie pleine (coût marginal nul : 6-7 trackers sur 7 bridés = ressource refusée) et interdiction de fermer le contacteur pour charger une voiture en journée batterie pleine (~150 Wh).
Document mis à jour le : 31/08/2026 (~18:20) — fenêtre d'établissement déployée dans src/, golden 48/48, PUT n8n en attente de validation
30. Session du 31/08/2026 (suite) — Séquence d'ouverture du contacteur
Le défaut, en deux morceaux
Le plafond de courant EVCS majoré (P5, input_number.evcs_plafond_courant_edf_on,
jusqu'à 28 A en BOOST) n'est légitime que tant que le réseau alimente AC-IN :
hors réseau, les deux MultiPlus portent seuls la voiture plus la maison.
C'est le mécanisme du W-105 déjà consigné (§3, maintenance du 09/07).
- Personne ne baissait avant l'ouverture. C'est l'hystérésis (cycle 1 min)
qui ouvre le contacteur ; le contrôleur EVCS ne l'apprenait qu'après coup, via
parsed.acInAvailable. Les deux automates n'ont aucune poignée de main. - Et quand il l'apprenait, il ne baissait pas vite. La branche
charging_step_down_system_capdeDECISION_ENGINEV2exigeaitlifecycle.currentCooldownElapsed— un compteur de confort — et ne descendait que decurrentStepDownApar cycle. De 28 A à 20 A : plusieurs cooldowns. L'exposition ne se comptait donc pas en secondes mais en minutes.
Le correctif
Aucune poignée de main entre les deux automates, aucun helper nouveau. Le plafond majoré exige désormais que l'EVCS demande lui-même le réseau :
const evcsGridCeilingAllowed =
parsed.acInAvailable &&
(sourceMode === 'grid_forced' || sourceMode === 'grid_hc_allowed' || forceGridRequested);
L'ordonnancement devient mécanique : la demande de l'EVCS tombe au cycle où la
fenêtre se ferme (30 s), alors que l'hystérésis ne peut ouvrir qu'ensuite
(sa branche turn_off_recovered exige justement que l'override ait disparu, puis
min_on écoulé, sur un cycle d'une minute). L'EVCS baisse donc toujours avant,
sans que rien ne soit coordonné.
⚠️ Le calcul du plafond a été DÉPLACÉ après le calcul de sourceMode dans
WINDOWS_SOURCEV2 (juste avant const operatingMode = profile;). Vérifié avant
de bouger : physicalCapA, canRunAtMinCurrent et les quatre variables
intermédiaires n'étaient utilisés qu'à leur définition et dans l'export — rien
entre les deux ne les lisait. Ne pas les remonter.
Second volet, dans DECISION_ENGINEV2 : un plafond n'est pas une cible qu'on
approche, c'est une limite qu'on respecte. La réduction ne dépend plus du
cooldown et descend directement au plafond (decision: 'step_down_fast', valeur
déjà utilisée par la branche surcharge). Réduire est toujours sûr ; c'est la
montée qui doit être temporisée, et elle l'est.
Portée réelle, et ce qui reste exposé
Avec le helper à sa valeur par défaut (20 A), le comportement est strictement inchangé : le plafond valait déjà 20 A dans les deux branches. Le correctif ne mord que sur le BOOST 28 A, c'est-à-dire exactement le cas dangereux.
Reste irréductible : une ouverture manuelle (EDF URGENCE) ou une perte physique du réseau ne se laissent pas anticiper. L'exposition y passe de plusieurs minutes à ≤ 30 s plus le temps de rampe de la borne, ce qui est le minimum atteignable sans signal matériel.
Golden 48 → 51
| scénario | attendu |
|---|---|
ouverture-plafond-tombe-hors-demande-reseau (AC-IN présent, BOOST 28, matin solaire, cooldown NON écoulé) |
charging_step_down_system_cap → 20 A immédiatement |
ouverture-plafond-maintenu-en-demande-reseau (CONTREFACTUEL, force_grid_now) |
charging_hold_grid 28 A, BOOST préservé |
ouverture-reseau-disparu-reduction-immediate (AC-IN absent, cooldown non écoulé) |
réduction immédiate à 20 A |
Preuve par mutation, les deux volets séparément :
- plafond majoré reconditionné au seul
acInAvailable→ 1 échec ciblé ; - réduction remise sous
currentCooldownElapsed→ 2 échecs ciblés ; - restauration → 51/51.
Suite
- priorité solaire pour la voiture à batterie pleine (coût marginal nul : 6-7 trackers sur 7 bridés = ressource refusée) et interdiction de fermer le contacteur pour charger une voiture en journée batterie pleine (~150 Wh).
Document mis à jour le : 31/08/2026 (~19:00) — séquence d'ouverture corrigée, golden 51/51, PUT n8n en attente de validation
31. Session du 01/09/2026 (nuit) — La consigne réseau appartient désormais au contrôleur
L'incident : charge HC démarrée puis morte en soixante secondes
Fenêtre HC ouverte normalement, tout notre pilotage a fait son travail :
| 01:24:03-04 | latch hystérésis ON, EDF fermé, AC-IN présent |
| 01:24:00 | takeover nuit ON, source grid_hc_allowed (P7) |
| 01:25:15 | ACM en evcs_takeover_hold, DVCC à 2 A |
| 01:25:30 | start_grid_hc_min_current — la charge démarre |
| 01:25:42 | le réseau prend la voiture : import 1 450 W |
| 01:26:33 | import retombé à 763 W puis négatif, batterie de −7 à −25 A |
| 01:26:30 | garde « batterie trop sollicitée (−21,5 A) » → arrêt + verrou 17 min |
| 01:44 → 01:47 | battery_current_hard_stop puis idle_wait_conditions |
Cause : l'ESS n'utilise le réseau que si on le lui demande
ac_power_setpoint = 50 W (corrigé le 31/08 depuis 4 200 W),
ess_min_soc_limit = 20 %, SOC = 50 % ⇒
ess_batterylife_state = self_consumption_soc_above_min. Au-dessus du SOC
minimal, l'ESS fait servir la batterie et ignore le réseau. Le pic à 1 450 W
était le transitoire de reprise, pas le régime : en 50 s le régulateur a rendu
la charge à la batterie en convergeant vers les 50 W.
Preuve indépendante, à 01:49 sans voiture : contacteur fermé, maison 761 W, tuile Réseau « – », batterie −803 W. Le réseau était branché et inerte.
Le garde a eu raison : la pente était monotone, sa fin était « la batterie porte la voiture toute la nuit ». Le neutraliser aurait été pire.
Et la consigne à 4 200 W du propriétaire était PORTEUSE : c'est elle qui faisait fonctionner toute la mécanique de prise de relais nocturne (P4/P7). J'avais écrit en la retirant que le besoin couvert restait entier et devait être repris ailleurs ; je ne l'avais pas repris. Règle qui en découle, désormais en mémoire : ne jamais retirer un de ces contournements avant d'avoir mis son remplaçant adaptable en production.
Correctif (déployé 01/09 02:10, golden 56/56)
Le contrôleur écrit lui-même number.gx_device_ac_power_setpoint.
- Snapshot :
grid_setpoint_w,grid_setpoint_idle_w(helper optionnelinput_number.evcs_grid_setpoint_idle_w, absent → 50 W, le défaut Victron). - INPUT_PARSE :
parsed.gridSetpointW,params.gridSetpointIdleW[0, 500]. - HA_SERVICE_CALLS_OUTPUT :
gridSessionActive = acInAvailable && sourceMode ∈ {grid_forced, grid_hc_allowed}— donc P7 nuit, P9 départ etforce_grid_now, et rien d'autre. Besoin =houseNonEvcsW + physicalCapA × acVoltageRef: une grandeur mesurée, pas une constante. Hors session, retour à la valeur de repos. - BUILD_ACTIONS : appel
grid_power_setpoint(number.set_value). - Trace
Consigne reseau=XWdans la raison pendant une session.
La bande d'écriture est ASYMÉTRIQUE, et c'est le point. Une consigne trop
basse fait porter la différence par la batterie — c'est le défaut de cette nuit,
corrigé sans attendre. Une consigne trop haute ne coûte rien : l'ESS ne prélève
que ce qu'il peut placer. On écrit donc avec une marge d'un pas de courant
EVCS (currentStepUpA × acVoltageRef ≈ 230 W — grandeur du système, pas un
chiffre inventé) et on ne redescend qu'au-delà de deux marges. Première version
sans cette asymétrie : réécriture toutes les 30 s en poursuivant le bruit de la
maison. Après : deux écritures en 25 minutes.
⚠️ Deux leviers, deux rôles, à ne pas confondre : la consigne dit ce que le
réseau a le DROIT de servir ; DVCC (Auto Charge Manager) dit ce que la batterie
a le droit de prendre. C'est DVCC à 2 A qui empêche une consigne généreuse de
gaver la batterie pendant un evcs_takeover_hold, et c'est DVCC à 30 A qui
autorise la recharge pendant un evcs_takeover_recover. Le couple se compose
correctement, mais la générosité de la consigne DÉPEND de ce cap : ne pas
supprimer l'un sans revoir l'autre.
Golden 51 → 56
summarize() expose gridSetpointW (valeur écrite ce cycle, null si aucune
écriture). Scénarios : session HC → 5 525 W · contrefactuel hors session → 50 W ·
consigne déjà généreuse (5 600) → aucune écriture · consigne 45 W sous le
besoin → correction immédiate (le défaut de la nuit) · dérive externe à
4 000 W hors session → ramenée à 50 W.
Preuve par mutation : consigne figée au repos → 2 échecs ciblés ; branche
« trop basse » neutralisée → 1 échec ciblé ; restauration → 56/56.
Vérification en réel
02:10 → 02:13, contacteur fermé : grid 4 350 W, batterie ~0 A, EVCS 3 500 W, SOC stable à 49 %, consigne écrite à 5 276 puis 5 616 W. La voiture charge sur le réseau en HC sans toucher à la réserve — ce que P7 était censé faire depuis le 07/08 et qui ne tenait que par un réglage manuel.
Risque résiduel assumé
Si le contrôleur s'arrête en pleine session (SPOF Synology, cf. §3), la consigne reste haute et la substitution de journée peut revenir. C'est le même mode de défaillance que le reste de la chaîne, mais il est nouveau pour ce paramètre : à reprendre le jour où le SPOF sera traité.
Non fait volontairement
La fenêtre d'établissement (§29) ne couvre que le chemin solaire ; les gardes réseau ne sont pas gardés. Ce n'est pas la cause de cet incident et, la consigne étant désormais correcte, le transitoire observé au redémarrage de 01:58 était de 20 s avec la batterie à −1,6 A. À traiter séparément si un cas se présente.
Document mis à jour le : 01/09/2026 (~02:15) — consigne de puissance réseau pilotée par le contrôleur, golden 56/56, charge HC vérifiée en réel
32. Session du 01/09/2026 (nuit) — État réel de la résilience, faits vérifiés
Relevés avant toute proposition de secours (second Synology 10.10.10.22).
Ce qui est déjà hors du NAS principal
/mnt/projects est monté depuis 10.10.10.22 : le dépôt git (src/nodes,
golden, tools, docs) ne dépend donc PAS de 10.10.10.17. Gitea vit sur la même
machine. La moitié du travail de résilience est faite sans qu'on l'ait cherché.
/mnt/ARCH3/docker (HA + n8n) est sur 10.10.10.17 uniquement, non répliqué.
Aucune sauvegarde Home Assistant n'existe
Pas de dossier backups/ dans /mnt/ARCH3/docker/HA (vérifié : seuls SAVE/
et SAVE_CONFIG/, créés à la main lors de nos sessions). La sauvegarde intégrée
de HA n'a jamais produit d'archive ici.
⚠️ La clé de chiffrement n8n tient dans 56 octets et n'a aucune copie
N8N_ENCRYPTION_KEY est absent de compose.yaml : n8n l'a générée au
premier démarrage et elle vit uniquement dans /mnt/ARCH3/docker/n8n/data/config
(56 octets, encryptionKey, 06/02/2026). Sans ce fichier, une copie parfaite de
database.sqlite (1,69 Go) ne redonne aucun credential — OpenRouter compris.
C'est l'objet le plus fragile de toute l'installation.
Volumétrie, et c'est elle qui commande la stratégie
| taille | nature | |
|---|---|---|
.storage + packages + dashboards + yaml |
~6 Mo | identité : entités, helpers, cartes |
custom_components |
52 Mo | réinstallable (HACS), versions à figer |
home-assistant_v2.db |
4,39 Go | historique |
n8n/data/database.sqlite |
1,69 Go | workflows (déjà en git) + credentials |
Les deux bases sont en mode WAL (-wal/-shm présents) : un cp à chaud
sur NFS produit une sauvegarde corrompue en silence. Il faut
sqlite3 .backup / VACUUM INTO, ou conteneur arrêté.
⚠️ Le contacteur EDF est un Tuya CLOUD — SPOF distinct du NAS
switch.edf_general_ok_switch : platform: tuya,
unique_id: tuya.bf3d7928e069594e5fzfi9switch (intégration tuya, projet
cloud gg-1060..., pas localtuya). Ouvrir ou fermer le réseau passe donc
par internet. Or les coupures d'orage de ce site emportent souvent la
connexion : le contacteur peut devenir incommandable exactement quand il faut
agir, et le tableau « EDF Urgence » avec lui.
En revanche, le GX est une troisième machine, indépendante des deux NAS
Intégration victron_gx = Venus OS c0619ab34ac5 sur 10.10.10.9:1883, et
le broker MQTT est sur cette même adresse. Consigne de puissance réseau, DVCC,
courant EVCS, arrêt de la borne, SOC, PV, états MPPT : tout est lisible et
écrivable en MQTT local, sans HA et sans cloud. C'est là-dessus qu'un chien
de garde doit s'appuyer — pas sur HA, et surtout pas sur Tuya.
Backlog levé sans qu'on le remarque : Météo-France est installée
meteo_france — Pleumeur-Bodou, Bretagne (22).
sensor.meteo_france_alert_for_department_22_22_weather_alert = Vert, et la
réserve d'orage (§26) tourne : storm_level vert, réserve 4,95 kWh
(SOC 55 %), manque 0,9 kWh au SOC actuel de 49 %. L'action utilisateur en
attente depuis le 27/08 est donc caduque.
Risque nouveau introduit le 01/09
Le contrôleur écrit maintenant la consigne réseau (§31). S'il s'arrête en pleine session, elle reste haute et la substitution de journée revient. Ce risque n'existait pas avant cette nuit : il justifie à lui seul un chien de garde.
33. Session du 01/09/2026 (nuit) — Chien de garde EVCS sur le second Synology
Ce qu'il protège
Le contrôleur pilote depuis §31 la consigne de puissance réseau de l'ESS. S'il s'arrête en pleine session, elle reste haute et le réseau se substitue au solaire toute la journée suivante. Ce risque est né cette nuit ; le chien de garde existe d'abord pour lui. Il couvre au passage l'incident du 09/07 (NAS éteint la nuit, EDF laissé fermé jusqu'au matin).
Le signal de vie : surtout PAS un helper Home Assistant
Piège trouvé en le construisant, et vérifié : HA ne rafraîchit pas
l'horodatage d'un input_text réécrit à l'identique. À 02:52,
victron_evcs_surplus_state paraissait figé depuis 2 440 s alors que le
contrôleur tournait normalement (charging_hold_grid stable depuis 02:12, et la
raison tronquée à 255 caractères perdait justement les champs variables). Un
helper HA est un mauvais battement de cœur.
⚠️ COMPLÉTÉ LE 16/09/2026 — et c'est pire que « un mauvais battement de
cœur ». En outillant la session OpenClaw j'ai cherché le champ qui
distinguerait « pas écrit » de « écrit à l'identique ». Home Assistant expose
last_reported depuis 2024, précisément censé marquer chaque remontée d'état
même sans changement de valeur. Il ne sert à rien ici : mesuré sur cinq
entités, il est rigoureusement ÉGAL à last_changed. Or le contrôleur réécrit
source_mode et night_target_soc à chaque cycle de 30 s (execute: true,
inconditionnel) — Home Assistant ne matérialise tout simplement pas une écriture
identique, sur aucun des deux horodatages.
Conséquence générale : aucun champ de l'API ne permet de distinguer un
écrivain mort d'un écrivain qui republie la même valeur. L'ancienneté d'une
entité métier n'est donc JAMAIS une preuve de panne, et un système stable
produit des entités immobiles par construction. Relevé à 08:15 sur une
installation parfaitement saine : vehicle_connected inchangé depuis 752 min
(la voiture est branchée depuis la veille 19:43), night_target_soc depuis
194 min (hors session de nuit, la cible n'a plus de raison de bouger),
source_mode depuis 139 min (la voiture est pleine, le système reste en
« Bloqué »). Trois faux positifs sur cinq entités.
Règle qui en découle, adoptée avec la session OpenClaw : la vivacité se lit sur un battement de cœur DÉDIÉ dont l'état est un horodatage — la seule entité dont l'immobilité soit interprétable, parce que sa valeur change forcément à chaque écriture. Les entités métier se lisent pour leur VALEUR, jamais pour leur fraîcheur. Et en vocabulaire : « inchangé depuis X » est un fait, « figé » est un verdict ; ne pas transformer le premier en second.
Signal retenu : l'API d'exécutions de n8n
(GET /api/v1/executions?workflowId=...&limit=3). Elle prouve que la boucle
s'exécute, et son absence couvre indifféremment « n8n arrêté », « NAS éteint »
et « réseau coupé ». Zéro modification du contrôleur, zéro helper à créer.
Cas supplémentaire traité : trois exécutions consécutives en error = le
contrôleur tourne à vide (typiquement HA inaccessible) → considéré mort.
Ce qu'il fait, et surtout ce qu'il ne fait pas
- Il ne prend aucune décision d'énergie : ni seuil, ni démarrage. Il remet l'état sûr par défaut (consigne → 50 W, borne arrêtée) et se tait.
- Il ne touche pas au contacteur EDF. C'est un Tuya cloud (§32), donc un actionneur non fiable en secours ; et ce n'est pas nécessaire : contacteur fermé + consigne au repos = autoconsommation normale, la maison passe sur la batterie jusqu'au SOC minimal puis le réseau reprend seul. État sûr.
- Il n'utilise ni HA ni le cloud pour agir : MQTT direct sur le GX Venus
(10.10.10.9, portail
c0619ab34ac5), troisième machine indépendante des deux NAS. Topics vérifiés en lecture avant tout code :settings/0/Settings/CGwacs/AcPowerSetPoint,evcharger/40/StartStop,evcharger/40/Ac/Power,battery/512/Soc,system/0/Ac/Grid/L1/Power. Broker sans authentification, keepaliveR/<portail>/keepaliveobligatoire. - Une seule action par panne, puis verrou jusqu'au retour d'un signe de vie. Sans ce verrou, une coupure réseau entre les deux NAS ferait rejouer l'action à chaque cycle pendant que le contrôleur, vivant, la défait : un oscillateur de plus, dans un projet qui en a déjà corrigé quatre.
Réglages
Seuil de silence 600 s (20 cycles manqués) : assez long pour ignorer un hoquet, assez court pour qu'une panne nocturne ne coûte pas une journée de production. Période 60 s. Consigne de repos 50 W (défaut Victron).
Tests passés
- Contrôleur vivant → aucune écriture, une ligne de veille par heure.
- Panne simulée (
--simulate-dead --dry-run) → détection, deux écritures annoncées et supprimées, message d'alerte complet avec l'état du GX. - Verrou : 5 cycles de panne consécutifs → 1 alerte, 2 écritures, puis silence.
Déploiement (action utilisateur)
deploy/watchdog/compose.yaml, à lancer sur 10.10.10.22 uniquement :
cd /volume2/projects/EVCS/EVCS/deploy/watchdog
cp watchdog.env.exemple watchdog.env # y coller la clé API n8n
docker compose up -d && docker compose logs -f
Le dépôt est monté en lecture seule ; seul le journal est écrit, hors dépôt.
node_modules (14 Mo, mqtt 5.15.2) est sans dépendance native — le
répertoire installé depuis le poste fonctionne tel quel dans le conteneur
alpine, et il vit déjà physiquement sur 10.10.10.22 (/mnt/projects = export
10.10.10.22:/volume2/projects).
⚠️ Ne jamais le lancer sur 10.10.10.17 : un chien de garde hébergé par la machine qu'il surveille meurt avec elle.
Ce qu'il ne couvre pas encore
Le contacteur EDF reste incommandable si internet tombe (Tuya cloud). Un
contacteur local — ou un localtuya — est le prochain vrai gain de résilience.
La sauvegarde (6 Mo d'identité vers Gitea, 6,1 Go d'historique en hebdomadaire
avec sqlite3 .backup) et le secours froid n8n restent à faire.
34. Session du 01/09/2026 (nuit) — Sauvegarde Home Assistant : elle n'existait pas, elle existe
Le point de départ
Aucune sauvegarde HA n'avait jamais été faite (§32). Et non, git ne la
couvrait pas : le dépôt contient le code du contrôleur, pas Home Assistant.
Un seul fichier HA y était versionné (dashboards/bilan_energetique.yaml).
Les helpers, les entités, les paquets, les cartes, les intégrations n'avaient
aucune copie nulle part.
La trouvaille qui a simplifié le problème
backup/agents/info déclare quatre destinations, et le croisement avec les
entrées synology_dsm de .storage/core.config_entries donne leur hôte réel —
le nom seul ne dit pas où chacune écrit :
| agent | nom | hôte |
|---|---|---|
backup.local |
local | 10.10.10.17 |
synology_dsm.1660NZN396007 |
ARCHIVE2 | 10.10.10.22 |
synology_dsm.14B0MPN914514 |
ARCHIVE | 10.10.10.50 |
synology_dsm.20A0SBRVM9PF2 |
ARCHIVE3 | 10.10.10.17 ⚠️ à ne PAS utiliser |
Home Assistant sait donc pousser lui-même ses sauvegardes vers deux autres machines. Aucun script de recopie, aucune tâche planifiée, aucun montage NFS supplémentaire : il n'y avait qu'à le configurer. (Troisième machine 10.10.10.50 découverte au passage — elle n'apparaissait dans aucun document du projet.)
Configuration appliquée (tools/ha-backup-config.mjs, rejouable)
La configuration de sauvegarde n'est pas exposée par l'API REST : elle vit
dans .storage/backup et ne se règle que par l'interface ou le websocket.
Le script passe par le websocket, ce qui la rend reproductible et relisible.
- Destinations : local + 10.10.10.22 + 10.10.10.50. ARCHIVE3 (10.10.10.17) volontairement exclu : une sauvegarde qui ne survit pas à la panne qu'elle couvre ne protège rien.
- Base de données EXCLUE. Ce qu'il faut restaurer vite, c'est l'identité du
système — 20 Mo compressés. L'historique pèse 4,4 Go, change en permanence et
n'a aucune valeur d'urgence : il se sauvegarde à part, plus rarement, et avec
les précautions SQLite (mode WAL, jamais un
cpà chaud). - Quotidienne à 04:45, rétention 7 copies.
- Sans mot de passe, choix assumé : ces archives restent sur le réseau local du propriétaire, et une sauvegarde restaurable vaut mieux qu'une sauvegarde chiffrée dont la clé est perdue le jour où elle sert.
Vérifiée, pas seulement déclenchée
Première archive : Automatic backup 2026.8.1 (a9d35ac9), 01/09 03:11,
20,3 Mo, présente sur les trois machines. Contenu contrôlé archive en main :
.storage 39 fichiers · packages 18 · dashboards 30 ·
custom_components 2 283 · configuration.yaml, secrets.yaml,
automations.yaml, scripts.yaml · et zéro occurrence de
home-assistant_v2.db, comme voulu.
tools/ha-backup-list.mjs affiche pour chaque sauvegarde les hôtes où elle est
réellement présente — c'est cette colonne-là qui compte.
Ce que cette sauvegarde ne couvre PAS
- n8n. Ni ses workflows (déjà en git, donc couverts autrement), ni sa base
database.sqlite(1,69 Go), ni surtout la clé de chiffrement de 56 octets (§32) sans laquelle aucun credential n'est récupérable. C'est le prochain trou à boucher, et le plus petit. - L'historique HA (4,4 Go) : perdu en cas de restauration. Conséquence réelle pour le chantier du bilan énergétique, qui s'appuie sur les statistiques long terme. À traiter séparément, en hebdomadaire.
Validation de bout en bout, nuit du 01/09 (fin de session)
Chemin d'écriture du chien de garde DÉMONTRÉ, pas seulement simulé
(tools/watchdog-selftest.mjs, rejouable) : écriture réelle d'une sonde de
777 W sur W/<portail>/settings/0/Settings/CGwacs/AcPowerSetPoint, relecture
conforme, remise de la valeur d'origine par le script lui-même. Le --dry-run
supprimait justement ce maillon-là ; sans cette preuve, le chien de garde
n'était que probablement fonctionnel. Test fait EDF ouvert, donc consigne
inerte : risque nul.
Et la séquence de fin de session s'est déroulée dans le bon ordre, sans que personne l'ait orchestrée :
| 03:31:30 | fin de charge → night_recovery_done_idle, et la consigne retombe à 50 W dans le même cycle |
| 03:32:04 | 34 s plus tard, l'hystérésis ouvre le contacteur (turn_off_recovery_done) |
La demande baisse AVANT que le réseau parte — le principe de la séquence d'ouverture (§30), obtenu ici par construction et non par coordination. C'est la garantie que le risque introduit en §31 (consigne laissée haute) ne se matérialise pas en fonctionnement normal ; le chien de garde ne couvre plus que le cas de la panne.
Bilan de la nuit HC : charge démarrée à 01:58 après correction, menée à son terme à 03:31 sans jamais entamer la réserve (batterie tenue entre −2 et +1 A pendant toute la session), SOC stable à 49 %.
Clé API n8n : désormais aussi dans .env à la racine (convention du
propriétaire), en plus de ~/.config/opencode/opencode.json. Le repli du chien
de garde sur .env est vérifié — utile pour tout déploiement hors poste de
travail.
35. Session du 01/09/2026 — La table des bandes SOC déclarait un temps que personne ne lisait
Bilan de la nuit 31/08→01/09 (contexte de la découverte)
Deux sessions réseau : 01:24→03:32 (charge HC, 6,51 kWh importés, dont 5,14 kWh dans la VW, batterie tenue entre −2 et +2 A, SOC plat à 49 %) et 07:12→09:51 (batterie tombée à 20 %, reprise par l'hystérésis, 0,70 kWh). À l'ouverture du contacteur de 09:51, en plein jour : PV 2,7 kW avant, 2,7 kW après, import résiduel 40 W — aucune éviction du solaire, la correction de consigne du 31/08 tient. Journée : 27,85 kWh produits, 6,81 kWh dans la voiture en solaire, 0,39 kWh de réseau.
Le point faible de cette nuit n'est pas un bug, c'est la définition de la cible. À 03:31 le SOC (49 %) dépassait la cible du matin (35 %) → recovery done, EDF relâché. Trois heures quarante plus tard la batterie était à 20 % et l'hystérésis a dû rallumer. Pour traverser jusqu'au relais solaire (~09:00) il aurait fallu partir de ~64 %. La cible est un niveau à un instant ; ce qu'il faut est une énergie à couvrir jusqu'au croisement PV/conso. C'est le chantier du bilan énergétique, pas un seuil de plus.
Le défaut : un critère physique évalué sur du bruit
Deux arrêts stop_conditions_lost le 01/09, à 14:26:00 et 14:56:30,
véhicule en charge à 20 A sous 6,6 kW de PV.
La règle de maintien exige courant batterie ≥ plancher, plancher effectif
0 A (input_number.victron_evcs_battery_discharge_soft_a, curseur borné
à [−50, 0]). Or à SOC 94 la batterie est en float. Mesure brute au pas de 2 s
dans la minute précédant l'arrêt :
14:25:34 +1,8 14:25:44 0,0 14:25:54 −0,4
14:25:38 −1,0 14:25:46 −0,4 14:25:56 −1,6
14:25:40 −2,6 14:25:50 +1,0 14:25:58 −0,7
Ce n'est pas une décharge, c'est le bruit de régulation d'une batterie qui ne
fait rien. Le contrôleur en prélève un échantillon toutes les 30 s et le
compare à zéro : pile ou face. Les états hold_min_run qui alternent avec
charging_hold toutes les 1 à 2 minutes dans le journal SONT ces tirages
perdants — sauvés uniquement parce que le min_run (29 min) n'était pas
écoulé. Quand un échantillon négatif tombe après le min_run, la charge
s'arrête. Le second arrêt avait une cause réelle mais fugace : pointe maison à
8 016 W pendant quelques secondes, batterie à −42 A.
Coût mesuré : à l'arrêt la PV s'effondre de 6 633 à 2 554 W en 30 s — batterie pleine, le véhicule était le seul preneur — et la reprise repart à 6 A pour mettre 21 minutes à revenir à 20 A. ≈ 0,45 kWh de production refusée par arrêt. Fréquence : 1 le 26/08, 4 le 27, 6 le 28, 4 le 29, 4 le 30.
Ce que le système savait déjà, et que rien ne lisait
INPUT_PARSE construit depuis toujours une table de politique batterie par
bande de SOC, avec deux régimes explicitement temporels :
| bande | souple | tolérance souple | dur | tolérance dure |
|---|---|---|---|---|
| > 90 | −50 A | 12 min | −100 A | 20 s |
| 80-90 | −40 A | 10 min | −80 A | 20 s |
| 60-80 | −20 A | 6 min | −50 A | 15 s |
| 40-60 | −12 A | 4 min | −30 A | 10 s |
| 25-40 | −5 A | 2 min | −15 A | 5 s |
| < 25 | −3 A | 1 min | −8 A | 2 s |
batterySoftToleranceMinutes et batteryHardToleranceSeconds étaient calculés
(BATTERY_CLOUD_LOCKS l.185-186), exportés dans le debug, et branchés sur
aucune décision. Toute la dimension temporelle de la politique batterie était
morte. Troisième mécanisme dans ce cas — houseSpikeDetected n'allonge que la
tolérance nuage, jamais la garde batterie, et son entrée
house_ac_power_prev_w lit le même capteur que house_ac_power_w dans le
gabarit de snapshot, donc il vaut structurellement false (non corrigé ici,
la tolérance rend ce chemin redondant — à traiter séparément).
Correctif (déployé 01/09 20:08, golden 61/61)
Purement temporel : aucun seuil n'est déplacé, aucune constante ajoutée. On applique la table telle qu'elle est écrite — un dépassement souple doit DURER pour compter.
- Nouveau helper de persistance
input_text.victron_evcs_battery_soft_breach_at(paquetvictron_evcs_battery_dynamic.yaml, sauvegarde danspackages/SAVE/, créé parinput_text.reload— aucun redémarrage HA). Vide = pas de dépassement. Écrit par le contrôleur, uniquement sur changement réel : la date de début doit rester figée, sinon le compteur ne monte jamais. BATTERY_CLOUD_LOCKS:batterySoftBreachNow/...StartedMs/...Minutes, etbatterySoftDischargeHoldOk= pas de dépassement, ou dépassement plus court que la tolérance de sa bande.- Branché sur les trois conditions de MAINTIEN qui utilisaient le test
instantané :
rawSolarHoldOk,batteryAssistSolarHoldOk,probeHoldGuardOk. (gridHoldOketbatteryAssistNightHoldOkutilisaient déjà le seuil DUR.)
Ce qui n'est PAS touché, et pourquoi. Le plancher de démarrage reste
instantané : ne pas lancer une charge sur une batterie qui se décharge déjà,
c'est la frontière « délestage et non charge prioritaire ». Le garde dur
reste instantané : hardToleranceSec n'est volontairement pas branché, cela ne
pourrait que ralentir une sécurité. La tolérance est du confort, jamais du
risque.
Pourquoi ce filtre discrimine. Le bruit change de signe en quelques secondes, donc le compteur se remet à zéro de lui-même et n'atteint jamais la tolérance. Une vraie décharge reste négative à chaque échantillon et finit par la dépasser. C'est parce que le signal est du bruit que le filtre marche.
Golden 56 → 61
batt-float-bruit-ne-coupe-pas (relevé du 14:26 : SOC 94, −1 A → charging_hold
à 20 A) · batt-decharge-soutenue-coupe (CONTREFACTUEL, mêmes mesures mais
dépassement de 20 min > 12 → stop_conditions_lost) · batt-pointe-maison-ne-coupe-pas
(relevé du 14:56, −42 A → réduction graduée au plafond système 18 A, pas un
arrêt) · batt-decharge-dure-arret-immediat (−120 A < seuil dur −100 →
battery_current_hard_stop, aucune tolérance) · batt-plancher-demarrage-reste-instantane
(véhicule à l'arrêt, −1 A → idle_wait_conditions).
Les trois scénarios de la fenêtre d'établissement portent désormais le même dépassement en cours (10 min > tolérance 4 min de leur bande) : sans cela, la tolérance les rendait tous identiques et la paire ne prouvait plus rien.
Preuve par mutation, les deux sens séparément :
- tolérance supprimée (comportement d'avant) → 2 échecs ciblés, et
etablissement-maintien-chargereste vert (c'est bien la fenêtre qui le protège, pas la tolérance) ; - tolérance infinie → 3 échecs ciblés, et l'arrêt dur reste vert (la sécurité ne dépend pas de la tolérance) ;
- restauration → 61/61.
À surveiller
- Journal : plus aucun
stop_conditions_losten pleine charge solaire à batterie pleine ; la traceBatt souple X/12minn'apparaît que pendant un dépassement réel. input_text.victron_evcs_battery_soft_breach_at: vide la plupart du temps, écrit une fois au début d'un dépassement, effacé à la fin. S'il reste rempli en permanence, c'est un vrai déficit à instruire, pas un bug du filtre.- Debug :
batt_soft_breach_now,batt_soft_breach_minutes,batt_soft_discharge_hold_ok.
Document mis à jour le : 01/09/2026 (~20:15) — tolérance temporelle de la politique batterie branchée, golden 61/61
36. Session du 03/09/2026 — Un demarrage se decide sur l'excedent, pas sur la production
Le releve
Le 03/09, la voiture est branchee a 19:44. Le controleur lance la charge
dans la seconde. En 33 minutes (19:44 → 20:17, arret par stop_source_lost) :
| valeur integree | |
|---|---|
| pris par la voiture | 0,897 kWh |
| rendu par la batterie | 1,479 kWh |
| produit par le PV | 0,327 kWh |
| SOC | 94 % → 85 % |
Au meme moment, le bilan energetique publiait : croisement PV/conso a 9 h,
11,66 kWh a couvrir, SOC requis 99,7 %, grid_needed ON. Dans sa propre
arithmetique (plancher 22 %, 15 kWh utiles), la charge du soir a fait passer le
manque de la nuit de 0,86 kWh a 2,21 kWh — elle l'a plus que double, pour
mettre dans la voiture 0,9 kWh qu'elle allait de toute facon reprendre en heures
creuses a 01:24. La fenetre du soir ferme a 20:30 ; il n'y a rien entre les deux.
Precedent immediat, nuit du 02-03/09 : EDF relache a 02:30 a 45 %, batterie a 20 % vers 05:45, reseau maintenu jusqu'a 10:33 — dont 1,49 kWh importes entre 07:24 et 10:33, hors heures creuses. Marge perdue le soir, rachetee au plein tarif le matin.
La cause
pv_start_min_w (700 W) est une puissance PV absolue. A 19:44 : PV 758 W,
maison 1202 W, batterie deja a −12,2 A. Le controleur a lu « 758 ≥ 700, donc il
y a du soleil » et a lance 1,6 kW. L'excedent valait −444 W.
Verification dans la vraie chaine (rejeu du releve) : rawSolarStartOk ET
batteryAssistSolarStartOk etaient TOUS DEUX vrais. Un correctif sur le seul
seuil PV n'aurait donc rien change — l'assistance batterie aurait demarre la
charge de toute facon. Les deux chemins devaient etre traites ensemble.
Deux temporisations ont ensuite prolonge la faute, sans en etre la cause :
charging_hold_cloud_tolerance a pris le coucher du soleil pour un passage
nuageux (19:46 → 20:00), puis, la tolerance souple du 01/09 epuisee a 19:57,
min_run (29 min) a maintenu la charge 16 minutes de plus, conditions de
maintien deja declarees perdues. Non traite ici.
Le correctif (deploye 03/09 20:51, golden 64/64)
Une seule grandeur, dans BATTERY_CLOUD_LOCKS.js. derived.houseNonEvcsW
existait deja (calcule dans WINDOWS_SOURCEV2, deja lu par BCL) : aucun champ
de snapshot, aucun helper, aucune constante nouvelle.
const solarSurplusW = parsed.pvPowerW - derived.houseNonEvcsW;
Sans injection et reseau absent, cette grandeur vaut la puissance batterie au
signe pres. C'est ce qui separe les deux situations que la mesure PV seule
confond (PV_mesuree = min(ressource, conso + absorption batterie)) :
- CENSURE — batterie pleine, MPPT bride faute de puits : la production se cale sur la maison, l'excedent vaut ~0, la batterie ne bouge pas. Il y a de la ressource REFUSEE : la provoquer avec la voiture est legitime (raison d'etre de P3 et de l'assistance batterie).
- DEFICIT — soir, nuages : l'excedent est NEGATIF, la batterie porte deja la maison. Charger la voiture, c'est la vider.
Zero n'est donc pas un seuil de plus, c'est la frontiere physique entre les deux. Deux tests, parce que les deux chemins n'ont pas le meme metier :
- chemin « soleil reel » (
rawSolarStartOk) : excedent ≥pv_start_min_w. Meme helper, meme valeur, appliquee a la bonne grandeur ; - chemin « provocation » (assistance batterie, ou sourceMode
solar_potential) : excedent ≥ 0. On n'exige pas d'excedent mesurable — il est censure par construction — seulement que la batterie ne porte pas deja la maison.
⚠️ PORTEE : DEMARRAGE UNIQUEMENT. Les conditions de MAINTIEN sont inchangees : une charge etablie a deja son plancher de decharge et sa tolerance temporelle (§35), et couper une charge en cours sur une mesure d'excedent bruitee recreerait l'oscillateur qu'on venait d'eteindre.
Verification empirique de la frontiere sur le releve du 03/09 : de 17:00 a 19:41,
pv_censored ON et excedent toujours positif (+14 a +212 W) ; a partir de
19:42, excedent negatif. La frontiere a zero separe exactement les deux regimes
sur la mesure reelle.
Golden 61 → 64, et un scenario corrige
La paire de releves REELS, prise a 54 min d'intervalle, est le coeur du test :
| scenario | PV | maison | batterie | excedent | attendu |
|---|---|---|---|---|---|
excedent-censure-demarre (18:50) |
769 W | 755 W | 0 A | +14 W | start_min_current 6 A |
excedent-deficit-ne-demarre-pas (19:44) |
758 W | 1202 W | −12,2 A | −444 W | idle_wait_conditions |
11 W d'ecart sur la lecture PV. L'ancien code voyait la meme chose dans les
deux. Sans cette paire, rien ne prouverait que la nouvelle grandeur discrimine
ce que l'ancienne confondait.
Troisieme scenario, excedent-grosse-production-grosse-maison : PV 3000 W,
maison 2800 W, SOC 60 — pas de censure possible, la production mesuree EST la
ressource et elle est deja prise. Il exerce le chemin « soleil reel », que la
paire ne touche pas (elle passe par l'assistance).
apresmidi-provocation-p3 a ete corrige : il portait PV 300 W, maison 600 W,
batterie +2 A, reseau ouvert — un trou de 300 W que rien n'alimente, etat
physiquement inatteignable. Il passait quand meme, precisement parce que le
demarrage ne regardait que la production et jamais le bilan. Remis coherent en
gardant son intention (production sous pv_hold_min 500 ET sous pv_start_min
700, donc seul solar_potential peut demarrer) : PV 430 W, maison 400 W,
excedent +30 W — la vraie signature de la censure.
Preuve par mutation, dans les deux sens : retour au seuil absolu → 2 echecs
cibles ; garde neutralisee → les 2 memes. Dans les deux cas
excedent-censure-demarre reste VERT, ce qui prouve qu'il tient par le chemin
assistance et non par la garde. Restauration → 64/64.
Limite connue
Si sensor.victron_consumption_power_l1 passe unavailable, la maison est lue
a 0 et l'excedent redevient la production brute — c'est-a-dire le comportement
d'avant ce correctif. Degradation vers l'existant, jamais pire.
Ce que ce correctif ne fait PAS (suite convenue avec le proprietaire)
- Lire
input_boolean.evcs_balance_pv_censoredpour conditionner la provocationsolar_potential, au lieu de l'approximer par SOC ≥ 90. Le 03/09 la censure s'est arretee a 19:30 (batterie_pleine_sans_puits→production non brideea 20:00) : l'approximation disait « oui » au moment exact ou la chose approximee devenait fausse. - Lire
evcs_balance_required_soc/_energy_to_cover_kwhpour que le plancher du soir cesse d'etre un niveau fixe (evening_soc_start_min= 85).
⚠️ CONSTAT STRUCTUREL, a garder en tete pour la suite :
grep evcs_balance workflows/evcs-surplus-controller-v2.6.json → 0, idem
pour l'hysteresis. Le bilan energetique calcule et publie l'heure de croisement,
l'energie a couvrir, le SOC requis, grid_needed et pv_censored — et aucun
des deux workflows qui decident n'en lit une seule valeur. Meme contradiction
sur la cible du matin : night_target_soc = 35 % (regle sur le TOTAL de demain,
33,9 kWh ≥ 18 → « fort ») contre 99,7 % pour le bilan, qui regarde QUAND
l'energie arrive — demain matin est creux : 0,03 kWh a 7 h, 0,77 a 8 h, 1,81 a
9 h. Deux nombres, memes donnees, facteur 3.
Document mis a jour le : 03/09/2026 (~20:55) — demarrage sur l'excedent, golden 64/64, deploye
37. Session du 03/09/2026 (suite) — La provocation solaire demandee aux chargeurs eux-memes
Le trou que laissait §36
Le correctif sur l'excedent bloque bien l'incident de 19:44 (excedent −444 W). Mais un excedent proche de zero est AMBIGU : c'est la signature de la censure ET celle de l'equilibre de fin de journee. Le releve du 03/09 contient les deux, avec le meme excedent :
| PV | maison | excedent | batterie | |
|---|---|---|---|---|
| 18:50 — censure | 769 W | 755 W | +14 W | 0 A |
| 19:41 — equilibre | 817 W | 816 W | +1 W | −1,4 A |
Rejeu du 19:41 apres §36 : batteryAssistSolarStartOk encore vrai. Trois
minutes avant l'incident, le systeme aurait commis la meme faute. Aucun seuil
de puissance ne separe ces deux etats.
Pourquoi ce n'est PAS pv_censored qu'il fallait lire
Le plan annoncait « lire input_boolean.evcs_balance_pv_censored ». Lecture du
code (src/energy-balance/CENSORSHIP.js) : ce drapeau est un OU de motifs,
et l'un d'eux est exactement le proxy qu'on voulait remplacer —
socPct >= 90 && batteryPowerW < 500 (motif batterie_pleine_sans_puits).
Le remplacer par lui serait remplacer un proxy par un sur-ensemble qui le
contient.
Chronologie du 03/09, verifiee : le bilan tourne toutes les 5 min ;
pv_censored est reste ON jusqu'au calcul de 19:50, soit 5 minutes APRES
le demarrage de la charge. Ce qui a fini par le faire basculer n'est pas la
censure mais sun_elevation passant sous 10°. Le point 2 tel qu'annonce
n'aurait pas bloque l'incident.
Le signal qui marche : le mode de fonctionnement des chargeurs
sensor.*_mppt_operation_mode et *_pv_tracker_solar_N_operation_mode — sept
entites, jamais lues par aucun workflow (deja signalees §28). Chaque chargeur
declare lui-meme son regime :
mppt_active= je suis a mon point de puissance maximale, je donne tout ;voltage_current_limited/off= je me retiens, faute de preneur.
Le 03/09 : sept trackers voltage_current_limited jusqu'a 19:10, tous
mppt_active a partir de 19:20. Soit 24 minutes avant pv_censored,
sans aucun proxy — ni SOC, ni elevation solaire, ni seuil de puissance. Signal
DIRECT (le materiel), INSTANTANE (30 s, la cadence de la boucle).
Contre-verification indispensable, le 01/09 a 14:22-14:34 : les trackers sont
restes voltage_current_limited PENDANT une charge de 3,4 kW sous 6,9 kW de
PV. A midi la ressource depasse ce que la voiture peut prendre : le signal ne
s'auto-annule donc pas, et voltage_current_limited en cours de charge veut
dire « il reste de la ressource refusee ».
Le correctif (deploye 03/09 21:40, golden 66/66)
- Gabarit snapshot :
mppt_operation_modes, collecte parstates.sensor | selectattr('entity_id','search','mppt') | selectattr('entity_id','search','operation_mode')— aucun entity_id code en dur, un chargeur ajoute demain est pris en compte tout seul. Expression testee sur/api/templateAVANT integration, puis gabarit complet re-rendu : 191 champs, JSON valide, aucun null. - INPUT_PARSE :
mpptReadableCount/mpptAtMaxPowerCount/mpptHoldingBackCount. Les lecturesunavailable/unknown/vides sont ecartees — on ne devine jamais un etat. - BATTERY_CLOUD_LOCKS :
solarProvocationJustified = !(lectures exploitables && aucun tracker ne se retient), exige parbatteryAssistSolarStartOket par la branchesolar_potentialdesolarSurplusStartOk.
⚠️ DEMARRAGE UNIQUEMENT, et c'est structurel, pas une precaution. Gager le
sourceMode ou le MAINTIEN sur ce signal ferait que la charge, en donnant un
preneur au MPPT, detruirait sa propre condition d'autorisation — exactement
le bistable de l'abondance P6 corrige le 24/08, et le chemin stop_source_lost
de la §36. La garde n'est lue que quand la voiture est a l'arret, instant ou
l'etat des trackers decrit la situation SANS elle.
Repli : aucune lecture exploitable => mpptReadableCount === 0 =>
solarProvocationJustified vrai => comportement anterieur. Degradation vers
l'existant, jamais vers une supposition.
Golden 64 → 66
provocation-trackers-a-fond-ne-demarre-pas: le releve du 19:41 (excedent +1 W, SOC 94, sept trackersmppt_active) →idle_wait_conditions;provocation-trackers-illisibles-comportement-anterieur: meme releve, lecturesunavailable→start_min_current(le repli est teste, pas seulement affirme).excedent-censure-demarreetapresmidi-provocation-p3portent desormais l'etat reel des trackers (limites), et continuent de demarrer.
Mutation, trois directions : garde neutralisee → 1 echec cible (le 19:41 seulement) ; provocation toujours refusee → 3 echecs (les cas qui provoquent legitimement) ; lecture illisible traitee comme « a fond » → 1 echec (celui du repli). Restauration → 66/66.
Reste du plan
- Lire
evcs_balance_required_soc/_energy_to_cover_kwhpour que le plancher du soir cesse d'etre un niveau fixe (evening_soc_start_min= 85). Le constat structurel de la §36 reste entier :grep evcs_balancesur les deux workflows de decision renvoie toujours 0.
Document mis a jour le : 03/09/2026 (~21:45) — provocation solaire gagee sur le mode des chargeurs, golden 66/66, deploye
38. Session du 03/09/2026 (suite) — Le plancher du soir cesse d'etre un niveau fixe
Le constat, enfin traite
grep evcs_balance sur les deux workflows de decision renvoyait 0 depuis la
mise en service du bilan (26/08). Il calculait l'heure de croisement,
l'energie a couvrir et le SOC requis, et personne ne l'ecoutait.
Le 03/09 a 19:44, les deux nombres coexistaient dans le meme systeme :
| source | valeur | nature |
|---|---|---|
evening_soc_start_min |
85 % | un NIVEAU, fixe, qui ne sait rien de la nuit |
evcs_balance_required_soc |
99,7 % | l'ENERGIE a couvrir jusqu'au croisement de 9 h |
SOC 94 : le premier autorisait la charge, le second disait que la batterie ne passerait meme pas la nuit toute seule. La charge du soir a fait passer le manque de 0,86 a 2,21 kWh.
Le correctif (deploye 03/09 22:18, golden 70/70)
Dans BATTERY_CLOUD_LOCKS.js, le plancher de DEMARRAGE du profil
evening_high_soc devient :
eveningSocStartFloorPct = Math.max(params.eveningSocStartMin, params.balanceRequiredSocPct)
SENS UNIQUE, ET C'EST DELIBERE. Le bilan ne peut que RENFORCER le plancher,
jamais l'abaisser. L'assouplir — il dira souvent moins de 85 % par forte
prevision d'ete — autoriserait des charges qui n'ont pas lieu aujourd'hui :
c'est une decision distincte, avec ses propres risques, qui doit etre validee
pour elle-meme et non glissee dans ce correctif. Corollaire agreable : le max
rend la garde robuste sans borne ni ecretage — une valeur nulle, negative ou
aberrante ne peut pas relacher le plancher.
PORTEE : demarrage. Le plancher de maintien reste
min(profileSocHoldFloor, ...), donc inchange a 80 % : relever le plancher de
demarrage ne coupe aucune charge en cours. Troisieme fois de suite que la
distinction demarrage/maintien est ce qui rend un correctif sur.
L'abondance P6 continue de primer (PV moyen ≥ 4 kW ET batterie a ≥ +20 A) : c'est une PREUVE MESUREE que la ressource depasse ce que le systeme absorbe, donc le cas ou la voiture est effectivement gratuite.
Gardes et repli
- fraicheur ≤ 30 min (six cycles du bilan, qui tourne toutes les 5 min), avec rejet d'un horodatage dans le futur de plus de 5 min ;
evcs_balance_statusexactementok;- valeur numerique finie. Sinon : regle historique inchangee.
Aucun nouveau toggle : le kill-switch existe deja.
input_boolean.evcs_balance_enabled a OFF arrete les ecritures du bilan
(BUILD_HA_CALLS : if (!enabled) return []), balance_last_run se fige, la
garde de fraicheur expire et le plancher retombe seul sur 85 % en une
demi-heure. Un helper de plus aurait fait doublon.
Chaine technique
HA_SNAPSHOT_TEMPLATE : +balance_required_soc, +balance_energy_to_cover_kwh,
+balance_status, +balance_last_run (gabarit complet re-rendu : 195 champs,
JSON valide, aucun null). INPUT_PARSE : params.balance*, en reutilisant
parseAdvisorRunToMs — l'advisor et le bilan publient le meme format
DD/MM HH:mm. HA_SERVICE_CALLS_OUTPUT : trace
Plancher soir=X% (BILAN, Y kWh a couvrir), emise uniquement quand le bilan
durcit le plancher — sans elle, un blocage un soir de SOC 94 % est
incomprehensible a la lecture du logbook.
Golden 66 → 70
Les quatre scenarios ont un VRAI excedent (PV 2000 W, maison 1000 W) et des chargeurs qui se retiennent : les gardes de §36 et §37 sont passantes, le plancher est le SEUL discriminant.
| scenario | SOC | bilan | attendu |
|---|---|---|---|
bilan-plancher-soir-durci |
94 | 99,7 %, frais | pas de charge |
bilan-plancher-soir-ne-relache-pas |
70 | 60 %, frais | pas de charge (85 % reste maitre) |
bilan-perime-regle-historique |
94 | 99,7 %, vieux de 2 h | charge |
bilan-statut-degrade-regle-historique |
94 | 99,7 %, statut non ok | charge |
Mutation, quatre directions — bilan ignore / Math.max remplace par une
affectation directe / garde de fraicheur supprimee / garde de statut supprimee :
chacune fait tomber exactement UN scenario, et un different a chaque fois.
Chaque garde a donc son test dedie, aucun ne couvre pour un autre.
Restauration → 70/70.
Observation
A 20:15 le bilan demandait 99,7 % (11,66 kWh a couvrir) ; a 22:15, 81,9 % (8,98 kWh). Le plancher suit la nuit qui s'ecoule — ce qu'un niveau fixe ne fait pas, et c'est tout l'interet.
Reste ouvert
- La cible du matin (
night_target_soc) reste sur la regle des paliers 35/55/85, deduite du TOTAL de demain et aveugle a l'heure d'arrivee de l'energie. Le 03/09 : 35 % cote controleur contre 99,7 % cote bilan. C'est le meme defaut que celui corrige ici, sur l'autre bout de la nuit. - Le workflow d'hysteresis ne lit toujours aucun helper du bilan : ses seuils
grid_soc_on/grid_soc_off(20/30) restent des niveaux fixes (§11.8.A). houseSpikeDetectedreste structurellement faux (§35).
Document mis a jour le : 03/09/2026 (~22:25) — plancher du soir issu du bilan energetique, golden 70/70, deploye
39. Session du 03/09/2026 (suite) — La cible du matin devient une energie
Le defaut, symetrique de la §38
La regle historique classe le lendemain en trois paliers a partir de son TOTAL :
>= 18 kWh -> 35 %, >= 10 -> 55 %, sinon 85 %. Elle est donc aveugle a
l'heure d'arrivee de l'energie. Le 03/09, demain annoncait 33,9 kWh — donc
« fort », donc 35 % — mais avec un matin creux (0,03 kWh a 7 h, 0,77 a 8 h,
1,81 a 9 h) et un croisement a 9 h. C'est le meme defaut que le plancher du soir
de la §38, a l'autre bout de la nuit.
Le releve qui a motive le cablage (nuit 02-03/09)
| heure | requis (bilan) | SOC reel | cible fixe |
|---|---|---|---|
| 00:30 | 62,4 | 53 | 35 |
| 03:00 | 50,4 | 45 | 35 |
| 04:30 | 41,7 | 37 | 35 |
| 06:00 | 37,1 | 22 | 35 |
required_soc decroit proprement avec la nuit qui se consomme (6,1 -> 0 kWh a
couvrir) : c'est une cible instantanee, pas une constante deguisee. La
batterie est restee sous la ligne toute la nuit, et le bilan le savait des
00:30. A 03:00 EDF a ete relache cinq points sous la ligne ; la batterie est
tombee a 20 % vers 06:30 et le reseau est revenu de 07:24 a 10:33 —
1,49 kWh AU PLEIN TARIF.
Le correctif (deploye 03/09 22:50, golden 75/75)
Dans WINDOWS_SOURCEV2.js :
batteryMorningTargetPct = balanceApplique
? Math.min(90, Math.max(batteryMorningTargetBasePct, params.balanceRequiredSocPct))
: batteryMorningTargetBasePct;
batteryMorningTargetBasePct = advisor si son toggle est actif, sinon les
paliers. L'ordre est donc : paliers ou advisor d'abord, puis le bilan ne peut
que RELEVER.
SENS UNIQUE, comme en §38 : la direction corrigee est celle de l'echec observe — cible trop basse, batterie a plat avant le croisement, reseau au plein tarif le matin. L'abaisser ouvrirait un risque neuf que rien n'a valide. Principe unificateur des deux cablages : le bilan ne pousse jamais dans le sens « couvre moins bien la nuit ».
PLAFOND 90 % : au-dela la batterie cesse d'etre un puits et la production du matin se fait evincer (mesures du 31/08). Charger au reseau au-dela de 90 echangerait un kWh bon marche cette nuit contre plusieurs kWh censures demain. C'est deja la borne du chemin advisor : aucune constante nouvelle. Le bilan publie sans ecretage des valeurs > 100 (voulu, §26 : signal de dimensionnement), donc ce plafond n'est pas cosmetique — sans lui la cible serait inatteignable et EDF resterait allume toute la nuit.
PAS D'OSCILLATEUR : required_soc decroit de facon monotone pendant la nuit,
et la passe unique de recovery (§26) reste en place.
Gardes et repli identiques a la §38 (fraicheur 30 min, statut ok, valeur
finie ; kill-switch evcs_balance_enabled deja existant). Trace logbook :
Nuit EVCS: target=X% (BILAN).
Golden 70 → 75
| scenario | attendu |
|---|---|
cible-bilan-tient-le-reseau (releve 03:00, SOC 45, requis 50,4) |
reseau MAINTENU |
cible-bilan-perime-relache-comme-avant (bilan vieux de 2 h) |
relache — le comportement qui a coute 1,49 kWh |
cible-bilan-ne-baisse-pas-la-cible (SOC 30, requis 25) |
palier 35 maitre, reseau maintenu |
cible-bilan-plafonnee-a-90 (SOC 92, requis 118,9) |
plafonnee, relache |
Mutation, quatre directions (bilan ignore / Math.max en affectation directe /
plafond 90 supprime / garde de fraicheur supprimee) : chacune fait tomber
exactement UN scenario, different a chaque fois.
⚠️ LIMITE CONNUE, MESUREE ET FIGEE — la passe unique
Scenario cible-bilan-limite-passe-unique, verifie par sonde : recovery deja
latchee, SOC 30 repassant sous un requis de 37,1 % => le reseau reste
relache, et la raison affiche meme « batterie a la cible » sept points sous
la ligne. La passe unique de recovery (§26, anti-oscillateur) latche
nightRecoveryDone pour toute la session : la cible dynamique n'ameliore donc
que la PREMIERE passe.
Filet restant : l'hysteresis a SOC ≤ 20 %, mais elle peut tomber HORS heures creuses — c'est exactement le 1,49 kWh plein tarif du 03/09.
Ce comportement est epingle par un scenario pour qu'un changement futur soit delibere. §26 avait deja designe le bon critere de re-engagement : « un budget d'energie restant a couvrir — il appartient au bilan energetique, pas a une constante de plus ici ». Le bilan est desormais cable ; la suite naturelle est une session reseau unique et dimensionnee — ne pas relacher tant qu'on est sous la ligne ET dans la fenetre HC. C'est un chantier a part : il touche la machine a etats de la nuit (§18/§19/§26), qui a deja produit quatre oscillateurs, et merite ses propres scenarios.
Etat du cablage du bilan
| consommateur | statut |
|---|---|
| plancher de demarrage du soir | ✅ §38 |
| cible du matin | ✅ §39 |
| re-engagement du reseau la nuit | ❌ passe unique (ci-dessus) |
hysteresis grid_soc_on/grid_soc_off (20/30) |
❌ toujours des niveaux fixes (§11.8.A) |
Document mis a jour le : 03/09/2026 (~22:55) — cible du matin issue du bilan energetique, golden 75/75, deploye
40. Session du 03/09/2026 (suite) — Session reseau UNIQUE de la nuit
La mesure qui a dicte la conception (a lire avant tout le reste)
Avant d'ecrire une ligne, j'ai mesure les kWh reellement sortis de la batterie par point de SOC, sur cinq fenetres et trois nuits :
| fenetre | bande SOC | kWh/point | capacite implicite |
|---|---|---|---|
| 03/09 21:30-22:50 | 74 → 57 | 0,171 | 17,1 kWh |
| 31/08 03:00-05:00 | 49 → 42 | 0,170 | 17,0 kWh |
| 03/09 19:44-20:17 | 94 → 85 | 0,164 | 16,4 kWh |
| 02-03/09 03:00-06:00 (zero reseau) | 45 → 22 | 0,121 | 12,1 kWh |
| 01-02/09 03:00-06:00 | 33 → 20 | 0,082 | 8,2 kWh |
Au-dessus de ~42 % la jauge est lineaire et coherente (~17 kWh). En dessous
elle s'effondre : un point de SOC y vaut moitie moins. Or BALANCE.js
convertit avec une constante unique (helper a 15 kWh).
⚠️ CORRECTION apportee le 03/09 au soir (§41). La phrase qui figurait ici — «
required_socest donc systematiquement TROP BAS ... c'est le facteur 1,75 » — etait FAUSSE, sur les deux points.
- 15 kWh se situe ENTRE les deux pentes reelles (0,097 et 0,169), donc l'erreur CHANGE DE SIGNE : trop bas de 3 a 4 points pour un petit deficit (fin de nuit), trop haut de 4 a 6 points pour un gros (debut de nuit).
- Le facteur 1,75 comparait la vitesse de descente du SOC a celle de la ligne. Ces deux grandeurs ne mesurent pas la meme chose : la ligne descend parce que le temps passe. L'ecart venait donc surtout de la sous-estimation de la consommation par le profil p50 (2,1 kWh annonces pour 03:00-06:00, 2,79 kWh mesures), et non de la conversion.
La conception de la session (relachement sur la fenetre et non sur un seuil de SOC) reste juste : elle ne dependait pas de l'ampleur de l'erreur, seulement du fait que le relachement ne doit pas porter sur une grandeur approximative ni rouvrir l'oscillateur du 27/08.
Conclusion de conception, et elle est structurante : le relachement de la
session reseau ne peut PAS etre un seuil de SOC. Il porterait sur une grandeur
fausse, et tout seuil que le SOC repasse ensuite rouvre l'oscillateur eteint le
27/08. C'est ce raisonnement qui a ecarte les deux variantes evidentes
(relacher sur required_soc, ou sur required_soc_prudent avec la bande p50/p80).
Le correctif (deploye 03/09 23:13, golden 78/78)
ENGAGEMENT sur le bilan, RELACHEMENT a la FIN DE LA FENETRE.
nightGridSessionUncovered = balanceMorningTargetApplied && batteryBelowMorningTarget;
nightGridSessionCommitted = nightSessionActive && (uncovered || committedPrev);
nightGridTakeoverNeeded = nightSessionActive && (nightGridSessionCommitted || ...);
L'oscillation devient structurellement impossible : la condition de sortie est monotone — une fenetre ne se ferme qu'une fois. Aucun seuil de re-engagement n'existe, donc aucun cycle limite ne peut s'installer.
Nouveau helper input_boolean.victron_evcs_night_grid_session_committed
(paquet victron_evcs_night_helpers.yaml, sauvegarde dans packages/SAVE/,
cree par input_boolean.reload — aucun redemarrage HA, meme procede que le
01/09). Ecrit uniquement sur changement reel. C'est ce bit qui rend la session
unique : sans persistance, l'engagement serait reevalue a chaque cycle et le
systeme retomberait dans une bande, donc dans un oscillateur.
Ce que ca fait concretement
L'ACM recharge jusqu'a la cible puis tient la batterie a 2 A
(evcs_takeover_hold) : la maison finit la nuit sur le reseau en heures
creuses au lieu de vider la reserve, et la batterie arrive a l'aube pres de sa
cible — donc avec de la place pour le solaire du matin. A comparer au 03/09 :
batterie a 20 %, reseau encore ouvert a 10:33, et 1,49 kWh achetes au plein
tarif entre 07:24 et 10:33 (pendant lesquels le reseau evincait en plus la
production naissante).
Composition avec l'hysteresis — verifiee
La coupure immediate E2 (turn_off_recovery_done) exige !evcsGridOverride, et
evcsExplicitGridRequest inclut evcsNightGridTakeoverNeeded. Tant que la
session est engagee, le helper takeover est ON, donc E2 ne s'applique pas et la
boucle generique maintient le reseau. Aucune modification du workflow
d'hysteresis n'a ete necessaire.
Golden 75 → 78 (et un scenario promu)
cible-bilan-limite-passe-unique, epingle quelques heures plus tot comme LIMITE
CONNUE, est devenu session-unique-tient-sous-la-ligne : c'est exactement ce
que ce correctif change. Attendu mis a jour dans le meme commit, conformement a
la regle du depot.
| scenario | role |
|---|---|
session-unique-tient-sous-la-ligne |
SOC 30 sous le requis 37,1 → engagement |
session-unique-ne-relache-pas-sur-le-soc |
preuve anti-oscillateur : SOC 45 repasse AU-DESSUS, la session tient et rien n'est reecrit |
session-unique-fin-de-fenetre |
la seule sortie : 08:00, hors fenetre → relache + bit nettoye |
session-unique-bilan-indisponible |
bilan perime → aucun engagement, comportement historique |
Mutation, quatre directions. La plus instructive est la quatrieme : retirer la
garde bilan fait tomber les TROIS scenarios anti-oscillateur du 27/08
(night-oscillateur-nuit-2627, night-recovery-done-pas-de-reengagement,
night-recovery-done-bande-collante). Les tests de l'ancien correctif protegent
activement le nouveau code — c'est exactement ce qu'on attend d'une suite golden
apres cinq semaines d'accumulation.
⚠️ PREREQUIS IDENTIFIE, NON TRAITE — la conversion kWh ↔ SOC
Le tableau en tete de cette section est un resultat a part entiere, et il
depasse ce correctif : evcs_balance_usable_capacity_kwh = 15 est une
constante la ou la realite est une courbe (17 kWh au-dessus de 42 %, 8 a
12 kWh en dessous), et INPUT_PARSE code 17,5 en dur de son cote. Tant que
cette conversion est lineaire :
required_socsous-estime le SOC necessaire en bas d'echelle ;- donc l'engagement de la session peut manquer des nuits ou il aurait fallu s'engager (sous-declenchement — jamais d'oscillation, la conception y est immune) ;
- et la reserve d'orage (§26) est optimiste dans la meme bande.
C'est le prochain chantier du bilan energetique. §28 avait deja signale l'effondrement de la jauge en bas d'echelle et conclu « a verser au chantier du bilan energetique ; ne pas corriger par un seuil de plus » — les mesures ci-dessus lui donnent maintenant sa courbe.
Limite de perimetre
La session ne s'engage que pendant une session nuit (fenetre HC ET vehicule branche) : c'est la portee du contrôleur EVCS. Une nuit sans vehicule reste geree par l'hysteresis seule, avec ses seuils fixes 20/30 (§11.8.A) — chantier distinct.
Etat du cablage du bilan
| consommateur | statut |
|---|---|
| plancher de demarrage du soir | ✅ §38 |
| cible du matin | ✅ §39 |
| session reseau unique de la nuit | ✅ §40 |
| conversion kWh ↔ SOC (prerequis) | ❌ constante 15 kWh vs courbe mesuree |
hysteresis grid_soc_on/off (20/30) |
❌ niveaux fixes |
Document mis a jour le : 03/09/2026 (~23:20) — session reseau unique de la nuit, golden 78/78, deploye
41. Session du 03/09/2026 (suite) — La conversion energie ↔ SOC devient une courbe mesuree
La mesure
tools/mesure-courbe-batterie.mjs (nouveau, rejouable). Methode : pour chaque
palier ou le SOC reste a une valeur puis descend d'un point, integrer la
puissance batterie sur ce palier — en ecartant les paliers ou la batterie a
charge et ceux ou le reseau a apporte quoi que ce soit (> 20 Wh). Chaque palier
donne donc directement une mesure de « kWh par point de SOC », attribuee a ce
point.
576 paliers retenus sur 8 jours (105 ecartes) :
| tranche | paliers | kWh/point | capacite implicite |
|---|---|---|---|
| 21-31 | 88 | 0,097 | 9,7 kWh |
| 35-44 | 78 | 0,171 | 17,1 kWh |
| 45-54 | 76 | 0,169 | 16,9 kWh |
| 55-64 | 85 | 0,163 | 16,3 kWh |
| 65-74 | 80 | 0,171 | 17,1 kWh |
| 75-84 | 81 | 0,170 | 17,0 kWh |
| 85-94 | 62 | 0,169 | 16,9 kWh |
Plate a 0,169 de 33 a 93 — remarquablement stable sur six tranches independantes — genou net a 32-33, puis 0,097 en dessous. Sous le genou, un point de SOC vaut 57 % de ce qu'il vaut au-dessus. Coherent avec §0 (un module en panne sur huit, un autre qui faiblit) et avec l'effondrement de la jauge signale au §28 : ceci lui donne enfin sa courbe.
Ce que la constante faisait vraiment
15 kWh se situe ENTRE les deux pentes, donc l'erreur change de signe :
| energie a couvrir | SOC requis annonce | reel | ecart |
|---|---|---|---|
| 1 kWh | 28,7 | 32,3 | −3,6 |
| 4,3 kWh | 50,7 | 52,1 | −1,4 |
| 8 kWh | 75,3 | 74,0 | +1,3 |
| 11,7 kWh | 99,7 | 95,7 | +4,1 |
Trop bas en fin de nuit — la direction dangereuse — et trop haut en debut de nuit. Ce n'est pas le facteur 1,75 annonce au §40, qui est corrige en tete de cette section.
Energie utile totale de 100 a 22 % : 12,39 kWh mesures, contre 11,70 vus par
le bilan (15 kWh a plat) et 13,65 par INPUT_PARSE (17,5). Les deux
constantes existantes etaient fausses, dans des sens opposes. Chiffre a
retenir pour le dimensionnement du parc prevu cet hiver.
Le correctif (deploye 03/09 23:27)
src/energy-balance/BALANCE.js: trois fonctions —energyBetweenSoc(lo, hi),socForEnergy(kWh, floor),socAfterEnergy(soc, kWh)— remplacent les cinq conversions lineaires :requiredSocPct,gridEnergyNeededKwh,requiredSocAtStartPct,storageShortfallKwh+reachable, et le manque de la reserve d'orage.requiredSocAtStartPctreste NON ecrete au-dela de 100 (§26 : signal de dimensionnement) ; l'extrapolation y utilise la pente haute.src/nodes/INPUT_PARSE.js:batteryUsableCapacityKwh17,5 → 16,9 (valeur mesuree). Cette constante ne sert qu'a des ecarts SOC → cible, tous deux au-dessus du genou. Un calcul qui traverserait le genou doit lire la COURBE, pas cette constante — commentaire pose sur place.usable_capacity_kwhreste exige en entree (garde de completude du snapshot) et publie, mais ne pilote plus aucune conversion.
Tests : 42/42 apres mise a jour deliberee de 6 attendus
Les six valeurs ont ete verifiees a la main contre la courbe avant d'etre
figees (0,82 kWh → 30,5 ; 2,42 → 41,0 ; 12,77 → 102,2 ; manque 100→102,2 = 0,37 ;
orage 21→41 = 2,52 ; orage 95→102,2 = 1,22). Un libelle a ete corrige :
bilan-rejeu-24-08-matin affirmait « SOC requis 27,5 % au lieu du seuil fixe de
30 % » ; la courbe donne 30,5 %, donc le seuil fixe tombait juste ce matin-la —
par coincidence, pas par construction. Le dire ainsi est plus honnete que de
laisser croire a un ecart.
Mutation, deux directions : pente basse ramenee a la pente haute, puis genou ramene au plancher — 6 echecs a chaque fois, les memes six. Restauration → 42/42. Les trois autres suites (golden 78, hysteresis 7, advisor) sont inchangees.
⚠️ A RE-MESURER apres l'extension du parc
Les trois nombres (kneePct, kwhPerPointAbove, kwhPerPointBelow) sont des
resultats de mesure, pas des reglages : node tools/mesure-courbe-batterie.mjs 8
puis mise a jour. C'est aussi la raison pour laquelle ils vivent dans le code
avec l'outil qui les produit, plutot que dans trois helpers de plus.
Ce que ce correctif NE corrige pas
Le profil de consommation p50 sous-estimait de ~33 % les heures 03-06 la nuit du
02-03/09 (2,1 kWh annonces, 2,79 mesures). C'est desormais la principale
source d'erreur du bilan, devant la conversion. Les profils sont recalcules
quotidiennement par evcs-energy-profiles-daily-v1 sur l'historique — a
instruire separement : fenetre d'apprentissage, saisonnalite, et le fait que
l'historique HA ne remonte qu'a ~7 jours (§28).
Etat du cablage du bilan
| consommateur | statut |
|---|---|
| plancher de demarrage du soir | ✅ §38 |
| cible du matin | ✅ §39 |
| session reseau unique de la nuit | ✅ §40 |
| conversion kWh ↔ SOC | ✅ §41 (courbe mesuree) |
| profil de consommation p50 | ⚠️ sous-estime, principale erreur restante |
hysteresis grid_soc_on/off (20/30) |
❌ niveaux fixes |
Document mis a jour le : 03/09/2026 (~23:35) — conversion energie/SOC mesuree (576 paliers), bilan 42/42, golden 78/78, deploye
42. Session du 04/09/2026 — Pourquoi la memoire s'arretait a la semaine
Trois corrections a ce que j'affirmais
- L'historique brut ne s'arrete pas a 7 jours. Il remonte au 14/08 (20,8 jours au 04/09) et s'allonge : la base a ete recreee a cette date et se remplit vers les 90 jours configures. Le « 7 jours » de la §28 etait une mesure du 27/08, jamais reevaluee depuis.
- La base n'est pas corrompue. Un « database disk image is malformed » vu
en la lisant hors ligne venait de l'option
immutable=1, qui ignore le journal WAL. Relue enmode=roseul, elle est saine. - Le volume de 10.10.10.17 n'est pas de 133 Go. J'avais interroge
dfsur/mnt/ARCH3, le dossier PARENT du point de montage — donc la racine chiffree de mon propre poste. Le chemin correct est/mnt/ARCH3/docker: 7,0 To dont 5,0 libres, occupe a 29 %. Signale par le proprietaire, capturedf -ha l'appui. ⚠️ Le montage n'est PAS/mnt/ARCH3mais/mnt/ARCH3/docker(autofs + NFSv3 vers 10.10.10.17:/volume1/docker). Interroger le parent ment.
Etat mesure de la base Home Assistant (04/09)
| table | etendue | lignes |
|---|---|---|
states |
14/08 → 04/09 (20,8 j) | 23 661 689 |
statistics (long terme) |
01/03 → 03/09 (186 j) | 596 412 |
statistics_short_term |
20,8 j | 1 471 840 |
1,14 M lignes par jour en brut. A 90 jours : ~102 M lignes, ~22 Go. La place ne manque pas (5 To libres) mais un SQLite de cette taille ralentit Home Assistant bien avant. Les statistiques long terme, elles, tiennent 186 jours en 596 k lignes — quarante fois plus compactes par jour, et jamais purgees.
La cause reelle du « 7 jours de memoire » : state_class manquant
Sur 288 entites disposant de statistiques long terme, les quatre series dont
le bilan depend n'y etaient pas : victron_consumption_power_l1,
victron_pv_power_system, victron_grid_power_l1, victron_battery_current.
victron_battery_soc et victron_evcs_ac_power en avaient six mois.
La difference tient a un attribut : Home Assistant ne construit de statistiques
long terme que pour les capteurs declarant state_class. Le SoC le declarait,
les quatre autres non. Neuf capteurs sur vingt a unite numerique le
declaraient dans toute la configuration.
Correctif (applique 04/09 00:57)
configuration.yaml, sauvegarde SAVE_CONFIG/configuration.yaml.bak.20260904-005706 :
+10 lignes state_class: measurement, rien d'autre (diff verifie).
Capteurs dotes : Battery Voltage / Current / Power / Temperature · PV Power (system) · VE.Bus DC Power (system) · Grid Power L1 · Consumption Power L1 · Max Charge Current Setting · Max Charge Voltage Setting.
Exclu volontairement : Victron VE.Bus Nominal Inverter Power — une
constante a 9000 W, dont une statistique n'apprendrait rien.
Procedure : homeassistant.check_config (vide, donc valide) → mqtt.reload →
mqtt.publish sur R/c0619ab34ac5/keepalive pour forcer la republication
Venus (piege documente §3 : sans lui les capteurs restent unknown). Resultat :
aucun trou, les quatre series sont revenues numeriques dans la seconde.
Verification : 288 → 299 entites avec statistiques long terme, les quatre series desormais listees.
⚠️ Choix d'horaire : fait a 00:57, soit 27 min avant l'ouverture de la
fenetre HC de 01:24 — premiere nuit du test de la session reseau unique. Un
rechargement MQTT aveugle le controleur le temps de la republication ; le faire
a 01:20 aurait compromis le test. A refaire de meme : jamais de mqtt.reload
dans les minutes qui precedent 01:24.
Ce que ca ne fait pas
Pas de retroactif, rien sous l'heure (5 min pour le court terme), et aucune valeur CALCULEE (SOC requis, decisions, prevision publiee). Le comparatif des deux propositions d'historisation — base SQL sur 10.10.10.22 alimentee par n8n, ou InfluxDB attache a Home Assistant — reste a trancher.
Prevision solaire : le total est bon, la FORME ne l'est pas
Verification prevision publiee le matin contre production reelle, trois jours :
| 03/09 | 02/09 | 01/09 | |
|---|---|---|---|
| total | 110 % du prevu | 97 % | 89 % |
| 11 h | +1,65 kWh | +3,38 | −0,32 |
| 13-14 h | +3,44 | +1,12 | +1,78 |
| 17 h | −1,47 | −1,84 | −1,68 |
| 18 h | −0,98 | −0,97 | −0,91 |
Systematique sur les trois jours : la production arrive plus tot que prevu, et l'apres-midi est sur-promis d'un facteur 2 a 3 apres 16 h. Le bilan ne consomme pas le total mais l'heure de croisement et l'energie a couvrir : un apres-midi sur-promis retarde le debut du deficit et sous-estime ce qu'il faut couvrir, dans le meme sens que le p50 trop bas.
Piste : les trois plans declares (sud-est / sud / ouest) fondent le suiveur dans le lot, et l'ombre du support des 30 panneaux sud sur la moitie du suiveur en fin d'apres-midi n'est modelisee nulle part. A instruire avec de l'historique, pas sur trois jours — ce qui renvoie a la base de mesures.
Le conseiller IA en mode ombre : a arreter
918 accords contre 74 divergences (92,5 %). Un conseiller d'accord 92,5 % du temps avec les regles ne fait que les reproduire. Pire, apres les cablages du 03/09 il recommande 35 % de cible du matin contre 63,7 % pour le systeme, en argumentant « prevision solaire forte demain (30 kWh) justifie une cible basse » — exactement le raisonnement des paliers qu'on venait de demontrer faux, avec 95 % de confiance.
Cause de conception : on lui a pose les questions auxquelles les regles repondaient deja, avec les memes entrees. Il ne pouvait que converger sur les memes reponses. Il n'a jamais recu les heures de repas, le decalage du diner de 20 h a 23 h, la strategie hivernale ni le blocage de charge a 94 %.
Recommandation : input_boolean.evcs_advisor_enabled a OFF. Le proprietaire
evoque openclaw (deja en service sur 10.10.10.17, conteneur
openclaw-gateway, port 18789) comme piste d'agent — mais un agent qui decide
le plan de la journee a besoin de la memoire longue, sans quoi il redecouvrira
les regles comme celui-ci.
Document mis a jour le : 04/09/2026 (~01:05) — state_class pose sur 10 capteurs, 299 entites en statistiques long terme
43. Nuit du 03-04/09 — La session unique tenait le contacteur, pas l'energie
Ce qui a marche
Veille de nuit, 69 releves de 01:26 a 07:07 : UNE seule bascule, celle de
01:24. La session reseau unique (§40) a fait exactement ce pour quoi elle a ete
concue — EDF ferme d'un bout a l'autre de la fenetre HC, aucun creneau, aucune
oscillation. Engagement au premier cycle : night_grid_session_committed a
01:24:00, contacteur a 01:24:03.
Ce qui n'a pas marche
| phase | reseau | batterie |
|---|---|---|
| 01:24 → 02:45 (voiture en charge) | +5,66 kWh | se recharge |
| 02:45 → 06:15 (voiture finie) | +0,07 kWh | −2,60 kWh |
A 02:50, la voiture finie : source_mode passe a Bloqué, et la consigne
reseau retombe de 5078 W a 50 W. Contacteur ferme, reseau disponible, heures
creuses — et l'ESS ne demandait que 50 W pendant que la maison tirait 770 W
sur la batterie. Trois heures et demie. SOC 44 % → 17 %.
L'Auto Charge Manager est reste en evcs_takeover_recover, DVCC a 30 A, tout du
long : pret a recharger, mais la consigne ne demandait jamais l'energie.
C'est le defaut du 01/09 (§31) revenu ailleurs. J'y avais etabli que fermer
le contacteur ne suffit pas, que la consigne doit demander la puissance — et
conditionne la consigne au sourceMode. La veille (§40) j'ai cree une NOUVELLE
raison de garder le contacteur ferme, l'engagement de session, sans la brancher
sur la consigne.
« Injection » a batterie vide : il n'y en a pas eu
Question du proprietaire devant des valeurs negatives au point de raccordement. Mesure sur 02:40-06:10, 12 074 echantillons :
| echantillons negatifs | 2 083 (17,3 %) |
| mediane des negatifs | −18 W |
| minimum | −356 W |
| plus long episode continu | 35 s |
| energie totale | 22,7 Wh |
Ce n'est pas de l'injection mais le regulateur qui chasse autour d'une cible trop proche de zero. L'energie ne vient pas d'un excedent : la batterie debitait 744 W au meme moment. Les 22,7 Wh representent 0,9 % des 2,60 kWh qu'elle perdait. Un compteur de facturation, integre a l'heure, ne verrait rien.
« Pourquoi 20 % » : c'est le plancher de l'ESS, pas le notre
number.gx_device_ess_min_soc_limit = 20 %, active_soc_limit = 20 %.
Sequence lisible dans ess_batterylife_state :
06:20 recharge_no_battery_life l'ESS touche son plancher, tire 2483 W
06:40 batterie a +30,8 A
07:11 self_consumption_soc_below_min il tient la batterie au plancher et
sert la maison sur le reseau (802 W)
Notre grid_soc_on vaut aussi 20, mais l'hysteresis n'y est pour rien : EDF
etait deja ferme. L'ESS a fini par faire de lui-meme, a 06:20, ce que la
session aurait du faire depuis 02:50 — apres avoir vide la batterie de 27
points.
Correctifs (deployes 04/09 07:20, golden 80/80)
HA_SERVICE_CALLS_OUTPUT—gridSessionActiveinclut desormaisnight.nightGridSessionCommitted. Regle : tant que le contacteur est ferme A NOTRE DEMANDE, la consigne doit couvrir la maison, peu importe laquelle des deux raisons le tient.INPUT_PARSE— consigne de repos 50 → 200 W (demande du proprietaire, justification physique ci-dessus : a 50 W le point de fonctionnement passe sous zero 17 % du temps). ⚠️ Reduit le nombre d'excursions negatives, ne les annule pas : une pointe a −356 W passerait encore sous zero.
Golden 78 → 80
consigne-session-engagee-couvre-la-maison (le releve de la nuit : session
engagee, voiture finie, maison 770 W → consigne 5595 W et non 200) et son
contrefactuel consigne-sans-engagement-retombe-au-repos (SOC 70 au-dessus de
la ligne, donc aucune session, donc repos). Deux attendus existants passent de
50 a 200.
Mutation : engagement retire de la condition → 1 echec cible ; repos ramene a 50 → 3 echecs. Restauration → 80/80.
⚠️ Piege rencontre en ecrivant le contrefactuel : sa premiere redaction
portait DEUX fois la clef balance_status dans le meme objet. En JavaScript la
derniere gagne, donc il testait l'inverse de son libelle — et il l'a signale en
renvoyant 5595 au lieu de 200. Ne jamais construire un contrefactuel en
surchargeant une clef deja presente : le faire porter par une difference
PHYSIQUE (ici le SOC au-dessus de la ligne).
A surveiller la nuit prochaine
- Voiture finie → la consigne doit RESTER haute, pas retomber a 200 ;
- SOC quasi plat entre la fin de charge et 07:24, au lieu de −27 points ;
ess_batterylife_statene doit jamais atteindrerecharge_no_battery_life.
Document mis a jour le : 04/09/2026 (~07:25) — session unique validee (1 bascule), consigne branchee sur l'engagement, repos a 200 W, golden 80/80
44. Mise en service d'InfluxDB (04/09/2026 07:28) — la memoire est complete
Le redemarrage, chronometre
07:28:25 redemarrage demande
07:28:48 API de retour (22 s)
07:28:53 keepalive Venus publie
07:28:56 les 5 capteurs cles revenus numeriques (31 s au total)
Un seul cycle du controleur manque (execution 1174255 en erreur a 07:28:30), reprise normale a 07:29:00. Aucun trou dans l'historisation PostgreSQL : l'ecart maximal entre deux mesures de SOC sur les 40 minutes encadrant le redemarrage est de 80 s, et il se situe AVANT — c'est la cadence normale du service, pas la coupure.
Creneau choisi : apres 07:24 (fenetre HC fermee), aucune charge en cours, PV
nul. Le keepalive Venus reste indispensable — sans lui les capteurs MQTT
restent unknown (§3).
InfluxDB recoit
249 entites distinctes ecrivent dans les 10 minutes qui suivent, a la resolution native :
| entite | points / 10 min |
|---|---|
victron_ve_bus_ac_out_l1_power |
84 |
victron_ve_bus_dc_current |
60 |
victron_consumption_power_l1 |
60 |
victron_grid_power_l1 |
60 |
mppt_450_200_..._pv_tracker_solar_2_power |
20 |
C'est precisement ce que la proposition B devait apporter et que PostgreSQL ne peut pas : la resolution native, et les entites auxquelles personne n'a pense.
Premiere interrogation reelle de la memoire
Le role evcs_lecture fonctionne. La question qui n'avait pas de reponse avant
avant-hier :
h soc requis marge a_couvrir
07:28 20 31.1 -11.1 0.88
07:15 20 31.0 -11.0 0.87
Onze points sous la ligne, lisibles en une requete.
⚠️ Faille de conception reperee
La table evenement a ete creee pour consigner deploiements et incidents —
« une courbe sans ses evenements ne s'interprete pas ». Mais le role d'analyse
est en LECTURE SEULE, donc je ne peux pas y ecrire. Ce redemarrage aurait du
y figurer et n'y figure pas.
Deux issues possibles, a trancher : donner au service d'historisation une
commande d'ecriture d'evenement (node tools/historise-evcs.mjs --evenement "..."), ou accorder au role de lecture un INSERT restreint a cette seule table.
La premiere est plus propre — le role d'analyse reste strictement lecteur.
Etat de la memoire
| brique | etat |
|---|---|
| statistiques long terme HA (4 series manquantes) | ✅ depuis 00:55 |
| PostgreSQL — valeurs calculees | ✅ depuis 01:38 |
| InfluxDB — flux complet | ✅ depuis 07:28 |
| ecriture des evenements | ❌ role en lecture seule |
Document mis a jour le : 04/09/2026 (~07:35) — InfluxDB en service, 249 entites, memoire complete
45. Session du 04/09/2026 — Grafana : les deux bases se consultent
Ce qu'il y a a consulter, et ce qu'il n'y a pas
Trois bases coexistent, et elles ne jouent pas le meme role :
| base | ou | contenu |
|---|---|---|
PostgreSQL evcs |
10.10.10.22:5433 | ce que le systeme CALCULE |
InfluxDB evcs |
10.10.10.22:8086 | ce que HA OBSERVE, ~1 pt/s, 113 grandeurs |
| SQLite de HA | 10.10.10.17 | son historique brut (20 j) + statistiques (186 j) |
⚠️ Le PostgreSQL du port 5432 appartient a DSM. Ce n'est pas une base du projet — la confusion « j'ai deux bases postgres » vient de la.
La SQLite de HA n'est PAS branchee a Grafana, et c'est deliberé : il faudrait lire un fichier de 4,4 Go en mode WAL a travers un partage reseau pendant que HA ecrit dedans (lecture incoherente garantie), pour un gain nul — tout ce qui compte pour l'avenir arrive desormais dans InfluxDB a bien meilleure resolution. L'historique de HA se consulte dans HA.
Deploiement
Service grafana ajoute a deploy/historisation/compose.yaml, image
grafana-oss:11.3.0, publie sur 3001.
⚠️ 3000 est deja occupe sur 10.10.10.22 — verifie par sonde AVANT d'ecrire
la ligne, exactement la lecon du 5432 deja pris par le PostgreSQL de DSM.
Telemetrie, verification de version et flux d'actualites desactives : cette
installation doit fonctionner pendant une coupure Internet d'orage (§32).
bash preparer-grafana.sh genere le mot de passe admin (genere, pas choisi),
l'affiche une fois, le laisse relisible dans le .env 600.
Aucun secret n'est duplique : le compose donne DEUX env_file a Grafana —
.env de la pile (mot de passe admin) et ../../.env du depot
(PG_LECTURE_PASSWORD, INFLUXDB_TOKEN). Le provisionnement les reference par
$VARIABLE, donc les fichiers YAML de sources de donnees sont commitables tels
quels. C'etait le piege du POSTGRES_PASSWORD/PGPASSWORD tenus egaux a la main.
Connexion PostgreSQL par le role evcs_lecture : un tableau de bord ne doit
pas pouvoir ecrire dans la memoire.
Provisionnement = git, comme les nodes n8n (regle R2)
grafana/provisioning/ (sources + fournisseur) et grafana/dashboards/
(les trois JSON) sont montes en LECTURE SEULE, allowUiUpdates: false. Une
modification faite dans l'interface serait ecrasee au redemarrage, en silence —
meme raison que « JAMAIS editer l'UI n8n ». Le generateur rejouable est
tools/gen-grafana-dashboards.py ; Grafana relit le dossier toutes les 30 s.
« Save as… » reste libre pour explorer (copie personnelle dans General).
tools/verifier-dashboards.mjs — l'equivalent des golden pour les tableaux
Rejoue chaque requete : SQL contre PostgreSQL, Flux contre InfluxDB, avec
les macros de Grafana ($__timeFilter, $__timeFrom/To, v.timeRangeStart,
v.windowPeriod) substituees comme il le fait. Les annotations sont testees
aussi : elles s'executent sur tous les panneaux, donc une annotation cassee
casse le tableau entier. Sortie non nulle au premier echec.
Sans lui, une requete cassee ne se voit qu'a l'ecran, panneau par panneau.
Etat au deploiement : 19/19.
Structure InfluxDB (relevee, a ne pas redecouvrir)
L'integration influxdb de HA n'ecrit PAS une mesure par entite :
_measurement= l'unite (W,A,V,%,kWh,°C,min,s,VA,Hz,°) pour tout capteur numerique ; tagsentity_id(sans le prefixe de domaine),domain,installation; champ numeriquevalue._measurement= l'entity_id complet (sensor.…,switch.…) pour les entites textuelles ; champstate= la chaine,value= 0/1 pour un switch.
Donc : r._measurement == "W" and r.entity_id == "victron_pv_power_system" and r._field == "value",
et pour les modes des chargeurs r._measurement =~ /operation_mode$/ and r._field == "state".
Les sept *_operation_mode (signal §37) et switch.edf_general_ok_switch sont
bien presents.
Les trois tableaux
- La nuit (
evcs-nuit) — SOC reel contre SOC exige par le bilan, marge (rouge sous zero : la batterie ne passera pas), decisions en frise, puissances, energie restant a couvrir, journal des evenements. - Le solaire (
evcs-solaire) — production / maison / voiture et surtout l'excedent (§36), la censure avec son motif, le mode declare par chacun des sept chargeurs (§37), et un panneau au pas natif InfluxDB pour montrer ce que le pas de 60 s de PostgreSQL ne voit pas. - Prevision contre reel (
evcs-prevision) — l'ecart heure par heure entre la prevision du matin meme et la production, avec la bande de censure juste dessous : une production sous la prevision peut vouloir dire mauvaise prevision OU absence de preneur, et seules les heures non bridees permettent de juger. C'est le biais de forme identifie au §42, qui demande des semaines.
Les evenements apparaissent en marqueurs verticaux sur tous les graphiques.
Document mis a jour le : 04/09/2026 (~08:30) — Grafana provisionne par git, 19/19 requetes verifiees
Correction du meme jour — les requetes marchaient, les grandeurs non
Trois defauts trouves en LISANT les tableaux, qu'aucune des 19 requetes ne signalait puisqu'elles s'executaient toutes sans erreur :
tsn'est pas un instant de cycle.historise-evcs.mjshorodate chaque mesure avec son proprelast_updated— le vrai instant de la mesure, et c'est le bon choix. Mais deux grandeurs ne partagent alors presque jamais le memets: 2656 lignes pour 2656 instants distincts. UnGROUP BY tsrend donc une ligne par grandeur avec une seule colonne remplie, et toute colonne CALCULEE est nulle. La courbe « Excedent » — la grandeur meme du §36 — etait vide sur les 2650 lignes, sans que rien ne le signale.- Le service n'ecrit que les changements (
ON CONFLICT DO NOTHINGsur(cle, ts)). Un simple regroupement a la minute laisse donc des trous : la difference n'existait que les 73 minutes sur 434 ou les deux grandeurs avaient bouge ensemble. Correction : report de la derniere valeur connue (first_valuesur les paliers numerotes parcount(x) OVER (ORDER BY t)), qui est aussi ce que fait le controleur en temps reel. 430/435 apres. - L'energie horaire par
sum(valeur)/60suppose 60 echantillons par heure ; il y en a eu 26 a 47 le 04/09. Remplace par une integration ponderee par la duree (valeur × intervalle jusqu'au point suivant, plafonne a 300 s pour qu'un arret du service n'extrapole pas une valeur perimee). Ecart mesure : ~9 %. ⚠️ Le chiffre de « 25 a 58 % » que j'avais d'abord annonce etait faux : il comparait la somme a la MOYENNE, qui ignore la duree et n'est donc pas la reference.
Plus deux corrections d'affichage : la colonne jour (type date) s'affichait
2026-09-04 02:00:00, rendue comme un instant UTC converti en heure de Paris
(to_char desormais) ; et le decompte des chargeurs MPPT est de six
entites publiees, pas sept comme l'affirmait le §28.
Lecon, dans la lignee des trois oscillateurs : verifier-dashboards.mjs
prouve qu'une requete s'EXECUTE, pas qu'elle rend la grandeur voulue. Une
colonne integralement nulle passe le controle sans un mot. Le seul test qui a
attrape ces trois defauts est d'avoir regarde les courbes et compare a ce qu'on
savait de la nuit.
Document mis a jour le : 04/09/2026 (~09:00) — trois defauts de grandeur corriges dans les tableaux, 19/19
46. Session du 04/09/2026 — « Le systeme » : replique locale de la vue VRM
Pourquoi
La console VRM est hebergee dans le cloud Victron. Elle disparait exactement quand on en a le plus besoin : une coupure d'orage sur ce site emporte souvent Internet. Demande du proprietaire : la meme vision, en local.
Ou, et pourquoi pas ailleurs
Tout dans Grafana, rien dans Home Assistant. Le tableau Energie de HA etait le candidat naturel pour la moitie basse (barres horaires) — il est declare mais entierement vide (aucune source, aucun appareil). En verifiant s'il pouvait servir, defaut majeur trouve :
| compteur | etat |
|---|---|
gx_device_pv_energy |
RESET non signale : −373,35 kWh enregistres sur l'heure de 07:00 |
⚠️ PRECISION du 16/09/2026 — ce ne sont pas des compteurs corrompus, ce sont
des compteurs JOURNALIERS. L'enquete sur une reponse fausse du concierge l'a
etabli : gx_device_pv_energy valait 29,136 alors que la production mesuree
de la veille etait de 29,13 kWh. Ils decrivent donc une JOURNEE, et les
sauts negatifs de plusieurs centaines de kWh sont leur remise a zero vue comme
un delta. La conclusion pratique ne change pas — inutilisables dans le tableau
Energie de Home Assistant, faute de last_reset — mais la caracterisation
« corrompus » etait fausse.
Second piege, celui qui a fait la reponse fausse : ils se FIGENT. A 01:32,
dc_battery_charge_energy valait encore 12,11 avec un last_changed de la
veille 17:31:50 — huit heures sans bouger. Un agent qui l'a cite comme
« 12,1 kWh absorbes cette nuit » decrivait en realite la veille. D'ou la regle
generale : verifier last_changed avant de citer une valeur comme actuelle,
l'API /api/states le donne sur chaque entite.
| gx_device_dc_battery_charge/discharge_energy | RESET : −134,7 et −142,6 kWh |
| airtria_mppt1_total_yield, mppt_450_200_..._total_yield, v1_total_yield | sains, monotones |
| airtria_mpii_v1_energy_from_ac_in_1_to_* et retours | sains, monotones |
state_class: total sans last_reset : Home Assistant n'auto-detecte PAS la
remise a zero (c'est le comportement de total_increasing). Chaque redemarrage
de HA injecterait donc un trou geant dans les barres du tableau Energie.
=> Les energies du tableau Grafana sont obtenues en integrant la puissance,
methode immunisee contre ce probleme. Aucune ecriture dans HA.
Trois versions du calcul d'energie horaire avant d'arriver a la bonne
- Somme ponderee par l'intervalle — laisse des TROUS : une grandeur qui ne change pas n'est jamais reecrite, donc la production nulle de la nuit ne produit qu'UNE ligne et les heures sans ligne sortaient vides au lieu de zero.
- Moyenne des minutes presentes — SUR-COMPTE : l'heure 01 annoncait 5,13 kWh de reseau alors que le service n'avait demarre qu'a 01:37 — 22 minutes comptees comme 60.
- Retenu : axe des minutes complet (
generate_series), report de la derniere valeur connue, puisavg x count / 60= energie REELLEMENT accumulee. Correct pour une heure pleine ET pour l'heure en cours, qu'un simple rejet des heures incompletes laissait toujours vide — or c'est celle qu'on regarde le plus.
Verification par recoupement avec la capture VRM
Total reseau du jour : 8,07 kWh cote Grafana contre « Depuis reseau 9,0 kWh » cote VRM au meme moment. L'ecart de 0,9 kWh correspond exactement aux treize minutes entre l'ouverture des heures creuses (01:24) et le demarrage du service d'historisation (01:37). Detail par tracker recoupe aussi : la chaine que j'ai etiquetee « Ouest » produit 132 W a 9 h contre 173-183 W pour les trois chaines sud — et VRM montre bien Solar 4 comme la plus faible des quatre.
Pieges InfluxDB notes au passage
- L'integration
influxdbde HA n'ecrit que sur changement d'etat. Une fenetrerange(start: -30m)laisse donc vides les cases dont la valeur est stable — la borne a 0 A, voiture debranchee. Fenetre portee a 7 jours,last()reste peu couteux. pivotrend les colonnes par ordre alphabetique (A, V, W) : une transformationorganizeretablit tension / courant / puissance.
Ce qui reste ouvert
- Le tableau Energie de HA pourrait etre configure avec les compteurs SAINS
(trois
*_total_yielden sources solaires, quatre compteurs MultiPlus pour le reseau). Il manquerait la batterie, faute de compteur monotone — a creer par integration de Riemann sursensor.victron_battery_powersi on le veut. Non fait : le tableau Grafana couvre le besoin sans rien ecrire dans HA. - Le suiveur de trois panneaux (« V1 », visible dans VRM a 84 V / 0,4 A / 33 W)
n'est pas dans les globs d'entites de
ha-influxdb.yaml: il manque au tableau des trackers. A ajouter si le detail compte.
Document mis a jour le : 04/09/2026 (~10:00) — tableau « Le systeme », 45/45 requetes verifiees
47. Session du 04/09/2026 (soir) — Connexion InfluxDB retiree du YAML, et un effet de bord du redemarrage
La depreciation, traitee
Home Assistant 2026.8 signale que la configuration YAML de la CONNEXION
InfluxDB est depreciee et cessera de fonctionner en 2026.9.0. Il avait DEJA
importe l'existant dans une entree d'integration (source import, titre
evcs (http://10.10.10.22:8086)) : l'historisation n'a jamais ete menacee,
seule la duplication restait.
Retire de configuration.yaml (sauvegarde
SAVE_CONFIG/configuration.yaml.bak.20260904-194304, diff verifie : sept
lignes, rien d'autre) : api_version, ssl, host, port, token,
organization, bucket. include / exclude / tags restent en YAML — ce
sont le perimetre des entites historisees, non concerne.
Le jeton vit desormais dans l'entree d'integration ; influxdb_token reste
dans secrets.yaml comme copie de reference.
Redemarrage : 30 s de bout en bout (API de retour a +25 s, keepalive Venus a +27 s, cinq capteurs cles numeriques a +30 s). Apres : InfluxDB recevait 1065 points en 90 s, aucune ligne influxdb dans le journal, PostgreSQL inchange. Le detecteur d'evenements a inscrit « Home Assistant a redemarre » tout seul — premier vrai cas depuis son ecriture le matin meme.
⚠️ EFFET DE BORD : un redemarrage peut ANNULER un reglage manuel recent
Releve dans le journal des evenements, sans que personne le cherche :
19:36:27 input_select.victron_evcs_vehicle VW -> JEEP (reglage manuel)
19:44:12 input_select.victron_evcs_vehicle JEEP -> VW (redemarrage)
Toutes les entites portent last_changed = 19:44:12 : c'est la signature de la
restauration d'etat au demarrage. Le choix fait huit minutes plus tot a ete
perdu. L'etat restaure venait d'un instantane anterieur au changement.
⚠️ CAUSE ETABLIE LE 06/09, ET CE N'ETAIT PAS LA RESTAURATION D'ETAT. Le
paquet declarait initial: VW. Dans Home Assistant, declarer initial:
DESACTIVE la restauration : le helper est force a cette valeur a CHAQUE
demarrage. Ce n'est donc pas un instantane pris trop tot, c'est une
reinitialisation systematique. Voir la session du 06/09 ci-dessous : quatre
autres reglages manuels souffraient du meme defaut, dont le toggle P9 et le
cablage de la cible SOC de l'advisor.
Pourquoi c'est grave ici et pas ailleurs : input_select.victron_evcs_vehicle
se regle a la main au branchement et determine l'heure de depart retenue
(VW 08:50 / JEEP 06:30), donc la priorite, l'urgence de recovery nocturne et
toute la mecanique P9. Un selecteur silencieusement revenu a VW alors que la
JEEP est branchee ferait manquer son depart de 06:30.
Regle a appliquer desormais : apres tout redemarrage de Home Assistant, RELIRE les helpers regles a la main dans l'heure precedente, au minimum le selecteur de vehicule et la strategie. Le journal des evenements le montre maintenant automatiquement : deux transitions du meme helper encadrant un « Home Assistant a redemarre » = reglage annule.
Constat annexe : sensor.victron_evcs_charging_time valait 28m56s juste apres
le redemarrage, donc la session de charge n'avait PAS ete interrompue — le
vehicule physiquement branche n'a probablement pas change a 19:36.
Document mis a jour le : 04/09/2026 (~19:50) — connexion InfluxDB hors du YAML, effet de bord du redemarrage sur les helpers manuels
48. Session du 05/09/2026 — Le bridage de charge est une CELLULE, et il commence a 75 %
La preuve etait deja enregistree
InfluxDB capture sensor.airtria_8_us2000c_* depuis le 04/09 07:28. L'episode
de l'apres-midi du 04/09 y est en entier, sans qu'aucun cable serie n'ait ete
branche. Trois entites suffisent :
..._tension_de_cellule_elevee (alarme), ..._maximum_allowed_charge_current
(la limite CCL que le BMS impose au systeme), ..._cell_imbalance.
Chronologie du 04/09
16:13:00 CCL 150 -> 120 A SOC ~70 %, PV ~3,8 kW
16:36:04 ALARME "tension de cellule elevee" = warning
16:36:14 ... = alarm puis warning/alarm toutes les ~10 s jusqu'a 16:40
16:39:40 CCL -> 20 A SOC ~76 %, tension parc 50,76 V
17:01:04 CCL 20 A SOC 82 %
17:53:20 CCL remonte a 30 A le soleil baisse, le courant retombe seul
19:40:23 CCL 150 A batterie en decharge
Ce que cela dit, et qui corrige notre comprehension
- Le bridage ne commence pas a 94 % mais vers 75 %. Le 94 % est seulement le point ou le parc finit par arriver a 20 A. Toute la fin d'apres-midi se fait au compte-gouttes.
- Ce n'est pas la tension du PARC qui limite. Au declenchement, le parc etait a 50,76 V pour une CVL autorisee de 53,2 V — il restait 2,4 V de marge. C'est une CELLULE qui touche son plafond.
- L'ecart entre cellules est important. 50,76 V sur 15 cellules en serie = 3,384 V de moyenne. Pour qu'une cellule declenche une protection de surtension (~3,6 V sur du LiFePO4), elle est ~200 mV au-dessus des quatorze autres. Ce n'est pas de l'usure diffuse, c'est un desequilibre.
cell_imbalanceest resteno_alarmtout du long. Le detecteur de desequilibre du BMS ne se declenche pas ; seule la protection de surtension le fait. Ne pas se fier au premier pour conclure a l'absence du second.- L'alarme oscille warning/alarm toutes les 10-20 s : la cellule traverse son seuil au rythme des reductions de courant. Signature caracteristique.
Pourquoi le proprietaire « ne voyait rien » dans les logs
Le desequilibre est invisible en dessous de ~70 % de SOC. La courbe du LiFePO4 est plate entre 20 et 80 % : toutes les cellules y lisent la meme tension a quelques millivolts pres. La divergence n'apparait que dans le dernier quart, quand la courbe se releve. Une capture faite le matin, batterie a 45 %, ne peut rien montrer — quelle que soit la qualite de l'outil.
Consequence pour le dimensionnement (§46 / page de decision)
La capacite mesuree de 11,6 kWh utiles integre deja ce bridage. Mais si le desequilibre est corrigeable, une partie de la capacite revient sans acheter un seul module. A instruire avant de commander quoi que ce soit.
Document mis a jour le : 05/09/2026 — bridage identifie : une cellule, des 75 % de SOC, prouve sans cable serie
49. Session du 05/09/2026 — Le bridage a un coupable nomme : le module d'adresse 3
Le proprietaire a exporte, depuis BatteryView (machine Windows 10.10.10.93), l'historique et les evenements de deux modules. Ces exports sont la sortie BRUTE de la console du BMS : trente-sept jours d'enregistrements, un toutes les trente minutes, avec les quinze tensions de cellule a chaque fois. Il n'y avait donc rien a attendre de l'apres-midi — la preuve etait deja ecrite.
Le protocole console, releve et non devine
Le module declare lui-meme sa configuration dans la reponse a info :
Device name : US2000C Cell Number : 15
Board : NF4.E2 Specification : 48V/50AH
Main Soft version : B69.13.0.0
EPONPort rate : 1200 Console Port rate : 115200
Invite pylon_debug>, fin de reponse $$, pagination par
Press [Enter] to be continued. Commandes utilisees par BatteryView :
info, stat, data history, datalist history dtls <n> (quinze
enregistrements par page). Tout est reproductible depuis Linux.
Cinq cellules sur quinze, et le BMS le dit lui-meme
Sur 1798 enregistrements du module K220803C30160319, adresse 3 :
| cellule | tension max | tension min |
|---|---|---|
| 1, 3, 4, 5, 7, 8, 9, 11, 13, 14 | 3436 a 3445 mV | 3251 a 3256 mV |
| 2, 6, 10, 12, 15 | 3554 a 3649 mV | 3128 a 3236 mV |
Ces cinq-la montent plus haut en charge ET descendent plus bas en decharge : signature d'une capacite moindre, elles se remplissent et se vident avant les autres. Ce n'est pas une erreur de mesure — le BMS leur attribue d'ailleurs un pourcentage de charge different des dix autres (100 % contre 96 %) dans chaque enregistrement.
Deux sources independantes concordent exactement : le groupe deduit des trente-sept jours d'historique et les cinq cellules les plus basses du releve instantane du 05/09 08:35 sont les memes cinq.
L'ecart n'apparait qu'en haut, ce qui explique tout
Ecart entre cellules, en charge :
| SOC | ecart median | cellule la plus haute |
|---|---|---|
| 60-69 % | 12 mV | 3416 mV |
| 70-79 % | 27 mV | 3621 mV |
| 80-89 % | 168 mV | 3649 mV |
A 60-69 % le module est irreprochable. C'est pourquoi une capture du matin ne peut rien montrer, et pourquoi le proprietaire « ne voyait rien » dans ses logs precedents. 3649 mV place la cellule dans la zone de protection : le BMS reduit alors le courant de charge autorise pour tout le parc.
Le contraste avec un module sain est brutal
Compteurs stat, deux modules du meme parc :
| K220803C30160322 (adresse 1) | K220803C30160319 (adresse 3) | |
|---|---|---|
| Bat HV Times | 7 | 416 |
| Bat OV Times | 8 | 179 |
| CYCLE Times | 0 | 24 |
| SOH | 100 | 0 |
Et c'est le module que le proprietaire croyait « le moins merdique ». Le module qu'il juge le plus abime, sa BAT3, est deja hors du parc — il n'est pour rien dans le bridage quotidien. Le bridage vient d'un module encore en service.
⚠️ CORRECTION du 05/09 12:05 — le champ SOH de stat n'est PAS une note de
sante sur 100. J'avais ecrit « le BMS note lui-meme ce module 0 sur 100 ».
C'est faux : la lecture en direct de la chaine montre un module 2 irreprochable
par toutes les autres mesures (0 evenement de surtension, 0 SOHCount sur ses
quinze cellules, 2 mV d'ecart) et qui affiche pourtant SOH 0. Les grandeurs
reellement discriminantes sont Bat HV Times, Bat OV Times, SOH Times, et
surtout le SOHCount par cellule que renvoie la commande soh [adresse].
C'est chronique, pas en fuite
Depassements de 3550 mV : 1 a 4 releves par jour, tous les jours depuis le 30/07, sans tendance. Le desequilibre est installe et stable. C'est une bonne nouvelle pour la reparation : rien ne s'emballe.
L'horloge du module avance d'environ huit heures
Le dernier enregistrement porte 26-09-05 16:42:48 alors que l'export a ete
fait a 08:35. Correlation du SOC du module contre le SOC du parc mesure dans
InfluxDB : minimum net a 7 h 45 - 8 h 15 d'avance (ecart moyen 9,7 points
contre 30 points sans decalage). La correlation sur le courant donne 6 h,
mais elle est ecartee : un enregistrement ne peut pas etre posterieur a
l'export qui le lit.
⚠️ Toute date lue dans l'historique du BMS doit etre corrigee d'environ -8 h avant d'etre comparee a InfluxDB, aux journaux n8n ou a Home Assistant. A remettre a l'heure depuis la console.
Outils ajoutes
tools/pylontech-console.mjs— console serie, sans aucune dependance (stty pour la ligne, fs pour les octets).--detecter·--commande help·--historique(extraction paginee complete) ·--suivi 30(releve cellule par cellule toutes les 30 s, horodate a l'heure REELLE de la machine et non a celle du module, en CSV). Le lecteur de table a ete eprouve sur la sortie reelle de BatteryView avant tout branchement. ⚠️datanumerote les cellules de 0 a 14,datalist history dtlsde 1 a 15. L'outil ramene tout sur 1..15.tools/analyse-historique-pylontech.py— rejoue l'analyse ci-dessus sur n'importe quel export.
Cote materiel
Le cable console mesure 30 cm, ce qui oblige a tenir le portable devant l'armoire. La rallonge doit se faire cote USB, pas cote RS232 : une rallonge USB active de 5 m coute une dizaine d'euros et supprime le besoin de support. Les quatre pilotes USB-serie usuels (ftdi_sio, ch341, cp210x, pl2303) sont presents sur le poste Ubuntu, et brltty — qui capture souvent les adaptateurs CH340 — y est desactive.
Document mis a jour le : 05/09/2026 (~11:40) — cellules 2, 6, 10, 12, 15 du module adresse 3 identifiees comme cause du bridage ; SOH 0 contre 100 sur un module sain
50. Session du 05/09/2026 (midi) — Lecture en direct de la chaine BMS depuis Linux
Cable FTDI FT232R branche sur le poste Ubuntu, tools/pylontech-console.mjs
en service. Le diagnostic du matin est confirme, elargi, et une de ses
affirmations est fausse.
La console voit toute la chaine : un branchement par PACK, pas par module
stat 3 et soh 3 repondent depuis le module 1 : l'adressage a distance
fonctionne, bien que help ne documente pas l'argument. Mais pwr ne liste
que trois modules presents (1, 2, 3) et « Absent » de 4 a 16.
⚠️ Les deux packs de quatre ont chacun leur chaine BMS. Celle-ci porte trois modules vivants (le quatrieme, BAT3, est eteint). Les quatre autres sont sur une chaine distincte, invisible d'ici : il faut deux positions de cable en tout, pas huit.
Jeu de commandes (releve par help, plus jamais a redecouvrir)
bat [pwr][index] · data [event/history][item] ·
datalist [event/history][item/bat][batnun][volt/curr/temp/coul][item] ·
disp (affichage periodique) · getpwr · info · log · pwr [index] ·
soh [adresse] · stat · time [a] [m] [j] [h] [mn] [s].
Section « Remote command » : data, info, login, logout, soh, stat.
⚠️ A NE JAMAIS ENVOYER : shut (arret du module), trst (reset),
updata (mise a jour firmware), login (mode admin).
soh <adresse> : le compteur de sante par cellule, en une seconde
C'est LA commande a retenir. Elle donne les quinze SOHCount d'un module sans
extraire son historique. Verdict de la chaine :
| module | code-barres | cellules marquees (SOHCount) |
total |
|---|---|---|---|
| 1 | K220803C30160322 | 6 (334) · 7 (50) · 12 (158) · 14 (8) | 550 |
| 2 | — | aucune | 0 |
| 3 | K220803C30160319 | 2 (132) · 6 (181) · 10 (146) · 12 (254) · 15 (400) | 1113 |
Le module 3 designe exactement les cinq cellules deduites le matin meme de ses 37 jours d'historique. Deux methodes sans rapport, meme resultat.
Le module 2 donne enfin un etalon
Quinze cellules entre 3363 et 3365 mV — 2 mV d'ecart, zero surtension, zero compteur. Voila a quoi ressemble un US2000C sain sur cette installation.
CORRECTION — SOH dans stat n'est pas une note sur 100
J'ai ecrit le matin « le BMS note ce module 0 sur 100 ». C'est faux : le
module 2, irreprochable par toute autre mesure, affiche lui aussi SOH 0.
Les grandeurs discriminantes sont Bat HV Times, Bat OV Times, SOH Times
et le SOHCount par cellule. Le diagnostic tient ; c'est cet argument-la qui
tombe.
Le module 1 s'est effondre depuis juillet
Memes compteurs, meme module, six semaines d'ecart :
| 24/07 | 05/09 | |
|---|---|---|
| Bat HV Times | 7 | 498 |
| Bat OV Times | 8 | 86 |
| Shut Times | 4 | 751 |
Il a desormais plus d'evenements de surtension que le module 3. Son
historique (1681 enregistrements, 14/08 au 05/09) designe les cellules
6, 7 et 12 — les memes que son SOHCount, quatrieme concordance.
La cellule 12 oscille entre 2953 et 3644 mV, soit 691 mV d'amplitude, quand les douze cellules saines de ce module font 178 mV. Elle est au bord de la coupure basse.
Consequence pour le bilan energetique : la jauge de SOC est fausse par le bas
Les pires ecarts du module 1 ne sont pas en haut mais en bas : 317 mV a « SOC 2-3 % » le 26/08, alors que le parc etait a 48,55 V, soit 3,236 V par cellule en moyenne — un etat de charge reel de l'ordre de 25 %, pas 2 %. Le BMS voit sa cellule 12 a 2953 mV, la croit vide, et annonce 2 %.
⚠️ C'est tres probablement l'explication de l'effondrement de la jauge en bas d'echelle documente en §28 et mesure en §41 (0,097 kWh par point sous 33 % contre 0,169 au-dessus). La jauge ne mesure pas le parc : elle suit la cellule la plus faible du module le plus faible. Le « genou » a 32-33 % n'est pas une propriete du LiFePO4, c'est la signature de la cellule 12 du module 1. A reverifier apres reequilibrage — la courbe devrait se redresser.
Horloge
pwr horodate les trois modules a 2026-09-05 18:07:5x alors qu'il etait
11:58 : environ 6 h 10 d'avance, mesure directement. L'estimation de 8 h
obtenue le matin par correlation est superseded par cette lecture directe.
La commande time permet de corriger — ecriture, donc en attente de
validation du proprietaire.
Correction d'outil
--historique bouclait sur des index croissants alors que une seule commande
datalist history dtls 1 pagine deja jusqu'au dernier enregistrement : les
119 commandes suivantes relancaient le meme vidage complet. Corrige.
Document mis a jour le : 05/09/2026 (~12:15) — chaine BMS lue en direct depuis Linux ; deux modules atteints sur trois ; la jauge de SOC suit la cellule la plus faible
51. Session du 05/09/2026 (12h30) — Le parc est sain a cinq modules sur sept, et le hub n'est pas coupable
RETRACTATION : je me suis alarme trop vite sur le LVHUB
J'ai ecrit en §50 que la moitie du parc etait « electriquement presente mais invisible pour le systeme qui le pilote ». C'est faux, et la mesure le dit sans ambiguite.
Lecture simultanee, cable sur le pack 2 :
| courant | |
|---|---|
| pack 2, quatre modules (12,93 + 12,43 + 12,39 + 12,90 A) | 50,65 A |
ce que le GX rapporte (battery/512/Dc/0/Current) |
90,60 A |
| difference, soit les trois modules du pack 1 | 39,95 A, ~13,3 A chacun |
Les tensions concordent aussi (50,66-50,72 V cote modules, 50,72 V cote GX). Le GX agrege bien les sept modules, et les deux packs se partagent le courant de facon remarquablement egale — 12,7 A par module sur le pack 2 contre 13,3 A sur le pack 1, cinq pour cent d'ecart.
Ce que j'avais lu sur le hub (SysError 0xe000, courants autorises a zero,
table des unites vide, journal de dix-neuf redemarrages) reste factuel, mais
n'autorisait pas cette conclusion. Le hub possede une commande cmdquit — Quit the Console Mode : brancher une console l'y fait probablement entrer, et
les valeurs lues sont celles de ce mode, pas de son fonctionnement normal.
Ne pas rebrancher de console sur le hub sans raison.
Le parc, module par module — inventaire complet
Pack 1, code-barres K220803C..., fabrique le 03/08/2022 :
| adresse | cellules marquees | OV | HV | cycles |
|---|---|---|---|---|
| 1 | 6, 7, 12 (la 12 oscille sur 691 mV) | 86 | 498 | 25 |
| 2 | aucune | 0 | 0 | 33 |
| 3 | 2, 6, 10, 12, 15 | 179 | 416 | 24 |
| 4 | eteinte (BAT3), non lue | — | — | — |
Pack 2, code-barres Y230202C..., fabrique le 02/02/2023 :
| adresse | code-barres | cellules marquees | OV | HV | cycles |
|---|---|---|---|---|---|
| 1 | Y230202C30371426 | aucune | 1 | 1 | 350 |
| 2 | Y230202C30371425 | aucune | 1 | 1 | 346 |
| 3 | Y230202C30371431 | aucune | 1 | 1 | 340 |
| 4 | Y230202C30371427 | aucune | 0 | 1 | 340 |
Soixante cellules a zero sur le pack 2. C'est un parc neuf apres 340 cycles.
⚠️ Question ouverte : le pack 1, plus ANCIEN de six mois, affiche 24 a 33
cycles quand le pack 2 en affiche 340 a 350. Un facteur quatorze. Soit ses
compteurs ont ete remis a zero (Reset Times 9 a 13), soit il est reste
longtemps hors service, soit le compteur n'incremente que sur cycle complet et
le pack 1 n'en acheve jamais parce qu'il est constamment limite. A trancher.
Le vrai probleme, enonce proprement
Le GX agrege les sept modules, donc quand une cellule des modules 1 ou 3 du pack 1 atteint 3550 mV, le courant de charge de TOUT le parc est reduit — y compris celui des cinq modules sains. Deux modules abimes brident cinq bons.
Et le perimetre de la reparation est desormais chiffre : huit cellules, sur deux modules. Module 1 : 6, 7, 12. Module 3 : 2, 6, 10, 12, 15. Rien d'autre dans le parc ne demande d'intervention.
Adressage
Chaque pack a sa propre chaine et sa propre numerotation 1..N. Les commandes
stat <n>, soh <n>, info <n> interrogent les modules de la chaine sur
laquelle le cable est pose. Deux positions de cable suffisent pour tout le
parc ; le hub n'en est pas une.
Document mis a jour le : 05/09/2026 (~12:35) — inventaire complet des sept modules ; retractation sur le LVHUB ; la reparation porte sur huit cellules de deux modules
52. Session du 05/09/2026 (12h45) — BAT3 lue isolee : UNE cellule, et elle est en meilleur etat que deux modules restes en service
BAT3 = K220803C30160324, quatrieme module du pack 1, allumee seule sur son propre courant, deconnectee du parc. 322 enregistrements exploitables, du 23/07 au 05/09.
Au repos, tout est dit en une ligne
SOC 93 %, aucun courant, quatorze cellules entre 3327 et 3331 mV — quatre millivolts — et la cellule 15 a 3274 mV, cinquante-cinq millivolts en dessous. Un module sain de ce parc montre 2 mV d'ecart.
Sur son historique, une seule cellule se distingue
| amplitude | |
|---|---|
| cellules 1 a 14 | 3282-3307 → 3421-3446 mV, soit ~150 mV |
| cellule 15 | 2939 → 3622 mV, soit 683 mV |
Signature quasi identique a la cellule 12 du module 1 (2953 → 3644, 691 mV).
Deux cellules dans le meme etat terminal, sur deux modules differents.
La cellule 15 descend sous l'alarme basse de niveau 1 (3100 mV) et monte
au-dessus du niveau 4 (3600) : d'ou Bat LV Times 59 et Bat OV Times 608.
Le classement du pack 1 est contre-intuitif
| module | cellules atteintes | pire amplitude |
|---|---|---|
| BAT3 (retiree du service) | 1 (la 15) | 683 mV |
| module 1 (en service) | 3 (6, 7, 12) | 691 mV |
| module 3 (en service) | 5 (2, 6, 10, 12, 15) | 521 mV |
| module 2 (en service) | 0 | — |
⚠️ Le module que le proprietaire a retire du parc a MOINS de cellules atteintes que deux de ceux qu'il y a laisses. Neuf cellules malades au total sur les 105 du parc, reparties sur trois modules ; les quatre du pack 2 et le module 2 du pack 1 sont intacts.
BAT3 est le banc d'essai ideal pour l'alimentation stabilisee
Isolee, hors service, une seule cellule a traiter, aucun risque pour le parc. C'est la ou eprouver la procedure avant de toucher aux modules en service.
⚠️ Limite a garder en tete : le reequilibrage corrige un DECALAGE de charge, pas une PERTE de capacite. Une cellule dont l'amplitude vaut 4,5 fois celle de ses voisines a perdu de la capacite : le rattrapage lui rendra sa place au repos, mais elle restera la premiere pleine et la premiere vide. Les cellules moderement decalees (6 et 7 du module 1, les cinq du module 3) sont les meilleures candidates ; les deux cellules terminales le sont beaucoup moins. A verifier sur BAT3 plutot qu'a supposer.
Horloge de BAT3 remise a l'heure (05/09 12:48, sur validation)
Avant : RTC 2026-09-05 18:56:35 pour une heure reelle de 12:47:23, soit
6 h 09 min 12 s d'avance, mesure directement. C'est cette valeur qui fait
foi ; l'estimation de 8 h obtenue le matin par correlation etait fausse.
⚠️ L'annee se donne sur DEUX chiffres. time 2026 9 5 12 47 53 est refuse
avec « Invalid command or fail to excute », time 26 9 5 12 48 3 passe.
Le message d'usage, lui, affiche time [year] [month] [day] [hour] [minute] [second] sans le preciser.
Apres : RTC 12:48:09 pour 12:48:06 reel — les trois secondes sont la marge
que j'avais ajoutee pour couvrir la latence de la liaison, qui s'est averee
negligeable. Sans objet devant un historique au pas de trente minutes.
Pour les modules en service : les trois modules du pack 1 affichaient la
meme seconde dans pwr, donc le maitre distribue vraisemblablement l'heure a
sa chaine. A verifier en reglant le maitre puis en relisant pwr : si les
horodatages des esclaves suivent, une ecriture par chaine suffit. time ne
figure pas dans la liste des « Remote command », donc ne pas compter sur un
adressage direct.
LVHUB — il n'y a rien a reparer, mais une precaution a retenir
Le proprietaire confirme voir toutes les diodes au vert, aucune alarme. Ajoute a la preuve fonctionnelle du §51 (le GX agrege bien les sept modules, les deux packs se partagent le courant a 5 % pres), le hub fait son travail.
⚠️ Brancher une console sur le LVHUB le fait redemarrer. Son horloge etait
a 2000-01-01 00:00:04 et son journal ne contient que des redemarrages : ce
sont les traces de nos propres branchements, pas d'une panne. Ne pas y
rebrancher de console pendant que le systeme tourne.
Document mis a jour le : 05/09/2026 (~12:50) — BAT3 : une seule cellule atteinte ; neuf cellules malades sur 105 dans tout le parc ; le LVHUB est sain
53. Session du 05/09/2026 (13h05) — Le declenchement saisi en direct, et l'origine des degats
La chaine de cause a effet, chronometree
Suivi console des trois modules du pack abime pendant que l'alarme montait, recoupe avec les entites Home Assistant :
| heure | evenement |
|---|---|
| 13:04:48 | module 1 cellule 7 a 3526 mV · module 3 cellule 6 a 3541 mV — sous le seuil |
| 13:05:08 | module 1 cellule 7 a 3555 mV · module 3 cellule 6 a 3575 mV — niveau 3 franchi |
| 13:05:13 | courant de charge autorise : 50 → 30 A |
Cinq secondes entre le franchissement des 3550 mV et l'effondrement du
plafond. Le seuil Volt High Level3 lu dans sysinfo du LVHUB est donc bien
celui qui commande, et la reaction est immediate.
Ensuite le courant tombe, les cellules se detendent (137 et 163 mV d'ecart deux
minutes plus tard), l'alarme repasse a no_alarm — et le plafond reste a
30 A. C'est ce decalage qui fait durer le bridage des heures alors que
l'alarme, elle, ne dure que quelques minutes. Chercher l'alarme dans les
journaux fait donc manquer l'essentiel du phenomene.
Le module sain de la meme chaine tranche la question
Au meme instant, meme courant, meme chaine : module 2, 13 a 17 mV d'ecart. Contre 189 et 215 mV pour ses deux voisins. Le bridage n'est pas une propriete du parc ni une consequence du courant : il est produit par deux modules et subi par les sept.
Le cout, mesure au moment du bridage
PV 5082 W · maison 3355 W · borne VE 0 W · batterie 1406 W plafonnee a 1518 W par la limite de 30 A, la DVCC autorisant 110 A soit 5,6 kW. La consommation de la maison masque le probleme a cet instant precis ; des qu'elle retombe sous 3 kW, la production est ecretee faute de preneur.
Ages : la contradiction est levee, et elle aggrave le diagnostic
Le proprietaire est formel — pack abime au moins 3 ans de service (il porte une etiquette), pack sain environ 1 an.
Les dates des codes-barres (K220803C = 03/08/2022, Y230202C = 02/02/2023)
sont donc des dates de fabrication, pas de mise en service : six mois
d'ecart a la fabrication, deux ans d'ecart en service, le pack sain ayant
sejourne en stock. Le « deux ans de plus » du MANUEL §0 est exact ; il parle
d'age de service.
⚠️ Mais cela rend les compteurs de cycles impossibles a prendre au pied de la
lettre : 345 cycles en un an pour le pack sain, soit un par jour, est
coherent ; 25 cycles en trois ans pour le pack abime ne l'est pas. Ses
compteurs ont vraisemblablement ete remis a zero (Reset Times 9 a 13).
L'origine des degats n'est pas l'usure, c'est la decharge profonde
Le pack sain a encaisse 345 cycles en un an et ses soixante cellules sont a zero. Cycler n'abime donc pas ces modules.
En revanche les deux cellules terminales du parc — cellule 12 du module
…0322 a 2953 mV, cellule 15 de …0324 a 2939 mV — sont descendues
sous 3,0 V. Un LiFePO4 mene la sous charge perd de la capacite de facon
irreversible. Ces cellules n'ont pas vieilli, elles ont ete videes trop
bas.
⚠️ Boucle a rompre, et elle touche directement le controleur EVCS : la jauge de SOC du parc suit la cellule la plus faible (§50). Quand le systeme croit etre a son plancher dur de 22 %, cette cellule-la est deja bien plus bas que ce que le chiffre laisse croire. Le plancher de securite est exprime dans une grandeur que la cellule endommagee fausse elle-meme — donc il ne protege pas ce qu'il croit proteger, et le meme mecanisme peut atteindre le pack sain. A instruire avant l'hiver.
Document mis a jour le : 05/09/2026 (~13:10) — declenchement chronometre a 3550 mV avec cinq secondes de reaction ; les degats viennent de la decharge profonde, pas de l'usure
54. Session du 05/09/2026 (18h30) — Le tampon d'evenements : la cellule 12 est detruite en ce moment, toutes les nuits
Un second tampon, que je n'avais pas vu
Le BMS tient DEUX journaux distincts, et je n'avais extrait que l'un des deux :
| commande | contenu | portee |
|---|---|---|
data <adr> history <n> |
un point toutes les 30 min | ~5 semaines |
data <adr> event <n> |
uniquement les declenchements, a la seconde | jusqu'a mai 2025 sur un module sain |
C'est le second que lit un fabricant. Il est adressable comme le premier.
Le contraste, sans aucune interpretation
| module | evenements | periode couverte |
|---|---|---|
| Y230202C30371427 | 1 | mai 2025 |
| Y230202C30371431 | 6 | mai 2025 |
| Y230202C30371425 | 12 | mai 2025 |
| Y230202C30371426 | 19 | mai 2025 |
| K220803C30160323 (temoin) | 0 | — |
| K220803C30160319 | 688 | depuis le 23/07/2026 seulement |
| K220803C30160322 | 850 | depuis le 23/07/2026 seulement |
Le temoin a un tampon VIDE, coherent avec ses compteurs tous a zero. Les quatre modules recents n'ont rien enregistre depuis mai 2025. Les deux abimes, eux, produisent tant d'evenements que leur tampon de 900 places ne couvre plus que six semaines.
⚠️ LA DECOUVERTE : le plancher de securite ne protege pas la cellule 12
Sur K220803C30160322, 52 enregistrements sous 3100 mV (alarme basse de
niveau 1), repartis sur 19 nuits. Et cela s'aggrave :
| periode | nuits concernees | plus basse tension |
|---|---|---|
| 23/07 → 22/08 | 8 nuits sur 31 | 3049 mV |
| 23/08 → 04/09 | 10 nuits sur 13 | 2999 mV |
Le SOC du PARC a ces moments-la, releve dans l'historique Home Assistant :
| moment | SOC du parc | cellule 12 |
|---|---|---|
| 23/08 04:22 | 38 % | 3050 mV |
| 31/08 03:12 | 34 % | 3006 mV |
| 01/09 05:09 | 41 % | 2999 mV |
| 04/09 04:11 | 38 % | 3014 mV |
Le plancher dur du controleur est a 22 %. Quand le parc affiche 34 a 41 %, cette cellule est deja sous l'alarme basse et a cent millivolts du niveau 2 (2900 mV). Le parc n'atteint JAMAIS son plancher : la cellule est detruite bien avant, nuit apres nuit, en ce moment meme.
C'est la boucle annoncee en §50, desormais datee et chiffree : le plancher est exprime dans un SOC que la cellule endommagee fausse elle-meme, donc il ne protege pas ce qu'il croit proteger. Et c'est l'explication complete du genou a 33 % mesure en §41.
⚠️ CELA REVISE MA RECOMMANDATION DU MEME JOUR
J'avais conseille d'attendre mardi avant d'envisager le retrait des trois modules abimes, au motif qu'on y perdrait 3 kWh de capacite utile. Ce calcul etait fonde sur une plage 100 → 22 % dans laquelle une cellule se detruit.
Avec la courbe mesuree (§41) et le plancher reellement sur :
| configuration | energie utile |
|---|---|
| sept modules, plancher 22 % (aujourd'hui, destructeur) | 12,39 kWh |
| sept modules, plancher 45 % (cellule protegee) | 9,29 kWh |
| cinq modules sains, plancher 22 % | 9,42 kWh |
Retirer les trois modules abimes ne coute donc RIEN en energie utile — a 0,12 kWh pres — et supprime en plus le bridage du courant de charge. La proposition du proprietaire etait meilleure que mon avis de l'apres-midi.
Reserve : le « cinq modules sans genou » suppose que le genou vient bien des
cellules faibles. C'est l'hypothese la mieux etayee, mais elle se verifie en
remesurant la courbe apres retrait, avec tools/mesure-courbe-batterie.mjs.
Decision a prendre AVANT la nuit
Trois options, par ordre de reversibilite :
- relever le plancher a 45 % (
input_number.victron_evcs_soc_stop_minet la logique de plancher dur) — une ecriture, effet immediat, coute 3,1 kWh ; - retirer
…0322et…0319— memes 3,1 kWh perdus, mais le bridage disparait aussi ; demande une intervention physique ; - ne rien faire cette nuit et perdre encore un peu de cette cellule.
Outillage
--tampon event ajoute a --historique-distant. Et
--decalage-minutes <n>, indispensable : l'horloge d'un module remise a
l'heure APRES l'ecriture des enregistrements se lit a zero alors que les
donnees portent l'ancien decalage. Un controle signale tout enregistrement
corrige qui tomberait dans le futur — signe qu'il a ete ecrit apres la remise
a l'heure et ne devait pas etre corrige.
Document mis a jour le : 05/09/2026 (~18h35) — la cellule 12 passe sous l'alarme basse 10 nuits sur 13 a un SOC parc de 34-41 %, tres au-dessus du plancher de 22 % cense la proteger
55. Session du 05/09/2026 (19h15) — La cellule 15 de BAT3 ne peut plus debiter, meme pleine
Dernier tampon manquant de l'archive. 900 evenements en SIX JOURS (23 → 28/07/2026) : le tampon est sature, ce qui explique a lui seul pourquoi ce module a ete retire du parc fin juillet.
Ce n'est pas une decharge profonde, c'est une resistance
| quand | SOC module | courant | cellule 15 | les 14 autres |
|---|---|---|---|---|
| 25/07 23:34 | 2 % | −5,00 A | 2935 mV | 3309-3321 |
| 23/07 17:20 | 84 % | −6,49 A | 2963 mV | 3291-3303 |
| 23/07 17:47 | 44 % | −6,27 A | 3013 mV | 3303-3313 |
La deuxieme ligne est la plus parlante : a 84 % de charge, sous seulement 6,5 A, cette cellule tombe a 2963 mV pendant que ses quatorze voisines sont a 3291-3303. Trois cent quarante millivolts d'ecart, a mi-charge. Ce n'est pas un probleme de profondeur de decharge — cette cellule ne peut simplement plus debiter de courant.
Elle est deux fois pire que la pire du parc en service
Deux points, meme methode qu'au §53 :
- au repos ce matin : 3274 mV contre 3329 de moyenne, soit 55 mV a 0 A ;
- sous 6,49 A : 2963 contre 3297, soit 334 mV.
Pente : ≈ 43 mΩ de resistance excedentaire, contre 18,6 mΩ pour la cellule
12 du module …0322. Deux fois et demie pire.
⚠️ CONSEQUENCE POUR L'ESSAI DE MARDI
BAT3 reste le bon banc d'essai — isole, hors service, sans risque. Mais il faut savoir ce qu'il peut et ne peut pas prouver :
- un ECHEC sur BAT3 ne condamne pas les autres. Sa cellule 15 est a 43 mΩ ;
celles de
…0322et…0319sont deux a trois fois moins resistives. Un reequilibrage qui echoue ici peut parfaitement reussir la-bas. - un SUCCES sur BAT3 serait en revanche decisif : si une cellule a 43 mΩ se rattrape, les autres suivront.
Autrement dit l'essai est asymetrique : concluant s'il marche, non concluant
s'il echoue. Pour trancher le cas de …0322, il faudra le traiter lui-meme.
Archive complete
Les huit modules sont desormais releves : info, stat, soh par cellule,
tampon history et tampon event. Seul …0323 a un tampon d'evenements vide
— parce qu'il n'a jamais rien declenche.
Document mis a jour le : 05/09/2026 (~19h20) — cellule 15 de BAT3 a 43 mΩ, elle s'effondre a 84 % de charge sous 6,5 A ; l'essai de mardi est asymetrique
56. Session du 05/09/2026 (soir) — Dates d'achat, et ce qu'elles eliminent
Les deux factures fournies par le proprietaire datent enfin le parc, ce qui manquait a toutes les analyses precedentes.
| pack ancien | pack recent | |
|---|---|---|
| facture | 190002543 du 27/04/2023 (La Boutique Solaire) | FACLBSPRO2503-0064 du 14/03/2025 (La Boutique Solaire Pro) |
| modules | 4 × US2000C a 940 € HT | 4 × US2000C a 582,42 € HT |
| code-barres | K220803C3016031 9/22/23/24 |
Y230202C303714 25/26/27/31 |
| service au 05/09/2026 | 3 ans 4 mois | 1 an 6 mois |
⚠️ Le second MultiPlus-II figure sur la facture de mars 2025 : le passage en parallèle date donc de mars 2025, et non d'une date inconnue comme le supposait la §26.
Cela DISCULPE le passage en parallele dans la degradation du pack ancien : le pack recent a ete installe au meme moment, sous la meme configuration parallele, et ses soixante cellules sont intactes apres dix-huit mois. Si le parallele abimait les modules, les deux packs seraient touches.
⚠️ Les deux factures emanent d'entites juridiques DIFFERENTES — La Boutique Solaire (SIRET 880 899 083, L'Herbergement) en 2023, La Boutique Solaire Pro (SIRET 98199581400010, Dompierre-sur-Yon) en 2025. Point a laisser au proprietaire : la garantie du premier lot se rattache formellement a la premiere entite.
Adresse d'installation actuelle : 5 hent Toull Kroaz, 22560 Pleumeur-Bodou — ce qui confirme la commune retenue pour l'integration Meteo-France (§32).
Document mis a jour le : 05/09/2026 — parc date par les factures ; le passage en parallele de mars 2025 est disculpe
57. Session du 06/09/2026 — Le relachement du contacteur se mesurait sur un instant
La nuit du 05-06/09, et une erreur de cadrage de ma part
Premiere nuit a quatre modules. La soiree a vide le pack : 87 % -> 20 % entre
20:00 et 00:00, 6,80 kWh sortis pour une maison a 1,2-3 kW. A 23:55 le SOC a
touche le seuil bas de l'hysteresis ET le plancher ESS (20 dans les deux cas),
le contacteur s'est ferme, l'ESS est passe en self_consumption_soc_below_min.
A 01:24 la fenetre HC s'est ouverte, la session reseau unique (§40) s'est
engagee, l'ACM est passe en evcs_takeover_recover et c'est lui qui a charge
la batterie, vers night_target_soc — cible issue du bilan depuis le 03/09
(§39), qui demandait 11,71 kWh a couvrir, soit plus de 100 % de SOC avec quatre
modules, donc 90 (plafond) puis 80,5 a 01:00.
La charge s'est arretee a 05:05 quand le SOC (55) a rattrape la cible qui
descendait (49,4) -> evcs_takeover_hold, DVCC 2 A. Bilan de la nuit :
13,34 kWh importes, dont 4,23 dans la JEEP, 3,28 dans la batterie et
~5,8 directement dans la maison. Verification par la suite : a 06:47 la JEEP est
debranchee, EDF ouvert, et la batterie a porte la maison de 55 % a 45 % jusqu'au
croisement PV vers 08:45 — 0,99 kWh consommes contre 1,49 annonces a 06:00.
La chaine a fonctionne.
⚠️ J'ai d'abord presente la vidange du soir comme un defaut. C'est faux : la batterie a delivre 6,80 kWh en heures pleines, ce qui est exactement son metier, et elle est partie de 87 % — elle ne pouvait pas etre plus pleine. Les 1,4 kWh achetes de 23:55 a 01:24 l'auraient ete de toute facon. Le defaut est ailleurs.
Le defaut : treize occasions de relacher au milieu du service
La regle du 05/09 ferme quand la maison depasse 80 % du plafond de decharge du BMS et relachait des que la charge repassait sous 50 % — sur un seul echantillon. Releve de la soiree :
| pic de decharge a 20:40 | -97,4 A pour un plafond de 100 A (maison 4 944 W) |
| depassements du seuil de fermeture (4 080 W) | 3 : 20:34:20, 20:39:30, 21:01:55 |
| passages sous le seuil de relachement (2 550 W) | 14 en quatre heures |
| contacteur ouvert a | 21:04 |
| maison apres le relachement | jusqu'a 3 423 W a 21:37, soit -73 A, 73 % du plafond |
La fermeture a mordu au bon instant, a 2,6 A de la limite du parc. Mais le troisieme pic est tombe deux minutes avant l'ouverture : decale de cinq minutes, la regle refermait, et chaque aller-retour du contacteur coute 50 a 150 Wh de transitoire (§28/§31). Le battement n'a pas eu lieu par chance, pas par construction.
La mesure qui donne les durees (et rend tout choix inutile)
Duree des accalmies sous 2 550 W, pas d'une minute :
| periode | accalmies | la plus longue |
|---|---|---|
| pendant la cuisson, 20:33 -> 21:37 | 13 | 5 min |
| apres la cuisson, 21:40 -> 01:00 | 1 | 201 min |
Distribution franchement bimodale, fosse net entre 5 et 201 minutes. Un creux de vingt minutes n'existe pas tant que ca cuisine. Les deux durees se lisent donc sur la mesure : 20 min (milieu du fosse, marge x4) et 6 min (juste au-dessus de la plus longue accalmie de cuisson).
Le correctif (deploye 06/09 13:03, golden-hysteresis 20/20)
tools/patch-20260906-relachement-sur-duree.mjs, rejouable et idempotent.
Fermeture inchangee — c'est une contrainte de PUISSANCE, ni le SOC ni un budget d'energie n'ont rien a y dire, et le plafond vient deja du BMS donc il suit le nombre de modules tout seul.
Relachement sur une DUREE : la maison doit rester sous le seuil bas pendant N minutes. Meme mecanisme que la tolerance temporelle de la politique batterie (§35) — un franchissement de seuil doit DURER pour compter.
Et N vient du bilan energetique. Tant que la batterie est de toute facon condamnee a toucher son plancher avant l'ouverture des heures creuses, le partage batterie/reseau de la soiree est FIGE : tenir le contacteur plus longtemps ne change que l'HEURE d'achat, pas le montant. Des qu'elle passerait la nuit seule, chaque kWh du soir deplace un kWh d'heures creuses vers des heures pleines. D'ou 20 min dans le premier cas, 6 dans le second. C'est la premiere lecture du bilan par le workflow d'hysteresis, qui n'en lisait aucun champ (trou structurel signale en §11.8.A).
⚠️ APPROXIMATION ASSUMEE : le bilan publie required_soc, qui vise le
CROISEMENT PV — cible strictement plus lointaine que 01:24. soc < required_soc est donc NECESSAIRE mais pas SUFFISANT pour « la batterie
n'atteindra pas l'ouverture des HC ». L'erreur est unilaterale : on tient
parfois le long dwell une nuit ou le court aurait suffi, ce qui deplace ~0,4 kWh
des heures creuses vers les heures pleines et menage le parc. Direction voulue.
⚠️ LE DWELL SE COMPTE SUR LA MAISON. Le nouvel horodatage
input_datetime.victron_grid_charge_high_last_at porte la derniere charge
maison HAUTE et il est ecrit que le contacteur soit ouvert ou ferme. Meme
raison qu'au 05/09 : fermer le contacteur fait chuter le courant batterie, s'en
servir comme critere ferait que la decision detruirait sa propre cause — le
piege des quatre oscillateurs deja corriges. Horodatage absent ou illisible =>
dwell repute ecoule => comportement d'avant ce correctif.
Helpers : paquet victron_grid_charge_dwell.yaml (..._dwell_long_min 20,
..._dwell_court_min 6, ..._charge_high_last_at), crees par
input_number.reload + input_datetime.reload — aucun redemarrage HA.
Capteurs muets — un mode de defaillance observe, pas suppose
Le 05/09 de 21:22:21 a 21:49:35, soit 27 minutes,
sensor.victron_battery_current et sensor.victron_battery_voltage n'ont RIEN
publie, pendant que sensor.victron_consumption_power_l1 continuait
normalement. Le SOC, lui, a publie en boucle une valeur figee a 60 % alors qu'il
tombait reellement a 49 %. Un courant fige a -73,2 A pendant que la maison
oscille entre 1,4 et 3,4 kW est physiquement impossible.
Consequences : les battery_current_hard_stop du controleur EVCS enregistres a
21:31, 21:36, 21:38 et 21:41 sont des decisions prises sur une valeur vieille
d'une demi-heure ; et la tension alimente plafondDechargeW, donc sans
repli la protection s'evanouissait pile a l'heure du diner.
Deux filets, tous deux fondes sur du mesure ou du persiste :
- plafond en AMPERES lisible mais tension muette => tension nominale 51 V. Sur toute la soiree la tension mesuree n'est pas sortie de 48,85-51,5 V : au pire 5 % d'erreur sur le seuil, contre 100 % si la regle s'eteint ;
- les deux muets => l'horodatage de charge haute reste un fait persiste : on tient jusqu'a la fin du dwell. La memoire ne survit pas au dwell, puisque l'horodatage n'est ecrit que quand le plafond est lisible.
Golden-hysteresis 11 -> 20
h-dwell-accalmie-de-cuisson-ne-relache-pas (le releve reel : accalmie de
5 min, SOC 65 sous les 92 requis) et son contrefactuel a 25 min · trois
scenarios a 8 minutes de calme, entre les deux durees, que seul l'etat du
bilan separe (frais+condamne -> tient · frais+reserve suffisante -> rend ·
perime -> rend) · l'horodatage ecrit contacteur OUVERT · les trois cas de
capteurs muets avec leur contrefactuel de memoire expiree.
Preuve par mutation, six directions, chacune faisant tomber un jeu different et aucune plus d'un ou deux scenarios : dwell retire · bilan ignore · garde de fraicheur retiree · horodatage ecrit seulement contacteur ferme · repli de tension retire · memoire de protection retiree. Restauration -> 20/20.
Le defaut s'est reproduit A MIDI, quinze minutes avant le deploiement
Contacteur ferme a 12:07:01 (maison au-dessus de 4 080 W), ouvert a 12:22:15 alors que la maison etait a 3 084 W. Lecture InfluxDB a la resolution native (~1 point/s), fenetre 12:07 -> 12:30, 1 346 points :
| franchissements du seuil de relachement (2 550 W) | 75, soit 3,3 par minute |
| temps passe sous le seuil | 28 % |
| plus longue serie continue sous le seuil PENDANT la cuisson | ~3 s |
| montees au-dessus du seuil de fermeture (4 080 W) | 9, toutes avant l'ouverture |
| maximum | 4 941 W |
La maison oscille avec une periode de ~8 secondes entre ~2 000 et ~3 080 W — un appareil qui cycle. Le workflow a echantillonne a 12:22:03 : 1 980 W. La decision de rendre le reseau etait un tirage a pile ou face sur une oscillation de huit secondes, prise dans un signal dont le rapport cyclique est a 3 kW deux tiers du temps.
Aucun battement, la encore, seulement parce que les pics de cuisson se sont arretes pile a 12:22 (zero montee au-dessus de 4 080 W apres l'ouverture, contre neuf avant). Avec le dwell court — c'est celui qui s'applique a midi, SOC 78 % tres au-dessus des ~25 % que demandait le bilan — le contacteur aurait tenu jusqu'a ~12:32, la premiere accalmie de plus de six minutes n'arrivant qu'apres 12:26.
A surveiller ce soir
input_datetime.victron_grid_charge_high_last_atdoit avancer pendant la cuisson et se figer ensuite ;- au plus UNE fermeture et UNE ouverture du contacteur sur la soiree ;
- la raison doit afficher
Charge maison retombee a XW depuis Y min : on garde le reseau jusqu'a 20 min (reserve deja condamnee...); - si la tension batterie se remet a disparaitre,
plafond_decharge_wdoit rester a ~5100 et non tomber a 0.
MEME JOUR, DEFAUT PLUS PROFOND : la regle comptait la maison BRUTE
Dimanche 06/09, deux fermetures du contacteur : 12:07 -> 12:22 (four + plaque) et 13:34 -> 13:42 (cuisson + JEEP branchee a 13:31). Journee nuageuse, parc reduit de moitie. Mesure a la resolution native a l'instant ou chaque decision a ete prise, contacteur encore ouvert — donc sur une production non censuree :
| fermeture | maison | PV | parc reellement sollicite | decharge batterie mesuree |
|---|---|---|---|---|
| 12:07, cuisson seule | 4 025 W | ~2 890 W | 1 132 W | 1 345 W |
| 13:34, cuisson + JEEP | 4 981 W | ~4 210 W | 772 W | 1 223 W |
La decharge batterie colle a maison - PV, pas a la maison brute : le controle
croise est net. Le plafond du parc est de 5 100 W — il etait a 22 % et 15 % de
sa limite, et la regle a ferme le contacteur pour rien. Meme en integrant les
periodes fermees (ou le PV peut etre bride, donc chiffres pessimistes), la
demande maximale sur le parc n'a atteint que 3 046 et 2 437 W. Cout : 0,50 kWh
importes en plein soleil, pour rien.
Cause : la regle du 05/09 compare la MAISON BRUTE au plafond de decharge du parc. Elle n'avait bien fonctionne la veille au soir que parce qu'il n'y avait plus de PV — la maison brute valait alors la demande sur le parc, et le pic de -97,4 A etait reel. Des qu'il y a du soleil, elle ferme sur une charge que le solaire couvre deja.
Correctif (deploye 06/09 13:49,
tools/patch-20260906b-demande-sur-le-parc.mjs) : les deux seuils portent sur
max(0, maison - PV). acOverSafeLimit n'est PAS touche — il compare la
charge au debit des onduleurs, qui portent la maison entiere quelle qu'en soit
la source.
⚠️ Pourquoi ce n'est pas un oscillateur, alors que la grandeur n'est pas
invariante a la decision (fermer peut brider le MPPT, §28) : contacteur OUVERT,
maison - PV EST la decharge batterie, grandeur physique exacte ; contacteur
FERME, un bridage du MPPT fait BAISSER PV donc MONTER maison - PV — l'erreur
pousse a rester ferme, jamais a relacher tot ; et le relachement exige de toute
facon le dwell (6 a 20 min), tres au-dela du transitoire d'etablissement mesure
a ~150 s. Erreur unilaterale et bornee, dans la direction qui menage le parc.
Golden 20 -> 24 : les deux releves de ce midi (aucune fermeture attendue), le
contrefactuel du nuage (meme cuisson a 4 500 W avec PV 200 W -> on ferme), et
h-midi-soleil-revenu-relache (contacteur ferme, cuisson a 3 000 W mais PV
2 000 -> le parc n'est plus sollicite qu'a 1 000 W, on rend). Les scenarios de
protection existants portent desormais pv_power_w: '0' explicitement : ils
decrivent le soir, ou le parc est seul.
⚠️ Piege evite grace au test de mutation : la premiere serie de scenarios ne
couvrait que le seuil de FERMETURE. Remettre le relachement sur la maison brute
ne faisait echouer aucun test — il a fallu ecrire h-midi-soleil-revenu-relache
pour que la mutation morde.
Ce que la JEEP n'a PAS cause, et la question qu'elle pose quand meme
Le propriétaire attribuait la fermeture de midi a la conjonction cuisson + voiture. Les faits : la voiture n'etait pas branchee pendant l'episode de 12:07 (EVCS a 0 W de bout en bout) ; elle n'a joue que dans celui de 13:34, qui n'aurait pas du fermer non plus. Les deux fermetures viennent de la regle, pas de la charge.
Reste que la question de fond est juste, et elle n'est pas traitee : houseAcPowerW
inclut l'EVCS, donc la voiture est comptee comme une charge menagere
incompressible alors qu'elle est la seule charge entierement compressible du
foyer — le controleur EVCS peut la ramener a 6 A ou l'arreter en un cycle. Le
jour ou la demande sur le parc depassera vraiment le plafond avec la voiture
dedans, la reponse economique est de brider la voiture d'abord et de ne fermer
le contacteur que pour le reste. Aucun des deux workflows ne sait faire cela
aujourd'hui : le controleur EVCS ignore le plafond de decharge du BMS, et
l'hysteresis ne sait pas commander la borne. A instruire.
Anomalie annexe : une session de charge comptee deux fois
Session de charge terminee : JEEP, 4.20 kWh a ete emis deux fois — a
02:52:31 (fin de la charge HC reelle) et a 13:42:32 (debranchement apres onze
minutes de charge diurne), avec la MEME valeur au centieme. Le compteur de
session de tools/historise-evcs.mjs semble rejouer la session de la nuit au
lieu d'en ouvrir une nouvelle. A corriger, sans quoi la table session_charge
double-compte.
Double comptage des sessions de charge — corrige
Session de charge terminee : JEEP, 4.20 kWh a ete emis a 02:52 (vraie charge
HC) puis a 13:42 (onze minutes de charge diurne), avec la MEME valeur au
centieme.
Cause : energie_station_kwh etait echantillonne a chaque cycle depuis
sensor.evcs_hq2239dnt7p_last_session_energy et conserve par un GREATEST
— un operateur monotone, qui ne peut donc que SURESTIMER et qui verrouille la
premiere valeur vue. Au premier echantillon de la session de midi, le capteur
portait encore le total de la nuit ; le maximum l'a fige pour toujours.
Confrontation de chaque ligne a l'integration de victron_evcs_ac_power relue
dans InfluxDB sur la periode de charge REELLE (periodes > 300 W separees de plus
de dix minutes) :
| periode reelle | energie reelle | valeur en base | ecart |
|---|---|---|---|
| 05 01:26-03:55 | 8,27 kWh | 9,400 | 14 % |
| 05 10:00-10:54 | 2,32 kWh | 2,200 | 5 % |
| 05 11:11-12:38 | 3,37 kWh | 5,400 | 60 % |
| 06 01:25-02:44 | 4,24 kWh | 4,200 | 1 % |
| 06 13:32-13:42 | 0,34 kWh | 4,200 | 1135 % |
⚠️ CORRECTION du 07/09/2026 — la semantique affirmee ici etait FAUSSE.
J'avais conclu que le capteur decrit la « derniere session TERMINEE » et que le
lire pendant une session donne la precedente. La serie InfluxDB, disponible
depuis le 06/09, montre l'inverse sans ambiguite : il suit la session EN
COURS, au pas de 0,1 kWh —
0 (14:25:12) -> 0,1 (14:27:52), remise a zero a 16:28:31, puis
0,1 -> 0,2 -> 0,3 ... -> 1,1 pendant la charge. _last_session_time fait de
meme en minutes. Le nom du capteur est trompeur.
Le correctif applique le 06/09 (compteur cumulatif total_energy) reste le bon
choix, et le GREATEST reste bien le coupable du double comptage : la remise a
zero est simplement TARDIVE en debut de session, si bien que le premier
echantillon porte encore le total de la session precedente et que l'operateur
monotone le verrouille. C'est le mecanisme qui etait mal decrit, pas la
conclusion pratique.
Correctif (tools/historise-evcs.mjs) :
- Le compteur CUMULATIF de la borne (
..._total_energy, ~3 780 kWh) devient la mesure de reference : releve a l'ouverture (compteur_debut_kwh), sa difference a la cloture donne l'energie sans ambiguite de semantique NI erreur d'integration. C'est desormais le chiffre annonce. last_session_energyetlast_session_timesont lus une seule fois, a la cloture, et enregistres comme des OBSERVATIONS (energie_station_kwh,duree_borne_min). Le journal ne les affiche que s'ils s'ecartent de plus de 10 % — ce qui rend visible le cas qui avait motive la colonne (le 05/09, 8 kWh reels pour 0,75 integres, session commencee avant le demarrage du service) au lieu de les substituer en silence.- Plus aucun
GREATESTsur une grandeur d'energie. - Les treize capteurs
sensor.evcs_*de la borne entrent dans l'historisation InfluxDB (configuration.yaml, sauvegarde dansSAVE_CONFIG/) : c'est leur absence qui a empeche d'etablir la semantique. ⚠️ Prend effet au prochain redemarrage de Home Assistant (les filtresinclude/excluded'InfluxDB restent en YAML et n'ont pas de rechargement).
Meme fonction, second defaut : l'heure de debranchement posee en bloc
debranche_a etait ecrit par un UPDATE sans limite sur toutes les sessions
non marquees des dix-huit dernieres heures. Le 05/09 a 15:02:16, les trois
sessions ouvertes ont recu la meme heure — dont la session de nuit de la JEEP,
qui a herite du debranchement de la VW. Or cette colonne existe precisement
pour departager les deux vehicules apres coup : elle affirmait l'inverse de son
role.
Correctif : la fenetre est bornee par
sensor.victron_evcs_charging_time, qui repart de zero a chaque connexion
(signal du reset P11) et se fige a la valeur du debranchement — il donne donc la
duree exacte du branchement qui s'acheve. Duree illisible ou nulle => on ne
marque que la session la plus recente, jamais celles d'avant.
Reparation des lignes existantes (migration idempotente)
Appliquee au prochain demarrage du service. On ne corrige QUE ce qui a ete
verifie : la ligne du 06 13:31 passe a 0,3 kWh (valeur du capteur au moment de
la reparation, corroboree par la duree de 11 min), celle du 05 11:11 est
EFFACEE (60 % d'ecart, aucun substitut fiable), les trois lignes coherentes ne
sont pas touchees, et le debranche_a errone de la session JEEP du 05 02:56
repasse a NULL — on efface plutot que d'inventer.
⚠️ debut porte les millisecondes : les reparations ciblent une fenetre d'une
seconde et non une egalite, et la condition sur la valeur attendue les rend
idempotentes. Verifie : chaque fenetre designe exactement une ligne.
Cinq reglages manuels se reinitialisaient a chaque redemarrage
Redemarrage de Home Assistant a 14:14 pour activer l'historisation des capteurs de la borne. 30 secondes de bout en bout (API a +22 s, keepalive Venus a +25 s, six capteurs cles numeriques a +30 s) — troisieme mesure identique.
Controle systematique des reglages manuels avant/apres, conformement a la regle posee en §47 : le selecteur de vehicule est repasse de JEEP a VW, les huit autres helpers surveilles ont survecu.
Cause etablie, et elle contredit le diagnostic du 04/09 (§47, corrige) : ce
n'est pas un instantane de restauration pris trop tot. Le paquet declarait
initial: VW, et dans Home Assistant declarer initial: desactive la
restauration d'etat — le helper est force a cette valeur a chaque demarrage.
Preuve par comparaison : input_select.victron_evcs_strategy, cree par
l'interface donc sans initial:, survit a tous les redemarrages.
Le defaut ne touchait pas que le selecteur. Cinq reglages manuels portaient
un initial: :
| helper | valeur forcee a chaque demarrage | consequence |
|---|---|---|
input_select.victron_evcs_vehicle |
VW | l'heure de depart du mauvais vehicule |
input_datetime.victron_evcs_departure_time_jeep |
06:30 | tout reglage perdu |
input_boolean.victron_evcs_allow_grid_departure_urgent |
off | P9 desactive en silence |
input_boolean.evcs_advisor_apply_night_target |
off | le cablage de la cible SOC coupe en silence |
input_boolean.evcs_advisor_enabled |
on | l'advisor se rallume tout seul |
Les deux avant-derniers sont les plus graves : ce sont des toggles de CABLAGE, poses en §20 et §21 avec une consigne explicite de les activer. S'ils ont ete actives depuis, chaque redemarrage les a silencieusement remis a zero — et rien dans le systeme ne le signalait.
Correctif : initial: retire de ces cinq helpers (paquets
victron_evcs_vehicles.yaml et evcs_advisor_shadow.yaml, sauvegardes dans
packages/SAVE/), avec la regle inscrite en tete des deux fichiers. Les onze
helpers de RUNTIME de l'advisor gardent le leur : ils sont reecrits par le
workflow au cycle suivant, et repartir d'un etat connu y est voulu. Applique par
input_select.reload + input_boolean.reload + input_datetime.reload, sans
redemarrage, etats preserves (verifie).
Contrepartie assumee : sur une installation neuve, sans etat a restaurer, un
input_select prend sa premiere option, un input_number son min et un
input_datetime 00:00.
⚠️ Le selecteur est reste sur VW : la JEEP etant debranchee, c'est au proprietaire de le positionner au prochain branchement.
Les capteurs de la borne sont historises
Les treize sensor.evcs_hq2239dnt7p_* arrivent dans InfluxDB depuis le
redemarrage (verifie : 5 a 6 points chacun en quinze minutes), dont
total_energy (compteur cumulatif) et last_session_energy / last_session_time
— c'est leur absence qui avait empeche d'etablir la semantique de ce dernier
quand il a fausse une cloture de session.
Injection furtive : deux phenomenes distincts, un seul se corrige ici
Le proprietaire a observe des valeurs negatives au point de raccordement contacteur ferme — « -580 W furtivement, et aussi du -80, -320 » — voiture debranchee, batterie au-dessus de 80 %, et il l'attribue aux a-coups de la plaque chauffante. Mesure des deux fenetres fermees du 06/09 :
| fenetre | echantillons negatifs | minimum | mediane des negatifs | energie |
|---|---|---|---|---|
| 12:07-12:22 | 9 % | -477 W | -146 W | 3,5 Wh |
| 13:34-13:42 | 17 % | -2 819 W | -1 655 W | 29,2 Wh |
Ce sont deux choses differentes.
La premiere est bien la plaque : le regulateur ne rattrape pas assez vite un
delestage brutal de ~1 kW, et avec une consigne de repos a 200 W le point de
fonctionnement passe sous zero. Correctif : consigne de repos 200 -> 700 W
(INPUT_PARSE, borne elargie de [0,500] a [0,1500]). Toute la courbe monte de
500 W : sur cette fenetre le minimum passe de -477 a +23 W, plus aucune
excursion negative.
Le surcout est faible et va dans le bon sens : la consigne de repos ne s'applique que contacteur FERME sans session reseau EVCS — donc apres une coupure sur SOC bas, ou apres une fermeture par la regle de protection du parc. Dans les deux cas on VEUT que le reseau contribue plus que 200 W. Contacteur ouvert, elle n'a aucun effet.
La seconde n'a rien a voir avec la plaque, et aucune consigne ne l'absorbe :
| heure | reseau | maison | PV | EVCS | batterie |
|---|---|---|---|---|---|
| 13:41:35 | 393 | 4 717 | 3 976 | 2 182 | -568 |
| 13:41:40 | -2 534 | 1 589 | 3 808 | 18 | -655 |
| 13:42:30 | -1 594 | 2 013 | 4 024 | — | 25 |
| 13:43:30 | -1 145 | 1 510 | 4 060 | — | 1 060 |
Le debranchement du vehicule supprime 2,2 kW d'un coup. La batterie, a 93 % de SOC, n'absorbe rien (elle DECHARGE encore a -655 W, plafond de charge abaisse par le BMS) et le MPPT produit toujours 4 kW : l'excedent part au reseau pendant plus de deux minutes, le temps que le systeme bride la production — la remontee du courant batterie de -655 a +1 060 W montre le rattrapage.
Ce qui l'evite est le correctif du meme jour sur la regle de protection : la demande sur le parc valait alors 741 W, donc avec la regle corrigee le contacteur n'aurait jamais ete ferme — et sans reseau, il n'y a rien ou injecter. Le probleme se resout en amont, pas par la consigne.
Golden 80/80, trois attendus mis a jour dans le meme commit (200 -> 700) avec la mesure en commentaire. Mutation : retour a 200 -> les 3 memes echouent.
Le binaire docker n'est pas dans le PATH en SSH sur DSM
sudo docker compose restart historisation echoue sur
sudo: docker: command not found : Synology ne met /usr/local/bin dans le
chemin que pour les sessions interactives de son interface. Toute commande
lancee a distance doit appeler /usr/local/bin/docker. Corrige dans
deploy/historisation/README.md (note en tete + 9 occurrences) et
deploy/watchdog/compose.yaml.
Pour memoire : l'acces SSH par cle a 10.10.10.22 fonctionne et sudo y est sans
mot de passe — la demande de validation avant un sudo releve de la regle posee
par le proprietaire, pas d'une incapacite.
Le plafond a 93 % de SOC etait notre reglage, pas une protection
Mesure du 06/09 a 15:01, quatre modules du pack recent :
ce que le BMS AUTORISE (maximum_allowed_charging_voltage) |
53,2 V |
| ce que nous IMPOSIONS | 51,3 V, soit 3,42 V par cellule |
| cellules du pack 2 | 3 386 a 3 423 mV, ecart 25-30 mV par module |
| premiere alarme haute du BMS | 3 480 mV — la plus haute cellule 57 mV en dessous |
Aucune alarme, et le BMS autorisait encore 40 A de charge : c'etait la consigne de tension qui plafonnait le SOC. Origine : la gestion « tension de cellule elevee » posee pour proteger les cellules faibles du pack ANCIEN (cellule 12 du ...0322 a 3 649 mV, cellule 15 du ...0324), hors service depuis le 05/09. Le garde-fou protegeait un parc qui n'est plus la, au prix d'environ 0,68 kWh — 12 % de l'energie utile a quatre modules, precisement ce qui manque en fin de nuit. Un Pylontech recale en outre sa jauge de SOC sur une charge pleine : ne jamais y arriver la fait deriver.
⚠️ input_number.victron_battery_charge_voltage_normal_v ET SES PALIERS
hcv_step1/step2 NE SONT LUS PAR PERSONNE. Verifie : le nom n'apparait nulle
part dans l'Auto Charge Manager. Les tensions sont une table CODEE EN DUR dans
le node « Compute Charge Targets » — meme famille que le toggle
allow_grid_hc_night, parse mais cable nulle part jusqu'a P7. La vraie
protection est le BMS lui-meme, qui a demontre le 05/09 une reaction en CINQ
SECONDES (cellule au-dessus de 3 550 mV -> plafond de courant effondre) ; elle
reste entiere.
Table des profils jour de l'ACM, par SOC :
| SOC | profil | courant | tension |
|---|---|---|---|
| < 80 | pv_boost | 130 A | 51,5 V |
| 80-90 | pv_mid | 110 A | 51,2 V |
| 90-95 | pv_topoff | 80 A | 51,3 -> 52,4 V |
| >= 95 | pv_hold | 30 A | 50,6 V |
Seul pv_topoff est modifie : c'est la tranche ou ca plafonnait. pv_boost et
pv_mid sont limites par le COURANT, pas par la tension. pv_hold n'est pas
touche — redescendre a 50,6 V une fois le haut atteint est le bon comportement
pour du LiFePO4, on ne veut pas y sejourner.
52,4 V = 3,493 V par cellule, SOUS la tension de charge nominale annoncee par Pylontech pour l'US2000C (52,5 V) et bien sous les 53,2 V que ce BMS autorise. Avec 30 mV d'ecart entre cellules, la plus haute atteint ~3,51 V, encore 40 mV sous le seuil de reduction.
Effet immediat, verifie a 15:14 : consigne DVCC 52,4 V, tension du parc
51,12 -> 52,33 V, courant batterie -5,3 -> +16,1 A, SOC 93 -> 94 %,
tension_de_cellule_elevee toujours no_alarm, plafond BMS inchange a 40 A.
Le parc reprend de la charge.
Patch rejouable : tools/patch-20260906c-tension-de-charge-52v4.mjs. L'Auto
Charge Manager entre au passage dans le depot :
workflows/victron-auto-charge-manager-v1.2.json — troisieme workflow
structurant, il n'etait suivi nulle part. Il n'a pas de suite de tests.
Rapport de nuit par courriel — et pourquoi il ne vit pas dans ma session
Le proprietaire le lit en voiture, en se le faisant lire a voix haute. D'ou la forme : pas de tableau, pas de liste a tirets, phrases courtes, et les nombres ecrits comme on les prononce (« 4,2 kilowattheures », virgule et non point).
⚠️ Ce qui ne marche PAS pour surveiller la nuit : CronCreate est en
memoire dans la session Claude, ne se declenche que si le REPL est vivant et
inactif, et disparait avec la session. Un sous-agent meurt avec elle aussi. Le
crontab pose le 05/09 est sur le PC Ubuntu du proprietaire et ne lance que le
releve des cellules.
Et il n'y a rien a surveiller : le service d'historisation tourne toutes les
soixante secondes sur une machine allumee en permanence et enregistre deja tout.
Ce qui manquait n'etait pas de la surveillance mais une LECTURE. Le rapport vit
donc DANS le service (tools/rapport-nuit.mjs, appele par
historise-evcs.mjs), envoye a 6 h 47 avec rattrapage jusqu'a 10 h.
Idempotence par une ligne evenement de genre rapport — aucun fichier
d'etat, et redemarrer le conteneur ne renvoie pas le courriel.
Canal : le webhook n8n deja existant et actif « Webhook Email solar v2 »
(https://n8n.airtria.pro/webhook/solar-email, id vfPbPFtfZt58HOt2), en GET
avec to, subject, message. Aucun identifiant nouveau : il porte un compte
SMTP configure. ⚠️ Il declare POST dans ses parametres mais n'est enregistre
qu'en GET — un POST renvoie 404. Teste de bout en bout le 06/09 a 15:10,
execution n8n en succes.
Chemins ecartes en route, pour ne pas les re-explorer : notify de Home
Assistant n'a que les applications mobiles, aucun SMTP ; synodsmnotify refuse
le texte libre (« is neither mail string key nor i18n format ») ;
notification_send se bloque ; sendmail de DSM rend 0 sans journal, donc sans
preuve de remise ; et DSM n'a pas node, donc un cron hote ne peut rien
executer.
Le rapport dit : les fermetures du contacteur et l'energie importee, la trajectoire du SOC avec l'heure du minimum et l'alerte si le plancher a ete touche, les sessions de charge par vehicule, l'injection residuelle — avec un jugement conditionne a la POINTE et non a l'energie, parce qu'une pointe a 2 819 W pendant deux minutes n'a rien d'anodin meme si le total reste faible — et les incidents techniques.
Suite de tests pour l'Auto Charge Manager (0 -> 20 scenarios)
Troisieme workflow structurant, et le seul sans filet : il decide ce que la BATTERIE a le droit de prendre (limites DVCC en courant et en tension), donc c'est lui qui plafonnait le parc a 93 % — et il n'etait meme pas suivi dans le depot avant ce matin.
tests/golden-acm/, sur le patron de tests/golden-hysteresis/ : le VRAI code
du node « Compute Charge Targets » execute dans une sandbox vm avec horloge
epinglee, sur un snapshot reel obtenu en rendant le gabarit du node
« HA Snapshot via Template » contre Home Assistant (20 champs). Pas de fixture
invente : c'est la lecon du 24/08, ou le fixture de l'hysteresis portait les
valeurs de REPLI du code (22/35) au lieu des seuils de production (20/30) et
masquait un bug de bande.
Les vingt scenarios suivent l'ordre reel des branches — automatisation desactivee, secours SOC < 20 hors HC, takeover EVCS, hiver+HC, ete+HC, HC midi, fenetre jour, hors fenetre, puis l'override qui se superpose a tout :
- gardes : automatisation OFF (reglages conserves), secours a SOC 18 ;
- takeover : recharge vers la cible, maintien a 2 A (le releve reel du 06/09 a 05:06), bornage d'une cible aberrante a 95 %, et le contrefactuel « takeover demande mais contacteur OUVERT » qui ne doit pas mordre ;
- nuit : paire hiver sombre / hiver ensoleille (cibles 80 et 40 selon la bande de prevision), paire ete secours / ete repos autour du seuil de 22 % ;
- midi : rattrapage reseau, et son contrefactuel a PV 2 000 W ou la garde
pvPowerW < 1500doit couper le rattrapage ; - jour : boost, mid, topoff a 52,4 V (le correctif du jour), et le hold au-dessus de 95 % qui redescend a 50,6 V — on atteint le haut, on n'y sejourne pas ;
- override actif contre override expire ;
- garde de publication : cible identique a l'existant, on n'ecrit rien.
Preuve par mutation, cinq directions, chacune faisant tomber exactement UN scenario, different a chaque fois : tension de topoff ramenee a 51,3 · bornage de la cible de takeover retire · secours SOC critique supprime · garde PV du rattrapage midi relachee · echeance de l'override ignoree. Restauration -> 20/20.
Cablee dans tools/n8n-push.mjs (isAcmWorkflow), bloquante au push comme les
trois autres suites. Verifie : le push de l'ACM execute bien les 20 scenarios
avant le PUT.
Suivre la conversation depuis le telephone
claude --remote-control [nom] est un drapeau de DEMARRAGE : une session deja
lancee ne peut pas etre rattachee apres coup. Il existe aussi
claude --teleport [session]. ListAgents confirme qu'une session Remote
Control existe deja sur le compte (« Configurer alertes electriques pour demo »,
hors ligne) — le mecanisme est donc en place cote compte.
Pour une session pilotable depuis le telephone, il faut donc la demarrer ainsi
sur le poste, dans tmux pour qu'elle survive a la fermeture du terminal :
tmux new -s evcs 'claude --remote-control "EVCS"'.
⚠️ La session courante n'a PAS ete demarree avec ce drapeau : elle ne sera pas joignable depuis le telephone. Et une session Claude, meme pilotable a distance, reste un poste de travail — ce n'est pas elle qui doit porter la surveillance de nuit, d'ou le rapport par courriel loge dans le service d'historisation.
La sauvegarde « Synology NAS 1 » echoue depuis au moins TROIS MOIS
Signalee par une notification DSM apercue dans une capture. Faits, lus dans le courriel d'echec :
Tache de sauvegarde : Synology NAS 1
Destination : 10.10.10.22 / ARCHIVEFAMILLE / ARCHIVE_1_PHOTOS_2008_2015.hbk
Duree : 0 Second
Duree zero : la tache n'a pas pu demarrer, ce n'est pas une erreur de
donnees. Verifie en SSH sur 10.10.10.22 : le dossier partage
ARCHIVEFAMILLE n'existe pas — ni en clair, ni comme partage chiffre, ni
dans la configuration Samba. Les partages reels y sont photo, video,
FILMS, PLONGEES, KEVIN, NetBackup, docker, projects...
La tache jumelle « Synology NAS 2 » vise 10.10.10.22 / video / ARCHIVE_1_VIDEO.hbk et reussit : l'hote de destination va bien, c'est le
dossier qui a disparu.
Anciennete : dix notifications d'echec retrouvees, zero reussite, la plus ancienne du 06/06/2026. ⚠️ Chiffre corrige apres lecture du journal Hyper Backup (voir plus bas) : la derniere sauvegarde reussie date du 24/08/2025, soit plus d'un an, et non trois mois — les courriels conserves ne remontaient simplement pas plus loin. L'alerte quotidienne partait dans une boite a 17 600 messages non lus.
⚠️ Acces SSH a 10.10.10.50 (ARCHIVE), etat au 06/09/2026. Le compte est
thierry (donne par le proprietaire, et confirme par l'entree
synology_dsm de Home Assistant : hote 10.10.10.50, port DSM 5002,
utilisateur thierry — les deux autres NAS utilisent TMZF5455). Le port SSH
22 repond, mais l'authentification par cle est refusee (Permission denied (publickey,password)) pour tous les comptes essayes, alors que le
proprietaire dit avoir ajoute la cle. Causes DSM habituelles, dans l'ordre de
probabilite : le repertoire personnel est en 0777 (DSM le cree ainsi) et
StrictModes de sshd le refuse ; le compte n'appartient pas au groupe
administrators, seul autorise a ouvrir une session SSH sur DSM ; le service
Repertoire personnel (User Home) est desactive donc ~ n'existe pas.
Verification et reparation, a lancer par le proprietaire depuis son terminal :
ssh thierry@10.10.10.50 'mkdir -p ~/.ssh; \
echo "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHWfzf2109Rsv9jt2P592NgcK+7qR5teFULPgQNleoHj tmzf5455@yd-5CD8345759" >> ~/.ssh/authorized_keys; \
chmod 700 ~/.ssh; chmod 600 ~/.ssh/authorized_keys; chmod 755 "$HOME"; \
id; ls -ld "$HOME" ~/.ssh ~/.ssh/authorized_keys'
Le chmod 755 sur le repertoire personnel est le point le plus souvent oublie :
sans lui sshd ignore silencieusement le fichier de cles, ce qui explique
exactement le symptome « j'ai ajoute la cle et ca ne marche pas ».
La tache « Synology NAS 1 », lue dans sa configuration (06/09/2026)
Acces SSH a 10.10.10.50 obtenu (voir ci-dessus), /usr/syno/etc/synobackup.conf
lu directement. Les trois taches de ce NAS :
| tache | source (sur .50) | destination (sur .22) | depot | etat |
|---|---|---|---|---|
| WEB petit sino vers gros | /web |
partage web |
ARCHIVE_2.hbk |
depot fige au 25/12/2019 |
| Synology NAS 1 | /photo, 399 Go |
partage ARCHIVEFAMILLE |
ARCHIVE_1_PHOTOS_2008_2015.hbk |
echoue depuis ≥ 06/06/2026 |
| Synology NAS 2 | /video |
partage video |
ARCHIVE_1_VIDEO.hbk |
reussit, depot du jour |
Details de la tache en echec : creee le 26/08/2018, compte TMZF5455,
port 6281, trans_type=image_remote, rotation activee, chiffrement
desactive.
Le partage ARCHIVEFAMILLE n'existe pas sur 10.10.10.22 — verifie a la
racine des deux volumes. Et aucun depot de photos n'existe nulle part sur
ce NAS : find /volume* -name "*.hbk" ne rend que les trois depots ci-dessus,
dont aucun n'est celui des photos. La duree d'execution nulle relevee dans le
courriel d'echec s'explique entierement : Hyper Backup ne trouve pas le partage
de destination et s'arrete avant d'ouvrir le depot.
Correction retenue : recreer un dossier partage nomme exactement
ARCHIVEFAMILLE sur 10.10.10.22, sur /volume2. La tache existante repart
alors sans etre touchee — c'est la voie la moins invasive, DSM 7 ne permettant
pas de changer la destination d'une tache Hyper Backup existante (il faudrait
la recreer, et perdre son historique de versions).
Place : /volume2 a 9,8 To libres sur 11, /volume1 seulement 850 Go sur
1,8 To. Les 399 Go du premier passage tiennent dans les deux, mais /volume2
laisse de la marge pour la rotation des versions.
⚠️ Le premier passage sera une sauvegarde COMPLETE de 399 Go, le depot n'existant plus. Prevoir plusieurs heures.
CORRECTION — recreer le partage etait necessaire, PAS suffisant
sudo libre obtenu sur 10.10.10.50 (procedure ci-dessous), donc
/var/log/synolog/synobackup.log enfin lisible. Il corrige deux choses que
j'avais affirmees.
1. L'echec ne date pas de trois mois mais de PLUS D'UN AN. Les courriels d'alerte ne remontaient qu'a juin 2026 ; le journal, lui, donne :
| date | evenement |
|---|---|
| 24/08/2025 03:06 | derniere sauvegarde REUSSIE — 112 818 fichiers |
| 06/09/2025 | premier echec, « The network connection is closed. » |
| 01/03/2026 | dernier echec de ce type |
| 07/03/2026 | l'erreur change : « The backup destination does not exist. [Path: ARCHIVEFAMILLE] » |
109 echecs enregistres. L'enchainement se lit tout seul : la connexion a
casse d'abord (septembre 2025), et le partage ARCHIVEFAMILLE a ete supprime
plus tard, vers debut mars 2026 — ce qui repond a la question ouverte du
paragraphe suivant.
2. Recreer le partage ne suffit pas a faire repartir la tache. Apres creation du partage, l'erreur a bien change — preuve que la destination est desormais jointe et authentifiee :
16:15:52 Failed to start backup task. (The destination file does not exist.)
[Path: /volume2/ARCHIVEFAMILLE/ARCHIVE_1_PHOTOS_2008_2015.hbk/Config/version_info.db not exist]
Une tache Hyper Backup existante exige de retrouver son depot et refuse de
le recreer : elle cherche <depot>.hbk/Config/version_info.db. Le depot ayant
disparu avec le partage, la tache est irrecuperable en l'etat. Mon
affirmation « la tache existante repart telle quelle » etait fausse.
Marche a suivre reelle : creer une NOUVELLE tache dans Hyper Backup sur
10.10.10.50 — Sauvegarde de donnees → serveur Synology distant → 10.10.10.22,
compte TMZF5455, partage ARCHIVEFAMILLE, depot
ARCHIVE_1_PHOTOS_2008_2015, source = dossier partage photo (399 Go) — puis
supprimer l'ancienne tache « Synology NAS 1 ». La creation d'une tache Hyper
Backup n'a pas d'equivalent en ligne de commande : elle se fait dans DSM.
L'historique de versions est perdu de toute facon, le depot n'existant plus.
⚠️ Piege a retenir : synobackup --backup <id> --type image rend
code 0 et une sortie vide meme quand il echoue, en root comme en simple
utilisateur. Le seul retour exploitable est
/var/log/synolog/synobackup.log.
Seconde tache cassee, decouverte au passage : « WEB petit sino vers gros »
Elle echoue tous les jours a 03:00, avec « The network connection is
closed. » — et enable_notify=true, donc elle alerte elle aussi. Configuration :
source /web du .50, destination 10.10.10.22 partage web, depot
ARCHIVE_2.hbk, port 873 (rsync en clair) la ou les deux autres taches
utilisent 6281.
Ce n'est PAS un probleme de port : depuis le .50, 873 et 6281 repondent tous
les deux, et sur le .22 netstat montre rsync en ecoute sur 873 et
img_backupd sur 6281. Le depot /volume1/web/ARCHIVE_2.hbk est fige au
25/12/2019. Cause a instruire separement (authentification rsync, ou depot
inutilisable).
RESOLU le 06/09/2026 a 22:53 — nouvelle tache, sauvegarde en cours
Journal Hyper Backup du .50 :
18:54:20 thierry: Backup task [Synology NAS 1] was removed.
20:59:51 thierry: Backup task [WEB petit sino vers gros] was removed.
22:53:04 thierry: Setting of backup task [Synology NAS 1 ARCHIVE FAMILLE] was created
to backup to [TMZF5455@10.10.10.22::ARCHIVEFAMILLE/ARCHIVE_1_PHOTOS_2008_2015.hbk]
22:53:32 SYSTEM: [Synology NAS 1 ARCHIVE FAMILLE] Backup task started.
Verifie cote destination : le depot
/volume2/ARCHIVEFAMILLE/ARCHIVE_1_PHOTOS_2008_2015.hbk existe, appartient a
TMZF5455, et grossit (395 Mo puis 604 Mo a une minute d'intervalle).
Aucune erreur depuis le demarrage. La seconde tache cassee
(« WEB petit sino vers gros ») a ete supprimee plutot que reparee — decision du
proprietaire ; le depot /volume1/web/ARCHIVE_2.hbk de 2019 reste sur le .22
et peut etre efface si /web n'a plus de valeur.
Ne restent donc que deux taches sur le .50 : task_3 (« Synology NAS 2 »,
/video → partage video) et task_4 (la nouvelle).
⚠️ La nouvelle tache sauvegarde ["/photo","/video"], alors que l'ancienne
ne prenait que /photo. /video est deja sauvegarde par « Synology NAS 2 »
dans un depot de 455 Go sur /volume1/video du .22. Le premier passage
copiera donc ~399 Go de photos plus ~455 Go de videos en double, soit
~854 Go. L'espace ne manque pas (9,8 To libres sur /volume2) mais c'est
plusieurs heures et un demi-tera pour rien si l'inclusion de /video etait une
case cochee par erreur. A trancher par le proprietaire — se corrige dans les
parametres de la tache.
Partage recree le 06/09/2026 a 16:14
/volume2/ARCHIVEFAMILLE existe. Verifie, cote destination :
- chemin et type conformes (
synoshare --get) ; - ACL identique a celle du partage
videodont la tache reussit :group:administrators:allow:rwxpdDaARWc--— et le compte de la tache,TMZF5455, est administrateur. Les droits ne bloqueront pas ; - port 6281 joignable depuis 10.10.10.50 (teste) ;
- 9,8 To libres sur
/volume2.
⚠️ Le lancement en ligne de commande ne fonctionne PAS depuis mon acces.
/usr/syno/bin/synobackup --backup 2 --type image rend code 0 et une sortie
vide, sans rien demarrer : aucun processus, --is-backup-restore-running
repond « pas en cours », et le depot n'apparait pas cote destination. La
commande exige root et echoue silencieusement — piege a connaitre, le code de
retour ne signale rien.
⚠️ sudo demande un mot de passe sur 10.10.10.50, contrairement a
10.10.10.22 ou il est libre. Consequence : ni le lancement de la tache ni la
lecture de /var/log/synolog/ et /var/log/messages ne me sont accessibles
sur ce NAS.
Lancement a faire par le proprietaire : Hyper Backup → tache « Synology NAS 1 » → Sauvegarder maintenant, qui donne en prime la progression des 399 Go.
Pourquoi sudo est libre sur 10.10.10.22 et pas sur 10.10.10.50
Les deux NAS ne tournent pas la meme version : 10.10.10.22 est en DSM 7.3.2,
10.10.10.50 en DSM 6.2.4 (fin de support Synology). Sur le .22,
/etc/sudoers porte en ligne 11 %administrators ALL=NOPASSWD: ALL, ce que
DSM 6 ne pose pas.
⚠️ visudo n'existe nulle part sur 10.10.10.50 (verifie par find), alors
que sudo lui-meme est en 1.9.5p2. Il n'y a donc aucune validation
syntaxique possible avant d'appliquer, et une erreur dans sudoers verrouille
sudo entierement. Garder une session root ouverte pendant la manipulation
n'est pas une precaution de style, c'est le seul filet.
Procedure (a lancer par le proprietaire, DSM 6.2.4) :
# terminal 1 — NE PAS FERMER avant la validation
ssh thierry@10.10.10.50
sudo -i # mot de passe DSM
cp -a /etc/sudoers /etc/sudoers.bak.$(date +%Y%m%d-%H%M%S)
grep -c '^#includedir /etc/sudoers.d' /etc/sudoers
Si la reponse est 1 (repertoire inclus, a preferer : le fichier survit aux
mises a jour DSM) :
echo 'thierry ALL=(ALL) NOPASSWD: ALL' > /etc/sudoers.d/thierry-nopasswd
chown root:root /etc/sudoers.d/thierry-nopasswd
chmod 0440 /etc/sudoers.d/thierry-nopasswd
Si la reponse est 0 :
echo 'thierry ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers
Validation depuis un second terminal, la session root toujours ouverte :
ssh thierry@10.10.10.50 'sudo -n id' # doit rendre uid=0(root)
En cas d'erreur de syntaxe, revenir au terminal 1 (toujours root) :
rm -f /etc/sudoers.d/thierry-nopasswd et/ou restaurer la sauvegarde
/etc/sudoers.bak.*.
Portee retenue : la regle ne vise que thierry, pas tout le groupe
administrators — plus etroit que ce qui est en place sur le .22. Contrepartie
assumee, identique a celle deja acceptee sur le .22 : toute cle SSH installee
pour ce compte devient root sans obstacle.
⚠️ Question ouverte : pourquoi ce partage a disparu de 10.10.10.22. Il existait
forcement en 2018 puisque la tache a tourne. A rapprocher d'une eventuelle
reorganisation des volumes — /volume2/video_1/ARCHIVE_1_VIDEO.hbk (fige au
05/03/2026) est un depot double de celui de /volume1/video, ce qui ressemble
a une migration de volume laissee en plan.
Les annotations Grafana etaient devenues illisibles
Sept jours d'historique produisaient une foret de pointilles orange masquant les
courbes. Deux corrections dans tools/gen-grafana-dashboards.py :
enable: False— les evenements deviennent OPT-IN. Le bouton « Evenements » en tete de tableau les rallume quand on veut le contexte d'un incident. C'est ce bouton qui etait allume par defaut.- Le genre
bilanest ecarte de l'annotation : a lui seul 26 % des lignes (36 sur 137), presque toutes des bascules de « production bridee » qui changent plusieurs fois par heure. Un trait vertical doit marquer ce qui ARRIVE — bascule du contacteur, vehicule branche, changement de mode ACM, redemarrage, intervention manuelle — pas un drapeau qui clignote.
Au passage : les deux panneaux qui COMPARENT les modules entre eux affichaient huit courbes dont quatre mortes. Le releveur lit aussi les modules isoles tant qu'ils sont sous tension ; le pack ancien a cesse d'emettre le 05/09 mais restait trace sur une fenetre de sept jours. Filtre pose : « module ayant emis dans les deux dernieres heures » — un critere d'activite, pas un prefixe de code-barres, donc rebrancher un pack le fait revenir tout seul. 62/62 requetes toujours valides.
La marge de 50 % de cette nuit, verifiee
Question du proprietaire : est-elle justifiee pour un demi-parc ? Mesure de 05:05 (fin de la recharge, SOC 55) a 08:45 (croisement PV) :
| consommation maison | 2,57 kWh |
| production PV sur la periode | 0,35 kWh |
| energie reellement a couvrir | 2,22 kWh |
| ce que le bilan avait reserve (49,4 % - plancher 22 %) | 2,65 kWh |
| ce que la batterie a effectivement fourni | 1,00 kWh |
Marge de 19 % entre ce qui etait reserve et ce qu'il fallait : c'est une assurance, pas un gaspillage. La batterie n'a fourni que 1,00 kWh parce que le reseau a porte la maison de 05:05 a 06:47 — mais la reserve devait couvrir le cas ou le contacteur disparait a 05:05, et dans ce cas les 2,22 kWh etaient tous a sa charge. Sur un parc de quatre modules (7,1 kWh utiles de 100 a 22 %), 2,65 kWh representent 37 % du parc. Justifie.
Reste ouvert
- Detecteur de gel dans le service d'historisation : une grandeur qui ne bouge pas alors qu'une autre du meme bilan bouge = alerte. Non fait.
grid_soc_on/grid_soc_off(20/30) restent des niveaux fixes : c'est le dernier consommateur du bilan qui n'est pas cable (§11.8.A).
Document mis a jour le : 06/09/2026 (~13:10) — relachement du contacteur sur une duree gagee sur le bilan, golden-hysteresis 20/20, deploye
58. Session du 07/09/2026 — La borne annonce « charge terminee » apres 0,10 kWh
Symptome
JEEP branchee a 14:26, immobile jusqu'a 16:29 en plein soleil. Le controleur
affichait day_vehicle_full_idle (P10, « vehicule plein en journee »), source
Bloque.
Ce que la borne a reellement declare
sensor.evcs_hq2239dnt7p_status, entree dans l'historisation InfluxDB la veille
— c'est ce capteur qui tranche, et il n'etait pas lisible avant le 06/09 :
14:25:12 waiting_for_start
14:25:32 start_charging
14:25:36 charging -> 1239 puis 1577 W, 6 -> 8 A
14:28:18 charged <-- apres 2 min 42 s et 0,10 kWh
14:28:30 waiting_for_start
(deux heures d'immobilite : l'etat collant tient, comme concu)
16:29:02 disconnected <-- cable branche, liaison perdue
16:29:06 waiting_for_start
16:29:31 start_charging
16:29:35 charged <-- QUATRE SECONDES apres le demarrage
16:29:55 disconnected
16:29:59 waiting_for_start
16:30:03 start_charging
16:30:07 charging -> tient, 1947 W a 10 A, en montee
Le controleur n'a rien bloque : il a cru la borne. Fix D (§17) et P10 (§22)
traitent status == charged + puissance nulle comme une fin de charge, et
latchent un etat collant jusqu'au debranchement. C'est exactement ce qui a ete
demande. Le signal d'entree, lui, etait faux.
Un charged quatre secondes apres start_charging ne peut pas etre vrai. Et
les deux disconnected cable branche designent une rupture du signal pilote —
piste materielle a instruire en premier : connecteur cote vehicule et cote
borne, verrouillage de la prise, cable. Aucune alarme n'est publiee par la
borne, aucune alarme VE.Bus active.
Ce qui a leve le collant : PAS le selecteur de vehicule
Le proprietaire a change input_select.victron_evcs_vehicle de VW a JEEP a
16:28:32, et la charge est repartie 90 s plus tard. Coincidence, pas cause :
dayVehicleFullIdle exige parsed.evcsVehicleConnected, et c'est le
disconnected de 16:29:02 qui a fait tomber cette condition. Le rythme des
transitions qui suivent (4 s, 20 s) exclut une manipulation humaine.
⚠️ Ne pas en conclure que corriger le selecteur debloque une charge.
Comparaison avec la veille (VW, 06/09) — le phenomene n'est pas tout neuf
La VW a subi deux interruptions (waiting_for_start a 15:20:35 et 15:52:35)
mais elle est repartie seule en une trentaine de secondes a chaque fois, et
a livre 4,30 kWh ; son charged de 16:06:42 est arrive apres 1 h 32 de charge,
donc legitime. Les interruptions existaient donc deja ; ce qui est nouveau
aujourd'hui, c'est le charged premature qui verrouille le systeme derriere.
Defaut de conception a corriger (PROPOSE, non deploye)
Le controleur traite identiquement trois situations que l'energie de session separe sans ambiguite :
| situation | energie livree | duree | verdict |
|---|---|---|---|
| vehicule plein rebranche | 0 kWh | — | charged legitime, collant justifie |
| interruption anormale | 0,10 kWh | 2-4 min | doit etre reessaye |
| vraie fin de charge | 4,30 kWh | 1 h 32 | charged legitime |
La distinction ne demande aucune constante inventee : la borne publie
sensor.evcs_hq2239dnt7p_last_session_energy et _last_session_time, toutes
deux historisees depuis le 06/09. Un charged qui suit une charge REELLE mais
dont la session n'a livre qu'une energie derisoire n'est pas une fin de charge,
c'est une interruption — et le collant ne doit pas se poser.
⚠️ Le cas « 0 kWh livre » doit rester collant : c'est le cas P10 d'origine
(VW pleine rebranchee, 09/08).
Cout de l'episode
Deux heures d'immobilite (14:28 → 16:29) un jour a 2,5 kW de production disponible et batterie a 96-97 %, donc sur de la ressource qui etait de toute facon refusee. Le cout n'est pas en energie achetee mais en energie solaire non captee par le vehicule.
Document mis a jour le : 07/09/2026 — charged premature de la borne, piste materielle a instruire, correctif controleur propose
59. Session du 07/09/2026 — Fix H : un statut « charged » doit etre credible
La mesure qui a decide de la conception
Premiere idee : distinguer une vraie fin de charge d'une interruption par la FORME de la coupure — une batterie qui se remplit decroit, une interruption coupe net. Mesure a la seconde des deux cas, elle est morte sur place :
| T-4 s | T-3 s | T-2 s | T-1 s | T | |
|---|---|---|---|---|---|
| VW 06/09 16:06:42, vraie fin (4,30 kWh, 1 h 32) | 1534 W | 550 | 82 | 11 | 0 |
| JEEP 07/09 14:28:18, faux positif (0,10 kWh, 2 min 42 s) | 1626 W | 741 | 100 | 13 | 0 |
Les deux courbes sont superposables. Aucun signal electrique instantane ne separe les deux cas — ni la puissance, ni sa pente. Ne pas reessayer cette piste.
Ce qui les separe est la DUREE de la session. Et le systeme porte deja la
grandeur qui dit ce qu'est une charge etablie : min_run_minutes, qui vaut
29 minutes en production (le 3 qu'on lit dans INPUT_PARSE.js n'est que le
repli du code — meme piege qu'au 24/08 avec les seuils 22/35 de l'hysteresis).
Le correctif
Dans WINDOWS_SOURCEV2.js, evcsStatusCharged gagne une condition de
credibilite. Trois formes de « charged », dont une seule est refusee :
| energie livree par la session | duree | verdict |
|---|---|---|
| nulle | — | vehicule plein a l'arrivee → collant (cas P10 d'origine) |
> 0 et session < min_run_minutes |
courte | charge INTERROMPUE → on ne latche pas |
> 0 et session >= min_run_minutes |
longue | charge menee a terme → collant |
Aucune constante nouvelle : min_run_minutes existe, et l'energie de session
est publiee par la borne.
Pas d'oscillateur : une session interrompue redemarre a energie nulle, donc un second « charged » immediat retombe dans le premier cas et latche. La convergence est garantie en une tentative supplementaire.
Chaine technique
HA_SNAPSHOT_TEMPLATE:+evcs_session_energy_kwh←sensor.evcs_hq2239dnt7p_last_session_energy(gabarit re-rendu contre HA : 198 champs, JSON valide, aucun null) ;INPUT_PARSE:parsed.evcsSessionEnergyKwh;WINDOWS_SOURCEV2:chargedSessionEnergyKwh,chargedSessionEstablished,chargedCredible, tous trois exportes pour la tracabilite.
Repli : capteur absent → energie 0 → credible → comportement d'avant. C'est pourquoi les 80 scenarios existants sont inchanges (leur fixture ne porte pas le champ).
Golden 80 → 84
charged-interrompu-ne-latche-pas (le releve du 07/09 : 2 min 42 s, 0,10 kWh →
la charge CONTINUE a 7 A au lieu de se bloquer) · charged-session-etablie-latche
(contrefactuel de duree : 1 h 32, 4,30 kWh → collant) ·
charged-energie-nulle-latche (contrefactuel d'energie : 0 kWh → collant, cas
P10 preserve) · night-charged-interrompu-ne-latche-pas (la meme garde en
fenetre HC : la charge reseau continue).
Preuve par mutation, trois directions, chacune tombant sur un jeu different :
garde retiree → les 2 scenarios « interrompu » ; cas energie nulle rendu non
credible → 4 scenarios dont 3 EXISTANTS (day-vw-pleine-statut3,
night-vw-statut-charged, night-charged-en-session-active) ; duree ignoree →
le scenario « session etablie ». Restauration → 84/84.
Confirmation independante par le constructeur (Gmail, 07/09)
Le vehicule dispose de sa propre telematique (Uconnect) qui envoie un courriel
depuis uconnectservices@jeep.com. Le 07/09 a 14:29, soit une minute
apres le charged de la borne de 14:28:18 :
« RECHARGE ELECTRIQUE INTERROMPUE — Nous avons detecte que la recharge de votre Jeep a ete interrompue. Nous vous invitons a controler votre borne et le connecteur de recharge, et a reessayer. »
Le vehicule qualifie l'evenement d'INTERRUPTION, pas de fin de charge. Fix H traite donc le bon probleme, et la piste materielle est celle que le constructeur designe lui-meme (« votre borne et le connecteur »).
⚠️ Jeep distingue QUATRE evenements la ou la borne n'a qu'un statut : « RECHARGE ELECTRIQUE COMMENCEE » · « VOUS VOILA CHARGE A BLOC » (fin normale) · « RECHARGE ELECTRIQUE INTERROMPUE » · « RECHARGE INFRUCTUEUSE » (echec au demarrage). C'est exactement l'information qui manque au controleur.
Le defaut est CHRONIQUE, et anterieur a tout notre outillage
Releve dans la boite de reception, decembre 2025 → mars 2026 : au moins 12 « interrompue » (31/12, 04/01 ×2, 05/01, 14/01, 17/01, 23/01, 29/01, 15/02, 20/02, 08/03 ×2) et 6 « infructueuse » (17/01, 23/01, 27/01, 28/01, 23/02, 08/03). Deux journees typiques :
| 08/03/2026 | 23/01/2026 |
|---|---|
| 08:24 infructueuse | 12:53 commencee |
| 11:44 commencee | 13:15 infructueuse |
| 11:59 interrompue (15 min) | 14:01 commencee |
| 15:12 commencee | 15:34 interrompue |
| 17:11 interrompue |
Ce n'est donc pas une consequence de nos correctifs ni de la borne en firmware 2.08 : le vehicule signale des interruptions depuis au moins neuf mois. Ce que nos automates ont change, c'est le COUT d'une interruption (deux heures de blocage au lieu d'une reprise).
⚠️ Aucun courriel Jeep entre le 18/03/2026 et le 07/09/2026 dans l'index de recherche, alors que l'index couvre bien les jours recents (le rapport de nuit de 06:47 y figure). Trois lectures possibles, non tranchees : notifications Uconnect desactivees puis reactivees, JEEP non chargee a la maison sur cette periode, ou trou d'indexation. A verifier avec le proprietaire.
Piste : faire du courriel Jeep un signal du systeme (PROPOSE)
Ces courriels arrivent ~1 minute apres l'evenement et portent la qualification
que la borne ne sait pas donner. Chaine possible : filtre Gmail → transfert vers
un webhook n8n → input_text.victron_evcs_jeep_last_event → lu par
WINDOWS_SOURCEV2 (« interrompue » ⇒ ne jamais latcher le collant).
⚠️ Signal d'appoint, jamais de securite : latence de deux cycles, dependance
a Internet (or les coupures d'orage sont frequentes ici) et a un service cloud
tiers. Fix H doit rester la garde principale, qui elle est locale.
Ce que le correctif ne traite PAS
La cause premiere reste materielle et n'est pas instruite : deux
disconnected cable branche le 07/09 a 16:29:02 et 16:29:55, et un « charged »
quatre secondes apres start_charging. Fix H borne le cout d'un signal
faux ; il ne repare pas le signal. Connecteur, verrouillage et cable restent a
inspecter.
Document mis a jour le : 07/09/2026 — Fix H prepare (golden 84/84, gabarit valide contre HA), en attente de validation avant PUT
60. Session du 07/09/2026 — Tableau « La journee », et un report de valeur qui fabriquait de l'energie
Le tableau
evcs-journee (« La journee »), fenetre now/d — le jour civil en cours et
non now-24h, qui melangerait deux journees et rendrait le total de production
incomparable d'un jour a l'autre. Sept panneaux : le bilan depuis minuit
(production, maison, reseau, voiture, batterie chargee/rendue, autonomie),
l'energie heure par heure, les cinq flux sur un axe commun, le SOC contre le
SOC requis par le bilan, une frise source/decisions/censure, les charges du jour
et le journal des evenements.
L'autonomie se calcule sur la maison, pas sur la maison + voiture : sinon une grosse charge solaire du vehicule gonfle artificiellement le taux.
⚠️ Le defaut trouve en controlant les chiffres — il touchait TOUS les tableaux
Premiere version du bilan : 6,9 kWh pour la voiture, alors que le compteur
cumulatif de la borne et la table session_charge donnent tous deux 3,6 kWh.
Cause, identifiee au watt pres. Le report de la derniere valeur connue propageait la MOYENNE de la minute au lieu de sa derniere valeur. Le 07/09 a 14:28, la borne passe de 1577 W a 0 W dans la meme minute : cette minute vaut donc 788 W en moyenne, et cette moyenne etait ensuite maintenue pendant toutes les heures sans ecriture qui suivaient. 788 W x 60 min = 0,79 kWh fabriques sur la seule heure de 15 h, et le meme mecanisme sur 19 h et 20 h.
Le defaut ne se voyait ni sur le solaire ni sur la maison — ecrits en continu, donc jamais de long report. Il frappe les grandeurs intermittentes, au premier rang desquelles la borne, qui passe l'essentiel de la journee a zero.
Correctif applique aux trois constructeurs de requetes
(sql_grandeurs, ENERGIE_SQL, BILAN_BASE) : la minute observee garde sa
moyenne — juste pour l'energie consommee DANS cette minute — mais le report
vers les minutes suivantes propage
(array_agg(valeur ORDER BY ts DESC))[1], c'est-a-dire la derniere valeur
reellement lue. Aucun seuil, aucune peremption arbitraire.
Verification : apres correction, « Voiture » tombe a 3,6 kWh, egal au centieme a la somme des sessions mesurees au compteur de la borne. Les autres totaux sont inchanges (22,1 / 26,1 / 6,2), ce qui confirme que seul l'intermittent etait touche. 73/73 requetes valides.
⚠️ Consequence a garder en tete : les colonnes « Voiture » des tableaux « Le systeme » et « Les vehicules » etaient fausses depuis leur creation (04/09), dans le sens de la SURESTIMATION. Toute lecture d'energie vehicule anterieure au 07/09 doit etre reprise.
Deux pieges d'affichage, corriges apres lecture a l'ecran
Les deux panneaux du haut etaient vides au premier rendu, alors que
verifier-dashboards les donnait verts — troisieme rappel que ce controle
prouve l'execution d'une requete, pas son affichage.
FILTRE_VUEne se recopie pas d'un tableau a l'autre. Cette transformation ne garde que les colonnes nommees par la variable$vue, propre au tableau « Le systeme ». Sur un tableau qui n'a pas cette variable, elle filtre TOUT et Grafana affiche « No numeric fields found ». Retiree : ici on veut les quatre colonnes en permanence.champs="/.*/"est reserve aux etats d'ALARME, qui sont du texte. Sur des colonnes numeriques il empeche le rendu — sept « — » a la place des valeurs. La valeur juste est""(defaut), qui ne garde que les numeriques, comme le panneau « Sur la periode » du tableau des vehicules. Les unites (kWh, %) ont ete ajoutees en overrides dans la foulee : sans elles les chiffres etaient nus.
Rappel de methode, deuxieme fois en trois jours
verifier-dashboards.mjs prouve qu'une requete s'EXECUTE, pas qu'elle rend la
grandeur voulue — il annoncait 73/73 avec le chiffre faux. Le seul controle qui
attrape ce genre de defaut est de confronter le resultat a une source
independante : ici le compteur cumulatif de la borne et la table des sessions.
Meme lecon que le 04/09 (colonne « Excedent » integralement nulle, 19/19 vertes).
Document mis a jour le : 07/09/2026 — tableau « La journee », report de la derniere valeur (et non de la moyenne) dans les trois constructeurs de requetes
Acces a l'API Grafana — la cause des 401 etait mon extraction
⚠️ Rectification. J'avais conclu, et ecrit dans le message du commit
686c2ba, que le GRAFANA_ADMIN_PASSWORD du .env « n'est pas celui en
service » et que Grafana n'appliquait la variable qu'a la creation du compte.
C'est faux. Le proprietaire a verifie : c'est bien le mot de passe de sa
session.
La valeur est entre guillemets dans deploy/historisation/.env (18
caracteres bruts pour un mot de passe de 16). Mon extraction les transmettait
tels quels a curl, d'ou le 401. Retirer les guillemets encadrants suffit :
V=$(sudo sed -n 's/^GRAFANA_ADMIN_PASSWORD=//p' .env | tr -d '\r\n')
PW=$(printf '%s' "$V" | sed -e "s/^['\"]//" -e "s/['\"]$//")
⚠️ CORRECTION du 17/09/2026 : ce sont des apostrophes SIMPLES, pas des
guillemets doubles — verifie a l'octet (od -c sur le premier et le dernier
caractere). La commande ci-dessus a ete elargie aux deux formes ; celle que
j'avais ecrite le 08/09 ne retirait rien et rendait donc toujours 401.
⚠️ ET GRAFANA BLOQUE LE COMPTE APRES QUELQUES ECHECS :
too many consecutive incorrect login attempts for user - login for user temporarily blocked. En sondant le mot de passe le 17/09 j'ai verrouille
admin pour quelques minutes — le proprietaire ne pouvait plus se
connecter a son interface non plus. Ne jamais essayer un mot de passe en
boucle sur cette API : extraire, verifier la longueur, essayer UNE fois.
Consequence pratique : une fois le compte bloque, un mot de passe CORRECT est
lui aussi refuse, donc l'echec ne prouve plus rien.
Et l'API n'est pas necessaire pour verifier un tableau provisionne : le
fournisseur lit les FICHIERS montes (updateIntervalSeconds: 30), pas l'API.
Il suffit donc de controler le contenu du fichier vu de l'interieur du
conteneur et l'absence de ligne provision en erreur dans son journal.
L'API repond alors du premier coup. Interroger un panneau se fait par
/api/ds/query depuis l'interieur du conteneur evcs-grafana :
sudo /usr/local/bin/docker cp charge.json evcs-grafana:/tmp/pl.json
sudo /usr/local/bin/docker exec evcs-grafana curl -s -u "admin:$PW" \
-H "Content-Type: application/json" -X POST --data @/tmp/pl.json \
http://localhost:3000/api/ds/query
Le corps est {"from": <ms>, "to": <ms>, "queries": [<la target du panneau>]}.
C'est le seul moyen de voir ce que le SERVEUR renvoie au panneau — le
vérificateur de tableaux, lui, ne fait qu'executer la requete en direct sur la
base, ce qui masque exactement ce genre d'ecart.
⚠️ Le fichier .env est en 600 et ignore par git (.gitignore ligne 19) : il
n'est jamais parti dans le depot.
Document mis a jour le : 08/09/2026 — acces API Grafana retabli, rectification sur la cause des 401
61. Session du 08/09/2026 — La prevision se juge sur la date qu'elle decrit
Le symptome
Nuit du 07 au 08/09, 32 calculs du bilan sur 120 en statut « degrade », tous entre 00 h et 03 h, avec ce motif :
« Aucun croisement PV/conso sur l'horizon : 47,6 kWh a couvrir → SOC requis 100 %. ⚠ chiffre indicatif, pas une exigence datee. »
Le controleur exige statut = ok pour suivre le bilan (§39) : a ces moments-la
il retombait sur le palier de 35 %, d'ou une cible du matin qui oscillait entre
35, 78,8 et 85 % au lieu de decroitre proprement.
La panne, et ce qu'elle a revele
input_text.evcs_balance_forecast_updated s'arrete a 23:45 et ne reprend
qu'a 04:30 — 4 h 45. Pendant ce temps le workflow s'executait bien quatre
fois par heure en « success », parce que le noeud d'appel porte
neverError: true et onError: continueRegularOutput.
Reponse reelle de l'API pendant la fenetre : socket hang up. Et ce n'etait pas
que Forecast.Solar — le noeud Execute HA Service Forecast rendait 0 item
la nuit contre 1 en journee : Home Assistant lui aussi etait injoignable
depuis n8n, si bien que le message d'echec ne pouvait meme pas etre publie.
Deux autres workflows ont eu des erreurs dans la meme fenetre. Cause racine de
l'indisponibilite reseau non etablie ; ce n'est pas la sauvegarde de 399 Go,
qui s'est terminee le 07 a 12:04. Une seule occurrence en quatre jours.
⚠️ Ce que l'on controle, c'est la REACTION a cette panne, et elle etait
fautive. Les courbes sont publiees dans evcs_balance_forecast_today et
_tomorrow sans leur date. PARSE_FORECAST prend days[0] comme
« aujourd'hui » : vrai a l'instant de la publication, faux des qu'on passe
minuit sans rafraichir. A 23:45 « today » etait la courbe du 7 ; a 00:45 le
bilan l'a lue comme celle du 8, journee dont toutes les heures etaient passees
— d'ou « aucune production sur l'horizon ». En temps normal le rafraichissement
de minuit corrige en quinze minutes et personne ne voit rien.
Le correctif
Une courbe est utilisable si elle DECRIT le jour dont on a besoin, pas si elle est recente. Une prevision publiee a 23:45 pour le 8 reste exacte a 03:00 : la nuit, il n'y a rien a reactualiser.
BUILD_HA_CALLS_FORECASTpublieinput_text.evcs_balance_forecast_day0(la date de la courbe « today ») ;- le gabarit du bilan la lit ;
RESOURCE_ESTIMATEremplace la garde d'age de 60 min par une garde de date, avec trois cas :a_jour(lecture directe),decalee_d_un_jour(publication d'avant minuit : la courbe « tomorrow » EST celle du jour, et l'enveloppe mesuree reprend la main pour le lendemain),perimee(au-dela : repli enveloppe).
⚠️ REPLI : forecast_day0 absent ou illisible → ancienne garde d'age.
Tant que le helper n'existe pas cote Home Assistant, le comportement est
strictement inchange. C'est ce qui permet de deployer le code avant le helper.
Visibilite de la panne
Le rapport de nuit signale desormais un trou de plus de deux heures dans les
publications de prevision. Il lit la table prevision sur l'autre NAS :
c'est le seul temoin qui survit quand le .17 est injoignable. Verifie sur la
nuit ecoulee : la requete rend bien 285 minutes.
Tests 44 → 48
Les quatre scenarios partagent la meme heure — 00:45, l'instant exact du
decrochage — et les memes courbes : seule la date publiee change, ce qui les
rend probants.
prevision-decalee-apres-minuit (le cas reel : day0 = la veille → la courbe du
lendemain devient celle du jour, 30 kWh redeviennent visibles) ·
prevision-a-jour-lecture-directe (contrefactuel) ·
prevision-vieille-mais-du-bon-jour (publiee cinq heures plus tot mais datee du
jour : utilisable — l'ancienne garde d'age la refusait) ·
prevision-perimee-de-deux-jours (repli enveloppe).
Un attendu existant passe de 4 a 5 ecritures (forecast-perimetre-respecte) :
la date est publiee a cote des courbes, et le perimetre evcs_balance_* reste
respecte.
Preuve par mutation, quatre directions, chacune tombant sur un jeu different et chaque scenario ayant la sienne : decalage non corrige · cas decale non reconnu · retour a la garde d'age (3 scenarios) · courbe perimee utilisee quand meme.
Deploye le 08/09 a 09:25 — et deux rectifications sur le diagnostic
Helper cree, input_text.reload, les deux workflows pousses (48/48 avant
chaque PUT). Verifie en production : evcs_balance_forecast_day0 = 2026-09-08,
statut du bilan ok.
⚠️ RECTIFICATION 1 — Home Assistant n'etait PAS injoignable. J'avais
interprete « le noeud d'ecriture rend 0 item » comme un echec. C'est faux :
[[]] est la sortie NORMALE d'un input_text.set_value reussi. Preuves que HA
tournait : le service d'historisation, qui interroge HA depuis l'autre NAS, a
enregistre 300 a 414 mesures par heure sans interruption de 22 h a 06 h, et
le controleur EVCS n'a eu aucune erreur. La panne ne concernait que l'acces
Internet du workflow (socket hang up vers Forecast.Solar).
Le .17 n'a par ailleurs ni redemarre (49 jours d'uptime) ni perdu son
reseau physique (rien dans dmesg depuis le 2 septembre). La cause de
l'indisponibilite Internet reste non etablie ; deux autres workflows a appel
sortant ont eu des erreurs dans la meme fenetre, une seule occurrence en quatre
jours.
⚠️ RECTIFICATION 2 — le mecanisme d'alerte existant n'a JAMAIS fonctionne
En cherchant pourquoi 4 h 45 de panne n'avaient laisse aucune trace,
constat : zero message d'echec dans
input_text.evcs_balance_forecast_updated en quatre jours, alors que
BUILD_HA_CALLS_FORECAST en publie un a chaque echec depuis toujours.
Cause : le helper etait declare max: 32, et
« échec : réponse illisible ou sans résultat » fait 43 caracteres. Home
Assistant rejetait la valeur. Le slice(0, 250) du code ne pouvait pas le
savoir. Reproduit puis corrige en direct : a 32 le message est refuse, a 128 il
passe (verifie par ecriture puis restauration de la valeur legitime).
max porte a 128 (sauvegarde SAVE/evcs_energy_balance.yaml.bak2.*).
⚠️ Regle a retenir : un helper input_text qui peut recevoir un message
d'erreur doit etre dimensionne pour le message le plus long, pas pour la valeur
nominale. Ici la valeur nominale fait 11 caracteres et le message 43.
Ce qui reste ouvert
La cause de l'indisponibilite Internet de la nuit du 07 au 08/09. Une seule occurrence ; a instruire si elle se reproduit — le rapport de nuit la signalera desormais.
Historique : ce qu'il fallait faire pour deployer
- Creer le helper
input_text.evcs_balance_forecast_day0dans/mnt/ARCH3/docker/HA/packages/evcs_energy_balance.yaml, a cote deevcs_balance_forecast_today, puisinput_text.reload— aucun redemarrage. - Pousser les deux workflows :
evcs-solar-forecast-v1.json(publie la date) etevcs-energy-balance-v1.json(la lit). L'ordre n'a pas d'importance grace au repli.
Document mis a jour le : 08/09/2026 — prevision jugee sur sa date et non son age, tests 48/48, en attente de deploiement
62. Session du 11/09/2026 — Le credit solaire sur la consigne reseau, valide en reel
Le defaut
BOOST 28 A pose en plein soleil pour charger un vehicule electrique emprunte.
La consigne reseau vaut houseNonEvcsW + physicalCapA x acVoltageRef : elle ne
soustrait JAMAIS le solaire. Releve du 11/09 17:02 :
| consigne | 8260 W |
| PV | 121 W |
| reseau | 7697 W |
| batterie | 0 A, SOC 96 % |
L'ESS n'a qu'un moyen de prelever 8,2 kW quand le soleil en donne 4 : brider les MPPT. Le commentaire qui justifiait une consigne genereuse (« elle ne coute rien, l'ESS ne preleve que ce qu'il peut placer ») datait de la nuit du 01/09, ou la PV valait zero. En plein jour il est faux : la consigne EST ce qui evince le solaire.
Pourquoi soustraire la PV mesuree ne marche pas
Elle vaut 121 W a cause de cette consigne : mesure censuree, la soustraction
ne bouge rien, le systeme reste sur son mauvais equilibre. Le proprietaire l'a
tranche par l'experience en baissant la consigne A LA MAIN a 2280 W : PV
4758 W en 50 secondes, batterie +9 A. La ressource etait la. (Le controleur
a ensuite reecrit 700 W au cycle suivant, gridSetpointNeedsWrite ramenant
toute valeur hors bande — un reglage manuel ne tient pas.)
Le correctif
On DESCEND par paliers et on laisse la physique repondre, sur deux signaux deja lus et tous deux incensurables :
mpptLimitedCount— les chargeurs declarent eux-memes s'ils se BRIDENT ;batteryPolicy.batterySoftBreachNow— le courant batterie dit si on est descendu trop bas, via la politique par bande de SOC deja calibree (§35).
Pas de nouveau helper : la valeur courante vit dans le GX, l'etat etait deja persiste. Pas pendant la fenetre d'etablissement (§29).
⚠️ off NE COMPTE PAS comme bride. mpptHoldingBackCount (§37) confond
« je me retiens » et « je n'ai rien a donner ». La nuit les six chargeurs sont
off : les confondre ferait descendre la consigne au crepuscule — un
oscillateur de plus. D'ou mpptLimitedCount, qui ne compte que ce qui se
declare explicitement bride ; tout libelle inconnu retombe du cote prudent.
⚠️ PLANCHER : jamais sous la maison hors voiture. Garantie du 01/09 et du 04/09 — le reseau, quand le contacteur est ferme a notre demande, couvre la maison. On ne libere que la part VOITURE, celle qui evince le solaire.
Validation en reel (17:39-17:41)
| heure | consigne | PV | reseau | batterie | voiture |
|---|---|---|---|---|---|
| 17:38:54 (mode solaire) | 700 | 4 255 W | 574 W | −11 A | 20 A |
| 17:39:30 (BOOST, plafond) | 7 488 | 3 109 W | 4 731 W | +65 A | 20 A |
| 17:40:30 (apres descente) | 2 241 | — | — | — | 28 A |
| 17:41:03 | 2 241 | 7 074 W | 2 063 W | +36 A | 28 A |
Convergence prouvee : les six chargeurs sont repasses mppt_active, donc
solarResourceRefused devient faux et la descente s'arrete d'elle-meme. La
consigne est restee stable a 2 241 W. Aucun emballement.
Contre le meme BOOST avant correctif (17:02) : 5,6 kW de reseau remplaces par du solaire, a courant voiture identique.
Resultat contre-intuitif et important : la PV est plus haute en BOOST (7 074 W) qu'en mode solaire pur (4 255 W). La voiture a 6 kW est un PUITS plus grand que la voiture a 4 kW — sans injection, plus de preneur = plus de production revelee. Le BOOST corrige est donc strictement meilleur que le mode solaire : plus de courant pour la voiture ET plus de solaire capte.
Cout du transitoire : ~60 s au plafond pendant que le systeme cherche son niveau (la descente ne demarre qu'une fois les chargeurs effectivement brides). Negligeable, et auto-correcteur.
Golden 84 -> 89
consigne-credit-solaire-descend (le releve de 17:02) ·
consigne-credit-trackers-a-fond-pas-de-descente ·
consigne-credit-batterie-porte-remonte ·
consigne-credit-nuit-trackers-eteints (ANTI-OSCILLATEUR) ·
consigne-credit-plancher-maison.
Mutation en quatre directions — descente neutralisee / off recompte comme
bride / garde batterie retiree / plancher retire : chacune fait tomber un jeu
DIFFERENT, aucune garde n'est couverte par le test d'une autre.
⚠️ Piege rencontre : mes deux premieres redactions des scenarios nuit et
plancher passaient POUR LA MAUVAISE RAISON (consigne courante deja sous le
plancher dans un cas, pas minimal qui n'atteignait pas le plancher dans
l'autre). Les mutations M2 et M4 ne cassaient rien. Un scenario qui passe ne
prouve rien tant qu'une mutation ciblee ne l'a pas fait tomber.
Document mis a jour le : 11/09/2026 (~17:45) — credit solaire sur la consigne reseau, golden 89/89, valide en production
63. Session du 13/09/2026 — Le conseiller ne lisait pas mal : on lui parlait mal
Le harnais n avait pas suivi la specialisation
tools/campagne-conseiller.mjs affichait encore strategy et night_target_soc,
deux champs que l observateur n emet plus depuis le 12/09. Il « mesurait » donc
des undefined. Reecrit : il note desormais deux choses, et deux seulement.
- La gravite tombe-t-elle dans la fourchette attendue. Un observateur ne se juge pas a une valeur qu il propose mais a sa capacite a SEPARER un systeme qui derape d un systeme qui va bien. D ou des cas « incident » et des contrefactuels « systeme calme », notes separement : un modele qui repond toujours « moyenne » reussit les premiers et echoue les seconds.
- A-t-il NOMME la chose, par des mots-cles choisis HORS du vocabulaire du prompt. Le premier jeu comptait « 45 » comme preuve que le cas 2 etait compris — or 45 est le SOC, ecrit tel quel dans le prompt : le mot-cle etait satisfait par la recopie. Remplace.
Les cas ouverts ne sont pas notes : ils se lisent.
Quatre defauts du PROMPT, tous trouves par la campagne
Aucun n est un defaut de raisonnement du modele. On lui donnait des phrases ambigues et des chiffres non interpretables.
1. « Contacteur FERME ». Pour un relais, ferme veut dire qui laisse passer. Le modele l a lu comme coupe et a ecrit, reseau present : « verifier pourquoi le contacteur reste FERME alors que le bilan juge le reseau necessaire » — l inverse exact de la situation. La ligne dit desormais ce qu elle veut dire : « Reseau EDF : PRESENT, il peut alimenter la maison (contacteur ferme, AC-IN present) ».
2. Le signe de l excedent. Le prompt affichait « EXCEDENT SOLAIRE : 14 W ». Le modele a ecrit « excedent negatif (-14 W) ». Sur le cas 8 il a ecrit « negatif (-149 W) » a un passage et « positif de 149 W » au suivant, SUR LE MEME PROMPT : erreur de lecture, pas convention. Le signe est desormais nomme (« +149 W, il RESTE du solaire » / « -444 W, DEFICIT reel »).
3. Le vocabulaire interne. « Controleur : day_vehicle_full_idle » ne veut rien dire pour lui : il signalait « ressource refusee alors qu un vehicule est branche » sans voir que ce vehicule est PLEIN. Une table de traduction rend les etats lisibles en gardant l identifiant entre parentheses. Effet mesure sur ce cas : 0/5 → 5/5 au run suivant.
4. Un excedent calcule sous CENSURE ne veut rien dire. C est la doctrine de l installation (PV mesuree = min(ressource, conso + absorption)), et le prompt l ignorait : sur le BOOST du 11/09 il annoncait « -1134 W, DEFICIT : la batterie porte deja la maison » et trois passages sur cinq en concluaient « pas de soleil, donc le reseau est normal ». Les 121 W de PV sont bas PARCE QUE la consigne de 8260 W les evince. Quand des chargeurs se declarent brides, la ligne le dit et previent que la ressource reelle est plus haute.
Une grandeur derivee de plus, et le piege qu elle a cree
Cas 2 (SOC 45, requis 50,4, reseau present, consigne 700 W, maison 700 W) : le
modele repondait « aucune » en ecrivant « le reseau comble le manque » — alors
qu une consigne egale a la charge de la maison ne laisse rien pour remonter
la batterie. Meme mecanisme que le cas 1, ou il voit juste parce que le SOC est
a 20 et le courant a 0 : il voit la version flagrante et manque la version
discrete. On lui donne donc la soustraction, comme pour l excedent solaire :
consigne − maison hors voiture. Cas 2 : 0/5 → 5/5, deux runs de suite.
⚠️ Mais cette ligne a fait tomber le cas 7 de 5/5 a 2/5, et c est instructif. Sur le BOOST elle annoncait « il reste 7005 W pour RECHARGER la batterie » — presente comme rassurant, sur le cas meme ou cette consigne est la faute, et avec une batterie a 96 % qui n a rien a recharger. Corrige en faisant dire a la meme grandeur l inverse quand il n y a rien a recharger, la question « la batterie doit-elle monter ? » ayant deja une reponse calculee dans le systeme — le SOC requis par le bilan, donc aucun seuil nouveau : « la batterie n a PAS besoin de monter (SOC 96 %, bilan 62 %), donc ces 7005 W de demande reseau n ont aucune destination : ils ne peuvent que remplacer du solaire ».
TROIS defauts etaient les miens, pas ceux du modele
- Deux fixtures incoherentes : cas 5 et 6 portaient
vehicle_connected: onavec l etat controleur reste avehicle_not_connected. C est le conseiller qui l a signale, et il avait raison — mon scenario decrivait un etat que l installation ne peut pas prendre. - Un attendu qui contredisait le prompt : j exigeais « haute » sur le BOOST alors que l echelle de gravite ancre elle-meme cet incident sur « moyenne ». Le modele repondait moyenne 5 fois sur 5 : il appliquait ma consigne et je le notais contre elle.
- Un attendu « calme » sur le cas 6 (ressource refusee, vehicule branche a l arret) : signaler est le bon comportement d un observateur. Mon attendu venait de la decision du controleur, pas de ce qu il y a a voir.
⚠️ NE PAS REGLER SUR UN SEUL RUN DE CINQ PASSAGES
J ai passe une partie de la session a poursuivre du bruit. Avec cinq tirages, un
3/5 et un 5/5 ne se distinguent pas (erreur type ~0,22). Trois runs consecutifs
partageant les memes attendus donnent quinze passages par cas et une image
stable — c est ce qu il faut lire. scratchpad/agreger.mjs fait le cumul.
Resultats
Cinq runs de cinq passages. Les runs 2 a 4 partagent les memes attendus et se cumulent (quinze passages par cas) ; le run 5 mesure la configuration finale.
| cas | avant | cumul r2-r4 | final |
|---|---|---|---|
| oscillation du contacteur (12/09) | 5/5 | 15/15 | 5/5 |
| pointe de cuisson, parc a 3 A de sa limite | 2/5 | 15/15 | 5/5 |
| nuit bloquee, reseau present sans recharge | 5/5 | 15/15 | 5/5 |
| bilan degrade | 5/5 | 15/15 | 5/5 |
| SOC sous le requis, session marquee terminee | 1/5 | 10/15 | 5/5 |
| BOOST qui evince le solaire (11/09) | 0/5 | 9/15 | 5/5 |
| vehicule plein, ne pas relancer | 0/5 | 10/15 | — |
| nuit couverte, rien a faire (SILENCE) | 5/5 | 15/15 | 5/5 |
| contacteur stable 90 min (SILENCE) | 5/5 | 14/15 | 5/5 |
Final : incidents 39/40. Les deux cas de VRAI silence restent silencieux 5/5 tous runs confondus.
⚠️ Ce que le score « systeme calme » mesurait vraiment
Deux cas y sont tombes a 0/5 en fin de campagne — le soir en deficit et le vehicule plein — et il ne faut PAS les lire comme des fausses alertes. Leurs textes sont defendables : « le bilan dit reseau necessaire OUI mais le contacteur reste ouvert », « la ressource solaire est refusee faute de preneur, verifier si la Jeep peut encore absorber ». Mon etiquette « calme » repondait a la question le controleur a-t-il raison, alors que l observateur repond a y a-t-il quelque chose a voir. Ce sont deux questions differentes, et seuls les cas 3 et 16 (rien du tout a voir) relevent du vrai silence.
Question a trancher avec le proprietaire, parce qu elle lui appartient : une batterie pleine qui refuse du solaire avec une voiture branchee merite-t-elle un signalement toutes les quinze minutes ? C est lui qui lira les notifications.
Trace des observations (meme session)
Les helpers du conseiller sont ecrases toutes les quinze minutes : un
signalement grave emis a 3 h du matin avait disparu au reveil, et rien dans la
memoire du projet n en gardait trace. Table observation ajoutee au service d
historisation (clef = horodatage du marqueur du conseiller, donc idempotente),
lisible par node tools/enquete.mjs observations [heures] [gravite], qui ne
montre par defaut que ce qui a ete signale et compte a part les cycles calmes.
⚠️ CINQ HELPERS DU CONSEILLER SONT GELES, ET UN CABLAGE LES LIT ENCORE
Depuis la specialisation du 12/09 06:15, evcs_advisor_night_target_soc (55),
_current_max_a, _strategy, _ev_charge_tonight et _divergence ne sont
plus ecrits. Le cablage de la cible SOC matin (§20, §39) les lit toujours, et
ses quatre gardes passent : toggle, statut ok, fraicheur — qui porte sur
_last_run, frais puisque le conseiller tourne — et bornage [22, 90], que 55
traverse. Si le toggle evcs_advisor_apply_night_target est active, la cible de
la nuit devient un 55 fige d hier matin.
La garde de fraicheur ne protege donc pas ce qu elle croit proteger : elle date le RUN, pas la VALEUR.
RETIRE le 13/09/2026 sur decision du proprietaire. Les quatre parametres
d INPUT_PARSE, le bloc de WINDOWS_SOURCEV2, le passe-plat de BATTERY_CLOUD_LOCKS,
la trace (ADVISOR) de DECISION_ENGINEV2 et les quatre champs du gabarit de
snapshot. batteryMorningTargetBasePct vaut desormais simplement la regle de
prevision ; le cablage du BILAN (§39), lui, est intact et continue de ne pouvoir
que RELEVER cette cible.
parseAdvisorRunToMs est renommee parseRunStampToMs : elle sert encore au
bilan, qui publie le meme format, mais son nom designait un appelant disparu.
Golden 98 → 96 : les trois scenarios du cablage (applique / perime / toggle OFF)
sont remplaces par UN seul, advisor-cible-ignoree, qui pinne l ABSENCE de
cablage — toutes les conditions de l ancienne regle reunies, et la cible doit
rester celle des regles. Preuve par mutation : cablage restaure → ce scenario
tombe avec target=22% (ADVISOR) dans la raison ; retire → 96/96.
Gabarit re-rendu contre Home Assistant : 196 champs, JSON valide, aucun null,
plus aucun champ advisor.
Les fantomes sont retires aussi (meme jour)
Un helper que plus personne n ecrit, affiche comme une recommandation
courante, EST le piege — c est lui qui a rendu le cablage dangereux. Retires
du paquet evcs_advisor_shadow.yaml (sauvegarde SAVE/*.bak.20260913-123734)
et de la carte du tableau EVCS Master :
evcs_advisor_strategy · _ev_charge_tonight · _divergence ·
_night_target_soc · _current_max_a · et le toggle _apply_night_target.
Le conseiller relisait en plus SA PROPRE strategie precedente
(advisor_prev_strategy dans son snapshot, prevAdvisorStrategy dans
BUILD_PROMPT) : figee depuis le 12/09, et parsee sans jamais servir dans le
prompt. Retiree des deux.
Carte reecrite pour ce qu il fait desormais — gravite, ce qu il voit, ce qui
cloche, la piste et sa confiance — au lieu de « Recommandation vs reel ».
Apres input_boolean/text/number.reload : dix helpers restants, exactement
ceux que l observateur ecrit. Les compteurs ont survecu au rechargement
(8 cycles calmes, 0 signalement), ce qui valide au passage la suppression des
initial: du matin meme.
Document mis a jour le : 13/09/2026 — campagne conseiller : quatre defauts du prompt corriges, incidents 39/40, table observation
64. Session du 13/09/2026 — Le parc butait sur NOTRE plafond, pas sur le sien
La question du proprietaire
Le conseiller affichait « ressource refusee » avec 6 chargeurs sur 7 brides, et il a demande si c'etait une config restant a finir ou un choix delibere de charger plus lentement. La reponse se mesure.
La mesure (13/09, 10:39 -> 12:50)
| DVCC appliquee par l'ACM | 130 A (profil pv_boost, SOC < 80) |
| courant reellement pris par le parc | 114 a 127 A, soit 88 a 98 % du plafond |
| tension du parc | 50,6-50,7 V, plate |
| notre consigne de tension | 51,2 V (pv_mid) |
| ce que le BMS declare autoriser | 250 A et 53,2 V |
| ecart entre les 135 cellules | 2 mV (3,327 a 3,329 V) |
| modules bloquant la charge / alarmes | 0 / aucune |
La tension plate a 0,5 V sous notre propre consigne est le point decisif : il n'y avait aucune phase d'absorption. Le courant ne retombait pas de lui-meme comme le ferait un LiFePO4 en fin de charge — il butait sur le plafond. Ce n'etait donc ni le materiel, ni le BMS, ni un choix de charge lente : c'etait la table, calibree pour l'ANCIEN parc dont une cellule montait a 3 649 mV et faisait effondrer le courant de tous (§48-§53). Ces modules sont sortis le 12/09.
Defaut visible dans la table : la colonne des tensions n'etait pas monotone
51,5 → 51,2 → 52,4 → 50,6. Elle REDESCENDAIT entre 80 et 90 % avant de
remonter — reste du 06/09, ou seul pv_topoff avait ete releve parce que
c'etait lui qui plafonnait le parc a 93 %.
Deux changements (patch tools/patch-20260913-plafond-150a-et-pvmid-51v5.mjs)
pv_boost: 130 → 150 A. Un palier, pas une cible : 150 A reste a 60 % de ce que le BMS declare. On regarde ce qu'il libere et l'ecart entre cellules avant d'aller plus loin.pv_mid: 51,2 → 51,5 V, ce qui rend la colonne monotone.
NON touches : pv_mid.current_a (110 A), pv_topoff (regle le 06/09 sur
mesure), pv_hold (30 A / 50,6 V — delibere : on atteint le haut, on n'y
sejourne pas).
⚠️ L'effet du 150 A ne se verra que sous 80 % de SOC, donc a la charge du
MATIN : au-dessus, c'est pv_mid et son plafond de 110 A qui s'applique.
Golden ACM 20/20 apres mise a jour de 3 attendus
La suite a fait son travail : acm-jour-boost, acm-jour-mid et
acm-override-expire-sans-effet sont tombes exactement sur les valeurs
changees. Attendus mis a jour dans le meme commit, mesure en commentaire.
Verification en reel (13:15-13:21, apres le PUT de 13:11)
Consigne 51,5 V appliquee, tension_de_cellule_elevee = no_alarm, cellules
toujours dans 2 mV, courant 54-72 A (bien sous les 110 A de pv_mid).
Plus aucun chargeur ne se retenait (0/7 contre 6/7 une heure plus tot) et la
production a touche 10 116 W, le plus haut releve de la journee.
⚠️ A NE PAS SUR-INTERPRETER : la charge de la maison est passee de 3,9 a 5,9 kW
dans le meme intervalle, donc le 0/7 ne s'attribue pas proprement a la tension.
Le vrai test du 150 A est la matinee suivante, sous 80 % de SOC.
Correction de documentation trouvee en route
L'ACM tourne toutes les 5 MINUTES, pas toutes les 30 s comme l'annoncaient le manuel et MEMORY.md (executions verifiees : 11:00, 11:05, 11:10 UTC). C'est pourquoi une consigne poussee a 11:11 ne s'applique qu'a 11:15.
A surveiller demain matin
- Sous 80 % de SOC, DVCC a 150 A et courant reellement pris — s'approche-t-il du nouveau plafond, ou plafonne-t-il ailleurs ?
tension_minimale/maximale_de_la_cellule: l'ecart doit rester de quelques millivolts. S'il s'ouvre, on s'arrete a ce palier.- Nombre de chargeurs
voltage_current_limiteda charge maison comparable.
Suite immediate : pv_mid 110 → 130 A
Le premier patch n'avait releve que pv_boost, donc rien n'etait observable a
SOC 88. Table finale : 150 / 130 / 80 / 30, toujours decroissante.
Applique a 13:31 (cycle ACM de 11:30 UTC). Ce qui s'est passe dans la minute :
| 13:29:54 | 13:31:09 | |
|---|---|---|
| DVCC | 110 A | 130 A |
| courant pris | 90 A | 116 A |
| tension | 50,82 V | 50,94 V |
| maison | 6 159 W | 3 561 W |
⚠️ A NE PAS PRESENTER COMME UNE PREUVE PROPRE : la charge de la maison a chute de 2,6 kW dans le meme intervalle. Le courant n'a repris que 1,3 kW des 2,6 kW liberes, et le nombre de chargeurs brides est monte de 1/7 a 2/7 — donc l'essentiel de ce qui a ete libere n'a PAS ete absorbe.
Ce que le releve montre vraiment : dans la tranche 80-90, le plafond de courant
n'etait que marginalement contraignant. Le courant a plafonne vers 116 A sans
jamais approcher les 130 et la tension montait vers la consigne (50,82 →
51,07 V) : a ce niveau de SOC, c'est la TENSION qui contraint, pas le courant.
Puis a 13:35, SOC 93, l'ACM est passe en day_pv_topoff (80 A / 52,4 V) et
6 chargeurs sur 7 se sont brides — ecretage NORMAL de haut de charge, batterie
quasi pleine, a ne pas confondre avec le sujet.
Ou le plafond mordait vraiment : a 12:36-12:42, SOC 78-80, le courant etait
a 129 A pour une DVCC de 130 — colle dessus. C'est la tranche pv_boost, et
c'est donc la charge du MATIN qui dira ce que le 150 A libere.
Cellules a 2 mV d'ecart et aucune alarme sur toute la sequence.
65. Session du 13/09/2026 (soir) — Six sessions sur douze etaient mal etiquetees
Le declencheur
Une session voisine sur OpenClaw prepare un « concierge vehicule » : un agent
qui demande par WhatsApp quel vehicule est branche quand l'etiquette est
douteuse. En lui donnant les noms d'entites, j'ai verifie le selecteur.
input_select.victron_evcs_vehicle etait sur VW, inchange depuis le 07/09
20:49 — six jours. Onze sessions consecutives en avaient herite.
⚠️ vehicule_sur ne dit pas ce que son nom laisse croire
Les onze lignes affichaient vehicule_sur = true. En lisant le code :
vehicule_sur = vehicule_sur AND (nouveau = ancien) — le drapeau ne passe a
faux que si le selecteur change PENDANT une session. Un selecteur perime depuis
six jours reste « sur ». C'est le defaut de conception qui a rendu l'anomalie
invisible, et il est de mon fait.
Ce que la puissance revele, et qui corrige une doctrine
p_max_w sur les douze sessions :
3 606 a 3 784 W -> onze sessions, toutes a 20 A de consigne
6 360 W / 28 A -> UNE session, le 11/09 16:54
Cette derniere est une Megane electrique de service, utilisee pour aller a Rennes — 7,45 kWh attribues a la VW qui ne les a jamais pris. Le proprietaire depanne aussi des amis en charge : le vehicule invite est un cas recurrent, et il n'etait documente nulle part.
La doctrine « vehicules indiscernables » ne vaut donc QUE pour la VW et la
JEEP l'une par rapport a l'autre. Regle a sens unique : consigne_max_a >= 20
ET p_max_w > 4200 ⟹ invite, certain ; en dessous on ne sait rien, car un
invite a chargeur 16 A est identique a la VW. Toute question sur l'identite d'un
vehicule doit offrir « aucune des deux ».
⚠️ LA FRONTIERE D'UN BRANCHEMENT EST UN FAIT, PAS UN ECART DE TEMPS
J'ai d'abord reconstruit les groupes soiree+nuit par l'ecart entre sessions. Les ecarts reels valent 5 h 19 a 5 h 25 — le creux entre la fin du solaire et l'ouverture des heures creuses. Aucun seuil de duree ne les distingue d'une nuit entiere.
La table evenement enregistre les transitions de
binary_sensor.victron_evcs_vehicle_connected, en jsonb structure :
detail->>'entite', detail->>'avant', detail->>'apres'. Un branchement va
d'un apres='on' au apres='off' suivant. C'est un FAIT observe.
Les branchements reels de la semaine :
mar 08/09 19:39 -> mer 08:57 2 sessions
mer 09/09 15:55 -> jeu 09:05 3 SESSIONS
jeu 10/09 18:19 -> ven 16:27 2 sessions
ven 11/09 16:53 -> 18:27 1 session (Megane)
sam 12/09 11:42 -> 11:53 1 session
sam 12/09 11:55 -> en cours 1 session
Le mercredi est un triplet, et j'avais applique une correction incoherente : 15:55 en JEEP, 19:35 et 01:25 laissees en VW. Physiquement impossible — un seul point de charge, le cable n'a pas bouge. Le journal me l'a mis sous le nez.
La raison, que rien n'aurait pu deduire
Mot du proprietaire : « mercredi c'est typique du contexte humain pas logique du tout car ma femme a branche la JEEP et le soir quand mon fils est arrive il n'a pas debranche la JEEP pour brancher la VW a la place, donc le jeudi il a roule a l'essence ». Plus le contexte de la semaine : sa femme (JEEP) en conges, son fils (VW) en stage sans retour le midi — la routine documentee entierement inversee.
Il ajoute qu'il lui est plus facile de donner ces informations par WhatsApp que de venir sur un PC. C'est l'argument central de l'agent conversationnel, et il est meilleur que celui que je defendais (« le selecteur s'oublie ») : le systeme n'a aucun canal d'entree pour le contexte humain.
Etiquettes finales (reparation idempotente, 6 lignes)
5 VW, 6 JEEP, 1 MEGANE — contre 11 VW et 1 JEEP avant. Chaque predicat verifie en lecture avant ecriture : fenetre d'une minute, exactement une ligne visee, energies concordantes.
L'INVARIANT a retenir
Un cable, un vehicule : toutes les sessions d'un meme branchement portent la meme etiquette. C'est une propriete physique, independante de toute connaissance metier, qu'aucune evolution de regle ne peut invalider. Un groupe a etiquettes mixtes signale soit une frontiere fausse, soit une ligne fausse — les deux meritent une alerte. C'est ce controle qui aurait signale mon triplet le jour meme ; il est desormais au cahier des charges du concierge, en continu.
Contrat arrete avec la session OpenClaw
- Airtria publie dans SON espace de noms, ne touche aucun actionneur, et le
service d'historisation — proprietaire de
session_charge— lit et applique. - Un seul helper JSON,
session_debuten LISTE (ecritures sequentielles sur un helper unique perdraient une confirmation sur deux, mon service ne passant que toutes les 60 s). - La clef
debutporte les millisecondes : renvoyer la chaine verbatim, jointure sur ±1 s. input_text: declarermax: 255— le defaut est 100, et un rejet est SILENCIEUX (incidentforecast_updateddu 08/09).- Une fenetre d'analyse historique doit commencer sur un
on, jamais au milieu d'un branchement.
⚠️ evcs_lecture N'EST PAS EN LECTURE SEULE
Verifie en repondant : SELECT partout, plus INSERT sur evenement — octroi
delibere du 04/09 pour les annotations manuelles, mais que le README decrivait
a deux endroits comme « lecture seule », et que j'ai repete a un tiers. Ne pas
confier ce role a un agent externe : evenement alimente les annotations
Grafana, le rapport de nuit et l'attribution des transitions, et une ligne
etrangere y serait indiscernable des notres. Un role dedie airtria_ro, SELECT
sur session_charge et evenement uniquement. README corrige.
⚠️ MIS A JOUR LE 16/09/2026 — le role EXISTAIT DEJA, et j'ai affirme deux
fois dans la nuit du 16 qu'il restait a creer, y compris dans un message envoye
a la session OpenClaw. C'etait faux : il etait en place, avec exactement les
deux SELECT du contrat et aucun droit d'ecriture. Ce qui manquait n'etait pas le
role mais son mot de passe, qui ne figurait ni dans le .env du projet, ni
dans celui de la pile d'historisation, ni dans la configuration d'OpenClaw — un
identifiant que personne ne connaissait. Verifie avant d'agir : aucune reference
au role dans les variables du conteneur openclaw-gateway, donc rien ne
l'utilisait et un renouvellement etait sans risque.
Fait le 16/09 : mot de passe renouvele (range dans AIRTRIA_RO_PASSWORD du
.env du projet, ignore par git) et CONNECTION LIMIT 5 pose, pour qu'un agent
emballe ne puisse pas epuiser le pool. Verifie par 14 essais depuis le poste :
SELECT autorise sur session_charge et evenement, refuse sur les sept autres
tables, et refuse pour INSERT, UPDATE, DELETE, CREATE TABLE et la lecture de
pg_authid. Acces reseau confirme : pg_hba porte
host all all all scram-sha-256 et le port 5433 est joignable depuis
10.10.10.17, ou tourne OpenClaw.
⚠️ Le mot de passe ne doit PAS transiter par un message a la session OpenClaw : c'est au proprietaire de le lui remettre.
⚠️ ET LE RENOUVELLEMENT A CASSE LE CONCIERGE. Quelques minutes apres, la
session OpenClaw a repondu qu'elle EST codee pour se connecter en airtria_ro
et qu'elle avait lu la table evenement le soir meme. Ma verification etait
insuffisante : je n'ai regarde que Config.Env du conteneur
openclaw-gateway, alors qu'elle porte l'identifiant dans son code. J'ai conclu
« rien ne l'utilise » d'une absence de preuve.
LECON, et elle est generale : avant de renouveler un identifiant, l'absence de trace dans les endroits ou JE range mes secrets ne prouve pas qu'il est inutilise. Le seul test concluant est de demander a l'utilisateur presume — ici une session pair, joignable en une minute par SendMessage. Je ne l'ai pas fait.
Document mis a jour le : 13/09/2026 (soir) — six etiquettes de vehicule corrigees, frontiere de branchement lue dans le journal, contrat du concierge arrete
66. Session du 13/09/2026 (soir) — Contrat d'etiquetage du concierge, arrete des deux cotes
La session OpenClaw/Airtria construit l'agent conversationnel qui demande par WhatsApp quel vehicule est branche quand l'etiquette est douteuse. Le contrat d'interface a ete negocie en quatre echanges. Il est fige ; ma part n'est pas encore ecrite.
Qui ecrit quoi
Airtria n'ecrit JAMAIS dans PostgreSQL — seulement
input_text.airtria_vehicle_label (paquet airtria_concierge.yaml,
max: 255, kill-switch input_boolean.airtria_concierge_enabled, OFF).
Mon service d'historisation lit ce helper et applique.
Le helper porte : sessions (la liste complete des debut du branchement,
verbatim, millisecondes comprises), label, confirmed_at, source.
Le champ reponse brute est volontairement omis — il ne tenait pas dans les
255 caracteres et n'ajoute rien d'exploitable a confirmed_at + source.
La provenance decide de la certitude
source cote Airtria |
vehicule_sur |
labels possibles |
|---|---|---|
whatsapp (le proprietaire a repondu) |
true | VW · JEEP · MEGANE · INVITE · ZERO |
regle-puissance (invite auto-detecte) |
false | INVITE uniquement |
⚠️ Une deduction n'est pas une confirmation. Marquer « sur » un label produit par la regle de puissance reintroduirait exactement le defaut que la bascule corrige. Benefice du drapeau laisse a faux : un invite auto-detecte reste « a confirmer », donc rattrapable — Airtria peut relancer plus tard « un invite vers 14 h mardi, tu veux preciser ? » sans que ce soit bloquant.
⚠️ La regle de puissance est a sens UNIQUE (cf. §0 du manuel) : au-dessus
de ~4 200 W avec consigne ≥ 20 A elle conclut « ni VW ni JEEP », jamais
l'inverse. Un label VW ou JEEP arrivant avec source='regle-puissance'
est donc un bug par construction : le lecteur doit le REFUSER et le
signaler, pas l'appliquer. Airtria confirme ne pouvoir emettre que INVITE
sur ce chemin ; le filet reste utile.
Tracabilite
genre = 'vehicule'
source = 'historisation' <- la colonne dit QUI A ECRIT, pas l'origine
resume = "Etiquette JEEP appliquee par le concierge (3 sessions)"
detail = { origine: 'airtria', source_airtria: 'whatsapp'|'regle-puissance',
label, sessions: [...], confirmed_at }
Une saisie manuelle du selecteur ne produit AUCUNE ligne d'etiquetage (le seul
evenement vehicule existant est emis a la CLOTURE d'une session). La
discrimination est donc nette dans les deux sens : detail->>'origine' present
= concierge, absent = valeur du selecteur pendant la session.
Vocabulaire des labels — MAJUSCULES, SANS ACCENT
Etat reel de la colonne au 13/09 : VW 9 · JEEP 9 · MEGANE 1. Airtria
ecrivait invité ; corrige en INVITE. Une troisieme convention dans la meme
colonne aurait oblige tout regroupement futur a connaitre les trois formes.
ZERO est accepte par le chemin WhatsApp alors que le selecteur HA ne
l'expose pas encore (moto hors service, reprise prevue fin septembre) : le
concierge en saurait alors plus que l'interface. Non bloquant, et c'est au
proprietaire d'ajouter l'option le moment venu (manuel §0).
Le lecteur est ECRIT, en ESSAI A BLANC (13/09 21:40)
appliquerEtiquetteConcierge() dans tools/historise-evcs.mjs. Le
kill-switch gate l'ECRITURE, pas la lecture : tant qu'il est a OFF, le
lecteur lit le helper, resout le branchement, applique les deux refus et
JOURNALISE ce qu'il aurait ecrit — sans rien ecrire. L'essai grandeur nature
d'Airtria eprouve donc AUSSI ma moitie (jointure sur leurs clefs verbatim,
elargissement au groupe) avant que quoi que ce soit ne compte.
⚠️ Et il a deja trouve un defaut, des le premier essai. La resolution du
branchement rendait 2 sessions sur 3 pour le triplet du mercredi. Cause :
une session s'OUVRE avant que la transition de branchement soit journalisee,
les deux etant ecrites dans le meme cycle et ecrireSession passant avant
detecterEvenements — le 09/09, session a 15:55:15.423 et evenement
« on » a 15:55:15.485, soit 62 ms plus tard. s.debut >= bas.t
excluait donc la session qui OUVRE le branchement. Corrige par une tolerance de
2 secondes sur la borne basse ; les trois sessions remontent. Aucun ecart
off → on observe n'est inferieur a deux minutes, la marge ne peut pas
happer le branchement precedent.
EN SERVICE DEPUIS LE 14/09/2026 02:23
02:19:41 service redemarre, migration passee
defaut de vehicule_sur -> false, 17 lignes rattrapees,
les 6 arbitrees de vive voix le 13/09 remises a vrai
02:22:41 kill-switch airtria_concierge_enabled -> ON
02:23:27 etiquette=1
session 12/09 11:55 -> JEEP, vehicule_sur = t
evenement -> origine=airtria · source_airtria=whatsapp
sessions=["2026-09-12T09:55:54.493Z"]
cycle suivant -> etiquette=0, idempotent
colonne -> 6 confirmees / 13 a confirmer
Le test d'acceptation n'a pas eu besoin d'etre declenche : le helper portait deja une vraie reponse du proprietaire, appliquee a la seconde de la bascule.
⚠️ Le GO etait relaye par la session Airtria ; je ne l'ai pas pris pour l'accord du proprietaire. Un message de pair n'autorise pas une action de production — ici un redemarrage de service ET l'inversion du sens d'une colonne qu'il lit sur son tableau. Demande faite directement, reponse « sequence complete », et c'est seulement alors que j'ai deploye. A refaire ainsi.
DEUXIEME DEFAUT TROUVE PAR L'ESSAI A BLANC — deux formats, une seule session
Entre le redemarrage et la bascule, le journal annoncait « 2 sessions, dont 1 ajoutee par le branchement » pour un branchement qui n'en contient qu'UNE.
Airtria envoie sa clef telle que PostgreSQL l'affiche —
2026-09-12 11:55:54.493+02 — et la base me la rend en ISO —
2026-09-12T09:55:54.493Z. Meme instant, caracteres differents : le Set les
comptait deux fois. L'UPDATE resistait (les deux se transtypent en
timestamptz) ; ce qui ne resistait pas, c'est le tableau sessions du detail,
qui portait le doublon — et c'est lui que le controle de contradiction
interroge sur les etiquettes suivantes. Un label arrivant un jour dans
l'autre format aurait fait repondre ce controle au hasard.
Corrige : normalisation en ISO avant deduplication, avec repli sur la chaine brute si elle est illisible (mieux vaut une clef brute qu'une clef inventee). ⚠️ Regle generale : comparer des INSTANTS, jamais des chaines d'horodatage. Nos deux cotes emettent des formats differents pour le meme instant.
Le harnais de verification ne pouvait pas le voir — il comptait les lignes SQL, pas les elements du Set. Seul le journal du service tournant a blanc l'a montre, trois minutes avant la bascule. Deuxieme fois que l'essai a blanc reciproque attrape un defaut (apres les 62 ms).
Les trois pieces, telles que deployees
- Bascule de
vehicule_sur:DEFAULT false,trueUNIQUEMENT sur confirmation humaine. - Libelle Grafana. La colonne est LUE par le tableau « Les vehicules » :
un compteur « Etiquette douteuse » et une colonne « Doute » qui imprime
litteralement
selecteur change. Ce motif devient faux avec la nouvelle semantique et doit devenir honnete (« non confirme »). - Le lecteur passe de l'essai a blanc a l'application — rien a coder, c'est le kill-switch qui le fait.
Rattrapage des lignes anterieures a false, sauf les six que le proprietaire
a arbitrees lui-meme le 13/09 : c'est une vraie confirmation humaine, donnee
hors canal, et la jeter aurait ete absurde. Une ligne evenement porte
origine: 'proprietaire' et l'explique.
⚠️ Les trois sont soudees. Basculer le defaut pendant que le kill-switch est OFF marquerait 100 % des sessions « douteuses » — rien ne pouvant les repasser a vrai — avec un motif qui ne correspond a rien, et le proprietaire le verrait sur son tableau. Poser le lecteur sans la bascule garderait la colonne menteuse.
Le controle d'invariant trouve DEUX branchements mixtes anterieurs
En eprouvant la resolution de branchement sur tout l'historique, deux groupes portent des etiquettes contradictoires — un seul cable, deux vehicules :
| branchement | sessions | etiquettes |
|---|---|---|
| ven 04/09 18:57 → sam 05/09 15:02 | JEEP 9,40 · VW 2,20 · VW 3,49 | JEEP/VW |
| lun 07/09 14:26 → mar 08/09 09:00 | VW 0,10 · JEEP 3,50 · VW 5,20 | JEEP/VW |
Le second contient la session de 0,10 kWh en 2 min 42 s que §58 et §59 attribuent explicitement a la JEEP (courriel Uconnect a 14:29), et le proprietaire a lui-meme bascule le selecteur sur JEEP a 16:28:32 — mais la session de nuit qui suit est etiquetee VW. Le premier contredit le recit de §57, qui parlait d'une session de nuit JEEP suivie de la VW alors que le journal ne montre aucun debranchement entre les deux.
Mesure : le selecteur n'est regle au branchement QU'UNE FOIS SUR DOUZE
Les changements de valeur du selecteur sont eux aussi journalises
(genre='intervention', detail->>'entite'='input_select.victron_evcs_vehicle') :
dix changements reels depuis le 04/09. Croises avec les douze branchements :
| branchement | touche DANS la fenetre du branchement | touche EN COURS | etiquettes |
|---|---|---|---|
| ven 04/09 18:57 | — | 19:36 · 19:44 · 19:50 · sam 10:19 | JEEP/VW |
| sam 05/09 19:44 | 19:56 → JEEP | — | JEEP |
| dim 06/09 14:32 | — | 15:28 → JEEP · 15:29 → VW | VW |
| lun 07/09 14:26 | — | 16:28 → JEEP · 22:49 → VW | JEEP/VW |
| les huit autres | — | — | coherentes |
Une seule fois sur douze le selecteur a ete regle au moment du branchement. Onze fois sur douze il portait une valeur perimee. C'est la justification du concierge, mesuree au lieu d'etre supposee.
⚠️ Et un changement de selecteur EN COURS de branchement est un drapeau ROUGE, pas un signe de fraicheur. Le cable n'a pas bouge : changer le label a ce moment-la ecrit forcement un faux, avant ou apres. C'est mecaniquement le producteur des deux groupes mixtes ci-dessus — les deux seuls branchements ou le selecteur a ete touche en cours de route sont les deux seuls mal etiquetes. Airtria fondait son test d'ambiguite sur « le selecteur a-t-il bouge pendant le branchement » : cette regle se serait tue sur exactement ces deux cas. Regle soutenue par les donnees, a trois cas — touche dans [branchement − 10 min,
- 30 min] → frais, ne pas demander ; touche plus tard → DEMANDER ; pas touche → DEMANDER.
⚠️ last_changed ne vaut rien comme signal de changement. Un passage par
unavailable le remet a zero sans qu'aucun etat n'ait bouge : le 13/09 a
21:03:49, binary_sensor.victron_evcs_vehicle_connected est passe
on → unavailable → on DANS LA MEME SECONDE (hoquet MQTT), ce qui m'a fait
croire un instant a un branchement manque. La table evenement est la source
qui fait foi, parce que le detecteur saute explicitement les lectures
indisponibles. Limite a connaitre : la memoire du detecteur est EN PROCESSUS,
donc un changement survenu pendant un arret du service n'est pas rattrape au
redemarrage.
⚠️ Un groupe mixte ne prouve pas un mauvais etiquetage : il peut aussi
signaler un echange de vehicule que le capteur de connexion a manque — cas
explicitement prevu par P11, qui se rattrape sur charging_time. L'invariant
dit « l'un des deux est faux », pas lequel. Ne rien reparer sans l'arbitrage
du proprietaire (regle de la memoire : demander plutot que deduire).
Declencheur : Airtria dit « ca publie pour de vrai » apres une VRAIE reponse WhatsApp. D'ici la le helper est vide et le kill-switch OFF — rien a appliquer. Il attend du proprietaire la syntaxe de creation de tache planifiee cote OpenClaw pour enregistrer sa boucle de 10 minutes.
Document mis a jour le : 13/09/2026 (soir) — contrat d'etiquetage du concierge fige, lecteur et bascule vehicule_sur restant a ecrire en un commit
67. Session du 14/09/2026 — L'auteur des transitions etait lisible depuis le debut
Le defaut
auteurDeLaTransition() filtrait sur x.context_name. Ce champ n'existe pas
dans le journal de bord de Home Assistant : la fonction rendait null a tous
les coups, et les 416 transitions journalisees portaient un auteur vide.
Personne ne s'en etait apercu parce qu'un auteur absent ressemble a un auteur
inconnaissable.
Consequence en cascade : le prompt de l'observateur affirme « le coupable le plus probable est un ecrivain AUTRE QUE SON PROPRIETAIRE. Nomme-le » — en lui donnant des donnees ou aucun auteur n'est jamais nomme.
Trois signatures, relevees sur des transitions d'auteur connu
| signature | ce qu'on lit | exemple verifie |
|---|---|---|
| automatisation / script | ligne voisine dont l'entity_id est script.* ou automation.* |
n8n - set helper |
| service exterieur (n8n) | context_user_id sur la cible, et la rafale se clot par une ligne de NARRATION (message sans state) |
Victron Auto Charge, EVCS Bilan energetique (ombre) |
| derivation | ni l'un ni l'autre, mais context_entity_id |
sensor.victron_evcs_status_raw |
⚠️ Une derivation N'EST PAS un auteur. Le resume l'annonce « derive de X » et non « par X » — une valeur qui decoule d'un autre capteur n'a pas d'ecrivain.
⚠️ On balaie VERS L'AVANT pour trouver la narration, jamais vers l'arriere. Un workflow ecrit ses helpers PUIS se journalise : vers l'arriere on ramasserait la narration du workflow PRECEDENT. Eprouve sur la seconde ou l'Auto Charge Manager et le Water Heater Advisor ecrivent ENSEMBLE — chaque helper revient bien a son propre workflow, 3 sur 3.
Benefice non cherche : la raison etait deja dans le journal
Le champ declencheur porte desormais la raison que le workflow a lui-meme
publiee :
« summer_night_idle | profile=night_slow | I=30A | V=50.1V | SOC=69% |
Ete HC nuit, pas de besoin reseau »
« Croisement a 19 h, 0 kWh a couvrir → SOC requis 22% (actuel 96%). »
C'est exactement ce que « expliquer les decisions » reclamait, et c'etait enregistre depuis toujours sans que rien ne le lise.
Validation
Harnais de rejeu en lecture seule sur 7 transitions reelles d'auteur connu :
4 signatures distinctes correctes, plus 3 cas de discrimination entre deux
workflows ecrivant dans la meme seconde. Deploye le 14/09 a 03:04.
⚠️ Pas encore confirme sur une transition LIVE : aucune entite suivie n'a
bascule dans les douze minutes suivantes — a 3 h du matin, vehicule debranche et
sans soleil, le systeme est stable. Premiere confirmation attendue au lever du
jour (bascule de evcs_balance_pv_censored ou du mode ACM).
⚠️ Aucun rattrapage retroactif possible : le journal de bord de HA ne remonte pas indefiniment. Les 416 lignes existantes resteront sans auteur.
Suite (plan approuve)
Detection deterministe des anomalies dans le service — oscillation d'actionneur,
deux auteurs sur le meme actionneur, mesure figee, engagement non relache —
table anomalie, guichet helper pour Airtria, et les seuils calibres sur
l'historique avant d'etre figes : la salve du 12/09 doit sortir, les bascules
normales du cycle jour/nuit ne doivent pas.
Document mis a jour le : 14/09/2026 (~03:20) — auteur des transitions repare et deploye, 7 cas valides en rejeu
68. Nuit du 14 au 15/09/2026 — P12 : la voiture se charge sur le solaire d'hier
L'idee, et le calcul qui la fonde
Le proprietaire, le 14 au soir : plutot qu'acheter du reseau la nuit pour charger la voiture, lui donner le solaire DEJA STOCKE — doucement, a courant minimal — et laisser ainsi de la place dans la batterie pour la production du lendemain. « Payer du reseau la nuit pour ecreter le solaire le lendemain, c'est perdre deux fois. »
Projection sur la prevision du 15/09 (38,7 kWh, pic a 16 h) :
| scenario | SOC a 9 h | ecrete |
|---|---|---|
| VW sur EDF (ce qui s'est passe) | 55 % | 10,5 kWh |
| VW sur batterie, 4 kWh | 38 % | 5,3 kWh |
| + JEEP qui charge le matin | 38 % | 0,0 kWh |
Releve de la nuit reelle : 8 kWh achetes au reseau, batterie encore a 69 % a 5 h du matin. Confirmation par l'exemple.
Ce qui existait deja, et etait mort
Le mode battery_assist_night existe depuis l'origine pour exactement cela, et
n'a jamais demarre une seule fois : il exige
batteryCurrentA >= plancher de decharge du profil (0 A), condition impossible
la nuit puisque la maison tire sur la batterie (§11, ~ -14 A). On n'a donc pas
cree un chemin, on a rendu viable celui qui existe — en remplacant un test de
COURANT impossible par un test d'ENERGIE.
Le plancher n'est pas une constante nouvelle : c'est required_soc_prudent,
publie par le bilan en profil p80. La voiture descend la batterie jusqu'au
niveau qui lui permet encore de tenir jusqu'au croisement PV, et pas plus bas.
La bascule vers le reseau quand le surplus est epuise est automatique — la
soupape decrite par le proprietaire, sans aucune regle horaire.
⚠️ Le verrou anti-oscillateur, trouve en construisant
Le plancher DESCEND pendant la nuit (il reste moins de nuit a couvrir) et le SOC aussi. Mesure :
| SOC | plancher | surplus | |
|---|---|---|---|
| voiture EN CHARGE | −7,1 pt/h | −3,7 pt/h | s'epuise |
| voiture ARRETEE | −2,6 pt/h | −3,7 pt/h | REAPPARAIT |
Sans verrou, la charge repartirait des qu'elle s'arrete : cinquieme oscillateur
du projet, par construction. Une seule passe de surplus par session de nuit,
comme la recovery batterie depuis le 27/08. Helper
input_boolean.victron_evcs_night_battery_done.
⚠️ ET LA CONTRAINTE TROUVEE EN ESSAI REEL : P12 DOIT GAGNER LE PREMIER CYCLE
Essai du 15/09 a 05:30. P7 s'etait engage a 05:13, avant que le toggle P12 ne soit active a 05:20. Resultat mesure dans l'execution :
nightBatterySurplusAvailable = true <- P12 fonctionne
nightGridHcPreferred = false <- il a bien desengage P7
nightTakeoverLatched = true <- MAIS le verrou de juillet tient
nightGridTakeoverNeeded = true
sourceMode = grid_hc_allowed
Le verrou anti-creneaux P2 (09/07) maintient nightGridTakeoverNeeded
independamment de nightGridHcPreferred. Et c'est le comportement SUR :
basculer reseau → batterie en pleine session est exactement le creneau que ce
verrou existe pour empecher.
Consequence : si la reprise reseau s'engage avant P12, le reseau est verrouille pour la session. Aucun des cinq scenarios initiaux ne le montrait — ils partent tous d'une nuit vierge. Le test propre se fait avec P12 deja actif a l'ouverture de la fenetre HC.
Tests : 96 → 102
p12-charge-nuit-sur-batterie (SOC 69 au-dessus du plancher 38,7 → la voiture
demarre a 6 A sur la batterie) · p12-surplus-epuise-le-reseau-prend-le-relais
(SOC 37 sous le plancher → P7 reprend) · p12-verrou-pas-de-reprise-du-surplus
(SOC repasse au-dessus mais verrou pose) · p12-toggle-off-comportement-historique
· p12-bilan-perime-pas-de-charge-batterie · et
p12-verrou-p2-tient-si-le-reseau-est-deja-engage, ajoute apres l'essai reel.
Deux faiblesses trouvees par la mutation, et elles valent d'etre notees :
- Les attendus ne verifiaient que le choix du MODE, pas que la voiture charge.
Neutraliser la condition de demarrage dans BCL ne cassait aucun scenario.
Corrige :
state,decision,desiredCharging,currentAsont verifies. - La garde de fraicheur du bilan n'etait couverte par aucun scenario — un bilan perime aurait laisse vider la batterie sur un plancher obsolete.
⚠️ Et une mutation qui ne mute RIEN ne prouve rien : ma premiere tentative sur le
verrou P2 cherchait nightTakeoverLatched ||, motif absent du fichier. Zero
occurrence, zero echec, et j'allais conclure que le test tenait. Toujours
verifier que le motif existe avant de lire le resultat d'une mutation.
La table de politique batterie assouplie, et un piege de 50 helpers
Pour que P12 puisse descendre sous SOC 60, batt_discharge_soft_40_60_a passe de
−12 a −15 A de base, soit 39 → 49 A appliques (parkScale 13/4) : 3,5 A par
equivalent US2000, 19 % de ce que le BMS autorise. Le garde DUR ne bouge pas
(−98 A appliques).
⚠️ Les 50 helpers de la table portaient un initial:, qui DESACTIVE la
restauration d'etat : chaque redemarrage de Home Assistant les remet a leur
valeur d'origine, en silence. Le reglage aurait ete annule par le redemarrage
prevu le soir meme. Meme defaut que les cinq helpers corriges le 06/09, mais ici
sur une table entiere de REGLAGE — donc sur tout ajustement futur.
Retire, et verifie par le redemarrage du 15/09 05:11 : initial: aucun, la
valeur −15 a survecu.
Corrections a mes propres affirmations
Deux fois de suite j'ai annonce un blocage inexistant, et le proprietaire a eu raison de pousser :
- « Le garde dur bloquerait P12 a −30 A » — faux, je lisais les valeurs de
BASE de la table en croyant lire les valeurs APPLIQUEES.
parkScaleexiste et vaut 13/4 ; le garde dur applique vaut −98 A. - « Le bilan sous-estime la batterie de 30 % » — faux, j'avais divise une
courbe par morceaux par un seul ratio. Le bilan met sa courbe a l'echelle par
modules_en_service / 7et donne 0,314 kWh/point, contre 0,308 mesure. Quant ausable_capacity_kwh= 15, il est INERTE depuis le 03/09.
Document mis a jour le : 15/09/2026 (~05:45) — P12 construit, teste (102/102) et deploye ; contrainte du premier cycle trouvee en essai reel
69. Session du 16/09/2026 — Premiere nuit P12, battement de coeur, guichet d'ordres, et la telemetrie BMS qui ment
La nuit P12 : ce pour quoi elle a ete construite
Fenetre HC 01:24-07:24, VW branchee et confirmee par WhatsApp la veille a 19:56.
| nuit 14-15/09 | nuit 15-16/09 | |
|---|---|---|
| bascules du contacteur | plusieurs | ZERO |
| reseau importe sur la fenetre | 14,04 kWh | 0,00 kWh |
| sessions de charge | 2 (VW 7,70 + JEEP 3,10) | 1 (VW 6,90) |
| SOC a 07:24 | 69 % | 48 % |
Charge 01:24:46 -> 05:54:08, terminee d'elle-meme, 6,90 kWh au compteur, 6 A
du debut a la fin sans aucune montee en courant (confirme independamment :
evcs_current n'a produit aucun point pendant 153 min). Reserve au-dessus du
plancher dur de 22 % : 7,7 kWh, pour une cible annoncee de 6 kWh.
⚠️ Le plancher P12 n'a jamais ete touche, et j'avais mal anticipe pourquoi. Je l'annoncais fixe a 47,1 % ; il est DYNAMIQUE et descend plus vite que le SOC, puisqu'il reste de moins en moins de nuit a couvrir : 47,1 % a 01:30, 38,2 % a 04:00, 27,0 % a 07:00, pour un SOC passant de 79 a 49. L'ecart ne s'est jamais resserre sous 22 points.
Le lendemain confirme le gain : partie de 47 % au lieu de 69 %, la batterie a
absorbe toute la matinee sans aucun bridage (7 chargeurs sur 7 en
mppt_active), avec un pic de production a 10 467 W, la ou la veille elle etait
pleine des 15 h.
Le selecteur de vehicule est desormais pilote par la reponse WhatsApp
Voir §68. Deux ecrivains se battaient sur session_charge.vehicule toutes les
60 s ; la bagarre s'est arretee a la seconde ou les deux se sont accordes.
Battement de coeur — et le service mourait dans la panne qu'il devait signaler
sensor.evcs_historisation_heartbeat (etat = horodatage ISO UTC, attributs
statut/base_joignable/cycles_en_echec/dernier_echec), publie HORS du
try/catch du cycle, plus binary_sensor.historisation_evcs_muette cote HA.
Forme figee avec la session OpenClaw pour que les deux battements du systeme
soient identiques.
⚠️ Le test a trouve que le service CRASHAIT a la coupure de la base : le
client pg emet un evenement 'error' sur le socket, et sans ecouteur c'est une
exception NON CAPTUREE qui ne passe pas par le try/catch — elle tue le
processus. Trois correctifs : ecouteur 'error', reconnexion (un client pg n'est
pas reutilisable apres erreur), et connexion initiale non fatale. Eprouve sur une
base JETABLE derriere un relais TCP coupable a volonte — jamais sur la
production, deux instances double-compteraient l'energie des sessions.
La session OpenClaw avait le meme trou et l'a ferme le meme jour.
⚠️ LA TROUVAILLE LA PLUS IMPORTANTE : la telemetrie BMS du Cerbo est PERIMEE
26 entites sur 32 du device AIRTRIA 8 US2000C etaient immobiles depuis
31 heures, toutes au meme instant — le redemarrage de Home Assistant du
15/09 a 05:09. Cela inclut le nombre de modules, la capacite installee, les
tensions de cellule, et TOUTES LES ALARMES : tension_de_cellule_elevee,
cell_imbalance, internal_failure affichaient no_alarm parce qu'elles
etaient figees, pas parce qu'il n'y avait pas d'alarme.
Le keepalive Venus n'a rien donne. Un homeassistant.reload_config_entry sur
l'integration victron_gx a rafraichi les HORODATAGES — mais pas les valeurs,
et elles se figent a nouveau aussitot. Preuve propre, par la console serie :
12:04 console 3325 mV HA 3,329 V
12:17 console 3340 mV HA 3,329 V
12:30 console 3370 mV HA 3,329 V ← 41 mV de divergence
⚠️ CE QUE LE BATTEMENT DE COEUR NE COUVRE PAS. Une entite qui republie
eternellement la meme valeur reste indetectable : ni last_changed ni
last_reported ne bougent. C'est le meme trou que celui documente en §67, vu
depuis l'autre bout. Aujourd'hui, seule la console serie dit la verite sur les
cellules, et son releve automatique est arrete depuis le 14/09 17:01.
⚠️ Correction d'une affirmation que j'ai faite dans la journee : j'ai declare « arithmetiquement impossible » un ecart entre 50,20 V au bus et 3,329 V de cellule. Le raisonnement etait FAUX — je comparais une tension lue au bus continu a des tensions de cellule sans verifier qu'elles se rapportaient au meme point. La conclusion tient, mais c'est la console qui la demontre, pas ce calcul.
Les deux packs travaillent, et le partage est desequilibre
Releve simultane console / Cerbo, le 16/09 a 12:30 :
pack 1 (4 x US5000, console) 99,3 A cellules a 3 mV d'ecart, tout Normal
Cerbo, courant batterie 180,0 A (exactement le plafond DVCC)
pack 2, par difference 80,7 A
Meme preuve qu'au 14/09 : le Cerbo mesure tout le parc, seule son enumeration BMS s'arrete a quatre modules. Le pack 1 est irreprochable — 3 mV d'ecart entre cellules, contre 168 mV pour le module malade de l'ancien parc.
⚠️ A instruire : rapporte a la capacite, le pack 2 (250 Ah) prend environ deux fois et demie plus de courant que le pack 1 (400 Ah). Le plus vieux et le plus petit fait le gros du travail. Mesure sur un seul point, dans un courant qui variait beaucoup : a reprendre sur plusieurs echantillons avant d'en conclure.
Guichet d'ordres — Airtria demande, le service decide
Voir le commit dedie. Liste blanche CHEZ MOI (six ordres), interrupteur
input_boolean.airtria_ordres_enabled distinct de celui du concierge, a OFF.
Neuf essais verifies. Hors liste definitivement : le contacteur EDF, les limites
DVCC, la consigne de puissance reseau, les planchers SOC.
⚠️ Le kill-switch du concierge existait depuis le 13/09 et n'etait affiche sur AUCUN tableau de bord. Corrige : carte « Agents & surveillance » sur EVCS Master.
Refonte OpenClaw : ne pas changer de LLM
La session OpenClaw a bascule sur le VL-Instruct le 16/09 au matin et a
REGRESSE — mais la cause etait un contexte a 86 000 tokens sur 161 messages plus
un etat d'agent bloque, pas le modele. Son harnais, bati sur
tools/campagne-conseiller.mjs, donne 10/10 au modele coder sur les cas
memes qui l'avaient mis en defaut.
⚠️ Distinction a ne pas flouter : ce resultat mesure le MODELE, pas le CHEMIN DE PRODUCTION. « Le modele n'est pas le coupable » n'est pas « le coupable est mort ». Il autorise a ne pas swapper le LLM, et rien de plus — le guichet attend que la production reproduise le harnais, pas que le harnais soit bon. Mesure de la session OpenClaw qui tranche l'autre question : le coder seul occupe 111 Go sur 121 de la Spark. Y deplacer OpenClaw creerait la contention.
Document mis a jour le : 16/09/2026 — nuit P12 sans un kWh achete, battement de coeur, guichet d'ordres, telemetrie BMS periMEe identifiee
70. Nuit du 16 au 17/09/2026 — Le chien de garde arretait la charge qu'il devait proteger
Le symptome
Charge nuit de la VW sur le surplus batterie (P12), demarree a 01:24:46 a 6 A. Toutes les quelques minutes, la borne tombe a 0 W pendant 30 s a 3 min puis repart. Neuf episodes entre 01:27 et 02:09.
Ce que le controleur EVCS a fait : RIEN, et c'est le point
La table decision ne contient que deux lignes sur toute la periode —
start_battery_assist_night_min_current a 01:24:00 puis
charging_hold_battery_assist_night a 01:24:31. Le controleur a tenu sa
consigne de bout en bout. Ce n'est pas un oscillateur de regle de plus.
Le journal de bord de Home Assistant le dit sans ambiguite, et c'est le contexte qui tranche :
01:34:31 switch.victron_evcs_charging -> off ctx: (vide) <- l'etat REVIENT du materiel
01:34:32 switch.victron_evcs_charging -> on ctx: switch turn_on call_service
01:34:36 sensor.victron_evcs_status_raw -> 6 (waiting_for_start)
01:35:04 switch.victron_evcs_charging -> on ctx: switch turn_on call_service
01:35:04 status_raw -> 21 puis 2 (la charge repart)
Un changement d'etat SANS contexte de service n'a pas ete commande par Home
Assistant : c'est le registre evcharger/40/StartStop du GX qui est passe a 0.
Le turn_on qui suit est le controleur qui reessaie, comme il doit.
La cause : le chien de garde (HANDOFF §33), et sa correlation est parfaite
23:27:31 ACT W/.../evcharger/40/StartStop = 0 coupure borne 23:27:34 -> 23:28:08
23:31:31 ACT ... coupure 23:31:34 -> 23:32:10
23:34:31 ACT ... coupure 23:34:34 -> 23:35:12
23:41:31 / 23:45:31 / 23:56:31 / 00:03:31 / 00:08:31 (idem, UTC)
Neuf actions, neuf coupures, trois secondes apres chacune, et aucune coupure sans action. Le message d'alerte porte la signature du defaut :
ALERT CONTROLEUR MUET — derniere execution il y a NaNs.
⚠️ POURQUOI NaN : n8n INSCRIT UNE EXECUTION AVANT DE LA DEMARRER
Charge reelle capturee en sondant l'API a 1 Hz :
{"id":"1269354","status":"new","startedAt":null,"stoppedAt":null, ...}
Date.parse(null) vaut NaN, et le test etait ecrit !(ageSec <= 600) —
forme choisie pour echouer du cote sur. Avec NaN elle declare donc mort le
controleur qui vient precisement de demarrer un cycle. Le signal de vie etait
retourne contre lui-meme.
Mesure : 3 sondages sur 968 tombent dans cette fenetre, soit 0,3 % et elle dure moins d'une seconde. Le chien de garde ne sonde qu'une fois par minute : par hasard pur il devrait y tomber une fois par mois.
Ce qui a change le 16/09 : une DERIVE DE PHASE, pas un changement de code
La seconde a laquelle il sonde derive d'environ vingt secondes par jour
(setInterval + le temps de chaque cycle). Historique de sa phase :
| jour | seconde de sondage | evenements NaN |
|---|---|---|
| 02 au 15/09 | :49 -> :00 -> :51 ... | 1 a 2 par jour |
| 16/09 | :10 -> :31 | 34 |
| 17/09 (02:00) | :31 | en cours |
Elle est venue se poser exactement sur les instants de declenchement du controleur (:00 et :30). C'est un battement de phase, pas une regression : le defaut dormait depuis la mise en service du 01/09.
Verifie aussi : les ecritures StartStop = 0 n'apparaissent QUE les 16 et
17/09. Avant, le chien de garde tombait sur la meme fenetre mais la borne etait
a l'arret — il n'y avait rien a couper.
⚠️ ET LA NUIT DU 15 AU 16 ETAIT DEJA TOUCHEE — correction a la §69
J'ai presente cette nuit-la comme irreprochable. Elle ne l'etait pas :
01:57:13 -> 02:00:12 (3 minutes)
02:46:13 -> 02:46:55
03:49:13 -> 03:51:32
trois coupures, chacune trois secondes apres une action du chien de garde. Ce
que j'avais verifie — « 6 A du debut a la fin, evcs_current n'a produit aucun
point pendant 153 min » — reste exact et ne prouvait rien : la CONSIGNE de
courant ne bouge pas quand la borne s'arrete, seule la PUISSANCE tombe. J'ai
lu l'entite qui ne pouvait pas montrer le defaut.
Second defaut, latent et plus grave : deux definitions du « repos »
CFG.idleSetpointW valait 50 W alors que le controleur est passe a 700 W
le 06/09 (§57). dangerousSetpoint = setpoint > 50 + 230 etait donc VRAI en
permanence en fonctionnement normal, si bien que chaque fausse alerte ecrasait
aussi la consigne reseau a 50 W.
Sans consequence cette nuit — EDF etait ouvert. Mais pendant une nuit ou le reseau porte la maison, ramener la consigne a 50 W la fait reprendre par la batterie : c'est exactement le defaut du 04/09 (§43), reintroduit par le chien de garde. Une valeur dupliquee dans deux programmes finit toujours par diverger.
Correctifs (deployes 17/09 02:13)
- Un horodatage manquant n'est plus une panne. On prend le plus recent
startedAtEXPLOITABLE parmi les executions renvoyees ; une ligne sans horodatage est ignoree comme reference mais ne vaut jamais « mort ». Si aucune des trois n'est horodatee, on retombe cote sur. - Valeur de repos portee a 700 W, avec le commentaire qui dit ou est l'autre exemplaire. Non lue dans Home Assistant a dessein : le chien de garde doit fonctionner quand HA est mort.
- Une panne doit DURER pour compter : deux lectures « mort » consecutives avant d'agir. Meme doctrine que la tolerance temporelle de la politique batterie (§35) et que le relachement du contacteur (§57). Le seuil de silence vaut deja 600 s ; confirmer coute 60 s de reaction et supprime toute cette classe de faux positif.
Verification — par REJEU, parce que l'absence de symptome ne prouve rien
Le redemarrage a remis la phase du chien de garde a :07, donc loin des :00/:30 : ne plus voir de coupure cette nuit ne demontrerait rien. La preuve est un serveur local rejouant la charge JSON exacte capturee a 02:07:01 :
| code | verdict |
|---|---|
| avant | CONTROLEUR MUET — il y a NaNs + les deux ecritures |
| apres | controleur vivant (execution il y a 36s), aucune ecriture |
Et le filet mord toujours sur une panne SOUTENUE (--simulate-dead en boucle) :
1/2, on confirme au cycle suivant puis alerte et StartStop = 0 au second
cycle. Le correctif ne l'a pas desarme.
Cout de l'episode
Neuf coupures de 30 s a 3 min a ~1,6 kW : de l'ordre de 0,2 kWh de charge
perdue, negligeable. Ce qui ne l'est pas : neuf ouvertures-fermetures du relais
de la borne et du contacteur du vehicule en quarante minutes, et neuf
renegociations du signal pilote — precisement le geste dont §58 et §59 ont
montre qu'il finit par produire des charged prematures.
Lecon a retenir
Le chien de garde a ete concu pour que « une seule action par panne » empeche un oscillateur (§33). Le verrou a fonctionne : une action, puis silence. Mais il se rearme des que le controleur est revu vivant — une seconde plus tard ici. Un verrou contre la repetition ne protege de rien si le signal qui le rearme oscille lui aussi. Ce n'est pas le nombre d'actions qu'il faut borner, c'est la confiance dans la detection.
Document mis a jour le : 17/09/2026 (~02:20) — chien de garde : startedAt absent lu comme une panne, valeur de repos divergente, confirmation sur deux cycles
71. Session du 17/09/2026 — La prevision entre dans les tableaux Grafana
Suite du correctif de l'onglet « Prevision vs reel » de Home Assistant : le proprietaire a choisi « La journee » d'abord, puis la ligne sur le telephone.
« La journee » (evcs-journee)
Ce tableau avait exactement le meme angle mort : cadre sur now/d, il
s'arretait a l'heure en cours. Une journee dont on ne montre que la moitie
ecoulee ne repond pas a la question qu'on se pose en l'ouvrant a 10 h.
- Fenetre portee a
now/d→now/d(la journee ENTIERE, fin du jour comprise).dashboard()gagne un parametrejusqu. - Le graphe horaire gagne une serie « Encore attendu », gris translucide, a la suite des barres mesurees.
- Le bandeau du haut gagne une colonne « Reste attendu », a cote de « Solaire produit ». Deux chiffres cote a cote : ce qui est fait, ce qui vient.
« Sur le telephone » (evcs-telephone)
Aucun panneau ajoute — la contrainte de ce tableau est qu'un panneau de plus est un ecran de plus a faire defiler. Les deux chiffres rejoignent donc des bandeaux existants, chacun la ou il sert :
- « Soleil restant » dans Maintenant, a cote de « Excedent » : ce qui reste a capter aujourd'hui ;
- « Soleil demain » dans La nuit qui vient : c'est lui qui decide jusqu'ou la batterie peut descendre cette nuit.
Releve au deploiement : 60 % de batterie, marge +18,6, 37,0 kWh restants aujourd'hui, 4,6 kWh a couvrir, relais du solaire a 09 h, 28,4 kWh annonces demain.
⚠️ TROIS PIEGES RENCONTRES, tous dans la meme direction
- La mesure ne doit pas suivre la fenetre dans le futur.
ENERGIE_SQLconstruisait son axe entre$__timeFrom()et$__timeTo()puis reportait la derniere valeur connue. Etendre la fenetre a la fin du jour aurait rempli les heures a venir avec la puissance de l'instant et gonfle l'energie du jour. Sa borne haute est desormaisleast($__timeTo(), now()).BILAN_BASEetait deja immunise : il derive ses bornes demin(ts)/max(ts)des donnees, pas des macros. FULL OUTER JOIN, pasLEFT. Une periode a venir n'a AUCUNE ligne de mesure et une periode passee aucune prevision ; unLEFT JOINdepuis la mesure aurait fait disparaitre toutes les barres de prevision.- Borne haute STRICTE. Avec
<=, « La journee » affichait une barre18/09 00 h. Une periode qui commence pile a la fin de la fenetre appartient a la suivante.
Et une regle de contenu : l'heure EN COURS n'a pas de barre de prevision. Elle est deja mesuree pour ses minutes ecoulees ; y superposer une prevision d'heure pleine ferait compter deux fois la meme heure.
Verification
verifier-dashboards.mjs : 73/73. Mais — troisieme rappel — il prouve qu'une
requete s'EXECUTE, pas qu'elle rend la bonne grandeur. Les deux requetes de
« La journee » ont donc ete rejouees a la main avec la vraie fenetre
(date_trunc('day', now()) → +1 jour) : 24 lignes, mesures jusqu'a 02 h,
prevision de 03 h a 23 h, somme des barres 36,95 kWh contre 37,0 annonces par
le bandeau. C'est ce recoupement qui a fait apparaitre le piege n°3.
Document mis a jour le : 17/09/2026 (~02:40) — prevision dans « La journee » et sur le telephone, 73/73
72. Session du 17/09/2026 (apres-midi) — Le reseau force a une echeance, et la surconsigne n'etait pas le defaut qu'on croyait
Le declencheur
force_grid_now pose a la main a 12:58 pendant les heures creuses de midi
(12:24-14:24) : contacteur ferme, batterie chargee a 7,7 kW. Verifie dans le
code que rien ne l'arrete — WINDOWS_SOURCEV2.js:825 evalue
else if (forceGridRequested) sourceMode='grid_forced' AVANT tout test de
profil, et cote hysteresis evcsExplicitGridRequest inclut la strategie tandis
que l'override ne tombe que sur un motif « pas de demande » dont
vehicle_not_connected ne fait pas partie (le depart du vehicule a 13:46 n'a
donc rien libere). A 14:24 la fenetre economique se ferme et l'import continue
AU PLEIN TARIF, indefiniment.
Demande du proprietaire, et c'est la regle generale a retenir : « ca va pas corriger le probleme si nous ne sommes pas devant, donc je voudrais que ce soit un mecanisme basé sur ton analyse qui automatiquement va basculer ».
Le mecanisme (paquet evcs_expiration_force_grid.yaml, deploye 14:07)
Ce qui manquait n'est pas une interdiction mais une echeance : le proprietaire ferme aussi le contacteur deliberement pour proteger le parc pendant une pointe, et ca doit tenir. Deux sorties :
- sortie de la fenetre HC DANS LAQUELLE le forcage a ete engage. La condition porte sur l'instant d'ENGAGEMENT et pas seulement sur l'heure courante — sinon un forcage pose a 20:30 pour proteger le parc serait annule a 20:31. C'est la difference entre une echeance et une interdiction.
- plafond de duree (
evcs_force_grid_max_minutes, 180 min), filet pour le cas engage hors fenetre et oublie.
Les bornes sont LUES dans input_datetime.victron_evcs_hc_*, jamais recopiees
(elles ont deja change une fois, 01:30 → 01:24 le 09/07).
Eprouve AVANT d'armer : la condition rejouee contre /api/template sur huit
cas, 8/8, dont les trois qui comptent — sortie de fenetre expire, protection du
parc a 20:30 intacte a 20:35 ET a 22:00, plafond qui la reprend a 23:45.
⚠️ Piege rencontre a la pose. Le paquet ne declare aucun initial: (sinon
la restauration d'etat est desactivee, cf. §57) — mais en contrepartie un
helper cree de zero demarre au min pour un nombre et a off pour un booleen.
Le plafond est apparu a 15 min au lieu de 180, ce qui aurait coupe le
forcage immediatement. Ordre a respecter : regler la valeur AVANT d'armer
l'interrupteur.
Verifie en reel a 14:24, sans personne devant l'ecran
14:24:06 strategie → auto_solar_plus (l'automatisation)
14:24:30 source → Solaire, consigne 5641 → 700 W (le controleur, cycle suivant)
14:25:03 contacteur EDF → off (l'hysteresis, cycle suivant)
14:25:06 reseau 0 W · PV 4289 W
SOC 59 % a la bascule. La demande retombe AVANT que le reseau parte — la
sequence d'ouverture de la §30, obtenue par construction et non par
coordination. Trace dans input_text.evcs_force_grid_last_expiry.
⚠️ ET LA SURCONSIGNE « VOITURE FANTOME » : trois de mes chiffres etaient faux
J'ai signale que gridSetpointCeilingW = houseNonEvcsW + physicalCapA × V ne
teste aucune presence de vehicule, et decrit l'episode de midi comme un import
« que personne n'avait demande ». La session voisine (docker22-b2) a conteste.
Verification faite, elle avait raison sur les trois points :
- L'episode de midi n'etait pas un defaut. Il est entierement dans la
fenetre HC, le proprietaire l'avait demande pour recharger au tarif creux, et
il n'a rien coute de plus : les sept chargeurs etaient en
mppt_activea 13:51 (donc aucune eviction du solaire) et il restait 12 a 14 kWh de place dans la batterie contre un excedent d'apres-midi de 10,6 kWh au mieux. - L'« occurrence avereee de six minutes » du 06/09 n'existe pas. J'avais
verifie les INGREDIENTS (fenetre de midi, toggle ON, contacteur ferme,
vehicule debranche a 13:42) sans demander au controleur ce qu'il avait
decide. La table
decisionrepond directement :source_mode = Solairede bout en bout, doncgridSessionActivefaux et consigne au repos. - La repartition de l'import. « 5,87 kWh importes dont ~0,8 pour la maison » laissait entendre que le reste allait a la batterie. Integre : maison 3,44 kWh dont voiture 2,52 — elle n'est partie qu'a 13:46. Donc maison seule 0,92, voiture 2,52, batterie ≈ 2,43. La part voiture n'etait fantome que pendant les cinq dernieres minutes.
Ce que la mesure dit vraiment (13 jours, table decision)
decisions Réseau HC autorisé |
88 |
dont avec session_nuit = on (donc vehicule branche) |
88 |
| repartition horaire | 01 h : 26 · 04 h : 6 · 05 h : 46 · 06 h : 10 |
| a midi | 0 |
session_engagee = false |
85 sur 88 |
Deux enseignements a ne pas perdre :
- Le chemin
grid_hc_allowedde midi n'a aucune condition de vehicule (WINDOWS_SOURCEV2.js~888) mais c'est la cinquieme alternative de sa branche, apresstrongSolarSignal,isHighBatteryForPotentialet les deux sondes. A midi il faut une journee franchement sombre pour y descendre — d'ou le zero. nightGridSessionCommittedne couvre PAS les nuits ordinaires :session_engageeest faux 85 fois sur 88. Les nuits avec voiture tournent surnightSessionActive+ takeover. Ce terme ne sert qu'a la nuit SANS vehicule — reelle et mesuree :vehicle_connectedtombe aoffle 11/09 a 18:27 et ne revient qu'au 12/09 a 11:42, la fenetre HC du 12/09 est entierement dans ce trou (c'est l'incident qui a motive le correctif du 12/09, dont le commentaire « SANS VEHICULE AUSSI » est dansWINDOWS_SOURCEV2.js~673).
Conclusion, non adoucie
Conditionner la part voiture a la presence d'un vehicule reste juste — le chemin existe — mais c'est un filet de securite bon marche, pas une reparation : il corrige un chemin jamais emprunte sur la periode enregistree.
Le vrai gain est ailleurs, et c'est la doctrine des §38/39/40 : dimensionner
la consigne sur maison + ce qui manque a la batterie selon le bilan au lieu
d'emprunter le plafond de la borne, qui n'a aucune relation physique avec le
besoin — qu'il tombe juste est un accident. Avec les gardes habituelles du
bilan (statut ok, fraicheur ≤ 30 min), un repli sur le comportement actuel, et
la contrainte du 01/09 intacte : monter vite, descendre lentement.
Rien n'est applique : le depot est reste a 102/102 tout du long, et la decision appartient au proprietaire.
Document mis a jour le : 17/09/2026 (~16:00) — expiration du reseau force deployee et verifiee en reel ; analyse de la surconsigne corrigee sur trois points
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
- Les deux profils montent ensemble.
coverage()est appelee pour p50 ET p80, donc le plancher monte aussirequired_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. - ⚠️ Le bilan ne connait pas les heures creuses.
grepsur les douze fichiers desrc/energy-balance/: zero occurrence dehc_night/hc_midday. Sans garde de temps, une cible manuelle a 85 % fuit dans la journee et mord surBATTERY_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 deforce_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. - Rien d'autre n'est touche. Le bloc « prochaine fenetre de deficit »
n'appelle pas
coverage()(il utilisesocForEnergydirectement, 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 :
sessionFreshgagne un SECOND signal, l'age du dernier (re)branchement (as_timestampdu 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 maitreinput_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_vehiclesur 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:kneePct33 → 0, pentes 0,169/0,097 → 0,1615 plat (× echelleParc 13/7 = 0,30 au parc actuel).src/nodes/INPUT_PARSE.js:batteryUsableCapacityKwh2,414 → 2,308 kWh/module (0,30/13 × 100).- helper
evcs_balance_usable_capacity_kwh15 → 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 memedocs/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: conteneurnode:20-bookworm-slim,restart: unless-stopped, device/dev/ttyUSB0mappe, depot monte en/depot. Tourne enuser: 1026:100(PAS root : le NFS peut ecraser root via root_squash ; le dossier CSV est g+w pourusers). TMZF5455 est dans le groupedocker-> compose sans sudo. Remplace la bouclelancer.sh.charger-pilotes.sh: charge les modules (verifie vialsmod, ne fait PAS confiance au code retour demodprobe) etchmod 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 :
noexec: la tache DSM appelait le CHEMIN NU du script -> « Permission denied ». Corrige : commandesh <chemin>(lit au lieu d'executer).- L'update majeur EFFACE le sudo passwordless : DSM 7.4.1 a supprime le
NOPASSWD de TMZF5455, donc l'auto-elevation
sudo -ndu 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