etm-powersync-app/docs/CADRAGE_zones_clim.md
Patrick Schurig f33b8c243a feat(support): le rapport de bug pré-rempli, repoussé depuis le premier jour
/bug-report était un stub. Un champ libre seul produit « ça ne marche pas », et l'aller-retour
pour obtenir versions, charges et état d'arbitrage coûte des jours. Tout ce que l'app SAIT
est donc joint d'office : version du paquet, hôte et état de connexion, ligne de versions du
schéma, les charges déclarées avec adaptateur / rang / domaine, et le dernier cycle publié.

Deux choix qui ne sont pas cosmétiques :

- LES DEUX IDENTITÉS DU BUDGET sont dans le rapport, avec leur verdict. Elles valent souvent
  plus que la description du symptôme : portées fausses, elles désignent le moteur ; portées
  vraies, elles désignent l'écran. Sans elles, le premier échange consiste à les demander.

- Aucun secret n'y entre : ni PIN installateur même haché, ni jeton, ni mot de passe. Un
  rapport voyage — courriel, capture, fil de discussion — et ce qui y entre en sort. Les
  UUID de Things restent : ce sont eux qui permettent de recouper avec le journal de la box,
  et ils n'ont aucune valeur hors de l'installation.

Les absences gardent leur sens, comme partout ailleurs : « aucune télémétrie reçue » n'est
pas un arbitrage à zéro, « budget ABSENT » n'est pas un budget de zéro watt, et un
financement omis se dit omis.

Pas d'envoi automatique : il n'existe aucun service de collecte, et un bouton « Envoyer »
sans destinataire donnerait le sentiment que quelqu'un l'a reçu. Le rapport se copie, ce qui
est vérifiable.

package_info_plus est ajouté plutôt qu'une constante de version recopiée à la main : une
constante dérive de pubspec.yaml en silence, et un rapport qui annonce la mauvaise version
envoie chercher un défaut dans un code qui n'est pas celui-là.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016vrTVifar2GN5rtUh89Wkq
2026-08-27 14:54:27 +02:00

