Logging und Tracker GUI weiterprogrammiert
This commit is contained in:
		
							parent
							
								
									d3a984075e
								
							
						
					
					
						commit
						eab6cdf4c9
					
				| 
						 | 
				
			
			@ -8,9 +8,10 @@ from tracker import Tracker
 | 
			
		|||
 | 
			
		||||
class AppData():
 | 
			
		||||
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
    def __init__(self, logger, cfg):
 | 
			
		||||
        self.shutdown = False # Globaler Ausschalter
 | 
			
		||||
        self.track = Tracker('NONE')
 | 
			
		||||
        self.log = logger
 | 
			
		||||
        self.track = Tracker(logger, cfg)
 | 
			
		||||
        self.frontend = None
 | 
			
		||||
        self.bv_lat = None
 | 
			
		||||
        self.bv_lon = None
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 291 B  | 
| 
						 | 
				
			
			@ -40,16 +40,20 @@ config = ~/.opencpn/opencpn.conf
 | 
			
		|||
[tracker]
 | 
			
		||||
type = NONE
 | 
			
		||||
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_pass = 123456
 | 
			
		||||
orgname = demo
 | 
			
		||||
passcode = 123456
 | 
			
		||||
trace = false
 | 
			
		||||
 | 
			
		||||
[boat]
 | 
			
		||||
name = My boat
 | 
			
		||||
sailno = GER 4711
 | 
			
		||||
sailno = GER 815
 | 
			
		||||
class = One off
 | 
			
		||||
handicap = 100.0
 | 
			
		||||
