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 \
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
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):
# Position data: position fix, time of position fix, and status
# UTC of position ignored
print(msg.data)
if not msg.status == 'A':
return
lat_fac = 1 if msg.lat_dir == 'N' else -1
lat_deg = int(msg.lat[0:2])
lat_min = float(msg.lat[2:])
print(lat_deg, lat_min)
boatdata.setValue("LAT", lat_fac * lat_deg + lat_min / 60)
lon_fac = 1 if msg.lon_dir == 'E' else -1
lon_deg = int(msg.lon[0:3])
lon_min = float(msg.lon[3:])
boatdata.setValue("LON", lon_fac * lon_deg + lon_min / 60)
boatdata.setValue("TSPOS", msg.timestamp) # datetime.time, UTC
def GSA(boatdata, msg):
# Satellites
@ -297,7 +296,7 @@ def VBW(boatdata, msg):
print("-> VBW")
def VHW(boatdata, msg):
print("-> VHW")
#print("-> VHW")
boatdata.setValue("STW", float(msg.water_speed_knots))
def VPW(boatdata, msg):
@ -319,15 +318,17 @@ def VTG(boatdata, msg):
('FAA mode indicator', 'faa_mode'))
['', 'T', '', 'M', '0.117', 'N', '0.216', 'K', 'A']
"""
print("-> VTG")
#print("-> VTG")
# msg.true_track true_track_sym
# msg.mag_track mag_track_sym
# msg.faa_mode
#TODO klären was für Typen hier ankommen können
# 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])
boatdata.setValue("SOG", sog)
#boatdata.setValue("SOG", sog)
#print("VTG", msg.spd_over_grnd_kts)
print("VTG", msg)
def VWR(boatdata, msg):
# Relative Wind Speed and Angle

View File

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

107
obp60v.py
View File

@ -126,41 +126,85 @@ cfg = {
'industrygroup': 4, # Marine
'gps': False,
'bme280': False,
'tracker': { 'type': 'NONE' }
'tracker': { 'type': 'NONE' },
'boat': { }
}
def mqtt_on_connect(client, userdata, flags, rc):
print(f"MQTT connected with result code {rc}")
client.subscribe("regattahero/orgstatus/thomas")
#client.subscribe(topic_racestatus)
#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)
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"):
racestatus = json.loads(msg.payload)
print(racestatus)
# 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_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
print("MQTT tracker enabled")
print(cfg)
client = mqtt.Client()
client.on_connect = mqtt_on_connect
client.on_message = mqtt_on_message
@ -170,10 +214,42 @@ def mqtt_tracker(cfg):
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)
#TODO publish here
if tracker_active:
mqtt_publish(client, topic, payload, bv_lat, bv_lon, bv_sog)
client.loop_stop()
client.disconnect()
@ -258,7 +334,7 @@ def rxd_0183(devname):
setthreadtitle("0183listener")
while not shutdown:
raw = ser.readline().decode('ascii')
if len(raw) == 0:
if len(raw.strip()) == 0:
continue
try:
msg = pynmea2.parse(raw)
@ -722,6 +798,7 @@ if __name__ == "__main__":
setproctitle("obp60v")
shutdown = False
tracker_active = False
owndevice = Device(100)
# Hardcoding device, not intended to change
@ -791,6 +868,14 @@ if __name__ == "__main__":
cfg['tracker']['orgname'] = config.get('tracker', 'orgname')
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
create_uuid = False
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.start()
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()
if not cfg['simulation']:
if cfg['bme280']:

View File

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

View File

@ -1,3 +1,8 @@
"""
Tracker with MQTT client
- currentry only Ragatta hero supported
"""
import cairo
from .page import Page
@ -9,7 +14,14 @@ class Tracker(Page):
def draw(self, ctx):
# Name
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.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")