La provocation solaire se demande aux chargeurs, pas au SOC
Le correctif precedent bloque l incident de 19:44, 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 — 18:50 : +14 W, ressource refusee ; 19:41 : +1 W, plus rien a donner. Rejeu du 19:41 : batteryAssistSolarStartOk etait encore vrai. Trois minutes avant l incident, le systeme aurait commis la meme faute. Le plan annoncait de lire evcs_balance_pv_censored. Lecture de CENSORSHIP.js : ce drapeau est un OU de motifs dont l un est exactement le proxy qu on voulait remplacer (socPct >= 90 et batterie sans puits). Et il est reste ON jusqu au calcul de 19:50, soit cinq minutes APRES le demarrage de la charge — ce qui l a fait basculer n est pas la censure mais sun_elevation passant sous 10 degres. Il n aurait pas bloque l incident. Le signal juste, ce sont les chargeurs eux-memes : sept entites sensor.*_mppt_operation_mode, jamais lues par aucun workflow. mppt_active = je suis a mon point de puissance maximale, je donne tout ; voltage_current_limited ou off = je me retiens faute de preneur. Le 03/09 elles ont bascule a 19:20, soit 24 minutes avant pv_censored, sans aucun proxy et a la cadence de la boucle. Contre-verification le 01/09 a 14:22 : les trackers restaient limites PENDANT une charge de 3,4 kW sous 6,9 kW de PV, donc le signal ne s auto-annule pas a midi. Collecte sans aucun entity_id code en dur (selectattr sur mppt et operation_mode), expression testee sur /api/template puis gabarit complet re-rendu : 191 champs, JSON valide, aucun null. Lectures inexploitables ecartees, et repli sur le comportement anterieur quand il n en reste aucune. Garde de DEMARRAGE uniquement, structurellement : la lier au sourceMode ou au maintien ferait que la charge, en donnant un preneur au MPPT, detruirait sa propre condition d autorisation — le bistable de l abondance P6 corrige le 24/08. Golden 64 -> 66. Mutation dans trois directions : garde neutralisee -> 1 echec cible, provocation toujours refusee -> 3 echecs, lecture illisible traitee comme "a fond" -> 1 echec sur le scenario de repli. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KKzkkVTG6KXnGXFFC7X9XJ
This commit is contained in:
98
HANDOFF.md
98
HANDOFF.md
@@ -2622,3 +2622,101 @@ l'energie arrive — demain matin est creux : 0,03 kWh a 7 h, 0,77 a 8 h, 1,81 a
|
||||
|
||||
---
|
||||
*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*
|
||||
|
||||
@@ -785,6 +785,23 @@ Golden 61 → 64, autour d'une paire de releves reels pris a 54 minutes d'ecart
|
||||
dont les lectures PV ne different que de **11 W** (769 puis 758) : le premier
|
||||
demarre, le second non — ce qui les separe est la maison. Detail : HANDOFF §36.
|
||||
|
||||
**Complement du 03/09 au soir — un excedent nul est ambigu.** L'excedent proche
|
||||
de zero est la signature de la censure ET celle de l'equilibre de fin de
|
||||
journee : 18:50 (+14 W, ressource refusee) et 19:41 (+1 W, plus rien a donner)
|
||||
sont indiscernables par la puissance. Ce sont les **chargeurs eux-memes** qui
|
||||
tranchent : `sensor.*_mppt_operation_mode` vaut `mppt_active` quand le tracker
|
||||
donne tout, `voltage_current_limited` ou `off` quand il se retient. Sept
|
||||
entites, que ne lisait aucun workflow. Le 03/09 elles ont bascule a 19:20 —
|
||||
**24 minutes avant** le drapeau `pv_censored` du bilan (qui tourne toutes les
|
||||
5 min et dont l'un des motifs est justement le proxy SOC ≥ 90 qu'on cherchait a
|
||||
remplacer). La provocation `solar_potential` et l'assistance batterie exigent
|
||||
desormais qu'au moins un chargeur se retienne.
|
||||
|
||||
⚠️ Garde de DEMARRAGE uniquement, structurellement : la lier au maintien ferait
|
||||
que la charge, en donnant un preneur au MPPT, detruirait sa propre autorisation
|
||||
— le bistable P6 du 24/08. Lectures indisponibles → repli sur le comportement
|
||||
anterieur. Golden 64 → 66. Detail : HANDOFF §37.
|
||||
|
||||
⚠️ Point structurel mis au jour par cet incident : le bilan energetique publie
|
||||
l'heure de croisement, l'energie a couvrir, le SOC requis, `grid_needed` et
|
||||
`pv_censored` — et **aucun des deux workflows de decision n'en lit une seule
|
||||
|
||||
@@ -607,6 +607,43 @@ const commonSafetyHoldOk =
|
||||
// recreerait l'oscillateur qu'on vient d'eteindre.
|
||||
const solarSurplusW = parsed.pvPowerW - derived.houseNonEvcsW;
|
||||
|
||||
// LA PROVOCATION DOIT ETRE JUSTIFIEE PAR LES CHARGEURS EUX-MEMES (03/09/2026)
|
||||
// =====================================================================
|
||||
// Un excedent proche de zero est AMBIGU : c'est la signature de la censure
|
||||
// (production bridee, calee sur la maison) ET celle de l'equilibre de fin de
|
||||
// journee (production reelle qui rejoint la consommation). Le releve du 03/09
|
||||
// contient les deux a 51 minutes d'ecart, avec le meme excedent :
|
||||
//
|
||||
// 18:50 PV 769 W, maison 755 W, excedent +14 W -> 7 trackers LIMITES
|
||||
// 19:41 PV 817 W, maison 816 W, excedent +1 W -> 7 trackers MPPT_ACTIVE
|
||||
//
|
||||
// Le premier cache de la ressource refusee, le second n'a plus rien a donner.
|
||||
// Aucun seuil de puissance ne les separe. Les chargeurs, eux, le disent :
|
||||
// 'mppt_active' = je suis a mon point de puissance maximale, je donne tout ;
|
||||
// 'voltage_current_limited' / 'off' = je me retiens faute de preneur.
|
||||
//
|
||||
// Signal DIRECT (le materiel lui-meme, pas un proxy), INSTANTANE (30 s, la
|
||||
// cadence de la boucle) et, sur l'incident du 03/09, il a bascule a 19:20 —
|
||||
// 24 minutes avant le drapeau `pv_censored` du bilan energetique, qui tourne
|
||||
// toutes les 5 min et dont l'un des motifs est justement le proxy SOC >= 90
|
||||
// qu'on cherchait a remplacer.
|
||||
//
|
||||
// ⚠️ 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. La garde n'est
|
||||
// lue que quand la voiture est a l'arret, instant ou l'etat des trackers
|
||||
// decrit la situation SANS elle. Verifie le 01/09 a 14:22-14:34 : les trackers
|
||||
// restaient limites 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 non plus.
|
||||
//
|
||||
// Aucune lecture exploitable (integration muette) => on ne sait pas => on
|
||||
// retombe sur le comportement anterieur. On ne devine jamais un etat.
|
||||
const mpptReadable = parsed.mpptReadableCount > 0;
|
||||
const mpptAllAtMaxPower = mpptReadable && parsed.mpptHoldingBackCount === 0;
|
||||
const solarProvocationJustified = !mpptAllAtMaxPower;
|
||||
|
||||
// Chemin PROVOCATION (batterie haute, ou sourceMode solar_potential) : on
|
||||
// n'exige pas d'excedent mesurable — il est censure par construction — mais on
|
||||
// exige que la batterie ne porte pas deja la maison.
|
||||
@@ -615,7 +652,7 @@ const solarSurplusProvocationOk = solarSurplusW >= 0;
|
||||
// Chemin SOLEIL REEL : l'excedent doit atteindre le seuil de demarrage.
|
||||
const solarSurplusStartOk =
|
||||
context.sourceMode === 'solar_potential'
|
||||
? solarSurplusProvocationOk
|
||||
? (solarSurplusProvocationOk && solarProvocationJustified)
|
||||
: solarSurplusW >= context.effectivePvStartMinW;
|
||||
|
||||
// =====================================================
|
||||
@@ -650,6 +687,7 @@ const batteryAssistSolarStartOk =
|
||||
parsed.soc > batteryAssistSolarHoldSocMin &&
|
||||
parsed.batteryCurrentA >= profileBatteryDischargeFloorA &&
|
||||
solarSurplusProvocationOk &&
|
||||
solarProvocationJustified &&
|
||||
cloudDebtNextKwh <= batteryAssistSolarDebtStartMaxKwh &&
|
||||
!cloudDebtActive;
|
||||
|
||||
@@ -1023,6 +1061,12 @@ return [{
|
||||
solarSurplusW,
|
||||
solarSurplusProvocationOk,
|
||||
solarSurplusStartOk,
|
||||
|
||||
// Etat declare par les chargeurs solaires (03/09/2026).
|
||||
mpptReadableCount: parsed.mpptReadableCount,
|
||||
mpptHoldingBackCount: parsed.mpptHoldingBackCount,
|
||||
mpptAllAtMaxPower,
|
||||
solarProvocationJustified,
|
||||
batteryCurrentHardExceeded,
|
||||
mustStopHard,
|
||||
gridBatteryProtectionStop,
|
||||
|
||||
@@ -6,6 +6,11 @@
|
||||
"pv_power_10m_avg_w": states("sensor.victron_pv_power_10m_avg"),
|
||||
"pv_trend_w": states("sensor.victron_pv_power_trend_w"),
|
||||
|
||||
"mppt_operation_modes": states.sensor
|
||||
| selectattr('entity_id','search','mppt')
|
||||
| selectattr('entity_id','search','operation_mode')
|
||||
| map(attribute='state') | join(','),
|
||||
|
||||
"house_ac_power_w": states("sensor.victron_consumption_power_l1"),
|
||||
"house_ac_power_prev_w": states("sensor.victron_consumption_power_l1"),
|
||||
|
||||
|
||||
@@ -236,6 +236,25 @@ const parsed = {
|
||||
batteryCurrentA: num(input.battery_current_a, 0),
|
||||
|
||||
pvPowerW: Math.max(0, num(input.pv_power_w, 0)),
|
||||
|
||||
// MODE DE FONCTIONNEMENT DES CHARGEURS SOLAIRES (03/09/2026)
|
||||
// Chaque tracker declare lui-meme s'il est a son point de puissance maximale
|
||||
// ('mppt_active' : il donne tout ce qu'il a) ou s'il se retient
|
||||
// ('voltage_current_limited', 'off' : il y a de la ressource REFUSEE).
|
||||
// Lectures inexploitables (integration muette) => listes vides => les gardes
|
||||
// aval retombent sur le comportement anterieur. On ne devine pas un etat.
|
||||
...(() => {
|
||||
const modes = String(input.mppt_operation_modes || '')
|
||||
.split(',')
|
||||
.map(s => s.trim().toLowerCase())
|
||||
.filter(s => s !== '' && s !== 'unavailable' && s !== 'unknown');
|
||||
const atMaxPower = modes.filter(s => s === 'mppt_active').length;
|
||||
return {
|
||||
mpptReadableCount: modes.length,
|
||||
mpptAtMaxPowerCount: atMaxPower,
|
||||
mpptHoldingBackCount: modes.length - atMaxPower,
|
||||
};
|
||||
})(),
|
||||
pvPowerAvg10mW: Math.max(0, num(input.pv_power_10m_avg_w, num(input.pv_power_w, 0))),
|
||||
pvTrendW: num(input.pv_trend_w, 0),
|
||||
|
||||
|
||||
@@ -903,6 +903,7 @@ export const scenarios = [
|
||||
soc_percent: '95', pv_power_w: '430', pv_power_10m_avg_w: '430',
|
||||
pv_trend_w: '0', battery_current_a: '2',
|
||||
house_ac_power_w: '400', vebus_ac_out_l1_power_w: '400',
|
||||
mppt_operation_modes: 'voltage_current_limited,voltage_current_limited,voltage_current_limited,voltage_current_limited,voltage_current_limited,voltage_current_limited,voltage_current_limited',
|
||||
},
|
||||
expect: {
|
||||
profile: 'outside_window',
|
||||
@@ -936,6 +937,7 @@ export const scenarios = [
|
||||
pv_power_w: '769.1', pv_power_10m_avg_w: '800', pv_trend_w: '0',
|
||||
house_ac_power_w: '755', vebus_ac_out_l1_power_w: '755',
|
||||
evcs_ac_power_w: '0',
|
||||
mppt_operation_modes: 'voltage_current_limited,voltage_current_limited,voltage_current_limited,voltage_current_limited,voltage_current_limited,voltage_current_limited,voltage_current_limited',
|
||||
},
|
||||
expect: {
|
||||
profile: 'evening_high_soc',
|
||||
@@ -991,6 +993,56 @@ export const scenarios = [
|
||||
},
|
||||
},
|
||||
|
||||
// ===== 03/09/2026 : la provocation doit etre justifiee par les chargeurs =====
|
||||
// Un excedent proche de zero est AMBIGU. Meme releve, meme excedent, deux
|
||||
// situations opposees a 51 min d'ecart le 03/09 :
|
||||
// 18:50 excedent +14 W, trackers LIMITES -> ressource refusee, on y va
|
||||
// 19:41 excedent +1 W, trackers MPPT_ACTIVE -> plus rien a donner, on reste
|
||||
// Aucun seuil de puissance ne les separe ; les chargeurs, eux, le disent.
|
||||
{
|
||||
id: 'provocation-trackers-a-fond-ne-demarre-pas',
|
||||
label: "Relevé du 03/09 19:41 : excédent +1 W (équilibre au crépuscule), SOC 94, mais les 7 trackers en mppt_active → plus rien à révéler → PAS de provocation",
|
||||
time: '2026-08-08T19:41:00+02:00',
|
||||
overrides: {
|
||||
evcs_vehicle_connected: 'on', evcs_device_connected: 'on',
|
||||
soc_percent: '94', battery_current_a: '-1.4',
|
||||
pv_power_w: '816.9', pv_power_10m_avg_w: '850', pv_trend_w: '0',
|
||||
house_ac_power_w: '816', vebus_ac_out_l1_power_w: '816',
|
||||
evcs_ac_power_w: '0',
|
||||
mppt_operation_modes: 'mppt_active,mppt_active,mppt_active,mppt_active,mppt_active,mppt_active,mppt_active',
|
||||
},
|
||||
expect: {
|
||||
profile: 'evening_high_soc',
|
||||
state: 'idle_wait_conditions',
|
||||
decision: 'hold',
|
||||
desiredCharging: false,
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
// Le repli doit etre sur le comportement ANTERIEUR, jamais sur une
|
||||
// supposition : integration muette => on ne sait pas => on autorise comme
|
||||
// avant. Meme releve que ci-dessus, seules les lectures changent.
|
||||
id: 'provocation-trackers-illisibles-comportement-anterieur',
|
||||
label: "Même relevé, mais chargeurs illisibles (unavailable) → on ne sait pas → repli sur le comportement antérieur, la charge démarre",
|
||||
time: '2026-08-08T19:41:00+02:00',
|
||||
overrides: {
|
||||
evcs_vehicle_connected: 'on', evcs_device_connected: 'on',
|
||||
soc_percent: '94', battery_current_a: '-1.4',
|
||||
pv_power_w: '816.9', pv_power_10m_avg_w: '850', pv_trend_w: '0',
|
||||
house_ac_power_w: '816', vebus_ac_out_l1_power_w: '816',
|
||||
evcs_ac_power_w: '0',
|
||||
mppt_operation_modes: 'unavailable,unknown,',
|
||||
},
|
||||
expect: {
|
||||
profile: 'evening_high_soc',
|
||||
state: 'start_min_current',
|
||||
decision: 'start',
|
||||
desiredCharging: true,
|
||||
currentA: 6,
|
||||
},
|
||||
},
|
||||
|
||||
// ===== 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
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user