Nuit : la recovery batterie est une passe unique, pas un oscillateur

La bande de 2 points du Fix A engageait le takeover a SOC 33 et le relachait
a 35 : 0,34 kWh, soit une demi-heure de conso nocturne plate. Trois cycles EDF
la nuit du 26-27/08, six la nuit du 20-21/08, 2,5 kWh importes pour rien.

Une seule passe de recovery par session de nuit. Le plancher dur reste un
terme independant, l hysteresis garde sa bande 20/30, et le vrai critere de
re-engagement (un budget d energie) appartient au bilan, pas a une constante
de plus ici.

Golden 40 -> 42, preuve par mutation. Documente aussi deux acteurs non lus :
les charges programmees de l ESS (45 % la nuit, contre notre cible 35 %) et
le regime majoritairement hors reseau.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KKzkkVTG6KXnGXFFC7X9XJ
This commit is contained in:
Tallagrand Thierry
2026-08-27 10:33:21 +02:00
parent 8ad34f8c93
commit f18c6e07f6
5 changed files with 183 additions and 13 deletions

View File

@@ -1109,6 +1109,94 @@ p11-changement-vehicule-nuit (prev=night_recovery_done_idle + charging_time 15s
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**. À
arbitrer (les désactiver et garder un seul propriétaire, ou les aligner).
### 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.
### 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

View File

@@ -93,6 +93,31 @@ batterie n'est publié côté HA — à corriger.
Deux véhicules alternent : **VW** (hybride, départ ~08:50) et **JEEP**
(départ ~06:30) — cf. §11.5.
### Régime d'exploitation — majoritairement HORS RÉSEAU
Précision donnée par le propriétaire le 27/08/2026, à ne pas redécouvrir :
l'installation fonctionne **la plupart du temps AC-IN ouvert**
(`switch.edf_general_ok_switch` fermé seulement lors des sessions réseau
décidées par les workflows). Conséquence assumée : **l'alarme perte-réseau du
MultiPlus (`switch.airtria_mpii_v1_grid_lost_alarm_setting`) est désactivée
volontairement** — avec cette topologie elle se déclencherait en permanence. Ne
pas proposer de la réactiver.
Pour détecter une **vraie coupure subie** (orage), le signal correct n'est donc
pas l'alarme mais la contradiction commande/mesure : contacteur EDF commandé
fermé alors que `sensor.victron_ac_in1_available` reste à 0 ou que
`sensor.airtria_mpii_v1_input_voltage_l1` reste nulle. Entièrement local.
Les coupures d'orage sont **fréquentes** sur ce site, et emportent souvent la
connexion Internet avec elles — d'où l'exigence d'outils consultables hors
ligne (la console VRM est hébergée dans le cloud Victron).
### Charges programmées de l'ESS (propriétaire concurrent — 27/08/2026)
Le GX porte ses propres fenêtres de charge réseau, **trois actives tous les
jours**, qu'aucun workflow ne lit : 01:35 → 45 %, 07:30 → 45 %, 12:24 → 55 %
(plus deux désactivées). Elles entrent en concurrence directe avec la cible du
matin calculée par l'EVCS (35 % en été). Entités
`switch.gx_device_ess_batterylife_schedule_charge_N_enabled` et associées.
### Réseau
Victron **ESS sans injection** : la production est bridée en permanence à ce que
le système peut absorber (consommation + charge batterie). Voir

View File

@@ -394,17 +394,24 @@ const vehicleChargeCompleted =
// ⚠ 'night_recovery_done_idle' ne doit matcher AUCUN pattern no-demand de
// l'hystérésis (full_or_paused / not_requesting_power / charge_complete /
// no_demand) — même contrainte que 'night_session_done_idle'.
const NIGHT_RECOVERY_REARM_BAND_PCT = 2;
// 27/08/2026 : la bande de 2 points fabriquait un oscillateur. Relevé de la
// nuit du 26-27/08 (cible 35) : engagement à SOC 33, relâchement à 35, trois
// cycles EDF de 52, 79 et 25 min, 2,5 kWh importés pour rien un matin de grand
// soleil ; la nuit du 20-21/08 en comptait six. Deux points de SOC valent
// ~0,34 kWh, soit une demi-heure de conso nocturne : ce n'est pas une
// hystérésis, c'est la période d'une horloge — d'autant plus régulière que la
// charge de nuit est parfaitement plate.
// La recovery batterie post-charge est donc UNE SEULE passe par session de
// nuit, ce qui était déjà l'intention affichée du Fix A ("relâché
// DÉFINITIVEMENT"). Aucun seuil de ré-engagement ici : le plancher dur reste
// un terme indépendant de nightGridTakeoverNeeded (voir plus bas), et
// l'hystérésis réseau garde sa propre bande 20/30. Le vrai critère de
// ré-engagement est un budget d'énergie restant à couvrir, pas un delta de
// SOC : il appartient au bilan énergétique, pas à une constante de plus ici.
const nightRecoveryDonePrev = prevSurplusState === 'night_recovery_done_idle';
const nightRecoveryDone =
vehicleChargeCompleted &&
(
parsed.soc >= batteryMorningTargetPct ||
(
nightRecoveryDonePrev &&
parsed.soc > batteryMorningTargetPct - NIGHT_RECOVERY_REARM_BAND_PCT
)
);
(parsed.soc >= batteryMorningTargetPct || nightRecoveryDonePrev);
// =====================================================
// P9 (09/08/2026) : CHARGE RÉSEAU AVANT DÉPART, HORS FENÊTRE HC.

View File

@@ -582,8 +582,8 @@ export const scenarios = [
},
},
{
id: 'night-recovery-rearm-sous-bande',
label: 'Nuit HC 05:30, recovery done mais SOC 32 ≤ cible−2 → ré-engagement takeover (recharge batterie)',
id: 'night-recovery-done-pas-de-reengagement',
label: 'Nuit HC 05:30, recovery done et SOC 32 sous la cible → AUCUN ré-engagement (la recovery est une passe unique, 27/08)',
time: '2026-08-08T05:30:00+02:00',
overrides: {
...VEHICLE_NIGHT, soc_percent: '32', allow_grid_hc_night: 'on',
@@ -595,15 +595,65 @@ export const scenarios = [
expect: {
profile: 'night_hc',
sourceMode: 'blocked',
state: 'night_session_done_idle',
// Collant désormais : la recovery ayant eu lieu, l'état reste
// 'night_recovery_done_idle' même sous la cible — c'est lui qui autorise
// la coupure EDF immédiate côté hystérésis (E2) dès SOC >= grid_soc_off.
state: 'night_recovery_done_idle',
decision: 'hold',
desiredCharging: false,
currentA: 6,
takeoverNeeded: true,
takeoverNeeded: false,
edfOnAction: false,
takeoverHelperService: 'turn_off',
},
},
{
// L'oscillateur mesuré : nuit du 26-27/08, Jeep pleine restée branchée,
// cible 35. L'ancienne bande de 2 points ré-engageait le takeover pile à
// SOC 33 -> EDF ON 04:24/05:31/07:05, OFF 05:16/06:50/07:30, 2,5 kWh
// importés une nuit où le lendemain était annoncé à 43 kWh.
id: 'night-oscillateur-nuit-2627',
label: 'Nuit HC 05:31, recovery done et SOC 33 (cible 35 − 2) → le takeover NE se ré-engage pas : plus d\'oscillateur',
time: '2026-08-27T05:31:00+02:00',
overrides: {
...VEHICLE_NIGHT, soc_percent: '33', allow_grid_hc_night: 'on',
grid_state: 'off', victron_acin1_available: '0',
battery_current_a: '-11', night_takeover_prev: 'off',
evcs_charging_switch: 'off',
prev_surplus_state: 'night_recovery_done_idle',
},
expect: {
profile: 'night_hc',
sourceMode: 'blocked',
state: 'night_recovery_done_idle',
takeoverNeeded: false,
edfOnAction: false,
takeoverHelperService: 'turn_off',
},
},
{
// Le filet de sécurité doit rester intact : sous le plancher dur, le
// takeover se ré-engage même si la recovery a déjà eu lieu, parce que
// batteryBelowHardFloor est un terme INDÉPENDANT de nightGridTakeoverNeeded.
id: 'night-recovery-done-plancher-dur',
label: 'Nuit HC 05:31, recovery done mais SOC 21 sous le plancher dur 22 → ré-engagement du takeover (sécurité)',
time: '2026-08-27T05:31:00+02:00',
overrides: {
...VEHICLE_NIGHT, soc_percent: '21', allow_grid_hc_night: 'on',
grid_state: 'off', victron_acin1_available: '0',
battery_current_a: '-11', night_takeover_prev: 'off',
evcs_charging_switch: 'off',
prev_surplus_state: 'night_recovery_done_idle',
},
expect: {
profile: 'night_hc',
takeoverNeeded: true,
takeoverHelperService: 'turn_on',
},
},
{
id: 'night-vw-statut-charged',
label: 'Nuit HC 03:00, statut EVCS brut 3 (charged), sans état VDF préalable → fin de charge immédiate (Fix D)',

File diff suppressed because one or more lines are too long