205 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			205 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			Python
		
	
	
	
| """
 | |
| Web Interface
 | |
| 
 | |
| Regelmäßiges Abfragen von Daten aus dem Internet
 | |
|  - NAVTEX
 | |
|     - DWD
 | |
|  - Pegelstände
 | |
|     - pegelonline
 | |
|     - BSH
 | |
|  - Wetterinformationen 
 | |
| 
 | |
| TODO
 | |
|   - Datenbanktabelle um feld bshid erweitern um mehrere Pegel gleichzeitig
 | |
|     verarbeiten zu können
 | |
| 
 | |
| """
 | |
| import os
 | |
| import http.client
 | |
| import ssl
 | |
| import json
 | |
| import sqlite3
 | |
| from datetime import datetime, timedelta
 | |
| from gi.repository import GLib
 | |
| 
 | |
| class WebInterface():
 | |
| 
 | |
|     pegeldata = {
 | |
|        'Amrum': '631P',
 | |
|        'Borkum': '101P',
 | |
|        'Büsum': '505P',
 | |
|        'Cuxhaven': '506P',
 | |
|        'Dagebüll': '635P',
 | |
|        'Dwarsgat': '737P',
 | |
|        'Eider Sperrwerk': '664P',
 | |
|        'Emden': '507P',
 | |
|        'Helgoland': '509A',
 | |
|        'Hörnum, Sylt': '624P',
 | |
|        'Hooksiel': '764B',
 | |
|        'Hooge': '636F',
 | |
|        'Husum': '510P',
 | |
|        'Leer': '806P',
 | |
|        'List, Sylt': '617P',
 | |
|        'Norderney': '111P',
 | |
|        'Papenburg': '814P',
 | |
|        'Scharhörn': '677C',
 | |
|        'Schulau': '714P',
 | |
|        'St. Pauli': '508P',
 | |
|        'Wangerooge': '754P'
 | |
|     }
 | |
| 
 | |
|     def __init__(self, logger, cfg):
 | |
|         self.log = logger
 | |
|         dbpath = os.path.join(cfg['histpath'], "tidedata.db")
 | |
|         try:
 | |
|             self.conn = sqlite3.connect(dbpath)
 | |
|             self.cur = self.conn.cursor()
 | |
|         except:
 | |
|             self.log.error(f"Failed to open tide database: {dbpath}")
 | |
|             return
 | |
|         # Datenbank erstellen wenn nicht vorhanden
 | |
|         sql = "SELECT name FROM sqlite_master WHERE type='table' AND name='station'"
 | |
|         self.cur.execute(sql)
 | |
|         if self.cur.fetchone() == None:
 | |
|             sql = ("CREATE TABLE IF NOT EXISTS station ("
 | |
|                    "uuid TEXT PRIMARY KEY NOT NULL,"
 | |
|                    "name TEXT)"
 | |
|                   )
 | |
|             self.cur.execute(sql)
 | |
|             sql = ("CREATE TABLE IF NOT EXISTS data ("
 | |
|                    "timestamp TEXT PRIMARY KEY NOT NULL,"
 | |
|                    "astro NUMBER NOT NULL,"
 | |
|                    "forecast NUMBER,"
 | |
|                    "measurement NUMBER)"
 | |
|                   )
 | |
|             self.cur.execute(sql)
 | |
|             self.log.info(f"Created tide database: {dbpath}")
 | |
|         else:
 | |
|             # TODO Alte Daten aus vorhandener Datenbank bereinigen
 | |
|             ts = datetime.now().replace(minute=0, second=0, microsecond=0) - timedelta(days=2)
 | |
|             sql = "DELETE FROM data WHERE timestamp < ?"
 | |
|             self.cur.execute(sql, (ts,))
 | |
|             self.conn.commit()
 | |
|             if self.cur.rowcount > 0:
 | |
|                 print("Tide: {} old data ponts have been deleted".format(self.cur.rowcount))
 | |
| 
 | |
|         self.pegelid = 'Schulau'
 | |
|         self.bshid = self.pegeldata[self.pegelid]
 | |
| 
 | |
|         # Tidendaten (Rahmen)
 | |
|         self.tide = {
 | |
|             'station': None,
 | |
|             'area': None,
 | |
|             'MHW': None,
 | |
|             'MNW': None,
 | |
|             'warning': None,
 | |
|             'forecast_ts': None
 | |
|         }
 | |
| 
 | |
|         # In der Konfiguration werden Minuten angegeben
 | |
|         #GLib.timeout_add_seconds(cfg['tide_refresh'] * 60, self.on_timer)
 | |
|         GLib.timeout_add_seconds(60 * 60, self.on_timer)
 | |
|         self.running = True
 | |
|         self.log.info(f"Web interface started")
 | |
| 
 | |
|     def __del__(self):
 | |
|         self.conn.close()
 | |
| 
 | |
|     def on_timer(self):
 | |
|         """
 | |
|         Data handling
 | |
|         """
 | |
|         self.refresh()
 | |
|         self.housekeeping()
 | |
