From 054cbfab3c5cb6f42fdf8bb302725da8a10694d9 Mon Sep 17 00:00:00 2001 From: Patrick Schurig Date: Tue, 25 Aug 2026 21:30:32 +0200 Subject: [PATCH] =?UTF-8?q?docs:=20maquettes=20de=20la=20refonte=20=C2=AB?= =?UTF-8?q?=20Charges=20pilotables=20=C2=BB=20+=20CLAUDE.md=20recal=C3=A9?= =?UTF-8?q?=20sur=20la=20box?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Trois maquettes, neuf écrans, après le passage sur appareil qui a montré que /energy/setup décrit autre chose que l'installation. - charges_pilotables_mockup.html : la liste à deux niveaux, avec le bandeau d'arbitrage hérité de /energy/loads (qui disparaît) et la télémétrie sur la carte de chaque charge. - configurer_mecanismes_mockup.html : les six « Configurer » — routeur de relais, modulable, SG-Ready, borne, zones HVAC, sélecteur d'appareils. - raccordement_reseau_mockup.html : compteur réseau et protection de surcharge. Décisions de conception que les images ne portent pas seules : les paliers n'existent que chez le routeur de relais et y sont DÉDUITS des combinaisons ; le mode fixed d'etmvariableload (legacy) n'est plus proposé mais reste lisible, parce qu'ouvrir un écran ne doit pas convertir une config déployée ; SG1/SG2 sont une convention d'app retrouvée par l'encodage standard, la box ne stocke qu'une liste de relais par état ; le compteur par charge est dessiné inactif avec sa raison ; la borne règle un mode par DÉFAUT, pas une commande. CLAUDE.md corrigé contre JSONRPC.Introspect (plugin 1.15.2+etm15) : - Energy.SetChargingMode n'existe pas — le « bug critique » décrivait un appel impossible. Energy.* n'a que cinq méthodes. - Le namespace EnergyPlugin.* n'existe pas : c'est NymeaEnergy.*, 20 méthodes et 14 notifications, toutes listées. - ChargingInfo : chargingMode / targetPercentage / endDateTime (Uint), et non mode / targetSoc / endTime. minCurrent n'existe nulle part. - phasePowerLimit est en AMPÈRES par phase, et 0 coupe toute la recharge intelligente. - AirConditioning expose 8 méthodes, pas 3. Toutes les méthodes citées ont été revérifiées une par une : aucune introuvable. Règle ajoutée : contrôler ce fichier contre Introspect avant chaque lot — trois fois aujourd'hui il a envoyé un agent dans le vide. Et docs/BRIEF_plugin_depuis_maquettes.md : les six points que les maquettes remontent vers le moteur, dont un seul blocage dur (pas de champ de courant minimum pour le mode Min+PV). Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01XPUo3RMr8SzK6qbFtfBm8H --- CLAUDE.md | 124 ++- RECAP.md | 60 ++ docs/BRIEF_plugin_depuis_maquettes.md | 196 ++++ docs/mockups/charges_pilotables_mockup.html | 317 ++++++ .../mockups/configurer_mecanismes_mockup.html | 998 ++++++++++++++++++ docs/mockups/raccordement_reseau_mockup.html | 321 ++++++ 6 files changed, 1986 insertions(+), 30 deletions(-) create mode 100644 docs/BRIEF_plugin_depuis_maquettes.md create mode 100644 docs/mockups/charges_pilotables_mockup.html create mode 100644 docs/mockups/configurer_mecanismes_mockup.html create mode 100644 docs/mockups/raccordement_reseau_mockup.html diff --git a/CLAUDE.md b/CLAUDE.md index 2bf1868..6fbe624 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -29,69 +29,130 @@ mais la source de vérité du tier est dans `powersync-optimizer`. --- -## 🔴 Bug critique — à corriger en priorité absolue +## 🔴 Recharge EV — l'appel réel, vérifié contre la box + +> **Ce bloc décrivait un bug qui n'existe plus, avec des champs qui n'ont jamais existé.** +> `Energy.SetChargingMode` **n'est pas une méthode de nymea** — il n'y en a que cinq dans +> `Energy.*`, et celle-là n'en fait pas partie : l'appel échouerait sur « No such method ». +> Et le namespace `EnergyPlugin.*` n'existe pas non plus. Corrigé le 2026-08-25 contre +> `JSONRPC.Introspect` sur `.75` (plugin **1.15.2+etm15**). ```dart -// ❌ FAUX — ne déclenche pas le SmartChargingManager -nymeaService.call('Energy.SetChargingMode', {'mode': 'pv'}); - -// ✅ CORRECT — appelle vraiment le plugin ETM -nymeaService.call('EnergyPlugin.SetChargingInfo', { +nymeaService.call('NymeaEnergy.SetChargingInfo', { 'chargingInfo': { - 'evChargerId': chargerId, - 'mode': 'Eco', // Eco | EcoWithMinCurrent | Normal - // EcoWithTargetTime | EcoMinWithTargetTime - 'minCurrent': 6, // requis pour EcoWithMinCurrent/* - 'targetSoc': 80, // requis pour *WithTargetTime - 'endTime': null, // requis pour *WithTargetTime + 'evChargerId': chargerId, // Uuid — requis + 'chargingMode': 'ChargingModeEco', // requis, énumération ci-dessous + 'targetPercentage': 80, // o: Uint — charge cible + 'endDateTime': 1786250400, // o: Uint — horodatage, PAS une chaîne + 'repeatDays': [1, 2, 3, 4, 5], // o: Int[] + 'assignedCarId': carId, // o: Uuid + 'spotMarketChargingEnabled': false, // o: Bool + 'dailySpotMarketPercentage': 30, // o: Uint } }); ``` -### Modes UI EV — 3 boutons + option deadline +`chargingState` est en **lecture seule** (`r:`) dans la charge utile : +`ChargingStateIdle` · `ChargingStateSurplusCharging` · `ChargingStateSpotMarketCharging` · +`ChargingStateTimeRequirement`. + +### Énumération `ChargingMode` — les vrais noms + +`ChargingModeNormal` · `ChargingModeEco` · `ChargingModeEcoWithTargetTime` · +`ChargingModeEcoWithMinCurrent` · `ChargingModeEcoMinWithTargetTime` + +### Modes UI EV — 3 boutons + option échéance ``` [PV] [Min+PV] [Boost] ↓ - Option deadline ? → afficher champs SOC cible + heure d'arrivée + Option échéance ? → charge cible (targetPercentage) + heure (endDateTime) -PV → mode: Eco -Min+PV → mode: EcoWithMinCurrent, minCurrent: 6 -Boost → mode: Normal -PV + deadline → mode: EcoWithTargetTime, targetSoc, endTime -Min+PV+deadline → mode: EcoMinWithTargetTime, minCurrent, targetSoc, endTime +PV → ChargingModeEco +Min+PV → ChargingModeEcoWithMinCurrent +Boost → ChargingModeNormal +PV + échéance → ChargingModeEcoWithTargetTime + targetPercentage + endDateTime +Min+PV + échéance → ChargingModeEcoMinWithTargetTime + targetPercentage + endDateTime ``` +> ⚠️ **`minCurrent` n'existe nulle part.** Le mode `ChargingModeEcoWithMinCurrent` est bien +> dans l'énumération, mais `ChargingInfo` ne porte **aucun champ** de courant minimum. Soit +> il est fixé ailleurs (paramètre du Thing borne), soit il manque côté plugin. **Ne pas +> proposer de réglage de courant minimum tant que ce n'est pas tranché** — un champ qui ne +> part nulle part est pire que pas de champ. + --- ## APIs JSON-RPC consommées -### `Energy.*` — nymea-experience-plugin-energy +### `Energy.*` — nymea-experience-plugin-energy · **5 méthodes** | Méthode | Usage | Tier | |---|---|---| | `GetPowerBalance` | Dashboard temps réel | Community | | `GetPowerBalanceLogs(sampleRate, from, to)` | Historique — envoyer les bons `from/to` pour 90j | Community | | `GetThingPowerLogs(sampleRate, thingIds[], from, to)` | Historique par device | Community | +| `GetRootMeter` | Lire le compteur racine configuré | Community | | `SetRootMeter(rootMeterThingId)` | Config installateur | Community | -### `EnergyPlugin.*` — powersync-energy-plugin-etm +> `Energy.*` s'arrête là. **`SetChargingMode` n'existe pas** — ni ici, ni ailleurs. + +### `NymeaEnergy.*` — powersync-energy-plugin-etm · **20 méthodes / 14 notifications** + +> ⚠️ Le namespace est **`NymeaEnergy`**, pas `EnergyPlugin`. Ce dernier n'existe pas. +> Source d'autorité : `energyplugin/nymeaenergyjsonhandler.cpp` et `JSONRPC.Introspect`. +> **Ce n'est pas dans `OPTIMIZER_PROTOCOL.md`** — ce document décrit le protocole +> plugin ↔ optimiseur (couche A ↔ couche B), pas la frontière JSON-RPC de l'app. + +**Charges pilotées** +| Méthode | Usage | Tier | +|---|---|---| +| `GetLoadConfig` | Lire les charges déclarées | Community | +| `SetLoadConfig(loadConfigs[])` | Écrire — **remplace TOUT l'ensemble**, refus en bloc | Community | +| `GetLoadTelemetry` | État runtime de l'arbitrage (budget, allocation, verrous, motifs) | Community | +| `ClearLoadFault(loadId)` | Lever le verrou de défaut — **l'ack ne prouve rien** | Community | + +**Recharge EV** | Méthode | Usage | Tier | |---|---|---| | `GetChargingInfos(evChargerId)` | Lire config EV | Community | -| `SetChargingInfo(chargingInfo)` | **⚠️ CORRECTION CRITIQUE** | Community | +| `SetChargingInfo(chargingInfo)` | Écrire config EV — voir le bloc plus haut | Community | | `GetChargingSchedules(evChargerId)` | Planning EV | Community | -| `GetAvailableSpotMarketProviders()` | Liste providers | Community | -| `SetSpotMarketConfiguration(enabled, providerId)` | Config tarif dynamique | Predict AI | -| `GetSpotMarketScoreEntries(date)` | Cotations aWATTar | Predict AI | -| `SetPhasePowerLimit(Uint)` | Config installateur | Community | -| `SetAcquisitionTolerance(Double)` | Seuil surplus | Community | +| `GetLockOnUnplug` / `SetLockOnUnplug(Bool)` | Verrouillage au débranchement | Community | -### `AirConditioning.*` — nymea-experience-plugin-airconditioning +**Réglages globaux d'installation** +| Méthode | Usage | Tier | +|---|---|---| +| `GetPhasePowerLimit` / `SetPhasePowerLimit(Uint)` | **AMPÈRES par phase**, pas des watts. `0` = désactivé, et coupe toute la recharge intelligente | Community | +| `GetAcquisitionTolerance` / `SetAcquisitionTolerance(Double)` | Seuil de surplus au démarrage — **unité non documentée par la box** | Community | +| `GetBatteryLevelConsideration` / `SetBatteryLevelConsideration(Double)` | Part du stockage prise en compte | Community | + +**Tarifs dynamiques** +| Méthode | Usage | Tier | +|---|---|---| +| `GetAvailableSpotMarketProviders` | Liste providers | Predict AI | +| `GetSpotMarketConfiguration` / `SetSpotMarketConfiguration(enabled, providerId)` | Config tarif dynamique | Predict AI | +| `GetSpotMarketScoreEntries(date)` | Cotations aWATTar | Predict AI | +| `GetEnergyRatios` | Autoconsommation / autonomie | Community | + +**Notifications** — `LoadConfigChanged` · `LoadTelemetryChanged` · `ChargingInfoAdded/Changed/Removed` · +`ChargingSchedulesChanged` · `EnergyRatiosChanged` · `PhasePowerLimitChanged` · +`AcquisitionToleranceChanged` · `BatteryLevelConsiderationChanged` · `LockOnUnplugChanged` · +`SpotMarketConfigurationChanged` · `SpotMarketStatusChanged` · `SpotMarketScoreEntriesChanged` + +### `AirConditioning.*` — nymea-experience-plugin-airconditioning · **8 méthodes** | Méthode | Usage | Tier | |---|---|---| | `GetZones` | Afficher zones PAC/thermostat | Auto | -| `SetZoneSetpointOverride` | Pilotage manuel | Auto | +| `AddZone` / `RemoveZone` | CRUD de zones | Auto | +| `SetZoneName` | Renommer | Auto | +| `SetZoneThings(zoneId, thermostats[], valves[], indoorSensors[], outdoorSensors[], windowSensors[], notifications[])` | Rattacher les appareils | Auto | +| `SetZoneSetpointOverride(zoneId, mode, setpointOverride, minutes)` | Dérogation manuelle | Auto | +| `SetZoneStandbySetpoint` | Consigne de réduit | Auto | | `SetZoneWeekSchedule` | Planning 7 jours | Auto | +> `ZoneInfo` est **presque entièrement en lecture** (`r:`) : température, humidité, PM2.5, COV, +> consigne courante, dérogation et sa fin, listes d'appareils. On écrit par les six `Set*`, +> jamais par un objet complet. + ### `Rules.*` — nymea core | Méthode | Usage | Tier | |---|---|---| @@ -175,6 +236,9 @@ batteryGreen boostRed pvGreen minPvBlue accentTeal ## Règles de modification - Tout nouvel écran → valider maquette avec Patrick avant de coder - Tout nouvel appel RPC → vérifier dans `INTERFACE.md` que la méthode existe -- Ne jamais appeler `Energy.SetChargingMode` pour piloter un EV +- Piloter un EV = `NymeaEnergy.SetChargingInfo` (`Energy.SetChargingMode` n'existe pas) +- **Vérifier ce fichier contre `JSONRPC.Introspect` avant chaque lot.** Trois fois le + 2026-08-25, la doc locale était en retard sur la machine et a envoyé un agent dans le vide. + La sonde tient en une ligne : `dart tools/rpc/probe.dart 192.168.1.75 JSONRPC.Introspect` - Toujours tester la persistance : killer l'app et vérifier que les données survivent - Flavors à configurer : `com.etm-powersync.community` / `.auto` / `.predictai` diff --git a/RECAP.md b/RECAP.md index 00bccde..9736cf6 100644 --- a/RECAP.md +++ b/RECAP.md @@ -260,6 +260,66 @@ autres (`cycleAge`, `sinceLastFrame`) sont court-circuités par l'éjection. la cause racine du défaut 2 ; sans ça, un glisser-déposer n'y écrira jamais un `priority` réel. +## Refonte décidée le 2026-08-25 (soir) — maquettes validées + +Patrick a tranché la question ouverte du RECAP : **l'écran « Ordre de service des charges » +(`/energy/loads`) disparaît.** Le rang se règle dans « Rôles & appareils → Charges +pilotables » au glisser-déposer, le **regroupement à deux niveaux est conservé**, et le bloc +de télémétrie descend sur la carte de chaque charge. + +| Maquette | Contenu | +|---|---| +| `docs/mockups/charges_pilotables_mockup.html` | La liste : bandeau arbitrage, domaines glissables, cartes avec télémétrie, défaut, bouton Configurer | +| `docs/mockups/configurer_mecanismes_mockup.html` | Les six « Configurer » : relais · modulable · SG-Ready · borne · zones HVAC · sélecteur d'appareils | +| `docs/mockups/raccordement_reseau_mockup.html` | Compteur réseau + protection de surcharge | + +**Décisions de conception qui ne se déduisent pas des maquettes :** + +- **Les paliers n'existent que chez le routeur de relais**, et ils y sont *déduits* des + combinaisons de contacts, jamais saisis. Le modulable ne connaît qu'un plafond + (`maxPowerW`). Le mode `fixed` d'`etmvariableload` — que le plugin qualifie lui-même de + *legacy* — **n'est plus proposé**, mais reste **lisible** : ouvrir l'écran ne doit pas + convertir une config déployée. +- **Le critère de choix entre les deux ECS**, formulé pour l'installateur : *où sont les + contacteurs ?* Sur les sorties de la box → routeur de relais. Dans l'appareil, piloté par + une consigne → modulable. +- **SG1 / SG2 n'existent pas côté box.** `sgReady.states[].relays[]` n'est qu'une liste par + état. Les étiquettes sont une convention d'app, retrouvée à la lecture par l'encodage + standard (SG1 = fermé en état 1, SG2 = fermé en état 3). Un encodage non standard perd les + noms, pas la configuration. +- **Le compteur par charge figure dans les cinq écrans, inactif**, avec la raison sur place. + Aucun champ n'existe côté box, et nymea rejette toute clé inconnue avant le handler. +- **La borne règle un mode PAR DÉFAUT**, pas une commande — le pilotage vit sur la carte + véhicule du tableau de bord. + +## Ce qui remonte vers le plugin + +Six points, rédigés en brief exploitable : `docs/BRIEF_plugin_depuis_maquettes.md`. +Le seul **blocage dur** est le premier — `ChargingInfo` n'a aucun champ de courant minimum, +donc le mode Min+PV reste sélectionnable mais non réglable. + +Les cinq autres : motif de refus de `SetLoadConfig` qui ne franchit pas la frontière · +`WRITE_FAILED` qui masque `THING_MISSING` · paliers atteignables non exposés (l'app devra +sinon dupliquer la combinatoire de `relayrouter.cpp`) · état matériel de `enabled:false` à +confirmer pour relais et modulable · `estimatedPowerW` du banc encore fictif. + +## Contrôle de la doc contre la machine — à refaire avant chaque lot + +`CLAUDE.md` a été **corrigé contre `JSONRPC.Introspect`** le 2026-08-25. Trois fois dans la +journée la doc locale était en retard sur la box : + +1. le namespace `LoadConfig` cherché dans `OPTIMIZER_PROTOCOL.md`, où il n'est pas (note de + périmètre ajoutée côté plugin, commit `afaa8c2`) ; +2. le « bug critique » `Energy.SetChargingMode` — **cette méthode n'existe pas**, l'appel + échouerait sur « No such method » ; +3. les champs de `ChargingInfo` (`mode`/`minCurrent`/`targetSoc`/`endTime` au lieu de + `chargingMode`/`targetPercentage`/`endDateTime`), le namespace `EnergyPlugin.*` + inexistant, et `AirConditioning` donné pour 3 méthodes quand il en expose 8. + +Toutes les méthodes citées par `CLAUDE.md` ont été revérifiées une par une : **aucune +introuvable**. La sonde tient en une ligne — +`dart tools/rpc/probe.dart 192.168.1.75 JSONRPC.Introspect`. + ## Vérifié / non vérifié (lot B app) | Critère | Statut | diff --git a/docs/BRIEF_plugin_depuis_maquettes.md b/docs/BRIEF_plugin_depuis_maquettes.md new file mode 100644 index 0000000..4546b8d --- /dev/null +++ b/docs/BRIEF_plugin_depuis_maquettes.md @@ -0,0 +1,196 @@ +# Brief plugin — ce que les maquettes « Configurer » demandent au moteur + +**Origine :** session du 2026-08-25, côté app. Écrit après le passage sur appareil et la +conception des six écrans « Configurer » (`docs/mockups/`). +**Cible :** `etm-powersync-energy-plugin-etm`, plugin déployé au moment du relevé : +**1.15.2+etm15** sur `.75`. +**Méthode :** chaque point est un constat vérifié contre la machine ou la source, pas une +préférence d'UI. Les maquettes sont jointes — elles montrent où chaque manque se voit. + +--- + +## 1. `SetChargingInfo` ne permet pas l'écran borne — seul blocage dur + +**Ce que l'écran veut** (`configurer_mecanismes_mockup.html`, écran ③) : trois modes +`[PV] [Min+PV] [Boost]`, une option d'échéance de départ avec charge cible et heure, et le +basculement automatique vers les variantes `…WithTargetTime`. + +**Ce que la box expose**, introspecté sur `.75` : + +``` +ChargingInfo = chargingMode · evChargerId + · o:assignedCarId · o:dailySpotMarketPercentage · o:endDateTime (Uint) + · o:repeatDays · o:spotMarketChargingEnabled · o:targetPercentage + · r:chargingState +``` + +L'échéance est **partiellement là** : `targetPercentage` et `endDateTime` existent, et les +modes `ChargingModeEcoWithTargetTime` / `ChargingModeEcoMinWithTargetTime` sont dans +l'énumération. Ce qui manque est ailleurs : + +**⛔ `minCurrent` n'existe nulle part.** `ChargingModeEcoWithMinCurrent` est un mode valide, +mais aucun champ de `ChargingInfo` ne porte le courant à maintenir. Deux issues possibles, +et elles ne demandent pas le même travail : + +- le courant est déjà fixé ailleurs (paramètre du Thing borne, réglage global) → **le dire**, + et l'app pointera l'utilisateur au bon endroit ; +- il manque → **ajouter le champ**, et dire s'il est par borne ou global. + +Tant que ce n'est pas tranché, l'app **ne proposera pas** de réglage de courant minimum : un +champ qui ne part nulle part est pire que pas de champ. Le mode Min+PV reste donc à moitié +livrable — sélectionnable, mais non réglable. + +> Corollaire déjà traité côté app : `CLAUDE.md` documentait `mode` / `minCurrent` / +> `targetSoc` / `endTime` sur un namespace `EnergyPlugin.*` inexistant. Corrigé le +> 2026-08-25 contre `Introspect`. + +--- + +## 2. Le motif de refus de `SetLoadConfig` ne franchit pas la frontière + +**Constat, reproduit deux fois.** Une charge invalide fait répondre : + +```json +{"params":{"energyError":"EnergyErrorInvalidParameter"},"status":"success"} +``` + +…et rien d'autre. Le motif reste au journal de la box : + +``` +W | NymeaEnergy: [NymeaEnergy] SetLoadConfig rejetée: + "Config \"SONDE-TEST-A-SUPPRIMER\" invalide : etmvariableload fixed : powerLevels requis (non vide)" +``` + +**Pourquoi ça compte maintenant.** Les six écrans « Configurer » écrivent tous par +`SetLoadConfig`, et le refus est **en bloc** : une seule charge invalide fait échouer +l'enregistrement de toutes. L'installateur apprendra qu'on a refusé, jamais pourquoi ni sur +quelle charge — devant un client, avec un tableau électrique ouvert. + +**Ce que l'app peut faire seule, et sa limite.** Rejouer `validateSet()` côté client couvre +l'unicité des ids et des Things. Elle ne couvre pas les règles de mécanisme +(`powerLevels` commençant par 0, `powerW > 0`, `relays[]` interdit en modulable, `mode` +imposé…) sans dupliquer `LoadConfig::validate()` — deuxième implémentation, deuxième +divergence. + +**Demande.** Le même remède que la télémétrie : un **code** et des **paramètres**, pas une +phrase. Par exemple, à côté de `energyError` : + +```json +{"energyError":"EnergyErrorInvalidParameter", + "rejection":{"loadId":"chauffe-eau","code":"POWERLEVELS_REQUIRED","params":{"mode":"fixed"}}} +``` + +Le libellé français est fabriqué par l'app, comme pour `decision.code`. + +--- + +## 3. `WRITE_FAILED` masque `THING_MISSING` + +**Constat**, provoqué délibérément sur `.75` (`tools/rpc/fault_probe.dart`) : une charge +`etmvariableload` dont le ThingId n'existe pas publie **`WRITE_FAILED`**, pas +`THING_MISSING`. L'écriture est tentée, échoue faute de Thing, l'échelle s'épuise, et +`m_faulted` prend le pas — l'ordre des `if` dans `etmvariableloadadapter.cpp:148-150` : + +```cpp +if (m_faulted) v.faultCode = "WRITE_FAILED"; +else if (!thingManager->findConfiguredThing(...)) v.faultCode = "THING_MISSING"; +``` + +**Pourquoi ça compte.** Les deux codes appellent **deux gestes différents** chez le client. +« Échec d'écriture » envoie chercher un défaut de liaison — bus Modbus, alimentation, +contacteur. « Appareil absent » envoie dans la configuration nymea. L'app rend fidèlement ce +que la box dit, et la box envoie au mauvais endroit. + +**Demande.** Faire primer la cause structurelle : un Thing absent est un `THING_MISSING`, +même si des écritures ont échoué entre-temps. Ou publier les deux. + +--- + +## 4. Les paliers atteignables ne sont pas exposés + +**Constat.** `mechanism` (kind `relay`) publie `stageW` et `maxStageW` — **la liste des +paliers atteignables n'existe dans aucun champ** : + +``` +mechanism = kind · o:stageW · o:maxStageW · o:setpointW · o:percent · o:maxPowerW + · o:state · o:estimatedPowerW · o:chargingEnabled · o:currentA + · o:phaseCount · o:pluggedIn +``` + +**Ce que la maquette affiche** (écran ①, routeur de relais) : + +> 3 relais → paliers atteignables : **0 · 500 · 1 000 · 1 500 · 2 000 · 2 500 · 3 000 · 3 500 W** + +C'est l'aide la plus utile du formulaire : elle dit à l'installateur ce que son câblage +permet, avant qu'il n'enregistre. + +**Le risque.** Faute d'exposition, l'app **dérivera** cette liste — donc réimplémentera la +combinatoire de `relayrouter.cpp` (toutes les combinaisons 2^N, doublons écartés, plafond +16 relais). Deux implémentations d'une même règle divergeront : à la première correction +côté moteur, l'app annoncera des paliers que la box n'atteint plus. + +**Demande.** Exposer la liste — soit dans la télémétrie (`mechanism.stagesW: [...]`), soit en +retour d'un `GetLoadConfig` enrichi. À défaut, **le dire explicitement**, et l'app +documentera sa dérivation comme un miroir à resynchroniser. + +--- + +## 5. `enabled: false` — ce que deviennent les contacts + +**Ce que l'app sait déjà**, et qui est bon : la charge quitte l'arbitrage, ne consomme aucun +budget, et disparaît de `loads[]` **au cycle suivant** — pas dans la seconde. + +**Ce qu'il reste à établir côté app :** l'état matériel. Le relevé du lot B-bis (§4) répond +sur `sg-ready` — contacts K1/K2 mesurés ouverts après désactivation, l'état sûr passe bien +par `applySafeState()` avant retrait de l'adaptateur. **Reste à confirmer pour +`relay-router` et `etmvariableload`**, sur matériel. + +**Pourquoi ça compte.** La maquette écrit aujourd'hui « à la désactivation, le retrait passe +par l'état sûr : tous les relais ouverts ». Si c'est faux pour un mécanisme, l'écran laisse +croire à une coupure qui n'a pas eu lieu — sur un chauffe-eau, c'est une promesse de +sécurité. + +**Demande.** Confirmer, par mécanisme, ce que produit `enabled: false` sur le matériel. Une +ligne de journal par adaptateur suffit ; c'est déjà le cas pour SG-Ready. + +--- + +## 6. `estimatedPowerW` SG-Ready reste fictif sur `.75`, et il alimente le recrédit + +**Constat, déjà relevé côté plugin** (`RELEVE_LOTBbis.md` §7) : les valeurs {état 3 : 1500 W, +état 4 : 3000 W} et `minStateHoldS: 300` sont reconduites du bloc codé en dur supprimé, où +elles décrivaient une PAC **inventée**. Aucune PAC n'est sous tension sur K1/K2. + +**Ce que ça change côté app.** L'écran SG-Ready (écran ②) présente `estimatedPowerW` comme +une **donnée d'installation**, saisie par l'installateur d'après la plaque signalétique. Sur +le banc, c'est une valeur de test — et elle sert de base au **recrédit de budget** et au +choix d'état, donc elle décale l'allocation de tout le waterfall en aval. + +**Demande.** Rien à changer dans le code : c'est un point de **données de banc**. Mais tant +qu'il n'est pas clos, aucune capture de `.75` ne doit servir d'exemple d'installation, et le +§4 de `TEST_TERRAIN.md` reste non transposable. + +--- + +## Ce que l'app livre sans rien attendre + +Pour situer ce qui est bloqué et ce qui ne l'est pas : + +| Écran | État | +|---|---| +| Liste « Charges pilotables » à deux niveaux, télémétrie sur carte | livrable | +| Configurer · routeur de relais | livrable — sauf l'aperçu des paliers (point 4) | +| Configurer · modulable | livrable | +| Configurer · SG-Ready | livrable | +| Configurer · borne de recharge | **bloqué sur le point 1** pour le mode Min+PV | +| Zones HVAC + sélecteur d'appareils | livrable côté RPC — **aucun matériel HVAC sur `.75`** | +| Raccordement réseau / protection de surcharge | livrable, mais voir ci-dessous | + +**Et une question de périmètre, pas un défaut.** La protection de surcharge ne déleste +aujourd'hui **que les bornes de recharge** (`m_evChargers` dans +`SmartChargingManager::verifyOverloadProtection`). Le chauffe-eau, la PAC et les charges +modulables ne sont pas réduits par elle. L'écran « Raccordement » le dit explicitement plutôt +que de promettre une protection générale. **Étendre le délestage au waterfall est une +décision produit** — si elle est prise, l'app aura besoin d'un ordre de délestage, qui n'est +pas l'ordre de service : le premier retire de la puissance en surcharge, le second en +répartit en surplus. Deux listes qui peuvent légitimement différer. diff --git a/docs/mockups/charges_pilotables_mockup.html b/docs/mockups/charges_pilotables_mockup.html new file mode 100644 index 0000000..f3eff78 --- /dev/null +++ b/docs/mockups/charges_pilotables_mockup.html @@ -0,0 +1,317 @@ + + + + + +ETM PowerSync — Charges pilotables (liste à deux niveaux) + + + + + + + +
+

Charges pilotables — l'ordre, la télémétrie et le mécanisme au même endroit

+

Décision du 2026-08-25 : l'écran « Ordre de service des charges » disparaît. Le rang se règle + ici au glisser-déposer, le regroupement à deux niveaux est conservé (domaines ordonnés entre eux, + charges ordonnées dedans), et le bloc de télémétrie descend sur la carte de chaque charge.

+

Les six écrans « Configurer » qu'ouvre le bouton de chaque carte sont dans + configurer_mecanismes_mockup.html.

+

Le changement de fond n'est pas visuel : ces cartes doivent être les charges lues par + NymeaEnergy.GetLoadConfig, pas des LoadDescriptor reconstruits depuis des + EmsRole. C'est la cause racine du défaut constaté sur appareil — l'écran actuel affichait + « 6/6 rôles configurés · tout est assigné » en pointant sur des appareils absents de la box, pendant que + celle-ci arbitrait deux charges qui n'apparaissaient nulle part.

+
+ +
+ + +
+
+
‹

Rôles & appareils

⟳
+
+ +
Arbitrage en direct · dernier cycle il y a 41 s
+
+
Budget de surplus du cycle2 571 W entrants
+
+
Surplus2 571
+
Réservé VE0
+
Recrédité500
+
Alloué3 000
+
Reliquat71
+
+
+ +
Charges pilotables · glissez les domaines, puis les charges
+ + +
+ ⠿ +

