diff --git a/appdata.py b/appdata.py new file mode 100644 index 0000000..404c207 --- /dev/null +++ b/appdata.py @@ -0,0 +1,10 @@ +""" +Generische Applikationsdaten + +""" +from tracker import Tracker + +class AppData(): + def __init__(self): + self.shutdown = False # Globaler Ausschalter + self.track = Tracker('NONE') diff --git a/obp60v.py b/obp60v.py index 7dfa928..1f311b0 100755 --- a/obp60v.py +++ b/obp60v.py @@ -104,12 +104,14 @@ import time from datetime import datetime from nmea2000 import Device, BoatData, History, HistoryBuffer from nmea2000 import parser -import nmea0183 -import pages import struct import uuid import json +import nmea0183 +from appdata import AppData +import pages + __author__ = "Thomas Hooge" __copyright__ = "Copyleft 2024-2025, all rights reversed" __version__ = "0.2" @@ -118,6 +120,8 @@ __status__ = "Development" cfg = { 'cfgfile': 'obp60v.conf', + 'logdir': '~/.local/share/obp60v', + 'logfile': 'obp60v.log', 'imgpath': os.path.join(sys.path[0], 'images'), 'deviceid': 100, 'manufcode': 2046, # Open Boat Projects (OBP) @@ -130,136 +134,13 @@ cfg = { 'boat': { } } -def mqtt_on_connect(client, userdata, flags, rc): - print(f"MQTT connected with result code {rc}") - #userdata['connect_rc'] = rc - if rc != 0: - # Result codes: - # 1: Connection Refused, unacceptable protocol version - # 2: Connection Refused, identifier rejected - # 3: Connection Refused, Server unavailable - # 4: Connection Refused, bad user name or password - # 5: Connection Refused, not authorized - #userdata['connect_ok'] = True - pass - else: - client.subscribe("regattahero/orgstatus/thomas") - client.subscribe("regattahero/racestatus/thomas/#") - #userdata['connect_ok'] = False - -def mqtt_on_message(client, userdata, msg): - """ - TODO raceid über userdata? dann topic prüfen? - """ - if msg.topic == "regattahero/orgstatus/thomas": - # kommt alle 10s - orgstatus = json.loads(msg.payload) - if orgstatus['allLogout']: - print("All logout received!") - client.disconnect() - sys.exit(0) # TODO nur die MQTT-Task beenden - if orgstatus['message']: - # TODO Alarm-Funktion nutzen? - print("Nachricht der Wettfahrtkeitung:") - print(orgstatus['message']) - print(orgstatus['races']) - #for r in orgstatus['races']: - # print(f"Race: {r}") - elif msg.topic.startswith("regattahero/racestatus/thomas"): - # kommt alle 1s - # dem Topic angehängt ist noch die raceid - payload = json.loads(msg.payload) - racestatus = payload['racestatus'] - """ - time: negativ: Zeit vor dem Start, positiv: Zeit nach dem Start - in Sekunden - - Signale der Wettfahrtleitung hier anzeigen - Regattaabbruch - Bahnverkürzung - Rückrufe - - """ - else: - print(f"UNKNOWN TOPIC: {msg.topic}") - print(msg.payload) - -def mqtt_publish(client, topic, payload, bv_lat, bv_lon, bv_sog): - """ - Payload vorbelegt als Template, so daß nur noch die veränderlichen - GPS-Daten eingefügt werden müssen: LAT LON SOG TIMESTAMP - """ - lat = bv_lat.getValueRaw() - lon = bv_lon.getValueRaw() - sog = bv_sog.getValueRaw() - if lat and lon and sog: - payload['gps']['lat'] = round(lat, 5) - payload['gps']['lon'] = round(lon, 5) - payload['gps']['speed'] = sog - payload['gps']['timestamp'] = time.strftime("%Y-%m-%dT%H:%M:%S.000Z", time.gmtime()) - - client.publish(topic, json.dumps(payload)) - else: - print("No GPS data available. Nothing published!") - -def mqtt_tracker(cfg, boat): - import paho.mqtt.client as mqtt - print("MQTT tracker enabled") - client = mqtt.Client() - client.on_connect = mqtt_on_connect - client.on_message = mqtt_on_message - client.username_pw_set(username=cfg['mqtt_user'], password=cfg['mqtt_pass']) - try: - client.connect(cfg['host'], cfg['port'], 60) - except ConnectionRefusedError: - print("MQTT connection refused. Check username and password.") - return - - topic = "regattahero/tracker/" + cfg['orgname'] - payload = { - "passcode": cfg['passcode'], - "orgid": cfg['orgname'], - "raceid": "Demo Regatta", # TODO aus Selektion einstellen - "gps": { - "lat": 0.0, - "lon": 0.0, - "speed": 0.0, - "age": 1000, - "odo": 1000, - "bat": 1.0, - "timestamp": "" # ISO8601 Format mit Millisekunden in UTC - }, - "boat": { - "boatid": cfg['uuid'], - "sailno": boat['sailno'], - "team": boat['team'], - "boatclass": boat['class'], - "handicap": boat['handicap'], - "club": boat['club'], - "boatname": boat['name'] - } - } - - # Zugriff auf Boatdata: Referenzen für leichten schnellen Zugriff - bv_lat = boatdata.getRef("LAT") - bv_lon = boatdata.getRef("LON") - bv_sog = boatdata.getRef("SOG") - - client.loop_start() - while not shutdown: - time.sleep(1) - if tracker_active: - mqtt_publish(client, topic, payload, bv_lat, bv_lon, bv_sog) - client.loop_stop() - client.disconnect() - def rxd_n2k(device): setthreadtitle("N2Klistener") bus = can.Bus(interface='socketcan', channel=device, bitrate=250000); wip = False sc = 0 nf = 0 - while not shutdown: + while not appdata.shutdown: msg = bus.recv(2) if not msg: continue @@ -332,7 +213,7 @@ def rxd_0183(devname): print("NMEA0183 serial port not available") return setthreadtitle("0183listener") - while not shutdown: + while not appdata.shutdown: raw = ser.readline().decode('ascii') if len(raw.strip()) == 0: continue @@ -372,7 +253,7 @@ def rxd_gps(devname, devspeed): print("GPS serial port not available") return setthreadtitle("GPSlistener") - while not shutdown: + while not appdata.shutdown: try: msg = pynmea2.parse(ser.readline().decode('ascii')) except pynmea2.nmea.ParseError: @@ -394,7 +275,7 @@ def rxd_network(address, port): # Wir verwenden UDP. Ein verlorenes Paket tut uns nicht weh. sock = socket.socket() sock.connect((address, port)) - while not shutdown: + while not appdata.shutdown: time.sleep(0.5) sock.close() @@ -433,7 +314,7 @@ def datareader(cfg, history): g = g_tick(1) n = 0 - while not shutdown: + while not appdata.shutdown: time.sleep(next(g)) # BME280 abfragen if cfg['bme280']: @@ -454,8 +335,9 @@ def datareader(cfg, history): class Frontend(Gtk.Window): - def __init__(self, cfg, device, boatdata, profile): + def __init__(self, cfg, appdata, device, boatdata, profile): super().__init__() + self.appdata = appdata self.owndev = device self.boatdata = boatdata self._config = cfg['_config'] @@ -740,10 +622,10 @@ def init_profile(config, cfg, boatdata): # Nummer und Art ermitteln pageno = int(s[4:]) pagedef[pageno] = {'type': config.get(s, "type")} - # Hole ein bin maximal 4 Werte je Seite + # Hole ein bin maximal 6 Werte je Seite values = {} valno = 1 - for i in (1, 2, 3, 4): + for i in (1, 2, 3, 4, 5, 6): try: values[i] = config.get(s, f"value{i}") except configparser.NoOptionError: @@ -762,10 +644,10 @@ def init_profile(config, cfg, boatdata): # Klasse nicht vorhanden, Seite wird nicht benutzt print(f"Klasse '{p['type']}' nicht gefunden") continue - c = cls(i, cfg, boatdata, *[v for v in p['values'].values()]) + c = cls(i, cfg, appdata, boatdata, *[v for v in p['values'].values()]) clist[i] = c return clist - + def set_loglevel(nr): """ Umsetzen der Nummer auf einen für "logging" passenden Wert @@ -779,11 +661,11 @@ def set_loglevel(nr): nr = 0 return level[nr] -def init_logging(logdir): +def init_logging(logdir, logfile='obp60v.log'): global log os.makedirs(logdir, exist_ok=True) log = logging.getLogger(os.path.basename(sys.argv[0])) - hdlr = logging.handlers.RotatingFileHandler(os.path.join(logdir, 'obp60v.log'), maxBytes=5242880, backupCount=5) + hdlr = logging.handlers.RotatingFileHandler(os.path.join(logdir, logfile), maxBytes=5242880, backupCount=5) formatter = logging.Formatter('%(asctime)s %(name)s %(levelname)s: %(message)s', datefmt='%Y-%m-%d %H:%M:%S') hdlr.setFormatter(formatter) log.addHandler(hdlr) @@ -797,8 +679,8 @@ if __name__ == "__main__": setproctitle("obp60v") - shutdown = False - tracker_active = False + # Globale Daten, u.a. auch Shutdown-Indikator + appdata = AppData() owndevice = Device(100) # Hardcoding device, not intended to change @@ -867,6 +749,7 @@ if __name__ == "__main__": cfg['tracker']['mqtt_pass'] = config.get('tracker', 'mqtt_pass') cfg['tracker']['orgname'] = config.get('tracker', 'orgname') cfg['tracker']['passcode'] = config.get('tracker', 'passcode') + cfg['tracker']['logdir'] = cfg['logdir'] # Boat data cfg['boat']['name'] = config.get('boat', 'name') @@ -892,7 +775,7 @@ if __name__ == "__main__": boatdata.enableSimulation() # Protokollierung - init_logging(os.path.expanduser("~/.local/share/obp60v")) + init_logging(os.path.expanduser(cfg['logdir']), cfg['logfile']) log.info("Logging initialized") # Gerät initialisieren u.a. mit den genutzten Seiten @@ -916,7 +799,7 @@ if __name__ == "__main__": t_rxd_net = threading.Thread(target=rxd_network, args=(cfg['net_port'],cfg['net_addr'])) t_rxd_net.start() if cfg['tracker']['type'] != 'NONE': - t_tracker = threading.Thread(target=mqtt_tracker, args=(cfg['tracker'],cfg['boat'])) + t_tracker = threading.Thread(target=appdata.track.mqtt_tracker, args=(cfg['tracker'],cfg['boat'],appdata,boatdata)) t_tracker.start() if not cfg['simulation']: if cfg['bme280']: @@ -925,9 +808,9 @@ if __name__ == "__main__": else: print("Simulation mode enabled") - app = Frontend(cfg, owndevice, boatdata, profile) + app = Frontend(cfg, appdata, owndevice, boatdata, profile) app.run() - shutdown = True + appdata.shutdown = True if cfg['can']: t_rxd_n2k.join() if cfg['nmea0183']: @@ -936,8 +819,10 @@ if __name__ == "__main__": t_rxd_gps.join() if cfg['network']: t_rxd_net.join() + if cfg['tracker']['type'] != 'NONE': + t_tracker.join() if not cfg['simulation'] and cfg['bme280']: t_data.join() - print("Another fine product of the Sirius Cybernetics Corporation.") print(boatdata) + print("Another fine product of the Sirius Cybernetics Corporation.") diff --git a/pages/anchor.py b/pages/anchor.py index 467ff2f..a2553f4 100644 --- a/pages/anchor.py +++ b/pages/anchor.py @@ -36,7 +36,7 @@ from .page import Page class Anchor(Page): - def __init__(self, pageno, cfg, boatdata): + def __init__(self, pageno, cfg, appdata, boatdata): super().__init__(pageno, cfg, boatdata) self.sym_anchor = cairo.ImageSurface.create_from_png(os.path.join(cfg['imgpath'], "anchor.png")) self.buttonlabel[1] = 'MODE' diff --git a/pages/apparentwind.py b/pages/apparentwind.py index 05a0dbb..3b98b72 100644 --- a/pages/apparentwind.py +++ b/pages/apparentwind.py @@ -5,7 +5,7 @@ from .page import Page class ApparentWind(Page): - def __init__(self, pageno, cfg, boatdata): + def __init__(self, pageno, cfg, appdata, boatdata): super().__init__(pageno, cfg, boatdata) self.buttonlabel[1] = 'MODE' self.mode = 'L' # (W)ind (L)ens diff --git a/pages/autobahn.py b/pages/autobahn.py index a144946..1fdcfe2 100644 --- a/pages/autobahn.py +++ b/pages/autobahn.py @@ -11,7 +11,7 @@ from .page import Page class Autobahn(Page): - def __init__(self, pageno, cfg, boatdata): + def __init__(self, pageno, cfg, appdata, boatdata): super().__init__(pageno, cfg, boatdata) self.xte = self.bd.getRef("XTE") self.cog = self.bd.getRef("COG") diff --git a/pages/barograph.py b/pages/barograph.py index e0e5f52..135a8c4 100644 --- a/pages/barograph.py +++ b/pages/barograph.py @@ -40,7 +40,7 @@ from .page import Page class Barograph(Page): - def __init__(self, pageno, cfg, boatdata): + def __init__(self, pageno, cfg, appdata, boatdata): super().__init__(pageno, cfg, boatdata) # Meßwert alle 15 Minuten: # 84 Stunden * 4 Werte je Stunde = 336 Meßwerte diff --git a/pages/battery.py b/pages/battery.py index 75df190..3ea6440 100644 --- a/pages/battery.py +++ b/pages/battery.py @@ -12,7 +12,7 @@ class Battery(Page): avg = (1, 10, 60, 300); - def __init__(self, pageno, cfg, boatdata): + def __init__(self, pageno, cfg, appdata, boatdata): super().__init__(pageno, cfg, boatdata) self.avgindex = 0 self.buttonlabel[1] = 'AVG' diff --git a/pages/bme280.py b/pages/bme280.py index 79a3a94..7149904 100644 --- a/pages/bme280.py +++ b/pages/bme280.py @@ -9,7 +9,7 @@ from .page import Page class BME280(Page): - def __init__(self, pageno, cfg, boatdata): + def __init__(self, pageno, cfg, appdata, boatdata): super().__init__(pageno, cfg, boatdata) #self.ref1 = self.bd.getRef(boatvalue1) #self.ref2 = self.bd.getRef(boatvalue2) diff --git a/pages/clock.py b/pages/clock.py index 341dd81..613cf2e 100644 --- a/pages/clock.py +++ b/pages/clock.py @@ -20,7 +20,7 @@ import astral class Clock(Page): - def __init__(self, pageno, cfg, boatdata): + def __init__(self, pageno, cfg, appdata, boatdata): super().__init__(pageno, cfg, boatdata) self.buttonlabel[1] = 'MODE' self.buttonlabel[2] = 'TZ' diff --git a/pages/fluid.py b/pages/fluid.py index 5cdcd6e..78fefad 100644 --- a/pages/fluid.py +++ b/pages/fluid.py @@ -22,7 +22,7 @@ import nmea2000.lookup class Fluid(Page): - def __init__(self, pageno, cfg, boatdata, fluidtype): + def __init__(self, pageno, cfg, appdata, boatdata, fluidtype): super().__init__(pageno, cfg, boatdata) self.fluidtype = int(fluidtype) if self.fluidtype == 0: diff --git a/pages/fourvalues.py b/pages/fourvalues.py index 2d85a9a..0a161f4 100644 --- a/pages/fourvalues.py +++ b/pages/fourvalues.py @@ -20,7 +20,7 @@ from .page import Page class FourValues(Page): - def __init__(self, pageno, cfg, boatdata, boatvalue1, boatvalue2, boatvalue3, boatvalue4): + def __init__(self, pageno, cfg, appdata, boatdata, boatvalue1, boatvalue2, boatvalue3, boatvalue4): super().__init__(pageno, cfg, boatdata) self.value1 = boatvalue1 self.value2 = boatvalue2 diff --git a/pages/fourvalues2.py b/pages/fourvalues2.py index 14ff400..475b4bd 100644 --- a/pages/fourvalues2.py +++ b/pages/fourvalues2.py @@ -18,7 +18,7 @@ from .page import Page class FourValues2(Page): - def __init__(self, pageno, cfg, boatdata, boatvalue1, boatvalue2, boatvalue3, boatvalue4): + def __init__(self, pageno, cfg, appdata, boatdata, boatvalue1, boatvalue2, boatvalue3, boatvalue4): super().__init__(pageno, cfg, boatdata) self.value1 = boatvalue1 self.value2 = boatvalue2 diff --git a/pages/keel.py b/pages/keel.py index e2aa630..60c1241 100644 --- a/pages/keel.py +++ b/pages/keel.py @@ -15,7 +15,7 @@ from .page import Page class Keel(Page): - def __init__(self, pageno, cfg, boatdata): + def __init__(self, pageno, cfg, appdata, boatdata): super().__init__(pageno, cfg, boatdata) # Wert für Kielrotation self.valref = self.bd.getRef("xdrRotK") diff --git a/pages/onevalue.py b/pages/onevalue.py index 346c29d..8f5ee7e 100644 --- a/pages/onevalue.py +++ b/pages/onevalue.py @@ -3,7 +3,7 @@ from .page import Page class OneValue(Page): - def __init__(self, pageno, cfg, boatdata, boatvalue): + def __init__(self, pageno, cfg, appdata, boatdata, boatvalue): super().__init__(pageno, cfg, boatdata) self.ref1 = self.bd.getRef(boatvalue) diff --git a/pages/rudder.py b/pages/rudder.py index 409b7e5..529971e 100644 --- a/pages/rudder.py +++ b/pages/rudder.py @@ -4,7 +4,7 @@ from .page import Page class Rudder(Page): - def __init__(self, pageno, cfg, boatdata): + def __init__(self, pageno, cfg, appdata, boatdata): super().__init__(pageno, cfg, boatdata) self.buttonlabel[1] = 'MODE' self.mode = 'P' diff --git a/pages/sixvalues.py b/pages/sixvalues.py index 9d79bc9..748337b 100644 --- a/pages/sixvalues.py +++ b/pages/sixvalues.py @@ -18,7 +18,8 @@ from .page import Page class SixValues(Page): - def __init__(self, pageno, cfg, boatdata, boatvalue1, boatvalue2, boatvalue3, boatvalue4): + def __init__(self, pageno, cfg, appdata, boatdata, boatvalue1, boatvalue2, + boatvalue3, boatvalue4, boatvalue5, boatvalue6): super().__init__(pageno, cfg, boatdata) self.value1 = boatvalue1 self.value2 = boatvalue2 diff --git a/pages/skyview.py b/pages/skyview.py index 195f3eb..d067bf3 100644 --- a/pages/skyview.py +++ b/pages/skyview.py @@ -14,7 +14,7 @@ from .page import Page class SkyView(Page): - def __init__(self, pageno, cfg, boatdata): + def __init__(self, pageno, cfg, appdata, boatdata): super().__init__(pageno, cfg, boatdata) def pol2cart(azimut, elevation): diff --git a/pages/threevalues.py b/pages/threevalues.py index 0c1093e..5de34ba 100644 --- a/pages/threevalues.py +++ b/pages/threevalues.py @@ -3,7 +3,7 @@ from .page import Page class ThreeValues(Page): - def __init__(self, pageno, cfg, boatdata, boatvalue1, boatvalue2, boatvalue3): + def __init__(self, pageno, cfg, appdata, boatdata, boatvalue1, boatvalue2, boatvalue3): super().__init__(pageno, cfg, boatdata) self.ref1 = self.bd.getRef(boatvalue1) self.ref2 = self.bd.getRef(boatvalue2) diff --git a/pages/tracker.py b/pages/tracker.py index cee2a7c..84d4e36 100644 --- a/pages/tracker.py +++ b/pages/tracker.py @@ -11,14 +11,18 @@ from .page import Page class Tracker(Page): - def __init__(self, pageno, cfg, boatdata): + def __init__(self, pageno, cfg, appdata, boatdata): super().__init__(pageno, cfg, boatdata) + self._appdata = appdata self.buttonlabel[1] = 'MODE' + print(cfg) def handle_key(self, buttonid): - global tracker_active; if buttonid == 1: - tracker_active = not tracker_active + if self._appdata.track.is_active(): + self._appdata.track.set_active(False) + else: + self._appdata.track.set_active(True) def draw(self, ctx): # Name @@ -30,7 +34,7 @@ class Tracker(Page): ctx.set_font_size(16) ctx.move_to(20, 140) ctx.show_text("active: ") - if tracker_active: + if self._appdata.track.is_active(): ctx.show_text("yes") else: ctx.show_text("no") diff --git a/pages/twovalues.py b/pages/twovalues.py index 5f055e6..6fb6916 100644 --- a/pages/twovalues.py +++ b/pages/twovalues.py @@ -16,7 +16,7 @@ from .page import Page class TwoValues(Page): - def __init__(self, pageno, cfg, boatdata, boatvalue1, boatvalue2): + def __init__(self, pageno, cfg, appdata, boatdata, boatvalue1, boatvalue2): super().__init__(pageno, cfg, boatdata) self.ref1 = self.bd.getRef(boatvalue1) self.ref2 = self.bd.getRef(boatvalue2) diff --git a/pages/voltage.py b/pages/voltage.py index 057d06b..724fb90 100644 --- a/pages/voltage.py +++ b/pages/voltage.py @@ -17,7 +17,7 @@ class Voltage(Page): avg = (1, 10, 60, 300); - def __init__(self, pageno, cfg, boatdata): + def __init__(self, pageno, cfg, appdata, boatdata): super().__init__(pageno, cfg, boatdata) self.trend = True self.mode = 'A' diff --git a/tracker.py b/tracker.py index 66affc5..ecd0258 100644 --- a/tracker.py +++ b/tracker.py @@ -13,12 +13,170 @@ Wiederherstellung der Verbindung übertragen. """ +import os +import time +import paho.mqtt.client as mqtt +import json + class Tracker(): - def __init__(self, trackertype): + def __init__(self, trackertype='NONE'): validtypes = ('HERO', 'SDCARD', 'SERVER', 'NONE') trackertype = trackertype.upper() if trackertype not in validtypes: raise TypeError(f"Invalid tracker type: '{valtype}'. Only supported: {validtypes}") self.ttype = trackertype + self.activated = False + self.races = set() # Liste der Regatten, eindeutige Namen + self.courses = set() # Liste der Bahnen, eindeutige Namen + self.lat = None # last latitude + self.lon = None # last longitude + self.tspos = None # timestamp (hh:ss:mm) as datetime.time + self.sog = None + + + def is_active(self): + return self.activated + + def set_active(self, newval): + self.activated = newval + + def get_position(self): + # Positionsabfrage für die Payload + # LAT, LON, TSPOS, SOG + return (self.lat, self.lon, self.tspos, self.sog) + + def hero_add_race(self, raceid): + self.races.add(raceid) + + def hero_set_races(self, newraces): + self.races = set(newraces) + + def mqtt_on_connect(self, client, userdata, flags, rc): + print(f"MQTT connected with result code {rc}") + #userdata['connect_rc'] = rc + if rc != 0: + # Result codes: + # 1: Connection Refused, unacceptable protocol version + # 2: Connection Refused, identifier rejected + # 3: Connection Refused, Server unavailable + # 4: Connection Refused, bad user name or password + # 5: Connection Refused, not authorized + #userdata['connect_ok'] = True + pass + else: + client.subscribe("regattahero/orgstatus/thomas") + client.subscribe("regattahero/racestatus/thomas/#") + #userdata['connect_ok'] = False + + def mqtt_on_message(self, client, userdata, msg): + """ + TODO raceid über userdata? dann topic prüfen? + """ + if msg.topic == "regattahero/orgstatus/thomas": + # kommt alle 10s + orgstatus = json.loads(msg.payload) + if orgstatus['allLogout']: + print("All logout received!") + client.disconnect() + sys.exit(0) # TODO nur die MQTT-Task beenden + if orgstatus['message']: + # TODO Alarm-Funktion nutzen? + print("Nachricht der Wettfahrtkeitung:") + print(orgstatus['message']) + print(orgstatus['races']) + #for r in orgstatus['races']: + # print(f"Race: {r}") + elif msg.topic.startswith("regattahero/racestatus/thomas"): + # kommt alle 1s + # dem Topic angehängt ist noch die raceid + payload = json.loads(msg.payload) + racestatus = payload['racestatus'] + """ + time: negativ: Zeit vor dem Start, positiv: Zeit nach dem Start + in Sekunden + + Signale der Wettfahrtleitung hier anzeigen + Regattaabbruch + Bahnverkürzung + Rückrufe + + """ + else: + print(f"UNKNOWN TOPIC: {msg.topic}") + print(msg.payload) + + def mqtt_publish(self, client, topic, payload, bv_lat, bv_lon, bv_sog): + """ + Payload vorbelegt als Template, so daß nur noch die veränderlichen + GPS-Daten eingefügt werden müssen: LAT LON SOG TIMESTAMP + """ + lat = bv_lat.getValueRaw() + lon = bv_lon.getValueRaw() + sog = bv_sog.getValueRaw() + if lat and lon and sog: + payload['gps']['lat'] = round(lat, 5) + payload['gps']['lon'] = round(lon, 5) + payload['gps']['speed'] = sog + payload['gps']['timestamp'] = time.strftime("%Y-%m-%dT%H:%M:%S.000Z", time.gmtime()) + + client.publish(topic, json.dumps(payload)) + else: + print("No GPS data available. Nothing published!") + + def mqtt_tracker(self, cfg, boat, appdata, boatdata): + print("MQTT tracker enabled") + client = mqtt.Client() + client.on_connect = self.mqtt_on_connect + client.on_message = self.mqtt_on_message + client.username_pw_set(username=cfg['mqtt_user'], password=cfg['mqtt_pass']) + try: + client.connect(cfg['host'], cfg['port'], 60) + except ConnectionRefusedError: + print("MQTT connection refused. Check username and password.") + return + + tracefile = os.path.join(os.path.expanduser(cfg['logdir']), 'tracker.log') + trace_fh = open(tracefile, 'w+') + client.user_data_set({'trace': trace_fh}) + + topic = "regattahero/tracker/" + cfg['orgname'] + payload = { + "passcode": cfg['passcode'], + "orgid": cfg['orgname'], + "raceid": "Demo Regatta", # TODO aus Selektion einstellen + "gps": { + "lat": 0.0, + "lon": 0.0, + "speed": 0.0, + "age": 1000, + "odo": 1000, + "bat": 1.0, + "timestamp": "" # ISO8601 Format mit Millisekunden in UTC + }, + "boat": { + "boatid": cfg['uuid'], + "sailno": boat['sailno'], + "team": boat['team'], + "boatclass": boat['class'], + "handicap": boat['handicap'], + "club": boat['club'], + "boatname": boat['name'] + } + } + + # Zugriff auf Boatdata: Referenzen für leichten schnellen Zugriff + bv_lat = boatdata.getRef("LAT") + bv_lon = boatdata.getRef("LON") + bv_sog = boatdata.getRef("SOG") + + client.loop_start() + while not appdata.shutdown: + time.sleep(1) + if appdata.track.is_active(): + self.mqtt_publish(client, topic, payload, bv_lat, bv_lon, bv_sog) + print("MQTT tracker shutdown") + client.loop_stop() + client.disconnect() + trace_fh.close()