OBP60v/navtex.py

198 lines
6.5 KiB
Python

"""
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]