Le lot plugin B-bis (1.15.2+etm15) est sur .75 et GetLoadConfig/GetLoadTelemetry
concordent : les critères 4 à 8 du brief deviennent atteignables.
- models/load_telemetry.dart : vue en lecture. timestamp/budget/mechanism/lock/
faultCode nullables — omis porte un sens que zéro ne porterait pas.
- services/telemetry_text.dart : rendu français des CODES (19 motifs, 3 défauts,
3 verrous, 4 mécanismes). Repli lisible sur un code inconnu ET sur un code connu
privé d'un paramètre : une phrase trouée a l'air d'une donnée, le code brut a
l'air de ce qu'il est.
- providers/load_telemetry_provider.dart : instantané + LoadTelemetryChanged,
fraîcheur à trois signaux (âge du timestamp, silence des trames, timestamp qui
cesse d'avancer), machine à états de ClearLoadFault qui ne conclut JAMAIS sur
l'acquittement du RPC.
- load_config_provider : assignDomain() en brouillon, énumération fermée.
- load_order_screen : budget, fraîcheur, mode dégradé, bloc par charge, encart
défaut, menu de domaine.
- nymea_service : getLoadTelemetry(), clearLoadFault(), aiguillage de la
notification. Une box sans la méthode s'annonce comme telle, pas comme en panne.
Seuil de fraîcheur à 180 s : le battement de cœur du plugin est de 60 s et le
timestamp avance d'un cycle par minute. Un seuil plus court crierait « données
anciennes » sur une installation vivante.
Fixtures v3 prises sur .75 après déploiement, dont un cycle porteur d'un verrou —
« lock » étant optionnel, un dump pris au mauvais moment ne l'éprouverait pas.
57 tests (30 avant), flutter analyze inchangé à 25 issues.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XPUo3RMr8SzK6qbFtfBm8H
Lot B côté app, partie NON bloquée par le lot plugin (voir §Limites).
§1 Locale (indépendant) — JSONRPC.Hello portait {} : l'app était en en_US de
fait alors que nymea gère la locale PAR CONNEXION (o:locale). Envoi de la
locale à chaque Hello, donc aussi après reconnexion subie (m_clientLocales
repart au défaut de la box à chaque socket). Locale demandée ET retenue
journalisées : un .qm introuvable rend exactement comme une absence de
traduction, sans cette trace un catalogue mal packagé passe inaperçu.
§2 i18n — flutter_localizations + intl + ARB (français seul). Aucune chaîne
en dur dans le nouvel écran. Les codes reçus de la box (domaine, mécanisme)
sont des clés ARB avec repli explicite sur code brut si l'app ne les connaît
pas : une box en avance sur l'app est un cas normal en parc déployé.
§3 Modèle — LoadConfigEntry est une VUE en lecture au-dessus de la map brute ;
l'écriture repart de la map d'origine (patched()). Pas de couche de filtrage
préventive : l'aller-retour neutre passe contre .75, donc l'écho verbatim est
sûr par construction (§7-1).
§4 Priorité à deux niveaux — groupByDomain / flattenToPayload. Le rang de
domaine se déduit de l'ordre d'apparition à plat ; la box ne sait rien des
groupes. Config non contiguë : signalée, ordre réel affiché, normalisation
seulement sur confirmation explicite. Miroir client de validateSet().
§5 Écritures asynchrones — LoadConfigProvider ne conclut jamais sur
l'acquittement du RPC : pending → LoadConfigChanged → confirmed|diverged.
NymeaEnergy.GetLoadConfig / SetLoadConfig deviennent RÉELS (ils étaient des
stubs journalisés). Conséquence traitée : EnergySetupProvider.persist()
n'émet plus setLoadConfig(buildLoadDescriptors()) — SetLoadConfig remplace
TOUT l'ensemble, et _commit() l'appelait à chaque changement d'UI : une
reconstruction depuis le modèle typé aurait écrasé la config réelle de la
box sans geste explicite.
Écran /energy/loads, gaté installateur (drawer + garde de routeur).
Limites — le lot plugin n'est PAS déployé sur .75 : GetLoadTelemetry,
LoadTelemetryChanged et o:domain sont absents (19 méthodes / 13 notifications
inchangées). Toute la moitié télémétrie du brief est donc hors d'atteinte, et
o:domain n'étant pas au schéma, la box rejetterait la clé (jsonvalidator.cpp:143).
Fixtures multi-domaines synthétiques : .75 n'a qu'une charge.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- supprime selfRate/autoRate de _parsePowerBalance, le getter autoconsommationW, les .abs() interim
- ratios dérivés par Δ-de-cumuls (§2.1/§8.1) dans lib/services/energy_ratios.dart
- reseed jour-roulant & Δ non-monotone ; gardes : dén≤0→null, pas de NaN, clamp [0,100]
- selfConsumptionPower net-signé sans .abs() (§2.2) ; signes nymea bruts préservés (§7.1)
- seam swappable vers un state plugin en Phase 2 (une seule fonction)
Interim (Phase 1). Gestion du signe d'affichage renvoyée à la Phase 4 (§1.1).
Réf. UI_DATA_CONTRACT.md rev.5.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NymeaService
- batterySOCSource : retourne {'thingId': 'sim-battery', 'stateName': 'soc'}
en simulation au lieu de null — le SOC est désormais fetchable
- fetchHistory en simulation : données SOC réalistes en % (0-100) avec
courbe charge solaire 8h-15h (20→85%) puis décharge soir (85→20%)
au lieu de valeurs sinus 100-900 W incorrectes
EnergyScreen
- initState : appelle startSimulation() si non connecté (comme les autres écrans)
- leftTitles : interval explicite (yMax/4) + SideTitleWidget pour ancrage correct
- gridData : horizontalInterval aligné sur yInterval
- rightTitles (SOC %) : SideTitleWidget + interval aligné
- bottomTitles : SideTitleWidget sur les deux graphes
- barChart leftTitles : interval + SideTitleWidget
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Persistance (FavoriteWidget)
- FavoriteWidget.fromJson / toJson ajoutés dans nymea_models.dart
- NymeaService : constructeur appelle _loadFavorites() au démarrage
- _saveFavorites() appelé après addFavorite / removeFavorite / reorderFavorites
- Clé SharedPreferences : 'etm_favorites_v1'
- Résistance aux données corrompues (try/catch sur fromJson)
Restyle (favorites_screen.dart)
- AppTheme → EtmTokens sur toutes les couleurs et typographies
- Valeurs en IBM Plex Mono (size 36 pour les grandes métriques)
- Cartes avec EtmTokens.cardShadow + border radius 22
- _EmptyState : bouton vert + étoile + message
- _AddSheet : DraggableScrollableSheet restyled, sans emoji, items visuellement
distingués (ajouté vs disponible)
- ReorderableDelayedDragStartListener pour le drag discret
- Simulation auto-démarrée si non connecté (comme Dashboard/Things)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>