Tracker in eigene Klasse und AppData eingeführt
This commit is contained in:
parent
fd673d5e55
commit
acbcfac425
|
@ -0,0 +1,10 @@
|
|||
"""
|
||||
Generische Applikationsdaten
|
||||
|
||||
"""
|
||||
from tracker import Tracker
|
||||
|
||||
class AppData():
|
||||
def __init__(self):
|
||||
self.shutdown = False # Globaler Ausschalter
|
||||
self.track = Tracker('NONE')
|
171
obp60v.py
171
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,7 +644,7 @@ 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
|
||||
|
||||
|
@ -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.")
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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'
|
||||
|
|
160
tracker.py
160
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()
|
||||
|
|
Loading…
Reference in New Issue