Publish für MQTT integriert

This commit is contained in:
Thomas Hooge 2025-09-11 15:31:50 +02:00
parent e738438a94
commit 4ba75b5686
6 changed files with 127 additions and 20 deletions

View File

@ -5,7 +5,7 @@ Python muß mindestens Version 3.10 sein.
apt-get install python3-cairo python3-gi python3-gi-cairo gir1.2-rsvg-2.0 \ apt-get install python3-cairo python3-gi python3-gi-cairo gir1.2-rsvg-2.0 \
python3-serial python3-nmea2 python3-smbus2 python3-bme280 python3-astral \ python3-serial python3-nmea2 python3-smbus2 python3-bme280 python3-astral \
python3-can python3-can python3-paho-mqtt
Zusätzlich zu diesem Programm muß auch die zugehörige NMEA2000-Bibliothek Zusätzlich zu diesem Programm muß auch die zugehörige NMEA2000-Bibliothek
für Python vorhanden sein. Diese kann momentan am besten parallel zu dem für Python vorhanden sein. Diese kann momentan am besten parallel zu dem

View File

@ -55,18 +55,17 @@ def GGA(boatdata, msg):
def GLL(boatdata, msg): def GLL(boatdata, msg):
# Position data: position fix, time of position fix, and status # Position data: position fix, time of position fix, and status
# UTC of position ignored # UTC of position ignored
print(msg.data)
if not msg.status == 'A': if not msg.status == 'A':
return return
lat_fac = 1 if msg.lat_dir == 'N' else -1 lat_fac = 1 if msg.lat_dir == 'N' else -1
lat_deg = int(msg.lat[0:2]) lat_deg = int(msg.lat[0:2])
lat_min = float(msg.lat[2:]) lat_min = float(msg.lat[2:])
print(lat_deg, lat_min)
boatdata.setValue("LAT", lat_fac * lat_deg + lat_min / 60) boatdata.setValue("LAT", lat_fac * lat_deg + lat_min / 60)
lon_fac = 1 if msg.lon_dir == 'E' else -1 lon_fac = 1 if msg.lon_dir == 'E' else -1
lon_deg = int(msg.lon[0:3]) lon_deg = int(msg.lon[0:3])
lon_min = float(msg.lon[3:]) lon_min = float(msg.lon[3:])
boatdata.setValue("LON", lon_fac * lon_deg + lon_min / 60) boatdata.setValue("LON", lon_fac * lon_deg + lon_min / 60)
boatdata.setValue("TSPOS", msg.timestamp) # datetime.time, UTC
def GSA(boatdata, msg): def GSA(boatdata, msg):
# Satellites # Satellites
@ -297,7 +296,7 @@ def VBW(boatdata, msg):
print("-> VBW") print("-> VBW")
def VHW(boatdata, msg): def VHW(boatdata, msg):
print("-> VHW") #print("-> VHW")
boatdata.setValue("STW", float(msg.water_speed_knots)) boatdata.setValue("STW", float(msg.water_speed_knots))
def VPW(boatdata, msg): def VPW(boatdata, msg):
@ -319,15 +318,17 @@ def VTG(boatdata, msg):
('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']
""" """
print("-> VTG") #print("-> VTG")
# msg.true_track true_track_sym # msg.true_track true_track_sym
# msg.mag_track mag_track_sym # msg.mag_track mag_track_sym
# msg.faa_mode # 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) #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) #boatdata.setValue("SOG", sog)
#print("VTG", msg.spd_over_grnd_kts)
print("VTG", msg)
def VWR(boatdata, msg): def VWR(boatdata, msg):
# Relative Wind Speed and Angle # Relative Wind Speed and Angle

View File

@ -46,6 +46,14 @@ mqtt_pass = 123456
orgname = demo orgname = demo
passcode = 123456 passcode = 123456
[boat]
name = MY boat
sailno = GER 4711
class = One off
handicap = 100.0
club = NONE
team = OBP
[settings] [settings]
timezone = 1 timezone = 1
boat_draft = 1.3 boat_draft = 1.3

109
obp60v.py
View File

