erster test mit http

This commit is contained in:
rxf
2026-01-26 16:38:50 +01:00
parent c6c3f4cf37
commit c3614ebab0

View File

@@ -1,28 +1,21 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """
Wetterstation - MQTT Datenempfang und Web-Visualisierung Wetterstation - HTTP-POST Datenempfang und Web-Visualisierung
""" """
import sqlite3 import sqlite3
import json import json
import threading
import os import os
from datetime import datetime, timedelta from datetime import datetime, timedelta
from flask import Flask, render_template, jsonify from flask import Flask, render_template, jsonify, request
import paho.mqtt.client as mqtt
from dotenv import load_dotenv from dotenv import load_dotenv
# Lade Umgebungsvariablen aus .env Datei # Lade Umgebungsvariablen aus .env Datei
load_dotenv() load_dotenv()
# Konfiguration aus Umgebungsvariablen # Konfiguration aus Umgebungsvariablen
MQTT_HOST = os.getenv("MQTT_HOST", "rexfue.de")
MQTT_PORT = int(os.getenv("MQTT_PORT", 1883))
MQTT_TOPIC = os.getenv("MQTT_TOPIC", "vantage/live")
MQTT_USER = os.getenv("MQTT_USER", "")
MQTT_PASSWORD = os.getenv("MQTT_PASSWORD", "")
DB_FILE = os.getenv("DB_FILE", "wetterdaten.db") DB_FILE = os.getenv("DB_FILE", "wetterdaten.db")
HTTP_PORT = int(os.getenv("HTTP_PORT", 5003))
app = Flask(__name__) app = Flask(__name__)
@@ -41,19 +34,19 @@ class WetterDB:
cursor.execute(''' cursor.execute('''
CREATE TABLE IF NOT EXISTS wetterdaten ( CREATE TABLE IF NOT EXISTS wetterdaten (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
datetime TEXT NOT NULL, dateTime TEXT NOT NULL,
pressure REAL, barometer REAL,
wind_gust REAL, outTemp REAL,
wind_speed REAL, outHumidity INTEGER,
wind_dir REAL, windSpeed REAL,
rain_rate REAL, windDir REAL,
rain REAL, windGust REAL,
humidity INTEGER, rainRate REAL,
temperature REAL rain REAL
) )
''') ''')
cursor.execute(''' cursor.execute('''
CREATE INDEX IF NOT EXISTS idx_datetime ON wetterdaten(datetime) CREATE INDEX IF NOT EXISTS idx_dateTime ON wetterdaten(dateTime)
''') ''')
conn.commit() conn.commit()
conn.close() conn.close()
@@ -64,23 +57,22 @@ class WetterDB:
cursor = conn.cursor() cursor = conn.cursor()
cursor.execute(''' cursor.execute('''
INSERT INTO wetterdaten INSERT INTO wetterdaten
(datetime, pressure, wind_gust, wind_speed, wind_dir, (dateTime, barometer, outTemp, outHumidity, windSpeed, windDir, windGust, rainRate, rain)
rain_rate, rain, humidity, temperature)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
''', ( ''', (
data['datetime'], data.get('dateTime'),
data.get('pressure'), data.get('barometer'),
data.get('wind_gust'), data.get('outTemp'),
data.get('wind_speed'), data.get('outHumidity'),
data.get('wind_dir'), data.get('windSpeed'),
data.get('rain_rate'), data.get('windDir'),
data.get('rain'), data.get('windGust'),
data.get('humidity'), data.get('rainRate'),
data.get('temperature') data.get('rain')
)) ))
conn.commit() conn.commit()
conn.close() conn.close()
print(f"Daten gespeichert: {data['datetime']}") 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"""
@@ -92,8 +84,8 @@ class WetterDB:
cursor.execute(''' cursor.execute('''
SELECT * FROM wetterdaten SELECT * FROM wetterdaten
WHERE datetime >= ? WHERE dateTime >= ?
ORDER BY datetime ASC ORDER BY dateTime ASC
''', (time_threshold,)) ''', (time_threshold,))
rows = cursor.fetchall() rows = cursor.fetchall()
@@ -110,10 +102,10 @@ class WetterDB:
cursor.execute(''' cursor.execute('''
SELECT SELECT
strftime('%Y-%m-%d %H:00:00', datetime) as hour, strftime('%Y-%m-%d %H:00:00', dateTime) as hour,
SUM(rain_rate) as total_rain SUM(rainRate) as total_rain
FROM wetterdaten FROM wetterdaten
WHERE datetime >= ? WHERE dateTime >= ?
GROUP BY hour GROUP BY hour
ORDER BY hour ASC ORDER BY hour ASC
''', (time_threshold,)) ''', (time_threshold,))
@@ -128,104 +120,6 @@ class WetterDB:
db = WetterDB(DB_FILE) db = WetterDB(DB_FILE)
class MQTTClient:
"""MQTT Client für Datenempfang"""
def __init__(self):
self.client = mqtt.Client()
self.client.username_pw_set(MQTT_USER, MQTT_PASSWORD)
# Stabilere Verbindungen bei Abbrüchen
try:
self.client.reconnect_delay_set(min_delay=1, max_delay=120)
except Exception:
pass
# Optionale Protokollierung (hilfreich beim Debuggen)
try:
self.client.enable_logger()
except Exception:
pass
self.client.on_connect = self.on_connect
self.client.on_message = self.on_message
@staticmethod
def _sanitize_data(payload: dict) -> dict:
"""Payload robust in erwartetes Format wandeln.
- Fehlende `datetime` wird mit aktueller Zeit ergänzt
- Felder in richtige Typen konvertieren
"""
def to_float(x):
try:
return float(x) if x is not None else None
except Exception:
return None
def to_int(x):
try:
return int(x) if x is not None else None
except Exception:
return None
# Zeitstempel: wenn vorhanden, in akzeptables Format bringen, sonst jetzt
dt = payload.get('datetime')
if isinstance(dt, (int, float)):
# Epoch -> ISO
dt = datetime.fromtimestamp(dt).strftime('%Y-%m-%d %H:%M:%S')
elif isinstance(dt, str):
# Versuchen, ISO-Varianten in 'YYYY-MM-DD HH:MM:SS' zu überführen
# Entferne ggf. 'T' oder 'Z'
dt_clean = dt.replace('T', ' ').replace('Z', '').strip()
# Falls Millisekunden enthalten, abschneiden
if '.' in dt_clean:
dt_clean = dt_clean.split('.')[0]
# Bei zu kurzem String: fallback auf jetzt
if len(dt_clean) < 16:
dt = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
else:
dt = dt_clean
else:
dt = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
return {
'datetime': dt,
'pressure': to_float(payload.get('pressure')),
'wind_gust': to_float(payload.get('wind_gust')),
'wind_speed': to_float(payload.get('wind_speed')),
'wind_dir': to_float(payload.get('wind_dir')),
'rain_rate': to_float(payload.get('rain_rate')),
'rain': to_float(payload.get('rain')),
'humidity': to_int(payload.get('humidity')),
'temperature': to_float(payload.get('temperature')),
}
def on_connect(self, client, userdata, flags, rc):
"""Callback bei Verbindung"""
if rc == 0:
print(f"Mit MQTT Broker verbunden: {MQTT_HOST}")
client.subscribe(MQTT_TOPIC)
print(f"Topic abonniert: {MQTT_TOPIC}")
else:
print(f"Verbindungsfehler: {rc}")
def on_message(self, client, userdata, msg):
"""Callback bei empfangener Nachricht"""
try:
raw = json.loads(msg.payload.decode())
data = self._sanitize_data(raw)
print(f"Empfangen und gespeichert: {data}")
db.save_data(data)
except Exception as e:
print(f"Fehler beim Verarbeiten der Nachricht: {e}")
def start(self):
"""MQTT Client starten"""
try:
self.client.connect(MQTT_HOST, MQTT_PORT, 60)
self.client.loop_start()
print("MQTT Client gestartet")
except Exception as e:
print(f"MQTT Verbindungsfehler: {e}")
# Flask Routes # Flask Routes
@app.route('/') @app.route('/')
def index(): def index():
@@ -233,9 +127,34 @@ def index():
return render_template('index.html') return render_template('index.html')
@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 (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>')
def get_data(period): def get_historical_data(period):
"""API Endpoint für Wetterdaten""" """API Endpoint für historische Wetterdaten"""
hours = 24 if period == 'day' else 168 # 168h = 1 Woche hours = 24 if period == 'day' else 168 # 168h = 1 Woche
data = db.get_data(hours) data = db.get_data(hours)
rain_data = db.get_hourly_rain(hours) rain_data = db.get_hourly_rain(hours)
@@ -246,21 +165,13 @@ def get_data(period):
}) })
def main(): def main():
"""Hauptprogramm""" """Hauptprogramm"""
print("Wetterstation wird gestartet...") print("Wetterstation wird gestartet...")
# MQTT Client starten
mqtt_client = MQTTClient()
mqtt_client.start()
# Flask Server starten
print("\nWeb-Interface verfügbar unter: http://localhost:5003") print("\nWeb-Interface verfügbar unter: http://localhost:5003")
print("HTTP-POST Endpoint: http://localhost:5003/api/data/upload")
print("Drücke CTRL+C zum Beenden\n") print("Drücke CTRL+C zum Beenden\n")
app.run(host='0.0.0.0', port=5003, debug=False) app.run(host='0.0.0.0', port=HTTP_PORT, debug=False)
if __name__ == '__main__': if __name__ == '__main__':