202 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			202 lines
		
	
	
		
			6.7 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 
 | |
| 
 | |
| Für Konfiguration: subjects = ABCDEFGHIJKLTVWXYZ
 | |
| 
 | |
| 
 | |
| 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
 | |
| 
 | |
| siehe auch: https://www.icselectronics.co.uk/support/info/navtexdb
 | |
| """
 | |
| 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]
 | |
| 
 |