""" Tracker-Daten Mögliche Typen: HERO - Regatta Hero SDCARD SERVER NONE - kein Tracking Wenn die Verbindung zum Server im Internet nicht funktioniert, werden die Positionen in eine Warteschlange gesichert und nach Wiederherstellung der Verbindung übertragen. """ import os import time import paho.mqtt.client as mqtt import json import socket class Tracker(): 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.appdata = None self.activated = False self.trace = False # Debugging self.trace_fh = None # File Handle der Tracedatei 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 self.hero_orgstatus = None self.hero_racestatus = None # Akluelle Regatta def is_server_active(self, hostname, port): """ ohne Netzwerkverbindung wirft socket.gethostbyname eine Exception, ggf. "Erro -3 temporary failure in name resolution" """ try: host = socket.gethostbyname(hostname) # DNS Lookup s = socket.create_connection((host, port), 2) s.close() return True except Exception: pass return False 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 hero_get_races(self): if not self.hero_orgstatus: return [] return [r for r in self.hero_orgstatus['races']] 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 self.trace: self.trace_fh.write(msg.topic) self.trace_fh.write("\n") self.trace_fh.write(msg.payload.decode()) self.trace_fh.write("\n\n") self.trace_fh.flush() if msg.topic == "regattahero/orgstatus/thomas": # kommt alle 10s self.hero_orgstatus = json.loads(msg.payload) if self.hero_orgstatus['allLogout']: print("All logout received!") client.disconnect() sys.exit(0) # TODO nur die MQTT-Task beenden if self.hero_orgstatus['message']: # TODO Alarm-Funktion nutzen? print("Nachricht der Wettfahrtkeitung:") print(orgstatus['message']) 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") self.appdata = appdata self.trace = cfg['trace'] 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 if cfg['trace']: # TODO Log Hinweis tracefile = os.path.join(os.path.expanduser(cfg['logdir']), 'tracker.log') self.trace_fh = open(tracefile, 'w+') 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) client.loop_stop() client.disconnect() if cfg['trace']: self.trace_fh.close()