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
7.0 KiB
Sondes JSON-RPC — tools/rpc/
Scripts dart:io autonomes pour interroger une box nymea sans lancer l'app.
Pourquoi ils existent : sur ce poste, Flutter Linux natif est bloqué (snap sans linker
ld.lld) et le web ne peut pas joindre le LAN en ws:// (CORS). Ces sondes sont donc le
moyen le plus court de vérifier un comportement réel de la box — et elles font plus autorité
qu'une lecture de code ou qu'un Introspect recopié de mémoire.
Aucune dépendance hors SDK : dart:io + dart:convert. Rien à installer.
dart tools/rpc/<script>.dart [host] [args…] # host par défaut : 192.168.1.75
Les scripts
| Script | Ce qu'il fait | Écrit ? |
|---|---|---|
probe.dart |
Un appel RPC quelconque, réponse en JSON indenté. Le couteau suisse. | non |
notifications.dart |
S'abonne à tous les namespaces, compte et échantillonne ce qui arrive. | non |
locale.dart |
Comportement de o:locale : connexions simultanées, re-Hello, locale absurde. |
non |
locale_diff.dart |
Aplatit toutes les chaînes affichables et les diffe entre locales. | non |
loadconfig_roundtrip.dart |
Aller-retour neutre Get → Set verbatim → Get. |
⚠️ OUI |
telemetry_watch.dart |
Instantané puis écoute de LoadTelemetryChanged : cadence, avancée du cycle, allocations. |
non |
domain_assign.dart |
Classe des charges par o:domain, par le code d'écriture de l'app. |
⚠️ OUI |
fault_probe.dart |
Provoque une charge en défaut, éprouve ClearLoadFault, restaure. |
⚠️ OUI |
probe.dart
dart tools/rpc/probe.dart 192.168.1.75 NymeaEnergy.GetLoadConfig
dart tools/rpc/probe.dart 192.168.1.75 JSONRPC.Introspect > introspect.json
dart tools/rpc/probe.dart 192.168.1.75 Integrations.GetThings
dart tools/rpc/probe.dart 192.168.1.75 NymeaEnergy.GetChargingInfos '{"evChargerId":"…"}'
Le Hello part toujours en premier et son contenu va sur stderr, la réponse sur
stdout — d'où le > fichier.json propre ci-dessus.
notifications.dart
dart tools/rpc/notifications.dart 192.168.1.75 90 # écoute 90 s
Utile pour répondre à « est-ce que cette information franchit la frontière RPC ? ». Un namespace qui ne produit rien pendant que le système travaille est une réponse, pas un silence à interpréter.
locale.dart / locale_diff.dart
locale.dart montre que nymea gère la locale par connexion (deux sockets, deux
langues). locale_diff.dart répond à la question qui compte vraiment : est-ce que ça
change quelque chose ? Relevé du 2026-08-25 sur .75 — sur 784 chaînes affichables,
de_DE en change 1, fr_FR/it_IT/es_ES/nl_NL zéro.
loadconfig_roundtrip.dart — ⚠️ écrit
dart tools/rpc/loadconfig_roundtrip.dart 192.168.1.75 --yes
SetLoadConfig remplace l'ensemble des charges. Le contenu renvoyé est identique à ce
qui vient d'être lu, donc l'opération est neutre — mais elle reste une écriture sur une
installation vivante, et elle déclenche une réévaluation des adaptateurs. Le drapeau
--yes est obligatoire pour qu'elle ne parte jamais par réflexe de flèche haut.
Pour confirmer qu'aucune reconstruction d'adaptateurs n'a lieu (ECS-412, m_builtFrom +
sameHardware()), lire le journal de la box en parallèle :
ssh etm@192.168.1.75 'journalctl -u nymead -f'
telemetry_watch.dart
dart tools/rpc/telemetry_watch.dart 192.168.1.75 240 # écoute 240 s
Répond à deux questions que la lecture de code ne tranche pas : une trame arrive-t-elle
sans qu'on la demande, et le timestamp avance-t-il ? La seconde est la seule qui
distingue une installation stable d'un moteur arrêté — les deux produisent le même flux de
battements de cœur. Relevé du 2026-08-25 sur .75 : battement à ~59 s, cycle qui avance
d'une minute, et une trame de changement significatif dans la foulée.
domain_assign.dart — ⚠️ écrit
dart run tools/rpc/domain_assign.dart 192.168.1.75 --set chauffe-eau=ecs --set pac-terrain=hvac --yes
dart run tools/rpc/domain_assign.dart 192.168.1.75 --clear --yes # tout déclasser
⚠️ dart run et non dart : ce script importe le code de l'app
(LoadConfigEntry.patched(), groupByDomain(), flattenToPayload()) plutôt que de
fabriquer sa propre charge utile. Une sonde qui sérialiserait à sa façon ne prouverait rien
sur l'app. Le domain est une pure métadonnée — l'arbitre ne la lit pas — mais l'écriture
est réelle.
fault_probe.dart — ⚠️ écrit, et ajoute une charge fictive
dart run tools/rpc/fault_probe.dart 192.168.1.75 --yes
dart run tools/rpc/fault_probe.dart 192.168.1.75 --yes --dump fixtures_defaut
dart run tools/rpc/fault_probe.dart 192.168.1.75 --restore-only --yes
Ajoute une charge etmvariableload dont le ThingId n'existe pas, attend deux cycles,
observe la télémétrie, appelle ClearLoadFault, puis restaure verbatim la
configuration lue au départ.
Trois garde-fous, tous délibérés :
- La configuration d'origine part sur le disque AVANT la première écriture
(
fault_probe_backup_<host>.json, git-ignoré). Un plantage à mi-parcours ne doit pas laisser le banc avec la charge fictive et aucune référence de retour. - La restauration rejoue ce fichier verbatim, jamais une relecture filtrée. Retirer
la sonde d'une config relue serait une reconstruction — le chemin exact qui a failli
écraser la configuration du banc via
EnergySetupProvider.persist(). - Après restauration, la config relue est comparée clé par clé à celle du départ, pas seulement « la sonde a disparu ». C'est le critère 1 appliqué ici.
Le label de la charge fictive est SONDE-TEST-A-SUPPRIMER : si elle survit à un incident,
personne n'aura à se demander d'où elle sort.
Ce que le passage du 2026-08-25 a montré, et qui ne se déduisait pas du code :
- le défaut publié est
WRITE_FAILED, pasTHING_MISSING— l'écriture est tentée, échoue faute de Thing, l'échelle s'épuise, etm_faultedmasque le second code ; ClearLoadFaultlève réellement le verrou (le journal le dit : « défaut LEVÉ ; la charge revient à l'arbitrage »), le RPC répondEnergyErrorNoError— et le cycle suivant reverrouille, la cause n'ayant pas disparu. La télémétrie ne repasse jamais àavailable: true.
C'est la démonstration la plus nette de la règle du lot : l'acquittement d'un RPC ne vaut pas application. Une app qui aurait affiché « défaut levé » sur le retour du RPC aurait menti deux secondes plus tard.
Bancs
| Cible | Particularité |
|---|---|
192.168.1.75 |
ouverte (authenticationRequired: false) — aucune auth à gérer dans les sondes. Deux charges depuis le plugin 1.15.2+etm15 : chauffe-eau (relay-router, ecs) et pac-terrain (sg-ready, hvac). |
192.168.1.120 |
auth requise — ces sondes ne gèrent PAS Authenticate. À étendre avant usage. |