Les règles d'affichage de `measurement` sont figées par test sur des trames fabriquées. Cette sonde les éprouve sur les trames réelles de la box — en important le modèle de l'app (LoadTelemetryEntry.fromJson), pas en relisant le JSON à sa façon : une lecture parallèle ne prouverait rien sur ce que l'écran fera. Elle montre ce qu'un écran regardé de loin ne distingue pas : `none` (bloc masqué, « pas mesurable ») d'une charge simplement absente de loads[] (rien à conclure), et la portée d'un écart selon `meter` ou `device`. Relevé du 2026-08-28 sur .75 en 1.15.2+etm26 : measuredW encore publié ? false chauffe-eau source=meter → Mesuré 1500 W · mesure indépendante : concordance = preuve pac-terrain source=meter → Mesuré 800 W · idem V2C Trydan absente de loads[] — pas `none` Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SQbZKrWqsMFP1Lh2jjjd9f
63 lines
2.8 KiB
Dart
63 lines
2.8 KiB
Dart
import 'dart:async';
|
|
import 'dart:convert';
|
|
import 'dart:io';
|
|
|
|
import 'package:etm_powersync_app/models/load_telemetry.dart';
|
|
|
|
// Lit `NymeaEnergy.GetLoadTelemetry` et dit, charge par charge, **ce que l'app afficherait**
|
|
// — en passant par le vrai modèle de l'app, pas par une lecture parallèle du JSON.
|
|
//
|
|
// ## Pourquoi cette sonde plutôt qu'un coup d'œil à l'écran
|
|
//
|
|
// Les règles d'affichage de `measurement` sont figées par test sur des trames fabriquées.
|
|
// Ceci vérifie la même chose sur les trames RÉELLES de la box, et c'est la seule façon de
|
|
// voir que le contrat déployé correspond à ce que l'app attend. Un écran regardé de loin ne
|
|
// distingue pas « pas mesurable » d'un bloc simplement absent.
|
|
//
|
|
// LECTURE SEULE. Usage : dart run tools/rpc/measurement_check.dart [host]
|
|
Future<void> main(List<String> args) async {
|
|
final host = args.isNotEmpty ? args[0] : '192.168.1.75';
|
|
final ws = await WebSocket.connect('ws://$host:4444')
|
|
.timeout(const Duration(seconds: 8));
|
|
final done = Completer<Map<String, dynamic>>();
|
|
|
|
ws.listen((d) {
|
|
final m = jsonDecode(d as String) as Map<String, dynamic>;
|
|
if (m['id'] == 2) done.complete(m);
|
|
});
|
|
ws.add(jsonEncode({'id': 1, 'method': 'JSONRPC.Hello', 'params': {}}));
|
|
ws.add(jsonEncode(
|
|
{'id': 2, 'method': 'NymeaEnergy.GetLoadTelemetry', 'params': {}}));
|
|
|
|
final r = await done.future.timeout(const Duration(seconds: 10));
|
|
await ws.close();
|
|
|
|
final p = r['params'] as Map<String, dynamic>;
|
|
final loads = (p['loads'] as List? ?? const []);
|
|
final brut = jsonEncode(p);
|
|
|
|
print('measuredW encore publié ? ${brut.contains('"measuredW"')} '
|
|
'(doit être false depuis 1.15.2+etm26)');
|
|
print('${loads.length} charge(s) dans loads[] — et `measurement` est présent pour '
|
|
'chacune, pour elles seules.\n');
|
|
|
|
for (final l in loads.whereType<Map>()) {
|
|
final e = LoadTelemetryEntry.fromJson(Map<String, dynamic>.from(l));
|
|
final m = e.measurement;
|
|
final ligne = switch (m.source) {
|
|
MeasurementSource.none =>
|
|
'bloc de mesure MASQUÉ — « pas mesurable » est un état, pas un vide',
|
|
MeasurementSource.meter =>
|
|
'Mesuré ${m.powerW!.round()} W · mesure indépendante : une concordance est une preuve',
|
|
MeasurementSource.device =>
|
|
'Mesuré ${m.powerW!.round()} W · publiée par l\'appareil : une concordance ne prouve rien',
|
|
MeasurementSource.inconnue =>
|
|
'Mesuré ${m.powerW?.round()} W · source inconnue de cette app, aucune portée affichée',
|
|
};
|
|
print(' ${e.loadId.padRight(38)} alloué ${e.allocatedW.round()} W');
|
|
print(' ${' '.padRight(38)} source=${m.source.name} → $ligne');
|
|
}
|
|
print('\nToute charge de GetLoadConfig ABSENTE de cette liste n\'est pas `none` :');
|
|
print('elle est hors de loads[], et l\'app n\'en conclut rien.');
|
|
}
|