docs: document internal data flow and EnergyManagerConfiguration in INTERFACE.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Patrick Schurig 2026-06-06 09:07:55 +02:00
parent 2931d295bc
commit b8e882616b

View File

@ -495,11 +495,129 @@ Le plugin détecte les appareils **par interface**, jamais par ThingClassId.
| Interface | États lus | Actions envoyées |
|---|---|---|
| `evcharger` | `chargingEnabled`, `maxChargingCurrent`, `pluggedIn`, `charging`, `currentPhaseA/B/C`, `currentPowerPhaseA/B/C` | `setChargingEnabled`, `setMaxChargingCurrent` |
| `electricvehicle` | `batteryLevel`, `maxChargingCurrent`, `capacity` | — |
| `electricvehicle` | `batteryLevel`, `maxChargingCurrent`, `capacity`, `minChargingCurrent`, `phaseCount` | — |
| `rootmeter` / `energymeter` | `currentPowerPhaseA/B/C`, `currentPhaseA/B/C` | — |
| `energystorage` | `currentPower`, `batteryLevel` | — |
**Déclencheur du cycle :** signal `PowerBalanceEntryAdded` de `nymea-experience-plugin-energy` (~1 min).
**Déclencheur overload :** signal `EnergyManager::powerBalanceChanged` (temps réel, découplé du cycle).
---
## EnergyManagerConfiguration
Paramètres de tuning chargés **une seule fois au démarrage** depuis un fichier JSON.
Pas de setters — un changement nécessite un redémarrage du daemon nymea.
**Chemin (ordre de priorité) :**
1. `$NYMEA_ENERGY_MANAGER_CONFIG` (variable d'environnement)
2. `/var/lib/nymea/energy-manager-configuration.json`
3. Valeurs par défaut si aucun fichier trouvé
**Format JSON :**
```json
{
"chargingEnabledLockDuration": 300,
"chargingCurrentLockDuration": 10,
"minimumScheduleDuration": 15,
"spotMarketChargePredictableEnergyPercentage": 0.5
}
```
| Paramètre | Défaut | Unité | Rôle |
|---|---|---|---|
| `chargingEnabledLockDuration` | 300 | secondes | Anti-flapping : durée de verrouillage après changement ON/OFF |
| `chargingCurrentLockDuration` | 10 | secondes | Anti-flapping : durée de verrouillage après changement de courant |
| `minimumScheduleDuration` | 15 | minutes | Durée minimale d'un créneau spot market planifié |
| `spotMarketChargePredictableEnergyPercentage` | 0.5 | ratio [01] | Fraction de l'énergie spot considérée "prédictible" dans le planning |
---
## Flux interne — `SmartChargingManager`
### Entrées de données
```
EnergyManager::logs()::powerBalanceEntryAdded (SampleRate1Min)
└─→ update(now) ← cycle principal ~1/min
EnergyManager::powerBalanceChanged ← temps réel
└─→ verifyOverloadProtection(now) ← safety loop immédiate, découplée du cycle
RootMeter (wraps Thing interface=rootmeter/energymeter)
├── currentPower() ← total W (négatif = surplus / export)
├── currentPowerPhaseA/B/C() ← W par phase
└── currentPhaseA/B/C() ← A par phase
ThingManager → interface "energystorage"
├── batteryLevel ← % (moyenne de tous les stockages)
└── currentPower ← W total (+ = charge, = décharge)
EvCharger → interface "evcharger" + "electricvehicle"
├── currentPower(), maxChargingCurrent(), phaseCount()
├── meteredPhases() ← phases réelles via currentPhaseA/B/C live
└── car: batteryLevel, capacity, minChargingCurrent, phaseCount
```
### Pipeline `update()` — exécuté à chaque cycle
```
update(currentDateTime)
├─ 1. updateManualSoCsWithoutMeter()
│ Estime le SoC à partir de l'énergie intégrée si pas de compteur sur le VE.
├─ 2. prepareInformation()
│ - Filtre les EV chargers actifs (plugged, car assignée, mode ≠ Normal)
│ - Calcule par charger : phases effectives, SoC, temps restant, phaseLimitPower
│ - Reset m_chargingActions à OFF/minCurrent pour les 3 issuers
├─ 3. verifyOverloadProtection()
│ - Lit rootMeter->currentPowerPhaseA/B/C()
│ - Si une phase dépasse phasePowerLimit × 230 W → throttle immédiat (issuer=OverloadProtection)
├─ 4. verifyOverloadProtectionRecovery()
│ - Ré-active les chargers throttlés si la marge est suffisante
├─ 5. planSpotMarketCharging()
│ - SpotMarketManager::scheduleChargingTime() → TimeFrames (créneaux bon marché)
│ - Remplit m_chargingSchedules + m_chargingActions[SpotMarket] = ON
├─ 6. planSurplusCharging()
│ - currentLoad = rootMeter->currentPower()
│ + correction batteries (fromBatteries)
│ + puissance ajoutée dans ce cycle (addedPower)
│ - allowanceInAmpere = currentLoad / 230
│ - Si allowance ≥ minCurrent × acquisitionTolerance → chargingActions[Surplus] = ON
└─ 7. adjustEvChargers()
Applique la décision finale par priorité décroissante :
1. TimeRequirement → ON au max courant disponible (deadline imminente)
2. SurplusCharging → ON au courant surplus calculé
3. SpotMarketCharging → ON au courant max dans le créneau
4. EcoWithMinCurrent fallback → ON à 6 A (EcoMinChargingCurrent)
5. Idle → OFF
├── executeChargingAction()
│ └─→ Thing::executeAction(setChargingEnabled, setMaxChargingCurrent)
├── emit chargingInfoChanged() ← met à jour ChargingState (lu par JSON-RPC)
└── emit chargingSchedulesChanged() ← planning rafraîchi (lu par JSON-RPC)
```
### Persistance des settings utilisateur
| Données | Fichier |
|---|---|
| `phasePowerLimit`, `acquisitionTolerance`, `batteryLevelConsideration` | `EnergySettings` (QSettings INI, `energy.conf`) |
| `ChargingInfo` par charger (mode, endDateTime, repeatDays, etc.) | même `EnergySettings`, groupe `ChargingInfos/` |
| `lockOnUnplug` | `/var/lib/nymea/energy.conf` (QSettings séparé) |
| SpotMarket `enabled`, `providerId` | `EnergySettings` (géré par `SpotMarketManager`) |
### Point d'injection pour `powersync-optimizer`
Le point naturel est **entre `prepareInformation()` et `adjustEvChargers()`**.
L'optimizer reçoit les données de contexte (`SurplusData`) et retourne une `ChargingAction` qui remplace ou complète les actions calculées localement. Voir `PowerSyncClient::requestOptimization()` dans `etm/`.
---
@ -510,4 +628,5 @@ Le plugin détecte les appareils **par interface**, jamais par ThingClassId.
- `phasePowerLimit` est en **Ampères** (par phase), pas en Watts.
- `weighting` des `ScoreEntry` : 1.0 = créneau le moins cher, 0.0 = le plus cher.
- `SetChargingInfo` est partiel : seuls les champs présents sont appliqués, sauf `evChargerId` qui est toujours requis.
- `currentPower` du root meter est **négatif** quand il y a surplus solaire (export réseau).
- Le plugin fonctionne sans `powersync-optimizer` (mode Community, dégradé proprement).