etm-powersync-app/CLAUDE.md

11 KiB
Raw Blame History

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 utilisateur
  • 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. 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.2NymeaService (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 via StringBuffer.
  • 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 avec copyWith, 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.primaryStateType dé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 par ThingsScreen.
  • FavoriteWidget (lib/models/nymea_models.dart) — descripteurs de widgets stockés en mémoire dans NymeaService._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é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 :

// 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> 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.

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.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

  • docs/nymea_api.md — introspection complète de l'API nymea
  • docs/nymea_integrations.md — namespace Integrations en détail