- specs -> docs/ (DASHBOARD_SPEC compagnon du contrat) - ref introspection nymea-jsonRPC -> docs/reference/ - mockups HTML -> docs/mockups/ ; briefs consommés -> docs/archive/ - track AGENTS.md + INTERFACE_etmvariableload.md (étaient untracked) - INTERFACE_*: marqué miroir, canonique = repo plugin
250 lines
15 KiB
HTML
250 lines
15 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 charge pilotée (ECS)</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;
|
||
--txt:#e8f1f6; --muted:#90a8b6; --muted2:#6f8b9a; --r:16px;
|
||
}
|
||
*{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:880px;margin:0 auto 26px}
|
||
.head h1{font-family:'Poppins';font-weight:700;font-size:20px;margin:0 0 6px}
|
||
.head p{margin:0;color:var(--muted);font-size:13.5px;line-height:1.55;max-width:700px}
|
||
.head code{color:#9fd4ee}
|
||
.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}
|
||
.cap{color:var(--muted2);font-size:12px;font-family:'Poppins';font-weight:500;letter-spacing:.4px;text-transform:uppercase}
|
||
|
||
.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:812px}
|
||
.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:15.5px;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}
|
||
|
||
/* segmented Dynamique/Fixe */
|
||
.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:13px;padding:10px;border-radius:8px;cursor:pointer;transition:.15s}
|
||
.seg button.on{background:var(--blue);color:#04222f}
|
||
.seg .hint{}
|
||
.mode-hint{margin-top:9px;font-size:11.5px;color:var(--muted2);line-height:1.5}
|
||
|
||
.pick{display:flex;align-items:center;gap:10px;padding:11px;border-radius:11px;border:1px solid var(--line);
|
||
background:var(--card2);margin-bottom:8px}
|
||
.pick.sel{border-color:var(--blue);background:rgba(49,163,221,.08)}
|
||
.pick .radio{width:18px;height:18px;border-radius:50%;border:2px solid var(--muted2);flex:none}
|
||
.pick.sel .radio{border-color:var(--blue);background:radial-gradient(circle,var(--blue) 42%,transparent 46%)}
|
||
.pick .t{font-size:13.5px;font-weight:500}.pick .s{font-size:11.5px;color:var(--muted2);margin-top:1px}
|
||
.typeflag{font-size:11px;color:var(--muted);background:rgba(255,255,255,.05);border:1px solid var(--line);
|
||
border-radius:7px;padding:3px 9px;display:inline-block}
|
||
|
||
/* paliers chips (le champ réellement sauvé) */
|
||
.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}
|
||
.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:13.5px}
|
||
.pchip i{font-style:normal;color:var(--muted2);font-weight:500;font-size:11px}
|
||
.pchip.off{opacity:.6}
|
||
.pchip .x{color:var(--muted2);cursor:pointer;font-size:13px;margin-left:2px}
|
||
.add{width:36px;height:36px;border-radius:9px;border:1px dashed var(--line2);color:var(--muted);
|
||
display:grid;place-items:center;font-size:19px;background:transparent;cursor:pointer}
|
||
|
||
/* assistant */
|
||
.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:12.5px;padding:10px;border-radius:10px;cursor:pointer}
|
||
.assist{margin-top:10px;border:1px solid rgba(49,163,221,.28);background:rgba(49,163,221,.06);
|
||
border-radius:12px;padding:13px;display:none}
|
||
.assist.open{display:block}
|
||
.assist h4{font-family:'Poppins';font-size:12px;margin:0 0 3px;color:var(--blue)}
|
||
.assist p{margin:0 0 11px;font-size:11px;color:var(--muted2);line-height:1.45}
|
||
.res-row{display:flex;gap:8px;margin-bottom:9px}
|
||
.res-row input{flex:1;background:var(--panel);border:1px solid var(--line2);border-radius:9px;color:var(--txt);
|
||
font-family:'Poppins';font-weight:600;font-size:13px;padding:9px 11px;width:100%;text-align:center}
|
||
.res-row input::placeholder{color:var(--muted2);font-weight:500}
|
||
.preview{font-size:11.5px;color:var(--muted);margin:4px 0 12px;line-height:1.5}
|
||
.preview b{color:var(--green);font-family:'Poppins'}
|
||
.assist .apply{width:100%;background:var(--blue);color:#04222f;border:0;border-radius:9px;padding:10px;
|
||
font-family:'Poppins';font-weight:600;font-size:12.5px;cursor:pointer}
|
||
|
||
.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:13px}.stepper .lab small{display:block;color:var(--muted2);font-size:11px;margin-top:1px}
|
||
.stepper .ctl{display:flex;align-items:center;gap:13px}
|
||
.stepper .ctl b{font-family:'Poppins';font-weight:600;font-size:14px;min-width:64px;text-align:right}
|
||
.pm{width:28px;height:28px;border-radius:8px;border:1px solid var(--line2);color:var(--blue);background:transparent;
|
||
font-size:17px;display:grid;place-items:center;cursor:pointer}
|
||
.prio{width:24px;height:24px;border-radius:7px;background:var(--blue);color:#04222f;font-family:'Poppins';
|
||
font-weight:700;font-size:12px;display:grid;place-items:center}
|
||
.sw{width:40px;height:23px;border-radius:20px;background:rgba(53,201,138,.85);position:relative;cursor:pointer}
|
||
.sw::after{content:"";position:absolute;top:2px;left:19px;width:19px;height:19px;border-radius:50%;background:#fff}
|
||
|
||
.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.test{flex:1.3;background:transparent;border:1px solid var(--line2);color:var(--txt)}
|
||
|
||
.note{max-width:392px;font-size:11.5px;color:var(--muted2);line-height:1.55;text-align:left}
|
||
.note b{color:var(--yellow)}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="head">
|
||
<h1>Configurer une charge pilotée — ECS</h1>
|
||
<p>Mode <b>Dynamique</b> (consigne continue) ou <b>Fixe</b> (paliers). Le champ sauvé est <b>toujours la liste de paliers en W</b> — jamais les résistances. L'assistant n'est qu'un <i>générateur de saisie côté UI</i> : il additionne les résistances et pré-remplit les paliers, puis disparaît. L'energymanager ne reçoit que <code>powerLevels: [0,600,1200,1800,2400]</code> et envoie un setpoint W ; le thing ECS décide la combinaison.</p>
|
||
</div>
|
||
|
||
<div class="stage">
|
||
|
||
<!-- MODE FIXE -->
|
||
<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>Appareil</label>
|
||
<div class="pick sel"><div class="radio"></div>
|
||
<div style="flex:1"><div class="t">Relay-ECS</div><div class="s">multipallier · 4 sorties</div></div>
|
||
<div class="typeflag">etmvariableload</div></div>
|
||
</div>
|
||
|
||
<div class="field">
|
||
<label>Mode de pilotage</label>
|
||
<div class="seg">
|
||
<button>Dynamique</button>
|
||
<button class="on">Fixe (paliers)</button>
|
||
</div>
|
||
<div class="mode-hint">Fixe : l'energymanager arrondit le surplus au palier atteignable ≤ surplus, puis l'envoie. Le thing choisit quelles résistances composent ce palier.</div>
|
||
</div>
|
||
|
||
<div class="field">
|
||
<label>Paliers de puissance <span class="saved-flag">enregistré · powerLevels</span><br><small>valeurs envoyées à l'energymanager</small></label>
|
||
<div class="chips" id="levels">
|
||
<div class="pchip off">0 W <i>off</i></div>
|
||
<div class="pchip">600 W<span class="x">×</span></div>
|
||
<div class="pchip">1200 W<span class="x">×</span></div>
|
||
<div class="pchip">1800 W<span class="x">×</span></div>
|
||
<div class="pchip">2400 W<span class="x">×</span></div>
|
||
<button class="add" title="Ajouter un palier">+</button>
|
||
</div>
|
||
|
||
<button class="assist-btn" onclick="document.getElementById('asst').classList.toggle('open')">⚡ Calculer depuis mes résistances</button>
|
||
<div class="assist" id="asst">
|
||
<h4>Assistant résistances</h4>
|
||
<p>Entrez la puissance de chaque résistance. L'UI calcule les combinaisons et remplit les paliers ci-dessus. Rien de tout ça n'est transmis — seuls les paliers le sont.</p>
|
||
<div class="res-row">
|
||
<input value="600"><input value="600"><input value="600"><input value="600">
|
||
</div>
|
||
<div class="preview">4 résistances → paliers atteignables : <b>0 · 600 · 1200 · 1800 · 2400 W</b></div>
|
||
<button class="apply" onclick="document.getElementById('asst').classList.remove('open')">Remplir les paliers</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="field">
|
||
<label>Stratégie</label>
|
||
<div class="stepper"><div class="lab">Rang de service<small>défini au glisser-déposer</small></div><div class="ctl"><div class="prio">2</div></div></div>
|
||
<div class="stepper"><div class="lab">Inclure dans l'arbitrage</div><div class="ctl"><div class="sw"></div></div></div>
|
||
</div>
|
||
|
||
<div class="field">
|
||
<label>Besoins · needs</label>
|
||
<div class="stepper"><div class="lab">Eau chaude prête à<small>dailyDeadline</small></div><div class="ctl"><b>06:00</b></div></div>
|
||
<div class="stepper"><div class="lab">Énergie min. / jour<small>minEnergyWhPerDay</small></div><div class="ctl"><button class="pm">−</button><b>4 kWh</b><button class="pm">+</button></div></div>
|
||
</div>
|
||
|
||
<div class="save"><div class="btn test">Tester</div><div class="btn go">Enregistrer</div></div>
|
||
</div>
|
||
</div>
|
||
<div class="cap">Mode Fixe · paliers + assistant</div>
|
||
</div>
|
||
|
||
<!-- MODE DYNAMIQUE -->
|
||
<div class="col">
|
||
<div class="phone">
|
||
<div class="appbar"><div class="ic">‹</div><h2>Configurer · Routeur solaire</h2><div class="ic"></div></div>
|
||
<div class="scroll">
|
||
|
||
<div class="field">
|
||
<label>Appareil</label>
|
||
<div class="pick sel"><div class="radio"></div>
|
||
<div style="flex:1"><div class="t">Routeur PV</div><div class="s">triac · modulation continue</div></div>
|
||
<div class="typeflag">etmvariableload</div></div>
|
||
</div>
|
||
|
||
<div class="field">
|
||
<label>Mode de pilotage</label>
|
||
<div class="seg">
|
||
<button class="on">Dynamique</button>
|
||
<button>Fixe (paliers)</button>
|
||
</div>
|
||
<div class="mode-hint">Dynamique : l'energymanager envoie n'importe quelle valeur entre 0 et la puissance nominale. Pas de paliers.</div>
|
||
</div>
|
||
|
||
<div class="field">
|
||
<label>Puissance nominale max <span class="saved-flag">enregistré · maxPowerW</span></label>
|
||
<div class="stepper"><div class="lab">Résistance ECS<small>plafond de modulation</small></div><div class="ctl"><button class="pm">−</button><b>3000 W</b><button class="pm">+</button></div></div>
|
||
</div>
|
||
|
||
<div class="field">
|
||
<label>Stratégie</label>
|
||
<div class="stepper"><div class="lab">Rang de service<small>défini au glisser-déposer</small></div><div class="ctl"><div class="prio">2</div></div></div>
|
||
<div class="stepper"><div class="lab">Inclure dans l'arbitrage</div><div class="ctl"><div class="sw"></div></div></div>
|
||
</div>
|
||
|
||
<div class="field">
|
||
<label>Besoins · needs</label>
|
||
<div class="stepper"><div class="lab">Eau chaude prête à<small>dailyDeadline</small></div><div class="ctl"><b>06:00</b></div></div>
|
||
<div class="stepper"><div class="lab">Énergie min. / jour<small>minEnergyWhPerDay</small></div><div class="ctl"><button class="pm">−</button><b>4 kWh</b><button class="pm">+</button></div></div>
|
||
</div>
|
||
|
||
<div class="save"><div class="btn test">Tester</div><div class="btn go">Enregistrer</div></div>
|
||
</div>
|
||
</div>
|
||
<div class="cap">Mode Dynamique · puissance max seule</div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div style="display:flex;justify-content:center;margin-top:24px">
|
||
<div class="note">
|
||
<b>Frontière.</b> Aucune notion de résistance ne franchit l'app. En Fixe, le contrat est <code style="color:#9fd4ee">powerLevels:[…]</code> ; en Dynamique, <code style="color:#9fd4ee">maxPowerW</code>. Dans les deux cas, l'energymanager raisonne en W et le thing décide du matériel.
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
document.querySelectorAll('.seg button').forEach(b=>b.addEventListener('click',()=>{
|
||
b.parentNode.querySelectorAll('button').forEach(x=>x.classList.remove('on'));b.classList.add('on');
|
||
}));
|
||
document.querySelectorAll('.sw').forEach(s=>s.addEventListener('click',()=>{
|
||
s.style.background=s.style.background.includes('53,201')?'rgba(255,255,255,.16)':'rgba(53,201,138,.85)';
|
||
s.querySelector?s.firstChild:0;
|
||
}));
|
||
document.querySelectorAll('.pchip .x').forEach(x=>x.addEventListener('click',e=>e.target.closest('.pchip').remove()));
|
||
</script>
|
||
</body>
|
||
</html>
|