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.shutdown = False # Globaler Ausschalter | ||||||
|         self.track = Tracker('NONE') |         self.track = Tracker('NONE') | ||||||
|         self.frontend = None |         self.frontend = None | ||||||
|  |         self.bv_lat = None | ||||||
|  |         self.bv_lon = None | ||||||
| 
 | 
 | ||||||
|         # Für u.a. Header-Indikatoren |         # Für u.a. Header-Indikatoren | ||||||
|         # TODO |         # TODO | ||||||
|  | @ -28,22 +30,38 @@ class AppData(): | ||||||
| 
 | 
 | ||||||
|     def setFrontend(self, frontend): |     def setFrontend(self, frontend): | ||||||
|         self.frontend = frontend # Referenz zur GUI |         self.frontend = frontend # Referenz zur GUI | ||||||
|  |         self.bv_lat = frontend.boatdata.getRef("LAT") | ||||||
|  |         self.bv_lon = frontend.boatdata.getRef("LON") | ||||||
| 
 | 
 | ||||||
|     def refreshStatus(self): |     def refreshStatus(self): | ||||||
|         self.status['AP'] = False |         self.status['AP'] = False # nicht implementiert | ||||||
| 
 | 
 | ||||||
|         self.status['TCP'] = False |         self.status['TCP'] = False | ||||||
|         self.status['WIFI'] = False |         self.status['WIFI'] = False | ||||||
|         for intf in os.listdir('/sys/class/net'): |         for intf in os.listdir('/sys/class/net'): | ||||||
|             statefile = os.path.join('/sys/class/net', interface, 'operstate') |             statefile = os.path.join('/sys/class/net', intf, 'operstate') | ||||||
|             wififile = os.path.join('/sys/class/net', interface, 'wireless') |             wififile = os.path.join('/sys/class/net', intf, 'wireless') | ||||||
|             if os.path.exists(statefile): |             if os.path.exists(statefile): | ||||||
|                 with open(statefile) as fh: |                 with open(statefile) as fh: | ||||||
|                     state = f.read().strip() |                     state = fh.read().strip() | ||||||
|                 if state == 'up': |                 if state == 'up': | ||||||
|                     if os.path.exists(wififile): |                     if os.path.exists(wififile): | ||||||
|                         self.status['WIFI'] = True |                         self.status['WIFI'] = True | ||||||
|                     else: |                     else: | ||||||
|                         self.status['TCP'] = True |                         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() |         self.status['TRK'] = self.track.is_active() | ||||||
|  |  | ||||||
							
								
								
									
										39
									
								
								nmea0183.py
								
								
								
								
							
							
						
						
									
										39
									
								
								nmea0183.py
								
								
								
								
							|  | @ -9,9 +9,10 @@ TODO Multi-Sentence verarbeiten | ||||||
| 
 | 
 | ||||||
| import serial | import serial | ||||||
| from setproctitle import setthreadtitle | from setproctitle import setthreadtitle | ||||||
|  | import pynmea2 | ||||||
| 
 | 
 | ||||||
