Erste funktionsfähige Tracker-Version
This commit is contained in:
		
							parent
							
								
									ebb7b42d48
								
							
						
					
					
						commit
						eb41bdafa4
					
				
							
								
								
									
										26
									
								
								appdata.py
								
								
								
								
							
							
						
						
									
										26
									
								
								appdata.py
								
								
								
								
							|  | @ -12,6 +12,8 @@ class AppData(): | |||
|         self.shutdown = False # Globaler Ausschalter | ||||
|         self.track = Tracker('NONE') | ||||
|         self.frontend = None | ||||
|         self.bv_lat = None | ||||
|         self.bv_lon = None | ||||
| 
 | ||||
|         # Für u.a. Header-Indikatoren | ||||
|         # TODO | ||||
|  | @ -28,22 +30,38 @@ class AppData(): | |||
| 
 | ||||
|     def setFrontend(self, frontend): | ||||
|         self.frontend = frontend # Referenz zur GUI | ||||
|         self.bv_lat = frontend.boatdata.getRef("LAT") | ||||
|         self.bv_lon = frontend.boatdata.getRef("LON") | ||||
| 
 | ||||
|     def refreshStatus(self): | ||||
|         self.status['AP'] = False | ||||
|         self.status['AP'] = False # nicht implementiert | ||||
| 
 | ||||
|         self.status['TCP'] = False | ||||
|         self.status['WIFI'] = False | ||||
|         for intf in os.listdir('/sys/class/net'): | ||||
|             statefile = os.path.join('/sys/class/net', interface, 'operstate') | ||||
|             wififile = os.path.join('/sys/class/net', interface, 'wireless') | ||||
|             statefile = os.path.join('/sys/class/net', intf, 'operstate') | ||||
|             wififile = os.path.join('/sys/class/net', intf, 'wireless') | ||||
|             if os.path.exists(statefile): | ||||
|                 with open(statefile) as fh: | ||||
|                     state = f.read().strip() | ||||
|                     state = fh.read().strip() | ||||
|                 if state == 'up': | ||||
|                     if os.path.exists(wififile): | ||||
|                         self.status['WIFI'] = True | ||||
|                     else: | ||||
|                         self.status['TCP'] = True | ||||
| 
 | ||||
|         # TODO NMEA2000 | ||||
|         # can-Interface can0 im Netzwerk. Identifikation? | ||||
| 
 | ||||
|         # TODO NMEA0183 tty auf Konfiguration | ||||
|         # enabled in Konfiguration | ||||
|         # port muß gültige Schnittstelle sein | ||||
| 
 | ||||
|         # TODO USB /dev/ttyUSB0? | ||||
| 
 | ||||
|         # GPS | ||||
|         # Kann ein Empfänger am USB sein. Siehe Konfiguration | ||||
|         self.status['GPS'] = self.bv_lat and self.bv_lon and self.bv_lat.valid and self.bv_lon.valid | ||||
|          | ||||
|         # Tracker | ||||
|         self.status['TRK'] = self.track.is_active() | ||||
|  |  | |||
							
								
								
									
										39
									
								
								nmea0183.py
								
								
								
								
							
							
						
						
									
										39
									
								
								nmea0183.py
								
								
								
								
							|  | @ -9,9 +9,10 @@ TODO Multi-Sentence verarbeiten | |||
| 
 | ||||
| import serial | ||||
| from setproctitle import setthreadtitle | ||||
| import pynmea2 | ||||
| 
 | ||||
