La batterie devient une charge du waterfall, donc son bouton Configurer ouvre un écran de
mécanisme comme les cinq autres. Ajouté à la planche existante plutôt qu'à part : c'est
précisément la comparaison côte à côte qui avait révélé « six écrans, trois contrats » avant
qu'une ligne ne soit écrite.
RIEN DE CET ÉCRAN N'EXISTE. Ni BatteryAdapter, ni entrée LoadConfig pour le stockage, ni
prévision PV. Les seuls leviers réels de .75 sont les cinq actions de la classe SunSpec
Storage, et l'état réel affiché se limite à ce que le Thing publie — tout le reste porte un
badge « à créer », y compris là où ça alourdit la maquette.
Le rang par défaut est la batterie EN PREMIER, et ce n'est pas un choix de conception : c'est
la reproduction du comportement actuel. Une mise à jour ne change jamais l'arbitrage sans
geste ; la nouveauté est qu'il devient réglable.
Quatre blocs dans Configurer, dont deux qui ont demandé à être reformulés avant d'être
dessinés :
- Les blocages ne sont PAS une question de rang mais de SOURCE. « Ne pas vider la batterie
dans la voiture la nuit » ne s'exprime pas par un ordre de priorité — la nuit il n'y a pas
de surplus à répartir. La formulation juste est « quelles charges ont le droit d'être
servies par la batterie », réglée charge par charge. Le contrat porte déjà
Source { Solar, GridSource } ; il manque le troisième terme et le droit de le refuser.
- La réserve adaptative est montrée AVEC sa réserve : Victron a conçu Battery Life pour du
plomb, et sur du LFP le problème traité — la sulfatation — n'existe quasiment pas. Si le
mécanisme est repris, ce sera pour un motif à nommer, pas par mimétisme.
- La charge différée montre le POINT DE NON-RETOUR, pas la prévision. C'est lui qui garantit
qu'une prévision fausse ne coûte rien.
- La charge réseau pose le mécanisme seul. Ni prix, ni Tempo, ni marché de gros : le jour où
une source tarifaire existera, elle remplacera le doigt de l'utilisateur sans que le bloc
ne change.
Le boost vers le véhicule est HORS Configurer, sur la carte du tableau de bord : c'est le
seul cas où la batterie est une source et non une charge, et c'est un geste immédiat, pas une
intention durable. Trois fins possibles, toutes obligatoires — une action qui ne sait pas
s'arrêter n'a pas sa place sur un écran client.
Et le motif que la planche fait ressortir : charge différée, charge réseau et échéance
véhicule (LM-1009) ont la MÊME forme — une cible, une échéance, un point de non-retour. Trois
colonnes côte à côte le montrent ; le moteur peut l'implémenter une fois.
Trois questions écrites pour le moteur, pas résolues ici : bloquer demande un interdit ACTIF
et non une allocation à zéro, puisque la batterie agit d'elle-même · cet interdit doit
EXPIRER, sinon un HEMS qui tombe laisse la batterie inerte · et controlHealthy == false doit
sortir la batterie de l'arbitrage, comme available == false pour une charge en défaut.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016vrTVifar2GN5rtUh89Wkq
1303 lines
77 KiB
HTML
1303 lines
77 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}
|
||
.perm{display:flex;align-items:center;gap:10px;background:var(--card2);border:1px solid var(--line);
|
||
border-radius:11px;padding:10px 11px;margin-bottom:7px}
|
||
.perm .ic{width:24px;height:24px;border-radius:7px;display:grid;place-items:center;font-size:13px;flex:none;
|
||
background:rgba(255,255,255,.06)}
|
||
.perm .nm{flex:1;min-width:0}
|
||
.perm .nm b{display:block;font-family:'Poppins';font-weight:600;font-size:12.5px}
|
||
.perm .nm span{font-size:10.2px;color:var(--muted2)}
|
||
.src{display:flex;gap:5px;flex:none}
|
||
.sbtn{font-size:10px;font-family:'Poppins';font-weight:600;border-radius:7px;padding:5px 8px;
|
||
border:1px solid var(--line2);background:var(--panel);color:var(--muted2);cursor:pointer}
|
||
.sbtn.on{border-color:var(--green);color:var(--green);background:rgba(53,201,138,.1)}
|
||
.sbtn.no{border-color:rgba(224,122,122,.4);color:var(--red);background:rgba(224,122,122,.08)}
|
||
|
||
.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. <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="newf">à créer · LoadConfig.priority</span><br>
|
||
<small>où le stockage se sert dans la cascade du surplus</small></label>
|
||
<div class="stepper">
|
||
<div class="lab">Position<small>1 = servi en premier</small></div>
|
||
<div class="ctl"><button class="pm">−</button><b>1</b><button class="pm">+</button></div>
|
||
</div>
|
||
<div class="info"><b>Défaut : la batterie en premier.</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 : glisser « 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>Qui a le droit d'être servi par la batterie
|
||
<span class="newf">à créer · Source par charge</span><br>
|
||
<small>réglé PAR CHARGE, pas par rang</small></label>
|
||
|
||
<div class="perm">
|
||
<div class="ic">🚿</div>
|
||
<div class="nm"><b>Eau chaude</b><span>rang 2 · relay-router</span></div>
|
||
<div class="src"><span class="sbtn on">Solaire</span><span class="sbtn no">Batterie</span></div>
|
||
</div>
|
||
<div class="perm">
|
||
<div class="ic">🔥</div>
|
||
<div class="nm"><b>PAC banc</b><span>rang 3 · sg-ready</span></div>
|
||
<div class="src"><span class="sbtn on">Solaire</span><span class="sbtn on">Batterie</span></div>
|
||
</div>
|
||
<div class="perm">
|
||
<div class="ic">🚗</div>
|
||
<div class="nm"><b>Simulated wallbox</b><span>rang 4 · evcharger</span></div>
|
||
<div class="src"><span class="sbtn on">Solaire</span><span class="sbtn no">Batterie</span></div>
|
||
</div>
|
||
|
||
<div class="info"><b>Ce n'est pas une question de rang, c'est une question de SOURCE.</b>
|
||
« Bloquer la décharge pendant la recharge nocturne du véhicule » ne se dit pas avec un ordre de
|
||
priorité : la nuit, il n'y a pas de surplus à répartir, il y a une batterie qu'on ne veut pas
|
||
vider dans la voiture. La formulation juste est donc <b>quelles charges ont le droit d'être
|
||
servies par la batterie</b>, et elle se règle charge par charge.<br><br>
|
||
Le contrat porte déjà <code>Source { Solar, GridSource }</code> — il manque le troisième terme,
|
||
et le droit de le refuser par charge.</div>
|
||
</div>
|
||
|
||
<!-- b. CHARGE DIFFÉRÉE -->
|
||
<div class="field">
|
||
<label>Charge différée sur prévision <span class="newf">à créer · prévision PV absente</span></label>
|
||
<div class="stepper">
|
||
<div class="lab">Viser 100 % juste avant la pointe<small>au lieu de plafonner dès midi</small></div>
|
||
<div class="ctl"><span class="sw"></span></div>
|
||
</div>
|
||
<div class="stepper">
|
||
<div class="lab">Prête à<small>2 à 3 h avant la pointe du soir</small></div>
|
||
<div class="ctl"><b>16:30</b></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 — 13:20</em><br>
|
||
Avant : la charge attend, le pic PV va à l'ECS et à la voiture.<br>
|
||
Après : on charge de <b>tout ce qui reste</b>, prévision ou pas — une prévision qui se trompe
|
||
ne doit jamais coûter une batterie vide. <u>┃ pointe du soir</u>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="mode-hint"><b>Ce que ça gagne :</b> le pic PV de midi est libéré pour l'ECS et le
|
||
véhicule, le vieillissement calendaire baisse (moins de temps passé à 100 %), et
|
||
<b>aucun kWh n'est sacrifié</b> — la même énergie est stockée, plus tard.</div>
|
||
</div>
|
||
|
||
<!-- c. RÉSERVE ADAPTATIVE -->
|
||
<div class="field">
|
||
<label>Réserve adaptative <span class="newf">à créer · à justifier</span></label>
|
||
<div class="stepper">
|
||
<div class="lab">Remonter le plancher après une nuit trop basse<small>façon « Battery Life »</small></div>
|
||
<div class="ctl"><span class="sw off"></span></div>
|
||
</div>
|
||
<div class="warn"><b>À reprendre pour la bonne raison, pas par mimétisme.</b>
|
||
Victron a conçu <i>Battery Life</i> 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. <b>Sur du LFP, le problème traité n'existe quasiment pas</b> — pas de
|
||
sulfatation, et un séjour à SOC bas n'abîme rien.<br><br>
|
||
Si ce mécanisme est repris, ce sera pour un autre motif à nommer explicitement (garder une
|
||
réserve de secours, lisser l'autonomie sur plusieurs jours gris). Le copier tel quel
|
||
importerait une contrainte de chimie que cette installation n'a pas.</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="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.</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><code>batteryLevelConsideration</code> fait aujourd'hui l'INVERSE de ce que cet écran
|
||
permet.</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>Vider dans le véhicule</b><span class="sw off"></span></div>
|
||
<div style="font-size:11px;color:var(--muted);line-height:1.55">
|
||
Pleine puissance, tout de suite. C'est le <b>seul cas où la batterie est une source</b> et
|
||
non une charge — d'où sa place ici, sur un geste immédiat, et non dans les réglages.
|
||
</div>
|
||
<div class="stepper" style="margin-top:10px">
|
||
<div class="lab">S'arrête à<small>cible sur la batterie maison</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>Il doit finir tout seul.</b> Cible atteinte, durée écoulée, ou véhicule
|
||
débranché — <b>jamais « jusqu'à annulation »</b>. Un transfert sans fin vide une maison
|
||
pendant que personne ne regarde, et l'écran qui l'a lancé n'est plus là pour le dire.</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
<div class="cap"><b>Boost</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>Trois fins possibles, toutes obligatoires.</b> Une action qui ne sait pas s'arrêter n'a
|
||
pas sa place sur un écran client.</li>
|
||
<li>Côté box, il faudrait <code>enableDischarging</code> + <code>dischargingRate</code> — déjà
|
||
déclarés par la classe.</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</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</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</span></div>
|
||
</div>
|
||
<p style="margin:8px 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> Le point de non-retour est la partie qui se
|
||
perd si on ne la nomme pas : c'est lui qui garantit qu'une prévision fausse ne coûte rien, et c'est
|
||
lui qu'un écran doit montrer — pas la prévision elle-même, qui n'engage personne.</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>
|