etm-powersync-app/CLAUDE.md
Patrick Schurig 054cbfab3c docs: maquettes de la refonte « Charges pilotables » + CLAUDE.md recalé sur la box
Trois maquettes, neuf écrans, après le passage sur appareil qui a montré que
/energy/setup décrit autre chose que l'installation.

- charges_pilotables_mockup.html : la liste à deux niveaux, avec le bandeau
  d'arbitrage hérité de /energy/loads (qui disparaît) et la télémétrie sur la carte
  de chaque charge.
- configurer_mecanismes_mockup.html : les six « Configurer » — routeur de relais,
  modulable, SG-Ready, borne, zones HVAC, sélecteur d'appareils.
- raccordement_reseau_mockup.html : compteur réseau et protection de surcharge.

Décisions de conception que les images ne portent pas seules : les paliers
n'existent que chez le routeur de relais et y sont DÉDUITS des combinaisons ; le
mode fixed d'etmvariableload (legacy) n'est plus proposé mais reste lisible, parce
qu'ouvrir un écran ne doit pas convertir une config déployée ; SG1/SG2 sont une
convention d'app retrouvée par l'encodage standard, la box ne stocke qu'une liste
de relais par état ; le compteur par charge est dessiné inactif avec sa raison ; la
borne règle un mode par DÉFAUT, pas une commande.

CLAUDE.md corrigé contre JSONRPC.Introspect (plugin 1.15.2+etm15) :

- Energy.SetChargingMode n'existe pas — le « bug critique » décrivait un appel
  impossible. Energy.* n'a que cinq méthodes.
- Le namespace EnergyPlugin.* n'existe pas : c'est NymeaEnergy.*, 20 méthodes et
  14 notifications, toutes listées.
- ChargingInfo : chargingMode / targetPercentage / endDateTime (Uint), et non
  mode / targetSoc / endTime. minCurrent n'existe nulle part.
- phasePowerLimit est en AMPÈRES par phase, et 0 coupe toute la recharge
  intelligente.
- AirConditioning expose 8 méthodes, pas 3.

Toutes les méthodes citées ont été revérifiées une par une : aucune introuvable.
Règle ajoutée : contrôler ce fichier contre Introspect avant chaque lot — trois
fois aujourd'hui il a envoyé un agent dans le vide.

Et docs/BRIEF_plugin_depuis_maquettes.md : les six points que les maquettes
remontent vers le moteur, dont un seul blocage dur (pas de champ de courant
minimum pour le mode Min+PV).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XPUo3RMr8SzK6qbFtfBm8H
2026-08-25 21:30:32 +02:00

11 KiB

Agent App — etm_powersync_app

Lire aussi le CLAUDE.md du dossier parent avant de commencer.


ssh banc .75

Accès banc .75 : ssh etm@192.168.1.75 (clé déjà installée). Ni root ni nymea n'existent. Journal : journalctl -u nymead.

Mon rôle

Interface utilisateur Flutter du HEMS ETM-PowerSync. Je communique exclusivement via JSON-RPC nymea port 4444. Le gating des features par tier est géré visuellement ici, mais la source de vérité du tier est dans powersync-optimizer.


Stack technique

Élément Valeur
Flutter SDK ^3.11.0
State management provider ^6.1.2 — NymeaService (ChangeNotifier)
Navigation go_router ^14.6.3 — ShellRoute + 25+ routes
Transport WebSocket port 4444 / TCP brut port 2222
Graphiques fl_chart ^0.70.2
Persistance shared_preferences ^2.3.2
Sécurité installateur crypto ^3.0.6 (PIN SHA-256)

🔴 Recharge EV — l'appel réel, vérifié contre la box

Ce bloc décrivait un bug qui n'existe plus, avec des champs qui n'ont jamais existé. Energy.SetChargingMode n'est pas une méthode de nymea — il n'y en a que cinq dans Energy.*, et celle-là n'en fait pas partie : l'appel échouerait sur « No such method ». Et le namespace EnergyPlugin.* n'existe pas non plus. Corrigé le 2026-08-25 contre JSONRPC.Introspect sur .75 (plugin 1.15.2+etm15).

nymeaService.call('NymeaEnergy.SetChargingInfo', {
  'chargingInfo': {
    'evChargerId': chargerId,              // Uuid — requis
    'chargingMode': 'ChargingModeEco',     // requis, énumération ci-dessous
    'targetPercentage': 80,                // o: Uint — charge cible
    'endDateTime': 1786250400,             // o: Uint — horodatage, PAS une chaîne
    'repeatDays': [1, 2, 3, 4, 5],         // o: Int[]
    'assignedCarId': carId,                // o: Uuid
    'spotMarketChargingEnabled': false,    // o: Bool
    'dailySpotMarketPercentage': 30,       // o: Uint
  }
});

