Nachdem ich die Basis-Version meines Raspberry-Pi-Setups als digitalen Bilderrahmen einige Wochen im Alltag genutzt habe, war klar: Das System läuft stabil – aber das Dashboard selbst lässt sich deutlich verbessern.
Die Grundlage habe ich hier beschrieben:
Raspberry Pi als digitaler Bilderrahmen im Kiosk-Modus – Hardware, Setup & Troubleshooting
Dieses Update baut exakt darauf auf und beschreibt den finalen, bereinigten Stand, der inzwischen dauerhaft und unbeaufsichtigt läuft.
Ziel des Dashboard-Updates
Das neue Dashboard sollte:
- mehrere Allsky-Ausgaben direkt am Display nutzbar machen
- Touch-fähig sein
- Videos stabil abspielen
- keine Sonderlogik oder Workarounds enthalten
- im Fehlerfall definierte Zustände haben
Kurz: weniger clever, mehr robust.
Dashboard-Aufbau (final)
Die Oberfläche besteht aus drei Bereichen:
- Aktuelles Bild (groß, klickbar → Vollbild)
- Keogramm „letzte Nacht“ mit Verlinkung zum Zeitraffer-Video im Vollbild
- Keogramm „letzter Tag“ mit Verlinkung zum Zeitraffer-Video im Vollbild
- Startrail ohne Verlinkung
Dashboard: index.html
Das Dashboard selbst ist eine einfache HTML-Datei mit CSS Grid und periodischem Reload der Bilder.
<img id="latestImage" src="https://access.allsky-rodgau.de/indi-allsky/latestimage?ts=INIT" onclick="window.location.href='fullscreen.html'">
Die Keogramme verlinken direkt auf die Timelapse-Ansicht:
<div class="thumb" onclick="window.location.href='timelapse.html?night=1'"> <img src="https://access.allsky-rodgau.de/indi-allsky/latestkeogram?night=1"> </div>
Die Bilder werden regelmäßig aktualisiert:
setInterval(() => { latestImage.src = 'https://access.allsky-rodgau.de/indi-allsky/latestimage?ts=' + Date.now(); }, 25000);index.html – Dashboard (komplett, final)
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=1280, height=800, initial-scale=1.0, user-scalable=no">
<title> </title>
<style>
html, body {
margin: 0;
padding: 0;
width: 1280px;
height: 800px;
background: #000;
color: #fff;
font-family: Arial, Helvetica, sans-serif;
overflow: hidden;
}
.page {
width: 100%;
height: 100%;
opacity: 0;
transition: opacity 0.8s ease;
}
.page.visible {
opacity: 1;
}
.grid {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 20px;
height: 100%;
padding: 20px;
box-sizing: border-box;
}
h1 {
font-size: 28px;
margin: 0 0 12px 0;
font-weight: normal;
}
h2 {
font-size: 22px;
margin: 0 0 10px 0;
font-weight: normal;
}
.main img {
width: 100%;
height: 100%;
object-fit: contain;
cursor: pointer;
}
.sidebar {
display: flex;
flex-direction: column;
gap: 30px;
}
.thumb img {
width: 100%;
border-radius: 4px;
border: 1px solid #333;
}
.thumb p {
margin: 6px 0 0 0;
font-size: 16px;
color: #ccc;
}
</style>
</head>
<body>
<div class="page" id="page">
<div class="grid">
<div class="main">
<h1>Aktuelles Bild</h1>
<img id="latestImage"
src="https://access.allsky-rodgau.de/indi-allsky/latestimage?ts=INIT"
onclick="window.location.href='fullscreen.html'">
</div>
<div class="sidebar">
<div>
<h2>Zeitraffer</h2>
<div class="thumb"
onclick="window.location.href='timelapse.html?night=1'">
<img id="keogramNight"
src="https://access.allsky-rodgau.de/indi-allsky/latestkeogram?night=1&ts=INIT">
<p>Letzte Nacht</p>
</div>
<div class="thumb"
onclick="window.location.href='timelapse.html?night=0'">
<img id="keogramDay"
src="https://access.allsky-rodgau.de/indi-allsky/latestkeogram?night=0&ts=INIT">
<p>Letzter Tag</p>
</div>
</div>
<div>
<h2>Startrail</h2>
<div class="thumb">
<img id="startrail"
src="https://access.allsky-rodgau.de/indi-allsky/lateststartrail?ts=INIT">
<p>Letzter Startrail</p>
</div>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
document.getElementById('page').classList.add('visible');
});
setInterval(() => {
latestImage.src =
'https://access.allsky-rodgau.de/indi-allsky/latestimage?ts=' + Date.now();
}, 25000);
setInterval(() => {
keogramNight.src =
'https://access.allsky-rodgau.de/indi-allsky/latestkeogram?night=1&ts=' + Date.now();
keogramDay.src =
'https://access.allsky-rodgau.de/indi-allsky/latestkeogram?night=0&ts=' + Date.now();
startrail.src =
'https://access.allsky-rodgau.de/indi-allsky/lateststartrail?ts=' + Date.now();
}, 3600000);
</script>
</body>
</html>
Timelapse-Videos per Redirect Views ziehen
Ein zentraler Punkt des Updates war die Video-Wiedergabe. Wichtigste Erkenntnis: Kein Fetch, keine Vorabprüfung, keine Redirect-Logik. Das Video-Element bekommt direkt die URL – der Browser erledigt den Rest.
video.src = 'https://access.allsky-rodgau.de/indi-allsky/latesttimelapse?night=' + night + '&ts=' + Date.now();
Chromium folgt dem Redirect automatisch und spielt das MP4 ab.
Fehlerfall sauber abfangen
Falls kein Video existiert, wird nicht „herumprobiert“, sondern ein klarer Zustand angezeigt.
video.addEventListener('error', () => { message.style.display = 'flex'; });Das verhindert schwarze Screens und macht sofort klar, was Sache ist.
Die komplette Logik beschränkt sich bewusst auf wenige Events:
video.addEventListener('canplay', () => { container.classList.add('visible'); }); video.addEventListener('error', () => { message.style.display = 'flex'; });Navigation zurück zum Dashboard erfolgt immer über das gleiche ✕-Symbol:
function goBack() { window.location.href = 'index.html'; }timelapse.html (komplett, final)
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=1280, height=800, initial-scale=1.0, user-scalable=no">
<title> </title>
<style>
html, body {
margin: 0;
padding: 0;
width: 1280px;
height: 800px;
background: #000;
font-family: Arial, Helvetica, sans-serif;
overflow: hidden;
color: #fff;
}
.container {
position: relative;
width: 100%;
height: 100%;
opacity: 0;
transition: opacity 0.8s ease;
}
.container.visible {
opacity: 1;
}
video {
width: 100%;
height: 100%;
object-fit: contain;
background: #000;
}
.message {
position: absolute;
inset: 0;
display: none;
align-items: center;
justify-content: center;
font-size: 22px;
color: #ccc;
text-align: center;
}
.close {
position: fixed;
top: 20px;
right: 20px;
width: 48px;
height: 48px;
font-size: 32px;
line-height: 48px;
text-align: center;
background: rgba(0,0,0,0.6);
border-radius: 4px;
cursor: pointer;
}
</style>
</head>
<body>
<div class="close" onclick="goBack()">✕</div>
<div class="container" id="container">
<video id="video"
autoplay
muted
playsinline>
</video>
<div class="message" id="message">
Kein Zeitraffer verfügbar
</div>
</div>
<script>
const params = new URLSearchParams(window.location.search);
const night = params.get('night');
const video = document.getElementById('video');
const container = document.getElementById('container');
const message = document.getElementById('message');
video.addEventListener('canplay', () => {
container.classList.add('visible');
});
video.addEventListener('error', () => {
message.style.display = 'flex';
container.classList.add('visible');
});
video.src =
'https://access.allsky-rodgau.de/indi-allsky/latesttimelapse?night=' +
night + '&ts=' + Date.now();
function goBack() {
container.classList.remove('visible');
setTimeout(() => {
window.location.href = 'index.html';
}, 800);
}
</script>
</body>
</html>
Vollbild-Ansicht für das Livebild: fullscreen.html
Das aktuelle Allsky-Bild kann im Vollbild angezeigt werden – ohne Browser-UI, ohne Navigationselemente.
<img id="latestImage" src="https://access.allsky-rodgau.de/indi-allsky/latestimage?ts=INIT">
Automatische Aktualisierung:
setInterval(() => { latestImage.src = 'https://access.allsky-rodgau.de/indi-allsky/latestimage?ts=' + Date.now(); }, 25000);fullscreen.html (komplett, final)
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=1280, height=800, initial-scale=1.0, user-scalable=no">
<title> </title>
<style>
html, body {
margin: 0;
padding: 0;
width: 1280px;
height: 800px;
background: #000;
overflow: hidden;
font-family: Arial, Helvetica, sans-serif;
}
img {
width: 100%;
height: 100%;
object-fit: contain;
}
.close {
position: fixed;
top: 20px;
right: 20px;
width: 48px;
height: 48px;
font-size: 32px;
line-height: 48px;
text-align: center;
background: rgba(0,0,0,0.6);
color: #fff;
cursor: pointer;
border-radius: 4px;
}
.dashboard {
position: fixed;
bottom: 30px;
left: 50%;
transform: translateX(-50%);
padding: 12px 24px;
background: rgba(0,0,0,0.6);
color: #fff;
border-radius: 4px;
cursor: pointer;
font-size: 18px;
}
</style>
</head>
<body>
<img id="latestImage"
src="https://access.allsky-rodgau.de/indi-allsky/latestimage?ts=INIT">
<div class="close" onclick="goDashboard()">✕</div>
<div class="dashboard" onclick="goDashboard()">Dashboard</div>
<script>
setInterval(() => {
latestImage.src =
'https://access.allsky-rodgau.de/indi-allsky/latestimage?ts=' + Date.now();
}, 25000);
function goDashboard() {
window.location.href = 'index.html';
}
</script>
</body>
</html>
Fertig, läuft!