Eau chaude sanitaire

+ +
+ +
+
+ ⠿1 +
Chauffe-eauRouteur de relais · 3 relais · min ON 60 s
+ +
+
+ Alloué 3 000 W · palier 3 000 W sur 3 500 W +
Consigne de 3 000 W servie par le surplus, arrondie sur un palier déclaré + (3 071 W de budget).
+
🔒 Maintien ON — 60 s restantes (au dernier cycle)
+
+ +
+ + +
+ ⠿ +

Chauffage

+ +
+ +
+
+ ⠿2 +
PAC bancSG-Ready · maintien d'état 300 s
+ +
+
+ Alloué 3 000 W · état SG-Ready 4 — estimation 3 000 W +
Pompe à chaleur en état 4 (forcé) — estimation 3 000 W sur 3 477 W de budget.
+
+ +
+ + +
+ ⠿ +

Non classé

+ +
+ +
+
+ ⠿3 +
Sèche-serviettesRouteur de relais · 1 relais
+ +
+
+ Non arbitrée · absente du dernier cycle publié +
Charge désactivée : son rôle reste décrit, elle ne consomme aucun budget.
+
+ +
+ +
+
+ ⠿4 +
Plancher chauffantCharge modulable
+ +
+
+ ⚠ En défaut + Échec d'écriture : l'échelle de commande est épuisée, plus aucune commande n'est émise tant que + le défaut n'est pas levé. + +
+ +
+ +
Sans appareil
+
+
Borne de rechargeAucune charge déclarée sur la box
+ +
+ +
+
+
Charges pilotables — deux niveaux + télémétrie
+
+ Ce qui part sur la box : le glisser-déposer produit une suite d'entiers priority à plat — + la box ne sait rien des domaines, le rang de domaine se déduit de l'ordre d'apparition. Le toggle écrit + enabled. +
    +
  • « Non arbitrée » n'est pas une erreur : une charge de GetLoadConfig absente de + loads[] se lit désactivée, jamais perdue. L'inclusion ne vaut que dans un sens.
  • +
  • « Lever le défaut » n'apparaît que si available: false, et son résultat n'est + jamais annoncé sur le retour du RPC : mesuré sur le banc, ClearLoadFault répond + EnergyErrorNoError, lève réellement le verrou… et le cycle suivant le repose si la cause + demeure. Le verdict vient de la télémétrie relue, deux cycles plus tard.
  • +
  • « Déclarer » mène au même formulaire que « Configurer », vide.
  • +
+
+
+ +
+ + diff --git a/docs/mockups/configurer_mecanismes_mockup.html b/docs/mockups/configurer_mecanismes_mockup.html new file mode 100644 index 0000000..5e61822 --- /dev/null +++ b/docs/mockups/configurer_mecanismes_mockup.html @@ -0,0 +1,998 @@ + + + + + +ETM PowerSync — Configurer : les cinq mécanismes + + + + + + + +
+

Six écrans, trois contrats — et ils ne se ressemblent pas

+

Le bouton Configurer ne peut pas être un formulaire unique. SG-Ready est une + LoadConfig comme le routeur de relais ; la borne de recharge et les zones de climatisation + n'en sont pas du tout — elles vivent sur d'autres namespaces, avec d'autres champs et d'autres tiers.

+

Deux écarts relevés en introspectant .75 (plugin 1.15.2+etm15) : + ① le ChargingInfo réellement déployé ne porte ni mode, ni minCurrent, + ni targetSoc, ni endTime — les noms écrits dans CLAUDE.md ne sont pas + ceux de la box. ② AirConditioning expose 8 méthodes, pas 3.

+
+ +
+ + +
+
+
‹

Configurer · Chauffe-eau

+
+ +
+ + +
Le seul texte de l'installation que l'utilisateur voit dans chaque motif + de décision.
+
+ +
+ +
Eau chaude sanitaire ▾
+
+ +
+ +
+ +
+
+ Ce sont vos contacteurs + La box ouvre et ferme des contacts qu'elle commande directement, et compose les puissances + en les combinant. C'est le seul mécanisme où l'app déclare du matériel. + Si les étages sont à l'intérieur + de l'appareil et qu'on lui envoie une consigne, ce n'est pas ici : c'est « modulable ». +
+
+ +
+ + +
+ K1 +
Relay1 Digital Output
+ + × +
+
+ K2 +
Relay2 Digital Output
+ + × +
+
+ K3 +
Relay3 Digital Output
+ + × +
+ + +
+ 3 relais → paliers atteignables : + 0 · 500 · 1 000 · 1 500 · 2 000 · 2 500 · 3 000 · 3 500 W + Déduit, jamais enregistré. La box compose elle-même les paliers en essayant toutes les + combinaisons de contacts (2N, plafond 16 relais). Deux relais de même puissance ne + créent pas de palier supplémentaire — les doublons sont écartés. +
+ +
Un ECS simple, c'est un seul relais — deux paliers, 0 et P. Une + résistance multipalier en déclare 2, 3 ou 4. La puissance saisie est celle de la + résistance, pas le calibre du contacteur : rien dans nymea ne dit ce qu'un relais + commande.
+ +
+ ⚠ Ce que la box refuse, en bloc et sans le dire + relays[] vide · un powerW ≤ 0 · un même Thing sur deux lignes · + un mode autre que fixed. Le motif n'apparaît qu'au journal de la box — + le formulaire doit donc valider avant d'envoyer. +
+
+ +
+ +
+
Maintien ON minimalanti court-cycle à l'enclenchement
+
60 s
+
+
+
Maintien OFF minimaldélai avant réenclenchement
+
60 s
+
+
Ces deux durées produisent les verrous LOCK_MIN_ON et + LOCK_MIN_OFF que la carte de la charge affiche en secondes restantes.
+
+ +
+ +
+
+
Rattacher un compteur à cette charge
+ +
+
+
Choisir un appareil de mesure… ▾
+
+

Aucun champ n'existe côté box. SetLoadConfig n'accepte que + adapter · enabled · id · label · mode · priority · domain · maxPowerW · minOffS · minOnS · + needs · powerLevels · relays · sgReady. nymea rejette toute clé inconnue avant le + handler : inventer meterThingId ferait échouer l'enregistrement de toutes les + charges. Ici le besoin est le plus fort des trois + mécanismes : la box estime la consommation depuis les paliers déclarés, elle ne la + mesure pas.

+
+
+ +
+ +
+
Inclure dans l'arbitragerang 1 · Eau chaude sanitaire
+
+
+
À la désactivation, le retrait passe par l'état sûr : tous les relais + ouverts. Vérifié sur matériel côté plugin.
+
+ +
Annuler
Enregistrer
+
+
+
Routeur de relais — ECS simple ou multipalier
+
+ NymeaEnergy.SetLoadConfig
+ Le seul mécanisme où l'app déclare des contacteurs. C'est aussi le seul où les paliers ont un + sens : ils naissent des combinaisons de relais, ils ne se saisissent pas. +
    +
  • mode doit valoir fixed — la box refuse toute autre valeur sur + ce mécanisme. Le champ n'a donc pas à être proposé : il est déterminé par le choix du mécanisme.
  • +
  • Toucher aux relais franchit sameHardware() : reconstruction de l'adaptateur + et réarmement des verrous à froid — contrairement à label, domain, + priority et enabled, qui passent en mise à jour sur place.
  • +
  • Configuration réelle du banc .75 : trois relais 500 / 1 000 / 2 000 W, huit paliers, + maxStageW: 3 500 — la télémétrie l'a confirmé pendant le passage sur appareil.
  • +
