OBP60v/tracker.py

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()