Add Pi Zero headless serial bridge with AP portal and daily RTC-based logs

This commit is contained in:
2026-02-11 21:16:23 +01:00
parent 7f0e872942
commit 6ee36ee45b
23 changed files with 1173 additions and 0 deletions

119
templates/index.html Normal file
View File

@@ -0,0 +1,119 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Serial Portal</title>
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
<main class="container">
<h1>WiFi Setup</h1>
<p>Fallback-AP: <strong>serial</strong> / Passwort: <strong>serialserial</strong></p>
<section class="card">
<h2>Netzwerkstatus</h2>
<div id="status">Lade Status...</div>
<div id="error" class="error"></div>
</section>
<section class="card">
<h2>Mit WLAN verbinden</h2>
<button id="scanBtn">Scan</button>
<label for="ssidSelect">SSID</label>
<select id="ssidSelect">
<option value="">Bitte zuerst scannen</option>
</select>
<label for="pw">Passwort</label>
<input id="pw" type="password" autocomplete="off" placeholder="WLAN Passwort">
<button id="connectBtn">Verbinden</button>
<div id="connectMsg"></div>
</section>
<section class="card">
<h2>Serial Monitor</h2>
<a href="/serial">Zur Live-Serial-Seite</a>
</section>
</main>
<script>
async function refreshStatus() {
try {
const resp = await fetch('/api/status');
const data = await resp.json();
document.getElementById('status').textContent =
`AP: ${data.ap_mode ? 'an' : 'aus'} | WLAN: ${data.wifi_connected ? 'verbunden' : 'getrennt'} | Internet: ${data.internet_available ? 'ok' : 'nein'} | Status: ${data.status_message}`;
document.getElementById('error').textContent = data.last_error || '';
} catch (e) {
document.getElementById('status').textContent = 'Status nicht erreichbar';
}
}
async function scan() {
const msg = document.getElementById('connectMsg');
msg.textContent = 'Scanne WLANs...';
try {
const resp = await fetch('/api/scan', {method: 'POST'});
const data = await resp.json();
const select = document.getElementById('ssidSelect');
select.innerHTML = '';
if (data.ssids && data.ssids.length) {
data.ssids.forEach(ssid => {
const opt = document.createElement('option');
opt.value = ssid;
opt.textContent = ssid;
select.appendChild(opt);
});
msg.textContent = `${data.ssids.length} Netzwerke gefunden.`;
} else {
const opt = document.createElement('option');
opt.value = '';
opt.textContent = 'Keine Netzwerke gefunden';
select.appendChild(opt);
msg.textContent = 'Keine Netzwerke gefunden.';
}
} catch (e) {
msg.textContent = 'Scan fehlgeschlagen.';
}
refreshStatus();
}
async function connectWifi() {
const ssid = document.getElementById('ssidSelect').value;
const password = document.getElementById('pw').value;
const msg = document.getElementById('connectMsg');
if (!ssid) {
msg.textContent = 'Bitte SSID wählen.';
return;
}
msg.textContent = `Verbinde mit ${ssid}...`;
try {
const resp = await fetch('/api/connect', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ssid, password})
});
const data = await resp.json();
if (resp.ok && data.ok) {
msg.textContent = 'Verbindung erfolgreich. AP wird beendet.';
} else {
msg.textContent = `Fehler: ${data.message || 'Connect fehlgeschlagen'}`;
}
} catch (e) {
msg.textContent = 'Verbindungsversuch fehlgeschlagen.';
}
setTimeout(refreshStatus, 1000);
}
document.getElementById('scanBtn').addEventListener('click', scan);
document.getElementById('connectBtn').addEventListener('click', connectWifi);
refreshStatus();
setInterval(refreshStatus, 5000);
</script>
</body>
</html>