| # Empfangsthread | # Empfangsthread | ||||||
| def rxd_0183(appdata, devname): | def rxd_0183(appdata,boatdata, devname): | ||||||
|     # Prüfe ob NMEA0183-Port vorhanden ist und sich öffnen läßt |     # Prüfe ob NMEA0183-Port vorhanden ist und sich öffnen läßt | ||||||
|     try: |     try: | ||||||
|         ser = serial.Serial(devname, 115200, timeout=3) |         ser = serial.Serial(devname, 115200, timeout=3) | ||||||
|  | @ -351,28 +352,28 @@ def VPW(boatdata, msg): | ||||||
| def VTG(boatdata, msg): | def VTG(boatdata, msg): | ||||||
|     # Track made good and speed over ground |     # Track made good and speed over ground | ||||||
|     """ |     """ | ||||||
| (('True Track made good', 'true_track', <class 'float'>), |     (('True Track made good', 'true_track', <class 'float'>), | ||||||
| ('True Track made good symbol', 'true_track_sym'), |     ('True Track made good symbol', 'true_track_sym'), | ||||||
| ('Magnetic Track made good', 'mag_track', <class 'decimal.Decimal'>), |     ('Magnetic Track made good', 'mag_track', <class 'decimal.Decimal'>), | ||||||
| ('Magnetic Track symbol', 'mag_track_sym'), |     ('Magnetic Track symbol', 'mag_track_sym'), | ||||||
| ('Speed over ground knots', 'spd_over_grnd_kts', <class 'decimal.Decimal'>), |     ('Speed over ground knots', 'spd_over_grnd_kts', <class 'decimal.Decimal'>), | ||||||
| ('Speed over ground symbol', 'spd_over_grnd_kts_sym'), |     ('Speed over ground symbol', 'spd_over_grnd_kts_sym'), | ||||||
| ('Speed over ground kmph', 'spd_over_grnd_kmph', <class 'float'>), |     ('Speed over ground kmph', 'spd_over_grnd_kmph', <class 'float'>), | ||||||
| ('Speed over ground kmph symbol', 'spd_over_grnd_kmph_sym'), |     ('Speed over ground kmph symbol', 'spd_over_grnd_kmph_sym'), | ||||||
| ('FAA mode indicator', 'faa_mode')) |     ('FAA mode indicator', 'faa_mode')) | ||||||
| ['', 'T', '', 'M', '0.117', 'N', '0.216', 'K', 'A'] |     ['', 'T', '', 'M', '0.117', 'N', '0.216', 'K', 'A'] | ||||||
|  |     $IIVTG,312.000000,T,,M,2.000000,N,3.704000,K,A*28 | ||||||
|     """ |     """ | ||||||
|     #print("-> VTG") |     if msg.faa_mode != 'A': | ||||||
|     # msg.true_track true_track_sym |         return | ||||||
|     # msg.mag_track mag_track_sym |  | ||||||
|     # msg.faa_mode |  | ||||||
|     #TODO klären was für Typen hier ankommen können |     #TODO klären was für Typen hier ankommen können | ||||||
|     # bytearray, str, decimal.Decimal? |     # bytearray, str, decimal.Decimal? | ||||||
|     #sog = float(msg.spd_over_grnd_kts) |  | ||||||
|     #str von OpenCPN: sog = float(msg.spd_over_grnd_kts[:-1]) |     #str von OpenCPN: sog = float(msg.spd_over_grnd_kts[:-1]) | ||||||
|     #boatdata.setValue("SOG", sog) |     #Ggf. ist OpenCPN buggy! | ||||||
|     #print("VTG", msg.spd_over_grnd_kts) |     cog = float(msg.true_track)        # in Grad | ||||||
|     print("VTG", msg) |     sog = float(msg.spd_over_grnd_kts) # in Knoten | ||||||
|  |     boatdata.setValue("COG", cog) | ||||||
|  |     boatdata.setValue("SOG", sog) | ||||||
| 
 | 
 | ||||||
| def VWR(boatdata, msg): | def VWR(boatdata, msg): | ||||||
|     # Relative Wind Speed and Angle |     # Relative Wind Speed and Angle | ||||||
|  |  | ||||||
							
								
								
									
										17
									
								
								obp60v.py
								
								
								
								
							
							
						
						
									
										17
									
								
								obp60v.py
								
								
								
								
							|  | @ -94,10 +94,10 @@ import cairo | ||||||
