Tracker in eigene Klasse und AppData eingeführt
This commit is contained in:
parent
fd673d5e55
commit
acbcfac425
|
@ -0,0 +1,10 @@
|
||||||
|
"""
|
||||||
|
Generische Applikationsdaten
|
||||||
|
|
||||||
|
"""
|
||||||
|
from tracker import Tracker
|
||||||
|
|
||||||
|
class AppData():
|
||||||
|
def __init__(self):
|
||||||
|
self.shutdown = False # Globaler Ausschalter
|
||||||
|
self.track = Tracker('NONE')
|
173
obp60v.py
173
obp60v.py
|
@ -104,12 +104,14 @@ import time
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from nmea2000 import Device, BoatData, History, HistoryBuffer
|
from nmea2000 import Device, BoatData, History, HistoryBuffer
|
||||||
from nmea2000 import parser
|
from nmea2000 import parser
|
||||||
import nmea0183
|
|
||||||
import pages
|
|
||||||
import struct
|
import struct
|
||||||
import uuid
|
import uuid
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
import nmea0183
|
||||||
|
from appdata import AppData
|
||||||
|
import pages
|
||||||
|
|
||||||
__author__ = "Thomas Hooge"
|
__author__ = "Thomas Hooge"
|
||||||
__copyright__ = "Copyleft 2024-2025, all rights reversed"
|
__copyright__ = "Copyleft 2024-2025, all rights reversed"
|
||||||
__version__ = "0.2"
|
__version__ = "0.2"
|
||||||
|
@ -118,6 +120,8 @@ __status__ = "Development"
|
||||||
|
|
||||||
cfg = {
|
cfg = {
|
||||||
'cfgfile': 'obp60v.conf',
|
'cfgfile': 'obp60v.conf',
|
||||||
|
'logdir': '~/.local/share/obp60v',
|
||||||
|
'logfile': 'obp60v.log',
|
||||||
'imgpath': os.path.join(sys.path[0], 'images'),
|
'imgpath': os.path.join(sys.path[0], 'images'),
|
||||||
'deviceid': 100,
|
'deviceid': 100,
|
||||||
'manufcode': 2046, # Open Boat Projects (OBP)
|
'manufcode': 2046, # Open Boat Projects (OBP)
|
||||||
|
@ -130,136 +134,13 @@ cfg = {
|
||||||
'boat': { }
|
'boat': { }
|
||||||
}
|
}
|
||||||
|
|
||||||
def mqtt_on_connect(client, userdata, flags, rc):
|
|
||||||
print(f"MQTT connected with result code {rc}")
|
|
||||||
#userdata['connect_rc'] = rc
|
|
||||||
if rc != 0:
|
|
||||||
# Result codes:
|
|
||||||
# 1: Connection Refused, unacceptable protocol version
|
|
||||||
# 2: Connection Refused, identifier rejected
|
|
||||||
# 3: Connection Refused, Server unavailable
|
|
||||||
# 4: Connection Refused, bad user name or password
|
|
||||||
# 5: Connection Refused, not authorized
|
|
||||||
#userdata['connect_ok'] = True
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
client.subscribe("regattahero/orgstatus/thomas")
|
|
||||||
client.subscribe("regattahero/racestatus/thomas/#")
|
|
||||||
#userdata['connect_ok'] = False
|
|
||||||
|
|
||||||
def mqtt_on_message(client, userdata, msg):
|
|
||||||
"""
|
|
||||||
TODO raceid über userdata? dann topic prüfen?
|
|
||||||
"""
|
|
||||||
if msg.topic == "regattahero/orgstatus/thomas":
|
|
||||||
# kommt alle 10s
|
|
||||||
orgstatus = json.loads(msg.payload)
|
|
||||||
if orgstatus['allLogout']:
|
|
||||||
print("All logout received!")
|
|
||||||
client.disconnect()
|
|
||||||
sys.exit(0) # TODO nur die MQTT-Task beenden
|
|
||||||
if orgstatus['message']:
|
|
||||||
# TODO Alarm-Funktion nutzen?
|
|
||||||
print("Nachricht der Wettfahrtkeitung:")
|
|
||||||
print(orgstatus['message'])
|
|
||||||
print(orgstatus['races'])
|
|
||||||
#for r in orgstatus['races']:
|
|
||||||
# print(f"Race: {r}")
|
|
||||||
elif msg.topic.startswith("regattahero/racestatus/thomas"):
|
|
||||||
# kommt alle 1s
|
|
||||||
# dem Topic angehängt ist noch die raceid
|
|
||||||
payload = json.loads(msg.payload)
|
|
||||||
racestatus = payload['racestatus']
|
|
||||||
"""
|
|
||||||
time: negativ: Zeit vor dem Start, positiv: Zeit nach dem Start
|
|
||||||
in Sekunden
|
|
||||||
|
|
||||||
Signale der Wettfahrtleitung hier anzeigen
|
|
||||||
Regattaabbruch
|
|
||||||
Bahnverkürzung
|
|
||||||
Rückrufe
|
|
||||||
|
|
||||||
"""
|
|
||||||
else:
|
|
||||||
print(f"UNKNOWN TOPIC: {msg.topic}")
|
|
||||||
print(msg.payload)
|
|
||||||
|
|
||||||
def mqtt_publish(client, topic, payload, bv_lat, bv_lon, bv_sog):
|
|
||||||
"""
|
|
||||||
Payload vorbelegt als Template, so daß nur noch die veränderlichen
|
|
||||||
GPS-Daten eingefügt werden müssen: LAT LON SOG TIMESTAMP
|
|
||||||
"""
|
|
||||||
lat = bv_lat.getValueRaw()
|
|
||||||
lon = bv_lon.getValueRaw()
|
|
||||||
sog = bv_sog.getValueRaw()
|
|
||||||
if lat and lon and sog:
|
|
||||||
payload['gps']['lat'] = round(lat, 5)
|
|
||||||
payload['gps']['lon'] = round(lon, 5)
|
|
||||||
payload['gps']['speed'] = sog
|
|
||||||
payload['gps']['timestamp'] = time.strftime("%Y-%m-%dT%H:%M:%S.000Z", time.gmtime())
|
|
||||||
|
|
||||||
client.publish(topic, json.dumps(payload))
|
|
||||||
else:
|
|
||||||
print("No GPS data available. Nothing published!")
|
|
||||||
|
|
||||||
def mqtt_tracker(cfg, boat):
|
|
||||||
import paho.mqtt.client as mqtt
|
|
||||||
print("MQTT tracker enabled")
|
|
||||||
client = mqtt.Client()
|
|
||||||
client.on_connect = mqtt_on_connect
|
|
||||||
client.on_message = mqtt_on_message
|
|
||||||
client.username_pw_set(username=cfg['mqtt_user'], password=cfg['mqtt_pass'])
|
|
||||||
try:
|
|
||||||
client.connect(cfg['host'], cfg['port'], 60)
|
|
||||||
except ConnectionRefusedError:
|
|
||||||
print("MQTT connection refused. Check username and password.")
|
|
||||||
return
|
|
||||||
|
|
||||||
topic = "regattahero/tracker/" + cfg['orgname']
|
|
||||||
payload = {
|
|
||||||
"passcode": cfg['passcode'],
|
|
||||||
"orgid": cfg['orgname'],
|
|
||||||
"raceid": "Demo Regatta", # TODO aus Selektion einstellen
|
|
||||||
"gps": {
|
|
||||||
"lat": 0.0,
|
|
||||||
"lon": 0.0,
|
|
||||||
"speed": 0.0,
|
|
||||||
"age": 1000,
|
|
||||||
"odo": 1000,
|
|
||||||
"bat": 1.0,
|
|
||||||
"timestamp": "" # ISO8601 Format mit Millisekunden in UTC
|
|
||||||
},
|
|
||||||
"boat": {
|
|
||||||
"boatid": cfg['uuid'],
|
|
||||||
"sailno": boat['sailno'],
|
|
||||||
"team": boat['team'],
|
|
||||||
"boatclass": boat['class'],
|
|
||||||
"handicap": boat['handicap'],
|
|
||||||
"club": boat['club'],
|
|
||||||
"boatname": boat['name']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Zugriff auf Boatdata: Referenzen für leichten schnellen Zugriff
|
|
||||||
bv_lat = boatdata.getRef("LAT")
|
|
||||||
bv_lon = boatdata.getRef("LON")
|
|
||||||
bv_sog = boatdata.getRef("SOG")
|
|
||||||
|
|
||||||
client.loop_start()
|
|
||||||
while not shutdown:
|
|
||||||
time.sleep(1)
|
|
||||||
if tracker_active:
|
|
||||||
mqtt_publish(client, topic, payload, bv_lat, bv_lon, bv_sog)
|
|
||||||
client.loop_stop()
|
|
||||||
client.disconnect()
|
|
||||||
|
|
||||||
def rxd_n2k(device):
|
def rxd_n2k(device):
|
||||||
setthreadtitle("N2Klistener")
|
setthreadtitle("N2Klistener")
|
||||||
bus = can.Bus(interface='socketcan', channel=device, bitrate=250000);
|
bus = can.Bus(interface='socketcan', channel=device, bitrate=250000);
|
||||||
wip = False
|
wip = False
|
||||||
sc = 0
|
sc = 0
|
||||||
nf = 0
|
nf = 0
|
||||||
while not shutdown:
|
while not appdata.shutdown:
|
||||||
msg = bus.recv(2)
|
msg = bus.recv(2)
|
||||||
if not msg:
|
if not msg:
|
||||||
continue
|
continue
|
||||||
|
@ -332,7 +213,7 @@ def rxd_0183(devname):
|
||||||
print("NMEA0183 serial port not available")
|
print("NMEA0183 serial port not available")
|
||||||
return
|
return
|
||||||
setthreadtitle("0183listener")
|
setthreadtitle("0183listener")
|
||||||
while not shutdown:
|
while not appdata.shutdown:
|
||||||
raw = ser.readline().decode('ascii')
|
raw = ser.readline().decode('ascii')
|
||||||
if len(raw.strip()) == 0:
|
if len(raw.strip()) == 0:
|
||||||
continue
|
continue
|
||||||
|
@ -372,7 +253,7 @@ def rxd_gps(devname, devspeed):
|
||||||
print("GPS serial port not available")
|
print("GPS serial port not available")
|
||||||
return
|
return
|
||||||
setthreadtitle("GPSlistener")
|
setthreadtitle("GPSlistener")
|
||||||
while not shutdown:
|
while not appdata.shutdown:
|
||||||
try:
|
try:
|
||||||
msg = pynmea2.parse(ser.readline().decode('ascii'))
|
msg = pynmea2.parse(ser.readline().decode('ascii'))
|
||||||
except pynmea2.nmea.ParseError:
|
except pynmea2.nmea.ParseError:
|
||||||
|
@ -394,7 +275,7 @@ def rxd_network(address, port):
|
||||||
# Wir verwenden UDP. Ein verlorenes Paket tut uns nicht weh.
|
# Wir verwenden UDP. Ein verlorenes Paket tut uns nicht weh.
|
||||||
sock = socket.socket()
|
sock = socket.socket()
|
||||||
sock.connect((address, port))
|
sock.connect((address, port))
|
||||||
while not shutdown:
|
while not appdata.shutdown:
|
||||||
time.sleep(0.5)
|
time.sleep(0.5)
|
||||||
sock.close()
|
sock.close()
|
||||||
|
|
||||||
|
@ -433,7 +314,7 @@ def datareader(cfg, history):
|
||||||
g = g_tick(1)
|
g = g_tick(1)
|
||||||
|
|
||||||
n = 0
|
n = 0
|
||||||
while not shutdown:
|
while not appdata.shutdown:
|
||||||
time.sleep(next(g))
|
time.sleep(next(g))
|
||||||
# BME280 abfragen
|
# BME280 abfragen
|
||||||
if cfg['bme280']:
|
if cfg['bme280']:
|
||||||
|
@ -454,8 +335,9 @@ def datareader(cfg, history):
|
||||||
|
|
||||||
class Frontend(Gtk.Window):
|
class Frontend(Gtk.Window):
|
||||||
|
|
||||||
def __init__(self, cfg, device, boatdata, profile):
|
def __init__(self, cfg, appdata, device, boatdata, profile):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
self.appdata = appdata
|
||||||
self.owndev = device
|
self.owndev = device
|
||||||
self.boatdata = boatdata
|
self.boatdata = boatdata
|
||||||
self._config = cfg['_config']
|
self._config = cfg['_config']
|
||||||
|
@ -740,10 +622,10 @@ def init_profile(config, cfg, boatdata):
|
||||||
# Nummer und Art ermitteln
|
# Nummer und Art ermitteln
|
||||||
pageno = int(s[4:])
|
pageno = int(s[4:])
|
||||||
pagedef[pageno] = {'type': config.get(s, "type")}
|
pagedef[pageno] = {'type': config.get(s, "type")}
|
||||||
# Hole ein bin maximal 4 Werte je Seite
|
# Hole ein bin maximal 6 Werte je Seite
|
||||||
values = {}
|
values = {}
|
||||||
valno = 1
|
valno = 1
|
||||||
for i in (1, 2, 3, 4):
|
for i in (1, 2, 3, 4, 5, 6):
|
||||||
try:
|
try:
|
||||||
values[i] = config.get(s, f"value{i}")
|
values[i] = config.get(s, f"value{i}")
|
||||||
except configparser.NoOptionError:
|
except configparser.NoOptionError:
|
||||||
|
@ -762,10 +644,10 @@ def init_profile(config, cfg, boatdata):
|
||||||
# Klasse nicht vorhanden, Seite wird nicht benutzt
|
# Klasse nicht vorhanden, Seite wird nicht benutzt
|
||||||
print(f"Klasse '{p['type']}' nicht gefunden")
|
print(f"Klasse '{p['type']}' nicht gefunden")
|
||||||
continue
|
continue
|
||||||
c = cls(i, cfg, boatdata, *[v for v in p['values'].values()])
|
c = cls(i, cfg, appdata, boatdata, *[v for v in p['values'].values()])
|
||||||
clist[i] = c
|
clist[i] = c
|
||||||
return clist
|
return clist
|
||||||
|
|
||||||
def set_loglevel(nr):
|
def set_loglevel(nr):
|
||||||
"""
|
"""
|
||||||
Umsetzen der Nummer auf einen für "logging" passenden Wert
|
Umsetzen der Nummer auf einen für "logging" passenden Wert
|
||||||
|
@ -779,11 +661,11 @@ def set_loglevel(nr):
|
||||||
nr = 0
|
nr = 0
|
||||||
return level[nr]
|
return level[nr]
|
||||||
|
|
||||||
def init_logging(logdir):
|
def init_logging(logdir, logfile='obp60v.log'):
|
||||||
global log
|
global log
|
||||||
os.makedirs(logdir, exist_ok=True)
|
os.makedirs(logdir, exist_ok=True)
|
||||||
log = logging.getLogger(os.path.basename(sys.argv[0]))
|
log = logging.getLogger(os.path.basename(sys.argv[0]))
|
||||||
hdlr = logging.handlers.RotatingFileHandler(os.path.join(logdir, 'obp60v.log'), maxBytes=5242880, backupCount=5)
|
hdlr = logging.handlers.RotatingFileHandler(os.path.join(logdir, logfile), maxBytes=5242880, backupCount=5)
|
||||||
formatter = logging.Formatter('%(asctime)s %(name)s %(levelname)s: %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
|
formatter = logging.Formatter('%(asctime)s %(name)s %(levelname)s: %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
|
||||||
hdlr.setFormatter(formatter)
|
hdlr.setFormatter(formatter)
|
||||||
log.addHandler(hdlr)
|
log.addHandler(hdlr)
|
||||||
|
@ -797,8 +679,8 @@ if __name__ == "__main__":
|
||||||
|
|
||||||
setproctitle("obp60v")
|
setproctitle("obp60v")
|
||||||
|
|
||||||
shutdown = False
|
# Globale Daten, u.a. auch Shutdown-Indikator
|
||||||
tracker_active = False
|
appdata = AppData()
|
||||||
|
|
||||||
owndevice = Device(100)
|
owndevice = Device(100)
|
||||||
# Hardcoding device, not intended to change
|
# Hardcoding device, not intended to change
|
||||||
|
@ -867,6 +749,7 @@ if __name__ == "__main__":
|
||||||
cfg['tracker']['mqtt_pass'] = config.get('tracker', 'mqtt_pass')
|
cfg['tracker']['mqtt_pass'] = config.get('tracker', 'mqtt_pass')
|
||||||
cfg['tracker']['orgname'] = config.get('tracker', 'orgname')
|
cfg['tracker']['orgname'] = config.get('tracker', 'orgname')
|
||||||
cfg['tracker']['passcode'] = config.get('tracker', 'passcode')
|
cfg['tracker']['passcode'] = config.get('tracker', 'passcode')
|
||||||
|
cfg['tracker']['logdir'] = cfg['logdir']
|
||||||
|
|
||||||
# Boat data
|
# Boat data
|
||||||
cfg['boat']['name'] = config.get('boat', 'name')
|
cfg['boat']['name'] = config.get('boat', 'name')
|
||||||
|
@ -892,7 +775,7 @@ if __name__ == "__main__":
|
||||||
boatdata.enableSimulation()
|
boatdata.enableSimulation()
|
||||||
|
|
||||||
# Protokollierung
|
# Protokollierung
|
||||||
init_logging(os.path.expanduser("~/.local/share/obp60v"))
|
init_logging(os.path.expanduser(cfg['logdir']), cfg['logfile'])
|
||||||
log.info("Logging initialized")
|
log.info("Logging initialized")
|
||||||
|
|
||||||
# Gerät initialisieren u.a. mit den genutzten Seiten
|
# Gerät initialisieren u.a. mit den genutzten Seiten
|
||||||
|
@ -916,7 +799,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':
|
||||||
t_tracker = threading.Thread(target=mqtt_tracker, args=(cfg['tracker'],cfg['boat']))
|
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']:
|
||||||
if cfg['bme280']:
|
if cfg['bme280']:
|
||||||
|
@ -925,9 +808,9 @@ if __name__ == "__main__":
|
||||||
else:
|
else:
|
||||||
print("Simulation mode enabled")
|
print("Simulation mode enabled")
|
||||||
|
|
||||||
app = Frontend(cfg, owndevice, boatdata, profile)
|
app = Frontend(cfg, appdata, owndevice, boatdata, profile)
|
||||||
app.run()
|
app.run()
|
||||||
shutdown = True
|
appdata.shutdown = True
|
||||||
if cfg['can']:
|
if cfg['can']:
|
||||||
t_rxd_n2k.join()
|
t_rxd_n2k.join()
|
||||||
if cfg['nmea0183']:
|
if cfg['nmea0183']:
|
||||||
|
@ -936,8 +819,10 @@ if __name__ == "__main__":
|
||||||
t_rxd_gps.join()
|
t_rxd_gps.join()
|
||||||
if cfg['network']:
|
if cfg['network']:
|
||||||
t_rxd_net.join()
|
t_rxd_net.join()
|
||||||
|
if cfg['tracker']['type'] != 'NONE':
|
||||||
|
t_tracker.join()
|
||||||
if not cfg['simulation'] and cfg['bme280']:
|
if not cfg['simulation'] and cfg['bme280']:
|
||||||
t_data.join()
|
t_data.join()
|
||||||
print("Another fine product of the Sirius Cybernetics Corporation.")
|
|
||||||
|
|
||||||
print(boatdata)
|
print(boatdata)
|
||||||
|
print("Another fine product of the Sirius Cybernetics Corporation.")
|
||||||
|
|
|
@ -36,7 +36,7 @@ from .page import Page
|
||||||
|
|
||||||
class Anchor(Page):
|
class Anchor(Page):
|
||||||
|
|
||||||
def __init__(self, pageno, cfg, boatdata):
|
def __init__(self, pageno, cfg, appdata, boatdata):
|
||||||
super().__init__(pageno, cfg, boatdata)
|
super().__init__(pageno, cfg, boatdata)
|
||||||
self.sym_anchor = cairo.ImageSurface.create_from_png(os.path.join(cfg['imgpath'], "anchor.png"))
|
self.sym_anchor = cairo.ImageSurface.create_from_png(os.path.join(cfg['imgpath'], "anchor.png"))
|
||||||
self.buttonlabel[1] = 'MODE'
|
self.buttonlabel[1] = 'MODE'
|
||||||
|
|
|
@ -5,7 +5,7 @@ from .page import Page
|
||||||
|
|
||||||
class ApparentWind(Page):
|
class ApparentWind(Page):
|
||||||
|
|
||||||
def __init__(self, pageno, cfg, boatdata):
|
def __init__(self, pageno, cfg, appdata, boatdata):
|
||||||
super().__init__(pageno, cfg, boatdata)
|
super().__init__(pageno, cfg, boatdata)
|
||||||
self.buttonlabel[1] = 'MODE'
|
self.buttonlabel[1] = 'MODE'
|
||||||
self.mode = 'L' # (W)ind (L)ens
|
self.mode = 'L' # (W)ind (L)ens
|
||||||
|
|
|
@ -11,7 +11,7 @@ from .page import Page
|
||||||
|
|
||||||
class Autobahn(Page):
|
class Autobahn(Page):
|
||||||
|
|
||||||
def __init__(self, pageno, cfg, boatdata):
|
def __init__(self, pageno, cfg, appdata, boatdata):
|
||||||
super().__init__(pageno, cfg, boatdata)
|
super().__init__(pageno, cfg, boatdata)
|
||||||
self.xte = self.bd.getRef("XTE")
|
self.xte = self.bd.getRef("XTE")
|
||||||
self.cog = self.bd.getRef("COG")
|
self.cog = self.bd.getRef("COG")
|
||||||
|
|
|
@ -40,7 +40,7 @@ from .page import Page
|
||||||
|
|
||||||
class Barograph(Page):
|
class Barograph(Page):
|
||||||
|
|
||||||
def __init__(self, pageno, cfg, boatdata):
|
def __init__(self, pageno, cfg, appdata, boatdata):
|
||||||
super().__init__(pageno, cfg, boatdata)
|
super().__init__(pageno, cfg, boatdata)
|
||||||
# Meßwert alle 15 Minuten:
|
# Meßwert alle 15 Minuten:
|
||||||
# 84 Stunden * 4 Werte je Stunde = 336 Meßwerte
|
# 84 Stunden * 4 Werte je Stunde = 336 Meßwerte
|
||||||
|
|
|
@ -12,7 +12,7 @@ class Battery(Page):
|
||||||
|
|
||||||
avg = (1, 10, 60, 300);
|
avg = (1, 10, 60, 300);
|
||||||
|
|
||||||
def __init__(self, pageno, cfg, boatdata):
|
def __init__(self, pageno, cfg, appdata, boatdata):
|
||||||
super().__init__(pageno, cfg, boatdata)
|
super().__init__(pageno, cfg, boatdata)
|
||||||
self.avgindex = 0
|
self.avgindex = 0
|
||||||
self.buttonlabel[1] = 'AVG'
|
self.buttonlabel[1] = 'AVG'
|
||||||
|
|
|
@ -9,7 +9,7 @@ from .page import Page
|
||||||
|
|
||||||
class BME280(Page):
|
class BME280(Page):
|
||||||
|
|
||||||
def __init__(self, pageno, cfg, boatdata):
|
def __init__(self, pageno, cfg, appdata, boatdata):
|
||||||
super().__init__(pageno, cfg, boatdata)
|
super().__init__(pageno, cfg, boatdata)
|
||||||
#self.ref1 = self.bd.getRef(boatvalue1)
|
#self.ref1 = self.bd.getRef(boatvalue1)
|
||||||
#self.ref2 = self.bd.getRef(boatvalue2)
|
#self.ref2 = self.bd.getRef(boatvalue2)
|
||||||
|
|
|
@ -20,7 +20,7 @@ import astral
|
||||||
|
|
||||||
class Clock(Page):
|
class Clock(Page):
|
||||||
|
|
||||||
def __init__(self, pageno, cfg, boatdata):
|
def __init__(self, pageno, cfg, appdata, boatdata):
|
||||||
super().__init__(pageno, cfg, boatdata)
|
super().__init__(pageno, cfg, boatdata)
|
||||||
self.buttonlabel[1] = 'MODE'
|
self.buttonlabel[1] = 'MODE'
|
||||||
self.buttonlabel[2] = 'TZ'
|
self.buttonlabel[2] = 'TZ'
|
||||||
|
|
|
@ -22,7 +22,7 @@ import nmea2000.lookup
|
||||||
|
|
||||||
class Fluid(Page):
|
class Fluid(Page):
|
||||||
|
|
||||||
def __init__(self, pageno, cfg, boatdata, fluidtype):
|
def __init__(self, pageno, cfg, appdata, boatdata, fluidtype):
|
||||||
super().__init__(pageno, cfg, boatdata)
|
super().__init__(pageno, cfg, boatdata)
|
||||||
self.fluidtype = int(fluidtype)
|
self.fluidtype = int(fluidtype)
|
||||||
if self.fluidtype == 0:
|
if self.fluidtype == 0:
|
||||||
|
|
|
@ -20,7 +20,7 @@ from .page import Page
|
||||||
|
|
||||||
class FourValues(Page):
|
class FourValues(Page):
|
||||||
|
|
||||||
def __init__(self, pageno, cfg, boatdata, boatvalue1, boatvalue2, boatvalue3, boatvalue4):
|
def __init__(self, pageno, cfg, appdata, boatdata, boatvalue1, boatvalue2, boatvalue3, boatvalue4):
|
||||||
super().__init__(pageno, cfg, boatdata)
|
super().__init__(pageno, cfg, boatdata)
|
||||||
self.value1 = boatvalue1
|
self.value1 = boatvalue1
|
||||||
self.value2 = boatvalue2
|
self.value2 = boatvalue2
|
||||||
|
|
|
@ -18,7 +18,7 @@ from .page import Page
|
||||||
|
|
||||||
class FourValues2(Page):
|
class FourValues2(Page):
|
||||||
|
|
||||||
def __init__(self, pageno, cfg, boatdata, boatvalue1, boatvalue2, boatvalue3, boatvalue4):
|
def __init__(self, pageno, cfg, appdata, boatdata, boatvalue1, boatvalue2, boatvalue3, boatvalue4):
|
||||||
super().__init__(pageno, cfg, boatdata)
|
super().__init__(pageno, cfg, boatdata)
|
||||||
self.value1 = boatvalue1
|
self.value1 = boatvalue1
|
||||||
self.value2 = boatvalue2
|
self.value2 = boatvalue2
|
||||||
|
|
|
@ -15,7 +15,7 @@ from .page import Page
|
||||||
|
|
||||||
class Keel(Page):
|
class Keel(Page):
|
||||||
|
|
||||||
def __init__(self, pageno, cfg, boatdata):
|
def __init__(self, pageno, cfg, appdata, boatdata):
|
||||||
super().__init__(pageno, cfg, boatdata)
|
super().__init__(pageno, cfg, boatdata)
|
||||||
# Wert für Kielrotation
|
# Wert für Kielrotation
|
||||||
self.valref = self.bd.getRef("xdrRotK")
|
self.valref = self.bd.getRef("xdrRotK")
|
||||||
|
|
|
@ -3,7 +3,7 @@ from .page import Page
|
||||||
|
|
||||||
class OneValue(Page):
|
class OneValue(Page):
|
||||||
|
|
||||||
def __init__(self, pageno, cfg, boatdata, boatvalue):
|
def __init__(self, pageno, cfg, appdata, boatdata, boatvalue):
|
||||||
super().__init__(pageno, cfg, boatdata)
|
super().__init__(pageno, cfg, boatdata)
|
||||||
self.ref1 = self.bd.getRef(boatvalue)
|
self.ref1 = self.bd.getRef(boatvalue)
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ from .page import Page
|
||||||
|
|
||||||
class Rudder(Page):
|
class Rudder(Page):
|
||||||
|
|
||||||
def __init__(self, pageno, cfg, boatdata):
|
def __init__(self, pageno, cfg, appdata, boatdata):
|
||||||
super().__init__(pageno, cfg, boatdata)
|
super().__init__(pageno, cfg, boatdata)
|
||||||
self.buttonlabel[1] = 'MODE'
|
self.buttonlabel[1] = 'MODE'
|
||||||
self.mode = 'P'
|
self.mode = 'P'
|
||||||
|
|
|
@ -18,7 +18,8 @@ from .page import Page
|
||||||
|
|
||||||
class SixValues(Page):
|
class SixValues(Page):
|
||||||
|
|
||||||
def __init__(self, pageno, cfg, boatdata, boatvalue1, boatvalue2, boatvalue3, boatvalue4):
|
def __init__(self, pageno, cfg, appdata, boatdata, boatvalue1, boatvalue2,
|
||||||
|
boatvalue3, boatvalue4, boatvalue5, boatvalue6):
|
||||||
super().__init__(pageno, cfg, boatdata)
|
super().__init__(pageno, cfg, boatdata)
|
||||||
self.value1 = boatvalue1
|
self.value1 = boatvalue1
|
||||||
self.value2 = boatvalue2
|
self.value2 = boatvalue2
|
||||||
|
|
|
@ -14,7 +14,7 @@ from .page import Page
|
||||||
|
|
||||||
class SkyView(Page):
|
class SkyView(Page):
|
||||||
|
|
||||||
def __init__(self, pageno, cfg, boatdata):
|
def __init__(self, pageno, cfg, appdata, boatdata):
|
||||||
super().__init__(pageno, cfg, boatdata)
|
super().__init__(pageno, cfg, boatdata)
|
||||||
|
|
||||||
def pol2cart(azimut, elevation):
|
def pol2cart(azimut, elevation):
|
||||||
|
|
|
@ -3,7 +3,7 @@ from .page import Page
|
||||||
|
|
||||||
class ThreeValues(Page):
|
class ThreeValues(Page):
|
||||||
|
|
||||||
def __init__(self, pageno, cfg, boatdata, boatvalue1, boatvalue2, boatvalue3):
|
def __init__(self, pageno, cfg, appdata, boatdata, boatvalue1, boatvalue2, boatvalue3):
|
||||||
super().__init__(pageno, cfg, boatdata)
|
super().__init__(pageno, cfg, boatdata)
|
||||||
self.ref1 = self.bd.getRef(boatvalue1)
|
self.ref1 = self.bd.getRef(boatvalue1)
|
||||||
self.ref2 = self.bd.getRef(boatvalue2)
|
self.ref2 = self.bd.getRef(boatvalue2)
|
||||||
|
|
|
@ -11,14 +11,18 @@ from .page import Page
|
||||||
|
|
||||||
class Tracker(Page):
|
class Tracker(Page):
|
||||||
|
|
||||||
def __init__(self, pageno, cfg, boatdata):
|
def __init__(self, pageno, cfg, appdata, boatdata):
|
||||||
super().__init__(pageno, cfg, boatdata)
|
super().__init__(pageno, cfg, boatdata)
|
||||||
|
self._appdata = appdata
|
||||||
self.buttonlabel[1] = 'MODE'
|
self.buttonlabel[1] = 'MODE'
|
||||||
|
print(cfg)
|
||||||
|
|
||||||
def handle_key(self, buttonid):
|
def handle_key(self, buttonid):
|
||||||
global tracker_active;
|
|
||||||
if buttonid == 1:
|
if buttonid == 1:
|
||||||
tracker_active = not tracker_active
|
if self._appdata.track.is_active():
|
||||||
|
self._appdata.track.set_active(False)
|
||||||
|
else:
|
||||||
|
self._appdata.track.set_active(True)
|
||||||
|
|
||||||
def draw(self, ctx):
|
def draw(self, ctx):
|
||||||
# Name
|
# Name
|
||||||
|
@ -30,7 +34,7 @@ class Tracker(Page):
|
||||||
ctx.set_font_size(16)
|
ctx.set_font_size(16)
|
||||||
ctx.move_to(20, 140)
|
ctx.move_to(20, 140)
|
||||||
ctx.show_text("active: ")
|
ctx.show_text("active: ")
|
||||||
if tracker_active:
|
if self._appdata.track.is_active():
|
||||||
ctx.show_text("yes")
|
ctx.show_text("yes")
|
||||||
else:
|
else:
|
||||||
ctx.show_text("no")
|
ctx.show_text("no")
|
||||||
|
|
|
@ -16,7 +16,7 @@ from .page import Page
|
||||||
|
|
||||||
class TwoValues(Page):
|
class TwoValues(Page):
|
||||||
|
|
||||||
def __init__(self, pageno, cfg, boatdata, boatvalue1, boatvalue2):
|
def __init__(self, pageno, cfg, appdata, boatdata, boatvalue1, boatvalue2):
|
||||||
super().__init__(pageno, cfg, boatdata)
|
super().__init__(pageno, cfg, boatdata)
|
||||||
self.ref1 = self.bd.getRef(boatvalue1)
|
self.ref1 = self.bd.getRef(boatvalue1)
|
||||||
self.ref2 = self.bd.getRef(boatvalue2)
|
self.ref2 = self.bd.getRef(boatvalue2)
|
||||||
|
|
|
@ -17,7 +17,7 @@ class Voltage(Page):
|
||||||
|
|
||||||
avg = (1, 10, 60, 300);
|
avg = (1, 10, 60, 300);
|
||||||
|
|
||||||
def __init__(self, pageno, cfg, boatdata):
|
def __init__(self, pageno, cfg, appdata, boatdata):
|
||||||
super().__init__(pageno, cfg, boatdata)
|
super().__init__(pageno, cfg, boatdata)
|
||||||
self.trend = True
|
self.trend = True
|
||||||
self.mode = 'A'
|
self.mode = 'A'
|
||||||
|
|
160
tracker.py
160
tracker.py
|
@ -13,12 +13,170 @@ Wiederherstellung der Verbindung übertragen.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
import paho.mqtt.client as mqtt
|
||||||
|
import json
|
||||||
|
|
||||||
class Tracker():
|
class Tracker():
|
||||||
|
|
||||||
def __init__(self, trackertype):
|
def __init__(self, trackertype='NONE'):
|
||||||
validtypes = ('HERO', 'SDCARD', 'SERVER', 'NONE')
|
validtypes = ('HERO', 'SDCARD', 'SERVER', 'NONE')
|
||||||
trackertype = trackertype.upper()
|
trackertype = trackertype.upper()
|
||||||
if trackertype not in validtypes:
|
if trackertype not in validtypes:
|
||||||
raise TypeError(f"Invalid tracker type: '{valtype}'. Only supported: {validtypes}")
|
raise TypeError(f"Invalid tracker type: '{valtype}'. Only supported: {validtypes}")
|
||||||
self.ttype = trackertype
|
self.ttype = trackertype
|
||||||
|
self.activated = False
|
||||||
|
self.races = set() # Liste der Regatten, eindeutige Namen
|
||||||
|
self.courses = set() # Liste der Bahnen, eindeutige Namen
|
||||||
|
|
||||||
|
self.lat = None # last latitude
|
||||||
|
self.lon = None # last longitude
|
||||||
|
self.tspos = None # timestamp (hh:ss:mm) as datetime.time
|
||||||
|
self.sog = None
|
||||||
|
|
||||||
|
|
||||||
|
def is_active(self):
|
||||||
|
return self.activated
|
||||||
|
|
||||||
|
def set_active(self, newval):
|
||||||
|
self.activated = newval
|
||||||
|
|
||||||
|
def get_position(self):
|
||||||
|
# Positionsabfrage für die Payload
|
||||||
|
# LAT, LON, TSPOS, SOG
|
||||||
|
return (self.lat, self.lon, self.tspos, self.sog)
|
||||||
|
|
||||||
|
def hero_add_race(self, raceid):
|
||||||
|
self.races.add(raceid)
|
||||||
|
|
||||||
|
def hero_set_races(self, newraces):
|
||||||
|
self.races = set(newraces)
|
||||||
|
|
||||||
|
def mqtt_on_connect(self, client, userdata, flags, rc):
|
||||||
|
print(f"MQTT connected with result code {rc}")
|
||||||
|
#userdata['connect_rc'] = rc
|
||||||
|
if rc != 0:
|
||||||
|
# Result codes:
|
||||||
|
# 1: Connection Refused, unacceptable protocol version
|
||||||
|
# 2: Connection Refused, identifier rejected
|
||||||
|
# 3: Connection Refused, Server unavailable
|
||||||
|
# 4: Connection Refused, bad user name or password
|
||||||
|
# 5: Connection Refused, not authorized
|
||||||
|
#userdata['connect_ok'] = True
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
client.subscribe("regattahero/orgstatus/thomas")
|
||||||
|
client.subscribe("regattahero/racestatus/thomas/#")
|
||||||
|
#userdata['connect_ok'] = False
|
||||||
|
|
||||||
|
def mqtt_on_message(self, client, userdata, msg):
|
||||||
|
"""
|
||||||
|
TODO raceid über userdata? dann topic prüfen?
|
||||||
|
"""
|
||||||
|
if msg.topic == "regattahero/orgstatus/thomas":
|
||||||
|
# kommt alle 10s
|
||||||
|
orgstatus = json.loads(msg.payload)
|
||||||
|
if orgstatus['allLogout']:
|
||||||
|
print("All logout received!")
|
||||||
|
client.disconnect()
|
||||||
|
sys.exit(0) # TODO nur die MQTT-Task beenden
|
||||||
|
if orgstatus['message']:
|
||||||
|
# TODO Alarm-Funktion nutzen?
|
||||||
|
print("Nachricht der Wettfahrtkeitung:")
|
||||||
|
print(orgstatus['message'])
|
||||||
|
print(orgstatus['races'])
|
||||||
|
#for r in orgstatus['races']:
|
||||||
|
# print(f"Race: {r}")
|
||||||
|
elif msg.topic.startswith("regattahero/racestatus/thomas"):
|
||||||
|
# kommt alle 1s
|
||||||
|
# dem Topic angehängt ist noch die raceid
|
||||||
|
payload = json.loads(msg.payload)
|
||||||
|
racestatus = payload['racestatus']
|
||||||
|
"""
|
||||||
|
time: negativ: Zeit vor dem Start, positiv: Zeit nach dem Start
|
||||||
|
in Sekunden
|
||||||
|
|
||||||
|
Signale der Wettfahrtleitung hier anzeigen
|
||||||
|
Regattaabbruch
|
||||||
|
Bahnverkürzung
|
||||||
|
Rückrufe
|
||||||
|
|
||||||
|
"""
|
||||||
|
else:
|
||||||
|
print(f"UNKNOWN TOPIC: {msg.topic}")
|
||||||
|
print(msg.payload)
|
||||||
|
|
||||||
|
def mqtt_publish(self, client, topic, payload, bv_lat, bv_lon, bv_sog):
|
||||||
|
"""
|
||||||
|
Payload vorbelegt als Template, so daß nur noch die veränderlichen
|
||||||
|
GPS-Daten eingefügt werden müssen: LAT LON SOG TIMESTAMP
|
||||||
|
"""
|
||||||
|
lat = bv_lat.getValueRaw()
|
||||||
|
lon = bv_lon.getValueRaw()
|
||||||
|
sog = bv_sog.getValueRaw()
|
||||||
|
if lat and lon and sog:
|
||||||
|
payload['gps']['lat'] = round(lat, 5)
|
||||||
|
payload['gps']['lon'] = round(lon, 5)
|
||||||
|
payload['gps']['speed'] = sog
|
||||||
|
payload['gps']['timestamp'] = time.strftime("%Y-%m-%dT%H:%M:%S.000Z", time.gmtime())
|
||||||
|
|
||||||
|
client.publish(topic, json.dumps(payload))
|
||||||
|
else:
|
||||||
|
print("No GPS data available. Nothing published!")
|
||||||
|
|
||||||
|
def mqtt_tracker(self, cfg, boat, appdata, boatdata):
|
||||||
|
print("MQTT tracker enabled")
|
||||||
|
client = mqtt.Client()
|
||||||
|
client.on_connect = self.mqtt_on_connect
|
||||||
|
client.on_message = self.mqtt_on_message
|
||||||
|
client.username_pw_set(username=cfg['mqtt_user'], password=cfg['mqtt_pass'])
|
||||||
|
try:
|
||||||
|
client.connect(cfg['host'], cfg['port'], 60)
|
||||||
|
except ConnectionRefusedError:
|
||||||
|
print("MQTT connection refused. Check username and password.")
|
||||||
|
return
|
||||||
|
|
||||||
|
tracefile = os.path.join(os.path.expanduser(cfg['logdir']), 'tracker.log')
|
||||||
|
trace_fh = open(tracefile, 'w+')
|
||||||
|
client.user_data_set({'trace': trace_fh})
|
||||||
|
|
||||||
|
topic = "regattahero/tracker/" + cfg['orgname']
|
||||||
|
payload = {
|
||||||
|
"passcode": cfg['passcode'],
|
||||||
|
"orgid": cfg['orgname'],
|
||||||
|
"raceid": "Demo Regatta", # TODO aus Selektion einstellen
|
||||||
|
"gps": {
|
||||||
|
"lat": 0.0,
|
||||||
|
"lon": 0.0,
|
||||||
|
"speed": 0.0,
|
||||||
|
"age": 1000,
|
||||||
|
"odo": 1000,
|
||||||
|
"bat": 1.0,
|
||||||
|
"timestamp": "" # ISO8601 Format mit Millisekunden in UTC
|
||||||
|
},
|
||||||
|
"boat": {
|
||||||
|
"boatid": cfg['uuid'],
|
||||||
|
"sailno": boat['sailno'],
|
||||||
|
"team": boat['team'],
|
||||||
|
"boatclass": boat['class'],
|
||||||
|
"handicap": boat['handicap'],
|
||||||
|
"club": boat['club'],
|
||||||
|
"boatname": boat['name']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Zugriff auf Boatdata: Referenzen für leichten schnellen Zugriff
|
||||||
|
bv_lat = boatdata.getRef("LAT")
|
||||||
|
bv_lon = boatdata.getRef("LON")
|
||||||
|
bv_sog = boatdata.getRef("SOG")
|
||||||
|
|
||||||
|
client.loop_start()
|
||||||
|
while not appdata.shutdown:
|
||||||
|
time.sleep(1)
|
||||||
|
if appdata.track.is_active():
|
||||||
|
self.mqtt_publish(client, topic, payload, bv_lat, bv_lon, bv_sog)
|
||||||
|
print("MQTT tracker shutdown")
|
||||||
|
client.loop_stop()
|
||||||
|
client.disconnect()
|
||||||
|
trace_fh.close()
|
||||||
|
|
Loading…
Reference in New Issue