# INTERFACE.md — API JSON-RPC du plugin `nymea-energy-plugin-nymea` > Ce fichier fait autorité sur l'interface exposée par le plugin. > Mettre à jour ce fichier à chaque modification de l'API. > > **Namespace :** `NymeaEnergy` > **Versions enregistrées :** 0–8 (`registerExperienceHandler(..., 0, 8)`) > **Transport :** WebSocket JSON-RPC 2.0, port 4444 (nymea standard) --- ## Enums ### `ChargingMode` | Valeur | Description | |---|---| | `ChargingModeNormal` | Recharge immédiate au maximum disponible | | `ChargingModeEco` | Recharge sur surplus solaire uniquement | | `ChargingModeEcoWithTargetTime` | Eco + deadline de fin (`endDateTime` + `targetPercentage`) | | `ChargingModeEcoWithMinCurrent` | Eco + courant minimum garanti (6 A) si pas de surplus | | `ChargingModeEcoMinWithTargetTime` | Eco + courant minimum + deadline | ### `ChargingState` (lecture seule — calculé par le manager) | Valeur | Description | |---|---| | `ChargingStateIdle` | Pas de recharge active | | `ChargingStateSurplusCharging` | Recharge sur surplus solaire en cours | | `ChargingStateSpotMarketCharging` | Recharge sur créneau spot market en cours | | `ChargingStateTimeRequirement` | Recharge forcée pour atteindre la deadline | ### `ChargingActionIssuer` | Valeur | Description | |---|---| | `ChargingActionIssuerUnknown` | Émetteur non identifié | | `ChargingActionIssuerSurplusCharging` | Action émise par le surplus manager | | `ChargingActionIssuerSpotMarketCharging` | Action émise par le spot market manager | | `ChargingActionIssuerTimeRequirement` | Action émise pour honorer la deadline | | `ChargingActionIssuerOverloadProtection` | Action émise par la protection surcharge | ### `EnergyError` | Valeur | Description | |---|---| | `EnergyErrorNoError` | Succès | | `EnergyErrorMissingParameter` | Paramètre obligatoire absent | | `EnergyErrorInvalidParameter` | Valeur de paramètre invalide | --- ## Types ### `ChargingInfo` Configuration d'une borne EV. Seuls les champs marqués `(writable)` peuvent être modifiés via `SetChargingInfo` — les autres sont ignorés ou read-only. | Champ | Type | Writable | Description | |---|---|---|---| | `evChargerId` | `Uuid` | — | Identifiant de la borne (obligatoire dans SetChargingInfo) | | `assignedCarId` | `Uuid` | ✓ | Véhicule associé (optionnel) | | `chargingMode` | `ChargingMode` | ✓ | Mode de recharge | | `endDateTime` | `DateTime` | ✓ | Deadline cible (EcoWithTargetTime / EcoMinWithTargetTime) | | `repeatDays` | `[Int]` | ✓ | Jours de répétition (0=Lun … 6=Dim, liste vide = pas de répétition) | | `targetPercentage` | `Uint` | ✓ | Niveau batterie cible en % (défaut : 80) | | `chargingState` | `ChargingState` | — | État calculé (lecture seule) | | `spotMarketChargingEnabled` | `Bool` | ✓ | Activer la recharge sur spot market | | `dailySpotMarketPercentage` | `Uint` | ✓ | % de la charge quotidienne à faire en heures spot bon marché | ### `ChargingAction` Action envoyée au charger physique (présente dans les schedules). | Champ | Type | Description | |---|---|---| | `chargingEnabled` | `Bool` | Activer/désactiver la recharge | | `maxChargingCurrent` | `Double` | Courant max en Ampères | | `desiredPhaseCount` | `Uint` | Nombre de phases souhaitées (1 ou 3) | | `issuer` | `ChargingActionIssuer` | Origine de la décision | ### `ChargingSchedule` Créneau de recharge planifié (résultat du calcul du manager). | Champ | Type | Description | |---|---|---| | `evChargerId` | `Uuid` | Borne concernée | | `startDateTime` | `DateTime` | Début du créneau | | `endDateTime` | `DateTime` | Fin du créneau | | `action` | `ChargingAction` | Action prévue sur ce créneau | ### `ScoreEntry` Entrée de cotation horaire du spot market, avec pondération relative. | Champ | Type | Description | |---|---|---| | `startDateTime` | `DateTime` | Début du créneau tarifaire | | `endDateTime` | `DateTime` | Fin du créneau tarifaire | | `value` | `Float` | Prix brut (€/MWh, selon provider) | | `weighting` | `Float` | Pondération relative : 0.0 = le plus cher, 1.0 = le moins cher | ### `SpotMarketProviderInfo` Fournisseur de données spot market disponible. | Champ | Type | Description | |---|---|---| | `providerId` | `Uuid` | Identifiant du provider | | `name` | `String` | Nom affiché | | `country` | `Uint` | Pays (`QLocale::Country`) | | `website` | `String` | URL du site du provider | **Providers enregistrés :** | Nom | `providerId` | |---|---| | aWATTar AT | `5196b3cc-b2ee-46d6-b63a-7af2cf70ba67` | | aWATTar DE | `0ca6ad88-e243-438d-a0f8-986cecf61834` | --- ## Méthodes > Notation : `o:` = paramètre optionnel. Tous les appels requièrent un token d'auth nymea. --- ### Overload protection #### `NymeaEnergy.GetPhasePowerLimit` Retourne la limite de courant par phase configurée. **Params :** aucun **Returns :** ```json { "phasePowerLimit": 25 } ``` > `0` signifie non configuré — tout le smart charging est désactivé. --- #### `NymeaEnergy.SetPhasePowerLimit` Définit la limite de courant par phase (en Ampères). `0` désactive le smart charging. **Params :** ```json { "phasePowerLimit": 25 } ``` **Returns :** ```json { "energyError": "EnergyErrorNoError" } ``` --- ### Paramètres de recharge #### `NymeaEnergy.GetAcquisitionTolerance` Retourne le seuil de surplus nécessaire pour démarrer une recharge Eco. **Params :** aucun **Returns :** ```json { "acquisitionTolerance": 0.5 } ``` > Valeur entre 0.0 et 1.0. Plus la valeur est haute, plus il faut de surplus. --- #### `NymeaEnergy.SetAcquisitionTolerance` **Params :** ```json { "acquisitionTolerance": 0.5 } ``` **Returns :** ```json { "energyError": "EnergyErrorNoError" } ``` > Retourne `EnergyErrorInvalidParameter` si hors de `[0.0, 1.0]`. --- #### `NymeaEnergy.GetBatteryLevelConsideration` Retourne le facteur de prise en compte du stockage batterie dans le calcul du surplus. **Params :** aucun **Returns :** ```json { "batteryLevelConsideration": 0.9 } ``` > 0.0 = stockage ignoré, 1.0 = surplus réduit intégralement de la puissance de charge batterie. --- #### `NymeaEnergy.SetBatteryLevelConsideration` **Params :** ```json { "batteryLevelConsideration": 0.9 } ``` **Returns :** ```json { "energyError": "EnergyErrorNoError" } ``` > Retourne `EnergyErrorInvalidParameter` si hors de `[0.0, 1.0]`. --- #### `NymeaEnergy.GetLockOnUnplug` Retourne si la borne doit être verrouillée à la déconnexion du véhicule. **Params :** aucun **Returns :** ```json { "lockOnUnplug": false } ``` --- #### `NymeaEnergy.SetLockOnUnplug` **Params :** ```json { "lockOnUnplug": true } ``` **Returns :** ```json { "energyError": "EnergyErrorNoError" } ``` --- ### Configuration EV #### `NymeaEnergy.GetChargingInfos` Retourne la configuration de recharge de toutes les bornes, ou d'une seule. **Params :** ```json { "o:evChargerId": "uuid-de-la-borne" } ``` **Returns :** ```json { "chargingInfos": [ { "evChargerId": "uuid-de-la-borne", "assignedCarId": "uuid-du-car", "chargingMode": "ChargingModeEco", "chargingState": "ChargingStateSurplusCharging", "endDateTime": "2025-06-15T08:00:00+02:00", "repeatDays": [1, 2, 3, 4, 5], "targetPercentage": 80, "spotMarketChargingEnabled": false, "dailySpotMarketPercentage": 0 } ] } ``` --- #### `NymeaEnergy.SetChargingInfo` Met à jour la configuration d'une borne. Seuls les champs `USER true` sont modifiables (voir type `ChargingInfo`). Les autres champs sont ignorés. **Params :** ```json { "chargingInfo": { "evChargerId": "uuid-de-la-borne", "chargingMode": "ChargingModeEcoWithTargetTime", "endDateTime": "2025-06-15T08:00:00+02:00", "targetPercentage": 80 } } ``` **Returns :** ```json { "energyError": "EnergyErrorNoError" } ``` --- #### `NymeaEnergy.GetChargingSchedules` Retourne le planning de recharge calculé (toutes les bornes, horizon ~24h). **Params :** aucun **Returns :** ```json { "chargingSchedules": [ { "evChargerId": "uuid-de-la-borne", "startDateTime": "2025-06-15T02:00:00+02:00", "endDateTime": "2025-06-15T04:30:00+02:00", "action": { "chargingEnabled": true, "maxChargingCurrent": 16.0, "desiredPhaseCount": 1, "issuer": "ChargingActionIssuerSpotMarketCharging" } } ] } ``` --- ### Spot market #### `NymeaEnergy.GetAvailableSpotMarketProviders` Retourne la liste des providers spot market disponibles. **Params :** aucun **Returns :** ```json { "providers": [ { "providerId": "5196b3cc-b2ee-46d6-b63a-7af2cf70ba67", "name": "aWATTar AT", "country": 14, "website": "https://www.awattar.at" }, { "providerId": "0ca6ad88-e243-438d-a0f8-986cecf61834", "name": "aWATTar DE", "country": 82, "website": "https://www.awattar.de" } ] } ``` --- #### `NymeaEnergy.GetSpotMarketConfiguration` Retourne la configuration spot market courante. **Params :** aucun **Returns :** ```json { "enabled": true, "available": true, "o:providerId": "5196b3cc-b2ee-46d6-b63a-7af2cf70ba67" } ``` > `providerId` est absent si `enabled` est `false`. --- #### `NymeaEnergy.SetSpotMarketConfiguration` Active ou désactive le spot market et sélectionne le provider. **Params :** ```json { "enabled": true, "o:providerId": "5196b3cc-b2ee-46d6-b63a-7af2cf70ba67" } ``` > Si `enabled: true`, `providerId` est obligatoire et doit être un UUID valide. **Returns :** ```json { "energyError": "EnergyErrorNoError" } ``` > Retourne `EnergyErrorInvalidParameter` si `enabled: true` sans `providerId`, ou `providerId` inconnu. --- #### `NymeaEnergy.GetSpotMarketScoreEntries` Retourne les cotations pondérées du provider actif. Liste vide si désactivé ou non disponible. **Params :** aucun **Returns :** ```json { "spotMarketScoreEntries": [ { "startDateTime": "2025-06-15T00:00:00+02:00", "endDateTime": "2025-06-15T01:00:00+02:00", "value": 45.2, "weighting": 0.85 } ] } ``` --- ## Notifications S'abonner via `JSONRPC.SetNotificationStatus` avec le namespace `"NymeaEnergy"`. --- ### `NymeaEnergy.PhasePowerLimitChanged` ```json { "phasePowerLimit": 25 } ``` --- ### `NymeaEnergy.AcquisitionToleranceChanged` ```json { "acquisitionTolerance": 0.5 } ``` --- ### `NymeaEnergy.BatteryLevelConsiderationChanged` ```json { "batteryLevelConsideration": 0.9 } ``` --- ### `NymeaEnergy.LockOnUnplugChanged` ```json { "lockOnUnplug": true } ``` --- ### `NymeaEnergy.ChargingInfoAdded` Émis quand une nouvelle borne est détectée et sa `ChargingInfo` créée. ```json { "chargingInfo": { ... } } ``` --- ### `NymeaEnergy.ChargingInfoRemoved` Émis quand une borne est supprimée. ```json { "evChargerThingId": "uuid-de-la-borne" } ``` --- ### `NymeaEnergy.ChargingInfoChanged` Émis à chaque modification de la `ChargingInfo` (config ou état). ```json { "chargingInfo": { ... } } ``` --- ### `NymeaEnergy.ChargingSchedulesChanged` Émis à chaque recalcul du planning (cycle ~1 min). ```json { "chargingSchedules": [ ... ] } ``` --- ### `NymeaEnergy.SpotMarketConfigurationChanged` Émis quand le provider actif, l'état enabled ou l'état available change. ```json { "enabled": true, "available": true, "o:providerId": "5196b3cc-b2ee-46d6-b63a-7af2cf70ba67" } ``` --- ### `NymeaEnergy.SpotMarketStatusChanged` Émis quand `enabled` ou `available` change (sans les détails du provider). ```json { "enabled": true, "available": true } ``` --- ### `NymeaEnergy.SpotMarketScoreEntriesChanged` Émis quand les cotations sont mises à jour par le provider. ```json { "spotMarketScoreEntries": [ ... ] } ``` --- ## Interfaces nymea consommées 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`, `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 [0–1] | 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/`. --- ## Notes d'intégration - Les timestamps sont des `QDateTime` sérialisés ISO 8601 avec timezone. - Les puissances sont en **Watts**, les courants en **Ampères**. - `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).