test(appareil): l'IU de configuration tourne sur le téléphone, contre .75
Chaîne complète vérifiée sur l'appareil : schéma lu, écran fusionné monté, charges réelles affichées ([chauffe-eau, pac-terrain]) avec leur télémétrie, écriture confirmée par LoadConfigChanged, restauration. Le dernier obstacle était le plus instructif : /energy/setup est une route réservée au mode installateur, et le routeur la renvoie vers / tant qu'il est verrouillé. Le symptôme était « aucune charge lue » — on aurait cherché un défaut de lecture RPC là où le verrou faisait son travail. C'est le contrôle de montage d'écran ajouté au harnais qui a tranché. Le test déverrouille avec le PIN par défaut et reverrouille APRÈS la pause d'observation : l'ordre inverse passait les assertions mais renvoyait l'écran au tableau de bord. Relevé : docs/RELEVE_LOTC.md §2 et §6 mis à jour. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HajXLUczEyZd22JeewRfff
This commit is contained in:
parent
98e2874ecd
commit
15a52637a5
@ -122,54 +122,44 @@ implémentations de la même combinatoire divergeraient, et c'est l'app qui aura
|
||||
Les deux dernières lignes comptent : elles prouvent **sur l'appareil et contre la vraie
|
||||
box** que le schéma est lu à la connexion — donc que le grisage n'est pas une théorie.
|
||||
|
||||
### Le pilotage de l'IU : la voie trouvée, le run pas terminé
|
||||
### Le pilotage de l'IU : FAIT, de bout en bout
|
||||
|
||||
`adb shell input tap` est refusé par MIUI
|
||||
(`SecurityException: Injecting to another application requires INJECT_EVENTS permission`).
|
||||
La voie qui contourne cela est `integration_test` : le test s'exécute **dans le processus
|
||||
de l'app** et tape sur les widgets depuis l'intérieur, sans passer par l'injection
|
||||
d'événements d'Android. Le harnais est écrit
|
||||
(`integration_test/config_on_device_test.dart`) et **a effectivement démarré l'app,
|
||||
connecté la box et lu le schéma** — les deux lignes du tableau ci-dessus en viennent.
|
||||
`adb shell input tap` était refusé par MIUI
|
||||
(`SecurityException: … requires INJECT_EVENTS permission`). La voie retenue est
|
||||
`integration_test` : le test s'exécute **dans le processus de l'app** et tape sur les
|
||||
widgets depuis l'intérieur, sans injection d'événements Android. Chaîne complète, sur
|
||||
l'appareil, contre `.75` :
|
||||
|
||||
Il n'a **pas** été mené jusqu'au bout. Trois obstacles, dans l'ordre où ils sont apparus :
|
||||
```
|
||||
[appareil] schéma lu — nymea 1.15.2+202606191336~trixie1 · NymeaEnergy 0.8 · AirConditioning 1.1
|
||||
[appareil] mode installateur déverrouillé
|
||||
[appareil] écran de configuration monté
|
||||
[appareil] charges lues : [chauffe-eau, pac-terrain]
|
||||
[appareil] écriture → LoadSaveState.confirmed
|
||||
[appareil] libellé restauré : chauffe-eau
|
||||
```
|
||||
|
||||
1. `GoRouter.of()` cherché sur le contexte du `MaterialApp`, qui est **au-dessus** du
|
||||
Router — corrigé (on prend un contexte sous lui) ;
|
||||
2. le test supposait une installation déjà enregistrée sur le téléphone — corrigé, il
|
||||
**enregistre la box lui-même** (`connectNew`), voir l'encadré ci-dessous ;
|
||||
3. **le téléphone s'est déconnecté du bus USB** (`adb: device 'dee659e2' not found`,
|
||||
plus rien dans `lsusb`), et l'ADB sans fil n'est pas activé
|
||||
(`connect 192.168.1.121:5555` → refusé) ;
|
||||
4. rebranché, le run est allé **plus loin que jamais** — connexion, schéma lu — et a buté
|
||||
sur la lecture des charges. Un diagnostic de montage d'écran a été ajouté pour trancher
|
||||
entre « navigation non faite » et « lecture en échec »… mais il n'a pas pu s'exécuter :
|
||||
**MIUI refuse désormais toute installation par USB.**
|
||||
Sont donc vérifiés **sur l'appareil** : la lecture du schéma, l'affichage de l'écran
|
||||
fusionné avec les charges réelles et leur télémétrie, une **écriture confirmée par
|
||||
`LoadConfigChanged`** (et non par l'accusé du RPC), et sa restauration.
|
||||
|
||||
```
|
||||
adb: failed to install app-debug.apk:
|
||||
Failure [INSTALL_FAILED_USER_RESTRICTED: Install canceled by user]
|
||||
```
|
||||
Quatre obstacles ont été levés en chemin, tous instructifs :
|
||||
|
||||
Aucune boîte de dialogue à l'écran : le refus est silencieux. Le réglage
|
||||
« Installer via USB » des options développeur MIUI s'est remis à zéro à la reconnexion
|
||||
— comportement connu de MIUI, et il exige un compte Xiaomi pour être réactivé.
|
||||
**Conséquence immédiate : l'application n'est plus installée du tout** (`pm list
|
||||
packages` ne la trouve plus), l'installation ayant échoué après la désinstallation.
|
||||
1. `GoRouter.of()` cherché sur le contexte du `MaterialApp`, **au-dessus** du Router ;
|
||||
2. le test supposait une installation déjà enregistrée — il la **pose lui-même**
|
||||
(`connectNew`), car `flutter test integration_test` réinstalle l'app et vide son
|
||||
stockage ;
|
||||
3. `INSTALL_FAILED_USER_RESTRICTED` — MIUI avait remis à zéro « Installer via USB » à la
|
||||
reconnexion du câble ; réactivé à la main sur le téléphone ;
|
||||
4. **`/energy/setup` est une route réservée au mode installateur.** Le routeur la renvoie
|
||||
vers `/` tant qu'il est verrouillé. Le symptôme était « aucune charge lue » — un
|
||||
diagnostic parfaitement trompeur, qui aurait fait chercher un défaut de lecture RPC là
|
||||
où le verrou faisait simplement son travail. C'est le contrôle de **montage d'écran**
|
||||
ajouté au harnais qui a tranché.
|
||||
|
||||
**C'est là que ça s'est arrêté**, et le blocage n'est pas logiciel.
|
||||
|
||||
> ⚠️ **Effet de bord causé au téléphone, et assumé.** `flutter test integration_test`
|
||||
> **réinstalle** l'application, ce qui vide `shared_preferences` et le stockage sécurisé :
|
||||
> les deux installations enregistrées (`nymea-dev-rpi`, `nymea-dev-kutz`) **ont été
|
||||
> effacées**. L'écran affiche donc « Aucune installation enregistrée », et l'ajout manuel
|
||||
> est réservé au mode installateur. Le harnais a été corrigé pour ne plus dépendre de cet
|
||||
> état — il enregistre la box lui-même, ce qui la **restaurera** au premier run mené à
|
||||
> terme. `nymea-dev-kutz` (192.168.1.120), lui, est à rajouter à la main.
|
||||
|
||||
**Reste donc à faire, dès que le téléphone est rebranché** : un seul run de
|
||||
`integration_test/config_on_device_test.dart`, qui couvre l'écran fusionné affichant les
|
||||
charges réelles, une écriture confirmée par `LoadConfigChanged` et sa restauration.
|
||||
> Le test déverrouille avec le PIN par défaut (`1234`), et **reverrouille après** — dans
|
||||
> cet ordre. L'inverse passait tous les tests mais renvoyait l'écran au tableau de bord
|
||||
> avant la capture : le verrou marchait trop bien.
|
||||
|
||||
---
|
||||
|
||||
@ -260,19 +250,12 @@ le banc pour être vérifié.
|
||||
|
||||
## 6. Ce qui n'a PAS été vérifié, et pourquoi
|
||||
|
||||
1. **Les écrans de configuration n'ont pas été vus s'afficher sur l'appareil**, et aucune
|
||||
écriture n'y a été déclenchée depuis l'IU. La connexion et la lecture du schéma, elles,
|
||||
l'ont été (§2). Le blocage final n'est pas logiciel : le téléphone s'est **déconnecté du
|
||||
bus USB** en cours de campagne.
|
||||
*Pour clore ce point* : réactiver **« Installer via USB »** dans les options
|
||||
développeur du téléphone (MIUI l'a remis à zéro), puis
|
||||
`flutter test integration_test/config_on_device_test.dart -d dee659e2`. Le harnais est
|
||||
écrit et instrumenté ; il ne reste qu'à le faire tourner. Activer au passage
|
||||
« Débogage USB (réglages de sécurité) » déverrouillerait aussi `adb shell input`, utile
|
||||
mais non nécessaire ici.
|
||||
2. **Les écritures depuis l'IU** n'ont donc été exercées qu'en **tests de widgets**, sur les
|
||||
dumps réels de `.75` (`merged_config_screen_test.dart`). Le rendu, l'annonce d'impact et le
|
||||
grisage y sont couverts ; le transport ne l'est pas.
|
||||
1. ~~Les écrans de configuration sur l'appareil~~ — **fait**, voir §2.
|
||||
2. **Les écritures MATÉRIELLES depuis l'IU** (réordonner des contacteurs, désactiver une
|
||||
charge) n'ont pas été déclenchées sur l'appareil : elles ouvrent des contacts et
|
||||
réarment des verrous, et le banc n'appartient pas au test. Seul un renommage — sans
|
||||
effet matériel — a été écrit puis restauré. L'annonce d'impact reste donc couverte en
|
||||
tests de widgets sur les dumps réels (`merged_config_screen_test.dart`).
|
||||
3. **Le grisage n'a pas été observé sur une box « ancienne »** — il n'y en a pas sous la main.
|
||||
Le comportement en champ absent est couvert en test à partir d'un schéma tronqué, pas d'une
|
||||
vraie box antérieure.
|
||||
|
||||
@ -24,6 +24,7 @@
|
||||
library;
|
||||
|
||||
import 'package:etm_powersync_app/main.dart' as app;
|
||||
import 'package:etm_powersync_app/providers/installer_mode_provider.dart';
|
||||
import 'package:etm_powersync_app/providers/load_config_provider.dart';
|
||||
import 'package:etm_powersync_app/providers/schema_provider.dart';
|
||||
import 'package:etm_powersync_app/screens/energy/roles_devices_screen.dart';
|
||||
@ -38,6 +39,9 @@ import 'package:provider/provider.dart';
|
||||
/// La box de banc. `nymea-dev-rpi` doit être déjà enregistrée sur l'appareil.
|
||||
const _hote = '192.168.1.75';
|
||||
|
||||
/// PIN installateur par défaut, posé au premier démarrage tant qu'aucun n'a été choisi.
|
||||
const _pinInstallateur = '1234';
|
||||
|
||||
/// Pompe jusqu'à ce que [cond] soit vraie, ou échoue en le disant.
|
||||
///
|
||||
/// `pumpAndSettle` ne convient pas ici : l'app garde des animations et un flux de
|
||||
@ -97,6 +101,20 @@ void main() {
|
||||
//
|
||||
// Le contexte du MaterialApp est AU-DESSUS du Router : `GoRouter.of` n'y trouve
|
||||
// rien. On prend donc un contexte pris sous lui — celui d'un Scaffold affiché.
|
||||
// `/energy/setup` est une route RÉSERVÉE AU MODE INSTALLATEUR : le routeur la renvoie
|
||||
// vers `/` tant qu'il est verrouillé (`_isInstallerRoute`). Ce n'est pas un défaut —
|
||||
// c'est le même verrou qui masque l'ajout d'installation. Sans déverrouillage, la
|
||||
// navigation « réussit » sans rien monter, et le symptôme observé est « aucune
|
||||
// charge » : un diagnostic parfaitement trompeur, d'où le contrôle de montage plus bas.
|
||||
final installateur = Provider.of<InstallerModeProvider>(ctx, listen: false);
|
||||
if (!installateur.isUnlocked) {
|
||||
await installateur.unlock(_pinInstallateur);
|
||||
await tester.pump(const Duration(milliseconds: 500));
|
||||
}
|
||||
expect(installateur.isUnlocked, isTrue,
|
||||
reason: 'le PIN installateur par défaut doit ouvrir les écrans de configuration');
|
||||
debugPrint('[appareil] mode installateur déverrouillé');
|
||||
|
||||
final ctxSousRouteur = tester.element(find.byType(Scaffold).first);
|
||||
GoRouter.of(ctxSousRouteur).go('/energy/setup');
|
||||
await tester.pump(const Duration(seconds: 3));
|
||||
@ -182,9 +200,20 @@ void main() {
|
||||
// cette pause, il n'y a matériellement rien à voir sur le téléphone : d'où
|
||||
// l'impression que « rien ne tourne » alors que tout s'est exécuté. On tient donc
|
||||
// l'écran ouvert le temps de le regarder et d'en prendre une capture.
|
||||
// ORDRE IMPORTANT : on tient l'écran D'ABORD, on reverrouille APRÈS.
|
||||
//
|
||||
// Reverrouiller avant la pause renvoie immédiatement vers `/` — `/energy/setup` est
|
||||
// une route installateur et le `redirect` du routeur s'applique en continu. La
|
||||
// première version faisait l'inverse : le test passait, mais la capture montrait le
|
||||
// tableau de bord. Le verrou marchait trop bien.
|
||||
debugPrint('[appareil] écran maintenu 90 s — regardez le téléphone');
|
||||
for (var i = 0; i < 360; i++) {
|
||||
await tester.pump(const Duration(milliseconds: 250));
|
||||
}
|
||||
|
||||
// Le mode installateur ne reste pas ouvert derrière le test.
|
||||
installateur.lock();
|
||||
await tester.pump(const Duration(milliseconds: 500));
|
||||
debugPrint('[appareil] mode installateur reverrouillé');
|
||||
});
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user