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 datetime import datetime | ||||||
| from nmea2000 import Device, BoatData, History, HistoryBuffer | from nmea2000 import Device, BoatData, History, HistoryBuffer | ||||||
| from nmea2000 import parser | from nmea2000 import parser | ||||||
| import nmea0183 |  | ||||||
| import pages |  | ||||||
| import struct | import struct | ||||||
| import uuid | import uuid | ||||||
| import json | import json | ||||||
| 
 | 
 | ||||||
|  | import nmea0183 | ||||||
|  | from appdata import AppData | ||||||
|  | import pages | ||||||
|  | 
 | ||||||
| __author__ = "Thomas Hooge" | __author__ = "Thomas Hooge" | ||||||
| __copyright__ = "Copyleft 2024-2025, all rights reversed" | __copyright__ = "Copyleft 2024-2025, all rights reversed" | ||||||
| __version__ = "0.2" | __version__ = "0.2" | ||||||
|  | @ -118,6 +120,8 @@ __status__ = "Development" | ||||||
| 
 | 
 | ||||||
| cfg = { | cfg = { | ||||||
|     'cfgfile': 'obp60v.conf', |     'cfgfile': 'obp60v.conf', | ||||||
|  |     'logdir': '~/.local/share/obp60v', | ||||||
|  |     'logfile': 'obp60v.log', | ||||||
|     'imgpath': os.path.join(sys.path[0], 'images'), |     'imgpath': os.path.join(sys.path[0], 'images'), | ||||||
|     'deviceid': 100, |     'deviceid': 100, | ||||||
|     'manufcode': 2046,  # Open Boat Projects (OBP) |     'manufcode': 2046,  # Open Boat Projects (OBP) | ||||||
|  | @ -130,136 +134,13 @@ cfg = { | ||||||
|     'boat': { } |     '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): | def rxd_n2k(device): | ||||||
|     setthreadtitle("N2Klistener") |     setthreadtitle("N2Klistener") | ||||||
|     bus = can.Bus(interface='socketcan', channel=device, bitrate=250000); |     bus = can.Bus(interface='socketcan', channel=device, bitrate=250000); | ||||||
|     wip = False |     wip = False | ||||||
|     sc = 0 |     sc = 0 | ||||||
|     nf = 0 |     nf = 0 | ||||||
|     while not shutdown: |     while not appdata.shutdown: | ||||||
|         msg = bus.recv(2) |         msg = bus.recv(2) | ||||||
|         if not msg: |         if not msg: | ||||||
|             continue |             continue | ||||||
|  | @ -332,7 +213,7 @@ def rxd_0183(devname): | ||||||
|         print("NMEA0183 serial port not available") |         print("NMEA0183 serial port not available") | ||||||
|         return |         return | ||||||
|     setthreadtitle("0183listener") |     setthreadtitle("0183listener") | ||||||
|     while not shutdown: |     while not appdata.shutdown: | ||||||
|         raw = ser.readline().decode('ascii') |         raw = ser.readline().decode('ascii') | ||||||
|         if len(raw.strip()) == 0: |         if len(raw.strip()) == 0: | ||||||
|             continue |             continue | ||||||
|  | @ -372,7 +253,7 @@ def rxd_gps(devname, devspeed): | ||||||
|         print("GPS serial port not available") |         print("GPS serial port not available") | ||||||
|         return |         return | ||||||
|     setthreadtitle("GPSlistener") |     setthreadtitle("GPSlistener") | ||||||
|     while not shutdown: |     while not appdata.shutdown: | ||||||
|         try: |         try: | ||||||
|             msg = pynmea2.parse(ser.readline().decode('ascii')) |             msg = pynmea2.parse(ser.readline().decode('ascii')) | ||||||
|         except pynmea2.nmea.ParseError: |         except pynmea2.nmea.ParseError: | ||||||
|  | @ -394,7 +275,7 @@ def rxd_network(address, port): | ||||||
|     # Wir verwenden UDP. Ein verlorenes Paket tut uns nicht weh. |     # Wir verwenden UDP. Ein verlorenes Paket tut uns nicht weh. | ||||||
|     sock = socket.socket() |     sock = socket.socket() | ||||||
|     sock.connect((address, port)) |     sock.connect((address, port)) | ||||||
|     while not shutdown: |     while not appdata.shutdown: | ||||||
|         time.sleep(0.5) |         time.sleep(0.5) | ||||||
|     sock.close() |     sock.close() | ||||||
| 
 | 
 | ||||||
|  | @ -433,7 +314,7 @@ def datareader(cfg, history): | ||||||
|     g = g_tick(1) |     g = g_tick(1) | ||||||
| 
 | 
 | ||||||
|     n = 0 |     n = 0 | ||||||
|     while not shutdown: |     while not appdata.shutdown: | ||||||
|         time.sleep(next(g)) |         time.sleep(next(g)) | ||||||
|         # BME280 abfragen |         # BME280 abfragen | ||||||
|         if cfg['bme280']: |         if cfg['bme280']: | ||||||
|  | @ -454,8 +335,9 @@ def datareader(cfg, history): | ||||||
| 
 | 
 | ||||||
| class Frontend(Gtk.Window): | class Frontend(Gtk.Window): | ||||||
| 
 | 
 | ||||||
|     def __init__(self, cfg, device, boatdata, profile): |     def __init__(self, cfg, appdata, device, boatdata, profile): | ||||||
|         super().__init__() |         super().__init__() | ||||||
|  |         self.appdata = appdata | ||||||
|         self.owndev = device |         self.owndev = device | ||||||
|         self.boatdata = boatdata |         self.boatdata = boatdata | ||||||
|         self._config = cfg['_config'] |         self._config = cfg['_config'] | ||||||
|  | @ -740,10 +622,10 @@ def init_profile(config, cfg, boatdata): | ||||||
|             # Nummer und Art ermitteln |             # Nummer und Art ermitteln | ||||||
|             pageno = int(s[4:]) |             pageno = int(s[4:]) | ||||||
|             pagedef[pageno] = {'type': config.get(s, "type")} |             pagedef[pageno] = {'type': config.get(s, "type")} | ||||||
|             # Hole ein bin maximal 4 Werte je Seite  |             # Hole ein bin maximal 6 Werte je Seite | ||||||
|             values = {} |             values = {} | ||||||
|             valno = 1 |             valno = 1 | ||||||
|             for i in (1, 2, 3, 4): |             for i in (1, 2, 3, 4, 5, 6): | ||||||
|                 try: |                 try: | ||||||
|                     values[i] = config.get(s, f"value{i}") |                     values[i] = config.get(s, f"value{i}") | ||||||
|                 except configparser.NoOptionError: |                 except configparser.NoOptionError: | ||||||
|  | @ -762,7 +644,7 @@ def init_profile(config, cfg, boatdata): | ||||||
|             # Klasse nicht vorhanden, Seite wird nicht benutzt |             # Klasse nicht vorhanden, Seite wird nicht benutzt | ||||||
|             print(f"Klasse '{p['type']}' nicht gefunden") |             print(f"Klasse '{p['type']}' nicht gefunden") | ||||||
|             continue |             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 |         clist[i] = c | ||||||
|     return clist |     return clist | ||||||
| 
 | 
 | ||||||
|  | @ -779,11 +661,11 @@ def set_loglevel(nr): | ||||||
|         nr = 0 |         nr = 0 | ||||||
|     return level[nr] |     return level[nr] | ||||||
| 
 | 
 | ||||||
| def init_logging(logdir): | def init_logging(logdir, logfile='obp60v.log'): | ||||||
|     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])) | ||||||
|     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') |     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) | ||||||
|  | @ -797,8 +679,8 @@ if __name__ == "__main__": | ||||||
| 
 | 
 | ||||||
