Tide weiterprogrammiert
This commit is contained in:
parent
7613a5b7cb
commit
9ffb28d65b
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
103
web.py
|
@ -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()
|
||||||
|
|
Loading…
Reference in New Issue