Trois sondes permanentes, et le relevé de ce qu'elles ont montré sur .75. - telemetry_watch.dart (lecture seule) : cadence du battement de cœur et avancée du cycle. C'est la seule mesure qui distingue une installation stable d'un moteur arrêté — les deux produisent le même flux de trames. - domain_assign.dart (écrit, --yes) : classe des charges en important le code d'écriture de l'app. Une sonde qui sérialiserait à sa façon ne prouverait rien sur l'app. - fault_probe.dart (écrit, --yes) : provoque une charge en défaut, éprouve ClearLoadFault, restaure. La config d'origine part sur le disque AVANT la première écriture et la restauration rejoue ce fichier verbatim — filtrer la sonde hors d'une relecture serait une reconstruction, c'est-à-dire le chemin qui a failli écraser la config du banc via persist(). La config relue est ensuite comparée clé par clé, pas seulement « la sonde a disparu ». Ce que le banc a appris, et qui ne se déduisait pas du code : - ClearLoadFault lève RÉELLEMENT le verrou (journal : « défaut LEVÉ ») et le cycle suivant reverrouille, la cause n'ayant pas disparu. La télémétrie ne repasse jamais à available:true. Une app qui aurait cru l'acquittement aurait menti deux secondes plus tard. - Le défaut publié est WRITE_FAILED, pas THING_MISSING : l'écriture est tentée, échoue, l'échelle s'épuise, et m_faulted masque le second code. - SetLoadConfig refuse en bloc et le motif n'est PAS dans la réponse RPC — il est au journal. L'app ne peut que rapporter un refus, pas l'expliquer. - ECS-412 confirmé au journal : une écriture de domaine ne reconstruit aucun adaptateur (« 2 inchangée(s), 0 retirée(s) »). Le §7-1bis n'est plus une lecture de code. .75 est laissé classé (chauffe-eau→ecs, pac-terrain→hvac) : métadonnée que l'arbitre ne lit pas, et le multi-domaines y devient exerçable. Retour arrière : domain_assign.dart --clear --yes Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XPUo3RMr8SzK6qbFtfBm8H
101 lines
4.1 KiB
Dart
101 lines
4.1 KiB
Dart
import 'dart:async';
|
|
import 'dart:convert';
|
|
import 'dart:io';
|
|
|
|
// Observation de la télémétrie d'arbitrage — LECTURE SEULE.
|
|
//
|
|
// Répond à deux questions que la lecture de code ne tranche pas :
|
|
//
|
|
// §7-5 « télémétrie vivante » — une trame arrive-t-elle sans qu'on la demande, et
|
|
// dans quel délai après un changement réel ?
|
|
// §7-6 « fraîcheur » — à quelle cadence le battement de cœur bat-il, et le
|
|
// `timestamp` avance-t-il vraiment d'un cycle à l'autre ?
|
|
//
|
|
// Le second point est le plus important : c'est le battement de cœur qui rend un arbitre
|
|
// FIGÉ détectable. Les trames continuent d'arriver tandis que leur `timestamp` cesse
|
|
// d'avancer. Sans lui, « installation stable » et « moteur arrêté » sont le même silence.
|
|
//
|
|
// Usage : dart tools/rpc/telemetry_watch.dart [host] [durée_s]
|
|
Future<void> main(List<String> args) async {
|
|
final host = args.isNotEmpty ? args[0] : '192.168.1.75';
|
|
final seconds = args.length > 1 ? int.parse(args[1]) : 180;
|
|
|
|
final ws = await WebSocket.connect('ws://$host:4444')
|
|
.timeout(const Duration(seconds: 8));
|
|
var id = 0;
|
|
final pending = <int, Completer<Map<String, dynamic>>>{};
|
|
final started = DateTime.now();
|
|
DateTime? lastFrameAt;
|
|
String? lastCycle;
|
|
var frames = 0;
|
|
|
|
String stamp() =>
|
|
'+${DateTime.now().difference(started).inSeconds.toString().padLeft(3)}s';
|
|
|
|
void report(String origin, Map<String, dynamic> p) {
|
|
frames++;
|
|
final now = DateTime.now();
|
|
final cycle = p['timestamp'] as String? ?? 'AUCUN';
|
|
final gap = lastFrameAt == null
|
|
? '—'
|
|
: '${now.difference(lastFrameAt!).inSeconds}s depuis la précédente';
|
|
final moved = lastCycle == null
|
|
? ''
|
|
: (cycle == lastCycle ? ' ⚠ CYCLE IDENTIQUE' : ' cycle avancé');
|
|
lastFrameAt = now;
|
|
lastCycle = cycle;
|
|
|
|
final loads = (p['loads'] as List?) ?? const [];
|
|
final budget = p['budget'] as Map?;
|
|
print('${stamp()} $origin cycle=$cycle ($gap)$moved');
|
|
print(' degradedMode=${p['degradedMode']} '
|
|
'budget=${budget == null ? 'ABSENT (repli L2)' : 'surplus ${budget['surplusW']} '
|
|
'/ alloué ${budget['allocatedW']} / reliquat ${budget['remainingW']}'}');
|
|
for (final l in loads.whereType<Map>()) {
|
|
final lock = l['lock'] as Map?;
|
|
final mech = l['mechanism'] as Map?;
|
|
final dec = l['decision'] as Map?;
|
|
print(' · ${l['loadId']} alloué=${l['allocatedW']}W '
|
|
'available=${l['available']}'
|
|
'${l['faultCode'] != null ? ' DÉFAUT=${l['faultCode']}' : ''}'
|
|
'${lock != null ? ' verrou=${lock['kind']}/${lock['remainingS']}s' : ''}'
|
|
'${mech != null ? ' ${mech['kind']}' : ''}'
|
|
' → ${dec?['code']} ${jsonEncode(dec?['params'] ?? {})}');
|
|
}
|
|
}
|
|
|
|
ws.listen((raw) {
|
|
final m = jsonDecode(raw as String) as Map<String, dynamic>;
|
|
final rid = m['id'];
|
|
if (rid is int && pending.containsKey(rid)) {
|
|
pending.remove(rid)!.complete(m);
|
|
} else if (m['notification'] == 'NymeaEnergy.LoadTelemetryChanged') {
|
|
report('◀ LoadTelemetryChanged', m['params'] as Map<String, dynamic>);
|
|
} else if (m['notification'] != null) {
|
|
print('${stamp()} ◀ ${m['notification']}');
|
|
}
|
|
});
|
|
|
|
Future<Map<String, dynamic>> call(String me, [Map<String, dynamic> p = const {}]) {
|
|
final i = ++id;
|
|
final c = Completer<Map<String, dynamic>>();
|
|
pending[i] = c;
|
|
ws.add(jsonEncode({'id': i, 'method': me, 'params': p}));
|
|
return c.future.timeout(const Duration(seconds: 15));
|
|
}
|
|
|
|
await call('JSONRPC.Hello', {'locale': 'fr_FR'});
|
|
await call('JSONRPC.SetNotificationStatus', {'enabled': true});
|
|
|
|
final snap = await call('NymeaEnergy.GetLoadTelemetry');
|
|
report('■ GetLoadTelemetry (instantané)', snap['params'] as Map<String, dynamic>);
|
|
|
|
print('\n… écoute pendant ${seconds}s\n');
|
|
await Future<void>.delayed(Duration(seconds: seconds));
|
|
|
|
print('\n--- RELEVÉ ---');
|
|
print('trames reçues (instantané compris) : $frames en ${seconds}s');
|
|
print('dernier cycle vu : $lastCycle');
|
|
await ws.close();
|
|
}
|