OBP60v/nmea0183.py

501 lines
16 KiB
Python

"""
NMEA0183 verarbeiten
TODO Multi-Sentence verarbeiten
TXT Textmeldungen (Alarm)
RTE Routendaten
AIS-Sentences mit Binärdaten
"""
import serial
from setproctitle import setthreadtitle
# Empfangsthread
def rxd_0183(appdata, devname):
# Prüfe ob NMEA0183-Port vorhanden ist und sich öffnen läßt
try:
ser = serial.Serial(devname, 115200, timeout=3)
except serial.SerialException as e:
print("NMEA0183 serial port not available")
return
setthreadtitle("0183listener")
while not appdata.shutdown:
raw = ser.readline().decode('ascii')
if len(raw.strip()) == 0:
continue
try:
msg = pynmea2.parse(raw)
except pynmea2.nmea.ParseError:
print(f"NMEA0183: Parse-Error: {raw}", end='')
continue
# sentence_type kann fehlen
try:
stype = msg.sentence_type
except:
print(f"NMEA0183: Sentence type missing: {raw}")
continue
# WIP Neuer Code aus Modul
# TODO Filter mit gewünschen Satztypen
# if stype in stypefilter:
# continue
if stype in decoder:
decoder[stype](boatdata, msg)
else:
# Hier unbekannter Satztyp: protokollieren und ignorieren
"""
['checksum', 'data', 'fields', 'identifier', 'name_to_idx', 'parse',
'proprietary_re', 'query_re', 'render', 'sentence_re',
'sentence_type', 'sentence_types', 'talker', 'talker_re']
"""
print(f"Nicht implementiert: '{stype}' from {msg.talker}")
ser.close()
def DBS(boatdata, msg):
# Wassertiefe unter der Oberfläche
pass
#boatdata.setValue("DBS", msg.depth)
def DBT(boatdata, msg):
# Wassertiefe unter Geber
pass
#boatdata.setValue("DBT", msg.depth)
def DPT(boatdata, msg):
# Depth
print("-> DPT")
print(msg.fields)
print(msg.data)
#boatdata.setValue("DBT", msg.depth)
def GBS(boatdata, msg):
# GNSS satellite fault detection
"""
(('Timestamp', 'timestamp', <function timestamp at 0x7f59cb0b65c0>), ('Expected error in latitude', 'lat_err'), ('Expected error in longitude', 'lon_err'), ('Expected error in altitude', 'alt_err'), ('PRN of most likely failed satellite', 'sat_prn_num_f'), ('Probability of missed detection for most likely failed satellite', 'pro_miss', <class 'decimal.Decimal'>), ('Estimate of bias in meters on most likely failed satellite', 'est_bias'), ('Standard deviation of bias estimate', 'est_bias_dev'))
['213024.00', '0.9', '0.6', '2.5', '', '', '', '']
NMEA0183: Parse-Error: !AIVDM,1,1,,A,H3ti3hPpDhiT0 """
print("-> GBS")
print(msg.fields)
print(msg.data)
#boatdata.setValue("LAT", msg.latitude)
def GGA(boatdata, msg):
# Time, position, and fix related data
# msg.num_sats
# msg.timestamp
if msg.gps_qual == 0:
# No fix
return
if msg.lat_dir == 'N':
boatdata.setValue("LAT", msg.latitude)
else:
boatdata.setValue("LAT", msg.latitude * -1)
if msg.lon_dir == 'E':
boatdata.setValue("LON", msg.longitude)
else:
boatdata.setValue("LON", msg.longitude * -1)
boatdata.setValue("HDOP", msg.horizontal_dil)
def GLL(boatdata, msg):
# Position data: position fix, time of position fix, and status
# UTC of position ignored
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:])
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
for i in range(1, 13):
satno = getattr(msg, f"sv_id{i:02}")
if (len(satno) > 0) and not satno in boatdata.sat:
boatdata.addSatellite(int(satno))
boatdata.setValue("PDOP", float(msg.pdop))
boatdata.setValue("PDOP", float(msg.hdop))
boatdata.setValue("PDOP", float(msg.vdop))
def GSV(boatdata, msg):
# Satellites in view
# mgs_num msg.num_messages # Nachricht n von m
# msg.num_sv_in_view # Anzahl sichtbarer Satelliten
rres = None # range residuals
for i in range(1, 5):
prn_num = getattr(msg, f"sv_prn_num_{i}")
if len(prn_num) > 0:
elevation = float(getattr(msg, f"elevation_deg_{i}"))
azimuth = float(getattr(msg, f"azimuth_{i}"))
snr = getattr(msg, f"snr_{i}")
if len(snr) == 0:
snr = 0
status = 1 # prnusage tracked
else:
status = 2 # prnusage used
boatdata.updateSatellite(int(prn_num), elevation, azimuth, int(snr), rres, status)
"""
if msg.sv_prn_num_1:
if msg.snr_1 == '':
status = 1
msg.snr_1 = 0
boatdata.updateSatellite(int(msg.sv_prn_num_1), float(msg.elevation_deg_1), float(msg.azimuth_1), int(msg.snr_1), rres, status)
if msg.sv_prn_num_2:
if msg.snr_2 == '':
status = 1
msg.snr_2 = 0
boatdata.updateSatellite(int(msg.sv_prn_num_2), float(msg.elevation_deg_2), float(msg.azimuth_2), int(msg.snr_2), rres, status)
if msg.sv_prn_num_3:
if msg.snr_3 == '':
status = 1
msg.snr_3 = 0
boatdata.updateSatellite(int(msg.sv_prn_num_3), float(msg.elevation_deg_3), float(msg.azimuth_3), int(msg.snr_3), rres, status)
if msg.sv_prn_num_4:
if msg.snr_4 == '':
status = 1
msg.snr_4 = 0
boatdata.updateSatellite(int(msg.sv_prn_num_4), float(msg.elevation_deg_4), float(msg.azimuth_4), int(msg.snr_4), rres, status)
"""
def HDG(boatdata, msg):
# UNUSED: Heading - Deviation & Variation
# Magnetic Sensor heading in degrees
# msg.heading
# .deviation, dev_dir E/W
# .variation, var_dir E/W
pass
def HDM(boatdata, msg):
# Heading magnetic
if msg.magnetic == 'M':
boatdata.setValue("HDM", msg.heading)
else:
print("HDM: M not set!")
def HDT(boatdata, msg):
# Heading True
if msg.hdg_true == 'T':
boatdata.setValue("HDT", msg.heading)
else:
print("HDT: T not set!")
def HTD(boatdata, msg):
# Heading/Track control data
# e.g. $YDHTD,V,1.5,,R,N,,,,,,,,,A,,,*48
print("-> HTD")
print(msg.fields)
def MWV(boatdata, msg):
# Windgeschwindigkeit und -winkel
print(f"Wind: {msg.wind_angle}° {msg.wind_speed}kt")
boatdata.setValue("AWA", msg.wind_angle)
boatdata.setValue("AWS", msg.wind_speed)
def MTW(boatdata, msg):
# Wassertemperatur
# boatdata.setValue("WTemp", msg.xxx)
print("-> MTW Wassertemperatur")
def RMB(boatdata, msg):
# Recommended Minimum Navigation Information
# Informationen bzgl. Erreichen des nächsten Wegepunkts
#
# (('Status', 'status'),
# ('Cross Track Error', 'cross_track_error'),
#('Cross Track Error, direction to corrent', 'cte_correction_dir'),
#('Origin Waypoint ID', 'origin_waypoint_id'),
# ('Destination Waypoint ID', 'dest_waypoint_id'),
#('Destination Waypoint Latitude', 'dest_lat'),
#('Destination Waypoint Lat Direction', 'dest_lat_dir'),
# ('Destination Waypoint Longitude', 'dest_lon'),
#('Destination Waypoint Lon Direction', 'dest_lon_dir'),
#('Range to Destination', 'dest_range'),
#('True Bearing to Destination', 'dest_true_bearing'),
#('Velocity Towards Destination', 'dest_velocity'),
#('Arrival Alarm', 'arrival_alarm'))
print("-> RMB")
if not msg.status == 'A':
return
lat_fac = 1 if msg.dest_lat_dir == 'N' else -1
lat_deg = int(msg.dest_lat[0:2])
lat_min = float(msg.dest_lat[2:])
lon_fac = 1 if msg.dest_lon_dir == 'E' else -1
lon_deg = int(msg.dest_lon[0:3])
lon_min = float(msg.dest_lon[3:])
boatdata.setValue("WPLat", lat_fac * lat_deg + lat_min / 60)
boatdata.setValue("WPLon", lon_fac * lon_deg + lon_min / 60)
boatdata.setValue("WPname", msg.dest_waypoint_id)
boatdata.setValue("DTW", float(msg.dest_range))
boatdata.setValue("BTW", float(msg.dest_true_bearing))
def RMC(boatdata, msg):
# Recommended Minimum Navigation Information
#print("-> RMC")
# print(msg.timestamp, msg.datestamp)
# print(msg.status) # V=Warning, P=Precise, A=OK
# mag_variation, mag_var_dir E/W
# TODO nav_status welche Bedeutung?
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:])
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)
if msg.spd_over_grnd:
boatdata.setValue("SOG", float(msg.spd_over_grnd))
if msg.true_course:
boatdata.setValue("COG", float(msg.true_course))
def ROT(boatdata, msg):
# Rate Of Turn
# print("-> ROT")
if msg.status == 'A':
boatdata.setValue("ROT", msg.rate_of_turn)
def RSA(boatdata, msg):
# Rudder Sensor Angle
# negative Werte bedeuten Backbord
#print("-> RSA")
# Boatdata: RPOS primär, PRPOS sekundär
if msg.rsa_starboard_status== 'A':
boatdata.setValue("RPOS", msg.rsa_starboard)
if msg.rsa_port_status == 'A':
boatdata.setValue("PRPOS", msg.rsa_port)
rte_curr = 0
rte_max = 0
rte_wpl = []
def RTE(boatdata, msg):
# Route: List of Waypoints
# num_in_seq, sen_num, start_type, active_route_id
global rte_curr, rte_max
nmax = int(msg.sen_num)
n = int(msg.num_in_seq)
if nmax > 1:
if rte_curr == 0 and n == 1:
# neue Nachricht
pass
else:
# Fortsetzung
pass
else:
pass
print("-> RTE")
print(msg.fields)
print(msg.waypoint_list)
txt_msg = ''
txt_type = None
txt_curr = 0
txt_max = 0
def TXT(boatdata, msg):
# Text Transmission (e.G. Alarms)
global txt_msg, txt_type, txt_curr, txt_max
#print("-> TXT")
nmax = int(msg.num_msg)
n = int(msg.msg_num)
if nmax > 1:
if txt_curr == 0 and n == 1:
# neue Nachricht
txt_msg = msg.text.rstrip('\r\n')
txt_type = msg.msg_type
txt_curr = 1
txt_max = nmax
else:
# Fortsetzung
if txt_curr == n - 1 and txt_type == msg.msg_type:
txt_msg += msg.text.rstrip('\r\n')
txt_curr = n
if n == nmax:
# Vollständig!
print(f"TXT: {msg.msg_type} - {txt_msg}")
if not boatdata.alarm:
# Momentan wird kein bereits anstehender Alarm überschrieben
boatdata.alarm_msg = txt_msg.strip()
boatdata.alarm = True
boatdata.alarm_id = msg.msg_type
boatdata.alarm_src = "NMEA0183"
txt_curr = 0
txt_max = 0
else:
print(f"TXT: {msg.msg_type} - {msg.text}", end='')
if not boatdata.alarm:
# Momentan wird kein bereits anstehender Alarm überschrieben
boatdata.alarm_msg = msg.text.strip()
boatdata.alarm = True
boatdata.alarm_id = msg.msg_type
boatdata.alarm_src = "NMEA0183"
def VBW(boatdata, msg):
print("-> VBW")
def VHW(boatdata, msg):
#print("-> VHW")
boatdata.setValue("STW", float(msg.water_speed_knots))
def VPW(boatdata, msg):
# UNUSED: Speed - Measured Parallel to Wind
# print(f"-> VPW: {msg.speed_kn} kn")
pass
def VTG(boatdata, msg):
# Track made good and speed over ground
"""
(('True Track made good', 'true_track', <class 'float'>),
('True Track made good symbol', 'true_track_sym'),
('Magnetic Track made good', 'mag_track', <class 'decimal.Decimal'>),
('Magnetic Track symbol', 'mag_track_sym'),
('Speed over ground knots', 'spd_over_grnd_kts', <class 'decimal.Decimal'>),
('Speed over ground symbol', 'spd_over_grnd_kts_sym'),
('Speed over ground kmph', 'spd_over_grnd_kmph', <class 'float'>),
('Speed over ground kmph symbol', 'spd_over_grnd_kmph_sym'),
('FAA mode indicator', 'faa_mode'))
['', 'T', '', 'M', '0.117', 'N', '0.216', 'K', 'A']
"""
#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)
#str von OpenCPN: sog = float(msg.spd_over_grnd_kts[:-1])
#boatdata.setValue("SOG", sog)
#print("VTG", msg.spd_over_grnd_kts)
print("VTG", msg)
def VWR(boatdata, msg):
# Relative Wind Speed and Angle
#print("-> VWR")
if msg.l_r == "R":
angle = msg.deg_r
else:
angle = 360 - msg.deg_r
boatdata.setValue("AWA", angle)
boatdata.setValue("AWS", msg.wind_speed_ms)
def WPL(boatdata, msg):
# Waypoint
# lat, lat_dir
# lon, lon_dir
# waypoint_id (name)
print("-> WPL")
print(msg.fields)
lat_fac = 1 if msg.lat_dir == 'N' else -1
lat_deg = int(msg.lat[0:2])
lat_min = float(msg.lat[2:])
#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)
# Prüfe, ob der Wegepunkt schon existiert
if msg.waypoint_id in boatdata.wps:
# Wegepunkt aktualisieren
pass
else:
# neuen Wegepunkt anlegen
# boatdata.wps[waypoint_id]
pass
def VWT(boatdata, msg):
# True Wind Speed and Angle
if msg.direction == "R":
angle = msg.wind_angle_vessel
else:
angle = 360 - msg.wind_angle_vessel
boatdata.setValue("TWA", angle)
boatdata.setValue("TWS", msg.wind_speed_meters)
def XDR(boatdata, msg):
# Extra sensor data / Transducer Measurement
# type, value, units, id
# type: A
# units: D
# id: Yaw
if msg.id.lower() == 'yaw':
boatdata.setValue("YAW", float(msg.value))
elif msg.id.lower() == 'ptch':
boatdata.setValue("PTCH", float(msg.value))
elif msg.id.lower() == 'roll':
boatdata.setValue("ROLL", float(msg.value))
elif msg.id.lower() == 'barometer':
boatdata.setValue("xdrPress", float(msg.value))
else:
print(f"-> XDR: {msg.type}, {msg.value}, {msg.units}, {msg.id}")
def XTE(boatdata, msg):
# Cross Track error measured
print("-> XTE")
def XTR(boatdata, msg):
# Cross Track error gekoppelt
print("-> XTR")
def ZDA(boatdata, msg):
# Time and date
print("-> XTE")
#boatdata.gpsd
#boatdata.gpst
# AIS
def VDM(boatdata, msg):
print("-> VDM")
print(msg.fields)
print(msg.data)
def VDO(boatdata, msg):
print("-> VDO")
# Aus Performancegründen eine direkte Sprungtabelle, ggf. können
# zukünftig außer der Funktion noch weitere Daten gespeichert werdeb
decoder = {
"DBS": DBS,
"DBT": DBT,
"DPT": DPT,
"GBS": GBS,
"GGA": GGA,
"GLL": GLL,
"GSA": GSA,
"GSV": GSV,
"HDG": HDG,
"HDM": HDM,
"HDT": HDT,
"HTD": HTD,
"MWV": MWV,
"MTW": MTW,
"RMB": RMB,
"ROT": ROT,
"RMC": RMC,
"RSA": RSA,
"RTE": RTE,
"TXT": TXT,
"VBW": VBW,
"VHW": VHW,
"VPW": VPW,
"VTG": VTG,
"VWR": VWR,
"VWT": VWT,
"WPL": WPL,
"XDR": XDR,
"XTE": XTE,
"XTR": XTR,
"ZDA": ZDA,
"VDM": VDM
}