etm-powersync-app/docs/mockups/raccordement_reseau_mockup.html
Patrick Schurig 054cbfab3c docs: maquettes de la refonte « Charges pilotables » + CLAUDE.md recalé sur la box
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) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XPUo3RMr8SzK6qbFtfBm8H
2026-08-25 21:30:32 +02:00

322 lines
18 KiB
HTML
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.

<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>ETM PowerSync — Raccordement réseau et protection de surcharge</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@500;600;700&family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
<style>
:root{
--bg:#081f2b; --panel:#0d2b3b; --card:#12384a; --card2:#0f3242;
--line:rgba(255,255,255,.08); --line2:rgba(255,255,255,.14);
--blue:#31a3dd; --yellow:#fec113; --green:#35c98a; --red:#e07a7a; --violet:#9d8ce0;
--txt:#e8f1f6; --muted:#90a8b6; --muted2:#6f8b9a;
}
*{box-sizing:border-box}
body{margin:0;background:
radial-gradient(900px 500px at 15% -10%, #0e3145 0%, transparent 60%),
radial-gradient(900px 600px at 110% 10%, #0b2535 0%, transparent 55%),
var(--bg);
font-family:'Inter',system-ui,sans-serif;color:var(--txt);min-height:100vh;padding:34px 20px 60px}
.head{max-width:1000px;margin:0 auto 26px}
.head h1{font-family:'Poppins';font-weight:700;font-size:20px;margin:0 0 6px}
.head p{margin:0 0 8px;color:var(--muted);font-size:13.5px;line-height:1.55;max-width:880px}
.head code{color:#9fd4ee} .head b{color:var(--yellow)}
.stage{display:flex;gap:34px;justify-content:center;flex-wrap:wrap;align-items:flex-start}
.col{display:flex;flex-direction:column;align-items:center;gap:12px;max-width:392px}
.cap{color:var(--muted2);font-size:12px;font-family:'Poppins';font-weight:500;letter-spacing:.4px;text-transform:uppercase}
.cap b{color:var(--blue)}
.phone{width:392px;background:var(--panel);border:1px solid var(--line2);border-radius:30px;
overflow:hidden;box-shadow:0 30px 80px -30px rgba(0,0,0,.7);display:flex;flex-direction:column;height:850px}
.appbar{display:flex;align-items:center;gap:12px;padding:18px 16px 14px;border-bottom:1px solid var(--line);
background:linear-gradient(#102f40,#0d2b3b)}
.appbar .ic{width:26px;height:26px;display:grid;place-items:center;color:var(--muted);font-size:18px}
.appbar h2{font-family:'Poppins';font-weight:600;font-size:15px;margin:0;flex:1;text-align:center}
.scroll{overflow:auto;padding:16px 15px 26px}
.scroll::-webkit-scrollbar{width:0}
.sect{font-family:'Poppins';font-weight:600;font-size:12px;color:var(--muted);letter-spacing:.3px;
margin:20px 0 9px}
.sect:first-child{margin-top:2px}
.field{margin-top:18px} .field:first-child{margin-top:4px}
.field>label{display:block;font-family:'Poppins';font-weight:600;font-size:12px;color:var(--muted);
letter-spacing:.3px;margin-bottom:9px}
.field>label small{color:var(--muted2);font-weight:500;letter-spacing:0}
.saved-flag{font-size:10.5px;color:var(--green);background:rgba(53,201,138,.12);border:1px solid rgba(53,201,138,.3);
border-radius:20px;padding:2px 8px;font-weight:600;float:right;margin-top:-2px}
.derived-flag{font-size:10.5px;color:var(--muted2);background:rgba(255,255,255,.05);border:1px solid var(--line2);
border-radius:20px;padding:2px 8px;font-weight:600;float:right;margin-top:-2px}
.todo-flag{font-size:10.5px;color:var(--yellow);background:rgba(254,193,19,.1);border:1px solid rgba(254,193,19,.32);
border-radius:20px;padding:2px 8px;font-weight:600;float:right;margin-top:-2px}
.sel{width:100%;background:var(--card2);border:1px solid var(--line2);border-radius:10px;color:var(--txt);
font-family:'Inter';font-size:12.5px;padding:11px 12px;display:flex;align-items:center;gap:8px}
.sel i{margin-left:auto;font-style:normal;color:var(--muted2);font-size:11px}
.mode-hint{margin-top:9px;font-size:11.5px;color:var(--muted2);line-height:1.5}
.mode-hint b{color:var(--muted)}
.meter{background:var(--card);border:1px solid var(--line);border-radius:14px;padding:13px}
.meter .m1{display:flex;align-items:center;gap:11px}
.meter .ico{width:38px;height:38px;border-radius:11px;background:rgba(254,193,19,.13);display:grid;
place-items:center;font-size:19px;flex:none}
.meter .m1 .nm{flex:1;min-width:0}
.meter .m1 .nm b{display:block;font-family:'Poppins';font-weight:600;font-size:13.5px}
.meter .m1 .nm span{font-size:11px;color:var(--muted2)}
.tag{font-size:10.5px;color:var(--blue);background:rgba(49,163,221,.12);border:1px solid rgba(49,163,221,.3);
border-radius:20px;padding:3px 9px;font-family:'Poppins';font-weight:600}
.phases{display:grid;grid-template-columns:repeat(3,1fr);gap:6px;margin-top:11px}
.ph{background:var(--card2);border:1px solid var(--line);border-radius:10px;padding:8px 5px;text-align:center}
.ph u{display:block;text-decoration:none;font-size:9.5px;color:var(--muted2);margin-bottom:2px}
.ph b{font-family:'Poppins';font-weight:600;font-size:12.5px}
.ph.hot b{color:var(--red)}
.gorow{margin-top:11px;display:flex;align-items:center;gap:9px;background:var(--card2);border:1px solid var(--line2);
border-radius:11px;padding:11px 12px;cursor:pointer}
.gorow .lab{flex:1;font-size:12.5px}
.gorow .val{font-family:'Poppins';font-weight:600;font-size:12.5px;color:var(--yellow)}
.gorow i{font-style:normal;color:var(--muted2)}
.amp{display:flex;align-items:center;justify-content:space-between;padding:14px;border:1px solid var(--line2);
border-radius:13px;background:var(--card2)}
.amp .v{font-family:'Poppins';font-weight:700;font-size:26px}
.amp .v small{font-size:14px;font-weight:600;color:var(--muted);margin-left:3px}
.pm{width:34px;height:34px;border-radius:10px;border:1px solid var(--line2);color:var(--blue);background:transparent;
font-size:19px;display:grid;place-items:center;cursor:pointer}
.presets{display:flex;gap:6px;margin-top:9px;flex-wrap:wrap}
.preset{font-size:11px;border-radius:20px;padding:6px 12px;border:1px solid var(--line2);
background:var(--card2);color:var(--muted);font-family:'Poppins';font-weight:600;cursor:pointer}
.preset.on{border-color:var(--blue);color:var(--blue);background:rgba(49,163,221,.1)}
.stepper{display:flex;align-items:center;justify-content:space-between;padding:11px 12px;border:1px solid var(--line);
border-radius:11px;background:var(--card2);margin-bottom:8px}
.stepper .lab{font-size:12.5px}.stepper .lab small{display:block;color:var(--muted2);font-size:10.5px;margin-top:1px}
.stepper .ctl{display:flex;align-items:center;gap:13px}
.stepper .ctl b{font-family:'Poppins';font-weight:600;font-size:13.5px;min-width:64px;text-align:right}
.stepper .pm{width:26px;height:26px;border-radius:8px;font-size:16px}
.sw{width:38px;height:22px;border-radius:20px;background:rgba(53,201,138,.85);position:relative;cursor:pointer;flex:none}
.sw::after{content:"";position:absolute;top:2px;left:18px;width:18px;height:18px;border-radius:50%;background:#fff}
.sw.off{background:rgba(255,255,255,.16)} .sw.off::after{left:2px}
.warn{margin-top:10px;border:1px solid rgba(224,122,122,.32);background:rgba(224,122,122,.07);
border-radius:11px;padding:10px 11px;font-size:11px;color:var(--muted);line-height:1.5}
.warn b{color:var(--red);display:block;font-family:'Poppins';font-size:11.5px;margin-bottom:3px}
.info{margin-top:10px;border:1px solid rgba(49,163,221,.26);background:rgba(49,163,221,.06);
border-radius:11px;padding:10px 11px;font-size:11px;color:var(--muted);line-height:1.5}
.info b{color:var(--blue);display:block;font-family:'Poppins';font-size:11.5px;margin-bottom:3px}
.blocked{border:1px dashed rgba(254,193,19,.35);background:rgba(254,193,19,.05);border-radius:12px;padding:12px}
.blocked p{margin:9px 0 0;font-size:10.8px;color:var(--muted2);line-height:1.5} .blocked p b{color:var(--yellow)}
.blocked .row{display:flex;align-items:center;gap:10px;opacity:.55}
.blocked .row .lab{font-size:12.5px;flex:1}
.save{display:flex;gap:10px;margin-top:24px}
.btn{text-align:center;font-family:'Poppins';font-weight:600;font-size:13px;padding:12px;border-radius:11px;cursor:pointer}
.btn.go{background:var(--blue);color:#04222f;flex:2;border:0}
.btn.alt{flex:1.3;background:transparent;border:1px solid var(--line2);color:var(--txt)}
.note{width:392px;font-size:11.5px;color:var(--muted2);line-height:1.55;text-align:left}
.note b{color:var(--yellow)} .note code{color:#9fd4ee}
.note ul{margin:7px 0 0;padding-left:16px} .note li{margin-bottom:5px}
</style>
</head>
<body>
<div class="head">
<h1>Raccordement réseau — la limite du disjoncteur, et ce qu'elle protège vraiment</h1>
<p>Le réglage existe déjà côté box : <code>NymeaEnergy.SetPhasePowerLimit</code>. Sa place naturelle est
bien le <b>compteur réseau</b>, puisqu'il n'a de sens qu'avec lui — la protection est inopérante sans
compteur racine.</p>
<p><b>Trois pièges relevés en lisant la box et le plugin :</b> ① le champ s'appelle « power » mais son unité
est l'<b>ampère par phase</b> ; ② <b>0 = désactivé</b>, et cela coupe aussi toute la recharge intelligente ;
③ la protection ne déleste <b>que le véhicule</b> — aucune charge du waterfall n'est réduite par elle.</p>
</div>
<div class="stage">
<!-- ══════════ ENTRÉE : CARTE COMPTEUR RÉSEAU ══════════ -->
<div class="col">
<div class="phone">
<div class="appbar"><div class="ic">‹</div><h2>Rôles &amp; appareils</h2><div class="ic">⟳</div></div>
<div class="scroll">
<div class="sect">Compteurs · mesure — hors priorité</div>
<div class="meter">
<div class="m1">
<div class="ico">⚡</div>
<div class="nm"><b>Compteur réseau</b><span>Fronius Three Phase Meter · SunSpec</span></div>
<span class="tag">rootMeter</span>
</div>
<div class="phases">
<div class="ph"><u>Phase A</u><b>9,2 A</b></div>
<div class="ph"><u>Phase B</u><b>4,1 A</b></div>
<div class="ph hot"><u>Phase C</u><b>28,7 A</b></div>
</div>
<div class="gorow">
<span class="lab">Raccordement &amp; protection</span>
<span class="val">32 A / phase</span>
<i>›</i>
</div>
</div>
<div class="info">
<b>Pourquoi ici</b>
La limite n'a de sens qu'attachée au compteur racine : la protection lit
<code>currentPowerPhaseA/B/C</code> sur ce Thing. Sans compteur réseau, la box journalise
« <i>overload protection is not available</i> » et ne fait rien.
</div>
<div class="sect">Compteur solaire</div>
<div class="meter" style="opacity:.75">
<div class="m1">
<div class="ico" style="background:rgba(53,201,138,.13)">☀</div>
<div class="nm"><b>Compteur solaire</b><span>Fronius Three Phase Inverter</span></div>
<span class="tag" style="color:var(--green);background:rgba(53,201,138,.12);
border-color:rgba(53,201,138,.3)">auto</span>
</div>
</div>
</div>
</div>
<div class="cap">L'entrée — sur la carte du compteur réseau</div>
<div class="note">
Les trois ampérages par phase sont <b>déjà lisibles</b> : ce sont les états
<code>currentPowerPhaseA/B/C</code> 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.
<ul>
<li><b>Rien de tout ça n'est encore branché dans l'app</b> : l'écran actuel n'affiche que le nom du
Thing et le badge <code>rootMeter</code>.</li>
</ul>
</div>
</div>
<!-- ══════════ ÉCRAN RACCORDEMENT ══════════ -->
<div class="col">
<div class="phone">
<div class="appbar"><div class="ic">‹</div><h2>Raccordement &amp; protection</h2><div class="ic"></div></div>
<div class="scroll">
<div class="field">
<label>Courant maximal par phase <span class="saved-flag">enregistré · phasePowerLimit</span><br>
<small>le calibre du disjoncteur de branchement</small></label>
<div class="amp">
<button class="pm">−</button>
<div class="v">32<small>A</small></div>
<button class="pm">+</button>
</div>
<div class="presets">
<span class="preset">15 A</span><span class="preset">30 A</span>
<span class="preset on">32 A</span><span class="preset">45 A</span>
<span class="preset">60 A</span><span class="preset">63 A</span>
</div>
<div class="mode-hint"><b>Des ampères, pas des watts.</b> Le champ s'appelle
<code>phasePowerLimit</code> mais la box attend un courant par phase — sa propre description le
dit : « <i>maximum allowed Ampere per phase</i> ». Un formulaire en kVA se tromperait de grandeur.</div>
<div class="info">
<b>Équivalence indicative</b>
32 A × 230 V × 3 phases ≈ <b>22 kVA</b>
<span style="display:block;margin-top:5px;color:var(--muted2)">Déduit, jamais enregistré. La box
convertit avec <b>230 V codés en dur</b> quand la tension mesurée n'est pas disponible ; le nombre
de phases vient des états du compteur, pas d'un réglage.</span>
</div>
<div class="warn">
<b>⚠ 0 A ne veut pas dire « pas de limite »</b>
0 <b>désactive la protection</b> — et avec elle <b>toute la recharge intelligente</b>
(« <i>All smart charging will be disabled</i> »). C'est la valeur actuelle du banc
<code>.75</code>, et c'est pour ça que son journal répète
« <i>overload protection is disabled</i> ». Un champ vide ne doit jamais être enregistré comme 0
par inadvertance.
</div>
</div>
<div class="field">
<label>Ce que la protection déleste <span class="derived-flag">comportement observé</span></label>
<div class="stepper">
<div class="lab">Borne de recharge<small>bridée, puis coupée si nécessaire</small></div>
<div class="ctl"><span class="sw"></span></div>
</div>
<div class="stepper" style="opacity:.55">
<div class="lab">Chauffe-eau, PAC, charges modulables<small>non concernées aujourd'hui</small></div>
<div class="ctl"><span class="sw off"></span></div>
</div>
<div class="warn">
<b>⚠ Ce n'est pas un délestage général</b>
Quand une phase dépasse, la box parcourt <b>uniquement ses bornes de recharge</b>
(<code>m_evChargers</code>) 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
<b>pas</b> réduites par cette protection.
<span style="display:block;margin-top:6px">Étendre le délestage à ces charges est une question
<b>côté plugin</b>, 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.</span>
</div>
</div>
<div class="field">
<label>Seuil de démarrage sur surplus <span class="saved-flag">enregistré · acquisitionTolerance</span></label>
<div class="stepper">
<div class="lab">Surplus requis pour lancer<small>valeur banc : 0,5</small></div>
<div class="ctl"><button class="pm">−</button><b>0,5</b><button class="pm">+</button></div>
</div>
<div class="mode-hint">La box décrit « <i>how much solar surplus is required to start the
charging</i> » sans donner d'unité. <b>À faire préciser avant d'écrire un suffixe</b> : afficher
« 0,5 kW » ou « 0,5 A » sur une supposition serait inventer la grandeur.</div>
</div>
<div class="field">
<label>Prise en compte de la batterie <span class="saved-flag">enregistré · batteryLevelConsideration</span></label>
<div class="stepper">
<div class="lab">Part du stockage considérée<small>valeur banc : 0,9</small></div>
<div class="ctl"><button class="pm">−</button><b>0,9</b><button class="pm">+</button></div>
</div>
</div>
<div class="field">
<label>Verrouillage au débranchement <span class="saved-flag">enregistré · lockOnUnplug</span></label>
<div class="stepper">
<div class="lab">Verrouiller la borne au retrait du câble<small>lockOnUnplug</small></div>
<div class="ctl"><span class="sw off"></span></div>
</div>
</div>
<div class="field">
<label>Délestage par charge <span class="todo-flag">n'existe pas</span></label>
<div class="blocked">
<div class="row"><div class="lab">Ordre de délestage en surcharge</div><span class="sw off"></span></div>
<p><b>À ne pas confondre avec l'ordre de service.</b> L'ordre de « Charges pilotables » répartit
un <b>surplus</b> ; un ordre de délestage retirerait de la puissance en <b>surcharge</b>. 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.</p>
</div>
</div>
<div class="save"><div class="btn alt">Annuler</div><div class="btn go">Enregistrer</div></div>
</div>
</div>
<div class="cap"><b>Raccordement</b> — la limite et ses voisins</div>
<div class="note">
Quatre réglages globaux vivent au même endroit parce qu'ils partagent le même public — l'installateur —
et le même namespace : <code>SetPhasePowerLimit</code>, <code>SetAcquisitionTolerance</code>,
<code>SetBatteryLevelConsideration</code>, <code>SetLockOnUnplug</code>.
<ul>
<li><b>Valeurs lues sur <code>.75</code> le 2026-08-25 :</b> <code>phasePowerLimit: 0</code> (non
réglé), <code>acquisitionTolerance: 0.5</code>, <code>batteryLevelConsideration: 0.9</code>.</li>
<li><b>Deux unités restent à faire confirmer</b> 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 ?).</li>
<li>La protection exige un compteur racine <b>et</b> 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.</li>
</ul>
</div>
</div>
</div>
</body>
</html>