''' !!! 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