Patrick Schurig eac1dde4ee docs: recalage sur la rév. 3 du contrat charge pilotée
Le miroir INTERFACE_etmvariableload.md est passé en rév. 3 pendant la session (corps
identique au canonique du dépôt plugin, vérifié). Son §10 s'adresse nommément à l'agent
app — et il RATIFIE les maquettes dessinées aujourd'hui : relays[] = {thingId, powerW}
pour l'ECS relais, maxPowerW seul pour le continu, powerLevels absents de la config et
dérivés par le routeur.

Trois recalages :

- Le picker de relais filtre sur l'interface nymea « power » et présente les things par
  NOM — nommé dans la maquette, il ne l'était pas.
- Le point 4 du brief plugin est tranché par la rév. 3 : la duplication de la
  combinatoire est voulue (l'app dérive pour montrer, le routeur pour exécuter). La
  demande devient donc un moyen de détecter la divergence — la télémétrie publie déjà
  maxStageW, l'app peut y confronter le maximum de sa propre liste.
- En-tête du pont VM Service, que le commit précédent avait laissé dans sa forme brute.

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

77 lines
3.1 KiB
Dart

import 'dart:async';
import 'dart:convert';
import 'dart:io';
// Pont VM Service — rend visibles les traces `dart:developer log()` de l'app.
//
// ## Pourquoi ce fichier existe
//
// `flutter run` **n'imprime pas** ces traces sur stdout : elles partent au VM Service, ni sur
// la console, ni dans `logcat`. Tout ce que `NymeaService._log()` journalise — locale demandée
// et retenue, aller-retours RPC, séquence d'écriture, télémétrie poussée — est donc invisible
// depuis le poste tant qu'on n'est pas branché ici.
//
// Éprouvé le 2026-08-25, premier passage de l'app sur appareil (Redmi Note 9S) : sans ce pont,
// aucun constat du journal de vérification n'était atteignable.
//
// ## Usage
//
// flutter run -d <device> --use-application-binary=build/app/outputs/flutter-apk/app-debug.apk
// # relever « A Dart VM Service on … is available at: http://127.0.0.1:PORT/TOKEN=/ »
// dart tools/devlog/vm_logs.dart 'ws://127.0.0.1:PORT/TOKEN=/ws'
//
// Le `/ws` final est obligatoire, et les guillemets aussi — le jeton contient un `=`.
//
// ## Le piège d'horodatage, déjà payé une fois
//
// Le VM Service livre **l'historique bufférisé d'un coup** au moment du `streamListen`.
// Horodater à la réception écrase donc toute la chronologie sous une seule seconde — c'est ce
// qui est arrivé au premier essai. On lit `event.timestamp`, jamais `DateTime.now()`.
Future<void> main(List<String> args) async {
final uri = args.first;
final ws = await WebSocket.connect(uri);
var id = 0;
void call(String method, [Map<String, dynamic> params = const {}]) {
ws.add(jsonEncode({
'jsonrpc': '2.0',
'id': (++id).toString(),
'method': method,
'params': params,
}));
}
ws.listen((raw) {
final m = jsonDecode(raw as String) as Map<String, dynamic>;
if (m['method'] != 'streamNotify') return;
final ev = (m['params'] as Map)['event'] as Map;
final kind = ev['kind'];
// L'heure de l'ENREGISTREMENT, pas celle de la réception : le VM Service livre
// l'historique bufférisé d'un coup au moment du streamListen, et horodater à la
// réception écraserait toute la chronologie sous une seule seconde.
final micros = (ev['timestamp'] as num?)?.toInt();
final ts = micros == null
? '--:--:--'
: DateTime.fromMillisecondsSinceEpoch(micros)
.toIso8601String()
.substring(11, 23);
if (kind == 'Logging') {
final rec = ev['logRecord'] as Map;
final name = (rec['loggerName'] as Map?)?['valueAsString'] ?? '';
final msg = (rec['message'] as Map?)?['valueAsString'] ?? '';
print('$ts [$name] $msg');
} else if (kind == 'WriteEvent') {
final bytes = ev['bytes'] as String?;
if (bytes != null) {
stdout.write('$ts [stdout] ${utf8.decode(base64.decode(bytes))}');
}
}
});
call('streamListen', {'streamId': 'Logging'});
call('streamListen', {'streamId': 'Stdout'});
call('streamListen', {'streamId': 'Stderr'});
print('--- branché sur $uri ---');
await Future<void>.delayed(const Duration(hours: 2));
}