|         return True
 | |
| 
 | |
|     def bsh_get_data(self, local=False):
 | |
|         """
 | |
|         Webseite auslesen
 | |
|         """
 | |
|         if local:
 | |
|             # Für Tests um nicht permanent die Webseite abzufragen
 | |
|             with open(f"/tmp/DE__{self.bshid}.json", "r") as fh:
 | |
|                 content = fh.read()
 | |
|         else:
 | |
|             ssl_context = ssl.create_default_context()
 | |
|             conn = http.client.HTTPSConnection("wasserstand-nordsee.bsh.de", 443, context=ssl_context)
 | |
|             url = f"https://wasserstand-nordsee.bsh.de/data/DE__{self.bshid}.json"
 | |
| 
 | |
|             try:
 | |
|                 conn.request("GET", url)
 | |
|                 response = conn.getresponse()
 | |
|                 if response.status == 200:
 | |
|                     content = response.read().decode()
 | |
|                 else:
 | |
|                     print(f"Error: {response.status}")
 | |
|                     return []
 | |
|             except http.client.HTTPException as e:
 | |
|                 self.log.warning(f"HTTP error occurred: {e}")
 | |
|                 return []
 | |
|             except ssl.SSLError as ssl_error:
 | |
|                 self.log.warning(f"SSL error occurred: {ssl_error}")
 | |
|                 return []
 | |
|             finally:
 | |
|                 conn.close()
 | |
|         return content
 | |
| 
 | |
|     def refresh(self):
 | |
|         self.log.info("Data refresh")
 | |
|         data = self.bsh_get_data(False)
 | |
|         tmpfile = open(f"/tmp/DE__{self.bshid}.json", "w")
 | |
|         tmpfile.write(data)
 | |
|         tmpfile.close()
 | |
|         wvdata = json.loads(data)
 | |
|         sql = "INSERT OR REPLACE INTO data (timestamp, astro, forecast, measurement) VALUES (?, ?, ?, ?)"
 | |
|         for wv in wvdata["curve_forecast"]["data"]:
 | |
|             self.cur.execute(sql, (wv['timestamp'], wv['astro'], wv['curveforecast'], wv['measurement']))
 | |
|         self.conn.commit()
 | |
|  
 | |
|         self.tide['station'] = wvdata["station_name"]
 | |
|         self.tide['area'] = wvdata["area"]
 | |
|         self.tide['MHW'] = wvdata["MHW"] # Mittleres Hochwasser
 | |
|         self.tide['MNW'] = wvdata["MNW"] # Mittleres Niedrigwasser
 | |
|         self.tide['warning'] = wvdata["warning"]
 | |
|         self.tide['forecast_ts'] = wvdata["creation_forecast"]
 | |
| 
 | |
|         # Zeitpunkte und Höhen der beiden nächsten Hoch- und Niedrigwasser
 | |
|         # wvdata["hwnwforecast"]["data"]["event"] HW / NW
 | |
|         # wvdata["hwnwforecast"]["data"]["timestamp"] 
 | |
|         # wvdata["hwnwforecast"]["data"]["value"]
 | |
|  
 | |
|     def housekeeping(self):
 | |
|         self.log.info("Housekeeping")
 | |
| 
 | |
|     def set_pegel(self, pegelid):
 | |
|         self.pegelid = pegelid
 | |
|         self.bshid = self.pegeldata[self.pegelid]
 | |
| 
 | |
|     def set_timestamp(self, vorlauf, dauer):
 | |
|         """
 | |
|         Manuell setzen, damit verschiedene Aufrufe nacheinander auch
 | |
|         auf den selben Zeitstempel zurückgreifen
 | |
|         """
 | |
|         self.current_ts = datetime.now().replace(minute=0, second=0, microsecond=0)
 | |
|         self.start_ts = self.current_ts - timedelta(hours=vorlauf)
 | |
|         self.end_ts = self.current_ts + timedelta(hours=dauer)
 | |
| 
 | |
|     # TODO
 | |
|     # Stationsdaten
 | |
|     # Berechnungszeitpunkt
 | |
|     # Warnhinweise (z.B. Sturmflut)
 | |
| 
 | |
|     def get_tide(self):
 | |
|         # 12 Stunden Vorhersage ab der aktuelle Stunde
 | |
|         # Vorlauf sind 4 Stunden
 | |
|         # TODO Aufbereiten, daß nur eine einfache Liste zurückgeliefert wird
 | |
|         sql = "select forecast from data where timestamp > ? and timestamp < ?"
 | |
|         self.cur.execute(sql, (self.start_ts, self.end_ts))
 | |
|         return [x[0] for x in self.cur.fetchall()]
 | |
| 
 | |
|     def get_tide_minmax(self):
 | |
|         # Liefert ein Tupel mit min, max zurück
 | |
|         sql = "select min(forecast), max(forecast) from data where timestamp > ? and timestamp < ?"
 | |
|         self.cur.execute(sql, (self.start_ts, self.end_ts))
 | |
|         return self.cur.fetchone()
 |