@ -126,41 +126,85 @@ cfg = {
'industrygroup': 4, # Marine 'industrygroup': 4, # Marine
'gps': False, 'gps': False,
'bme280': False, 'bme280': False,
'tracker': { 'type': 'NONE' } 'tracker': { 'type': 'NONE' },
'boat': { }
} }
def mqtt_on_connect(client, userdata, flags, rc): def mqtt_on_connect(client, userdata, flags, rc):
print(f"MQTT connected with result code {rc}") print(f"MQTT connected with result code {rc}")
client.subscribe("regattahero/orgstatus/thomas") #userdata['connect_rc'] = rc
#client.subscribe(topic_racestatus) 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): def mqtt_on_message(client, userdata, msg):
""" """
TODO raceid über userdata? dann topic prüfen? TODO raceid über userdata? dann topic prüfen?
""" """
if msg.topic == "regattahero/orgstatus/thomas": if msg.topic == "regattahero/orgstatus/thomas":
# kommt alle 10s
orgstatus = json.loads(msg.payload) orgstatus = json.loads(msg.payload)
if orgstatus['allLogout']: if orgstatus['allLogout']:
print("All logout received!") print("All logout received!")
client.disconnect() client.disconnect()
sys.exit(0) sys.exit(0) # TODO nur die MQTT-Task beenden
if orgstatus['message']: if orgstatus['message']:
# TODO Alarm-Funktion nutzen? # TODO Alarm-Funktion nutzen?
print("Nachricht der Wettfahrtkeitung:") print("Nachricht der Wettfahrtkeitung:")
print(orgstatus['message']) print(orgstatus['message'])
print(orgstatus['races'])
#for r in orgstatus['races']: #for r in orgstatus['races']:
# print(f"Race: {r}") # print(f"Race: {r}")
elif msg.topic.startswith("regattahero/racestatus/thomas"): elif msg.topic.startswith("regattahero/racestatus/thomas"):
racestatus = json.loads(msg.payload) # kommt alle 1s
print(racestatus) # 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: else:
print(f"UNKNOWN TOPIC: {msg.topic}") print(f"UNKNOWN TOPIC: {msg.topic}")
print(msg.payload) print(msg.payload)
def mqtt_tracker(cfg): 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 import paho.mqtt.client as mqtt
print("MQTT tracker enabled") print("MQTT tracker enabled")
print(cfg)
client = mqtt.Client() client = mqtt.Client()
client.on_connect = mqtt_on_connect client.on_connect = mqtt_on_connect
client.on_message = mqtt_on_message client.on_message = mqtt_on_message
@ -169,11 +213,43 @@ def mqtt_tracker(cfg):
client.connect(cfg['host'], cfg['port'], 60) client.connect(cfg['host'], cfg['port'], 60)
except ConnectionRefusedError: except ConnectionRefusedError:
print("MQTT connection refused. Check username and password.") print("MQTT connection refused. Check username and password.")
return 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() client.loop_start()
while not shutdown: while not shutdown:
time.sleep(1) time.sleep(1)
#TODO publish here if tracker_active:
mqtt_publish(client, topic, payload, bv_lat, bv_lon, bv_sog)
client.loop_stop() client.loop_stop()
client.disconnect() client.disconnect()
@ -258,7 +334,7 @@ def rxd_0183(devname):
setthreadtitle("0183listener") setthreadtitle("0183listener")
while not shutdown: while not shutdown:
raw = ser.readline().decode('ascii') raw = ser.readline().decode('ascii')
if len(raw) == 0: if len(raw.strip()) == 0:
continue continue
try: try:
msg = pynmea2.parse(raw) msg = pynmea2.parse(raw)
@ -722,6 +798,7 @@ if __name__ == "__main__":
setproctitle("obp60v") setproctitle("obp60v")
shutdown = False shutdown = False
tracker_active = False
owndevice = Device(100) owndevice = Device(100)
# Hardcoding device, not intended to change # Hardcoding device, not intended to change
@ -791,6 +868,14 @@ if __name__ == "__main__":
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')
# Boat data
cfg['boat']['name'] = config.get('boat', 'name')
cfg['boat']['sailno'] = config.get('boat', 'sailno')
cfg['boat']['class'] = config.get('boat', 'class')
cfg['boat']['handicap'] = config.getfloat('boat', 'handicap')
cfg['boat']['club'] = config.get('boat', 'club')
cfg['boat']['team'] = config.get('boat', 'team')
# Client UUID. Automatisch erzeugen wenn noch nicht vorhanden # Client UUID. Automatisch erzeugen wenn noch nicht vorhanden
create_uuid = False create_uuid = False
try: try:
@ -831,7 +916,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'],)) t_tracker = threading.Thread(target=mqtt_tracker, args=(cfg['tracker'],cfg['boat']))
t_tracker.start() t_tracker.start()
if not cfg['simulation']: if not cfg['simulation']:
if cfg['bme280']: if cfg['bme280']:

View File

@ -37,6 +37,7 @@ from .mob import MOB
from .rollpitch import RollPitch from .rollpitch import RollPitch
from .skyview import SkyView from .skyview import SkyView
from .solar import Solar from .solar import Solar
from .tracker import Tracker
from .rudder import Rudder from .rudder import Rudder
from .voltage import Voltage from .voltage import Voltage
from .windrose import WindRose from .windrose import WindRose

View File

@ -1,3 +1,8 @@
"""
Tracker with MQTT client
- currentry only Ragatta hero supported
"""
import cairo import cairo
from .page import Page from .page import Page
@ -9,7 +14,14 @@ class Tracker(Page):
def draw(self, ctx): def draw(self, ctx):
# Name # Name
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(60) ctx.set_font_size(32)
ctx.move_to(20, 100) ctx.move_to(20, 100)
ctx.show_text("Tracker") ctx.show_text("Tracker")
ctx.set_font_size(16)
ctx.move_to(20, 140)
ctx.show_text("active: ")
if tracker_active:
ctx.show_text("yes")
else:
ctx.show_text("no")