Fenetre d etablissement : ne plus decider sur les mesures du transitoire
A la fermeture du contacteur, PV, import et courant batterie ne decrivent aucun regime permanent pendant ~150 s (mesure du 31/08). La boucle de 30 s y prenait cinq decisions, et la moyenne PV 10 min en gardait la trace dix minutes de plus -- le systeme lisait "plus de soleil", en concluait qu il avait besoin du reseau, et confirmait sa propre premisse. Aucun seuil nouveau : le systeme publiait deja l horodatage de la transition et personne ne le lisait. Pendant la fenetre, on ne demarre pas et on ne laisse pas une charge s arreter sur ces mesures ; les step-down restent libres. Deux asymetries voulues : la fenetre ne s ouvre qu a l ARRIVEE du reseau (a sa disparition les onduleurs reprennent tout, les gardes doivent agir plus vite), et elle suspend le confort, jamais le risque. Golden 42 -> 48, chaque cas apparie a son contrefactuel hors fenetre. Mutation : gridSettling force a false -> 2 echecs cibles, restauration -> 48/48. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KKzkkVTG6KXnGXFFC7X9XJ
This commit is contained in:
91
HANDOFF.md
91
HANDOFF.md
@@ -1860,3 +1860,94 @@ 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*
|
||||
|
||||
@@ -563,8 +563,17 @@ const batteryAssistSolarHoldOk =
|
||||
// =====================================================
|
||||
// START / HOLD CONDITIONS
|
||||
// =====================================================
|
||||
// FENETRE D'ETABLISSEMENT (31/08/2026) — cf. WINDOWS_SOURCEV2 pour la mesure.
|
||||
// Pendant les ~150 s qui suivent l'arrivee du reseau, PV et courant batterie ne
|
||||
// mesurent rien d'exploitable. On NE DEMARRE PAS sur ces valeurs, et on ne
|
||||
// laisse pas une charge en cours s'arreter a cause d'elles. Les gardes de
|
||||
// securite (commonSafety*, planchers SOC, surcharge, pas de tampon) restent
|
||||
// evaluees normalement : c'est le risque qui prime, jamais le confort.
|
||||
const gridSettling = derived.gridSettling === true;
|
||||
|
||||
const rawSolarStartOk =
|
||||
(context.sourceMode === 'solar_only' || context.sourceMode === 'solar_potential') &&
|
||||
!gridSettling &&
|
||||
commonSafetyStartOk &&
|
||||
parsed.soc >= effectiveSocStartFloor &&
|
||||
parsed.batteryCurrentA >= profileBatteryDischargeFloorA &&
|
||||
@@ -580,8 +589,12 @@ const rawSolarHoldOk =
|
||||
(context.sourceMode === 'solar_only' || context.sourceMode === 'solar_potential') &&
|
||||
commonSafetyHoldOk &&
|
||||
parsed.soc >= effectiveSocHoldFloor &&
|
||||
parsed.batteryCurrentA >= profileBatteryDischargeFloorA &&
|
||||
(
|
||||
gridSettling ||
|
||||
parsed.batteryCurrentA >= profileBatteryDischargeFloorA
|
||||
) &&
|
||||
(
|
||||
gridSettling ||
|
||||
parsed.pvPowerW >= params.pvHoldMinW ||
|
||||
cloudToleranceOk
|
||||
);
|
||||
@@ -667,9 +680,13 @@ const probeSuccessNow =
|
||||
parsed.batteryCurrentA >= Math.min(0, batteryDischargeSoftDynamicA)
|
||||
);
|
||||
|
||||
// Une sonde ne peut pas etre declaree ratee sur une mesure prise pendant
|
||||
// l'etablissement : c'est le reseau qui masque la production, pas la sonde qui
|
||||
// echoue. On differe le verdict, on ne l'invente pas.
|
||||
const probeFailedNow =
|
||||
probeModeActive &&
|
||||
probeElapsedReached &&
|
||||
!gridSettling &&
|
||||
!probeSuccessNow;
|
||||
|
||||
// =====================================================
|
||||
@@ -689,7 +706,10 @@ const stepUpByForecastMode =
|
||||
derived.projectedEndSocAfterCharge >= (derived.endDayGuardSoc - 5) &&
|
||||
cloudToleranceOk;
|
||||
|
||||
// Pas de montee en courant sur des mesures d'etablissement. La descente, elle,
|
||||
// n'est pas bridee : c'est la direction sure.
|
||||
const solarShouldStepUp =
|
||||
!gridSettling &&
|
||||
lifecycle.currentCooldownElapsed &&
|
||||
derived.currentReferenceA < derived.physicalCapA &&
|
||||
derived.physicalCapA >= params.currentMinA &&
|
||||
@@ -906,6 +926,8 @@ return [{
|
||||
|
||||
solarStartOk,
|
||||
solarHoldOk,
|
||||
gridSettling,
|
||||
gridTransitionAgeSec: parsed.gridTransitionAgeSec,
|
||||
gridStartOk,
|
||||
gridHoldOk,
|
||||
|
||||
|
||||
@@ -310,6 +310,12 @@ reason.push(`Batt=${round1(parsed.batteryCurrentA)}A`);
|
||||
reason.push(`PV=${Math.round(parsed.pvPowerW)}W`);
|
||||
reason.push(`PV10=${Math.round(parsed.pvPowerAvg10mW)}W`);
|
||||
reason.push(`Grid=${parsed.gridAvailable ? 'on' : 'off'} (${Math.round(parsed.gridPowerW)}W)`);
|
||||
// Trace de la fenetre d'etablissement : sans elle, une lecture du logbook
|
||||
// pendant le transitoire donne l'impression que le systeme decide n'importe
|
||||
// quoi. Il decide, au contraire, de ne PAS decider sur ces mesures.
|
||||
if (derived.gridSettling) {
|
||||
reason.push(`ETABLISSEMENT reseau ${Math.round(parsed.gridTransitionAgeSec)}s/${params.gridSettleSeconds}s (mesures PV/batterie non fiables)`);
|
||||
}
|
||||
reason.push(`Cap=${derived.physicalCapA}A`);
|
||||
reason.push(`Ref=${round1(derived.currentReferenceA)}A`);
|
||||
reason.push(`Target=${round1(desiredCurrentA)}A`);
|
||||
|
||||
@@ -13,6 +13,11 @@
|
||||
"grid_state": states("switch.edf_general_ok_switch"),
|
||||
"victron_acin1_available": states("sensor.victron_ac_in1_available"),
|
||||
|
||||
|
||||
"acin1_last_changed_ts": as_timestamp(states.sensor.victron_ac_in1_available.last_changed, 0) if states.sensor.victron_ac_in1_available is not none else 0,
|
||||
"grid_switch_last_changed_ts": as_timestamp(states.switch.edf_general_ok_switch.last_changed, 0) if states.switch.edf_general_ok_switch is not none else 0,
|
||||
"grid_settle_seconds": states("input_number.evcs_grid_settle_seconds"),
|
||||
|
||||
"today_remaining_kwh": states("sensor.energy_production_today_remaining"),
|
||||
"tomorrow_kwh": states("sensor.energy_production_tomorrow"),
|
||||
|
||||
|
||||
@@ -249,6 +249,21 @@ const parsed = {
|
||||
(low(input.grid_state || 'off') === 'on') ||
|
||||
(num(input.victron_acin1_available ?? input.ac_in1_available, 0) === 1),
|
||||
|
||||
// FENETRE D'ETABLISSEMENT (31/08/2026) — age, en secondes, de la derniere
|
||||
// transition du reseau. On retient la plus RECENTE des deux sources : la
|
||||
// presence physique AC-IN (celle qui declenche reellement le transitoire) et
|
||||
// le contacteur (qui peut bouger sans qu'AC-IN suive, disjoncteur ouvert).
|
||||
// Horodatage absent ou nul => age tres grand => aucune fenetre, comportement
|
||||
// historique. On ne devine jamais une transition qu'on n'a pas observee.
|
||||
gridTransitionAgeSec: (() => {
|
||||
const ts = Math.max(
|
||||
num(input.acin1_last_changed_ts, 0),
|
||||
num(input.grid_switch_last_changed_ts, 0)
|
||||
);
|
||||
if (!(ts > 0)) return Number.POSITIVE_INFINITY;
|
||||
return Math.max(0, (nowMs / 1000) - ts);
|
||||
})(),
|
||||
|
||||
todayRemainingKwh: Math.max(0, num(input.today_remaining_kwh, 0)),
|
||||
tomorrowKwh: Math.max(0, num(input.tomorrow_kwh, 0)),
|
||||
|
||||
@@ -398,6 +413,12 @@ const params = {
|
||||
advisorStatusOk: cleanText(input.advisor_status).toLowerCase() === 'ok',
|
||||
advisorLastRunMs: parseAdvisorRunToMs(cleanText(input.advisor_last_run)),
|
||||
|
||||
// Duree de la fenetre d'etablissement. Valeur mesuree le 31/08/2026 : le
|
||||
// transitoire de fermeture dure ~150 s (import 1985 -> 25 W et 2600 -> 52 W,
|
||||
// deux campagnes), on prend 180 s de marge. Reglable sans redeploiement via
|
||||
// input_number.evcs_grid_settle_seconds ; absent => 180.
|
||||
gridSettleSeconds: clamp(num(input.grid_settle_seconds, 180), 0, 900),
|
||||
|
||||
probeEnabled: low(input.probe_enabled || 'off') === 'on',
|
||||
probeMinutes: Math.max(1, num(input.probe_minutes, 3)),
|
||||
probeRetryMinutes: Math.max(5, num(input.probe_retry_minutes, 45)),
|
||||
|
||||
@@ -136,6 +136,28 @@ const physicalCapA = clamp(
|
||||
);
|
||||
const canRunAtMinCurrent = physicalCapA >= params.currentMinA;
|
||||
|
||||
// =====================================================
|
||||
// FENETRE D'ETABLISSEMENT DU RESEAU (31/08/2026)
|
||||
// =====================================================
|
||||
// Mesure du 31/08 : a la fermeture du contacteur, le reseau reprend d'un coup
|
||||
// TOUTE la charge de la maison et le solaire bascule vers la batterie. Batterie
|
||||
// preneuse, la production ne bouge pas ; batterie pleine, elle s'effondre (2663
|
||||
// -> 559 W) faute de destination. Dans les deux cas l'import decroit ensuite
|
||||
// jusqu'a la consigne en ~150 s (1985 -> 25 W, puis 2600 -> 52 W).
|
||||
//
|
||||
// Pendant cette phase, PV, import et courant batterie ne decrivent aucun regime
|
||||
// permanent : la boucle de 30 s y prend cinq decisions, et la moyenne PV 10 min
|
||||
// en garde la trace une dizaine de minutes de plus. Le systeme lit "plus de
|
||||
// soleil", en conclut qu'il a besoin du reseau, et confirme ainsi sa propre
|
||||
// premisse -- une boucle de rattrapage, comme les trois du 24/08.
|
||||
//
|
||||
// ASYMETRIE VOULUE : la fenetre ne s'ouvre qu'a l'ARRIVEE du reseau. A sa
|
||||
// disparition, les onduleurs reprennent instantanement toute la charge : les
|
||||
// gardes doivent agir plus vite, jamais moins vite.
|
||||
const gridSettling =
|
||||
parsed.gridAvailable &&
|
||||
parsed.gridTransitionAgeSec < params.gridSettleSeconds;
|
||||
|
||||
const endDayGuardSoc = Math.max(params.endOfDaySocGuard, params.endOfDaySocTarget);
|
||||
|
||||
const energyNeededForEndDayGuardKwh = Math.max(
|
||||
@@ -805,6 +827,8 @@ return [{
|
||||
systemCapCurrentA,
|
||||
physicalCapA,
|
||||
canRunAtMinCurrent,
|
||||
gridSettling,
|
||||
gridTransitionAgeSec: parsed.gridTransitionAgeSec,
|
||||
endDayGuardSoc,
|
||||
energyNeededForEndDayGuardKwh,
|
||||
forecastBufferKwh,
|
||||
|
||||
@@ -14,6 +14,11 @@ const MIDDAY = '2026-08-08T13:00:00+02:00';
|
||||
const AFTERNOON = '2026-08-08T15:30:00+02:00';
|
||||
const EVENING = '2026-08-08T18:00:00+02:00';
|
||||
|
||||
// Fenêtre d'établissement du réseau (31/08/2026) : horodatage de la dernière
|
||||
// transition d'AC-IN, en secondes epoch. Le harnais fige l'horloge sur `time`,
|
||||
// donc on exprime l'âge de la transition par rapport à cet instant simulé.
|
||||
const MORNING_TS = Date.parse(MORNING) / 1000;
|
||||
|
||||
const VEHICLE_NIGHT = {
|
||||
evcs_vehicle_connected: 'on',
|
||||
evcs_device_connected: 'on',
|
||||
@@ -1019,4 +1024,183 @@ export const scenarios = [
|
||||
takeoverHelperService: 'turn_off',
|
||||
},
|
||||
},
|
||||
// ======= FENÊTRE D'ÉTABLISSEMENT DU RÉSEAU (31/08/2026) =======
|
||||
// Mesuré ce jour-là : à la fermeture du contacteur, le réseau reprend toute la
|
||||
// charge de la maison, le solaire bascule vers la batterie (ou se fait brider
|
||||
// s'il n'y a plus de tampon : 2663 -> 559 W), puis l'import redescend à la
|
||||
// consigne en ~150 s. Pendant ce temps la boucle de 30 s prend cinq décisions
|
||||
// sur des mesures qui ne décrivent aucun régime permanent.
|
||||
// Chaque cas est apparié à son contrefactuel hors fenêtre : sans cet appariement
|
||||
// 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',
|
||||
time: MORNING,
|
||||
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 08:00:00',
|
||||
soc_percent: '60',
|
||||
evcs_current_a: '16', evcs_set_current_a: '16', evcs_command_current_a: '16',
|
||||
evcs_ac_power_w: '3680',
|
||||
house_ac_power_w: '4400', vebus_ac_out_l1_power_w: '4400',
|
||||
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',
|
||||
acin1_last_changed_ts: String(MORNING_TS - 30),
|
||||
grid_switch_last_changed_ts: String(MORNING_TS - 30),
|
||||
},
|
||||
expect: {
|
||||
profile: "morning_solar",
|
||||
sourceMode: "solar_only",
|
||||
state: "charging_step_down_battery",
|
||||
decision: "step_down",
|
||||
desiredCharging: true,
|
||||
currentA: 14,
|
||||
takeoverNeeded: false,
|
||||
edfOnAction: false,
|
||||
takeoverHelperService: "turn_off",
|
||||
},
|
||||
},
|
||||
{
|
||||
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',
|
||||
time: MORNING,
|
||||
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 08:00:00',
|
||||
soc_percent: '60',
|
||||
evcs_current_a: '16', evcs_set_current_a: '16', evcs_command_current_a: '16',
|
||||
evcs_ac_power_w: '3680',
|
||||
house_ac_power_w: '4400', vebus_ac_out_l1_power_w: '4400',
|
||||
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',
|
||||
acin1_last_changed_ts: String(MORNING_TS - 400),
|
||||
grid_switch_last_changed_ts: String(MORNING_TS - 400),
|
||||
},
|
||||
expect: {
|
||||
profile: "morning_solar",
|
||||
sourceMode: "solar_only",
|
||||
state: "stop_conditions_lost",
|
||||
decision: "stop",
|
||||
desiredCharging: false,
|
||||
currentA: 6,
|
||||
takeoverNeeded: false,
|
||||
edfOnAction: false,
|
||||
takeoverHelperService: "turn_off",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'etablissement-securite-non-suspendue',
|
||||
label: "SÉCURITÉ : dans la fenêtre, batterie à -40 A → l'arrêt dur reste actif. La fenêtre suspend le confort, jamais le risque",
|
||||
time: MORNING,
|
||||
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 08:00:00',
|
||||
soc_percent: '60',
|
||||
evcs_current_a: '16', evcs_set_current_a: '16', evcs_command_current_a: '16',
|
||||
evcs_ac_power_w: '3680',
|
||||
house_ac_power_w: '4400', vebus_ac_out_l1_power_w: '4400',
|
||||
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: '-40',
|
||||
acin1_last_changed_ts: String(MORNING_TS - 30),
|
||||
grid_switch_last_changed_ts: String(MORNING_TS - 30),
|
||||
},
|
||||
expect: {
|
||||
profile: "morning_solar",
|
||||
sourceMode: "solar_only",
|
||||
state: "battery_current_hard_stop",
|
||||
decision: "stop_hard",
|
||||
desiredCharging: false,
|
||||
currentA: 6,
|
||||
takeoverNeeded: false,
|
||||
edfOnAction: false,
|
||||
takeoverHelperService: "turn_off",
|
||||
},
|
||||
},
|
||||
{
|
||||
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",
|
||||
time: MORNING,
|
||||
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 08:00:00',
|
||||
soc_percent: '60',
|
||||
evcs_current_a: '16', evcs_set_current_a: '16', evcs_command_current_a: '16',
|
||||
evcs_ac_power_w: '3680',
|
||||
house_ac_power_w: '4400', vebus_ac_out_l1_power_w: '4400',
|
||||
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',
|
||||
grid_state: 'off', victron_acin1_available: '0', grid_power_w: '0',
|
||||
acin1_last_changed_ts: String(MORNING_TS - 30),
|
||||
grid_switch_last_changed_ts: String(MORNING_TS - 30),
|
||||
},
|
||||
expect: {
|
||||
profile: "morning_solar",
|
||||
sourceMode: "solar_only",
|
||||
state: "stop_conditions_lost",
|
||||
decision: "stop",
|
||||
desiredCharging: false,
|
||||
currentA: 6,
|
||||
takeoverNeeded: false,
|
||||
edfOnAction: false,
|
||||
takeoverHelperService: "turn_off",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'etablissement-pas-de-demarrage',
|
||||
label: "Réseau fermé il y a 30 s, rien en charge : on ne démarre pas non plus sur des mesures d'établissement",
|
||||
time: MORNING,
|
||||
overrides: {
|
||||
evcs_vehicle_connected: 'on', evcs_device_connected: 'on',
|
||||
soc_percent: '60', pv_power_w: '4500', pv_power_10m_avg_w: '4200',
|
||||
pv_trend_w: '100', battery_current_a: '10',
|
||||
house_ac_power_w: '900', vebus_ac_out_l1_power_w: '900',
|
||||
grid_state: 'on', victron_acin1_available: '1', grid_power_w: '1800',
|
||||
acin1_last_changed_ts: String(MORNING_TS - 30),
|
||||
grid_switch_last_changed_ts: String(MORNING_TS - 30),
|
||||
},
|
||||
expect: {
|
||||
profile: "morning_solar",
|
||||
sourceMode: "solar_only",
|
||||
state: "idle_wait_conditions",
|
||||
decision: "hold",
|
||||
desiredCharging: false,
|
||||
currentA: 6,
|
||||
takeoverNeeded: false,
|
||||
edfOnAction: false,
|
||||
takeoverHelperService: "turn_off",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'etablissement-demarrage-hors-fenetre',
|
||||
label: 'CONTREFACTUEL : mêmes mesures, transition vieille de 400 s → le démarrage solaire a bien lieu',
|
||||
time: MORNING,
|
||||
overrides: {
|
||||
evcs_vehicle_connected: 'on', evcs_device_connected: 'on',
|
||||
soc_percent: '60', pv_power_w: '4500', pv_power_10m_avg_w: '4200',
|
||||
pv_trend_w: '100', battery_current_a: '10',
|
||||
house_ac_power_w: '900', vebus_ac_out_l1_power_w: '900',
|
||||
grid_state: 'on', victron_acin1_available: '1', grid_power_w: '1800',
|
||||
acin1_last_changed_ts: String(MORNING_TS - 400),
|
||||
grid_switch_last_changed_ts: String(MORNING_TS - 400),
|
||||
},
|
||||
expect: {
|
||||
profile: "morning_solar",
|
||||
sourceMode: "solar_only",
|
||||
state: "start_min_current",
|
||||
decision: "start",
|
||||
desiredCharging: true,
|
||||
currentA: 6,
|
||||
takeoverNeeded: false,
|
||||
edfOnAction: false,
|
||||
takeoverHelperService: "turn_off",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user