217 lines
7.3 KiB
Python
217 lines
7.3 KiB
Python
"""
|
|
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()
|