chargingState est en lecture seule (r:) dans la charge utile : ChargingStateIdle · ChargingStateSurplusCharging · ChargingStateSpotMarketCharging · ChargingStateTimeRequirement.

Énumération ChargingMode — les vrais noms

ChargingModeNormal · ChargingModeEco · ChargingModeEcoWithTargetTime · ChargingModeEcoWithMinCurrent · ChargingModeEcoMinWithTargetTime

Modes UI EV — 3 boutons + option échéance

[PV]  [Min+PV]  [Boost]
        ↓
  Option échéance ? → charge cible (targetPercentage) + heure (endDateTime)

PV                → ChargingModeEco
Min+PV            → ChargingModeEcoWithMinCurrent
Boost             → ChargingModeNormal
PV + échéance     → ChargingModeEcoWithTargetTime      + targetPercentage + endDateTime
Min+PV + échéance → ChargingModeEcoMinWithTargetTime   + targetPercentage + endDateTime

⚠️ minCurrent n'existe nulle part. Le mode ChargingModeEcoWithMinCurrent est bien dans l'énumération, mais ChargingInfo ne porte aucun champ de courant minimum. Soit il est fixé ailleurs (paramètre du Thing borne), soit il manque côté plugin. Ne pas proposer de réglage de courant minimum tant que ce n'est pas tranché — un champ qui ne part nulle part est pire que pas de champ.


APIs JSON-RPC consommées

Energy.* — nymea-experience-plugin-energy · 5 méthodes

Méthode Usage Tier
GetPowerBalance Dashboard temps réel Community
GetPowerBalanceLogs(sampleRate, from, to) Historique — envoyer les bons from/to pour 90j Community
GetThingPowerLogs(sampleRate, thingIds[], from, to) Historique par device Community
GetRootMeter Lire le compteur racine configuré Community
SetRootMeter(rootMeterThingId) Config installateur Community

Energy.* s'arrête là. SetChargingMode n'existe pas — ni ici, ni ailleurs.

NymeaEnergy.* — powersync-energy-plugin-etm · 20 méthodes / 14 notifications

⚠️ Le namespace est NymeaEnergy, pas EnergyPlugin. Ce dernier n'existe pas. Source d'autorité : energyplugin/nymeaenergyjsonhandler.cpp et JSONRPC.Introspect. Ce n'est pas dans OPTIMIZER_PROTOCOL.md — ce document décrit le protocole plugin ↔ optimiseur (couche A ↔ couche B), pas la frontière JSON-RPC de l'app.

Charges pilotées

Méthode Usage Tier
GetLoadConfig Lire les charges déclarées Community
SetLoadConfig(loadConfigs[]) Écrire — remplace TOUT l'ensemble, refus en bloc Community
GetLoadTelemetry État runtime de l'arbitrage (budget, allocation, verrous, motifs) Community
ClearLoadFault(loadId) Lever le verrou de défaut — l'ack ne prouve rien Community

Recharge EV

Méthode Usage Tier
GetChargingInfos(evChargerId) Lire config EV Community
SetChargingInfo(chargingInfo) Écrire config EV — voir le bloc plus haut Community
GetChargingSchedules(evChargerId) Planning EV Community
GetLockOnUnplug / SetLockOnUnplug(Bool) Verrouillage au débranchement Community

Réglages globaux d'installation

Méthode Usage Tier
GetPhasePowerLimit / SetPhasePowerLimit(Uint) AMPÈRES par phase, pas des watts. 0 = désactivé, et coupe toute la recharge intelligente Community
GetAcquisitionTolerance / SetAcquisitionTolerance(Double) Seuil de surplus au démarrage — unité non documentée par la box Community
GetBatteryLevelConsideration / SetBatteryLevelConsideration(Double) Part du stockage prise en compte Community

Tarifs dynamiques

Méthode Usage Tier
GetAvailableSpotMarketProviders Liste providers Predict AI
GetSpotMarketConfiguration / SetSpotMarketConfiguration(enabled, providerId) Config tarif dynamique Predict AI
GetSpotMarketScoreEntries(date) Cotations aWATTar Predict AI
GetEnergyRatios Autoconsommation / autonomie Community

Notifications — LoadConfigChanged · LoadTelemetryChanged · ChargingInfoAdded/Changed/Removed · ChargingSchedulesChanged · EnergyRatiosChanged · PhasePowerLimitChanged · AcquisitionToleranceChanged · BatteryLevelConsiderationChanged · LockOnUnplugChanged · SpotMarketConfigurationChanged · SpotMarketStatusChanged · SpotMarketScoreEntriesChanged

