Tracker in eigene Klasse und AppData eingeführt
This commit is contained in:
		
							parent
							
								
									fd673d5e55
								
							
						
					
					
						commit
						acbcfac425
					
				|  | @ -0,0 +1,10 @@ | |||
| """ | ||||
| Generische Applikationsdaten | ||||
| 
 | ||||
| """ | ||||
| from tracker import Tracker | ||||
| 
 | ||||
| class AppData(): | ||||
|     def __init__(self): | ||||
|         self.shutdown = False # Globaler Ausschalter | ||||
|         self.track = Tracker('NONE') | ||||
							
								
								
									
										171
									
								
								obp60v.py
								
								
								
								
							
							
						
						
									
										171
									
								
								obp60v.py
								
								
								
								
							|  | @ -104,12 +104,14 @@ import time | |||
| from datetime import datetime | ||||
| from nmea2000 import Device, BoatData, History, HistoryBuffer | ||||
| from nmea2000 import parser | ||||
| import nmea0183 | ||||
| import pages | ||||
| import struct | ||||
| import uuid | ||||
| import json | ||||
| 
 | ||||
| import nmea0183 | ||||
| from appdata import AppData | ||||
| import pages | ||||
| 
 | ||||
| __author__ = "Thomas Hooge" | ||||
| __copyright__ = "Copyleft 2024-2025, all rights reversed" | ||||
| __version__ = "0.2" | ||||
|  | @ -118,6 +120,8 @@ __status__ = "Development" | |||
| 
 | ||||
| cfg = { | ||||
|     'cfgfile': 'obp60v.conf', | ||||
|     'logdir': '~/.local/share/obp60v', | ||||
|     'logfile': 'obp60v.log', | ||||
|     'imgpath': os.path.join(sys.path[0], 'images'), | ||||
|     'deviceid': 100, | ||||
|     'manufcode': 2046,  # Open Boat Projects (OBP) | ||||
|  | @ -130,136 +134,13 @@ cfg = { | |||
|     'boat': { } | ||||
| } | ||||
| 
 | ||||
| def mqtt_on_connect(client, userdata, flags, rc): | ||||
|     print(f"MQTT connected with result code {rc}") | ||||
|     #userdata['connect_rc'] = rc | ||||
|     if rc != 0: | ||||
|         # Result codes: | ||||
|         # 1: Connection Refused, unacceptable protocol version | ||||
|         # 2: Connection Refused, identifier rejected | ||||
|         # 3: Connection Refused, Server unavailable | ||||
|         # 4: Connection Refused, bad user name or password | ||||
|         # 5: Connection Refused, not authorized | ||||
|         #userdata['connect_ok'] = True | ||||
|         pass | ||||
|     else: | ||||
|         client.subscribe("regattahero/orgstatus/thomas") | ||||
|         client.subscribe("regattahero/racestatus/thomas/#") | ||||
|         #userdata['connect_ok'] = False | ||||
| 
 | ||||
| def mqtt_on_message(client, userdata, msg): | ||||
|     """ | ||||
|     TODO raceid über userdata? dann topic prüfen? | ||||
|     """ | ||||
|     if msg.topic == "regattahero/orgstatus/thomas": | ||||
|         # kommt alle 10s | ||||
|         orgstatus = json.loads(msg.payload) | ||||
|         if orgstatus['allLogout']: | ||||
|             print("All logout received!") | ||||
|             client.disconnect() | ||||
|             sys.exit(0) # TODO nur die MQTT-Task beenden | ||||
|         if orgstatus['message']: | ||||
|             # TODO Alarm-Funktion nutzen? | ||||
|             print("Nachricht der Wettfahrtkeitung:") | ||||
|             print(orgstatus['message']) | ||||
|         print(orgstatus['races']) | ||||
|         #for r in orgstatus['races']: | ||||
|         #    print(f"Race: {r}") | ||||
|     elif msg.topic.startswith("regattahero/racestatus/thomas"): | ||||
|         # kommt alle 1s | ||||
|         # dem Topic angehängt ist noch die raceid | ||||
|         payload = json.loads(msg.payload) | ||||
|         racestatus = payload['racestatus'] | ||||
|         """ | ||||
|         time: negativ: Zeit vor dem Start, positiv: Zeit nach dem Start | ||||
|               in Sekunden | ||||
|          | ||||
|         Signale der Wettfahrtleitung hier anzeigen  | ||||
|            Regattaabbruch | ||||
|            Bahnverkürzung | ||||
|            Rückrufe | ||||
|          | ||||
|         """ | ||||
|     else: | ||||
|         print(f"UNKNOWN TOPIC: {msg.topic}") | ||||
|         print(msg.payload) | ||||
| 
 | ||||
| def mqtt_publish(client, topic, payload, bv_lat, bv_lon, bv_sog): | ||||
|     """ | ||||
|     Payload vorbelegt als Template, so daß nur noch die veränderlichen | ||||
|     GPS-Daten eingefügt werden müssen: LAT LON SOG TIMESTAMP | ||||
|     """ | ||||
|     lat = bv_lat.getValueRaw() | ||||
|     lon = bv_lon.getValueRaw() | ||||
|     sog = bv_sog.getValueRaw() | ||||
|     if lat and lon and sog: | ||||
|         payload['gps']['lat'] = round(lat, 5) | ||||
|         payload['gps']['lon'] = round(lon, 5) | ||||
|         payload['gps']['speed'] = sog | ||||
|         payload['gps']['timestamp'] = time.strftime("%Y-%m-%dT%H:%M:%S.000Z", time.gmtime()) | ||||
|          | ||||
|         client.publish(topic, json.dumps(payload)) | ||||
|     else: | ||||
|         print("No GPS data available. Nothing published!") | ||||
| 
 | ||||
| def mqtt_tracker(cfg, boat): | ||||
|     import paho.mqtt.client as mqtt | ||||
|     print("MQTT tracker enabled") | ||||
|     client = mqtt.Client() | ||||
|     client.on_connect = mqtt_on_connect | ||||
|     client.on_message = mqtt_on_message | ||||
|     client.username_pw_set(username=cfg['mqtt_user'], password=cfg['mqtt_pass']) | ||||
|     try: | ||||
|         client.connect(cfg['host'], cfg['port'], 60) | ||||
|     except ConnectionRefusedError: | ||||
|         print("MQTT connection refused. Check username and password.") | ||||
|         return | ||||
| 
 | ||||
|     topic = "regattahero/tracker/" + cfg['orgname'] | ||||
|     payload = { | ||||
|         "passcode": cfg['passcode'], | ||||
|         "orgid":  cfg['orgname'], | ||||
|         "raceid": "Demo Regatta", # TODO aus Selektion einstellen  | ||||
|         "gps": { | ||||
|             "lat": 0.0, | ||||
|             "lon": 0.0, | ||||
|             "speed": 0.0, | ||||
|             "age": 1000, | ||||
|             "odo": 1000, | ||||
|             "bat": 1.0, | ||||
|             "timestamp": ""  # ISO8601 Format mit Millisekunden in UTC | ||||
|             }, | ||||
|         "boat": { | ||||
|             "boatid": cfg['uuid'], | ||||
|             "sailno": boat['sailno'], | ||||
|             "team": boat['team'], | ||||
|             "boatclass": boat['class'], | ||||
|             "handicap": boat['handicap'], | ||||
|             "club": boat['club'], | ||||
|             "boatname": boat['name'] | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|     # Zugriff auf Boatdata: Referenzen für leichten schnellen Zugriff | ||||
|     bv_lat = boatdata.getRef("LAT") | ||||
|     bv_lon = boatdata.getRef("LON") | ||||
|     bv_sog = boatdata.getRef("SOG") | ||||
| 
 | ||||
|     client.loop_start() | ||||
|     while not shutdown: | ||||
|         time.sleep(1) | ||||
|         if tracker_active: | ||||
|             mqtt_publish(client, topic, payload, bv_lat, bv_lon, bv_sog) | ||||
|     client.loop_stop() | ||||
|     client.disconnect() | ||||
| 
 | ||||
| def rxd_n2k(device): | ||||
|     setthreadtitle("N2Klistener") | ||||
|     bus = can.Bus(interface='socketcan', channel=device, bitrate=250000); | ||||
|     wip = False | ||||
|     sc = 0 | ||||
|     nf = 0 | ||||
|     while not shutdown: | ||||
|     while not appdata.shutdown: | ||||
|         msg = bus.recv(2) | ||||
|         if not msg: | ||||
|             continue | ||||
|  | @ -332,7 +213,7 @@ def rxd_0183(devname): | |||
|         print("NMEA0183 serial port not available") | ||||
|         return | ||||
|     setthreadtitle("0183listener") | ||||
|     while not shutdown: | ||||
|     while not appdata.shutdown: | ||||
|         raw = ser.readline().decode('ascii') | ||||
|         if len(raw.strip()) == 0: | ||||
|             continue | ||||
|  | @ -372,7 +253,7 @@ def rxd_gps(devname, devspeed): | |||
|         print("GPS serial port not available") | ||||
|         return | ||||
|     setthreadtitle("GPSlistener") | ||||
|     while not shutdown: | ||||
|     while not appdata.shutdown: | ||||
|         try: | ||||
|             msg = pynmea2.parse(ser.readline().decode('ascii')) | ||||
|         except pynmea2.nmea.ParseError: | ||||
|  | @ -394,7 +275,7 @@ def rxd_network(address, port): | |||
|     # Wir verwenden UDP. Ein verlorenes Paket tut uns nicht weh. | ||||
|     sock = socket.socket() | ||||
|     sock.connect((address, port)) | ||||
|     while not shutdown: | ||||
|     while not appdata.shutdown: | ||||
|         time.sleep(0.5) | ||||
|     sock.close() | ||||
| 
 | ||||
|  | @ -433,7 +314,7 @@ def datareader(cfg, history): | |||
|     g = g_tick(1) | ||||
| 
 | ||||
|     n = 0 | ||||
|     while not shutdown: | ||||
|     while not appdata.shutdown: | ||||
|         time.sleep(next(g)) | ||||
|         # BME280 abfragen | ||||
|         if cfg['bme280']: | ||||
|  | @ -454,8 +335,9 @@ def datareader(cfg, history): | |||
| 
 | ||||
| class Frontend(Gtk.Window): | ||||
| 
 | ||||
|     def __init__(self, cfg, device, boatdata, profile): | ||||
|     def __init__(self, cfg, appdata, device, boatdata, profile): | ||||
|         super().__init__() | ||||
|         self.appdata = appdata | ||||
|         self.owndev = device | ||||
|         self.boatdata = boatdata | ||||
|         self._config = cfg['_config'] | ||||
|  | @ -740,10 +622,10 @@ def init_profile(config, cfg, boatdata): | |||
|             # Nummer und Art ermitteln | ||||
|             pageno = int(s[4:]) | ||||
|             pagedef[pageno] = {'type': config.get(s, "type")} | ||||
|             # Hole ein bin maximal 4 Werte je Seite  | ||||
|             # Hole ein bin maximal 6 Werte je Seite | ||||
|             values = {} | ||||
|             valno = 1 | ||||
|             for i in (1, 2, 3, 4): | ||||
|             for i in (1, 2, 3, 4, 5, 6): | ||||
|                 try: | ||||
|                     values[i] = config.get(s, f"value{i}") | ||||
|                 except configparser.NoOptionError: | ||||
|  | @ -762,7 +644,7 @@ def init_profile(config, cfg, boatdata): | |||
|             # Klasse nicht vorhanden, Seite wird nicht benutzt | ||||
|             print(f"Klasse '{p['type']}' nicht gefunden") | ||||
|             continue | ||||
|         c = cls(i, cfg, boatdata, *[v for v in p['values'].values()]) | ||||
|         c = cls(i, cfg, appdata, boatdata, *[v for v in p['values'].values()]) | ||||
|         clist[i] = c | ||||
|     return clist | ||||
| 
 | ||||
|  | @ -779,11 +661,11 @@ def set_loglevel(nr): | |||
|         nr = 0 | ||||
|     return level[nr] | ||||
| 
 | ||||
| def init_logging(logdir): | ||||
| def init_logging(logdir, logfile='obp60v.log'): | ||||
|     global log | ||||
|     os.makedirs(logdir, exist_ok=True) | ||||
|     log = logging.getLogger(os.path.basename(sys.argv[0])) | ||||
|     hdlr = logging.handlers.RotatingFileHandler(os.path.join(logdir, 'obp60v.log'), maxBytes=5242880, backupCount=5) | ||||
|     hdlr = logging.handlers.RotatingFileHandler(os.path.join(logdir, logfile), maxBytes=5242880, backupCount=5) | ||||
|     formatter = logging.Formatter('%(asctime)s %(name)s %(levelname)s: %(message)s', datefmt='%Y-%m-%d %H:%M:%S') | ||||
|     hdlr.setFormatter(formatter) | ||||
|     log.addHandler(hdlr) | ||||
|  | @ -797,8 +679,8 @@ if __name__ == "__main__": | |||
| 
 | ||||
|     setproctitle("obp60v") | ||||
| 
 | ||||
|     shutdown = False | ||||
|     tracker_active = False | ||||
|     # Globale Daten, u.a. auch Shutdown-Indikator | ||||
|     appdata = AppData() | ||||
| 
 | ||||
|     owndevice = Device(100) | ||||
|     # Hardcoding device, not intended to change | ||||
|  | @ -867,6 +749,7 @@ if __name__ == "__main__": | |||
|     cfg['tracker']['mqtt_pass'] = config.get('tracker', 'mqtt_pass') | ||||
|     cfg['tracker']['orgname'] = config.get('tracker', 'orgname') | ||||
|     cfg['tracker']['passcode'] = config.get('tracker', 'passcode') | ||||
|     cfg['tracker']['logdir'] = cfg['logdir'] | ||||
| 
 | ||||
|     # Boat data | ||||
|     cfg['boat']['name'] = config.get('boat', 'name') | ||||
|  | @ -892,7 +775,7 @@ if __name__ == "__main__": | |||
|         boatdata.enableSimulation() | ||||
| 
 | ||||
|     # Protokollierung | ||||
|     init_logging(os.path.expanduser("~/.local/share/obp60v")) | ||||
|     init_logging(os.path.expanduser(cfg['logdir']), cfg['logfile']) | ||||
|     log.info("Logging initialized") | ||||
| 
 | ||||
|     # Gerät initialisieren u.a. mit den genutzten Seiten | ||||
|  | @ -916,7 +799,7 @@ if __name__ == "__main__": | |||
|         t_rxd_net = threading.Thread(target=rxd_network, args=(cfg['net_port'],cfg['net_addr'])) | ||||
|         t_rxd_net.start() | ||||
|     if cfg['tracker']['type'] != 'NONE': | ||||
|         t_tracker = threading.Thread(target=mqtt_tracker, args=(cfg['tracker'],cfg['boat'])) | ||||
|         t_tracker = threading.Thread(target=appdata.track.mqtt_tracker, args=(cfg['tracker'],cfg['boat'],appdata,boatdata)) | ||||
|         t_tracker.start() | ||||
|     if not cfg['simulation']: | ||||
|         if cfg['bme280']: | ||||
|  | @ -925,9 +808,9 @@ if __name__ == "__main__": | |||
|     else: | ||||
|         print("Simulation mode enabled") | ||||
| 
 | ||||
|     app = Frontend(cfg, owndevice, boatdata, profile) | ||||
|     app = Frontend(cfg, appdata, owndevice, boatdata, profile) | ||||
|     app.run() | ||||
|     shutdown = True | ||||
|     appdata.shutdown = True | ||||
|     if cfg['can']: | ||||
|         t_rxd_n2k.join() | ||||
|     if cfg['nmea0183']: | ||||
|  | @ -936,8 +819,10 @@ if __name__ == "__main__": | |||
|         t_rxd_gps.join() | ||||
|     if cfg['network']: | ||||
|         t_rxd_net.join() | ||||
|     if cfg['tracker']['type'] != 'NONE': | ||||
|         t_tracker.join() | ||||
|     if not cfg['simulation'] and cfg['bme280']: | ||||
|         t_data.join() | ||||
|     print("Another fine product of the Sirius Cybernetics Corporation.") | ||||
| 
 | ||||
|     print(boatdata) | ||||
|     print("Another fine product of the Sirius Cybernetics Corporation.") | ||||
|  |  | |||
|  | @ -36,7 +36,7 @@ from .page import Page | |||
| 
 | ||||
| class Anchor(Page): | ||||
| 
 | ||||
|     def __init__(self, pageno, cfg, boatdata): | ||||
|     def __init__(self, pageno, cfg, appdata, boatdata): | ||||
|         super().__init__(pageno, cfg, boatdata) | ||||
|         self.sym_anchor = cairo.ImageSurface.create_from_png(os.path.join(cfg['imgpath'], "anchor.png")) | ||||
|         self.buttonlabel[1] = 'MODE' | ||||
|  |  | |||
|  | @ -5,7 +5,7 @@ from .page import Page | |||
| 
 | ||||
| class ApparentWind(Page): | ||||
| 
 | ||||
|     def __init__(self, pageno, cfg, boatdata): | ||||
|     def __init__(self, pageno, cfg, appdata, boatdata): | ||||
|         super().__init__(pageno, cfg, boatdata) | ||||
|         self.buttonlabel[1] = 'MODE' | ||||
|         self.mode = 'L' # (W)ind (L)ens | ||||
|  |  | |||
|  | @ -11,7 +11,7 @@ from .page import Page | |||
| 
 | ||||
| class Autobahn(Page): | ||||
| 
 | ||||
|     def __init__(self, pageno, cfg, boatdata): | ||||
|     def __init__(self, pageno, cfg, appdata, boatdata): | ||||
|         super().__init__(pageno, cfg, boatdata) | ||||
|         self.xte = self.bd.getRef("XTE") | ||||
|         self.cog = self.bd.getRef("COG") | ||||
|  |  | |||
|  | @ -40,7 +40,7 @@ from .page import Page | |||
| 
 | ||||
| class Barograph(Page): | ||||
| 
 | ||||
|     def __init__(self, pageno, cfg, boatdata): | ||||
|     def __init__(self, pageno, cfg, appdata, boatdata): | ||||
|         super().__init__(pageno, cfg, boatdata) | ||||
|         # Meßwert alle 15 Minuten:  | ||||
|         # 84 Stunden * 4 Werte je Stunde = 336 Meßwerte | ||||
|  |  | |||
|  | @ -12,7 +12,7 @@ class Battery(Page): | |||
| 
 | ||||
|     avg = (1, 10, 60, 300); | ||||
| 
 | ||||
|     def __init__(self, pageno, cfg, boatdata): | ||||
|     def __init__(self, pageno, cfg, appdata, boatdata): | ||||
|         super().__init__(pageno, cfg, boatdata) | ||||
|         self.avgindex = 0 | ||||
|         self.buttonlabel[1] = 'AVG' | ||||
|  |  | |||
|  | @ -9,7 +9,7 @@ from .page import Page | |||
| 
 | ||||
| class BME280(Page): | ||||
| 
 | ||||
|     def __init__(self, pageno, cfg, boatdata): | ||||
|     def __init__(self, pageno, cfg, appdata, boatdata): | ||||
|         super().__init__(pageno, cfg, boatdata) | ||||
|         #self.ref1 = self.bd.getRef(boatvalue1) | ||||
|         #self.ref2 = self.bd.getRef(boatvalue2) | ||||
|  |  | |||
|  | @ -20,7 +20,7 @@ import astral | |||
| 
 | ||||
| class Clock(Page): | ||||
| 
 | ||||
|     def __init__(self, pageno, cfg, boatdata): | ||||
|     def __init__(self, pageno, cfg, appdata, boatdata): | ||||
|         super().__init__(pageno, cfg, boatdata) | ||||
|         self.buttonlabel[1] = 'MODE' | ||||
|         self.buttonlabel[2] = 'TZ' | ||||
|  |  | |||
|  | @ -22,7 +22,7 @@ import nmea2000.lookup | |||
| 
 | ||||
| class Fluid(Page): | ||||
| 
 | ||||
|     def __init__(self, pageno, cfg, boatdata, fluidtype): | ||||
|     def __init__(self, pageno, cfg, appdata, boatdata, fluidtype): | ||||
|         super().__init__(pageno, cfg, boatdata) | ||||
|         self.fluidtype = int(fluidtype) | ||||
|         if self.fluidtype == 0: | ||||
|  |  | |||
|  | @ -20,7 +20,7 @@ from .page import Page | |||
| 
 | ||||
| class FourValues(Page): | ||||
| 
 | ||||
|     def __init__(self, pageno, cfg, boatdata, boatvalue1, boatvalue2, boatvalue3, boatvalue4): | ||||
|     def __init__(self, pageno, cfg, appdata, boatdata, boatvalue1, boatvalue2, boatvalue3, boatvalue4): | ||||
|         super().__init__(pageno, cfg, boatdata) | ||||
|         self.value1 = boatvalue1 | ||||
|         self.value2 = boatvalue2 | ||||
|  |  | |||
|  | @ -18,7 +18,7 @@ from .page import Page | |||
| 
 | ||||
| class FourValues2(Page): | ||||
| 
 | ||||
|     def __init__(self, pageno, cfg, boatdata, boatvalue1, boatvalue2, boatvalue3, boatvalue4): | ||||
|     def __init__(self, pageno, cfg, appdata, boatdata, boatvalue1, boatvalue2, boatvalue3, boatvalue4): | ||||
|         super().__init__(pageno, cfg, boatdata) | ||||
|         self.value1 = boatvalue1 | ||||
|         self.value2 = boatvalue2 | ||||
|  |  | |||
|  | @ -15,7 +15,7 @@ from .page import Page | |||
| 
 | ||||
| class Keel(Page): | ||||
| 
 | ||||
|     def __init__(self, pageno, cfg, boatdata): | ||||
|     def __init__(self, pageno, cfg, appdata, boatdata): | ||||
|         super().__init__(pageno, cfg, boatdata) | ||||
|         # Wert für Kielrotation | ||||
|         self.valref = self.bd.getRef("xdrRotK") | ||||
|  |  | |||
|  | @ -3,7 +3,7 @@ from .page import Page | |||
| 
 | ||||
| class OneValue(Page): | ||||
| 
 | ||||
|     def __init__(self, pageno, cfg, boatdata, boatvalue): | ||||
|     def __init__(self, pageno, cfg, appdata, boatdata, boatvalue): | ||||
|         super().__init__(pageno, cfg, boatdata) | ||||
|         self.ref1 = self.bd.getRef(boatvalue) | ||||
| 
 | ||||
|  |  | |||
|  | @ -4,7 +4,7 @@ from .page import Page | |||
| 
 | ||||
| class Rudder(Page): | ||||
| 
 | ||||
|     def __init__(self, pageno, cfg, boatdata): | ||||
|     def __init__(self, pageno, cfg, appdata, boatdata): | ||||
|         super().__init__(pageno, cfg, boatdata) | ||||
|         self.buttonlabel[1] = 'MODE' | ||||
|         self.mode = 'P' | ||||
|  |  | |||
|  | @ -18,7 +18,8 @@ from .page import Page | |||
| 
 | ||||
| class SixValues(Page): | ||||
| 
 | ||||
|     def __init__(self, pageno, cfg, boatdata, boatvalue1, boatvalue2, boatvalue3, boatvalue4): | ||||
|     def __init__(self, pageno, cfg, appdata, boatdata, boatvalue1, boatvalue2, | ||||
|                  boatvalue3, boatvalue4, boatvalue5, boatvalue6): | ||||
|         super().__init__(pageno, cfg, boatdata) | ||||
|         self.value1 = boatvalue1 | ||||
|         self.value2 = boatvalue2 | ||||
|  |  | |||
|  | @ -14,7 +14,7 @@ from .page import Page | |||
| 
 | ||||
| class SkyView(Page): | ||||
| 
 | ||||
|     def __init__(self, pageno, cfg, boatdata): | ||||
|     def __init__(self, pageno, cfg, appdata, boatdata): | ||||
|         super().__init__(pageno, cfg, boatdata) | ||||
| 
 | ||||
|     def pol2cart(azimut, elevation): | ||||
|  |  | |||
|  | @ -3,7 +3,7 @@ from .page import Page | |||
| 
 | ||||
| class ThreeValues(Page): | ||||
| 
 | ||||
|     def __init__(self, pageno, cfg, boatdata, boatvalue1, boatvalue2, boatvalue3): | ||||
|     def __init__(self, pageno, cfg, appdata, boatdata, boatvalue1, boatvalue2, boatvalue3): | ||||
|         super().__init__(pageno, cfg, boatdata) | ||||
|         self.ref1 = self.bd.getRef(boatvalue1) | ||||
|         self.ref2 = self.bd.getRef(boatvalue2) | ||||
|  |  | |||
|  | @ -11,14 +11,18 @@ from .page import Page | |||
| 
 | ||||
| class Tracker(Page): | ||||
| 
 | ||||
|     def __init__(self, pageno, cfg, boatdata): | ||||
|     def __init__(self, pageno, cfg, appdata, boatdata): | ||||
|         super().__init__(pageno, cfg, boatdata) | ||||
|         self._appdata = appdata | ||||
|         self.buttonlabel[1] = 'MODE' | ||||
|         print(cfg) | ||||
| 
 | ||||
|     def handle_key(self, buttonid): | ||||
|         global tracker_active; | ||||
|         if buttonid == 1: | ||||
|             tracker_active = not tracker_active | ||||
|             if self._appdata.track.is_active(): | ||||
|                 self._appdata.track.set_active(False) | ||||
|             else: | ||||
|                 self._appdata.track.set_active(True) | ||||
| 
 | ||||
|     def draw(self, ctx): | ||||
|         # Name | ||||
|  | @ -30,7 +34,7 @@ class Tracker(Page): | |||
|         ctx.set_font_size(16) | ||||
|         ctx.move_to(20, 140) | ||||
|         ctx.show_text("active: ") | ||||
|         if tracker_active: | ||||
|         if self._appdata.track.is_active(): | ||||
|             ctx.show_text("yes") | ||||
|         else: | ||||
|             ctx.show_text("no") | ||||
|  |  | |||
|  | @ -16,7 +16,7 @@ from .page import Page | |||
| 
 | ||||
| class TwoValues(Page): | ||||
| 
 | ||||
|     def __init__(self, pageno, cfg, boatdata, boatvalue1, boatvalue2): | ||||
|     def __init__(self, pageno, cfg, appdata, boatdata, boatvalue1, boatvalue2): | ||||
|         super().__init__(pageno, cfg, boatdata) | ||||
|         self.ref1 = self.bd.getRef(boatvalue1) | ||||
|         self.ref2 = self.bd.getRef(boatvalue2) | ||||
|  |  | |||
|  | @ -17,7 +17,7 @@ class Voltage(Page): | |||
| 
 | ||||
|     avg = (1, 10, 60, 300); | ||||
| 
 | ||||
|     def __init__(self, pageno, cfg, boatdata): | ||||
|     def __init__(self, pageno, cfg, appdata, boatdata): | ||||
|         super().__init__(pageno, cfg, boatdata) | ||||
|         self.trend = True | ||||
|         self.mode = 'A' | ||||
|  |  | |||
							
								
								
									
										160
									
								
								tracker.py
								
								
								
								
							
							
						
						
									
										160
									
								
								tracker.py
								
								
								
								
							|  | @ -13,12 +13,170 @@ Wiederherstellung der Verbindung übertragen. | |||
| 
 | ||||
| """ | ||||
| 
 | ||||
| import os | ||||
| import time | ||||
| import paho.mqtt.client as mqtt | ||||
| import json | ||||
| 
 | ||||
| class Tracker(): | ||||
| 
 | ||||
|     def __init__(self, trackertype): | ||||
|     def __init__(self, trackertype='NONE'): | ||||
|         validtypes = ('HERO', 'SDCARD', 'SERVER', 'NONE') | ||||
|         trackertype = trackertype.upper()  | ||||
|         if trackertype not in validtypes: | ||||
|             raise TypeError(f"Invalid tracker type: '{valtype}'. Only supported: {validtypes}") | ||||
|         self.ttype = trackertype | ||||
|         self.activated = False | ||||
|         self.races = set()   # Liste der Regatten, eindeutige Namen | ||||
|         self.courses = set() # Liste der Bahnen, eindeutige Namen | ||||
| 
 | ||||
|         self.lat = None    # last latitude | ||||
|         self.lon = None    # last longitude | ||||
|         self.tspos = None  # timestamp (hh:ss:mm) as datetime.time | ||||
|         self.sog = None | ||||
| 
 | ||||
| 
 | ||||
|     def is_active(self): | ||||
|        return self.activated | ||||
| 
 | ||||
|     def set_active(self, newval): | ||||
|        self.activated = newval | ||||
| 
 | ||||
|     def get_position(self): | ||||
|         # Positionsabfrage für die Payload | ||||
|         # LAT, LON, TSPOS, SOG | ||||
|         return (self.lat, self.lon, self.tspos, self.sog) | ||||
| 
 | ||||
|     def hero_add_race(self, raceid): | ||||
|         self.races.add(raceid) | ||||
| 
 | ||||
|     def hero_set_races(self, newraces): | ||||
|         self.races = set(newraces) | ||||
| 
 | ||||
|     def mqtt_on_connect(self, client, userdata, flags, rc): | ||||
|         print(f"MQTT connected with result code {rc}") | ||||
|         #userdata['connect_rc'] = rc | ||||
|         if rc != 0: | ||||
|             # Result codes: | ||||
|             # 1: Connection Refused, unacceptable protocol version | ||||
|             # 2: Connection Refused, identifier rejected | ||||
|             # 3: Connection Refused, Server unavailable | ||||
|             # 4: Connection Refused, bad user name or password | ||||
|             # 5: Connection Refused, not authorized | ||||
|             #userdata['connect_ok'] = True | ||||
|             pass | ||||
|         else: | ||||
|             client.subscribe("regattahero/orgstatus/thomas") | ||||
|             client.subscribe("regattahero/racestatus/thomas/#") | ||||
|             #userdata['connect_ok'] = False | ||||
| 
 | ||||
|     def mqtt_on_message(self, client, userdata, msg): | ||||
|         """ | ||||
|         TODO raceid über userdata? dann topic prüfen? | ||||
|         """ | ||||
|         if msg.topic == "regattahero/orgstatus/thomas": | ||||
|             # kommt alle 10s | ||||
|             orgstatus = json.loads(msg.payload) | ||||
|             if orgstatus['allLogout']: | ||||
|                 print("All logout received!") | ||||
|                 client.disconnect() | ||||
|                 sys.exit(0) # TODO nur die MQTT-Task beenden | ||||
|             if orgstatus['message']: | ||||
|                 # TODO Alarm-Funktion nutzen? | ||||
|                 print("Nachricht der Wettfahrtkeitung:") | ||||
|                 print(orgstatus['message']) | ||||
|             print(orgstatus['races']) | ||||
|             #for r in orgstatus['races']: | ||||
|             #    print(f"Race: {r}") | ||||
|         elif msg.topic.startswith("regattahero/racestatus/thomas"): | ||||
|             # kommt alle 1s | ||||
|             # dem Topic angehängt ist noch die raceid | ||||
|             payload = json.loads(msg.payload) | ||||
|             racestatus = payload['racestatus'] | ||||
|             """ | ||||
|             time: negativ: Zeit vor dem Start, positiv: Zeit nach dem Start | ||||
|                   in Sekunden | ||||
| 
 | ||||
|             Signale der Wettfahrtleitung hier anzeigen | ||||
|                Regattaabbruch | ||||
|                Bahnverkürzung | ||||
|                Rückrufe | ||||
| 
 | ||||
|             """ | ||||
|         else: | ||||
|             print(f"UNKNOWN TOPIC: {msg.topic}") | ||||
|             print(msg.payload) | ||||
| 
 | ||||
|     def mqtt_publish(self, client, topic, payload, bv_lat, bv_lon, bv_sog): | ||||
|         """ | ||||
|         Payload vorbelegt als Template, so daß nur noch die veränderlichen | ||||
|         GPS-Daten eingefügt werden müssen: LAT LON SOG TIMESTAMP | ||||
|         """ | ||||
|         lat = bv_lat.getValueRaw() | ||||
|         lon = bv_lon.getValueRaw() | ||||
|         sog = bv_sog.getValueRaw() | ||||
|         if lat and lon and sog: | ||||
|             payload['gps']['lat'] = round(lat, 5) | ||||
|             payload['gps']['lon'] = round(lon, 5) | ||||
|             payload['gps']['speed'] = sog | ||||
|             payload['gps']['timestamp'] = time.strftime("%Y-%m-%dT%H:%M:%S.000Z", time.gmtime()) | ||||
|              | ||||
|             client.publish(topic, json.dumps(payload)) | ||||
|         else: | ||||
|             print("No GPS data available. Nothing published!") | ||||
| 
 | ||||
|     def mqtt_tracker(self, cfg, boat, appdata, boatdata): | ||||
|         print("MQTT tracker enabled") | ||||
|         client = mqtt.Client() | ||||
|         client.on_connect = self.mqtt_on_connect | ||||
|         client.on_message = self.mqtt_on_message | ||||
|         client.username_pw_set(username=cfg['mqtt_user'], password=cfg['mqtt_pass']) | ||||
|         try: | ||||
|             client.connect(cfg['host'], cfg['port'], 60) | ||||
|         except ConnectionRefusedError: | ||||
|             print("MQTT connection refused. Check username and password.") | ||||
|             return | ||||
| 
 | ||||
|         tracefile = os.path.join(os.path.expanduser(cfg['logdir']), 'tracker.log') | ||||
|         trace_fh = open(tracefile, 'w+') | ||||
|         client.user_data_set({'trace': trace_fh}) | ||||
| 
 | ||||
|         topic = "regattahero/tracker/" + cfg['orgname'] | ||||
|         payload = { | ||||
|             "passcode": cfg['passcode'], | ||||
|             "orgid":  cfg['orgname'], | ||||
|             "raceid": "Demo Regatta", # TODO aus Selektion einstellen  | ||||
|             "gps": { | ||||
|                 "lat": 0.0, | ||||
|                 "lon": 0.0, | ||||
|                 "speed": 0.0, | ||||
|                 "age": 1000, | ||||
|                 "odo": 1000, | ||||
|                 "bat": 1.0, | ||||
|                 "timestamp": ""  # ISO8601 Format mit Millisekunden in UTC | ||||
|                 }, | ||||
|             "boat": { | ||||
|                 "boatid": cfg['uuid'], | ||||
|                 "sailno": boat['sailno'], | ||||
|                 "team": boat['team'], | ||||
|                 "boatclass": boat['class'], | ||||
|                 "handicap": boat['handicap'], | ||||
|                 "club": boat['club'], | ||||
|                 "boatname": boat['name'] | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|         # Zugriff auf Boatdata: Referenzen für leichten schnellen Zugriff | ||||
|         bv_lat = boatdata.getRef("LAT") | ||||
|         bv_lon = boatdata.getRef("LON") | ||||
|         bv_sog = boatdata.getRef("SOG") | ||||
| 
 | ||||
|         client.loop_start() | ||||
|         while not appdata.shutdown: | ||||
|             time.sleep(1) | ||||
|             if appdata.track.is_active(): | ||||
|                 self.mqtt_publish(client, topic, payload, bv_lat, bv_lon, bv_sog) | ||||
|         print("MQTT tracker shutdown") | ||||
|         client.loop_stop() | ||||
|         client.disconnect() | ||||
|         trace_fh.close() | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue