Compare commits

2 Commits

Author SHA1 Message Date
rxf
0285fde580 Zeiten besser dargestellt
Werte in europäisches System umgerechnet
2026-01-28 14:18:00 +00:00
rxf
511cc31dc0 Einlesen und Anzeigen getrennt
Einlesen per HTPP (nicht mehr MQTT)
2026-01-27 12:52:54 +00:00
7 changed files with 321 additions and 138 deletions

View File

@@ -1,26 +1,26 @@
# Wetterstation Docker Setup # Wetterstation Docker Setup
## Architektur
Die Wetterstation besteht aus zwei unabhängigen Services:
1. **Ingestion Service** (Port 5004): Empfängt POST-Daten und schreibt in die Datenbank
2. **Web Service** (Port 5003): Stellt das Web-Interface und Lese-APIs bereit
Beide Services teilen sich eine gemeinsame SQLite-Datenbank via Volume.
## Voraussetzungen ## Voraussetzungen
- Docker und Docker Compose installiert - Docker und Docker Compose installiert
- MQTT Broker Zugang (Host, Port, Benutzername, Passwort)
## Installation ## Installation
### 1. `.env` Datei erstellen ### 1. `.env` Datei erstellen (optional)
Kopiere `.env.example` zu `.env` und fülle deine Daten ein: Erstelle eine `.env` Datei für benutzerdefinierte Konfiguration:
```bash ```bash
cp .env.example .env
```
Bearbeite `.env` mit deinen MQTT-Credentials:
```
MQTT_HOST=dein_broker.com
MQTT_PORT=1883
MQTT_TOPIC=vantage/live
MQTT_USER=dein_benutzer
MQTT_PASSWORD=dein_passwort
DB_FILE=wetterdaten.db DB_FILE=wetterdaten.db
HTTP_PORT=5003
INGESTION_PORT=5004
``` ```
### 2. Container starten ### 2. Container starten
@@ -28,34 +28,102 @@ DB_FILE=wetterdaten.db
docker-compose up -d docker-compose up -d
``` ```
Die Anwendung läuft dann unter `http://localhost:5003` Die Services laufen dann unter:
- Web-Interface: `http://localhost:5003`
- Ingestion API: `http://localhost:5004/api/data/upload`
### 3. Container verwalten ### 3. Container verwalten
```bash ```bash
# Logs anschauen # Logs anschauen (beide Services)
docker-compose logs -f docker-compose logs -f
# Logs nur Ingestion Service
docker-compose logs -f wetterstation-ingestion
# Logs nur Web Service
docker-compose logs -f wetterstation-web
# Container stoppen # Container stoppen
docker-compose down docker-compose down
# Container neustarten # Container neustarten
docker-compose restart docker-compose restart
# Nur Ingestion Service neustarten
docker-compose restart wetterstation-ingestion
``` ```
## Datenverwaltung ## Datenverwaltung
Die SQLite-Datenbank (`wetterdaten.db`) wird als Volume persistiert und bleibt erhalten, auch wenn der Container gelöscht wird. Die SQLite-Datenbank (`wetterdaten.db`) wird als Volume persistiert und bleibt erhalten, auch wenn die Container gelöscht werden.
## Services im Detail
### Ingestion Service
- **Port**: 5004
- **Endpoints**:
- `POST /api/data/upload` - Empfängt Wetterdaten
- `GET /health` - Health-Check
- **Zweck**: Schreibt Daten in die Datenbank
### Web Service
- **Port**: 5003
- **Endpoints**:
- `GET /` - Web-Interface
- `GET /api/data/day` - Daten der letzten 24h
- `GET /api/data/week` - Daten der letzten Woche
- `GET /health` - Health-Check
- **Zweck**: Visualisierung und Datenabruf
## Externe Zugriffe
### Daten senden (von außen)
```bash
curl -X POST http://your-server-ip:5004/api/data/upload \
-H 'Content-Type: application/json' \
-d '{
"dateTime": "2026-01-27 12:00:00",
"barometer": 1013.2,
"outTemp": 5.6,
"outHumidity": 72,
"windSpeed": 3.2,
"windDir": 180,
"windGust": 5.0,
"rainRate": 0.0,
"rain": 0.0
}'
```
### Web-Interface aufrufen
Öffne im Browser: `http://your-server-ip:5003`
## Troubleshooting ## Troubleshooting
### Datenbank-Fehler ### Datenbank-Fehler
Falls die Datenbank beschädigt ist, kannst du sie löschen und neu erstellen: Falls die Datenbank beschädigt ist:
```bash ```bash
rm wetterdaten.db rm wetterdaten.db
docker-compose restart docker-compose restart wetterstation-ingestion
``` ```
### MQTT-Verbindungsfehler ### Container neu bauen
Überprüfe deine `.env` Datei auf korrekte Credentials: Nach Code-Änderungen:
```bash ```bash
docker-compose logs wetterstation | grep -i mqtt docker-compose build --no-cache
docker-compose up -d
``` ```
### Port-Konflikte
Falls Ports bereits belegt sind, passe die Ports in `docker-compose.yml` an:
```yaml
ports:
- "NEUER_PORT:5003" # für Web Service
- "NEUER_PORT:5004" # für Ingestion Service
```
### Health-Checks
Überprüfe, ob Services laufen:
```bash
curl http://localhost:5003/health
curl http://localhost:5004/health
```

