Compare commits
1 Commits
c6c3f4cf37
...
http_post
| Author | SHA1 | Date | |
|---|---|---|---|
| c3614ebab0 |
205
wetterstation.py
205
wetterstation.py
@@ -1,28 +1,21 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Wetterstation - MQTT Datenempfang und Web-Visualisierung
|
||||
Wetterstation - HTTP-POST Datenempfang und Web-Visualisierung
|
||||
"""
|
||||
|
||||
import sqlite3
|
||||
import json
|
||||
import threading
|
||||
import os
|
||||
from datetime import datetime, timedelta
|
||||
from flask import Flask, render_template, jsonify
|
||||
import paho.mqtt.client as mqtt
|
||||
from flask import Flask, render_template, jsonify, request
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Lade Umgebungsvariablen aus .env Datei
|
||||
load_dotenv()
|
||||
|
||||
# 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")
|
||||
HTTP_PORT = int(os.getenv("HTTP_PORT", 5003))
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
@@ -41,19 +34,19 @@ class WetterDB:
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS wetterdaten (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
datetime TEXT NOT NULL,
|
||||
pressure REAL,
|
||||
wind_gust REAL,
|
||||
wind_speed REAL,
|
||||
wind_dir REAL,
|
||||
rain_rate REAL,
|
||||
rain REAL,
|
||||
humidity INTEGER,
|
||||
temperature REAL
|
||||
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)
|
||||
CREATE INDEX IF NOT EXISTS idx_dateTime ON wetterdaten(dateTime)
|
||||
''')
|
||||
conn.commit()
|
||||
conn.close()
|
||||
@@ -64,23 +57,22 @@ class WetterDB:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('''
|
||||
INSERT INTO wetterdaten
|
||||
(datetime, pressure, wind_gust, wind_speed, wind_dir,
|
||||
rain_rate, rain, humidity, temperature)
|
||||
(dateTime, barometer, outTemp, outHumidity, windSpeed, windDir, windGust, rainRate, rain)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
''', (
|
||||
data['datetime'],
|
||||
data.get('pressure'),
|
||||
data.get('wind_gust'),
|
||||
data.get('wind_speed'),
|
||||
data.get('wind_dir'),
|
||||
data.get('rain_rate'),
|
||||
data.get('rain'),
|
||||
data.get('humidity'),
|
||||
data.get('temperature')
|
||||
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['datetime']}")
|
||||
print(f"Daten gespeichert: {data.get('dateTime')}")
|
||||
|
||||
def get_data(self, hours=24):
|
||||
"""Daten der letzten X Stunden abrufen"""
|
||||
@@ -92,8 +84,8 @@ class WetterDB:
|
||||
|
||||
cursor.execute('''
|
||||
SELECT * FROM wetterdaten
|
||||
WHERE datetime >= ?
|
||||
ORDER BY datetime ASC
|
||||
WHERE dateTime >= ?
|
||||
ORDER BY dateTime ASC
|
||||
''', (time_threshold,))
|
||||
|
||||
rows = cursor.fetchall()
|
||||
@@ -110,10 +102,10 @@ class WetterDB:
|
||||
|
||||
cursor.execute('''
|
||||
SELECT
|
||||
strftime('%Y-%m-%d %H:00:00', datetime) as hour,
|
||||
SUM(rain_rate) as total_rain
|
||||
strftime('%Y-%m-%d %H:00:00', dateTime) as hour,
|
||||
SUM(rainRate) as total_rain
|
||||
FROM wetterdaten
|
||||
WHERE datetime >= ?
|
||||
WHERE dateTime >= ?
|
||||
GROUP BY hour
|
||||
ORDER BY hour ASC
|
||||
''', (time_threshold,))
|
||||
@@ -128,104 +120,6 @@ class WetterDB:
|
||||
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
|
||||
@app.route('/')
|
||||
def index():
|
||||
@@ -233,9 +127,34 @@ def index():
|
||||
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>')
|
||||
def get_data(period):
|
||||
"""API Endpoint für Wetterdaten"""
|
||||
def get_historical_data(period):
|
||||
"""API Endpoint für historische Wetterdaten"""
|
||||
hours = 24 if period == 'day' else 168 # 168h = 1 Woche
|
||||
data = db.get_data(hours)
|
||||
rain_data = db.get_hourly_rain(hours)
|
||||
@@ -246,21 +165,13 @@ def get_data(period):
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def main():
|
||||
"""Hauptprogramm"""
|
||||
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("HTTP-POST Endpoint: http://localhost:5003/api/data/upload")
|
||||
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__':
|
||||
|
||||
Reference in New Issue
Block a user