Compare commits
4 Commits
349a77af26
...
a5b88b14b8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a5b88b14b8 | ||
|
|
394cf56e58 | ||
|
|
af87a76a65 | ||
|
|
0d91819577 |
@@ -594,7 +594,7 @@ const probeModeActive = context.sourceMode === 'hybrid_probe';
|
||||
const probeStartOk =
|
||||
probeModeActive &&
|
||||
commonSafetyStartOk &&
|
||||
(context.morningProbeEligible || context.middayProbeEligible);
|
||||
(context.morningProbeEligible || context.middayProbeEligible || context.afternoonProbeEligible);
|
||||
|
||||
const probeHoldGuardOk =
|
||||
probeModeActive &&
|
||||
|
||||
197
HANDOFF.md
Normal file
197
HANDOFF.md
Normal file
@@ -0,0 +1,197 @@
|
||||
# HANDOFF.md — EVCS Outside-Window Solar Charging Block
|
||||
|
||||
## 1. Contexte Projet
|
||||
|
||||
- **Installation** : Victron ESS sans injection réseau. Batterie 17,5 kWh.
|
||||
- **Workflow actif** : `HA Snapshot EVCS Surplus Controller V2.6` (n8n)
|
||||
- **Code source** : `/mnt/projects/EVCS/EVCS/decoupesurplus/` (architecture modulaire V2)
|
||||
- **Fichiers critiques** :
|
||||
- `WINDOWS/WINDOW_SOURCEV2.js` — Détermine `profile` et `sourceMode`
|
||||
- `BATTERY/BATTERY_CLOUD_LOCKS.js` — Calcule `cloudDebt`, `cloudToleranceOk`
|
||||
- `snapshot.js` — Récupération des helpers HA
|
||||
- `modified/EVCS_V2.5_PATCHED.json` — Export n8n de référence (avant upgrade V2.6)
|
||||
|
||||
## 2. Architecture Data-Flow
|
||||
|
||||
```
|
||||
Schedule Trigger (30s)
|
||||
→ HA Snapshot (template)
|
||||
→ INPUT_PARSE
|
||||
→ WINDOWS_SOURCEV2 ← C'EST ICI QUE LE BLOCAGE SE PRODUIT
|
||||
→ BATTERY_CLOUD_LOCKS
|
||||
→ DECISION_ENGINEV2
|
||||
→ HA_SERVICE_CALLS_OUTPUT
|
||||
→ Execute HA Service
|
||||
```
|
||||
|
||||
## 3. Problème Observé
|
||||
|
||||
### Symptôme
|
||||
L'après-midi, avec un fort ensoleillement et un SOC élevé (> 85 %), l'EVCS reste en :
|
||||
- **Profil** : `outside_window`
|
||||
- **Décision** : `stop_hard` ou `hold` / `blocked`
|
||||
- **Source** : `Bloqué`
|
||||
- **Action utilisateur requise** : Forcer manuellement `force_charge_now`
|
||||
|
||||
### Capture d'état typique
|
||||
```
|
||||
Stratégie : auto_solar_plus
|
||||
Profil : outside_window
|
||||
Source : Bloqué
|
||||
SOC : 91 %
|
||||
Cloud Debt: 1.66 kWh
|
||||
PV Power : ~800 W (aligné sur charge maison, pas d'injection)
|
||||
Decision : stop_hard | battery_current_hard_stop
|
||||
```
|
||||
|
||||
**Note contextuelle importante** : L'utilisateur a attendu ~45 min sans succès, puis a volontairement passé en `force_charge_now` pour charger sa moto électrique. Ce forçage a fait tirer sur les batteries à cause des nuages, augmentant encore le `cloud_debt`.
|
||||
|
||||
## 4. Root Cause Analysis
|
||||
|
||||
### 4.1. Définition de outside_window
|
||||
`outside_window` = tout horaire **hors** des 4 fenêtres configurées :
|
||||
- `night_hc` : 23:00 → 06:00
|
||||
- `morning_solar` : 07:30 → 11:00
|
||||
- `midday_hc_or_solar` : 12:24 → 14:24
|
||||
- `evening_high_soc` : 17:00 → 20:30
|
||||
|
||||
Donc `outside_window` couvre : **11:00→12:24, 14:24→17:00, 20:30→23:00**.
|
||||
|
||||
### 4.2. Logique du sourceMode en outside_window (WINDOW_SOURCEV2.js, L454-468)
|
||||
|
||||
```javascript
|
||||
else if (profile === 'outside_window') {
|
||||
if (strongSolarSignal) {
|
||||
sourceMode = 'solar_only';
|
||||
} else if (isHighBatteryForPotential && potentialAllowed) {
|
||||
sourceMode = 'solar_potential';
|
||||
} else {
|
||||
sourceMode = 'blocked'; // ← TOMBE ICI
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Pour sortir de `blocked`, il faut **obligatoirement** l'un des deux :
|
||||
|
||||
#### A. strongSolarSignal (L343-352)
|
||||
```javascript
|
||||
const strongSolarSignal =
|
||||
(parsed.pvPowerW > 0 && parsed.pvPowerW >= effectivePvStartMinW) ||
|
||||
parsed.pvPowerAvg10mW >= params.pvHoldMinW ||
|
||||
(departurePriorityActive && parsed.pvPowerAvg10mW >= params.pvHoldMinW);
|
||||
```
|
||||
- `pvStartMinW` défaut = **1800 W**
|
||||
- `pvHoldMinW` défaut = **1200 W**
|
||||
|
||||
**Pourquoi c'est faux chez l'utilisateur** :
|
||||
Comme il n'injecte pas, quand SOC > 85 % et charge maison = 800 W, le MPPT régule les PV à ~800 W. Ni `pvPowerW`, ni `pvPowerAvg10mW` ne dépassent les seuils.
|
||||
|
||||
#### B. isHighBatteryForPotential (L335-339)
|
||||
```javascript
|
||||
const isHighBatteryForPotential =
|
||||
parsed.soc >= 85 &&
|
||||
forecastBufferKwh > 0 &&
|
||||
cloudDebtOkForPotential && // ← BLOQUÉ ICI
|
||||
batteryOkForPotential;
|
||||
```
|
||||
|
||||
**Pourquoi c'est faux chez l'utilisateur** :
|
||||
```javascript
|
||||
const cloudDebtOkForPotential = params.cloudDebtPrevKwh <= params.cloudDebtMaxKwh;
|
||||
// cloudDebtPrevKwh = 1.66 kWh
|
||||
// cloudDebtMaxKwh = 1.2 kWh (défaut)
|
||||
// → cloudDebtOkForPotential = FALSE
|
||||
```
|
||||
|
||||
**Donc `isHighBatteryForPotential` est FORCÉMENT FALSE**, même avec SOC 91 %.
|
||||
|
||||
### 4.3. Pourquoi le cloud debt est à 1.66 kWh
|
||||
Le `cloud_debt` s'accumule quand :
|
||||
- Session EVCS active
|
||||
- `sourceMode` n'est PAS `grid_hc_allowed` (donc solaire ou batterie)
|
||||
- `batteryCurrentA < -neutralBand` (batterie qui décharge)
|
||||
|
||||
Sur une journée nuageuse avec intermittences, l'EVCS a démarré/stoppé plusieurs fois. Pendant les passages nuageux, la batterie a complété la puissance manquante → accumulation de dette. Cette dette ne se rembourse que quand `batteryCurrentA > 0` (batterie en charge). Or, si le système bloque l'EVCS, il n'y a pas de passage à `batteryCurrentA > 0` pendant la charge EVCS, donc la dette reste bloquée haute.
|
||||
|
||||
### 4.4. Absence de probe en outside_window
|
||||
Les probes (`morningProbeEligible`, `middayProbeEligible`) sont **strictement limitées** aux profiles `morning_solar` et `midday_hc_or_solar` (WINDOW_SOURCEV2.js L317-330). Il n'existe **aucun mécanisme de sonde** en `outside_window` pour tester si le PV peut supporter une charge à 6 A.
|
||||
|
||||
### 4.5. Réseau HC midi / nuit
|
||||
Les toggles `allow_grid_hc_night` et `allow_grid_hc_midday` ne concernent QUE les fenêtres `night_hc` et `midday_hc_or_solar`. Ils sont **totalement ignorés** en `outside_window`. Ce n'est donc PAS une solution.
|
||||
|
||||
### 4.6. Priorité départ
|
||||
`departurePriorityActive` s'active seulement si :
|
||||
- `departurePriorityEnabled = ON`
|
||||
- `minutesUntilDeparture <= 240 min`
|
||||
- Véhicule branché
|
||||
|
||||
Effet : facilite `strongSolarSignal` en baissant la barre si `pvPowerAvg10mW >= pvHoldMinW`. Mais ne résout pas le blocage `cloudDebt`.
|
||||
|
||||
## 5. Résumé des Verrous
|
||||
|
||||
| Verrou | Condition | État actuel | Impact |
|
||||
|--------|-----------|-------------|--------|
|
||||
| Seuil PV instantané | `pvPowerW >= 1800 W` | ~800 W | `strongSolarSignal = false` |
|
||||
| Seuil PV moyenne | `pvPowerAvg10mW >= 1200 W` | Souvent < 1200 W | `strongSolarSignal = false` |
|
||||
| Cloud Debt | `cloudDebt <= 1.2 kWh` | 1.66 kWh | `isHighBatteryForPotential = false` |
|
||||
| Probe absente | Seulement `morning` / `midday` | N/A en afternoon | Aucun test de démarrage |
|
||||
| Réseau HC | Ignoré hors fenêtre | N/A | Pas de fallback grid |
|
||||
|
||||
## 6. Options de Correction Envisagées
|
||||
|
||||
### Option A — Relâcher `cloudDebtOkForPotential` en `outside_window`
|
||||
Dans `WINDOW_SOURCEV2.js`, ignorer la condition `cloudDebtOkForPotential` pour le calcul de `isHighBatteryForPotential` quand `profile === 'outside_window'`.
|
||||
|
||||
**Avantage** : Simple, 1 ligne.
|
||||
**Risque** : Peut entraîner une décharge excessive de la batterie si le PV baisse brutalement.
|
||||
|
||||
### Option B — Ajouter une `afternoon_probe` en `outside_window` (RECOMMANDÉ)
|
||||
Créer une logique de sonde (probe) active en `outside_window` lorsque `auto_solar_plus` est sélectionné. Démarrage à 6 A pendant 3 min, puis évaluation.
|
||||
|
||||
**Avantage** : Fidèle à la philosophie du système (ne charge que si le PV confirme).
|
||||
**Risque** : Nécessite modification de `BATTERY_CLOUD_LOCKS.js` + `DECISION_ENGINEV2.js`.
|
||||
|
||||
### Option C — Ajouter une fenêtre `afternoon_solar`
|
||||
Étendre la logique des fenêtres pour inclure une plage 14:24→17:00 (ou configurable) avec des seuils PV adaptés.
|
||||
|
||||
**Avantage** : Résout définitivement le "trou" de l'après-midi.
|
||||
**Risque** : Change la sémantique utilisateur des fenêtres.
|
||||
|
||||
### Option D — Abaisser `pvStartMinW` / `pvHoldMinW` dynamiquement
|
||||
En `outside_window` + `auto_solar_plus`, baisser les seuils si `soc >= 90` et `forecastBufferKwh > 0`.
|
||||
|
||||
**Avantage** : Adaptatif.
|
||||
**Risque** : Peut causer des démarrages/stops fréquents si le PV est instable.
|
||||
|
||||
## 7. Prochaines Étapes Recommandées
|
||||
|
||||
1. **Décider** quelle Option (A, B, C ou D) correspond au comportement souhaité.
|
||||
2. **Vérifier** la valeur exacte de `cloud_debt_max_kwh` dans HA (`input_number.victron_cloud_debt_max_kwh` ou équivalent).
|
||||
3. **Implémenter** le patch dans le fichier cible :
|
||||
- Si Option A : `WINDOWS/WINDOW_SOURCEV2.js`
|
||||
- Si Option B : `WINDOWS/WINDOW_SOURCEV2.js` + `BATTERY_CLOUD_LOCKS.js` + `DECISION_ENGINEV2.js`
|
||||
- Si Option C : `WINDOWS/WINDOW_SOURCEV2.js` + `INPUT_PARSE.js`
|
||||
- Si Option D : `WINDOWS/WINDOW_SOURCEV2.js`
|
||||
4. **Tester** en mode "dry-run" (logique uniquement, pas d'appels HA).
|
||||
5. **Redéployer** via export/import du workflow n8n.
|
||||
|
||||
## 8. Paramètres Clés à Vérifier dans Home Assistant
|
||||
|
||||
| Paramètre HA | Entité typique | Valeur défaut | Valeur actuelle ? |
|
||||
|--------------|----------------|---------------|-------------------|
|
||||
| `pv_start_min_w` | `input_number.victron_pv_start_min_w` | 1800 W | ? |
|
||||
| `pv_hold_min_w` | `input_number.victron_pv_hold_min_w` | 1200 W | ? |
|
||||
| `cloud_debt_max_kwh` | `input_number.victron_cloud_debt_max_kwh` | 1.2 kWh | ? |
|
||||
| `battery_neutral_band_a` | `input_number.victron_battery_neutral_band_a` | 3 A | ? |
|
||||
| `allow_grid_hc_midday` | `input_boolean.victron_allow_grid_hc_midday` | OFF | ON (actif) |
|
||||
| `allow_grid_hc_night` | `input_boolean.victron_allow_grid_hc_night` | OFF | ON (actif) |
|
||||
|
||||
## 9. Notes de Version
|
||||
|
||||
- Workflow mis à jour de V2.5 → **V2.6** par l'utilisateur via l'interface n8n.
|
||||
- La feature afternoon_probe y est désormais active.
|
||||
- Le fichier `modified/EVCS_V2.5_PATCHED.json` reste l'export de référence pour rollback.
|
||||
|
||||
---
|
||||
*Document créé le : 12/06/2026 | Mis à jour : 12/06/2026*
|
||||
*Prochaine action : Observer le comportement en afternoon et ajuster les seuils des helpers si nécessaire*
|
||||
@@ -329,6 +329,13 @@ const middayProbeEligible =
|
||||
parsed.soc >= params.middaySocMin &&
|
||||
parsed.batteryCurrentA >= params.middayBatteryMaxDischargeA;
|
||||
|
||||
const afternoonProbeEligible =
|
||||
profile === 'outside_window' &&
|
||||
params.probeEnabled &&
|
||||
parsed.soc >= params.afternoonProbeSocMin &&
|
||||
parsed.batteryCurrentA >= params.afternoonBatteryMaxDischargeA &&
|
||||
parsed.pvPowerAvg10mW >= params.afternoonProbePvAvgMinW;
|
||||
|
||||
const cloudDebtOkForPotential = params.cloudDebtPrevKwh <= params.cloudDebtMaxKwh;
|
||||
const batteryOkForPotential = parsed.batteryCurrentA >= -Math.max(1, params.batteryNeutralBandA);
|
||||
|
||||
@@ -460,6 +467,10 @@ else if (profile === 'outside_window') {
|
||||
sourceMode = 'solar_potential';
|
||||
}
|
||||
|
||||
else if (afternoonProbeEligible) {
|
||||
sourceMode = 'hybrid_probe';
|
||||
}
|
||||
|
||||
else {
|
||||
// IMPORTANT:
|
||||
// hors fenêtre = pas de réseau programmé.
|
||||
@@ -537,6 +548,7 @@ return [{
|
||||
manualChargeLocalRequested,
|
||||
morningProbeEligible,
|
||||
middayProbeEligible,
|
||||
afternoonProbeEligible,
|
||||
isHighBatteryForPotential,
|
||||
strongSolarSignal,
|
||||
effectivePvStartMinW,
|
||||
|
||||
@@ -10,7 +10,7 @@ const nextAllowedWindowStartMs =
|
||||
const blockActive = blockedUntilMs !== null && blockedUntilMs > nowMs + 1000;
|
||||
const blockStillRelevant =
|
||||
lastBlockReason === 'outside_window' ? profile === 'outside_window' :
|
||||
lastBlockReason === 'probe_failed' ? (profile === 'morning_solar' || profile === 'midday_hc_or_solar') :
|
||||
lastBlockReason === 'probe_failed' ? (profile === 'morning_solar' || profile === 'midday_hc_or_solar' || profile === 'outside_window') :
|
||||
lastBlockReason === 'battery_protection_stop' ? (
|
||||
batteryCurrentA < -batteryNeutralBandA || anyVebusLowBattery || vebusDcVoltageSoftLow
|
||||
) :
|
||||
@@ -133,7 +133,7 @@ const gridHoldOk =
|
||||
const probeStartOk =
|
||||
sourceMode === 'hybrid_probe' &&
|
||||
commonSafetyStartOk &&
|
||||
(morningProbeEligible || middayProbeEligible);
|
||||
(morningProbeEligible || middayProbeEligible || afternoonProbeEligible);
|
||||
const probeHoldGuardOk =
|
||||
commonSafetyHoldOk &&
|
||||
batteryCurrentA >= profileBatteryDischargeFloorA;
|
||||
|
||||
@@ -346,6 +346,10 @@ const params = {
|
||||
morningProbePvTrendMinW: num(input.morning_probe_pv_trend_min_w, 0),
|
||||
middayProbePvAvgMinW: Math.max(0, num(input.midday_probe_pv_avg_min_w, 1200)),
|
||||
|
||||
afternoonProbePvAvgMinW: Math.max(0, num(input.afternoon_probe_pv_avg_min_w, 1000)),
|
||||
afternoonProbeSocMin: clamp(num(input.afternoon_probe_soc_min, 85), 0, 100),
|
||||
afternoonBatteryMaxDischargeA: num(input.afternoon_battery_max_discharge_a, -20),
|
||||
|
||||
morningSocMin: clamp(num(input.morning_soc_min, 35), 0, 100),
|
||||
middaySocMin: clamp(num(input.midday_soc_min, 75), 0, 100),
|
||||
eveningSocStartMin: clamp(num(input.evening_soc_start_min, 85), 0, 100),
|
||||
|
||||
@@ -85,6 +85,10 @@ const params = {
|
||||
morningProbePvTrendMinW: num(input.morning_probe_pv_trend_min_w, 0),
|
||||
middayProbePvAvgMinW: Math.max(0, num(input.midday_probe_pv_avg_min_w, 1200)),
|
||||
|
||||
afternoonProbePvAvgMinW: Math.max(0, num(input.afternoon_probe_pv_avg_min_w, 1000)),
|
||||
afternoonProbeSocMin: clamp(num(input.afternoon_probe_soc_min, 85), 0, 100),
|
||||
afternoonBatteryMaxDischargeA: num(input.afternoon_battery_max_discharge_a, -20),
|
||||
|
||||
morningSocMin: clamp(num(input.morning_soc_min, 35), 0, 100),
|
||||
middaySocMin: clamp(num(input.midday_soc_min, 75), 0, 100),
|
||||
eveningSocStartMin: clamp(num(input.evening_soc_start_min, 85), 0, 100),
|
||||
|
||||
1
jeton_EVCS.txt
Normal file
1
jeton_EVCS.txt
Normal file
@@ -0,0 +1 @@
|
||||
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI2YmVlYTUyMDcyNjI0NmU4YTNmYzM1NWFiYTQ1MTRlNSIsImlhdCI6MTc4MTIyODI4OCwiZXhwIjoyMDk2NTg4Mjg4fQ.Kgb8EOUGoqfXTLvJhvfZ030bmznjyvgrg7vtQ_OX57A
|
||||
@@ -120,6 +120,10 @@
|
||||
"morning_probe_pv_trend_min_w": states("input_number.victron_evcs_morning_probe_pv_trend_min_w"),
|
||||
"midday_probe_pv_avg_min_w": states("input_number.victron_evcs_midday_probe_pv_avg_min_w"),
|
||||
|
||||
"afternoon_probe_pv_avg_min_w": states("input_number.victron_evcs_afternoon_probe_pv_avg_min_w"),
|
||||
"afternoon_probe_soc_min": states("input_number.victron_evcs_afternoon_probe_soc_min"),
|
||||
"afternoon_battery_max_discharge_a": states("input_number.victron_evcs_afternoon_battery_max_discharge_a"),
|
||||
|
||||
"morning_soc_min": states("input_number.victron_evcs_morning_soc_min"),
|
||||
"midday_soc_min": states("input_number.victron_evcs_midday_soc_min"),
|
||||
"evening_soc_start_min": states("input_number.victron_evcs_evening_soc_start_min"),
|
||||
|
||||
Reference in New Issue
Block a user