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
146 lines
7.0 KiB
Markdown
146 lines
7.0 KiB
Markdown
# 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.
|
|
|
|
```bash
|
|
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`
|
|
|
|
```bash
|
|
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`
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
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 :
|
|
|
|
```bash
|
|
ssh etm@192.168.1.75 'journalctl -u nymead -f'
|
|
```
|
|
|
|
### `telemetry_watch.dart`
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
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 :
|
|
|
|
1. **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.
|
|
2. **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()`.
|
|
3. **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`, **pas** `THING_MISSING` — l'écriture est tentée,
|
|
échoue faute de Thing, l'échelle s'épuise, et `m_faulted` masque le second code ;
|
|
- `ClearLoadFault` **lève réellement** le verrou (le journal le dit : « défaut LEVÉ ; la
|
|
charge revient à l'arbitrage »), le RPC répond `EnergyErrorNoError` — 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. |
|