etm-powersync-docs/ARCHITECTURE.md

249 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Architecture — écosystème ETM-PowerSync
> Document de référence transverse. Source de vérité ; les `README` de dépôt en sont des vues locales.
> Statut : v0.1 (structuration initiale). Les contrats d'interface sont en `draft`.
---
## 1. Principe directeur : deux frontières alignées
Tout l'écosystème repose sur une seule ligne, qui sépare **deux choses à la fois** :
| | Côté plugin | Côté service |
|---|---|---|
| **Licence** | GPL-3.0-or-later | Propriétaire (ou ouvrable) |
| **Exécution** | dans le processus `nymead` | processus séparé |
| **Données** | scalaire, événementiel (valeur instantanée) | série temporelle (horizon) |
| **Rôle** | capteurs, données, transport, règles simples | intelligence (prévision, MPC, arbitrage) |
La frontière de licence et la frontière technique **coïncident**. Un plugin nymea est lié à `libnymea` (GPL) et chargé dans `nymead` : il doit être GPL et ne contient donc **aucune logique propriétaire**. L'intelligence vit dans des services séparés, joints uniquement par API HTTP (« arm's length »), ce qui préserve le propriétaire. Précédent de référence : [EOS](https://github.com/Akkudoktor-EOS/EOS), optimiseur autonome consommé par HTTP.
---
## 2. Vue d'ensemble par couches
```
┌─ Couche plugins nymea (GPL-3, dans nymead) ───────────────────────────┐
│ etm-powersync-plugins (mono-repo) energy-plugin-etm │
│ ├─ openmeteo (Integration) (Energy) │
│ ├─ linky (Integration) rule-based + load-management│
│ └─ tarif-api (Integration) + OptimizerManager │
│ + plugins matériel existants : Keba, Eastron, Waveshare │
└───────────────────────────────────────────────────────────────────────┘
│ HTTP │ TIC local │ HTTP │ socket/REST
▼ ▼ ▼ ▼
┌─ Couche services (hors nymea) ────────────────────────────────────────┐
│ Open-Meteo self-hosted tarif-provider optimizer │
│ (meteo-service) (propriétaire/ (PROPRIÉTAIRE) │
│ déploiement) ouvrable) MPC, Perez, │
│ météo + satellite grille TRV + RTE Tempo ombrage, arbitrage │
│ → rank / prix │
└───────────────────────────────────────────────────────────────────────┘
┌─ Couche infrastructure (existante) ───────────────────────────────────┐
│ Proxmox (LXC/VM) · reverse proxy · dépôt APT (reprepro/GPG, 3 canaux)│
└───────────────────────────────────────────────────────────────────────┘
```
> Dans les schémas, `energy-plugin-etm` désigne le dépôt `etm-powersync-energy-plugin-etm` (cf. §10).
Symétrie à retenir : pour la **météo** comme pour le **tarif**, on a *un backend* (produit la donnée, maintenu une fois) + *un plugin GPL* (l'expose dans nymea). Le plugin ne contient jamais la logique du backend.
---
## 3. Composants
### Plugins nymea (GPL-3)
Les plugins d'intégration sont des **modules d'un seul dépôt** `etm-powersync-plugins` (chacun dans son sous-dossier, build et packaging communs). L'energy manager est à part (fork avec amont distinct, type de plugin différent).
| Module / dépôt | Type (`IID`) | Rôle |
|---|---|---|
| `etm-powersync-plugins/openmeteo` | `io.nymea.IntegrationPlugin` | Conditions météo/solaires actuelles (GHI/DNI/DHI, GTI par pan, satellite) |
| `etm-powersync-plugins/linky` | `io.nymea.IntegrationPlugin` | Compteur principal (TIC) : option, période HC/HP, `ISOUSC`, couleur Tempo, puissances |
| `etm-powersync-plugins/tarif-api` | `io.nymea.IntegrationPlugin` | Client GPL d'une API tarifaire ; expose `currentMarketPrice` + `rank` |
| `etm-powersync-energy-plugin-etm` | `io.nymea.EnergyPlugin` | Energy manager : rule-based surplus, load-management, `OptimizerManager` |
| *(existants)* Keba, Eastron, Waveshare | `io.nymea.IntegrationPlugin` | Charges et compteurs pilotés |
### Services et déploiements (hors nymea)
| Dépôt | Licence | Nature | Rôle |
|---|---|---|---|
| `etm-powersync-optimizer` | **propriétaire** | code (FastAPI) | MPC, transposition Perez, dérating NOCT, masque d'ombrage, arbitrage |
| `etm-powersync-tarif-provider` | propriétaire / **ouvrable** | code | Grille TRV (maj 2×/an) + poll RTE Tempo → prix/`rank` par client |
| `etm-powersync-meteo-service` | — | **déploiement + how-to** | Instance Open-Meteo auto-hébergée (docker-compose, config `sync`) — pas du code applicatif |
> L'`optimizer` n'est **pas** un plugin : c'est le cœur de l'écosystème, composant à part entière.
> `meteo-service` documente/déploie un logiciel amont (Open-Meteo, AGPLv3) ; on ne le développe pas.
### `tarif-api` vs `tarif-provider` — à ne pas confondre
- **`tarif-api`** : le **plugin GPL** (client), dans `etm-powersync-plugins`. Façade nymea, bête.
- **`tarif-provider`** : le **backend** de référence (serveur), qui produit prix/`rank`.
- **`interfaces/tariff.md`** : le contrat qui les relie. Comme `tarif-api` parle un protocole ouvert, un tiers peut substituer son propre provider sans toucher au plugin.
---
## 4. Frontière de licence
**Ne doit JAMAIS entrer dans un plugin GPL :**
- transposition Perez, dérating thermique (NOCT) ;
- modèle de prévision, MPC, scheduling sur horizon ;
- masque d'ombrage appris (grille azimut × élévation) ;
- stratégie de pondération tarifaire ;
- arbitrage économique (revente, coût de stockage).
**Peut/doit être dans un plugin GPL :**
- lecture des capteurs et de l'équilibre énergétique ;
- rule-based surplus (hystérésis, priorités) ;
- load-management (cf. §6) ;
- `OptimizerManager` = **transport pur** : sérialise l'état → HTTP → applique les consignes. Aucune mathématique d'optimisation.
**Communication arm's length** : l'optimizer et le `tarif-provider` sont des processus séparés joints par REST/socket. Ils ne lient pas `libnymea` ni aucun en-tête GPL. Échange de **données**, jamais de code.
**Attributions à conserver :**
- code amont : copyright nymea GmbH + chargebyte (fork `etm-powersync-energy-plugin-etm`), licence GPL-3 ;
- données : Météo-France / EUMETSAT / Open-Meteo (CC-BY), RTE / CRE (Tempo, TRV).
*(Ceci décrit la mécanique technique, pas un avis juridique formel.)*
---
## 5. Flux
```
nymea (energy experience)
┌──────────────┬─────────────────┼──────────────────┬──────────────┐
openmeteo linky tarif-api energy-plugin-etm
(weather) (TIC: période, (rank, prix) ├─ rule-based (Keba,
ISOUSC, Tempo) ├─ load-management Eastron,
│ │ │ └─ OptimizerManager Waveshare)
│ │ │ │ (HTTP, transport)
▼ ▼ ▼ ▼
Open-Meteo (local) tarif-provider ◄─── optimizer
(meteo- (RTE Tempo, (récupère lui-même
service) grille TRV) météo + tarif en série)
```
L'optimizer récupère **lui-même** ses séries (météo Open-Meteo, prix/`rank` du `tarif-provider`). Les plugins ne lui poussent que l'état temps réel et les contraintes.
---
## 6. Conventions partagées
### Scalaire vs série
Plugin = valeur instantanée (état nymea). Service = série temporelle (horizon). Aucune série ne transite par un état nymea.
### Azimut
Stockage en **convention géographique** (0=N, 90=E, **180=S**, 270=O), cohérent avec les diagrammes de course du soleil et le masque d'ombrage. Conversion vers Open-Meteo au moment de la requête :
```
azimuth_openmeteo = azimuth_geographique 180
```
### Vocabulaire tarifaire (states nymea, adopté tel quel)
nymea n'a **pas** d'interface `pricing` formelle ; le vocabulaire est conventionnel (cf. plugin Awattar) :
| État | Unité | Rôle |
|---|---|---|
| `currentMarketPrice` | `EuroCentPerKiloWattHour` | prix courant |
| `rank` | 0100 (plus bas = meilleur) | **pondération** (classement de l'heure) |
| `validUntil` | `UnixTime` | fraîcheur |
| `averagePrice` / `lowestPrice` / `highestPrice` | `EuroCentPerKiloWattHour` | stats ±12 h |
**Pondération = `rank`.** Le signal relatif de scheduling se mappe sur le `rank` natif de nymea ; rule-based et optimizer le consomment. Tout fournisseur (Awattar, Tibber, `tarif-api`) exposant ce vocabulaire est interchangeable du point de vue de l'energy manager.
### Load-management = contrainte dure, locale, prioritaire
La surveillance de la puissance souscrite (`ISOUSC`, protection fusible/disjoncteur) est une fonction de **sécurité** : elle agit en temps réel, sans dépendre du réseau ni de l'optimizer, et a **priorité absolue** sur toute consigne — y compris celles de l'optimizer. C'est une contrainte appliquée *après coup* par le plugin, pas une suggestion envoyée à l'optimizer.
### Dégradation gracieuse
Si l'optimizer est injoignable ou renvoie un planning périmé (`valid_until` dépassé), `OptimizerManager` retombe sur le rule-based local. Le plugin fonctionne **toujours** seul. Résilience et hygiène de licence dans le même mécanisme.
---
## 7. Contrats d'interface (draft v0.1, à versionner)
Référence détaillée et versionnée dans [`interfaces/`](interfaces/README.md).
- [`interfaces/optimize.md`](interfaces/optimize.md) — `POST {optimizerUrl}/optimize` : état temps réel + contraintes → planning.
- [`interfaces/tariff.md`](interfaces/tariff.md) — `GET {tariffUrl}/tariff/{client}/now` (scalaire) et `/forecast` (série).
- Données Open-Meteo : voir le module `openmeteo` (`etm-powersync-plugins`) et `etm-powersync-meteo-service`.
---
## 8. Configuration à l'installation
Un **écran unique** pour l'installateur (paramètres généraux de l'installation), mais **deux destinations** de données selon la frontière : le tarif va au `tarif-provider`/config locale, le matériel d'arbitrage va à l'optimizer.
### Tarif consommation
- **Manuel** : tarif unique *ou* HC/HP — saisie des prix et des plages horaires.
- **Provider** : choix du fournisseur et de l'option (EDF + Base/HC-HP/Tempo, Awattar, Tibber…).
- Sortie unifiée : `rank` + prix absolu (même en tarif fixe, via un `rank` trivial).
### Tarif revente
- **Même mécanisme** (manuel ou provider).
- Cas dominant : OA surplus → **constante par client**. Le mécanisme identique permet de basculer en série (revente spot) sans changer le modèle.
### Coût de stockage
Saisie : prix d'achat batterie, garantie (années), cycles garantis fabricant, DOD.
```
capacité_utile = capacité_nominale × DOD
cycles_effectifs = min(cycles_garantis, années × cycles_par_an_estimés)
c_batt (€/kWh) = prix_achat / (cycles_effectifs × capacité_utile)
```
- **Attention** : les cycles fabricant sont donnés *à* un DOD ; le DOD qualifie la capacité utile, il ne se multiplie pas une seconde fois.
- La garantie en années plafonne la durée de vie quand le cyclage annuel est faible (d'où le `min`).
### Rendement
- `η` (aller-retour, défaut ~0,90, ajustable) — paramètre de l'optimizer, par site.
| Saisie installateur | Stocké dans | Nature |
|---|---|---|
| Tarif conso (manuel/provider) | tarif-provider + config locale | tarif |
| Tarif revente (manuel/provider) | tarif-provider + config locale | tarif |
| Prix, garantie, cycles, DOD | config optimizer (par install) | matériel |
| `η` | config optimizer | matériel |
---
## 9. Niveaux d'optimisation (tiers)
| Tier | Optimisation | Données nécessaires | Où |
|---|---|---|---|
| **Community** | Surplus rule-based + load-management | `rank` (+ équilibre énergétique) | plugin GPL |
| **Auto / Predict** | MPC, prévision, arbitrage | `rank` + `p_achat` + `p_revente` + `c_batt` + `η` | optimizer propriétaire |
**Règle d'arbitrage** (optimizer) — stocker/revendre n'est rentable que si l'écart de prix dépasse le coût de cycle :
```
p_utilisation_évitée p_achat(t_charge) > c_batt / η (stockage pour autoconso différée)
p_revente(t_vente) p_achat(t_charge) > c_batt / η (revente depuis batterie)
```
Sans le terme `c_batt`, l'optimizer sur-cycle la batterie pour des gains inexistants.
La détermination du tier (clé de licence / config) est lue par `OptimizerManager`, qui active ou non la stratégie distante — avec repli rule-based dans tous les cas.
---
## 10. Dépôts
| Dépôt | Contenu | Licence |
|---|---|---|
| `etm-powersync-docs` | ce document, source de vérité transverse | — |
| `etm-powersync-plugins` | plugins GPL (modules : `openmeteo`, `linky`, `tarif-api`) | GPL-3 |
| `etm-powersync-energy-plugin-etm` | energy manager (fork nymea-energy-plugin-nymea) | GPL-3 |
| `etm-powersync-optimizer` | service d'optimisation | propriétaire |
| `etm-powersync-tarif-provider` | backend tarif (implémentation de référence) | propriétaire / ouvrable |
| `etm-powersync-meteo-service` | déploiement Open-Meteo + how-to | — |
Chaque dépôt porte son `README` (vue locale + renvoi ici), un `CLAUDE.md` (workflow multi-agent), et ses fichiers de licence/attribution.
---
## Annexe — points encore ouverts
- Schéma exact et versionnement formel des contrats `/optimize` et `/tariff` (passer de `draft` à `v1`).
- Modèle fin de `c_batt` (dépendance DOD/vieillissement) — plus tard ; coût plat suffisant pour démarrer.
- Cas revente en série (spot) — prévu par le mécanisme, non prioritaire.
- Éventuelle lib commune `optimizer``tarif-provider` (client Open-Meteo, modèles de séries) — seulement si le code partagé grossit. YAGNI pour l'instant.