170 lines
8.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Cadrage — écran des zones de climatisation
**Date** : 2026-08-27 · **Banc** : `.75`, nymea `1.15.2+202606191336~trixie1`
**Objet** : ce qu'il faut sur le banc pour rendre l'écran **exerçable**, avant d'écrire une
ligne de code d'affichage.
> Ce document ne propose pas de maquette. C'est un cadrage : il dit ce qui existe, ce qui
> manque, et quelles décisions doivent être prises avant de coder. La maquette se valide
> avec Patrick, comme tout nouvel écran.
---
## 1. Où en est l'écran aujourd'hui
`lib/screens/ac_screen.dart`, 1 105 lignes. Il appelle **exactement une** méthode du
contrat : `AirConditioning.GetZones`, et **seulement pour en compter le résultat**
(`_zonesSurLaBox`). Tout le reste est de la maquette :
```dart
const _Zone(name: 'Salon', currentTemp: 19.5, targetTemp: 21.0, …),
const _Zone(name: 'Chambre', currentTemp: 18.0, targetTemp: 19.0, …),
const _Zone(name: 'Bureau', currentTemp: 20.0, targetTemp: 22.0, …),
const _Zone(name: 'Cuisine', currentTemp: 21.5, targetTemp: 21.0, …),
```
Quatre pièces, huit températures, un état SG-Ready et deux consignes ECS — **aucun de ces
nombres ne vient de la box.** C'est le même défaut que le SOC véhicule à 62 %, en plus
grand : des grandeurs physiques crédibles, affichées comme des mesures.
`.75` déclare **zéro zone** (`GetZones` → `zones: []`). L'écran affiche donc quatre pièces
là où l'installation n'en connaît aucune.
---
## 2. Le contrat, tel que la box le publie
Huit méthodes, trois notifications (`ZoneAdded` · `ZoneChanged` · `ZoneRemoved`).
`ZoneInfo` est **presque entièrement en lecture** (`r:`) — température, humidité, PM2.5,
COV, consigne courante, dérogation et sa fin, `zoneStatus`, et les six listes d'appareils.
On n'écrit **jamais** un objet complet : on écrit par les six `Set*`.
| Écriture | Ce qu'elle pose |
|---|---|
| `AddZone(name, o:thermostats, o:valves, o:indoorSensors, o:outdoorSensors, o:windowSensors, o:notifications)` | crée |
| `RemoveZone(zoneId)` | supprime |
| `SetZoneName(zoneId, name)` | renomme |
| `SetZoneThings(zoneId, …les six listes…)` | rattache |
| `SetZoneSetpointOverride(zoneId, mode, setpointOverride, o:minutes)` | dérogation manuelle |
| `SetZoneStandbySetpoint(zoneId, …)` | consigne de réduit |
| `SetZoneWeekSchedule(zoneId, …)` | planning 7 jours |
`SetpointOverrideMode` : `None` · `Timed` · `Unlimited` · `Eventual`.
`ZoneStatusFlag` : `None` · `TimeScheduleActive` · `SetpointOverrideActive` ·
`WindowOpen` · `BadAir` · `HighHumidity` — **un drapeau, donc combinable**.
Le planning est un `TemperatureWeekSchedule` = 7 × `TemperatureDaySchedule` =
liste de `{startTime, endTime, temperature}`.
---
## 3. ⚠️ Le banc ne peut PAS héberger une zone — et ce n'est pas « il manque un appareil »
Vérifié le 2026-08-27 sur les **58 classes** déclarées par `.75` : **aucune classe** ne
porte l'une des interfaces qu'une zone exige. Pas « aucun appareil créé » — aucune classe,
donc **aucun plugin installé ne sait en créer un**.
Les huit plugins installés sont : `AbbB2x`, `AbbTerra`, `GpioController`, `SunSpec`,
`eastron`, `energySimulation`, `genericEnergy`, `mqttclient`. Tous énergie ou entrées/
sorties : rien de thermique, rien de capteur d'ambiance.
### Ce que chaque emplacement exige exactement
Lu dans `airconditioningmanager.cpp:605` (`verifyThingIds`), qui refuse par
`AirConditioningErrorInvalidThingType` :
| Emplacement | Interface exigée |
|---|---|
| `thermostats` | `thermostat` |
| `windowSensors` | `closablesensor` |
| `indoorSensors` + `outdoorSensors` | `temperaturesensor` **ou** `humiditysensor` **ou** `vocsensor` **ou** `pm25sensor` |
| `notifications` | `notifications` |
| `valves` | **aucune — non vérifié** |
> `valves` est au schéma d'`AddZone` et de `SetZoneThings`, mais `verifyThingIds` ne le
> reçoit pas : la box accepte n'importe quel ThingId dans cet emplacement sans le
> contrôler. À ne pas exposer comme un choix guidé tant que le point n'est pas tranché —
> un champ que la box accepte sans vérifier produit une configuration qui « marche » et ne
> fait rien.
---
## 4. Ce qu'il faut sur le banc — la liste courte
Trois paquets, tous **présents dans le dépôt apt du banc** (`apt-cache search`
le 2026-08-27) :
```sh
ssh etm@192.168.1.75
sudo apt install nymea-plugin-generic-heatingcooling \
nymea-plugin-generic-sensors
# optionnel, pour le drapeau WindowOpen :
# les capteurs d'ouverture sont DÉJÀ dans generic-sensors (voir tableau)
```
Ce que ces deux paquets apportent, vérifié dans
`nymea-plugins-genericthings/*/integrationplugin*.json` :
| Classe à créer | Interfaces | Remplit |
|---|---|---|
| **Generic thermostat** | `thermostat`, `temperaturesensor` | `thermostats` — et sert aussi de sonde |
| **Generic temperature sensor** | `temperaturesensor` | `indoorSensors` / `outdoorSensors` |
| **Generic humidity sensor** | `humiditysensor` | `indoorSensors` |
| **Generic door or window sensor** | `closablesensor` | `windowSensors` → drapeau `WindowOpen` |
Les paquets `*-simulation` (`heating-simulation`, `sensors-simulation`,
`closables-simulation`) sont une alternative : ils **animent** leurs valeurs au lieu de les
laisser fixes, ce qui est préférable pour voir un écran vivre. Les classes `generic*`, elles,
sont pilotables à la main — préférable pour **provoquer** un état précis (fenêtre ouverte,
humidité haute) sans attendre.
**Recommandation** : les deux. `generic*` pour fabriquer un cas à volonté, `*-simulation`
pour regarder l'écran bouger tout seul.
### Le banc minimal qui exerce tout le contrat
Deux zones, parce qu'une seule ne montre ni le tri ni le cas « la pièce d'à côté » :
| Zone | Contenu | Ce qu'elle rend exerçable |
|---|---|---|
| **Salon** | 1 thermostat + 1 capteur d'humidité + 1 capteur d'ouverture | dérogation, planning, `WindowOpen`, `HighHumidity` |
| **Chambre** | 1 thermostat seul | le cas pauvre : aucune sonde d'ambiance, et l'écran doit le dire au lieu d'afficher des tirets |
Plus **un capteur de température déclaré `outdoorSensors`**, pour que la distinction
intérieur/extérieur ne reste pas théorique.
---
## 5. Ce qui doit être tranché avant de coder
1. **Le drapeau `zoneStatus` est un ensemble, pas un état.** `WindowOpen` + `BadAir`
peuvent être vrais ensemble. Un écran qui affiche « le » statut choisira lequel taire —
c'est une décision d'interface, à prendre explicitement.
2. **`vocsensor` et `pm25sensor` sont acceptés comme sondes d'ambiance**, et `ZoneInfo`
publie `voc` et `pm25`. La qualité d'air entre donc dans cet écran par la porte du
contrat. Est-ce qu'on l'affiche, ou est-ce qu'on la garde pour plus tard ?
3. **`valves` — exposé ou pas ?** Voir la réserve du §3.
4. **Le rattachement d'appareils appartient-il à cet écran ou au mode installateur ?**
`AddZone` et `SetZoneThings` sont `PermissionScopeAdmin` ; les six autres sont
`PermissionScopeControlThings`. La box sépare déjà les deux : l'écran devrait suivre.
5. **Le lien avec les charges pilotées reste à définir.** La PAC du banc est une charge
`sg-ready` arbitrée par le waterfall ; une zone est un objet du contrat
`AirConditioning`. **Les deux ne se connaissent pas.** L'écran actuel les mélange (un
bloc SG-Ready sous les pièces), et ce mélange n'a aucun support dans le contrat. À
trancher avant de le reconduire.
6. **Tier `Auto`.** Les zones sont derrière ce palier ; `pro_lock_badge.dart` existe déjà.
---
## 6. Ordre de marche proposé
1. Installer les deux paquets sur `.75` et créer le banc minimal du §4.
2. Écrire la sonde `tools/rpc/zones.dart` — instantané + écoute des trois notifications,
comme `telemetry_watch.dart`. **Aucun écran avant elle** : c'est elle qui dira ce que la
box publie vraiment quand une fenêtre s'ouvre.
3. Modéliser `ZoneInfo` en lecture seule, avec la règle maison — omis, jamais nul.
4. Retirer les quatre pièces de maquette. Tant que le modèle n'est pas branché, un écran
vide qui dit « aucune zone déclarée » est plus juste que quatre pièces inventées.
5. Maquette, validation avec Patrick, puis l'écran.