Publish für MQTT integriert
This commit is contained in:
		
							parent
							
								
									e738438a94
								
							
						
					
					
						commit
						4ba75b5686
					
				
							
								
								
									
										2
									
								
								INSTALL
								
								
								
								
							
							
						
						
									
										2
									
								
								INSTALL
								
								
								
								
							|  | @ -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  | ||||||
|  |  | ||||||
							
								
								
									
										13
									
								
								nmea0183.py
								
								
								
								
							
							
						
						
									
										13
									
								
								nmea0183.py
								
								
								
								
							|  | @ -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 | ||||||
|  |  | ||||||
|  | @ -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
								
								
								
								
							
							
						
						
									
										109
									
								
								obp60v.py
								
								
								
								
							|  | @ -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']: | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|  |  | ||||||
|  | @ -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") | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue