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