AirConditioning.* — nymea-experience-plugin-airconditioning · 8 méthodes

Méthode Usage Tier
GetZones Afficher zones PAC/thermostat Auto
AddZone / RemoveZone CRUD de zones Auto
SetZoneName Renommer Auto
SetZoneThings(zoneId, thermostats[], valves[], indoorSensors[], outdoorSensors[], windowSensors[], notifications[]) Rattacher les appareils Auto
SetZoneSetpointOverride(zoneId, mode, setpointOverride, minutes) Dérogation manuelle Auto
SetZoneStandbySetpoint Consigne de réduit Auto
SetZoneWeekSchedule Planning 7 jours Auto

ZoneInfo est presque entièrement en lecture (r:) : température, humidité, PM2.5, COV, consigne courante, dérogation et sa fin, listes d'appareils. On écrit par les six Set*, jamais par un objet complet.

Rules.* — nymea core

Méthode Usage Tier
GetRules Lecture automatisations Community
AddRule Créer règle HP/HC, surplus → relais Community
RemoveRule / EditRule Gérer règles Community

État des écrans

Écran État Action requise
Dashboard (Sankey + EV card) ✅ Corriger API EV + brancher notifications
EnergyScreen (4 onglets) ✅ Ajouter sélecteur plage 90j
ThingsScreen + ThingDetail ✅ —
FavoritesScreen ✅ Persister dans SharedPreferences
InstallerMode (PIN SHA-256) ✅ —
RoleConfigFlow wizard ⚠️ Stub Brancher sur vrais RPC
TariffScreen ⚠️ Stub Brancher SetSpotMarketConfiguration
SchedulerScreen ⚠️ Stub Brancher GetChargingSchedules
TimelineScreen ⚠️ Stub Brancher scheduler réel
AirConditioning zones ❌ Absent Créer (Auto)
Rules UI (automatisations) ❌ Absent Créer (Community)
DeveloperScreen ❌ Vide Créer
AboutScreen ❌ Vide Créer

Persistance — tout doit survivre au redémarrage

Donnée État Action
Adresse serveur, PIN, préférences UI ✅ SharedPreferences —
RoleAssignments ❌ Mémoire Persister SharedPreferences
FavoriteWidgets ❌ Mémoire Persister SharedPreferences
TariffConfig / HcHpConfig ❌ Mémoire Persister SharedPreferences
SchedulerConfig ❌ Mémoire Persister SharedPreferences

Feature gating par tier

// TierProvider — à créer, lit le tier depuis le plugin via RPC
// En attendant : valeur par défaut 'community'

if (tierProvider.tier >= Tier.auto) {
  // afficher feature Auto
}

// Utiliser pro_lock_badge.dart (déjà présent) pour verrouiller visuellement

Features par tier

Feature Community Auto Predict AI
Dashboard temps réel ✅ ✅ ✅
Config EV (SetChargingInfo) ✅ ✅ ✅
Tarif HP/HC statique ✅ ✅ ✅
UI automatisations (Rules.*) ✅ ✅ ✅
Historique 90 jours 🔒 ✅ ✅
Wizard onboarding 🔒 ✅ ✅
Zones PAC/ECS (AirConditioning.*) 🔒 ✅ ✅
Prévision solaire Open-Meteo 🔒 ✅ ✅
Notifications d'anomalies 🔒 ✅ ✅
Accès distant sécurisé 🔒 ✅ ✅
Tarifs dynamiques aWATTar 🔒 🔒 ✅
Accès fonctionnalités beta 🔒 🔒 ✅

Thème — utiliser app_theme.dart systématiquement

primaryGreen  solarYellow  gridGray   homeBlue
batteryGreen  boostRed     pvGreen    minPvBlue  accentTeal

Règles de modification

  • Tout nouvel écran → valider maquette avec Patrick avant de coder
  • Tout nouvel appel RPC → vérifier dans INTERFACE.md que la méthode existe
  • Piloter un EV = NymeaEnergy.SetChargingInfo (Energy.SetChargingMode n'existe pas)
  • Vérifier ce fichier contre JSONRPC.Introspect avant chaque lot. Trois fois le 2026-08-25, la doc locale était en retard sur la machine et a envoyé un agent dans le vide. La sonde tient en une ligne : dart tools/rpc/probe.dart 192.168.1.75 JSONRPC.Introspect
  • Toujours tester la persistance : killer l'app et vérifier que les données survivent
  • Flavors à configurer : com.etm-powersync.community / .auto / .predictai