| # Empfangsthread | ||||
| def rxd_0183(appdata, devname): | ||||
| def rxd_0183(appdata,boatdata, devname): | ||||
|     # Prüfe ob NMEA0183-Port vorhanden ist und sich öffnen läßt | ||||
|     try: | ||||
|         ser = serial.Serial(devname, 115200, timeout=3) | ||||
|  | @ -351,28 +352,28 @@ def VPW(boatdata, msg): | |||
| def VTG(boatdata, msg): | ||||
|     # Track made good and speed over ground | ||||
|     """ | ||||
| (('True Track made good', 'true_track', <class 'float'>), | ||||
| ('True Track made good symbol', 'true_track_sym'), | ||||
| ('Magnetic Track made good', 'mag_track', <class 'decimal.Decimal'>), | ||||
| ('Magnetic Track symbol', 'mag_track_sym'), | ||||
| ('Speed over ground knots', 'spd_over_grnd_kts', <class 'decimal.Decimal'>), | ||||
| ('Speed over ground symbol', 'spd_over_grnd_kts_sym'), | ||||
| ('Speed over ground kmph', 'spd_over_grnd_kmph', <class 'float'>), | ||||
| ('Speed over ground kmph symbol', 'spd_over_grnd_kmph_sym'), | ||||
| ('FAA mode indicator', 'faa_mode')) | ||||
| ['', 'T', '', 'M', '0.117', 'N', '0.216', 'K', 'A'] | ||||
|     (('True Track made good', 'true_track', <class 'float'>), | ||||
|     ('True Track made good symbol', 'true_track_sym'), | ||||
|     ('Magnetic Track made good', 'mag_track', <class 'decimal.Decimal'>), | ||||
|     ('Magnetic Track symbol', 'mag_track_sym'), | ||||
|     ('Speed over ground knots', 'spd_over_grnd_kts', <class 'decimal.Decimal'>), | ||||
|     ('Speed over ground symbol', 'spd_over_grnd_kts_sym'), | ||||
|     ('Speed over ground kmph', 'spd_over_grnd_kmph', <class 'float'>), | ||||
|     ('Speed over ground kmph symbol', 'spd_over_grnd_kmph_sym'), | ||||
|     ('FAA mode indicator', 'faa_mode')) | ||||
|     ['', 'T', '', 'M', '0.117', 'N', '0.216', 'K', 'A'] | ||||
|     $IIVTG,312.000000,T,,M,2.000000,N,3.704000,K,A*28 | ||||
|     """ | ||||
|     #print("-> VTG") | ||||
|     # msg.true_track true_track_sym | ||||
|     # msg.mag_track mag_track_sym | ||||
|     # msg.faa_mode | ||||
|     if msg.faa_mode != 'A': | ||||
|         return | ||||
|     #TODO klären was für Typen hier ankommen können | ||||
|     # bytearray, str, decimal.Decimal? | ||||
|     #sog = float(msg.spd_over_grnd_kts) | ||||
|     #str von OpenCPN: sog = float(msg.spd_over_grnd_kts[:-1]) | ||||
|     #boatdata.setValue("SOG", sog) | ||||
|     #print("VTG", msg.spd_over_grnd_kts) | ||||
|     print("VTG", msg) | ||||
|     #Ggf. ist OpenCPN buggy! | ||||
|     cog = float(msg.true_track)        # in Grad | ||||
|     sog = float(msg.spd_over_grnd_kts) # in Knoten | ||||
|     boatdata.setValue("COG", cog) | ||||
|     boatdata.setValue("SOG", sog) | ||||
| 
 | ||||
| def VWR(boatdata, msg): | ||||
|     # Relative Wind Speed and Angle | ||||
|  |  | |||
							
								
								
									
										17
									
								
								obp60v.py
								
								
								
								
							
							
						
						
									
										17
									
								
								obp60v.py
								
								
								
								
							|  | @ -94,10 +94,10 @@ import cairo | |||
| import math | ||||
| import threading | ||||
| import socket | ||||
| import pynmea2 | ||||
| import can | ||||
| import serial | ||||
| import smbus2 | ||||
| import pynmea2 | ||||
| import bme280 | ||||
| import math | ||||
| import time | ||||
|  | @ -301,7 +301,6 @@ class Frontend(Gtk.Window): | |||
|     def __init__(self, cfg, appdata, device, boatdata, profile): | ||||
|         super().__init__() | ||||
|         self.appdata = appdata | ||||
|         self.appdata.setFrontend(self) | ||||
|         self.owndev = device | ||||
|         self.boatdata = boatdata | ||||
|         self._config = cfg['_config'] | ||||
|  | @ -311,6 +310,7 @@ class Frontend(Gtk.Window): | |||
| 
 | ||||
|         self.connect("delete-event", self.on_delete) | ||||
|         self.connect("destroy", self.on_destroy) | ||||
|         self.appdata.setFrontend(self) | ||||
| 
 | ||||
|         if self._fullscreen: | ||||
|             self.fullscreen() | ||||
|  | @ -396,11 +396,13 @@ class Frontend(Gtk.Window): | |||
|             self.get_window().set_cursor(Gdk.Cursor(Gdk.CursorType.BLANK_CURSOR)) | ||||
| 
 | ||||
|     def run(self): | ||||
|         GLib.timeout_add_seconds(1, self.on_timer) | ||||
|         appdata.refreshStatus() | ||||
|         GLib.timeout_add_seconds(1, self.on_timer_fast) | ||||
|         GLib.timeout_add_seconds(10, self.on_timer_slow) | ||||
|         self.show_all() | ||||
|         Gtk.main() | ||||
| 
 | ||||
|     def on_timer(self): | ||||
|     def on_timer_fast(self): | ||||
|         # Boatdata validator | ||||
|         boatdata.updateValid(5) | ||||
|         # Tastaturstatus an Seite durchreichen | ||||
|  | @ -409,6 +411,10 @@ class Frontend(Gtk.Window): | |||
|         self.da.queue_draw() | ||||
|         return True | ||||
| 
 | ||||
|     def on_timer_slow(self): | ||||
|         appdata.refreshStatus() | ||||
|         return True | ||||
| 
 | ||||
|     def on_draw(self, widget, ctx): | ||||
|         # Fenstertransparenz | ||||
|         ctx.set_source_rgba(0, 0, 0, 0) | ||||
|  | @ -765,7 +771,7 @@ if __name__ == "__main__": | |||
|         t_rxd_n2k.start() | ||||
|     if cfg['nmea0183']: | ||||
|         print("NMEA0183 enabled, library version {}".format(pynmea2.version)) | ||||
|         t_rxd_0183 = threading.Thread(target=nmea0183.rxd_0183, args=(appdata,cfg['0183_port'],)) | ||||
|         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)") | ||||
|  | @ -776,6 +782,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': | ||||
|         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']: | ||||
|  |  | |||
|  | @ -142,13 +142,9 @@ 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(f"N2K GPS") | ||||
|         ctx.show_text(' '.join([s for s in self.appdata.status if self.appdata.status[s]])) | ||||
|         ctx.stroke() | ||||
| 
 | ||||
|         # AP: Nicht implementiert | ||||
|         # WIFI:  | ||||
|         # /proc/net/wireless | ||||
| 
 | ||||
|         # Tastenstatus | ||||
|         ctx.save() | ||||
|         if self.keylock: | ||||
|  | @ -325,6 +321,7 @@ class Page(): | |||
|         ctx.stroke() | ||||
| 
 | ||||
|     def draw_text_boxed(self, ctx, x, y, w, h, content, inverted=False, border=False): | ||||
|         ctx.save() | ||||
|         ctx.set_line_width(1) | ||||
|         # Background fill | ||||
|         ctx.set_source_rgb(*self.fgcolor) | ||||
|  | @ -343,6 +340,7 @@ class Page(): | |||
|         ctx.move_to(x + 4, y + h - 5 + 0.5) | ||||
|         ctx.show_text(content) | ||||
|         ctx.stroke() | ||||
|         ctx.restore() | ||||
| 
 | ||||
| def wordwrap(text, wrap): | ||||
|     # Wrap long line to multiple lines, monospaced character set | ||||
|  |  | |||
							
								
								
									
										231
									
								
								pages/tracker.py
								
								
								
								
							
							
						
						
									
										231
									
								
								pages/tracker.py
								
								
								
								
							|  | @ -22,17 +22,20 @@ Behandlung von Verbindungsabbrüchen: | |||
| import os | ||||
| import cairo | ||||
| from .page import Page | ||||
| from cfgmenu import Menu | ||||
| 
 | ||||
| 
 | ||||
| class Tracker(Page): | ||||
| 
 | ||||