|     setproctitle("obp60v") |     setproctitle("obp60v") | ||||||
| 
 | 
 | ||||||
|     shutdown = False |     # Globale Daten, u.a. auch Shutdown-Indikator | ||||||
|     tracker_active = False |     appdata = AppData() | ||||||
| 
 | 
 | ||||||
|     owndevice = Device(100) |     owndevice = Device(100) | ||||||
|     # Hardcoding device, not intended to change |     # Hardcoding device, not intended to change | ||||||
|  | @ -867,6 +749,7 @@ if __name__ == "__main__": | ||||||
|     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']['orgname'] = config.get('tracker', 'orgname') | ||||||
|     cfg['tracker']['passcode'] = config.get('tracker', 'passcode') |     cfg['tracker']['passcode'] = config.get('tracker', 'passcode') | ||||||
|  |     cfg['tracker']['logdir'] = cfg['logdir'] | ||||||
| 
 | 
 | ||||||
|     # Boat data |     # Boat data | ||||||
|     cfg['boat']['name'] = config.get('boat', 'name') |     cfg['boat']['name'] = config.get('boat', 'name') | ||||||
|  | @ -892,7 +775,7 @@ if __name__ == "__main__": | ||||||
|         boatdata.enableSimulation() |         boatdata.enableSimulation() | ||||||
| 
 | 
 | ||||||
|     # Protokollierung |     # Protokollierung | ||||||
|     init_logging(os.path.expanduser("~/.local/share/obp60v")) |     init_logging(os.path.expanduser(cfg['logdir']), cfg['logfile']) | ||||||
|     log.info("Logging initialized") |     log.info("Logging initialized") | ||||||
| 
 | 
 | ||||||
|     # Gerät initialisieren u.a. mit den genutzten Seiten |     # 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 = 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': | ||||||
|         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() |         t_tracker.start() | ||||||
|     if not cfg['simulation']: |     if not cfg['simulation']: | ||||||
|         if cfg['bme280']: |         if cfg['bme280']: | ||||||
|  | @ -925,9 +808,9 @@ if __name__ == "__main__": | ||||||
|     else: |     else: | ||||||
|         print("Simulation mode enabled") |         print("Simulation mode enabled") | ||||||
| 
 | 
 | ||||||
|     app = Frontend(cfg, owndevice, boatdata, profile) |     app = Frontend(cfg, appdata, owndevice, boatdata, profile) | ||||||
|     app.run() |     app.run() | ||||||
|     shutdown = True |     appdata.shutdown = True | ||||||
|     if cfg['can']: |     if cfg['can']: | ||||||
|         t_rxd_n2k.join() |         t_rxd_n2k.join() | ||||||
|     if cfg['nmea0183']: |     if cfg['nmea0183']: | ||||||
|  | @ -936,8 +819,10 @@ if __name__ == "__main__": | ||||||
|         t_rxd_gps.join() |         t_rxd_gps.join() | ||||||
|     if cfg['network']: |     if cfg['network']: | ||||||
|         t_rxd_net.join() |         t_rxd_net.join() | ||||||
|  |     if cfg['tracker']['type'] != 'NONE': | ||||||
|  |         t_tracker.join() | ||||||
|     if not cfg['simulation'] and cfg['bme280']: |     if not cfg['simulation'] and cfg['bme280']: | ||||||
|         t_data.join() |         t_data.join() | ||||||
|     print("Another fine product of the Sirius Cybernetics Corporation.") |  | ||||||
| 
 | 
 | ||||||
|     print(boatdata) |     print(boatdata) | ||||||
|  |     print("Another fine product of the Sirius Cybernetics Corporation.") | ||||||
|  |  | ||||||
|  | @ -36,7 +36,7 @@ from .page import Page | ||||||
| 
 | 
 | ||||||
| class Anchor(Page): | class Anchor(Page): | ||||||
| 
 | 
 | ||||||
|     def __init__(self, pageno, cfg, boatdata): |     def __init__(self, pageno, cfg, appdata, boatdata): | ||||||
|         super().__init__(pageno, cfg, boatdata) |         super().__init__(pageno, cfg, boatdata) | ||||||
|         self.sym_anchor = cairo.ImageSurface.create_from_png(os.path.join(cfg['imgpath'], "anchor.png")) |         self.sym_anchor = cairo.ImageSurface.create_from_png(os.path.join(cfg['imgpath'], "anchor.png")) | ||||||
|         self.buttonlabel[1] = 'MODE' |         self.buttonlabel[1] = 'MODE' | ||||||
|  |  | ||||||
|  | @ -5,7 +5,7 @@ from .page import Page | ||||||
| 
 | 
 | ||||||
| class ApparentWind(Page): | class ApparentWind(Page): | ||||||
| 
 | 
 | ||||||
|     def __init__(self, pageno, cfg, boatdata): |     def __init__(self, pageno, cfg, appdata, boatdata): | ||||||
|         super().__init__(pageno, cfg, boatdata) |         super().__init__(pageno, cfg, boatdata) | ||||||
|         self.buttonlabel[1] = 'MODE' |         self.buttonlabel[1] = 'MODE' | ||||||
|         self.mode = 'L' # (W)ind (L)ens |         self.mode = 'L' # (W)ind (L)ens | ||||||
|  |  | ||||||
|  | @ -11,7 +11,7 @@ from .page import Page | ||||||
| 
 | 
 | ||||||
| class Autobahn(Page): | class Autobahn(Page): | ||||||
| 
 | 
 | ||||||
|     def __init__(self, pageno, cfg, boatdata): |     def __init__(self, pageno, cfg, appdata, boatdata): | ||||||
|         super().__init__(pageno, cfg, boatdata) |         super().__init__(pageno, cfg, boatdata) | ||||||
|         self.xte = self.bd.getRef("XTE") |         self.xte = self.bd.getRef("XTE") | ||||||
|         self.cog = self.bd.getRef("COG") |         self.cog = self.bd.getRef("COG") | ||||||
|  |  | ||||||
|  | @ -40,7 +40,7 @@ from .page import Page | ||||||
| 
 | 
 | ||||||
| class Barograph(Page): | class Barograph(Page): | ||||||
| 
 | 
 | ||||||
|     def __init__(self, pageno, cfg, boatdata): |     def __init__(self, pageno, cfg, appdata, boatdata): | ||||||
|         super().__init__(pageno, cfg, boatdata) |         super().__init__(pageno, cfg, boatdata) | ||||||
|         # Meßwert alle 15 Minuten:  |         # Meßwert alle 15 Minuten:  | ||||||
|         # 84 Stunden * 4 Werte je Stunde = 336 Meßwerte |         # 84 Stunden * 4 Werte je Stunde = 336 Meßwerte | ||||||
|  |  | ||||||
|  | @ -12,7 +12,7 @@ class Battery(Page): | ||||||
| 
 | 
 | ||||||
|     avg = (1, 10, 60, 300); |     avg = (1, 10, 60, 300); | ||||||
| 
 | 
 | ||||||
|     def __init__(self, pageno, cfg, boatdata): |     def __init__(self, pageno, cfg, appdata, boatdata): | ||||||
|         super().__init__(pageno, cfg, boatdata) |         super().__init__(pageno, cfg, boatdata) | ||||||
|         self.avgindex = 0 |         self.avgindex = 0 | ||||||
|         self.buttonlabel[1] = 'AVG' |         self.buttonlabel[1] = 'AVG' | ||||||
|  |  | ||||||
|  | @ -9,7 +9,7 @@ from .page import Page | ||||||
| 
 | 
 | ||||||
| class BME280(Page): | class BME280(Page): | ||||||
| 
 | 
 | ||||||
|     def __init__(self, pageno, cfg, boatdata): |     def __init__(self, pageno, cfg, appdata, boatdata): | ||||||
|         super().__init__(pageno, cfg, boatdata) |         super().__init__(pageno, cfg, boatdata) | ||||||
|         #self.ref1 = self.bd.getRef(boatvalue1) |         #self.ref1 = self.bd.getRef(boatvalue1) | ||||||
|         #self.ref2 = self.bd.getRef(boatvalue2) |         #self.ref2 = self.bd.getRef(boatvalue2) | ||||||
|  |  | ||||||
|  | @ -20,7 +20,7 @@ import astral | ||||||
| 
 | 
 | ||||||
| class Clock(Page): | class Clock(Page): | ||||||
| 
 | 
 | ||||||
|     def __init__(self, pageno, cfg, boatdata): |     def __init__(self, pageno, cfg, appdata, boatdata): | ||||||
|         super().__init__(pageno, cfg, boatdata) |         super().__init__(pageno, cfg, boatdata) | ||||||
|         self.buttonlabel[1] = 'MODE' |         self.buttonlabel[1] = 'MODE' | ||||||
|         self.buttonlabel[2] = 'TZ' |         self.buttonlabel[2] = 'TZ' | ||||||
|  |  | ||||||
|  | @ -22,7 +22,7 @@ import nmea2000.lookup | ||||||
| 
 | 
 | ||||||
| class Fluid(Page): | class Fluid(Page): | ||||||
| 
 | 
 | ||||||
|     def __init__(self, pageno, cfg, boatdata, fluidtype): |     def __init__(self, pageno, cfg, appdata, boatdata, fluidtype): | ||||||
|         super().__init__(pageno, cfg, boatdata) |         super().__init__(pageno, cfg, boatdata) | ||||||
|         self.fluidtype = int(fluidtype) |         self.fluidtype = int(fluidtype) | ||||||
|         if self.fluidtype == 0: |         if self.fluidtype == 0: | ||||||
|  |  | ||||||
|  | @ -20,7 +20,7 @@ from .page import Page | ||||||
| 
 | 
 | ||||||
| class FourValues(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) |         super().__init__(pageno, cfg, boatdata) | ||||||
|         self.value1 = boatvalue1 |         self.value1 = boatvalue1 | ||||||
|         self.value2 = boatvalue2 |         self.value2 = boatvalue2 | ||||||
|  |  | ||||||
|  | @ -18,7 +18,7 @@ from .page import Page | ||||||
| 
 | 
 | ||||||
| class FourValues2(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) |         super().__init__(pageno, cfg, boatdata) | ||||||
|         self.value1 = boatvalue1 |         self.value1 = boatvalue1 | ||||||
|         self.value2 = boatvalue2 |         self.value2 = boatvalue2 | ||||||
|  |  | ||||||
|  | @ -15,7 +15,7 @@ from .page import Page | ||||||
| 
 | 
 | ||||||
| class Keel(Page): | class Keel(Page): | ||||||
| 
 | 
 | ||||||
|     def __init__(self, pageno, cfg, boatdata): |     def __init__(self, pageno, cfg, appdata, boatdata): | ||||||
|         super().__init__(pageno, cfg, boatdata) |         super().__init__(pageno, cfg, boatdata) | ||||||
|         # Wert für Kielrotation |         # Wert für Kielrotation | ||||||
|         self.valref = self.bd.getRef("xdrRotK") |         self.valref = self.bd.getRef("xdrRotK") | ||||||
|  |  | ||||||
|  | @ -3,7 +3,7 @@ from .page import Page | ||||||
| 
 | 
 | ||||||
| class OneValue(Page): | class OneValue(Page): | ||||||
| 
 | 
 | ||||||
|     def __init__(self, pageno, cfg, boatdata, boatvalue): |     def __init__(self, pageno, cfg, appdata, boatdata, boatvalue): | ||||||
|         super().__init__(pageno, cfg, boatdata) |         super().__init__(pageno, cfg, boatdata) | ||||||
|         self.ref1 = self.bd.getRef(boatvalue) |         self.ref1 = self.bd.getRef(boatvalue) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -4,7 +4,7 @@ from .page import Page | ||||||
| 
 | 
 | ||||||
| class Rudder(Page): | class Rudder(Page): | ||||||
| 
 | 
 | ||||||
|     def __init__(self, pageno, cfg, boatdata): |     def __init__(self, pageno, cfg, appdata, boatdata): | ||||||
|         super().__init__(pageno, cfg, boatdata) |         super().__init__(pageno, cfg, boatdata) | ||||||
|         self.buttonlabel[1] = 'MODE' |         self.buttonlabel[1] = 'MODE' | ||||||
|         self.mode = 'P' |         self.mode = 'P' | ||||||
|  |  | ||||||
|  | @ -18,7 +18,8 @@ from .page import Page | ||||||
| 
 | 
 | ||||||
| class SixValues(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) |         super().__init__(pageno, cfg, boatdata) | ||||||
|         self.value1 = boatvalue1 |         self.value1 = boatvalue1 | ||||||
|         self.value2 = boatvalue2 |         self.value2 = boatvalue2 | ||||||
|  |  | ||||||
|  | @ -14,7 +14,7 @@ from .page import Page | ||||||
| 
 | 
 | ||||||
| class SkyView(Page): | class SkyView(Page): | ||||||
| 
 | 
 | ||||||
|     def __init__(self, pageno, cfg, boatdata): |     def __init__(self, pageno, cfg, appdata, boatdata): | ||||||
|         super().__init__(pageno, cfg, boatdata) |         super().__init__(pageno, cfg, boatdata) | ||||||
| 
 | 
 | ||||||
|     def pol2cart(azimut, elevation): |     def pol2cart(azimut, elevation): | ||||||
|  |  | ||||||
|  | @ -3,7 +3,7 @@ from .page import Page | ||||||
| 
 | 
 | ||||||
| class ThreeValues(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) |         super().__init__(pageno, cfg, boatdata) | ||||||
|         self.ref1 = self.bd.getRef(boatvalue1) |         self.ref1 = self.bd.getRef(boatvalue1) | ||||||
|         self.ref2 = self.bd.getRef(boatvalue2) |         self.ref2 = self.bd.getRef(boatvalue2) | ||||||
|  |  | ||||||
|  | @ -11,14 +11,18 @@ from .page import Page | ||||||
| 
 | 
 | ||||||
| class Tracker(Page): | class Tracker(Page): | ||||||
| 
 | 
 | ||||||
|     def __init__(self, pageno, cfg, boatdata): |     def __init__(self, pageno, cfg, appdata, boatdata): | ||||||
|         super().__init__(pageno, cfg, boatdata) |         super().__init__(pageno, cfg, boatdata) | ||||||
|  |         self._appdata = appdata | ||||||
|         self.buttonlabel[1] = 'MODE' |         self.buttonlabel[1] = 'MODE' | ||||||
|  |         print(cfg) | ||||||
| 
 | 
 | ||||||
|     def handle_key(self, buttonid): |     def handle_key(self, buttonid): | ||||||
|         global tracker_active; |  | ||||||
|         if buttonid == 1: |         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): |     def draw(self, ctx): | ||||||
|         # Name |         # Name | ||||||
|  | @ -30,7 +34,7 @@ class Tracker(Page): | ||||||
|         ctx.set_font_size(16) |         ctx.set_font_size(16) | ||||||
|         ctx.move_to(20, 140) |         ctx.move_to(20, 140) | ||||||
|         ctx.show_text("active: ") |         ctx.show_text("active: ") | ||||||
|         if tracker_active: |         if self._appdata.track.is_active(): | ||||||
|             ctx.show_text("yes") |             ctx.show_text("yes") | ||||||
|         else: |         else: | ||||||
|             ctx.show_text("no") |             ctx.show_text("no") | ||||||
|  |  | ||||||
|  | @ -16,7 +16,7 @@ from .page import Page | ||||||
| 
 | 
 | ||||||
| class TwoValues(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) |         super().__init__(pageno, cfg, boatdata) | ||||||
|         self.ref1 = self.bd.getRef(boatvalue1) |         self.ref1 = self.bd.getRef(boatvalue1) | ||||||
|         self.ref2 = self.bd.getRef(boatvalue2) |         self.ref2 = self.bd.getRef(boatvalue2) | ||||||
|  |  | ||||||
|  | @ -17,7 +17,7 @@ class Voltage(Page): | ||||||
| 
 | 
 | ||||||
|     avg = (1, 10, 60, 300); |     avg = (1, 10, 60, 300); | ||||||
| 
 | 
 | ||||||
|     def __init__(self, pageno, cfg, boatdata): |     def __init__(self, pageno, cfg, appdata, boatdata): | ||||||
|         super().__init__(pageno, cfg, boatdata) |         super().__init__(pageno, cfg, boatdata) | ||||||
|         self.trend = True |         self.trend = True | ||||||
|         self.mode = 'A' |         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(): | class Tracker(): | ||||||
| 
 | 
 | ||||||
|     def __init__(self, trackertype): |     def __init__(self, trackertype='NONE'): | ||||||
|         validtypes = ('HERO', 'SDCARD', 'SERVER', 'NONE') |         validtypes = ('HERO', 'SDCARD', 'SERVER', 'NONE') | ||||||
|         trackertype = trackertype.upper()  |         trackertype = trackertype.upper()  | ||||||
|         if trackertype not in validtypes: |         if trackertype not in validtypes: | ||||||
|             raise TypeError(f"Invalid tracker type: '{valtype}'. Only supported: {validtypes}") |             raise TypeError(f"Invalid tracker type: '{valtype}'. Only supported: {validtypes}") | ||||||
|         self.ttype = trackertype |         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