| import math | import math | ||||||
| import threading | import threading | ||||||
| import socket | import socket | ||||||
|  | import pynmea2 | ||||||
| import can | import can | ||||||
| import serial | import serial | ||||||
| import smbus2 | import smbus2 | ||||||
| import pynmea2 |  | ||||||
| import bme280 | import bme280 | ||||||
| import math | import math | ||||||
| import time | import time | ||||||
|  | @ -301,7 +301,6 @@ class Frontend(Gtk.Window): | ||||||
|     def __init__(self, cfg, appdata, device, boatdata, profile): |     def __init__(self, cfg, appdata, device, boatdata, profile): | ||||||
|         super().__init__() |         super().__init__() | ||||||
|         self.appdata = appdata |         self.appdata = appdata | ||||||
|         self.appdata.setFrontend(self) |  | ||||||
|         self.owndev = device |         self.owndev = device | ||||||
|         self.boatdata = boatdata |         self.boatdata = boatdata | ||||||
|         self._config = cfg['_config'] |         self._config = cfg['_config'] | ||||||
|  | @ -311,6 +310,7 @@ class Frontend(Gtk.Window): | ||||||
| 
 | 
 | ||||||
|         self.connect("delete-event", self.on_delete) |         self.connect("delete-event", self.on_delete) | ||||||
|         self.connect("destroy", self.on_destroy) |         self.connect("destroy", self.on_destroy) | ||||||
|  |         self.appdata.setFrontend(self) | ||||||
| 
 | 
 | ||||||
|         if self._fullscreen: |         if self._fullscreen: | ||||||
|             self.fullscreen() |             self.fullscreen() | ||||||
|  | @ -396,11 +396,13 @@ class Frontend(Gtk.Window): | ||||||
|             self.get_window().set_cursor(Gdk.Cursor(Gdk.CursorType.BLANK_CURSOR)) |             self.get_window().set_cursor(Gdk.Cursor(Gdk.CursorType.BLANK_CURSOR)) | ||||||
| 
 | 
 | ||||||
|     def run(self): |     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() |         self.show_all() | ||||||
|         Gtk.main() |         Gtk.main() | ||||||
| 
 | 
 | ||||||
|     def on_timer(self): |     def on_timer_fast(self): | ||||||
|         # Boatdata validator |         # Boatdata validator | ||||||
|         boatdata.updateValid(5) |         boatdata.updateValid(5) | ||||||
|         # Tastaturstatus an Seite durchreichen |         # Tastaturstatus an Seite durchreichen | ||||||
|  | @ -409,6 +411,10 @@ class Frontend(Gtk.Window): | ||||||
|         self.da.queue_draw() |         self.da.queue_draw() | ||||||
|         return True |         return True | ||||||
| 
 | 
 | ||||||
|  |     def on_timer_slow(self): | ||||||
|  |         appdata.refreshStatus() | ||||||
|  |         return True | ||||||
|  | 
 | ||||||
|     def on_draw(self, widget, ctx): |     def on_draw(self, widget, ctx): | ||||||
|         # Fenstertransparenz |         # Fenstertransparenz | ||||||
|         ctx.set_source_rgba(0, 0, 0, 0) |         ctx.set_source_rgba(0, 0, 0, 0) | ||||||
|  | @ -765,7 +771,7 @@ if __name__ == "__main__": | ||||||
|         t_rxd_n2k.start() |         t_rxd_n2k.start() | ||||||
|     if cfg['nmea0183']: |     if cfg['nmea0183']: | ||||||
|         print("NMEA0183 enabled, library version {}".format(pynmea2.version)) |         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() |         t_rxd_0183.start() | ||||||
|     if cfg['gps']: |     if cfg['gps']: | ||||||
|         print("GPS enabled (local)") |         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 = threading.Thread(target=rxd_network, args=(cfg['net_port'],cfg['net_addr'])) | ||||||
|         t_rxd_net.start() |         t_rxd_net.start() | ||||||
|     if cfg['tracker']['type'] != 'NONE': |     if cfg['tracker']['type'] != 'NONE': | ||||||
|  |         appdata.track.set_type( cfg['tracker']['type']) | ||||||
|         t_tracker = threading.Thread(target=appdata.track.mqtt_tracker, args=(cfg['tracker'],cfg['boat'],appdata,boatdata)) |         t_tracker = threading.Thread(target=appdata.track.mqtt_tracker, args=(cfg['tracker'],cfg['boat'],appdata,boatdata)) | ||||||
|         t_tracker.start() |         t_tracker.start() | ||||||
|     if not cfg['simulation']: |     if not cfg['simulation']: | ||||||
|  |  | ||||||
|  | @ -142,13 +142,9 @@ class Page(): | ||||||
|         ctx.select_font_face("Ubuntu", cairo.FontSlant.NORMAL, cairo.FontWeight.BOLD) |         ctx.select_font_face("Ubuntu", cairo.FontSlant.NORMAL, cairo.FontWeight.BOLD) | ||||||
|         ctx.set_font_size(16) |         ctx.set_font_size(16) | ||||||
|         ctx.move_to(0.5, 14.5) |         ctx.move_to(0.5, 14.5) | ||||||
|         ctx.show_text(f"N2K GPS") |         ctx.show_text(' '.join([s for s in self.appdata.status if self.appdata.status[s]])) | ||||||
|         ctx.stroke() |         ctx.stroke() | ||||||
| 
 | 
 | ||||||
