Logging und Tracker GUI weiterprogrammiert
This commit is contained in:
parent
d3a984075e
commit
eab6cdf4c9
|
@ -8,9 +8,10 @@ from tracker import Tracker
|
||||||
|
|
||||||
class AppData():
|
class AppData():
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, logger, cfg):
|
||||||
self.shutdown = False # Globaler Ausschalter
|
self.shutdown = False # Globaler Ausschalter
|
||||||
self.track = Tracker('NONE')
|
self.log = logger
|
||||||
|
self.track = Tracker(logger, cfg)
|
||||||
self.frontend = None
|
self.frontend = None
|
||||||
self.bv_lat = None
|
self.bv_lat = None
|
||||||
self.bv_lon = None
|
self.bv_lon = None
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 291 B |
|
@ -40,16 +40,20 @@ config = ~/.opencpn/opencpn.conf
|
||||||
[tracker]
|
[tracker]
|
||||||
type = NONE
|
type = NONE
|
||||||
host = 127.0.0.1
|
host = 127.0.0.1
|
||||||
port = 1883
|
port = 80
|
||||||
|
ssl = false
|
||||||
|
username = demo
|
||||||
|
password = secret
|
||||||
|
mqtt_host = 127.0.0.1
|
||||||
|
mqtt_port = 1883
|
||||||
|
mqtt_ssl = False
|
||||||
mqtt_user = demo
|
mqtt_user = demo
|
||||||
mqtt_pass = 123456
|
mqtt_pass = 123456
|
||||||
orgname = demo
|
|
||||||
passcode = 123456
|
|
||||||
trace = false
|
trace = false
|
||||||
|
|
||||||
[boat]
|
[boat]
|
||||||
name = My boat
|
name = My boat
|
||||||
sailno = GER 4711
|
sailno = GER 815
|
||||||
class = One off
|
class = One off
|
||||||
handicap = 100.0
|
handicap = 100.0
|
||||||
club = NONE
|
club = NONE
|
||||||
|
|
104
obp60v.py
104
obp60v.py
|
@ -124,7 +124,9 @@ cfg = {
|
||||||
'cfgfile': 'obp60v.conf',
|
'cfgfile': 'obp60v.conf',
|
||||||
'logdir': '~/.local/share/obp60v',
|
'logdir': '~/.local/share/obp60v',
|
||||||
'logfile': 'obp60v.log',
|
'logfile': 'obp60v.log',
|
||||||
|
'loglevel': 3,
|
||||||
'imgpath': os.path.join(sys.path[0], 'images'),
|
'imgpath': os.path.join(sys.path[0], 'images'),
|
||||||
|
'audiopath': os.path.join(sys.path[0], 'audio'),
|
||||||
'deviceid': 100,
|
'deviceid': 100,
|
||||||
'manufcode': 2046, # Open Boat Projects (OBP)
|
'manufcode': 2046, # Open Boat Projects (OBP)
|
||||||
'devfunc': 120, # Display
|
'devfunc': 120, # Display
|
||||||
|
@ -212,7 +214,7 @@ def rxd_gps(devname, devspeed):
|
||||||
try:
|
try:
|
||||||
ser = serial.Serial(devname, devspeed, timeout=3)
|
ser = serial.Serial(devname, devspeed, timeout=3)
|
||||||
except serial.SerialException as e:
|
except serial.SerialException as e:
|
||||||
print("GPS serial port not available")
|
log.error("GPS serial port not available")
|
||||||
return
|
return
|
||||||
setthreadtitle("GPSlistener")
|
setthreadtitle("GPSlistener")
|
||||||
while not appdata.shutdown:
|
while not appdata.shutdown:
|
||||||
|
@ -624,7 +626,7 @@ def init_profile(config, cfg, boatdata):
|
||||||
cls = getattr(pages, p['type'])
|
cls = getattr(pages, p['type'])
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
# Klasse nicht vorhanden, Seite wird nicht benutzt
|
# Klasse nicht vorhanden, Seite wird nicht benutzt
|
||||||
print(f"Klasse '{p['type']}' nicht gefunden")
|
log.error(f"Klasse '{p['type']}' nicht gefunden")
|
||||||
continue
|
continue
|
||||||
c = cls(i, cfg, appdata, boatdata, *[v for v in p['values'].values()])
|
c = cls(i, cfg, appdata, boatdata, *[v for v in p['values'].values()])
|
||||||
clist[i] = c
|
clist[i] = c
|
||||||
|
@ -643,7 +645,7 @@ def set_loglevel(nr):
|
||||||
nr = 0
|
nr = 0
|
||||||
return level[nr]
|
return level[nr]
|
||||||
|
|
||||||
def init_logging(logdir, logfile='obp60v.log'):
|
def init_logging(logdir, logfile='obp60v.log', loglevel=logging.INFO):
|
||||||
global log
|
global log
|
||||||
os.makedirs(logdir, exist_ok=True)
|
os.makedirs(logdir, exist_ok=True)
|
||||||
log = logging.getLogger(os.path.basename(sys.argv[0]))
|
log = logging.getLogger(os.path.basename(sys.argv[0]))
|
||||||
|
@ -651,30 +653,16 @@ def init_logging(logdir, logfile='obp60v.log'):
|
||||||
formatter = logging.Formatter('%(asctime)s %(name)s %(levelname)s: %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
|
formatter = logging.Formatter('%(asctime)s %(name)s %(levelname)s: %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
|
||||||
hdlr.setFormatter(formatter)
|
hdlr.setFormatter(formatter)
|
||||||
log.addHandler(hdlr)
|
log.addHandler(hdlr)
|
||||||
log.setLevel(logging.INFO)
|
log.setLevel(loglevel)
|
||||||
console = logging.StreamHandler()
|
console = logging.StreamHandler()
|
||||||
console.setFormatter(logging.Formatter('%(levelname)s:%(message)s'))
|
console.setFormatter(logging.Formatter('%(levelname)s:%(message)s'))
|
||||||
console.setLevel(logging.INFO)
|
console.setLevel(loglevel)
|
||||||
log.addHandler(console)
|
log.addHandler(console)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
||||||
setproctitle("obp60v")
|
setproctitle("obp60v")
|
||||||
|
|
||||||
# Globale Daten, u.a. auch Shutdown-Indikator
|
|
||||||
appdata = AppData()
|
|
||||||
|
|
||||||
owndevice = Device(100)
|
|
||||||
# Hardcoding device, not intended to change
|
|
||||||
owndevice.manufacturercode = cfg['manufcode']
|
|
||||||
owndevice.industrygroup = cfg['industrygroup']
|
|
||||||
owndevice.deviceclass = cfg['devclass']
|
|
||||||
owndevice.devicefunction = cfg['devfunc']
|
|
||||||
|
|
||||||
boatdata = BoatData()
|
|
||||||
boatdata.addTank(0)
|
|
||||||
boatdata.addEngine(0)
|
|
||||||
|
|
||||||
# Basiskonfiguration aus Datei lesen
|
# Basiskonfiguration aus Datei lesen
|
||||||
config = configparser.ConfigParser()
|
config = configparser.ConfigParser()
|
||||||
config_path = os.path.join(sys.path[0], cfg['cfgfile'])
|
config_path = os.path.join(sys.path[0], cfg['cfgfile'])
|
||||||
|
@ -683,11 +671,11 @@ if __name__ == "__main__":
|
||||||
print("Konfigurationsdatei '{}' konnte nicht gelesen werden!".format(cfg['cfgfile']))
|
print("Konfigurationsdatei '{}' konnte nicht gelesen werden!".format(cfg['cfgfile']))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
cfg['_config'] = config # Objekt zum späteren schreiben
|
cfg['_config'] = config # Objekt zum späteren schreiben
|
||||||
|
cfg['loglevel'] = config.getint('system', 'loglevel')
|
||||||
cfg['deviceid'] = config.getint('system', 'deviceid')
|
cfg['deviceid'] = config.getint('system', 'deviceid')
|
||||||
cfg['simulation'] = config.getboolean('system', 'simulation')
|
cfg['simulation'] = config.getboolean('system', 'simulation')
|
||||||
cfg['histpath'] = os.path.expanduser(config.get('system', 'histpath'))
|
cfg['histpath'] = os.path.expanduser(config.get('system', 'histpath'))
|
||||||
cfg['guistyle'] = config.get('system', 'guistyle')
|
cfg['guistyle'] = config.get('system', 'guistyle')
|
||||||
print("Setting GUI style to '{}'".format(cfg['guistyle']))
|
|
||||||
cfg['mouseptr'] = config.getboolean('system', 'mouseptr')
|
cfg['mouseptr'] = config.getboolean('system', 'mouseptr')
|
||||||
try:
|
try:
|
||||||
cfg['win_x'] = config.getint('gui', 'win_x')
|
cfg['win_x'] = config.getint('gui', 'win_x')
|
||||||
|
@ -724,13 +712,15 @@ if __name__ == "__main__":
|
||||||
boatdata.addHistory(history, "press")
|
boatdata.addHistory(history, "press")
|
||||||
|
|
||||||
# Tracker data
|
# Tracker data
|
||||||
cfg['tracker']['type'] = config.get('tracker', 'type')
|
cfg['tracker']['type'] = config.get('tracker', 'type').upper()
|
||||||
cfg['tracker']['host'] = config.get('tracker', 'host')
|
cfg['tracker']['host'] = config.get('tracker', 'host')
|
||||||
cfg['tracker']['port'] = config.getint('tracker', 'port')
|
cfg['tracker']['port'] = config.getint('tracker', 'port')
|
||||||
|
cfg['tracker']['username'] = config.get('tracker', 'username')
|
||||||
|
cfg['tracker']['password'] = config.get('tracker', 'password')
|
||||||
|
cfg['tracker']['mqtt_host'] = config.get('tracker', 'mqtt_host')
|
||||||
|
cfg['tracker']['mqtt_port'] = config.getint('tracker', 'mqtt_port')
|
||||||
cfg['tracker']['mqtt_user'] = config.get('tracker', 'mqtt_user')
|
cfg['tracker']['mqtt_user'] = config.get('tracker', 'mqtt_user')
|
||||||
cfg['tracker']['mqtt_pass'] = config.get('tracker', 'mqtt_pass')
|
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']
|
cfg['tracker']['logdir'] = cfg['logdir']
|
||||||
cfg['tracker']['trace'] = config.getboolean('tracker', 'trace')
|
cfg['tracker']['trace'] = config.getboolean('tracker', 'trace')
|
||||||
|
|
||||||
|
@ -742,55 +732,76 @@ if __name__ == "__main__":
|
||||||
cfg['boat']['club'] = config.get('boat', 'club')
|
cfg['boat']['club'] = config.get('boat', 'club')
|
||||||
cfg['boat']['team'] = config.get('boat', 'team')
|
cfg['boat']['team'] = config.get('boat', 'team')
|
||||||
|
|
||||||
# Client UUID. Automatisch erzeugen wenn noch nicht vorhanden
|
|
||||||
create_uuid = False
|
|
||||||
try:
|
|
||||||
cfg['tracker']['uuid'] = config.get('tracker', 'uuid')
|
|
||||||
except configparser.NoOptionError:
|
|
||||||
create_uuid = True
|
|
||||||
if create_uuid or (len(cfg['tracker']['uuid']) != 36):
|
|
||||||
cfg['tracker']['uuid'] = str(uuid.uuid4())
|
|
||||||
config.set('tracker', 'uuid', cfg['tracker']['uuid'])
|
|
||||||
with open(config_path, 'w') as fh:
|
|
||||||
config.write(fh)
|
|
||||||
|
|
||||||
if cfg['simulation']:
|
|
||||||
boatdata.enableSimulation()
|
|
||||||
|
|
||||||
# Protokollierung
|
# Protokollierung
|
||||||
init_logging(os.path.expanduser(cfg['logdir']), cfg['logfile'])
|
loglevel = set_loglevel(cfg['loglevel'])
|
||||||
log.info("Logging initialized")
|
init_logging(os.path.expanduser(cfg['logdir']), cfg['logfile'], loglevel)
|
||||||
|
log.info("Client started")
|
||||||
|
log.info("Setting GUI style to '{}'".format(cfg['guistyle']))
|
||||||
|
|
||||||
|
# Eindeutige Bootskennung UUID. Automatisch erzeugen wenn noch nicht vorhanden
|
||||||
|
create_uuid = False
|
||||||
|
try:
|
||||||
|
cfg['boat']['uuid'] = config.get('boat', 'uuid')
|
||||||
|
except configparser.NoOptionError:
|
||||||
|
create_uuid = True
|
||||||
|
if create_uuid or (len(cfg['boat']['uuid']) != 36):
|
||||||
|
cfg['boat']['uuid'] = str(uuid.uuid4())
|
||||||
|
config.set('boat', 'uuid', cfg['boat']['uuid'])
|
||||||
|
with open(config_path, 'w') as fh:
|
||||||
|
config.write(fh)
|
||||||
|
log.info("Created new boat UUID: {}".format(cfg['boat']['uuid']))
|
||||||
|
|
||||||
|
# Globale Daten, u.a. auch Shutdown-Indikator
|
||||||
|
appdata = AppData(log, cfg)
|
||||||
|
|
||||||
|
owndevice = Device(100)
|
||||||
|
# Hardcoding device, not intended to change
|
||||||
|
owndevice.manufacturercode = cfg['manufcode']
|
||||||
|
owndevice.industrygroup = cfg['industrygroup']
|
||||||
|
owndevice.deviceclass = cfg['devclass']
|
||||||
|
owndevice.devicefunction = cfg['devfunc']
|
||||||
|
|
||||||
|
boatdata = BoatData()
|
||||||
|
boatdata.addTank(0)
|
||||||
|
boatdata.addEngine(0)
|
||||||
|
|
||||||
|
# Ggf. Simulationsdaten einschalten
|
||||||
|
if cfg['simulation']:
|
||||||
|
boatdata.enableSimulation()
|
||||||
|
|
||||||
# Gerät initialisieren u.a. mit den genutzten Seiten
|
# Gerät initialisieren u.a. mit den genutzten Seiten
|
||||||
profile = init_profile(config, cfg, boatdata)
|
profile = init_profile(config, cfg, boatdata)
|
||||||
|
|
||||||
# Schnittstellen aktivieren
|
# Schnittstellen aktivieren, jew. eigener Thread
|
||||||
if cfg['can']:
|
if cfg['can']:
|
||||||
print("CAN enabled")
|
log.info("CAN enabled")
|
||||||
t_rxd_n2k = threading.Thread(target=rxd_n2k, args=(cfg['can_intf'],))
|
t_rxd_n2k = threading.Thread(target=rxd_n2k, args=(cfg['can_intf'],))
|
||||||
t_rxd_n2k.start()
|
t_rxd_n2k.start()
|
||||||
if cfg['nmea0183']:
|
if cfg['nmea0183']:
|
||||||
print("NMEA0183 enabled, library version {}".format(pynmea2.version))
|
log.info("NMEA0183 enabled, library version {}".format(pynmea2.version))
|
||||||
t_rxd_0183 = threading.Thread(target=nmea0183.rxd_0183, args=(appdata,boatdata,cfg['0183_port'],))
|
t_rxd_0183 = threading.Thread(target=nmea0183.rxd_0183, args=(appdata,boatdata,cfg['0183_port'],))
|
||||||
t_rxd_0183.start()
|
t_rxd_0183.start()
|
||||||
if cfg['gps']:
|
if cfg['gps']:
|
||||||
print("GPS enabled (local)")
|
log.info("GPS enabled (local)")
|
||||||
t_rxd_gps = threading.Thread(target=rxd_gps, args=(cfg['gps_port'],))
|
t_rxd_gps = threading.Thread(target=rxd_gps, args=(cfg['gps_port'],))
|
||||||
t_rxd_gps.start()
|
t_rxd_gps.start()
|
||||||
if cfg['network']:
|
if cfg['network']:
|
||||||
print("Networking enabled")
|
log.info("Networking enabled")
|
||||||
t_rxd_net = threading.Thread(target=rxd_network, args=(cfg['net_port'],cfg['net_addr']))
|
t_rxd_net = threading.Thread(target=rxd_network, args=(cfg['net_port'],cfg['net_addr']))
|
||||||
t_rxd_net.start()
|
t_rxd_net.start()
|
||||||
if cfg['tracker']['type'] != 'NONE':
|
if cfg['tracker']['type'] != 'NONE':
|
||||||
appdata.track.set_type( cfg['tracker']['type'])
|
log.info(f"Tracking enabled, mode {cfg['tracker']['type']}")
|
||||||
|
#appdata.track.set_type( cfg['tracker']['type'])
|
||||||
t_tracker = threading.Thread(target=appdata.track.mqtt_tracker, args=(cfg['tracker'],cfg['boat'],appdata,boatdata))
|
t_tracker = threading.Thread(target=appdata.track.mqtt_tracker, args=(cfg['tracker'],cfg['boat'],appdata,boatdata))
|
||||||
t_tracker.start()
|
t_tracker.start()
|
||||||
if not cfg['simulation']:
|
if not cfg['simulation']:
|
||||||
if cfg['bme280']:
|
if cfg['bme280']:
|
||||||
|
log.info("Environment sensor enabled")
|
||||||
t_data = threading.Thread(target=datareader, args=(cfg, history))
|
t_data = threading.Thread(target=datareader, args=(cfg, history))
|
||||||
t_data.start()
|
t_data.start()
|
||||||
else:
|
else:
|
||||||
print("Simulation mode enabled")
|
log.info("Simulation mode enabled")
|
||||||
|
|
||||||
app = Frontend(cfg, appdata, owndevice, boatdata, profile)
|
app = Frontend(cfg, appdata, owndevice, boatdata, profile)
|
||||||
app.run()
|
app.run()
|
||||||
|
@ -808,5 +819,6 @@ if __name__ == "__main__":
|
||||||
if not cfg['simulation'] and cfg['bme280']:
|
if not cfg['simulation'] and cfg['bme280']:
|
||||||
t_data.join()
|
t_data.join()
|
||||||
|
|
||||||
|
log.info("Client terminated")
|
||||||
print(boatdata)
|
print(boatdata)
|
||||||
print("Another fine product of the Sirius Cybernetics Corporation.")
|
print("Another fine product of the Sirius Cybernetics Corporation.")
|
||||||
|
|
|
@ -32,10 +32,10 @@ from .dst810 import DST810
|
||||||
from .epropulsion import EPropulsion
|
from .epropulsion import EPropulsion
|
||||||
from .keel import Keel
|
from .keel import Keel
|
||||||
from .mob import MOB
|
from .mob import MOB
|
||||||
|
from .racetracker import RaceTracker
|
||||||
from .rollpitch import RollPitch
|
from .rollpitch import RollPitch
|
||||||
from .skyview import SkyView
|
from .skyview import SkyView
|
||||||
from .solar import Solar
|
from .solar import Solar
|
||||||
from .tracker import Tracker
|
|
||||||
from .rudder import Rudder
|
from .rudder import Rudder
|
||||||
from .voltage import Voltage
|
from .voltage import Voltage
|
||||||
from .wind import Wind
|
from .wind import Wind
|
||||||
|
|
|
@ -68,7 +68,7 @@ class Page():
|
||||||
self.pageno = pageno
|
self.pageno = pageno
|
||||||
self.cfg = cfg
|
self.cfg = cfg
|
||||||
self.fullscreen = cfg['guistyle'] == 'fullscreen'
|
self.fullscreen = cfg['guistyle'] == 'fullscreen'
|
||||||
self.appdata = appdata
|
self.app = appdata
|
||||||
self.bd = boatdata
|
self.bd = boatdata
|
||||||
self.header = True
|
self.header = True
|
||||||
self.footer = True
|
self.footer = True
|
||||||
|
@ -142,7 +142,7 @@ class Page():
|
||||||
ctx.select_font_face("Ubuntu", cairo.FontSlant.NORMAL, cairo.FontWeight.BOLD)
|
ctx.select_font_face("Ubuntu", cairo.FontSlant.NORMAL, cairo.FontWeight.BOLD)
|
||||||
ctx.set_font_size(16)
|
ctx.set_font_size(16)
|
||||||
ctx.move_to(0.5, 14.5)
|
ctx.move_to(0.5, 14.5)
|
||||||
ctx.show_text(' '.join([s for s in self.appdata.status if self.appdata.status[s]]))
|
ctx.show_text(' '.join([s for s in self.app.status if self.app.status[s]]))
|
||||||
ctx.stroke()
|
ctx.stroke()
|
||||||
|
|
||||||
# Tastenstatus
|
# Tastenstatus
|
||||||
|
|
|
@ -25,7 +25,7 @@ import os
|
||||||
import cairo
|
import cairo
|
||||||
from .page import Page
|
from .page import Page
|
||||||
|
|
||||||
class Tracker(Page):
|
class RaceTracker(Page):
|
||||||
|
|
||||||
def __init__(self, pageno, cfg, appdata, boatdata):
|
def __init__(self, pageno, cfg, appdata, boatdata):
|
||||||
super().__init__(pageno, cfg, appdata, boatdata)
|
super().__init__(pageno, cfg, appdata, boatdata)
|
||||||
|
@ -33,7 +33,7 @@ class Tracker(Page):
|
||||||
self.bv_lon = boatdata.getRef("LON")
|
self.bv_lon = boatdata.getRef("LON")
|
||||||
self.bv_sog = boatdata.getRef("SOG")
|
self.bv_sog = boatdata.getRef("SOG")
|
||||||
self.races = None
|
self.races = None
|
||||||
self.raceid = None # Ausgewählte Regatta
|
self.raceid = self.app.track.hero_raceid # Ausgewählte Regatta
|
||||||
self.menupos = 0
|
self.menupos = 0
|
||||||
self.buttonlabel[1] = 'MODE'
|
self.buttonlabel[1] = 'MODE'
|
||||||
self.buttonlabel[2] = 'INFO'
|
self.buttonlabel[2] = 'INFO'
|
||||||
|
@ -45,9 +45,9 @@ class Tracker(Page):
|
||||||
|
|
||||||
# Flaggen laden
|
# Flaggen laden
|
||||||
flag = ('alpha', 'answer', 'black', 'blue', 'charlie', 'class',
|
flag = ('alpha', 'answer', 'black', 'blue', 'charlie', 'class',
|
||||||
'finish', 'hotel', 'india', 'november', 'orange',
|
'finish', 'foxtrot', 'hotel', 'india', 'november',
|
||||||
'papa', 'repeat_one', 'sierra', 'start', 'uniform',
|
'orange', 'papa', 'repeat_one', 'sierra', 'start',
|
||||||
'xray', 'yankee', 'zulu')
|
'uniform', 'xray', 'yankee', 'zulu')
|
||||||
# Mapping
|
# Mapping
|
||||||
self.flagmap = {
|
self.flagmap = {
|
||||||
3: 'blue', # Zielflagge
|
3: 'blue', # Zielflagge
|
||||||
|
@ -81,7 +81,7 @@ class Tracker(Page):
|
||||||
self.buttonlabel[2] = '#UP'
|
self.buttonlabel[2] = '#UP'
|
||||||
self.buttonlabel[3] = '#DOWN'
|
self.buttonlabel[3] = '#DOWN'
|
||||||
self.buttonlabel[4] = 'SET'
|
self.buttonlabel[4] = 'SET'
|
||||||
if self.appdata.track.is_active():
|
if self.app.track.is_active():
|
||||||
self.buttonlabel[5] = 'OFF'
|
self.buttonlabel[5] = 'OFF'
|
||||||
else:
|
else:
|
||||||
self.buttonlabel[5] = 'ON'
|
self.buttonlabel[5] = 'ON'
|
||||||
|
@ -118,38 +118,33 @@ class Tracker(Page):
|
||||||
# Set / Select regatta
|
# Set / Select regatta
|
||||||
if self.menupos > 0:
|
if self.menupos > 0:
|
||||||
self.raceid = self.races[self.menupos - 1] # Nullbasiert
|
self.raceid = self.races[self.menupos - 1] # Nullbasiert
|
||||||
self.appdata.track.hero_raceid = self.raceid
|
self.app.track.hero_raceid = self.raceid
|
||||||
print(f"Selected race '{self.raceid}'")
|
print(f"Selected race '{self.raceid}'")
|
||||||
return True
|
return True
|
||||||
elif buttonid == 5:
|
elif buttonid == 5:
|
||||||
if self.mode == 'C':
|
if self.mode == 'C':
|
||||||
# Tracking ein/-ausschalten
|
# Tracking ein/-ausschalten
|
||||||
if self.appdata.track.is_active():
|
if self.app.track.is_active():
|
||||||
self.appdata.track.set_active(False)
|
self.app.track.set_active(False)
|
||||||
self.buttonlabel[5] = 'ON'
|
self.buttonlabel[5] = 'ON'
|
||||||
else:
|
else:
|
||||||
self.appdata.track.set_active(True)
|
self.app.track.set_active(True)
|
||||||
self.buttonlabel[5] = 'OFF'
|
self.buttonlabel[5] = 'OFF'
|
||||||
elif self.mode == 'M':
|
elif self.mode == 'M':
|
||||||
self.appdata.frontend.flashled.setColor('yellow')
|
self.app.frontend.flashled.setColor('yellow')
|
||||||
#self.appdata.frontend.flashled.switchOn(4)
|
#self.app.frontend.flashled.switchOn(4)
|
||||||
self.appdata.frontend.flashled.doFlash(2)
|
self.app.frontend.flashled.doFlash(2)
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def draw_normal(self, ctx):
|
def draw_normal(self, ctx):
|
||||||
# Name
|
|
||||||
#ctx.select_font_face("Ubuntu", cairo.FontSlant.NORMAL, cairo.FontWeight.BOLD)
|
|
||||||
#ctx.set_font_size(32)
|
|
||||||
#ctx.move_to(20, 80)
|
|
||||||
#ctx.show_text("Tracker")
|
|
||||||
|
|
||||||
ctx.select_font_face("DSEG7 Classic")
|
ctx.select_font_face("DSEG7 Classic")
|
||||||
ctx.set_font_size(80)
|
ctx.set_font_size(80)
|
||||||
|
|
||||||
if self.appdata.track.is_active():
|
if self.app.track.is_active():
|
||||||
if self.appdata.track.hero_racestatus:
|
if self.app.track.hero_racestatus:
|
||||||
counter = self.appdata.track.hero_racestatus['time']
|
counter = self.app.track.hero_racestatus['time']
|
||||||
minutes, seconds = divmod(abs(counter), 60)
|
minutes, seconds = divmod(abs(counter), 60)
|
||||||
if counter < 0:
|
if counter < 0:
|
||||||
ctx.move_to(16, 120)
|
ctx.move_to(16, 120)
|
||||||
|
@ -164,11 +159,11 @@ class Tracker(Page):
|
||||||
ctx.move_to(100, 120)
|
ctx.move_to(100, 120)
|
||||||
ctx.show_text("off")
|
ctx.show_text("off")
|
||||||
|
|
||||||
if self.appdata.track.hero_timedelta > 5:
|
if self.app.track.hero_timedelta > 5:
|
||||||
ctx.select_font_face("Ubuntu", cairo.FontSlant.NORMAL, cairo.FontWeight.BOLD)
|
ctx.select_font_face("Ubuntu", cairo.FontSlant.NORMAL, cairo.FontWeight.BOLD)
|
||||||
ctx.set_font_size(16)
|
ctx.set_font_size(16)
|
||||||
ctx.move_to(8, 260)
|
ctx.move_to(8, 260)
|
||||||
ctx.show_text(f"!!! Time drift of {self.appdata.track.hero_timedelta} seconds")
|
ctx.show_text(f"!!! Time drift of {self.app.track.hero_timedelta} seconds")
|
||||||
|
|
||||||
x0 = 8
|
x0 = 8
|
||||||
x1 = 96
|
x1 = 96
|
||||||
|
@ -179,20 +174,20 @@ class Tracker(Page):
|
||||||
ctx.move_to(x0, y0)
|
ctx.move_to(x0, y0)
|
||||||
ctx.show_text("Type")
|
ctx.show_text("Type")
|
||||||
ctx.move_to(x1, y0)
|
ctx.move_to(x1, y0)
|
||||||
ctx.show_text(self.appdata.track.ttype)
|
ctx.show_text(self.app.track.ttype)
|
||||||
|
|
||||||
y0 += yoffset
|
y0 += yoffset
|
||||||
ctx.move_to(x0, y0)
|
ctx.move_to(x0, y0)
|
||||||
ctx.show_text("Regatta")
|
ctx.show_text("Regatta")
|
||||||
ctx.move_to(x1, y0)
|
ctx.move_to(x1, y0)
|
||||||
ctx.show_text(self.appdata.track.hero_raceid or '[not selected]')
|
ctx.show_text(self.app.track.hero_raceid or '[not selected]')
|
||||||
|
|
||||||
y0 += yoffset
|
y0 += yoffset
|
||||||
ctx.move_to(x0, y0)
|
ctx.move_to(x0, y0)
|
||||||
ctx.show_text("Course")
|
ctx.show_text("Course")
|
||||||
ctx.move_to(x1, y0)
|
ctx.move_to(x1, y0)
|
||||||
if self.appdata.track.hero_orgstatus and self.appdata.track.hero_raceid:
|
if self.app.track.hero_orgstatus and self.app.track.hero_raceid:
|
||||||
ctx.show_text(self.appdata.track.hero_orgstatus['races'][self.appdata.track.hero_raceid]['courseid'])
|
ctx.show_text(self.app.track.hero_orgstatus['races'][self.app.track.hero_raceid]['courseid'])
|
||||||
else:
|
else:
|
||||||
ctx.show_text('[not selected]')
|
ctx.show_text('[not selected]')
|
||||||
|
|
||||||
|
@ -215,9 +210,9 @@ class Tracker(Page):
|
||||||
ctx.show_text(self.bv_sog.format())
|
ctx.show_text(self.bv_sog.format())
|
||||||
|
|
||||||
# Flaggen
|
# Flaggen
|
||||||
if self.appdata.track.hero_racestatus:
|
if self.app.track.hero_racestatus:
|
||||||
pos = 0
|
pos = 0
|
||||||
for f in self.appdata.track.hero_racestatus['flags']:
|
for f in self.app.track.hero_racestatus['flags']:
|
||||||
if f in self.flagmap:
|
if f in self.flagmap:
|
||||||
# TODO Context save/restore erforderlich?
|
# TODO Context save/restore erforderlich?
|
||||||
ctx.save()
|
ctx.save()
|
||||||
|
@ -234,14 +229,19 @@ class Tracker(Page):
|
||||||
ctx.move_to(4, 42)
|
ctx.move_to(4, 42)
|
||||||
ctx.show_text("Tracker configuration")
|
ctx.show_text("Tracker configuration")
|
||||||
|
|
||||||
x0 = 8
|
# Linke Spalte mit Daten
|
||||||
x1 = 88
|
|
||||||
y0 = 96
|
|
||||||
|
|
||||||
|
x0 = 8 # Labelspalte
|
||||||
|
x1 = 88 # Datenspalte
|
||||||
|
y0 = 75
|
||||||
|
yoffset = 16
|
||||||
|
|
||||||
|
# Bootsdaten
|
||||||
ctx.set_font_size(20)
|
ctx.set_font_size(20)
|
||||||
ctx.move_to(x0, 75)
|
ctx.move_to(x0, y0)
|
||||||
ctx.show_text("Boat data")
|
ctx.show_text("Boat data")
|
||||||
|
|
||||||
|
y0 += yoffset + 5
|
||||||
ctx.set_font_size(16)
|
ctx.set_font_size(16)
|
||||||
|
|
||||||
ctx.move_to(x0, y0)
|
ctx.move_to(x0, y0)
|
||||||
|
@ -249,48 +249,50 @@ class Tracker(Page):
|
||||||
ctx.move_to(x1, y0)
|
ctx.move_to(x1, y0)
|
||||||
ctx.show_text(self.cfg['boat']['name'])
|
ctx.show_text(self.cfg['boat']['name'])
|
||||||
|
|
||||||
ctx.move_to(x0, y0 + 16)
|
y0 += yoffset
|
||||||
|
ctx.move_to(x0, y0)
|
||||||
ctx.show_text("Class")
|
ctx.show_text("Class")
|
||||||
ctx.move_to(x1, y0 + 16)
|
ctx.move_to(x1, y0)
|
||||||
ctx.show_text(self.cfg['boat']['class'])
|
ctx.show_text(self.cfg['boat']['class'])
|
||||||
|
|
||||||
ctx.move_to(x0, y0 + 32)
|
y0 += yoffset
|
||||||
|
ctx.move_to(x0, y0)
|
||||||
ctx.show_text("Handicap")
|
ctx.show_text("Handicap")
|
||||||
ctx.move_to(x1, y0 + 32)
|
ctx.move_to(x1, y0)
|
||||||
ctx.show_text(str(self.cfg['boat']['handicap']))
|
ctx.show_text(str(self.cfg['boat']['handicap']))
|
||||||
|
|
||||||
ctx.move_to(x0, y0 + 48)
|
y0 += yoffset
|
||||||
|
ctx.move_to(x0, y0)
|
||||||
ctx.show_text("Club")
|
ctx.show_text("Club")
|
||||||
ctx.move_to(x1, y0 + 48)
|
ctx.move_to(x1, y0)
|
||||||
ctx.show_text(self.cfg['boat']['club'])
|
ctx.show_text(self.cfg['boat']['club'])
|
||||||
|
|
||||||
ctx.move_to(x0, y0 + 64)
|
y0 += yoffset
|
||||||
|
ctx.move_to(x0, y0)
|
||||||
ctx.show_text("Sailno.")
|
ctx.show_text("Sailno.")
|
||||||
ctx.move_to(x1, y0 + 64)
|
ctx.move_to(x1, y0)
|
||||||
ctx.show_text(self.cfg['boat']['sailno'])
|
ctx.show_text(self.cfg['boat']['sailno'])
|
||||||
|
|
||||||
x0 = 208
|
# Trackerdaten
|
||||||
x1 = 272
|
y0 += yoffset + 10
|
||||||
y0 = 96
|
|
||||||
yoffset = 16
|
|
||||||
|
|
||||||
ctx.set_font_size(20)
|
ctx.set_font_size(20)
|
||||||
ctx.move_to(x0, 75)
|
ctx.move_to(x0, y0)
|
||||||
ctx.show_text("Tracker info")
|
ctx.show_text("Tracker info")
|
||||||
|
|
||||||
|
y0 += yoffset + 5
|
||||||
ctx.set_font_size(16)
|
ctx.set_font_size(16)
|
||||||
|
|
||||||
ctx.move_to(x0, y0)
|
ctx.move_to(x0, y0)
|
||||||
ctx.show_text("Type")
|
ctx.show_text("Type")
|
||||||
ctx.move_to(x1, y0)
|
ctx.move_to(x1, y0)
|
||||||
ctx.show_text(self.appdata.track.ttype)
|
ctx.show_text(self.app.track.ttype)
|
||||||
|
|
||||||
y0 += yoffset
|
y0 += yoffset
|
||||||
ctx.move_to(x0, y0)
|
ctx.move_to(x0, y0)
|
||||||
ctx.show_text("Org.")
|
ctx.show_text("Org.")
|
||||||
ctx.move_to(x1, y0)
|
ctx.move_to(x1, y0)
|
||||||
if self.appdata.track.hero_orgstatus:
|
if self.app.track.hero_orgstatus:
|
||||||
ctx.show_text(self.appdata.track.hero_orgstatus['orgname'])
|
ctx.show_text(self.app.track.hero_orgstatus['orgname'])
|
||||||
else:
|
else:
|
||||||
ctx.show_text("n/a")
|
ctx.show_text("n/a")
|
||||||
|
|
||||||
|
@ -298,7 +300,7 @@ class Tracker(Page):
|
||||||
ctx.move_to(x0, y0)
|
ctx.move_to(x0, y0)
|
||||||
ctx.show_text("Status")
|
ctx.show_text("Status")
|
||||||
ctx.move_to(x1, y0)
|
ctx.move_to(x1, y0)
|
||||||
if not self.appdata.track.hero_racestatus:
|
if not self.app.track.hero_racestatus:
|
||||||
ctx.show_text("inactive")
|
ctx.show_text("inactive")
|
||||||
else:
|
else:
|
||||||
#TODO Mehr Details
|
#TODO Mehr Details
|
||||||
|
@ -308,18 +310,29 @@ class Tracker(Page):
|
||||||
ctx.move_to(x0, y0)
|
ctx.move_to(x0, y0)
|
||||||
ctx.show_text("Team")
|
ctx.show_text("Team")
|
||||||
ctx.move_to(x1, y0)
|
ctx.move_to(x1, y0)
|
||||||
if self.appdata.track.hero_racestatus:
|
ctx.show_text(self.app.track.team)
|
||||||
ctx.show_text(self.appdata.track.hero_racestatus['team'])
|
|
||||||
|
y0 += yoffset
|
||||||
|
ctx.move_to(x0, y0)
|
||||||
|
ctx.show_text("Server")
|
||||||
|
ctx.move_to(x1, y0)
|
||||||
|
if self.app.track.mqtt_connected:
|
||||||
|
ctx.show_text("MQTT")
|
||||||
else:
|
else:
|
||||||
ctx.show_text("n/a")
|
ctx.show_text("offline")
|
||||||
|
|
||||||
|
# Rechte Spalte mit Regattaauswahl
|
||||||
|
|
||||||
|
x = 208
|
||||||
|
y = 75
|
||||||
|
yoffset = 16
|
||||||
|
|
||||||
# Mögliche Regatten
|
# Mögliche Regatten
|
||||||
self.races = self.appdata.track.hero_get_races()
|
self.races = self.app.track.hero_get_races()
|
||||||
if len(self.races) == 1:
|
if len(self.races) == 1:
|
||||||
self.raceid = self.races[0]
|
self.hero_raceid = self.races[0]
|
||||||
self.menupos = 1
|
self.menupos = 1
|
||||||
x = 208
|
|
||||||
y = 180
|
|
||||||
ctx.set_font_size(20)
|
ctx.set_font_size(20)
|
||||||
ctx.move_to(x, y)
|
ctx.move_to(x, y)
|
||||||
ctx.show_text("Select Regatta")
|
ctx.show_text("Select Regatta")
|
||||||
|
@ -333,7 +346,7 @@ class Tracker(Page):
|
||||||
for r in self.races:
|
for r in self.races:
|
||||||
i += 1
|
i += 1
|
||||||
if r == self.raceid:
|
if r == self.raceid:
|
||||||
r += '*'
|
r = f"\xbb {r} \xab"
|
||||||
self.draw_text_boxed(ctx, x, y, 180, 20, r, (self.menupos == i))
|
self.draw_text_boxed(ctx, x, y, 180, 20, r, (self.menupos == i))
|
||||||
y += 20
|
y += 20
|
||||||
if i == 0:
|
if i == 0:
|
||||||
|
@ -350,11 +363,11 @@ class Tracker(Page):
|
||||||
ctx.show_text("Message from race officers")
|
ctx.show_text("Message from race officers")
|
||||||
|
|
||||||
ctx.set_font_size(16)
|
ctx.set_font_size(16)
|
||||||
if not self.appdata.track.hero_orgstatus:
|
if not self.app.track.hero_orgstatus or self.app.track.hero_orgstatus['message'] == '':
|
||||||
ctx.move_to(8, 72)
|
ctx.move_to(8, 72)
|
||||||
ctx.show_text("[ empty ]")
|
ctx.show_text("[ empty ]")
|
||||||
else:
|
else:
|
||||||
lines = self.appdata.track.hero_orgstatus['message'].splitlines()
|
lines = self.app.track.hero_orgstatus['message'].splitlines()
|
||||||
y = 72
|
y = 72
|
||||||
for l in lines:
|
for l in lines:
|
||||||
ctx.move_to(8, y)
|
ctx.move_to(8, y)
|
327
tracker.py
327
tracker.py
|
@ -14,49 +14,87 @@ Wiederherstellung der Verbindung übertragen.
|
||||||
|
|
||||||
TODO
|
TODO
|
||||||
- Nach einem Disconnect manuelle Neuverbindung ermöglichen
|
- Nach einem Disconnect manuelle Neuverbindung ermöglichen
|
||||||
|
- Audioausgabe für Regatta Hero
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
import paho.mqtt.client as mqtt
|
import paho.mqtt.client as mqtt
|
||||||
|
import http.client
|
||||||
|
import ssl
|
||||||
import json
|
import json
|
||||||
import socket
|
import socket
|
||||||
|
import ssl
|
||||||
|
import math
|
||||||
|
import subprocess # für Audioausgabe / mpg123
|
||||||
|
|
||||||
class Tracker():
|
class Tracker():
|
||||||
|
|
||||||
def __init__(self, trackertype='NONE'):
|
def __init__(self, logger, cfg):
|
||||||
self.ttype = 'NONE'
|
self.log = logger
|
||||||
self.set_type(trackertype)
|
|
||||||
|
|
||||||
self.appdata = None
|
self.appdata = None
|
||||||
|
|
||||||
|
validtypes = ('HERO', 'SDCARD', 'SERVER', 'LOCAL', 'NONE')
|
||||||
|
if cfg['tracker']['type'] not in validtypes:
|
||||||
|
raise TypeError("Invalid tracker type: '{}'. Only supported: {}".format(cfg['tracker']['type'], ','.join(validtypes)))
|
||||||
|
self.ttype = cfg['tracker']['type']
|
||||||
|
|
||||||
|
self.trace = cfg['tracker']['trace'] # Debugging
|
||||||
|
self.trace_fh = None # File Handle der Tracedatei
|
||||||
|
|
||||||
|
self.buoys = {} # Tonnen (Hero=20)
|
||||||
|
self.courses = [] # Bahnen
|
||||||
|
self.races = [] # Regatten
|
||||||
|
|
||||||
|
self.mqtt_connected = False
|
||||||
self.activated = False
|
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.lat = None # last latitude
|
||||||
self.lon = None # last longitude
|
self.lon = None # last longitude
|
||||||
self.tspos = None # timestamp (hh:ss:mm) as datetime.time
|
self.tspos = None # timestamp (hh:ss:mm) as datetime.time
|
||||||
self.sog = None
|
self.sog = None
|
||||||
|
|
||||||
self.hero_orgid = None # Eingestellt in Gerätekonfiguration
|
# Statische Daten
|
||||||
|
# TODO Wirklich alles hier im Tracker oder ist einiges generisch?
|
||||||
|
# appdata?
|
||||||
|
self.boatid = cfg['boat']['uuid']
|
||||||
|
self.sailno = cfg['boat']['sailno']
|
||||||
|
self.boatname = cfg['boat']['name']
|
||||||
|
self.boatclass = cfg['boat']['class']
|
||||||
|
self.handicap = cfg['boat']['handicap']
|
||||||
|
self.club = cfg['boat']['club']
|
||||||
|
self.team = cfg['boat']['team']
|
||||||
|
|
||||||
|
# Regatta Hero
|
||||||
|
self.hero_orgid = cfg['tracker']['username'] # Eingestellt in Gerätekonfiguration
|
||||||
|
self.hero_passcode = cfg['tracker']['password']
|
||||||
|
self.hero_host = cfg['tracker']['host']
|
||||||
|
self.hero_port = cfg['tracker']['port']
|
||||||
|
self.hero_viewerpass = None # Wird vom Server in "org" gesendet
|
||||||
|
|
||||||
|
# Vorlage für Anfragen
|
||||||
|
self.http_payload_template = {
|
||||||
|
"orgid": self.hero_orgid,
|
||||||
|
"passcode": self.hero_passcode,
|
||||||
|
"raceid": "",
|
||||||
|
"replay": "live",
|
||||||
|
"replaytime": 0,
|
||||||
|
"updateType": "timerUpdate"
|
||||||
|
}
|
||||||
|
|
||||||
|
self.hero_raceid = None # Aktuell ausgewählte Regatta
|
||||||
|
|
||||||
|
# MQTT
|
||||||
|
self.client = mqtt.Client()
|
||||||
|
self.client.on_connect = self.mqtt_on_connect
|
||||||
|
self.client.on_message = self.mqtt_on_message
|
||||||
|
|
||||||
self.hero_orgstatus = None
|
self.hero_orgstatus = None
|
||||||
self.hero_racestatus = None
|
self.hero_racestatus = None
|
||||||
self.hero_raceid = None # Aktuelle Regatta
|
|
||||||
self.hero_timedelta = 0 # Zeitdifferenz zum Server in sec
|
self.hero_timedelta = 0 # Zeitdifferenz zum Server in sec
|
||||||
|
|
||||||
# TODO Wirklich alles im Tracker oder ist einiges generisch?
|
# Hole erste Daten vom Server
|
||||||
self.boatid = None
|
self.hero_query_org()
|
||||||
self.sailno = None
|
|
||||||
self.boatname = None
|
|
||||||
self.boatclass = None
|
|
||||||
self.handicap = None
|
|
||||||
self.club = None
|
|
||||||
self.team = None
|
|
||||||
|
|
||||||
def is_server_active(self, hostname, port):
|
def is_server_active(self, hostname, port):
|
||||||
"""
|
"""
|
||||||
|
@ -81,12 +119,157 @@ class Tracker():
|
||||||
def set_hero_raceid(self, newraceid):
|
def set_hero_raceid(self, newraceid):
|
||||||
self.hero_raceid = newraceid
|
self.hero_raceid = newraceid
|
||||||
|
|
||||||
def set_type(self, newtype):
|
#def set_type(self, newtype):
|
||||||
validtypes = ('HERO', 'SDCARD', 'SERVER', 'NONE')
|
# validtypes = ('HERO', 'SDCARD', 'SERVER', 'LOCAL', 'NONE')
|
||||||
newtype = newtype.upper()
|
# newtype = newtype.upper()
|
||||||
if newtype not in validtypes:
|
# if newtype not in validtypes:
|
||||||
raise TypeError(f"Invalid tracker type: '{newtype}'. Only supported: {validtypes}")
|
# raise TypeError(f"Invalid tracker type: '{newtype}'. Only supported: {validtypes}")
|
||||||
self.ttype = newtype
|
# self.ttype = newtype
|
||||||
|
|
||||||
|
"""
|
||||||
|
Audioevents:
|
||||||
|
|
||||||
|
Einfach nur die Zahl
|
||||||
|
'eins', 'zwei', 'drei', 'vier', 'fuenf', 'sechs', 'sieben',
|
||||||
|
'acht', 'neun', 'zehn',
|
||||||
|
|
||||||
|
<n> Minuten bis zum Start
|
||||||
|
'eineMin', 'zweiMin', 'dreiMin', 'vierMin', 'fuenfMin'
|
||||||
|
|
||||||
|
<n> Sekunden bis zum Start
|
||||||
|
'fuenfzehn', 'zwanzig', 'dreissig', 'vierzig', 'fuenfzig'
|
||||||
|
|
||||||
|
'30sek_alerter' 30 sec kein GPS. Smartphonetext
|
||||||
|
'abbruch' Abbruch der Wettfahrt
|
||||||
|
'alive' sehr leises Geräusch
|
||||||
|
'allgmRueck'
|
||||||
|
'bahnmarke'
|
||||||
|
'bahnVerk' Bahnverkürzung
|
||||||
|
'batteryLevel' Ladezustand unter 20%. Smartphonetext
|
||||||
|
'cellback' Mobilfunkverbindung wiederhergestellt, Daten übertragen
|
||||||
|
'einzelRueck'
|
||||||
|
'endeEineMinute' Zeitrennen in einer Minute zuende
|
||||||
|
'endeWettfahrt' Ende der Wettfahrt
|
||||||
|
'endstartVerschiebung'
|
||||||
|
'jumping' Springende Koordinaten. Smartphonetext.
|
||||||
|
'neueAnsage' Achtung neue Bekanntmachung
|
||||||
|
'noconnection' Kein Mobilfunk. GPS-Daten werden zwischengespeichert.
|
||||||
|
'runde'
|
||||||
|
'startErfolgt' BEEEP
|
||||||
|
'startlinie' Startlinie überquert
|
||||||
|
'startnotready' Startlinie nicht bereit
|
||||||
|
'startready'
|
||||||
|
'startVerschiebung'
|
||||||
|
'wartenAnkuend' Warten auf Ankündigungssignal
|
||||||
|
'ziellinie' Ziellinie überquert
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def hero_play_audio(self, event, lang='de'):
|
||||||
|
"""
|
||||||
|
Ein Event ist mit einer Audiodatei gekoppelt. Das Event trägt
|
||||||
|
den Basisnamen der Audiodatei. Es werden nur MP3-Dateien unterstützt
|
||||||
|
"""
|
||||||
|
lang = lang.lower()
|
||||||
|
if lang == 'de':
|
||||||
|
filename = f"{event}.mp3"
|
||||||
|
else:
|
||||||
|
filename = f"{event}_{lang}.mp3"
|
||||||
|
mp3file = os.path.join(cfg['audiopath'], filename)
|
||||||
|
subprocess.run(["mpg123", "-q", mp3_file])
|
||||||
|
|
||||||
|
def hero_get_degrees(azimut):
|
||||||
|
"""
|
||||||
|
Winkel von einer Bahnmarke zur nächsten basierend auf dem Datenfeld azimutNext
|
||||||
|
"""
|
||||||
|
if (azimut <= math.pi * 3/2):
|
||||||
|
return math.degrees(2 * math.pi - azimut - math.pi / 2)
|
||||||
|
else:
|
||||||
|
return math.degrees(2 * math.pi - azimut + math.pi * 3 / 2)
|
||||||
|
|
||||||
|
def hero_query_org(self):
|
||||||
|
"""
|
||||||
|
Abfrage des Datenservers / Basisdaten
|
||||||
|
- Namen der Regatten und Kurse
|
||||||
|
"""
|
||||||
|
ssl_context = ssl.create_default_context()
|
||||||
|
conn = http.client.HTTPSConnection(self.hero_host, self.hero_port, context=ssl_context)
|
||||||
|
endpoint = '/mapupdate'
|
||||||
|
payload = self.http_payload_template
|
||||||
|
payload['raceid'] = '[No race]'
|
||||||
|
json_data = json.dumps(payload)
|
||||||
|
headers = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Content-Length': str(len(json_data))
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
conn.request("POST", endpoint, body=json_data, headers=headers)
|
||||||
|
response = conn.getresponse()
|
||||||
|
if response.status == 200:
|
||||||
|
self.log.info("HTTP: Response received successfully!")
|
||||||
|
data = json.loads(response.read().decode())
|
||||||
|
else:
|
||||||
|
self.log.warning(f"HTTP: Failed to retrieve data. Status code: {response.status}")
|
||||||
|
return
|
||||||
|
except http.client.HTTPException as e:
|
||||||
|
self.log.warning(f"HTTP error occurred: {e}")
|
||||||
|
except ssl.SSLError as ssl_error:
|
||||||
|
self.log.warning(f"SSL error occurred: {ssl_error}")
|
||||||
|
finally:
|
||||||
|
conn.close()
|
||||||
|
self.viewerpass = data['org']['viewerPasscode']
|
||||||
|
self.courses = []
|
||||||
|
for c in data['org']['courses']:
|
||||||
|
self.courses.append(c)
|
||||||
|
self.races = []
|
||||||
|
for r in data['org']['races'].values():
|
||||||
|
if not r['hiderace']:
|
||||||
|
self.races.append(r['raceid'])
|
||||||
|
# Regatta automatisch auswählen wenn nur genau eine aktivierbar ist
|
||||||
|
if len(self.races) == 1:
|
||||||
|
self.hero_raceid = self.races[0]
|
||||||
|
|
||||||
|
def hero_query_course(self, raceid):
|
||||||
|
# Bojen und Kurs für ein gegebenes Rennen
|
||||||
|
ssl_context = ssl.create_default_context()
|
||||||
|
endpoint = '/mapupdate'
|
||||||
|
conn = http.client.HTTPSConnection(self.hero_host, self.hero_port, context=ssl_context)
|
||||||
|
payload = self.http_payload_template
|
||||||
|
payload['raceid'] = raceid
|
||||||
|
json_data = json.dumps(payload)
|
||||||
|
headers = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Content-Length': str(len(json_data))
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
conn.request("POST", endpoint, body=json_data, headers=headers)
|
||||||
|
response = conn.getresponse()
|
||||||
|
if response.status == 200:
|
||||||
|
self.log.info("HTTP: Response received successfully!")
|
||||||
|
data = json.loads(response.read().decode())
|
||||||
|
else:
|
||||||
|
self.log.warning(f"HTTP: Failed to retrieve data. Status code: {response.status}")
|
||||||
|
return
|
||||||
|
except http.client.HTTPException as e:
|
||||||
|
self.log.warning(f"HTTP error occurred: {e}")
|
||||||
|
except ssl.SSLError as ssl_error:
|
||||||
|
self.log.warning(f"SSL error occurred: {ssl_error}")
|
||||||
|
finally:
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
print(self.hero_orgdata)
|
||||||
|
|
||||||
|
# Bojen
|
||||||
|
self.buoys.clear()
|
||||||
|
for key, val in self.hero_orgdata['org']['buoysets']['default']['buoyset']['marks'].items():
|
||||||
|
self.buoys[key] = {
|
||||||
|
'label': val['label'],
|
||||||
|
'lat': val['label'],
|
||||||
|
'lon': val['label']
|
||||||
|
}
|
||||||
|
print(self.buoys)
|
||||||
|
|
||||||
|
# Kurse
|
||||||
|
|
||||||
def get_position(self):
|
def get_position(self):
|
||||||
# Positionsabfrage für die Payload
|
# Positionsabfrage für die Payload
|
||||||
|
@ -109,21 +292,24 @@ class Tracker():
|
||||||
return races
|
return races
|
||||||
|
|
||||||
def mqtt_on_connect(self, client, userdata, flags, rc):
|
def mqtt_on_connect(self, client, userdata, flags, rc):
|
||||||
print(f"MQTT connected with result code {rc}")
|
self.mqtt_connected = False
|
||||||
#userdata['connect_rc'] = rc
|
if rc == 0:
|
||||||
if rc != 0:
|
self.log.info(f"MQTT connected, subscribing to topics")
|
||||||
# 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/orgstatus/thomas")
|
||||||
client.subscribe("regattahero/racestatus/thomas/#")
|
client.subscribe("regattahero/racestatus/thomas/#")
|
||||||
#userdata['connect_ok'] = False
|
self.mqtt_connected = True
|
||||||
|
elif rc == 1:
|
||||||
|
self.log.error("MQTT connection refused, unacceptable protocol version")
|
||||||
|
elif rc == 2:
|
||||||
|
self.log.error("MQTT connection refused, identifier rejected")
|
||||||
|
elif rc == 3:
|
||||||
|
self.log.error("MQTT connection refused, server unavailable")
|
||||||
|
elif rc == 4:
|
||||||
|
self.log.error("MQTT connection refused, bad user name or password")
|
||||||
|
elif rc == 5:
|
||||||
|
self.log.error("MQTT connection refused, not authorized")
|
||||||
|
else:
|
||||||
|
self.log.info(f"MQTT connection refused, error #{rc}")
|
||||||
|
|
||||||
def mqtt_on_message(self, client, userdata, msg):
|
def mqtt_on_message(self, client, userdata, msg):
|
||||||
"""
|
"""
|
||||||
|
@ -147,7 +333,7 @@ class Tracker():
|
||||||
self.hero_timedelta = abs(sec1 - sec2)
|
self.hero_timedelta = abs(sec1 - sec2)
|
||||||
|
|
||||||
if self.hero_orgstatus['allLogout']:
|
if self.hero_orgstatus['allLogout']:
|
||||||
print("All logout received!")
|
self.log.info("All logout received!")
|
||||||
client.disconnect()
|
client.disconnect()
|
||||||
self.activated = False
|
self.activated = False
|
||||||
return
|
return
|
||||||
|
@ -180,8 +366,8 @@ class Tracker():
|
||||||
|
|
||||||
"""
|
"""
|
||||||
else:
|
else:
|
||||||
print(f"UNKNOWN TOPIC: {msg.topic}")
|
self.log.warning(f"UNKNOWN TOPIC: {msg.topic}")
|
||||||
print(msg.payload)
|
self.log.debug(msg.payload)
|
||||||
|
|
||||||
def mqtt_publish(self, client, topic, payload, bv_lat, bv_lon, bv_sog):
|
def mqtt_publish(self, client, topic, payload, bv_lat, bv_lon, bv_sog):
|
||||||
"""
|
"""
|
||||||
|
@ -201,40 +387,41 @@ class Tracker():
|
||||||
payload['gps']['timestamp'] = time.strftime("%Y-%m-%dT%H:%M:%S.000Z", time.gmtime())
|
payload['gps']['timestamp'] = time.strftime("%Y-%m-%dT%H:%M:%S.000Z", time.gmtime())
|
||||||
client.publish(topic, json.dumps(payload))
|
client.publish(topic, json.dumps(payload))
|
||||||
else:
|
else:
|
||||||
#print("No GPS data available. Nothing published!")
|
self.log.debug("No GPS data available. Nothing published!")
|
||||||
pass
|
|
||||||
|
|
||||||
def mqtt_tracker(self, cfg, boat, appdata, boatdata):
|
def mqtt_tracker(self, cfg, boat, appdata, boatdata):
|
||||||
print("MQTT tracker enabled")
|
self.log.info("MQTT tracker enabled")
|
||||||
self.appdata = appdata
|
self.appdata = appdata
|
||||||
self.boatid = cfg['uuid']
|
"""
|
||||||
|
self.boatid = boat['uuid']
|
||||||
self.sailno = boat['sailno']
|
self.sailno = boat['sailno']
|
||||||
self.boatname = boat['name']
|
self.boatname = boat['name']
|
||||||
self.boatclass = boat['class']
|
self.boatclass = boat['class']
|
||||||
self.handicap = boat['handicap']
|
self.handicap = boat['handicap']
|
||||||
self.club = boat['club']
|
self.club = boat['club']
|
||||||
self.team = boat['team'] # TODO eher zu Tracker gehörig?
|
self.team = boat['team']
|
||||||
self.trace = cfg['trace']
|
self.trace = cfg['trace']
|
||||||
self.hero_orgid = cfg['orgname']
|
self.hero_orgid = cfg['username']
|
||||||
client = mqtt.Client()
|
"""
|
||||||
client.on_connect = self.mqtt_on_connect
|
self.client.username_pw_set(username=cfg['mqtt_user'], password=cfg['mqtt_pass'])
|
||||||
client.on_message = self.mqtt_on_message
|
|
||||||
client.username_pw_set(username=cfg['mqtt_user'], password=cfg['mqtt_pass'])
|
|
||||||
try:
|
try:
|
||||||
client.connect(cfg['host'], cfg['port'], 60)
|
self.client.connect(cfg['mqtt_host'], cfg['mqtt_port'], 60)
|
||||||
except ConnectionRefusedError:
|
except ConnectionRefusedError:
|
||||||
print("MQTT connection refused. Check username and password.")
|
self.log.error("MQTT connection refused. Check username and password.")
|
||||||
return
|
return
|
||||||
|
|
||||||
if cfg['trace']:
|
# Initial die Organisationsdaten abfragen um Tonnen und Kurse zu erhalten
|
||||||
# TODO Log Hinweis
|
# self.hero_query_org(cfg['host'], cfg['port'], cfg['username'], cfg['password'])
|
||||||
|
|
||||||
|
if self.trace:
|
||||||
tracefile = os.path.join(os.path.expanduser(cfg['logdir']), 'tracker.log')
|
tracefile = os.path.join(os.path.expanduser(cfg['logdir']), 'tracker.log')
|
||||||
self.trace_fh = open(tracefile, 'w+')
|
self.trace_fh = open(tracefile, 'w+')
|
||||||
|
self.log.info(f"MQTT trace file '{tracefile}' enabled")
|
||||||
|
|
||||||
topic = "regattahero/tracker/" + cfg['orgname']
|
topic = "regattahero/tracker/" + self.hero_orgid
|
||||||
payload = {
|
payload = {
|
||||||
"passcode": cfg['passcode'],
|
"passcode": cfg['password'],
|
||||||
"orgid": cfg['orgname'],
|
"orgid": self.hero_orgid,
|
||||||
"raceid": None, # Nach Auswahl einstellen
|
"raceid": None, # Nach Auswahl einstellen
|
||||||
"gps": {
|
"gps": {
|
||||||
"lat": 0.0,
|
"lat": 0.0,
|
||||||
|
@ -246,13 +433,13 @@ class Tracker():
|
||||||
"timestamp": "" # ISO8601 Format mit Millisekunden in UTC
|
"timestamp": "" # ISO8601 Format mit Millisekunden in UTC
|
||||||
},
|
},
|
||||||
"boat": {
|
"boat": {
|
||||||
"boatid": cfg['uuid'],
|
"boatid": self.boatid,
|
||||||
"sailno": boat['sailno'],
|
"sailno": self.sailno,
|
||||||
"team": boat['team'],
|
"team": self.team,
|
||||||
"boatclass": boat['class'],
|
"boatclass": self.boatclass,
|
||||||
"handicap": boat['handicap'],
|
"handicap": self.handicap,
|
||||||
"club": boat['club'],
|
"club": self.club,
|
||||||
"boatname": boat['name'],
|
"boatname": self.boatname,
|
||||||
"isTracking": True,
|
"isTracking": True,
|
||||||
"hasGivenUp": False
|
"hasGivenUp": False
|
||||||
},
|
},
|
||||||
|
@ -270,12 +457,12 @@ class Tracker():
|
||||||
bv_lon = boatdata.getRef("LON")
|
bv_lon = boatdata.getRef("LON")
|
||||||
bv_sog = boatdata.getRef("SOG")
|
bv_sog = boatdata.getRef("SOG")
|
||||||
|
|
||||||
client.loop_start()
|
self.client.loop_start()
|
||||||
while not appdata.shutdown:
|
while not appdata.shutdown:
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
if self.activated and self.hero_raceid is not None:
|
if self.activated and self.hero_raceid is not None:
|
||||||
self.mqtt_publish(client, topic, payload, bv_lat, bv_lon, bv_sog)
|
self.mqtt_publish(topic, payload, bv_lat, bv_lon, bv_sog)
|
||||||
client.loop_stop()
|
self.client.loop_stop()
|
||||||
client.disconnect()
|
self.client.disconnect()
|
||||||
if cfg['trace']:
|
if cfg['trace']:
|
||||||
self.trace_fh.close()
|
self.trace_fh.close()
|
||||||
|
|
Loading…
Reference in New Issue