|     def __init__(self, pageno, cfg, appdata, boatdata): | ||||
|         super().__init__(pageno, cfg, appdata, boatdata) | ||||
|         self._appdata = appdata | ||||
|         self.bv_lat = boatdata.getRef("LAT") | ||||
|         self.bv_lon = boatdata.getRef("LON") | ||||
|         self.bv_sog = boatdata.getRef("SOG") | ||||
|         self.races = None | ||||
|         self.raceid = None # Ausgewählte Regatta | ||||
|         self.menupos = 0 | ||||
|         self.buttonlabel[1] = 'MODE' | ||||
|         self.buttonlabel[2] = 'ON' | ||||
|         self.mode = 'N' # (N)ormal, (C)onfiguration | ||||
| 
 | ||||
|         # Flaggengröße: 96 x 64 Pixel | ||||
|  | @ -43,31 +46,88 @@ class Tracker(Page): | |||
|                 'finish', 'hotel', 'india', 'november', 'orange',  | ||||
|                 'papa', 'repeat_one', 'sierra', 'start', 'uniform', | ||||
|                 'xray', 'yankee', 'zulu') | ||||
|         # Mapping | ||||
|         self.flagmap = { | ||||
|              3: 'blue',         #  | ||||
|              8: 'sierra',       # Bahnverkürzung | ||||
|              9: 'november',     # Abbruch | ||||
|             10: 'yankee',       # Schwimmwesten | ||||
|             11: 'repeat_one',   # Rückruf | ||||
|             12: 'answer',       # Startverschiebung | ||||
|             14: 'start', | ||||
|             15: 'class',        # Klassenflagge | ||||
|             100: 'papa',        # Vorbereitung, Frühstart: Zurückfallen über Startlinie | ||||
|             101: 'india',       # Vorbereitung, Frühstart: Starttonne umrunden | ||||
|             102: 'zulu',        # Frühstart: 20%-Strafe | ||||
|             103: 'uniform',     # Frühstart: Disqualifikation, Wiederholung erlaubt | ||||
|             104: 'black'        # Frühstart: Disqualifikation, Wiederholung nicht erlaubt | ||||
|         } | ||||
|         self.sym_flag = {} | ||||
|         for f in flag: | ||||
|             flagfile = os.path.join(cfg['imgpath'], 'flags', f + '.png') | ||||
|             self.sym_flag[f] = cairo.ImageSurface.create_from_png(flagfile) | ||||
|         print(self.sym_flag) | ||||
| 
 | ||||
|         self._menu = Menu("Regattas", 200, 250) | ||||
|         self._menu.setItemDimension(120, 20) | ||||
| 
 | ||||
|     def handle_key(self, buttonid): | ||||
|         if buttonid == 1: | ||||
|             # Modus umschalten | ||||
|             if self.mode == 'N': | ||||
|                 self.mode = 'C' | ||||
|                 self.buttonlabel[2] = '#UP' | ||||
|                 self.buttonlabel[3] = '#DOWN' | ||||
|                 self.buttonlabel[4] = 'SET' | ||||
|                 if self.appdata.track.is_active(): | ||||
|                     self.buttonlabel[5] = 'OFF' | ||||
|                 else: | ||||
|                     self.buttonlabel[5] = 'ON' | ||||
|             else: | ||||
|                 self.mode = 'N' | ||||
|                 self.buttonlabel[2] = '' | ||||
|                 self.buttonlabel[3] = '#PREV' | ||||
|                 self.buttonlabel[4] = '#NEXT' | ||||
|                 self.buttonlabel[5] = '' | ||||
|             return True | ||||
|         elif buttonid == 2: | ||||
|             # Tracking ein/-ausschalten | ||||
|             if self._appdata.track.is_active(): | ||||
|                 self._appdata.track.set_active(False) | ||||
|                 self.buttonlabel[2] = 'ON' | ||||
|             else: | ||||
|                 self._appdata.track.set_active(True) | ||||
|                 self.buttonlabel[2] = 'OFF' | ||||
|             if self.mode == 'C': | ||||
|                 # Up | ||||
|                 if self.menupos > 1: | ||||
|                     self.menupos -= 1 | ||||
|                 else: | ||||
|                     self.menupos = len(self.races) | ||||
|                 return True | ||||
|         elif buttonid == 3: | ||||
|             if self.mode == 'C': | ||||
|                 # Down | ||||
|                 if self.menupos < len(self.races): | ||||
|                     self.menupos += 1 | ||||
|                 else: | ||||
|                     self.menupos = 1 | ||||
|                 return True | ||||
|         elif buttonid == 4: | ||||
|             if self.mode == 'C': | ||||
|                 # Set / Select regatta | ||||
|                 if self.menupos > 0: | ||||
|                     self.raceid = self.races[self.menupos - 1] # Nullbasiert | ||||
|                     self.appdata.track.hero_raceid = self.raceid | ||||
|                     print(f"Selected race '{self.raceid}'") | ||||
|                 return True | ||||
|         elif buttonid == 5: | ||||
|             self._appdata.frontend.flashled.setColor('yellow') | ||||
|             #self._appdata.frontend.flashled.switchOn(4) | ||||
|             self._appdata.frontend.flashled.doFlash(2) | ||||
|             if self.mode == 'C': | ||||
|                 # Tracking ein/-ausschalten | ||||
|                 if self.appdata.track.is_active(): | ||||
|                     self.appdata.track.set_active(False) | ||||
|                     self.buttonlabel[5] = 'ON' | ||||
|                 else: | ||||
|                     self.appdata.track.set_active(True) | ||||
|                     self.buttonlabel[5] = 'OFF' | ||||
|             else: | ||||
|                 self.appdata.frontend.flashled.setColor('yellow') | ||||
|                 #self.appdata.frontend.flashled.switchOn(4) | ||||
|                 self.appdata.frontend.flashled.doFlash(2) | ||||
|             return True | ||||
|         return False | ||||
| 
 | ||||
|     def draw_normal(self, ctx): | ||||
|         # Name | ||||
|  | @ -79,9 +139,19 @@ class Tracker(Page): | |||
|         ctx.select_font_face("DSEG7 Classic") | ||||
|         ctx.set_font_size(80) | ||||
| 
 | ||||
|         if self._appdata.track.is_active(): | ||||
|             ctx.move_to(20, 120) | ||||
|             ctx.show_text("-00:00") | ||||
|         if self.appdata.track.is_active(): | ||||
|             if self.appdata.track.hero_racestatus: | ||||
|                 counter = self.appdata.track.hero_racestatus['time'] | ||||
|                 minutes, seconds = divmod(abs(counter), 60) | ||||
|                 if counter < 0: | ||||
|                     ctx.move_to(16, 120) | ||||
|                     ctx.show_text(f"-{minutes:02d}:{seconds:02d}") | ||||
|                 else: | ||||
|                     ctx.move_to(28, 120) | ||||
|                     ctx.show_text(f"{minutes:03d}:{seconds:02d}") | ||||
|             else: | ||||
|                 ctx.move_to(48, 120) | ||||
|                 ctx.show_text("--:--") | ||||
|         else: | ||||
|             ctx.move_to(100, 120) | ||||
|             ctx.show_text("off") | ||||
|  | @ -94,58 +164,133 @@ 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.appdata.track.ttype) | ||||
| 
 | ||||
|         ctx.move_to(x0, y0 + 16) | ||||
|         ctx.show_text("Regatta") | ||||
|         ctx.move_to(x1, y0 + 16) | ||||
|         ctx.show_text('') | ||||
|         ctx.show_text(self.appdata.track.hero_raceid or '[not selected]') | ||||
| 
 | ||||
|         ctx.move_to(x0, y0 + 32) | ||||
|         ctx.show_text("Lat=") | ||||
|         ctx.show_text("Course") | ||||
|         ctx.move_to(x1, y0 + 32) | ||||
|         ctx.show_text(self.bv_lat.format()) | ||||
|         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']) | ||||
|         else: | ||||
|             ctx.show_text('[not selected]') | ||||
| 
 | ||||
|         ctx.move_to(x0, y0 + 48) | ||||
|         ctx.show_text("Lon=") | ||||
|         ctx.show_text("Latitude") | ||||
|         ctx.move_to(x1, y0 + 48) | ||||
|         ctx.show_text(self.bv_lon.format()) | ||||
|         ctx.show_text(self.bv_lat.format()) | ||||
| 
 | ||||
|         ctx.move_to(x0, y0 + 64) | ||||
|         ctx.show_text("Sog=") | ||||
|         ctx.show_text("Longitude") | ||||
|         ctx.move_to(x1, y0 + 64) | ||||
|         ctx.show_text(self.bv_lon.format()) | ||||
| 
 | ||||
|         ctx.move_to(x0, y0 + 80) | ||||
|         ctx.show_text("Speed") | ||||
|         ctx.move_to(x1, y0 + 80) | ||||
|         ctx.show_text(self.bv_sog.format()) | ||||
| 
 | ||||
|         # Flaggen | ||||
|         if self.appdata.track.hero_racestatus: | ||||
|             pos = 0 | ||||
|             for f in self.appdata.track.hero_racestatus['flags']: | ||||
|                 if f in self.flagmap: | ||||
|                     # TODO Context save/restore erforderlich? | ||||
|                     ctx.save() | ||||
|                     ctx.set_source_surface(self.sym_flag[self.flagmap[f]], *self.flagpos[pos]) | ||||
|                     ctx.paint() | ||||
|                     ctx.restore() | ||||
|                 pos += 1 | ||||
| 
 | ||||
|                  | ||||
|     def draw_config(self, ctx): | ||||
| 
 | ||||
|         ctx.select_font_face("Ubuntu", cairo.FontSlant.NORMAL, cairo.FontWeight.BOLD) | ||||
|         ctx.set_font_size(32)  | ||||
|         ctx.move_to(20, 80) | ||||
|         ctx.set_font_size(24) | ||||
|         ctx.move_to(4, 42) | ||||
|         ctx.show_text("Tracker configuration") | ||||
|         # Daten aus Konfiguration anzeigen | ||||
|         # - boot | ||||
|         # - tracker | ||||
| 
 | ||||
|         x0 = 8 | ||||
|         x1 = 88 | ||||
|         y0 = 96 | ||||
| 
 | ||||
|         ctx.set_font_size(20) | ||||
|         ctx.move_to(x0, 75) | ||||
|         ctx.show_text("Boat data") | ||||
| 
 | ||||
|         ctx.set_font_size(16) | ||||
| 
 | ||||
|         # Mögliche Regatten | ||||
|         # -> auf Konfigurationsmodus verschieben | ||||
|         x = 250 | ||||
|         y = 100 | ||||
|         ctx.move_to(x, y - 24) | ||||
|         ctx.show_text("Regattas") | ||||
|         for r in self._appdata.track.hero_get_races(): | ||||
|             ctx.move_to(x, y) | ||||
|             ctx.show_text(r) | ||||
|             y += 20 | ||||
|         if y == 160: | ||||
|             ctx.move_to(x, y) | ||||
|             ctx.show_text("keine") | ||||
|         ctx.move_to(x0, y0) | ||||
|         ctx.show_text("Name") | ||||
|         ctx.move_to(x1, y0) | ||||
|         ctx.show_text(self.cfg['boat']['name']) | ||||
| 
 | ||||
|         ctx.move_to(x0, y0 + 16) | ||||
|         ctx.show_text("Class") | ||||
|         ctx.move_to(x1, y0 + 16) | ||||
|         ctx.show_text(self.cfg['boat']['class']) | ||||
| 
 | ||||
|         ctx.move_to(20, 120) | ||||
|         ctx.move_to(x0, y0 + 32) | ||||
|         ctx.show_text("Handicap") | ||||
|         ctx.move_to(x1, y0 + 32) | ||||
|         ctx.show_text(str(self.cfg['boat']['handicap'])) | ||||
| 
 | ||||
|         ctx.move_to(x0, y0 + 48) | ||||
|         ctx.show_text("Club") | ||||
|         ctx.move_to(x1, y0 + 48) | ||||
|         ctx.show_text(self.cfg['boat']['club']) | ||||
| 
 | ||||
|         ctx.move_to(x0, y0 + 64) | ||||
|         ctx.show_text("Sailno.") | ||||
|         ctx.move_to(x1, y0 + 64) | ||||
|         ctx.show_text(self.cfg['boat']['sailno']) | ||||
| 
 | ||||
|         x0 = 208 | ||||
|         x1 = 272 | ||||
| 
 | ||||
|         ctx.set_font_size(20) | ||||
|         ctx.move_to(x0, 75) | ||||
|         ctx.show_text("Tracker info") | ||||
| 
 | ||||
|         ctx.set_font_size(16) | ||||
| 
 | ||||
|         ctx.move_to(x0, y0) | ||||
|         ctx.show_text("Type: ") | ||||
|         ctx.show_text(self._appdata.track.ttype) | ||||
|         ctx.show_text(self.appdata.track.ttype) | ||||
|         ctx.move_to(x0, y0 + 16) | ||||
|         ctx.show_text("Status") | ||||
|         ctx.move_to(x0, y0 + 32) | ||||
|         ctx.show_text("Org.") | ||||
|         ctx.move_to(x0, y0 + 48) | ||||
|         ctx.show_text("Team") | ||||
| 
 | ||||
|         # Mögliche Regatten | ||||
|         self.races = self.appdata.track.hero_get_races() | ||||
|         x = 208 | ||||
|         y = 180 | ||||
|         ctx.set_font_size(20) | ||||
|         ctx.move_to(x, y) | ||||
|         ctx.show_text("Regattas") | ||||
|         ctx.set_font_size(16) | ||||
|         y += 4 | ||||
|         if self.menupos > len(self.races): | ||||
|             # Nichts auswählen | ||||
|             self.menupos = 0 | ||||
|             self.raceid = None | ||||
|         i = 0 | ||||
|         for r in self.races: | ||||
|             i += 1 | ||||
|             if r == self.raceid: | ||||
|                 r += '*' | ||||
|             self.draw_text_boxed(ctx, x, y, 180, 20, r, (self.menupos == i)) | ||||
|             y += 20 | ||||
|         if i == 0: | ||||
|             ctx.move_to(x, y + 20) | ||||
|             ctx.show_text("[ none ]") | ||||
| 
 | ||||
|     def draw(self, ctx): | ||||
|         if self.mode == 'N': | ||||
|  |  | |||
							
								
								
									
										62
									
								
								tracker.py
								
								
								
								
							
							
						
						
									
										62
									
								
								tracker.py
								
								
								
								
							|  | @ -12,6 +12,9 @@ Wenn die Verbindung zum Server im Internet nicht funktioniert, werden | |||
| die Positionen in eine Warteschlange gesichert und nach  | ||||
| Wiederherstellung der Verbindung übertragen. | ||||
| 
 | ||||
| TODO | ||||
| - Nach einem Disconnect manuelle Neuverbindung ermöglichen | ||||
| 
 | ||||
| """ | ||||
| 
 | ||||
| import os | ||||
|  | @ -23,11 +26,8 @@ import socket | |||
| class Tracker(): | ||||
| 
 | ||||
|     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.ttype = 'NONE' | ||||
|         self.set_type(trackertype) | ||||
| 
 | ||||
|         self.appdata = None | ||||
| 
 | ||||
|  | @ -44,7 +44,8 @@ class Tracker(): | |||
|         self.sog = None | ||||
| 
 | ||||
|         self.hero_orgstatus = None | ||||
|         self.hero_racestatus = None # Akluelle Regatta | ||||
|         self.hero_racestatus = None # Aktuelle Regatta | ||||
|         self.hero_raceid = None | ||||
| 
 | ||||
|         # TODO Wirklich alles im Tracker oder ist einiges generisch? | ||||
|         self.boatid = None | ||||
|  | @ -75,6 +76,16 @@ class Tracker(): | |||
|     def set_active(self, newval): | ||||
|        self.activated = newval | ||||
| 
 | ||||
|     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 get_position(self): | ||||
|         # Positionsabfrage für die Payload | ||||
|         # LAT, LON, TSPOS, SOG | ||||
|  | @ -124,25 +135,39 @@ class Tracker(): | |||
|             if self.hero_orgstatus['allLogout']: | ||||
|                 print("All logout received!") | ||||
|                 client.disconnect() | ||||
|                 sys.exit(0) # TODO nur die MQTT-Task beenden | ||||
|                 self.activated = False | ||||
|                 return | ||||
|             if self.hero_orgstatus['message']: | ||||
|                 # TODO Alarm-Funktion nutzen? | ||||
|                 print("Nachricht der Wettfahrtkeitung:") | ||||
|                 print(orgstatus['message']) | ||||
|                 print(self.hero_orgstatus['message']) | ||||
|             #print(self.hero_orgstatus) | ||||
|         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'] | ||||
|             self.hero_racestatus = payload['racestatus'] | ||||
|              | ||||
|             print(self.hero_racestatus['flags']) | ||||
|             #print(self.hero_racestatus) | ||||
|             """ | ||||
|             time: negativ: Zeit vor dem Start, positiv: Zeit nach dem Start | ||||
|                   in Sekunden | ||||
|             flags: [0, 0, 14, 10]  | ||||
|             raceactive: true bedeutet orange Flagge ist oben | ||||
|             racestarted: true | ||||
| 
 | ||||
