Erste funktionsfähige Tracker-Version

This commit is contained in:
Thomas Hooge 2025-09-15 19:33:33 +02:00
parent ebb7b42d48
commit eb41bdafa4
6 changed files with 291 additions and 92 deletions

View File

@ -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()

View File

@ -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

View File

@ -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']:

View File

@ -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

View File

@ -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':

View File

@ -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()