18
Dockerfile.ingestion Normal file
View File

@@ -0,0 +1,18 @@
# Multi-stage build: Leichtgewichtiger Container für Ingestion Service
FROM python:3.13-slim
# Setze Arbeitsverzeichnis
WORKDIR /app
# Installiere Dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Kopiere die Anwendung
COPY wetterstation_ingestion.py .
# Exponiere Port
EXPOSE 5004
# Starten Sie die Anwendung
CMD ["python", "wetterstation_ingestion.py"]

View File

@@ -1,7 +1,29 @@
services: services:
wetterstation: # Ingestion Service - empfängt POST-Daten
build: . wetterstation-ingestion:
container_name: wetterstation build:
context: .
dockerfile: Dockerfile.ingestion
container_name: wetterstation-ingestion
ports:
- "5004:5004"
volumes:
- ./wetterdaten.db:/app/wetterdaten.db
env_file:
- .env
environment:
- FLASK_ENV=production
- INGESTION_PORT=5004
restart: unless-stopped
networks:
- wetterstation_network
# Web Service - stellt UI und Lese-APIs bereit
wetterstation-web:
build:
context: .
dockerfile: Dockerfile
container_name: wetterstation-web
ports: ports:
- "5003:5003" - "5003:5003"
volumes: volumes:
@@ -13,6 +35,8 @@ services:
restart: unless-stopped restart: unless-stopped
networks: networks:
- wetterstation_network - wetterstation_network
depends_on:
- wetterstation-ingestion
networks: networks:
wetterstation_network: wetterstation_network:

View File

@@ -1,3 +1,2 @@
flask==3.0.0 flask==3.0.0
paho-mqtt==1.6.1
python-dotenv==1.0.0 python-dotenv==1.0.0

View File

