diff --git a/TODO.md b/TODO.md index 5844e35..d79f721 100644 --- a/TODO.md +++ b/TODO.md @@ -35,6 +35,17 @@ prêtes, les motifs n'ont **jamais été vus sur machine**. Signaler la première occurrence réelle. Ne rien bâtir autour d'ici là. +- [ ] ⏳ **Deux grandeurs de la V2C attendent le plugin — ne rien câbler d'ici là.** + Mesuré sur `.75` le 2026-08-28, sonde `tools/rpc/evcharger_watch.dart`. + - `chargeEnergy` est **exact pendant la charge** et **remis à zéro à chaque + interruption** — donc à chaque nuage sur du pilotage par surplus. Le plugin V2C + publiera un `sessionEnergy` construit ; attendre celui-là, et **n'accumuler rien côté + app**. D'ici là, la condition C1 de LM-1009 s'applique en vrai sur cette borne : + « pas mesurable » et « rien livré » sont deux états différents, et zéro dit le second. + - `powerL1/L2/L3` sont des **ampères mal nommés** ; le renommage `currentL1/L2/L3` + (unité Ampere) est en cours côté plugin. Ne pas les afficher avant son déploiement, et + **n'écrire aucune conversion côté app** — elle survivrait au correctif. + - [ ] **`EVChargingCard` n'est montée par AUCUN écran** (constaté le 2026-08-27 — le tableau de bord utilise `features/dashboard/widgets/`). Soit elle rejoint un écran, soit elle part ; en l'état c'est du code mort qui a l'air vivant. diff --git a/lib/widgets/ev_charging_card.dart b/lib/widgets/ev_charging_card.dart index 9230256..d2ccd50 100644 --- a/lib/widgets/ev_charging_card.dart +++ b/lib/widgets/ev_charging_card.dart @@ -472,9 +472,16 @@ class _ModeButton extends StatelessWidget { /// /// **aucun SOC de véhicule n'existe dans ce contrat.** Vérifié sur `.75` le 2026-08-27, /// sur les 58 classes de la box : aucune classe portant l'interface `evcharger` ne -/// déclare d'état de charge, aucune classe ne porte l'interface `car`, et les deux bornes -/// du banc publient `pluggedIn`, `charging`, `maxChargingCurrent`, `phaseCount`, -/// `sessionEnergy` — jamais un pourcentage. +/// déclare d'état de charge, aucune classe ne porte l'interface `car`, et les bornes du +/// banc publient `pluggedIn`, `charging`, `maxChargingCurrent` — jamais un pourcentage. +/// +/// **Et l'énergie livrée n'est pas là non plus, sur la borne du banc.** La V2C Trydan +/// arrivée le 2026-08-28 ne publie ni `sessionEnergy` ni `phaseCount` : elle publie +/// `chargeEnergy`, exact pendant la charge et **remis à zéro à chaque interruption** — +/// donc à chaque nuage sur du pilotage par surplus. Le détail et la mesure sont dans +/// `LoadMechanism` (`models/load_telemetry.dart`). Tant que le plugin ne publie pas le +/// `sessionEnergy` construit qu'il prépare, l'avancement de LM-1009 se dit **« pas +/// mesurable »** — et surtout pas zéro, qui se lirait « rien livré ». /// /// Le seul `batteryLevel` de l'installation est celui de la **batterie de la maison** /// (Fronius Storage, 50 %), rendu visible par le correctif `energystorage` du plugin diff --git a/test/merged_config_screen_test.dart b/test/merged_config_screen_test.dart index 0d62174..4df16b2 100644 --- a/test/merged_config_screen_test.dart +++ b/test/merged_config_screen_test.dart @@ -227,6 +227,101 @@ void main() { FieldSupport.unknown); }); }); + + // ─────────────────────────────────────────────────────────────────────────── + // Les deux notes de la configuration réelle du 2026-08-28 — MONTÉES, pas + // seulement calculées. + // + // `tiedByPriority` et `GroupedOrder.isContiguous` ont leurs tests de fonction dans + // `evcharger_config_test.dart`, et ils passaient déjà le 2026-08-27 : la contiguïté + // était calculée, exposée par le provider, et **affichée nulle part**. Un test de + // fonction ne pouvait pas le voir. Ceux-ci montent l'écran et cherchent le texte. + group('terrain 2026-08-28 — les notes s\'affichent VRAIMENT', () { + List> v2cConfig() => + (_fixture('loadconfig_hems75_v2c.json')['loadConfigs'] as List) + .cast>(); + + testWidgets('la note de rangs ex æquo est à l\'écran, et elle nomme les charges', + (tester) async { + final config = LoadConfigProvider()..adoptForTest(v2cConfig()); + final telemetry = LoadTelemetryProvider() + ..adoptForTest({'degradedMode': false, 'loads': const []}); + + await tester.pumpWidget(_harness(config: config, telemetry: telemetry)); + await tester.pump(); + + final note = find.textContaining('Rangs à égalité'); + expect(note, findsOneWidget); + // Nommer les charges est le point : « des rangs sont à égalité » sans dire + // lesquelles laisse l'installateur chercher dans cinq cartes. + final texte = tester.widget(note).data!; + expect(texte, contains('chauffe-eau')); + expect(texte, contains('V2C Trydan')); + }); + + testWidgets('la note d\'ordre entrelacé est à l\'écran, et annonce l\'effet de l\'enregistrement', + (tester) async { + final config = LoadConfigProvider()..adoptForTest(v2cConfig()); + final telemetry = LoadTelemetryProvider() + ..adoptForTest({'degradedMode': false, 'loads': const []}); + + await tester.pumpWidget(_harness(config: config, telemetry: telemetry)); + await tester.pump(); + + final note = find.textContaining('n\'est pas groupé par domaine'); + expect(note, findsOneWidget); + // Ce que l'enregistrement fera doit être dit AVANT, pas constaté après. + expect(tester.widget(note).data!, contains('REGROUPERA')); + }); + + testWidgets('un ordre déjà groupé et classé ne montre AUCUNE des deux notes', + (tester) async { + // Le contrôle négatif : les deux notes doivent disparaître d'elles-mêmes au + // premier vrai réglage, sinon elles deviennent du décor qu'on n'ouvre plus. + final config = LoadConfigProvider()..adoptForTest(_benchConfig()); + final telemetry = LoadTelemetryProvider() + ..adoptForTest({'degradedMode': false, 'loads': const []}); + + await tester.pumpWidget(_harness(config: config, telemetry: telemetry)); + await tester.pump(); + + expect(find.textContaining('Rangs à égalité'), findsNothing); + expect(find.textContaining('n\'est pas groupé par domaine'), findsNothing); + }); + + testWidgets('THING_MISSING se lit « appareil absent », et n\'offre RIEN à lever', + (tester) async { + // ⚠️ Télémétrie COMPOSÉE ici, pas capturée : le banc publiait `WRITE_FAILED` sur + // cette charge au dernier relevé (l'adaptateur épuise son échelle d'écriture avant + // que `THING_MISSING` ne sorte). Ce test éprouve donc le RENDU du code, sur le + // `loadId` réel de la Terra AC supprimée — pas la présence du code sur la box. + const terra = '{88160e45-7df2-48cc-9639-f0d15ae6e382}'; + final config = LoadConfigProvider()..adoptForTest(v2cConfig()); + final telemetry = LoadTelemetryProvider()..adoptForTest({ + 'degradedMode': false, + 'loads': [ + { + 'loadId': terra, + 'allocatedW': 0, + 'available': false, + 'faultCode': 'THING_MISSING', + 'decision': {'code': 'NO_BUDGET', 'params': const {}}, + }, + ], + }); + + await tester.pumpWidget(_harness(config: config, telemetry: telemetry)); + await tester.pump(); + + // Le bouton ne peut pas tenir sa promesse — il n'existe pas. + expect(find.text('Lever le défaut'), findsNothing); + expect(find.textContaining('La levée est une action délibérée'), findsNothing); + // À la place : les deux gestes qui, eux, résolvent le cas. + expect(find.textContaining('Réinstallez-le dans nymea'), findsOneWidget); + // Et le libellé ne doit pas se lire « votre configuration est cassée ». + expect(find.textContaining('n\'existe plus dans nymea'), findsOneWidget); + }); + }); } extension on RpcSchema { diff --git a/tools/rpc/README.md b/tools/rpc/README.md index 331d093..1378cdd 100644 --- a/tools/rpc/README.md +++ b/tools/rpc/README.md @@ -23,6 +23,7 @@ dart tools/rpc/