Trois corrections de Patrick sur la section batterie, plus le brief séparé. 1. LE BOOST CHANGE DE NATURE, et ce n'est pas un détail de libellé. Ce n'est pas « vider la batterie dans la voiture » : c'est charger la voiture plus vite que l'abonnement ne le permet. Un client en 6 kVA veut 7,5 ; le réseau donne 6, la batterie comble l'écart, le disjoncteur ne saute pas. Le réglage devient donc une PUISSANCE DE CHARGE VISÉE, pas une cible de SOC batterie, et la première des trois fins devient « la voiture a atteint sa cible » — le plancher batterie reste, mais comme garde-fou et non comme objectif. On ne cherche pas à vider la batterie, on cherche à finir la charge plus tôt. Ma réserve « le boost pourrait acheter en cachette » tombe, et elle tombe à l'envers : ce mécanisme existe précisément pour NE PAS dépasser ce qu'on a le droit de soutirer. Écrit dans la note : ce bloc appartient au délestage de LM-1006-1 vu par l'autre bout — au lieu de réduire les charges pour tenir sous le plafond, on complète par la batterie. Même objet, pas un mécanisme parallèle. Et c'est un argument de vente réel, donc il est dit à l'écran : recharger plus vite sans passer de 6 à 9 kVA. 2. L'INTERRUPTEUR DE DÉCHARGE PROMETTAIT CE QUE LE MÉCANISME NE TIENT PAS. Il disait « la maison continue d'être servie ; seule la voiture ne l'est pas ». enableDischarging est binaire et l'onduleur ne voit qu'une consommation nette : coupé, le réfrigérateur passe au réseau comme la voiture. Reformulé en « geler la batterie », avec le mécanisme expliqué. La version modulée — plafonner dischargingRate sur la consommation hors borne, recalculé à chaque cycle — part au brief comme question, pas à l'écran comme réglage. C'est exactement la classe de défaut traquée depuis trois jours, et je l'avais écrite moi-même. 3. LA RÉSERVE ADAPTATIVE DEVIENT UN PLANCHER DE SECOURS. Mon avertissement sur Battery Life était juste mais incomplet : il disait ce que le bloc n'est pas sans dire ce qu'il est. Combien d'autonomie garder pour une coupure est une préférence de RISQUE — elle dépend du congélateur, du télétravail, de la fiabilité du réseau local. Héos ne pourra jamais la décider à la place du client, ce qui la qualifie exactement selon le critère qui a fait sortir les quatre autres blocs. Deux détails de cohérence : la batterie est la seule charge sans compteur dédié possible — elle publie currentPower elle-même — et c'est DIT plutôt qu'omis en silence comme dans les cinq autres écrans. Et chargingRate trouve sa place : plafond de soutirage sur la charge réseau, même motif que la recharge accélérée à l'envers. Le brief moteur est à part, dans BRIEF_plugin_depuis_maquettes.md : les trois questions de mécanisme (interdit actif, bail qui expire, controlHealthy), plus quatre que la recharge accélérée soulève — la boucle à la minute contre un disjoncteur en secondes, la marge dimensionnée contre le pire démarrage non piloté, le filet qui est la BORNE et non la batterie, et la latence du Fronius à mesurer sur le banc. Avec la conséquence honnête écrite noir sur blanc : en 6 kVA le gain réel est d'un à deux kilowatts, pas de 1,5 garanti. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016vrTVifar2GN5rtUh89Wkq
1328 lines
80 KiB
HTML
1328 lines
80 KiB
HTML
<!doctype html>
|
||
<html lang="fr">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>ETM PowerSync — Configurer : les cinq mécanismes</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:1300px;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:900px}
|
||
.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:880px}
|
||
.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}
|
||
|
||
.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}
|
||
.other-flag{font-size:10.5px;color:var(--violet);background:rgba(157,140,224,.12);border:1px solid rgba(157,140,224,.32);
|
||
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}
|
||
.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}
|
||
|
||
.inp{width:100%;background:var(--card2);border:1px solid var(--line2);border-radius:10px;color:var(--txt);
|
||
font-family:'Poppins';font-weight:600;font-size:13.5px;padding:11px 12px}
|
||
.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)}
|
||
|
||
.seg{display:flex;background:var(--card2);border:1px solid var(--line);border-radius:11px;padding:4px;gap:4px}
|
||
.seg button{flex:1;border:0;background:transparent;color:var(--muted);font-family:'Poppins';font-weight:600;
|
||
font-size:11.5px;padding:9px 4px;border-radius:8px;cursor:pointer}
|
||
.seg button.on{background:var(--blue);color:#04222f}
|
||
|
||
/* table d'états SG-Ready */
|
||
.st{border:1px solid var(--line);border-radius:12px;background:var(--card2);margin-bottom:9px;padding:11px 12px}
|
||
.st .h{display:flex;align-items:center;gap:9px;margin-bottom:8px}
|
||
.st .badge{width:24px;height:24px;border-radius:7px;font-family:'Poppins';font-weight:700;font-size:12px;
|
||
display:grid;place-items:center;flex:none}
|
||
.st .badge.s1{background:rgba(224,122,122,.18);color:var(--red)}
|
||
.st .badge.s2{background:rgba(255,255,255,.08);color:var(--muted)}
|
||
.st .badge.s3{background:rgba(53,201,138,.16);color:var(--green)}
|
||
.st .badge.s4{background:rgba(254,193,19,.16);color:var(--yellow)}
|
||
.st .h b{font-family:'Poppins';font-weight:600;font-size:12.5px;flex:1}
|
||
.st .h span{font-size:10.5px;color:var(--muted2)}
|
||
.st .contacts{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:8px}
|
||
.kchip{font-size:11px;border-radius:8px;padding:5px 9px;border:1px solid var(--line2);
|
||
background:var(--panel);color:var(--muted);font-family:'Poppins';font-weight:600;cursor:pointer}
|
||
.kchip.on{border-color:var(--blue);color:var(--blue);background:rgba(49,163,221,.1)}
|
||
.st .pw{display:flex;align-items:center;gap:9px}
|
||
.st .pw label{font-size:11px;color:var(--muted2);flex:1}
|
||
.st .pw input{width:96px;background:var(--panel);border:1px solid var(--line2);border-radius:9px;color:var(--txt);
|
||
font-family:'Poppins';font-weight:600;font-size:12.5px;padding:8px;text-align:center}
|
||
.st.locked{opacity:.62}
|
||
|
||
.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:56px;text-align:right}
|
||
.pm{width:26px;height:26px;border-radius:8px;border:1px solid var(--line2);color:var(--blue);background:transparent;
|
||
font-size:16px;display:grid;place-items:center;cursor:pointer}
|
||
.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}
|
||
|
||
/* EV */
|
||
.evmode{display:flex;gap:8px}
|
||
.evmode button{flex:1;border:1px solid var(--line2);background:var(--card2);color:var(--muted);
|
||
font-family:'Poppins';font-weight:600;font-size:12px;padding:14px 6px;border-radius:12px;cursor:pointer;
|
||
display:flex;flex-direction:column;align-items:center;gap:5px}
|
||
.evmode button.on{border-color:var(--green);background:rgba(53,201,138,.1);color:var(--green)}
|
||
.evmode button u{text-decoration:none;font-size:17px}
|
||
.evmode button small{font-weight:500;font-size:9.5px;color:var(--muted2)}
|
||
.evmode button.on small{color:rgba(53,201,138,.75)}
|
||
.maps{margin-top:10px;font-size:10.8px;color:var(--muted2);line-height:1.6}
|
||
.maps code{color:#9fd4ee}
|
||
.state{display:flex;align-items:center;gap:9px;background:var(--card2);border:1px solid var(--line);
|
||
border-radius:11px;padding:11px 12px}
|
||
.state .dot{width:8px;height:8px;border-radius:50%;background:var(--green);box-shadow:0 0 0 4px rgba(53,201,138,.15)}
|
||
.state b{font-family:'Poppins';font-size:12.5px;font-weight:600;flex:1}
|
||
.state i{font-style:normal;font-size:10.5px;color:var(--muted2)}
|
||
|
||
/* zones */
|
||
.zone{background:var(--card);border:1px solid var(--line);border-radius:13px;padding:12px;margin-bottom:9px}
|
||
.zone .z1{display:flex;align-items:center;gap:10px}
|
||
.zone .z1 b{font-family:'Poppins';font-weight:600;font-size:13.5px;flex:1}
|
||
.zone .temp{font-family:'Poppins';font-weight:700;font-size:17px}
|
||
.zone .temp small{font-size:11px;font-weight:500;color:var(--muted2);display:block;text-align:right}
|
||
.zone .z2{margin-top:9px;display:flex;gap:6px;flex-wrap:wrap}
|
||
.tchip{font-size:10.5px;border-radius:7px;padding:4px 8px;border:1px solid var(--line);
|
||
background:var(--card2);color:var(--muted2)}
|
||
.zone .ovr{margin-top:9px;padding-top:9px;border-top:1px dashed var(--line);font-size:11px;color:var(--yellow)}
|
||
.empty{border:1px dashed var(--line2);border-radius:13px;padding:22px 14px;text-align:center}
|
||
.empty b{display:block;font-family:'Poppins';font-size:13px;margin-bottom:5px}
|
||
.empty p{margin:0 0 13px;font-size:11.5px;color:var(--muted2);line-height:1.5}
|
||
.empty button{background:var(--blue);color:#04222f;border:0;border-radius:10px;font-family:'Poppins';
|
||
font-weight:600;font-size:12.5px;padding:10px 16px;cursor:pointer}
|
||
.rowsel{display:flex;align-items:center;gap:9px;background:var(--card2);border:1px solid var(--line);
|
||
border-radius:11px;padding:10px 11px;margin-bottom:7px}
|
||
.rowsel .lab{font-size:12px;flex:1}
|
||
.rowsel .cnt{font-size:11px;color:var(--muted2)}
|
||
.rowsel i{font-style:normal;color:var(--muted2);font-size:11px}
|
||
|
||
/* lignes de relais */
|
||
.relay{display:flex;align-items:center;gap:8px;margin-bottom:8px}
|
||
.relay .k{width:30px;font-family:'Poppins';font-weight:700;font-size:12px;color:var(--muted2);flex:none}
|
||
.relay .thing{flex:1;background:var(--card2);border:1px solid var(--line2);border-radius:10px;
|
||
font-size:12.5px;padding:10px 11px;display:flex;align-items:center;gap:6px;min-width:0}
|
||
.relay .thing i{margin-left:auto;font-style:normal;color:var(--muted2);font-size:10px}
|
||
.relay .w{width:88px;flex:none;background:var(--card2);border:1px solid var(--line2);border-radius:10px;
|
||
color:var(--txt);font-family:'Poppins';font-weight:600;font-size:13px;padding:10px 8px;text-align:center}
|
||
.relay .del{color:var(--muted2);font-size:14px;cursor:pointer;flex:none;width:14px;text-align:center}
|
||
.addrow{width:100%;background:transparent;border:1px dashed var(--line2);color:var(--muted);
|
||
font-family:'Poppins';font-weight:600;font-size:12px;padding:10px;border-radius:10px;cursor:pointer}
|
||
.preview{margin-top:11px;border:1px solid rgba(53,201,138,.22);background:rgba(53,201,138,.06);
|
||
border-radius:11px;padding:11px 12px;font-size:11.5px;color:var(--muted);line-height:1.55}
|
||
.preview b{color:var(--green);font-family:'Poppins'}
|
||
.preview em{display:block;font-style:normal;color:var(--muted2);font-size:10.5px;margin-top:6px}
|
||
|
||
/* paliers (modulable) */
|
||
.chips{display:flex;flex-wrap:wrap;gap:8px;align-items:center}
|
||
.pchip{display:flex;align-items:center;gap:8px;background:var(--card2);border:1px solid var(--line2);
|
||
border-radius:9px;padding:9px 11px;font-family:'Poppins';font-weight:600;font-size:13px}
|
||
.pchip i{font-style:normal;color:var(--muted2);font-weight:500;font-size:10.5px}
|
||
.pchip.off{opacity:.6} .pchip .x{color:var(--muted2);cursor:pointer;font-size:12px}
|
||
.add{width:34px;height:34px;border-radius:9px;border:1px dashed var(--line2);color:var(--muted);
|
||
display:grid;place-items:center;font-size:18px;background:transparent;cursor:pointer}
|
||
.assist-btn{margin-top:11px;display:flex;align-items:center;gap:8px;width:100%;justify-content:center;
|
||
background:transparent;border:1px dashed rgba(49,163,221,.4);color:var(--blue);font-family:'Poppins';
|
||
font-weight:600;font-size:12px;padding:10px;border-radius:10px;cursor:pointer}
|
||
|
||
/* sélecteur de things */
|
||
.search{width:100%;background:var(--card2);border:1px solid var(--line2);border-radius:11px;color:var(--muted2);
|
||
font-size:12.5px;padding:11px 12px;margin-bottom:11px}
|
||
.filters{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:12px}
|
||
.fchip{font-size:11px;border-radius:20px;padding:5px 11px;border:1px solid var(--line2);
|
||
background:var(--card2);color:var(--muted);font-family:'Poppins';font-weight:600;cursor:pointer}
|
||
.fchip.on{border-color:var(--blue);color:var(--blue);background:rgba(49,163,221,.1)}
|
||
.pickrow{display:flex;align-items:center;gap:11px;background:var(--card2);border:1px solid var(--line);
|
||
border-radius:11px;padding:11px 12px;margin-bottom:7px}
|
||
.cb{width:19px;height:19px;border-radius:6px;border:2px solid var(--muted2);flex:none}
|
||
.cb.on{border-color:var(--blue);background:var(--blue);position:relative}
|
||
.cb.on::after{content:"✓";position:absolute;inset:0;display:grid;place-items:center;color:#04222f;
|
||
font-size:12px;font-weight:700}
|
||
.pickrow .nm{flex:1;min-width:0}
|
||
.pickrow .nm b{display:block;font-size:12.5px;font-family:'Poppins';font-weight:600}
|
||
.pickrow .nm span{font-size:10.5px;color:var(--muted2)}
|
||
.pickrow .taken{font-size:10px;color:var(--yellow);background:rgba(254,193,19,.1);
|
||
border:1px solid rgba(254,193,19,.3);border-radius:20px;padding:3px 8px;font-weight:600}
|
||
.pickrow.dim{opacity:.5}
|
||
|
||
.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}
|
||
.pill{display:inline-block;font-size:10px;font-family:'Poppins';font-weight:600;border-radius:20px;
|
||
padding:2px 9px;margin-bottom:6px}
|
||
.pill.load{background:rgba(53,201,138,.12);color:var(--green);border:1px solid rgba(53,201,138,.3)}
|
||
.pill.ev{background:rgba(157,140,224,.12);color:var(--violet);border:1px solid rgba(157,140,224,.32)}
|
||
.pill.ac{background:rgba(49,163,221,.12);color:var(--blue);border:1px solid rgba(49,163,221,.32)}
|
||
.pill.bat{background:rgba(254,193,19,.1);color:var(--yellow);border:1px solid rgba(254,193,19,.32)}
|
||
|
||
/* ── batterie ───────────────────────────────────────────────────────── */
|
||
.newf{font-size:10.5px;color:#ff9d6b;background:rgba(255,157,107,.1);border:1px solid rgba(255,157,107,.34);
|
||
border-radius:20px;padding:2px 8px;font-weight:600;float:right;margin-top:-2px}
|
||
.tl{margin-top:10px;background:var(--card2);border:1px solid var(--line);border-radius:11px;padding:12px 12px 10px}
|
||
.tl .bar{position:relative;height:26px;border-radius:7px;overflow:hidden;
|
||
background:linear-gradient(90deg,#0f3242 0%,#123c4f 34%,#16506a 50%,#123c4f 66%,#0f3242 100%)}
|
||
.tl .seg1{position:absolute;left:56%;right:14%;top:0;bottom:0;background:rgba(53,201,138,.28)}
|
||
.tl .pnr{position:absolute;left:56%;top:-4px;bottom:-4px;width:2px;background:var(--red)}
|
||
.tl .pnr::after{content:"";position:absolute;top:-3px;left:-3px;width:8px;height:8px;border-radius:50%;
|
||
background:var(--red)}
|
||
.tl .peak{position:absolute;left:86%;top:0;bottom:0;width:2px;background:var(--yellow);opacity:.8}
|
||
.tl .ax{display:flex;justify-content:space-between;margin-top:6px;font-size:9.5px;color:var(--muted2)}
|
||
.tl .lg{margin-top:9px;font-size:10.5px;color:var(--muted2);line-height:1.55}
|
||
.tl .lg em{font-style:normal;color:var(--red);font-weight:600}
|
||
.tl .lg u{text-decoration:none;color:var(--yellow);font-weight:600}
|
||
|
||
.boost{border:1px solid rgba(157,140,224,.35);background:rgba(157,140,224,.07);border-radius:13px;padding:13px}
|
||
.boost .t{display:flex;align-items:center;gap:9px;margin-bottom:8px}
|
||
.boost .t b{font-family:'Poppins';font-weight:600;font-size:13px;flex:1}
|
||
.boost .t u{text-decoration:none;font-size:16px}
|
||
.boost .end{margin-top:9px;border-top:1px dashed rgba(157,140,224,.3);padding-top:9px;
|
||
font-size:10.6px;color:var(--muted2);line-height:1.55}
|
||
.boost .end b{color:var(--violet)}
|
||
.gauge{display:flex;align-items:center;gap:9px;margin:9px 0 2px}
|
||
.gauge .tr{flex:1;height:7px;border-radius:20px;background:rgba(255,255,255,.09);overflow:hidden}
|
||
.gauge .fl{height:100%;width:64%;background:var(--yellow)}
|
||
.gauge b{font-family:'Poppins';font-weight:700;font-size:13px}
|
||
.ask{margin-top:12px;border:1px solid rgba(224,122,122,.3);background:rgba(224,122,122,.06);
|
||
border-radius:11px;padding:11px 12px;font-size:11px;color:var(--muted);line-height:1.55}
|
||
.ask b{color:var(--red);display:block;font-family:'Poppins';font-size:11.5px;margin-bottom:5px}
|
||
.ask ol{margin:6px 0 0;padding-left:16px} .ask li{margin-bottom:6px}
|
||
.wide{max-width:824px;width:824px}
|
||
.wide h3{font-family:'Poppins';font-weight:600;font-size:13px;margin:0 0 7px;color:var(--txt)}
|
||
.mot{display:flex;gap:10px;margin:10px 0 4px;flex-wrap:wrap}
|
||
.mot .m{flex:1;min-width:230px;background:var(--card2);border:1px solid var(--line);border-radius:11px;padding:11px}
|
||
.mot .m b{display:block;font-family:'Poppins';font-weight:600;font-size:12px;margin-bottom:5px;color:var(--blue)}
|
||
.mot .m span{font-size:10.6px;color:var(--muted2);line-height:1.5;display:block}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<div class="head">
|
||
<h1>Sept écrans, quatre contrats — et ils ne se ressemblent pas</h1>
|
||
<p>Le bouton <b>Configurer</b> ne peut pas être un formulaire unique. SG-Ready est une
|
||
<code>LoadConfig</code> 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.</p>
|
||
<p><b>Ajout du 2026-08-27 — la batterie, septième écran.</b> Elle devient une charge du waterfall, donc
|
||
son bouton <b>Configurer</b> ouvre un écran de mécanisme comme les autres — et, comme les autres,
|
||
<b>il ne porte pas le rang</b> : celui-ci se règle par glisser-déposer dans la liste. Défaut : la
|
||
batterie en <b>première position de charge</b>, ce qui reproduit le comportement actuel. <b>Rien de ce qui suit
|
||
n'existe côté box</b> : ni <code>BatteryAdapter</code>, ni entrée <code>LoadConfig</code> pour le
|
||
stockage, ni prévision PV. Cette colonne est un <b>brief pour le moteur</b>, comme les six autres
|
||
l'ont été — et les seuls leviers réels de <code>.75</code> sont les cinq actions de la classe
|
||
<code>SunSpec Storage</code> : <code>enableCharging</code>, <code>enableDischarging</code>,
|
||
<code>chargingRate</code>, <code>dischargingRate</code>, <code>gridCharging</code>, plus l'état
|
||
<code>controlHealthy</code>. Tout le reste est à créer.</p>
|
||
<p><b>Deux écarts relevés en introspectant <code>.75</code> (plugin 1.15.2+etm15) :</b>
|
||
① le <code>ChargingInfo</code> réellement déployé ne porte <b>ni <code>mode</code>, ni <code>minCurrent</code>,
|
||
ni <code>targetSoc</code>, ni <code>endTime</code></b> — les noms écrits dans <code>CLAUDE.md</code> ne sont pas
|
||
ceux de la box. ② <code>AirConditioning</code> expose <b>8 méthodes</b>, pas 3.</p>
|
||
</div>
|
||
|
||
<div class="stage">
|
||
|
||
<!-- ══════════ ECS RELAIS / MULTIPALIER ══════════ -->
|
||
<div class="col">
|
||
<div class="phone">
|
||
<div class="appbar"><div class="ic">‹</div><h2>Configurer · Chauffe-eau</h2><div class="ic"></div></div>
|
||
<div class="scroll">
|
||
|
||
<div class="field">
|
||
<label>Nom de la charge <span class="saved-flag">enregistré · label</span></label>
|
||
<input class="inp" value="Chauffe-eau">
|
||
<div class="mode-hint">Le seul texte de l'installation que l'utilisateur voit dans chaque motif
|
||
de décision.</div>
|
||
</div>
|
||
|
||
<div class="field">
|
||
<label>Domaine <span class="saved-flag">enregistré · domain</span></label>
|
||
<div class="sel">Eau chaude sanitaire <i>▾</i></div>
|
||
</div>
|
||
|
||
<div class="field">
|
||
<label>Mécanisme <span class="saved-flag">enregistré · adapter</span></label>
|
||
<div class="seg">
|
||
<button class="on">Routeur de relais</button><button>Modulable</button><button>SG-Ready</button>
|
||
</div>
|
||
<div class="info">
|
||
<b>Ce sont vos contacteurs</b>
|
||
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 <b>matériel</b>.
|
||
<span style="display:block;margin-top:5px;color:var(--muted2)">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 ».</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="field">
|
||
<label>Relais commandés <span class="saved-flag">enregistré · relays[]</span><br>
|
||
<small>un contacteur par ligne — puissance de la résistance câblée derrière</small></label>
|
||
|
||
<div class="relay">
|
||
<span class="k">K1</span>
|
||
<div class="thing">Relay1 <i>Digital Output</i></div>
|
||
<input class="w" value="500 W">
|
||
<span class="del">×</span>
|
||
</div>
|
||
<div class="relay">
|
||
<span class="k">K2</span>
|
||
<div class="thing">Relay2 <i>Digital Output</i></div>
|
||
<input class="w" value="1 000 W">
|
||
<span class="del">×</span>
|
||
</div>
|
||
<div class="relay">
|
||
<span class="k">K3</span>
|
||
<div class="thing">Relay3 <i>Digital Output</i></div>
|
||
<input class="w" value="2 000 W">
|
||
<span class="del">×</span>
|
||
</div>
|
||
<button class="addrow">+ Ajouter un relais</button>
|
||
|
||
<div class="preview">
|
||
3 relais → paliers atteignables :
|
||
<b>0 · 500 · 1 000 · 1 500 · 2 000 · 2 500 · 3 000 · 3 500 W</b>
|
||
<em>Déduit, jamais enregistré. La box compose elle-même les paliers en essayant toutes les
|
||
combinaisons de contacts (2<sup>N</sup>, plafond 16 relais). Deux relais de même puissance ne
|
||
créent pas de palier supplémentaire — les doublons sont écartés.</em>
|
||
</div>
|
||
|
||
<div class="mode-hint"><b>Le picker liste les things par NOM</b>, filtrés sur
|
||
l'interface nymea <code>power</code> — taper un UUID au doigt est une erreur
|
||
garantie. Il résout nom → ThingId et n'ajoute aucun champ au contrat.</div>
|
||
<div class="mode-hint"><b>Un ECS simple, c'est un seul relais</b> — deux paliers, 0 et P. Une
|
||
résistance multipalier en déclare 2, 3 ou 4. La puissance saisie est celle de la
|
||
<b>résistance</b>, pas le calibre du contacteur : rien dans nymea ne dit ce qu'un relais
|
||
commande.</div>
|
||
|
||
<div class="warn">
|
||
<b>⚠ Ce que la box refuse, en bloc et sans le dire</b>
|
||
<code>relays[]</code> vide · un <code>powerW</code> ≤ 0 · un même Thing sur deux lignes ·
|
||
un <code>mode</code> autre que <code>fixed</code>. Le motif n'apparaît qu'au journal de la box —
|
||
le formulaire doit donc valider <b>avant</b> d'envoyer.
|
||
</div>
|
||
</div>
|
||
|
||
<div class="field">
|
||
<label>Temporisations <span class="saved-flag">enregistré · minOnS / minOffS</span></label>
|
||
<div class="stepper">
|
||
<div class="lab">Maintien ON minimal<small>anti court-cycle à l'enclenchement</small></div>
|
||
<div class="ctl"><button class="pm">−</button><b>60 s</b><button class="pm">+</button></div>
|
||
</div>
|
||
<div class="stepper">
|
||
<div class="lab">Maintien OFF minimal<small>délai avant réenclenchement</small></div>
|
||
<div class="ctl"><button class="pm">−</button><b>60 s</b><button class="pm">+</button></div>
|
||
</div>
|
||
<div class="mode-hint">Ces deux durées produisent les verrous <b>LOCK_MIN_ON</b> et
|
||
<b>LOCK_MIN_OFF</b> que la carte de la charge affiche en secondes restantes.</div>
|
||
</div>
|
||
|
||
<div class="field">
|
||
<label>Compteur dédié <span class="todo-flag">prévu · pas encore supporté</span></label>
|
||
<div class="blocked">
|
||
<div class="row">
|
||
<div class="lab">Rattacher un compteur à cette charge</div>
|
||
<span class="sw off"></span>
|
||
</div>
|
||
<div class="row" style="margin-top:9px">
|
||
<div class="sel" style="flex:1">Choisir un appareil de mesure… <i>▾</i></div>
|
||
</div>
|
||
<p><b>Aucun champ n'existe côté box.</b> <code>SetLoadConfig</code> n'accepte que
|
||
<code>adapter · enabled · id · label · mode · priority · domain · maxPowerW · minOffS · minOnS ·
|
||
needs · powerLevels · relays · sgReady</code>. nymea rejette toute clé inconnue <b>avant</b> le
|
||
handler : inventer <code>meterThingId</code> ferait échouer l'enregistrement de <b>toutes</b> les
|
||
charges. <span style="display:block;margin-top:5px">Ici le besoin est le plus fort des trois
|
||
mécanismes : la box <b>estime</b> la consommation depuis les paliers déclarés, elle ne la
|
||
mesure pas.</span></p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="field">
|
||
<label>Arbitrage <span class="saved-flag">enregistré · enabled</span></label>
|
||
<div class="stepper">
|
||
<div class="lab">Inclure dans l'arbitrage<small>rang 1 · Eau chaude sanitaire</small></div>
|
||
<div class="ctl"><span class="sw"></span></div>
|
||
</div>
|
||
<div class="mode-hint">À la désactivation, le retrait passe par l'état sûr : <b>tous les relais
|
||
ouverts</b>. Vérifié sur matériel côté plugin.</div>
|
||
</div>
|
||
|
||
<div class="save"><div class="btn alt">Annuler</div><div class="btn go">Enregistrer</div></div>
|
||
</div>
|
||
</div>
|
||
<div class="cap"><b>Routeur de relais</b> — ECS simple ou multipalier</div>
|
||
<div class="note">
|
||
<span class="pill load">NymeaEnergy.SetLoadConfig</span><br>
|
||
Le seul mécanisme où l'app déclare des <b>contacteurs</b>. C'est aussi le seul où les paliers ont un
|
||
sens : ils naissent des combinaisons de relais, ils ne se saisissent pas.
|
||
<ul>
|
||
<li><b><code>mode</code> doit valoir <code>fixed</code></b> — 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.</li>
|
||
<li><b>Toucher aux relais franchit <code>sameHardware()</code></b> : reconstruction de l'adaptateur
|
||
et réarmement des verrous à froid — contrairement à <code>label</code>, <code>domain</code>,
|
||
<code>priority</code> et <code>enabled</code>, qui passent en mise à jour sur place.</li>
|
||
<li>Configuration réelle du banc <code>.75</code> : trois relais 500 / 1 000 / 2 000 W, huit paliers,
|
||
<code>maxStageW: 3 500</code> — la télémétrie l'a confirmé pendant le passage sur appareil.</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ══════════ ECS MODULABLE ══════════ -->
|
||
<div class="col">
|
||
<div class="phone">
|
||
<div class="appbar"><div class="ic">‹</div><h2>Configurer · Chauffe-eau</h2><div class="ic"></div></div>
|
||
<div class="scroll">
|
||
|
||
<div class="field">
|
||
<label>Nom de la charge <span class="saved-flag">enregistré · label</span></label>
|
||
<input class="inp" value="Chauffe-eau">
|
||
</div>
|
||
|
||
<div class="field">
|
||
<label>Mécanisme <span class="saved-flag">enregistré · adapter</span></label>
|
||
<div class="seg">
|
||
<button>Routeur de relais</button><button class="on">Modulable</button><button>SG-Ready</button>
|
||
</div>
|
||
<div class="info">
|
||
<b>Une consigne continue, pas des étages</b>
|
||
La box envoie une <b>valeur en watts</b>, n'importe laquelle entre 0 et le plafond, et l'appareil
|
||
module. Aucun contact n'est commandé, aucun palier n'est déclaré.
|
||
<span style="display:block;margin-top:5px;color:var(--muted2)">Des paliers ⇒ ce sont des
|
||
contacteurs ⇒ c'est « routeur de relais ». Les deux mécanismes ne se recouvrent pas.</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="field">
|
||
<label>Appareil piloté <span class="saved-flag">enregistré · id</span><br>
|
||
<small>un seul Thing, portant l'interface etmvariableload</small></label>
|
||
<div class="sel">Chauffe-eau Modbus · etmvariableload <i>▾</i></div>
|
||
<div class="warn">
|
||
<b>⚠ Changer d'appareil, c'est changer de charge</b>
|
||
L'identifiant de la charge <b>est</b> 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.
|
||
</div>
|
||
</div>
|
||
|
||
<div class="field">
|
||
<label>Puissance nominale <span class="saved-flag">enregistré · maxPowerW</span><br>
|
||
<small>plafond de modulation — la consigne ira de 0 à cette valeur</small></label>
|
||
<div class="amp">
|
||
<button class="pm">−</button>
|
||
<div class="v">3 000<small>W</small></div>
|
||
<button class="pm">+</button>
|
||
</div>
|
||
<div class="mode-hint">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.</div>
|
||
</div>
|
||
|
||
<div class="field">
|
||
<label>Temporisations <span class="saved-flag">enregistré · minOnS / minOffS</span></label>
|
||
<div class="stepper">
|
||
<div class="lab">Maintien ON minimal<small>anti court-cycle</small></div>
|
||
<div class="ctl"><button class="pm">−</button><b>0 s</b><button class="pm">+</button></div>
|
||
</div>
|
||
<div class="stepper">
|
||
<div class="lab">Maintien OFF minimal<small>délai avant reprise</small></div>
|
||
<div class="ctl"><button class="pm">−</button><b>0 s</b><button class="pm">+</button></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="field">
|
||
<label>Compteur dédié <span class="todo-flag">prévu · pas encore supporté</span></label>
|
||
<div class="blocked">
|
||
<div class="row">
|
||
<div class="lab">Rattacher un compteur à cette charge</div>
|
||
<span class="sw off"></span>
|
||
</div>
|
||
<div class="row" style="margin-top:9px">
|
||
<div class="sel" style="flex:1">Choisir un appareil de mesure… <i>▾</i></div>
|
||
</div>
|
||
<p><b>Aucun champ n'existe côté box.</b> <code>SetLoadConfig</code> n'accepte que
|
||
<code>adapter · enabled · id · label · mode · priority · domain · maxPowerW · minOffS · minOnS ·
|
||
needs · powerLevels · relays · sgReady</code>, et nymea rejette toute clé inconnue <b>avant</b>
|
||
le handler. <span style="display:block;margin-top:5px">Ici la question se double : l'interface
|
||
remonte déjà la <b>puissance réelle</b> de l'appareil — un compteur externe mesurerait-il autre
|
||
chose ?</span></p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="field">
|
||
<label>Arbitrage <span class="saved-flag">enregistré · enabled</span></label>
|
||
<div class="stepper">
|
||
<div class="lab">Inclure dans l'arbitrage<small>rang 1 · Eau chaude sanitaire</small></div>
|
||
<div class="ctl"><span class="sw"></span></div>
|
||
</div>
|
||
<div class="mode-hint">À la désactivation, le retrait passe par l'état sûr : <b>consigne 0 W</b>.</div>
|
||
</div>
|
||
|
||
<div class="field">
|
||
<label>Configuration héritée <span class="derived-flag">lecture seule · mode fixed</span><br>
|
||
<small>n'apparaît que si la box renvoie déjà une charge en paliers</small></label>
|
||
<div class="blocked">
|
||
<div class="row" style="opacity:.8">
|
||
<div class="lab">Paliers déclarés : 0 · 600 · 1 200 · 1 800 · 2 400 W</div>
|
||
</div>
|
||
<p><b>L'app ne propose plus ce mode</b> — le plugin lui-même le qualifie de <i>legacy</i> — mais
|
||
elle ne le réécrit pas en douce : basculer une charge de <code>fixed</code> vers
|
||
<code>dynamic</code> change l'arbitrage réel, et cela demande un geste explicite.
|
||
<span style="display:block;margin-top:6px">La conversion exige aussi une
|
||
<b>puissance nominale</b>, que la déclaration en paliers ne porte pas : la box refuse
|
||
<code>dynamic</code> sans <code>maxPowerW</code>, et refuse <code>powerLevels</code> avec.</span>
|
||
</p>
|
||
<div class="row" style="opacity:1;margin-top:10px">
|
||
<div class="sel" style="flex:1;border-color:rgba(254,193,19,.4);color:var(--yellow)">
|
||
Convertir en modulation continue…</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="save"><div class="btn alt">Annuler</div><div class="btn go">Enregistrer</div></div>
|
||
</div>
|
||
</div>
|
||
<div class="cap"><b>Modulable</b> — une consigne continue</div>
|
||
<div class="note">
|
||
<span class="pill load">NymeaEnergy.SetLoadConfig</span><br>
|
||
<code>etmvariableload</code> : <b>un seul appareil</b>, une consigne en watts, aucun palier.
|
||
<code>relays[]</code> y est interdit — la box refuse en bloc.
|
||
<ul>
|
||
<li><b>Le critère de choix entre les deux mécanismes</b>, et c'est le seul qui compte pour
|
||
l'installateur : <b>où sont les contacteurs ?</b> 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.</li>
|
||
<li><b>Le mode <code>fixed</code> existe encore côté box</b> — « multipalier natif, <i>legacy</i> »
|
||
dans les mots du plugin. L'app doit savoir le <b>lire</b> sans le proposer : une config déployée
|
||
qui l'utilise ne doit pas être convertie par le simple fait d'ouvrir l'écran.</li>
|
||
<li>Trois niveaux, trois responsables (contrat rév. 2) : le <b>câblage</b> au wizard Things, la
|
||
<b>déclaration</b> à cet écran, le <b>runtime</b> à l'interface. « Le thing décide du matériel ;
|
||
l'energymanager décide de la puissance. »</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ══════════ SG-READY ══════════ -->
|
||
<div class="col">
|
||
<div class="phone">
|
||
<div class="appbar"><div class="ic">‹</div><h2>Configurer · PAC banc</h2><div class="ic"></div></div>
|
||
<div class="scroll">
|
||
|
||
<div class="field">
|
||
<label>Nom de la charge <span class="saved-flag">enregistré · label</span></label>
|
||
<input class="inp" value="PAC banc">
|
||
</div>
|
||
|
||
<div class="field">
|
||
<label>Mécanisme <span class="saved-flag">enregistré · adapter</span></label>
|
||
<div class="seg">
|
||
<button>Routeur de relais</button><button>Modulable</button><button class="on">SG-Ready</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="field">
|
||
<label>Contacts SG-Ready <span class="derived-flag">déduit · pas de champ dédié</span><br>
|
||
<small>les deux entrées de la pompe à chaleur, et le relais qui les commande</small></label>
|
||
<div class="relay">
|
||
<span class="k">SG1</span>
|
||
<div class="thing">Relay4 <i>Digital Output</i></div>
|
||
</div>
|
||
<div class="relay">
|
||
<span class="k">SG2</span>
|
||
<div class="thing">Relay5 <i>Digital Output</i></div>
|
||
</div>
|
||
<div class="info">
|
||
<b>« SG1 » et « SG2 » n'existent pas côté box</b>
|
||
<code>sgReady.states[]</code> ne stocke qu'une <b>liste de relais par état</b>, sans nom ni ordre.
|
||
Les deux étiquettes sont une convention de cet écran, et elles se retrouvent à la lecture :
|
||
<b>SG1 = le contact fermé en état 1</b> (blocage), <b>SG2 = celui fermé en état 3</b>
|
||
(recommandation). C'est l'encodage SG-Ready standard, et c'est celui du banc.
|
||
</div>
|
||
<div class="warn">
|
||
<b>⚠ Un encodage non standard perd les étiquettes</b>
|
||
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.
|
||
</div>
|
||
</div>
|
||
|
||
<div class="field">
|
||
<label>Table d'états <span class="saved-flag">enregistré · sgReady.states[]</span><br>
|
||
<small>quels contacts fermer pour chaque état, et ce que la PAC consomme alors</small></label>
|
||
|
||
<div class="st">
|
||
<div class="h"><span class="badge s1">1</span><b>Blocage</b><span>arrêt forcé</span></div>
|
||
<div class="contacts">
|
||
<span class="kchip on">SG1 fermé</span><span class="kchip">SG2 ouvert</span>
|
||
</div>
|
||
<div class="pw"><label>Puissance estimée</label><input value="0 W"></div>
|
||
</div>
|
||
|
||
<div class="st">
|
||
<div class="h"><span class="badge s2">2</span><b>Normal</b><span>aucune consigne</span></div>
|
||
<div class="contacts">
|
||
<span class="kchip">SG1 ouvert</span><span class="kchip">SG2 ouvert</span>
|
||
</div>
|
||
<div class="pw"><label>Puissance estimée</label><input value="0 W"></div>
|
||
</div>
|
||
|
||
<div class="st">
|
||
<div class="h"><span class="badge s3">3</span><b>Recommandation</b><span>incitation douce</span></div>
|
||
<div class="contacts">
|
||
<span class="kchip">SG1 ouvert</span><span class="kchip on">SG2 fermé</span>
|
||
</div>
|
||
<div class="pw"><label>Puissance estimée</label><input value="1 500 W"></div>
|
||
</div>
|
||
|
||
<div class="st">
|
||
<div class="h"><span class="badge s4">4</span><b>Forcé</b><span>marche imposée</span></div>
|
||
<div class="contacts">
|
||
<span class="kchip on">SG1 fermé</span><span class="kchip on">SG2 fermé</span>
|
||
</div>
|
||
<div class="pw"><label>Puissance estimée</label><input value="3 000 W"></div>
|
||
</div>
|
||
|
||
<div class="warn">
|
||
<b>⚠ L'état 2 doit être exprimable</b>
|
||
Si l'encodage des contacts ne permet pas de représenter l'état « normal », la pompe à chaleur
|
||
n'est <b>jamais</b> pilotable : la box publie <code>UNUSABLE_ENCODING</code> et la charge sort de
|
||
l'arbitrage définitivement (ECS-110). Le formulaire doit le refuser avant l'envoi.
|
||
</div>
|
||
<div class="info">
|
||
<b>Une estimation, jamais un engagement</b>
|
||
<code>estimatedPowerW</code> sert de base au <b>recrédit de budget</b> 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.
|
||
</div>
|
||
</div>
|
||
|
||
<div class="field">
|
||
<label>Anti court-cycle <span class="saved-flag">enregistré · sgReady.minStateHoldS</span></label>
|
||
<div class="stepper">
|
||
<div class="lab">Maintien d'état minimal<small>durée avant tout changement d'état</small></div>
|
||
<div class="ctl"><button class="pm">−</button><b>300 s</b><button class="pm">+</button></div>
|
||
</div>
|
||
<div class="mode-hint">Pendant ce maintien, la télémétrie publie le verrou
|
||
<b>LOCK_MIN_STATE_HOLD</b> avec les secondes restantes — c'est ce que l'écran affiche
|
||
« au dernier cycle », sans le décompter lui-même.</div>
|
||
</div>
|
||
|
||
|
||
<div class="field">
|
||
<label>Compteur dédié <span class="todo-flag">prévu · pas encore supporté</span></label>
|
||
<div class="blocked">
|
||
<div class="row">
|
||
<div class="lab">Rattacher un compteur à cette charge</div>
|
||
<span class="sw off"></span>
|
||
</div>
|
||
<div class="row" style="margin-top:9px">
|
||
<div class="sel" style="flex:1">Choisir un appareil de mesure… <i>▾</i></div>
|
||
</div>
|
||
<p><b>Aucun champ n'existe côté box.</b> <code>SetLoadConfig</code> n'accepte que <code>adapter · enabled · id · label · mode · priority · domain · maxPowerW · minOffS · minOnS · needs · powerLevels · relays · sgReady</code>. nymea rejette toute clé inconnue <b>avant</b> le handler : inventer <code>meterThingId</code> ferait échouer l'enregistrement de <b>toutes</b> les charges.</p>
|
||
</div>
|
||
</div>
|
||
<div class="field">
|
||
<label>Arbitrage <span class="saved-flag">enregistré · enabled</span></label>
|
||
<div class="stepper">
|
||
<div class="lab">Inclure dans l'arbitrage<small>rang 2 · Chauffage</small></div>
|
||
<div class="ctl"><span class="sw"></span></div>
|
||
</div>
|
||
<div class="mode-hint">À la désactivation, le retrait passe par l'état sûr : <b>état 2</b>,
|
||
jamais le blocage. Vérifié sur matériel côté plugin.</div>
|
||
</div>
|
||
|
||
<div class="save"><div class="btn alt">Annuler</div><div class="btn go">Enregistrer</div></div>
|
||
</div>
|
||
</div>
|
||
<div class="cap"><b>SG-Ready</b> — même contrat que le relais</div>
|
||
<div class="note">
|
||
<span class="pill load">NymeaEnergy.SetLoadConfig</span><br>
|
||
Même famille que le routeur de relais : c'est une <code>LoadConfig</code>, elle est arbitrée par le
|
||
waterfall, elle apparaît dans <code>GetLoadTelemetry</code>, elle a un rang et un domaine.
|
||
<ul>
|
||
<li>Ce que les contacts commandent, ce sont des <b>états normés 1→4</b>, pas des watts. Les watts
|
||
n'y sont qu'une <b>estimation déclarée</b> — d'où le libellé.</li>
|
||
<li>Toucher aux contacts ou à la table d'états franchit <code>sameHardware()</code> :
|
||
<b>reconstruction de l'adaptateur</b> et réarmement des verrous à froid.</li>
|
||
<li><b>Le choix des Things se fait une fois, en haut</b> — pas état par état. Ce qui se coche dans le
|
||
tableau, c'est <b>fermé / ouvert</b>, et c'est ça qui devient <code>relays[]</code> à
|
||
l'enregistrement : la liste des contacts fermés pour cet état.</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ══════════ BORNE DE RECHARGE ══════════ -->
|
||
<div class="col">
|
||
<div class="phone">
|
||
<div class="appbar"><div class="ic">‹</div><h2>Configurer · Borne de recharge</h2><div class="ic"></div></div>
|
||
<div class="scroll">
|
||
|
||
<div class="field">
|
||
<label>Appareil <span class="other-flag">chargingInfo · evChargerId</span></label>
|
||
<div class="sel">Terra AC Charger (TCP) <i>▾</i></div>
|
||
</div>
|
||
|
||
<div class="field">
|
||
<label>État actuel <span class="derived-flag">lecture seule · chargingState</span></label>
|
||
<div class="state"><span class="dot"></span><b>Recharge sur surplus</b><i>SurplusCharging</i></div>
|
||
</div>
|
||
|
||
<div class="field">
|
||
<label>Mode de recharge par défaut <span class="other-flag">chargingInfo · chargingMode</span><br>
|
||
<small>ce que la borne applique quand personne n'a rien demandé</small></label>
|
||
<div class="evmode">
|
||
<button class="on"><u>☀</u>PV<small>surplus seul</small></button>
|
||
<button><u>⚡</u>Min+PV<small>courant mini</small></button>
|
||
<button><u>🚀</u>Boost<small>pleine puissance</small></button>
|
||
</div>
|
||
<div class="maps">
|
||
PV → <code>ChargingModeEco</code> · Min+PV → <code>ChargingModeEcoWithMinCurrent</code> ·
|
||
Boost → <code>ChargingModeNormal</code><br>
|
||
avec échéance → <code>ChargingModeEcoWithTargetTime</code> /
|
||
<code>ChargingModeEcoMinWithTargetTime</code>
|
||
</div>
|
||
<div class="mode-hint"><b>C'est un défaut, pas une commande.</b> 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 <code>chargingMode</code> — d'où l'importance de ne pas laisser croire
|
||
que ce bouton agit à l'instant.</div>
|
||
</div>
|
||
|
||
<div class="field">
|
||
<label>Échéance de départ <span class="other-flag">targetPercentage · endDateTime · repeatDays</span></label>
|
||
<div class="stepper">
|
||
<div class="lab">Fixer une heure de départ<small>bascule le mode en « …WithTargetTime »</small></div>
|
||
<div class="ctl"><span class="sw"></span></div>
|
||
</div>
|
||
<div class="stepper">
|
||
<div class="lab">Charge cible<small>targetPercentage</small></div>
|
||
<div class="ctl"><button class="pm">−</button><b>80 %</b><button class="pm">+</button></div>
|
||
</div>
|
||
<div class="stepper">
|
||
<div class="lab">Prête à<small>endDateTime</small></div>
|
||
<div class="ctl"><b>07:30</b></div>
|
||
</div>
|
||
<div class="sel">Répéter : Lun · Mar · Mer · Jeu · Ven <i>▾</i></div>
|
||
</div>
|
||
|
||
<div class="field">
|
||
<label>Courant minimum <span class="todo-flag">champ absent de la box</span></label>
|
||
<div class="blocked">
|
||
<div class="row">
|
||
<div class="lab">Courant maintenu en Min+PV</div>
|
||
<div class="ctl" style="display:flex;align-items:center;gap:12px">
|
||
<button class="pm">−</button><b style="font-family:Poppins;font-size:13.5px">6 A</b><button class="pm">+</button>
|
||
</div>
|
||
</div>
|
||
<p><b>Le mode existe, le réglage n'existe pas.</b> <code>ChargingModeEcoWithMinCurrent</code> est
|
||
bien dans l'énumération de la box, mais <code>ChargingInfo</code> n'a <b>aucun champ</b> de courant
|
||
minimum — ni <code>minCurrent</code>, ni équivalent. Soit la valeur est fixée ailleurs (paramètre
|
||
du Thing borne), soit le champ manque. <b>À trancher avec l'agent plugin avant de livrer ce
|
||
mode</b> : proposer un réglage qui ne part nulle part serait pire que de ne pas le proposer.</p>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<div class="field">
|
||
<label>Compteur dédié <span class="todo-flag">prévu · pas encore supporté</span></label>
|
||
<div class="blocked">
|
||
<div class="row">
|
||
<div class="lab">Rattacher un compteur à la borne</div>
|
||
<span class="sw off"></span>
|
||
</div>
|
||
<div class="row" style="margin-top:9px">
|
||
<div class="sel" style="flex:1">Choisir un appareil de mesure… <i>▾</i></div>
|
||
</div>
|
||
<p><b>Aucun champ n'existe côté box.</b> <code>ChargingInfo</code> ne porte que <code>chargingMode · evChargerId · assignedCarId · dailySpotMarketPercentage · endDateTime · repeatDays · spotMarketChargingEnabled · targetPercentage</code>, plus <code>chargingState</code> en lecture. Rien pour un compteur — et la borne mesure déjà, ce qui pose la question de <b>quelle</b> mesure fait foi.</p>
|
||
</div>
|
||
</div>
|
||
<div class="field">
|
||
<label>Tarif dynamique <span class="other-flag">spotMarketChargingEnabled · dailySpotMarketPercentage</span></label>
|
||
<div class="stepper">
|
||
<div class="lab">Recharger aux heures les moins chères<small>Predict AI</small></div>
|
||
<div class="ctl"><span class="sw off"></span></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="save"><div class="btn alt">Annuler</div><div class="btn go">Enregistrer</div></div>
|
||
</div>
|
||
</div>
|
||
<div class="cap"><b>Borne</b> — un autre contrat</div>
|
||
<div class="note">
|
||
<span class="pill ev">NymeaEnergy.SetChargingInfo</span><br>
|
||
<b>Ce n'est pas une <code>LoadConfig</code>.</b> Le véhicule n'a ni <code>adapter</code>, ni
|
||
<code>priority</code>, ni <code>domain</code> — et il <b>n'apparaît pas</b> dans
|
||
<code>GetLoadTelemetry</code> en beta : il est décidé en amont du waterfall, et sa puissance est
|
||
seulement <b>réservée</b> dans <code>budget.evReservedW</code>. Il rejoindra <code>loads[]</code> à
|
||
l'étape 3g.
|
||
<ul>
|
||
<li><b>Les noms de champs de <code>CLAUDE.md</code> sont périmés.</b> La box déploie
|
||
<code>chargingMode</code> (pas <code>mode</code>), <code>targetPercentage</code> (pas
|
||
<code>targetSoc</code>), <code>endDateTime</code> en <b>Uint</b> (pas <code>endTime</code>), plus
|
||
<code>assignedCarId</code>, <code>repeatDays</code> et deux champs spot market. À corriger dans
|
||
<code>CLAUDE.md</code>.</li>
|
||
<li>Le namespace est <code>NymeaEnergy.*</code>, pas <code>EnergyPlugin.*</code>.</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ══════════ ZONES HVAC ══════════ -->
|
||
<div class="col">
|
||
<div class="phone">
|
||
<div class="appbar"><div class="ic">‹</div><h2>Zones de climatisation</h2><div class="ic">+</div></div>
|
||
<div class="scroll">
|
||
|
||
<div class="field" style="margin-top:2px">
|
||
<label>Zones déclarées <span class="other-flag">AirConditioning.GetZones</span></label>
|
||
|
||
<div class="zone">
|
||
<div class="z1">
|
||
<b>Séjour</b>
|
||
<div class="temp">20,4 °<small>consigne 21,0 °</small></div>
|
||
</div>
|
||
<div class="z2">
|
||
<span class="tchip">2 thermostats</span><span class="tchip">1 vanne</span>
|
||
<span class="tchip">1 sonde int.</span><span class="tchip">1 contact fenêtre</span>
|
||
</div>
|
||
<div class="ovr">⏱ Dérogation 22,0 ° — encore 45 min</div>
|
||
</div>
|
||
|
||
<div class="zone">
|
||
<div class="z1">
|
||
<b>Chambres</b>
|
||
<div class="temp">18,9 °<small>consigne 19,0 °</small></div>
|
||
</div>
|
||
<div class="z2">
|
||
<span class="tchip">3 thermostats</span><span class="tchip">1 sonde int.</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="zone">
|
||
<div class="z1">
|
||
<b>Bureau</b>
|
||
<div class="temp">21,1 °<small>réduit 17,0 °</small></div>
|
||
</div>
|
||
<div class="z2">
|
||
<span class="tchip">1 thermostat</span><span class="tchip">humidité 47 %</span>
|
||
<span class="tchip">COV 320</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="field">
|
||
<label>Zone « Séjour » <span class="saved-flag">SetZoneThings · SetZoneName</span></label>
|
||
<input class="inp" value="Séjour" style="margin-bottom:9px">
|
||
<div class="rowsel"><span class="lab">Thermostats</span><span class="cnt">2 appareils</span><i>▾</i></div>
|
||
<div class="rowsel"><span class="lab">Vannes</span><span class="cnt">1 appareil</span><i>▾</i></div>
|
||
<div class="rowsel"><span class="lab">Sondes intérieures</span><span class="cnt">1 appareil</span><i>▾</i></div>
|
||
<div class="rowsel"><span class="lab">Sondes extérieures</span><span class="cnt">aucune</span><i>▾</i></div>
|
||
<div class="rowsel"><span class="lab">Contacts de fenêtre</span><span class="cnt">1 appareil</span><i>▾</i></div>
|
||
<div class="rowsel"><span class="lab">Notifications</span><span class="cnt">aucune</span><i>▾</i></div>
|
||
</div>
|
||
|
||
|
||
<div class="field">
|
||
<label>Compteur dédié <span class="todo-flag">prévu · pas encore supporté</span></label>
|
||
<div class="blocked">
|
||
<div class="row">
|
||
<div class="lab">Rattacher un compteur à la zone</div>
|
||
<span class="sw off"></span>
|
||
</div>
|
||
<div class="row" style="margin-top:9px">
|
||
<div class="sel" style="flex:1">Choisir un appareil de mesure… <i>▾</i></div>
|
||
</div>
|
||
<p><b>Aucun champ n'existe côté box.</b> <code>ZoneInfo</code> rattache des <code>indoorSensors</code> et <code>outdoorSensors</code> — des sondes de <b>température</b>, 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.</p>
|
||
</div>
|
||
</div>
|
||
<div class="field">
|
||
<label>Dérogation <span class="saved-flag">SetZoneSetpointOverride</span></label>
|
||
<div class="seg">
|
||
<button>Aucune</button><button class="on">Minutée</button>
|
||
<button>Illimitée</button><button>Éventuelle</button>
|
||
</div>
|
||
<div class="stepper" style="margin-top:9px">
|
||
<div class="lab">Consigne dérogée<small>setpointOverride</small></div>
|
||
<div class="ctl"><button class="pm">−</button><b>22,0 °</b><button class="pm">+</button></div>
|
||
</div>
|
||
<div class="stepper">
|
||
<div class="lab">Durée<small>minutes — mode minuté seul</small></div>
|
||
<div class="ctl"><button class="pm">−</button><b>60 min</b><button class="pm">+</button></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="field">
|
||
<label>Consigne de réduit <span class="saved-flag">SetZoneStandbySetpoint</span></label>
|
||
<div class="stepper">
|
||
<div class="lab">Hors planning<small>standbySetpoint</small></div>
|
||
<div class="ctl"><button class="pm">−</button><b>17,0 °</b><button class="pm">+</button></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="field">
|
||
<label>Planning hebdomadaire <span class="saved-flag">SetZoneWeekSchedule</span></label>
|
||
<div class="sel">7 jours · 4 créneaux par jour <i>›</i></div>
|
||
<div class="mode-hint">Écran dédié — la forme exacte de <code>TemperatureWeekSchedule</code> reste
|
||
à introspecter, le type n'est pas détaillé dans le schéma de cette box.</div>
|
||
</div>
|
||
|
||
<div class="info" style="margin-top:20px">
|
||
<b>Sur le banc, il n'y a aucune zone</b>
|
||
<code>AirConditioning.GetZones</code> renvoie <code>zones: []</code> sur <code>.75</code>.
|
||
L'état vide ci-dessous est donc le <b>premier</b> écran que verra un installateur.
|
||
</div>
|
||
|
||
<div class="empty" style="margin-top:12px">
|
||
<b>Aucune zone déclarée</b>
|
||
<p>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.</p>
|
||
<button>+ Créer une zone</button>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
<div class="cap"><b>HVAC</b> — encore un autre contrat</div>
|
||
<div class="note">
|
||
<span class="pill ac">AirConditioning.* · tier Auto</span><br>
|
||
Troisième contrat : <b>ni <code>LoadConfig</code>, ni <code>ChargingInfo</code></b>. Une zone n'a pas de
|
||
rang de service ni de domaine — c'est un regroupement de Things sous une consigne de température.
|
||
<ul>
|
||
<li><b>8 méthodes, pas 3</b> comme l'annonce <code>CLAUDE.md</code> : s'ajoutent
|
||
<code>AddZone</code>, <code>RemoveZone</code>, <code>SetZoneName</code>,
|
||
<code>SetZoneThings</code> et <code>SetZoneStandbySetpoint</code>. Le CRUD complet est donc
|
||
possible depuis l'app.</li>
|
||
<li>Une zone expose <b>en lecture seule</b> température, humidité, PM2.5, COV, statut, et la
|
||
dérogation en cours avec sa fin. Tout le reste des champs de <code>ZoneInfo</code> est
|
||
<code>r:</code> — on écrit par les six méthodes <code>Set*</code>, jamais par un objet complet.</li>
|
||
<li><b>Lien avec l'énergie à établir :</b> rien dans <code>ZoneInfo</code> ne relie une zone à une
|
||
<code>LoadConfig</code>. 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.</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<!-- ══════════ SÉLECTEUR DE THINGS (ZONE) ══════════ -->
|
||
<div class="col">
|
||
<div class="phone">
|
||
<div class="appbar"><div class="ic">‹</div><h2>Séjour · Thermostats</h2><div class="ic">✓</div></div>
|
||
<div class="scroll">
|
||
|
||
<div class="field" style="margin-top:2px">
|
||
<label>Rôle dans la zone <span class="saved-flag">SetZoneThings</span></label>
|
||
<div class="filters">
|
||
<span class="fchip on">Thermostats</span>
|
||
<span class="fchip">Vannes</span>
|
||
<span class="fchip">Sondes int.</span>
|
||
<span class="fchip">Sondes ext.</span>
|
||
<span class="fchip">Fenêtres</span>
|
||
<span class="fchip">Notifications</span>
|
||
</div>
|
||
<div class="mode-hint">Six listes distinctes, six paramètres de <code>SetZoneThings</code>. On
|
||
n'envoie que celles qu'on a touchées — les autres sont optionnelles et restent en place.</div>
|
||
</div>
|
||
|
||
<div class="field">
|
||
<label>Appareils compatibles <span class="derived-flag">filtré · interfaces nymea</span><br>
|
||
<small>ThingClass portant l'interface « thermostat »</small></label>
|
||
<div class="search">🔍 Rechercher un appareil…</div>
|
||
|
||
<div class="pickrow">
|
||
<span class="cb on"></span>
|
||
<div class="nm"><b>Thermostat Séjour Nord</b><span>Danfoss Ally · thermostat</span></div>
|
||
</div>
|
||
<div class="pickrow">
|
||
<span class="cb on"></span>
|
||
<div class="nm"><b>Thermostat Séjour Sud</b><span>Danfoss Ally · thermostat</span></div>
|
||
</div>
|
||
<div class="pickrow dim">
|
||
<span class="cb"></span>
|
||
<div class="nm"><b>Thermostat Chambre 1</b><span>Danfoss Ally · thermostat</span></div>
|
||
<span class="taken">zone « Chambres »</span>
|
||
</div>
|
||
<div class="pickrow">
|
||
<span class="cb"></span>
|
||
<div class="nm"><b>Thermostat Bureau</b><span>Shelly TRV · thermostat</span></div>
|
||
</div>
|
||
|
||
<div class="info">
|
||
<b>Un appareil dans deux zones ?</b>
|
||
Le schéma ne l'interdit pas — <code>SetZoneThings</code> prend une liste d'UUID sans contrainte
|
||
d'unicité déclarée. Tant que ce n'est pas éprouvé sur la box, l'app <b>signale</b> l'appartenance
|
||
existante sans bloquer, et ne prétend pas connaître la règle.
|
||
</div>
|
||
|
||
<div class="warn">
|
||
<b>⚠ Sur le banc, ces listes sont vides</b>
|
||
Les 58 ThingClass de <code>.75</code> ne portent <b>aucune</b> 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.
|
||
</div>
|
||
</div>
|
||
|
||
<div class="field">
|
||
<label>Compteur de zone <span class="todo-flag">prévu · pas encore supporté</span></label>
|
||
<div class="blocked">
|
||
<div class="row">
|
||
<div class="lab">Rattacher un compteur à la zone</div>
|
||
<span class="sw off"></span>
|
||
</div>
|
||
<div class="row" style="margin-top:9px">
|
||
<div class="sel" style="flex:1">Filtrer sur « energymeter »… <i>▾</i></div>
|
||
</div>
|
||
<p><b>Le filtre, lui, existe déjà.</b> <code>.75</code> porte les interfaces
|
||
<code>energymeter</code>, <code>smartmeter</code>, <code>smartmeterconsumer</code> et
|
||
<code>smartmeterproducer</code> : le jour où le champ est ajouté, l'app saura proposer les bons
|
||
appareils sans table maintenue à la main. C'est le <b>champ de destination</b> qui manque, pas la
|
||
façon de choisir.</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="save"><div class="btn alt">Annuler</div><div class="btn go">Valider (2)</div></div>
|
||
</div>
|
||
</div>
|
||
<div class="cap"><b>Zone</b> — choisir les appareils</div>
|
||
<div class="note">
|
||
<span class="pill ac">AirConditioning.SetZoneThings</span><br>
|
||
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.
|
||
<ul>
|
||
<li><b>Filtrer par interface, pas par nom de classe.</b> <code>ThingClass.interfaces</code> 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é.</li>
|
||
<li><b>Ne rien attendre du vocabulaire affiché.</b> Mesuré sur <code>.75</code> : sur 784 chaînes,
|
||
le français en traduit <b>zéro</b>. Les <code>displayName</code> sont des identifiants anglais, à
|
||
afficher tels quels.</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<!-- ══════════ BATTERIE ══════════ -->
|
||
<div class="col">
|
||
<div class="phone">
|
||
<div class="appbar"><div class="ic">‹</div><h2>Configurer · Batterie</h2><div class="ic"></div></div>
|
||
<div class="scroll">
|
||
|
||
<div class="field">
|
||
<label>Nom de la charge <span class="newf">à créer · LoadConfig.label</span></label>
|
||
<input class="inp" value="Batterie">
|
||
</div>
|
||
|
||
<div class="field">
|
||
<label>Rang de service <span class="derived-flag">réglé dans la liste, pas ici</span></label>
|
||
<div class="state"><span class="dot" style="background:var(--yellow);box-shadow:0 0 0 4px rgba(254,193,19,.15)"></span><b>1<sup>re</sup> position de charge</b><i>défaut</i></div>
|
||
<div class="mode-hint">Le rang se règle <b>par glisser-déposer dans la liste des charges</b>, comme
|
||
pour toutes les autres — aucun écran de mécanisme n'en porte un second réglage. Il est rappelé
|
||
ici parce que c'est lui qui répond à « eau chaude avant batterie », et rien d'autre.</div>
|
||
<div class="info"><b>Défaut : la batterie en première position de charge.</b> Ce n'est pas un
|
||
choix de conception, c'est la reproduction du comportement actuel — aujourd'hui l'onduleur
|
||
charge avant que le HEMS ne serve quoi que ce soit. <b>Une mise à jour ne change jamais
|
||
l'arbitrage sans geste.</b> La nouveauté n'est pas l'ordre, c'est qu'il devient réglable :
|
||
remonter « Eau chaude » au-dessus suffit alors à obtenir « ECS d'abord, batterie ensuite ».</div>
|
||
</div>
|
||
|
||
<div class="field">
|
||
<label>État réel <span class="derived-flag">lecture seule · SunSpec Storage</span></label>
|
||
<div class="state"><span class="dot"></span><b>50 % · 10 kWh · 0 W</b><i>Holding</i></div>
|
||
<div class="mode-hint"><code>batteryLevel</code> · <code>capacity</code> ·
|
||
<code>currentPower</code> · <code>storageStatus</code> — publiés par le Thing, jamais
|
||
recalculés ici.</div>
|
||
</div>
|
||
|
||
<!-- a. BLOCAGES PAR INTENTION -->
|
||
<div class="field">
|
||
<label>Décharge <span class="newf">à créer · enableDischarging</span></label>
|
||
<div class="stepper">
|
||
<div class="lab">Geler la batterie pendant la recharge du véhicule<small>plus rien n'est
|
||
déchargé, maison comprise</small></div>
|
||
<div class="ctl"><span class="sw"></span></div>
|
||
</div>
|
||
<div class="warn"><b>Le libellé dit ce que le mécanisme fait, pas ce qu'on voudrait.</b>
|
||
<code>enableDischarging</code> est <b>binaire</b>, et l'onduleur ne sait pas qui tire — il voit
|
||
une consommation nette. Coupé, la maison n'est plus servie non plus : le réfrigérateur passe au
|
||
réseau comme la voiture.<br><br>
|
||
« Servir la maison mais pas la voiture » demanderait de plafonner
|
||
<code>dischargingRate</code> sur la consommation <b>hors borne</b>, recalculé à chaque
|
||
cycle — de la modulation continue, pas un interrupteur. <b>Posé comme question au
|
||
moteur</b>, pas dessiné ici.</div>
|
||
<div class="info"><b>Pourquoi c'est le seul réglage de décharge.</b> « Charger l'eau chaude avant
|
||
la batterie » se règle par le <b>rang</b>, et n'a pas besoin d'un second réglage qui dirait la
|
||
même chose autrement. Décider en général <i>qui</i> mérite d'être servi par la batterie n'est
|
||
pas un réglage d'installateur : c'est un arbitrage, et il reviendra à <b>Héos</b> (optimiseur
|
||
MILP) ou à une décision par le prix. Offrir la question à la main figerait dans un écran ce qui
|
||
doit se calculer.</div>
|
||
</div>
|
||
|
||
<!-- c. RÉSERVE ADAPTATIVE -->
|
||
<div class="field">
|
||
<label>Plancher de secours <span class="newf">à créer · réserve de coupure</span><br>
|
||
<small>combien d'autonomie garder pour une coupure de courant</small></label>
|
||
<div class="stepper">
|
||
<div class="lab">Ne jamais descendre sous<small>l'arbitrage s'arrête là</small></div>
|
||
<div class="ctl"><button class="pm">−</button><b>20 %</b><button class="pm">+</button></div>
|
||
</div>
|
||
<div class="mode-hint">≈ 2 kWh gardés sur 10 — de quoi tenir l'essentiel quelques heures.
|
||
Ce plancher <b>ne bloque pas la charge</b>, il borne la décharge.</div>
|
||
<div class="info"><b>Une préférence de risque, pas une optimisation — et c'est pour ça qu'elle
|
||
est ici.</b> Combien d'autonomie garder pour une coupure ne se calcule pas : cela dépend de ce
|
||
que le client craint, de son congélateur, de son télétravail, de la fiabilité de son réseau
|
||
local. <b>Héos ne pourra jamais le décider à sa place</b>, quelle que soit la qualité de sa
|
||
prévision.<br><br>
|
||
<b>Ce n'est PAS « Battery Life ».</b> Victron a conçu le sien pour du <b>plomb</b> : une
|
||
batterie plomb laissée partiellement déchargée se sulfate, d'où un plancher qui remonte tant
|
||
qu'une recharge complète n'a pas eu lieu. Sur du <b>LFP, ce problème n'existe quasiment
|
||
pas</b> — le copier tel quel importerait une contrainte de chimie que cette installation n'a
|
||
pas. Le plancher gardé ici a un autre motif, et il est nommé.</div>
|
||
</div>
|
||
|
||
<!-- d. CHARGE DEPUIS LE RÉSEAU -->
|
||
<div class="field">
|
||
<label>Charger depuis le réseau <span class="newf">à créer · gridCharging</span><br>
|
||
<small>le mécanisme seul — ce qui le déclenche est hors périmètre</small></label>
|
||
<div class="seg"><button class="on">Manuel</button><button>Plage horaire</button></div>
|
||
<div class="stepper" style="margin-top:8px">
|
||
<div class="lab">Cible d'énergie<small>en kWh, pas en %</small></div>
|
||
<div class="ctl"><button class="pm">−</button><b>6 kWh</b><button class="pm">+</button></div>
|
||
</div>
|
||
<div class="stepper">
|
||
<div class="lab">Terminé à<small>heure de fin</small></div>
|
||
<div class="ctl"><b>06:00</b></div>
|
||
</div>
|
||
<div class="stepper">
|
||
<div class="lab">Ne pas soutirer plus de<small>chargingRate — plafond de puissance</small></div>
|
||
<div class="ctl"><button class="pm">−</button><b>3 kW</b><button class="pm">+</button></div>
|
||
</div>
|
||
<div class="info"><b>Ni prix, ni Tempo, ni marché de gros ici.</b> Cet écran pose le mécanisme :
|
||
soutirer au réseau une quantité donnée avant une heure donnée. <b>Ce qui décide</b> qu'il faut
|
||
le faire reste dehors — le jour où une source tarifaire existera, elle remplacera le doigt de
|
||
l'utilisateur sans que ce bloc ne change.<br><br>
|
||
Le plafond de puissance est le seul emploi de <code>chargingRate</code> dans cet écran, et il
|
||
a le <b>même motif que le boost, à l'envers</b> : ne pas dépasser ce qu'on a le droit de
|
||
soutirer. Charger 6 kWh en pleine nuit à pleine puissance sur un abonnement 6 kVA ferait
|
||
sauter le disjoncteur au premier four allumé.</div>
|
||
</div>
|
||
|
||
<div class="field">
|
||
<label>Compteur dédié <span class="derived-flag">sans objet</span></label>
|
||
<div class="mode-hint"><b>La batterie n'en a pas besoin</b>, et c'est la seule charge dans ce
|
||
cas : elle publie <code>currentPower</code> elle-même. Le bloc est dit ici plutôt qu'omis en
|
||
silence comme dans les cinq autres écrans — une absence expliquée n'est pas un oubli.</div>
|
||
</div>
|
||
|
||
<div class="save"><div class="btn alt">Annuler</div><div class="btn go">Enregistrer</div></div>
|
||
</div>
|
||
</div>
|
||
<div class="cap"><b>Batterie</b> — une charge, enfin</div>
|
||
<div class="note">
|
||
<span class="pill bat">à créer — BatteryAdapter · LoadConfig</span><br>
|
||
<b>Rien de cet écran n'existe.</b> Le stockage n'a aucune entrée <code>GetLoadConfig</code>, et
|
||
<code>enableCharging</code> / <code>chargingRate</code> / <code>enableDischarging</code> ont
|
||
<b>zéro occurrence</b> dans <code>energyplugin/</code>. Le levier matériel, lui, est déclaré : la
|
||
classe <code>SunSpec Storage</code> de <code>.75</code> publie ces cinq actions.
|
||
<ul>
|
||
<li><b>Aucune valeur affichée ici n'est inventée.</b> L'état réel se limite à ce que le Thing
|
||
publie ; tout le reste porte un badge <span class="newf" style="float:none;display:inline-block;margin:0">à créer</span>.</li>
|
||
<li><b>Le rang n'est PAS sur cet écran</b>, et c'est volontaire : il se règle par glisser-déposer
|
||
dans la liste des charges, comme pour toutes les autres. Un second réglage de rang ici dirait la
|
||
même chose autrement, et les deux finiraient par diverger.</li>
|
||
<li><b>Pas de permission par charge non plus.</b> Le seul interrupteur de décharge couvre le cas
|
||
que le rang ne sait pas dire ; l'arbitrage fin — qui mérite d'être servi par la batterie, et
|
||
quand — reviendra à <b>Héos</b> ou à une décision par le prix. Un écran ne doit pas figer ce qui
|
||
doit se calculer.</li>
|
||
<li><b>Trois questions au moteur, plus quatre soulevées par la recharge accélérée</b>, sont
|
||
écrites dans <code>docs/BRIEF_plugin_depuis_maquettes.md</code> — pas ici : ce sont des détails
|
||
techniques, pas de l'interface.</li>
|
||
<li><b>La charge différée n'est pas un réglage non plus — c'est une décision d'Héos.</b> Démarrer
|
||
assez tard pour arriver à 100 % juste avant la pointe suppose de savoir ce que le soleil va
|
||
faire : <b>sans prévision, ça ne marche pas</b>, et une heure saisie à la main serait un pari
|
||
présenté comme un réglage. Le gain reste réel — pic PV de midi libéré pour l'ECS et la voiture,
|
||
moins de temps passé à 100 % donc moins de vieillissement calendaire, et <b>aucun kWh
|
||
sacrifié</b> : la même énergie, stockée plus tard. Mais il se calcule.</li>
|
||
<li><b><code>batteryLevelConsideration</code> fait aujourd'hui l'INVERSE de ce que le rang
|
||
permettra.</b> Sous le seuil, le budget est <i>annulé</i> — la batterie passe donc avant, et
|
||
l'ECS ne reçoit rien. Si le rang arrive, il faut dire lequel des deux gagne, sinon deux réglages
|
||
se défont l'un l'autre en silence.</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ══════════ BOOST — TABLEAU DE BORD, PAS RÉGLAGES ══════════ -->
|
||
<div class="col">
|
||
<div class="phone" style="height:auto;min-height:420px">
|
||
<div class="appbar"><div class="ic">☰</div><h2>Tableau de bord</h2><div class="ic">⋯</div></div>
|
||
<div class="scroll">
|
||
|
||
<div class="field" style="margin-top:2px">
|
||
<label>Batterie <span class="derived-flag">carte du tableau de bord</span></label>
|
||
<div class="gauge"><b>64 %</b><div class="tr"><div class="fl"></div></div><span
|
||
style="font-size:10.5px;color:var(--muted2)">6,4 / 10 kWh</span></div>
|
||
|
||
<div class="boost" style="margin-top:12px">
|
||
<div class="t"><u>⚡</u><b>Recharge accélérée</b><span class="sw off"></span></div>
|
||
<div style="font-size:11px;color:var(--muted);line-height:1.55">
|
||
<b>Charger la voiture plus vite que l'abonnement ne le permet.</b> Le compteur est limité à
|
||
6 kVA ; la borne peut monter à 7,5. La <b>batterie comble l'écart</b> — le réseau fournit ce
|
||
qu'il a le droit de fournir, la maison prend le reste sur son stock, et le disjoncteur ne
|
||
saute pas.
|
||
</div>
|
||
<div class="stepper" style="margin-top:10px">
|
||
<div class="lab">Puissance de charge visée<small>ce que la borne délivre à la voiture</small></div>
|
||
<div class="ctl"><button class="pm">−</button><b>7,5 kW</b><button class="pm">+</button></div>
|
||
</div>
|
||
<div class="mode-hint" style="margin-top:8px">Réseau <b>6,0 kW</b> (abonnement)
|
||
+ batterie <b>1,5 kW</b> (complément) — la batterie ne fixe pas la cible, elle bouche le
|
||
trou.</div>
|
||
<div class="stepper" style="margin-top:9px">
|
||
<div class="lab">Sans descendre sous<small>garde-fou, pas objectif</small></div>
|
||
<div class="ctl"><button class="pm">−</button><b>20 %</b><button class="pm">+</button></div>
|
||
</div>
|
||
<div class="stepper">
|
||
<div class="lab">Ou au bout de<small>durée maximale</small></div>
|
||
<div class="ctl"><button class="pm">−</button><b>45 min</b><button class="pm">+</button></div>
|
||
</div>
|
||
<div class="end"><b>Trois fins, toutes obligatoires :</b> la <b>voiture a atteint sa
|
||
cible</b> — c'est la fin normale, celle qu'on vise —, la durée est écoulée, ou le véhicule
|
||
est débranché. Le plancher batterie est un <b>garde-fou</b>, pas un objectif : on ne cherche
|
||
pas à vider la batterie, on cherche à finir la charge plus tôt.
|
||
<b>Jamais « jusqu'à annulation »</b> — un transfert sans fin court pendant que personne ne
|
||
regarde.</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
<div class="cap"><b>Recharge accélérée</b> — hors Configurer</div>
|
||
<div class="note">
|
||
<span class="pill ev">geste immédiat, pas un réglage</span><br>
|
||
Un réglage décrit une intention durable ; ceci est une action ponctuelle, avec un effet immédiat et
|
||
visible. La mettre dans « Configurer » la rendrait introuvable au moment où on la veut, et
|
||
persistante alors qu'elle ne doit pas l'être.
|
||
<ul>
|
||
<li><b>C'est un argument de vente, et il mérite d'être dit à l'écran :</b> recharger plus vite
|
||
<b>sans passer de 6 à 9 kVA</b>. Le surcoût d'abonnement évité est chiffrable ; le confort
|
||
gagné se constate au premier départ pressé.</li>
|
||
<li><b>Ce bloc appartient au délestage de LM-1006-1, vu par l'autre bout.</b> Le délestage
|
||
applique un <i>plafond de soutirage</i> en <b>réduisant les charges</b> pour tenir dessous ; ici
|
||
on tient sous le même plafond en <b>complétant par la batterie</b>. Même objet, même plafond,
|
||
même arbitre — <b>pas un mécanisme parallèle</b>, et surtout pas un second commandeur.</li>
|
||
<li><b>La remarque « le boost pourrait acheter en cachette » tombe.</b> C'est l'inverse : il
|
||
existe pour <b>ne pas dépasser</b> ce qu'on a le droit de soutirer.</li>
|
||
<li>Côté box, <code>enableDischarging</code> + <code>dischargingRate</code> — déjà déclarés par la
|
||
classe. Le dimensionnement de la marge, la latence de l'onduleur et le rôle de filet de la borne
|
||
sont des <b>questions moteur</b>, écrites dans le brief, pas dessinées ici.</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ══════════ LE MOTIF UNIQUE, ET LES QUESTIONS AU MOTEUR ══════════ -->
|
||
<div class="col wide">
|
||
<div class="note wide">
|
||
<h3>Un seul motif, pas trois écrans voisins</h3>
|
||
<p style="margin:0 0 4px;color:var(--muted2);font-size:11.5px;line-height:1.55">
|
||
Trois fonctionnalités demandées à des moments différents ont <b>exactement la même forme</b>. Si la
|
||
maquette le montre comme un motif unique, le moteur peut l'implémenter une fois au lieu de trois —
|
||
et l'écran peut le présenter une fois au lieu de trois.</p>
|
||
<div class="mot">
|
||
<div class="m"><b>Charge différée</b><span>cible : 100 % de la batterie<br>échéance : 2–3 h avant
|
||
la pointe<br>non-retour : au-delà, on charge de tout<br><br>
|
||
<i style="color:var(--yellow);font-style:normal">paramètres calculés par Héos</i></span></div>
|
||
<div class="m"><b>Charge depuis le réseau</b><span>cible : 6 kWh soutirés<br>échéance : 06:00<br>
|
||
non-retour : au-delà, pleine puissance<br><br>
|
||
<i style="color:var(--green);font-style:normal">paramètres saisis par l'utilisateur</i></span></div>
|
||
<div class="m"><b>Échéance véhicule (LM-1009)</b><span>cible : énergie livrée<br>échéance : heure
|
||
de départ<br>non-retour : au-delà, on passe au réseau<br><br>
|
||
<i style="color:var(--green);font-style:normal">paramètres saisis par l'utilisateur</i></span></div>
|
||
</div>
|
||
|
||
<div class="tl">
|
||
<div class="bar"><div class="seg1"></div><div class="pnr"></div><div class="peak"></div></div>
|
||
<div class="ax"><span>06:00</span><span>12:00</span><span>18:00</span><span>22:00</span></div>
|
||
<div class="lg">
|
||
<em>┃ point de non-retour</em> — avant, on peut encore attendre ; après, on sert de
|
||
<b>tout ce qui reste</b>, prévision ou pas. <u>┃ échéance</u><br>
|
||
C'est la seule partie du motif qui doit être <b>montrée</b> : une prévision n'engage personne,
|
||
un point de non-retour si.
|
||
</div>
|
||
</div>
|
||
|
||
<p style="margin:10px 0 0;color:var(--muted2);font-size:11.5px;line-height:1.55">
|
||
<b>Une cible, une échéance, un point de non-retour</b> — et cela vaut que les paramètres soient
|
||
saisis ou calculés. C'est ce qui rend le motif implémentable une fois : le moteur porte la forme,
|
||
Héos ou l'utilisateur la remplit. Le point de non-retour est la partie qui se perd si on ne la
|
||
nomme pas, et c'est lui qui garantit qu'<b>une prévision fausse ne coûte rien</b>.</p>
|
||
|
||
<div class="ask">
|
||
<b>Trois questions pour le moteur — à trancher, pas à deviner</b>
|
||
<ol>
|
||
<li><b>Bloquer demande un INTERDIT ACTIF, pas une allocation à zéro.</b> La batterie agit
|
||
d'elle-même : l'onduleur charge sans qu'on le lui demande. Ne rien lui allouer ne l'empêche donc
|
||
de rien — il faut lui dire <code>enableCharging = false</code>. C'est une différence de nature
|
||
avec toutes les autres charges du waterfall, où « 0 W alloué » suffit à ce qu'il ne se passe
|
||
rien.</li>
|
||
<li><b>Cet interdit doit EXPIRER.</b> Un HEMS qui tombe après avoir posé
|
||
<code>enableCharging = false</code> laisse une batterie inerte, sans que rien ne le signale et
|
||
sans que personne ne sache pourquoi. Il faut un bail à durée limitée, renouvelé tant que
|
||
l'arbitre vit — l'état sûr d'un stockage, c'est « il fait ce qu'il ferait sans nous », pas
|
||
« il reste sur le dernier ordre reçu ».</li>
|
||
<li><b><code>controlHealthy == false</code> doit SORTIR la batterie de l'arbitrage</b>, comme
|
||
<code>available == false</code> sort une charge en défaut. L'état existe déjà sur la classe. Une
|
||
batterie qu'on croit piloter et qui ne répond plus fausse le budget en silence, exactement comme
|
||
le faisait le double commandeur des bornes avant 3g-2.</li>
|
||
</ol>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</body>
|
||
</html>
|