|         # AP: Nicht implementiert |  | ||||||
|         # WIFI:  |  | ||||||
|         # /proc/net/wireless |  | ||||||
| 
 |  | ||||||
|         # Tastenstatus |         # Tastenstatus | ||||||
|         ctx.save() |         ctx.save() | ||||||
|         if self.keylock: |         if self.keylock: | ||||||
|  | @ -325,6 +321,7 @@ class Page(): | ||||||
|         ctx.stroke() |         ctx.stroke() | ||||||
| 
 | 
 | ||||||
|     def draw_text_boxed(self, ctx, x, y, w, h, content, inverted=False, border=False): |     def draw_text_boxed(self, ctx, x, y, w, h, content, inverted=False, border=False): | ||||||
|  |         ctx.save() | ||||||
|         ctx.set_line_width(1) |         ctx.set_line_width(1) | ||||||
|         # Background fill |         # Background fill | ||||||
|         ctx.set_source_rgb(*self.fgcolor) |         ctx.set_source_rgb(*self.fgcolor) | ||||||
|  | @ -343,6 +340,7 @@ class Page(): | ||||||
|         ctx.move_to(x + 4, y + h - 5 + 0.5) |         ctx.move_to(x + 4, y + h - 5 + 0.5) | ||||||
|         ctx.show_text(content) |         ctx.show_text(content) | ||||||
|         ctx.stroke() |         ctx.stroke() | ||||||
|  |         ctx.restore() | ||||||
| 
 | 
 | ||||||
| def wordwrap(text, wrap): | def wordwrap(text, wrap): | ||||||
|     # Wrap long line to multiple lines, monospaced character set |     # 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 os | ||||||
| import cairo | import cairo | ||||||
| from .page import Page | from .page import Page | ||||||
|  | from cfgmenu import Menu | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| class Tracker(Page): | class Tracker(Page): | ||||||
| 
 | 
 | ||||||
|     def __init__(self, pageno, cfg, appdata, boatdata): |     def __init__(self, pageno, cfg, appdata, boatdata): | ||||||
|         super().__init__(pageno, cfg, appdata, boatdata) |         super().__init__(pageno, cfg, appdata, boatdata) | ||||||
|         self._appdata = appdata |  | ||||||
|         self.bv_lat = boatdata.getRef("LAT") |         self.bv_lat = boatdata.getRef("LAT") | ||||||
|         self.bv_lon = boatdata.getRef("LON") |         self.bv_lon = boatdata.getRef("LON") | ||||||
|         self.bv_sog = boatdata.getRef("SOG") |         self.bv_sog = boatdata.getRef("SOG") | ||||||
|  |         self.races = None | ||||||
|  |         self.raceid = None # Ausgewählte Regatta | ||||||
|  |         self.menupos = 0 | ||||||
|         self.buttonlabel[1] = 'MODE' |         self.buttonlabel[1] = 'MODE' | ||||||
|         self.buttonlabel[2] = 'ON' |  | ||||||
|         self.mode = 'N' # (N)ormal, (C)onfiguration |         self.mode = 'N' # (N)ormal, (C)onfiguration | ||||||
| 
 | 
 | ||||||