|             Signale der Wettfahrtleitung hier anzeigen | ||||
|                Regattaabbruch | ||||
|                Bahnverkürzung | ||||
|                Rückrufe | ||||
| 
 | ||||
|             phase: 0 vor dem Start racephase: 1 | ||||
|             racephase: 4 | ||||
|             5 Vorbereitungssignal | ||||
|             racephase: 6 nach vorbereitnug wieder runter | ||||
|             7: Rennen gestartet | ||||
| 
 | ||||
|             """ | ||||
|         else: | ||||
|             print(f"UNKNOWN TOPIC: {msg.topic}") | ||||
|  | @ -152,16 +177,19 @@ class Tracker(): | |||
|         """ | ||||
|         Payload vorbelegt als Template, so daß nur noch die veränderlichen | ||||
|         GPS-Daten eingefügt werden müssen: LAT LON SOG TIMESTAMP | ||||
|          | ||||
|         isTracking kann ausgeschaltet werden, | ||||
|         """ | ||||
|         lat = bv_lat.getValueRaw() | ||||
|         lon = bv_lon.getValueRaw() | ||||
|         sog = bv_sog.getValueRaw() | ||||
|         if lat and lon and sog: | ||||
|         if lat and lon and (sog is not None): | ||||
|             payload['raceid'] = self.hero_raceid | ||||
|             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()) | ||||
|              | ||||
|             print(payload) | ||||
|             client.publish(topic, json.dumps(payload)) | ||||
|         else: | ||||
|             print("No GPS data available. Nothing published!") | ||||
|  | @ -189,13 +217,13 @@ class Tracker(): | |||
|         payload = { | ||||
|             "passcode": cfg['passcode'], | ||||
|             "orgid":  cfg['orgname'], | ||||
|             "raceid": "Demo Regatta", # TODO aus Selektion einstellen  | ||||
|             "raceid": None, # Nach Auswahl einstellen | ||||
|             "gps": { | ||||
|                 "lat": 0.0, | ||||
|                 "lon": 0.0, | ||||
|                 "speed": 0.0, | ||||
|                 "age": 1000, | ||||
|                 "odo": 1000, | ||||
|                 "age": 500, | ||||
|                 # "odo": 1000, # deprecated | ||||
|                 "bat": 1.0, | ||||
|                 "timestamp": ""  # ISO8601 Format mit Millisekunden in UTC | ||||
|                 }, | ||||
|  | @ -206,7 +234,9 @@ class Tracker(): | |||
|                 "boatclass": boat['class'], | ||||
|                 "handicap": boat['handicap'], | ||||
|                 "club": boat['club'], | ||||
|                 "boatname": boat['name'] | ||||
|                 "boatname": boat['name'], | ||||
|                 "isTracking": True, | ||||
|                 "hasGivenUp": False | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|  | @ -218,7 +248,7 @@ class Tracker(): | |||
|         client.loop_start() | ||||
|         while not appdata.shutdown: | ||||
|             time.sleep(1) | ||||
|             if appdata.track.is_active(): | ||||
|             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() | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue