diff --git a/HANDOFF.md b/HANDOFF.md index 0093bc7..ce96231 100644 --- a/HANDOFF.md +++ b/HANDOFF.md @@ -1,7152 +1,89 @@ # HANDOFF.md — EVCS / Grid Hysteresis — État au 09/07/2026 -## 1. Contexte Projet +> ⚠️ Les sessions **§1 à §72** (09/07 → 17/09/2026) sont ARCHIVÉES SANS PERTE +> dans `HANDOFF_ARCHIVE_2026.md` (fichier NON chargé automatiquement — +> `grep`/lecture à la demande). Ce fichier ne garde vivantes que les sessions +> récentes (**§73→**). Split lossless vérifié par md5 le 24/09/2026. -- **Installation** : Victron ESS **sans injection réseau**. Batterie 17,5 kWh utile. -- **Workflows n8n actifs** : - - `HA Snapshot EVCS Surplus Controller V2.6` (id `ATswvGfG4KJUls0K`) — pilote la borne EVCS (30 s) - - `Victron - Battery / Grid Hysteresis Fix V1` (id `7iIlu7LLTW7TZA8o`) — pilote le switch EDF (1 min) -- **Accès** : - - HA : `http://10.10.10.17:8123` — jeton dans `jeton_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 SSE `https://n8n.airtria.pro/mcp//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). -- **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 P1 - - `modified/EVCS_V2.6_backup_20260709-*.json` — rollbacks avant P2 puis P3 (2 fichiers) +## Index des sessions archivées (§1→§72 — détail dans HANDOFF_ARCHIVE_2026.md) -## 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é) -1. **Entité fantôme corrigée** : le snapshot lisait `input_select.victron_evcs_user_strategy` - (inexistante → toujours `unknown`). Corrigé en `input_select.victron_evcs_strategy`. - Conséquence avant fix : les overrides `force_grid_now`/`force_charge_now` et la coupure - « voiture pleine » ne fonctionnaient JAMAIS dans l'hystérésis. -2. **Switch EDF `unavailable`** : nouvel état `switch_unavailable` = aucun ordre, aucun timestamp, - réévaluation complète au retour du switch. Avant : spam `turn_on` chaque 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 %). -3. **`last_on_at`** : réécrit uniquement sur vraie transition (latch OFF→ON), plus à chaque retry. - Avant : `hold_on_min_runtime` pouvait durer éternellement. -4. **Bug latent corrigé** : le bloc `turn_off_force_grid_vehicle_full` (V2.5) était écrasé par la - chaîne principale (code mort). Flag `forcedCut` ajouté. - -### 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). -- `nightTakeoverLatched` dans `WINDOWS_SOURCEV2` **et** `BATTERY_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, et `cloudDebt` en 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` (id `cTeeHy4ePiVeELLh`) est le - propriétaire des limites DVCC (`MaxChargeCurrent`/`MaxChargeVoltage` via 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` : - si `night_grid_takeover_needed` ON + EDF ON → SOC < cible-1 : recharge 30 A ; sinon **maintien - 2 A / 50.1 V** (maison + VE sur HC, batterie en pause). Cible lue depuis - `input_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é). - Ajout `tomorrow_kwh` au 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`)** : si `pvPowerAvg10m ≥ 4000 W` **et** `batteryCurrentA ≥ +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_current` immé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_on` toutes 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: all`** activé 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_1` est 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. Backup `modified/GARAGE_backup_*`. -- **Templates MQTT durcis** (33 occurrences, `configuration.yaml` + `packages/victron_vebus_mqtt.yaml`) : - garde `value_json is defined` → plus d'erreurs `value_json is undefined` aux reconnexions MQTT. - Backups `SAVE_CONFIG/*.bak.20260709-234800`, `check_config` valid, `mqtt.reload` fait. - ⚠️ Après un `mqtt.reload`, les sensors restent `unknown` jusqu'à republication Venus : - publier `R/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 : -1. Borne fantôme au matin du 08/07 (« véhicule en charge » sans véhicule) + `force_grid_now` - de la veille ⇒ EDF maintenu ON (la coupure voiture-pleine était du code mort, cf. P1.1/P1.4). -2. L'utilisateur coupe le **disjoncteur physique** ⇒ switch `unavailable` pendant ~5 h. -3. Le workflow reste coincé en `hold_on_min_runtime` (P1.3) en spammant `turn_on` (P1.2). -4. 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égie `stop` + hystérésis OFF + latch OFF. -- **Fenêtres EVCS (helpers HA, corrigées/vérifiées le 09/07)** : `night_hc` **01:24→07:24** (HC réelles), - `morning_solar` 07:30→11:00, `midday_hc_or_solar` **12:24→14:24** (HC réelles), - `evening_high_soc` 17:00→20:30, sinon `outside_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) - -1. Backup : `GET /api/v1/workflows/` → `modified/*_backup_.json`. -2. Patch des `jsCode` par remplacement exact (script node), `node --check` sur chaque node. -3. **Dry-run local de la chaîne complète** : snapshot réel via `POST /api/template` HA, - exécution séquentielle des 5 nodes avec shim `$input`, horloge simulable en surchargeant - `global.Date` (voir session du 09/07). -4. `PUT /api/v1/workflows/` (⚠️ filtrer `settings` : seuls - `executionOrder/timezone/save*/executionTimeout/errorWorkflow` sont acceptés). -5. Vérification live : helpers HA 40-70 s après. - -## 7. Backlog / Point du 10/07 au réveil - -1. **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_recover` puis **`evcs_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_mode` entre 01:00 et 08:00. -2. **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. -3. Baisser `input_number.victron_evcs_soc_start_min` 94 → 90 (aligner avec P3) si souhaité. -4. **Sécuriser** : auth Bearer sur les 5 MCP Server Triggers n8n + révoquer/déplacer le jeton HA. -5. **À investiguer** : « conditions de démarrage nuit batterie non réunies » quand la batterie - suffit pour la nuit (conditions de départ `battery_assist_night`). -6. 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_on` conditionné 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 de `noDemandScoreOff` - - Conséquence : `evcsVehicleNoDemandSemantic` restait `false` mê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_active` restait `on` depuis 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_on` déclenchée dès `sourceMode` = `grid_forced` ou `grid_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 batterie - - `battery_neutral_band_a` : 0A (au lieu de -2A) — tolérance nulle - - `soc_start_min` : 80% (au lieu de 94%) — seuil de démarrage plus réaliste - - `night_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. - -## ⚙️ 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 :8000 - - `use-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%) vs `force_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 -1. **Charge HC ne démarre pas à 1h30** : Le grid ne se mettait pas en route à l'ouverture de la fenêtre HC -2. **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 -1. **`gridAllowedBySoc`** (BATTERY_CLOUD_LOCKS) : autorise le grid seulement si `soc <= 35%` ou `forceGridRequested`. En profile `night_hc` avec SOC > 35%, le grid était bloqué. -2. **`nightTakeoverLatched`** (WINDOWS_SOURCEV2) : reste latché tant que `soc < 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` -```javascript -// 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` -```javascript -// 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.mjs` créé, 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 +- 1. Contexte Projet +- 2. Architecture Data-Flow +- 3. Patchs déployés le 09/07/2026 (session opencode) +- 4. Root Cause de l'incident « EDF ON à 48 % sans charge Jeep » (nuit 08→09/07) +- 5. Sémantique utile (pour ne pas re-chercher) +- 6. Méthode de travail validée (à réutiliser) +- 7. Backlog / Point du 10/07 au réveil +- 8. Historique des sessions +- ⚙️ CONFIG LOCALE — Qwen3.6-35B sur DGX Spark (≠ gros modèle cloud) +- 🧩 SKILLS n8n — validées le 16/07/2026 (Qwen3.6 local, PAS gros modèle) +- 9. Session du 18/07/2026 (matin) +- 10. Session du 04/08/2026 (matin) — Fix grid HC 1h30 + libération takeover +- 11. Session du 07/08/2026 — P7 : Charge HC nuit automatique (VW hybride) + outillage push +- 12. Session du 07/08/2026 (soir) — R1 : Déduplication du moteur nuit +- 13. Session du 07/08/2026 (soir) — R3 : Suite de tests golden +- 14. Session du 07/08/2026 (soir) — P8 : Libération EDF quand le véhicule est plein +- 15. Session du 07/08/2026 (soir) — R2 : Git = source de vérité du code des nodes +- 16. Session du 07/08/2026 (nuit) — Agent IA planificateur en MODE OMBRE +- 17. Session du 08/08/2026 — Incident rebond EDF nuit 07-08/08 : fixes A/B/C/D +- 18. Session du 09/08/2026 — Incident nuit 08-09/08 : le rebond est revenu par un AUTRE chemin (Fix E) +- 19. Session du 09/08/2026 (suite) — Fix F : la mémoire "charge terminée" est prioritaire sur les gardes +- 20. Session du 09/08/2026 — PREMIER CÂBLAGE RÉEL de l'advisor : cible SOC matin +- 21. Session du 09/08/2026 — P9 : charge réseau avant départ + sélecteur véhicule VW/JEEP +- 22. Session du 09/08/2026 — P10 : mémoire "véhicule plein" en journée +- 23. Session du 12/08/2026 — P11 : reset du collant au changement de véhicule +- 26. Session du 27/08/2026 — L'oscillateur du takeover nuit, et deux acteurs non lus +- Diagnostic en attente de vérification (Qwen, 22/08) +- 24. Session du 22/08/2026 — Fix G : coupure EDF quand l'override EVCS disparaît mi-session (hystérésis) +- 25. Session du 24/08/2026 — Fix G retiré + diagnostic des deux oscillateurs +- 27. Session du 30/08/2026 — Deux propriétaires GX de la décision réseau, jamais lus +- 28. Session du 31/08/2026 — La substitution ne se reproduit plus (batterie preneuse). Cas « batterie pleine » encore ouvert +- 29. Session du 31/08/2026 (suite) — Fenêtre d'établissement du réseau +- 30. Session du 31/08/2026 (suite) — Séquence d'ouverture du contacteur +- 31. Session du 01/09/2026 (nuit) — La consigne réseau appartient désormais au contrôleur +- 32. Session du 01/09/2026 (nuit) — État réel de la résilience, faits vérifiés +- 33. Session du 01/09/2026 (nuit) — Chien de garde EVCS sur le second Synology +- 34. Session du 01/09/2026 (nuit) — Sauvegarde Home Assistant : elle n'existait pas, elle existe +- 35. Session du 01/09/2026 — La table des bandes SOC déclarait un temps que personne ne lisait +- 36. Session du 03/09/2026 — Un demarrage se decide sur l'excedent, pas sur la production +- 37. Session du 03/09/2026 (suite) — La provocation solaire demandee aux chargeurs eux-memes +- 38. Session du 03/09/2026 (suite) — Le plancher du soir cesse d'etre un niveau fixe +- 39. Session du 03/09/2026 (suite) — La cible du matin devient une energie +- 40. Session du 03/09/2026 (suite) — Session reseau UNIQUE de la nuit +- 41. Session du 03/09/2026 (suite) — La conversion energie ↔ SOC devient une courbe mesuree +- 42. Session du 04/09/2026 — Pourquoi la memoire s'arretait a la semaine +- 43. Nuit du 03-04/09 — La session unique tenait le contacteur, pas l'energie +- 44. Mise en service d'InfluxDB (04/09/2026 07:28) — la memoire est complete +- 45. Session du 04/09/2026 — Grafana : les deux bases se consultent +- 46. Session du 04/09/2026 — « Le systeme » : replique locale de la vue VRM +- 47. Session du 04/09/2026 (soir) — Connexion InfluxDB retiree du YAML, et un effet de bord du redemarrage +- 48. Session du 05/09/2026 — Le bridage de charge est une CELLULE, et il commence a 75 % +- 49. Session du 05/09/2026 — Le bridage a un coupable nomme : le module d'adresse 3 +- 50. Session du 05/09/2026 (midi) — Lecture en direct de la chaine BMS depuis Linux +- 51. Session du 05/09/2026 (12h30) — Le parc est sain a cinq modules sur sept, et le hub n'est pas coupable +- 52. Session du 05/09/2026 (12h45) — BAT3 lue isolee : UNE cellule, et elle est en meilleur etat que deux modules restes en service +- 53. Session du 05/09/2026 (13h05) — Le declenchement saisi en direct, et l'origine des degats +- 54. Session du 05/09/2026 (18h30) — Le tampon d'evenements : la cellule 12 est detruite en ce moment, toutes les nuits +- 55. Session du 05/09/2026 (19h15) — La cellule 15 de BAT3 ne peut plus debiter, meme pleine +- 56. Session du 05/09/2026 (soir) — Dates d'achat, et ce qu'elles eliminent +- 57. Session du 06/09/2026 — Le relachement du contacteur se mesurait sur un instant +- 58. Session du 07/09/2026 — La borne annonce « charge terminee » apres 0,10 kWh +- 59. Session du 07/09/2026 — Fix H : un statut « charged » doit etre credible +- 60. Session du 07/09/2026 — Tableau « La journee », et un report de valeur qui fabriquait de l'energie +- 61. Session du 08/09/2026 — La prevision se juge sur la date qu'elle decrit +- 62. Session du 11/09/2026 — Le credit solaire sur la consigne reseau, valide en reel +- 63. Session du 13/09/2026 — Le conseiller ne lisait pas mal : on lui parlait mal +- 64. Session du 13/09/2026 — Le parc butait sur NOTRE plafond, pas sur le sien +- 65. Session du 13/09/2026 (soir) — Six sessions sur douze etaient mal etiquetees +- 66. Session du 13/09/2026 (soir) — Contrat d'etiquetage du concierge, arrete des deux cotes +- 67. Session du 14/09/2026 — L'auteur des transitions etait lisible depuis le debut +- 68. Nuit du 14 au 15/09/2026 — P12 : la voiture se charge sur le solaire d'hier +- 69. Session du 16/09/2026 — Premiere nuit P12, battement de coeur, guichet d'ordres, et la telemetrie BMS qui ment +- 70. Nuit du 16 au 17/09/2026 — Le chien de garde arretait la charge qu'il devait proteger +- 71. Session du 17/09/2026 — La prevision entre dans les tableaux Grafana +- 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 --- -*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 -1. **`battery_assist_night` ne démarre jamais** : `batteryAssistNightStartOk` exige - `batteryCurrentA >= 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.) -2. **`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) : -```javascript -const nightGridHcPreferred = - nightSessionActive && // fenêtre HC nuit + véhicule branché - params.allowGridHcNight; // toggle "Réseau HC nuit" ON -``` -- `batteryAssistAllowed` : ajout `&& !nightGridHcPreferred` -- `nightGridTakeoverNeeded` : ajout `nightGridHcPreferred ||` 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 `** : GET workflow → JSON local (clé API lue - automatiquement dans `~/.config/opencode/opencode.json`). -- **`tools/n8n-push.mjs [id]`** : PUT REST (body minimal validé 04/08) avec - **backup serveur auto pré-push** (`modified/prepush-*.json`) et **`node --check` - sur 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) -1. 01:24-01:25 : `source_mode` → `Réseau HC autorisé`, EDF ON, takeover helper ON ; -2. `start_grid_hc_min_current` → montée en courant (plafond EDF ON = helper 20 A / boost 28 A) ; -3. ACM en `evcs_takeover_hold` (batterie ~2 A, SOC stable) ; -4. 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`, `nightSessionNeedsCapture` version stricte - (critère startMs inclus), `batteryRecoveryAllowedNow`, `batteryTargetReached`. - Export `nightEvcsPreview` é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 `nightEvcs` de 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 via `tools/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=` : 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_now` la nuit (toggle HC ON) → `start_manual_local_min_current` - ET takeover helper ON (session nuit active) : EDF peut venir en renfort. Voulu. -- Garde SOC>35 midi : sourceMode `grid_hc_allowed` mais `idle_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 -1. **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é. -2. **P7 ne relâchait pas** : `nightGridHcPreferred` restait vrai tant que véhicule - branché → l'EVCS redemandait EDF ON toutes les 30 s jusqu'à 07:24. -3. **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) -1. 01:24-01:25 : grid_hc_allowed → EDF ON → charge 6 A puis montée (P7) -2. 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) -3. Batterie sous cible matin (35 été) : EDF maintenu + recharge ACM jusqu'à cible -4. 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/.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 , 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) -1. Template /api/template DOIT être du Jinja : `{{ { ... } | tojson }}` — - sans wrapper, HA renvoie le texte brut non rendu. -2. 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.) -3. MCP activate_workflow → erreur 415 : activer via REST POST .../activate body {}. -4. n8n-push : le fallback d'id par défaut (EVCS) était DANGEREUX avec un 2e - workflow → supprimé, l'id vient du champ `id` du 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) -1. Taux d'accord stratégie ≥ 80% (compteurs agree/diverge sur la carte) ; -2. Zéro sortie invalide NON rejetée (advisor_status) ; -3. 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 : -1. Fin de charge détectée très tardivement (~04:15 au lieu de ~02:56) ; -2. 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) ; -3. 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`, release `soc > cible`, recovery ACM `soc < cible` (strict), - et l'ACM recharge PILE à la cible → aucune position stable, oscillation. -- **B — snapshot incomplet** : `evcs_night_session_start_soc/_at` jamais relus - (absents du template) → `nightSessionNeedsCapture` vrai à 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_idle` dé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.mjs` exécute le JSON BUILDÉ - (workflows/*.json), pas src/ → toujours `node tools/n8n-build.mjs` avant 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) -1. Fin de charge : passage à vehicle_full_or_paused OU night_recovery_done_idle - rapide (Fix D si la VW publie le statut 3) ; -2. SOC à la cible → UNE coupure EDF, puis plus aucun ON/OFF (sauf SOC ≤ cible−2) ; -3. "Night Session Start At" écrit UNE fois en début de session (Fix B) ; -4. 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) -1. Status 3 arrive PENDANT la session armée (relais fermé, chg=1/perm=1/l=1). -2. 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 !) puis `stop_source_lost`. - La branche P8 (état collant) est dans la chaîne NON-active → jamais atteinte. -3. 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_on` rallume EDF + restart → status 3 → goto 2. Rebond ~30 min. -4. L'hystérésis adoptait l'EDF ON du cycle de flap puis `hold_on_min_runtime` - 25 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_idle` si SOC ≥ cible, sinon - `night_session_done_idle`), decision `stop_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 -1. 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) ; -2. VW refuse (0 W, status 3) → ≤ 1 min : `night_recovery_done_idle` collant, - takeover OFF, session stoppée proprement ; -3. Hystérésis : `turn_off_recovery_done` → EDF OFF ≤ 1 min (plus de min_on 25) ; -4. 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) -1. ✅ Fix E validé en réel : 06:51:00 `night_recovery_done_idle` latché, - 06:51:03 `turn_off_recovery_done` → EDF coupé immédiatement (bypass min_on). -2. ❌ 06:53-06:56 : la VW a relancé une charge (status 2) qui, EDF OFF, tirait - sur la batterie (−47 A) → garde `mustStopHard` → état - `battery_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_connected` et `mustStopHard` : - `night_hc && vehicleChargeCompleted && !force*` → setOff avec l'état collant - (recovery_done/done_idle), `mustStop` si 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 : -1. `input_boolean.evcs_advisor_apply_night_target` = ON (nouveau toggle, - initial OFF, package evcs_advisor_shadow.yaml + carte dashboard) ; -2. `input_text.evcs_advisor_status` = 'ok' (exact) ; -3. recommandation fraîche ≤ 120 min (parse `DD/MM HH:mm` de advisor_last_run, - année courante, garde passage d'année — parseAdvisorRunToMs INPUT_PARSE) ; -4. 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) -1. Toggle « APPLIQUER cible SOC matin » OFF → effet au cycle suivant (30 s) ; -2. Kill-switch advisor OFF → plus de runs → fraîcheur > 120 min → fallback - auto en ≤ 2 h même si le toggle reste ON ; -3. `git revert` du 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'ancien `input_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'existant - `victron_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 : `departureTimeMin` suit 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` → sourceMode `grid_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 : `gridAllowedBySoc` inclut 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 -1. Recharger HA (YAML : input_boolean, input_datetime, input_select) ; -2. Sur la carte : sélectionner le véhicule au branchement (VW ou JEEP) ; -3. Activer « ⚡ Réseau avant départ (P9) » ; -4. 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 : `dayVehicleFullIdle` collant = 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. - Neutralise `vehicleChargeCompleted`, `dayVehicleFullIdle`, - `departureChargeDone` ET `evcsStatusCharged` (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" -1. DÉBRANCHEMENT (vehicle_not_connected) ; -2. CHANGEMENT DE VÉHICULE / nouvelle connexion (charging_time ≤ 90 s, P11) ; -3. 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 :** -1. **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. -2. **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. -3. **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 SOC` de 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é : -```javascript -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) -1. Logbook doit montrer `turn_off_evcs_override_released` peu après la - fermeture de fenêtre si le véhicule n'est pas encore flag "terminé" ; -2. La PV doit remonter (MPPT non-plafonné) immédiatement après ; -3. Aucune coupure/rallumage répété (`turn_off_evcs_override_released` suivi de - près par `turn_on_evcs_override` ré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 : -1. `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é. -2. 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 W` contre **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é dans `docs/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 : -1. **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). -2. **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 sur - `edf on|off`, jamais sur le mot `MPPT`. - -### 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 garde `is not none` pour une entité - absente), plus `grid_settle_seconds` (helper **optionnel** - `input_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.gridSettleSeconds` borné [0,900]. -- **WINDOWS_SOURCEV2** : `derived.gridSettling = gridAvailable && age < seuil`. -- **BATTERY_CLOUD_LOCKS** : pendant la fenêtre, `rawSolarStartOk` est faux (on ne - démarre pas sur des mesures fausses), les deux termes de mesure de - `rawSolarHoldOk` (plancher de décharge, seuil PV) sont neutralisés (on ne laisse - pas une charge en cours s'arrêter à cause d'elles), `probeFailedNow` est - suspendu (le réseau masque la production, la sonde n'échoue pas) et - `solarShouldStepUp` est bloqué. **Les step-down ne sont pas bridés** : c'est la - direction sûre. -- **HA_SERVICE_CALLS_OUTPUT** : trace `ETABLISSEMENT reseau Xs/Ys` dans 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) -1. ✅ fenêtre d'établissement ; -2. 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é ; -3. 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). - -1. **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. -2. **Et quand il l'apprenait, il ne baissait pas vite.** La branche - `charging_step_down_system_cap` de `DECISION_ENGINEV2` exigeait - `lifecycle.currentCooldownElapsed` — un compteur de **confort** — et ne - descendait que de `currentStepDownA` par 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** : - -```js -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 -3. 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 optionnel - `input_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 et `force_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=XW` dans 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, keepalive `R//keepalive` obligatoire. -- **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 -1. Contrôleur vivant → aucune écriture, une ligne de veille par heure. -2. 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. -3. 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 -1. **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. -2. **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//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` - (paquet `victron_evcs_battery_dynamic.yaml`, sauvegarde dans `packages/SAVE/`, - créé par `input_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`, et `batterySoftDischargeHoldOk` = 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`. - (`gridHoldOk` et `batteryAssistNightHoldOk` utilisaient 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-charge` reste 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 -1. Journal : plus aucun `stop_conditions_lost` en pleine charge solaire à - batterie pleine ; la trace `Batt souple X/12min` n'apparaît que pendant un - dépassement réel. -2. `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. -3. 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.** - -```js -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) -2. **Lire `input_boolean.evcs_balance_pv_censored`** pour conditionner la - provocation `solar_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 bridee` a 20:00) : l'approximation disait « oui » au moment - exact ou la chose approximee devenait fausse. -3. **Lire `evcs_balance_required_soc` / `_energy_to_cover_kwh`** pour 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 par - `states.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/template` AVANT integration, - puis gabarit complet re-rendu : 191 champs, JSON valide, aucun null. -- **INPUT_PARSE** : `mpptReadableCount` / `mpptAtMaxPowerCount` / - `mpptHoldingBackCount`. Les lectures `unavailable`/`unknown`/vides sont - ecartees — on ne devine jamais un etat. -- **BATTERY_CLOUD_LOCKS** : - `solarProvocationJustified = !(lectures exploitables && aucun tracker ne se retient)`, - exige par `batteryAssistSolarStartOk` et par la branche `solar_potential` de - `solarSurplusStartOk`. - -⚠️ **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 trackers `mppt_active`) → `idle_wait_conditions` ; -- `provocation-trackers-illisibles-comportement-anterieur` : meme releve, - lectures `unavailable` → `start_min_current` (le repli est teste, pas - seulement affirme). -- `excedent-censure-demarre` et `apresmidi-provocation-p3` portent 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 -3. **Lire `evcs_balance_required_soc` / `_energy_to_cover_kwh`** pour 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_balance` sur 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 : - -```js -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_status` exactement `ok` ; -- 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). -- `houseSpikeDetected` reste 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` : - -```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_soc` est donc systematiquement TROP BAS ... c'est le facteur -> 1,75 » — etait FAUSSE, sur les deux points. -> 1. 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). -> 2. 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.** - -```js -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_soc` sous-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. - `requiredSocAtStartPct` reste **NON ecrete** au-dela de 100 (§26 : signal de - dimensionnement) ; l'extrapolation y utilise la pente haute. -- **`src/nodes/INPUT_PARSE.js`** : `batteryUsableCapacityKwh` 17,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_kwh` reste 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 -1. **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. -2. **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 en `mode=ro` seul, elle est saine. -3. **Le volume de 10.10.10.17 n'est pas de 133 Go.** J'avais interroge `df` sur - `/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, capture - `df -h` a l'appui. - ⚠️ Le montage n'est PAS `/mnt/ARCH3` mais `/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) -1. **`HA_SERVICE_CALLS_OUTPUT`** — `gridSessionActive` inclut desormais - `night.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. -2. **`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 -1. Voiture finie → la consigne doit RESTER haute, pas retomber a 200 ; -2. SOC quasi plat entre la fin de charge et 07:24, au lieu de −27 points ; -3. `ess_batterylife_state` ne doit jamais atteindre `recharge_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 ; tags `entity_id` (**sans le prefixe de domaine**), - `domain`, `installation` ; champ numerique `value`. -- `_measurement` = **l'entity_id complet** (`sensor.…`, `switch.…`) pour les - entites textuelles ; champ `state` = 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 : - -1. **`ts` n'est pas un instant de cycle.** `historise-evcs.mjs` horodate chaque - mesure avec son propre `last_updated` — le vrai instant de la mesure, et - c'est le bon choix. Mais deux grandeurs ne partagent alors presque jamais le - meme `ts` : **2656 lignes pour 2656 instants distincts**. Un `GROUP BY ts` - rend 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. -2. **Le service n'ecrit que les changements** (`ON CONFLICT DO NOTHING` sur - `(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_value` sur les paliers numerotes par `count(x) OVER (ORDER BY t)`), - qui est aussi ce que fait le controleur en temps reel. **430/435 apres.** -3. **L'energie horaire par `sum(valeur)/60`** suppose 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 -1. **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. -2. **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. -3. **Retenu** : axe des minutes complet (`generate_series`), report de la - derniere valeur connue, puis `avg 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 `influxdb` de HA n'ecrit **que sur changement d'etat**. Une - fenetre `range(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. -- `pivot` rend les colonnes par ordre alphabetique (A, V, W) : une - transformation `organize` retablit tension / courant / puissance. - -### Ce qui reste ouvert -- Le tableau Energie de HA pourrait etre configure avec les compteurs SAINS - (trois `*_total_yield` en sources solaires, quatre compteurs MultiPlus pour - le reseau). Il manquerait la batterie, faute de compteur monotone — a creer - par integration de Riemann sur `sensor.victron_battery_power` si 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 -1. **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. -2. **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. -3. **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. -4. **`cell_imbalance` est reste `no_alarm` tout 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. -5. **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 `** (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. - ⚠️ `data` numerote les cellules de 0 a 14, `datalist history dtls` de 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 ` : 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 `, `soh `, `info ` 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 history ` | un point toutes les 30 min | ~5 semaines | -| **`data event `** | **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 : -1. **relever le plancher a 45 %** (`input_number.victron_evcs_soc_stop_min` et - la logique de plancher dur) — une ecriture, effet immediat, coute 3,1 kWh ; -2. **retirer `…0322` et `…0319`** — memes 3,1 kWh perdus, mais le bridage - disparait aussi ; demande une intervention physique ; -3. ne rien faire cette nuit et perdre encore un peu de cette cellule. - -### Outillage -`--tampon event` ajoute a `--historique-distant`. Et -**`--decalage-minutes `**, 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 `…0322` et `…0319` sont 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 : -1. 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 ; -2. 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 -1. `input_datetime.victron_grid_charge_high_last_at` doit avancer pendant la - cuisson et se figer ensuite ; -2. au plus UNE fermeture et UNE ouverture du contacteur sur la soiree ; -3. la raison doit afficher `Charge maison retombee a XW depuis Y min : on garde - le reseau jusqu'a 20 min (reserve deja condamnee...)` ; -4. si la tension batterie se remet a disparaitre, `plafond_decharge_w` doit - 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`) : -1. **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. -2. `last_session_energy` et `last_session_time` sont 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. -3. Plus aucun `GREATEST` sur une grandeur d'energie. -4. **Les treize capteurs `sensor.evcs_*` de la borne entrent dans - l'historisation InfluxDB** (`configuration.yaml`, sauvegarde dans - `SAVE_CONFIG/`) : c'est leur absence qui a empeche d'etablir la semantique. - ⚠️ Prend effet au prochain redemarrage de Home Assistant (les filtres - `include`/`exclude` d'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 < 1500` doit 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 `.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 --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 `video` dont 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` : - -1. **`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. -2. **Le genre `bilan` est 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. - -1. **`FILTRE_VUE` ne 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. -2. **`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 : - -```sh -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` : - -```sh -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": , "to": , "queries": []}`. -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_FORECAST` publie `input_text.evcs_balance_forecast_day0` - (la date de la courbe « today ») ; -- le gabarit du bilan la lit ; -- `RESOURCE_ESTIMATE` remplace 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 -1. Creer le helper `input_text.evcs_balance_forecast_day0` dans - `/mnt/ARCH3/docker/HA/packages/evcs_energy_balance.yaml`, a cote de - `evcs_balance_forecast_today`, puis `input_text.reload` — aucun redemarrage. -2. Pousser les deux workflows : `evcs-solar-forecast-v1.json` (publie la date) - et `evcs-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. - -1. **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. -2. **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: on` - avec l etat controleur reste a `vehicle_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 -1. Sous 80 % de SOC, DVCC a **150 A** et courant reellement pris — s'approche-t-il - du nouveau plafond, ou plafonne-t-il ailleurs ? -2. `tension_minimale/maximale_de_la_cellule` : l'ecart doit rester de quelques - millivolts. S'il s'ouvre, on s'arrete a ce palier. -3. Nombre de chargeurs `voltage_current_limited` a 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_debut` en LISTE (ecritures sequentielles sur un - helper unique perdraient une confirmation sur deux, mon service ne passant que - toutes les 60 s). -- La clef `debut` porte les **millisecondes** : renvoyer la chaine verbatim, - jointure sur ±1 s. -- `input_text` : declarer `max: 255` — le defaut est 100, et un rejet est - SILENCIEUX (incident `forecast_updated` du 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 -1. **Bascule de `vehicule_sur`** : `DEFAULT false`, `true` UNIQUEMENT sur - confirmation humaine. -2. **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 »). -3. 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 :** -1. 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`, `currentA` sont verifies. -2. 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 : -1. « Le garde dur bloquerait P12 a −30 A » — **faux**, je lisais les valeurs de - BASE de la table en croyant lire les valeurs APPLIQUEES. `parkScale` existe - et vaut 13/4 ; le garde dur applique vaut −98 A. -2. « 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 / 7` et donne 0,314 kWh/point, contre **0,308 mesure**. - Quant a `usable_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 : - -```json -{"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) -1. **Un horodatage manquant n'est plus une panne.** On prend le plus recent - `startedAt` EXPLOITABLE 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. -2. **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. -3. **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 parametre `jusqu`. -- 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 -1. **La mesure ne doit pas suivre la fenetre dans le futur.** `ENERGIE_SQL` - construisait 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 desormais `least($__timeTo(), now())`. - `BILAN_BASE` etait deja immunise : il derive ses bornes de `min(ts)/max(ts)` - des donnees, pas des macros. -2. **`FULL OUTER JOIN`, pas `LEFT`.** Une periode a venir n'a AUCUNE ligne de - mesure et une periode passee aucune prevision ; un `LEFT JOIN` depuis la - mesure aurait fait disparaitre toutes les barres de prevision. -3. **Borne haute STRICTE.** Avec `<=`, « La journee » affichait une barre - `18/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 : -1. **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. -2. **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** : - -1. **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_active` a - 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. -2. **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 `decision` repond directement : `source_mode = Solaire` - de bout en bout, donc `gridSessionActive` faux et consigne au repos. -3. **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_allowed` de midi n'a aucune condition de vehicule** - (`WINDOWS_SOURCEV2.js` ~888) mais c'est la **cinquieme** alternative de sa - branche, apres `strongSolarSignal`, `isHighBatteryForPotential` et les deux - sondes. A midi il faut une journee franchement sombre pour y descendre — d'ou - le zero. -- **`nightGridSessionCommitted` ne couvre PAS les nuits ordinaires** : - `session_engagee` est faux 85 fois sur 88. Les nuits avec voiture tournent sur - `nightSessionActive` + takeover. Ce terme ne sert qu'a la nuit SANS vehicule — - reelle et mesuree : `vehicle_connected` tombe a `off` le 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 dans `WINDOWS_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 diff --git a/HANDOFF_ARCHIVE_2026.md b/HANDOFF_ARCHIVE_2026.md new file mode 100644 index 0000000..25be9e7 --- /dev/null +++ b/HANDOFF_ARCHIVE_2026.md @@ -0,0 +1,7155 @@ +# HANDOFF_ARCHIVE_2026.md — Archive des sessions §1 à §72 (09/07 → 17/09/2026) + +> Extrait SANS PERTE de HANDOFF.md le 24/09/2026 (split vérifié par md5). Ce +> fichier n'est PAS chargé automatiquement par CLAUDE.md : il se lit/greppe à la +> demande. Les sessions récentes (§73→) vivent dans HANDOFF.md. Ne rien réécrire +> ici — c'est un dépôt historique figé. + +## 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` (id `ATswvGfG4KJUls0K`) — pilote la borne EVCS (30 s) + - `Victron - Battery / Grid Hysteresis Fix V1` (id `7iIlu7LLTW7TZA8o`) — pilote le switch EDF (1 min) +- **Accès** : + - HA : `http://10.10.10.17:8123` — jeton dans `jeton_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 SSE `https://n8n.airtria.pro/mcp//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). +- **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 P1 + - `modified/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é) +1. **Entité fantôme corrigée** : le snapshot lisait `input_select.victron_evcs_user_strategy` + (inexistante → toujours `unknown`). Corrigé en `input_select.victron_evcs_strategy`. + Conséquence avant fix : les overrides `force_grid_now`/`force_charge_now` et la coupure + « voiture pleine » ne fonctionnaient JAMAIS dans l'hystérésis. +2. **Switch EDF `unavailable`** : nouvel état `switch_unavailable` = aucun ordre, aucun timestamp, + réévaluation complète au retour du switch. Avant : spam `turn_on` chaque 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 %). +3. **`last_on_at`** : réécrit uniquement sur vraie transition (latch OFF→ON), plus à chaque retry. + Avant : `hold_on_min_runtime` pouvait durer éternellement. +4. **Bug latent corrigé** : le bloc `turn_off_force_grid_vehicle_full` (V2.5) était écrasé par la + chaîne principale (code mort). Flag `forcedCut` ajouté. + +### 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). +- `nightTakeoverLatched` dans `WINDOWS_SOURCEV2` **et** `BATTERY_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, et `cloudDebt` en 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` (id `cTeeHy4ePiVeELLh`) est le + propriétaire des limites DVCC (`MaxChargeCurrent`/`MaxChargeVoltage` via 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` : + si `night_grid_takeover_needed` ON + EDF ON → SOC < cible-1 : recharge 30 A ; sinon **maintien + 2 A / 50.1 V** (maison + VE sur HC, batterie en pause). Cible lue depuis + `input_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é). + Ajout `tomorrow_kwh` au 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`)** : si `pvPowerAvg10m ≥ 4000 W` **et** `batteryCurrentA ≥ +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_current` immé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_on` toutes 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: all`** activé 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_1` est 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. Backup `modified/GARAGE_backup_*`. +- **Templates MQTT durcis** (33 occurrences, `configuration.yaml` + `packages/victron_vebus_mqtt.yaml`) : + garde `value_json is defined` → plus d'erreurs `value_json is undefined` aux reconnexions MQTT. + Backups `SAVE_CONFIG/*.bak.20260709-234800`, `check_config` valid, `mqtt.reload` fait. + ⚠️ Après un `mqtt.reload`, les sensors restent `unknown` jusqu'à republication Venus : + publier `R/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 : +1. Borne fantôme au matin du 08/07 (« véhicule en charge » sans véhicule) + `force_grid_now` + de la veille ⇒ EDF maintenu ON (la coupure voiture-pleine était du code mort, cf. P1.1/P1.4). +2. L'utilisateur coupe le **disjoncteur physique** ⇒ switch `unavailable` pendant ~5 h. +3. Le workflow reste coincé en `hold_on_min_runtime` (P1.3) en spammant `turn_on` (P1.2). +4. 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égie `stop` + hystérésis OFF + latch OFF. +- **Fenêtres EVCS (helpers HA, corrigées/vérifiées le 09/07)** : `night_hc` **01:24→07:24** (HC réelles), + `morning_solar` 07:30→11:00, `midday_hc_or_solar` **12:24→14:24** (HC réelles), + `evening_high_soc` 17:00→20:30, sinon `outside_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) + +1. Backup : `GET /api/v1/workflows/` → `modified/*_backup_.json`. +2. Patch des `jsCode` par remplacement exact (script node), `node --check` sur chaque node. +3. **Dry-run local de la chaîne complète** : snapshot réel via `POST /api/template` HA, + exécution séquentielle des 5 nodes avec shim `$input`, horloge simulable en surchargeant + `global.Date` (voir session du 09/07). +4. `PUT /api/v1/workflows/` (⚠️ filtrer `settings` : seuls + `executionOrder/timezone/save*/executionTimeout/errorWorkflow` sont acceptés). +5. Vérification live : helpers HA 40-70 s après. + +## 7. Backlog / Point du 10/07 au réveil + +1. **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_recover` puis **`evcs_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_mode` entre 01:00 et 08:00. +2. **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. +3. Baisser `input_number.victron_evcs_soc_start_min` 94 → 90 (aligner avec P3) si souhaité. +4. **Sécuriser** : auth Bearer sur les 5 MCP Server Triggers n8n + révoquer/déplacer le jeton HA. +5. **À investiguer** : « conditions de démarrage nuit batterie non réunies » quand la batterie + suffit pour la nuit (conditions de départ `battery_assist_night`). +6. 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_on` conditionné 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 de `noDemandScoreOff` + - Conséquence : `evcsVehicleNoDemandSemantic` restait `false` mê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_active` restait `on` depuis 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_on` déclenchée dès `sourceMode` = `grid_forced` ou `grid_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 batterie + - `battery_neutral_band_a` : 0A (au lieu de -2A) — tolérance nulle + - `soc_start_min` : 80% (au lieu de 94%) — seuil de démarrage plus réaliste + - `night_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. + +## ⚙️ 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 :8000 + - `use-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%) vs `force_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 +1. **Charge HC ne démarre pas à 1h30** : Le grid ne se mettait pas en route à l'ouverture de la fenêtre HC +2. **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 +1. **`gridAllowedBySoc`** (BATTERY_CLOUD_LOCKS) : autorise le grid seulement si `soc <= 35%` ou `forceGridRequested`. En profile `night_hc` avec SOC > 35%, le grid était bloqué. +2. **`nightTakeoverLatched`** (WINDOWS_SOURCEV2) : reste latché tant que `soc < 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` +```javascript +// 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` +```javascript +// 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.mjs` créé, 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 +1. **`battery_assist_night` ne démarre jamais** : `batteryAssistNightStartOk` exige + `batteryCurrentA >= 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.) +2. **`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) : +```javascript +const nightGridHcPreferred = + nightSessionActive && // fenêtre HC nuit + véhicule branché + params.allowGridHcNight; // toggle "Réseau HC nuit" ON +``` +- `batteryAssistAllowed` : ajout `&& !nightGridHcPreferred` +- `nightGridTakeoverNeeded` : ajout `nightGridHcPreferred ||` 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 `** : GET workflow → JSON local (clé API lue + automatiquement dans `~/.config/opencode/opencode.json`). +- **`tools/n8n-push.mjs [id]`** : PUT REST (body minimal validé 04/08) avec + **backup serveur auto pré-push** (`modified/prepush-*.json`) et **`node --check` + sur 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) +1. 01:24-01:25 : `source_mode` → `Réseau HC autorisé`, EDF ON, takeover helper ON ; +2. `start_grid_hc_min_current` → montée en courant (plafond EDF ON = helper 20 A / boost 28 A) ; +3. ACM en `evcs_takeover_hold` (batterie ~2 A, SOC stable) ; +4. 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`, `nightSessionNeedsCapture` version stricte + (critère startMs inclus), `batteryRecoveryAllowedNow`, `batteryTargetReached`. + Export `nightEvcsPreview` é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 `nightEvcs` de 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 via `tools/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=` : 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_now` la nuit (toggle HC ON) → `start_manual_local_min_current` + ET takeover helper ON (session nuit active) : EDF peut venir en renfort. Voulu. +- Garde SOC>35 midi : sourceMode `grid_hc_allowed` mais `idle_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 +1. **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é. +2. **P7 ne relâchait pas** : `nightGridHcPreferred` restait vrai tant que véhicule + branché → l'EVCS redemandait EDF ON toutes les 30 s jusqu'à 07:24. +3. **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) +1. 01:24-01:25 : grid_hc_allowed → EDF ON → charge 6 A puis montée (P7) +2. 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) +3. Batterie sous cible matin (35 été) : EDF maintenu + recharge ACM jusqu'à cible +4. 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/.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 , 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) +1. Template /api/template DOIT être du Jinja : `{{ { ... } | tojson }}` — + sans wrapper, HA renvoie le texte brut non rendu. +2. 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.) +3. MCP activate_workflow → erreur 415 : activer via REST POST .../activate body {}. +4. n8n-push : le fallback d'id par défaut (EVCS) était DANGEREUX avec un 2e + workflow → supprimé, l'id vient du champ `id` du 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) +1. Taux d'accord stratégie ≥ 80% (compteurs agree/diverge sur la carte) ; +2. Zéro sortie invalide NON rejetée (advisor_status) ; +3. 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 : +1. Fin de charge détectée très tardivement (~04:15 au lieu de ~02:56) ; +2. 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) ; +3. 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`, release `soc > cible`, recovery ACM `soc < cible` (strict), + et l'ACM recharge PILE à la cible → aucune position stable, oscillation. +- **B — snapshot incomplet** : `evcs_night_session_start_soc/_at` jamais relus + (absents du template) → `nightSessionNeedsCapture` vrai à 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_idle` dé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.mjs` exécute le JSON BUILDÉ + (workflows/*.json), pas src/ → toujours `node tools/n8n-build.mjs` avant 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) +1. Fin de charge : passage à vehicle_full_or_paused OU night_recovery_done_idle + rapide (Fix D si la VW publie le statut 3) ; +2. SOC à la cible → UNE coupure EDF, puis plus aucun ON/OFF (sauf SOC ≤ cible−2) ; +3. "Night Session Start At" écrit UNE fois en début de session (Fix B) ; +4. 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) +1. Status 3 arrive PENDANT la session armée (relais fermé, chg=1/perm=1/l=1). +2. 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 !) puis `stop_source_lost`. + La branche P8 (état collant) est dans la chaîne NON-active → jamais atteinte. +3. 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_on` rallume EDF + restart → status 3 → goto 2. Rebond ~30 min. +4. L'hystérésis adoptait l'EDF ON du cycle de flap puis `hold_on_min_runtime` + 25 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_idle` si SOC ≥ cible, sinon + `night_session_done_idle`), decision `stop_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 +1. 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) ; +2. VW refuse (0 W, status 3) → ≤ 1 min : `night_recovery_done_idle` collant, + takeover OFF, session stoppée proprement ; +3. Hystérésis : `turn_off_recovery_done` → EDF OFF ≤ 1 min (plus de min_on 25) ; +4. 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) +1. ✅ Fix E validé en réel : 06:51:00 `night_recovery_done_idle` latché, + 06:51:03 `turn_off_recovery_done` → EDF coupé immédiatement (bypass min_on). +2. ❌ 06:53-06:56 : la VW a relancé une charge (status 2) qui, EDF OFF, tirait + sur la batterie (−47 A) → garde `mustStopHard` → état + `battery_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_connected` et `mustStopHard` : + `night_hc && vehicleChargeCompleted && !force*` → setOff avec l'état collant + (recovery_done/done_idle), `mustStop` si 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 : +1. `input_boolean.evcs_advisor_apply_night_target` = ON (nouveau toggle, + initial OFF, package evcs_advisor_shadow.yaml + carte dashboard) ; +2. `input_text.evcs_advisor_status` = 'ok' (exact) ; +3. recommandation fraîche ≤ 120 min (parse `DD/MM HH:mm` de advisor_last_run, + année courante, garde passage d'année — parseAdvisorRunToMs INPUT_PARSE) ; +4. 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) +1. Toggle « APPLIQUER cible SOC matin » OFF → effet au cycle suivant (30 s) ; +2. Kill-switch advisor OFF → plus de runs → fraîcheur > 120 min → fallback + auto en ≤ 2 h même si le toggle reste ON ; +3. `git revert` du 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'ancien `input_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'existant + `victron_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 : `departureTimeMin` suit 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` → sourceMode `grid_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 : `gridAllowedBySoc` inclut 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 +1. Recharger HA (YAML : input_boolean, input_datetime, input_select) ; +2. Sur la carte : sélectionner le véhicule au branchement (VW ou JEEP) ; +3. Activer « ⚡ Réseau avant départ (P9) » ; +4. 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 : `dayVehicleFullIdle` collant = 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. + Neutralise `vehicleChargeCompleted`, `dayVehicleFullIdle`, + `departureChargeDone` ET `evcsStatusCharged` (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" +1. DÉBRANCHEMENT (vehicle_not_connected) ; +2. CHANGEMENT DE VÉHICULE / nouvelle connexion (charging_time ≤ 90 s, P11) ; +3. 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 :** +1. **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. +2. **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. +3. **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 SOC` de 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é : +```javascript +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) +1. Logbook doit montrer `turn_off_evcs_override_released` peu après la + fermeture de fenêtre si le véhicule n'est pas encore flag "terminé" ; +2. La PV doit remonter (MPPT non-plafonné) immédiatement après ; +3. Aucune coupure/rallumage répété (`turn_off_evcs_override_released` suivi de + près par `turn_on_evcs_override` ré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 : +1. `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é. +2. 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 W` contre **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é dans `docs/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 : +1. **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). +2. **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 sur + `edf on|off`, jamais sur le mot `MPPT`. + +### 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 garde `is not none` pour une entité + absente), plus `grid_settle_seconds` (helper **optionnel** + `input_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.gridSettleSeconds` borné [0,900]. +- **WINDOWS_SOURCEV2** : `derived.gridSettling = gridAvailable && age < seuil`. +- **BATTERY_CLOUD_LOCKS** : pendant la fenêtre, `rawSolarStartOk` est faux (on ne + démarre pas sur des mesures fausses), les deux termes de mesure de + `rawSolarHoldOk` (plancher de décharge, seuil PV) sont neutralisés (on ne laisse + pas une charge en cours s'arrêter à cause d'elles), `probeFailedNow` est + suspendu (le réseau masque la production, la sonde n'échoue pas) et + `solarShouldStepUp` est bloqué. **Les step-down ne sont pas bridés** : c'est la + direction sûre. +- **HA_SERVICE_CALLS_OUTPUT** : trace `ETABLISSEMENT reseau Xs/Ys` dans 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) +1. ✅ fenêtre d'établissement ; +2. 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é ; +3. 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). + +1. **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. +2. **Et quand il l'apprenait, il ne baissait pas vite.** La branche + `charging_step_down_system_cap` de `DECISION_ENGINEV2` exigeait + `lifecycle.currentCooldownElapsed` — un compteur de **confort** — et ne + descendait que de `currentStepDownA` par 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** : + +```js +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 +3. 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 optionnel + `input_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 et `force_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=XW` dans 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, keepalive `R//keepalive` obligatoire. +- **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 +1. Contrôleur vivant → aucune écriture, une ligne de veille par heure. +2. 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. +3. 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 +1. **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. +2. **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//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` + (paquet `victron_evcs_battery_dynamic.yaml`, sauvegarde dans `packages/SAVE/`, + créé par `input_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`, et `batterySoftDischargeHoldOk` = 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`. + (`gridHoldOk` et `batteryAssistNightHoldOk` utilisaient 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-charge` reste 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 +1. Journal : plus aucun `stop_conditions_lost` en pleine charge solaire à + batterie pleine ; la trace `Batt souple X/12min` n'apparaît que pendant un + dépassement réel. +2. `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. +3. 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.** + +```js +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) +2. **Lire `input_boolean.evcs_balance_pv_censored`** pour conditionner la + provocation `solar_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 bridee` a 20:00) : l'approximation disait « oui » au moment + exact ou la chose approximee devenait fausse. +3. **Lire `evcs_balance_required_soc` / `_energy_to_cover_kwh`** pour 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 par + `states.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/template` AVANT integration, + puis gabarit complet re-rendu : 191 champs, JSON valide, aucun null. +- **INPUT_PARSE** : `mpptReadableCount` / `mpptAtMaxPowerCount` / + `mpptHoldingBackCount`. Les lectures `unavailable`/`unknown`/vides sont + ecartees — on ne devine jamais un etat. +- **BATTERY_CLOUD_LOCKS** : + `solarProvocationJustified = !(lectures exploitables && aucun tracker ne se retient)`, + exige par `batteryAssistSolarStartOk` et par la branche `solar_potential` de + `solarSurplusStartOk`. + +⚠️ **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 trackers `mppt_active`) → `idle_wait_conditions` ; +- `provocation-trackers-illisibles-comportement-anterieur` : meme releve, + lectures `unavailable` → `start_min_current` (le repli est teste, pas + seulement affirme). +- `excedent-censure-demarre` et `apresmidi-provocation-p3` portent 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 +3. **Lire `evcs_balance_required_soc` / `_energy_to_cover_kwh`** pour 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_balance` sur 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 : + +```js +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_status` exactement `ok` ; +- 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). +- `houseSpikeDetected` reste 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` : + +```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_soc` est donc systematiquement TROP BAS ... c'est le facteur +> 1,75 » — etait FAUSSE, sur les deux points. +> 1. 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). +> 2. 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.** + +```js +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_soc` sous-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. + `requiredSocAtStartPct` reste **NON ecrete** au-dela de 100 (§26 : signal de + dimensionnement) ; l'extrapolation y utilise la pente haute. +- **`src/nodes/INPUT_PARSE.js`** : `batteryUsableCapacityKwh` 17,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_kwh` reste 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 +1. **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. +2. **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 en `mode=ro` seul, elle est saine. +3. **Le volume de 10.10.10.17 n'est pas de 133 Go.** J'avais interroge `df` sur + `/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, capture + `df -h` a l'appui. + ⚠️ Le montage n'est PAS `/mnt/ARCH3` mais `/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) +1. **`HA_SERVICE_CALLS_OUTPUT`** — `gridSessionActive` inclut desormais + `night.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. +2. **`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 +1. Voiture finie → la consigne doit RESTER haute, pas retomber a 200 ; +2. SOC quasi plat entre la fin de charge et 07:24, au lieu de −27 points ; +3. `ess_batterylife_state` ne doit jamais atteindre `recharge_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 ; tags `entity_id` (**sans le prefixe de domaine**), + `domain`, `installation` ; champ numerique `value`. +- `_measurement` = **l'entity_id complet** (`sensor.…`, `switch.…`) pour les + entites textuelles ; champ `state` = 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 : + +1. **`ts` n'est pas un instant de cycle.** `historise-evcs.mjs` horodate chaque + mesure avec son propre `last_updated` — le vrai instant de la mesure, et + c'est le bon choix. Mais deux grandeurs ne partagent alors presque jamais le + meme `ts` : **2656 lignes pour 2656 instants distincts**. Un `GROUP BY ts` + rend 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. +2. **Le service n'ecrit que les changements** (`ON CONFLICT DO NOTHING` sur + `(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_value` sur les paliers numerotes par `count(x) OVER (ORDER BY t)`), + qui est aussi ce que fait le controleur en temps reel. **430/435 apres.** +3. **L'energie horaire par `sum(valeur)/60`** suppose 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 +1. **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. +2. **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. +3. **Retenu** : axe des minutes complet (`generate_series`), report de la + derniere valeur connue, puis `avg 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 `influxdb` de HA n'ecrit **que sur changement d'etat**. Une + fenetre `range(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. +- `pivot` rend les colonnes par ordre alphabetique (A, V, W) : une + transformation `organize` retablit tension / courant / puissance. + +### Ce qui reste ouvert +- Le tableau Energie de HA pourrait etre configure avec les compteurs SAINS + (trois `*_total_yield` en sources solaires, quatre compteurs MultiPlus pour + le reseau). Il manquerait la batterie, faute de compteur monotone — a creer + par integration de Riemann sur `sensor.victron_battery_power` si 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 +1. **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. +2. **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. +3. **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. +4. **`cell_imbalance` est reste `no_alarm` tout 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. +5. **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 `** (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. + ⚠️ `data` numerote les cellules de 0 a 14, `datalist history dtls` de 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 ` : 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 `, `soh `, `info ` 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 history ` | un point toutes les 30 min | ~5 semaines | +| **`data event `** | **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 : +1. **relever le plancher a 45 %** (`input_number.victron_evcs_soc_stop_min` et + la logique de plancher dur) — une ecriture, effet immediat, coute 3,1 kWh ; +2. **retirer `…0322` et `…0319`** — memes 3,1 kWh perdus, mais le bridage + disparait aussi ; demande une intervention physique ; +3. ne rien faire cette nuit et perdre encore un peu de cette cellule. + +### Outillage +`--tampon event` ajoute a `--historique-distant`. Et +**`--decalage-minutes `**, 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 `…0322` et `…0319` sont 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 : +1. 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 ; +2. 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 +1. `input_datetime.victron_grid_charge_high_last_at` doit avancer pendant la + cuisson et se figer ensuite ; +2. au plus UNE fermeture et UNE ouverture du contacteur sur la soiree ; +3. la raison doit afficher `Charge maison retombee a XW depuis Y min : on garde + le reseau jusqu'a 20 min (reserve deja condamnee...)` ; +4. si la tension batterie se remet a disparaitre, `plafond_decharge_w` doit + 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`) : +1. **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. +2. `last_session_energy` et `last_session_time` sont 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. +3. Plus aucun `GREATEST` sur une grandeur d'energie. +4. **Les treize capteurs `sensor.evcs_*` de la borne entrent dans + l'historisation InfluxDB** (`configuration.yaml`, sauvegarde dans + `SAVE_CONFIG/`) : c'est leur absence qui a empeche d'etablir la semantique. + ⚠️ Prend effet au prochain redemarrage de Home Assistant (les filtres + `include`/`exclude` d'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 < 1500` doit 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 `.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 --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 `video` dont 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` : + +1. **`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. +2. **Le genre `bilan` est 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. + +1. **`FILTRE_VUE` ne 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. +2. **`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 : + +```sh +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` : + +```sh +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": , "to": , "queries": []}`. +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_FORECAST` publie `input_text.evcs_balance_forecast_day0` + (la date de la courbe « today ») ; +- le gabarit du bilan la lit ; +- `RESOURCE_ESTIMATE` remplace 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 +1. Creer le helper `input_text.evcs_balance_forecast_day0` dans + `/mnt/ARCH3/docker/HA/packages/evcs_energy_balance.yaml`, a cote de + `evcs_balance_forecast_today`, puis `input_text.reload` — aucun redemarrage. +2. Pousser les deux workflows : `evcs-solar-forecast-v1.json` (publie la date) + et `evcs-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. + +1. **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. +2. **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: on` + avec l etat controleur reste a `vehicle_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 +1. Sous 80 % de SOC, DVCC a **150 A** et courant reellement pris — s'approche-t-il + du nouveau plafond, ou plafonne-t-il ailleurs ? +2. `tension_minimale/maximale_de_la_cellule` : l'ecart doit rester de quelques + millivolts. S'il s'ouvre, on s'arrete a ce palier. +3. Nombre de chargeurs `voltage_current_limited` a 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_debut` en LISTE (ecritures sequentielles sur un + helper unique perdraient une confirmation sur deux, mon service ne passant que + toutes les 60 s). +- La clef `debut` porte les **millisecondes** : renvoyer la chaine verbatim, + jointure sur ±1 s. +- `input_text` : declarer `max: 255` — le defaut est 100, et un rejet est + SILENCIEUX (incident `forecast_updated` du 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 +1. **Bascule de `vehicule_sur`** : `DEFAULT false`, `true` UNIQUEMENT sur + confirmation humaine. +2. **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 »). +3. 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 :** +1. 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`, `currentA` sont verifies. +2. 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 : +1. « Le garde dur bloquerait P12 a −30 A » — **faux**, je lisais les valeurs de + BASE de la table en croyant lire les valeurs APPLIQUEES. `parkScale` existe + et vaut 13/4 ; le garde dur applique vaut −98 A. +2. « 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 / 7` et donne 0,314 kWh/point, contre **0,308 mesure**. + Quant a `usable_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 : + +```json +{"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) +1. **Un horodatage manquant n'est plus une panne.** On prend le plus recent + `startedAt` EXPLOITABLE 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. +2. **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. +3. **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 parametre `jusqu`. +- 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 +1. **La mesure ne doit pas suivre la fenetre dans le futur.** `ENERGIE_SQL` + construisait 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 desormais `least($__timeTo(), now())`. + `BILAN_BASE` etait deja immunise : il derive ses bornes de `min(ts)/max(ts)` + des donnees, pas des macros. +2. **`FULL OUTER JOIN`, pas `LEFT`.** Une periode a venir n'a AUCUNE ligne de + mesure et une periode passee aucune prevision ; un `LEFT JOIN` depuis la + mesure aurait fait disparaitre toutes les barres de prevision. +3. **Borne haute STRICTE.** Avec `<=`, « La journee » affichait une barre + `18/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 : +1. **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. +2. **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** : + +1. **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_active` a + 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. +2. **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 `decision` repond directement : `source_mode = Solaire` + de bout en bout, donc `gridSessionActive` faux et consigne au repos. +3. **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_allowed` de midi n'a aucune condition de vehicule** + (`WINDOWS_SOURCEV2.js` ~888) mais c'est la **cinquieme** alternative de sa + branche, apres `strongSolarSignal`, `isHighBatteryForPotential` et les deux + sondes. A midi il faut une journee franchement sombre pour y descendre — d'ou + le zero. +- **`nightGridSessionCommitted` ne couvre PAS les nuits ordinaires** : + `session_engagee` est faux 85 fois sur 88. Les nuits avec voiture tournent sur + `nightSessionActive` + takeover. Ce terme ne sert qu'a la nuit SANS vehicule — + reelle et mesuree : `vehicle_connected` tombe a `off` le 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 dans `WINDOWS_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* +