|         # Flaggengröße: 96 x 64 Pixel |         # Flaggengröße: 96 x 64 Pixel | ||||||
|  | @ -43,31 +46,88 @@ class Tracker(Page): | ||||||
|                 'finish', 'hotel', 'india', 'november', 'orange',  |                 'finish', 'hotel', 'india', 'november', 'orange',  | ||||||
|                 'papa', 'repeat_one', 'sierra', 'start', 'uniform', |                 'papa', 'repeat_one', 'sierra', 'start', 'uniform', | ||||||
|                 'xray', 'yankee', 'zulu') |                 '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 = {} |         self.sym_flag = {} | ||||||
|         for f in flag: |         for f in flag: | ||||||
|             flagfile = os.path.join(cfg['imgpath'], 'flags', f + '.png') |             flagfile = os.path.join(cfg['imgpath'], 'flags', f + '.png') | ||||||
|             self.sym_flag[f] = cairo.ImageSurface.create_from_png(flagfile) |             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): |     def handle_key(self, buttonid): | ||||||
|         if buttonid == 1: |         if buttonid == 1: | ||||||
|             # Modus umschalten |             # Modus umschalten | ||||||
|             if self.mode == 'N': |             if self.mode == 'N': | ||||||
|                 self.mode = 'C' |                 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: |             else: | ||||||
|                 self.mode = 'N' |                 self.mode = 'N' | ||||||
|  |                 self.buttonlabel[2] = '' | ||||||
|  |                 self.buttonlabel[3] = '#PREV' | ||||||
|  |                 self.buttonlabel[4] = '#NEXT' | ||||||
|  |                 self.buttonlabel[5] = '' | ||||||
|  |             return True | ||||||
|         elif buttonid == 2: |         elif buttonid == 2: | ||||||
|             # Tracking ein/-ausschalten |             if self.mode == 'C': | ||||||
|             if self._appdata.track.is_active(): |                 # Up | ||||||
|                 self._appdata.track.set_active(False) |                 if self.menupos > 1: | ||||||
|                 self.buttonlabel[2] = 'ON' |                     self.menupos -= 1 | ||||||
|             else: |                 else: | ||||||
|                 self._appdata.track.set_active(True) |                     self.menupos = len(self.races) | ||||||
|                 self.buttonlabel[2] = 'OFF' |                 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: |         elif buttonid == 5: | ||||||
|             self._appdata.frontend.flashled.setColor('yellow') |             if self.mode == 'C': | ||||||
|             #self._appdata.frontend.flashled.switchOn(4) |                 # Tracking ein/-ausschalten | ||||||
|             self._appdata.frontend.flashled.doFlash(2) |                 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): |     def draw_normal(self, ctx): | ||||||
|         # Name |         # Name | ||||||
|  | @ -79,9 +139,19 @@ class Tracker(Page): | ||||||
|         ctx.select_font_face("DSEG7 Classic") |         ctx.select_font_face("DSEG7 Classic") | ||||||
|         ctx.set_font_size(80) |         ctx.set_font_size(80) | ||||||
| 
 | 
 | ||||||