@@ -48,15 +48,8 @@ function renderCharts(apiData) {
const rainData = apiData.rain_hourly; const rainData = apiData.rain_hourly;
// Konvertiere Timestamps in Millisekunden // Konvertiere Timestamps in Millisekunden
const timestamps = data.map(d => { const timestamps = data.map(d => d.dateTime)
const [date, time] = d.datetime.split(' '); const rainTimestamps = rainData.map(d => new Date(d.hour).getTime())
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 // Berechne Zeitbereich für die Achsen
const minTime = Math.min(...timestamps, ...rainTimestamps); const minTime = Math.min(...timestamps, ...rainTimestamps);
@@ -66,7 +59,7 @@ function renderCharts(apiData) {
const ONE_HOUR = 3600000; const ONE_HOUR = 3600000;
const FOUR_HOURS = ONE_HOUR * 4; const FOUR_HOURS = ONE_HOUR * 4;
// Temperatur // Temperatur (Fahrenheit -> Celsius umrechnen)
Highcharts.chart('temp-chart', { Highcharts.chart('temp-chart', {
chart: { type: 'line', height: DEFAULT_CHART_HEIGHT, spacingRight: 20 }, chart: { type: 'line', height: DEFAULT_CHART_HEIGHT, spacingRight: 20 },
title: { text: '🌡️ Temperatur (°C)' }, title: { text: '🌡️ Temperatur (°C)' },
@@ -74,13 +67,14 @@ function renderCharts(apiData) {
type: 'datetime', type: 'datetime',
title: { text: 'Zeit' }, title: { text: 'Zeit' },
labels: { format: '{value:%H}' }, labels: { format: '{value:%H}' },
tickInterval: FOUR_HOURS tickInterval: FOUR_HOURS,
gridLineWidth: 1
}, },
yAxis: { title: { text: '°C' } }, yAxis: { title: { text: '°C' } },
legend: { enabled: true }, legend: { enabled: true },
series: [{ series: [{
name: 'Temperatur', name: 'Temperatur',
data: data.map((d, i) => [timestamps[i], d.temperature]), data: data.map((d, i) => [d.dateTime*1000, (d.outTemp - 32) * 5/9]),
color: '#ff6b6b', color: '#ff6b6b',
lineWidth: 2 lineWidth: 2
}], }],
@@ -95,20 +89,21 @@ function renderCharts(apiData) {
type: 'datetime', type: 'datetime',
title: { text: 'Zeit' }, title: { text: 'Zeit' },
labels: { format: '{value:%H}' }, labels: { format: '{value:%H}' },
tickInterval: FOUR_HOURS tickInterval: FOUR_HOURS,
gridLineWidth: 1
}, },
yAxis: { title: { text: '%' } }, yAxis: { title: { text: '%' } },
legend: { enabled: true }, legend: { enabled: true },
series: [{ series: [{
name: 'Luftfeuchtigkeit', name: 'Luftfeuchtigkeit',
data: data.map((d, i) => [timestamps[i], d.humidity]), data: data.map((d, i) => [d.dateTime*1000, d.outHumidity]),
color: '#4ecdc4', color: '#4ecdc4',
lineWidth: 2 lineWidth: 2
}], }],
credits: { enabled: false } credits: { enabled: false }
}); });
// Luftdruck // Luftdruck (inHg -> hPa umrechnen)
Highcharts.chart('pressure-chart', { Highcharts.chart('pressure-chart', {
chart: { type: 'line', height: DEFAULT_CHART_HEIGHT, spacingRight: 20 }, chart: { type: 'line', height: DEFAULT_CHART_HEIGHT, spacingRight: 20 },
title: { text: '🎈 Luftdruck (hPa)' }, title: { text: '🎈 Luftdruck (hPa)' },
@@ -116,13 +111,14 @@ function renderCharts(apiData) {
type: 'datetime', type: 'datetime',
title: { text: 'Zeit' }, title: { text: 'Zeit' },
labels: { format: '{value:%H}' }, labels: { format: '{value:%H}' },
tickInterval: FOUR_HOURS tickInterval: FOUR_HOURS,
gridLineWidth: 1
}, },
yAxis: { title: { text: 'hPa' } }, yAxis: { title: { text: 'hPa' } },
legend: { enabled: true }, legend: { enabled: true },
series: [{ series: [{
name: 'Luftdruck', name: 'Luftdruck',
data: data.map((d, i) => [timestamps[i], d.pressure]), data: data.map((d, i) => [d.dateTime*1000, d.barometer * 33.8639]),
color: '#95e1d3', color: '#95e1d3',
lineWidth: 2 lineWidth: 2
}], }],
@@ -137,67 +133,86 @@ function renderCharts(apiData) {
type: 'datetime', type: 'datetime',
title: { text: 'Zeit' }, title: { text: 'Zeit' },
labels: { format: '{value:%H}' }, labels: { format: '{value:%H}' },
tickInterval: FOUR_HOURS tickInterval: FOUR_HOURS,
gridLineWidth: 1
}, },
yAxis: { title: { text: 'mm' } }, yAxis: { title: { text: 'mm' } },
legend: { enabled: false }, legend: { enabled: false },
series: [{ series: [{
name: 'Regen', name: 'Regen',
data: rainData.map((d, i) => [rainTimestamps[i], d.rain]), data: rainData.map((d, i) => [d.dateTime*1000, d.rain]),
color: '#3498db' color: '#3498db'
}], }],
credits: { enabled: false } credits: { enabled: false }
}); });
// Windgeschwindigkeit // Windgeschwindigkeit (mph -> km/h umrechnen)
Highcharts.chart('wind-speed-chart', { Highcharts.chart('wind-speed-chart', {
chart: { type: 'line', height: DEFAULT_CHART_HEIGHT, spacingRight: 20 }, chart: { type: 'line', height: DEFAULT_CHART_HEIGHT, spacingRight: 20 },
title: { text: '💨 Windgeschwindigkeit (m/s)' }, title: { text: '💨 Windgeschwindigkeit (km/h)' },
xAxis: { xAxis: {
type: 'datetime', type: 'datetime',
title: { text: 'Zeit' }, title: { text: 'Zeit' },
labels: { format: '{value:%H}' }, labels: { format: '{value:%H}' },
tickInterval: FOUR_HOURS tickInterval: FOUR_HOURS,
gridLineWidth: 1
}, },
yAxis: { title: { text: 'm/s' } }, yAxis: { title: { text: 'km/h' } },
legend: { enabled: true }, legend: { enabled: true },
series: [{ series: [{
name: 'Windgeschwindigkeit', name: 'Windgeschwindigkeit',
data: data.map((d, i) => [timestamps[i], d.wind_speed]), data: data.map((d, i) => [d.dateTime*1000, d.windSpeed * 1.60934]),
color: '#f38181', color: 'blue',
lineWidth: 2 lineWidth: 2
}, { }, {
name: 'Böen', name: 'Böen',
data: data.map((d, i) => [timestamps[i], d.wind_gust]), data: data.map((d, i) => [d.dateTime*1000, d.windGust * 1.60934]),
color: '#aa96da', color: 'red',
lineWidth: 2, lineWidth: 2,
dashStyle: 'dash'
}], }],
credits: { enabled: false } credits: { enabled: false }
}); });
// Windrichtung // Windrichtung
Highcharts.chart('wind-dir-chart', { Highcharts.chart('wind-dir-chart', {
chart: { type: 'line', height: DEFAULT_CHART_HEIGHT, spacingRight: 20 }, chart: { type: 'scatter', height: DEFAULT_CHART_HEIGHT, spacingRight: 20 },
title: { text: '🧭 Windrichtung (°)' }, title: { text: '🧭 Windrichtung (°)' },
xAxis: { xAxis: {
type: 'datetime', type: 'datetime',
title: { text: 'Zeit' }, title: { text: 'Zeit' },
labels: { format: '{value:%H}' }, labels: { format: '{value:%H}' },
tickInterval: FOUR_HOURS tickInterval: FOUR_HOURS,
gridLineWidth: 1
}, },
yAxis: { yAxis: {
title: { text: 'Richtung (°)' }, title: { text: 'Richtung (°)' },
min: 0, min: 0,
max: 360, max: 360,
tickPositions: [0, 90, 180, 270, 360] // tickPositions: [0, 90, 180, 270, 360]
tickInterval: 90,
labels: {
formatter: function() {
// Windrichtungen zuordnen
const directions = {
0: 'Nord',
90: 'Ost',
180: 'Süd',
270: 'West',
360: 'Nord'
};
return directions[this.value] || this.value + '°';
}
}
}, },
legend: { enabled: true }, legend: { enabled: true },
series: [{ series: [{
name: 'Windrichtung', name: 'Windrichtung',
data: data.map((d, i) => [timestamps[i], d.wind_dir || 0]), data: data.map((d, i) => [d.dateTime*1000, d.windDir || 0]),
color: '#f39c12', color: '#f39c12',
lineWidth: 2 marker: {
radius: 2
}
// lineWidth: 2
}], }],
credits: { enabled: false } credits: { enabled: false }
}); });

View File

@@ -1,13 +1,14 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """
Wetterstation - HTTP-POST Datenempfang und Web-Visualisierung Wetterstation Web-Interface - Visualisierung und API
Stellt das Web-Interface und Lese-APIs für historische Daten bereit
""" """
import sqlite3 import sqlite3
import json import json
import os import os
from datetime import datetime, timedelta from datetime import datetime, timedelta
from flask import Flask, render_template, jsonify, request from flask import Flask, render_template, jsonify
from dotenv import load_dotenv from dotenv import load_dotenv
# Lade Umgebungsvariablen aus .env Datei # Lade Umgebungsvariablen aus .env Datei
@@ -21,58 +22,10 @@ app = Flask(__name__)
class WetterDB: class WetterDB:
"""Klasse für Datenbankoperationen""" """Klasse für Datenbankoperationen (nur Lesezugriff)"""
def __init__(self, db_file): def __init__(self, db_file):
self.db_file = db_file self.db_file = db_file
self.init_db()
def init_db(self):
"""Datenbank initialisieren"""
conn = sqlite3.connect(self.db_file)
cursor = conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS wetterdaten (
id INTEGER PRIMARY KEY AUTOINCREMENT,
dateTime TEXT NOT NULL,
barometer REAL,
outTemp REAL,
outHumidity INTEGER,
windSpeed REAL,
windDir REAL,
windGust REAL,
rainRate REAL,
rain REAL
)
''')
cursor.execute('''
CREATE INDEX IF NOT EXISTS idx_dateTime ON wetterdaten(dateTime)
''')
conn.commit()
conn.close()
def save_data(self, data):
"""Wetterdaten speichern"""
conn = sqlite3.connect(self.db_file)
cursor = conn.cursor()
cursor.execute('''
INSERT INTO wetterdaten
(dateTime, barometer, outTemp, outHumidity, windSpeed, windDir, windGust, rainRate, rain)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
''', (
data.get('dateTime'),
data.get('barometer'),
data.get('outTemp'),
data.get('outHumidity'),
data.get('windSpeed'),
data.get('windDir'),
data.get('windGust'),
data.get('rainRate'),
data.get('rain')
))
conn.commit()
conn.close()
print(f"Daten gespeichert: {data.get('dateTime')}")
def get_data(self, hours=24): def get_data(self, hours=24):
"""Daten der letzten X Stunden abrufen""" """Daten der letzten X Stunden abrufen"""
@@ -80,7 +33,7 @@ class WetterDB:
conn.row_factory = sqlite3.Row conn.row_factory = sqlite3.Row
cursor = conn.cursor() cursor = conn.cursor()
time_threshold = (datetime.now() - timedelta(hours=hours)).strftime('%Y-%m-%d %H:%M:%S') time_threshold = int((datetime.now() - timedelta(hours=hours)).timestamp())
cursor.execute(''' cursor.execute('''
SELECT * FROM wetterdaten SELECT * FROM wetterdaten
@@ -98,11 +51,11 @@ class WetterDB:
conn = sqlite3.connect(self.db_file) conn = sqlite3.connect(self.db_file)
cursor = conn.cursor() cursor = conn.cursor()
time_threshold = (datetime.now() - timedelta(hours=hours)).strftime('%Y-%m-%d %H:%M:%S') time_threshold = int((datetime.now() - timedelta(hours=hours)).timestamp())
cursor.execute(''' cursor.execute('''
SELECT SELECT
strftime('%Y-%m-%d %H:00:00', dateTime) as hour, strftime('%Y-%m-%d %H:00:00', datetime(dateTime, 'unixepoch', 'localtime')) as hour,
SUM(rainRate) as total_rain SUM(rainRate) as total_rain
FROM wetterdaten FROM wetterdaten
WHERE dateTime >= ? WHERE dateTime >= ?
@@ -127,29 +80,10 @@ def index():
return render_template('index.html') return render_template('index.html')
@app.route('/api/data/upload', methods=['POST']) @app.route('/health')
def upload_data(): def health():
"""HTTP-POST Endpoint für Wetterdaten""" """Health-Check Endpoint"""
try: return jsonify({'status': 'ok', 'service': 'web'}), 200
data = request.get_json()
if not data:
return jsonify({'error': 'Keine Daten empfangen'}), 400
# Daten speichern (unverändert)
db.save_data(data)
return jsonify({
'status': 'success',
'message': 'Daten empfangen und gespeichert'
}), 200
except Exception as e:
print(f"Fehler beim Verarbeiten der POST-Anfrage: {e}")
return jsonify({'error': str(e)}), 400
@app.route('/api/data/<period>') @app.route('/api/data/<period>')
@@ -167,9 +101,12 @@ def get_historical_data(period):
def main(): def main():
"""Hauptprogramm""" """Hauptprogramm"""
print("Wetterstation wird gestartet...") print("Wetterstation Web-Interface wird gestartet...")
print("\nWeb-Interface verfügbar unter: http://localhost:5003") print(f"\nWeb-Interface verfügbar unter: http://0.0.0.0:{HTTP_PORT}")
print("HTTP-POST Endpoint: http://localhost:5003/api/data/upload") print(f"API Endpoints:")
print(f" - http://0.0.0.0:{HTTP_PORT}/api/data/day")
print(f" - http://0.0.0.0:{HTTP_PORT}/api/data/week")
print(f"Health-Check: http://0.0.0.0:{HTTP_PORT}/health")
print("Drücke CTRL+C zum Beenden\n") print("Drücke CTRL+C zum Beenden\n")
app.run(host='0.0.0.0', port=HTTP_PORT, debug=False) app.run(host='0.0.0.0', port=HTTP_PORT, debug=False)

122
wetterstation_ingestion.py Normal file
View File

@@ -0,0 +1,122 @@
#!/usr/bin/env python3
"""
Wetterstation Ingestion Service - HTTP-POST Datenempfang
Empfängt Wetterdaten via POST und speichert sie in der Datenbank
"""
import sqlite3
import os
from flask import Flask, jsonify, request
from dotenv import load_dotenv
# Lade Umgebungsvariablen aus .env Datei
load_dotenv()
# Konfiguration aus Umgebungsvariablen
DB_FILE = os.getenv("DB_FILE", "wetterdaten.db")
HTTP_PORT = int(os.getenv("INGESTION_PORT", 5004))
app = Flask(__name__)
app.url_map.strict_slashes = False
class WetterDB:
"""Klasse für Datenbankoperationen"""
def __init__(self, db_file):
self.db_file = db_file
self.init_db()
def init_db(self):
"""Datenbank initialisieren"""
conn = sqlite3.connect(self.db_file)
cursor = conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS wetterdaten (
id INTEGER PRIMARY KEY AUTOINCREMENT,
dateTime INTEGER NOT NULL,
barometer REAL,
outTemp REAL,
outHumidity INTEGER,
windSpeed REAL,
windDir REAL,
windGust REAL,
rainRate REAL,
rain REAL
)
''')
cursor.execute('''
CREATE INDEX IF NOT EXISTS idx_dateTime ON wetterdaten(dateTime)
''')
conn.commit()
conn.close()
def save_data(self, data):
"""Wetterdaten speichern"""
conn = sqlite3.connect(self.db_file)
cursor = conn.cursor()
cursor.execute('''
INSERT INTO wetterdaten
(dateTime, barometer, outTemp, outHumidity, windSpeed, windDir, windGust, rainRate, rain)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
''', (
data.get('dateTime'),
data.get('barometer'),
data.get('outTemp'),
data.get('outHumidity'),
data.get('windSpeed'),
data.get('windDir'),
data.get('windGust'),
data.get('rainRate'),
data.get('rain')
))
conn.commit()
conn.close()
print(f"Daten gespeichert: {data.get('dateTime')}")
# Globale Datenbankinstanz
db = WetterDB(DB_FILE)
# Flask Routes
@app.route('/health')
def health():
"""Health-Check Endpoint"""
return jsonify({'status': 'ok', 'service': 'ingestion'}), 200
# @app.route('/api/data/upload', methods=['POST'])
@app.route('/api/data/upload/', methods=['POST'])
def upload_data():
"""HTTP-POST Endpoint für Wetterdaten"""
try:
data = request.get_json()
if not data:
return jsonify({'error': 'Keine Daten empfangen'}), 400
# Daten speichern
db.save_data(data)
return jsonify({
'status': 'success',
'message': 'Daten empfangen und gespeichert'
}), 200
except Exception as e:
print(f"Fehler beim Verarbeiten der POST-Anfrage: {e}")
return jsonify({'error': str(e)}), 400
def main():
"""Hauptprogramm"""
print("Wetterstation Ingestion Service wird gestartet...")
print(f"\nHTTP-POST Endpoint: http://0.0.0.0:{HTTP_PORT}/api/data/upload")
print(f"Health-Check: http://0.0.0.0:{HTTP_PORT}/health")
print("Drücke CTRL+C zum Beenden\n")
app.run(host='0.0.0.0', port=HTTP_PORT, debug=False)
if __name__ == '__main__':
main()