From 56e62c01da40e135d9bf49410715e12e53b47e54 Mon Sep 17 00:00:00 2001 From: Patrick Schurig Date: Sat, 30 May 2026 10:22:04 +0200 Subject: [PATCH] =?UTF-8?q?T=C3=A9l=C3=A9verser=20les=20fichiers=20vers=20?= =?UTF-8?q?"/"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ARCHITECTURE.md | 258 ++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 17 +++- 2 files changed, 274 insertions(+), 1 deletion(-) create mode 100644 ARCHITECTURE.md diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000..0322058 --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,258 @@ +# 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 | +| **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) ───────────────────────────┐ +│ openmeteo linky tarif energy-plugin-etm │ +│ (Integration) (Integration) (Integration) (Energy) │ +│ météo/solaire compteur TIC prix/rank rule-based + LM + │ +│ OptimizerManager │ +│ + plugins matériel existants : Keba, Eastron, Waveshare │ +└────────────┬──────────────┬──────────────┬───────────────┬───────────┘ + │ (HTTP) │ (TIC local) │ (HTTP) │ (socket/REST) +┌────────────▼──────────────▼──────────────▼───────────────▼───────────┐ +│ Couche services (hors nymea) │ +│ Open-Meteo self-hosted tarif-provider powersync-optimizer │ +│ (serveur, données) (propriétaire) (PROPRIÉTAIRE) │ +│ météo+satellite grille TRV + RTE Tempo MPC, Perez, │ +│ → rank/prix ombrage, arbitrage │ +└───────────────────────────────────────────────────────────────────────┘ +┌─ Couche infrastructure (existante) ───────────────────────────────────┐ +│ Proxmox (LXC/VM) · reverse proxy · dépôt APT (reprepro/GPG, 3 canaux) │ +└───────────────────────────────────────────────────────────────────────┘ +``` + +Symétrie à retenir : pour la **météo** comme pour le **tarif**, on a *un serveur* (produit la donnée, maintenu une fois) + *un plugin GPL* (l'expose dans nymea). Le plugin ne contient jamais la logique du serveur. + +--- + +## 3. Composants + +### Plugins nymea (GPL-3) + +| Dépôt | Type (`IID`) | Rôle | +|---|---|---| +| `openmeteo` | `io.nymea.IntegrationPlugin` | Conditions météo/solaires actuelles (GHI/DNI/DHI, GTI par pan, satellite) | +| `linky` | `io.nymea.IntegrationPlugin` | Compteur principal (TIC) : option, période HC/HP, `ISOUSC`, couleur Tempo, puissances | +| `tarif` | `io.nymea.IntegrationPlugin` | Façade nymea du `tarif-provider` : expose `currentMarketPrice` + `rank` | +| `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 (hors nymea) + +| Composant | Licence | Nature | Rôle | +|---|---|---|---| +| `powersync-optimizer` | **propriétaire** | FastAPI, service | MPC, transposition Perez, dérating NOCT, masque d'ombrage, arbitrage | +| Open-Meteo self-hosted | AGPLv3 (amont) | serveur | Produit la donnée météo/satellite (prod commerciale) | +| `tarif-provider` | propriétaire | service | Grille TRV (maj 2×/an) + poll RTE Tempo → prix/`rank` par client | + +> Le `powersync-optimizer` n'est **pas** un plugin : c'est le cœur de l'écosystème, composant à part entière. + +--- + +## 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'optimiseur 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 `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 energy-plugin-etm (Keba, + (weather) (TIC: période, (rank, prix) ├─ rule-based Eastron, + ISOUSC, Tempo) ├─ load-management Waveshare) + │ │ │ └─ OptimizerManager + │ │ │ │ (HTTP, transport) + ▼ ▼ ▼ ▼ + Open-Meteo (local) tarif-provider ◄─── powersync-optimizer + self-hosted (RTE Tempo, (récupère lui-même + grille TRV) météo + tarif en série) +``` + +L'optimiseur 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` | 0–100 (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 optimiseur le consomment. Tout fournisseur (Awattar, Tibber, `tarif`) 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'optimiseur, et a **priorité absolue** sur toute consigne — y compris celles de l'optimiseur. C'est une contrainte appliquée *après coup* par le plugin, pas une suggestion envoyée à l'optimiseur. + +### Dégradation gracieuse +Si l'optimiseur 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) + +### `POST {optimizerUrl}/optimize` +État temps réel + contraintes → planning. L'optimiseur a déjà ses séries météo/tarif. +```jsonc +// requête +{ + "timestamp": 0, + "site": { "gridPower": 0, "pvPower": 0, "batterySoc": 0 }, + "loads": [ { "id": "keba1", "type": "evcharger", + "controllable": true, "min": 6, "max": 16, "constraints": {} } ] +} +// réponse +{ + "valid_until": 0, // garde de fraîcheur + "setpoints": [ { "id": "keba1", "current": 10, "from": 0, "to": 0 } ] +} +``` + +### `GET {tariffUrl}/tariff/{client}/now` et `/forecast` +```jsonc +// /now → scalaire (consommé par le plugin) +{ "rank": 18, "price": 13.25, "validUntil": 0 } // price en c€/kWh +// /forecast → série (consommée par l'optimiseur) +{ "horizon": [ { "t": 0, "rank": 18, "buy": 13.25, "sell": 10.0 } ] } +``` + +### Données Open-Meteo +Voir le `README`/docs du dépôt `openmeteo` (variables, requêtes, conventions). Endpoint prévision (Météo-France) + endpoint satellite (MTG). + +--- + +## 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'optimiseur. + +### 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'optimiseur, 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 optimiseur (par install) | matériel | +| `η` | config optimiseur | 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` + `η` | optimiseur propriétaire | + +**Règle d'arbitrage** (optimiseur) — 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'optimiseur 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 | +|---|---|---| +| `powersync-docs` | ce document, source de vérité transverse | — | +| `openmeteo` (nymea-plugin) | plugin météo/solaire | GPL-3 | +| `linky` (nymea-plugin) | plugin compteur TIC | GPL-3 | +| `tarif` (nymea-plugin) | plugin façade tarif | GPL-3 | +| `powersync-energy-plugin-etm` | energy manager (fork nymea-energy-plugin-nymea) | GPL-3 | +| `powersync-optimizer` | service d'optimisation | propriétaire | +| `tarif-provider` | service tarif central | propriétaire | + +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. diff --git a/README.md b/README.md index 388cf17..e1747e0 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,17 @@ -# etm-powersync-docs +# powersync-docs +Documentation transverse de l'écosystème **ETM-PowerSync** (HEMS sur nymea). + +Ce dépôt est la **source de vérité** pour l'architecture, les frontières de licence, les contrats d'interface et les conventions partagées entre tous les composants. Les `README` de chaque dépôt (plugins, services) sont des vues *locales* et renvoient ici pour la vue d'ensemble. + +## Contenu + +- [`ARCHITECTURE.md`](ARCHITECTURE.md) — le document de référence : composants, couches, frontière de licence, flux, conventions, contrats d'interface, configuration à l'installation, niveaux d'optimisation. + +## Règle de cohérence + +Toute décision transverse (nouvelle convention, changement de contrat d'interface, déplacement d'une responsabilité entre composants) se prend **ici d'abord**, puis se décline dans les dépôts concernés. Si un `README` de dépôt contredit ce document, c'est ce document qui fait foi. + +## Évolution + +À terme, ce dépôt pourra être agrégé en site mkdocs. Pour l'instant, README + Markdown versionné suffit.