club = NONE
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										104
									
								
								obp60v.py
								
								
								
								
							
							
						
						
									
										104
									
								
								obp60v.py
								
								
								
								
							| 
						 | 
				
			
			@ -124,7 +124,9 @@ cfg = {
 | 
			
		|||
    'cfgfile': 'obp60v.conf',
 | 
			
		||||
    'logdir': '~/.local/share/obp60v',
 | 
			
		||||
    'logfile': 'obp60v.log',
 | 
			
		||||
    'loglevel': 3,
 | 
			
		||||
    'imgpath': os.path.join(sys.path[0], 'images'),
 | 
			
		||||
    'audiopath': os.path.join(sys.path[0], 'audio'),
 | 
			
		||||
    'deviceid': 100,
 | 
			
		||||
    'manufcode': 2046,  # Open Boat Projects (OBP)
 | 
			
		||||
    'devfunc': 120,     # Display
 | 
			
		||||
| 
						 | 
				
			
			@ -212,7 +214,7 @@ def rxd_gps(devname, devspeed):
 | 
			
		|||
    try:
 | 
			
		||||
        ser = serial.Serial(devname, devspeed, timeout=3)
 | 
			
		||||
    except serial.SerialException as e:
 | 
			
		||||
        print("GPS serial port not available")
 | 
			
		||||
        log.error("GPS serial port not available")
 | 
			
		||||
        return
 | 
			
		||||
    setthreadtitle("GPSlistener")
 | 
			
		||||
    while not appdata.shutdown:
 | 
			
		||||
| 
						 | 
				
			
			@ -624,7 +626,7 @@ def init_profile(config, cfg, boatdata):
 | 
			
		|||
            cls = getattr(pages, p['type'])
 | 
			
		||||
        except AttributeError:
 | 
			
		||||
            # Klasse nicht vorhanden, Seite wird nicht benutzt
 | 
			
		||||
            print(f"Klasse '{p['type']}' nicht gefunden")
 | 
			
		||||
            log.error(f"Klasse '{p['type']}' nicht gefunden")
 | 
			
		||||
            continue
 | 
			
		||||
        c = cls(i, cfg, appdata, boatdata, *[v for v in p['values'].values()])
 | 
			
		||||
        clist[i] = c
 | 
			
		||||
| 
						 | 
				
			
			@ -643,7 +645,7 @@ def set_loglevel(nr):
 | 
			
		|||
        nr = 0
 | 
			
		||||
    return level[nr]
 | 
			
		||||
 | 
			
		||||
def init_logging(logdir, logfile='obp60v.log'):
 | 
			
		||||
def init_logging(logdir, logfile='obp60v.log', loglevel=logging.INFO):
 | 
			
		||||
    global log
 | 
			
		||||
    os.makedirs(logdir, exist_ok=True)
 | 
			
		||||
    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')
 | 
			
		||||
    hdlr.setFormatter(formatter)
 | 
			
		||||
    log.addHandler(hdlr)
 | 
			
		||||
    log.setLevel(logging.INFO)
 | 
			
		||||
    log.setLevel(loglevel)
 | 
			
		||||
    console = logging.StreamHandler()
 | 
			
		||||
    console.setFormatter(logging.Formatter('%(levelname)s:%(message)s'))
 | 
			
		||||
    console.setLevel(logging.INFO)
 | 
			
		||||
    console.setLevel(loglevel)
 | 
			
		||||
    log.addHandler(console)
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
 | 
			
		||||
    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
 | 
			
		||||
    config = configparser.ConfigParser()
 | 
			
		||||
    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']))
 | 
			
		||||
        sys.exit(1)
 | 
			
		||||
    cfg['_config'] = config # Objekt zum späteren schreiben
 | 
			
		||||
    cfg['loglevel'] = config.getint('system', 'loglevel')
 | 
			
		||||
    cfg['deviceid'] = config.getint('system', 'deviceid')
 | 
			
		||||
    cfg['simulation'] = config.getboolean('system', 'simulation')
 | 
			
		||||
    cfg['histpath'] = os.path.expanduser(config.get('system', 'histpath'))
 | 
			
		||||
    cfg['guistyle'] = config.get('system', 'guistyle')
 | 
			
		||||
    print("Setting GUI style to '{}'".format(cfg['guistyle']))
 | 
			
		||||
    cfg['mouseptr'] = config.getboolean('system', 'mouseptr')
 | 
			
		||||
    try:
 | 
			
		||||
        cfg['win_x'] = config.getint('gui', 'win_x')
 | 
			
		||||
| 
						 | 
				
			
			@ -724,13 +712,15 @@ if __name__ == "__main__":
 | 
			
		|||
        boatdata.addHistory(history, "press")
 | 
			
		||||
 | 
			
		||||
    # 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']['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_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']['trace'] =  config.getboolean('tracker', 'trace')
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -742,55 +732,76 @@ if __name__ == "__main__":
 | 
			
		|||
    cfg['boat']['club'] = config.get('boat', 'club')
 | 
			
		||||
    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
 | 
			
		||||
    init_logging(os.path.expanduser(cfg['logdir']), cfg['logfile'])
 | 
			
		||||
    log.info("Logging initialized")
 | 
			
		||||
    loglevel = set_loglevel(cfg['loglevel'])
 | 
			
		||||
    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
 | 
			
		||||
    profile = init_profile(config, cfg, boatdata)
 | 
			
		||||
 | 
			
		||||
    # Schnittstellen aktivieren
 | 
			
		||||
    # Schnittstellen aktivieren, jew. eigener Thread
 | 
			
		||||
    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.start()
 | 
			
		||||
    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.start()
 | 
			
		||||
    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.start()
 | 
			
		||||
    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.start()
 | 
			
		||||
    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.start()
 | 
			
		||||
    if not cfg['simulation']:
 | 
			
		||||
        if cfg['bme280']:
 | 
			
		||||
            log.info("Environment sensor enabled")
 | 
			
		||||
            t_data = threading.Thread(target=datareader, args=(cfg, history))
 | 
			
		||||
            t_data.start()
 | 
			
		||||
    else:
 | 
			
		||||
        print("Simulation mode enabled")
 | 
			
		||||
        log.info("Simulation mode enabled")
 | 
			
		||||
 | 
			
		||||
    app = Frontend(cfg, appdata, owndevice, boatdata, profile)
 | 
			
		||||
    app.run()
 | 
			
		||||
| 
						 | 
				
			
			@ -808,5 +819,6 @@ if __name__ == "__main__":
 | 
			
		|||
    if not cfg['simulation'] and cfg['bme280']:
 | 
			
		||||
        t_data.join()
 | 
			
		||||
 | 
			
		||||
    log.info("Client terminated")
 | 
			
		||||
    print(boatdata)
 | 
			
		||||
    print("Another fine product of the Sirius Cybernetics Corporation.")
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -32,10 +32,10 @@ from .dst810 import DST810
 | 
			
		|||
from .epropulsion import EPropulsion
 | 
			
		||||
from .keel import Keel
 | 
			
		||||
from .mob import MOB
 | 
			
		||||
from .racetracker import RaceTracker
 | 
			
		||||
from .rollpitch import RollPitch
 | 
			
		||||
from .skyview import SkyView
 | 
			
		||||
from .solar import Solar
 | 
			
		||||
from .tracker import Tracker
 | 
			
		||||
from .rudder import Rudder
 | 
			
		||||
from .voltage import Voltage
 | 
			
		||||
from .wind import Wind
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -68,7 +68,7 @@ class Page():
 | 
			
		|||
        self.pageno = pageno
 | 
			
		||||
        self.cfg = cfg
 | 
			
		||||
        self.fullscreen = cfg['guistyle'] == 'fullscreen'
 | 
			
		||||
        self.appdata = appdata
 | 
			
		||||
        self.app = appdata
 | 
			
		||||
        self.bd = boatdata
 | 
			
		||||
        self.header = True
 | 
			
		||||
        self.footer = True
 | 
			
		||||
| 
						 | 
				
			
			@ -142,7 +142,7 @@ class Page():
 | 
			
		|||
        ctx.select_font_face("Ubuntu", cairo.FontSlant.NORMAL, cairo.FontWeight.BOLD)
 | 
			
		||||
        ctx.set_font_size(16)
 | 
			
		||||
        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()
 | 
			
		||||
 | 
			
		||||
        # Tastenstatus
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,7 +25,7 @@ import os
 | 
			
		|||
import cairo
 | 
			
		||||
from .page import Page
 | 
			
		||||
 | 
			
		||||
class Tracker(Page):
 | 
			
		||||
class RaceTracker(Page):
 | 
			
		||||
 | 
			
		||||
    def __init__(self, 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_sog = boatdata.getRef("SOG")
 | 
			
		||||
        self.races = None
 | 
			
		||||
        self.raceid = None # Ausgewählte Regatta
 | 
			
		||||
        self.raceid = self.app.track.hero_raceid # Ausgewählte Regatta
 | 
			
		||||
        self.menupos = 0
 | 
			
		||||
        self.buttonlabel[1] = 'MODE'
 | 
			
		||||
        self.buttonlabel[2] = 'INFO'
 | 
			
		||||
| 
						 | 
				
			
			@ -45,9 +45,9 @@ class Tracker(Page):
 | 
			
		|||
 | 
			
		||||
        # Flaggen laden
 | 
			
		||||
        flag = ('alpha', 'answer', 'black', 'blue', 'charlie', 'class',
 | 
			
		||||
                'finish', 'hotel', 'india', 'november', 'orange', 
 | 
			
		||||
                'papa', 'repeat_one', 'sierra', 'start', 'uniform',
 | 
			
		||||
                'xray', 'yankee', 'zulu')
 | 
			
		||||
                'finish', 'foxtrot', 'hotel', 'india', 'november',
 | 
			
		||||
                'orange', 'papa', 'repeat_one', 'sierra', 'start',
 | 
			
		||||
                'uniform', 'xray', 'yankee', 'zulu')
 | 
			
		||||
        # Mapping
 | 
			
		||||
        self.flagmap = {
 | 
			
		||||
              3: 'blue',        # Zielflagge
 | 
			
		||||
| 
						 | 
				
			
			@ -81,7 +81,7 @@ class Tracker(Page):
 | 
			
		|||
                self.buttonlabel[2] = '#UP'
 | 
			
		||||
                self.buttonlabel[3] = '#DOWN'
 | 
			
		||||
                self.buttonlabel[4] = 'SET'
 | 
			
		||||
                if self.appdata.track.is_active():
 | 
			
		||||
                if self.app.track.is_active():
 | 
			
		||||
                    self.buttonlabel[5] = 'OFF'
 | 
			
		||||
                else:
 | 
			
		||||
                    self.buttonlabel[5] = 'ON'
 | 
			
		||||
| 
						 | 
				
			
			@ -118,38 +118,33 @@ class Tracker(Page):
 | 
			
		|||
                # Set / Select regatta
 | 
			
		||||
                if self.menupos > 0:
 | 
			
		||||
                    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}'")
 | 
			
		||||
                return True
 | 
			
		||||
        elif buttonid == 5:
 | 
			
		||||
            if self.mode == 'C':
 | 
			
		||||
                # Tracking ein/-ausschalten
 | 
			
		||||
                if self.appdata.track.is_active():
 | 
			
		||||
                    self.appdata.track.set_active(False)
 | 
			
		||||
                if self.app.track.is_active():
 | 
			
		||||
                    self.app.track.set_active(False)
 | 
			
		||||
                    self.buttonlabel[5] = 'ON'
 | 
			
		||||
                else:
 | 
			
		||||
                    self.appdata.track.set_active(True)
 | 
			
		||||
                    self.app.track.set_active(True)
 | 
			
		||||
                    self.buttonlabel[5] = 'OFF'
 | 
			
		||||
            elif self.mode == 'M':
 | 
			
		||||
                self.appdata.frontend.flashled.setColor('yellow')
 | 
			
		||||
                #self.appdata.frontend.flashled.switchOn(4)
 | 
			
		||||
                self.appdata.frontend.flashled.doFlash(2)
 | 
			
		||||
                self.app.frontend.flashled.setColor('yellow')
 | 
			
		||||
                #self.app.frontend.flashled.switchOn(4)
 | 
			
		||||
                self.app.frontend.flashled.doFlash(2)
 | 
			
		||||
            return True
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
    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.set_font_size(80)
 | 
			
		||||
 | 
			
		||||
        if self.appdata.track.is_active():
 | 
			
		||||
            if self.appdata.track.hero_racestatus:
 | 
			
		||||
                counter = self.appdata.track.hero_racestatus['time']
 | 
			
		||||
        if self.app.track.is_active():
 | 
			
		||||
            if self.app.track.hero_racestatus:
 | 
			
		||||
                counter = self.app.track.hero_racestatus['time']
 | 
			
		||||
                minutes, seconds = divmod(abs(counter), 60)
 | 
			
		||||
                if counter < 0:
 | 
			
		||||
                    ctx.move_to(16, 120)
 | 
			
		||||
| 
						 | 
				
			
			@ -164,11 +159,11 @@ class Tracker(Page):
 | 
			
		|||
            ctx.move_to(100, 120)
 | 
			
		||||
            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.set_font_size(16)
 | 
			
		||||
            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
 | 
			
		||||
        x1 = 96
 | 
			
		||||
| 
						 | 
				
			
			@ -179,20 +174,20 @@ class Tracker(Page):
 | 
			
		|||
        ctx.move_to(x0, y0)
 | 
			
		||||
        ctx.show_text("Type")
 | 
			
		||||
        ctx.move_to(x1, y0)
 | 
			
		||||
        ctx.show_text(self.appdata.track.ttype)
 | 
			
		||||
        ctx.show_text(self.app.track.ttype)
 | 
			
		||||
 | 
			
		||||
        y0 += yoffset
 | 
			
		||||
        ctx.move_to(x0, y0)
 | 
			
		||||
        ctx.show_text("Regatta")
 | 
			
		||||
        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
 | 
			
		||||
        ctx.move_to(x0, y0)
 | 
			
		||||
        ctx.show_text("Course")
 | 
			
		||||
        ctx.move_to(x1, y0)
 | 
			
		||||
        if self.appdata.track.hero_orgstatus and self.appdata.track.hero_raceid:
 | 
			
		||||
            ctx.show_text(self.appdata.track.hero_orgstatus['races'][self.appdata.track.hero_raceid]['courseid'])
 | 
			
		||||
        if self.app.track.hero_orgstatus and self.app.track.hero_raceid:
 | 
			
		||||
            ctx.show_text(self.app.track.hero_orgstatus['races'][self.app.track.hero_raceid]['courseid'])
 | 
			
		||||
        else:
 | 
			
		||||
            ctx.show_text('[not selected]')
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -215,9 +210,9 @@ class Tracker(Page):
 | 
			
		|||
        ctx.show_text(self.bv_sog.format())
 | 
			
		||||
 | 
			
		||||
        # Flaggen
 | 
			
		||||
        if self.appdata.track.hero_racestatus:
 | 
			
		||||
        if self.app.track.hero_racestatus:
 | 
			
		||||
            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:
 | 
			
		||||
                    # TODO Context save/restore erforderlich?
 | 
			
		||||
                    ctx.save()
 | 
			
		||||
| 
						 | 
				
			
			@ -234,14 +229,19 @@ class Tracker(Page):
 | 
			
		|||
        ctx.move_to(4, 42)
 | 
			
		||||
        ctx.show_text("Tracker configuration")
 | 
			
		||||
 | 
			
		||||
        x0 = 8
 | 
			
		||||
        x1 = 88
 | 
			
		||||
        y0 = 96
 | 
			
		||||
        # Linke Spalte mit Daten
 | 
			
		||||
 | 
			
		||||
        x0 = 8  # Labelspalte
 | 
			
		||||
        x1 = 88 # Datenspalte
 | 
			
		||||
        y0 = 75
 | 
			
		||||
        yoffset = 16
 | 
			
		||||
 | 
			
		||||
        # Bootsdaten
 | 
			
		||||
        ctx.set_font_size(20)
 | 
			
		||||
        ctx.move_to(x0, 75)
 | 
			
		||||
        ctx.move_to(x0, y0)
 | 
			
		||||
        ctx.show_text("Boat data")
 | 
			
		||||
 | 
			
		||||
        y0 += yoffset + 5
 | 
			
		||||
        ctx.set_font_size(16)
 | 
			
		||||
 | 
			
		||||
        ctx.move_to(x0, y0)
 | 
			
		||||
| 
						 | 
				
			
			@ -249,48 +249,50 @@ class Tracker(Page):
 | 
			
		|||
        ctx.move_to(x1, y0)
 | 
			
		||||
        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.move_to(x1, y0 + 16)
 | 
			
		||||
        ctx.move_to(x1, y0)
 | 
			
		||||
        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.move_to(x1, y0 + 32)
 | 
			
		||||
        ctx.move_to(x1, y0)
 | 
			
		||||
        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.move_to(x1, y0 + 48)
 | 
			
		||||
        ctx.move_to(x1, y0)
 | 
			
		||||
        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.move_to(x1, y0 + 64)
 | 
			
		||||
        ctx.move_to(x1, y0)
 | 
			
		||||
        ctx.show_text(self.cfg['boat']['sailno'])
 | 
			
		||||
 | 
			
		||||
        x0 = 208
 | 
			
		||||
        x1 = 272
 | 
			
		||||
        y0 = 96
 | 
			
		||||
        yoffset = 16
 | 
			
		||||
 | 
			
		||||
        # Trackerdaten
 | 
			
		||||
        y0 += yoffset + 10
 | 
			
		||||
        ctx.set_font_size(20)
 | 
			
		||||
        ctx.move_to(x0, 75)
 | 
			
		||||
        ctx.move_to(x0, y0)
 | 
			
		||||
        ctx.show_text("Tracker info")
 | 
			
		||||
 | 
			
		||||
        y0 += yoffset + 5
 | 
			
		||||
        ctx.set_font_size(16)
 | 
			
		||||
 | 
			
		||||
        ctx.move_to(x0, y0)
 | 
			
		||||
        ctx.show_text("Type")
 | 
			
		||||
        ctx.move_to(x1, y0)
 | 
			
		||||
        ctx.show_text(self.appdata.track.ttype)
 | 
			
		||||
        ctx.show_text(self.app.track.ttype)
 | 
			
		||||
 | 
			
		||||
        y0 += yoffset
 | 
			
		||||
        ctx.move_to(x0, y0)
 | 
			
		||||
        ctx.show_text("Org.")
 | 
			
		||||
        ctx.move_to(x1, y0)
 | 
			
		||||
        if self.appdata.track.hero_orgstatus:
 | 
			
		||||
            ctx.show_text(self.appdata.track.hero_orgstatus['orgname'])
 | 
			
		||||
        if self.app.track.hero_orgstatus:
 | 
			
		||||
            ctx.show_text(self.app.track.hero_orgstatus['orgname'])
 | 
			
		||||
        else:
 | 
			
		||||
            ctx.show_text("n/a")
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -298,7 +300,7 @@ class Tracker(Page):
 | 
			
		|||
        ctx.move_to(x0, y0)
 | 
			
		||||
        ctx.show_text("Status")
 | 
			
		||||
        ctx.move_to(x1, y0)
 | 
			
		||||
        if not self.appdata.track.hero_racestatus:
 | 
			
		||||
        if not self.app.track.hero_racestatus:
 | 
			
		||||
            ctx.show_text("inactive")
 | 
			
		||||
        else:
 | 
			
		||||
            #TODO Mehr Details
 | 
			
		||||
| 
						 | 
				
			
			@ -308,18 +310,29 @@ class Tracker(Page):
 | 
			
		|||
        ctx.move_to(x0, y0)
 | 
			
		||||
        ctx.show_text("Team")
 | 
			
		||||
        ctx.move_to(x1, y0)
 | 
			
		||||
        if self.appdata.track.hero_racestatus:
 | 
			
		||||
            ctx.show_text(self.appdata.track.hero_racestatus['team'])
 | 
			
		||||
        ctx.show_text(self.app.track.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:
 | 
			
		||||
            ctx.show_text("n/a")
 | 
			
		||||
            ctx.show_text("offline")
 | 
			
		||||
 | 
			
		||||
        # Rechte Spalte mit Regattaauswahl
 | 
			
		||||
 | 
			
		||||
        x = 208
 | 
			
		||||
        y = 75
 | 
			
		||||
        yoffset = 16
 | 
			
		||||
 | 
			
		||||
        # Mögliche Regatten
 | 
			
		||||
        self.races = self.appdata.track.hero_get_races()
 | 
			
		||||
        self.races = self.app.track.hero_get_races()
 | 
			
		||||
        if len(self.races) == 1:
 | 
			
		||||
            self.raceid = self.races[0]
 | 
			
		||||
            self.hero_raceid = self.races[0]
 | 
			
		||||
            self.menupos = 1
 | 
			
		||||
        x = 208
 | 
			
		||||
        y = 180
 | 
			
		||||
 | 
			
		||||
        ctx.set_font_size(20)
 | 
			
		||||
        ctx.move_to(x, y)
 | 
			
		||||
        ctx.show_text("Select Regatta")
 | 
			
		||||
| 
						 | 
				
			
			@ -333,7 +346,7 @@ class Tracker(Page):
 | 
			
		|||
        for r in self.races:
 | 
			
		||||
            i += 1
 | 
			
		||||
            if r == self.raceid:
 | 
			
		||||
                r += '*'
 | 
			
		||||
                r = f"\xbb {r} \xab"
 | 
			
		||||
            self.draw_text_boxed(ctx, x, y, 180, 20, r, (self.menupos == i))
 | 
			
		||||
            y += 20
 | 
			
		||||
        if i == 0:
 | 
			
		||||
| 
						 | 
				
			
			@ -350,11 +363,11 @@ class Tracker(Page):
 | 
			
		|||
        ctx.show_text("Message from race officers")
 | 
			
		||||
 | 
			
		||||
        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.show_text("[ empty ]")
 | 
			
		||||
        else:
 | 
			
		||||
            lines = self.appdata.track.hero_orgstatus['message'].splitlines()
 | 
			
		||||
            lines = self.app.track.hero_orgstatus['message'].splitlines()
 | 
			
		||||
            y = 72
 | 
			
		||||
            for l in lines:
 | 
			
		||||
                ctx.move_to(8, y)
 | 
			
		||||
							
								
								
									
										323
									
								
								tracker.py
								
								
								
								
							
							
						
						
									
										323
									
								
								tracker.py
								
								
								
								
							| 
						 | 
				
			
			@ -14,49 +14,87 @@ Wiederherstellung der Verbindung übertragen.
 | 
			
		|||
 | 
			
		||||
TODO
 | 
			
		||||
- Nach einem Disconnect manuelle Neuverbindung ermöglichen
 | 
			
		||||
- Audioausgabe für Regatta Hero
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import os
 | 
			
		||||
import time
 | 
			
		||||
import paho.mqtt.client as mqtt
 | 
			
		||||
import http.client
 | 
			
		||||
import ssl
 | 
			
		||||
import json
 | 
			
		||||
import socket
 | 
			
		||||
import ssl
 | 
			
		||||
import math
 | 
			
		||||
import subprocess # für Audioausgabe / mpg123
 | 
			
		||||
 | 
			
		||||
class Tracker():
 | 
			
		||||
 | 
			
		||||
    def __init__(self, trackertype='NONE'):
 | 
			
		||||
        self.ttype = 'NONE'
 | 
			
		||||
        self.set_type(trackertype)
 | 
			
		||||
 | 
			
		||||
    def __init__(self, logger, cfg):
 | 
			
		||||
        self.log = logger
 | 
			
		||||
        self.appdata = None
 | 
			
		||||
 | 
			
		||||
        self.activated = False
 | 
			
		||||
        self.trace = False   # Debugging
 | 
			
		||||
        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.races = set()   # Liste der Regatten, eindeutige Namen
 | 
			
		||||
        self.courses = set() # Liste der Bahnen, eindeutige Namen
 | 
			
		||||
        self.buoys = {}      # Tonnen (Hero=20)
 | 
			
		||||
        self.courses = []    # Bahnen
 | 
			
		||||
        self.races = []      # Regatten
 | 
			
		||||
 | 
			
		||||
        self.mqtt_connected = False
 | 
			
		||||
        self.activated = False
 | 
			
		||||
        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_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_racestatus = None
 | 
			
		||||
        self.hero_raceid = None  # Aktuelle Regatta
 | 
			
		||||
        self.hero_timedelta = 0 # Zeitdifferenz zum Server in sec
 | 
			
		||||
 | 
			
		||||
        # TODO Wirklich alles im Tracker oder ist einiges generisch?
 | 
			
		||||
        self.boatid = None
 | 
			
		||||
        self.sailno = None
 | 
			
		||||
        self.boatname = None
 | 
			
		||||
        self.boatclass = None
 | 
			
		||||
        self.handicap = None
 | 
			
		||||
        self.club = None
 | 
			
		||||
        self.team = None
 | 
			
		||||
        # Hole erste Daten vom Server
 | 
			
		||||
        self.hero_query_org()
 | 
			
		||||
 | 
			
		||||
    def is_server_active(self, hostname, port):
 | 
			
		||||
        """
 | 
			
		||||
| 
						 | 
				
			
			@ -81,12 +119,157 @@ class Tracker():
 | 
			
		|||
    def set_hero_raceid(self, newraceid):
 | 
			
		||||
        self.hero_raceid = newraceid
 | 
			
		||||
 | 
			
		||||
    def set_type(self, newtype):
 | 
			
		||||
        validtypes = ('HERO', 'SDCARD', 'SERVER', 'NONE')
 | 
			
		||||
        newtype = newtype.upper() 
 | 
			
		||||
        if newtype not in validtypes:
 | 
			
		||||
            raise TypeError(f"Invalid tracker type: '{newtype}'. Only supported: {validtypes}")
 | 
			
		||||
        self.ttype = newtype
 | 
			
		||||
    #def set_type(self, newtype):
 | 
			
		||||
    #    validtypes = ('HERO', 'SDCARD', 'SERVER', 'LOCAL', 'NONE')
 | 
			
		||||
    #    newtype = newtype.upper() 
 | 
			
		||||
    #    if newtype not in validtypes:
 | 
			
		||||
    #        raise TypeError(f"Invalid tracker type: '{newtype}'. Only supported: {validtypes}")
 | 
			
		||||
    #    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):
 | 
			
		||||
        # Positionsabfrage für die Payload
 | 
			
		||||
| 
						 | 
				
			
			@ -109,21 +292,24 @@ class Tracker():
 | 
			
		|||
        return 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:
 | 
			
		||||
        self.mqtt_connected = False
 | 
			
		||||
        if rc == 0:
 | 
			
		||||
            self.log.info(f"MQTT connected, subscribing to topics")
 | 
			
		||||
            client.subscribe("regattahero/orgstatus/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):
 | 
			
		||||
        """
 | 
			
		||||
| 
						 | 
				
			
			@ -147,7 +333,7 @@ class Tracker():
 | 
			
		|||
            self.hero_timedelta = abs(sec1 - sec2)
 | 
			
		||||
 | 
			
		||||
            if self.hero_orgstatus['allLogout']:
 | 
			
		||||
                print("All logout received!")
 | 
			
		||||
                self.log.info("All logout received!")
 | 
			
		||||
                client.disconnect()
 | 
			
		||||
                self.activated = False
 | 
			
		||||
                return
 | 
			
		||||
| 
						 | 
				
			
			@ -180,8 +366,8 @@ class Tracker():
 | 
			
		|||
 | 
			
		||||
            """
 | 
			
		||||
        else:
 | 
			
		||||
            print(f"UNKNOWN TOPIC: {msg.topic}")
 | 
			
		||||
            print(msg.payload)
 | 
			
		||||
            self.log.warning(f"UNKNOWN TOPIC: {msg.topic}")
 | 
			
		||||
            self.log.debug(msg.payload)
 | 
			
		||||
 | 
			
		||||
    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())
 | 
			
		||||
            client.publish(topic, json.dumps(payload))
 | 
			
		||||
        else:
 | 
			
		||||
            #print("No GPS data available. Nothing published!")
 | 
			
		||||
            pass
 | 
			
		||||
            self.log.debug("No GPS data available. Nothing published!")
 | 
			
		||||
 | 
			
		||||
    def mqtt_tracker(self, cfg, boat, appdata, boatdata):
 | 
			
		||||
        print("MQTT tracker enabled")
 | 
			
		||||
        self.log.info("MQTT tracker enabled")
 | 
			
		||||
        self.appdata = appdata
 | 
			
		||||
        self.boatid = cfg['uuid']
 | 
			
		||||
        """
 | 
			
		||||
        self.boatid = boat['uuid']
 | 
			
		||||
        self.sailno =  boat['sailno']
 | 
			
		||||
        self.boatname = boat['name']
 | 
			
		||||
        self.boatclass = boat['class']
 | 
			
		||||
        self.handicap = boat['handicap']
 | 
			
		||||
        self.club = boat['club']
 | 
			
		||||
        self.team =  boat['team'] # TODO eher zu Tracker gehörig?
 | 
			
		||||
        self.team =  boat['team']
 | 
			
		||||
        self.trace = cfg['trace']
 | 
			
		||||
        self.hero_orgid = cfg['orgname']
 | 
			
		||||
        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'])
 | 
			
		||||
        self.hero_orgid = cfg['username']
 | 
			
		||||
        """
 | 
			
		||||
        self.client.username_pw_set(username=cfg['mqtt_user'], password=cfg['mqtt_pass'])
 | 
			
		||||
        try:
 | 
			
		||||
            client.connect(cfg['host'], cfg['port'], 60)
 | 
			
		||||
            self.client.connect(cfg['mqtt_host'], cfg['mqtt_port'], 60)
 | 
			
		||||
        except ConnectionRefusedError:
 | 
			
		||||
            print("MQTT connection refused. Check username and password.")
 | 
			
		||||
            self.log.error("MQTT connection refused. Check username and password.")
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        if cfg['trace']:
 | 
			
		||||
            # TODO Log Hinweis
 | 
			
		||||
        # Initial die Organisationsdaten abfragen um Tonnen und Kurse zu erhalten
 | 
			
		||||
       # 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')
 | 
			
		||||
            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 = {
 | 
			
		||||
            "passcode": cfg['passcode'],
 | 
			
		||||
            "orgid":  cfg['orgname'],
 | 
			
		||||
            "passcode": cfg['password'],
 | 
			
		||||
            "orgid": self.hero_orgid,
 | 
			
		||||
            "raceid": None, # Nach Auswahl einstellen
 | 
			
		||||
            "gps": {
 | 
			
		||||
                "lat": 0.0,
 | 
			
		||||
| 
						 | 
				
			
			@ -246,13 +433,13 @@ class Tracker():
 | 
			
		|||
                "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'],
 | 
			
		||||
                "boatid": self.boatid,
 | 
			
		||||
                "sailno": self.sailno,
 | 
			
		||||
                "team": self.team,
 | 
			
		||||
                "boatclass": self.boatclass,
 | 
			
		||||
                "handicap": self.handicap,
 | 
			
		||||
                "club": self.club,
 | 
			
		||||
                "boatname": self.boatname,
 | 
			
		||||
                "isTracking": True,
 | 
			
		||||
                "hasGivenUp": False
 | 
			
		||||
                },
 | 
			
		||||
| 
						 | 
				
			
			@ -270,12 +457,12 @@ class Tracker():
 | 
			
		|||
        bv_lon = boatdata.getRef("LON")
 | 
			
		||||
        bv_sog = boatdata.getRef("SOG")
 | 
			
		||||
 | 
			
		||||
        client.loop_start()
 | 
			
		||||
        self.client.loop_start()
 | 
			
		||||
        while not appdata.shutdown:
 | 
			
		||||
            time.sleep(1)
 | 
			
		||||
            if self.activated and self.hero_raceid is not None:
 | 
			
		||||
                self.mqtt_publish(client, topic, payload, bv_lat, bv_lon, bv_sog)
 | 
			
		||||
        client.loop_stop()
 | 
			
		||||
        client.disconnect()
 | 
			
		||||
                self.mqtt_publish(topic, payload, bv_lat, bv_lon, bv_sog)
 | 
			
		||||
        self.client.loop_stop()
 | 
			
		||||
        self.client.disconnect()
 | 
			
		||||
        if cfg['trace']:
 | 
			
		||||
            self.trace_fh.close()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue