# 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.