NAVTEX-Feature hinzugefügt. Daten nur über das Netzwerk
This commit is contained in:
197
navtex.py
Normal file
197
navtex.py
Normal file
@@ -0,0 +1,197 @@
|
||||
"""
|
||||
DWD NAVTEX
|
||||
|
||||
Funkmodul (radio) noch nicht implementiert mangels Hardware
|
||||
|
||||
B1: Transmitter identity
|
||||
|
||||
B2: Subject indicator character
|
||||
|
||||
A Navigational warnings
|
||||
B Meteorological warnings
|
||||
C Ice reports
|
||||
D Search & rescue information, and pirate warnings
|
||||
E Meteorological forecasts
|
||||
F Pilot service messages
|
||||
G AIS messages (formerly Decca messages[6])
|
||||
H LORAN messages
|
||||
I Not used (formerly OMEGA messages[6])
|
||||
J SATNAV messages (i.e. GPS or GLONASS)
|
||||
K Other electronic navaid messages
|
||||
L Navigational warnings — additional to letter A (Should not be rejected by the receiver)
|
||||
T Test transmissions (UK only — not official)
|
||||
V Notice to fishermen (U.S. only — currently not used)
|
||||
W Environmental (U.S. only — currently not used)
|
||||
X Special services — allocation by IMO NAVTEX Panel
|
||||
Y Special services — allocation by IMO NAVTEX Panel
|
||||
Z No message on hand
|
||||
|
||||
B3, B4: Serial number 01-99, 00: immediate printout
|
||||
|
||||
Timecode: DDHHmm UTC MMM YY
|
||||
|
||||
Sqlite database schema
|
||||
.schema message
|
||||
msgid TEXT PRIMARY KEY
|
||||
timestamp TEXT
|
||||
station TEXT
|
||||
content TEXT
|
||||
received TEXT
|
||||
|
||||
"""
|
||||
import os
|
||||
import http.client
|
||||
import ssl
|
||||
import re
|
||||
import sqlite3
|
||||
import datetime
|
||||
from gi.repository import GLib
|
||||
|
||||
class NAVTEX():
|
||||
|
||||
def __init__(self, logger, cfg):
|
||||
self.log = logger
|
||||
self.source = cfg['ntx_source'].lower() # net | radio
|
||||
self.maxage = cfg['ntx_housekeeping'] # message hold time in hours
|
||||
self.running = False
|
||||
dbpath = os.path.join(cfg['histpath'], "navtex.db")
|
||||
try:
|
||||
self.conn = sqlite3.connect(dbpath)
|
||||
self.cur = self.conn.cursor()
|
||||
except:
|
||||
self.log.error(f"Failed to open local database: {dbpath}")
|
||||
return
|
||||
# Datenbank erstellen wenn nicht vorhanden
|
||||
sql = "SELECT name FROM sqlite_master WHERE type='table' AND name='message'"
|
||||
self.cur.execute(sql)
|
||||
if self.cur.fetchone() == None:
|
||||
sql = ("CREATE TABLE IF NOT EXISTS message ("
|
||||
"msgid TEXT PRIMARY KEY NOT NULL,"
|
||||
"station TEXT,"
|
||||
"timestamp TEXT,"
|
||||
"content TEXT NOT NULL,"
|
||||
"received TEXT NOT NULL DEFAULT current_timestamp)"
|
||||
)
|
||||
self.cur.execute(sql)
|
||||
self.log.info(f"Created NAVTEX database: {dbpath}")
|
||||
# Aktualisieren bei Programmstart
|
||||
# TODO Ausgeschaltet für Programmentwicklung
|
||||
if self.source == 'net':
|
||||
self.refresh()
|
||||
# In der Konfiguration werden Minuten angegeben
|
||||
GLib.timeout_add_seconds(cfg['ntx_refresh'] * 60, self.on_timer)
|
||||
self.running = True
|
||||
|
||||
def __del__(self):
|
||||
self.conn.close()
|
||||
|
||||
def on_timer(self):
|
||||
"""
|
||||
NAVTEX data handling
|
||||
"""
|
||||
self.refresh()
|
||||
self.housekeeping()
|
||||
return True
|
||||
|
||||
def parse_message(self, plainmsg):
|
||||
"""
|
||||
Zeile 1: ZCZC<space><BBBB>
|
||||
Zeile 2: Stationskennung
|
||||
Zeile 3: Zeitstempel (meistens)
|
||||
Zeile 4 bis n-1: Nachrichteninhalt
|
||||
Zeile n: NNNN
|
||||
|
||||
Je nach Code B1, B2 kann das folgende Format unterschiedlich sein
|
||||
"""
|
||||
msg = {}
|
||||
data = plainmsg.splitlines()
|
||||
msg['id'] = data[0][5:9]
|
||||
msg['station'] = data[1].strip()
|
||||
timestamp = data[2]
|
||||
if len(timestamp) == 17:
|
||||
day = int(timestamp[0:2])
|
||||
hour = int(timestamp[2:4])
|
||||
minute = int(timestamp[4:6])
|
||||
monmap = ('JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC')
|
||||
month = monmap.index(timestamp[11:14]) + 1
|
||||
year = 2000 + int(timestamp[15:17])
|
||||
try:
|
||||
msg['timestamp'] = datetime.datetime(year, month, day, hour, minute, 0)
|
||||
except:
|
||||
msg['timestamp'] = None
|
||||
else:
|
||||
msg['timestamp'] = None
|
||||
return msg
|
||||
|
||||
def dwd_get_data(self, local=False):
|
||||
"""
|
||||
net: Webseite auslesen
|
||||
https://www.dwd.de/DE/fachnutzer/schifffahrt/funkausstrahlung/navtex
|
||||
"""
|
||||
if local:
|
||||
# Für Tests um nicht permanent die Webseite abzufragen
|
||||
with open("490_emd.html", "r") as fh:
|
||||
content = fh.read()
|
||||
else:
|
||||
ssl_context = ssl.create_default_context()
|
||||
conn = http.client.HTTPSConnection("www.dwd.de", 443, context=ssl_context)
|
||||
url = "https://www.dwd.de/DE/fachnutzer/schifffahrt/funkausstrahlung/navtex/490_emd.html"
|
||||
|
||||
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 []
|
||||
|
||||
expr = re.compile("(ZCZC.*?NNNN)", re.DOTALL)
|
||||
matches = re.findall(expr, content)
|
||||
|
||||
return matches
|
||||
|
||||
def refresh(self):
|
||||
self.log.info("NAVTEX refresh")
|
||||
messages = self.dwd_get_data(False)
|
||||
sql = "INSERT INTO message (msgid, station, content) VALUES (?, ?, ?)"
|
||||
for m in messages:
|
||||
msg = self.parse_message(m)
|
||||
self.cur.execute("SELECT COUNT(*) FROM message WHERE msgid=?", (msg['id'],))
|
||||
result = self.cur.fetchone()
|
||||
if result[0] == 0:
|
||||
self.log.debug(f"NAVTEX: insert new message '{msg['id']}'")
|
||||
self.cur.execute(sql, (msg['id'], msg['station'], m))
|
||||
self.conn.commit()
|
||||
|
||||
def housekeeping(self):
|
||||
self.log.info("NAVTEX housekeeping")
|
||||
sql = "DELETE FROM message WHERE (julianday('now') - julianday(received)) * 24 > ?"
|
||||
self.cur.execute(sql, (self.maxage, ))
|
||||
|
||||
def get_count(self):
|
||||
sql = "SELECT COUNT(*) FROM message"
|
||||
self.cur.execute(sql)
|
||||
result = self.cur.fetchone()
|
||||
return result[0]
|
||||
|
||||
def get_ids(self):
|
||||
sql = "SELECT msgid FROM message"
|
||||
result = self.cur.execute(sql)
|
||||
msgids = []
|
||||
for row in result.fetchall():
|
||||
msgids.append(row[0])
|
||||
return msgids
|
||||
|
||||
def get_message(self, msgid):
|
||||
sql = "SELECT content FROM message WHERE msgid=?"
|
||||
self.cur.execute(sql, (msgid, ))
|
||||
result = self.cur.fetchone()
|
||||
return result[0]
|
||||
|
||||
Reference in New Issue
Block a user