Le bilan rendait sept tirets et l'histogramme « No numeric fields found », alors que verifier-dashboards donnait les deux requetes vertes : il prouve qu'une requete s'execute, pas qu'elle s'affiche. 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 a « Le systeme ». Sur un tableau qui n'a pas cette variable, elle filtre tout. Retiree. champs="/.*/" est reserve aux etats d'alarme, qui sont du texte ; sur des colonnes numeriques il empeche le rendu. Remis a "" comme le panneau « Sur la periode » des vehicules, et les unites kWh et % ajoutees en overrides — sans elles les chiffres etaient nus. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GwpFpy2aQ2rJok6tNQSbVj
5557 lines
303 KiB
Markdown
5557 lines
303 KiB
Markdown
# HANDOFF.md — EVCS / Grid Hysteresis — État au 09/07/2026
|
||
|
||
## 1. Contexte Projet
|
||
|
||
- **Installation** : Victron ESS **sans injection réseau**. Batterie 17,5 kWh utile.
|
||
- **Workflows n8n actifs** :
|
||
- `HA Snapshot EVCS Surplus Controller V2.6` (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/<path>/sse`.
|
||
Les 4 outils paramétrés utilisent `$fromAI(...)` (corrigé le 09/07).
|
||
⚠️ **Endpoints MCP n8n sans authentification** — à sécuriser (Bearer sur les nodes MCP Server Trigger).
|
||
- **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/<id>` → `modified/*_backup_<stamp>.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/<id>` (⚠️ 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: <nom>" AVANT le JSON => json.load ÉCHOUE.
|
||
Extraction validée : grep -n '"name":"NOEUD"' <fichier> 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/<nom-skill>/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: <nom>" 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 <id> <fichier>`** : GET workflow → JSON local (clé API lue
|
||
automatiquement dans `~/.config/opencode/opencode.json`).
|
||
- **`tools/n8n-push.mjs <fichier> [id]`** : PUT REST (body minimal validé 04/08) avec
|
||
**backup serveur auto pré-push** (`modified/prepush-*.json`) 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=<motif>` : filtre par id de scénario
|
||
|
||
### Intégration au déploiement (le point clé)
|
||
`tools/n8n-push.mjs` exécute la suite AVANT tout PUT du workflow EVCS :
|
||
**golden rouge = rien n'est poussé**. Échappatoire consciente : `--skip-golden`.
|
||
Évolution volontaire du comportement → mettre à jour les attendus dans
|
||
`scenarios.mjs` DANS LE MÊME COMMIT que le patch.
|
||
|
||
### Validation par mutation
|
||
P7 volontairement cassé (`nightGridHcPreferred = false`) → la suite détecte
|
||
2 échecs avec diffs précis (sourceMode, takeover, EDF) et bloque le push ;
|
||
restauration → 16/16. Le filet fonctionne.
|
||
|
||
### Résumé testé par scénario
|
||
profile, sourceMode, state, decision, desiredCharging, currentA,
|
||
takeoverNeeded, action EDF ON, service helper takeover.
|
||
|
||
### Notes de comportement figées (découvertes en écrivant les attendus)
|
||
- SOC 20 la nuit avec maison sur batterie (−14 A) → `battery_current_hard_stop`
|
||
(bande lt25 : hard −8 A) + demande réseau simultanée. Protection cohérente.
|
||
- `force_charge_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/<NODE>.js
|
||
node tools/n8n-push.mjs workflows/evcs-surplus-controller-v2.6.json
|
||
→ build src→JSON → node --check → GOLDEN 19/19 (bloquant) → backup serveur → PUT
|
||
git commit (src + json + scénarios si évolution voulue) && git push
|
||
```
|
||
JAMAIS d'édition directe du JSON ou de l'UI n8n (écrasée au prochain push).
|
||
|
||
### Validations effectuées
|
||
- Extract/build idempotents (extract → build → --check : zéro diff).
|
||
- Test mutation : édition src → build injecte 1 node → restauration git+extract OK.
|
||
- Pipeline complet de push exécuté en réel : build (no-op) → golden 19/19 → PUT OK.
|
||
- Nota : tailles `ls` ≠ longueur de code (octets UTF-8 vs caractères, accents FR).
|
||
|
||
### Plan de maintenabilité : état
|
||
- R1 ✅ déduplication moteur nuit (07/08)
|
||
- R3 ✅ suite golden 19 scénarios bloquante (07/08)
|
||
- R2 ✅ git source de vérité + build/push (07/08)
|
||
- Suivant : agent IA planificateur en MODE OMBRE (jamais dans la boucle 30 s) —
|
||
helpers advisor_*, validation bornée des sorties, kill-switch, cf. HANDOFF §13/§14.
|
||
|
||
## 16. Session du 07/08/2026 (nuit) — Agent IA planificateur en MODE OMBRE
|
||
|
||
### Ce qui tourne désormais
|
||
Workflow n8n **"EVCS Advisor Shadow V1"** (id `tdfInZns7cqo8fU2`, actif, 15 min) :
|
||
```
|
||
Schedule 15min → HA Snapshot compact (template Jinja {{ {...} | tojson }})
|
||
→ BUILD_PROMPT (kill-switch input_boolean.evcs_advisor_enabled, prompt FR ~4K chars)
|
||
→ IF_ENABLED → LLM_LOCAL_MODELS (GET 10.10.10.8:8000/v1/models, 5s)
|
||
→ LLM_LOCAL (modèle DYNAMIQUE = celui chargé sur la Spark, max_tokens 6000)
|
||
→ IF_LOCAL_OK → sinon LLM_CLOUD (OpenRouter anthropic/claude-haiku-4.5,
|
||
credential chiffré "OpenRouter Advisor" id eOM9xofDHT0KMoeY — clé JAMAIS dans git)
|
||
→ VALIDATE_PARSE (strip <think>, JSON strict, enums+bornes, invalide = REJET)
|
||
→ BUILD_HA_CALLS (helpers advisor_* + divergence + compteurs + logbook)
|
||
→ Execute HA Service
|
||
```
|
||
|
||
### GARANTIE MODE OMBRE (structurelle, testée)
|
||
BUILD_HA_CALLS n'écrit QUE vers `input_text/input_number.evcs_advisor_*` et
|
||
logbook. Le test `tests/advisor/run.mjs` ÉCHOUE si une écriture sort de ce
|
||
périmètre (16 tests : kill-switch, réponses valides/hors-bornes/non-JSON/
|
||
<think>/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. Cohérent avec « sans injection », à confirmer comme voulu.
|
||
|
||
### 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.
|
||
|
||
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/<portail>/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/<portail>/settings/0/Settings/CGwacs/AcPowerSetPoint`, relecture
|
||
conforme, remise de la valeur d'origine par le script lui-même. Le `--dry-run`
|
||
supprimait justement ce maillon-là ; sans cette preuve, le chien de garde
|
||
n'était que probablement fonctionnel. Test fait EDF ouvert, donc consigne
|
||
inerte : risque nul.
|
||
|
||
**Et la séquence de fin de session s'est déroulée dans le bon ordre**, sans
|
||
que personne l'ait orchestrée :
|
||
|
||
| 03:31:30 | fin de charge → `night_recovery_done_idle`, et **la consigne retombe à 50 W dans le même cycle** |
|
||
| 03:32:04 | 34 s plus tard, l'hystérésis ouvre le contacteur (`turn_off_recovery_done`) |
|
||
|
||
La demande baisse AVANT que le réseau parte — le principe de la séquence
|
||
d'ouverture (§30), obtenu ici par construction et non par coordination. C'est
|
||
la garantie que le risque introduit en §31 (consigne laissée haute) ne se
|
||
matérialise pas en fonctionnement normal ; le chien de garde ne couvre plus que
|
||
le cas de la panne.
|
||
|
||
**Bilan de la nuit HC** : charge démarrée à 01:58 après correction, menée à son
|
||
terme à 03:31 sans jamais entamer la réserve (batterie tenue entre −2 et +1 A
|
||
pendant toute la session), SOC stable à 49 %.
|
||
|
||
**Clé API n8n** : désormais aussi dans `.env` à la racine (convention du
|
||
propriétaire), en plus de `~/.config/opencode/opencode.json`. Le repli du chien
|
||
de garde sur `.env` est vérifié — utile pour tout déploiement hors poste de
|
||
travail.
|
||
|
||
## 35. Session du 01/09/2026 — La table des bandes SOC déclarait un temps que personne ne lisait
|
||
|
||
### Bilan de la nuit 31/08→01/09 (contexte de la découverte)
|
||
Deux sessions réseau : 01:24→03:32 (charge HC, **6,51 kWh importés**, dont
|
||
**5,14 kWh dans la VW**, batterie tenue entre −2 et +2 A, SOC plat à 49 %) et
|
||
07:12→09:51 (batterie tombée à 20 %, reprise par l'hystérésis, **0,70 kWh**).
|
||
À l'ouverture du contacteur de 09:51, en plein jour : PV 2,7 kW avant, 2,7 kW
|
||
après, import résiduel 40 W — **aucune éviction du solaire**, la correction de
|
||
consigne du 31/08 tient. Journée : 27,85 kWh produits, 6,81 kWh dans la voiture
|
||
en solaire, 0,39 kWh de réseau.
|
||
|
||
**Le point faible de cette nuit n'est pas un bug, c'est la définition de la
|
||
cible.** À 03:31 le SOC (49 %) dépassait la cible du matin (35 %) → recovery
|
||
done, EDF relâché. Trois heures quarante plus tard la batterie était à 20 % et
|
||
l'hystérésis a dû rallumer. Pour traverser jusqu'au relais solaire (~09:00) il
|
||
aurait fallu partir de **~64 %**. La cible est un niveau à un instant ; ce qu'il
|
||
faut est une énergie à couvrir jusqu'au croisement PV/conso. C'est le chantier
|
||
du bilan énergétique, pas un seuil de plus.
|
||
|
||
### Le défaut : un critère physique évalué sur du bruit
|
||
Deux arrêts `stop_conditions_lost` le 01/09, à **14:26:00** et **14:56:30**,
|
||
véhicule en charge à 20 A sous 6,6 kW de PV.
|
||
|
||
La règle de maintien exige `courant batterie ≥ plancher`, plancher effectif
|
||
**0 A** (`input_number.victron_evcs_battery_discharge_soft_a`, curseur borné
|
||
à [−50, 0]). Or à SOC 94 la batterie est en float. Mesure brute au pas de 2 s
|
||
dans la minute précédant l'arrêt :
|
||
|
||
```
|
||
14:25:34 +1,8 14:25:44 0,0 14:25:54 −0,4
|
||
14:25:38 −1,0 14:25:46 −0,4 14:25:56 −1,6
|
||
14:25:40 −2,6 14:25:50 +1,0 14:25:58 −0,7
|
||
```
|
||
|
||
Ce n'est pas une décharge, **c'est le bruit de régulation d'une batterie qui ne
|
||
fait rien**. Le contrôleur en prélève un échantillon toutes les 30 s et le
|
||
compare à zéro : pile ou face. Les états `hold_min_run` qui alternent avec
|
||
`charging_hold` toutes les 1 à 2 minutes dans le journal SONT ces tirages
|
||
perdants — sauvés uniquement parce que le `min_run` (29 min) n'était pas
|
||
écoulé. Quand un échantillon négatif tombe après le `min_run`, la charge
|
||
s'arrête. Le second arrêt avait une cause réelle mais fugace : pointe maison à
|
||
**8 016 W** pendant quelques secondes, batterie à −42 A.
|
||
|
||
**Coût mesuré :** à l'arrêt la PV s'effondre de 6 633 à **2 554 W** en 30 s —
|
||
batterie pleine, le véhicule était le seul preneur — et la reprise repart à 6 A
|
||
pour mettre **21 minutes** à revenir à 20 A. ≈ **0,45 kWh de production refusée
|
||
par arrêt**. Fréquence : 1 le 26/08, 4 le 27, 6 le 28, 4 le 29, 4 le 30.
|
||
|
||
### Ce que le système savait déjà, et que rien ne lisait
|
||
`INPUT_PARSE` construit depuis toujours une table de politique batterie par
|
||
bande de SOC, avec **deux régimes explicitement temporels** :
|
||
|
||
| bande | souple | tolérance souple | dur | tolérance dure |
|
||
|---|---:|---:|---:|---:|
|
||
| > 90 | −50 A | **12 min** | −100 A | 20 s |
|
||
| 80-90 | −40 A | 10 min | −80 A | 20 s |
|
||
| 60-80 | −20 A | 6 min | −50 A | 15 s |
|
||
| 40-60 | −12 A | 4 min | −30 A | 10 s |
|
||
| 25-40 | −5 A | 2 min | −15 A | 5 s |
|
||
| < 25 | −3 A | 1 min | −8 A | 2 s |
|
||
|
||
`batterySoftToleranceMinutes` et `batteryHardToleranceSeconds` étaient calculés
|
||
(BATTERY_CLOUD_LOCKS l.185-186), exportés dans le debug, et **branchés sur
|
||
aucune décision**. Toute la dimension temporelle de la politique batterie était
|
||
morte. Troisième mécanisme dans ce cas — `houseSpikeDetected` n'allonge que la
|
||
tolérance *nuage*, jamais la garde batterie, et son entrée
|
||
`house_ac_power_prev_w` lit **le même capteur** que `house_ac_power_w` dans le
|
||
gabarit de snapshot, donc il vaut structurellement `false` (non corrigé ici,
|
||
la tolérance rend ce chemin redondant — à traiter séparément).
|
||
|
||
### Correctif (déployé 01/09 20:08, golden 61/61)
|
||
**Purement temporel : aucun seuil n'est déplacé, aucune constante ajoutée.**
|
||
On applique la table telle qu'elle est écrite — un dépassement souple doit
|
||
DURER pour compter.
|
||
|
||
- Nouveau helper de persistance `input_text.victron_evcs_battery_soft_breach_at`
|
||
(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 |
|
||
| `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 <n>`** (quinze
|
||
enregistrements par page). Tout est reproductible depuis Linux.
|
||
|
||
### Cinq cellules sur quinze, et le BMS le dit lui-meme
|
||
Sur 1798 enregistrements du module **K220803C30160319, adresse 3** :
|
||
|
||
| cellule | tension max | tension min |
|
||
|---|---:|---:|
|
||
| 1, 3, 4, 5, 7, 8, 9, 11, 13, 14 | 3436 a 3445 mV | 3251 a 3256 mV |
|
||
| **2, 6, 10, 12, 15** | **3554 a 3649 mV** | **3128 a 3236 mV** |
|
||
|
||
Ces cinq-la **montent plus haut en charge ET descendent plus bas en decharge** :
|
||
signature d'une capacite moindre, elles se remplissent et se vident avant les
|
||
autres. Ce n'est pas une erreur de mesure — le BMS leur attribue d'ailleurs un
|
||
pourcentage de charge different des dix autres (100 % contre 96 %) dans chaque
|
||
enregistrement.
|
||
|
||
Deux sources independantes concordent exactement : le groupe deduit des
|
||
trente-sept jours d'historique et les cinq cellules les plus basses du releve
|
||
instantane du 05/09 08:35 sont **les memes cinq**.
|
||
|
||
### L'ecart n'apparait qu'en haut, ce qui explique tout
|
||
Ecart entre cellules, en charge :
|
||
|
||
| SOC | ecart median | cellule la plus haute |
|
||
|---|---:|---:|
|
||
| 60-69 % | **12 mV** | 3416 mV |
|
||
| 70-79 % | 27 mV | **3621 mV** |
|
||
| 80-89 % | **168 mV** | **3649 mV** |
|
||
|
||
A 60-69 % le module est irreprochable. C'est pourquoi une capture du matin ne
|
||
peut rien montrer, et pourquoi le proprietaire « ne voyait rien » dans ses logs
|
||
precedents. 3649 mV place la cellule dans la zone de protection : le BMS reduit
|
||
alors le courant de charge autorise pour tout le parc.
|
||
|
||
### Le contraste avec un module sain est brutal
|
||
Compteurs `stat`, deux modules du meme parc :
|
||
|
||
| | K220803C30160322 (adresse 1) | K220803C30160319 (adresse 3) |
|
||
|---|---:|---:|
|
||
| Bat HV Times | **7** | **416** |
|
||
| Bat OV Times | 8 | 179 |
|
||
| CYCLE Times | 0 | 24 |
|
||
| SOH | 100 | 0 |
|
||
|
||
Et c'est le module que le proprietaire croyait « le moins merdique ». Le module
|
||
qu'il juge le plus abime, sa BAT3, est deja hors du parc — il n'est pour rien
|
||
dans le bridage quotidien. **Le bridage vient d'un module encore en service.**
|
||
|
||
⚠️ **CORRECTION du 05/09 12:05 — le champ `SOH` de `stat` n'est PAS une note de
|
||
sante sur 100.** J'avais ecrit « le BMS note lui-meme ce module 0 sur 100 ».
|
||
C'est faux : la lecture en direct de la chaine montre un module 2 irreprochable
|
||
par toutes les autres mesures (0 evenement de surtension, 0 `SOHCount` sur ses
|
||
quinze cellules, 2 mV d'ecart) et qui affiche pourtant `SOH 0`. Les grandeurs
|
||
reellement discriminantes sont `Bat HV Times`, `Bat OV Times`, `SOH Times`, et
|
||
surtout le `SOHCount` par cellule que renvoie la commande `soh [adresse]`.
|
||
|
||
### C'est chronique, pas en fuite
|
||
Depassements de 3550 mV : 1 a 4 releves par jour, **tous les jours depuis le
|
||
30/07**, sans tendance. Le desequilibre est installe et stable. C'est une
|
||
bonne nouvelle pour la reparation : rien ne s'emballe.
|
||
|
||
### L'horloge du module avance d'environ huit heures
|
||
Le dernier enregistrement porte `26-09-05 16:42:48` alors que l'export a ete
|
||
fait a 08:35. Correlation du SOC du module contre le SOC du parc mesure dans
|
||
InfluxDB : minimum net a **7 h 45 - 8 h 15 d'avance** (ecart moyen 9,7 points
|
||
contre 30 points sans decalage). La correlation sur le courant donne 6 h,
|
||
mais elle est ecartee : un enregistrement ne peut pas etre posterieur a
|
||
l'export qui le lit.
|
||
|
||
⚠️ **Toute date lue dans l'historique du BMS doit etre corrigee d'environ
|
||
-8 h** avant d'etre comparee a InfluxDB, aux journaux n8n ou a Home Assistant.
|
||
A remettre a l'heure depuis la console.
|
||
|
||
### Outils ajoutes
|
||
- **`tools/pylontech-console.mjs`** — console serie, sans aucune dependance
|
||
(stty pour la ligne, fs pour les octets). `--detecter` · `--commande help` ·
|
||
`--historique` (extraction paginee complete) · **`--suivi 30`** (releve
|
||
cellule par cellule toutes les 30 s, horodate a l'heure REELLE de la machine
|
||
et non a celle du module, en CSV). Le lecteur de table a ete eprouve sur la
|
||
sortie reelle de BatteryView avant tout branchement.
|
||
⚠️ `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 <adresse>` : le compteur de sante par cellule, en une seconde
|
||
C'est LA commande a retenir. Elle donne les quinze `SOHCount` d'un module sans
|
||
extraire son historique. Verdict de la chaine :
|
||
|
||
| module | code-barres | cellules marquees (`SOHCount`) | total |
|
||
|---|---|---|---:|
|
||
| 1 | K220803C30160322 | 6 (334) · 7 (50) · 12 (158) · 14 (8) | **550** |
|
||
| 2 | — | aucune | **0** |
|
||
| 3 | K220803C30160319 | 2 (132) · 6 (181) · 10 (146) · 12 (254) · 15 (400) | **1113** |
|
||
|
||
Le module 3 designe **exactement les cinq cellules** deduites le matin meme de
|
||
ses 37 jours d'historique. Deux methodes sans rapport, meme resultat.
|
||
|
||
### Le module 2 donne enfin un etalon
|
||
Quinze cellules entre 3363 et 3365 mV — **2 mV d'ecart**, zero surtension, zero
|
||
compteur. Voila a quoi ressemble un US2000C sain sur cette installation.
|
||
|
||
### CORRECTION — `SOH` dans `stat` n'est pas une note sur 100
|
||
J'ai ecrit le matin « le BMS note ce module 0 sur 100 ». **C'est faux** : le
|
||
module 2, irreprochable par toute autre mesure, affiche lui aussi `SOH 0`.
|
||
Les grandeurs discriminantes sont `Bat HV Times`, `Bat OV Times`, `SOH Times`
|
||
et le `SOHCount` par cellule. Le diagnostic tient ; c'est cet argument-la qui
|
||
tombe.
|
||
|
||
### Le module 1 s'est effondre depuis juillet
|
||
Memes compteurs, meme module, six semaines d'ecart :
|
||
|
||
| | 24/07 | 05/09 |
|
||
|---|---:|---:|
|
||
| Bat HV Times | 7 | **498** |
|
||
| Bat OV Times | 8 | **86** |
|
||
| Shut Times | 4 | **751** |
|
||
|
||
Il a desormais **plus** d'evenements de surtension que le module 3. Son
|
||
historique (1681 enregistrements, 14/08 au 05/09) designe les cellules
|
||
**6, 7 et 12** — les memes que son `SOHCount`, quatrieme concordance.
|
||
|
||
**La cellule 12 oscille entre 2953 et 3644 mV**, soit 691 mV d'amplitude,
|
||
quand les douze cellules saines de ce module font 178 mV. Elle est au bord de
|
||
la coupure basse.
|
||
|
||
### Consequence pour le bilan energetique : la jauge de SOC est fausse par le bas
|
||
Les pires ecarts du module 1 ne sont pas en haut mais **en bas** : 317 mV a
|
||
« SOC 2-3 % » le 26/08, alors que le parc etait a 48,55 V, soit 3,236 V par
|
||
cellule en moyenne — un etat de charge reel de l'ordre de 25 %, pas 2 %. Le
|
||
BMS voit sa cellule 12 a 2953 mV, la croit vide, et annonce 2 %.
|
||
|
||
⚠️ **C'est tres probablement l'explication de l'effondrement de la jauge en bas
|
||
d'echelle** documente en §28 et mesure en §41 (0,097 kWh par point sous 33 %
|
||
contre 0,169 au-dessus). La jauge ne mesure pas le parc : elle suit la cellule
|
||
la plus faible du module le plus faible. Le « genou » a 32-33 % n'est pas une
|
||
propriete du LiFePO4, c'est la signature de la cellule 12 du module 1.
|
||
A reverifier apres reequilibrage — la courbe devrait se redresser.
|
||
|
||
### Horloge
|
||
`pwr` horodate les trois modules a `2026-09-05 18:07:5x` alors qu'il etait
|
||
11:58 : **environ 6 h 10 d'avance**, mesure directement. L'estimation de 8 h
|
||
obtenue le matin par correlation est superseded par cette lecture directe.
|
||
La commande `time` permet de corriger — ecriture, donc en attente de
|
||
validation du proprietaire.
|
||
|
||
### Correction d'outil
|
||
`--historique` bouclait sur des index croissants alors que **une seule commande
|
||
`datalist history dtls 1` pagine deja jusqu'au dernier enregistrement** : les
|
||
119 commandes suivantes relancaient le meme vidage complet. Corrige.
|
||
|
||
---
|
||
*Document mis a jour le : 05/09/2026 (~12:15) — chaine BMS lue en direct depuis Linux ; deux modules atteints sur trois ; la jauge de SOC suit la cellule la plus faible*
|
||
|
||
## 51. Session du 05/09/2026 (12h30) — Le parc est sain a cinq modules sur sept, et le hub n'est pas coupable
|
||
|
||
### RETRACTATION : je me suis alarme trop vite sur le LVHUB
|
||
J'ai ecrit en §50 que la moitie du parc etait « electriquement presente mais
|
||
invisible pour le systeme qui le pilote ». **C'est faux, et la mesure le dit
|
||
sans ambiguite.**
|
||
|
||
Lecture simultanee, cable sur le pack 2 :
|
||
|
||
| | courant |
|
||
|---|---:|
|
||
| pack 2, quatre modules (12,93 + 12,43 + 12,39 + 12,90 A) | **50,65 A** |
|
||
| ce que le GX rapporte (`battery/512/Dc/0/Current`) | **90,60 A** |
|
||
| difference, soit les trois modules du pack 1 | **39,95 A**, ~13,3 A chacun |
|
||
|
||
Les tensions concordent aussi (50,66-50,72 V cote modules, 50,72 V cote GX).
|
||
**Le GX agrege bien les sept modules**, et les deux packs se partagent le
|
||
courant de facon remarquablement egale — 12,7 A par module sur le pack 2
|
||
contre 13,3 A sur le pack 1, cinq pour cent d'ecart.
|
||
|
||
Ce que j'avais lu sur le hub (`SysError 0xe000`, courants autorises a zero,
|
||
table des unites vide, journal de dix-neuf redemarrages) reste factuel, mais
|
||
n'autorisait pas cette conclusion. Le hub possede une commande `cmdquit —
|
||
Quit the Console Mode` : brancher une console l'y fait probablement entrer, et
|
||
les valeurs lues sont celles de ce mode, pas de son fonctionnement normal.
|
||
**Ne pas rebrancher de console sur le hub sans raison.**
|
||
|
||
### Le parc, module par module — inventaire complet
|
||
Pack 1, code-barres `K220803C...`, fabrique le 03/08/2022 :
|
||
|
||
| adresse | cellules marquees | OV | HV | cycles |
|
||
|---|---|---:|---:|---:|
|
||
| 1 | **6, 7, 12** (la 12 oscille sur 691 mV) | 86 | **498** | 25 |
|
||
| 2 | aucune | 0 | 0 | 33 |
|
||
| 3 | **2, 6, 10, 12, 15** | 179 | **416** | 24 |
|
||
| 4 | eteinte (BAT3), non lue | — | — | — |
|
||
|
||
Pack 2, code-barres `Y230202C...`, fabrique le 02/02/2023 :
|
||
|
||
| adresse | code-barres | cellules marquees | OV | HV | cycles |
|
||
|---|---|---|---:|---:|---:|
|
||
| 1 | Y230202C30371426 | aucune | 1 | 1 | **350** |
|
||
| 2 | Y230202C30371425 | aucune | 1 | 1 | **346** |
|
||
| 3 | Y230202C30371431 | aucune | 1 | 1 | **340** |
|
||
| 4 | Y230202C30371427 | aucune | 0 | 1 | **340** |
|
||
|
||
**Soixante cellules a zero sur le pack 2.** C'est un parc neuf apres 340 cycles.
|
||
|
||
⚠️ **Question ouverte : le pack 1, plus ANCIEN de six mois, affiche 24 a 33
|
||
cycles quand le pack 2 en affiche 340 a 350.** Un facteur quatorze. Soit ses
|
||
compteurs ont ete remis a zero (`Reset Times` 9 a 13), soit il est reste
|
||
longtemps hors service, soit le compteur n'incremente que sur cycle complet et
|
||
le pack 1 n'en acheve jamais parce qu'il est constamment limite. A trancher.
|
||
|
||
### Le vrai probleme, enonce proprement
|
||
Le GX agrege les sept modules, donc **quand une cellule des modules 1 ou 3 du
|
||
pack 1 atteint 3550 mV, le courant de charge de TOUT le parc est reduit** — y
|
||
compris celui des cinq modules sains. Deux modules abimes brident cinq bons.
|
||
|
||
Et le perimetre de la reparation est desormais chiffre : **huit cellules, sur
|
||
deux modules**. Module 1 : 6, 7, 12. Module 3 : 2, 6, 10, 12, 15. Rien d'autre
|
||
dans le parc ne demande d'intervention.
|
||
|
||
### Adressage
|
||
Chaque pack a sa propre chaine et sa propre numerotation 1..N. Les commandes
|
||
`stat <n>`, `soh <n>`, `info <n>` interrogent les modules de la chaine sur
|
||
laquelle le cable est pose. **Deux positions de cable suffisent pour tout le
|
||
parc** ; le hub n'en est pas une.
|
||
|
||
---
|
||
*Document mis a jour le : 05/09/2026 (~12:35) — inventaire complet des sept modules ; retractation sur le LVHUB ; la reparation porte sur huit cellules de deux modules*
|
||
|
||
## 52. Session du 05/09/2026 (12h45) — BAT3 lue isolee : UNE cellule, et elle est en meilleur etat que deux modules restes en service
|
||
|
||
BAT3 = **K220803C30160324**, quatrieme module du pack 1, allumee seule sur son
|
||
propre courant, deconnectee du parc. 322 enregistrements exploitables,
|
||
du 23/07 au 05/09.
|
||
|
||
### Au repos, tout est dit en une ligne
|
||
SOC 93 %, aucun courant, quatorze cellules entre **3327 et 3331 mV** — quatre
|
||
millivolts — et la **cellule 15 a 3274 mV**, cinquante-cinq millivolts en
|
||
dessous. Un module sain de ce parc montre 2 mV d'ecart.
|
||
|
||
### Sur son historique, une seule cellule se distingue
|
||
| | amplitude |
|
||
|---|---:|
|
||
| cellules 1 a 14 | 3282-3307 → 3421-3446 mV, soit **~150 mV** |
|
||
| **cellule 15** | **2939 → 3622 mV, soit 683 mV** |
|
||
|
||
Signature quasi identique a la cellule 12 du module 1 (2953 → 3644, 691 mV).
|
||
**Deux cellules dans le meme etat terminal, sur deux modules differents.**
|
||
La cellule 15 descend sous l'alarme basse de niveau 1 (3100 mV) et monte
|
||
au-dessus du niveau 4 (3600) : d'ou `Bat LV Times 59` et `Bat OV Times 608`.
|
||
|
||
### Le classement du pack 1 est contre-intuitif
|
||
| module | cellules atteintes | pire amplitude |
|
||
|---|---:|---:|
|
||
| **BAT3** (retiree du service) | **1** (la 15) | 683 mV |
|
||
| module 1 (en service) | **3** (6, 7, 12) | 691 mV |
|
||
| module 3 (en service) | **5** (2, 6, 10, 12, 15) | 521 mV |
|
||
| module 2 (en service) | 0 | — |
|
||
|
||
⚠️ **Le module que le proprietaire a retire du parc a MOINS de cellules
|
||
atteintes que deux de ceux qu'il y a laisses.** Neuf cellules malades au total
|
||
sur les 105 du parc, reparties sur trois modules ; les quatre du pack 2 et le
|
||
module 2 du pack 1 sont intacts.
|
||
|
||
### BAT3 est le banc d'essai ideal pour l'alimentation stabilisee
|
||
Isolee, hors service, une seule cellule a traiter, aucun risque pour le parc.
|
||
C'est la ou eprouver la procedure avant de toucher aux modules en service.
|
||
|
||
⚠️ **Limite a garder en tete** : le reequilibrage corrige un DECALAGE de
|
||
charge, pas une PERTE de capacite. Une cellule dont l'amplitude vaut 4,5 fois
|
||
celle de ses voisines a perdu de la capacite : le rattrapage lui rendra sa
|
||
place au repos, mais elle restera la premiere pleine et la premiere vide. Les
|
||
cellules moderement decalees (6 et 7 du module 1, les cinq du module 3) sont
|
||
les meilleures candidates ; les deux cellules terminales le sont beaucoup
|
||
moins. A verifier sur BAT3 plutot qu'a supposer.
|
||
|
||
### Horloge de BAT3 remise a l'heure (05/09 12:48, sur validation)
|
||
Avant : RTC `2026-09-05 18:56:35` pour une heure reelle de `12:47:23`, soit
|
||
**6 h 09 min 12 s d'avance**, mesure directement. C'est cette valeur qui fait
|
||
foi ; l'estimation de 8 h obtenue le matin par correlation etait fausse.
|
||
|
||
⚠️ **L'annee se donne sur DEUX chiffres.** `time 2026 9 5 12 47 53` est refuse
|
||
avec « Invalid command or fail to excute », `time 26 9 5 12 48 3` passe.
|
||
Le message d'usage, lui, affiche `time [year] [month] [day] [hour] [minute]
|
||
[second]` sans le preciser.
|
||
|
||
Apres : RTC `12:48:09` pour `12:48:06` reel — les trois secondes sont la marge
|
||
que j'avais ajoutee pour couvrir la latence de la liaison, qui s'est averee
|
||
negligeable. Sans objet devant un historique au pas de trente minutes.
|
||
|
||
**Pour les modules en service** : les trois modules du pack 1 affichaient la
|
||
meme seconde dans `pwr`, donc le maitre distribue vraisemblablement l'heure a
|
||
sa chaine. A verifier en reglant le maitre puis en relisant `pwr` : si les
|
||
horodatages des esclaves suivent, une ecriture par chaine suffit. `time` ne
|
||
figure pas dans la liste des « Remote command », donc ne pas compter sur un
|
||
adressage direct.
|
||
|
||
### LVHUB — il n'y a rien a reparer, mais une precaution a retenir
|
||
Le proprietaire confirme voir toutes les diodes au vert, aucune alarme. Ajoute
|
||
a la preuve fonctionnelle du §51 (le GX agrege bien les sept modules, les deux
|
||
packs se partagent le courant a 5 % pres), le hub fait son travail.
|
||
|
||
⚠️ **Brancher une console sur le LVHUB le fait redemarrer.** Son horloge etait
|
||
a `2000-01-01 00:00:04` et son journal ne contient que des redemarrages : ce
|
||
sont les traces de nos propres branchements, pas d'une panne. Ne pas y
|
||
rebrancher de console pendant que le systeme tourne.
|
||
|
||
---
|
||
*Document mis a jour le : 05/09/2026 (~12:50) — BAT3 : une seule cellule atteinte ; neuf cellules malades sur 105 dans tout le parc ; le LVHUB est sain*
|
||
|
||
## 53. Session du 05/09/2026 (13h05) — Le declenchement saisi en direct, et l'origine des degats
|
||
|
||
### La chaine de cause a effet, chronometree
|
||
Suivi console des trois modules du pack abime pendant que l'alarme montait,
|
||
recoupe avec les entites Home Assistant :
|
||
|
||
| heure | evenement |
|
||
|---|---|
|
||
| 13:04:48 | module 1 cellule 7 a **3526 mV** · module 3 cellule 6 a **3541 mV** — sous le seuil |
|
||
| 13:05:08 | module 1 cellule 7 a **3555 mV** · module 3 cellule 6 a **3575 mV** — niveau 3 franchi |
|
||
| **13:05:13** | **courant de charge autorise : 50 → 30 A** |
|
||
|
||
**Cinq secondes** entre le franchissement des 3550 mV et l'effondrement du
|
||
plafond. Le seuil `Volt High Level3` lu dans `sysinfo` du LVHUB est donc bien
|
||
celui qui commande, et la reaction est immediate.
|
||
|
||
Ensuite le courant tombe, les cellules se detendent (137 et 163 mV d'ecart deux
|
||
minutes plus tard), **l'alarme repasse a `no_alarm` — et le plafond reste a
|
||
30 A**. C'est ce decalage qui fait durer le bridage des heures alors que
|
||
l'alarme, elle, ne dure que quelques minutes. Chercher l'alarme dans les
|
||
journaux fait donc manquer l'essentiel du phenomene.
|
||
|
||
### Le module sain de la meme chaine tranche la question
|
||
Au meme instant, meme courant, meme chaine : **module 2, 13 a 17 mV d'ecart**.
|
||
Contre 189 et 215 mV pour ses deux voisins. Le bridage n'est pas une propriete
|
||
du parc ni une consequence du courant : il est produit par deux modules et subi
|
||
par les sept.
|
||
|
||
### Le cout, mesure au moment du bridage
|
||
PV 5082 W · maison 3355 W · borne VE 0 W · batterie **1406 W plafonnee a 1518 W**
|
||
par la limite de 30 A, la DVCC autorisant 110 A soit 5,6 kW. La consommation de
|
||
la maison masque le probleme a cet instant precis ; des qu'elle retombe sous
|
||
3 kW, la production est ecretee faute de preneur.
|
||
|
||
### Ages : la contradiction est levee, et elle aggrave le diagnostic
|
||
Le proprietaire est formel — pack abime **au moins 3 ans** de service
|
||
(il porte une etiquette), pack sain **environ 1 an**.
|
||
|
||
Les dates des codes-barres (`K220803C` = 03/08/2022, `Y230202C` = 02/02/2023)
|
||
sont donc des dates de **fabrication**, pas de mise en service : six mois
|
||
d'ecart a la fabrication, deux ans d'ecart en service, le pack sain ayant
|
||
sejourne en stock. Le « deux ans de plus » du MANUEL §0 est exact ; il parle
|
||
d'age de service.
|
||
|
||
⚠️ **Mais cela rend les compteurs de cycles impossibles a prendre au pied de la
|
||
lettre** : 345 cycles en un an pour le pack sain, soit un par jour, est
|
||
coherent ; 25 cycles en trois ans pour le pack abime ne l'est pas. Ses
|
||
compteurs ont vraisemblablement ete remis a zero (`Reset Times` 9 a 13).
|
||
|
||
### L'origine des degats n'est pas l'usure, c'est la decharge profonde
|
||
Le pack sain a encaisse **345 cycles en un an et ses soixante cellules sont a
|
||
zero**. Cycler n'abime donc pas ces modules.
|
||
|
||
En revanche les deux cellules terminales du parc — cellule 12 du module
|
||
`…0322` a **2953 mV**, cellule 15 de `…0324` a **2939 mV** — sont descendues
|
||
sous 3,0 V. Un LiFePO4 mene la sous charge perd de la capacite de facon
|
||
irreversible. **Ces cellules n'ont pas vieilli, elles ont ete videes trop
|
||
bas.**
|
||
|
||
⚠️ **Boucle a rompre, et elle touche directement le controleur EVCS** : la
|
||
jauge de SOC du parc suit la cellule la plus faible (§50). Quand le systeme
|
||
croit etre a son plancher dur de 22 %, cette cellule-la est deja bien plus bas
|
||
que ce que le chiffre laisse croire. Le plancher de securite est exprime dans
|
||
une grandeur que la cellule endommagee fausse elle-meme — donc il ne protege
|
||
pas ce qu'il croit proteger, et le meme mecanisme peut atteindre le pack sain.
|
||
A instruire avant l'hiver.
|
||
|
||
---
|
||
*Document mis a jour le : 05/09/2026 (~13:10) — declenchement chronometre a 3550 mV avec cinq secondes de reaction ; les degats viennent de la decharge profonde, pas de l'usure*
|
||
|
||
## 54. Session du 05/09/2026 (18h30) — Le tampon d'evenements : la cellule 12 est detruite en ce moment, toutes les nuits
|
||
|
||
### Un second tampon, que je n'avais pas vu
|
||
Le BMS tient DEUX journaux distincts, et je n'avais extrait que l'un des deux :
|
||
|
||
| commande | contenu | portee |
|
||
|---|---|---|
|
||
| `data <adr> history <n>` | un point toutes les 30 min | ~5 semaines |
|
||
| **`data <adr> event <n>`** | **uniquement les declenchements, a la seconde** | jusqu'a mai 2025 sur un module sain |
|
||
|
||
C'est le second que lit un fabricant. Il est adressable comme le premier.
|
||
|
||
### Le contraste, sans aucune interpretation
|
||
| module | evenements | periode couverte |
|
||
|---|---:|---|
|
||
| Y230202C30371427 | **1** | mai 2025 |
|
||
| Y230202C30371431 | **6** | mai 2025 |
|
||
| Y230202C30371425 | **12** | mai 2025 |
|
||
| Y230202C30371426 | **19** | mai 2025 |
|
||
| **K220803C30160323** (temoin) | **0** | — |
|
||
| K220803C30160319 | **688** | depuis le 23/07/2026 seulement |
|
||
| K220803C30160322 | **850** | depuis le 23/07/2026 seulement |
|
||
|
||
Le temoin a un tampon VIDE, coherent avec ses compteurs tous a zero. Les quatre
|
||
modules recents n'ont rien enregistre depuis mai 2025. Les deux abimes, eux,
|
||
produisent tant d'evenements que leur tampon de 900 places ne couvre plus que
|
||
six semaines.
|
||
|
||
### ⚠️ LA DECOUVERTE : le plancher de securite ne protege pas la cellule 12
|
||
Sur `K220803C30160322`, **52 enregistrements sous 3100 mV** (alarme basse de
|
||
niveau 1), repartis sur **19 nuits**. Et cela s'aggrave :
|
||
|
||
| periode | nuits concernees | plus basse tension |
|
||
|---|---:|---:|
|
||
| 23/07 → 22/08 | 8 nuits sur 31 | 3049 mV |
|
||
| **23/08 → 04/09** | **10 nuits sur 13** | **2999 mV** |
|
||
|
||
Le SOC du PARC a ces moments-la, releve dans l'historique Home Assistant :
|
||
|
||
| moment | SOC du parc | cellule 12 |
|
||
|---|---:|---:|
|
||
| 23/08 04:22 | 38 % | 3050 mV |
|
||
| 31/08 03:12 | 34 % | 3006 mV |
|
||
| 01/09 05:09 | **41 %** | **2999 mV** |
|
||
| 04/09 04:11 | 38 % | 3014 mV |
|
||
|
||
**Le plancher dur du controleur est a 22 %.** Quand le parc affiche 34 a 41 %,
|
||
cette cellule est deja sous l'alarme basse et a cent millivolts du niveau 2
|
||
(2900 mV). Le parc n'atteint JAMAIS son plancher : la cellule est detruite bien
|
||
avant, nuit apres nuit, en ce moment meme.
|
||
|
||
C'est la boucle annoncee en §50, desormais datee et chiffree : le plancher est
|
||
exprime dans un SOC que la cellule endommagee fausse elle-meme, donc il ne
|
||
protege pas ce qu'il croit proteger. Et c'est l'explication complete du genou a
|
||
33 % mesure en §41.
|
||
|
||
### ⚠️ CELA REVISE MA RECOMMANDATION DU MEME JOUR
|
||
J'avais conseille d'attendre mardi avant d'envisager le retrait des trois
|
||
modules abimes, au motif qu'on y perdrait 3 kWh de capacite utile. **Ce calcul
|
||
etait fonde sur une plage 100 → 22 % dans laquelle une cellule se detruit.**
|
||
|
||
Avec la courbe mesuree (§41) et le plancher reellement sur :
|
||
|
||
| configuration | energie utile |
|
||
|---|---:|
|
||
| sept modules, plancher 22 % (aujourd'hui, destructeur) | 12,39 kWh |
|
||
| sept modules, plancher 45 % (cellule protegee) | **9,29 kWh** |
|
||
| **cinq modules sains, plancher 22 %** | **9,42 kWh** |
|
||
|
||
**Retirer les trois modules abimes ne coute donc RIEN en energie utile** — a
|
||
0,12 kWh pres — et supprime en plus le bridage du courant de charge. La
|
||
proposition du proprietaire etait meilleure que mon avis de l'apres-midi.
|
||
|
||
Reserve : le « cinq modules sans genou » suppose que le genou vient bien des
|
||
cellules faibles. C'est l'hypothese la mieux etayee, mais elle se verifie en
|
||
remesurant la courbe apres retrait, avec `tools/mesure-courbe-batterie.mjs`.
|
||
|
||
### Decision a prendre AVANT la nuit
|
||
Trois options, par ordre de reversibilite :
|
||
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 <n>`**, indispensable : l'horloge d'un module remise a
|
||
l'heure APRES l'ecriture des enregistrements se lit a zero alors que les
|
||
donnees portent l'ancien decalage. Un controle signale tout enregistrement
|
||
corrige qui tomberait dans le futur — signe qu'il a ete ecrit apres la remise
|
||
a l'heure et ne devait pas etre corrige.
|
||
|
||
---
|
||
*Document mis a jour le : 05/09/2026 (~18h35) — la cellule 12 passe sous l'alarme basse 10 nuits sur 13 a un SOC parc de 34-41 %, tres au-dessus du plancher de 22 % cense la proteger*
|
||
|
||
## 55. Session du 05/09/2026 (19h15) — La cellule 15 de BAT3 ne peut plus debiter, meme pleine
|
||
|
||
Dernier tampon manquant de l'archive. **900 evenements en SIX JOURS**
|
||
(23 → 28/07/2026) : le tampon est sature, ce qui explique a lui seul pourquoi
|
||
ce module a ete retire du parc fin juillet.
|
||
|
||
### Ce n'est pas une decharge profonde, c'est une resistance
|
||
| quand | SOC module | courant | cellule 15 | les 14 autres |
|
||
|---|---:|---:|---:|---|
|
||
| 25/07 23:34 | 2 % | −5,00 A | **2935 mV** | 3309-3321 |
|
||
| **23/07 17:20** | **84 %** | **−6,49 A** | **2963 mV** | **3291-3303** |
|
||
| 23/07 17:47 | 44 % | −6,27 A | 3013 mV | 3303-3313 |
|
||
|
||
La deuxieme ligne est la plus parlante : **a 84 % de charge**, sous seulement
|
||
6,5 A, cette cellule tombe a 2963 mV pendant que ses quatorze voisines sont a
|
||
3291-3303. Trois cent quarante millivolts d'ecart, a mi-charge. Ce n'est pas un
|
||
probleme de profondeur de decharge — **cette cellule ne peut simplement plus
|
||
debiter de courant**.
|
||
|
||
### Elle est deux fois pire que la pire du parc en service
|
||
Deux points, meme methode qu'au §53 :
|
||
- au repos ce matin : 3274 mV contre 3329 de moyenne, soit **55 mV a 0 A** ;
|
||
- sous 6,49 A : 2963 contre 3297, soit **334 mV**.
|
||
|
||
Pente : **≈ 43 mΩ** de resistance excedentaire, contre 18,6 mΩ pour la cellule
|
||
12 du module `…0322`. **Deux fois et demie pire.**
|
||
|
||
### ⚠️ CONSEQUENCE POUR L'ESSAI DE MARDI
|
||
BAT3 reste le bon banc d'essai — isole, hors service, sans risque. Mais il faut
|
||
savoir ce qu'il peut et ne peut pas prouver :
|
||
|
||
- **un ECHEC sur BAT3 ne condamne pas les autres.** Sa cellule 15 est a 43 mΩ ;
|
||
celles de `…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 `<depot>.hbk/Config/version_info.db`. Le depot ayant
|
||
disparu avec le partage, la tache est **irrecuperable en l'etat**. Mon
|
||
affirmation « la tache existante repart telle quelle » etait fausse.
|
||
|
||
**Marche a suivre reelle** : creer une NOUVELLE tache dans Hyper Backup sur
|
||
10.10.10.50 — Sauvegarde de donnees → serveur Synology distant → `10.10.10.22`,
|
||
compte `TMZF5455`, partage `ARCHIVEFAMILLE`, depot
|
||
`ARCHIVE_1_PHOTOS_2008_2015`, source = dossier partage `photo` (399 Go) — puis
|
||
supprimer l'ancienne tache « Synology NAS 1 ». La creation d'une tache Hyper
|
||
Backup n'a pas d'equivalent en ligne de commande : elle se fait dans DSM.
|
||
L'historique de versions est perdu de toute facon, le depot n'existant plus.
|
||
|
||
⚠️ **Piege a retenir** : `synobackup --backup <id> --type image` rend
|
||
**code 0 et une sortie vide meme quand il echoue**, en root comme en simple
|
||
utilisateur. Le seul retour exploitable est
|
||
`/var/log/synolog/synobackup.log`.
|
||
|
||
### Seconde tache cassee, decouverte au passage : « WEB petit sino vers gros »
|
||
|
||
Elle echoue **tous les jours a 03:00**, avec « The network connection is
|
||
closed. » — et `enable_notify=true`, donc elle alerte elle aussi. Configuration :
|
||
source `/web` du .50, destination `10.10.10.22` partage `web`, depot
|
||
`ARCHIVE_2.hbk`, **port 873** (rsync en clair) la ou les deux autres taches
|
||
utilisent 6281.
|
||
|
||
Ce n'est PAS un probleme de port : depuis le .50, **873 et 6281 repondent tous
|
||
les deux**, et sur le .22 `netstat` montre `rsync` en ecoute sur 873 et
|
||
`img_backupd` sur 6281. Le depot `/volume1/web/ARCHIVE_2.hbk` est fige au
|
||
**25/12/2019**. Cause a instruire separement (authentification rsync, ou depot
|
||
inutilisable).
|
||
|
||
### RESOLU le 06/09/2026 a 22:53 — nouvelle tache, sauvegarde en cours
|
||
|
||
Journal Hyper Backup du .50 :
|
||
|
||
```
|
||
18:54:20 thierry: Backup task [Synology NAS 1] was removed.
|
||
20:59:51 thierry: Backup task [WEB petit sino vers gros] was removed.
|
||
22:53:04 thierry: Setting of backup task [Synology NAS 1 ARCHIVE FAMILLE] was created
|
||
to backup to [TMZF5455@10.10.10.22::ARCHIVEFAMILLE/ARCHIVE_1_PHOTOS_2008_2015.hbk]
|
||
22:53:32 SYSTEM: [Synology NAS 1 ARCHIVE FAMILLE] Backup task started.
|
||
```
|
||
|
||
Verifie cote destination : le depot
|
||
`/volume2/ARCHIVEFAMILLE/ARCHIVE_1_PHOTOS_2008_2015.hbk` existe, appartient a
|
||
`TMZF5455`, et **grossit** (395 Mo puis 604 Mo a une minute d'intervalle).
|
||
Aucune erreur depuis le demarrage. La seconde tache cassee
|
||
(« WEB petit sino vers gros ») a ete supprimee plutot que reparee — decision du
|
||
proprietaire ; le depot `/volume1/web/ARCHIVE_2.hbk` de 2019 reste sur le .22
|
||
et peut etre efface si `/web` n'a plus de valeur.
|
||
|
||
Ne restent donc que deux taches sur le .50 : `task_3` (« Synology NAS 2 »,
|
||
`/video` → partage `video`) et `task_4` (la nouvelle).
|
||
|
||
⚠️ **La nouvelle tache sauvegarde `["/photo","/video"]`**, alors que l'ancienne
|
||
ne prenait que `/photo`. `/video` est deja sauvegarde par « Synology NAS 2 »
|
||
dans un depot de **455 Go** sur `/volume1/video` du .22. Le premier passage
|
||
copiera donc ~399 Go de photos **plus ~455 Go de videos en double**, soit
|
||
~854 Go. L'espace ne manque pas (9,8 To libres sur `/volume2`) mais c'est
|
||
plusieurs heures et un demi-tera pour rien si l'inclusion de `/video` etait une
|
||
case cochee par erreur. A trancher par le proprietaire — se corrige dans les
|
||
parametres de la tache.
|
||
|
||
### Partage recree le 06/09/2026 a 16:14
|
||
|
||
`/volume2/ARCHIVEFAMILLE` existe. Verifie, cote destination :
|
||
- chemin et type conformes (`synoshare --get`) ;
|
||
- ACL **identique a celle du partage `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*
|