OBP60v/pages/racetracker.py

384 lines
12 KiB
Python

"""
Tracker mit MQTT client
Es gibt zwei Modi: Track und Race
A) Track: Es wird nur der Track gesended / aufgezeichnet
B) Race: Es werden zusätzlich Regattadaten angezeigt, das beinhaltet
die Auswahl einer Regatta, das Teilnehmen, das Aufgeben und die
Signalisierung vor und während einer Wettfahrt.
Das Tracking kann über eine Taste ein- und ausgeschaltet werden.
Um versehentliches Umschalten zu vermeiden ist eine Nachfrage
integriert.
- currentry only Ragatta hero supported
Behandlung von Verbindungsabbrüchen:
- on_disconnect
TODO
"""
import os
import cairo
from .page import Page
class RaceTracker(Page):
def __init__(self, pageno, cfg, appdata, boatdata):
super().__init__(pageno, cfg, appdata, boatdata)
self.bv_lat = boatdata.getRef("LAT")
self.bv_lon = boatdata.getRef("LON")
self.bv_sog = boatdata.getRef("SOG")
self.races = None
self.raceid = self.app.track.hero_raceid # Ausgewählte Regatta
self.menupos = 0
self.buttonlabel[1] = 'MODE'
self.buttonlabel[2] = 'INFO'
self.buttonlabel[5] = 'ABORT'
self.mode = 'N' # (N)ormal, (C)onfiguration, (M)itteilung
# Flaggengröße: 96 x 64 Pixel
self.flagpos = ((208, 140), (308, 140), (208, 210), (308, 210))
# Flaggen laden
flag = ('alpha', 'answer', 'black', 'blue', 'charlie', 'class',
'finish', 'foxtrot', 'hotel', 'india', 'november',
'orange', 'papa', 'repeat_one', 'sierra', 'start',
'uniform', 'xray', 'yankee', 'zulu')
# Mapping
self.flagmap = {
3: 'blue', # Zielflagge
7: 'xray', # Einzelrückruf
8: 'sierra', # Bahnverkürzung
9: 'november', # Abbruch
10: 'yankee', # Schwimmwesten
11: 'repeat_one', # Allgemeiner Rückruf
12: 'answer', # Startverschiebung
14: 'start', # Startflagge
15: 'class', # Klassenflagge
18: 'alpha',
19: 'hotel',
20: 'charlie',
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)
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.app.track.is_active():
self.buttonlabel[5] = 'OFF'
else:
self.buttonlabel[5] = 'ON'
else:
self.mode = 'N'
self.buttonlabel[2] = 'INFO'
self.buttonlabel[3] = '#PREV'
self.buttonlabel[4] = '#NEXT'
self.buttonlabel[5] = 'ABORT'
return True
elif buttonid == 2:
if self.mode == 'N':
self.mode = 'M' # Nachrichten der Wettfahrtleitung
self.buttonlabel[2] = ''
self.buttonlabel[5] = ''
return True
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.app.track.hero_raceid = self.raceid
print(f"Selected race '{self.raceid}'")
return True
elif buttonid == 5:
if self.mode == 'C':
# Tracking ein/-ausschalten
if self.app.track.is_active():
self.app.track.set_active(False)
self.buttonlabel[5] = 'ON'
else:
self.app.track.set_active(True)
self.buttonlabel[5] = 'OFF'
elif self.mode == 'M':
self.app.frontend.flashled.setColor('yellow')
#self.app.frontend.flashled.switchOn(4)
self.app.frontend.flashled.doFlash(2)
return True
return False
def draw_normal(self, ctx):
ctx.select_font_face("DSEG7 Classic")
ctx.set_font_size(80)
if self.app.track.is_active():
if self.app.track.hero_racestatus:
counter = self.app.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")
if self.app.track.hero_timedelta > 5:
ctx.select_font_face("Ubuntu", cairo.FontSlant.NORMAL, cairo.FontWeight.BOLD)
ctx.set_font_size(16)
ctx.move_to(8, 260)
ctx.show_text(f"!!! Time drift of {self.app.track.hero_timedelta} seconds")
x0 = 8
x1 = 96
y0 = 150
yoffset = 18
ctx.select_font_face("Ubuntu", cairo.FontSlant.NORMAL, cairo.FontWeight.BOLD)
ctx.set_font_size(16)
ctx.move_to(x0, y0)
ctx.show_text("Type")
ctx.move_to(x1, y0)
ctx.show_text(self.app.track.ttype)
y0 += yoffset
ctx.move_to(x0, y0)
ctx.show_text("Regatta")
ctx.move_to(x1, y0)
ctx.show_text(self.app.track.hero_raceid or '[not selected]')
y0 += yoffset
ctx.move_to(x0, y0)
ctx.show_text("Course")
ctx.move_to(x1, y0)
if self.app.track.hero_orgstatus and self.app.track.hero_raceid:
ctx.show_text(self.app.track.hero_orgstatus['races'][self.app.track.hero_raceid]['courseid'])
else:
ctx.show_text('[not selected]')
y0 += yoffset
ctx.move_to(x0, y0)
ctx.show_text("Latitude")
ctx.move_to(x1, y0)
ctx.show_text(self.bv_lat.format())
y0 += yoffset
ctx.move_to(x0, y0)
ctx.show_text("Longitude")
ctx.move_to(x1, y0)
ctx.show_text(self.bv_lon.format())
y0 += yoffset
ctx.move_to(x0, y0)
ctx.show_text("Speed")
ctx.move_to(x1, y0)
ctx.show_text(self.bv_sog.format())
# Flaggen
if self.app.track.hero_racestatus:
pos = 0
for f in self.app.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(24)
ctx.move_to(4, 42)
ctx.show_text("Tracker configuration")
# Linke Spalte mit Daten
x0 = 8 # Labelspalte
x1 = 88 # Datenspalte
y0 = 70
yoffset = 16
# Bootsdaten
ctx.set_font_size(20)
ctx.move_to(x0, y0)
ctx.show_text("Boat data")
y0 += yoffset + 5
ctx.set_font_size(16)
ctx.move_to(x0, y0)
ctx.show_text("Name")
ctx.move_to(x1, y0)
ctx.show_text(self.cfg['boat']['name'])
y0 += yoffset
ctx.move_to(x0, y0)
ctx.show_text("Class")
ctx.move_to(x1, y0)
ctx.show_text(self.cfg['boat']['class'])
y0 += yoffset
ctx.move_to(x0, y0)
ctx.show_text("Handicap")
ctx.move_to(x1, y0)
ctx.show_text(str(self.cfg['boat']['handicap']))
y0 += yoffset
ctx.move_to(x0, y0)
ctx.show_text("Club")
ctx.move_to(x1, y0)
ctx.show_text(self.cfg['boat']['club'])
y0 += yoffset
ctx.move_to(x0, y0)
ctx.show_text("Sailno.")
ctx.move_to(x1, y0)
ctx.show_text(self.cfg['boat']['sailno'])
# Trackerdaten
y0 += yoffset + 10
ctx.set_font_size(20)
ctx.move_to(x0, y0)
ctx.show_text("Tracker info")
y0 += yoffset + 5
ctx.set_font_size(16)
ctx.move_to(x0, y0)
ctx.show_text("Type")
ctx.move_to(x1, y0)
ctx.show_text(self.app.track.ttype)
y0 += yoffset
ctx.move_to(x0, y0)
ctx.show_text("Org.")
ctx.move_to(x1, y0)
if self.app.track.hero_orgstatus:
ctx.show_text(self.app.track.hero_orgstatus['orgname'])
else:
ctx.show_text("n/a")
y0 += yoffset
ctx.move_to(x0, y0)
ctx.show_text("Status")
ctx.move_to(x1, y0)
if not self.app.track.hero_racestatus:
ctx.show_text("inactive")
else:
#TODO Mehr Details
ctx.show_text("active")
y0 += yoffset
ctx.move_to(x0, y0)
ctx.show_text("Team")
ctx.move_to(x1, y0)
ctx.show_text(self.app.track.team)
y0 += yoffset
ctx.move_to(x0, y0)
ctx.show_text("Server")
ctx.move_to(x1, y0)
if self.app.track.mqtt_connected:
ctx.show_text("MQTT")
else:
ctx.show_text("offline")
# Rechte Spalte mit Regattaauswahl
x = 208
y = 70
yoffset = 21
# Mögliche Regatten
self.races = self.app.track.hero_get_races()
if len(self.races) == 1:
self.hero_raceid = self.races[0]
self.menupos = 1
ctx.set_font_size(20)
ctx.move_to(x, y)
ctx.show_text("Select Regatta")
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 = f"\xbb {r} \xab"
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_info(self, ctx):
"""
Nachricht der Wettfahrtleitung
"""
ctx.select_font_face("Ubuntu", cairo.FontSlant.NORMAL, cairo.FontWeight.BOLD)
ctx.set_font_size(24)
ctx.move_to(4, 42)
ctx.show_text("Message from race officers")
ctx.set_font_size(16)
if not self.app.track.hero_orgstatus or self.app.track.hero_orgstatus['message'] == '':
ctx.move_to(8, 72)
ctx.show_text("[ empty ]")
else:
lines = self.app.track.hero_orgstatus['message'].splitlines()
y = 72
for l in lines:
ctx.move_to(8, y)
ctx.show_text(l)
y += 18
def draw(self, ctx):
if self.mode == 'N':
self.draw_normal(ctx)
elif self.mode == 'C':
self.draw_config(ctx)
else:
self.draw_info(ctx)