|         if self._appdata.track.is_active(): |         if self.appdata.track.is_active(): | ||||||
|             ctx.move_to(20, 120) |             if self.appdata.track.hero_racestatus: | ||||||
|             ctx.show_text("-00:00") |                 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: |         else: | ||||||
|             ctx.move_to(100, 120) |             ctx.move_to(100, 120) | ||||||
|             ctx.show_text("off") |             ctx.show_text("off") | ||||||
|  | @ -94,58 +164,133 @@ class Tracker(Page): | ||||||
|         ctx.move_to(x0, y0) |         ctx.move_to(x0, y0) | ||||||
|         ctx.show_text("Type: ") |         ctx.show_text("Type: ") | ||||||
|         ctx.move_to(x1, y0) |         ctx.move_to(x1, y0) | ||||||
|         ctx.show_text(self._appdata.track.ttype) |         ctx.show_text(self.appdata.track.ttype) | ||||||
| 
 | 
 | ||||||
|         ctx.move_to(x0, y0 + 16) |         ctx.move_to(x0, y0 + 16) | ||||||
|         ctx.show_text("Regatta") |         ctx.show_text("Regatta") | ||||||
|         ctx.move_to(x1, y0 + 16) |         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.move_to(x0, y0 + 32) | ||||||
|         ctx.show_text("Lat=") |         ctx.show_text("Course") | ||||||
|         ctx.move_to(x1, y0 + 32) |         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.move_to(x0, y0 + 48) | ||||||
|         ctx.show_text("Lon=") |         ctx.show_text("Latitude") | ||||||
|         ctx.move_to(x1, y0 + 48) |         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.move_to(x0, y0 + 64) | ||||||
|         ctx.show_text("Sog=") |         ctx.show_text("Longitude") | ||||||
|         ctx.move_to(x1, y0 + 64) |         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()) |         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): |     def draw_config(self, ctx): | ||||||
| 
 | 
 | ||||||
|         ctx.select_font_face("Ubuntu", cairo.FontSlant.NORMAL, cairo.FontWeight.BOLD) |         ctx.select_font_face("Ubuntu", cairo.FontSlant.NORMAL, cairo.FontWeight.BOLD) | ||||||
|         ctx.set_font_size(32)  |         ctx.set_font_size(24) | ||||||
|         ctx.move_to(20, 80) |         ctx.move_to(4, 42) | ||||||
|         ctx.show_text("Tracker configuration") |         ctx.show_text("Tracker configuration") | ||||||
|         # Daten aus Konfiguration anzeigen | 
 | ||||||
|         # - boot |         x0 = 8 | ||||||
|         # - tracker |         x1 = 88 | ||||||
|  |         y0 = 96 | ||||||
|  | 
 | ||||||
|  |         ctx.set_font_size(20) | ||||||
|  |         ctx.move_to(x0, 75) | ||||||
|  |         ctx.show_text("Boat data") | ||||||
| 
 | 
 | ||||||
|         ctx.set_font_size(16) |         ctx.set_font_size(16) | ||||||
| 
 | 
 | ||||||
