Un depassement souple de la batterie doit durer pour compter
La table des bandes SOC declarait depuis toujours un courant souple tolere pendant des minutes et un courant dur tolere quelques secondes. Les deux tolerances etaient calculees, exportees dans le debug, et lues par aucune decision : le controleur comparait un echantillon instantane a un plancher, toutes les 30 s. A SOC 94 la batterie est en float et son courant oscille entre +1,8 et -2,6 A d'un echantillon a l'autre. Le test devenait un tirage a pile ou face et la charge du vehicule s'arretait sur du bruit : deux fois le 01/09, quatre a six fois par jour la semaine precedente. Chaque arret coutait environ 0,45 kWh, la batterie etant pleine et le vehicule le seul preneur. Le correctif est purement temporel : aucun seuil n'est deplace, aucune constante ajoutee. La tolerance de la bande est branchee sur les trois conditions de MAINTIEN, via un helper de persistance qui porte le debut du depassement en cours. Le plancher de demarrage et le garde dur restent instantanes : la tolerance est du confort, jamais du risque. Golden 56 -> 61. Preuve par mutation dans les deux sens : tolerance supprimee -> 2 echecs cibles, tolerance infinie -> 3 echecs cibles, l'arret dur restant vert dans les deux cas. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KKzkkVTG6KXnGXFFC7X9XJ
This commit is contained in:
133
HANDOFF.md
133
HANDOFF.md
@@ -2352,3 +2352,136 @@ pendant toute la session), SOC stable à 49 %.
|
||||
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*
|
||||
|
||||
@@ -717,6 +717,39 @@ charge EVCS), lisser le SOC pour les décisions de confort en gardant
|
||||
l'instantané pour la sécurité, et exploiter les messages d'état réels du
|
||||
Victron et de la borne plutôt que de s'auto-limiter à l'aveugle sur un proxy.
|
||||
|
||||
#### C. Arrêt de la charge sur le bruit de la mesure batterie (01/09/2026)
|
||||
|
||||
Troisième membre de la même famille, découvert le 01/09 : la règle de maintien
|
||||
comparait le **courant batterie instantané** à un plancher de 0 A, toutes les
|
||||
30 s. À SOC 94 % la batterie est en float et son courant oscille entre +1,8 et
|
||||
−2,6 A d'un échantillon à l'autre — le test devenait un tirage à pile ou face.
|
||||
Deux arrêts ce jour-là (14:26 et 14:56), 1 à 6 par jour la semaine précédente.
|
||||
|
||||
Chaque arrêt coûtait ~0,45 kWh : batterie pleine, le véhicule était le **seul
|
||||
preneur**, donc la PV s'effondrait de 6,6 à 2,5 kW en 30 s et la reprise mettait
|
||||
21 min à remonter de 6 A à 20 A.
|
||||
|
||||
La table de politique batterie par bande de SOC (`INPUT_PARSE`) déclarait
|
||||
pourtant déjà, pour chaque bande, un courant **souple** toléré pendant des
|
||||
**minutes** (12 min au-dessus de 90 %) et un courant **dur** toléré quelques
|
||||
**secondes**. Les deux tolérances étaient calculées, exportées dans le debug, et
|
||||
lues par **aucune décision** : toute la dimension temporelle de la politique
|
||||
était morte.
|
||||
|
||||
Correctif : brancher la tolérance souple sur les trois conditions de maintien,
|
||||
via un helper de persistance
|
||||
`input_text.victron_evcs_battery_soft_breach_at` qui porte le début du
|
||||
dépassement en cours. **Aucun seuil déplacé, aucune constante ajoutée.** Le
|
||||
plancher de démarrage et le garde dur restent instantanés — la tolérance est du
|
||||
confort, jamais du risque. Le filtre discrimine parce que le bruit change de
|
||||
signe en quelques secondes (compteur remis à zéro) alors qu'une vraie décharge
|
||||
reste négative à chaque échantillon.
|
||||
|
||||
Golden 56 → 61, dont le contrefactuel `batt-decharge-soutenue-coupe` qui prouve
|
||||
que la garde mord toujours après sa tolérance. Détail : HANDOFF §35.
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
## 12. Configuration opencode.json
|
||||
|
||||
@@ -453,6 +453,58 @@ const profileSpecificDischargeFloorA =
|
||||
const profileBatteryDischargeFloorA =
|
||||
Math.max(profileSpecificDischargeFloorA, batteryDischargeSoftDynamicA);
|
||||
|
||||
// =====================================================
|
||||
// DEPASSEMENT SOUPLE DU PLANCHER DE DECHARGE (01/09/2026)
|
||||
// =====================================================
|
||||
// La table des bandes SOC (INPUT_PARSE) declare depuis toujours DEUX regimes :
|
||||
// un courant SOUPLE tolere pendant des MINUTES (softToleranceMin : 12 min a
|
||||
// SOC > 90, 1 min sous 25) et un courant DUR tolere quelques SECONDES. Seuls
|
||||
// les seuils de courant etaient lus ; la dimension temporelle etait calculee,
|
||||
// exportee dans le debug, et branchee sur AUCUNE decision. Le controleur
|
||||
// comparait donc un echantillon instantane, toutes les 30 s, a un plancher.
|
||||
//
|
||||
// Incident du 01/09 (14:26 et 14:56, SOC 94, batterie en float) : le courant
|
||||
// batterie oscillait entre +1,8 et -2,6 A d'un echantillon a l'autre, soit le
|
||||
// bruit de regulation d'une batterie qui ne fait rien. Le test devenait un
|
||||
// tirage a pile ou face et la charge du vehicule s'arretait dessus. Chaque
|
||||
// arret coutait ~0,45 kWh de production refusee : batterie pleine, le vehicule
|
||||
// etait le SEUL preneur, donc la PV s'effondrait de 6,6 a 2,5 kW en 30 s, et
|
||||
// la reprise mettait 21 min a remonter de 6 A a 20 A. 1 a 6 arrets par jour
|
||||
// sur la semaine du 26 au 31/08.
|
||||
//
|
||||
// Le correctif est PUREMENT TEMPOREL : aucun seuil n'est deplace, aucune
|
||||
// constante n'est ajoutee. On applique la table telle qu'elle est ecrite :
|
||||
// un depassement souple doit DURER pour compter. Comme le bruit change de
|
||||
// signe en quelques secondes, le compteur se remet a zero de lui-meme ; une
|
||||
// vraie decharge, elle, reste negative a chaque echantillon et finit par
|
||||
// depasser la tolerance de sa bande.
|
||||
//
|
||||
// CE QUI N'EST PAS TOUCHE : le plancher de DEMARRAGE reste instantane (ne pas
|
||||
// lancer une charge sur une batterie qui se decharge deja, c'est la frontiere
|
||||
// "delestage et non charge prioritaire"), et le garde DUR reste instantane —
|
||||
// hardToleranceSec n'est volontairement PAS branche, cela ne pourrait que
|
||||
// ralentir une securite, et c'est le risque qui prime.
|
||||
const batterySoftBreachNow =
|
||||
sessionObservedActive &&
|
||||
parsed.batteryCurrentA < profileBatteryDischargeFloorA;
|
||||
|
||||
const batterySoftBreachStartedMs = batterySoftBreachNow
|
||||
? (history.batterySoftBreachMsPrev !== null
|
||||
? history.batterySoftBreachMsPrev
|
||||
: nowMs)
|
||||
: null;
|
||||
|
||||
const batterySoftBreachMinutes =
|
||||
batterySoftBreachStartedMs !== null
|
||||
? Math.max(0, (nowMs - batterySoftBreachStartedMs) / 60000)
|
||||
: 0;
|
||||
|
||||
// Vrai tant que le depassement n'a pas dure plus que la tolerance de sa bande.
|
||||
// Une tolerance a 0 minute redonne exactement le comportement instantane.
|
||||
const batterySoftDischargeHoldOk =
|
||||
!batterySoftBreachNow ||
|
||||
batterySoftBreachMinutes <= batterySoftToleranceMinutes;
|
||||
|
||||
// =====================================================
|
||||
// GRID RELAX IN force_charge_now
|
||||
// =====================================================
|
||||
@@ -557,7 +609,7 @@ const batteryAssistSolarHoldOk =
|
||||
commonSafetyHoldOk &&
|
||||
!isGridSource &&
|
||||
parsed.soc >= Math.max(profileSocHoldFloor, batteryAssistSolarHoldSocMin) &&
|
||||
parsed.batteryCurrentA >= profileBatteryDischargeFloorA &&
|
||||
batterySoftDischargeHoldOk &&
|
||||
cloudToleranceOk;
|
||||
|
||||
// =====================================================
|
||||
@@ -591,7 +643,7 @@ const rawSolarHoldOk =
|
||||
parsed.soc >= effectiveSocHoldFloor &&
|
||||
(
|
||||
gridSettling ||
|
||||
parsed.batteryCurrentA >= profileBatteryDischargeFloorA
|
||||
batterySoftDischargeHoldOk
|
||||
) &&
|
||||
(
|
||||
gridSettling ||
|
||||
@@ -655,7 +707,7 @@ const probeStartOk =
|
||||
const probeHoldGuardOk =
|
||||
probeModeActive &&
|
||||
commonSafetyHoldOk &&
|
||||
parsed.batteryCurrentA >= profileBatteryDischargeFloorA;
|
||||
batterySoftDischargeHoldOk;
|
||||
|
||||
const probeWindowActive =
|
||||
probeModeActive &&
|
||||
@@ -824,6 +876,14 @@ return [{
|
||||
batteryHardToleranceSeconds,
|
||||
batteryRecoveryMinutes,
|
||||
|
||||
// Depassement souple en cours (01/09/2026). startedMs = null quand il
|
||||
// n'y a pas de depassement : c'est ce que HA_SERVICE_CALLS_OUTPUT lit
|
||||
// pour ecrire ou effacer le helper de persistance.
|
||||
batterySoftBreachNow,
|
||||
batterySoftBreachStartedMs,
|
||||
batterySoftBreachMinutes,
|
||||
batterySoftDischargeHoldOk,
|
||||
|
||||
gridBatterySoftDynamicA,
|
||||
gridBatteryHardDynamicA,
|
||||
gridForceChargeRelaxSoftA,
|
||||
|
||||
@@ -67,6 +67,12 @@ pushAction(
|
||||
!!decision.set_cloud_debt_started_at
|
||||
);
|
||||
|
||||
pushAction(
|
||||
'helper_battery_soft_breach_at',
|
||||
calls.helper_battery_soft_breach_at,
|
||||
!!calls.helper_battery_soft_breach_at?.execute
|
||||
);
|
||||
|
||||
pushAction(
|
||||
'helper_block_reason',
|
||||
calls.helper_block_reason,
|
||||
|
||||
@@ -387,6 +387,13 @@ if (derived.gridSettling) {
|
||||
if (gridSessionActive) {
|
||||
reason.push(`Consigne reseau=${gridSetpointTargetW}W`);
|
||||
}
|
||||
// Visible dans le logbook uniquement pendant un depassement souple : c'est le
|
||||
// seul moment ou l'information sert, et la raison est tronquee a 255 caracteres.
|
||||
if (batteryPolicy.batterySoftBreachNow) {
|
||||
reason.push(
|
||||
`Batt souple ${round1(batteryPolicy.batterySoftBreachMinutes)}/${round1(batteryPolicy.batterySoftToleranceMinutes)}min`
|
||||
);
|
||||
}
|
||||
reason.push(`Cap=${derived.physicalCapA}A`);
|
||||
reason.push(`Ref=${round1(derived.currentReferenceA)}A`);
|
||||
reason.push(`Target=${round1(desiredCurrentA)}A`);
|
||||
@@ -498,6 +505,19 @@ const cloudDebtStartedAtToWrite =
|
||||
? formatParisStampFromMs(cloud.cloudDebtStartedMs)
|
||||
: now.stamp;
|
||||
|
||||
// DEPASSEMENT SOUPLE BATTERIE (01/09/2026) : chaine vide quand il n'y a pas
|
||||
// de depassement en cours. Le helper n'est reecrit que sur changement reel
|
||||
// (debut ou fin), pas a chaque cycle : la valeur du debut doit RESTER figee
|
||||
// pendant toute la duree du depassement, sinon le compteur ne monte jamais.
|
||||
const batterySoftBreachAtToWrite =
|
||||
batteryPolicy.batterySoftBreachStartedMs !== null &&
|
||||
batteryPolicy.batterySoftBreachStartedMs !== undefined
|
||||
? formatParisStampFromMs(batteryPolicy.batterySoftBreachStartedMs)
|
||||
: '';
|
||||
|
||||
const setBatterySoftBreachAt =
|
||||
batterySoftBreachAtToWrite !== (history.batterySoftBreachAt || '');
|
||||
|
||||
const probeStartedAtToWrite = now.stamp;
|
||||
const lastStartAtToWrite = now.stamp;
|
||||
const lastStopAtToWrite = now.stamp;
|
||||
@@ -587,6 +607,10 @@ return [{
|
||||
house_spike_detected: batteryPolicy.houseSpikeDetected,
|
||||
solar_potential_context: batteryPolicy.solarPotentialContext,
|
||||
|
||||
batt_soft_breach_now: !!batteryPolicy.batterySoftBreachNow,
|
||||
batt_soft_breach_minutes: round1(batteryPolicy.batterySoftBreachMinutes || 0),
|
||||
batt_soft_discharge_hold_ok: batteryPolicy.batterySoftDischargeHoldOk !== false,
|
||||
|
||||
evcs_priority_active: evcsPriorityActive,
|
||||
energy_priority_mode: energyPriorityMode,
|
||||
energy_priority_reason: energyPriorityReason,
|
||||
@@ -797,6 +821,16 @@ return [{
|
||||
}
|
||||
},
|
||||
|
||||
helper_battery_soft_breach_at: {
|
||||
execute: setBatterySoftBreachAt,
|
||||
domain: 'input_text',
|
||||
service: 'set_value',
|
||||
data: {
|
||||
entity_id: 'input_text.victron_evcs_battery_soft_breach_at',
|
||||
value: batterySoftBreachAtToWrite
|
||||
}
|
||||
},
|
||||
|
||||
helper_last_current_change_at: {
|
||||
execute: setLastCurrentChangeAt,
|
||||
domain: 'input_datetime',
|
||||
|
||||
@@ -84,6 +84,7 @@
|
||||
"cloud_debt_max_kwh": states("input_number.victron_evcs_cloud_debt_max_kwh"),
|
||||
"cloud_tolerance_minutes": states("input_number.victron_evcs_cloud_tolerance_minutes"),
|
||||
"cloud_debt_started_at": states("input_datetime.victron_evcs_cloud_debt_started_at"),
|
||||
"battery_soft_breach_at": states("input_text.victron_evcs_battery_soft_breach_at"),
|
||||
"operating_mode_helper": states("input_text.victron_evcs_operating_mode"),
|
||||
|
||||
"departure_time": states("input_datetime.victron_evcs_departure_time"),
|
||||
|
||||
@@ -538,6 +538,12 @@ const history = {
|
||||
lastStopMs: parseParisStampToMs(cleanText(input.last_stop_at)),
|
||||
lastCurrentChangeMs: parseParisStampToMs(cleanText(input.last_current_change_at)),
|
||||
cloudDebtStartedMsPrev: parseParisStampToMs(cleanText(input.cloud_debt_started_at)),
|
||||
|
||||
// DEPASSEMENT SOUPLE BATTERIE (01/09/2026) : horodatage du debut du
|
||||
// depassement en cours, ecrit par HA_SERVICE_CALLS_OUTPUT. Vide/illisible
|
||||
// => null => aucun depassement en cours, comportement de repli sur.
|
||||
batterySoftBreachAt: cleanText(input.battery_soft_breach_at),
|
||||
batterySoftBreachMsPrev: parseParisStampToMs(cleanText(input.battery_soft_breach_at)),
|
||||
evcsPriorityManualUntilMs: parseParisStampToMs(cleanText(input.evcs_priority_manual_until)),
|
||||
|
||||
// =====================================================
|
||||
|
||||
@@ -909,6 +909,147 @@ export const scenarios = [
|
||||
},
|
||||
},
|
||||
|
||||
// ===== 01/09/2026 : le depassement souple de la batterie doit DURER =====
|
||||
// Incident du 01/09 (14:26 et 14:56) : SOC 94, batterie en float, courant
|
||||
// oscillant entre +1,8 et -2,6 A d'un echantillon a l'autre. Le controleur
|
||||
// comparait un echantillon instantane a un plancher de 0 A toutes les 30 s :
|
||||
// pile ou face. Chaque arret coutait ~0,45 kWh de production refusee, la
|
||||
// voiture etant le seul preneur d'une batterie pleine.
|
||||
// La table des bandes SOC declarait deja une tolerance de 12 min a SOC > 90 ;
|
||||
// elle n'etait branchee nulle part. Aucun seuil n'est deplace ici.
|
||||
{
|
||||
id: 'batt-float-bruit-ne-coupe-pas',
|
||||
label: "Relevé du 01/09 14:26 : SOC 94, batterie en float à -1 A, charge à 20 A → le bruit de régulation NE DOIT PAS arrêter la charge",
|
||||
time: AFTERNOON,
|
||||
overrides: {
|
||||
evcs_vehicle_connected: 'on', evcs_device_connected: 'on',
|
||||
evcs_charging_switch: 'on', control_latch: 'on', charge_permission_now: 'on',
|
||||
last_start_at: '2026-08-08 14:00:00',
|
||||
soc_percent: '94',
|
||||
evcs_current_a: '20', evcs_set_current_a: '20', evcs_command_current_a: '20',
|
||||
evcs_ac_power_w: '3600',
|
||||
pv_power_w: '6600', pv_power_10m_avg_w: '6000', pv_trend_w: '0',
|
||||
house_ac_power_w: '6000', vebus_ac_out_l1_power_w: '6000',
|
||||
battery_current_a: '-1',
|
||||
},
|
||||
expect: {
|
||||
profile: 'outside_window',
|
||||
sourceMode: 'solar_only',
|
||||
state: 'charging_hold',
|
||||
decision: 'hold',
|
||||
desiredCharging: true,
|
||||
currentA: 20,
|
||||
takeoverNeeded: false,
|
||||
edfOnAction: false,
|
||||
takeoverHelperService: 'turn_off',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'batt-decharge-soutenue-coupe',
|
||||
label: "CONTREFACTUEL : mêmes mesures, mais le dépassement dure depuis 20 min > tolérance 12 min de la bande gt90 → la garde reprend la main et arrête",
|
||||
time: AFTERNOON,
|
||||
overrides: {
|
||||
evcs_vehicle_connected: 'on', evcs_device_connected: 'on',
|
||||
evcs_charging_switch: 'on', control_latch: 'on', charge_permission_now: 'on',
|
||||
last_start_at: '2026-08-08 14:00:00',
|
||||
soc_percent: '94',
|
||||
evcs_current_a: '20', evcs_set_current_a: '20', evcs_command_current_a: '20',
|
||||
evcs_ac_power_w: '3600',
|
||||
pv_power_w: '6600', pv_power_10m_avg_w: '6000', pv_trend_w: '0',
|
||||
house_ac_power_w: '6000', vebus_ac_out_l1_power_w: '6000',
|
||||
battery_current_a: '-1',
|
||||
battery_soft_breach_at: '2026-08-08 15:10:00',
|
||||
},
|
||||
expect: {
|
||||
profile: 'outside_window',
|
||||
sourceMode: 'solar_only',
|
||||
state: 'stop_conditions_lost',
|
||||
decision: 'stop',
|
||||
desiredCharging: false,
|
||||
currentA: 6,
|
||||
takeoverNeeded: false,
|
||||
edfOnAction: false,
|
||||
takeoverHelperService: 'turn_off',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'batt-pointe-maison-ne-coupe-pas',
|
||||
label: "Relevé du 01/09 14:56 : pointe maison à 8 kW, batterie à -42 A → réduction graduée au plafond système (18 A), PAS un arrêt",
|
||||
time: AFTERNOON,
|
||||
overrides: {
|
||||
evcs_vehicle_connected: 'on', evcs_device_connected: 'on',
|
||||
evcs_charging_switch: 'on', control_latch: 'on', charge_permission_now: 'on',
|
||||
last_start_at: '2026-08-08 14:00:00',
|
||||
soc_percent: '94',
|
||||
evcs_current_a: '20', evcs_set_current_a: '20', evcs_command_current_a: '20',
|
||||
evcs_ac_power_w: '3600',
|
||||
pv_power_w: '9100', pv_power_10m_avg_w: '6000', pv_trend_w: '0',
|
||||
house_ac_power_w: '8000', vebus_ac_out_l1_power_w: '8000',
|
||||
battery_current_a: '-42',
|
||||
},
|
||||
expect: {
|
||||
profile: 'outside_window',
|
||||
sourceMode: 'solar_only',
|
||||
state: 'charging_step_down_system_cap',
|
||||
decision: 'step_down_fast',
|
||||
desiredCharging: true,
|
||||
currentA: 18,
|
||||
takeoverNeeded: false,
|
||||
edfOnAction: false,
|
||||
takeoverHelperService: 'turn_off',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'batt-decharge-dure-arret-immediat',
|
||||
label: "SÉCURITÉ : batterie à -120 A, sous le seuil DUR de la bande (-100 A) → arrêt immédiat, aucune tolérance. La tolérance est du confort, jamais du risque",
|
||||
time: AFTERNOON,
|
||||
overrides: {
|
||||
evcs_vehicle_connected: 'on', evcs_device_connected: 'on',
|
||||
evcs_charging_switch: 'on', control_latch: 'on', charge_permission_now: 'on',
|
||||
last_start_at: '2026-08-08 14:00:00',
|
||||
soc_percent: '94',
|
||||
evcs_current_a: '20', evcs_set_current_a: '20', evcs_command_current_a: '20',
|
||||
evcs_ac_power_w: '3600',
|
||||
pv_power_w: '1000', pv_power_10m_avg_w: '6000', pv_trend_w: '0',
|
||||
house_ac_power_w: '9000', vebus_ac_out_l1_power_w: '9000',
|
||||
battery_current_a: '-120',
|
||||
},
|
||||
expect: {
|
||||
profile: 'outside_window',
|
||||
sourceMode: 'solar_only',
|
||||
state: 'battery_current_hard_stop',
|
||||
decision: 'stop_hard',
|
||||
desiredCharging: false,
|
||||
currentA: 6,
|
||||
takeoverNeeded: false,
|
||||
edfOnAction: false,
|
||||
takeoverHelperService: 'turn_off',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'batt-plancher-demarrage-reste-instantane',
|
||||
label: "FRONTIÈRE : véhicule branché mais à l'arrêt, batterie à -1 A → on NE DÉMARRE PAS sur une batterie qui se décharge. Le plancher de démarrage reste instantané",
|
||||
time: AFTERNOON,
|
||||
overrides: {
|
||||
evcs_vehicle_connected: 'on', evcs_device_connected: 'on',
|
||||
soc_percent: '94',
|
||||
pv_power_w: '6600', pv_power_10m_avg_w: '6000', pv_trend_w: '0',
|
||||
house_ac_power_w: '6000', vebus_ac_out_l1_power_w: '6000',
|
||||
battery_current_a: '-1',
|
||||
},
|
||||
expect: {
|
||||
profile: 'outside_window',
|
||||
sourceMode: 'solar_only',
|
||||
state: 'idle_wait_conditions',
|
||||
decision: 'hold',
|
||||
desiredCharging: false,
|
||||
currentA: 6,
|
||||
takeoverNeeded: false,
|
||||
edfOnAction: false,
|
||||
takeoverHelperService: 'turn_off',
|
||||
},
|
||||
},
|
||||
|
||||
// ================= STRATÉGIES MANUELLES =================
|
||||
{
|
||||
id: 'force-grid-jour',
|
||||
@@ -1034,7 +1175,7 @@ export const scenarios = [
|
||||
// le test ne prouverait rien.
|
||||
{
|
||||
id: 'etablissement-maintien-charge',
|
||||
label: 'Réseau fermé il y a 30 s, PV effondrée à 600 W et batterie à -15 A : la charge en cours NE DOIT PAS être arrêtée',
|
||||
label: 'Réseau fermé il y a 30 s, PV effondrée à 600 W, batterie à -15 A depuis 10 min : dans la fenêtre, la mesure batterie est neutralisée, la charge NE DOIT PAS être arrêtée',
|
||||
time: MORNING,
|
||||
overrides: {
|
||||
evcs_vehicle_connected: 'on', evcs_device_connected: 'on',
|
||||
@@ -1047,6 +1188,7 @@ export const scenarios = [
|
||||
pv_power_w: '600', pv_power_10m_avg_w: '3900', pv_trend_w: '-2000',
|
||||
grid_state: 'on', victron_acin1_available: '1', grid_power_w: '1800',
|
||||
battery_current_a: '-15',
|
||||
battery_soft_breach_at: '2026-08-08 08:50:00',
|
||||
acin1_last_changed_ts: String(MORNING_TS - 30),
|
||||
grid_switch_last_changed_ts: String(MORNING_TS - 30),
|
||||
},
|
||||
@@ -1064,7 +1206,7 @@ export const scenarios = [
|
||||
},
|
||||
{
|
||||
id: 'etablissement-termine-arret-normal',
|
||||
label: 'CONTREFACTUEL : mêmes mesures, transition vieille de 400 s → fenêtre fermée, la garde de maintien reprend la main et arrête',
|
||||
label: 'CONTREFACTUEL : mêmes mesures ET même dépassement de 10 min, transition vieille de 400 s → fenêtre fermée, la garde reprend la main et arrête (10 min > tolérance 4 min de la bande 40_60)',
|
||||
time: MORNING,
|
||||
overrides: {
|
||||
evcs_vehicle_connected: 'on', evcs_device_connected: 'on',
|
||||
@@ -1077,6 +1219,7 @@ export const scenarios = [
|
||||
pv_power_w: '600', pv_power_10m_avg_w: '3900', pv_trend_w: '-2000',
|
||||
grid_state: 'on', victron_acin1_available: '1', grid_power_w: '1800',
|
||||
battery_current_a: '-15',
|
||||
battery_soft_breach_at: '2026-08-08 08:50:00',
|
||||
acin1_last_changed_ts: String(MORNING_TS - 400),
|
||||
grid_switch_last_changed_ts: String(MORNING_TS - 400),
|
||||
},
|
||||
@@ -1124,7 +1267,7 @@ export const scenarios = [
|
||||
},
|
||||
{
|
||||
id: 'etablissement-ouverture-aucune-fenetre',
|
||||
label: "ASYMÉTRIE : réseau DISPARU il y a 30 s → aucune fenêtre. Les onduleurs reprennent tout, les gardes doivent agir plus vite, pas moins",
|
||||
label: "ASYMÉTRIE : réseau DISPARU il y a 30 s → aucune fenêtre. Les onduleurs reprennent tout, les gardes doivent agir plus vite, pas moins (dépassement de 10 min > tolérance 4 min)",
|
||||
time: MORNING,
|
||||
overrides: {
|
||||
evcs_vehicle_connected: 'on', evcs_device_connected: 'on',
|
||||
@@ -1138,6 +1281,7 @@ export const scenarios = [
|
||||
grid_state: 'on', victron_acin1_available: '1', grid_power_w: '1800',
|
||||
battery_current_a: '-15',
|
||||
grid_state: 'off', victron_acin1_available: '0', grid_power_w: '0',
|
||||
battery_soft_breach_at: '2026-08-08 08:50:00',
|
||||
acin1_last_changed_ts: String(MORNING_TS - 30),
|
||||
grid_switch_last_changed_ts: String(MORNING_TS - 30),
|
||||
},
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user