NMEA2000-Code entfernt. Wird über eigenständige Bibliothek eingebunden.
This commit is contained in:
parent
bdbd168123
commit
81836bc5f1
6
README
6
README
|
@ -17,7 +17,7 @@ Zusatzhardware:
|
||||||
- NMEA2000 Interface
|
- NMEA2000 Interface
|
||||||
- PiCAN-M (hiermit wird entwickelt)
|
- PiCAN-M (hiermit wird entwickelt)
|
||||||
- Waveshare RS485 CAN HAT (ungetestet)
|
- Waveshare RS485 CAN HAT (ungetestet)
|
||||||
- BME280-Sensor
|
- BME280-Sensor über I2C
|
||||||
- GPS über USB/seriell angeschlossen
|
- GPS über USB/seriell angeschlossen
|
||||||
|
|
||||||
Zusatzsoftware:
|
Zusatzsoftware:
|
||||||
|
@ -35,7 +35,7 @@ Für BME280
|
||||||
- smbus2
|
- smbus2
|
||||||
- bme280
|
- 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
|
links nach rechts. Die Tasten können angeklickt werden und führen dann direkt
|
||||||
eine von der jeweiligen Seite abhängige Funktion aus.
|
eine von der jeweiligen Seite abhängige Funktion aus.
|
||||||
Die jeweilige Funktion wird durch ein Symbol oberhalb der Taste dargestellt.
|
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"
|
1. Programmende durch die Wischfunktion "2" -> "1"
|
||||||
2. Tastensperre an: "6" -> "1"
|
2. Tastensperre an: "6" -> "1"
|
||||||
3. Tastensperre aus: "1" -> "6"
|
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
|
Routen und Wegepunkte können von OpenCPN empfangen werden. Dazu muß eine
|
||||||
passende serielle Schnittstelle für den NMEA0183-Ausgang definiert werden.
|
passende serielle Schnittstelle für den NMEA0183-Ausgang definiert werden.
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
from .device import Device
|
|
||||||
from .boatdata import BoatData
|
|
||||||
from .hbuffer import History, HistoryBuffer
|
|
|
@ -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
|
|
|
@ -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('<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))
|
|
||||||
return data
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
intNAME = int.from_bytes(self.getNAME())
|
|
||||||
out = f"Device: {self.address} : '{self.product}'\n"
|
|
||||||
out += " NAME: {} ({})\n".format(self.getNAME(), intNAME)
|
|
||||||
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
|
|
|
@ -1,17 +0,0 @@
|
||||||
|
|
||||||
'''
|
|
||||||
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)
|
|
|
@ -1,300 +0,0 @@
|
||||||
"""
|
|
||||||
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()
|
|
|
@ -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"
|
|
||||||
}
|
|
|
@ -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
|
|
|
@ -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('<L', buf, 4)[0] * 0.0001
|
|
||||||
print(f" source={source}, date={dval}, secs={secs}, ts={lookup.timesource[src]}")
|
|
||||||
|
|
||||||
def parse_126993(buf, device):
|
|
||||||
# Heartbeat
|
|
||||||
print(f"Heartbeat from {device.address}")
|
|
||||||
print(buf)
|
|
||||||
|
|
||||||
def parse_126996(buf, device):
|
|
||||||
# Product information
|
|
||||||
n2kvers = (buf[0] + buf[1] * 256) / 1000
|
|
||||||
prodcode = buf[2] + buf[3] * 256
|
|
||||||
modelid = buf[4:36] # 256bit modelid ascii text
|
|
||||||
softvers = buf[36:68] # 256bit software version code ascii text
|
|
||||||
modelvers = buf[68:100] # 256bit model version ascii text
|
|
||||||
serial = buf[100:132] # 256bit model serial code ascii text
|
|
||||||
# Füllzeichen entfernen. 0x20, 0xff, '@' für AIS
|
|
||||||
# Die N2K-Bibliothek von ttlappalainen liefert 0xff
|
|
||||||
modelid = modelid.rstrip(b'\xff')
|
|
||||||
softvers = softvers.rstrip(b'\xff')
|
|
||||||
modelvers = modelvers.rstrip(b'\xff')
|
|
||||||
serial = serial.rstrip(b'\xff')
|
|
||||||
# Übertragen in die Gerätedaten
|
|
||||||
device.n2kvers = n2kvers
|
|
||||||
device.productcode = prodcode
|
|
||||||
device.modelvers = modelvers.decode('ascii').rstrip()
|
|
||||||
device.softvers = softvers.decode('ascii').rstrip()
|
|
||||||
device.product = modelid.decode('ascii').rstrip()
|
|
||||||
device.serial = serial.decode('ascii').rstrip()
|
|
||||||
device.certlevel = buf[132]
|
|
||||||
device.loadequiv = buf[133]
|
|
||||||
|
|
||||||
def parse_126998(buf, source, device):
|
|
||||||
# Configuration information
|
|
||||||
# Installation Description 1
|
|
||||||
txtlen = buf[0]
|
|
||||||
if txtlen > 2:
|
|
||||||
device.instdesc1 = buf[2:txtlen].decode('ascii')
|
|
||||||
p = txtlen
|
|
||||||
else:
|
|
||||||
device.instdesc1 = ""
|
|
||||||
p = 2
|
|
||||||
# Installation Description 2
|
|
||||||
txtlen = buf[p]
|
|
||||||
if txtlen > 2:
|
|
||||||
device.instdesc2 = buf[p+2:p+txtlen].decode('ascii')
|
|
||||||
p += txtlen
|
|
||||||
else:
|
|
||||||
device.instdesc2 = ""
|
|
||||||
p += 2
|
|
||||||
# Manufacturer Info
|
|
||||||
txtlen = buf[p]
|
|
||||||
if txtlen > 2:
|
|
||||||
device.manufinfo = buf[p+2:p+txtlen].decode('ascii')
|
|
||||||
else:
|
|
||||||
device.manufinfo = ""
|
|
||||||
|
|
||||||
def parse_127257(buf, boatdata):
|
|
||||||
# Attitude
|
|
||||||
sid = buf[0]
|
|
||||||
yaw = struct.unpack_from('<h', buf, 1)[0] * 0.0001
|
|
||||||
pitch = struct.unpack_from('<h', buf, 3)[0] * 0.0001
|
|
||||||
roll = struct.unpack_from('<h', buf, 5)[0] * 0.0001
|
|
||||||
boatdata.setValue("YAW", yaw)
|
|
||||||
boatdata.setValue("PTCH", pitch)
|
|
||||||
boatdata.setValue("ROLL", roll)
|
|
||||||
|
|
||||||
def parse_127505(buf, boatdata):
|
|
||||||
# Fluid Level
|
|
||||||
instance = buf[0] & 0x0f
|
|
||||||
if instance in boatdata.tank:
|
|
||||||
boatdata.tank[instance].fluidtype = buf[0] >> 4
|
|
||||||
boatdata.tank[instance].level = struct.unpack_from('<H', buf, 1)[0] * 0.004
|
|
||||||
boatdata.tank[instance].capacity = struct.unpack_from('<L', buf, 3)[0] * 0.1
|
|
||||||
print(boatdata.tank[instance])
|
|
||||||
else:
|
|
||||||
print(f"Tank {instance} not defined!")
|
|
||||||
|
|
||||||
def parse_127508(buf, boatdata):
|
|
||||||
# Battery status
|
|
||||||
instance = buf[0]
|
|
||||||
voltage = (buf[2] * 256 + buf[1]) * 0.01
|
|
||||||
current = (buf[4] * 256 + buf[3]) * 0.1
|
|
||||||
temp = (buf[6] * 256 + buf[5]) * 0.01 - 273.15
|
|
||||||
sid = buf[7]
|
|
||||||
|
|
||||||
def parse_129025(buf, boatdata):
|
|
||||||
# Position, Rapid Update
|
|
||||||
lat = struct.unpack_from('<l', buf, 0)[0] * 1e-07
|
|
||||||
lon = struct.unpack_from('<l', buf, 4)[0] * 1e-07
|
|
||||||
boatdata.setValue("LAT", lat)
|
|
||||||
boatdata.setValue("LON", lon)
|
|
||||||
|
|
||||||
def parse_129026(buf, boatdata):
|
|
||||||
# COG & SOG, Rapid Update
|
|
||||||
sid = buf[0]
|
|
||||||
cogref = buf[1] >> 6 # 0: true, 1: magnetic, 2: error
|
|
||||||
cog = struct.unpack_from('<H', buf, 2)[0] * 0.0001 # rad
|
|
||||||
sog = struct.unpack_from('<H', buf, 4)[0] * 0.01 # m/s
|
|
||||||
# 2 Byte reserved
|
|
||||||
boatdata.setValue("COG", cog)
|
|
||||||
boatdata.setValue("SOG", sog)
|
|
||||||
|
|
||||||
def parse_129027(buf, boatdata):
|
|
||||||
# TODO
|
|
||||||
# Position Delta Rapid Update
|
|
||||||
sid = buf[0]
|
|
||||||
dt = struct.unpack_from('<H', buf, 1)[0],
|
|
||||||
dlat = struct.unpack_from('<h', buf, 3)[0]
|
|
||||||
dlon = struct.unpack_from('<h', buf, 5)[0]
|
|
||||||
# 1 Byte reserved
|
|
||||||
|
|
||||||
def parse_129029(buf, boatdata):
|
|
||||||
# GNSS Position Data
|
|
||||||
'''
|
|
||||||
1 sid
|
|
||||||
2 date
|
|
||||||
4 time seconds since midn
|
|
||||||
8 lat
|
|
||||||
8 lon
|
|
||||||
8 alt
|
|
||||||
4 bit gnss type
|
|
||||||
{"name": "GPS", "value": 0},
|
|
||||||
{"name": "GLONASS", "value": 1},
|
|
||||||
{"name": "GPS+GLONASS", "value": 2},
|
|
||||||
{"name": "GPS+SBAS/WAAS", "value": 3},
|
|
||||||
{"name": "GPS+SBAS/WAAS+GLONASS", "value": 4},
|
|
||||||
{"name": "Chayka", "value": 5},
|
|
||||||
{"name": "integrated", "value": 6},
|
|
||||||
{"name": "surveyed", "value": 7},
|
|
||||||
{"name": "Galileo", "value": 8}]},
|
|
||||||
4bit method
|
|
||||||
{"name": "no GNSS", "value": 0},
|
|
||||||
{"name": "GNSS fix", "value": 1},
|
|
||||||
{"name": "DGNSS fix", "value": 2},
|
|
||||||
{"name": "Precise GNSS", "value": 3},
|
|
||||||
{"name": "RTK Fixed Integer", "value": 4},
|
|
||||||
{"name": "RTK float", "value": 5},
|
|
||||||
{"name": "Estimated (DR) mode", "value": 6},
|
|
||||||
{"name": "Manual Input", "value": 7},
|
|
||||||
{"name": "Simulate mode", "value": 8}]},
|
|
||||||
|
|
||||||
2bit integrity
|
|
||||||
{"name": "No integrity checking", "value": 0},
|
|
||||||
{"name": "Safe", "value": 1},
|
|
||||||
{"name": "Caution", "value": 2}]},
|
|
||||||
bit reserved
|
|
||||||
|
|
||||||
1 byte uint numberOfSvs Number of satellites used in solution
|
|
||||||
2byte hdop
|
|
||||||
2 byte tdop
|
|
||||||
4 byte geoidalSeparation
|
|
||||||
1 byte Number of reference stations
|
|
||||||
|
|
||||||
4bit referenceStationType
|
|
||||||
12bit referenceStationId
|
|
||||||
2byte sageOfDgnssCorrections
|
|
||||||
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
def parse_129033(buf, boatdata):
|
|
||||||
# Time & Date
|
|
||||||
gpsdate = struct.unpack_from('<H', buf, 0)[0] # days
|
|
||||||
gpstime = struct.unpack_from('<L', buf, 2)[0] # seconds since midnight
|
|
||||||
offset = struct.unpack_from('<h', buf, 6)[0] # local offset
|
|
||||||
# TODO
|
|
||||||
|
|
||||||
def parse_129283(buf, boatdata):
|
|
||||||
# TODO Cross Track Error XTE
|
|
||||||
sid = buf[0]
|
|
||||||
mode = buf[1] >> 4
|
|
||||||
navterm = buf[1] & 0x03
|
|
||||||
xte = struct.unpack_from('<l', buf, 2)[0] * 0.01 # m
|
|
||||||
# 2 Byte reserved
|
|
||||||
boatdata.setValue("XTE", xte)
|
|
||||||
|
|
||||||
def parse_129540(buf, boatdata):
|
|
||||||
#sid = buf[0]
|
|
||||||
#rrmode = buf[1]
|
|
||||||
nsats = buf[2]
|
|
||||||
# Datensatz je Sat Länge: 12 Byte
|
|
||||||
smax = nsats * 12
|
|
||||||
for s in range(0, smax, 12):
|
|
||||||
prn = buf[3 + s]
|
|
||||||
elevation = struct.unpack_from('<h', buf, s+4)[0] * 0.0001
|
|
||||||
azimuth = struct.unpack_from('<H', buf, s+6)[0] * 0.0001
|
|
||||||
snr = struct.unpack_from('<H', buf, s+8)[0] * 0.01
|
|
||||||
rres = struct.unpack_from('<l', buf, s+10)[0]
|
|
||||||
status = buf[s+14] & 0x0f
|
|
||||||
boatdata.updateSatellite(prn, elevation, azimuth, snr, rres, status)
|
|
||||||
|
|
||||||
def parse_130312(buf, boatdata):
|
|
||||||
# Temperature
|
|
||||||
src = buf[2] # lookup "temperature" (0 .. 15)
|
|
||||||
val = (buf[4] * 256 + buf[3]) * 0.01 # Kelvin
|
|
||||||
if instance == 0 and src == 1:
|
|
||||||
boatdata.setValue("xdrTemp", val)
|
|
||||||
elif instance in boatdata.temp:
|
|
||||||
boatdata.temp[instance].value = val
|
|
||||||
|
|
||||||
def parse_130314(buf, boatdata):
|
|
||||||
# Pressure
|
|
||||||
sid = buf[0]
|
|
||||||
instance = buf[1]
|
|
||||||
src = buf[2] # lookup "pressure"
|
|
||||||
pressure = struct.unpack_from('<L', buf, s+3)[0] * 0.1 # Pa
|
|
||||||
if instance == 0 and src == 0:
|
|
||||||
# Generischer Luftdruckwert
|
|
||||||
boatdata.setValue("xdrPress", pressure)
|
|
||||||
if instance in boatdata.press:
|
|
||||||
# Verschiedene weitere Drücke
|
|
||||||
# TODO sensortype "src"
|
|
||||||
boatdata.press[instance].value = pressure
|
|
||||||
|
|
||||||
def parse_130316(buf, boatdata):
|
|
||||||
# Temperature, extended range
|
|
||||||
sid = buf[0]
|
|
||||||
instance = buf[1]
|
|
||||||
src = buf[2] # lookup "temperature" (0 .. 15)
|
|
||||||
val = ((buf[5] << 16) | (buf[4] << 8) | buf[3]) * 0.001
|
|
||||||
# TODO save in global temp data
|
|
||||||
# Konflikt mit 130312?
|
|
||||||
#if instance == 0 and src == 2:
|
|
||||||
# boatdata.setValue("xdrTemp", val)
|
|
||||||
# save in engine data
|
|
||||||
if src == 14 and instance in boatdata.engine:
|
|
||||||
boatdata.engine[instance].exhaust_temp = val
|
|
|
@ -1,275 +0,0 @@
|
||||||
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"
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
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
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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"
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
31
obp60.py
31
obp60.py
|
@ -1,8 +1,12 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
'''
|
"""
|
||||||
Virtuelles Multifunktionsgerät
|
Virtuelles Multifunktionsgerät
|
||||||
|
|
||||||
|
NMEA2000
|
||||||
|
deviceclass: 120 - Display
|
||||||
|
devicefunction: 130 - Display
|
||||||
|
|
||||||
Benötigte Pakete:
|
Benötigte Pakete:
|
||||||
python3-cairo python3-gi python3-gi-cairo gir1.2-rsvg-2.0
|
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
|
Button 6 ist reserviert für Beleuchtung ILUM
|
||||||
|
|
||||||
Neu
|
Standardbelegung der Tasten
|
||||||
|
Button 1: Mode
|
||||||
Button 2: Abbruch
|
Button 2: Abbruch
|
||||||
Button 3: zurück
|
Button 3: zurück
|
||||||
Button 4: hoch
|
Button 4: weiter
|
||||||
Button 5: Ok/Menu
|
Button 5: Ok/Menu
|
||||||
|
|
||||||
Verlagerung der Seitenanzeige in den Titel als Ziffer in Klammern
|
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:
|
Vergleich zum Garmin GMI20:
|
||||||
[ Zurück ] [ Hoch ] [ Menü ] [ Runter ] [ on/off ]
|
[ 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 1 wird für AVG verwendet auf mehreren Seiten
|
||||||
Button 5 wird für Trend TRND verwendet
|
Button 5 wird für Trend TRND verwendet
|
||||||
|
|
||||||
Aber sowas von WIP
|
|
||||||
|
|
||||||
|
|
||||||
Änderungsprotokoll
|
Änderungsprotokoll
|
||||||
==================
|
==================
|
||||||
|
@ -65,7 +70,7 @@ Version Datum Änderung(en) von
|
||||||
0.2 2024-12-24 Veräffentlichung als Git-Repository tho
|
0.2 2024-12-24 Veräffentlichung als Git-Repository tho
|
||||||
|
|
||||||
|
|
||||||
'''
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
@ -429,14 +434,15 @@ class Frontend(Gtk.Window):
|
||||||
self.curpage = self.pages[self.pageno]
|
self.curpage = self.pages[self.pageno]
|
||||||
elif selected == 5:
|
elif selected == 5:
|
||||||
# Ok/Menü
|
# Ok/Menü
|
||||||
self.curpage.handle_key(5)
|
if self.button_clicked == 4:
|
||||||
elif selected == 6:
|
|
||||||
if self.button_clicked == 6:
|
|
||||||
# Backlight on/off
|
|
||||||
self.curpage.backlight = not self.curpage.backlight
|
|
||||||
elif self.button_clicked == 5:
|
|
||||||
# Umschalten zur Systemseite
|
# Umschalten zur Systemseite
|
||||||
self.curpage = self.pages[0]
|
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
|
return True
|
||||||
|
|
||||||
def on_destroy(self, widget):
|
def on_destroy(self, widget):
|
||||||
|
@ -551,4 +557,5 @@ if __name__ == "__main__":
|
||||||
shutdown = True
|
shutdown = True
|
||||||
t_rxd_n2k.join()
|
t_rxd_n2k.join()
|
||||||
t_rxd_0183.join()
|
t_rxd_0183.join()
|
||||||
|
t_data.join()
|
||||||
print("Another fine product of the Sirius Cybernetics Corporation.")
|
print("Another fine product of the Sirius Cybernetics Corporation.")
|
||||||
|
|
Loading…
Reference in New Issue