|         # Mögliche Regatten |         ctx.move_to(x0, y0) | ||||||
|         # -> auf Konfigurationsmodus verschieben |         ctx.show_text("Name") | ||||||
|         x = 250 |         ctx.move_to(x1, y0) | ||||||
|         y = 100 |         ctx.show_text(self.cfg['boat']['name']) | ||||||
|         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 + 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("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): |     def draw(self, ctx): | ||||||
|         if self.mode == 'N': |         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  | die Positionen in eine Warteschlange gesichert und nach  | ||||||
| Wiederherstellung der Verbindung übertragen. | Wiederherstellung der Verbindung übertragen. | ||||||
| 
 | 
 | ||||||
|  | TODO | ||||||
|  | - Nach einem Disconnect manuelle Neuverbindung ermöglichen | ||||||
|  | 
 | ||||||
| """ | """ | ||||||
| 
 | 
 | ||||||
| import os | import os | ||||||
|  | @ -23,11 +26,8 @@ import socket | ||||||
| class Tracker(): | class Tracker(): | ||||||
| 
 | 
 | ||||||
|     def __init__(self, trackertype='NONE'): |     def __init__(self, trackertype='NONE'): | ||||||
|         validtypes = ('HERO', 'SDCARD', 'SERVER', 'NONE') |         self.ttype = 'NONE' | ||||||
|         trackertype = trackertype.upper()  |         self.set_type(trackertype) | ||||||
|         if trackertype not in validtypes: |  | ||||||
|             raise TypeError(f"Invalid tracker type: '{valtype}'. Only supported: {validtypes}") |  | ||||||
|         self.ttype = trackertype |  | ||||||
| 
 | 
 | ||||||
|         self.appdata = None |         self.appdata = None | ||||||
| 
 | 
 | ||||||
|  | @ -44,7 +44,8 @@ class Tracker(): | ||||||
|         self.sog = None |         self.sog = None | ||||||
| 
 | 
 | ||||||
|         self.hero_orgstatus = 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? |         # TODO Wirklich alles im Tracker oder ist einiges generisch? | ||||||
|         self.boatid = None |         self.boatid = None | ||||||
|  | @ -75,6 +76,16 @@ class Tracker(): | ||||||
|     def set_active(self, newval): |     def set_active(self, newval): | ||||||
|        self.activated = 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): |     def get_position(self): | ||||||
|         # Positionsabfrage für die Payload |         # Positionsabfrage für die Payload | ||||||
|         # LAT, LON, TSPOS, SOG |         # LAT, LON, TSPOS, SOG | ||||||
|  | @ -124,25 +135,39 @@ class Tracker(): | ||||||
|             if self.hero_orgstatus['allLogout']: |             if self.hero_orgstatus['allLogout']: | ||||||
|                 print("All logout received!") |                 print("All logout received!") | ||||||
|                 client.disconnect() |                 client.disconnect() | ||||||
|                 sys.exit(0) # TODO nur die MQTT-Task beenden |                 self.activated = False | ||||||
|  |                 return | ||||||
|             if self.hero_orgstatus['message']: |             if self.hero_orgstatus['message']: | ||||||
|                 # TODO Alarm-Funktion nutzen? |                 # TODO Alarm-Funktion nutzen? | ||||||
|                 print("Nachricht der Wettfahrtkeitung:") |                 print("Nachricht der Wettfahrtkeitung:") | ||||||
|                 print(orgstatus['message']) |                 print(self.hero_orgstatus['message']) | ||||||
|  |             #print(self.hero_orgstatus) | ||||||
|         elif msg.topic.startswith("regattahero/racestatus/thomas"): |         elif msg.topic.startswith("regattahero/racestatus/thomas"): | ||||||
|             # kommt alle 1s |             # kommt alle 1s | ||||||
|             # dem Topic angehängt ist noch die raceid |             # dem Topic angehängt ist noch die raceid | ||||||
|             payload = json.loads(msg.payload) |             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 |             time: negativ: Zeit vor dem Start, positiv: Zeit nach dem Start | ||||||
|                   in Sekunden |                   in Sekunden | ||||||
|  |             flags: [0, 0, 14, 10]  | ||||||
|  |             raceactive: true bedeutet orange Flagge ist oben | ||||||
|  |             racestarted: true | ||||||
| 
 | 
 | ||||||
|             Signale der Wettfahrtleitung hier anzeigen |             Signale der Wettfahrtleitung hier anzeigen | ||||||
|                Regattaabbruch |                Regattaabbruch | ||||||
|                Bahnverkürzung |                Bahnverkürzung | ||||||
|                Rückrufe |                Rückrufe | ||||||
| 
 | 
 | ||||||
|  |             phase: 0 vor dem Start racephase: 1 | ||||||
|  |             racephase: 4 | ||||||
|  |             5 Vorbereitungssignal | ||||||
|  |             racephase: 6 nach vorbereitnug wieder runter | ||||||
|  |             7: Rennen gestartet | ||||||
|  | 
 | ||||||
|             """ |             """ | ||||||
|         else: |         else: | ||||||
|             print(f"UNKNOWN TOPIC: {msg.topic}") |             print(f"UNKNOWN TOPIC: {msg.topic}") | ||||||
|  | @ -152,16 +177,19 @@ class Tracker(): | ||||||
|         """ |         """ | ||||||
|         Payload vorbelegt als Template, so daß nur noch die veränderlichen |         Payload vorbelegt als Template, so daß nur noch die veränderlichen | ||||||
|         GPS-Daten eingefügt werden müssen: LAT LON SOG TIMESTAMP |         GPS-Daten eingefügt werden müssen: LAT LON SOG TIMESTAMP | ||||||
|  |          | ||||||
|  |         isTracking kann ausgeschaltet werden, | ||||||
|         """ |         """ | ||||||
|         lat = bv_lat.getValueRaw() |         lat = bv_lat.getValueRaw() | ||||||
|         lon = bv_lon.getValueRaw() |         lon = bv_lon.getValueRaw() | ||||||
|         sog = bv_sog.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']['lat'] = round(lat, 5) | ||||||
|             payload['gps']['lon'] = round(lon, 5) |             payload['gps']['lon'] = round(lon, 5) | ||||||
|             payload['gps']['speed'] = sog |             payload['gps']['speed'] = sog | ||||||
|             payload['gps']['timestamp'] = time.strftime("%Y-%m-%dT%H:%M:%S.000Z", time.gmtime()) |             payload['gps']['timestamp'] = time.strftime("%Y-%m-%dT%H:%M:%S.000Z", time.gmtime()) | ||||||
|              |             print(payload) | ||||||
|             client.publish(topic, json.dumps(payload)) |             client.publish(topic, json.dumps(payload)) | ||||||
|         else: |         else: | ||||||
|             print("No GPS data available. Nothing published!") |             print("No GPS data available. Nothing published!") | ||||||
|  | @ -189,13 +217,13 @@ class Tracker(): | ||||||
|         payload = { |         payload = { | ||||||
|             "passcode": cfg['passcode'], |             "passcode": cfg['passcode'], | ||||||
|             "orgid":  cfg['orgname'], |             "orgid":  cfg['orgname'], | ||||||
|             "raceid": "Demo Regatta", # TODO aus Selektion einstellen  |             "raceid": None, # Nach Auswahl einstellen | ||||||
|             "gps": { |             "gps": { | ||||||
|                 "lat": 0.0, |                 "lat": 0.0, | ||||||
|                 "lon": 0.0, |                 "lon": 0.0, | ||||||
|                 "speed": 0.0, |                 "speed": 0.0, | ||||||
|                 "age": 1000, |                 "age": 500, | ||||||
|                 "odo": 1000, |                 # "odo": 1000, # deprecated | ||||||
|                 "bat": 1.0, |                 "bat": 1.0, | ||||||
|                 "timestamp": ""  # ISO8601 Format mit Millisekunden in UTC |                 "timestamp": ""  # ISO8601 Format mit Millisekunden in UTC | ||||||
|                 }, |                 }, | ||||||
|  | @ -206,7 +234,9 @@ class Tracker(): | ||||||
|                 "boatclass": boat['class'], |                 "boatclass": boat['class'], | ||||||
|                 "handicap": boat['handicap'], |                 "handicap": boat['handicap'], | ||||||
|                 "club": boat['club'], |                 "club": boat['club'], | ||||||
|                 "boatname": boat['name'] |                 "boatname": boat['name'], | ||||||
|  |                 "isTracking": True, | ||||||
|  |                 "hasGivenUp": False | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|  | @ -218,7 +248,7 @@ class Tracker(): | ||||||
|         client.loop_start() |         client.loop_start() | ||||||
|         while not appdata.shutdown: |         while not appdata.shutdown: | ||||||
|             time.sleep(1) |             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) |                 self.mqtt_publish(client, topic, payload, bv_lat, bv_lon, bv_sog) | ||||||
|         client.loop_stop() |         client.loop_stop() | ||||||
|         client.disconnect() |         client.disconnect() | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue