kugelmuehle/web_ui.h
2026-10-11 21:41:53 +02:00

168 lines
6.3 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#pragma once
#include <Arduino.h>
// Komplette Weboberfläche im Flash des Programms – kein LittleFS-Upload nötig.
static const char INDEX_HTML[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Kugelmühle</title>
<style>
:root { --bg:#1b1d21; --card:#272a30; --txt:#eceff4; --dim:#8b93a1; --ok:#3fb950; --warn:#f85149; --acc:#2f81f7; }
* { box-sizing:border-box; }
body { margin:0; font-family:system-ui,Arial,sans-serif; background:var(--bg); color:var(--txt); padding:16px; }
.wrap { max-width:460px; margin:0 auto; display:grid; gap:14px; }
.card { background:var(--card); border-radius:12px; padding:16px; }
h1 { font-size:1.2rem; margin:0; display:flex; justify-content:space-between; align-items:center; }
h2 { font-size:.8rem; margin:0 0 10px; color:var(--dim); text-transform:uppercase; letter-spacing:.06em; }
.dot { width:10px; height:10px; border-radius:50%; background:var(--warn); }
.dot.on { background:var(--ok); }
.grid { display:grid; grid-template-columns:1fr 1fr; gap:12px; }
.val { font-size:1.8rem; font-weight:700; }
.lbl { font-size:.8rem; color:var(--dim); }
.hot { color:var(--warn); }
.state { font-weight:700; }
.state.run { color:var(--ok); }
.state.err { color:var(--warn); }
button { width:100%; padding:14px; border:0; border-radius:8px; font-size:1rem; font-weight:600; color:#fff; background:var(--acc); cursor:pointer; }
button.go { background:var(--ok); }
button.stop { background:var(--warn); }
button.off { background:#444b57; }
button:disabled { opacity:.4; cursor:not-allowed; }
.row { display:flex; gap:10px; align-items:center; margin-top:10px; }
.row > * { flex:1; }
input[type=number], input[type=time] { width:100%; padding:10px; border-radius:8px; border:1px solid #444b57; background:#1b1d21; color:var(--txt); font-size:1rem; }
input[type=range] { width:100%; }
.small { font-size:.8rem; color:var(--dim); margin-top:6px; }
</style>
</head>
<body>
<div class="wrap">
<div class="card">
<h1>Kugelmühle <span class="dot" id="conn" title="Verbindung"></span></h1>
</div>
<div class="card">
<h2>Status</h2>
<div class="grid">
<div><div class="val" id="temp">--</div><div class="lbl">Temperatur °C (Limit <span id="maxT">--</span>)</div></div>
<div><div class="val" id="rpm">--</div><div class="lbl">U/min</div></div>
<div><div class="state" id="state">--</div><div class="lbl">Motor</div></div>
<div><div class="state" id="clock">--:--</div><div class="lbl">Uhrzeit</div></div>
</div>
</div>
<div class="card">
<h2>Steuerung</h2>
<button id="btnRun" onclick="toggleRun()">Start</button>
<div class="small" id="runHint"></div>
<div class="row">
<button id="btnTimer" onclick="toggleTimer()">Zeitschaltuhr</button>
</div>
<div class="small">Bei aktiver Zeitschaltuhr läuft der Motor nur im eingestellten Zeitfenster.</div>
</div>
<div class="card">
<h2>Drehzahl (Automatik / Start)</h2>
<div><b id="speedVal">--</b> U/min</div>
<input type="range" id="speed" min="50" max="1987" step="50"
oninput="$('speedVal').textContent=this.value"
onchange="send('speed', +this.value)">
</div>
<div class="card">
<h2>Zeitfenster</h2>
<div class="row">
<div><div class="lbl">Start</div><input type="time" id="tStart"></div>
<div><div class="lbl">Ende</div><input type="time" id="tEnd"></div>
</div>
<div class="row"><button onclick="sendWindow()">Übernehmen</button></div>
</div>
<div class="card">
<h2>Einstellungen</h2>
<div class="row">
<div><div class="lbl">Max. Temperatur °C</div><input type="number" id="maxTemp" min="-20" max="150" step="5"></div>
<div><div class="lbl">Beschleunigung</div><input type="number" id="accel" min="1000" max="30000" step="500"></div>
</div>
<div class="row"><button onclick="sendSettings()">Übernehmen</button></div>
</div>
</div>
<script>
const $ = id => document.getElementById(id);
let ws, last = {};
function connect() {
ws = new WebSocket(`ws://${location.host}/ws`);
ws.onopen = () => $('conn').classList.add('on');
ws.onclose = () => { $('conn').classList.remove('on'); setTimeout(connect, 2000); };
ws.onmessage = e => render(JSON.parse(e.data));
}
function send(cmd, v) {
if (ws && ws.readyState === 1) ws.send(JSON.stringify({ cmd, v }));
}
const pad = n => String(n).padStart(2, '0');
function setIfIdle(id, value) {
const el = $(id);
if (document.activeElement !== el) el.value = value; // nicht überschreiben, während getippt/gezogen wird
}
function render(d) {
last = d;
$('temp').textContent = d.temp;
$('temp').classList.toggle('hot', d.overheat);
$('maxT').textContent = d.maxTemp;
$('rpm').textContent = d.rpm;
$('clock').textContent = d.time;
const st = $('state');
st.className = 'state';
if (d.overheat) { st.textContent = 'ÜBERHITZT'; st.classList.add('err'); }
else if (d.running) { st.textContent = 'Läuft'; st.classList.add('run'); }
else { st.textContent = 'Gestoppt'; }
const run = $('btnRun');
run.disabled = d.overheat || d.timerOn;
run.textContent = d.webRun ? 'Stopp' : 'Start';
run.className = d.webRun ? 'stop' : 'go';
$('runHint').textContent = d.timerOn ? 'Start/Stopp ist gesperrt, solange die Zeitschaltuhr aktiv ist.' : '';
const tm = $('btnTimer');
tm.textContent = 'Zeitschaltuhr: ' + (d.timerOn ? 'AN' : 'AUS');
tm.className = d.timerOn ? 'go' : 'off';
$('speed').max = d.maxRpm;
if (document.activeElement !== $('speed')) { $('speed').value = d.autoSpeed; $('speedVal').textContent = d.autoSpeed; }
setIfIdle('tStart', pad(d.sh) + ':' + pad(d.sm));
setIfIdle('tEnd', pad(d.eh) + ':' + pad(d.em));
setIfIdle('maxTemp', d.maxTemp);
setIfIdle('accel', d.accel);
}
function toggleRun() { send('run', last.webRun ? 0 : 1); }
function toggleTimer() { send('timer', last.timerOn ? 0 : 1); }
function sendWindow() {
const [sh, sm] = $('tStart').value.split(':').map(Number);
const [eh, em] = $('tEnd').value.split(':').map(Number);
if ([sh, sm, eh, em].some(isNaN)) return;
send('window', [sh, sm, eh, em]);
}
function sendSettings() {
send('maxTemp', +$('maxTemp').value);
send('accel', +$('accel').value);
}
connect();
</script>
</body>
</html>
)rawliteral";