First commit
This commit is contained in:
		
						commit
						9ce295f085
					
				| 
						 | 
					@ -0,0 +1 @@
 | 
				
			||||||
 | 
					*~
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,3 @@
 | 
				
			||||||
 | 
					from .device import Device
 | 
				
			||||||
 | 
					from .boatdata import BoatData
 | 
				
			||||||
 | 
					from .hbuffer import History, HistoryBuffer
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,574 @@
 | 
				
			||||||
 | 
					'''
 | 
				
			||||||
 | 
					!!! Dies ist noch im Ideen-Stadium 
 | 
				
			||||||
 | 
					WIP TBD
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Die Werte der Daten werden wie in NMEA2000 gespeichert:
 | 
				
			||||||
 | 
					Längen: m
 | 
				
			||||||
 | 
					Geschwindigkeiten: m/s
 | 
				
			||||||
 | 
					Winkel, Kurse: radiant (Bogenmaß)
 | 
				
			||||||
 | 
					Temperaturen: K
 | 
				
			||||||
 | 
					Druck: Pa
 | 
				
			||||||
 | 
					Geokoordinaten sind eine vorzeichenbehaftete Fileßkommazahl
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Liste der Daten mit Langbezeichnung siehe: valdesc.py
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Die format()-Funktion liefert immer einen String zurück.
 | 
				
			||||||
 | 
					Unterscheidung zwischen "kein Wert" und "ungültiger Wert"?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Normale Daten
 | 
				
			||||||
 | 
					-------------
 | 
				
			||||||
 | 
					ALT - Altitude, Höhe über Grund
 | 
				
			||||||
 | 
					AWA - Apparant Wind Angle, scheinbare Windrichtung
 | 
				
			||||||
 | 
					AWS - Apparant Wind Speed, scheinbare Windgeschwindigkeit
 | 
				
			||||||
 | 
					BTW - Bearing To Waipoynt, Winkel zum aktuellen Wegpunkt
 | 
				
			||||||
 | 
					COG - Course over Ground, Kurs über Grund
 | 
				
			||||||
 | 
					DBS - Depth Below Surface, Tiefe unter Wasseroberfläche
 | 
				
			||||||
 | 
					DBT - Depth Below Transducer, Tiefe unter Sensor
 | 
				
			||||||
 | 
					DEV - Deviation, Kursabweichung
 | 
				
			||||||
 | 
					DTW - Distance To Waypoint, Entfernung zum aktuellen Wegpunkt
 | 
				
			||||||
 | 
					GPSD - GPS Date, GPS-Datum
 | 
				
			||||||
 | 
					GPDT - GPS Time, GPS-Zeit als UTC (Weltzeit)
 | 
				
			||||||
 | 
					HDM - Magnetic Heading, magnetischer rechtweisender Kurs
 | 
				
			||||||
 | 
					HDT - Heading, wahrer rechtweisender Kurs
 | 
				
			||||||
 | 
					HDOP - GPS-Genauigkeit in der Horizontalen
 | 
				
			||||||
 | 
					LAT - Latitude, geografische Breite
 | 
				
			||||||
 | 
					LON - Longitude, geografische Höhe
 | 
				
			||||||
 | 
					Log - Log, Entfernung
 | 
				
			||||||
 | 
					MaxAws - Maximum Apperant Wind Speed, Maximum der relativen Windgeschwindigkeit seit Gerätestart
 | 
				
			||||||
 | 
					MaxTws - Maximum True Wind Speed, Maximum der wahren Windgeschwindigkeit seit Gerätestart
 | 
				
			||||||
 | 
					PDOP - GPS-Genauigkeit über alle 3 Raumachsen
 | 
				
			||||||
 | 
					PRPOS - Auslenkung Sekundärruder
 | 
				
			||||||
 | 
					ROT - Rotation, Drehrate
 | 
				
			||||||
 | 
					RPOS - Rudder Position, Auslenkung Hauptruder
 | 
				
			||||||
 | 
					SOG - Speed Over Ground, Geschwindigkeit über Grund
 | 
				
			||||||
 | 
					STW - Speed Through Water, Geschwindigkeit durch das Wasser
 | 
				
			||||||
 | 
					SatInfo - Satellit Info, Anzahl der sichtbaren Satelliten
 | 
				
			||||||
 | 
					TWD - True Wind Direction, wahre Windrichtung
 | 
				
			||||||
 | 
					TWS - True Wind Speed, wahre Windgeschwindigkeit
 | 
				
			||||||
 | 
					TZ - Time Zone, Zeitzone
 | 
				
			||||||
 | 
					TripLog - Trip Log, Tages-Entfernungszähler
 | 
				
			||||||
 | 
					VAR - Variation, Abweichung vom Sollkurs
 | 
				
			||||||
 | 
					VDOP - GPS-Genauigkeit in der Vertikalen
 | 
				
			||||||
 | 
					WPLat - Waypoint Latitude, geogr. Breite des Wegpunktes
 | 
				
			||||||
 | 
					WPLon - Waypoint Longitude, geogr. Länge des Wegpunktes
 | 
				
			||||||
 | 
					WTemp - Water Temperature, Wassertemperatur
 | 
				
			||||||
 | 
					XTE - Cross Track Error, Kursfehler
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Normale Daten erweitert
 | 
				
			||||||
 | 
					-----------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ROLL - Roll - Krängen / Rotation in Querrichtung
 | 
				
			||||||
 | 
					PTCH - Pitch - Rollen / Rotation in Längsrichtung
 | 
				
			||||||
 | 
					YAW - Yaw - Gieren / Rotation um die Senkrechte Achse
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					XDR-Daten
 | 
				
			||||||
 | 
					---------
 | 
				
			||||||
 | 
					xdrVBat - Bordspannung
 | 
				
			||||||
 | 
					xdrHum - Luftfeuchte
 | 
				
			||||||
 | 
					xdrPress - Luftdruck
 | 
				
			||||||
 | 
					xdrTemp - Temperatur
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					xdrRotK - Kielrotation
 | 
				
			||||||
 | 
					xdrRoll
 | 
				
			||||||
 | 
					xdrPitch
 | 
				
			||||||
 | 
					xdrYaw
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					'''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import datetime
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
 | 
					import math
 | 
				
			||||||
 | 
					import random
 | 
				
			||||||
 | 
					#from .lookup import fluidtype
 | 
				
			||||||
 | 
					from . import lookup
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BoatValue():
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Wert mit Datentyp, Einheit, Validekennzeichen und Skalierungsfaktor
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    placeholder = '---'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, shortname, unit=''):
 | 
				
			||||||
 | 
					        self.valname = shortname
 | 
				
			||||||
 | 
					        self.unit = unit
 | 
				
			||||||
 | 
					        self.value = None
 | 
				
			||||||
 | 
					        self.valid = False
 | 
				
			||||||
 | 
					        self.resolution = 1
 | 
				
			||||||
 | 
					        self.decpl = None # decimal places for format
 | 
				
			||||||
 | 
					        self.desc = "" # long description
 | 
				
			||||||
 | 
					        self.timestamp = time.time()
 | 
				
			||||||
 | 
					        self.simulated = False
 | 
				
			||||||
 | 
					        self.history = False
 | 
				
			||||||
 | 
					        self.hbuf = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def getValue(self):
 | 
				
			||||||
 | 
					        # Wert unter Beachtung der Unit zurückgeben
 | 
				
			||||||
 | 
					        if self.value and valid:
 | 
				
			||||||
 | 
					            return self.value
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            return self.placeholder
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def getValueRaw(self, ignorevalid=False):
 | 
				
			||||||
 | 
					        if ignorevalid or self.valid:
 | 
				
			||||||
 | 
					            return self.value
 | 
				
			||||||
 | 
					        return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def setValue(self, newvalue):
 | 
				
			||||||
 | 
					        self.value = newvalue
 | 
				
			||||||
 | 
					        self.timestamp = time.time()
 | 
				
			||||||
 | 
					        self.valid = True
 | 
				
			||||||
 | 
					        # if self.history:
 | 
				
			||||||
 | 
					        # TODO es kann mehrere verschiedene Zeitreihen geben!
 | 
				
			||||||
 | 
					        # Implementierung nich unklar.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def enableHistory(self, size, delta_t):
 | 
				
			||||||
 | 
					        if self.history:
 | 
				
			||||||
 | 
					            # Wiederholter Aufruf löscht die bisherige Historie
 | 
				
			||||||
 | 
					            self.hbuf.clear()
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        self.history = True
 | 
				
			||||||
 | 
					        self.hbuf = HistoryBuffer(size, delta_t)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def format(self):
 | 
				
			||||||
 | 
					        if self.simulated:
 | 
				
			||||||
 | 
					            if self.valname == "xdrVBat":
 | 
				
			||||||
 | 
					                return "{:.1f}".format(random.uniform(11.8, 14.2))
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                return "{:.0f}".format(random.uniform(95, 130))
 | 
				
			||||||
 | 
					        if not self.value or not self.valid:
 | 
				
			||||||
 | 
					            return self.placeholder
 | 
				
			||||||
 | 
					        if not self.decpl:
 | 
				
			||||||
 | 
					            if self.value < 10:
 | 
				
			||||||
 | 
					                self.decpl = 1
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                self.decpl = 0
 | 
				
			||||||
 | 
					        return "{0:3.{1}f}".format(self.value, self.decpl)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __str__(self):
 | 
				
			||||||
 | 
					        out = self.valname
 | 
				
			||||||
 | 
					        #out += self.format()
 | 
				
			||||||
 | 
					        if not self.valid:
 | 
				
			||||||
 | 
					            out += (" (invalid)")
 | 
				
			||||||
 | 
					        return out
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BoatValueGeo(BoatValue): 
 | 
				
			||||||
 | 
					    # geofmt = lat | lon
 | 
				
			||||||
 | 
					    # unit = rad | deg
 | 
				
			||||||
 | 
					    def __init__(self, shortname, geofmt, unit='deg'):
 | 
				
			||||||
 | 
					        super().__init__(shortname, unit)
 | 
				
			||||||
 | 
					        self.geofmt = geofmt
 | 
				
			||||||
 | 
					        self.decpl = 3
 | 
				
			||||||
 | 
					        #print(self.valname)
 | 
				
			||||||
 | 
					    def format(self):
 | 
				
			||||||
 | 
					        # latitude, longitude
 | 
				
			||||||
 | 
					        if not self.value or not self.valid:
 | 
				
			||||||
 | 
					            return self.placeholder
 | 
				
			||||||
 | 
					        degrees = int(self.value)
 | 
				
			||||||
 | 
					        minutes = (self.value - degrees) * 60
 | 
				
			||||||
 | 
					        if self.geofmt == 'lat':
 | 
				
			||||||
 | 
					            direction = ('E' if self.value > 0 else 'W')
 | 
				
			||||||
 | 
					            formatted = "{0}° {1:.{3}f}' {2}".format(degrees, minutes, direction, decpl)
 | 
				
			||||||
 | 
					        elif self.geofmt == 'lon':
 | 
				
			||||||
 | 
					            direction = 'N' if self.value > 0 else 'S'
 | 
				
			||||||
 | 
					            formatted = "{0}° {1:.{3}f}' {2}".format(degrees, minutes, direction, decpl)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            formatted = str(self.placeholder)
 | 
				
			||||||
 | 
					        return formatted
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BoatValueDate(BoatValue): 
 | 
				
			||||||
 | 
					    # datefmt = GB | US | DE | ISO
 | 
				
			||||||
 | 
					    def __init__(self, shortname, datefmt='ISO'):
 | 
				
			||||||
 | 
					        super().__init__(shortname)
 | 
				
			||||||
 | 
					        self.datefmt = datefmt
 | 
				
			||||||
 | 
					    def format(self):
 | 
				
			||||||
 | 
					        if self.datefmt == 'DE':
 | 
				
			||||||
 | 
					            formatted = self.value.strftime("%d.%m.%Y")
 | 
				
			||||||
 | 
					        elif self.datefmt == 'GB':
 | 
				
			||||||
 | 
					            formatted = self.value.strftime("%d/%m/%Y")
 | 
				
			||||||
 | 
					        elif self.datefmt == 'US':
 | 
				
			||||||
 | 
					            formatted = self.value.strftime("%m/%d/%Y")
 | 
				
			||||||
 | 
					        elif self.datefmt == 'ISO':
 | 
				
			||||||
 | 
					            formatted = self.value.strftime("%Y-%m-%d")
 | 
				
			||||||
 | 
					        return formatted
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BoatValueTime(BoatValue):
 | 
				
			||||||
 | 
					    def __init__(self, shortname, timezone='UTC'):
 | 
				
			||||||
 | 
					        super().__init__(shortname)
 | 
				
			||||||
 | 
					        self.tz = timezone
 | 
				
			||||||
 | 
					        self.timefmt = 'hh:mm:' # TODO hh:mm:ss | ...?
 | 
				
			||||||
 | 
					    def format(self):
 | 
				
			||||||
 | 
					        formatted = self.value
 | 
				
			||||||
 | 
					        return formatted
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BoatValueSpeed(BoatValue):
 | 
				
			||||||
 | 
					    # unsigned? Was ist mit Rückwärts?
 | 
				
			||||||
 | 
					    def format(self):
 | 
				
			||||||
 | 
					        if self.simulated:
 | 
				
			||||||
 | 
					            return "5.3"
 | 
				
			||||||
 | 
					        if not self.value or not self.valid:
 | 
				
			||||||
 | 
					            return self.placeholder
 | 
				
			||||||
 | 
					        if self.value < 20:
 | 
				
			||||||
 | 
					            formatted = f"{self.value:3.1f}"
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            formatted = f"{self.value:3.0f}"
 | 
				
			||||||
 | 
					        return formatted
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BoatValueAngle(BoatValue):
 | 
				
			||||||
 | 
					    # course, wind, heading, bearing
 | 
				
			||||||
 | 
					    # roll, pitch, yaw, rudder, keel
 | 
				
			||||||
 | 
					    def format(self):
 | 
				
			||||||
 | 
					        if self.simulated:
 | 
				
			||||||
 | 
					            if self.valname == "BTW":
 | 
				
			||||||
 | 
					                return "253"
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                return "120"
 | 
				
			||||||
 | 
					        if self.value:
 | 
				
			||||||
 | 
					            return f"{self.value:03.0f}"
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            return self.placeholder
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BoatValueRotation(BoatValue):
 | 
				
			||||||
 | 
					    # signed
 | 
				
			||||||
 | 
					    def format(self):
 | 
				
			||||||
 | 
					        if self.value < 10 and self.value > -10:
 | 
				
			||||||
 | 
					            formatted = f"{self.value:.1f}"
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            formatted = f"{self.value:.1f}"
 | 
				
			||||||
 | 
					        return formatted
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BoatValueDepth(BoatValue):
 | 
				
			||||||
 | 
					    # unsigned
 | 
				
			||||||
 | 
					    def format(self):
 | 
				
			||||||
 | 
					        if self.simulated:
 | 
				
			||||||
 | 
					            if self.valname == "DBT":
 | 
				
			||||||
 | 
					                return "6.2"
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                return "6.5"
 | 
				
			||||||
 | 
					        if not self.value or not self.valid:
 | 
				
			||||||
 | 
					            return self.placeholder
 | 
				
			||||||
 | 
					        if self.value < 100:
 | 
				
			||||||
 | 
					            formatted = f"{self.value:3.1f}"
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            formatted = f"{self.value:3.0f}"
 | 
				
			||||||
 | 
					        return formatted
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BoatValueDistance(BoatValue):
 | 
				
			||||||
 | 
					    # unsigned integer?
 | 
				
			||||||
 | 
					    def format(self, signed=False):
 | 
				
			||||||
 | 
					        if self.value:
 | 
				
			||||||
 | 
					            return f"{self.value:d}"
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            return self.placeholder
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BoatValueTemperature(BoatValue):
 | 
				
			||||||
 | 
					    # signed
 | 
				
			||||||
 | 
					    def __init__(self, shortname, unit='K'):
 | 
				
			||||||
 | 
					        super().__init__(shortname, unit)
 | 
				
			||||||
 | 
					        self.instance = None
 | 
				
			||||||
 | 
					        self.sensortype = None
 | 
				
			||||||
 | 
					        self.decpl = 0
 | 
				
			||||||
 | 
					    def format(self):
 | 
				
			||||||
 | 
					        if self.value < 100:
 | 
				
			||||||
 | 
					            formatted = f"{self.value:3.1f}"
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            formatted = f"{self.value:3.0f}"
 | 
				
			||||||
 | 
					        return formatted
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BoatValueHumidity(BoatValue):
 | 
				
			||||||
 | 
					    # unsigned integer
 | 
				
			||||||
 | 
					    # range 0 .. 100
 | 
				
			||||||
 | 
					    def __init__(self, shortname, unit='%'):
 | 
				
			||||||
 | 
					        super().__init__(shortname, unit)
 | 
				
			||||||
 | 
					        self.instance = None
 | 
				
			||||||
 | 
					        self.sensortype = None
 | 
				
			||||||
 | 
					        self.decpl = 0
 | 
				
			||||||
 | 
					    def format(self):
 | 
				
			||||||
 | 
					        return f"{self.value:d}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BoatValuePressure(BoatValue):
 | 
				
			||||||
 | 
					    # unsigned integer
 | 
				
			||||||
 | 
					    # range ca. 800 .. 1100 for athmospheric pressure
 | 
				
			||||||
 | 
					    def __init__(self, shortname, unit='Pa'):
 | 
				
			||||||
 | 
					        super().__init__(shortname, unit)
 | 
				
			||||||
 | 
					        self.instance = None
 | 
				
			||||||
 | 
					        self.sensortype = None
 | 
				
			||||||
 | 
					        self.decpl = 1
 | 
				
			||||||
 | 
					    def format(self):
 | 
				
			||||||
 | 
					        if self.value and self.valid:
 | 
				
			||||||
 | 
					            return f"{self.value:4.{self.decpl}f}"
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            return self.placeholder
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Tank():
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Die Instanz beziegt sich auf den Typ. So kann die Instanz 0
 | 
				
			||||||
 | 
					    für einen Wassertank und einen Dieseltank existieren
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, instance=0):
 | 
				
			||||||
 | 
					        self.fluidtype = 1 # water -> lookup
 | 
				
			||||||
 | 
					        self.instance = instance
 | 
				
			||||||
 | 
					        self.level = None # percent
 | 
				
			||||||
 | 
					        self.capacity = None # liter
 | 
				
			||||||
 | 
					        self.desc = "" # long description
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __str__(self):
 | 
				
			||||||
 | 
					        typedesc = lookup.fluidtype[self.fluidtype]
 | 
				
			||||||
 | 
					        out = f"  Tank / {typedesc}: #{self.instance}\n"
 | 
				
			||||||
 | 
					        out += f"    Capacity: {self.capacity} l\n"
 | 
				
			||||||
 | 
					        out += f"    Fluid level: {self.level} %\n"
 | 
				
			||||||
 | 
					        return out
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Engine():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, instance=0):
 | 
				
			||||||
 | 
					        self.instance = instance
 | 
				
			||||||
 | 
					        self.speed_rpm = None
 | 
				
			||||||
 | 
					        self.exhaust_temp = None
 | 
				
			||||||
 | 
					        self.desc = "" # long description
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __str__(self):
 | 
				
			||||||
 | 
					        out = f"  Engine #{self.instance}\n"
 | 
				
			||||||
 | 
					        if self.exhaust_temp:
 | 
				
			||||||
 | 
					            out += f"    Exhaust temp: {self.exhaust_temp:.1f} °C\n"
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            out += "     Exhaust temp: no data\n"
 | 
				
			||||||
 | 
					        return out
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Satellite():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, prn_num):
 | 
				
			||||||
 | 
					        self.prn_num = prn_num 
 | 
				
			||||||
 | 
					        self.elevation = None
 | 
				
			||||||
 | 
					        self.azimuth = None
 | 
				
			||||||
 | 
					        self.snr = None   # signal noise ratio
 | 
				
			||||||
 | 
					        self.rres = None  # range residuals
 | 
				
			||||||
 | 
					        self.status = None # lookup -> prnusage
 | 
				
			||||||
 | 
					        self.lastseen = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __str__(self):
 | 
				
			||||||
 | 
					        out = f"SAT {self.prn_num:02d}: "
 | 
				
			||||||
 | 
					        if self.snr:
 | 
				
			||||||
 | 
					            out += f"snr={self.snr}dB elevation={self.elevation:.4f} azimuth={self.azimuth:.4f} status={self.status}\n"
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            out += "no signal\n"
 | 
				
			||||||
 | 
					        return out
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class SatelliteList():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self):
 | 
				
			||||||
 | 
					        sat = {}
 | 
				
			||||||
 | 
					        rrmode = None
 | 
				
			||||||
 | 
					        maxage = 300 # sec
 | 
				
			||||||
 | 
					    def getCount(self):
 | 
				
			||||||
 | 
					        return len(sat)
 | 
				
			||||||
 | 
					    def addSat(self, pnr_num):
 | 
				
			||||||
 | 
					        pass
 | 
				
			||||||
 | 
					    def delSat(self, pnr_num):
 | 
				
			||||||
 | 
					        pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BoatData():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.simulation = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # nach Überschreiten dieser Schwelle in Sekunden wird
 | 
				
			||||||
 | 
					        # ein Meßwert als ungültig angesehen
 | 
				
			||||||
 | 
					        self.maxage = 5
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Systemspannung; temporär. später auf bessere Weise speichern
 | 
				
			||||||
 | 
					        self.voltage = BoatValue("xdrVBat", "V")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Navigationsdaten
 | 
				
			||||||
 | 
					        self.awa = BoatValueAngle("AWA", "kn")
 | 
				
			||||||
 | 
					        self.aws = BoatValueSpeed("AWS", "kn")
 | 
				
			||||||
 | 
					        self.twd = BoatValueAngle("TWD", "kn")
 | 
				
			||||||
 | 
					        self.tws = BoatValueSpeed("TWS", "kn")
 | 
				
			||||||
 | 
					        self.lat = BoatValueGeo("LAT", "lat", "deg")
 | 
				
			||||||
 | 
					        self.lon = BoatValueGeo("LON", "lon", "deg")
 | 
				
			||||||
 | 
					        self.gpsd = BoatValueDate("GPSD", "ISO")
 | 
				
			||||||
 | 
					        self.gpst = BoatValueTime("GPST")
 | 
				
			||||||
 | 
					        self.sog = BoatValueSpeed("SOG", "kn")
 | 
				
			||||||
 | 
					        self.cog = BoatValueAngle("COG", "deg")
 | 
				
			||||||
 | 
					        self.xte = BoatValueDistance("XTE", "m")
 | 
				
			||||||
 | 
					        self.stw = BoatValueSpeed("STW", "kn")
 | 
				
			||||||
 | 
					        self.dbt = BoatValueDepth("DBT", "m")
 | 
				
			||||||
 | 
					        self.roll = BoatValueAngle("ROLL", "deg")
 | 
				
			||||||
 | 
					        self.pitch = BoatValueAngle("PTCH", "deg")
 | 
				
			||||||
 | 
					        self.yaw = BoatValueAngle("YAW", "deg")
 | 
				
			||||||
 | 
					        self.rpos = BoatValueAngle("RPOS", "deg")
 | 
				
			||||||
 | 
					        self.prpos = BoatValueAngle("PRPOS", "deg")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Nächster Wegepunkt
 | 
				
			||||||
 | 
					        self.wpno = BoatValue("WP")
 | 
				
			||||||
 | 
					        self.wpname = BoatValue("WPname")
 | 
				
			||||||
 | 
					        self.wplat = BoatValueGeo("WPLat", "lat", "deg")
 | 
				
			||||||
 | 
					        self.wplon = BoatValueGeo("WPLon", "lon", "deg")
 | 
				
			||||||
 | 
					        self.wpdist = BoatValueDistance("DTW", "m")
 | 
				
			||||||
 | 
					        self.bearing = BoatValueAngle("BTW", "kn")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Umgebung
 | 
				
			||||||
 | 
					        self.temp_water = BoatValueTemperature("WTemp", "°C")
 | 
				
			||||||
 | 
					        self.temp_air =  BoatValueTemperature("xdrTemp", "°C")
 | 
				
			||||||
 | 
					        self.pressure =  BoatValuePressure("xdrPress", "hPa")
 | 
				
			||||||
 | 
					        self.humidity = BoatValueHumidity("xdrHum", "%")
 | 
				
			||||||
 | 
					        self.temp = {}  # Erweiterte Temperaturdaten
 | 
				
			||||||
 | 
					        self.press = {} # Erweiterte Druckdaten
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Sonderdaten
 | 
				
			||||||
 | 
					        self.rotk = BoatValueAngle("xdrRotK", "deg") # Kielrotation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Maschinen
 | 
				
			||||||
 | 
					        self.engine = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Tanks
 | 
				
			||||||
 | 
					        self.tank = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Mehrere getrennte Batteriekreise
 | 
				
			||||||
 | 
					        # - Starter
 | 
				
			||||||
 | 
					        # - Verbrauchen
 | 
				
			||||||
 | 
					        # - Ankerwinsch / Bugstrahlruder
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Stromerzeugung
 | 
				
			||||||
 | 
					        #   Solarleistung
 | 
				
			||||||
 | 
					        #   Generatorleistung
 | 
				
			||||||
 | 
					        #   Benzingenerator
 | 
				
			||||||
 | 
					        #   Windgenerator
 | 
				
			||||||
 | 
					        #   Wasser-/Schleppgenerator
 | 
				
			||||||
 | 
					        #   Maschine Rekuperation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Satelliten
 | 
				
			||||||
 | 
					        self.sat = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Zeitreihen für diverse Daten
 | 
				
			||||||
 | 
					        self.history = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.valref = {
 | 
				
			||||||
 | 
					            'AWA': self.awa,
 | 
				
			||||||
 | 
					            'AWS': self.aws,
 | 
				
			||||||
 | 
					            'BTW': self.bearing,
 | 
				
			||||||
 | 
					            'COG': self.cog,
 | 
				
			||||||
 | 
					            'DBT': self.dbt,
 | 
				
			||||||
 | 
					            'DTW': self.wpdist,
 | 
				
			||||||
 | 
					            'GPSD': self.gpsd,
 | 
				
			||||||
 | 
					            'GPST': self.gpst,
 | 
				
			||||||
 | 
					            'LAT': self.lat,
 | 
				
			||||||
 | 
					            'LON': self.lon,
 | 
				
			||||||
 | 
					            'PRPOS': self.prpos,
 | 
				
			||||||
 | 
					            'PTCH': self.pitch,
 | 
				
			||||||
 | 
					            'RPOS': self.rpos,
 | 
				
			||||||
 | 
					            'ROLL': self.roll,
 | 
				
			||||||
 | 
					            'SOG': self.sog,
 | 
				
			||||||
 | 
					            'STW': self.stw,
 | 
				
			||||||
 | 
					            'TWD': self.twd,
 | 
				
			||||||
 | 
					            'TWS': self.tws,
 | 
				
			||||||
 | 
					            'WTemp': self.temp_water,
 | 
				
			||||||
 | 
					            'WPLat': self.wplat,
 | 
				
			||||||
 | 
					            'WPLon': self.wplon,
 | 
				
			||||||
 | 
					            'XTE': self.xte,
 | 
				
			||||||
 | 
					            'xdrRotK': self.rotk,
 | 
				
			||||||
 | 
					            'xdrVBat': self.voltage,
 | 
				
			||||||
 | 
					            'xdrTemp': self.temp_air,
 | 
				
			||||||
 | 
					            'xdrPress': self.pressure,
 | 
				
			||||||
 | 
					            'xdrHum': self.humidity,
 | 
				
			||||||
 | 
					            'YAW': self.yaw
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def addTank(self, instance):
 | 
				
			||||||
 | 
					        self.tank[instance] = Tank(instance)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def addEngine(self, instance):
 | 
				
			||||||
 | 
					        self.engine[instance] = Engine(instance)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def addSensor(self, sensortype, instance):
 | 
				
			||||||
 | 
					        if sensortype == 'temp':
 | 
				
			||||||
 | 
					            if not instance in self.temp:
 | 
				
			||||||
 | 
					                self.temp[instance] = BoatValueTemperature()
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                raise ValueError(f"duplicate key '{instance}'")
 | 
				
			||||||
 | 
					        elif sensortype == 'press':
 | 
				
			||||||
 | 
					            if not instance in self.press:
 | 
				
			||||||
 | 
					                self.press[instance] = BoatValuePressure()
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                raise ValueError(f"duplicate key '{instance}'")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def updateSatellite(self, prn_num, elevation, azimuth, snr, rres, status):
 | 
				
			||||||
 | 
					        if not prn_num in self.sat:
 | 
				
			||||||
 | 
					            self.sat[prn_num] = Satellite(prn_num)
 | 
				
			||||||
 | 
					        self.sat[prn_num].elevation = elevation
 | 
				
			||||||
 | 
					        self.sat[prn_num].azimuth = azimuth
 | 
				
			||||||
 | 
					        self.sat[prn_num].snr = snr
 | 
				
			||||||
 | 
					        self.sat[prn_num].rres = rres
 | 
				
			||||||
 | 
					        self.sat[prn_num].status = status
 | 
				
			||||||
 | 
					        self.sat[prn_num].lastseen = time.time()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __str__(self):
 | 
				
			||||||
 | 
					        out = "Boat Data\n"
 | 
				
			||||||
 | 
					        out += f"  Voltage: {self.voltage}\n"
 | 
				
			||||||
 | 
					        out += f"  Latitude: {self.lat.value}\n"
 | 
				
			||||||
 | 
					        out += f"  Longitude: {self.lon.value}\n"
 | 
				
			||||||
 | 
					        out += f"  SOG: {self.sog}\n"
 | 
				
			||||||
 | 
					        for e in self.engine.values():
 | 
				
			||||||
 | 
					            out += str(e)
 | 
				
			||||||
 | 
					        for t in self.tank.values():
 | 
				
			||||||
 | 
					            out += str(t)
 | 
				
			||||||
 | 
					        out += "  Satellite info\n"
 | 
				
			||||||
 | 
					        for s in self.sat.values():
 | 
				
			||||||
 | 
					            out += str(s)
 | 
				
			||||||
 | 
					        return out
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def updateValid(self, age=None):
 | 
				
			||||||
 | 
					        # age: Alter eines Meßwerts in Sekunden
 | 
				
			||||||
 | 
					        if not age:
 | 
				
			||||||
 | 
					            age = self.maxage
 | 
				
			||||||
 | 
					        t = time.time()
 | 
				
			||||||
 | 
					        for v in vars(self).values():
 | 
				
			||||||
 | 
					            if isinstance(v,BoatValue):
 | 
				
			||||||
 | 
					                if t - v.timestamp > age:
 | 
				
			||||||
 | 
					                    v.valid = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def getRef(self, shortname):
 | 
				
			||||||
 | 
					        '''
 | 
				
			||||||
 | 
					        Referenz auf ein BoatValue-Objekt
 | 
				
			||||||
 | 
					        '''
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            bv = self.valref[shortname]
 | 
				
			||||||
 | 
					        except KeyError:
 | 
				
			||||||
 | 
					            bv = None
 | 
				
			||||||
 | 
					        return bv
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def getValue(self, shortname):
 | 
				
			||||||
 | 
					        '''
 | 
				
			||||||
 | 
					        Wert aufgrund textuellem Kurznamen zurückliefern
 | 
				
			||||||
 | 
					        '''
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            value = self.valref[shortname].value
 | 
				
			||||||
 | 
					        except KeyError:
 | 
				
			||||||
 | 
					            value = None
 | 
				
			||||||
 | 
					        return value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def setValue(self, shortname, newvalue):
 | 
				
			||||||
 | 
					        '''
 | 
				
			||||||
 | 
					        Rückgabewert True bei erfolgreichem Speichern des Werts
 | 
				
			||||||
 | 
					        '''
 | 
				
			||||||
 | 
					        if not shortname in self.valref:
 | 
				
			||||||
 | 
					            return False
 | 
				
			||||||
 | 
					        field = self.valref[shortname]
 | 
				
			||||||
 | 
					        field.value = newvalue
 | 
				
			||||||
 | 
					        field.timestamp = time.time()
 | 
				
			||||||
 | 
					        field.valid = True
 | 
				
			||||||
 | 
					        return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def enableSimulation(self):
 | 
				
			||||||
 | 
					        self.simulation = True
 | 
				
			||||||
 | 
					        for v in self.valref.values():
 | 
				
			||||||
 | 
					            v.simulated = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def addHistory(self, history, htype):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        htype: press, temp, hum
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.history[htype] = history
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,121 @@
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					NMEA2000-Gerät
 | 
				
			||||||
 | 
					- auf dem Bus erkannte Geräte
 | 
				
			||||||
 | 
					- für das eigene Gerät steht initUid() zur Verfügung
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
 | 
					import struct
 | 
				
			||||||
 | 
					from . import lookup
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Device():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, address):
 | 
				
			||||||
 | 
					        # WIP: Felder können sich noch ändern!
 | 
				
			||||||
 | 
					        self.address = address       # Kann sich zur Laufzeit ändern
 | 
				
			||||||
 | 
					        self.lastseen = time.time()
 | 
				
			||||||
 | 
					        self.lastpinfo = None        # Wann letztes Mal Productinfo erhalten?
 | 
				
			||||||
 | 
					        self.lastcinfo = None        # Wann letztes Mal Configurationinfo erhalten?
 | 
				
			||||||
 | 
					        self.has_cinfo = True        # Weitere Abfragen können verhindert werden
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Device info
 | 
				
			||||||
 | 
					        self.NAME = 0                # Wird über getNAME (address claim) gefüllt, 64bit Integer
 | 
				
			||||||
 | 
					        self.uniqueid = None         # Z.B. aus der Geräteseriennummer abgeleitet
 | 
				
			||||||
 | 
					        self.manufacturercode = 2046 # Open Boat Projects
 | 
				
			||||||
 | 
					        self.instance = 0            # default 0
 | 
				
			||||||
 | 
					        self.instlower = 0           # 3bit, ISO ECU Instance
 | 
				
			||||||
 | 
					        self.instupper = 0           # 5bit, ISO Function Instance
 | 
				
			||||||
 | 
					        self.sysinstance = 0         # used with bridged networks, default 0
 | 
				
			||||||
 | 
					        self.industrygroup = None
 | 
				
			||||||
 | 
					        self.devicefunction = None
 | 
				
			||||||
 | 
					        self.deviceclass = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Product info
 | 
				
			||||||
 | 
					        self.product = None             # Product name
 | 
				
			||||||
 | 
					        self.productcode = None         # Product code
 | 
				
			||||||
 | 
					        self.serial = None
 | 
				
			||||||
 | 
					        self.modelvers = None 		# Hardware Version
 | 
				
			||||||
 | 
					        self.softvers = None            # Current Software Version
 | 
				
			||||||
 | 
					        self.n2kvers = None             # NMEA2000 Network Message Database Version
 | 
				
			||||||
 | 
					        self.certlevel = None           # NMEA2000 Certification Level
 | 
				
			||||||
 | 
					        self.loadequiv = None           # NMEA2000 LEN
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Configuration info
 | 
				
			||||||
 | 
					        self.instdesc1 = None
 | 
				
			||||||
 | 
					        self.instdesc2 = None
 | 
				
			||||||
 | 
					        self.manufinfo = None 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Additional data
 | 
				
			||||||
 | 
					        self.customname = None          # User defined device name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _ISOtime(self, epoch):
 | 
				
			||||||
 | 
					        if not epoch:
 | 
				
			||||||
 | 
					            return "n/a"
 | 
				
			||||||
 | 
					        return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(epoch))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def initUid(self):
 | 
				
			||||||
 | 
					        # Initialize unique id from raspi cpu id and return 21bit value
 | 
				
			||||||
 | 
					        with open("/sys/firmware/devicetree/base/serial-number", "r") as f:
 | 
				
			||||||
 | 
					            hexid = f.read().rstrip('\x00')
 | 
				
			||||||
 | 
					        return int(hexid, 16) & 0x001FFFFF # 21bit mask
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def getNAME(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        NAME is unique on bus
 | 
				
			||||||
 | 
					        Returns bytearray and sets integer NAME for later easy and fast access
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        data = bytearray()
 | 
				
			||||||
 | 
					        data.extend(struct.pack('<I', (self.uniqueid & 0x001fffff) | (self.manufacturercode << 21)))
 | 
				
			||||||
 | 
					        data.append((self.instlower & 0x07) | ((self.instupper & 0x1f) << 3))
 | 
				
			||||||
 | 
					        data.append(self.devicefunction)
 | 
				
			||||||
 | 
					        data.append((self.deviceclass & 0x7f) << 1)
 | 
				
			||||||
 | 
					        data.append(0x80 | ((self.industrygroup & 0x07) << 4) | (self.sysinstance & 0x0f))
 | 
				
			||||||
 | 
					        self.NAME = struct.unpack_from('<Q', data, 0)[0]
 | 
				
			||||||
 | 
					        return data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __str__(self):
 | 
				
			||||||
 | 
					        out =  f"Device: {self.address} : '{self.product}'\n"
 | 
				
			||||||
 | 
					        out +=  "  NAME: {} ({})\n".format(self.getNAME(), self.NAME)
 | 
				
			||||||
 | 
					        out +=  "  last seen: {}\n".format(self._ISOtime(self.lastseen))
 | 
				
			||||||
 | 
					        out +=  "  Device info\n"
 | 
				
			||||||
 | 
					        out += f"    Unique ID: {self.uniqueid}\n"
 | 
				
			||||||
 | 
					        out += f"    Instance: {self.instance} ({self.instupper}/{self.instlower})\n"
 | 
				
			||||||
 | 
					        out += f"    System instance: {self.sysinstance}\n"
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            devfnname = lookup.devicefunction[self.deviceclass][self.devicefunction]
 | 
				
			||||||
 | 
					        except KeyError:
 | 
				
			||||||
 | 
					            devfnname = "*key error*"
 | 
				
			||||||
 | 
					        out += f"    Device function: {devfnname} ({self.devicefunction})\n"
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            devclassname = lookup.deviceclass[self.deviceclass]
 | 
				
			||||||
 | 
					        except KeyError:
 | 
				
			||||||
 | 
					            devclassname = "*key error*"
 | 
				
			||||||
 | 
					        out += f"    Device class: {devclassname} ({self.deviceclass})\n"
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            igrpname = lookup.industrygroup[self.industrygroup]
 | 
				
			||||||
 | 
					        except KeyError:
 | 
				
			||||||
 | 
					            igrpname = "*key error*"
 | 
				
			||||||
 | 
					        out += f"    Industry group: {igrpname} ({self.industrygroup})\n"
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            manufname = lookup.manufacturer[self.manufacturercode]
 | 
				
			||||||
 | 
					        except KeyError:
 | 
				
			||||||
 | 
					            manufname = "*key error*"
 | 
				
			||||||
 | 
					        out += f"    Manufacturer code: {manufname} ({self.manufacturercode})\n"
 | 
				
			||||||
 | 
					        out +=  "  Product info at {}\n".format(self._ISOtime(self.lastpinfo))
 | 
				
			||||||
 | 
					        out += f"    Product Code: {self.productcode}\n"
 | 
				
			||||||
 | 
					        out += f"    Product: {self.product}\n"
 | 
				
			||||||
 | 
					        out += f"    Serial: {self.serial}\n"
 | 
				
			||||||
 | 
					        out += f"    Model Version: {self.modelvers}\n"
 | 
				
			||||||
 | 
					        out += f"    Software Version: {self.softvers}\n"
 | 
				
			||||||
 | 
					        out += f"    NMEA2000 Version: {self.n2kvers}\n"
 | 
				
			||||||
 | 
					        out += f"    Cert-Level: {lookup.certlevel[self.certlevel]} ({self.certlevel})\n"
 | 
				
			||||||
 | 
					        out += f"    LEN: {self.loadequiv}\n"
 | 
				
			||||||
 | 
					        out +=  "  Configuration info at {}\n".format(self._ISOtime(self.lastcinfo))
 | 
				
			||||||
 | 
					        if self.has_cinfo:
 | 
				
			||||||
 | 
					            out += f"    Installation description 1: {self.instdesc1}\n"
 | 
				
			||||||
 | 
					            out += f"    Installation description 2: {self.instdesc2}\n"
 | 
				
			||||||
 | 
					            out += f"    Manufacturer info: {self.manufinfo}\n"
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            out += "    not available\n"
 | 
				
			||||||
 | 
					        return out
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,17 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					'''
 | 
				
			||||||
 | 
					Platzhalter WIP
 | 
				
			||||||
 | 
					- ausprogrammieren nach Bedarf
 | 
				
			||||||
 | 
					Geräteliste
 | 
				
			||||||
 | 
					 - wird regelmäßig aktualisiert
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					'''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DeviceList():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self):
 | 
				
			||||||
 | 
					        self.devices = list()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def print(self):
 | 
				
			||||||
 | 
					        for d in self.devicelist:
 | 
				
			||||||
 | 
					            print(d)
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,300 @@
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					History Buffer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Permanent storage backed buffer for sensordata
 | 
				
			||||||
 | 
					Only supported at the moment: file system storage
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Values can be 1 to 4 bytes in length
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Header: 32 bytes of size
 | 
				
			||||||
 | 
					     0 0x00 HB00              4 magic number
 | 
				
			||||||
 | 
					     4 0x04 xxxxxxxxxxxxxxxx 16 name, space padded
 | 
				
			||||||
 | 
					    20 0x14 n                 1 byte size of values in buffer
 | 
				
			||||||
 | 
					    21 0x15 mm                2 buffer size in count of values
 | 
				
			||||||
 | 
					    23 0x17 dd                2 time step in seconds between values
 | 
				
			||||||
 | 
					    25 0x19 tttt              4 unix timestamp of head
 | 
				
			||||||
 | 
					    29 0x1d hh                2 head pointer
 | 
				
			||||||
 | 
					    31 0x1f 0xff              1 header end sign
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    32 0x20 ...                 start of buffer data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Usage example: 7 hours of data collected every 75 seconds
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def g_tick(n=1):
 | 
				
			||||||
 | 
					    t = time.time()
 | 
				
			||||||
 | 
					    count = 0
 | 
				
			||||||
 | 
					    while True:
 | 
				
			||||||
 | 
					        count += n
 | 
				
			||||||
 | 
					        yield max(t + count - time.time(), 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					hb = HistoryBuffer("test", 336, 75)
 | 
				
			||||||
 | 
					g = g_tick(hb.dt)
 | 
				
			||||||
 | 
					hb.filename = "/tmp/test.dat"
 | 
				
			||||||
 | 
					hb.begin()
 | 
				
			||||||
 | 
					while True:
 | 
				
			||||||
 | 
					    time.sleep(next(g))
 | 
				
			||||||
 | 
					    hb.add(measured_new_value)
 | 
				
			||||||
 | 
					hb.finish()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TODO
 | 
				
			||||||
 | 
					  - Logging
 | 
				
			||||||
 | 
					  - Additional backend: I2C FRAM module
 | 
				
			||||||
 | 
					  - Sync to next tick after loading from storage
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
 | 
					import struct
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class HistoryBuffer():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, name, size, delta_t):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Buffer can have an optional name of max. 16 characters
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.magic = b'HB00'
 | 
				
			||||||
 | 
					        self.name = name[:16] or ''
 | 
				
			||||||
 | 
					        self.bytesize = 2
 | 
				
			||||||
 | 
					        self.size = size
 | 
				
			||||||
 | 
					        self.dt = delta_t
 | 
				
			||||||
 | 
					        self.headdate = int(time.time())
 | 
				
			||||||
 | 
					        self.head = 0
 | 
				
			||||||
 | 
					        self.buf = [0 for _ in range(size)]
 | 
				
			||||||
 | 
					        self.filename = f"/tmp/hb{name}_{size}-{delta_t}.dat"
 | 
				
			||||||
 | 
					        self.fp = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def begin(self):
 | 
				
			||||||
 | 
					        # Check if data exists and format is correct
 | 
				
			||||||
 | 
					        if not os.path.exists(self.filename):
 | 
				
			||||||
 | 
					            self.createfile()
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            if not self.checkfile():
 | 
				
			||||||
 | 
					                print(f"Incompatible data file: {self.filename}")
 | 
				
			||||||
 | 
					                return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Read old data to continue processing
 | 
				
			||||||
 | 
					        self.fp = open(self.filename, 'r+b')
 | 
				
			||||||
 | 
					        self.headdate = int(time.time())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.fp.seek(25)
 | 
				
			||||||
 | 
					        timestamp = struct.unpack('I', self.fp.read(4))[0]
 | 
				
			||||||
 | 
					        self.head = struct.unpack('H', self.fp.read(2))[0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.fp.seek(32)
 | 
				
			||||||
 | 
					        data = self.fp.read(self.bytesize * self.size)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Fix difference between current time and data time
 | 
				
			||||||
 | 
					        missing = (self.headdate - timestamp) // self.dt
 | 
				
			||||||
 | 
					        if missing > self.size:
 | 
				
			||||||
 | 
					            # too old start new
 | 
				
			||||||
 | 
					            self.clearfile
 | 
				
			||||||
 | 
					            self.head = 0
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            # usable data found, fix missing
 | 
				
			||||||
 | 
					            self.fp.seek(32)
 | 
				
			||||||
 | 
					            data = self.fp.read(self.bytesize * self.size)
 | 
				
			||||||
 | 
					            i = 0
 | 
				
			||||||
 | 
					            for d in range(0, self.size, self.bytesize):
 | 
				
			||||||
 | 
					                if self.bytesize == 1:
 | 
				
			||||||
 | 
					                    self.buf[i] = data[d]
 | 
				
			||||||
 | 
					                elif self.bytesize == 2:
 | 
				
			||||||
 | 
					                    self.buf[i] = data[d] + data[d+1] * 256
 | 
				
			||||||
 | 
					                elif self.bytesize == 3:
 | 
				
			||||||
 | 
					                    self.buf[i] = data[d] + (data[d+1] << 8) + (data[d+2] << 16)
 | 
				
			||||||
 | 
					                elif self.bytesize == 4:
 | 
				
			||||||
 | 
					                    self.buf[i] = data[d] + (data[d+1] << 8) + (data[d+2] << 16) + (data[d+3] << 24)
 | 
				
			||||||
 | 
					                i += 1
 | 
				
			||||||
 | 
					            # add empty data for missing steps
 | 
				
			||||||
 | 
					            for s in range(missing):
 | 
				
			||||||
 | 
					                self.add(0)
 | 
				
			||||||
 | 
					        return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def finish(self):
 | 
				
			||||||
 | 
					        if not self.fp.closed:
 | 
				
			||||||
 | 
					            self.fp.close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def add(self, value):
 | 
				
			||||||
 | 
					        # check if add request perhaps too early
 | 
				
			||||||
 | 
					        timestamp = int(time.time())
 | 
				
			||||||
 | 
					        if timestamp - self.headdate < self.dt * 0.98: # a little bit of tolerance
 | 
				
			||||||
 | 
					            print("add new value too early, ignored")
 | 
				
			||||||
 | 
					            return False
 | 
				
			||||||
 | 
					        self.headdate = timestamp
 | 
				
			||||||
 | 
					        self.buf[self.head] = value
 | 
				
			||||||
 | 
					        self.updatefile(value)
 | 
				
			||||||
 | 
					        self.head += 1
 | 
				
			||||||
 | 
					        if self.head == self.size:
 | 
				
			||||||
 | 
					            self.head = 0
 | 
				
			||||||
 | 
					        return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Return buffer in linear sequence, newest values first
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        for i in range(self.head -1, -1, -1):
 | 
				
			||||||
 | 
					            yield self.buf[i]
 | 
				
			||||||
 | 
					        for i in range(self.size - 1, self.head -1, -1):
 | 
				
			||||||
 | 
					            yield self.buf[i]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def getvalue(self, delta):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Return a single value dt seconds ago
 | 
				
			||||||
 | 
					        delta has to be smaller than self.dt * self.size
 | 
				
			||||||
 | 
					        TODO check if value is missing, perhaps allow tolerance (+/- <n>)
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        index = self.head - abs(delta) // self.dt
 | 
				
			||||||
 | 
					        if index < 0:
 | 
				
			||||||
 | 
					            index = self.size - index - 1
 | 
				
			||||||
 | 
					        return self.buf[index]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def getvalue3(self, delta):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        same as getvalue but calculate mean with two neighbor values
 | 
				
			||||||
 | 
					        TODO check for missing values (=0)
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        index = self.head - abs(delta) // self.dt
 | 
				
			||||||
 | 
					        if index < 0:
 | 
				
			||||||
 | 
					            index = self.size - index - 1
 | 
				
			||||||
 | 
					        ixprev = index - 1
 | 
				
			||||||
 | 
					        if ixprev < 0:
 | 
				
			||||||
 | 
					            ixprev = self.size - 1
 | 
				
			||||||
 | 
					        ixnext = index + 1
 | 
				
			||||||
 | 
					        if ixnext > self.size - 1:
 | 
				
			||||||
 | 
					            ixnext = 0
 | 
				
			||||||
 | 
					        return round((self.buf[index] + self.buf[ixprev] + self.buf[ixnext]) / 3)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def setname(self, newname):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        set new name in buffer and storage backend
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.name = newname[:16] or ''
 | 
				
			||||||
 | 
					        self.fp.seek(4)
 | 
				
			||||||
 | 
					        fp.write(self.name.ljust(16, ' ').encode())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def createfile(self):
 | 
				
			||||||
 | 
					        """"
 | 
				
			||||||
 | 
					        Creates new file from current buffer
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        with open(self.filename, 'wb') as fp:
 | 
				
			||||||
 | 
					            fp.write(self.magic)
 | 
				
			||||||
 | 
					            fp.write(self.name.ljust(16, ' ').encode())
 | 
				
			||||||
 | 
					            fp.write(struct.pack('B', self.bytesize))
 | 
				
			||||||
 | 
					            fp.write(struct.pack('H', self.size))
 | 
				
			||||||
 | 
					            fp.write(struct.pack('H', self.dt))
 | 
				
			||||||
 | 
					            fp.write(struct.pack('I', self.headdate))
 | 
				
			||||||
 | 
					            fp.write(struct.pack('H', self.head))
 | 
				
			||||||
 | 
					            fp.write(b"\xff") # header end
 | 
				
			||||||
 | 
					            if self.bytesize == 1:
 | 
				
			||||||
 | 
					                fp.write(bytes(self.buf))
 | 
				
			||||||
 | 
					            elif self.bytesize == 2:
 | 
				
			||||||
 | 
					                for val in self.buf:
 | 
				
			||||||
 | 
					                    fp.write(struct.pack('H', val))
 | 
				
			||||||
 | 
					            elif self.bytesize == 3:
 | 
				
			||||||
 | 
					                for val in self.buf:
 | 
				
			||||||
 | 
					                    fp.write((val >> 16) & 0xff)
 | 
				
			||||||
 | 
					                    fp.write((val >> 8) & 0xff)
 | 
				
			||||||
 | 
					                    fp.write(val & 0xff)
 | 
				
			||||||
 | 
					            elif self.bytesize == 4:
 | 
				
			||||||
 | 
					                for val in self.buf:
 | 
				
			||||||
 | 
					                    fp.write(struct.pack('I', val))
 | 
				
			||||||
 | 
					        return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def checkfile(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Check if file header matches buffer metadata
 | 
				
			||||||
 | 
					        Name is not taken into account because it is optional
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        with open(self.filename, 'rb') as fp:
 | 
				
			||||||
 | 
					            header = fp.read(32)
 | 
				
			||||||
 | 
					            magic = header[:4]
 | 
				
			||||||
 | 
					            if not (header[:4] == self.magic):
 | 
				
			||||||
 | 
					                print(f"Invalid magic: {magic}")
 | 
				
			||||||
 | 
					                return False
 | 
				
			||||||
 | 
					            bs = header[20]
 | 
				
			||||||
 | 
					            if not (bs == self.bytesize):
 | 
				
			||||||
 | 
					                print(f"Invalid bytesize: {bs}")
 | 
				
			||||||
 | 
					                return False
 | 
				
			||||||
 | 
					            vc = struct.unpack('H', header[21:23])[0]
 | 
				
			||||||
 | 
					            if not (vc == self.size):
 | 
				
			||||||
 | 
					                eprint(f"Invalid value count: {vc}")
 | 
				
			||||||
 | 
					                return False
 | 
				
			||||||
 | 
					            ts = struct.unpack('H', header[23:25])[0]
 | 
				
			||||||
 | 
					            if not (ts == self.dt):
 | 
				
			||||||
 | 
					                eprint(f"Invalid time step: {ts}")
 | 
				
			||||||
 | 
					                return False
 | 
				
			||||||
 | 
					        return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def updatefile(self, value):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Write value to file and update header accordingly
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        pos = 32 + self.head * self.bytesize
 | 
				
			||||||
 | 
					        self.fp.seek(25)
 | 
				
			||||||
 | 
					        self.fp.write(struct.pack('IH', self.headdate, self.head + 1))
 | 
				
			||||||
 | 
					        self.fp.seek(pos)
 | 
				
			||||||
 | 
					        if self.bytesize == 1:
 | 
				
			||||||
 | 
					            self.fp.write(struct.pack('B', value))
 | 
				
			||||||
 | 
					        elif self.bytesize == 2:
 | 
				
			||||||
 | 
					            self.fp.write(struct.pack('H', value))
 | 
				
			||||||
 | 
					        elif self.bytesize == 3:
 | 
				
			||||||
 | 
					            self.fp.write((value >> 16) & 0xff)
 | 
				
			||||||
 | 
					            self.fp.write((value >> 8) & 0xff)
 | 
				
			||||||
 | 
					            self.fp.write(value & 0xff)
 | 
				
			||||||
 | 
					        elif self.bytesize == 4:
 | 
				
			||||||
 | 
					            self.fp.write(struct.pack('I', value))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def clearfile(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Clear data part of history file
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.fp.seek(25)
 | 
				
			||||||
 | 
					        self.fp.write(struct.pack('IH', int(time.time()), 0))
 | 
				
			||||||
 | 
					        fp.seek(32)
 | 
				
			||||||
 | 
					        for p in range(32, self.size * self.bytesize):
 | 
				
			||||||
 | 
					            fp.write(0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class History():
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    A history can consist of different time series with different
 | 
				
			||||||
 | 
					    temporal resolutions
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    TODO implement type (e.g. pressure humidity temp etc.) to identify data
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    def __init__(self, basename, delta_min):
 | 
				
			||||||
 | 
					        self.delta_t = delta_min  # smallest unit of time in the series
 | 
				
			||||||
 | 
					        self.series = dict()
 | 
				
			||||||
 | 
					        self.basepath = "/tmp"
 | 
				
			||||||
 | 
					        self.basename = basename
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __str__(self):
 | 
				
			||||||
 | 
					        out = f"History {self.basename} (min. {self.delta_t}s) in {self.basepath}\n"
 | 
				
			||||||
 | 
					        n = 0
 | 
				
			||||||
 | 
					        for ser in self.series.values():
 | 
				
			||||||
 | 
					            out += f"  Series: {ser.name} {ser.dt}s {ser.filename}\n"
 | 
				
			||||||
 | 
					            n += 1
 | 
				
			||||||
 | 
					        if n == 0:
 | 
				
			||||||
 | 
					            out += "  No series found\n"
 | 
				
			||||||
 | 
					        return out
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def addSeries(self, name, size, delta_t):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Check whether a series already exists and throw an error if so.
 | 
				
			||||||
 | 
					        The new delta t must also be divisible by delta_min
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        if delta_t in self.series:
 | 
				
			||||||
 | 
					            raise KeyError(f"Error: delta t {delta_t} already exists")
 | 
				
			||||||
 | 
					        if delta_t < self.delta_t:
 | 
				
			||||||
 | 
					            raise ValueError(f"Error: delta t {delta_t} too small, minimum is {self.delta_t}")
 | 
				
			||||||
 | 
					        if delta_t % self.delta_t != 0:
 | 
				
			||||||
 | 
					            raise ValueError(f"Error: delta t have to be a multiple of {self.delta_t}")
 | 
				
			||||||
 | 
					        hb = HistoryBuffer(name, size, delta_t)
 | 
				
			||||||
 | 
					        histfilename = f"hb{self.basename}_{size}-{delta_t}.dat"
 | 
				
			||||||
 | 
					        hb.filename = os.path.join(self.basepath, histfilename)
 | 
				
			||||||
 | 
					        self.series[delta_t] = hb
 | 
				
			||||||
 | 
					        return hb
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def clear(self):
 | 
				
			||||||
 | 
					        # Clear all time series buffer
 | 
				
			||||||
 | 
					        self.series.clear()
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,572 @@
 | 
				
			||||||
 | 
					# Lookup tables
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					accesslevel = {
 | 
				
			||||||
 | 
					    0: "Locked",
 | 
				
			||||||
 | 
					    1: "unlocked level 1",
 | 
				
			||||||
 | 
					    2: "unlocked level 2"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					alarmgroup = {
 | 
				
			||||||
 | 
					    0: "Instrument",
 | 
				
			||||||
 | 
					    1: "Autopilot",
 | 
				
			||||||
 | 
					    2: "Radar",
 | 
				
			||||||
 | 
					    3: "Chart Plotter",
 | 
				
			||||||
 | 
					    4: "AIS"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					alarmid = {
 | 
				
			||||||
 | 
					    0: "No Alarm",
 | 
				
			||||||
 | 
					    1: "Shallow Depth",
 | 
				
			||||||
 | 
					    2: "Deep Depth",
 | 
				
			||||||
 | 
					    3: "Shallow Anchor",
 | 
				
			||||||
 | 
					    4: "Deep Anchor",
 | 
				
			||||||
 | 
					    5: "Off Course",
 | 
				
			||||||
 | 
					    6: "AWA High",
 | 
				
			||||||
 | 
					    7: "AWA Low",
 | 
				
			||||||
 | 
					    8: "AWS High",
 | 
				
			||||||
 | 
					    9: "AWS Low",
 | 
				
			||||||
 | 
					    10: "TWA High",
 | 
				
			||||||
 | 
					    11: "TWA Low",
 | 
				
			||||||
 | 
					    12: "TWS High",
 | 
				
			||||||
 | 
					    13: "TWS Low",
 | 
				
			||||||
 | 
					    14: "WP Arrival",
 | 
				
			||||||
 | 
					    15: "Boat Speed High",
 | 
				
			||||||
 | 
					    16: "Boat Speed Low",
 | 
				
			||||||
 | 
					    17: "Sea Temperature High",
 | 
				
			||||||
 | 
					    18: "Sea Temperature Low",
 | 
				
			||||||
 | 
					    19: "Pilot Watch",
 | 
				
			||||||
 | 
					    20: "Pilot Off Course",
 | 
				
			||||||
 | 
					    21: "Pilot Wind Shift",
 | 
				
			||||||
 | 
					    22: "Pilot Low Battery",
 | 
				
			||||||
 | 
					    23: "Pilot Last Minute Of Watch",
 | 
				
			||||||
 | 
					    24: "Pilot No NMEA Data",
 | 
				
			||||||
 | 
					    25: "Pilot Large XTE",
 | 
				
			||||||
 | 
					    26: "Pilot NMEA DataError",
 | 
				
			||||||
 | 
					    27: "Pilot CU Disconnected",
 | 
				
			||||||
 | 
					    28: "Pilot Auto Release",
 | 
				
			||||||
 | 
					    29: "Pilot Way Point Advance",
 | 
				
			||||||
 | 
					    30: "Pilot Drive Stopped",
 | 
				
			||||||
 | 
					    31: "Pilot Type Unspecified",
 | 
				
			||||||
 | 
					    32: "Pilot Calibration Required",
 | 
				
			||||||
 | 
					    33: "Pilot Last Heading",
 | 
				
			||||||
 | 
					    34: "Pilot No Pilot",
 | 
				
			||||||
 | 
					    35: "Pilot Route Complete",
 | 
				
			||||||
 | 
					    36: "Pilot Variable Text",
 | 
				
			||||||
 | 
					    37: "GPS Failure",
 | 
				
			||||||
 | 
					    38: "MOB",
 | 
				
			||||||
 | 
					    39: "Seatalk1 Anchor",
 | 
				
			||||||
 | 
					    40: "Pilot Swapped Motor Power",
 | 
				
			||||||
 | 
					    41: "Pilot Standby Too Fast To Fish",
 | 
				
			||||||
 | 
					    42: "Pilot No GPS Fix",
 | 
				
			||||||
 | 
					    43: "Pilot No GPS COG",
 | 
				
			||||||
 | 
					    44: "Pilot Start Up",
 | 
				
			||||||
 | 
					    45: "Pilot Too Slow",
 | 
				
			||||||
 | 
					    46: "Pilot No Compass",
 | 
				
			||||||
 | 
					    47: "Pilot Rate Gyro Fault",
 | 
				
			||||||
 | 
					    48: "Pilot Current Limit",
 | 
				
			||||||
 | 
					    49: "Pilot Way Point Advance Port",
 | 
				
			||||||
 | 
					    50: "Pilot Way Point Advance Stbd",
 | 
				
			||||||
 | 
					    51: "Pilot No Wind Data",
 | 
				
			||||||
 | 
					    52: "Pilot No Speed Data",
 | 
				
			||||||
 | 
					    53: "Pilot Seatalk Fail1",
 | 
				
			||||||
 | 
					    54: "Pilot Seatalk Fail2",
 | 
				
			||||||
 | 
					    55: "Pilot Warning Too Fast To Fish",
 | 
				
			||||||
 | 
					    56: "Pilot Auto Dockside Fail",
 | 
				
			||||||
 | 
					    57: "Pilot Turn Too Fast",
 | 
				
			||||||
 | 
					    58: "Pilot No Nav Data",
 | 
				
			||||||
 | 
					    59: "Pilot Lost Waypoint Data",
 | 
				
			||||||
 | 
					    60: "Pilot EEPROM Corrupt",
 | 
				
			||||||
 | 
					    61: "Pilot Rudder Feedback Fail",
 | 
				
			||||||
 | 
					    62: "Pilot Autolearn Fail1",
 | 
				
			||||||
 | 
					    63: "Pilot Autolearn Fail2",
 | 
				
			||||||
 | 
					    64: "Pilot Autolearn Fail3",
 | 
				
			||||||
 | 
					    65: "Pilot Autolearn Fail4",
 | 
				
			||||||
 | 
					    66: "Pilot Autolearn Fail5",
 | 
				
			||||||
 | 
					    67: "Pilot Autolearn Fail6",
 | 
				
			||||||
 | 
					    68: "Pilot Warning Cal Required",
 | 
				
			||||||
 | 
					    69: "Pilot Warning OffCourse",
 | 
				
			||||||
 | 
					    70: "Pilot Warning XTE",
 | 
				
			||||||
 | 
					    71: "Pilot Warning Wind Shift",
 | 
				
			||||||
 | 
					    72: "Pilot Warning Drive Short",
 | 
				
			||||||
 | 
					    73: "Pilot Warning Clutch Short",
 | 
				
			||||||
 | 
					    74: "Pilot Warning Solenoid Short",
 | 
				
			||||||
 | 
					    75: "Pilot Joystick Fault",
 | 
				
			||||||
 | 
					    76: "Pilot No Joystick Data",
 | 
				
			||||||
 | 
					    80: "Pilot Invalid Command",
 | 
				
			||||||
 | 
					    81: "AIS TX Malfunction",
 | 
				
			||||||
 | 
					    82: "AIS Antenna VSWR fault",
 | 
				
			||||||
 | 
					    83: "AIS Rx channel 1 malfunction",
 | 
				
			||||||
 | 
					    84: "AIS Rx channel 2 malfunction",
 | 
				
			||||||
 | 
					    85: "AIS No sensor position in use",
 | 
				
			||||||
 | 
					    86: "AIS No valid SOG information",
 | 
				
			||||||
 | 
					    87: "AIS No valid COG information",
 | 
				
			||||||
 | 
					    88: "AIS 12V alarm",
 | 
				
			||||||
 | 
					    89: "AIS 6V alarm",
 | 
				
			||||||
 | 
					    90: "AIS Noise threshold exceeded channel A",
 | 
				
			||||||
 | 
					    91: "AIS Noise threshold exceeded channel B",
 | 
				
			||||||
 | 
					    92: "AIS Transmitter PA fault",
 | 
				
			||||||
 | 
					    93: "AIS 3V3 alarm",
 | 
				
			||||||
 | 
					    94: "AIS Rx channel 70 malfunction",
 | 
				
			||||||
 | 
					    95: "AIS Heading lost/invalid",
 | 
				
			||||||
 | 
					    96: "AIS internal GPS lost",
 | 
				
			||||||
 | 
					    97: "AIS No sensor position",
 | 
				
			||||||
 | 
					    98: "AIS Lock failure",
 | 
				
			||||||
 | 
					    99: "AIS Internal GGA timeout",
 | 
				
			||||||
 | 
					    100: "AIS Protocol stack restart",
 | 
				
			||||||
 | 
					    101: "Pilot No IPS communications",
 | 
				
			||||||
 | 
					    102: "Pilot Power-On or Sleep-Switch Reset While Engaged",
 | 
				
			||||||
 | 
					    103: "Pilot Unexpected Reset While Engaged",
 | 
				
			||||||
 | 
					    104: "AIS Dangerous Target",
 | 
				
			||||||
 | 
					    105: "AIS Lost Target",
 | 
				
			||||||
 | 
					    106: "AIS Safety Related Message (used to silence)",
 | 
				
			||||||
 | 
					    107: "AIS Connection Lost",
 | 
				
			||||||
 | 
					    108: "No Fix"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					alarmstatus = {
 | 
				
			||||||
 | 
					    0: "Alarm condition not met",
 | 
				
			||||||
 | 
					    1: "Alarm condition met and not silenced",
 | 
				
			||||||
 | 
					    2: "Alarm condition met and silenced"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Class 1, 2, Level A, B?
 | 
				
			||||||
 | 
					certlevel = { # Not yet verified!
 | 
				
			||||||
 | 
					    0: "None",
 | 
				
			||||||
 | 
					    1: "Certified",
 | 
				
			||||||
 | 
					    2: "Not applicable"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					control = {
 | 
				
			||||||
 | 
					    0: "ACK",
 | 
				
			||||||
 | 
					    1: "NAK",
 | 
				
			||||||
 | 
					    2: "Access Denied",
 | 
				
			||||||
 | 
					    3: "Address Busy"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					deviceclass = {
 | 
				
			||||||
 | 
					    0: "Reserved for 2000 Use",
 | 
				
			||||||
 | 
					    10: "System tools",
 | 
				
			||||||
 | 
					    11: "WEMA Custom?",
 | 
				
			||||||
 | 
					    20: "Safety systems",
 | 
				
			||||||
 | 
					    25: "Internetwork device",
 | 
				
			||||||
 | 
					    30: "Electrical Distribution",
 | 
				
			||||||
 | 
					    35: "Electrical Generation",
 | 
				
			||||||
 | 
					    40: "Steering and Control surfaces",
 | 
				
			||||||
 | 
					    50: "Propulsion",
 | 
				
			||||||
 | 
					    60: "Navigation",
 | 
				
			||||||
 | 
					    70: "Communication",
 | 
				
			||||||
 | 
					    75: "Sensor Communication Interface",
 | 
				
			||||||
 | 
					    80: "Instrumentation/general systems", # deprecated
 | 
				
			||||||
 | 
					    85: "External Environment",
 | 
				
			||||||
 | 
					    90: "Internal Environment",
 | 
				
			||||||
 | 
					    100: "Deck + cargo + fishing equipment systems",
 | 
				
			||||||
 | 
					    110: "Human Interface",
 | 
				
			||||||
 | 
					    120: "Display",
 | 
				
			||||||
 | 
					    125: "Entertainment"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					devicefunction = { # dependent of deviceclass above
 | 
				
			||||||
 | 
					    0: {},
 | 
				
			||||||
 | 
					    10: {130: "Diagnostic",
 | 
				
			||||||
 | 
					         140: "Bus Traffic Logger"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    11: {150: "WEMA Fluid level" # Custom?
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    20: {110: "Alarm Enunciator",
 | 
				
			||||||
 | 
					         130: "Emergency Positon Indicating Radia Beacon (EPIRB)",
 | 
				
			||||||
 | 
					         135: "Man Overboard",
 | 
				
			||||||
 | 
					         140: "Voyage Date Recorder",
 | 
				
			||||||
 | 
					         150: "Camera"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    25: {130: "PC Gateway",
 | 
				
			||||||
 | 
					         131: "NMEA 2000 to Analog Gateway",
 | 
				
			||||||
 | 
					         132: "Analog to NMEA 2000 Gateway",
 | 
				
			||||||
 | 
					         133: "NMEA 2000 to Serial Gateway",
 | 
				
			||||||
 | 
					         135: "NMEA 0183 Gateway",
 | 
				
			||||||
 | 
					         136: "NMEA Network Gateway",
 | 
				
			||||||
 | 
					         137: "NMEA 2000 Wireless Gateway",
 | 
				
			||||||
 | 
					         140: "Router",
 | 
				
			||||||
 | 
					         150: "Bridge",
 | 
				
			||||||
 | 
					         160: "Repeater"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    30: {130: "Binary Event Monitor",
 | 
				
			||||||
 | 
					         140: "Load Controller",
 | 
				
			||||||
 | 
					         141: "AC/DC Input",
 | 
				
			||||||
 | 
					         150: "Function Controller"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    35: {140: "Engine",
 | 
				
			||||||
 | 
					         141: "DC Generator/Alternator",
 | 
				
			||||||
 | 
					         142: "Solar Panel (Solar Array)",
 | 
				
			||||||
 | 
					         143: "Wind Generator (DC)",
 | 
				
			||||||
 | 
					         144: "Fuel Cell",
 | 
				
			||||||
 | 
					         145: "Network Power Supply",
 | 
				
			||||||
 | 
					         151: "AC Generator",
 | 
				
			||||||
 | 
					         152: "AC Bus",
 | 
				
			||||||
 | 
					         153: "AC Mains (Utility/Shore)",
 | 
				
			||||||
 | 
					         154: "AC Output",
 | 
				
			||||||
 | 
					         160: "Power Converter - Battery Charger",
 | 
				
			||||||
 | 
					         161: "Power Converter - Battery Charger+Inverter",
 | 
				
			||||||
 | 
					         162: "Power Converter - Inverter",
 | 
				
			||||||
 | 
					         163: "Power Converter DC",
 | 
				
			||||||
 | 
					         170: "Battery",
 | 
				
			||||||
 | 
					         180: "Engine Gateway"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    40: {130: "Follow-up Controller",
 | 
				
			||||||
 | 
					         140: "Mode Controller",
 | 
				
			||||||
 | 
					         150: "Autopilot",
 | 
				
			||||||
 | 
					         155: "Rudder",
 | 
				
			||||||
 | 
					         160: "Heading Sensors", # deprecated
 | 
				
			||||||
 | 
					         170: "Trim (Tabs)/Interceptors",
 | 
				
			||||||
 | 
					         180: "Attitude (Pitch, Roll, Yaw) Control"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    50: {130: "Engineroom Monitoring", # deprecated
 | 
				
			||||||
 | 
					         140: "Engine",
 | 
				
			||||||
 | 
					         141: "DC Generator/Alternator",
 | 
				
			||||||
 | 
					         150: "Engine Controller", # deprecated
 | 
				
			||||||
 | 
					         151: "AC Generator",
 | 
				
			||||||
 | 
					         155: "Motor",
 | 
				
			||||||
 | 
					         160: "Engine Gateway",
 | 
				
			||||||
 | 
					         165: "Transmission",
 | 
				
			||||||
 | 
					         170: "Throttle/Shift Control",
 | 
				
			||||||
 | 
					         180: "Actuator", # deprecated
 | 
				
			||||||
 | 
					         190: "Gauge Interface", #deprecated
 | 
				
			||||||
 | 
					         200: "Gauge Large", # deprecated
 | 
				
			||||||
 | 
					         210: "Gauge Small" # deprecated
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    60: {130: "Bottom Depth",
 | 
				
			||||||
 | 
					         135: "Bottom Depth/Speed",
 | 
				
			||||||
 | 
					         136: "Bottom Depth/Speed/Temperature",
 | 
				
			||||||
 | 
					         140: "Ownship Attitude",
 | 
				
			||||||
 | 
					         145: "Ownship Position (GNSS)",
 | 
				
			||||||
 | 
					         150: "Ownship Position (Loran C)",
 | 
				
			||||||
 | 
					         155: "Speed",
 | 
				
			||||||
 | 
					         160: "Turn Rate Indicator", # deprecated
 | 
				
			||||||
 | 
					         170: "Integrated Navigaton", # deprecated
 | 
				
			||||||
 | 
					         175: "Integrated Navigation System",
 | 
				
			||||||
 | 
					         190: "Navigation Management",
 | 
				
			||||||
 | 
					         195: "Automatic Identification System (AIS)",
 | 
				
			||||||
 | 
					         200: "Radar",
 | 
				
			||||||
 | 
					         201: "Infrared Imaging",
 | 
				
			||||||
 | 
					         205: "ECDIS", # deprecated
 | 
				
			||||||
 | 
					         210: "ECS", # deprecated
 | 
				
			||||||
 | 
					         220: "Direction Finder", # deprecated
 | 
				
			||||||
 | 
					         230: "Voyage Status"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    70: {130: "EPIRB", # deprecated
 | 
				
			||||||
 | 
					         140: "AIS", # deprecated
 | 
				
			||||||
 | 
					         150: "DSC", # deprecated
 | 
				
			||||||
 | 
					         160: "Data Receiver/Transceiver",
 | 
				
			||||||
 | 
					         170: "Satellite",
 | 
				
			||||||
 | 
					         180: "Radio-telephone (MF/HF)", # deprecated
 | 
				
			||||||
 | 
					         190: "Radiotelephone"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    75: {130: "Temperature",
 | 
				
			||||||
 | 
					         140: "Pressure",
 | 
				
			||||||
 | 
					         150: "Fluid Level",
 | 
				
			||||||
 | 
					         160: "Flow",
 | 
				
			||||||
 | 
					         170: "Humidity"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    80: {130: "Time/Date Systems", # deprecated
 | 
				
			||||||
 | 
					         140: "VDR", # deprecated
 | 
				
			||||||
 | 
					         150: "Integrated Instrumentation", # deprecated
 | 
				
			||||||
 | 
					         160: "General Purpose Displays", # deprecated
 | 
				
			||||||
 | 
					         170: "General Sensor Box", # deprecated
 | 
				
			||||||
 | 
					         180: "Wheather Instruments", # deprecated
 | 
				
			||||||
 | 
					         190: "Transducer/General", # deprecated
 | 
				
			||||||
 | 
					         200: "NMEA 0183 Converter" # deprecated
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    85: {130: "Athmospheric",
 | 
				
			||||||
 | 
					         140: "Aquatic"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    90: {130: "HVAC"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    100: {130: "Scale (Catch)"
 | 
				
			||||||
 | 
					         },
 | 
				
			||||||
 | 
					    110: {130: "Button Interface",
 | 
				
			||||||
 | 
					          135: "Switch Interface",
 | 
				
			||||||
 | 
					          140: "Analog Interface"
 | 
				
			||||||
 | 
					         },
 | 
				
			||||||
 | 
					    120: {130: "Display",
 | 
				
			||||||
 | 
					          140: "Alarm Enunciator"
 | 
				
			||||||
 | 
					         },
 | 
				
			||||||
 | 
					    125: {130: "Multimedia Player",
 | 
				
			||||||
 | 
					          140: "Multimedia Controller"
 | 
				
			||||||
 | 
					         }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fluidtype = {
 | 
				
			||||||
 | 
					    0: "Fuel",
 | 
				
			||||||
 | 
					    1: "Water",
 | 
				
			||||||
 | 
					    2: "Gray Water",
 | 
				
			||||||
 | 
					    3: "Live Well",
 | 
				
			||||||
 | 
					    4: "Oil",
 | 
				
			||||||
 | 
					    5: "Black Water",
 | 
				
			||||||
 | 
					    6: "Fuel Gasoline",
 | 
				
			||||||
 | 
					    14: "Error",
 | 
				
			||||||
 | 
					    15: "Unavailable"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					industrygroup = {
 | 
				
			||||||
 | 
					    0: "Global",
 | 
				
			||||||
 | 
					    1: "Highway",
 | 
				
			||||||
 | 
					    2: "Agriculture",
 | 
				
			||||||
 | 
					    3: "Construction",
 | 
				
			||||||
 | 
					    4: "Marine",
 | 
				
			||||||
 | 
					    5: "Industrial"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					manufacturer = {
 | 
				
			||||||
 | 
					    69: "ARKS Enterprises, Inc.",
 | 
				
			||||||
 | 
					    78: "FW Murphy/Enovation Controls",
 | 
				
			||||||
 | 
					    80: "Twin Disc",
 | 
				
			||||||
 | 
					    85: "Kohler Power Systems",
 | 
				
			||||||
 | 
					    88: "Hemisphere GPS Inc",
 | 
				
			||||||
 | 
					    116: "BEP Marine",
 | 
				
			||||||
 | 
					    135: "Airmar",
 | 
				
			||||||
 | 
					    137: "Maretron",
 | 
				
			||||||
 | 
					    140: "Lowrance",
 | 
				
			||||||
 | 
					    144: "Mercury Marine",
 | 
				
			||||||
 | 
					    147: "Nautibus Electronic GmbH",
 | 
				
			||||||
 | 
					    148: "Blue Water Data",
 | 
				
			||||||
 | 
					    154: "Westerbeke",
 | 
				
			||||||
 | 
					    161: "Offshore Systems (UK) Ltd.",
 | 
				
			||||||
 | 
					    163: "Evinrude/BRP",
 | 
				
			||||||
 | 
					    165: "CPAC Systems AB",
 | 
				
			||||||
 | 
					    168: "Xantrex Technology Inc.",
 | 
				
			||||||
 | 
					    172: "Yanmar Marine",
 | 
				
			||||||
 | 
					    174: "Volvo Penta",
 | 
				
			||||||
 | 
					    175: "Honda Marine",
 | 
				
			||||||
 | 
					    176: "Carling Technologies Inc. (Moritz Aerospace)",
 | 
				
			||||||
 | 
					    185: "Beede Instruments",
 | 
				
			||||||
 | 
					    192: "Floscan Instrument Co. Inc.",
 | 
				
			||||||
 | 
					    193: "Nobletec",
 | 
				
			||||||
 | 
					    198: "Mystic Valley Communications",
 | 
				
			||||||
 | 
					    199: "Actia",
 | 
				
			||||||
 | 
					    200: "Honda Marine",
 | 
				
			||||||
 | 
					    201: "Disenos Y Technologia",
 | 
				
			||||||
 | 
					    211: "Digital Switching Systems",
 | 
				
			||||||
 | 
					    215: "Xintex/Atena",
 | 
				
			||||||
 | 
					    224: "EMMI NETWORK S.L.",
 | 
				
			||||||
 | 
					    225: "Honda Marine",
 | 
				
			||||||
 | 
					    228: "ZF",
 | 
				
			||||||
 | 
					    229: "Garmin",
 | 
				
			||||||
 | 
					    233: "Yacht Monitoring Solutions",
 | 
				
			||||||
 | 
					    235: "Sailormade Marine Telemetry/Tetra Technology LTD",
 | 
				
			||||||
 | 
					    243: "Eride",
 | 
				
			||||||
 | 
					    250: "Honda Marine",
 | 
				
			||||||
 | 
					    257: "Honda Motor Company LTD",
 | 
				
			||||||
 | 
					    272: "Groco",
 | 
				
			||||||
 | 
					    273: "Actisense",
 | 
				
			||||||
 | 
					    274: "Amphenol LTW Technology",
 | 
				
			||||||
 | 
					    275: "Navico",
 | 
				
			||||||
 | 
					    283: "Hamilton Jet",
 | 
				
			||||||
 | 
					    285: "Sea Recovery",
 | 
				
			||||||
 | 
					    286: "Coelmo SRL Italy",
 | 
				
			||||||
 | 
					    295: "BEP Marine",
 | 
				
			||||||
 | 
					    304: "Empir Bus",
 | 
				
			||||||
 | 
					    305: "NovAtel",
 | 
				
			||||||
 | 
					    306: "Sleipner Motor AS",
 | 
				
			||||||
 | 
					    307: "MBW Technologies",
 | 
				
			||||||
 | 
					    311: "Fischer Panda",
 | 
				
			||||||
 | 
					    315: "ICOM",
 | 
				
			||||||
 | 
					    328: "Qwerty",
 | 
				
			||||||
 | 
					    329: "Dief",
 | 
				
			||||||
 | 
					    341: "Böning Automationstechnologie GmbH & Co. KG",
 | 
				
			||||||
 | 
					    345: "Korean Maritime University",
 | 
				
			||||||
 | 
					    351: "Thrane and Thrane",
 | 
				
			||||||
 | 
					    355: "Mastervolt",
 | 
				
			||||||
 | 
					    356: "Fischer Panda Generators",
 | 
				
			||||||
 | 
					    358: "Victron Energy",
 | 
				
			||||||
 | 
					    370: "Rolls Royce Marine",
 | 
				
			||||||
 | 
					    373: "Electronic Design",
 | 
				
			||||||
 | 
					    374: "Northern Lights",
 | 
				
			||||||
 | 
					    378: "Glendinning",
 | 
				
			||||||
 | 
					    381: "B & G",
 | 
				
			||||||
 | 
					    384: "Rose Point Navigation Systems",
 | 
				
			||||||
 | 
					    385: "Johnson Outdoors Marine Electronics Inc Geonav",
 | 
				
			||||||
 | 
					    394: "Capi 2",
 | 
				
			||||||
 | 
					    396: "Beyond Measure",
 | 
				
			||||||
 | 
					    400: "Livorsi Marine",
 | 
				
			||||||
 | 
					    404: "ComNav",
 | 
				
			||||||
 | 
					    409: "Chetco",
 | 
				
			||||||
 | 
					    419: "Fusion Electronics",
 | 
				
			||||||
 | 
					    421: "Standard Horizon",
 | 
				
			||||||
 | 
					    422: "True Heading AB",
 | 
				
			||||||
 | 
					    426: "Egersund Marine Electronics AS",
 | 
				
			||||||
 | 
					    427: "em-trak Marine Electronics",
 | 
				
			||||||
 | 
					    431: "Tohatsu Co, JP",
 | 
				
			||||||
 | 
					    437: "Digital Yacht",
 | 
				
			||||||
 | 
					    438: "Comar Systems Limited",
 | 
				
			||||||
 | 
					    440: "Cummins",
 | 
				
			||||||
 | 
					    443: "VDO (aka Continental-Corporation)",
 | 
				
			||||||
 | 
					    451: "Parker Hannifin aka Village Marine Tech",
 | 
				
			||||||
 | 
					    459: "Alltek Marine Electronics Corp",
 | 
				
			||||||
 | 
					    460: "SAN GIORGIO S.E.I.N",
 | 
				
			||||||
 | 
					    466: "Veethree Electronics & Marine",
 | 
				
			||||||
 | 
					    467: "Humminbird Marine Electronics",
 | 
				
			||||||
 | 
					    470: "SI-TEX Marine Electronics",
 | 
				
			||||||
 | 
					    471: "Sea Cross Marine AB",
 | 
				
			||||||
 | 
					    475: "GME aka Standard Communications Pty LTD",
 | 
				
			||||||
 | 
					    476: "Humminbird Marine Electronics",
 | 
				
			||||||
 | 
					    478: "Ocean Sat BV",
 | 
				
			||||||
 | 
					    481: "Chetco Digitial Instruments",
 | 
				
			||||||
 | 
					    493: "Watcheye",
 | 
				
			||||||
 | 
					    499: "Lcj Capteurs",
 | 
				
			||||||
 | 
					    502: "Attwood Marine",
 | 
				
			||||||
 | 
					    503: "Naviop S.R.L.",
 | 
				
			||||||
 | 
					    504: "Vesper Marine Ltd",
 | 
				
			||||||
 | 
					    510: "Marinesoft Co. LTD",
 | 
				
			||||||
 | 
					    517: "NoLand Engineering",
 | 
				
			||||||
 | 
					    518: "Transas USA",
 | 
				
			||||||
 | 
					    529: "National Instruments Korea",
 | 
				
			||||||
 | 
					    532: "Onwa Marine",
 | 
				
			||||||
 | 
					    571: "Marinecraft (South Korea)",
 | 
				
			||||||
 | 
					    573: "McMurdo Group aka Orolia LTD",
 | 
				
			||||||
 | 
					    578: "Advansea",
 | 
				
			||||||
 | 
					    579: "KVH",
 | 
				
			||||||
 | 
					    580: "San Jose Technology",
 | 
				
			||||||
 | 
					    583: "Yacht Control",
 | 
				
			||||||
 | 
					    586: "Suzuki Motor Corporation",
 | 
				
			||||||
 | 
					    591: "US Coast Guard",
 | 
				
			||||||
 | 
					    595: "Ship Module aka Customware",
 | 
				
			||||||
 | 
					    600: "Aquatic AV",
 | 
				
			||||||
 | 
					    605: "Aventics GmbH",
 | 
				
			||||||
 | 
					    606: "Intellian",
 | 
				
			||||||
 | 
					    612: "SamwonIT",
 | 
				
			||||||
 | 
					    614: "Arlt Tecnologies",
 | 
				
			||||||
 | 
					    637: "Bavaria Yacts",
 | 
				
			||||||
 | 
					    641: "Diverse Yacht Services",
 | 
				
			||||||
 | 
					    644: "Wema U.S.A dba KUS",
 | 
				
			||||||
 | 
					    645: "Garmin",
 | 
				
			||||||
 | 
					    658: "Shenzhen Jiuzhou Himunication",
 | 
				
			||||||
 | 
					    688: "Rockford Corp",
 | 
				
			||||||
 | 
					    704: "JL Audio",
 | 
				
			||||||
 | 
					    715: "Autonnic",
 | 
				
			||||||
 | 
					    717: "Yacht Devices",
 | 
				
			||||||
 | 
					    734: "REAP Systems",
 | 
				
			||||||
 | 
					    735: "Au Electronics Group",
 | 
				
			||||||
 | 
					    739: "LxNav",
 | 
				
			||||||
 | 
					    743: "DaeMyung",
 | 
				
			||||||
 | 
					    744: "Woosung",
 | 
				
			||||||
 | 
					    773: "Clarion US",
 | 
				
			||||||
 | 
					    776: "HMI Systems",
 | 
				
			||||||
 | 
					    777: "Ocean Signal",
 | 
				
			||||||
 | 
					    778: "Seekeeper",
 | 
				
			||||||
 | 
					    781: "Poly Planar",
 | 
				
			||||||
 | 
					    785: "Fischer Panda DE",
 | 
				
			||||||
 | 
					    795: "Broyda Industries",
 | 
				
			||||||
 | 
					    796: "Canadian Automotive",
 | 
				
			||||||
 | 
					    797: "Tides Marine",
 | 
				
			||||||
 | 
					    798: "Lumishore",
 | 
				
			||||||
 | 
					    799: "Still Water Designs and Audio",
 | 
				
			||||||
 | 
					    802: "BJ Technologies (Beneteau)",
 | 
				
			||||||
 | 
					    803: "Gill Sensors",
 | 
				
			||||||
 | 
					    811: "Blue Water Desalination",
 | 
				
			||||||
 | 
					    815: "FLIR",
 | 
				
			||||||
 | 
					    824: "Undheim Systems",
 | 
				
			||||||
 | 
					    838: "TeamSurv",
 | 
				
			||||||
 | 
					    844: "Fell Marine",
 | 
				
			||||||
 | 
					    847: "Oceanvolt",
 | 
				
			||||||
 | 
					    862: "Prospec",
 | 
				
			||||||
 | 
					    868: "Data Panel Corp",
 | 
				
			||||||
 | 
					    890: "L3 Technologies",
 | 
				
			||||||
 | 
					    894: "Rhodan Marine Systems",
 | 
				
			||||||
 | 
					    896: "Nexfour Solutions",
 | 
				
			||||||
 | 
					    905: "ASA Electronics",
 | 
				
			||||||
 | 
					    909: "Marines Co (South Korea)",
 | 
				
			||||||
 | 
					    911: "Nautic-on",
 | 
				
			||||||
 | 
					    930: "Ecotronix",
 | 
				
			||||||
 | 
					    962: "Timbolier Industries",
 | 
				
			||||||
 | 
					    963: "TJC Micro",
 | 
				
			||||||
 | 
					    968: "Cox Powertrain",
 | 
				
			||||||
 | 
					    969: "Blue Seas",
 | 
				
			||||||
 | 
					    1850: "Teleflex Marine (SeaStar Solutions)",
 | 
				
			||||||
 | 
					    1851: "Raymarine",
 | 
				
			||||||
 | 
					    1852: "Navionics",
 | 
				
			||||||
 | 
					    1853: "Japan Radio Co",
 | 
				
			||||||
 | 
					    1854: "Northstar Technologies",
 | 
				
			||||||
 | 
					    1855: "Furuno",
 | 
				
			||||||
 | 
					    1856: "Trimble",
 | 
				
			||||||
 | 
					    1857: "Simrad",
 | 
				
			||||||
 | 
					    1858: "Litton",
 | 
				
			||||||
 | 
					    1859: "Kvasar AB",
 | 
				
			||||||
 | 
					    1860: "MMP",
 | 
				
			||||||
 | 
					    1861: "Vector Cantech",
 | 
				
			||||||
 | 
					    1862: "Yamaha Marine",
 | 
				
			||||||
 | 
					    1863: "Faria Instruments",
 | 
				
			||||||
 | 
					    2046: "Open Boat Projects"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pilotmode = {
 | 
				
			||||||
 | 
					    64: "Standby",
 | 
				
			||||||
 | 
					    66: "Auto",
 | 
				
			||||||
 | 
					    70: "Wind",
 | 
				
			||||||
 | 
					    74: "Track"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pressure = {
 | 
				
			||||||
 | 
					    0: "Athmospheric",
 | 
				
			||||||
 | 
					    1: "Water",
 | 
				
			||||||
 | 
					    2: "Steam",
 | 
				
			||||||
 | 
					    3: "Compressed Air",
 | 
				
			||||||
 | 
					    4: "Hydraulic",
 | 
				
			||||||
 | 
					    5: "Filter",
 | 
				
			||||||
 | 
					    6: "AltimeterSetting",
 | 
				
			||||||
 | 
					    7: "Oil",
 | 
				
			||||||
 | 
					    8: "Fuel"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					prnusage = {
 | 
				
			||||||
 | 
					    0: "Not Tracked",
 | 
				
			||||||
 | 
					    1: "Tracked",
 | 
				
			||||||
 | 
					    2: "Used",
 | 
				
			||||||
 | 
					    3: "Not tracked+Diff",
 | 
				
			||||||
 | 
					    4: "Tracked+Diff",
 | 
				
			||||||
 | 
					    5: "Used+Diff",
 | 
				
			||||||
 | 
					    14: "Error",
 | 
				
			||||||
 | 
					    15: "No Selection"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					speedwater = {
 | 
				
			||||||
 | 
					    0: "Paddle wheel",
 | 
				
			||||||
 | 
					    1: "Pitot tube",
 | 
				
			||||||
 | 
					    2: "Doppler",
 | 
				
			||||||
 | 
					    3: "Correlation (ultra sound)",
 | 
				
			||||||
 | 
					    4: "Electro Magnetic"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					timesource = {
 | 
				
			||||||
 | 
					    0: "GPS",
 | 
				
			||||||
 | 
					    1: "GLONASS",
 | 
				
			||||||
 | 
					    2: "Radio Station",
 | 
				
			||||||
 | 
					    3: "Local Cesium clock",
 | 
				
			||||||
 | 
					    4: "Local Rubidium clock",
 | 
				
			||||||
 | 
					    5: "Local Crystal clock"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					temperature = {
 | 
				
			||||||
 | 
					    0: "Sea Temperature",
 | 
				
			||||||
 | 
					    1: "Outside Temperature",
 | 
				
			||||||
 | 
					    2: "Inside Temperature",
 | 
				
			||||||
 | 
					    3: "Engine Room Temperature",
 | 
				
			||||||
 | 
					    4: "Main Cabin Temperature",
 | 
				
			||||||
 | 
					    5: "Live Well Temperature",
 | 
				
			||||||
 | 
					    6: "Bait Well Temperature",
 | 
				
			||||||
 | 
					    7: "Refrigeration Temperature",
 | 
				
			||||||
 | 
					    8: "Heating System Temperature",
 | 
				
			||||||
 | 
					    9: "Dew Point Temperature",
 | 
				
			||||||
 | 
					    10: "Apparent Wind Chill Temperature",
 | 
				
			||||||
 | 
					    11: "Theoretical Wind Chill Temperature",
 | 
				
			||||||
 | 
					    12: "Heat Index Temperature",
 | 
				
			||||||
 | 
					    13: "Freezer Temperature",
 | 
				
			||||||
 | 
					    14: "Exhaust Gas Temperature",
 | 
				
			||||||
 | 
					    15: "Shaft Seal Temperature"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					xtemode = {
 | 
				
			||||||
 | 
					    0: "auto",
 | 
				
			||||||
 | 
					    1: "differential",
 | 
				
			||||||
 | 
					    2: "estimated",
 | 
				
			||||||
 | 
					    3: "simulation",
 | 
				
			||||||
 | 
					    4: "manual"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,56 @@
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					Moving Average
 | 
				
			||||||
 | 
					returns a float so no integer rounding applied
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class mAvg():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, size):
 | 
				
			||||||
 | 
					        self.size = size
 | 
				
			||||||
 | 
					        self.data = [0] * size
 | 
				
			||||||
 | 
					        self.nval = 0
 | 
				
			||||||
 | 
					        self.sum = 0
 | 
				
			||||||
 | 
					        self.next = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def addVal(self, value):
 | 
				
			||||||
 | 
					        # add a new value und return new current avg
 | 
				
			||||||
 | 
					        if self.nval < self.size:
 | 
				
			||||||
 | 
					            # list is not filled
 | 
				
			||||||
 | 
					            self.nval += 1
 | 
				
			||||||
 | 
					            self.sum += value
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            # list is filled
 | 
				
			||||||
 | 
					            self.sum = self.sum - self.data[self.next] - value
 | 
				
			||||||
 | 
					        self.data.[self.next] = value
 | 
				
			||||||
 | 
					        self.next += 1
 | 
				
			||||||
 | 
					        # if end of list reached, start over from beginning
 | 
				
			||||||
 | 
					        if self.next = self.size:
 | 
				
			||||||
 | 
					            self.next = 0
 | 
				
			||||||
 | 
					        return self.sum / self.nval
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def getAvg(self, points=None):
 | 
				
			||||||
 | 
					        if self.nval == 0:
 | 
				
			||||||
 | 
					            return None
 | 
				
			||||||
 | 
					        # get avg of all collected data
 | 
				
			||||||
 | 
					        if not points:
 | 
				
			||||||
 | 
					            return self.sum / self.nval
 | 
				
			||||||
 | 
					        # get avg of subset
 | 
				
			||||||
 | 
					        if points > self.nval:
 | 
				
			||||||
 | 
					            return None
 | 
				
			||||||
 | 
					        sum = 0
 | 
				
			||||||
 | 
					        i = self.next
 | 
				
			||||||
 | 
					        p = points
 | 
				
			||||||
 | 
					        while p > 0:
 | 
				
			||||||
 | 
					            p -= 1
 | 
				
			||||||
 | 
					            if i == 0:
 | 
				
			||||||
 | 
					                i = self.size - 1
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                i -= 1
 | 
				
			||||||
 | 
					            sum += self.data[i]
 | 
				
			||||||
 | 
					        return sum / points
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def reset(self):
 | 
				
			||||||
 | 
					        self.nval = 0
 | 
				
			||||||
 | 
					        self.next = 0
 | 
				
			||||||
 | 
					        self.sum = 0
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,288 @@
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					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_127501(buf):
 | 
				
			||||||
 | 
					    # Switch control status
 | 
				
			||||||
 | 
					    # 7 byte for switches every switch takes 2 bits
 | 
				
			||||||
 | 
					    # 0 = Off, 1=On, 2 and 3 unknown
 | 
				
			||||||
 | 
					    instance = buf[0]
 | 
				
			||||||
 | 
					    switch = [0] * 28
 | 
				
			||||||
 | 
					    ix = 0
 | 
				
			||||||
 | 
					    for b in range(1, 8):
 | 
				
			||||||
 | 
					        val = buf[b]
 | 
				
			||||||
 | 
					        for x in range(4):
 | 
				
			||||||
 | 
					            switch[ix] = val & 0x03
 | 
				
			||||||
 | 
					            val = val >> 2
 | 
				
			||||||
 | 
					            ix += 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def parse_127502(buf):
 | 
				
			||||||
 | 
					    # Switch control
 | 
				
			||||||
 | 
					    # 7 byte for switches every switch takes 2 bits
 | 
				
			||||||
 | 
					    # 0 = Off, 1=On, 2 and 3 unknown
 | 
				
			||||||
 | 
					    instance = buf[0]
 | 
				
			||||||
 | 
					    switch = [0] * 28
 | 
				
			||||||
 | 
					    ix = 0
 | 
				
			||||||
 | 
					    for b in range(1, 8):
 | 
				
			||||||
 | 
					        val = buf[b]
 | 
				
			||||||
 | 
					        for x in range(4):
 | 
				
			||||||
 | 
					            switch[ix] = val & 0x03
 | 
				
			||||||
 | 
					            val = val >> 2
 | 
				
			||||||
 | 
					            ix += 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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, 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
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,275 @@
 | 
				
			||||||
 | 
					pgntype = {
 | 
				
			||||||
 | 
					    59392: "S",
 | 
				
			||||||
 | 
					    59904: "S",
 | 
				
			||||||
 | 
					    60160: "S",
 | 
				
			||||||
 | 
					    60416: "S",
 | 
				
			||||||
 | 
					    60928: "S",
 | 
				
			||||||
 | 
					    61184: "S",
 | 
				
			||||||
 | 
					    65001: "S",
 | 
				
			||||||
 | 
					    65002: "S",
 | 
				
			||||||
 | 
					    65003: "S",
 | 
				
			||||||
 | 
					    65004: "S",
 | 
				
			||||||
 | 
					    65005: "S",
 | 
				
			||||||
 | 
					    65006: "S",
 | 
				
			||||||
 | 
					    65007: "S",
 | 
				
			||||||
 | 
					    65008: "S",
 | 
				
			||||||
 | 
					    65009: "S",
 | 
				
			||||||
 | 
					    65010: "S",
 | 
				
			||||||
 | 
					    65011: "S",
 | 
				
			||||||
 | 
					    65012: "S",
 | 
				
			||||||
 | 
					    65013: "S",
 | 
				
			||||||
 | 
					    65014: "S",
 | 
				
			||||||
 | 
					    65015: "S",
 | 
				
			||||||
 | 
					    65016: "S",
 | 
				
			||||||
 | 
					    65017: "S",
 | 
				
			||||||
 | 
					    65018: "S",
 | 
				
			||||||
 | 
					    65019: "S",
 | 
				
			||||||
 | 
					    65020: "S",
 | 
				
			||||||
 | 
					    65021: "S",
 | 
				
			||||||
 | 
					    65022: "S",
 | 
				
			||||||
 | 
					    65023: "S",
 | 
				
			||||||
 | 
					    65024: "S",
 | 
				
			||||||
 | 
					    65025: "S",
 | 
				
			||||||
 | 
					    65026: "S",
 | 
				
			||||||
 | 
					    65027: "S",
 | 
				
			||||||
 | 
					    65028: "S",
 | 
				
			||||||
 | 
					    65029: "S",
 | 
				
			||||||
 | 
					    65030: "S",
 | 
				
			||||||
 | 
					    65240: "I",
 | 
				
			||||||
 | 
					    65280: "S",
 | 
				
			||||||
 | 
					    65284: "S",
 | 
				
			||||||
 | 
					    65285: "S",
 | 
				
			||||||
 | 
					    65286: "S",
 | 
				
			||||||
 | 
					    65287: "S",
 | 
				
			||||||
 | 
					    65288: "S",
 | 
				
			||||||
 | 
					    65289: "S",
 | 
				
			||||||
 | 
					    65290: "S",
 | 
				
			||||||
 | 
					    65292: "S",
 | 
				
			||||||
 | 
					    65293: "S",
 | 
				
			||||||
 | 
					    65302: "S",
 | 
				
			||||||
 | 
					    65305: "S",
 | 
				
			||||||
 | 
					    65309: "S",
 | 
				
			||||||
 | 
					    65312: "S",
 | 
				
			||||||
 | 
					    65340: "S",
 | 
				
			||||||
 | 
					    65341: "S",
 | 
				
			||||||
 | 
					    65345: "S",
 | 
				
			||||||
 | 
					    65350: "S",
 | 
				
			||||||
 | 
					    65359: "S",
 | 
				
			||||||
 | 
					    65360: "S",
 | 
				
			||||||
 | 
					    65361: "S",
 | 
				
			||||||
 | 
					    65371: "S",
 | 
				
			||||||
 | 
					    65374: "S",
 | 
				
			||||||
 | 
					    65379: "S",
 | 
				
			||||||
 | 
					    65408: "S",
 | 
				
			||||||
 | 
					    65409: "S",
 | 
				
			||||||
 | 
					    65410: "S",
 | 
				
			||||||
 | 
					    65420: "S",
 | 
				
			||||||
 | 
					    65480: "S",
 | 
				
			||||||
 | 
					    126208: "F",
 | 
				
			||||||
 | 
					    126464: "F",
 | 
				
			||||||
 | 
					    126720: "F",
 | 
				
			||||||
 | 
					    126983: "F",
 | 
				
			||||||
 | 
					    126984: "F",
 | 
				
			||||||
 | 
					    126985: "F",
 | 
				
			||||||
 | 
					    126986: "F",
 | 
				
			||||||
 | 
					    126987: "F",
 | 
				
			||||||
 | 
					    126988: "F",
 | 
				
			||||||
 | 
					    126992: "S",
 | 
				
			||||||
 | 
					    126993: "S",
 | 
				
			||||||
 | 
					    126996: "F",
 | 
				
			||||||
 | 
					    126998: "F",
 | 
				
			||||||
 | 
					    127233: "F",
 | 
				
			||||||
 | 
					    127237: "F",
 | 
				
			||||||
 | 
					    127245: "S",
 | 
				
			||||||
 | 
					    127250: "S",
 | 
				
			||||||
 | 
					    127251: "S",
 | 
				
			||||||
 | 
					    127252: "S",
 | 
				
			||||||
 | 
					    127257: "S",
 | 
				
			||||||
 | 
					    127258: "S",
 | 
				
			||||||
 | 
					    127488: "S",
 | 
				
			||||||
 | 
					    127489: "F",
 | 
				
			||||||
 | 
					    127490: "F",
 | 
				
			||||||
 | 
					    127491: "F",
 | 
				
			||||||
 | 
					    127493: "S",
 | 
				
			||||||
 | 
					    127494: "F",
 | 
				
			||||||
 | 
					    127495: "F",
 | 
				
			||||||
 | 
					    127496: "F",
 | 
				
			||||||
 | 
					    127497: "F",
 | 
				
			||||||
 | 
					    127498: "F",
 | 
				
			||||||
 | 
					    127500: "S",
 | 
				
			||||||
 | 
					    127501: "S",
 | 
				
			||||||
 | 
					    127502: "S",
 | 
				
			||||||
 | 
					    127503: "F",
 | 
				
			||||||
 | 
					    127504: "F",
 | 
				
			||||||
 | 
					    127505: "S",
 | 
				
			||||||
 | 
					    127506: "F",
 | 
				
			||||||
 | 
					    127507: "F",
 | 
				
			||||||
 | 
					    127508: "S",
 | 
				
			||||||
 | 
					    127509: "F",
 | 
				
			||||||
 | 
					    127510: "F",
 | 
				
			||||||
 | 
					    127511: "S",
 | 
				
			||||||
 | 
					    127512: "S",
 | 
				
			||||||
 | 
					    127513: "F",
 | 
				
			||||||
 | 
					    127514: "S",
 | 
				
			||||||
 | 
					    127744: "S",
 | 
				
			||||||
 | 
					    127745: "S",
 | 
				
			||||||
 | 
					    127746: "S",
 | 
				
			||||||
 | 
					    127750: "S",
 | 
				
			||||||
 | 
					    127751: "S",
 | 
				
			||||||
 | 
					    128000: "S",
 | 
				
			||||||
 | 
					    128001: "S",
 | 
				
			||||||
 | 
					    128002: "S",
 | 
				
			||||||
 | 
					    128003: "S",
 | 
				
			||||||
 | 
					    128006: "S",
 | 
				
			||||||
 | 
					    128007: "S",
 | 
				
			||||||
 | 
					    128008: "S",
 | 
				
			||||||
 | 
					    128259: "S",
 | 
				
			||||||
 | 
					    128267: "S",
 | 
				
			||||||
 | 
					    128275: "F",
 | 
				
			||||||
 | 
					    128520: "F",
 | 
				
			||||||
 | 
					    128538: "F",
 | 
				
			||||||
 | 
					    128768: "S",
 | 
				
			||||||
 | 
					    128769: "S",
 | 
				
			||||||
 | 
					    128776: "S",
 | 
				
			||||||
 | 
					    128777: "S",
 | 
				
			||||||
 | 
					    128778: "S",
 | 
				
			||||||
 | 
					    128780: "S",
 | 
				
			||||||
 | 
					    129025: "S",
 | 
				
			||||||
 | 
					    129026: "S",
 | 
				
			||||||
 | 
					    129027: "S",
 | 
				
			||||||
 | 
					    129028: "S",
 | 
				
			||||||
 | 
					    129029: "F",
 | 
				
			||||||
 | 
					    129033: "S",
 | 
				
			||||||
 | 
					    129038: "F",
 | 
				
			||||||
 | 
					    129039: "F",
 | 
				
			||||||
 | 
					    129040: "F",
 | 
				
			||||||
 | 
					    129041: "F",
 | 
				
			||||||
 | 
					    129044: "F",
 | 
				
			||||||
 | 
					    129045: "F",
 | 
				
			||||||
 | 
					    129283: "S",
 | 
				
			||||||
 | 
					    129284: "F",
 | 
				
			||||||
 | 
					    129285: "F",
 | 
				
			||||||
 | 
					    129291: "S",
 | 
				
			||||||
 | 
					    129301: "F",
 | 
				
			||||||
 | 
					    129302: "F",
 | 
				
			||||||
 | 
					    129538: "F",
 | 
				
			||||||
 | 
					    129539: "S",
 | 
				
			||||||
 | 
					    129540: "F",
 | 
				
			||||||
 | 
					    129541: "F",
 | 
				
			||||||
 | 
					    129542: "F",
 | 
				
			||||||
 | 
					    129545: "F",
 | 
				
			||||||
 | 
					    129546: "S",
 | 
				
			||||||
 | 
					    129547: "F",
 | 
				
			||||||
 | 
					    129549: "F",
 | 
				
			||||||
 | 
					    129550: "F",
 | 
				
			||||||
 | 
					    129551: "F",
 | 
				
			||||||
 | 
					    129556: "F",
 | 
				
			||||||
 | 
					    129792: "F",
 | 
				
			||||||
 | 
					    129793: "F",
 | 
				
			||||||
 | 
					    129794: "F",
 | 
				
			||||||
 | 
					    129795: "F",
 | 
				
			||||||
 | 
					    129796: "F",
 | 
				
			||||||
 | 
					    129797: "F",
 | 
				
			||||||
 | 
					    129798: "F",
 | 
				
			||||||
 | 
					    129799: "F",
 | 
				
			||||||
 | 
					    129800: "F",
 | 
				
			||||||
 | 
					    129801: "F",
 | 
				
			||||||
 | 
					    129802: "F",
 | 
				
			||||||
 | 
					    129803: "F",
 | 
				
			||||||
 | 
					    129804: "F",
 | 
				
			||||||
 | 
					    129805: "F",
 | 
				
			||||||
 | 
					    129806: "F",
 | 
				
			||||||
 | 
					    129807: "F",
 | 
				
			||||||
 | 
					    129808: "F",
 | 
				
			||||||
 | 
					    129809: "F",
 | 
				
			||||||
 | 
					    129810: "F",
 | 
				
			||||||
 | 
					    130052: "F",
 | 
				
			||||||
 | 
					    130053: "F",
 | 
				
			||||||
 | 
					    130054: "F",
 | 
				
			||||||
 | 
					    130060: "F",
 | 
				
			||||||
 | 
					    130061: "F",
 | 
				
			||||||
 | 
					    130064: "F",
 | 
				
			||||||
 | 
					    130065: "F",
 | 
				
			||||||
 | 
					    130066: "F",
 | 
				
			||||||
 | 
					    130067: "F",
 | 
				
			||||||
 | 
					    130068: "F",
 | 
				
			||||||
 | 
					    130069: "F",
 | 
				
			||||||
 | 
					    130070: "F",
 | 
				
			||||||
 | 
					    130071: "F",
 | 
				
			||||||
 | 
					    130072: "F",
 | 
				
			||||||
 | 
					    130073: "F",
 | 
				
			||||||
 | 
					    130074: "F",
 | 
				
			||||||
 | 
					    130306: "S",
 | 
				
			||||||
 | 
					    130310: "S",
 | 
				
			||||||
 | 
					    130311: "S",
 | 
				
			||||||
 | 
					    130312: "S",
 | 
				
			||||||
 | 
					    130313: "S",
 | 
				
			||||||
 | 
					    130314: "S",
 | 
				
			||||||
 | 
					    130315: "S",
 | 
				
			||||||
 | 
					    130316: "S",
 | 
				
			||||||
 | 
					    130320: "F",
 | 
				
			||||||
 | 
					    130321: "F",
 | 
				
			||||||
 | 
					    130322: "F",
 | 
				
			||||||
 | 
					    130323: "F",
 | 
				
			||||||
 | 
					    130324: "F",
 | 
				
			||||||
 | 
					    130330: "F",
 | 
				
			||||||
 | 
					    130560: "S",
 | 
				
			||||||
 | 
					    130561: "F",
 | 
				
			||||||
 | 
					    130562: "F",
 | 
				
			||||||
 | 
					    130563: "F",
 | 
				
			||||||
 | 
					    130564: "F",
 | 
				
			||||||
 | 
					    130565: "F",
 | 
				
			||||||
 | 
					    130566: "F",
 | 
				
			||||||
 | 
					    130567: "F",
 | 
				
			||||||
 | 
					    130569: "F",
 | 
				
			||||||
 | 
					    130570: "F",
 | 
				
			||||||
 | 
					    130571: "F",
 | 
				
			||||||
 | 
					    130572: "F",
 | 
				
			||||||
 | 
					    130573: "F",
 | 
				
			||||||
 | 
					    130574: "F",
 | 
				
			||||||
 | 
					    130576: "S",
 | 
				
			||||||
 | 
					    130577: "F",
 | 
				
			||||||
 | 
					    130578: "F",
 | 
				
			||||||
 | 
					    130579: "S",
 | 
				
			||||||
 | 
					    130580: "F",
 | 
				
			||||||
 | 
					    130581: "F",
 | 
				
			||||||
 | 
					    130582: "S",
 | 
				
			||||||
 | 
					    130583: "F",
 | 
				
			||||||
 | 
					    130584: "F",
 | 
				
			||||||
 | 
					    130585: "S",
 | 
				
			||||||
 | 
					    130586: "F",
 | 
				
			||||||
 | 
					    130816: "F",
 | 
				
			||||||
 | 
					    130817: "F",
 | 
				
			||||||
 | 
					    130818: "F",
 | 
				
			||||||
 | 
					    130819: "F",
 | 
				
			||||||
 | 
					    130820: "F",
 | 
				
			||||||
 | 
					    130821: "F",
 | 
				
			||||||
 | 
					    130822: "F",
 | 
				
			||||||
 | 
					    130823: "F",
 | 
				
			||||||
 | 
					    130824: "F",
 | 
				
			||||||
 | 
					    130825: "F",
 | 
				
			||||||
 | 
					    130827: "F",
 | 
				
			||||||
 | 
					    130828: "F",
 | 
				
			||||||
 | 
					    130831: "F",
 | 
				
			||||||
 | 
					    130832: "F",
 | 
				
			||||||
 | 
					    130833: "F",
 | 
				
			||||||
 | 
					    130834: "F",
 | 
				
			||||||
 | 
					    130835: "F",
 | 
				
			||||||
 | 
					    130836: "F",
 | 
				
			||||||
 | 
					    130837: "F",
 | 
				
			||||||
 | 
					    130838: "F",
 | 
				
			||||||
 | 
					    130839: "F",
 | 
				
			||||||
 | 
					    130840: "F",
 | 
				
			||||||
 | 
					    130842: "F",
 | 
				
			||||||
 | 
					    130843: "F",
 | 
				
			||||||
 | 
					    130845: "F",
 | 
				
			||||||
 | 
					    130846: "F",
 | 
				
			||||||
 | 
					    130847: "F",
 | 
				
			||||||
 | 
					    130850: "F",
 | 
				
			||||||
 | 
					    130851: "F",
 | 
				
			||||||
 | 
					    130856: "F",
 | 
				
			||||||
 | 
					    130860: "F",
 | 
				
			||||||
 | 
					    130880: "F",
 | 
				
			||||||
 | 
					    130881: "F",
 | 
				
			||||||
 | 
					    130944: "F"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,27 @@
 | 
				
			||||||
 | 
					from heapdict import heapdict
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
 | 
					import threading
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class FrameQueue():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self):
 | 
				
			||||||
 | 
					        self.heap = heapdict()
 | 
				
			||||||
 | 
					        self.age_interval = 5    # seconds
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def push(self, frame, priority):
 | 
				
			||||||
 | 
					        timestamp = time.time()  # microsecond resolution
 | 
				
			||||||
 | 
					        self.heap[timestamp] = (priority, timestamp, frame)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def pop(self):
 | 
				
			||||||
 | 
					        p, f = self.heap.popitem()
 | 
				
			||||||
 | 
					        self.age()
 | 
				
			||||||
 | 
					        return f[2]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def age(self):
 | 
				
			||||||
 | 
					        current_time = time.time()
 | 
				
			||||||
 | 
					        for key in list(self.heap.keys()):
 | 
				
			||||||
 | 
					            if current_time - key > self.age_interval:
 | 
				
			||||||
 | 
					                self.heap[key][0] -= 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def is_empty(self): 
 | 
				
			||||||
 | 
					        return len(self.heap) == 0
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,61 @@
 | 
				
			||||||
 | 
					'''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Momentan nur Idee
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Verarbeiten von Frames
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Nur Empfang von Paketen / Frames
 | 
				
			||||||
 | 
					- single
 | 
				
			||||||
 | 
					- fast
 | 
				
			||||||
 | 
					- transport
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fast packets können parallel von mehreren Quellen verarbeitet werden
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					'''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import math
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class FpReceiver():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self):
 | 
				
			||||||
 | 
					        self.sc0 = {}
 | 
				
			||||||
 | 
					        self.nf = {}
 | 
				
			||||||
 | 
					        self.frame = {}
 | 
				
			||||||
 | 
					        self.wip = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def receive(self, data, source)
 | 
				
			||||||
 | 
					        '''
 | 
				
			||||||
 | 
					        Liefert True wenn Paket komplett empfangen wurde
 | 
				
			||||||
 | 
					        '''
 | 
				
			||||||
 | 
					        sc = (data[0] & 0xf0) >> 5
 | 
				
			||||||
 | 
					        fc = data[0] & 0x1f
 | 
				
			||||||
 | 
					        if not source in self.wip:
 | 
				
			||||||
 | 
					            # Erster Frame
 | 
				
			||||||
 | 
					            if fc != 0:
 | 
				
			||||||
 | 
					                # unbekannte Herkunft und kein Startframe
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					            self.sc0[source] = sc
 | 
				
			||||||
 | 
					            datalen = data[1]
 | 
				
			||||||
 | 
					            self.nf[source] = math.ceil((datalen - 6) / 7) + 1 # Anzahl Frames
 | 
				
			||||||
 | 
					            self.frame[source] = {fc : data[2:]} # Ersten Frame merken
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            # Folgeframes
 | 
				
			||||||
 | 
					            if (sc == self.sc0[source]):
 | 
				
			||||||
 | 
					                # TODO prüfe ob der Framecounter fc schon vorgekommen ist
 | 
				
			||||||
 | 
					                if not fc in self.frame[source]:
 | 
				
			||||||
 | 
					                    self.frame[source][fc] = data[1:8]
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    # TODO Fehler im Fast-Packet: doppelter fc!
 | 
				
			||||||
 | 
					                    raise('Frame error: duplicate fc')
 | 
				
			||||||
 | 
					        if len(self.frame[source]) == self.nf[source]:
 | 
				
			||||||
 | 
					            self.wip.remove(source)
 | 
				
			||||||
 | 
					            return True
 | 
				
			||||||
 | 
					        return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def getPacket(self, source)
 | 
				
			||||||
 | 
					        # TODO Frames in der richtigen reihenfolge zusammensetzen
 | 
				
			||||||
 | 
					        packet = bytearray()
 | 
				
			||||||
 | 
					        for frame in sorted(self.frame.items()):
 | 
				
			||||||
 | 
					            print(frame)
 | 
				
			||||||
 | 
					            #packet.extend()
 | 
				
			||||||
 | 
					        return packet
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,17 @@
 | 
				
			||||||
 | 
					'''
 | 
				
			||||||
 | 
					Routen und Wegepunkte
 | 
				
			||||||
 | 
					'''
 | 
				
			||||||
 | 
					class Waypoint():
 | 
				
			||||||
 | 
					    def __init__(self, number, name):
 | 
				
			||||||
 | 
					        self.number = number
 | 
				
			||||||
 | 
					        self.name = name
 | 
				
			||||||
 | 
					        self.lat = None
 | 
				
			||||||
 | 
					        self.lon = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Route():
 | 
				
			||||||
 | 
					    def __init__(self, number, name)
 | 
				
			||||||
 | 
					        self.number = number
 | 
				
			||||||
 | 
					        self.name = name
 | 
				
			||||||
 | 
					        self.wps = dict()
 | 
				
			||||||
 | 
					    def getActiveWP(self):
 | 
				
			||||||
 | 
					        return None
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,179 @@
 | 
				
			||||||
 | 
					'''
 | 
				
			||||||
 | 
					Lange Beschreibung der Daten, mehrsprachig
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					'''
 | 
				
			||||||
 | 
					desc = {
 | 
				
			||||||
 | 
					    "ALT": {
 | 
				
			||||||
 | 
					        "en": "Altitude",
 | 
				
			||||||
 | 
					        "de": "Höhe über Grund"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "AWA": {
 | 
				
			||||||
 | 
					        "en": "Apparant Wind Angle",
 | 
				
			||||||
 | 
					        "de": "Scheinbare Windrichtung"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "AWS": {
 | 
				
			||||||
 | 
					        "en": "Apparant Wind Speed",
 | 
				
			||||||
 | 
					        "de": "Scheinbare Windgeschwindigkeit"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "BTW": {
 | 
				
			||||||
 | 
					        "en": "Bearing To Waypoint",
 | 
				
			||||||
 | 
					        "de": "Kurs zum nächsten Wegepunkt"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "COG": {
 | 
				
			||||||
 | 
					        "en": "Course Over Ground",
 | 
				
			||||||
 | 
					        "de": "Kurs über Grund"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "DBS": {
 | 
				
			||||||
 | 
					        "en": "Depth Below Surface",
 | 
				
			||||||
 | 
					        "de": "Tiefe unter Wasseroberfläche"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "DBT": {
 | 
				
			||||||
 | 
					        "en": "Depth Below Transducer",
 | 
				
			||||||
 | 
					        "de": "Tiefe unter Sensor"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "DEV": {
 | 
				
			||||||
 | 
					        "en": "Deviation",
 | 
				
			||||||
 | 
					        "de": "Kursabweichung"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "DTW": {
 | 
				
			||||||
 | 
					        "en": "Distance To Waypoint",
 | 
				
			||||||
 | 
					        "de": "Entfernung zum nächsten Wegepunkt"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "GPSD": {
 | 
				
			||||||
 | 
					        "en": "GPS Date",
 | 
				
			||||||
 | 
					        "de": "GPS-Datum"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "GPST": {
 | 
				
			||||||
 | 
					        "en": "GPS Time",
 | 
				
			||||||
 | 
					        "de": "GPS-Zeit"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "HDM": {
 | 
				
			||||||
 | 
					        "en": "Magnetic Heading",
 | 
				
			||||||
 | 
					        "de": "Magnetischer Kurs"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "HDT": {
 | 
				
			||||||
 | 
					        "en": "Heading",
 | 
				
			||||||
 | 
					        "de": "Wahrer rechtweisender Kurs"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "HDOP": {
 | 
				
			||||||
 | 
					        "en": "Horizontal Dilation Of Position",
 | 
				
			||||||
 | 
					        "de": "Positionsgenauigkeit in der Horizontalen"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "LAT": {
 | 
				
			||||||
 | 
					        "en": "Latitude",
 | 
				
			||||||
 | 
					        "de": "Geographische Breite"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "LON": {
 | 
				
			||||||
 | 
					        "en": "Longitude",
 | 
				
			||||||
 | 
					        "de": "Geographische Länge"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "Log": {
 | 
				
			||||||
 | 
					        "en": "Logged distance",
 | 
				
			||||||
 | 
					        "de": "Entfernung"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "MaxAws": {
 | 
				
			||||||
 | 
					        "en": "Maximum Apperant Wind Speed",
 | 
				
			||||||
 | 
					        "de": "Maximum der relativen Windgeschwindigkeit"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "MaxTws": {
 | 
				
			||||||
 | 
					        "en": "Maximum True Wind Speed",
 | 
				
			||||||
 | 
					        "de": "Maximum der wahren Windgeschwindigkeit"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "PDOP": {
 | 
				
			||||||
 | 
					        "en": "Position dilation",
 | 
				
			||||||
 | 
					        "de": "Positionsgenauigkeit im Raum"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "PRPOS": {
 | 
				
			||||||
 | 
					        "en": "Secondary Rudder Position",
 | 
				
			||||||
 | 
					        "de": "Auslenkung Sekundärruder"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "ROT": {
 | 
				
			||||||
 | 
					        "en": "Rotation",
 | 
				
			||||||
 | 
					        "de": "Drehrate"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "RPOS": {
 | 
				
			||||||
 | 
					        "en": "Rudder Position",
 | 
				
			||||||
 | 
					        "de": "Auslenkung Ruder"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "SOG": {
 | 
				
			||||||
 | 
					        "en": "Speed Over Ground",
 | 
				
			||||||
 | 
					        "de": "Geschwindigkeit über Grund"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "STW": {
 | 
				
			||||||
 | 
					        "en": "Speed Through Water",
 | 
				
			||||||
 | 
					        "de": "Geschwindigkeit durch das Wasser"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "SatInfo": {
 | 
				
			||||||
 | 
					        "en": "Satellit Info",
 | 
				
			||||||
 | 
					        "de": "Anzahl der sichtbaren Satelliten"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "TWD": {
 | 
				
			||||||
 | 
					        "en": "True Wind Direction",
 | 
				
			||||||
 | 
					        "de": "Wahre Windrichtung"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "TWS": {
 | 
				
			||||||
 | 
					        "en": "True Wind Speed",
 | 
				
			||||||
 | 
					        "de": "Wahre Windgeschwindigkeit"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "TZ": {
 | 
				
			||||||
 | 
					        "en": "Timezone",
 | 
				
			||||||
 | 
					        "de": "Zeitzone"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "TripLog": {
 | 
				
			||||||
 | 
					        "en": "Trip Log",
 | 
				
			||||||
 | 
					        "de": "Tages-Entfernungszähler"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "VAR": {
 | 
				
			||||||
 | 
					        "en": "Course Variation",
 | 
				
			||||||
 | 
					        "de": "Abweichung vom Sollkurs"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "VDOP": {
 | 
				
			||||||
 | 
					        "en": "Vertical Dilation Of Position",
 | 
				
			||||||
 | 
					        "de": "Positionsgenauigkeit in der Vertikalen"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "WPLat": {
 | 
				
			||||||
 | 
					        "en": "Waypoint Latitude",
 | 
				
			||||||
 | 
					        "de": "Geo. Breite des Wegpunktes"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "WPLon": {
 | 
				
			||||||
 | 
					        "en": "Waypoint Longitude",
 | 
				
			||||||
 | 
					        "de": "Geo. Länge des Wegpunktes"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "WTemp": {
 | 
				
			||||||
 | 
					        "en": "Water Temperature",
 | 
				
			||||||
 | 
					        "de": "Wassertemperatur"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "XTE": {
 | 
				
			||||||
 | 
					        "en": "Cross Track Error",
 | 
				
			||||||
 | 
					        "de": "Kursfehler"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Sonderwerte
 | 
				
			||||||
 | 
					    "xdrHum": {
 | 
				
			||||||
 | 
					        "en": "Humidity",
 | 
				
			||||||
 | 
					        "de": "Luftfeuchte"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "xdrPress": {
 | 
				
			||||||
 | 
					        "en": "Pressure",
 | 
				
			||||||
 | 
					        "de": "Luftdruck"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "xdrRotK": {
 | 
				
			||||||
 | 
					        "en": "Keel Rotation",
 | 
				
			||||||
 | 
					        "de": "Kielrotation"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "tdrTemp": {
 | 
				
			||||||
 | 
					        "en": "Temperature",
 | 
				
			||||||
 | 
					        "de": "Temperatur"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "xdrVBat": {
 | 
				
			||||||
 | 
					        "en": "Battery Voltage",
 | 
				
			||||||
 | 
					        "de": "Batteriespannung"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # WIP
 | 
				
			||||||
 | 
					    "xdrCurr": {
 | 
				
			||||||
 | 
					        "en": "Current",
 | 
				
			||||||
 | 
					        "de": "Stromstärke"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
		Reference in New Issue