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
11 KiB
Agent App — etm_powersync_app
Lire aussi le
CLAUDE.mddu 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.SetChargingModen'est pas une méthode de nymea — il n'y en a que cinq dansEnergy.*, et celle-là n'en fait pas partie : l'appel échouerait sur « No such method ». Et le namespaceEnergyPlugin.*n'existe pas non plus. Corrigé le 2026-08-25 contreJSONRPC.Introspectsur.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
⚠️
minCurrentn'existe nulle part. Le modeChargingModeEcoWithMinCurrentest bien dans l'énumération, maisChargingInfone 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à.SetChargingModen'existe pas — ni ici, ni ailleurs.
NymeaEnergy.* — powersync-energy-plugin-etm · 20 méthodes / 14 notifications
⚠️ Le namespace est
NymeaEnergy, pasEnergyPlugin. Ce dernier n'existe pas. Source d'autorité :energyplugin/nymeaenergyjsonhandler.cppetJSONRPC.Introspect. Ce n'est pas dansOPTIMIZER_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 |
ZoneInfoest 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 sixSet*, 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.mdque la méthode existe - Piloter un EV =
NymeaEnergy.SetChargingInfo(Energy.SetChargingModen'existe pas) - Vérifier ce fichier contre
JSONRPC.Introspectavant 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