chore: add CLAUDE.md agent context
This commit is contained in:
parent
0bf5003992
commit
1278da2a04
196
CLAUDE.md
196
CLAUDE.md
@ -6,10 +6,13 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
|||||||
|
|
||||||
Dossier parent de tous les projets : `/home/etm/Projects/`
|
Dossier parent de tous les projets : `/home/etm/Projects/`
|
||||||
|
|
||||||
- **`etm_powersync_app/`** (ce projet) — application Flutter HEMS, client mobile
|
- **`etm_powersync_app/`** (ce projet) — application Flutter HEMS, interface utilisateur
|
||||||
- **`etm-nymea/`** — serveur nymea et plugins associés (C++/Qt), le backend
|
- **`etm-nymea/`** — serveur nymea et plugins associés (C++/Qt), le backend
|
||||||
|
|
||||||
Ce projet est le **client Flutter** qui communique avec le serveur nymea via JSON-RPC 2.0.
|
Ce projet est le **client Flutter** qui communique avec le serveur nymea via JSON-RPC 2.0.
|
||||||
|
Il n'a aucun accès direct aux plugins — tout passe par les APIs JSON-RPC.
|
||||||
|
|
||||||
|
Lire aussi le `CLAUDE.md` du dossier parent (`/home/etm/Projects/CLAUDE.md`) pour le contexte global.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -23,6 +26,20 @@ flutter analyze # analyse statique Dart
|
|||||||
flutter test # lancer les tests
|
flutter test # lancer les tests
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Stack technique
|
||||||
|
|
||||||
|
| Élément | Valeur |
|
||||||
|
|---|---|
|
||||||
|
| Flutter SDK | ^3.11.0 |
|
||||||
|
| State management | `provider ^6.1.2` — `NymeaService` (ChangeNotifier unique) |
|
||||||
|
| Navigation | `go_router ^14.6.3` — ShellRoute + 25+ routes |
|
||||||
|
| Transport | WebSocket port 4444 / TCP brut port 2222 |
|
||||||
|
| Graphiques | `fl_chart ^0.70.2` |
|
||||||
|
| Persistance locale | `shared_preferences ^2.3.2` |
|
||||||
|
| Sécurité installateur | `crypto ^3.0.6` (PIN SHA-256, lock 30s, auto-lock 10 min) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
### State management — un seul Provider
|
### State management — un seul Provider
|
||||||
@ -50,7 +67,7 @@ flutter test # lancer les tests
|
|||||||
|
|
||||||
Les requêtes utilisent des IDs auto-incrémentés stockés dans `_pendingRequests: Map<int, Completer>`. Timeout global : 15 secondes. Les notifications push nymea (pas de champ `id`, ont un champ `notification`) sont dispatchées dans `_handleNotification()`.
|
Les requêtes utilisent des IDs auto-incrémentés stockés dans `_pendingRequests: Map<int, Completer>`. Timeout global : 15 secondes. Les notifications push nymea (pas de champ `id`, ont un champ `notification`) sont dispatchées dans `_handleNotification()`.
|
||||||
|
|
||||||
**Convention nymea énergie importante** : `currentPowerProduction` est **négatif** (producteur = négatif). Le service convertit en positif dans `_parsePowerBalance()`.
|
**Convention nymea énergie importante** : `currentPowerProduction` est **négatif** (producteur = négatif). Le service convertit en positif dans `_parsePowerBalance()`. Puissances en **W**, énergies journalières en **Wh**, totaux API nymea en **kWh** (× 1000 dans le service). Timestamps : **secondes** dans toutes les APIs energy.
|
||||||
|
|
||||||
### Mode simulation
|
### Mode simulation
|
||||||
|
|
||||||
@ -65,7 +82,160 @@ Les requêtes utilisent des IDs auto-incrémentés stockés dans `_pendingReques
|
|||||||
|
|
||||||
### Thème
|
### Thème
|
||||||
|
|
||||||
`AppTheme` (`lib/theme/app_theme.dart`) expose des constantes `Color` nommées : `primaryGreen`, `solarYellow`, `gridGray`, `homeBlue`, `batteryGreen`, `boostRed`, `pvGreen`, `minPvBlue`, etc. Toujours utiliser ces constantes plutôt que des valeurs hex brutes.
|
`AppTheme` (`lib/theme/app_theme.dart`) expose des constantes `Color` nommées. Toujours utiliser ces constantes plutôt que des valeurs hex brutes :
|
||||||
|
|
||||||
|
```dart
|
||||||
|
primaryGreen // principal
|
||||||
|
solarYellow // production PV
|
||||||
|
gridGray // réseau
|
||||||
|
homeBlue // consommation maison
|
||||||
|
batteryGreen // batterie
|
||||||
|
boostRed // mode boost
|
||||||
|
pvGreen // surplus PV
|
||||||
|
minPvBlue // mode minPV
|
||||||
|
accentTeal // accent
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔴 Bug critique — priorité absolue
|
||||||
|
|
||||||
|
`EVChargingCard` appelle `Energy.SetChargingMode` — cette méthode appartient à l'experience plugin et concerne le comptage du rootmeter, **pas le pilotage EV**. Le SmartChargingManager n'est jamais déclenché depuis l'app dans l'état actuel.
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// ❌ FAUX — ne déclenche pas le SmartChargingManager
|
||||||
|
nymeaService.call('Energy.SetChargingMode', {'mode': 'pv'});
|
||||||
|
|
||||||
|
// ✅ CORRECT — appelle vraiment le plugin ETM
|
||||||
|
nymeaService.call('EnergyPlugin.SetChargingInfo', {
|
||||||
|
'chargingInfo': {
|
||||||
|
'evChargerId': chargerId,
|
||||||
|
'mode': 'Eco', // Normal | Eco | EcoWithTargetTime
|
||||||
|
'targetSoc': 80,
|
||||||
|
'endTime': null,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## APIs JSON-RPC consommées
|
||||||
|
|
||||||
|
### `Energy.*` (nymea-experience-plugin-energy)
|
||||||
|
|
||||||
|
| Méthode | Usage |
|
||||||
|
|---|---|
|
||||||
|
| `GetPowerBalance` | Dashboard temps réel |
|
||||||
|
| `GetPowerBalanceLogs(sampleRate, from, to)` | Historique — envoyer les bons `from/to` pour les 90j |
|
||||||
|
| `GetThingPowerLogs(sampleRate, thingIds[], from, to)` | Historique par device |
|
||||||
|
| `SetRootMeter(rootMeterThingId)` | Config installateur |
|
||||||
|
|
||||||
|
**Notifications push :** `PowerBalanceChanged`, `PowerBalanceLogEntryAdded`, `ThingPowerLogEntryAdded`, `RootMeterChanged`
|
||||||
|
|
||||||
|
### `EnergyPlugin.*` (powersync-energy-plugin-etm)
|
||||||
|
|
||||||
|
| Méthode | Usage |
|
||||||
|
|---|---|
|
||||||
|
| `GetChargingInfos(o:evChargerId)` | Lire config recharge EV |
|
||||||
|
| `SetChargingInfo(chargingInfo)` | **Piloter le smart charging EV** (Eco / EcoWithTargetTime / Normal) |
|
||||||
|
| `GetChargingSchedules(o:evChargerId)` | Afficher le planning EV calculé |
|
||||||
|
| `GetAvailableSpotMarketProviders()` | Liste des fournisseurs tarifs disponibles |
|
||||||
|
| `SetSpotMarketConfiguration(enabled, providerId)` | Activer/choisir fournisseur spot market |
|
||||||
|
| `GetSpotMarketScoreEntries(o:date)` | Cotations horaires aWATTar |
|
||||||
|
| `SetPhasePowerLimit(Uint)` | Config installateur — protection surcharge (A/phase) |
|
||||||
|
| `SetAcquisitionTolerance(Double)` | Config seuil surplus déclenchant la recharge |
|
||||||
|
| `SetBatteryLevelConsideration(Double)` | Facteur batterie dans le calcul surplus |
|
||||||
|
|
||||||
|
**Notifications push :** `ChargingInfoAdded/Removed/Changed`, `ChargingSchedulesChanged`, `SpotMarketConfigurationChanged`, `SpotMarketScoreEntriesChanged`, `PhasePowerLimitChanged`
|
||||||
|
|
||||||
|
### `AirConditioning.*` (nymea-experience-plugin-airconditioning)
|
||||||
|
|
||||||
|
| Méthode | Usage |
|
||||||
|
|---|---|
|
||||||
|
| `GetZones` | Afficher les zones PAC/thermostat |
|
||||||
|
| `AddZone` / `RemoveZone` | Gestion des zones |
|
||||||
|
| `SetZoneName` / `SetZoneThings` | Configuration zone |
|
||||||
|
| `SetZoneStandbySetpoint` | Consigne hors-horaire |
|
||||||
|
| `SetZoneSetpointOverride` | Pilotage manuel zone |
|
||||||
|
| `SetZoneWeekSchedule` | Planning 7 jours |
|
||||||
|
|
||||||
|
**Notifications push :** `ZoneAdded`, `ZoneRemoved`, `ZoneChanged`
|
||||||
|
|
||||||
|
### `Integrations.*` / `Rules.*` / `Logging.*`
|
||||||
|
|
||||||
|
- `GetThings`, `GetThingClasses`, `ExecuteAction`, `SetStateValue`, `SetThingSettings`, `GetStateValue` — ✅ déjà implémentés
|
||||||
|
- `DiscoverThings`, `AddThing`, `RemoveThing`, `EditThing` — ✅ déjà implémentés
|
||||||
|
- `GetRules` — ✅ lecture seule déjà implémentée
|
||||||
|
- `AddRule`, `RemoveRule`, `EditRule` — ❌ à implémenter (UI automatisations Community)
|
||||||
|
- `Logging.GetLogEntries` — ✅ implémenté (historique SOC batterie, température)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## État des écrans
|
||||||
|
|
||||||
|
| Écran | État | Action requise |
|
||||||
|
|---|---|---|
|
||||||
|
| Dashboard (Sankey + gains + EV card) | ✅ | Icône notifications à brancher |
|
||||||
|
| EnergyScreen (4 onglets + charts) | ✅ | Ajouter sélecteur plage 90j |
|
||||||
|
| ThingsScreen + ThingDetailScreen | ✅ | — |
|
||||||
|
| FavoritesScreen | ✅ | **Persister dans SharedPreferences** |
|
||||||
|
| InstallerMode (PIN SHA-256) | ✅ | — |
|
||||||
|
| RoleConfigFlow wizard | ⚠️ Stub | Brancher Step 3 sur les vrais RPC |
|
||||||
|
| TariffScreen | ⚠️ Stub | Brancher sur `EnergyPlugin.SetSpotMarketConfiguration` |
|
||||||
|
| SchedulerScreen | ⚠️ Stub | `setStrategy()` / `forceRecalc()` = fonctions vides |
|
||||||
|
| TimelineScreen | ⚠️ Stub | Données simulées → brancher scheduler réel |
|
||||||
|
| AirConditioning zones | ❌ Absent | À créer (tier Auto) |
|
||||||
|
| DeveloperScreen | ❌ Vide | À créer |
|
||||||
|
| AboutScreen | ❌ Vide | À créer |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Persistance
|
||||||
|
|
||||||
|
| Donnée | Actuellement | À faire |
|
||||||
|
|---|---|---|
|
||||||
|
| Adresse serveur, PIN, préférences UI | ✅ SharedPreferences | — |
|
||||||
|
| `RoleAssignments` (configuration EMS) | ❌ En mémoire | Persister SharedPreferences |
|
||||||
|
| `FavoriteWidgets` | ❌ En mémoire | Persister SharedPreferences |
|
||||||
|
| `TariffConfig` / `HcHpConfig` | ❌ En mémoire | Persister SharedPreferences |
|
||||||
|
| `SchedulerConfig` | ❌ En mémoire | Persister SharedPreferences |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Feature gating par tier
|
||||||
|
|
||||||
|
Le tier actif est lu depuis `/etc/powersync/tier.conf` via un futur RPC.
|
||||||
|
En attendant, utiliser `TierProvider` (classe à créer) avec valeur par défaut `community`.
|
||||||
|
|
||||||
|
Utiliser `pro_lock_badge.dart` (déjà présent) pour verrouiller visuellement les features non disponibles. **Ne jamais hardcoder le tier** — toujours passer par `TierProvider` :
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// Toujours via TierProvider — jamais de valeur hardcodée
|
||||||
|
if (tierProvider.tier >= Tier.auto) {
|
||||||
|
// afficher feature Auto
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Feature | Community | Auto | Predict AI |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Dashboard temps réel | ✅ | ✅ | ✅ |
|
||||||
|
| Historique journée en cours | ✅ | ✅ | ✅ |
|
||||||
|
| Config EV (`EnergyPlugin.SetChargingInfo`) | ✅ | ✅ | ✅ |
|
||||||
|
| Tarif HP/HC statique | ✅ | ✅ | ✅ |
|
||||||
|
| UI automatisations (`Rules.*`) | ✅ | ✅ | ✅ |
|
||||||
|
| Historique 90 jours | 🔒 | ✅ | ✅ |
|
||||||
|
| Wizard onboarding guidé | 🔒 | ✅ | ✅ |
|
||||||
|
| Zones PAC/ECS (`AirConditioning.*`) | 🔒 | ✅ | ✅ |
|
||||||
|
| Prévision solaire Open-Meteo | 🔒 | ✅ | ✅ |
|
||||||
|
| Notifications d'anomalies | 🔒 | ✅ | ✅ |
|
||||||
|
| Accès distant sécurisé | 🔒 | ✅ | ✅ |
|
||||||
|
| Tarifs dynamiques aWATTar / spot market | 🔒 | 🔒 | ✅ |
|
||||||
|
| ENTSO-E / Tibber | 🔒 | 🔒 | ✅ |
|
||||||
|
| Accès fonctionnalités beta | 🔒 | 🔒 | ✅ |
|
||||||
|
|
||||||
|
Flavors Android/iOS à configurer : `com.etm-powersync.community` / `.auto` / `.predictai`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Conventions
|
## Conventions
|
||||||
|
|
||||||
@ -74,20 +244,16 @@ Les requêtes utilisent des IDs auto-incrémentés stockés dans `_pendingReques
|
|||||||
- Les écrans utilisent `Consumer<NymeaService>` ou `context.watch` — ne jamais dupliquer l'état du service dans un `State`
|
- Les écrans utilisent `Consumer<NymeaService>` ou `context.watch` — ne jamais dupliquer l'état du service dans un `State`
|
||||||
- **Unités** : Watts (W) pour les puissances instantanées, Wh pour les énergies journalières. L'API nymea retourne des totaux en kWh que le service multiplie par 1000.
|
- **Unités** : Watts (W) pour les puissances instantanées, Wh pour les énergies journalières. L'API nymea retourne des totaux en kWh que le service multiplie par 1000.
|
||||||
|
|
||||||
## Méthodes nymea principales
|
---
|
||||||
|
|
||||||
| Méthode | Rôle |
|
## Règles de modification
|
||||||
|---------|------|
|
|
||||||
| `JSONRPC.Hello` | Handshake (WebSocket uniquement — envoyé par le client) |
|
|
||||||
| `Integrations.GetThings` | Charger les devices configurés |
|
|
||||||
| `Integrations.GetThingClasses` | Charger les définitions de classes (filtre `thingClassIds` supporté) |
|
|
||||||
| `Integrations.ExecuteAction` | Exécuter une action sur un thing |
|
|
||||||
| `Integrations.GetStateValue` | Lire une valeur d'état |
|
|
||||||
| `Energy.GetPowerBalance` | Snapshot du flux énergétique actuel |
|
|
||||||
| `Energy.GetEnergyLogs` | Historique énergie (échantillons horaires) |
|
|
||||||
| `Energy.SetChargingMode` | Mode borne EV : `pv`, `minpv`, `boost` |
|
|
||||||
|
|
||||||
Notifications push gérées : `Energy.PowerBalanceChanged`, `Energy.RootMeterChanged`, `Integrations.StateChanged`, `Integrations.ThingAdded`, `Integrations.ThingRemoved`.
|
- Tout nouvel écran → valider la maquette avec Patrick avant de coder
|
||||||
|
- Tout nouvel appel RPC → vérifier dans `docs/nymea_api.md` que la méthode existe
|
||||||
|
- Ne **jamais** appeler `Energy.SetChargingMode` pour piloter un EV
|
||||||
|
- Toujours tester la persistance : killer l'app et vérifier que les données survivent au redémarrage
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Documentation API
|
## Documentation API
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user