Trennung HTML/Javascript von Python

Wind als Linechart
This commit is contained in:
rxf
2026-01-25 10:53:47 +01:00
parent 2907f5de18
commit 5f516f5dd4
3 changed files with 299 additions and 320 deletions

89
static/css/style.css Normal file
View File

@@ -0,0 +1,89 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1400px;
margin: 0 auto;
background: white;
border-radius: 20px;
padding: 30px;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
}
h1 {
text-align: center;
color: #333;
margin-bottom: 30px;
font-size: 2.5em;
}
.tabs {
display: flex;
justify-content: center;
gap: 20px;
margin-bottom: 30px;
}
.tab {
padding: 15px 40px;
background: #f0f0f0;
border: none;
border-radius: 10px;
cursor: pointer;
font-size: 18px;
transition: all 0.3s;
font-weight: 600;
}
.tab:hover {
background: #e0e0e0;
transform: translateY(-2px);
}
.tab.active {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.charts-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 20px;
margin-top: 20px;
}
.chart-container {
background: #f9f9f9;
border-radius: 15px;
padding: 20px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
/* feste Mindesthöhe sorgt für konsistente Layouts */
min-height: 420px;
}
/* Stellt sicher, dass Charts nicht breiter als ihre Container werden */
#charts { width: 100%; }
.chart-container > div { width: 100%; max-width: 100%; }
.loading {
text-align: center;
padding: 50px;
font-size: 20px;
color: #666;
}
@media (max-width: 1024px) {
.charts-grid {
grid-template-columns: 1fr;
}
}

210
static/js/app.js Normal file
View File

@@ -0,0 +1,210 @@
let currentPeriod = 'day';
const DEFAULT_CHART_HEIGHT = 360;
const POLAR_CHART_HEIGHT = 420;
// HighCharts globale Einstellungen
Highcharts.setOptions({
lang: {
months: ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni',
'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'],
shortMonths: ['Jan', 'Feb', 'Mär', 'Apr', 'Mai', 'Jun',
'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'],
weekdays: ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'],
shortWeekdays: ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa']
}
});
function switchPeriod(period) {
currentPeriod = period;
// Tab-Status aktualisieren
document.querySelectorAll('.tab').forEach(tab => {
tab.classList.remove('active');
});
event.target.classList.add('active');
loadData();
}
function loadData() {
document.getElementById('loading').style.display = 'block';
document.getElementById('charts').style.display = 'none';
fetch(`/api/data/${currentPeriod}`)
.then(response => response.json())
.then(data => {
renderCharts(data);
document.getElementById('loading').style.display = 'none';
document.getElementById('charts').style.display = 'grid';
})
.catch(error => {
console.error('Fehler beim Laden:', error);
document.getElementById('loading').innerHTML = 'Fehler beim Laden der Daten';
});
}
function renderCharts(apiData) {
const data = apiData.data;
const rainData = apiData.rain_hourly;
// Konvertiere Timestamps in Millisekunden
const timestamps = data.map(d => {
const [date, time] = d.datetime.split(' ');
return new Date(date + 'T' + time).getTime();
});
const rainTimestamps = rainData.map(d => {
const [date, time] = d.hour.split(' ');
return new Date(date + 'T' + time).getTime();
});
// Berechne Zeitbereich für die Achsen
const minTime = Math.min(...timestamps, ...rainTimestamps);
const maxTime = Math.max(...timestamps, ...rainTimestamps);
// 1-Stunden-Intervalle für die Achsen-Labels (3600000 ms = 1 Stunde)
const ONE_HOUR = 3600000;
const FOUR_HOURS = ONE_HOUR * 4;
// Temperatur
Highcharts.chart('temp-chart', {
chart: { type: 'line', height: DEFAULT_CHART_HEIGHT, spacingRight: 20 },
title: { text: '🌡️ Temperatur (°C)' },
xAxis: {
type: 'datetime',
title: { text: 'Zeit' },
labels: { format: '{value:%H}' },
tickInterval: FOUR_HOURS
},
yAxis: { title: { text: '°C' } },
legend: { enabled: true },
series: [{
name: 'Temperatur',
data: data.map((d, i) => [timestamps[i], d.temperature]),
color: '#ff6b6b',
lineWidth: 2
}],
credits: { enabled: false }
});
// Luftfeuchtigkeit
Highcharts.chart('humidity-chart', {
chart: { type: 'line', height: DEFAULT_CHART_HEIGHT, spacingRight: 20 },
title: { text: '💧 Luftfeuchtigkeit (%)' },
xAxis: {
type: 'datetime',
title: { text: 'Zeit' },
labels: { format: '{value:%H}' },
tickInterval: FOUR_HOURS
},
yAxis: { title: { text: '%' } },
legend: { enabled: true },
series: [{
name: 'Luftfeuchtigkeit',
data: data.map((d, i) => [timestamps[i], d.humidity]),
color: '#4ecdc4',
lineWidth: 2
}],
credits: { enabled: false }
});
// Luftdruck
Highcharts.chart('pressure-chart', {
chart: { type: 'line', height: DEFAULT_CHART_HEIGHT, spacingRight: 20 },
title: { text: '🎈 Luftdruck (hPa)' },
xAxis: {
type: 'datetime',
title: { text: 'Zeit' },
labels: { format: '{value:%H}' },
tickInterval: FOUR_HOURS
},
yAxis: { title: { text: 'hPa' } },
legend: { enabled: true },
series: [{
name: 'Luftdruck',
data: data.map((d, i) => [timestamps[i], d.pressure]),
color: '#95e1d3',
lineWidth: 2
}],
credits: { enabled: false }
});
// Regenmenge pro Stunde
Highcharts.chart('rain-chart', {
chart: { type: 'column', height: DEFAULT_CHART_HEIGHT, spacingRight: 20 },
title: { text: '🌧️ Regenmenge pro Stunde (mm)' },
xAxis: {
type: 'datetime',
title: { text: 'Zeit' },
labels: { format: '{value:%H}' },
tickInterval: FOUR_HOURS
},
yAxis: { title: { text: 'mm' } },
legend: { enabled: false },
series: [{
name: 'Regen',
data: rainData.map((d, i) => [rainTimestamps[i], d.rain]),
color: '#3498db'
}],
credits: { enabled: false }
});
// Windgeschwindigkeit
Highcharts.chart('wind-speed-chart', {
chart: { type: 'line', height: DEFAULT_CHART_HEIGHT, spacingRight: 20 },
title: { text: '💨 Windgeschwindigkeit (m/s)' },
xAxis: {
type: 'datetime',
title: { text: 'Zeit' },
labels: { format: '{value:%H}' },
tickInterval: FOUR_HOURS
},
yAxis: { title: { text: 'm/s' } },
legend: { enabled: true },
series: [{
name: 'Windgeschwindigkeit',
data: data.map((d, i) => [timestamps[i], d.wind_speed]),
color: '#f38181',
lineWidth: 2
}, {
name: 'Böen',
data: data.map((d, i) => [timestamps[i], d.wind_gust]),
color: '#aa96da',
lineWidth: 2,
dashStyle: 'dash'
}],
credits: { enabled: false }
});
// Windrichtung
Highcharts.chart('wind-dir-chart', {
chart: { type: 'line', height: DEFAULT_CHART_HEIGHT, spacingRight: 20 },
title: { text: '🧭 Windrichtung (°)' },
xAxis: {
type: 'datetime',
title: { text: 'Zeit' },
labels: { format: '{value:%H}' },
tickInterval: FOUR_HOURS
},
yAxis: {
title: { text: 'Richtung (°)' },
min: 0,
max: 360,
tickPositions: [0, 90, 180, 270, 360]
},
legend: { enabled: true },
series: [{
name: 'Windrichtung',
data: data.map((d, i) => [timestamps[i], d.wind_dir || 0]),
color: '#f39c12',
lineWidth: 2
}],
credits: { enabled: false }
});
}
// Initiales Laden
loadData();
// Auto-Refresh alle 5 Minuten
setInterval(loadData, 5 * 60 * 1000);

View File

@@ -241,333 +241,13 @@ def get_data(period):
})
def create_html_template():
"""HTML Template erstellen"""
import os
os.makedirs('templates', exist_ok=True)
html_content = '''<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Wetterstation</title>
<script src="https://code.highcharts.com/highcharts.js"></script>
<script src="https://code.highcharts.com/modules/exporting.js"></script>
<script src="https://code.highcharts.com/modules/export-data.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1400px;
margin: 0 auto;
background: white;
border-radius: 20px;
padding: 30px;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
}
h1 {
text-align: center;
color: #333;
margin-bottom: 30px;
font-size: 2.5em;
}
.tabs {
display: flex;
justify-content: center;
gap: 20px;
margin-bottom: 30px;
}
.tab {
padding: 15px 40px;
background: #f0f0f0;
border: none;
border-radius: 10px;
cursor: pointer;
font-size: 18px;
transition: all 0.3s;
font-weight: 600;
}
.tab:hover {
background: #e0e0e0;
transform: translateY(-2px);
}
.tab.active {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.charts-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 20px;
margin-top: 20px;
}
.chart-container {
background: #f9f9f9;
border-radius: 15px;
padding: 20px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
/* feste Mindesthöhe sorgt für konsistente Layouts */
min-height: 420px;
}
/* Stellt sicher, dass Charts nicht breiter als ihre Container werden */
#charts { width: 100%; }
.chart-container > div { width: 100%; max-width: 100%; }
.loading {
text-align: center;
padding: 50px;
font-size: 20px;
color: #666;
}
@media (max-width: 1024px) {
.charts-grid {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div class="container">
<h1>🌤️ Wetterstation Dashboard</h1>
<div class="tabs">
<button class="tab active" onclick="switchPeriod('day')">Tag (24h)</button>
<button class="tab" onclick="switchPeriod('week')">Woche (7 Tage)</button>
</div>
<div class="loading" id="loading">Lade Daten...</div>
<div class="charts-grid" id="charts" style="display: none;">
<div class="chart-container">
<div id="temp-chart"></div>
</div>
<div class="chart-container">
<div id="humidity-chart"></div>
</div>
<div class="chart-container">
<div id="pressure-chart"></div>
</div>
<div class="chart-container">
<div id="rain-chart"></div>
</div>
<div class="chart-container">
<div id="wind-speed-chart"></div>
</div>
<div class="chart-container">
<div id="wind-dir-chart"></div>
</div>
</div>
</div>
<script>
let currentPeriod = 'day';
const DEFAULT_CHART_HEIGHT = 360;
const POLAR_CHART_HEIGHT = 420;
// HighCharts globale Einstellungen
Highcharts.setOptions({
lang: {
months: ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni',
'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'],
shortMonths: ['Jan', 'Feb', 'Mär', 'Apr', 'Mai', 'Jun',
'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'],
weekdays: ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'],
shortWeekdays: ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa']
}
});
function switchPeriod(period) {
currentPeriod = period;
// Tab-Status aktualisieren
document.querySelectorAll('.tab').forEach(tab => {
tab.classList.remove('active');
});
event.target.classList.add('active');
loadData();
}
function loadData() {
document.getElementById('loading').style.display = 'block';
document.getElementById('charts').style.display = 'none';
fetch(`/api/data/${currentPeriod}`)
.then(response => response.json())
.then(data => {
renderCharts(data);
document.getElementById('loading').style.display = 'none';
document.getElementById('charts').style.display = 'grid';
})
.catch(error => {
console.error('Fehler beim Laden:', error);
document.getElementById('loading').innerHTML = 'Fehler beim Laden der Daten';
});
}
function renderCharts(apiData) {
const data = apiData.data;
const rainData = apiData.rain_hourly;
// Formatiere Timestamps: nur Uhrzeit ohne Datum und Sekunden (HH:MM)
const timestamps = data.map(d => d.datetime.substring(11, 16));
const rainTimestamps = rainData.map(d => d.hour.substring(11, 16));
// Berechne step für x-Achsen Labels: zeige nur alle 30 Minuten eine Zeit
// Bei 48 Labels pro Tag (alle 30 Min) ergibt sich der step aus der Datenmenge
const step = Math.max(1, Math.ceil(data.length / 48));
const rainStep = Math.max(1, Math.ceil(rainData.length / 48));
// Temperatur
Highcharts.chart('temp-chart', {
chart: { type: 'line', height: DEFAULT_CHART_HEIGHT, spacingRight: 20 },
title: { text: '🌡️ Temperatur (°C)' },
xAxis: { categories: timestamps, title: { text: 'Zeit' }, labels: { step: step } },
yAxis: { title: { text: '°C' } },
legend: { enabled: true },
series: [{
name: 'Temperatur',
data: data.map(d => d.temperature),
color: '#ff6b6b',
lineWidth: 2
}],
credits: { enabled: false }
});
// Luftfeuchtigkeit
Highcharts.chart('humidity-chart', {
chart: { type: 'line', height: DEFAULT_CHART_HEIGHT, spacingRight: 20 },
title: { text: '💧 Luftfeuchtigkeit (%)' },
xAxis: { categories: timestamps, title: { text: 'Zeit' }, labels: { step: step } },
yAxis: { title: { text: '%' } },
legend: { enabled: true },
series: [{
name: 'Luftfeuchtigkeit',
data: data.map(d => d.humidity),
color: '#4ecdc4',
lineWidth: 2
}],
credits: { enabled: false }
});
// Luftdruck
Highcharts.chart('pressure-chart', {
chart: { type: 'line', height: DEFAULT_CHART_HEIGHT, spacingRight: 20 },
title: { text: '🎈 Luftdruck (hPa)' },
xAxis: { categories: timestamps, title: { text: 'Zeit' } },
yAxis: { title: { text: 'hPa' } },
legend: { enabled: true },
series: [{
name: 'Luftdruck',
data: data.map(d => d.pressure),
color: '#95e1d3',
lineWidth: 2
}],
credits: { enabled: false }
});
// Regenmenge pro Stunde
Highcharts.chart('rain-chart', {
chart: { type: 'column', height: DEFAULT_CHART_HEIGHT, spacingRight: 20 },
title: { text: '🌧️ Regenmenge pro Stunde (mm)' },
xAxis: { categories: rainTimestamps, title: { text: 'Zeit' } },
yAxis: { title: { text: 'mm' } },
legend: { enabled: false },
series: [{
name: 'Regen',
data: rainData.map(d => d.rain),
color: '#3498db'
}],
credits: { enabled: false }
});
// Windgeschwindigkeit
Highcharts.chart('wind-speed-chart', {
chart: { type: 'line', height: DEFAULT_CHART_HEIGHT, spacingRight: 20 },
title: { text: '💨 Windgeschwindigkeit (m/s)' },
xAxis: { categories: timestamps, title: { text: 'Zeit' } },
yAxis: { title: { text: 'm/s' } },
legend: { enabled: true },
series: [{
name: 'Windgeschwindigkeit',
data: data.map(d => d.wind_speed),
color: '#f38181',
lineWidth: 2
}, {
name: 'Böen',
data: data.map(d => d.wind_gust),
color: '#aa96da',
lineWidth: 2,
dashStyle: 'dash'
}],
credits: { enabled: false }
});
// Windrichtung (Scatter-Plot mit Farbcodierung)
Highcharts.chart('wind-dir-chart', {
chart: { type: 'scatter', height: POLAR_CHART_HEIGHT, spacingRight: 20 },
title: { text: '🧭 Windrichtung' },
xAxis: { title: { text: 'Windrichtung (°)' }, min: 0, max: 360 },
yAxis: { title: { text: 'Geschwindigkeit (m/s)' } },
legend: { enabled: true },
colorAxis: { min: 0, max: Math.max(...data.map(d => d.wind_speed || 0)) },
series: [{
name: 'Wind',
data: data.map(d => ({
x: d.wind_dir || 0,
y: d.wind_speed || 0,
colorValue: d.wind_speed || 0
})),
colorByPoint: true,
marker: { radius: 4 }
}],
credits: { enabled: false }
});
}
// Initiales Laden
loadData();
// Auto-Refresh alle 5 Minuten
setInterval(loadData, 5 * 60 * 1000);
</script>
</body>
</html>'''
with open('templates/index.html', 'w', encoding='utf-8') as f:
f.write(html_content)
print("HTML Template erstellt: templates/index.html")
def main():
"""Hauptprogramm"""
print("Wetterstation wird gestartet...")
# HTML Template erstellen
create_html_template()
# MQTT Client starten
mqtt_client = MQTTClient()
mqtt_client.start()