+
+
+ + +
+
+
‹

Configurer · Chauffe-eau

+
+ +
+ + +
+ +
+ +
+ +
+
+ Une consigne continue, pas des étages + La box envoie une valeur en watts, n'importe laquelle entre 0 et le plafond, et l'appareil + module. Aucun contact n'est commandé, aucun palier n'est déclaré. + Des paliers ⇒ ce sont des + contacteurs ⇒ c'est « routeur de relais ». Les deux mécanismes ne se recouvrent pas. +
+
+ +
+ +
Chauffe-eau Modbus · etmvariableload ▾
+
+ ⚠ Changer d'appareil, c'est changer de charge + L'identifiant de la charge est le ThingId piloté. Choisir un autre appareil ne modifie donc + pas cette charge : cela en crée une autre et supprime celle-ci — avec son passage par l'état sûr + (consigne 0 W). À dire avant d'enregistrer, pas après. +
+
+ +
+ +
+ +
3 000W
+ +
+
Requis, et strictement positif : la box refuse une charge modulable sans + plafond. C'est la plaque signalétique de la résistance, pas une estimation.
+
+ +
+ +
+
Maintien ON minimalanti court-cycle
+
0 s
+
+
+
Maintien OFF minimaldélai avant reprise
+
0 s
+
+
+ +
+ +
+
+
Rattacher un compteur à cette charge
+ +
+
+
Choisir un appareil de mesure… ▾
+
+

Aucun champ n'existe côté box. SetLoadConfig n'accepte que + adapter · enabled · id · label · mode · priority · domain · maxPowerW · minOffS · minOnS · + needs · powerLevels · relays · sgReady, et nymea rejette toute clé inconnue avant + le handler. Ici la question se double : l'interface + remonte déjà la puissance réelle de l'appareil — un compteur externe mesurerait-il autre + chose ?

+
+
+ +
+ +
+
Inclure dans l'arbitragerang 1 · Eau chaude sanitaire
+
+
+
À la désactivation, le retrait passe par l'état sûr : consigne 0 W.
+
+ +
+ +
+
+
Paliers déclarés : 0 · 600 · 1 200 · 1 800 · 2 400 W
+
+

L'app ne propose plus ce mode — le plugin lui-même le qualifie de legacy — mais + elle ne le réécrit pas en douce : basculer une charge de fixed vers + dynamic change l'arbitrage réel, et cela demande un geste explicite. + La conversion exige aussi une + puissance nominale, que la déclaration en paliers ne porte pas : la box refuse + dynamic sans maxPowerW, et refuse powerLevels avec. +

+
+
+ Convertir en modulation continue…
+
+
+
+ +
Annuler
Enregistrer
+
+
+
Modulable — une consigne continue
+
+ NymeaEnergy.SetLoadConfig
+ etmvariableload : un seul appareil, une consigne en watts, aucun palier. + relays[] y est interdit — la box refuse en bloc. +
    +
  • Le critère de choix entre les deux mécanismes, et c'est le seul qui compte pour + l'installateur : où sont les contacteurs ? Sur les sorties de la box → routeur de relais, et + il déclare {Thing, W} par contacteur. Dans l'appareil, piloté par une consigne → modulable, et il + ne déclare qu'un plafond.
  • +
  • Le mode fixed existe encore côté box — « multipalier natif, legacy » + dans les mots du plugin. L'app doit savoir le lire sans le proposer : une config déployée + qui l'utilise ne doit pas être convertie par le simple fait d'ouvrir l'écran.
  • +
  • Trois niveaux, trois responsables (contrat rév. 2) : le câblage au wizard Things, la + déclaration à cet écran, le runtime à l'interface. « Le thing décide du matériel ; + l'energymanager décide de la puissance. »
  • +
+
+
+ + +
+
+
‹

Configurer · PAC banc

+
+ +
+ + +
+ +
+ +
+ +
+
+ +
+ +
+ SG1 +
Relay4 Digital Output
+
+
+ SG2 +
Relay5 Digital Output
+
+
+ « SG1 » et « SG2 » n'existent pas côté box + sgReady.states[] ne stocke qu'une liste de relais par état, sans nom ni ordre. + Les deux étiquettes sont une convention de cet écran, et elles se retrouvent à la lecture : + SG1 = le contact fermé en état 1 (blocage), SG2 = celui fermé en état 3 + (recommandation). C'est l'encodage SG-Ready standard, et c'est celui du banc. +
+
+ ⚠ Un encodage non standard perd les étiquettes + Si la configuration lue ne correspond pas à ce schéma — trois contacts, ou un état 1 sans contact + fermé — l'écran doit renoncer aux noms SG1/SG2 et afficher les relais tels quels, plutôt que + d'attribuer une étiquette au hasard. Le tableau ci-dessous reste éditable ; ce sont les noms qui + disparaissent, pas la configuration. +
+
+ +
+ + +
+
1Blocagearrêt forcé
+
+ SG1 ferméSG2 ouvert +
+
+
+ +
+
2Normalaucune consigne
+
+ SG1 ouvertSG2 ouvert +
+
+
+ +
+
3Recommandationincitation douce
+
+ SG1 ouvertSG2 fermé +
+
+
+ +
+
4Forcémarche imposée
+
+ SG1 ferméSG2 fermé +
+
+
+ +
+ ⚠ L'état 2 doit être exprimable + Si l'encodage des contacts ne permet pas de représenter l'état « normal », la pompe à chaleur + n'est jamais pilotable : la box publie UNUSABLE_ENCODING et la charge sort de + l'arbitrage définitivement (ECS-110). Le formulaire doit le refuser avant l'envoi. +
+
+ Une estimation, jamais un engagement + estimatedPowerW sert de base au recrédit de budget et au choix d'état : une + valeur fausse ne décale pas seulement la PAC, elle décale l'allocation de tout le waterfall en + aval. À relever sur la plaque signalétique, puis à mesurer sous les deux régimes. +
+
+ +
+ +
+
Maintien d'état minimaldurée avant tout changement d'état
+
300 s
+
+
Pendant ce maintien, la télémétrie publie le verrou + LOCK_MIN_STATE_HOLD avec les secondes restantes — c'est ce que l'écran affiche + « au dernier cycle », sans le décompter lui-même.
+
+ + +
+ +
+
+
Rattacher un compteur à cette charge
+ +
+
+
Choisir un appareil de mesure… ▾
+
+

Aucun champ n'existe côté box. SetLoadConfig n'accepte que adapter · enabled · id · label · mode · priority · domain · maxPowerW · minOffS · minOnS · needs · powerLevels · relays · sgReady. nymea rejette toute clé inconnue avant le handler : inventer meterThingId ferait échouer l'enregistrement de toutes les charges.

+
+
+
+ +
+
Inclure dans l'arbitragerang 2 · Chauffage
+
+
+
À la désactivation, le retrait passe par l'état sûr : état 2, + jamais le blocage. Vérifié sur matériel côté plugin.
+
+ +
Annuler
Enregistrer
+
+
+
SG-Ready — même contrat que le relais
+
+ NymeaEnergy.SetLoadConfig
+ Même famille que le routeur de relais : c'est une LoadConfig, elle est arbitrée par le + waterfall, elle apparaît dans GetLoadTelemetry, elle a un rang et un domaine. +
    +
  • Ce que les contacts commandent, ce sont des états normés 1→4, pas des watts. Les watts + n'y sont qu'une estimation déclarée — d'où le libellé.
  • +
  • Toucher aux contacts ou à la table d'états franchit sameHardware() : + reconstruction de l'adaptateur et réarmement des verrous à froid.
  • +
  • Le choix des Things se fait une fois, en haut — pas état par état. Ce qui se coche dans le + tableau, c'est fermé / ouvert, et c'est ça qui devient relays[] à + l'enregistrement : la liste des contacts fermés pour cet état.
  • +
+
+
+ + +
+
+
‹

Configurer · Borne de recharge

+
+ +
+ +
Terra AC Charger (TCP) ▾
+
+ +
+ +
Recharge sur surplusSurplusCharging
+
+ +
+ +
+ + + +
+
+ PV → ChargingModeEco · Min+PV → ChargingModeEcoWithMinCurrent · + Boost → ChargingModeNormal
+ avec échéance → ChargingModeEcoWithTargetTime / + ChargingModeEcoMinWithTargetTime +
+
C'est un défaut, pas une commande. Le pilotage au jour le jour se + fait depuis la carte véhicule du tableau de bord ; ici on règle ce vers quoi la borne revient. + Les deux écrivent le même chargingMode — d'où l'importance de ne pas laisser croire + que ce bouton agit à l'instant.
+
+ +
+ +
+
Fixer une heure de départbascule le mode en « …WithTargetTime »
+
+
+
+
Charge cibletargetPercentage
+
80 %
+
+
+
Prête àendDateTime
+
07:30
+
+
Répéter : Lun · Mar · Mer · Jeu · Ven ▾
+
+ +
+ +
+
+
Courant maintenu en Min+PV
+
+ 6 A +
+
+

Le mode existe, le réglage n'existe pas. ChargingModeEcoWithMinCurrent est + bien dans l'énumération de la box, mais ChargingInfo n'a aucun champ de courant + minimum — ni minCurrent, ni équivalent. Soit la valeur est fixée ailleurs (paramètre + du Thing borne), soit le champ manque. À trancher avec l'agent plugin avant de livrer ce + mode : proposer un réglage qui ne part nulle part serait pire que de ne pas le proposer.

+
+
+ + +
+ +
+
+
Rattacher un compteur à la borne
+ +
+
+
Choisir un appareil de mesure… ▾
+
+

Aucun champ n'existe côté box. ChargingInfo ne porte que chargingMode · evChargerId · assignedCarId · dailySpotMarketPercentage · endDateTime · repeatDays · spotMarketChargingEnabled · targetPercentage, plus chargingState en lecture. Rien pour un compteur — et la borne mesure déjà, ce qui pose la question de quelle mesure fait foi.

+
+
+
+ +
+
Recharger aux heures les moins chèresPredict AI
+
+
+
+ +
Annuler
Enregistrer
+
+
+
Borne — un autre contrat
+
+ NymeaEnergy.SetChargingInfo
+ Ce n'est pas une LoadConfig. Le véhicule n'a ni adapter, ni + priority, ni domain — et il n'apparaît pas dans + GetLoadTelemetry en beta : il est décidé en amont du waterfall, et sa puissance est + seulement réservée dans budget.evReservedW. Il rejoindra loads[] à + l'étape 3g. +
    +
  • Les noms de champs de CLAUDE.md sont périmés. La box déploie + chargingMode (pas mode), targetPercentage (pas + targetSoc), endDateTime en Uint (pas endTime), plus + assignedCarId, repeatDays et deux champs spot market. À corriger dans + CLAUDE.md.
  • +
  • Le namespace est NymeaEnergy.*, pas EnergyPlugin.*.
  • +
+
+
+ + +
+
+
‹

Zones de climatisation

+
+
+ +
+ + +
+
+ Séjour +
20,4 °consigne 21,0 °
+
+
+ 2 thermostats1 vanne + 1 sonde int.1 contact fenêtre +
+
⏱ Dérogation 22,0 ° — encore 45 min
+
+ +
+
+ Chambres +
18,9 °consigne 19,0 °
+
+
+ 3 thermostats1 sonde int. +
+
+ +
+
+ Bureau +
21,1 °réduit 17,0 °
+
+
+ 1 thermostathumidité 47 % + COV 320 +
+
+
+ +
+ + +
Thermostats2 appareils▾
+
Vannes1 appareil▾
+
Sondes intérieures1 appareil▾
+
Sondes extérieuresaucune▾
+
Contacts de fenêtre1 appareil▾
+
Notificationsaucune▾
+
+ + +
+ +
+
+
Rattacher un compteur à la zone
+ +
+
+
Choisir un appareil de mesure… ▾
+
+

Aucun champ n'existe côté box. ZoneInfo rattache des indoorSensors et outdoorSensors — des sondes de température, pas de consommation. Un compteur de zone est un champ à ajouter, et il faudra dire s'il mesure la zone ou l'appareil qui la chauffe.

+
+
+
+ +
+ + +
+
+
Consigne dérogéesetpointOverride
+
22,0 °
+
+
+
Duréeminutes — mode minuté seul
+
60 min
+
+
+ +
+ +
+
Hors planningstandbySetpoint
+
17,0 °
+
+
+ +
+ +
7 jours · 4 créneaux par jour ›
+
Écran dédié — la forme exacte de TemperatureWeekSchedule reste + à introspecter, le type n'est pas détaillé dans le schéma de cette box.
+
+ +
+ Sur le banc, il n'y a aucune zone + AirConditioning.GetZones renvoie zones: [] sur .75. + L'état vide ci-dessous est donc le premier écran que verra un installateur. +
+ +
+ Aucune zone déclarée +

Une zone regroupe des thermostats, des vannes et des sondes sous une même consigne. + Sans zone, la climatisation n'est pas pilotée par l'énergie.

+ +
+ +
+
+
HVAC — encore un autre contrat
+
+ AirConditioning.* · tier Auto
+ Troisième contrat : ni LoadConfig, ni ChargingInfo. Une zone n'a pas de + rang de service ni de domaine — c'est un regroupement de Things sous une consigne de température. +
    +
  • 8 méthodes, pas 3 comme l'annonce CLAUDE.md : s'ajoutent + AddZone, RemoveZone, SetZoneName, + SetZoneThings et SetZoneStandbySetpoint. Le CRUD complet est donc + possible depuis l'app.
  • +
  • Une zone expose en lecture seule température, humidité, PM2.5, COV, statut, et la + dérogation en cours avec sa fin. Tout le reste des champs de ZoneInfo est + r: — on écrit par les six méthodes Set*, jamais par un objet complet.
  • +
  • Lien avec l'énergie à établir : rien dans ZoneInfo ne relie une zone à une + LoadConfig. La PAC SG-Ready et les zones vivent côte à côte sans se connaître — si + l'ordre de service doit tenir compte des zones, c'est une question de conception, pas de câblage.
  • +
+
+
+ + + +
+
+
‹

Séjour · Thermostats

✓
+
+ +
+ +
+ Thermostats + Vannes + Sondes int. + Sondes ext. + Fenêtres + Notifications +
+
Six listes distinctes, six paramètres de SetZoneThings. On + n'envoie que celles qu'on a touchées — les autres sont optionnelles et restent en place.
+
+ +
+ + + +
+ +
Thermostat Séjour NordDanfoss Ally · thermostat
+
+
+ +
Thermostat Séjour SudDanfoss Ally · thermostat
+
+
+ +
Thermostat Chambre 1Danfoss Ally · thermostat
+ zone « Chambres » +
+
+ +
Thermostat BureauShelly TRV · thermostat
+
+ +
+ Un appareil dans deux zones ? + Le schéma ne l'interdit pas — SetZoneThings prend une liste d'UUID sans contrainte + d'unicité déclarée. Tant que ce n'est pas éprouvé sur la box, l'app signale l'appartenance + existante sans bloquer, et ne prétend pas connaître la règle. +
+ +
+ ⚠ Sur le banc, ces listes sont vides + Les 58 ThingClass de .75 ne portent aucune interface thermostat, vanne ou + sonde de température — il n'y a pas de matériel HVAC dessus. Les appareils ci-dessus sont + fictifs : cet écran n'est pas éprouvable en l'état, et la première vérification à faire est + d'y brancher un thermostat réel. +
+
+ +
+ +
+
+
Rattacher un compteur à la zone
+ +
+
+
Filtrer sur « energymeter »… ▾
+
+

Le filtre, lui, existe déjà. .75 porte les interfaces + energymeter, smartmeter, smartmeterconsumer et + smartmeterproducer : le jour où le champ est ajouté, l'app saura proposer les bons + appareils sans table maintenue à la main. C'est le champ de destination qui manque, pas la + façon de choisir.

+
+
+ +
Annuler
Valider (2)
+
+
+
Zone — choisir les appareils
+
+ AirConditioning.SetZoneThings
+ Le même écran sert aux six rôles : thermostats, vannes, sondes intérieures, sondes extérieures, + contacts de fenêtre, notifications. Seul le filtre d'interface change. +
    +
  • Filtrer par interface, pas par nom de classe. ThingClass.interfaces est une + liste en lecture sur chaque classe ; s'en servir évite la table de correspondance codée en dur que + le brief interdit — et qui vieillirait à chaque plugin ajouté.
  • +
  • Ne rien attendre du vocabulaire affiché. Mesuré sur .75 : sur 784 chaînes, + le français en traduit zéro. Les displayName sont des identifiants anglais, à + afficher tels quels.
  • +
+
+
+ +
+ + diff --git a/docs/mockups/raccordement_reseau_mockup.html b/docs/mockups/raccordement_reseau_mockup.html new file mode 100644 index 0000000..f445bf4 --- /dev/null +++ b/docs/mockups/raccordement_reseau_mockup.html @@ -0,0 +1,321 @@ + + + + + +ETM PowerSync — Raccordement réseau et protection de surcharge + + + + + + + +
+

Raccordement réseau — la limite du disjoncteur, et ce qu'elle protège vraiment

+

Le réglage existe déjà côté box : NymeaEnergy.SetPhasePowerLimit. Sa place naturelle est + bien le compteur réseau, puisqu'il n'a de sens qu'avec lui — la protection est inopérante sans + compteur racine.

+

Trois pièges relevés en lisant la box et le plugin : ① le champ s'appelle « power » mais son unité + est l'ampère par phase ; ② 0 = désactivé, et cela coupe aussi toute la recharge intelligente ; + ③ la protection ne déleste que le véhicule — aucune charge du waterfall n'est réduite par elle.

+
+ +
+ + +
+
+
‹

Rôles & appareils

⟳
+
+ +
Compteurs · mesure — hors priorité
+ +
+
+
⚡
+
Compteur réseauFronius Three Phase Meter · SunSpec
+ rootMeter +
+ +
+
Phase A9,2 A
+
Phase B4,1 A
+
Phase C28,7 A
+
+ +
+ Raccordement & protection + 32 A / phase + › +
+
+ +
+ Pourquoi ici + La limite n'a de sens qu'attachée au compteur racine : la protection lit + currentPowerPhaseA/B/C sur ce Thing. Sans compteur réseau, la box journalise + « overload protection is not available » et ne fait rien. +
+ +
Compteur solaire
+
+
+
☀
+
Compteur solaireFronius Three Phase Inverter
+ auto +
+
+ +
+
+
L'entrée — sur la carte du compteur réseau
+
+ Les trois ampérages par phase sont déjà lisibles : ce sont les états + currentPowerPhaseA/B/C du Thing compteur, divisés par la tension. Les afficher ici rend la + limite concrète — l'installateur voit tout de suite laquelle de ses phases approche. +
    +
  • Rien de tout ça n'est encore branché dans l'app : l'écran actuel n'affiche que le nom du + Thing et le badge rootMeter.
  • +
+
+
+ + +
+
+
‹

Raccordement & protection

+
+ +
+ +
+ +
32A
+ +
+
+ 15 A30 A + 32 A45 A + 60 A63 A +
+
Des ampères, pas des watts. Le champ s'appelle + phasePowerLimit mais la box attend un courant par phase — sa propre description le + dit : « maximum allowed Ampere per phase ». Un formulaire en kVA se tromperait de grandeur.
+ +
+ Équivalence indicative + 32 A × 230 V × 3 phases ≈ 22 kVA + Déduit, jamais enregistré. La box + convertit avec 230 V codés en dur quand la tension mesurée n'est pas disponible ; le nombre + de phases vient des états du compteur, pas d'un réglage. +
+ +
+ ⚠ 0 A ne veut pas dire « pas de limite » + 0 désactive la protection — et avec elle toute la recharge intelligente + (« All smart charging will be disabled »). C'est la valeur actuelle du banc + .75, et c'est pour ça que son journal répète + « overload protection is disabled ». Un champ vide ne doit jamais être enregistré comme 0 + par inadvertance. +
+
+ +
+ +
+
Borne de rechargebridée, puis coupée si nécessaire
+
+
+
+
Chauffe-eau, PAC, charges modulablesnon concernées aujourd'hui
+
+
+
+ ⚠ Ce n'est pas un délestage général + Quand une phase dépasse, la box parcourt uniquement ses bornes de recharge + (m_evChargers) et réduit leur courant, jusqu'à couper si le minimum n'est plus + tenable. Les charges du waterfall — routeur de relais, modulable, SG-Ready — ne sont + pas réduites par cette protection. + Étendre le délestage à ces charges est une question + côté plugin, pas côté app. Tant qu'elle n'est pas tranchée, l'écran ne doit pas laisser + croire que le raccordement est protégé dans son ensemble. +
+
+ +
+ +
+
Surplus requis pour lancervaleur banc : 0,5
+
0,5
+
+
La box décrit « how much solar surplus is required to start the + charging » sans donner d'unité. À faire préciser avant d'écrire un suffixe : afficher + « 0,5 kW » ou « 0,5 A » sur une supposition serait inventer la grandeur.
+
+ +
+ +
+
Part du stockage considéréevaleur banc : 0,9
+
0,9
+
+
+ +
+ +
+
Verrouiller la borne au retrait du câblelockOnUnplug
+
+
+
+ +
+ +
+
Ordre de délestage en surcharge
+

À ne pas confondre avec l'ordre de service. L'ordre de « Charges pilotables » répartit + un surplus ; un ordre de délestage retirerait de la puissance en surcharge. Ce + sont deux listes qui peuvent différer — et la seconde n'a aujourd'hui aucun support côté box. + À spécifier avec l'agent plugin si le délestage doit dépasser le véhicule.

+
+
+ +
Annuler
Enregistrer
+
+
+
Raccordement — la limite et ses voisins
+
+ Quatre réglages globaux vivent au même endroit parce qu'ils partagent le même public — l'installateur — + et le même namespace : SetPhasePowerLimit, SetAcquisitionTolerance, + SetBatteryLevelConsideration, SetLockOnUnplug. +
    +
  • Valeurs lues sur .75 le 2026-08-25 : phasePowerLimit: 0 (non + réglé), acquisitionTolerance: 0.5, batteryLevelConsideration: 0.9.
  • +
  • Deux unités restent à faire confirmer par l'agent plugin avant d'écrire un suffixe à + l'écran : celle de la tolérance d'acquisition, et celle de la prise en compte batterie (un ratio + 0–1, ou un pourcentage ?).
  • +
  • La protection exige un compteur racine et des états par phase. Un compteur monophasé ou + sans détail de phase la rend inopérante — l'écran doit le dire au lieu d'afficher un réglage sans + effet.
  • +
+
+
+ +
+ +