Range dans tools/rpc/ les sondes qui vivaient dans un scratchpad volatil : probe (appel RPC quelconque), notifications (abonnement + comptage), locale et locale_diff (comportement o:locale et son effet réel), loadconfig_roundtrip (aller-retour neutre §7-1). README avec usages. loadconfig_roundtrip est le SEUL qui écrit sur la box : garde --yes ajouté pour qu'un SetLoadConfig ne parte jamais par réflexe de flèche haut. analysis_options : tools/** et les classes l10n générées sortent de l'analyse — flutter analyze revient à 25 issues (ligne de base). Fixtures : le lot plugin télémétrie EST déployé sur .75 depuis ce matin (20 méthodes / 14 notifications). Capturé avant que ça ne se reperde : - loadtelemetry_hems75.json : dump réel de GetLoadTelemetry - loadconfig_hems75_v2.json : GetLoadConfig avec la clé 'domain' - schema_hems75_v2.json : schémas SET + télémétrie introspectés Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
94 lines
3.4 KiB
Dart
94 lines
3.4 KiB
Dart
import 'dart:async';
|
|
import 'dart:convert';
|
|
import 'dart:io';
|
|
|
|
class Rpc {
|
|
final WebSocket ws;
|
|
int _id = 0;
|
|
final _pending = <int, Completer<Map<String, dynamic>>>{};
|
|
Rpc(this.ws) {
|
|
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);
|
|
});
|
|
}
|
|
Future<Map<String, dynamic>> call(String m, [Map<String, dynamic> p = const {}]) {
|
|
final id = ++_id;
|
|
final c = Completer<Map<String, dynamic>>();
|
|
_pending[id] = c;
|
|
ws.add(jsonEncode({'id': id, 'method': m, 'params': p}));
|
|
return c.future.timeout(const Duration(seconds: 20));
|
|
}
|
|
}
|
|
|
|
Future<Rpc> open(String host, String? locale) async {
|
|
final ws = await WebSocket.connect('ws://$host:4444');
|
|
final rpc = Rpc(ws);
|
|
final hello = await rpc.call('JSONRPC.Hello',
|
|
locale == null ? {} : {'locale': locale});
|
|
final p = hello['params'] as Map<String, dynamic>;
|
|
print('Hello(locale=${locale ?? "<omis>"}) -> locale renvoyée: "${p['locale']}" language: "${p['language']}"');
|
|
return rpc;
|
|
}
|
|
|
|
/// Renvoie quelques displayName de ThingClass + leurs states/actions.
|
|
Future<void> sample(Rpc rpc, String tag) async {
|
|
final r = await rpc.call('Integrations.GetThingClasses', {});
|
|
final classes = (r['params']['thingClasses'] as List).cast<Map<String, dynamic>>();
|
|
print(' [$tag] ${classes.length} thingClasses');
|
|
for (final name in ['Smart Meter', 'Wallbox', 'Relay', 'Light']) {
|
|
final hit = classes.firstWhere(
|
|
(c) => (c['displayName'] as String?)?.contains(name) ?? false,
|
|
orElse: () => {});
|
|
if (hit.isNotEmpty) {
|
|
print(' [$tag] ThingClass "${hit['displayName']}" (name=${hit['name']})');
|
|
final states = (hit['stateTypes'] as List?) ?? [];
|
|
if (states.isNotEmpty) {
|
|
print(' [$tag] state[0].displayName = "${states.first['displayName']}"');
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
// Premiers displayName bruts, pour comparaison inter-locale
|
|
final first = classes.take(5).map((c) => c['displayName']).toList();
|
|
print(' [$tag] 5 premiers: $first');
|
|
|
|
final v = await rpc.call('Integrations.GetVendors', {});
|
|
final vendors = (v['params']['vendors'] as List).cast<Map<String, dynamic>>();
|
|
print(' [$tag] vendors: ${vendors.take(5).map((e) => e['displayName']).toList()}');
|
|
}
|
|
|
|
Future<void> main(List<String> args) async {
|
|
final host = args.isNotEmpty ? args[0] : '192.168.1.75';
|
|
|
|
print('=== 1. Deux connexions SIMULTANÉES, locales différentes ===');
|
|
final de = await open(host, 'de_DE');
|
|
final fr = await open(host, 'fr_FR');
|
|
await sample(de, 'de_DE');
|
|
await sample(fr, 'fr_FR');
|
|
|
|
print('\n=== 2. Locale omise (défaut du processus) ===');
|
|
final def = await open(host, null);
|
|
await sample(def, 'défaut');
|
|
|
|
print('\n=== 3. Locale absurde ===');
|
|
final zz = await open(host, 'zz_ZZ');
|
|
await sample(zz, 'zz_ZZ');
|
|
|
|
print('\n=== 4. Re-Hello sur une connexion existante (de_DE -> fr_FR) ===');
|
|
final h = await de.call('JSONRPC.Hello', {'locale': 'fr_FR'});
|
|
print(' locale renvoyée après re-Hello: "${h['params']['locale']}"');
|
|
await sample(de, 'de->fr');
|
|
|
|
print('\n=== 5. Nos chaînes NymeaEnergy ===');
|
|
final lc = await fr.call('NymeaEnergy.GetLoadConfig', {});
|
|
print(' [fr_FR] label de charge: '
|
|
'${(lc['params']['loadConfigs'] as List).map((e) => e['label']).toList()}');
|
|
|
|
await de.ws.close();
|
|
await fr.ws.close();
|
|
await def.ws.close();
|
|
await zz.ws.close();
|
|
}
|