diff --git a/README b/README index 51c94b3..764d7a2 100644 --- a/README +++ b/README @@ -17,7 +17,7 @@ Zusatzhardware: - NMEA2000 Interface - PiCAN-M (hiermit wird entwickelt) - Waveshare RS485 CAN HAT (ungetestet) -- BME280-Sensor +- BME280-Sensor über I2C - GPS über USB/seriell angeschlossen Zusatzsoftware: @@ -35,7 +35,7 @@ Für BME280 - smbus2 - bme280 -Zur Steuerung des Geräts sind 6 Tasten vorhanen. Numeriert von 1 bis 6 von +Zur Steuerung des Geräts sind 6 Tasten vorhanden. Numeriert von 1 bis 6 von links nach rechts. Die Tasten können angeklickt werden und führen dann direkt eine von der jeweiligen Seite abhängige Funktion aus. Die jeweilige Funktion wird durch ein Symbol oberhalb der Taste dargestellt. @@ -49,7 +49,7 @@ Folgende Wischfunktionen sind implementiert: 1. Programmende durch die Wischfunktion "2" -> "1" 2. Tastensperre an: "6" -> "1" 3. Tastensperre aus: "1" -> "6" - 4. Systemseite: "5" -> "6" + 4. Systemseite: "4" -> "5" Routen und Wegepunkte können von OpenCPN empfangen werden. Dazu muß eine passende serielle Schnittstelle für den NMEA0183-Ausgang definiert werden. diff --git a/nmea2000/__init__.py b/nmea2000/__init__.py deleted file mode 100644 index 16ba3d3..0000000 --- a/nmea2000/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .device import Device -from .boatdata import BoatData -from .hbuffer import History, HistoryBuffer diff --git a/nmea2000/boatdata.py b/nmea2000/boatdata.py deleted file mode 100644 index 74b5254..0000000 --- a/nmea2000/boatdata.py +++ /dev/null @@ -1,574 +0,0 @@ -''' -!!! 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 diff --git a/nmea2000/device.py b/nmea2000/device.py deleted file mode 100644 index 9c9dc00..0000000 --- a/nmea2000/device.py +++ /dev/null @@ -1,120 +0,0 @@ -""" -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 Address-Claim gefüllt - 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 - """ - data = bytearray() - data.extend(struct.pack(' 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 (+/- ) - """ - 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() diff --git a/nmea2000/lookup.py b/nmea2000/lookup.py deleted file mode 100644 index 807fdf4..0000000 --- a/nmea2000/lookup.py +++ /dev/null @@ -1,566 +0,0 @@ -# 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", - 135: "NMEA 0183 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", - 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: { # NEW? WIP - }, - 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" -} diff --git a/nmea2000/mavg.py b/nmea2000/mavg.py deleted file mode 100644 index 9f5327f..0000000 --- a/nmea2000/mavg.py +++ /dev/null @@ -1,56 +0,0 @@ -""" -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 diff --git a/nmea2000/parser.py b/nmea2000/parser.py deleted file mode 100644 index 59af91c..0000000 --- a/nmea2000/parser.py +++ /dev/null @@ -1,262 +0,0 @@ -''' - -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(' 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('> 4 - boatdata.tank[instance].level = struct.unpack_from('> 6 # 0: true, 1: magnetic, 2: error - cog = struct.unpack_from('> 4 - navterm = buf[1] & 0x03 - xte = struct.unpack_from(' self.age_interval: - self.heap[key][0] -= 1 - - def is_empty(self): - return len(self.heap) == 0 diff --git a/nmea2000/receiver.py b/nmea2000/receiver.py deleted file mode 100644 index 4108533..0000000 --- a/nmea2000/receiver.py +++ /dev/null @@ -1,61 +0,0 @@ -''' - -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 diff --git a/nmea2000/routing.py b/nmea2000/routing.py deleted file mode 100644 index 773fb25..0000000 --- a/nmea2000/routing.py +++ /dev/null @@ -1,17 +0,0 @@ -''' -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 diff --git a/nmea2000/valdesc.py b/nmea2000/valdesc.py deleted file mode 100644 index fced0e4..0000000 --- a/nmea2000/valdesc.py +++ /dev/null @@ -1,179 +0,0 @@ -''' -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" - } - -} diff --git a/obp60.py b/obp60.py index 971ade7..5805e76 100755 --- a/obp60.py +++ b/obp60.py @@ -1,8 +1,12 @@ #!/usr/bin/env python -''' +""" Virtuelles Multifunktionsgerät +NMEA2000 + deviceclass: 120 - Display + devicefunction: 130 - Display + Benötigte Pakete: python3-cairo python3-gi python3-gi-cairo gir1.2-rsvg-2.0 @@ -38,10 +42,11 @@ Buttonlayout: Button 6 ist reserviert für Beleuchtung ILUM -Neu +Standardbelegung der Tasten +Button 1: Mode Button 2: Abbruch Button 3: zurück -Button 4: hoch +Button 4: weiter Button 5: Ok/Menu Verlagerung der Seitenanzeige in den Titel als Ziffer in Klammern @@ -49,12 +54,12 @@ Verlagerung der Seitenanzeige in den Titel als Ziffer in Klammern Vergleich zum Garmin GMI20: [ Zurück ] [ Hoch ] [ Menü ] [ Runter ] [ on/off ] +Vergleich zum Raymarine I70s: +[ ON/ILUM ] [ Hoch ] [ Runter ] [ MENU ] Button 1 wird für AVG verwendet auf mehreren Seiten Button 5 wird für Trend TRND verwendet -Aber sowas von WIP - Änderungsprotokoll ================== @@ -65,7 +70,7 @@ Version Datum Änderung(en) von 0.2 2024-12-24 Veräffentlichung als Git-Repository tho -''' +""" import os import sys @@ -429,14 +434,15 @@ class Frontend(Gtk.Window): self.curpage = self.pages[self.pageno] elif selected == 5: # Ok/Menü - self.curpage.handle_key(5) - elif selected == 6: - if self.button_clicked == 6: - # Backlight on/off - self.curpage.backlight = not self.curpage.backlight - elif self.button_clicked == 5: + if self.button_clicked == 4: # Umschalten zur Systemseite self.curpage = self.pages[0] + else: + self.curpage.handle_key(5) + elif selected == 6: + if not self.curpage.handle_key(6): + # Backlight on/off + self.curpage.backlight = not self.curpage.backlight return True def on_destroy(self, widget): @@ -551,4 +557,5 @@ if __name__ == "__main__": shutdown = True t_rxd_n2k.join() t_rxd_0183.join() + t_data.join() print("Another fine product of the Sirius Cybernetics Corporation.")