Tide weiterprogrammiert

This commit is contained in:
Thomas Hooge 2025-10-09 20:12:54 +02:00
parent 7613a5b7cb
commit 9ffb28d65b
4 changed files with 159 additions and 35 deletions

View File

@ -19,7 +19,6 @@ class AppData():
self.navtex = None self.navtex = None
if cfg['tide']: if cfg['tide']:
self.web = WebInterface(logger, cfg) self.web = WebInterface(logger, cfg)
self.web.refresh()
else: else:
self.web = None self.web = None
self.track = Tracker(logger, cfg) self.track = Tracker(logger, cfg)

View File

@ -66,6 +66,7 @@ name = My boat
sailno = GER 815 sailno = GER 815
class = One off class = One off
handicap = 100.0 handicap = 100.0
draft = 1.90
club = NONE club = NONE
team = OBP team = OBP

View File

@ -6,6 +6,7 @@ Tide Vorausschau
import cairo import cairo
from .page import Page from .page import Page
from datetime import datetime
class Tide(Page): class Tide(Page):
@ -13,6 +14,8 @@ class Tide(Page):
super().__init__(pageno, cfg, appdata, boatdata) super().__init__(pageno, cfg, appdata, boatdata)
self.station = "f3c6ee73-5561-4068-96ec-364016e7d9ef" # Schulau self.station = "f3c6ee73-5561-4068-96ec-364016e7d9ef" # Schulau
self.app.web.set_pegel('List, Sylt')
self.app.web.refresh()
def draw(self, ctx): def draw(self, ctx):
# Title # Title
@ -20,23 +23,70 @@ class Tide(Page):
ctx.set_font_size(24) ctx.set_font_size(24)
ctx.move_to(8, 40) ctx.move_to(8, 40)
ctx.show_text("Tide prediction") ctx.show_text("Tide prediction")
ctx.set_font_size(10)
ctx.set_font_size(16) # Daten holen
ctx.set_line_width(2) self.app.web.set_timestamp(2, 48)
ymin, ymax = self.app.web.get_tide_minmax()
rawdata = self.app.web.get_tide()
ctx.move_to(220, 40) # scale_y_step
ctx.show_text("Schulau, Elbe") scale_y_step = 50
ymin = min(ymin, self.app.web.tide['MNW']) - 10
ymin = (ymin // scale_y_step - 1) * scale_y_step
ymax = max(ymax, self.app.web.tide['MHW']) + 10
ymax = (ymax // scale_y_step + 1) * scale_y_step
#self.tide['warning'] = wvdata["warning"]
#self.tide['forecast_ts'] = wvdata["creation_forecast"]
x0 = 40 # links unten x0 = 40 # links unten
y0 = 250 y0 = 250
x1 = 380 # rechts oben x1 = 380 # rechts oben
y1 = 60 y1 = 60
ctx.set_line_width(1)
ctx.set_dash((2, 2), 0)
y = (self.app.web.tide['MNW'] - ymin) / (ymax - ymin) * (y0 - y1)
ctx.move_to(x0 - 8, y0 - y)
ctx.line_to(x1 + 8, y0 - y)
y = (self.app.web.tide['MHW'] - ymin) / (ymax - ymin) * (y0 - y1)
ctx.move_to(x0 - 8, y0 - y)
ctx.line_to(x1 + 8, y0 - y)
ctx.stroke()
ctx.set_dash([])
ctx.set_font_size(16)
ctx.set_line_width(2)
ctx.move_to(220, 40)
self.draw_text_ralign(ctx, 392, 36, self.app.web.tide['station'])
ctx.move_to(8, 50)
calc_ts = datetime.fromisoformat(self.app.web.tide['forecast_ts'])
self.draw_text_ralign(ctx, 392, 52, "calc: " + calc_ts.strftime('%H:%M'))
#self.tide['area'] = wvdata["area"]
# X-Achse # X-Achse
ctx.move_to(x0 + 0.5, y0 + 0.5) ctx.move_to(x0 + 0.5, y0 + 0.5)
ctx.line_to(x0 + 0.5, y1 + 0.5) ctx.line_to(x0 + 0.5, y1 + 0.5)
ctx.stroke()
# Pfeilspitze Y
ctx.move_to(x1 + 0.5, y0 + 0.5 - 4)
ctx.line_to(x1 + 0.5 + 12, y0 + 0.5)
ctx.line_to(x1 + 0.5, y0 + 0.5 + 4)
ctx.close_path()
ctx.fill()
# Pfeilspitze Y
ctx.move_to(x0 + 0.5 - 4, y1 + 0.5)
ctx.line_to(x0 + 0.5, y1 + 0.5 - 12)
ctx.line_to(x0 + 0.5 + 4, y1 + 0.5)
ctx.close_path()
ctx.fill()
# self.draw_text_center(ctx, x0 - 20, y0 + (y1 -y0) / 2, "Höhe, cm", rotate=True) # self.draw_text_center(ctx, x0 - 20, y0 + (y1 -y0) / 2, "Höhe, cm", rotate=True)
self.draw_text_center(ctx, 60, 150, "Höhe, cm", rotate=True) self.draw_text_center(ctx, 60, 150, "height, cm", rotate=True)
#self.draw_text_center(ctx, 100, 100, "Höhe, cm", rotate=False) #self.draw_text_center(ctx, 100, 100, "Höhe, cm", rotate=False)
#ctx.move_to(90, 90) # Rotationsursprung #ctx.move_to(90, 90) # Rotationsursprung
#ctx.line_to(110, 110) #ctx.line_to(110, 110)
@ -47,31 +97,30 @@ class Tide(Page):
# Y-Achse # Y-Achse
ctx.move_to(x0 + 0.5, y0 + 0.5) ctx.move_to(x0 + 0.5, y0 + 0.5)
ctx.line_to(x1 + 0.5, y0 + 0.5) ctx.line_to(x1 + 0.5, y0 + 0.5)
self.draw_text_center(ctx, x0 + (x1 - x0) / 2, y0 + 12, "Zeit, h") self.draw_text_center(ctx, x0 + (x1 - x0) / 2, y0 + 12, "time, h")
ctx.stroke() ctx.stroke()
ymin, ymax = self.app.web.get_tide_minmax()
rawdata = self.app.web.get_tide()
ctx.move_to(x0 - 32, y0 + 8) ctx.move_to(x0 - 32, y0 + 8)
ctx.show_text(str(ymin)) ctx.show_text(str(ymin))
ctx.move_to(x0 - 32, y1 + 8) ctx.move_to(x0 - 32, y1 + 8)
ctx.show_text(str(ymax)) ctx.show_text(str(ymax))
ctx.move_to(x0 + 16, y0 + 16) # Anzahl Meßwerte für die X-Achse
ctx.show_text(f"n = {len(rawdata)}") #ctx.move_to(x0 + 16, y0 + 16)
#ctx.show_text(f"n = {len(rawdata)}")
dx = (x1 - x0) / len(rawdata) dx = (x1 - x0) / len(rawdata)
x = x0 x = x0
while rawdata[0][0] is None: prev_valid = False
x += dx
del rawdata[0]
y = (rawdata[0][0] - ymin) / (ymax - ymin) * (y0 - y1)
ctx.move_to(x, y0 - (rawdata[0][0] - ymin))
del rawdata[0]
x += dx
for val in rawdata: for val in rawdata:
y = (val[0] - ymin) / (ymax - ymin) * (y0 - y1) if val is not None:
y = (val - ymin) / (ymax - ymin) * (y0 - y1)
if prev_valid:
ctx.line_to(x, y0 - y) ctx.line_to(x, y0 - y)
else:
ctx.move_to(x, y0 - y)
prev_valid = True
else:
prev_valid = False
x += dx x += dx

103
web.py
View File

@ -6,20 +6,48 @@ Regelmäßiges Abfragen von Daten aus dem Internet
- DWD - DWD
- Pegelstände - Pegelstände
- pegelonline - pegelonline
- WSV - BSH
- Wetterinformationen - Wetterinformationen
TODO
- Datenbanktabelle um feld bshid erweitern um mehrere Pegel gleichzeitig
verarbeiten zu können
""" """
import os import os
import http.client import http.client
import ssl import ssl
import json import json
import sqlite3 import sqlite3
import datetime from datetime import datetime, timedelta
from gi.repository import GLib from gi.repository import GLib
class WebInterface(): 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): def __init__(self, logger, cfg):
self.log = logger self.log = logger
dbpath = os.path.join(cfg['histpath'], "tidedata.db") dbpath = os.path.join(cfg['histpath'], "tidedata.db")
@ -47,6 +75,19 @@ class WebInterface():
self.cur.execute(sql) self.cur.execute(sql)
self.log.info(f"Created tide database: {dbpath}") self.log.info(f"Created tide database: {dbpath}")
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 # In der Konfiguration werden Minuten angegeben
#GLib.timeout_add_seconds(cfg['tide_refresh'] * 60, self.on_timer) #GLib.timeout_add_seconds(cfg['tide_refresh'] * 60, self.on_timer)
GLib.timeout_add_seconds(60 * 60, self.on_timer) GLib.timeout_add_seconds(60 * 60, self.on_timer)
@ -70,12 +111,12 @@ class WebInterface():
""" """
if local: if local:
# Für Tests um nicht permanent die Webseite abzufragen # Für Tests um nicht permanent die Webseite abzufragen
with open("/tmp/DE__714P.json", "r") as fh: with open(f"/tmp/DE__{self.bshid}.json", "r") as fh:
content = fh.read() content = fh.read()
else: else:
ssl_context = ssl.create_default_context() ssl_context = ssl.create_default_context()
conn = http.client.HTTPSConnection("wasserstand-nordsee.bsh.de", 443, context=ssl_context) conn = http.client.HTTPSConnection("wasserstand-nordsee.bsh.de", 443, context=ssl_context)
url = "https://wasserstand-nordsee.bsh.de/data/DE__714P.json" url = f"https://wasserstand-nordsee.bsh.de/data/DE__{self.bshid}.json"
try: try:
conn.request("GET", url) conn.request("GET", url)
@ -91,31 +132,65 @@ class WebInterface():
except ssl.SSLError as ssl_error: except ssl.SSLError as ssl_error:
self.log.warning(f"SSL error occurred: {ssl_error}") self.log.warning(f"SSL error occurred: {ssl_error}")
return [] return []
finally:
conn.close()
return content return content
def refresh(self): def refresh(self):
self.log.info("Data refresh") self.log.info("Data refresh")
data = self.bsh_get_data(False) data = self.bsh_get_data(False)
#tmpfile = open("/tmp/DE__714P.json", "w") tmpfile = open(f"/tmp/DE__{self.bshid}.json", "w")
#tmpfile.write(data) tmpfile.write(data)
#tmpfile.close() tmpfile.close()
wvdata = json.loads(data) wvdata = json.loads(data)
sql = "INSERT OR REPLACE INTO data (timestamp, astro, forecast, measurement) VALUES (?, ?, ?, ?)" sql = "INSERT OR REPLACE INTO data (timestamp, astro, forecast, measurement) VALUES (?, ?, ?, ?)"
for wv in wvdata["curve_forecast"]["data"]: for wv in wvdata["curve_forecast"]["data"]:
self.cur.execute(sql, (wv['timestamp'], wv['astro'], wv['curveforecast'], wv['measurement'])) self.cur.execute(sql, (wv['timestamp'], wv['astro'], wv['curveforecast'], wv['measurement']))
self.conn.commit() 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): def housekeeping(self):
self.log.info("Housekeeping") 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): def get_tide(self):
# 12 Stunden # 12 Stunden Vorhersage ab der aktuelle Stunde
sql = "select forecast from data where timestamp > '2025-10-05 12:00' and timestamp < '2025-10-06 22:00'" # Vorlauf sind 4 Stunden
self.cur.execute(sql) # TODO Aufbereiten, daß nur eine einfache Liste zurückgeliefert wird
return self.cur.fetchall() 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): def get_tide_minmax(self):
sql = "select min(forecast), max(forecast) from data where timestamp > '2025-10-05 12:00' and timestamp < '2025-10-06 22:00'" # Liefert ein Tupel mit min, max zurück
self.cur.execute(sql) 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() return self.cur.fetchone()