11 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Contexte général
Dossier parent de tous les projets : /home/etm/Projects/
etm_powersync_app/(ce projet) — application Flutter HEMS, interface utilisateuretm-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. 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.
Commandes
flutter pub get # installer/mettre à jour les dépendances
flutter run # lancer sur device/émulateur connecté
flutter build apk # build Android release
flutter analyze # analyse statique Dart
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
State management — un seul Provider
NymeaService (lib/services/nymea_service.dart) est le seul ChangeNotifier. Il est fourni à la racine dans main.dart via ChangeNotifierProvider. Tous les écrans le consomment avec context.watch<NymeaService>() ou context.read<NymeaService>(). Il n'y a aucune autre couche de state management.
Navigation
MainShell dans main.dart utilise un IndexedStack de 5 écrans (l'état est préservé entre les onglets) :
| Index | Écran | Fichier |
|---|---|---|
| 0 | Dashboard | lib/screens/dashboard_screen.dart |
| 1 | Énergie | lib/screens/energy_screen.dart |
| 2 | Things | lib/screens/things_screen.dart |
| 3 | A/C | lib/screens/ac_screen.dart |
| 4 | Favoris | lib/screens/favorites_screen.dart |
Communication avec nymea
NymeaService.connect() supporte deux transports :
- TCP brut port 2222 (défaut) : nymea parle en premier (JSON délimité par
\n). La fragmentation est gérée viaStringBuffer. - WebSocket port 4444 : c'est le client qui parle en premier en envoyant
JSONRPC.Hello.
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(). 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
NymeaService.startSimulation() déconnecte toute connexion live, active _isSimulation = true, génère des things fictifs et une production PV simulée en sinus (timer 2 s). Le dashboard démarre automatiquement la simulation au premier affichage si non connecté. Toutes les méthodes du service font un garde if (_isSimulation) return … pour éviter les vrais appels RPC.
Modèles de données
EnergyData(lib/models/energy_data.dart) — objet immutable aveccopyWith, regroupe toutes les métriques énergie temps-réel et journalières + état de la borne EV.NymeaThing/NymeaThingClass/NymeaStateType/NymeaActionType(lib/models/nymea_models.dart) — miroir de l'API Integrations nymea.NymeaThingClass.primaryStateTypedétermine l'état affiché sur les cards.ThingCategory+interfaceToCategoryMap(lib/models/thing_category.dart) — mappe les interfaces nymea (ex."evcharger","solarinverter") vers des catégories d'affichage utilisées parThingsScreen.FavoriteWidget(lib/models/nymea_models.dart) — descripteurs de widgets stockés en mémoire dansNymeaService._favoriteWidgets.
Thème
AppTheme (lib/theme/app_theme.dart) expose des constantes Color nommées. Toujours utiliser ces constantes plutôt que des valeurs hex brutes :
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.
// ❌ 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ésDiscoverThings,AddThing,RemoveThing,EditThing— ✅ déjà implémentésGetRules— ✅ lecture seule déjà implémentéeAddRule,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 :
// 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
- Code : anglais — Commentaires : français
- Nommage : camelCase variables, PascalCase classes
- Les écrans utilisent
Consumer<NymeaService>oucontext.watch— ne jamais dupliquer l'état du service dans unState - 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.
Règles de modification
- Tout nouvel écran → valider la maquette avec Patrick avant de coder
- Tout nouvel appel RPC → vérifier dans
docs/nymea_api.mdque la méthode existe - Ne jamais appeler
Energy.SetChargingModepour piloter un EV - Toujours tester la persistance : killer l'app et vérifier que les données survivent au redémarrage
Documentation API
docs/nymea_api.md— introspection complète de l'API nymeadocs/nymea_integrations.md— namespace Integrations en détail