OBP60v/nmea2000/parser.py

263 lines
8.4 KiB
Python

'''
PGNs verarbeiten
'''
import struct
import time
from datetime import timedelta, date
from . import lookup
def parse_60928(buf, device):
"""
Sets data in device and returns the 64bit NAME of device
"""
device.lastseen = time.time()
# 21 bits Unique-ID und 11 bits Manuf.-Code
device.uniqueid = ((buf[0] << 16) + (buf[1] << 8) + buf[2]) >> 3
device.manufacturercode = (buf[3] * 256 + buf[2]) >> 5
device.instance = buf[4]
device.instlower = buf[4] & 0x07
device.instupper = buf[4] >> 3
device.devicefunction = buf[5]
device.deviceclass = (buf[6] & 0x7f) >> 1
device.industrygroup = (buf[7] >> 4) & 0x07 # 3bit
device.sysinstance = buf[7] & 0x0f # 4bit
return struct.unpack_from('>Q', buf, 0)[0]
def parse_126992(buf, source):
# System time
print(f"PGN 126992 System time from {source}")
sid = buf[0]
src = buf[1] & 0x0f
dval = date(1970,1,1) + timedelta(days=(buf[3] << 8) + buf[2])
secs = struct.unpack_from('<L', buf, 4)[0] * 0.0001
print(f" source={source}, date={dval}, secs={secs}, ts={lookup.timesource[src]}")
def parse_126993(buf, device):
# Heartbeat
print(f"Heartbeat from {device.address}")
print(buf)
def parse_126996(buf, device):
# Product information
n2kvers = (buf[0] + buf[1] * 256) / 1000
prodcode = buf[2] + buf[3] * 256
modelid = buf[4:36] # 256bit modelid ascii text
softvers = buf[36:68] # 256bit software version code ascii text
modelvers = buf[68:100] # 256bit model version ascii text
serial = buf[100:132] # 256bit model serial code ascii text
# Füllzeichen entfernen. 0x20, 0xff, '@' für AIS
# Die N2K-Bibliothek von ttlappalainen liefert 0xff
modelid = modelid.rstrip(b'\xff')
softvers = softvers.rstrip(b'\xff')
modelvers = modelvers.rstrip(b'\xff')
serial = serial.rstrip(b'\xff')
# Übertragen in die Gerätedaten
device.n2kvers = n2kvers
device.productcode = prodcode
device.modelvers = modelvers.decode('ascii').rstrip()
device.softvers = softvers.decode('ascii').rstrip()
device.product = modelid.decode('ascii').rstrip()
device.serial = serial.decode('ascii').rstrip()
device.certlevel = buf[132]
device.loadequiv = buf[133]
def parse_126998(buf, source, device):
# Configuration information
# Installation Description 1
txtlen = buf[0]
if txtlen > 2:
device.instdesc1 = buf[2:txtlen].decode('ascii')
p = txtlen
else:
device.instdesc1 = ""
p = 2
# Installation Description 2
txtlen = buf[p]
if txtlen > 2:
device.instdesc2 = buf[p+2:p+txtlen].decode('ascii')
p += txtlen
else:
device.instdesc2 = ""
p += 2
# Manufacturer Info
txtlen = buf[p]
if txtlen > 2:
device.manufinfo = buf[p+2:p+txtlen].decode('ascii')
else:
device.manufinfo = ""
def parse_127257(buf, boatdata):
# Attitude
sid = buf[0]
yaw = struct.unpack_from('<h', buf, 1)[0] * 0.0001
pitch = struct.unpack_from('<h', buf, 3)[0] * 0.0001
roll = struct.unpack_from('<h', buf, 5)[0] * 0.0001
boatdata.setValue("YAW", yaw)
boatdata.setValue("PTCH", pitch)
boatdata.setValue("ROLL", roll)
def parse_127505(buf, boatdata):
# Fluid Level
instance = buf[0] & 0x0f
if instance in boatdata.tank:
boatdata.tank[instance].fluidtype = buf[0] >> 4
boatdata.tank[instance].level = struct.unpack_from('<H', buf, 1)[0] * 0.004
boatdata.tank[instance].capacity = struct.unpack_from('<L', buf, 3)[0] * 0.1
print(boatdata.tank[instance])
else:
print(f"Tank {instance} not defined!")
def parse_127508(buf, boatdata):
# Battery status
instance = buf[0]
voltage = (buf[2] * 256 + buf[1]) * 0.01
current = (buf[4] * 256 + buf[3]) * 0.1
temp = (buf[6] * 256 + buf[5]) * 0.01 - 273.15
sid = buf[7]
def parse_129025(buf, boatdata):
# Position, Rapid Update
lat = struct.unpack_from('<l', buf, 0)[0] * 1e-07
lon = struct.unpack_from('<l', buf, 4)[0] * 1e-07
boatdata.setValue("LAT", lat)
boatdata.setValue("LON", lon)
def parse_129026(buf, boatdata):
# COG & SOG, Rapid Update
sid = buf[0]
cogref = buf[1] >> 6 # 0: true, 1: magnetic, 2: error
cog = struct.unpack_from('<H', buf, 2)[0] * 0.0001 # rad
sog = struct.unpack_from('<H', buf, 4)[0] * 0.01 # m/s
# 2 Byte reserved
boatdata.setValue("COG", cog)
boatdata.setValue("SOG", sog)
def parse_129027(buf, boatdata):
# TODO
# Position Delta Rapid Update
sid = buf[0]
dt = struct.unpack_from('<H', buf, 1)[0],
dlat = struct.unpack_from('<h', buf, 3)[0]
dlon = struct.unpack_from('<h', buf, 5)[0]
# 1 Byte reserved
def parse_129029(buf, boatdata):
# GNSS Position Data
'''
1 sid
2 date
4 time seconds since midn
8 lat
8 lon
8 alt
4 bit gnss type
{"name": "GPS", "value": 0},
{"name": "GLONASS", "value": 1},
{"name": "GPS+GLONASS", "value": 2},
{"name": "GPS+SBAS/WAAS", "value": 3},
{"name": "GPS+SBAS/WAAS+GLONASS", "value": 4},
{"name": "Chayka", "value": 5},
{"name": "integrated", "value": 6},
{"name": "surveyed", "value": 7},
{"name": "Galileo", "value": 8}]},
4bit method
{"name": "no GNSS", "value": 0},
{"name": "GNSS fix", "value": 1},
{"name": "DGNSS fix", "value": 2},
{"name": "Precise GNSS", "value": 3},
{"name": "RTK Fixed Integer", "value": 4},
{"name": "RTK float", "value": 5},
{"name": "Estimated (DR) mode", "value": 6},
{"name": "Manual Input", "value": 7},
{"name": "Simulate mode", "value": 8}]},
2bit integrity
{"name": "No integrity checking", "value": 0},
{"name": "Safe", "value": 1},
{"name": "Caution", "value": 2}]},
bit reserved
1 byte uint numberOfSvs Number of satellites used in solution
2byte hdop
2 byte tdop
4 byte geoidalSeparation
1 byte Number of reference stations
4bit referenceStationType
12bit referenceStationId
2byte sageOfDgnssCorrections
'''
def parse_129033(buf, boatdata):
# Time & Date
gpsdate = struct.unpack_from('<H', buf, 0)[0] # days
gpstime = struct.unpack_from('<L', buf, 2)[0] # seconds since midnight
offset = struct.unpack_from('<h', buf, 6)[0] # local offset
# TODO
def parse_129283(buf, boatdata):
# TODO Cross Track Error XTE
sid = buf[0]
mode = buf[1] >> 4
navterm = buf[1] & 0x03
xte = struct.unpack_from('<l', buf, 2)[0] * 0.01 # m
# 2 Byte reserved
boatdata.setValue("XTE", xte)
def parse_129540(buf, boatdata):
#sid = buf[0]
#rrmode = buf[1]
nsats = buf[2]
# Datensatz je Sat Länge: 12 Byte
smax = nsats * 12
for s in range(0, smax, 12):
prn = buf[3 + s]
elevation = struct.unpack_from('<h', buf, s+4)[0] * 0.0001
azimuth = struct.unpack_from('<H', buf, s+6)[0] * 0.0001
snr = struct.unpack_from('<H', buf, s+8)[0] * 0.01
rres = struct.unpack_from('<l', buf, s+10)[0]
status = buf[s+14] & 0x0f
boatdata.updateSatellite(prn, elevation, azimuth, snr, rres, status)
def parse_130312(buf, boatdata):
# Temperature
src = buf[2] # lookup "temperature" (0 .. 15)
val = (buf[4] * 256 + buf[3]) * 0.01 # Kelvin
if instance == 0 and src == 1:
boatdata.setValue("xdrTemp", val)
elif instance in boatdata.temp:
boatdata.temp[instance].value = val
def parse_130314(buf, boatdata):
# Pressure
sid = buf[0]
instance = buf[1]
src = buf[2] # lookup "pressure"
pressure = struct.unpack_from('<L', buf, s+3)[0] * 0.1 # Pa
if instance == 0 and src == 0:
# Generischer Luftdruckwert
boatdata.setValue("xdrPress", pressure)
if instance in boatdata.press:
# Verschiedene weitere Drücke
# TODO sensortype "src"
boatdata.press[instance].value = pressure
def parse_130316(buf, boatdata):
# Temperature, extended range
sid = buf[0]
instance = buf[1]
src = buf[2] # lookup "temperature" (0 .. 15)
val = ((buf[5] << 16) | (buf[4] << 8) | buf[3]) * 0.001
# TODO save in global temp data
# Konflikt mit 130312?
#if instance == 0 and src == 2:
# boatdata.setValue("xdrTemp", val)
# save in engine data
if src == 14 and instance in boatdata.engine:
boatdata.engine[instance].exhaust_temp = val