Le miroir INTERFACE_etmvariableload.md est passé en rév. 3 pendant la session (corps
identique au canonique du dépôt plugin, vérifié). Son §10 s'adresse nommément à l'agent
app — et il RATIFIE les maquettes dessinées aujourd'hui : relays[] = {thingId, powerW}
pour l'ECS relais, maxPowerW seul pour le continu, powerLevels absents de la config et
dérivés par le routeur.
Trois recalages :
- Le picker de relais filtre sur l'interface nymea « power » et présente les things par
NOM — nommé dans la maquette, il ne l'était pas.
- Le point 4 du brief plugin est tranché par la rév. 3 : la duplication de la
combinatoire est voulue (l'app dérive pour montrer, le routeur pour exécuter). La
demande devient donc un moyen de détecter la divergence — la télémétrie publie déjà
maxStageW, l'app peut y confronter le maximum de sa propre liste.
- En-tête du pont VM Service, que le commit précédent avait laissé dans sa forme brute.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XPUo3RMr8SzK6qbFtfBm8H
1002 lines
57 KiB
HTML
1002 lines
57 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)}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<div class="head">
|
||
<h1>Six écrans, trois 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>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>
|
||
|
||
</div>
|
||
</body>
|
||
</html>
|