Erstveröffentlichung Weihnachten 2024
| 
						 | 
					@ -0,0 +1,2 @@
 | 
				
			||||||
 | 
					*~
 | 
				
			||||||
 | 
					__pycache__
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,13 @@
 | 
				
			||||||
 | 
					Das Programm kann direkt gestartet werden. Eine Installation ist nicht
 | 
				
			||||||
 | 
					erforderlich. Die unten angegebenen Abhängigkeiten müssen erfüllt sein.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					apt-get install python3-cairo python3-gi python3-gi-cairo gir1.2-rsvg-2.0 \
 | 
				
			||||||
 | 
					    python-serial python3-nmea2 python3-smbus2 python3-bme280
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Das Programm wird über eine Konfigurationsdatei obp60.conf im gleichen
 | 
				
			||||||
 | 
					Verzeichnis wie das Hauptprogramm gesteuert. Die Konfiguration wird
 | 
				
			||||||
 | 
					einmalig beim Programmstart eingelesen.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Meßdaten werden im Homeverzeichnis unter  ~/.local/lib/obp60 gespeichert.
 | 
				
			||||||
 | 
					Dies betrifft momentan Luftdruckmessungen mit dem BME280.
 | 
				
			||||||
 | 
					Das Verzeichnis wird automatisch angelegt.
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,61 @@
 | 
				
			||||||
 | 
					Multifunktionsdisplay (MFD) virtuell: OBP60v
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Hinweis: Dieses Programm dient in erster Linie dazu die GUI der "echten"
 | 
				
			||||||
 | 
					OBP60-Hardware zu designen. Eine eigenständige Nutzung ist selbstverständlich
 | 
				
			||||||
 | 
					"auf eigene Gefahr" hin möglich.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Für Informationen zum OBP60 in Hardware siehe:
 | 
				
			||||||
 | 
					  - https://open-boat-projects.org/de/diy-multifunktionsdisplay-obp-60/
 | 
				
			||||||
 | 
					  - https://obp60-v2-docu.readthedocs.io/de/latest/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Fehlermeldungen und Patches gerne an thomas@hoogi.de senden.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Basishardware
 | 
				
			||||||
 | 
					- Raspberry Pi 4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Zusatzhardware:
 | 
				
			||||||
 | 
					- NMEA2000 Interface
 | 
				
			||||||
 | 
					  - PiCAN-M (hiermit wird entwickelt)
 | 
				
			||||||
 | 
					  - Waveshare RS485 CAN HAT (ungetestet)
 | 
				
			||||||
 | 
					- BME280-Sensor
 | 
				
			||||||
 | 
					- GPS über USB/seriell angeschlossen
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Zusatzsoftware:
 | 
				
			||||||
 | 
					- OpenCPN
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Abhängigkeiten
 | 
				
			||||||
 | 
					- python-can
 | 
				
			||||||
 | 
					- heapdict
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Für GPS
 | 
				
			||||||
 | 
					- python-serial
 | 
				
			||||||
 | 
					- python3-nmea2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Für BME280
 | 
				
			||||||
 | 
					- smbus2
 | 
				
			||||||
 | 
					- bme280
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Zur Steuerung des Geräts sind 6 Tasten vorhanen. Numeriert von 1 bis 6 von
 | 
				
			||||||
 | 
					links nach rechts. Die Tasten können angeklickt werden und führen dann direkt
 | 
				
			||||||
 | 
					eine von der jeweiligen Seite abhängige Funktion aus.
 | 
				
			||||||
 | 
					Die jeweilige Funktion wird durch ein Symbol oberhalb der Taste dargestellt.
 | 
				
			||||||
 | 
					Die Tasten 3 und 4 sind für die Seitennavigation vorgesehen: zurück und vor.
 | 
				
			||||||
 | 
					Sie können jedoch von einer Seite bei Bedarf übersteuert werden.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Wischgesten werden simuliert, indem die Maustaste auf einer Tastenfläche
 | 
				
			||||||
 | 
					gedrückt und auf einer anderen Taste losgelassen wird.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Folgende Wischfunktionen sind implementiert:
 | 
				
			||||||
 | 
					  1. Programmende durch die Wischfunktion "2" -> "1"
 | 
				
			||||||
 | 
					  2. Tastensperre an: "6" -> "1"
 | 
				
			||||||
 | 
					  3. Tastensperre aus: "1" -> "6"
 | 
				
			||||||
 | 
					  4. Systemseite: "5" -> "6"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Routen und Wegepunkte können von OpenCPN empfangen werden. Dazu muß eine
 | 
				
			||||||
 | 
					passende serielle Schnittstelle für den NMEA0183-Ausgang definiert werden.
 | 
				
			||||||
 | 
					Im System kann diese in der Datei rc.local aktiviert werden:
 | 
				
			||||||
 | 
					  # Create virtual serial connection
 | 
				
			||||||
 | 
					  socat pty,rawer,echo=0,group-late=dialout,mode=0660,link=/dev/ttyV0 \
 | 
				
			||||||
 | 
					        pty,rawer,echo=0,group-late=dialout,mode=0660,link=/dev/ttyV1 &
 | 
				
			||||||
 | 
					OpenCPN sendet dann Datensätze über ttyV0 und dieses Programm
 | 
				
			||||||
 | 
					empfängt sie über ttyV1.
 | 
				
			||||||
| 
		 After Width: | Height: | Size: 1.1 KiB  | 
| 
		 After Width: | Height: | Size: 626 B  | 
| 
		 After Width: | Height: | Size: 841 B  | 
| 
		 After Width: | Height: | Size: 1.0 KiB  | 
| 
		 After Width: | Height: | Size: 569 B  | 
| 
		 After Width: | Height: | Size: 847 B  | 
| 
		 After Width: | Height: | Size: 559 B  | 
| 
		 After Width: | Height: | Size: 150 B  | 
| 
		 After Width: | Height: | Size: 148 B  | 
| 
		 After Width: | Height: | Size: 136 B  | 
| 
		 After Width: | Height: | Size: 135 B  | 
| 
		 After Width: | Height: | Size: 157 B  | 
| 
		 After Width: | Height: | Size: 124 B  | 
| 
		 After Width: | Height: | Size: 4.6 KiB  | 
| 
		 After Width: | Height: | Size: 4.2 KiB  | 
| 
		 After Width: | Height: | Size: 140 B  | 
| 
		 After Width: | Height: | Size: 144 B  | 
| 
		 After Width: | Height: | Size: 143 B  | 
| 
		 After Width: | Height: | Size: 146 B  | 
| 
		 After Width: | Height: | Size: 143 B  | 
| 
		 After Width: | Height: | Size: 5.2 KiB  | 
| 
		 After Width: | Height: | Size: 543 B  | 
| 
		 After Width: | Height: | Size: 518 B  | 
| 
		 After Width: | Height: | Size: 510 B  | 
| 
		 After Width: | Height: | Size: 592 B  | 
| 
		 After Width: | Height: | Size: 236 B  | 
| 
		 After Width: | Height: | Size: 584 B  | 
| 
		 After Width: | Height: | Size: 516 B  | 
| 
		 After Width: | Height: | Size: 507 B  | 
| 
		 After Width: | Height: | Size: 569 B  | 
| 
						 | 
					@ -0,0 +1,3 @@
 | 
				
			||||||
 | 
					from .device import Device
 | 
				
			||||||
 | 
					from .boatdata import BoatData
 | 
				
			||||||
 | 
					from .hbuffer import History, HistoryBuffer
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,567 @@
 | 
				
			||||||
 | 
					'''
 | 
				
			||||||
 | 
					!!! 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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, instance=0):
 | 
				
			||||||
 | 
					        self.instance = instance
 | 
				
			||||||
 | 
					        self.fluidtype = 1 # water -> lookup
 | 
				
			||||||
 | 
					        self.volume = None
 | 
				
			||||||
 | 
					        self.capacity = None
 | 
				
			||||||
 | 
					        self.desc = "" # long description
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __str__(self):
 | 
				
			||||||
 | 
					        out = f"  Tank #{self.instance}"
 | 
				
			||||||
 | 
					        out += f"    Capacity: {self.capacity} l\n"
 | 
				
			||||||
 | 
					        out += f"    Fluid level: {self.volume} l\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():
 | 
				
			||||||
 | 
					            print(e)
 | 
				
			||||||
 | 
					        for t in self.tank.values():
 | 
				
			||||||
 | 
					            print(t)
 | 
				
			||||||
 | 
					        out += "  Satellite info\n"
 | 
				
			||||||
 | 
					        for s in self.sat.values():
 | 
				
			||||||
 | 
					            out += str(s)
 | 
				
			||||||
 | 
					        return out
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def updateValid(self, age=None):
 | 
				
			||||||
 | 
					        # age: Alter eines Meßwerts in Sekunden
 | 
				
			||||||
 | 
					        if not age:
 | 
				
			||||||
 | 
					            age = self.maxage
 | 
				
			||||||
 | 
					        t = time.time()
 | 
				
			||||||
 | 
					        for v in vars(self).values():
 | 
				
			||||||
 | 
					            if isinstance(v,BoatValue):
 | 
				
			||||||
 | 
					                if t - v.timestamp > age:
 | 
				
			||||||
 | 
					                    v.valid = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def getRef(self, shortname):
 | 
				
			||||||
 | 
					        '''
 | 
				
			||||||
 | 
					        Referenz auf ein BoatValue-Objekt
 | 
				
			||||||
 | 
					        '''
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            bv = self.valref[shortname]
 | 
				
			||||||
 | 
					        except KeyError:
 | 
				
			||||||
 | 
					            bv = None
 | 
				
			||||||
 | 
					        return bv
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def getValue(self, shortname):
 | 
				
			||||||
 | 
					        '''
 | 
				
			||||||
 | 
					        Wert aufgrund textuellem Kurznamen zurückliefern
 | 
				
			||||||
 | 
					        '''
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            value = self.valref[shortname].value
 | 
				
			||||||
 | 
					        except KeyError:
 | 
				
			||||||
 | 
					            value = None
 | 
				
			||||||
 | 
					        return value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def setValue(self, shortname, newvalue):
 | 
				
			||||||
 | 
					        '''
 | 
				
			||||||
 | 
					        Rückgabewert True bei erfolgreichem Speichern des Werts
 | 
				
			||||||
 | 
					        '''
 | 
				
			||||||
 | 
					        if not shortname in self.valref:
 | 
				
			||||||
 | 
					            return False
 | 
				
			||||||
 | 
					        field = self.valref[shortname]
 | 
				
			||||||
 | 
					        field.value = newvalue
 | 
				
			||||||
 | 
					        field.timestamp = time.time()
 | 
				
			||||||
 | 
					        field.valid = True
 | 
				
			||||||
 | 
					        return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def enableSimulation(self):
 | 
				
			||||||
 | 
					        self.simulation = True
 | 
				
			||||||
 | 
					        for v in self.valref.values():
 | 
				
			||||||
 | 
					            v.simulated = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def addHistory(self, history, htype):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        htype: press, temp, hum
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.history[htype] = history
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,63 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					'''
 | 
				
			||||||
 | 
					Platzhalter WIP
 | 
				
			||||||
 | 
					- ausprogrammieren nach Bedarf
 | 
				
			||||||
 | 
					Geräteliste
 | 
				
			||||||
 | 
					 - wird regelmäßig aktualisiert
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					'''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
 | 
					import struct
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Device():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, address):
 | 
				
			||||||
 | 
					        #WIP
 | 
				
			||||||
 | 
					        #Felder können sich noch ändern!
 | 
				
			||||||
 | 
					        self.address = address
 | 
				
			||||||
 | 
					        self.instance = 0            # default 0
 | 
				
			||||||
 | 
					        self.sysinstance = 0         # used with bridged networks, default 0
 | 
				
			||||||
 | 
					        self.lastseen = time.time()
 | 
				
			||||||
 | 
					        self.uniqueid = None
 | 
				
			||||||
 | 
					        self.manufacturercode = None
 | 
				
			||||||
 | 
					        self.industrygroup = None
 | 
				
			||||||
 | 
					        self.name = None                # User defined device name
 | 
				
			||||||
 | 
					        self.product = None             # Product name
 | 
				
			||||||
 | 
					        self.productcode = None         # Product code
 | 
				
			||||||
 | 
					        self.devicefunction = None
 | 
				
			||||||
 | 
					        self.deviceclass = None
 | 
				
			||||||
 | 
					        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 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def getName(self):
 | 
				
			||||||
 | 
					        # NAME errechnen aus den Claim-Daten
 | 
				
			||||||
 | 
					        # TODO Das hier ist noch fehlerhaft!
 | 
				
			||||||
 | 
					        data = bytearray()
 | 
				
			||||||
 | 
					        data.append((self.deviceclass << 4) | (self.devicefunction & 0x0f))
 | 
				
			||||||
 | 
					        data.extend(struct.pack('<L', self.uniqueid))
 | 
				
			||||||
 | 
					        data.extend(struct.pack('<L', self.manufacturercode))
 | 
				
			||||||
 | 
					        data.extend(struct.pack('<L', self.industrygroup))
 | 
				
			||||||
 | 
					        return data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __str__(self):
 | 
				
			||||||
 | 
					        out = f"Device: {self.address} : '{self.product}'\n"
 | 
				
			||||||
 | 
					        out += f"  Instance: {self.instance}\n"
 | 
				
			||||||
 | 
					        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: {self.certlevel}\n"
 | 
				
			||||||
 | 
					        out += f"  LEN: {self.loadequiv}"
 | 
				
			||||||
 | 
					        return out
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,17 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					'''
 | 
				
			||||||
 | 
					Platzhalter WIP
 | 
				
			||||||
 | 
					- ausprogrammieren nach Bedarf
 | 
				
			||||||
 | 
					Geräteliste
 | 
				
			||||||
 | 
					 - wird regelmäßig aktualisiert
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					'''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DeviceList():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self):
 | 
				
			||||||
 | 
					        self.devices = list()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def print(self):
 | 
				
			||||||
 | 
					        for d in self.devicelist:
 | 
				
			||||||
 | 
					            print(d)
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,300 @@
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					History Buffer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Permanent storage backed buffer for sensordata
 | 
				
			||||||
 | 
					Only supported at the moment: file system storage
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Values can be 1 to 4 bytes in length
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Header: 32 bytes of size
 | 
				
			||||||
 | 
					     0 0x00 HB00              4 magic number
 | 
				
			||||||
 | 
					     4 0x04 xxxxxxxxxxxxxxxx 16 name, space padded
 | 
				
			||||||
 | 
					    20 0x14 n                 1 byte size of values in buffer
 | 
				
			||||||
 | 
					    21 0x15 mm                2 buffer size in count of values
 | 
				
			||||||
 | 
					    23 0x17 dd                2 time step in seconds between values
 | 
				
			||||||
 | 
					    25 0x19 tttt              4 unix timestamp of head
 | 
				
			||||||
 | 
					    29 0x1d hh                2 head pointer
 | 
				
			||||||
 | 
					    31 0x1f 0xff              1 header end sign
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    32 0x20 ...                 start of buffer data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Usage example: 7 hours of data collected every 75 seconds
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def g_tick(n=1):
 | 
				
			||||||
 | 
					    t = time.time()
 | 
				
			||||||
 | 
					    count = 0
 | 
				
			||||||
 | 
					    while True:
 | 
				
			||||||
 | 
					        count += n
 | 
				
			||||||
 | 
					        yield max(t + count - time.time(), 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					hb = HistoryBuffer("test", 336, 75)
 | 
				
			||||||
 | 
					g = g_tick(hb.dt)
 | 
				
			||||||
 | 
					hb.filename = "/tmp/test.dat"
 | 
				
			||||||
 | 
					hb.begin()
 | 
				
			||||||
 | 
					while True:
 | 
				
			||||||
 | 
					    time.sleep(next(g))
 | 
				
			||||||
 | 
					    hb.add(measured_new_value)
 | 
				
			||||||
 | 
					hb.finish()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TODO
 | 
				
			||||||
 | 
					  - Logging
 | 
				
			||||||
 | 
					  - Additional backend: I2C FRAM module
 | 
				
			||||||
 | 
					  - Sync to next tick after loading from storage
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
 | 
					import struct
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class HistoryBuffer():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, name, size, delta_t):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Buffer can have an optional name of max. 16 characters
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.magic = b'HB00'
 | 
				
			||||||
 | 
					        self.name = name[:16] or ''
 | 
				
			||||||
 | 
					        self.bytesize = 2
 | 
				
			||||||
 | 
					        self.size = size
 | 
				
			||||||
 | 
					        self.dt = delta_t
 | 
				
			||||||
 | 
					        self.headdate = int(time.time())
 | 
				
			||||||
 | 
					        self.head = 0
 | 
				
			||||||
 | 
					        self.buf = [0 for _ in range(size)]
 | 
				
			||||||
 | 
					        self.filename = f"/tmp/hb{name}_{size}-{delta_t}.dat"
 | 
				
			||||||
 | 
					        self.fp = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def begin(self):
 | 
				
			||||||
 | 
					        # Check if data exists and format is correct
 | 
				
			||||||
 | 
					        if not os.path.exists(self.filename):
 | 
				
			||||||
 | 
					            self.createfile()
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            if not self.checkfile():
 | 
				
			||||||
 | 
					                print(f"Incompatible data file: {self.filename}")
 | 
				
			||||||
 | 
					                return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Read old data to continue processing
 | 
				
			||||||
 | 
					        self.fp = open(self.filename, 'r+b')
 | 
				
			||||||
 | 
					        self.headdate = int(time.time())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.fp.seek(25)
 | 
				
			||||||
 | 
					        timestamp = struct.unpack('I', self.fp.read(4))[0]
 | 
				
			||||||
 | 
					        self.head = struct.unpack('H', self.fp.read(2))[0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.fp.seek(32)
 | 
				
			||||||
 | 
					        data = self.fp.read(self.bytesize * self.size)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Fix difference between current time and data time
 | 
				
			||||||
 | 
					        missing = (self.headdate - timestamp) // self.dt
 | 
				
			||||||
 | 
					        if missing > self.size:
 | 
				
			||||||
 | 
					            # too old start new
 | 
				
			||||||
 | 
					            self.clearfile
 | 
				
			||||||
 | 
					            self.head = 0
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            # usable data found, fix missing
 | 
				
			||||||
 | 
					            self.fp.seek(32)
 | 
				
			||||||
 | 
					            data = self.fp.read(self.bytesize * self.size)
 | 
				
			||||||
 | 
					            i = 0
 | 
				
			||||||
 | 
					            for d in range(0, self.size, self.bytesize):
 | 
				
			||||||
 | 
					                if self.bytesize == 1:
 | 
				
			||||||
 | 
					                    self.buf[i] = data[d]
 | 
				
			||||||
 | 
					                elif self.bytesize == 2:
 | 
				
			||||||
 | 
					                    self.buf[i] = data[d] + data[d+1] * 256
 | 
				
			||||||
 | 
					                elif self.bytesize == 3:
 | 
				
			||||||
 | 
					                    self.buf[i] = data[d] + (data[d+1] << 8) + (data[d+2] << 16)
 | 
				
			||||||
 | 
					                elif self.bytesize == 4:
 | 
				
			||||||
 | 
					                    self.buf[i] = data[d] + (data[d+1] << 8) + (data[d+2] << 16) + (data[d+3] << 24)
 | 
				
			||||||
 | 
					                i += 1
 | 
				
			||||||
 | 
					            # add empty data for missing steps
 | 
				
			||||||
 | 
					            for s in range(missing):
 | 
				
			||||||
 | 
					                self.add(0)
 | 
				
			||||||
 | 
					        return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def finish(self):
 | 
				
			||||||
 | 
					        if not self.fp.closed:
 | 
				
			||||||
 | 
					            self.fp.close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def add(self, value):
 | 
				
			||||||
 | 
					        # check if add request perhaps too early
 | 
				
			||||||
 | 
					        timestamp = int(time.time())
 | 
				
			||||||
 | 
					        if timestamp - self.headdate < self.dt * 0.98: # a little bit of tolerance
 | 
				
			||||||
 | 
					            print("add new value too early, ignored")
 | 
				
			||||||
 | 
					            return False
 | 
				
			||||||
 | 
					        self.headdate = timestamp
 | 
				
			||||||
 | 
					        self.buf[self.head] = value
 | 
				
			||||||
 | 
					        self.updatefile(value)
 | 
				
			||||||
 | 
					        self.head += 1
 | 
				
			||||||
 | 
					        if self.head == self.size:
 | 
				
			||||||
 | 
					            self.head = 0
 | 
				
			||||||
 | 
					        return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Return buffer in linear sequence, newest values first
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        for i in range(self.head -1, -1, -1):
 | 
				
			||||||
 | 
					            yield self.buf[i]
 | 
				
			||||||
 | 
					        for i in range(self.size - 1, self.head -1, -1):
 | 
				
			||||||
 | 
					            yield self.buf[i]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def getvalue(self, delta):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Return a single value dt seconds ago
 | 
				
			||||||
 | 
					        delta has to be smaller than self.dt * self.size
 | 
				
			||||||
 | 
					        TODO check if value is missing, perhaps allow tolerance (+/- <n>)
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        index = self.head - abs(delta) // self.dt
 | 
				
			||||||
 | 
					        if index < 0:
 | 
				
			||||||
 | 
					            index = self.size - index - 1
 | 
				
			||||||
 | 
					        return self.buf[index]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def getvalue3(self, delta):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        same as getvalue but calculate mean with two neighbor values
 | 
				
			||||||
 | 
					        TODO check for missing values (=0)
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        index = self.head - abs(delta) // self.dt
 | 
				
			||||||
 | 
					        if index < 0:
 | 
				
			||||||
 | 
					            index = self.size - index - 1
 | 
				
			||||||
 | 
					        ixprev = index - 1
 | 
				
			||||||
 | 
					        if ixprev < 0:
 | 
				
			||||||
 | 
					            ixprev = self.size - 1
 | 
				
			||||||
 | 
					        ixnext = index + 1
 | 
				
			||||||
 | 
					        if ixnext > self.size - 1:
 | 
				
			||||||
 | 
					            ixnext = 0
 | 
				
			||||||
 | 
					        return round((self.buf[ix] + self.buf[ixprev] + self.buf[ixnext]) / 3)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def setname(self, newname):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        set new name in buffer and storage backend
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.name = newname[:16] or ''
 | 
				
			||||||
 | 
					        self.fp.seek(4)
 | 
				
			||||||
 | 
					        fp.write(self.name.ljust(16, ' ').encode())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def createfile(self):
 | 
				
			||||||
 | 
					        """"
 | 
				
			||||||
 | 
					        Creates new file from current buffer
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        with open(self.filename, 'wb') as fp:
 | 
				
			||||||
 | 
					            fp.write(self.magic)
 | 
				
			||||||
 | 
					            fp.write(self.name.ljust(16, ' ').encode())
 | 
				
			||||||
 | 
					            fp.write(struct.pack('B', self.bytesize))
 | 
				
			||||||
 | 
					            fp.write(struct.pack('H', self.size))
 | 
				
			||||||
 | 
					            fp.write(struct.pack('H', self.dt))
 | 
				
			||||||
 | 
					            fp.write(struct.pack('I', self.headdate))
 | 
				
			||||||
 | 
					            fp.write(struct.pack('H', self.head))
 | 
				
			||||||
 | 
					            fp.write(b"\xff") # header end
 | 
				
			||||||
 | 
					            if self.bytesize == 1:
 | 
				
			||||||
 | 
					                fp.write(bytes(self.buf))
 | 
				
			||||||
 | 
					            elif self.bytesize == 2:
 | 
				
			||||||
 | 
					                for val in self.buf:
 | 
				
			||||||
 | 
					                    fp.write(struct.pack('H', val))
 | 
				
			||||||
 | 
					            elif self.bytesize == 3:
 | 
				
			||||||
 | 
					                for val in self.buf:
 | 
				
			||||||
 | 
					                    fp.write((val >> 16) & 0xff)
 | 
				
			||||||
 | 
					                    fp.write((val >> 8) & 0xff)
 | 
				
			||||||
 | 
					                    fp.write(val & 0xff)
 | 
				
			||||||
 | 
					            elif self.bytesize == 4:
 | 
				
			||||||
 | 
					                for val in self.buf:
 | 
				
			||||||
 | 
					                    fp.write(struct.pack('I', val))
 | 
				
			||||||
 | 
					        return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def checkfile(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Check if file header matches buffer metadata
 | 
				
			||||||
 | 
					        Name is not taken into account because it is optional
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        with open(self.filename, 'rb') as fp:
 | 
				
			||||||
 | 
					            header = fp.read(32)
 | 
				
			||||||
 | 
					            magic = header[:4]
 | 
				
			||||||
 | 
					            if not (header[:4] == self.magic):
 | 
				
			||||||
 | 
					                print(f"Invalid magic: {magic}")
 | 
				
			||||||
 | 
					                return False
 | 
				
			||||||
 | 
					            bs = header[20]
 | 
				
			||||||
 | 
					            if not (bs == self.bytesize):
 | 
				
			||||||
 | 
					                print(f"Invalid bytesize: {bs}")
 | 
				
			||||||
 | 
					                return False
 | 
				
			||||||
 | 
					            vc = struct.unpack('H', header[21:23])[0]
 | 
				
			||||||
 | 
					            if not (vc == self.size):
 | 
				
			||||||
 | 
					                eprint(f"Invalid value count: {vc}")
 | 
				
			||||||
 | 
					                return False
 | 
				
			||||||
 | 
					            ts = struct.unpack('H', header[23:25])[0]
 | 
				
			||||||
 | 
					            if not (ts == self.dt):
 | 
				
			||||||
 | 
					                eprint(f"Invalid time step: {ts}")
 | 
				
			||||||
 | 
					                return False
 | 
				
			||||||
 | 
					        return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def updatefile(self, value):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Write value to file and update header accordingly
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        pos = 32 + self.head * self.bytesize
 | 
				
			||||||
 | 
					        self.fp.seek(25)
 | 
				
			||||||
 | 
					        self.fp.write(struct.pack('IH', self.headdate, self.head + 1))
 | 
				
			||||||
 | 
					        self.fp.seek(pos)
 | 
				
			||||||
 | 
					        if self.bytesize == 1:
 | 
				
			||||||
 | 
					            self.fp.write(struct.pack('B', value))
 | 
				
			||||||
 | 
					        elif self.bytesize == 2:
 | 
				
			||||||
 | 
					            self.fp.write(struct.pack('H', value))
 | 
				
			||||||
 | 
					        elif self.bytesize == 3:
 | 
				
			||||||
 | 
					            self.fp.write((value >> 16) & 0xff)
 | 
				
			||||||
 | 
					            self.fp.write((value >> 8) & 0xff)
 | 
				
			||||||
 | 
					            self.fp.write(value & 0xff)
 | 
				
			||||||
 | 
					        elif self.bytesize == 4:
 | 
				
			||||||
 | 
					            self.fp.write(struct.pack('I', value))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def clearfile(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Clear data part of history file
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.fp.seek(25)
 | 
				
			||||||
 | 
					        self.fp.write(struct.pack('IH', int(time.time()), 0))
 | 
				
			||||||
 | 
					        fp.seek(32)
 | 
				
			||||||
 | 
					        for p in range(32, self.size * self.bytesize):
 | 
				
			||||||
 | 
					            fp.write(0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class History():
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    A history can consist of different time series with different
 | 
				
			||||||
 | 
					    temporal resolutions
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    TODO implement type (e.g. pressure humidity temp etc.) to identify data
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    def __init__(self, basename, delta_min):
 | 
				
			||||||
 | 
					        self.delta_t = delta_min  # smallest unit of time in the series
 | 
				
			||||||
 | 
					        self.series = dict()
 | 
				
			||||||
 | 
					        self.basepath = "/tmp"
 | 
				
			||||||
 | 
					        self.basename = basename
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __str__(self):
 | 
				
			||||||
 | 
					        out = f"History {self.basename} (min. {self.delta_t}s) in {self.basepath}\n"
 | 
				
			||||||
 | 
					        n = 0
 | 
				
			||||||
 | 
					        for ser in self.series.values():
 | 
				
			||||||
 | 
					            out += f"  Series: {ser.name} {ser.dt}s {ser.filename}\n"
 | 
				
			||||||
 | 
					            n += 1
 | 
				
			||||||
 | 
					        if n == 0:
 | 
				
			||||||
 | 
					            out += "  No series found\n"
 | 
				
			||||||
 | 
					        return out
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def addSeries(self, name, size, delta_t):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Check whether a series already exists and throw an error if so.
 | 
				
			||||||
 | 
					        The new delta t must also be divisible by delta_min
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        if delta_t in self.series:
 | 
				
			||||||
 | 
					            raise KeyError(f"Error: delta t {delta_t} already exists")
 | 
				
			||||||
 | 
					        if delta_t < self.delta_t:
 | 
				
			||||||
 | 
					            raise ValueError(f"Error: delta t {delta_t} too small, minimum is {self.delta_t}")
 | 
				
			||||||
 | 
					        if delta_t % self.delta_t != 0:
 | 
				
			||||||
 | 
					            raise ValueError(f"Error: delta t have to be a multiple of {self.delta_t}")
 | 
				
			||||||
 | 
					        hb = HistoryBuffer(name, size, delta_t)
 | 
				
			||||||
 | 
					        histfilename = f"hb{self.basename}_{size}-{delta_t}.dat"
 | 
				
			||||||
 | 
					        hb.filename = os.path.join(self.basepath, histfilename)
 | 
				
			||||||
 | 
					        self.series[delta_t] = hb
 | 
				
			||||||
 | 
					        return hb
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def clear(self):
 | 
				
			||||||
 | 
					        # Clear all time series buffer
 | 
				
			||||||
 | 
					        self.series.clear()
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,563 @@
 | 
				
			||||||
 | 
					# 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",
 | 
				
			||||||
 | 
					    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"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    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",
 | 
				
			||||||
 | 
					    2001: "Open Boat Projects"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pilotmode = {
 | 
				
			||||||
 | 
					    64: "Standby",
 | 
				
			||||||
 | 
					    66: "Auto",
 | 
				
			||||||
 | 
					    70: "Wind",
 | 
				
			||||||
 | 
					    74: "Track"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pressure = {
 | 
				
			||||||
 | 
					    0: "Athmospheric",
 | 
				
			||||||
 | 
					    1: "Water",
 | 
				
			||||||
 | 
					    2: "Steam",
 | 
				
			||||||
 | 
					    3: "Compressed Air",
 | 
				
			||||||
 | 
					    4: "Hydraulic",
 | 
				
			||||||
 | 
					    5: "Filter",
 | 
				
			||||||
 | 
					    6: "AltimeterSetting",
 | 
				
			||||||
 | 
					    7: "Oil",
 | 
				
			||||||
 | 
					    8: "Fuel"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					prnusage = {
 | 
				
			||||||
 | 
					    0: "Not Tracked",
 | 
				
			||||||
 | 
					    1: "Tracked",
 | 
				
			||||||
 | 
					    2: "Used",
 | 
				
			||||||
 | 
					    3: "Not tracked+Diff",
 | 
				
			||||||
 | 
					    4: "Tracked+Diff",
 | 
				
			||||||
 | 
					    5: "Used+Diff",
 | 
				
			||||||
 | 
					    14: "Error",
 | 
				
			||||||
 | 
					    15: "No Selection"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					speedwater = {
 | 
				
			||||||
 | 
					    0: "Paddle wheel",
 | 
				
			||||||
 | 
					    1: "Pitot tube",
 | 
				
			||||||
 | 
					    2: "Doppler",
 | 
				
			||||||
 | 
					    3: "Correlation (ultra sound)",
 | 
				
			||||||
 | 
					    4: "Electro Magnetic"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					timesource = {
 | 
				
			||||||
 | 
					    0: "GPS",
 | 
				
			||||||
 | 
					    1: "GLONASS",
 | 
				
			||||||
 | 
					    2: "Radio Station",
 | 
				
			||||||
 | 
					    3: "Local Cesium clock",
 | 
				
			||||||
 | 
					    4: "Local Rubidium clock",
 | 
				
			||||||
 | 
					    5: "Local Crystal clock"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					temperature = {
 | 
				
			||||||
 | 
					    0: "Sea Temperature",
 | 
				
			||||||
 | 
					    1: "Outside Temperature",
 | 
				
			||||||
 | 
					    2: "Inside Temperature",
 | 
				
			||||||
 | 
					    3: "Engine Room Temperature",
 | 
				
			||||||
 | 
					    4: "Main Cabin Temperature",
 | 
				
			||||||
 | 
					    5: "Live Well Temperature",
 | 
				
			||||||
 | 
					    6: "Bait Well Temperature",
 | 
				
			||||||
 | 
					    7: "Refrigeration Temperature",
 | 
				
			||||||
 | 
					    8: "Heating System Temperature",
 | 
				
			||||||
 | 
					    9: "Dew Point Temperature",
 | 
				
			||||||
 | 
					    10: "Apparent Wind Chill Temperature",
 | 
				
			||||||
 | 
					    11: "Theoretical Wind Chill Temperature",
 | 
				
			||||||
 | 
					    12: "Heat Index Temperature",
 | 
				
			||||||
 | 
					    13: "Freezer Temperature",
 | 
				
			||||||
 | 
					    14: "Exhaust Gas Temperature",
 | 
				
			||||||
 | 
					    15: "Shaft Seal Temperature"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					xtemode = {
 | 
				
			||||||
 | 
					    0: "auto",
 | 
				
			||||||
 | 
					    1: "differential",
 | 
				
			||||||
 | 
					    2: "estimated",
 | 
				
			||||||
 | 
					    3: "simulation",
 | 
				
			||||||
 | 
					    4: "manual"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,25 @@
 | 
				
			||||||
 | 
					'''
 | 
				
			||||||
 | 
					Moving Average
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Sortierung einer Liste nach Alter? FIFO?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					'''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class mAvg():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, interval):
 | 
				
			||||||
 | 
					        self.interval = interval
 | 
				
			||||||
 | 
					        self.avg = None
 | 
				
			||||||
 | 
					        self.data = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def addVal(self, value):
 | 
				
			||||||
 | 
					        self.data.append((time.time(), value))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def setInterval(self, interval):
 | 
				
			||||||
 | 
					        pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def getAvg(self):
 | 
				
			||||||
 | 
					        return self.avg
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,224 @@
 | 
				
			||||||
 | 
					'''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					PGNs verarbeiten
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					'''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import struct
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def parse_126996(buf, source, 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äteliste
 | 
				
			||||||
 | 
					    devices[source].n2kvers = n2kvers
 | 
				
			||||||
 | 
					    devices[source].productcode = prodcode
 | 
				
			||||||
 | 
					    devices[source].modelvers = modelvers.decode('ascii').rstrip()
 | 
				
			||||||
 | 
					    devices[source].softvers = softvers.decode('ascii').rstrip()
 | 
				
			||||||
 | 
					    devices[source].product = modelid.decode('ascii').rstrip()
 | 
				
			||||||
 | 
					    devices[source].serial = serial.decode('ascii').rstrip()
 | 
				
			||||||
 | 
					    devices[source].certlevel = buf[132]
 | 
				
			||||||
 | 
					    devices[source].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
 | 
				
			||||||
 | 
					    boatdata.tank[instance].fluidtype = buf[0] >> 4
 | 
				
			||||||
 | 
					    boatdata.tank[instance].capacity = struct.unpack_from('<L', buf, 2)[0] * 0.1
 | 
				
			||||||
 | 
					    boatdata.tank[instance].volume = struct.unpack_from('<H', buf, 1)[0] * 0.004
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,275 @@
 | 
				
			||||||
 | 
					pgntype = {
 | 
				
			||||||
 | 
					    59392: "S",
 | 
				
			||||||
 | 
					    59904: "S",
 | 
				
			||||||
 | 
					    60160: "S",
 | 
				
			||||||
 | 
					    60416: "S",
 | 
				
			||||||
 | 
					    60928: "S",
 | 
				
			||||||
 | 
					    61184: "S",
 | 
				
			||||||
 | 
					    65001: "S",
 | 
				
			||||||
 | 
					    65002: "S",
 | 
				
			||||||
 | 
					    65003: "S",
 | 
				
			||||||
 | 
					    65004: "S",
 | 
				
			||||||
 | 
					    65005: "S",
 | 
				
			||||||
 | 
					    65006: "S",
 | 
				
			||||||
 | 
					    65007: "S",
 | 
				
			||||||
 | 
					    65008: "S",
 | 
				
			||||||
 | 
					    65009: "S",
 | 
				
			||||||
 | 
					    65010: "S",
 | 
				
			||||||
 | 
					    65011: "S",
 | 
				
			||||||
 | 
					    65012: "S",
 | 
				
			||||||
 | 
					    65013: "S",
 | 
				
			||||||
 | 
					    65014: "S",
 | 
				
			||||||
 | 
					    65015: "S",
 | 
				
			||||||
 | 
					    65016: "S",
 | 
				
			||||||
 | 
					    65017: "S",
 | 
				
			||||||
 | 
					    65018: "S",
 | 
				
			||||||
 | 
					    65019: "S",
 | 
				
			||||||
 | 
					    65020: "S",
 | 
				
			||||||
 | 
					    65021: "S",
 | 
				
			||||||
 | 
					    65022: "S",
 | 
				
			||||||
 | 
					    65023: "S",
 | 
				
			||||||
 | 
					    65024: "S",
 | 
				
			||||||
 | 
					    65025: "S",
 | 
				
			||||||
 | 
					    65026: "S",
 | 
				
			||||||
 | 
					    65027: "S",
 | 
				
			||||||
 | 
					    65028: "S",
 | 
				
			||||||
 | 
					    65029: "S",
 | 
				
			||||||
 | 
					    65030: "S",
 | 
				
			||||||
 | 
					    65240: "I",
 | 
				
			||||||
 | 
					    65280: "S",
 | 
				
			||||||
 | 
					    65284: "S",
 | 
				
			||||||
 | 
					    65285: "S",
 | 
				
			||||||
 | 
					    65286: "S",
 | 
				
			||||||
 | 
					    65287: "S",
 | 
				
			||||||
 | 
					    65288: "S",
 | 
				
			||||||
 | 
					    65289: "S",
 | 
				
			||||||
 | 
					    65290: "S",
 | 
				
			||||||
 | 
					    65292: "S",
 | 
				
			||||||
 | 
					    65293: "S",
 | 
				
			||||||
 | 
					    65302: "S",
 | 
				
			||||||
 | 
					    65305: "S",
 | 
				
			||||||
 | 
					    65309: "S",
 | 
				
			||||||
 | 
					    65312: "S",
 | 
				
			||||||
 | 
					    65340: "S",
 | 
				
			||||||
 | 
					    65341: "S",
 | 
				
			||||||
 | 
					    65345: "S",
 | 
				
			||||||
 | 
					    65350: "S",
 | 
				
			||||||
 | 
					    65359: "S",
 | 
				
			||||||
 | 
					    65360: "S",
 | 
				
			||||||
 | 
					    65361: "S",
 | 
				
			||||||
 | 
					    65371: "S",
 | 
				
			||||||
 | 
					    65374: "S",
 | 
				
			||||||
 | 
					    65379: "S",
 | 
				
			||||||
 | 
					    65408: "S",
 | 
				
			||||||
 | 
					    65409: "S",
 | 
				
			||||||
 | 
					    65410: "S",
 | 
				
			||||||
 | 
					    65420: "S",
 | 
				
			||||||
 | 
					    65480: "S",
 | 
				
			||||||
 | 
					    126208: "F",
 | 
				
			||||||
 | 
					    126464: "F",
 | 
				
			||||||
 | 
					    126720: "F",
 | 
				
			||||||
 | 
					    126983: "F",
 | 
				
			||||||
 | 
					    126984: "F",
 | 
				
			||||||
 | 
					    126985: "F",
 | 
				
			||||||
 | 
					    126986: "F",
 | 
				
			||||||
 | 
					    126987: "F",
 | 
				
			||||||
 | 
					    126988: "F",
 | 
				
			||||||
 | 
					    126992: "S",
 | 
				
			||||||
 | 
					    126993: "S",
 | 
				
			||||||
 | 
					    126996: "F",
 | 
				
			||||||
 | 
					    126998: "F",
 | 
				
			||||||
 | 
					    127233: "F",
 | 
				
			||||||
 | 
					    127237: "F",
 | 
				
			||||||
 | 
					    127245: "S",
 | 
				
			||||||
 | 
					    127250: "S",
 | 
				
			||||||
 | 
					    127251: "S",
 | 
				
			||||||
 | 
					    127252: "S",
 | 
				
			||||||
 | 
					    127257: "S",
 | 
				
			||||||
 | 
					    127258: "S",
 | 
				
			||||||
 | 
					    127488: "S",
 | 
				
			||||||
 | 
					    127489: "F",
 | 
				
			||||||
 | 
					    127490: "F",
 | 
				
			||||||
 | 
					    127491: "F",
 | 
				
			||||||
 | 
					    127493: "S",
 | 
				
			||||||
 | 
					    127494: "F",
 | 
				
			||||||
 | 
					    127495: "F",
 | 
				
			||||||
 | 
					    127496: "F",
 | 
				
			||||||
 | 
					    127497: "F",
 | 
				
			||||||
 | 
					    127498: "F",
 | 
				
			||||||
 | 
					    127500: "S",
 | 
				
			||||||
 | 
					    127501: "S",
 | 
				
			||||||
 | 
					    127502: "S",
 | 
				
			||||||
 | 
					    127503: "F",
 | 
				
			||||||
 | 
					    127504: "F",
 | 
				
			||||||
 | 
					    127505: "S",
 | 
				
			||||||
 | 
					    127506: "F",
 | 
				
			||||||
 | 
					    127507: "F",
 | 
				
			||||||
 | 
					    127508: "S",
 | 
				
			||||||
 | 
					    127509: "F",
 | 
				
			||||||
 | 
					    127510: "F",
 | 
				
			||||||
 | 
					    127511: "S",
 | 
				
			||||||
 | 
					    127512: "S",
 | 
				
			||||||
 | 
					    127513: "F",
 | 
				
			||||||
 | 
					    127514: "S",
 | 
				
			||||||
 | 
					    127744: "S",
 | 
				
			||||||
 | 
					    127745: "S",
 | 
				
			||||||
 | 
					    127746: "S",
 | 
				
			||||||
 | 
					    127750: "S",
 | 
				
			||||||
 | 
					    127751: "S",
 | 
				
			||||||
 | 
					    128000: "S",
 | 
				
			||||||
 | 
					    128001: "S",
 | 
				
			||||||
 | 
					    128002: "S",
 | 
				
			||||||
 | 
					    128003: "S",
 | 
				
			||||||
 | 
					    128006: "S",
 | 
				
			||||||
 | 
					    128007: "S",
 | 
				
			||||||
 | 
					    128008: "S",
 | 
				
			||||||
 | 
					    128259: "S",
 | 
				
			||||||
 | 
					    128267: "S",
 | 
				
			||||||
 | 
					    128275: "F",
 | 
				
			||||||
 | 
					    128520: "F",
 | 
				
			||||||
 | 
					    128538: "F",
 | 
				
			||||||
 | 
					    128768: "S",
 | 
				
			||||||
 | 
					    128769: "S",
 | 
				
			||||||
 | 
					    128776: "S",
 | 
				
			||||||
 | 
					    128777: "S",
 | 
				
			||||||
 | 
					    128778: "S",
 | 
				
			||||||
 | 
					    128780: "S",
 | 
				
			||||||
 | 
					    129025: "S",
 | 
				
			||||||
 | 
					    129026: "S",
 | 
				
			||||||
 | 
					    129027: "S",
 | 
				
			||||||
 | 
					    129028: "S",
 | 
				
			||||||
 | 
					    129029: "F",
 | 
				
			||||||
 | 
					    129033: "S",
 | 
				
			||||||
 | 
					    129038: "F",
 | 
				
			||||||
 | 
					    129039: "F",
 | 
				
			||||||
 | 
					    129040: "F",
 | 
				
			||||||
 | 
					    129041: "F",
 | 
				
			||||||
 | 
					    129044: "F",
 | 
				
			||||||
 | 
					    129045: "F",
 | 
				
			||||||
 | 
					    129283: "S",
 | 
				
			||||||
 | 
					    129284: "F",
 | 
				
			||||||
 | 
					    129285: "F",
 | 
				
			||||||
 | 
					    129291: "S",
 | 
				
			||||||
 | 
					    129301: "F",
 | 
				
			||||||
 | 
					    129302: "F",
 | 
				
			||||||
 | 
					    129538: "F",
 | 
				
			||||||
 | 
					    129539: "S",
 | 
				
			||||||
 | 
					    129540: "F",
 | 
				
			||||||
 | 
					    129541: "F",
 | 
				
			||||||
 | 
					    129542: "F",
 | 
				
			||||||
 | 
					    129545: "F",
 | 
				
			||||||
 | 
					    129546: "S",
 | 
				
			||||||
 | 
					    129547: "F",
 | 
				
			||||||
 | 
					    129549: "F",
 | 
				
			||||||
 | 
					    129550: "F",
 | 
				
			||||||
 | 
					    129551: "F",
 | 
				
			||||||
 | 
					    129556: "F",
 | 
				
			||||||
 | 
					    129792: "F",
 | 
				
			||||||
 | 
					    129793: "F",
 | 
				
			||||||
 | 
					    129794: "F",
 | 
				
			||||||
 | 
					    129795: "F",
 | 
				
			||||||
 | 
					    129796: "F",
 | 
				
			||||||
 | 
					    129797: "F",
 | 
				
			||||||
 | 
					    129798: "F",
 | 
				
			||||||
 | 
					    129799: "F",
 | 
				
			||||||
 | 
					    129800: "F",
 | 
				
			||||||
 | 
					    129801: "F",
 | 
				
			||||||
 | 
					    129802: "F",
 | 
				
			||||||
 | 
					    129803: "F",
 | 
				
			||||||
 | 
					    129804: "F",
 | 
				
			||||||
 | 
					    129805: "F",
 | 
				
			||||||
 | 
					    129806: "F",
 | 
				
			||||||
 | 
					    129807: "F",
 | 
				
			||||||
 | 
					    129808: "F",
 | 
				
			||||||
 | 
					    129809: "F",
 | 
				
			||||||
 | 
					    129810: "F",
 | 
				
			||||||
 | 
					    130052: "F",
 | 
				
			||||||
 | 
					    130053: "F",
 | 
				
			||||||
 | 
					    130054: "F",
 | 
				
			||||||
 | 
					    130060: "F",
 | 
				
			||||||
 | 
					    130061: "F",
 | 
				
			||||||
 | 
					    130064: "F",
 | 
				
			||||||
 | 
					    130065: "F",
 | 
				
			||||||
 | 
					    130066: "F",
 | 
				
			||||||
 | 
					    130067: "F",
 | 
				
			||||||
 | 
					    130068: "F",
 | 
				
			||||||
 | 
					    130069: "F",
 | 
				
			||||||
 | 
					    130070: "F",
 | 
				
			||||||
 | 
					    130071: "F",
 | 
				
			||||||
 | 
					    130072: "F",
 | 
				
			||||||
 | 
					    130073: "F",
 | 
				
			||||||
 | 
					    130074: "F",
 | 
				
			||||||
 | 
					    130306: "S",
 | 
				
			||||||
 | 
					    130310: "S",
 | 
				
			||||||
 | 
					    130311: "S",
 | 
				
			||||||
 | 
					    130312: "S",
 | 
				
			||||||
 | 
					    130313: "S",
 | 
				
			||||||
 | 
					    130314: "S",
 | 
				
			||||||
 | 
					    130315: "S",
 | 
				
			||||||
 | 
					    130316: "S",
 | 
				
			||||||
 | 
					    130320: "F",
 | 
				
			||||||
 | 
					    130321: "F",
 | 
				
			||||||
 | 
					    130322: "F",
 | 
				
			||||||
 | 
					    130323: "F",
 | 
				
			||||||
 | 
					    130324: "F",
 | 
				
			||||||
 | 
					    130330: "F",
 | 
				
			||||||
 | 
					    130560: "S",
 | 
				
			||||||
 | 
					    130561: "F",
 | 
				
			||||||
 | 
					    130562: "F",
 | 
				
			||||||
 | 
					    130563: "F",
 | 
				
			||||||
 | 
					    130564: "F",
 | 
				
			||||||
 | 
					    130565: "F",
 | 
				
			||||||
 | 
					    130566: "F",
 | 
				
			||||||
 | 
					    130567: "F",
 | 
				
			||||||
 | 
					    130569: "F",
 | 
				
			||||||
 | 
					    130570: "F",
 | 
				
			||||||
 | 
					    130571: "F",
 | 
				
			||||||
 | 
					    130572: "F",
 | 
				
			||||||
 | 
					    130573: "F",
 | 
				
			||||||
 | 
					    130574: "F",
 | 
				
			||||||
 | 
					    130576: "S",
 | 
				
			||||||
 | 
					    130577: "F",
 | 
				
			||||||
 | 
					    130578: "F",
 | 
				
			||||||
 | 
					    130579: "S",
 | 
				
			||||||
 | 
					    130580: "F",
 | 
				
			||||||
 | 
					    130581: "F",
 | 
				
			||||||
 | 
					    130582: "S",
 | 
				
			||||||
 | 
					    130583: "F",
 | 
				
			||||||
 | 
					    130584: "F",
 | 
				
			||||||
 | 
					    130585: "S",
 | 
				
			||||||
 | 
					    130586: "F",
 | 
				
			||||||
 | 
					    130816: "F",
 | 
				
			||||||
 | 
					    130817: "F",
 | 
				
			||||||
 | 
					    130818: "F",
 | 
				
			||||||
 | 
					    130819: "F",
 | 
				
			||||||
 | 
					    130820: "F",
 | 
				
			||||||
 | 
					    130821: "F",
 | 
				
			||||||
 | 
					    130822: "F",
 | 
				
			||||||
 | 
					    130823: "F",
 | 
				
			||||||
 | 
					    130824: "F",
 | 
				
			||||||
 | 
					    130825: "F",
 | 
				
			||||||
 | 
					    130827: "F",
 | 
				
			||||||
 | 
					    130828: "F",
 | 
				
			||||||
 | 
					    130831: "F",
 | 
				
			||||||
 | 
					    130832: "F",
 | 
				
			||||||
 | 
					    130833: "F",
 | 
				
			||||||
 | 
					    130834: "F",
 | 
				
			||||||
 | 
					    130835: "F",
 | 
				
			||||||
 | 
					    130836: "F",
 | 
				
			||||||
 | 
					    130837: "F",
 | 
				
			||||||
 | 
					    130838: "F",
 | 
				
			||||||
 | 
					    130839: "F",
 | 
				
			||||||
 | 
					    130840: "F",
 | 
				
			||||||
 | 
					    130842: "F",
 | 
				
			||||||
 | 
					    130843: "F",
 | 
				
			||||||
 | 
					    130845: "F",
 | 
				
			||||||
 | 
					    130846: "F",
 | 
				
			||||||
 | 
					    130847: "F",
 | 
				
			||||||
 | 
					    130850: "F",
 | 
				
			||||||
 | 
					    130851: "F",
 | 
				
			||||||
 | 
					    130856: "F",
 | 
				
			||||||
 | 
					    130860: "F",
 | 
				
			||||||
 | 
					    130880: "F",
 | 
				
			||||||
 | 
					    130881: "F",
 | 
				
			||||||
 | 
					    130944: "F"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,27 @@
 | 
				
			||||||
 | 
					from heapdict import heapdict
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
 | 
					import threading
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class FrameQueue():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self):
 | 
				
			||||||
 | 
					        self.heap = heapdict()
 | 
				
			||||||
 | 
					        self.age_interval = 5    # seconds
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def push(self, frame, priority):
 | 
				
			||||||
 | 
					        timestamp = time.time()  # microsecond resolution
 | 
				
			||||||
 | 
					        self.heap[timestamp] = (priority, timestamp, frame)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def pop(self):
 | 
				
			||||||
 | 
					        p, f = self.heap.popitem()
 | 
				
			||||||
 | 
					        self.age()
 | 
				
			||||||
 | 
					        return f[2]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def age(self):
 | 
				
			||||||
 | 
					        current_time = time.time()
 | 
				
			||||||
 | 
					        for key in list(self.heap.keys()):
 | 
				
			||||||
 | 
					            if current_time - key > self.age_interval:
 | 
				
			||||||
 | 
					                self.heap[key][0] -= 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def is_empty(self): 
 | 
				
			||||||
 | 
					        return len(self.heap) == 0
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,61 @@
 | 
				
			||||||
 | 
					'''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Momentan nur Idee
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Verarbeiten von Frames
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Nur Empfang von Paketen / Frames
 | 
				
			||||||
 | 
					- single
 | 
				
			||||||
 | 
					- fast
 | 
				
			||||||
 | 
					- transport
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fast packets können parallel von mehreren Quellen verarbeitet werden
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					'''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import math
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class FpReceiver():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self):
 | 
				
			||||||
 | 
					        self.sc0 = {}
 | 
				
			||||||
 | 
					        self.nf = {}
 | 
				
			||||||
 | 
					        self.frame = {}
 | 
				
			||||||
 | 
					        self.wip = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def receive(self, data, source)
 | 
				
			||||||
 | 
					        '''
 | 
				
			||||||
 | 
					        Liefert True wenn Paket komplett empfangen wurde
 | 
				
			||||||
 | 
					        '''
 | 
				
			||||||
 | 
					        sc = (data[0] & 0xf0) >> 5
 | 
				
			||||||
 | 
					        fc = data[0] & 0x1f
 | 
				
			||||||
 | 
					        if not source in self.wip:
 | 
				
			||||||
 | 
					            # Erster Frame
 | 
				
			||||||
 | 
					            if fc != 0:
 | 
				
			||||||
 | 
					                # unbekannte Herkunft und kein Startframe
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					            self.sc0[source] = sc
 | 
				
			||||||
 | 
					            datalen = data[1]
 | 
				
			||||||
 | 
					            self.nf[source] = math.ceil((datalen - 6) / 7) + 1 # Anzahl Frames
 | 
				
			||||||
 | 
					            self.frame[source] = {fc : data[2:]} # Ersten Frame merken
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            # Folgeframes
 | 
				
			||||||
 | 
					            if (sc == self.sc0[source]):
 | 
				
			||||||
 | 
					                # TODO prüfe ob der Framecounter fc schon vorgekommen ist
 | 
				
			||||||
 | 
					                if not fc in self.frame[source]:
 | 
				
			||||||
 | 
					                    self.frame[source][fc] = data[1:8]
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    # TODO Fehler im Fast-Packet: doppelter fc!
 | 
				
			||||||
 | 
					                    raise('Frame error: duplicate fc')
 | 
				
			||||||
 | 
					        if len(self.frame[source]) == self.nf[source]:
 | 
				
			||||||
 | 
					            self.wip.remove(source)
 | 
				
			||||||
 | 
					            return True
 | 
				
			||||||
 | 
					        return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def getPacket(self, source)
 | 
				
			||||||
 | 
					        # TODO Frames in der richtigen reihenfolge zusammensetzen
 | 
				
			||||||
 | 
					        packet = bytearray()
 | 
				
			||||||
 | 
					        for frame in sorted(self.frame.items()):
 | 
				
			||||||
 | 
					            print(frame)
 | 
				
			||||||
 | 
					            #packet.extend()
 | 
				
			||||||
 | 
					        return packet
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,17 @@
 | 
				
			||||||
 | 
					'''
 | 
				
			||||||
 | 
					Routen und Wegepunkte
 | 
				
			||||||
 | 
					'''
 | 
				
			||||||
 | 
					class Waypoint():
 | 
				
			||||||
 | 
					    def __init__(self, number, name):
 | 
				
			||||||
 | 
					        self.number = number
 | 
				
			||||||
 | 
					        self.name = name
 | 
				
			||||||
 | 
					        self.lat = None
 | 
				
			||||||
 | 
					        self.lon = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Route():
 | 
				
			||||||
 | 
					    def __init__(self, number, name)
 | 
				
			||||||
 | 
					        self.number = number
 | 
				
			||||||
 | 
					        self.name = name
 | 
				
			||||||
 | 
					        self.wps = dict()
 | 
				
			||||||
 | 
					    def getActiveWP(self):
 | 
				
			||||||
 | 
					        return None
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,179 @@
 | 
				
			||||||
 | 
					'''
 | 
				
			||||||
 | 
					Lange Beschreibung der Daten, mehrsprachig
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					'''
 | 
				
			||||||
 | 
					desc = {
 | 
				
			||||||
 | 
					    "ALT": {
 | 
				
			||||||
 | 
					        "en": "Altitude",
 | 
				
			||||||
 | 
					        "de": "Höhe über Grund"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "AWA": {
 | 
				
			||||||
 | 
					        "en": "Apparant Wind Angle",
 | 
				
			||||||
 | 
					        "de": "Scheinbare Windrichtung"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "AWS": {
 | 
				
			||||||
 | 
					        "en": "Apparant Wind Speed",
 | 
				
			||||||
 | 
					        "de": "Scheinbare Windgeschwindigkeit"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "BTW": {
 | 
				
			||||||
 | 
					        "en": "Bearing To Waypoint",
 | 
				
			||||||
 | 
					        "de": "Kurs zum nächsten Wegepunkt"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "COG": {
 | 
				
			||||||
 | 
					        "en": "Course Over Ground",
 | 
				
			||||||
 | 
					        "de": "Kurs über Grund"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "DBS": {
 | 
				
			||||||
 | 
					        "en": "Depth Below Surface",
 | 
				
			||||||
 | 
					        "de": "Tiefe unter Wasseroberfläche"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "DBT": {
 | 
				
			||||||
 | 
					        "en": "Depth Below Transducer",
 | 
				
			||||||
 | 
					        "de": "Tiefe unter Sensor"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "DEV": {
 | 
				
			||||||
 | 
					        "en": "Deviation",
 | 
				
			||||||
 | 
					        "de": "Kursabweichung"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "DTW": {
 | 
				
			||||||
 | 
					        "en": "Distance To Waypoint",
 | 
				
			||||||
 | 
					        "de": "Entfernung zum nächsten Wegepunkt"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "GPSD": {
 | 
				
			||||||
 | 
					        "en": "GPS Date",
 | 
				
			||||||
 | 
					        "de": "GPS-Datum"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "GPST": {
 | 
				
			||||||
 | 
					        "en": "GPS Time",
 | 
				
			||||||
 | 
					        "de": "GPS-Zeit"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "HDM": {
 | 
				
			||||||
 | 
					        "en": "Magnetic Heading",
 | 
				
			||||||
 | 
					        "de": "Magnetischer Kurs"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "HDT": {
 | 
				
			||||||
 | 
					        "en": "Heading",
 | 
				
			||||||
 | 
					        "de": "Wahrer rechtweisender Kurs"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "HDOP": {
 | 
				
			||||||
 | 
					        "en": "Horizontal Dilation Of Position",
 | 
				
			||||||
 | 
					        "de": "Positionsgenauigkeit in der Horizontalen"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "LAT": {
 | 
				
			||||||
 | 
					        "en": "Latitude",
 | 
				
			||||||
 | 
					        "de": "Geographische Breite"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "LON": {
 | 
				
			||||||
 | 
					        "en": "Longitude",
 | 
				
			||||||
 | 
					        "de": "Geographische Länge"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "Log": {
 | 
				
			||||||
 | 
					        "en": "Logged distance",
 | 
				
			||||||
 | 
					        "de": "Entfernung"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "MaxAws": {
 | 
				
			||||||
 | 
					        "en": "Maximum Apperant Wind Speed",
 | 
				
			||||||
 | 
					        "de": "Maximum der relativen Windgeschwindigkeit"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "MaxTws": {
 | 
				
			||||||
 | 
					        "en": "Maximum True Wind Speed",
 | 
				
			||||||
 | 
					        "de": "Maximum der wahren Windgeschwindigkeit"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "PDOP": {
 | 
				
			||||||
 | 
					        "en": "Position dilation",
 | 
				
			||||||
 | 
					        "de": "Positionsgenauigkeit im Raum"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "PRPOS": {
 | 
				
			||||||
 | 
					        "en": "Secondary Rudder Position",
 | 
				
			||||||
 | 
					        "de": "Auslenkung Sekundärruder"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "ROT": {
 | 
				
			||||||
 | 
					        "en": "Rotation",
 | 
				
			||||||
 | 
					        "de": "Drehrate"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "RPOS": {
 | 
				
			||||||
 | 
					        "en": "Rudder Position",
 | 
				
			||||||
 | 
					        "de": "Auslenkung Ruder"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "SOG": {
 | 
				
			||||||
 | 
					        "en": "Speed Over Ground",
 | 
				
			||||||
 | 
					        "de": "Geschwindigkeit über Grund"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "STW": {
 | 
				
			||||||
 | 
					        "en": "Speed Through Water",
 | 
				
			||||||
 | 
					        "de": "Geschwindigkeit durch das Wasser"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "SatInfo": {
 | 
				
			||||||
 | 
					        "en": "Satellit Info",
 | 
				
			||||||
 | 
					        "de": "Anzahl der sichtbaren Satelliten"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "TWD": {
 | 
				
			||||||
 | 
					        "en": "True Wind Direction",
 | 
				
			||||||
 | 
					        "de": "Wahre Windrichtung"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "TWS": {
 | 
				
			||||||
 | 
					        "en": "True Wind Speed",
 | 
				
			||||||
 | 
					        "de": "Wahre Windgeschwindigkeit"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "TZ": {
 | 
				
			||||||
 | 
					        "en": "Timezone",
 | 
				
			||||||
 | 
					        "de": "Zeitzone"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "TripLog": {
 | 
				
			||||||
 | 
					        "en": "Trip Log",
 | 
				
			||||||
 | 
					        "de": "Tages-Entfernungszähler"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "VAR": {
 | 
				
			||||||
 | 
					        "en": "Course Variation",
 | 
				
			||||||
 | 
					        "de": "Abweichung vom Sollkurs"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "VDOP": {
 | 
				
			||||||
 | 
					        "en": "Vertical Dilation Of Position",
 | 
				
			||||||
 | 
					        "de": "Positionsgenauigkeit in der Vertikalen"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "WPLat": {
 | 
				
			||||||
 | 
					        "en": "Waypoint Latitude",
 | 
				
			||||||
 | 
					        "de": "Geo. Breite des Wegpunktes"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "WPLon": {
 | 
				
			||||||
 | 
					        "en": "Waypoint Longitude",
 | 
				
			||||||
 | 
					        "de": "Geo. Länge des Wegpunktes"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "WTemp": {
 | 
				
			||||||
 | 
					        "en": "Water Temperature",
 | 
				
			||||||
 | 
					        "de": "Wassertemperatur"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "XTE": {
 | 
				
			||||||
 | 
					        "en": "Cross Track Error",
 | 
				
			||||||
 | 
					        "de": "Kursfehler"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Sonderwerte
 | 
				
			||||||
 | 
					    "xdrHum": {
 | 
				
			||||||
 | 
					        "en": "Humidity",
 | 
				
			||||||
 | 
					        "de": "Luftfeuchte"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "xdrPress": {
 | 
				
			||||||
 | 
					        "en": "Pressure",
 | 
				
			||||||
 | 
					        "de": "Luftdruck"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "xdrRotK": {
 | 
				
			||||||
 | 
					        "en": "Keel Rotation",
 | 
				
			||||||
 | 
					        "de": "Kielrotation"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "tdrTemp": {
 | 
				
			||||||
 | 
					        "en": "Temperature",
 | 
				
			||||||
 | 
					        "de": "Temperatur"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "xdrVBat": {
 | 
				
			||||||
 | 
					        "en": "Battery Voltage",
 | 
				
			||||||
 | 
					        "de": "Batteriespannung"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # WIP
 | 
				
			||||||
 | 
					    "xdrCurr": {
 | 
				
			||||||
 | 
					        "en": "Current",
 | 
				
			||||||
 | 
					        "de": "Stromstärke"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,96 @@
 | 
				
			||||||
 | 
					[system]
 | 
				
			||||||
 | 
					systemname = OBP60v
 | 
				
			||||||
 | 
					loglevel = 3
 | 
				
			||||||
 | 
					deviceid = 100
 | 
				
			||||||
 | 
					simulation = on
 | 
				
			||||||
 | 
					histpath = ~/.local/lib/obp60
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[bme280]
 | 
				
			||||||
 | 
					enabled = true
 | 
				
			||||||
 | 
					port = 1
 | 
				
			||||||
 | 
					address = 0x76
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[gps]
 | 
				
			||||||
 | 
					enabled = false
 | 
				
			||||||
 | 
					port = /dev/ttyACM0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[opencpn]
 | 
				
			||||||
 | 
					port = /dev/ttyV1
 | 
				
			||||||
 | 
					navobj = ~/.opencpn/navobj.xml
 | 
				
			||||||
 | 
					config = ~/.opencpn/opencpn.conf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[settings]
 | 
				
			||||||
 | 
					timezone = 1
 | 
				
			||||||
 | 
					boat_draft = 1.3
 | 
				
			||||||
 | 
					fuel_tank = 17
 | 
				
			||||||
 | 
					fuel_consumption = 1.5
 | 
				
			||||||
 | 
					water_tank_1 = 47
 | 
				
			||||||
 | 
					water_tank_2 = 50
 | 
				
			||||||
 | 
					battery_voltage = 12
 | 
				
			||||||
 | 
					battery_type = AGM
 | 
				
			||||||
 | 
					battery_capacity = 200
 | 
				
			||||||
 | 
					solar_power = 0
 | 
				
			||||||
 | 
					generator_power = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[units]
 | 
				
			||||||
 | 
					length_format = m
 | 
				
			||||||
 | 
					distance_format = nm
 | 
				
			||||||
 | 
					speed_format = kn
 | 
				
			||||||
 | 
					wind_speed_format = ln
 | 
				
			||||||
 | 
					temperature_format = C
 | 
				
			||||||
 | 
					date_format = ISO
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[display]
 | 
				
			||||||
 | 
					hold_values = off
 | 
				
			||||||
 | 
					backlight_color = red
 | 
				
			||||||
 | 
					flash_led_mode = limit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[buzzer]
 | 
				
			||||||
 | 
					buzzer_gps_error = off
 | 
				
			||||||
 | 
					buzzer_gps_fix = off
 | 
				
			||||||
 | 
					buzzer_by_limits = off
 | 
				
			||||||
 | 
					buzzer_mode = off
 | 
				
			||||||
 | 
					buzzer_power = 50
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[pages]
 | 
				
			||||||
 | 
					number_of_pages = 10
 | 
				
			||||||
 | 
					start_page = 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[page1]
 | 
				
			||||||
 | 
					type=Voltage
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[page2]
 | 
				
			||||||
 | 
					type=Barograph
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[page3]
 | 
				
			||||||
 | 
					type=Anchor
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[page4]
 | 
				
			||||||
 | 
					type=Autobahn
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[page5]
 | 
				
			||||||
 | 
					type=Clock
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[page6]
 | 
				
			||||||
 | 
					type=TwoValues
 | 
				
			||||||
 | 
					value1=LAT
 | 
				
			||||||
 | 
					value2=LON
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[page7]
 | 
				
			||||||
 | 
					type=ThreeValues
 | 
				
			||||||
 | 
					value1=COG
 | 
				
			||||||
 | 
					value2=STW
 | 
				
			||||||
 | 
					value3=DBT
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[page8]
 | 
				
			||||||
 | 
					type=FourValues
 | 
				
			||||||
 | 
					value1=AWA
 | 
				
			||||||
 | 
					value2=AWS
 | 
				
			||||||
 | 
					value3=COG
 | 
				
			||||||
 | 
					value4=STW
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[page9]
 | 
				
			||||||
 | 
					type=Rudder
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[page10]
 | 
				
			||||||
 | 
					type=SkyView
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,543 @@
 | 
				
			||||||
 | 
					#!/usr/bin/env python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					'''
 | 
				
			||||||
 | 
					Virtuelles Multifunktionsgerät
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Benötigte Pakete:
 | 
				
			||||||
 | 
					  python3-cairo python3-gi python3-gi-cairo gir1.2-rsvg-2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Um transparente Darstellung unter Openbox zu erhalten muß xcompmgr 
 | 
				
			||||||
 | 
					installiert und konfiguriert werden.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Wenn ein lokaler GPS-Empfänger angeschlossen ist, so kann dieser genutzt
 | 
				
			||||||
 | 
					werden. Zusätzlich benötigte Pakete: 
 | 
				
			||||||
 | 
					  python-serial python3-nmea2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Für Unterstützung des BME280-Sensors sind die Bibliotheken smbus2 und 
 | 
				
			||||||
 | 
					bme280 erforderlich.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Routen und Wegepunkte können von OpenCPN empfangen werden. Dazu muß eine
 | 
				
			||||||
 | 
					passende serielle Schnittstelle für NMEA0183-Ausgang definiert werden.
 | 
				
			||||||
 | 
					Im System kann diese in der Datei rc.local aktiviert werden:
 | 
				
			||||||
 | 
					  # Create virtual serial connection
 | 
				
			||||||
 | 
					  socat pty,rawer,echo=0,group-late=dialout,mode=0660,link=/dev/ttyV0 \
 | 
				
			||||||
 | 
					        pty,rawer,echo=0,group-late=dialout,mode=0660,link=/dev/ttyV1 &
 | 
				
			||||||
 | 
					OpenCPN sendet dann Datensätze über ttyV0 und dieses Programm
 | 
				
			||||||
 | 
					empfängt sie über ttyV1.
 | 
				
			||||||
 | 
					Die Wegepunkte werden in OpenCPN im Standard auf 6 Zeichen gekürzt.
 | 
				
			||||||
 | 
					Über die Konfigurationsdatei Settings / MaxWaypointNameLength=<nn>
 | 
				
			||||||
 | 
					ist es möglich einen anderen Wert einzustellen im Bereich zwischen
 | 
				
			||||||
 | 
					3 und 32. Lt. NMEA0183 ist die maximale Länge 10 Zeichen.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Zeichensätze müssen, sofern sie noch nicht vorhanden sind in 
 | 
				
			||||||
 | 
					/usr/local/share/fonts abgelegt werden
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Buttonlayout:
 | 
				
			||||||
 | 
					[ 1 ]   [ 2 ]   [ 3 ]   [ 4 ]   [ 5 ]   [ 6 ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Button 6 ist reserviert für Beleuchtung ILUM
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Neu
 | 
				
			||||||
 | 
					Button 2: Abbruch
 | 
				
			||||||
 | 
					Button 3: zurück
 | 
				
			||||||
 | 
					Button 4: hoch
 | 
				
			||||||
 | 
					Button 5: Ok/Menu
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Verlagerung der Seitenanzeige in den Titel als Ziffer in Klammern
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Vergleich zum Garmin GMI20:
 | 
				
			||||||
 | 
					[ Zurück ]    [ Hoch ]  [ Menü ]  [ Runter ]    [ on/off ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Button 1 wird für AVG verwendet auf mehreren Seiten
 | 
				
			||||||
 | 
					Button 5 wird für Trend TRND verwendet
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Aber sowas von WIP
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Änderungsprotokoll
 | 
				
			||||||
 | 
					==================
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Version  Datum       Änderung(en)                                           von 
 | 
				
			||||||
 | 
					-------- ----------- ------------------------------------------------------ ----
 | 
				
			||||||
 | 
					0.1      2024-10-31  Entwicklung begonnen                                   tho
 | 
				
			||||||
 | 
					0.2      2024-12-24  Veräffentlichung als Git-Repository                    tho
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					'''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
 | 
					import configparser
 | 
				
			||||||
 | 
					from setproctitle import setproctitle, setthreadtitle
 | 
				
			||||||
 | 
					import gi
 | 
				
			||||||
 | 
					gi.require_version('Gtk', '3.0')
 | 
				
			||||||
 | 
					gi.require_version('Rsvg', '2.0')
 | 
				
			||||||
 | 
					from gi.repository import GLib, Gtk, Gdk, Rsvg
 | 
				
			||||||
 | 
					import cairo
 | 
				
			||||||
 | 
					import math
 | 
				
			||||||
 | 
					import threading
 | 
				
			||||||
 | 
					import can
 | 
				
			||||||
 | 
					import serial
 | 
				
			||||||
 | 
					import smbus2
 | 
				
			||||||
 | 
					import pynmea2
 | 
				
			||||||
 | 
					import bme280
 | 
				
			||||||
 | 
					import math
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
 | 
					from datetime import datetime
 | 
				
			||||||
 | 
					from nmea2000 import Device, BoatData, History, HistoryBuffer
 | 
				
			||||||
 | 
					from nmea2000 import parser
 | 
				
			||||||
 | 
					import pages
 | 
				
			||||||
 | 
					import struct
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					__author__ = "Thomas Hooge"
 | 
				
			||||||
 | 
					__copyright__ = "Copyleft 2024, all rights reversed"
 | 
				
			||||||
 | 
					__version__ = "0.2"
 | 
				
			||||||
 | 
					__email__ = "thomas@hoogi.de"
 | 
				
			||||||
 | 
					__status__ = "Development"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					cfg = {
 | 
				
			||||||
 | 
					    'cfgfile': 'obp60.conf',
 | 
				
			||||||
 | 
					    'imgpath': os.path.join(sys.path[0], 'images'),
 | 
				
			||||||
 | 
					    'deviceid': 100,
 | 
				
			||||||
 | 
					    'gps': False,
 | 
				
			||||||
 | 
					    'bme280': False
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def rxd_n2k():
 | 
				
			||||||
 | 
					    setthreadtitle("N2Klistener")
 | 
				
			||||||
 | 
					    bus = can.Bus(interface='socketcan', channel='can0', bitrate=250000);
 | 
				
			||||||
 | 
					    wip = False
 | 
				
			||||||
 | 
					    sc = 0
 | 
				
			||||||
 | 
					    nf = 0
 | 
				
			||||||
 | 
					    while not shutdown:
 | 
				
			||||||
 | 
					        msg = bus.recv(2)
 | 
				
			||||||
 | 
					        if not msg:
 | 
				
			||||||
 | 
					            continue
 | 
				
			||||||
 | 
					        priority = (msg.arbitration_id & 0x1c000000) >> 26
 | 
				
			||||||
 | 
					        source = msg.arbitration_id & 0x000000ff
 | 
				
			||||||
 | 
					        pgn = (msg.arbitration_id & 0x3ffff00) >> 8
 | 
				
			||||||
 | 
					        match pgn:
 | 
				
			||||||
 | 
					            case 129025:
 | 
				
			||||||
 | 
					                # Position
 | 
				
			||||||
 | 
					                #lat = struct.unpack_from('<l', msg.data, 0)[0] * 1e-07
 | 
				
			||||||
 | 
					                #lon = struct.unpack_from('<l', msg.data, 4)[0] * 1e-07
 | 
				
			||||||
 | 
					                #boatdata.setValue("LAT", lat)
 | 
				
			||||||
 | 
					                #boatdata.setValue("LON", lon)
 | 
				
			||||||
 | 
					                parser.parse_129025(msg.data, boatdata)
 | 
				
			||||||
 | 
					            case 127505:
 | 
				
			||||||
 | 
					                # Fluid level
 | 
				
			||||||
 | 
					                #fluidtype = msg.data[0] >> 4
 | 
				
			||||||
 | 
					                #instance = msg.data[0] & 0x0f
 | 
				
			||||||
 | 
					                #level = struct.unpack_from('<H', msg.data, 1)[0] * 0.004
 | 
				
			||||||
 | 
					                #capacity = struct.unpack_from('<L', msg.data, 2)[0] * 0.1
 | 
				
			||||||
 | 
					                #boatdata.tank[instance].capacity = capacity
 | 
				
			||||||
 | 
					                #boatdata.tank[instance].volume = level
 | 
				
			||||||
 | 
					                #boatdata.tank[instance].fluidtype = fluidtype
 | 
				
			||||||
 | 
					                parser.parse_127505(msg.data, boatdata)
 | 
				
			||||||
 | 
					            case 127508:
 | 
				
			||||||
 | 
					                # Battery status
 | 
				
			||||||
 | 
					                #instance = msg.data[0]
 | 
				
			||||||
 | 
					                #boatdata.voltage = (msg.data[2] * 256 + msg.data[1]) * 0.01 # Spannung
 | 
				
			||||||
 | 
					                #ampere = (msg.data[4] * 256 + msg.data[3]) * 0.1 # Stromstärke
 | 
				
			||||||
 | 
					                #temp = (msg.data[6] * 256 + msg.data[5]) * 0.01  - 273.15 # Temperatur
 | 
				
			||||||
 | 
					                #sid = msg.data[7]
 | 
				
			||||||
 | 
					                parser.parse_127508(msg.data, boatdata)
 | 
				
			||||||
 | 
					            case 129540:
 | 
				
			||||||
 | 
					                # GNS sats in view
 | 
				
			||||||
 | 
					                # TODO es kann mehrere Geräte geben die senden 
 | 
				
			||||||
 | 
					                # - source beachten (muß gleich bleiben)
 | 
				
			||||||
 | 
					                # - gemischte Reihenfolge der Pakete ermöglichen
 | 
				
			||||||
 | 
					                sc = (msg.data[0] & 0xf0) >> 5
 | 
				
			||||||
 | 
					                fc = msg.data[0] & 0x1f
 | 
				
			||||||
 | 
					                if not wip:
 | 
				
			||||||
 | 
					                    if fc != 0:
 | 
				
			||||||
 | 
					                        continue
 | 
				
			||||||
 | 
					                    source0 = source # muß über das Fast-packet konstant bleiben
 | 
				
			||||||
 | 
					                    sc0 = sc         #                 -"- 
 | 
				
			||||||
 | 
					                    fc0 = fc         # dieser Zähler wird inkrementiert
 | 
				
			||||||
 | 
					                    datalen = msg.data[1]
 | 
				
			||||||
 | 
					                    nf = math.ceil((datalen - 6) / 7) + 1
 | 
				
			||||||
 | 
					                    buf129540 = msg.data[2:]
 | 
				
			||||||
 | 
					                    wip = True
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    if (source == source0) and (sc == sc0) and (fc == fc0 + 1):
 | 
				
			||||||
 | 
					                        buf129540.extend(msg.data[1:8])
 | 
				
			||||||
 | 
					                        fc0 = fc
 | 
				
			||||||
 | 
					                    else:
 | 
				
			||||||
 | 
					                        # Dieser Frame paßt nicht
 | 
				
			||||||
 | 
					                        #print("PGN 129540: sc/fc mismatch")
 | 
				
			||||||
 | 
					                        pass
 | 
				
			||||||
 | 
					                if fc == nf:
 | 
				
			||||||
 | 
					                    wip = False
 | 
				
			||||||
 | 
					                    parser.parse_129540(buf129540, boatdata)
 | 
				
			||||||
 | 
					            case _:
 | 
				
			||||||
 | 
					                pass
 | 
				
			||||||
 | 
					    bus.shutdown()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def rxd_0183(devname):
 | 
				
			||||||
 | 
					    # Prüfe ob Port vorhanden ist und sich öffnen läßt
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        ser = serial.Serial(devname, 115200, timeout=3)
 | 
				
			||||||
 | 
					    except serial.SerialException as e:
 | 
				
			||||||
 | 
					        print("OpenCPN serial port not available")
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
					    setthreadtitle("0183listener")
 | 
				
			||||||
 | 
					    while not shutdown:
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            msg = pynmea2.parse(ser.readline().decode('ascii'))
 | 
				
			||||||
 | 
					        except pynmea2.nmea.ParseError:
 | 
				
			||||||
 | 
					            continue
 | 
				
			||||||
 | 
					        if msg.sentence_type == 'GLL':
 | 
				
			||||||
 | 
					            boatdata.setValue("LAT", msg.latitude)
 | 
				
			||||||
 | 
					            boatdata.setValue("LON", msg.longitude)
 | 
				
			||||||
 | 
					        elif msg.sentence_type == 'VTG':
 | 
				
			||||||
 | 
					            boatdata.setValue("COG", int(msg.true_track))
 | 
				
			||||||
 | 
					            boatdata.setValue("SOG", float(msg.spd_over_grnd_kts[:-1]))
 | 
				
			||||||
 | 
					        elif msg.sentence_type == 'VHW':
 | 
				
			||||||
 | 
					            boatdata.setValue("STW", float(msg.water_speed_knots))
 | 
				
			||||||
 | 
					        elif msg.sentence_type == 'WPL':
 | 
				
			||||||
 | 
					            # Wegepunkt
 | 
				
			||||||
 | 
					            print(msg.fields)
 | 
				
			||||||
 | 
					        elif msg.sentence_type == 'RTE':
 | 
				
			||||||
 | 
					            # Route
 | 
				
			||||||
 | 
					            print(msg.fields)
 | 
				
			||||||
 | 
					    ser.close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def datareader(histpath, history):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Daten zu fest definierten Zeitpunkten lesen
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Die Schleife läuft einmal alle <n> Sekunden immer zum gleichen Zeitpunkt.
 | 
				
			||||||
 | 
					    Die Nutzlast sollte demnach weniger als eine Sekunde Laufzeit haben.
 | 
				
			||||||
 | 
					    Falls durch eine außergewöhnliche Situation doch einmal mehr als eine
 | 
				
			||||||
 | 
					    Sekunde benötigt werden sollte, gleicht sich das in den darauffolgenden
 | 
				
			||||||
 | 
					    Durchläufen wieder aus.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    setthreadtitle("datareader")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Speicherpfad für Meßwertdaten
 | 
				
			||||||
 | 
					    if not os.path.exists(histpath):
 | 
				
			||||||
 | 
					        os.makedirs(histpath)
 | 
				
			||||||
 | 
					    history.basepath = histpath
 | 
				
			||||||
 | 
					    # Serien initialisieren
 | 
				
			||||||
 | 
					    history.addSeries("BMP280-75", 336 ,75)
 | 
				
			||||||
 | 
					    history.addSeries("BMP280-150", 336 , 150)
 | 
				
			||||||
 | 
					    history.addSeries("BMP280-300", 336 , 300)
 | 
				
			||||||
 | 
					    history.addSeries("BMP280-600", 336 , 600)
 | 
				
			||||||
 | 
					    history.addSeries("BMP280-900", 336 , 900)
 | 
				
			||||||
 | 
					    for s in history.series.values():
 | 
				
			||||||
 | 
					        s.begin()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def g_tick(n=1):
 | 
				
			||||||
 | 
					        t = time.time()
 | 
				
			||||||
 | 
					        count = 0
 | 
				
			||||||
 | 
					        while True:
 | 
				
			||||||
 | 
					            count += n
 | 
				
			||||||
 | 
					            yield max(t + count - time.time(), 0)
 | 
				
			||||||
 | 
					    g = g_tick(1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    n = 0
 | 
				
			||||||
 | 
					    while not shutdown:
 | 
				
			||||||
 | 
					        time.sleep(next(g))
 | 
				
			||||||
 | 
					        # BME280 abfragen
 | 
				
			||||||
 | 
					        if cfg['bme280']:
 | 
				
			||||||
 | 
					            sensordata = bme280.sample(smbus, cfg['bme280_address'], cfg['bme280_cp'])
 | 
				
			||||||
 | 
					            # Aktuellen Wert merken
 | 
				
			||||||
 | 
					            boatdata.setValue("xdrTemp", sensordata.temperature)
 | 
				
			||||||
 | 
					            boatdata.setValue("xdrPress", sensordata.pressure)
 | 
				
			||||||
 | 
					            boatdata.setValue("xdrHum", sensordata.humidity)
 | 
				
			||||||
 | 
					            # Historie schreiben
 | 
				
			||||||
 | 
					            pval = int(sensordata.pressure *10)
 | 
				
			||||||
 | 
					            for k, v in history.series.items():
 | 
				
			||||||
 | 
					                if n % k == 0:
 | 
				
			||||||
 | 
					                    v.add(pval)
 | 
				
			||||||
 | 
					        # Lokales GPS abfragen
 | 
				
			||||||
 | 
					        # TODO
 | 
				
			||||||
 | 
					        if cfg['gps']:
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					        n += 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for s in history.series.values():
 | 
				
			||||||
 | 
					        s.finish()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Frontend(Gtk.Window):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    button = {
 | 
				
			||||||
 | 
					        1: (75, 485),
 | 
				
			||||||
 | 
					        2: (150, 492),
 | 
				
			||||||
 | 
					        3: (227, 496),
 | 
				
			||||||
 | 
					        4: (306, 496),
 | 
				
			||||||
 | 
					        5: (382, 492),
 | 
				
			||||||
 | 
					        6: (459, 485)
 | 
				
			||||||
 | 
					    }    
 | 
				
			||||||
 | 
					    radius = 30
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, device, boatdata, profile):
 | 
				
			||||||
 | 
					        super().__init__()
 | 
				
			||||||
 | 
					        self.owndev = device
 | 
				
			||||||
 | 
					        self.boatdata = boatdata
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.connect("destroy", self.on_destroy)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.set_position(Gtk.WindowPosition.CENTER)
 | 
				
			||||||
 | 
					        self.set_size_request(530, 555)
 | 
				
			||||||
 | 
					        self.set_title("OBP60 virt")
 | 
				
			||||||
 | 
					        self.set_app_paintable(True)
 | 
				
			||||||
 | 
					        self.set_decorated(False)
 | 
				
			||||||
 | 
					        self.set_keep_above(True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.screen = self.get_screen()
 | 
				
			||||||
 | 
					        self.visual = self.screen.get_rgba_visual()
 | 
				
			||||||
 | 
					        if (self.visual is not None and self.screen.is_composited()):
 | 
				
			||||||
 | 
					             self.set_visual(self.visual)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        handle = Rsvg.Handle()
 | 
				
			||||||
 | 
					        self._svg = handle.new_from_file(os.path.join(sys.path[0], "obp60.svg"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.connect("draw", self.on_draw)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.da = Gtk.DrawingArea()
 | 
				
			||||||
 | 
					        self.da.add_events(Gdk.EventMask.BUTTON_PRESS_MASK|Gdk.EventMask.BUTTON_RELEASE_MASK)
 | 
				
			||||||
 | 
					        self.add(self.da)
 | 
				
			||||||
 | 
					        self.da.connect("draw", self.da_draw)
 | 
				
			||||||
 | 
					        self.da.connect('button-press-event', self.da_button_press)
 | 
				
			||||||
 | 
					        self.da.connect('button-release-event', self.da_button_release)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.button_clicked = 0 # Geklickter Button vor Loslassen
 | 
				
			||||||
 | 
					        self.keylock = False
 | 
				
			||||||
 | 
					        self.pages = profile
 | 
				
			||||||
 | 
					        self.pageno = 1
 | 
				
			||||||
 | 
					        self.curpage = self.pages[self.pageno]
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        print("Wische von 2 nach 1 für Programmende")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def run(self):
 | 
				
			||||||
 | 
					        GLib.timeout_add_seconds(2, self.on_timer)
 | 
				
			||||||
 | 
					        self.show_all()
 | 
				
			||||||
 | 
					        Gtk.main()
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					    def on_timer(self):
 | 
				
			||||||
 | 
					        # Boatdata validator
 | 
				
			||||||
 | 
					        boatdata.updateValid(5)
 | 
				
			||||||
 | 
					        # Tastaturstatus an Seite durchreichen
 | 
				
			||||||
 | 
					        self.curpage.keylock = self.keylock
 | 
				
			||||||
 | 
					        # Neuzeichnen
 | 
				
			||||||
 | 
					        self.queue_draw()
 | 
				
			||||||
 | 
					        return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def on_draw(self, widget, ctx):
 | 
				
			||||||
 | 
					        # Fenstertransparenz
 | 
				
			||||||
 | 
					        ctx.set_source_rgba(0, 0, 0, 0)
 | 
				
			||||||
 | 
					        ctx.paint()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def da_draw(self, widget, ctx):
 | 
				
			||||||
 | 
					        viewport = Rsvg.Rectangle()
 | 
				
			||||||
 | 
					        viewport.x = 0
 | 
				
			||||||
 | 
					        viewport.y = 0
 | 
				
			||||||
 | 
					        viewport.width = 530
 | 
				
			||||||
 | 
					        viewport.height = 555
 | 
				
			||||||
 | 
					        self._svg.render_document(ctx, viewport)
 | 
				
			||||||
 | 
					        ctx.set_source_rgb(1.0, 0, 0)
 | 
				
			||||||
 | 
					        ctx.translate(64, 95) # Koordinatenursprung auf virtuellen Displaybereich setzen
 | 
				
			||||||
 | 
					        ctx.rectangle(0, 0, 400, 300)
 | 
				
			||||||
 | 
					        ctx.clip()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ctx.set_source_rgb(0, 0, 0) # Schwarz auf Weiß
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Heartbeat umschalten
 | 
				
			||||||
 | 
					        if self.curpage.header:
 | 
				
			||||||
 | 
					            self.curpage.draw_header(ctx)
 | 
				
			||||||
 | 
					        self.curpage.draw(ctx)
 | 
				
			||||||
 | 
					        if self.curpage.footer:
 | 
				
			||||||
 | 
					            self.curpage.draw_footer(ctx)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def da_button_press(self, widget, event):
 | 
				
			||||||
 | 
					        # Es gibt eine Liste mit Objekten und hier wird
 | 
				
			||||||
 | 
					        # geprüft ob und in welches Objekt geklickt wurde
 | 
				
			||||||
 | 
					        # Die eigentliche Funktion wird beim Loslassen ausgelöst.
 | 
				
			||||||
 | 
					        # Damit sind Wischgesten simulierbar
 | 
				
			||||||
 | 
					        self.button_clicked = 0
 | 
				
			||||||
 | 
					        if (event.x < self.button[1][0] - self.radius or event.x > self.button[6][0] + self.radius):
 | 
				
			||||||
 | 
					           return True
 | 
				
			||||||
 | 
					        if (event.y < self.button[1][1] - self.radius or event.y > self.button[3][1] + self.radius):
 | 
				
			||||||
 | 
					           return True
 | 
				
			||||||
 | 
					        for b, v in self.button.items():
 | 
				
			||||||
 | 
					            diff = math.sqrt((event.x - v[0])**2 + (event.y - v[1])**2)
 | 
				
			||||||
 | 
					            if diff < self.radius:
 | 
				
			||||||
 | 
					                self.button_clicked = b
 | 
				
			||||||
 | 
					                break
 | 
				
			||||||
 | 
					        return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def da_button_release(self, widget, event):
 | 
				
			||||||
 | 
					        # Hier sind die eigentlichen Tastenfunktionen
 | 
				
			||||||
 | 
					        #
 | 
				
			||||||
 | 
					        # Die Auswertung ist abhängig von der jew. angezeigten Seite
 | 
				
			||||||
 | 
					        # Jede Seite kann eine Methode "handle_key" implementieren
 | 
				
			||||||
 | 
					        # Falls der Rückgabewert "True" ist, hat die Seite die Taste
 | 
				
			||||||
 | 
					        # verarbeitet, die Funktion hier wird damit unterdrückt.
 | 
				
			||||||
 | 
					        # TODO
 | 
				
			||||||
 | 
					        if (event.x < self.button[1][0] - self.radius or event.x > self.button[6][0] + self.radius):
 | 
				
			||||||
 | 
					           return True
 | 
				
			||||||
 | 
					        if (event.y < self.button[1][1] - self.radius or event.y > self.button[3][1] + self.radius):
 | 
				
			||||||
 | 
					           return True
 | 
				
			||||||
 | 
					        selected = 0
 | 
				
			||||||
 | 
					        for b, v in self.button.items():
 | 
				
			||||||
 | 
					            diff = math.sqrt((event.x - v[0])**2 + (event.y - v[1])**2)
 | 
				
			||||||
 | 
					            if diff < self.radius:
 | 
				
			||||||
 | 
					                selected = b
 | 
				
			||||||
 | 
					                break
 | 
				
			||||||
 | 
					        if self.keylock:
 | 
				
			||||||
 | 
					            # Bei Tastensperre einzige Möglichkeit: Tastensperre ausschalten
 | 
				
			||||||
 | 
					            if selected == 6 and self.button_clicked == 1:
 | 
				
			||||||
 | 
					                self.keylock = False
 | 
				
			||||||
 | 
					            return True
 | 
				
			||||||
 | 
					        if selected == 1:
 | 
				
			||||||
 | 
					            if self.button_clicked == 2:
 | 
				
			||||||
 | 
					                # Programmende bei Klicken auf 2 und loslassen auf 1
 | 
				
			||||||
 | 
					                self.get_window().set_cursor(Gdk.Cursor(Gdk.CursorType.WATCH))
 | 
				
			||||||
 | 
					                Gtk.main_quit()
 | 
				
			||||||
 | 
					            elif self.button_clicked == 6:
 | 
				
			||||||
 | 
					                # Klick auf 6 und loslassen auf 1 ist Tastatursperre
 | 
				
			||||||
 | 
					                self.keylock = True
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                self.curpage.handle_key(1)
 | 
				
			||||||
 | 
					        elif selected == 2:
 | 
				
			||||||
 | 
					            # Abbruch/Zurück
 | 
				
			||||||
 | 
					            self.curpage.handle_key(2)
 | 
				
			||||||
 | 
					        elif selected == 3:
 | 
				
			||||||
 | 
					            # runter / eine Seite vor
 | 
				
			||||||
 | 
					            if not self.curpage.handle_key(3):
 | 
				
			||||||
 | 
					                if self.pageno > 1:
 | 
				
			||||||
 | 
					                    self.pageno -= 1
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    self.pageno = len(self.pages) - 1
 | 
				
			||||||
 | 
					                self.curpage = self.pages[self.pageno]
 | 
				
			||||||
 | 
					        elif selected == 4:
 | 
				
			||||||
 | 
					            if not self.curpage.handle_key(4):
 | 
				
			||||||
 | 
					                if self.pageno < len(self.pages) - 1:
 | 
				
			||||||
 | 
					                    self.pageno += 1
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    self.pageno = 1
 | 
				
			||||||
 | 
					                # hoch / eine Seite zurück
 | 
				
			||||||
 | 
					                self.curpage = self.pages[self.pageno]
 | 
				
			||||||
 | 
					        elif selected == 5:
 | 
				
			||||||
 | 
					            # Ok/Menü
 | 
				
			||||||
 | 
					            self.curpage.handle_key(5)
 | 
				
			||||||
 | 
					        elif selected == 6:
 | 
				
			||||||
 | 
					            if self.button_clicked == 6:
 | 
				
			||||||
 | 
					                # Backlight on/off
 | 
				
			||||||
 | 
					                self.curpage.backlight = not self.curpage.backlight
 | 
				
			||||||
 | 
					            elif self.button_clicked == 5:
 | 
				
			||||||
 | 
					                # Umschalten zur Systemseite
 | 
				
			||||||
 | 
					                self.curpage = self.pages[0]
 | 
				
			||||||
 | 
					        return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def on_destroy(self, widget):
 | 
				
			||||||
 | 
					        Gtk.main_quit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def init_profile(config, cfg, boatdata):
 | 
				
			||||||
 | 
					    '''
 | 
				
			||||||
 | 
					    config: Configparser-Objekt
 | 
				
			||||||
 | 
					    cfg: Laufende Programmkonfiguration
 | 
				
			||||||
 | 
					    Die Liste und Anordnung der Seiten nennen wir "Profil"
 | 
				
			||||||
 | 
					    Seiten-Profil aus Konfiguration erstellen
 | 
				
			||||||
 | 
					    Seite Nummer 0 ist immer die Systemseite. Diese ist nicht
 | 
				
			||||||
 | 
					    über die normale Seitenreihenfolge erreichbar, sondern
 | 
				
			||||||
 | 
					    durch eine spezielle Tastenkombination/Geste.
 | 
				
			||||||
 | 
					    TODO Prüfungen einbauen: 
 | 
				
			||||||
 | 
					      Fortlaufende Seitennummern ab 1
 | 
				
			||||||
 | 
					      Fortlaufende Wertenummern ab 1
 | 
				
			||||||
 | 
					      Maximalwerte nicht überschreiten
 | 
				
			||||||
 | 
					    '''
 | 
				
			||||||
 | 
					    pages_max = config.getint('pages', 'number_of_pages')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Suche alle Abschnitte, die mit "page" beginnen
 | 
				
			||||||
 | 
					    sects = config.sections()
 | 
				
			||||||
 | 
					    sects.remove('pages')
 | 
				
			||||||
 | 
					    pagedef = {}
 | 
				
			||||||
 | 
					    n = 0
 | 
				
			||||||
 | 
					    for s in sects:
 | 
				
			||||||
 | 
					        if s.startswith('page'):
 | 
				
			||||||
 | 
					            # Nummer und Art ermitteln
 | 
				
			||||||
 | 
					            pageno = int(s[4:])
 | 
				
			||||||
 | 
					            pagedef[pageno] = {'type': config.get(s, "type")}
 | 
				
			||||||
 | 
					            # Hole ein bin maximal 4 Werte je Seite 
 | 
				
			||||||
 | 
					            values = {}
 | 
				
			||||||
 | 
					            valno = 1
 | 
				
			||||||
 | 
					            for i in (1, 2, 3, 4):
 | 
				
			||||||
 | 
					                try:
 | 
				
			||||||
 | 
					                    values[i] = config.get(s, f"value{i}")
 | 
				
			||||||
 | 
					                except configparser.NoOptionError:
 | 
				
			||||||
 | 
					                    break
 | 
				
			||||||
 | 
					            pagedef[pageno]['values'] = values
 | 
				
			||||||
 | 
					            n += 1
 | 
				
			||||||
 | 
					            if n >= pages_max:
 | 
				
			||||||
 | 
					                break
 | 
				
			||||||
 | 
					    clist = {
 | 
				
			||||||
 | 
					        0: pages.System(0, cfg, boatdata)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    for i, p in pagedef.items():
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            cls = getattr(pages, p['type'])
 | 
				
			||||||
 | 
					        except AttributeError:
 | 
				
			||||||
 | 
					            # Klasse nicht vorhanden, Seite wird nicht benutzt
 | 
				
			||||||
 | 
					            print(f"Klasse '{type}' nicht gefunden")
 | 
				
			||||||
 | 
					            continue
 | 
				
			||||||
 | 
					        c = cls(i, cfg, boatdata, *[v for v in p['values'].values()])
 | 
				
			||||||
 | 
					        clist[i] = c
 | 
				
			||||||
 | 
					    return clist
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ == "__main__":
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #setproctitle("obp60v")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    shutdown = False
 | 
				
			||||||
 | 
					    owndevice = Device(100)
 | 
				
			||||||
 | 
					    boatdata = BoatData()
 | 
				
			||||||
 | 
					    boatdata.addTank(0)
 | 
				
			||||||
 | 
					    boatdata.addEngine(0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Basiskonfiguration aus Datei lesen
 | 
				
			||||||
 | 
					    config = configparser.ConfigParser()
 | 
				
			||||||
 | 
					    config.read(os.path.join(sys.path[0], cfg['cfgfile']))
 | 
				
			||||||
 | 
					    cfg['deviceid'] = config.getint('system', 'deviceid')
 | 
				
			||||||
 | 
					    cfg['simulation'] = config.getboolean('system', 'simulation')
 | 
				
			||||||
 | 
					    cfg['histpath'] = os.path.expanduser(config.get('system', 'histpath'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    cfg['gps'] = config.getboolean('gps', 'enabled')
 | 
				
			||||||
 | 
					    if cfg['gps']:
 | 
				
			||||||
 | 
					        cfg['gps_port'] = config.get('gps', 'port')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    cfg['bme280'] = config.getboolean('bme280', 'enabled')
 | 
				
			||||||
 | 
					    if cfg['bme280']:
 | 
				
			||||||
 | 
					        cfg['bme280_port'] = config.getint('bme280', 'port')
 | 
				
			||||||
 | 
					        cfg['bme280_address'] = int(config.get('bme280', 'address'), 16) # convert 0x76
 | 
				
			||||||
 | 
					        smbus = smbus2.SMBus(cfg['bme280_port'])
 | 
				
			||||||
 | 
					        cfg['bme280_cp'] = bme280.load_calibration_params(smbus, cfg['bme280_address'])
 | 
				
			||||||
 | 
					        history = History("press", 75)
 | 
				
			||||||
 | 
					        boatdata.addHistory(history, "press")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    cfg['ocpn_port'] = config.get('opencpn', 'port')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if cfg['simulation']:
 | 
				
			||||||
 | 
					        boatdata.enableSimulation()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    profile = init_profile(config, cfg, boatdata)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    t_rxd_n2k = threading.Thread(target=rxd_n2k)
 | 
				
			||||||
 | 
					    t_rxd_n2k.start()
 | 
				
			||||||
 | 
					    t_rxd_0183 = threading.Thread(target=rxd_0183, args=(cfg['ocpn_port'],))
 | 
				
			||||||
 | 
					    t_rxd_0183.start()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    t_data = threading.Thread(target=datareader, args=(cfg['histpath'], history))
 | 
				
			||||||
 | 
					    t_data.start()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    app = Frontend(owndevice, boatdata, profile)
 | 
				
			||||||
 | 
					    app.run()
 | 
				
			||||||
 | 
					    shutdown = True
 | 
				
			||||||
 | 
					    t_rxd_n2k.join()
 | 
				
			||||||
 | 
					    t_rxd_0183.join()
 | 
				
			||||||
 | 
					    print("Another fine product of the Sirius Cybernetics Corporation.")
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,106 @@
 | 
				
			||||||
 | 
					<?xml version="1.0" encoding="UTF-8" standalone="no"?>
 | 
				
			||||||
 | 
					<!-- Created with Inkscape (http://www.inkscape.org/) -->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<svg
 | 
				
			||||||
 | 
					   width="529.258"
 | 
				
			||||||
 | 
					   height="554.04303"
 | 
				
			||||||
 | 
					   viewBox="0 0 140.03284 146.59055"
 | 
				
			||||||
 | 
					   version="1.1"
 | 
				
			||||||
 | 
					   id="svg5"
 | 
				
			||||||
 | 
					   xml:space="preserve"
 | 
				
			||||||
 | 
					   inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
 | 
				
			||||||
 | 
					   sodipodi:docname="obp60.svg"
 | 
				
			||||||
 | 
					   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
 | 
				
			||||||
 | 
					   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
 | 
				
			||||||
 | 
					   xmlns:xlink="http://www.w3.org/1999/xlink"
 | 
				
			||||||
 | 
					   xmlns="http://www.w3.org/2000/svg"
 | 
				
			||||||
 | 
					   xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
 | 
				
			||||||
 | 
					     id="namedview7"
 | 
				
			||||||
 | 
					     pagecolor="#ffffff"
 | 
				
			||||||
 | 
					     bordercolor="#666666"
 | 
				
			||||||
 | 
					     borderopacity="1.0"
 | 
				
			||||||
 | 
					     inkscape:showpageshadow="2"
 | 
				
			||||||
 | 
					     inkscape:pageopacity="0.0"
 | 
				
			||||||
 | 
					     inkscape:pagecheckerboard="0"
 | 
				
			||||||
 | 
					     inkscape:deskcolor="#d1d1d1"
 | 
				
			||||||
 | 
					     inkscape:document-units="px"
 | 
				
			||||||
 | 
					     showgrid="false"
 | 
				
			||||||
 | 
					     inkscape:zoom="0.84096521"
 | 
				
			||||||
 | 
					     inkscape:cx="394.78446"
 | 
				
			||||||
 | 
					     inkscape:cy="291.92646"
 | 
				
			||||||
 | 
					     inkscape:current-layer="layer2" /><defs
 | 
				
			||||||
 | 
					     id="defs2" /><g
 | 
				
			||||||
 | 
					     inkscape:groupmode="layer"
 | 
				
			||||||
 | 
					     id="layer2"
 | 
				
			||||||
 | 
					     inkscape:label="Rahmen"
 | 
				
			||||||
 | 
					     style="display:inline"><rect
 | 
				
			||||||
 | 
					       style="fill:#383838;fill-opacity:0.995886;stroke:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
 | 
				
			||||||
 | 
					       id="rect5580"
 | 
				
			||||||
 | 
					       width="111.65156"
 | 
				
			||||||
 | 
					       height="101.83302"
 | 
				
			||||||
 | 
					       x="13.757235"
 | 
				
			||||||
 | 
					       y="13.865232" /><circle
 | 
				
			||||||
 | 
					       style="fill:#5f6d9b;fill-opacity:1;stroke:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
 | 
				
			||||||
 | 
					       id="path5526"
 | 
				
			||||||
 | 
					       cx="24.958796"
 | 
				
			||||||
 | 
					       cy="18.318338"
 | 
				
			||||||
 | 
					       r="1.1906251" /><rect
 | 
				
			||||||
 | 
					       style="fill:#dcdcdc;fill-opacity:1;stroke:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
 | 
				
			||||||
 | 
					       id="rect6475"
 | 
				
			||||||
 | 
					       width="105.56875"
 | 
				
			||||||
 | 
					       height="79.11042"
 | 
				
			||||||
 | 
					       x="16.723654"
 | 
				
			||||||
 | 
					       y="24.941565" /><path
 | 
				
			||||||
 | 
					       id="rect376"
 | 
				
			||||||
 | 
					       style="fill:#a2a2a2;fill-opacity:1;stroke:#000000;stroke-width:0.529167;stroke-linecap:round;stroke-linejoin:round"
 | 
				
			||||||
 | 
					       d="m 4.9387167,0.2645835 c -2.5895378,0 -4.6741332,2.0296907 -4.6741332,4.5511433 V 135.78447 c 0,2.52146 2.527424,4.27506 4.6741332,4.55166 0,0 36.5284973,6.49111 65.5148633,5.95881 28.986364,-0.5323 64.64049,-5.95881 64.64049,-5.95881 2.76316,-0.44318 4.67414,-2.0302 4.67414,-4.55166 V 4.8157268 c 0,-2.5214526 -2.0846,-4.5511433 -4.67414,-4.5511433 z M 13.857552,13.984676 H 125.45952 v 97.921664 c 0,0 -37.032483,3.56413 -55.632775,3.56413 -18.600293,0 -55.969193,-3.56413 -55.969193,-3.56413 z" /><g
 | 
				
			||||||
 | 
					       id="g11099"
 | 
				
			||||||
 | 
					       transform="translate(-5.4497963,-2.865458)"><g
 | 
				
			||||||
 | 
					         id="g11079"><circle
 | 
				
			||||||
 | 
					           style="fill:#2a2a2a;fill-opacity:1;stroke:#000000;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
 | 
				
			||||||
 | 
					           id="path8373"
 | 
				
			||||||
 | 
					           cx="24.618317"
 | 
				
			||||||
 | 
					           cy="130.36757"
 | 
				
			||||||
 | 
					           r="7.9375" /><path
 | 
				
			||||||
 | 
					           sodipodi:type="star"
 | 
				
			||||||
 | 
					           style="fill:#525252;fill-opacity:1;stroke:#000000;stroke-width:0.616746;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
 | 
				
			||||||
 | 
					           id="path9879"
 | 
				
			||||||
 | 
					           inkscape:flatsided="true"
 | 
				
			||||||
 | 
					           sodipodi:sides="6"
 | 
				
			||||||
 | 
					           sodipodi:cx="16.355543"
 | 
				
			||||||
 | 
					           sodipodi:cy="150.80168"
 | 
				
			||||||
 | 
					           sodipodi:r1="7.745985"
 | 
				
			||||||
 | 
					           sodipodi:r2="4.8291588"
 | 
				
			||||||
 | 
					           sodipodi:arg1="1.0332539"
 | 
				
			||||||
 | 
					           sodipodi:arg2="1.5568527"
 | 
				
			||||||
 | 
					           inkscape:rounded="0"
 | 
				
			||||||
 | 
					           inkscape:randomized="0"
 | 
				
			||||||
 | 
					           d="m 20.321693,157.45525 -7.745232,0.108 -3.9661499,-6.65356 3.7790819,-6.76157 7.745232,-0.10801 3.96615,6.65357 z"
 | 
				
			||||||
 | 
					           inkscape:transform-center-x="-0.093238378"
 | 
				
			||||||
 | 
					           inkscape:transform-center-y="-0.22711284"
 | 
				
			||||||
 | 
					           transform="matrix(0.4296957,0,0,0.42830241,17.59041,65.778845)" /></g><use
 | 
				
			||||||
 | 
					         x="0"
 | 
				
			||||||
 | 
					         y="0"
 | 
				
			||||||
 | 
					         xlink:href="#g11079"
 | 
				
			||||||
 | 
					         id="use11081"
 | 
				
			||||||
 | 
					         transform="rotate(-15.936104,42.162291,58.466326)" /><use
 | 
				
			||||||
 | 
					         x="0"
 | 
				
			||||||
 | 
					         y="0"
 | 
				
			||||||
 | 
					         xlink:href="#g11079"
 | 
				
			||||||
 | 
					         id="use11083"
 | 
				
			||||||
 | 
					         transform="rotate(-5.080918,80.937544,-325.73636)" /><use
 | 
				
			||||||
 | 
					         x="0"
 | 
				
			||||||
 | 
					         y="0"
 | 
				
			||||||
 | 
					         xlink:href="#g11079"
 | 
				
			||||||
 | 
					         id="use11085"
 | 
				
			||||||
 | 
					         transform="rotate(17.398182,44.604565,331.50397)" /><use
 | 
				
			||||||
 | 
					         x="0"
 | 
				
			||||||
 | 
					         y="0"
 | 
				
			||||||
 | 
					         xlink:href="#g11079"
 | 
				
			||||||
 | 
					         id="use11087"
 | 
				
			||||||
 | 
					         transform="translate(81.407112,2.126153)" /><use
 | 
				
			||||||
 | 
					         x="0"
 | 
				
			||||||
 | 
					         y="0"
 | 
				
			||||||
 | 
					         xlink:href="#g11079"
 | 
				
			||||||
 | 
					         id="use11089"
 | 
				
			||||||
 | 
					         transform="rotate(-15.814908,75.67043,-235.37704)" /></g></g></svg>
 | 
				
			||||||
| 
		 After Width: | Height: | Size: 4.6 KiB  | 
| 
						 | 
					@ -0,0 +1,36 @@
 | 
				
			||||||
 | 
					# Displayseiten
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from .system import System
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Generische Seiten
 | 
				
			||||||
 | 
					from .onevalue import OneValue
 | 
				
			||||||
 | 
					from .twovalues import TwoValues
 | 
				
			||||||
 | 
					from .threevalues import ThreeValues
 | 
				
			||||||
 | 
					from .fourvalues import FourValues
 | 
				
			||||||
 | 
					from .fourvalues2 import FourValues2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Graphen
 | 
				
			||||||
 | 
					from .onegraph import OneGraph
 | 
				
			||||||
 | 
					from .twographs import TwoGraphs
 | 
				
			||||||
 | 
					from .exhaust import Exhaust
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Analoginstrumente
 | 
				
			||||||
 | 
					from .clock import Clock
 | 
				
			||||||
 | 
					from .fluid import Fluid
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Spezialseiten
 | 
				
			||||||
 | 
					from .anchor import Anchor
 | 
				
			||||||
 | 
					from .apparentwind import ApparentWind
 | 
				
			||||||
 | 
					from .autobahn import Autobahn
 | 
				
			||||||
 | 
					from .barograph import Barograph
 | 
				
			||||||
 | 
					from .battery import Battery
 | 
				
			||||||
 | 
					from .battery2 import Battery2
 | 
				
			||||||
 | 
					from .bme280 import BME280
 | 
				
			||||||
 | 
					from .dst810 import DST810
 | 
				
			||||||
 | 
					from .keel import Keel
 | 
				
			||||||
 | 
					from .rollpitch import RollPitch
 | 
				
			||||||
 | 
					from .skyview import SkyView
 | 
				
			||||||
 | 
					from .solar import Solar
 | 
				
			||||||
 | 
					from .rudder import Rudder
 | 
				
			||||||
 | 
					from .voltage import Voltage
 | 
				
			||||||
 | 
					from .windrose import WindRose
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,52 @@
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Ankerinfo / -alarm
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					import cairo
 | 
				
			||||||
 | 
					import math
 | 
				
			||||||
 | 
					from .page import Page
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Anchor(Page):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, pageno, cfg, boatdata):
 | 
				
			||||||
 | 
					        super().__init__(pageno, cfg, boatdata)
 | 
				
			||||||
 | 
					        self.sym_anchor = cairo.ImageSurface.create_from_png(os.path.join(cfg['imgpath'], "anchor.png"))
 | 
				
			||||||
 | 
					        self.buttonlabel[1] = 'DEC'
 | 
				
			||||||
 | 
					        self.buttonlabel[2] = 'INC'
 | 
				
			||||||
 | 
					        self.buttonlabel[5] = 'SET'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def draw(self, ctx):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Name
 | 
				
			||||||
 | 
					        ctx.select_font_face("Ubuntu", cairo.FontSlant.NORMAL, cairo.FontWeight.BOLD)
 | 
				
			||||||
 | 
					        ctx.set_font_size(20) 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ctx.move_to(2, 50)
 | 
				
			||||||
 | 
					        ctx.show_text("Anchor")
 | 
				
			||||||
 | 
					        ctx.move_to(320, 50)
 | 
				
			||||||
 | 
					        ctx.show_text("Chain")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ctx.set_font_size(16)
 | 
				
			||||||
 | 
					        ctx.move_to(2, 70)
 | 
				
			||||||
 | 
					        ctx.show_text("Alarm: off")
 | 
				
			||||||
 | 
					        ctx.move_to(320, 70)
 | 
				
			||||||
 | 
					        ctx.show_text("45 m")
 | 
				
			||||||
 | 
					        ctx.stroke()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Spezialseite
 | 
				
			||||||
 | 
					        cx = 200
 | 
				
			||||||
 | 
					        cy = 150
 | 
				
			||||||
 | 
					        r = 125
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ctx.set_line_width(1.5)
 | 
				
			||||||
 | 
					        ctx.arc(cx, cy, r, 0, 2*math.pi)
 | 
				
			||||||
 | 
					        ctx.stroke()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ctx.save()
 | 
				
			||||||
 | 
					        ctx.set_source_surface(self.sym_anchor, cx-8, cy-8)
 | 
				
			||||||
 | 
					        ctx.paint()
 | 
				
			||||||
 | 
					        ctx.restore()
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,16 @@
 | 
				
			||||||
 | 
					import cairo
 | 
				
			||||||
 | 
					import math
 | 
				
			||||||
 | 
					from .page import Page
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ApparentWind(Page):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, pageno, cfg, boatdata):
 | 
				
			||||||
 | 
					        super().__init__(pageno, cfg, boatdata)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def draw(self, ctx):
 | 
				
			||||||
 | 
					        # Name
 | 
				
			||||||
 | 
					        ctx.select_font_face("Ubuntu", cairo.FontSlant.NORMAL, cairo.FontWeight.BOLD)
 | 
				
			||||||
 | 
					        ctx.set_font_size(60) 
 | 
				
			||||||
 | 
					        ctx.move_to(20, 100)
 | 
				
			||||||
 | 
					        ctx.show_text("Apparent Wind")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,102 @@
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					3D-View angelehnt an die NASA Clipper GPS-Darstellung
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					import cairo
 | 
				
			||||||
 | 
					import math
 | 
				
			||||||
 | 
					from .page import Page
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Autobahn(Page):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, pageno, cfg, boatdata):
 | 
				
			||||||
 | 
					        super().__init__(pageno, cfg, boatdata)
 | 
				
			||||||
 | 
					        self.xte = self.bd.getRef("XTE")
 | 
				
			||||||
 | 
					        self.cog = self.bd.getRef("COG")
 | 
				
			||||||
 | 
					        self.btw = self.bd.getRef("BTW")
 | 
				
			||||||
 | 
					        self.dtw = self.bd.getRef("DTW")
 | 
				
			||||||
 | 
					        self.wpname = "no data"
 | 
				
			||||||
 | 
					        self.symbol = cairo.ImageSurface.create_from_png(os.path.join(cfg['imgpath'], "ship.png"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def draw(self, ctx):
 | 
				
			||||||
 | 
					        # Beschriftung unter den Werten
 | 
				
			||||||
 | 
					        ctx.select_font_face("Ubuntu", cairo.FontSlant.NORMAL, cairo.FontWeight.BOLD)
 | 
				
			||||||
 | 
					        ctx.set_font_size(16) 
 | 
				
			||||||
 | 
					        ctx.move_to(50, 188);
 | 
				
			||||||
 | 
					        ctx.show_text("Cross-track error")
 | 
				
			||||||
 | 
					        ctx.move_to(270, 188)
 | 
				
			||||||
 | 
					        ctx.show_text("Track")
 | 
				
			||||||
 | 
					        ctx.move_to(45, 275);
 | 
				
			||||||
 | 
					        ctx.show_text("Distance to waypoint")
 | 
				
			||||||
 | 
					        ctx.move_to(260, 275);
 | 
				
			||||||
 | 
					        ctx.show_text("Bearing")
 | 
				
			||||||
 | 
					        ctx.stroke()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Meßwerte
 | 
				
			||||||
 | 
					        ctx.select_font_face("DSEG7 Classic")
 | 
				
			||||||
 | 
					        ctx.set_font_size(60)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ctx.move_to(40, 170)
 | 
				
			||||||
 | 
					        #ctx.show_text(self.xte.format())
 | 
				
			||||||
 | 
					        ctx.show_text("2.3")
 | 
				
			||||||
 | 
					        ctx.move_to(220, 170)
 | 
				
			||||||
 | 
					        #ctx.show_text(self.cog.format())
 | 
				
			||||||
 | 
					        ctx.show_text("253")
 | 
				
			||||||
 | 
					        ctx.move_to(40, 257)
 | 
				
			||||||
 | 
					        #ctx.show_text(self.dtw.format())
 | 
				
			||||||
 | 
					        ctx.show_text("5.8")
 | 
				
			||||||
 | 
					        ctx.move_to(220, 257)
 | 
				
			||||||
 | 
					        #ctx.show_text(self.btw.format())
 | 
				
			||||||
 | 
					        ctx.show_text("248")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # 3D-Ansicht oben
 | 
				
			||||||
 | 
					        # TODO Schiffssymbol
 | 
				
			||||||
 | 
					        ctx.save()
 | 
				
			||||||
 | 
					        ctx.set_source_surface(self.symbol, 186, 68)
 | 
				
			||||||
 | 
					        ctx.paint()
 | 
				
			||||||
 | 
					        ctx.restore()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Segmente: 2 1 0   3 4 5
 | 
				
			||||||
 | 
					        seg = [True] * 6
 | 
				
			||||||
 | 
					        points = {
 | 
				
			||||||
 | 
					            2: ((0, 54), (46, 24), (75, 24), (0, 90)),
 | 
				
			||||||
 | 
					            1: ((0, 100), (82, 24), (112, 24), (50, 100)),
 | 
				
			||||||
 | 
					            0: ((60, 100), (117, 24), (147, 24), (110, 100)),
 | 
				
			||||||
 | 
					            3: ((340, 100), (283, 24), (253, 24), (290, 100)),
 | 
				
			||||||
 | 
					            4: ((399, 100), (318, 24), (289, 24), (350, 100)),
 | 
				
			||||||
 | 
					            5: ((399, 54), (354, 24), (325, 24), (399, 90))
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Winkeldifferenz
 | 
				
			||||||
 | 
					        diff = (self.cog.value or 0) - (self.btw.value or 0)
 | 
				
			||||||
 | 
					        if diff < -180:
 | 
				
			||||||
 | 
					            diff += 360
 | 
				
			||||||
 | 
					        elif diff > 180:
 | 
				
			||||||
 | 
					            diff -= 360
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if diff > 0:
 | 
				
			||||||
 | 
					            order = (3, 4, 5, 0, 1, 2)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            order = (0, 1, 2, 3, 4, 5)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Anzahl aktiver Segmente
 | 
				
			||||||
 | 
					        seg_step = math.radians(3)
 | 
				
			||||||
 | 
					        nseg = min(abs(diff) / seg_step, 5)
 | 
				
			||||||
 | 
					        i = 0
 | 
				
			||||||
 | 
					        while nseg > 0:
 | 
				
			||||||
 | 
					            seg[order[i]] = False
 | 
				
			||||||
 | 
					            i += 1
 | 
				
			||||||
 | 
					            nseg -= 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Segmente zeichnen
 | 
				
			||||||
 | 
					        for p in range(6):
 | 
				
			||||||
 | 
					            ctx.move_to(*points[p][0])
 | 
				
			||||||
 | 
					            ctx.line_to(*points[p][1])
 | 
				
			||||||
 | 
					            ctx.line_to(*points[p][2])
 | 
				
			||||||
 | 
					            ctx.line_to(*points[p][3])
 | 
				
			||||||
 | 
					            if seg[p]:
 | 
				
			||||||
 | 
					                ctx.fill()
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                ctx.stroke()
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,366 @@
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Siehe auch: Steamrock Digital Barometer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Meßwert alls 15 Minuten: 
 | 
				
			||||||
 | 
					  Es wird in hPa gemessen mit einer Nachkommastelle
 | 
				
			||||||
 | 
					  84 Stunden * 4 Werte je Stunde = 336 Meßwerte
 | 
				
			||||||
 | 
					Tendenzwert über 3 Stunden
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Je Zoomstufe wird ein eigener Buffer vorgehalten um ein sauberes
 | 
				
			||||||
 | 
					Diagramm zu erhalten. Überall gilt: Je Pixel ein Meßwert.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Drucktendenz:
 | 
				
			||||||
 | 
					  - 1 hour tendency
 | 
				
			||||||
 | 
					  - 3 hour tendency
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Verschiedene Datenquellen auswählbar:
 | 
				
			||||||
 | 
					- intern (BME280, BMP280)
 | 
				
			||||||
 | 
					- N2K generisch
 | 
				
			||||||
 | 
					- Historie von 
 | 
				
			||||||
 | 
					    - Yacht devices
 | 
				
			||||||
 | 
					    - Capteurs?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Das Diagramm wird mit Ursprung rechts unten (x0, y0) gezeichnet,
 | 
				
			||||||
 | 
					da die Werte in der Vergangenhait liegen, also links vom Ursprung.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Damit eine saubere Skala auf der Y-Achse erreicht wird, gibt einige
 | 
				
			||||||
 | 
					feste Skalierungen.
 | 
				
			||||||
 | 
					Standard: 20hPa von unten nach oben, z.B. 1015, 1020, 1025, 1030, 1035
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
 | 
					import cairo
 | 
				
			||||||
 | 
					from .page import Page
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Barograph(Page):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, pageno, cfg, boatdata):
 | 
				
			||||||
 | 
					        super().__init__(pageno, cfg, boatdata)
 | 
				
			||||||
 | 
					        # Meßwert alle 15 Minuten: 
 | 
				
			||||||
 | 
					        # 84 Stunden * 4 Werte je Stunde = 336 Meßwerte
 | 
				
			||||||
 | 
					        self.bd = boatdata
 | 
				
			||||||
 | 
					        self.source = 'I' # (I)ntern, e(X)tern
 | 
				
			||||||
 | 
					        self.zoom = (1, 2, 3, 6, 12)
 | 
				
			||||||
 | 
					        self.zoomindex = 4
 | 
				
			||||||
 | 
					        self.series = (75, 150, 300, 600, 900)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Y-Axis
 | 
				
			||||||
 | 
					        self.vmin = 0
 | 
				
			||||||
 | 
					        self.vmax = 0
 | 
				
			||||||
 | 
					        self.scalemin = 1000
 | 
				
			||||||
 | 
					        self.scalemax = 1020
 | 
				
			||||||
 | 
					        self.scalestep = 5
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Tendenzwert über 3 Stunden
 | 
				
			||||||
 | 
					        self.hist3 = None
 | 
				
			||||||
 | 
					        self.hist1 = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.buttonlabel[1] = '+'
 | 
				
			||||||
 | 
					        self.buttonlabel[2] = '-'
 | 
				
			||||||
 | 
					        self.buttonlabel[5] = 'SRC'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.refresh = time.time() - 30
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def handle_key(self, buttonid):
 | 
				
			||||||
 | 
					        # TODO Serie auswählen aufgrund Zoomlevel
 | 
				
			||||||
 | 
					        if buttonid == 1:
 | 
				
			||||||
 | 
					            # Zoom in
 | 
				
			||||||
 | 
					            if self.zoomindex > 0:
 | 
				
			||||||
 | 
					                self.zoomindex -= 1
 | 
				
			||||||
 | 
					            self.refresh = time.time() - 30
 | 
				
			||||||
 | 
					        elif buttonid == 2:
 | 
				
			||||||
 | 
					            # Zoom out
 | 
				
			||||||
 | 
					            if self.zoomindex <  len(self.zoom) - 1:
 | 
				
			||||||
 | 
					                self.zoomindex += 1
 | 
				
			||||||
 | 
					            self.refresh = time.time() - 30
 | 
				
			||||||
 | 
					        if buttonid == 5:
 | 
				
			||||||
 | 
					            # Source
 | 
				
			||||||
 | 
					            if self.source == 'I':
 | 
				
			||||||
 | 
					                self.source = 'X'
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                self.source = 'I'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Testausgabe der Datenerfassung
 | 
				
			||||||
 | 
					            data = []
 | 
				
			||||||
 | 
					            vmin = data[0]
 | 
				
			||||||
 | 
					            vmax = data[0]
 | 
				
			||||||
 | 
					            i = self.series[self.zoomindex]
 | 
				
			||||||
 | 
					            for value in self.bd.history['press'].series[i].get():
 | 
				
			||||||
 | 
					                v = value / 10
 | 
				
			||||||
 | 
					                data.append(v)
 | 
				
			||||||
 | 
					                if v < vmin and v != 0:
 | 
				
			||||||
 | 
					                    vmin = v
 | 
				
			||||||
 | 
					                elif v > vmax and v != 0:
 | 
				
			||||||
 | 
					                    vmax = v
 | 
				
			||||||
 | 
					            print(f"Werte: vmin={vmin}, vmax={vmax}")
 | 
				
			||||||
 | 
					            ymin, ymax, step = self.getYScale(vmin, vmax)
 | 
				
			||||||
 | 
					            print(f"Skala: ymin={ymin}, ymax={ymax}, step={step}")
 | 
				
			||||||
 | 
					            print(f"zoomindex={self.zoomindex}, series={self.series[self.zoomindex]}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            hist1a = self.bd.history['press'].series[i].getvalue(3600)
 | 
				
			||||||
 | 
					            hist1b = self.bd.history['press'].series[i].getvalue3(3600)
 | 
				
			||||||
 | 
					            trend1 = data[0] - hist1b
 | 
				
			||||||
 | 
					            print(f"{hist1a} / {hist1b} -> Trend1: {trend1:.1f}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def loadData(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Transfer data from history to page buffer
 | 
				
			||||||
 | 
					        set y-axis according to data
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.data = []
 | 
				
			||||||
 | 
					        self.vmin = 9999
 | 
				
			||||||
 | 
					        self.vmax = 0
 | 
				
			||||||
 | 
					        i = self.series[self.zoomindex]
 | 
				
			||||||
 | 
					        for value in self.bd.history['press'].series[i].get():
 | 
				
			||||||
 | 
					            v = value / 10
 | 
				
			||||||
 | 
					            self.data.append(v)
 | 
				
			||||||
 | 
					            if v < self.vmin and v != 0:
 | 
				
			||||||
 | 
					                self.vmin = v
 | 
				
			||||||
 | 
					            elif v > self.vmax and v != 0:
 | 
				
			||||||
 | 
					                self.vmax = v
 | 
				
			||||||
 | 
					        self.scalemin, self.scalemax, self.scalestep = self.getYScale(self.vmin, self.vmax)
 | 
				
			||||||
 | 
					        return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def drawTrend(self, ctx, code, x, y, w):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        One hour Trend
 | 
				
			||||||
 | 
					        0: Stationary            <= 1 hPa
 | 
				
			||||||
 | 
					        1: Rising                >1 and <= 2 hPa
 | 
				
			||||||
 | 
					        2: Rising fast           >2 and <= 3 hPa
 | 
				
			||||||
 | 
					        3: Rising very fast      >3 hPa
 | 
				
			||||||
 | 
					        -1: Falling
 | 
				
			||||||
 | 
					        -2: Falling fast
 | 
				
			||||||
 | 
					        -3: Falling very fast       
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        trend1map = {
 | 
				
			||||||
 | 
					            -3: "Falling_Very_Fast.png",    # > 3 hPa
 | 
				
			||||||
 | 
					            -2: "Falling_Fast.png",         # > 2 and <= 3 hPa
 | 
				
			||||||
 | 
					            -1: "Falling.png",              # > 1 and <= 2 hPa
 | 
				
			||||||
 | 
					             0: "Stationary.png",           # <= +/- 1 hPa
 | 
				
			||||||
 | 
					             1: "Rising.png",               # < -1 and >= -2 hPa
 | 
				
			||||||
 | 
					             2: "Rising_Fast.png",          # < -2 and >= -3 hPa
 | 
				
			||||||
 | 
					             3: "Rising_Very_Fast.png"      # < -3 hPa
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if code == 0:
 | 
				
			||||||
 | 
					            # Pfeil horizontal rechts
 | 
				
			||||||
 | 
					            ctx.move_to(x, y - w / 2)
 | 
				
			||||||
 | 
					            ctx.line_to(x + w, y - w / 2)
 | 
				
			||||||
 | 
					            ctx.draw()
 | 
				
			||||||
 | 
					            # Position merken
 | 
				
			||||||
 | 
					            ctx.line_to(x - w / 4, y - w)
 | 
				
			||||||
 | 
					            ctx.line_to(x - w / 4, y)
 | 
				
			||||||
 | 
					            ctx.line_to(x + w, y - w / 2)
 | 
				
			||||||
 | 
					            ctx.fill()
 | 
				
			||||||
 | 
					        elif code == 1:
 | 
				
			||||||
 | 
					            # Pfeil schräg nach oben
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					        elif code == 2:
 | 
				
			||||||
 | 
					            # Pfeil gerade nach oben
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					        elif code == 3:
 | 
				
			||||||
 | 
					            # Doppelpfeil nach oben
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					        elif code == -1:
 | 
				
			||||||
 | 
					            # Pfeil schräg nach unten
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					        elif code == -2:
 | 
				
			||||||
 | 
					            # Pfeil gerade nach unten
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					        elif code == -3:
 | 
				
			||||||
 | 
					            # Doppelpfeil nach unten
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def drawWMOCode(self, ctx, code, x, y, w):
 | 
				
			||||||
 | 
					       """
 | 
				
			||||||
 | 
					       Three hour code
 | 
				
			||||||
 | 
					       Code 0 to 8:
 | 
				
			||||||
 | 
					           0: Increasing, then decreasing; athmospheric pressure the same 
 | 
				
			||||||
 | 
					              as or higher than three hours ago
 | 
				
			||||||
 | 
					           1: Increasing then steady; or increasing, then increasing more 
 | 
				
			||||||
 | 
					              slowly; athmospheric pressure now higher than three hours ago
 | 
				
			||||||
 | 
					           2: Increasing (steadily or unsteadily); athmospheric pressure
 | 
				
			||||||
 | 
					              now higher than three hours ago
 | 
				
			||||||
 | 
					           3: Decreasing or steady, then increasing; or increasing then 
 | 
				
			||||||
 | 
					              increasing more rapidly; athmospheric pressure now higher
 | 
				
			||||||
 | 
					              than three hours ago
 | 
				
			||||||
 | 
					           4: Steady; athmospheric pressure is the same as three hours ago
 | 
				
			||||||
 | 
					           5: Decreasing, then increasing; athmospheric pressure now is the 
 | 
				
			||||||
 | 
					              same as or lower than three hours ago
 | 
				
			||||||
 | 
					           6: 
 | 
				
			||||||
 | 
					           7:
 | 
				
			||||||
 | 
					           8:
 | 
				
			||||||
 | 
					       """
 | 
				
			||||||
 | 
					       pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def getYScale(self, vmin, vmax):
 | 
				
			||||||
 | 
					        # Y-Achse aufgrund Meßwerten einstellen
 | 
				
			||||||
 | 
					        diff = vmax - vmin
 | 
				
			||||||
 | 
					        if diff < 20:
 | 
				
			||||||
 | 
					            step = 5
 | 
				
			||||||
 | 
					        elif diff <= 40:
 | 
				
			||||||
 | 
					            step = 10
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            step = 15
 | 
				
			||||||
 | 
					        vmin = int(vmin - (vmin % step))        # Nächstes Vielfaches nach oben
 | 
				
			||||||
 | 
					        vmax = int(vmax + step - (vmax % step)) # Nächstes Vielfaches nach unten 
 | 
				
			||||||
 | 
					        return (vmin, vmax, step)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def draw(self, ctx):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Darstellung angelehnt an klassisches Gerät
 | 
				
			||||||
 | 
					        Daten werden im nichtflüchtigen Speicher gehalten
 | 
				
			||||||
 | 
					        Da sich die Daten langsam verändern, reicht es, diese z.B. nur alle
 | 
				
			||||||
 | 
					        30 Sekunden oder langsamer zu laden.
 | 
				
			||||||
 | 
					        Der aktuelle Wert oben ist natürlich nicht alt.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Datenreihen 
 | 
				
			||||||
 | 
					        - 1 Woche, stündlich: 7 * 24 = 168 Meßwerte
 | 
				
			||||||
 | 
					        - 1 Tag, alle 10 min: 24 * 6 = 144 Meßwerte
 | 
				
			||||||
 | 
					        Der Druck wird in zwei Bytes abgespeichert. Es wird eine Nachkommastelle
 | 
				
			||||||
 | 
					        verwendet. Um ohne Fließkommazahlen auszukommen wird der Maßwert einfach
 | 
				
			||||||
 | 
					        mit 10 multipliziert.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Darstellung wie Steamrock:
 | 
				
			||||||
 | 
					        1 Pixel entspricht einem Meßwert alle 15min.
 | 
				
			||||||
 | 
					        1 Tag hat dementsprechend eine Breite von 48px
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        timestamp = time.time()
 | 
				
			||||||
 | 
					        if timestamp - self.refresh >= 30:
 | 
				
			||||||
 | 
					            self.refresh = timestamp
 | 
				
			||||||
 | 
					            self.loadData()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ctx.set_source_rgb(0, 0, 0)
 | 
				
			||||||
 | 
					        ctx.select_font_face("Ubuntu", cairo.FontSlant.NORMAL, cairo.FontWeight.BOLD)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Datenquelle rechts oben
 | 
				
			||||||
 | 
					        ctx.set_font_size(16)
 | 
				
			||||||
 | 
					        ctx.move_to(330, 50)
 | 
				
			||||||
 | 
					        if self.source == 'I':
 | 
				
			||||||
 | 
					            ctx.show_text("BMP280")
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            ctx.show_text("N2K Bus")
 | 
				
			||||||
 | 
					        ctx.stroke()
 | 
				
			||||||
 | 
					        # Zoomstufe
 | 
				
			||||||
 | 
					        datastep = self.series[self.zoomindex]
 | 
				
			||||||
 | 
					        if datastep > 120:
 | 
				
			||||||
 | 
					            if datastep % 60 == 0:
 | 
				
			||||||
 | 
					                fmt = "{:.0f} min"
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                fmt = "{:.1f} min"
 | 
				
			||||||
 | 
					            datastep /= 60
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            fmt = '{} s'
 | 
				
			||||||
 | 
					        self.draw_text_center(ctx, 360, 62, fmt.format(datastep))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Aktueller Luftdruck hPa
 | 
				
			||||||
 | 
					        ctx.set_font_size(32)
 | 
				
			||||||
 | 
					        self.draw_text_center(ctx, 200, 40, self.bd.pressure.format())
 | 
				
			||||||
 | 
					        #self.draw_text_center(ctx, 200, 40, "1019.2")
 | 
				
			||||||
 | 
					        ctx.set_font_size(16) 
 | 
				
			||||||
 | 
					        self.draw_text_center(ctx, 200, 62, "hPa")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Trend
 | 
				
			||||||
 | 
					        ctx.set_font_size(16)
 | 
				
			||||||
 | 
					        # TODO Trend linie
 | 
				
			||||||
 | 
					        #trend = 
 | 
				
			||||||
 | 
					        self.draw_text_center(ctx, 295, 62, "0.0")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # min/max
 | 
				
			||||||
 | 
					        ctx.move_to(10, 38)
 | 
				
			||||||
 | 
					        ctx.show_text(f"min: {self.vmin}")
 | 
				
			||||||
 | 
					        ctx.move_to(10, 50)
 | 
				
			||||||
 | 
					        ctx.show_text(f"max: {self.vmax}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Alarm
 | 
				
			||||||
 | 
					        self.draw_text_center(ctx, 70, 62, "Alarm Off")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Hintergrundrahmen
 | 
				
			||||||
 | 
					        ctx.set_line_width(2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ctx.move_to(0, 75)
 | 
				
			||||||
 | 
					        ctx.line_to(400, 75)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ctx.move_to(130, 20)
 | 
				
			||||||
 | 
					        ctx.line_to(130, 75)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ctx.move_to(270, 20)
 | 
				
			||||||
 | 
					        ctx.line_to(270, 75)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ctx.move_to(325, 20)
 | 
				
			||||||
 | 
					        ctx.line_to(325, 75)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ctx.stroke()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Diagramm
 | 
				
			||||||
 | 
					        # --------
 | 
				
			||||||
 | 
					        ymin = self.scalemin
 | 
				
			||||||
 | 
					        ymax = self.scalemax
 | 
				
			||||||
 | 
					        yn = self.scalestep
 | 
				
			||||||
 | 
					        ystep = (ymax - ymin) / yn
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        xstep = 48
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Ursprung ist rechts unten
 | 
				
			||||||
 | 
					        x0 = 350
 | 
				
			||||||
 | 
					        y0 = 270
 | 
				
			||||||
 | 
					        w = 7 * 48
 | 
				
			||||||
 | 
					        h = 180
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ctx.set_line_width(1)
 | 
				
			||||||
 | 
					        ctx.rectangle(x0 - w + 0.5, y0 - h + 0.5, w, h)
 | 
				
			||||||
 | 
					        ctx.stroke()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # X-Achse sind Stunden
 | 
				
			||||||
 | 
					        xn = 0
 | 
				
			||||||
 | 
					        for xt in [x * -1 * self.zoom[self.zoomindex] for x in range(1,7)]:
 | 
				
			||||||
 | 
					            xn += 1
 | 
				
			||||||
 | 
					            ctx.move_to(x0 - xn * xstep + 0.5, y0)
 | 
				
			||||||
 | 
					            ctx.line_to(x0 - xn * xstep + 0.5, y0 - h)
 | 
				
			||||||
 | 
					            ctx.stroke()
 | 
				
			||||||
 | 
					            self.draw_text_center(ctx, x0 - xn * xstep + 0.5, y0 - 8, str(xt), fill=True)
 | 
				
			||||||
 | 
					        ctx.stroke()
 | 
				
			||||||
 | 
					        #for x in (1, 2, 3, 4, 5, 6):
 | 
				
			||||||
 | 
					        #    ctx.move_to(x0 - x * 48 + 0.5, y0 + 0.5)
 | 
				
			||||||
 | 
					        #    ctx.line_to(x0 - x * 48 + 0.5, y0 - h + 0.5)
 | 
				
			||||||
 | 
					        #ctx.stroke()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Y-Achse
 | 
				
			||||||
 | 
					        ctx.move_to(x0 + 5.5, y0 + 0.5)
 | 
				
			||||||
 | 
					        ctx.line_to(x0 + 5.5, y0 - h)
 | 
				
			||||||
 | 
					        ctx.move_to(x0 - w - 5.5, y0 + 0.5)
 | 
				
			||||||
 | 
					        ctx.line_to(x0 - w - 5.5, y0 -h )
 | 
				
			||||||
 | 
					        ctx.stroke()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        dy = 9 # Pixel je hPa
 | 
				
			||||||
 | 
					        ysmin = self.scalemin
 | 
				
			||||||
 | 
					        ysmax = self.scalemax
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        y = y0 + 0.5
 | 
				
			||||||
 | 
					        ystep = self.scalestep
 | 
				
			||||||
 | 
					        ys = ysmin
 | 
				
			||||||
 | 
					        while y >= y0 - h:
 | 
				
			||||||
 | 
					            if ys % ystep == 0:
 | 
				
			||||||
 | 
					                ctx.move_to(x0 + 10, y + 5.5)
 | 
				
			||||||
 | 
					                ctx.show_text(str(ys))
 | 
				
			||||||
 | 
					                ctx.move_to(x0 - w - 5, y)
 | 
				
			||||||
 | 
					                ctx.line_to(x0 + 5, y)
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                ctx.move_to(x0, y)
 | 
				
			||||||
 | 
					                ctx.line_to(x0 + 5, y)
 | 
				
			||||||
 | 
					                ctx.move_to(x0 - w - 5, y)
 | 
				
			||||||
 | 
					                ctx.line_to(x0 - w, y)
 | 
				
			||||||
 | 
					            y -= dy
 | 
				
			||||||
 | 
					            ys += 1
 | 
				
			||||||
 | 
					        ctx.stroke()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Meßdaten
 | 
				
			||||||
 | 
					        for v in self.data:
 | 
				
			||||||
 | 
					            x0 -= 1
 | 
				
			||||||
 | 
					            if v > 0:
 | 
				
			||||||
 | 
					                ctx.rectangle(x0, y0 - (v - ysmin) * dy, 1.5, 1.5)
 | 
				
			||||||
 | 
					        ctx.fill()
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,66 @@
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Batteriewerte eines INA219 oder INA226 Sensors
 | 
				
			||||||
 | 
					Ähnlich ThreeValue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cairo
 | 
				
			||||||
 | 
					from .page import Page
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Battery(Page):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    avg = (1, 10, 60, 300);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, pageno, cfg, boatdata):
 | 
				
			||||||
 | 
					        super().__init__(pageno, cfg, boatdata)
 | 
				
			||||||
 | 
					        self.avgindex = 0
 | 
				
			||||||
 | 
					        self.buttonlabel[1] = 'AVG'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def handle_key(self, buttonid):
 | 
				
			||||||
 | 
					        if buttonid == 1:
 | 
				
			||||||
 | 
					            if self.avgindex < len(self.avg) -1:
 | 
				
			||||||
 | 
					                self.avgindex += 1
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                self.avgindex = 0
 | 
				
			||||||
 | 
					            return True
 | 
				
			||||||
 | 
					        return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def draw(self, ctx):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Aufteilung in 3 Bereiche durch 2 Linien
 | 
				
			||||||
 | 
					        ctx.rectangle(0, 105, 400, 3);
 | 
				
			||||||
 | 
					        ctx.rectangle(0, 195, 400, 3);
 | 
				
			||||||
 | 
					        ctx.fill()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Name
 | 
				
			||||||
 | 
					        ctx.select_font_face("Ubuntu", cairo.FontSlant.NORMAL, cairo.FontWeight.BOLD)
 | 
				
			||||||
 | 
					        ctx.set_font_size(40) 
 | 
				
			||||||
 | 
					        ctx.move_to(20, 55)
 | 
				
			||||||
 | 
					        ctx.show_text("VBat")
 | 
				
			||||||
 | 
					        ctx.move_to(20, 145)
 | 
				
			||||||
 | 
					        ctx.show_text("IBat")
 | 
				
			||||||
 | 
					        ctx.move_to(20, 235)
 | 
				
			||||||
 | 
					        ctx.show_text("PBat")
 | 
				
			||||||
 | 
					        ctx.stroke()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Einheit
 | 
				
			||||||
 | 
					        ctx.set_font_size(24) 
 | 
				
			||||||
 | 
					        ctx.move_to(20, 90)
 | 
				
			||||||
 | 
					        ctx.show_text("V")
 | 
				
			||||||
 | 
					        ctx.move_to(20, 180)
 | 
				
			||||||
 | 
					        ctx.show_text("A")
 | 
				
			||||||
 | 
					        ctx.move_to(20, 270)
 | 
				
			||||||
 | 
					        ctx.show_text("W")
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Werte
 | 
				
			||||||
 | 
					        ctx.select_font_face("DSEG7 Classic")
 | 
				
			||||||
 | 
					        ctx.set_font_size(60)
 | 
				
			||||||
 | 
					        ctx.move_to(180, 90)
 | 
				
			||||||
 | 
					        ctx.show_text("12.3")
 | 
				
			||||||
 | 
					        ctx.move_to(180, 180)
 | 
				
			||||||
 | 
					        ctx.show_text("3.2")
 | 
				
			||||||
 | 
					        ctx.move_to(180, 270)
 | 
				
			||||||
 | 
					        ctx.show_text("39.4")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,90 @@
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Komplexe Batterieübersichtsseite
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cairo
 | 
				
			||||||
 | 
					from .page import Page
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Battery2(Page):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def draw_battery(self, ctx, x, y, w, h, level):
 | 
				
			||||||
 | 
					        '''
 | 
				
			||||||
 | 
					        Das Rechteck ist das komplett umschließende
 | 
				
			||||||
 | 
					        Level ist der prozentuale Füllstand
 | 
				
			||||||
 | 
					        '''
 | 
				
			||||||
 | 
					        pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        '''
 | 
				
			||||||
 | 
					        // Battery graphic with fill level
 | 
				
			||||||
 | 
					        void batteryGraphic(uint x, uint y, float percent, int pcolor, int bcolor){
 | 
				
			||||||
 | 
					            // Show battery
 | 
				
			||||||
 | 
					                int xb = x;     // X position
 | 
				
			||||||
 | 
					                int yb = y;     // Y position
 | 
				
			||||||
 | 
					                int t = 4;      // Line thickness
 | 
				
			||||||
 | 
					                // Percent limits
 | 
				
			||||||
 | 
					                if(percent < 0){
 | 
				
			||||||
 | 
					                    percent = 0;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                 if(percent > 99){
 | 
				
			||||||
 | 
					                    percent = 99;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                // Battery corpus 100x80 with fill level
 | 
				
			||||||
 | 
					                int level = int((100.0 - percent) * (80-(2*t)) / 100.0);
 | 
				
			||||||
 | 
					                getdisplay().fillRect(xb, yb, 100, 80, pcolor);
 | 
				
			||||||
 | 
					                if(percent < 99){
 | 
				
			||||||
 | 
					                    getdisplay().fillRect(xb+t, yb+t, 100-(2*t), level, bcolor);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                // Plus pol 20x15
 | 
				
			||||||
 | 
					                int xp = xb + 20;
 | 
				
			||||||
 | 
					                int yp = yb - 15 + t;
 | 
				
			||||||
 | 
					                getdisplay().fillRect(xp, yp, 20, 15, pcolor);
 | 
				
			||||||
 | 
					                getdisplay().fillRect(xp+t, yp+t, 20-(2*t), 15-(2*t), bcolor);
 | 
				
			||||||
 | 
					                // Minus pol 20x15
 | 
				
			||||||
 | 
					                int xm = xb + 60;
 | 
				
			||||||
 | 
					                int ym = yb -15 + t;
 | 
				
			||||||
 | 
					                getdisplay().fillRect(xm, ym, 20, 15, pcolor);
 | 
				
			||||||
 | 
					                getdisplay().fillRect(xm+t, ym+t, 20-(2*t), 15-(2*t), bcolor);
 | 
				
			||||||
 | 
					        '''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def draw(self, ctx):
 | 
				
			||||||
 | 
					        ctx.select_font_face("Ubuntu", cairo.FontSlant.NORMAL, cairo.FontWeight.BOLD)
 | 
				
			||||||
 | 
					        ctx.set_font_size(40)
 | 
				
			||||||
 | 
					        ctx.move_to(10, 65)
 | 
				
			||||||
 | 
					        ctx.show_text("Bat.")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Batterietyp
 | 
				
			||||||
 | 
					        ctx.move_to(90, 65)
 | 
				
			||||||
 | 
					        ctx.show_text("AGM")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Kapazität
 | 
				
			||||||
 | 
					        ctx.move_to(10, 200)
 | 
				
			||||||
 | 
					        ctx.select_font_face("DSEG7 Classic")
 | 
				
			||||||
 | 
					        ctx.set_font_size(40)
 | 
				
			||||||
 | 
					        ctx.show_text("12")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ctx.select_font_face("Ubuntu", cairo.FontSlant.NORMAL, cairo.FontWeight.BOLD)
 | 
				
			||||||
 | 
					        ctx.set_font_size(16)
 | 
				
			||||||
 | 
					        ctx.show_text("Ah")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ctx.move_to(10, 235)
 | 
				
			||||||
 | 
					        ctx.show_text("Installed")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ctx.move_to(10, 255)
 | 
				
			||||||
 | 
					        ctx.show_text("Battery Type")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Batteriegraphik
 | 
				
			||||||
 | 
					        # Rechteck mit Füllstand 100x80, oben zwei Pole
 | 
				
			||||||
 | 
					        ctx.rectangle(150, 100, 100, 80)
 | 
				
			||||||
 | 
					        ctx.stroke()
 | 
				
			||||||
 | 
					        # Füllstand
 | 
				
			||||||
 | 
					        # Pole
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        '''
 | 
				
			||||||
 | 
					        ctx.line_to(2.5, 1.5)
 | 
				
			||||||
 | 
					       
 | 
				
			||||||
 | 
					        ctx.set_line_width(0.06)
 | 
				
			||||||
 | 
					        '''
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,55 @@
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Werte eines lokal angeschlossenen BME280/BMP280
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cairo
 | 
				
			||||||
 | 
					from .page import Page
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BME280(Page):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, pageno, cfg, boatdata):
 | 
				
			||||||
 | 
					        super().__init__(pageno, cfg, boatdata)
 | 
				
			||||||
 | 
					        #self.ref1 = self.bd.getRef(boatvalue1)
 | 
				
			||||||
 | 
					        #self.ref2 = self.bd.getRef(boatvalue2)
 | 
				
			||||||
 | 
					        #self.ref3 = self.bd.getRef(boatvalue3)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def draw(self, ctx):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Bildschirmunterteilung mit Linien
 | 
				
			||||||
 | 
					        ctx.rectangle(0, 105, 399, 3) 
 | 
				
			||||||
 | 
					        ctx.rectangle(0, 195, 399, 3) 
 | 
				
			||||||
 | 
					        ctx.fill()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Beschriftung
 | 
				
			||||||
 | 
					        ctx.select_font_face("Ubuntu", cairo.FontSlant.NORMAL, cairo.FontWeight.BOLD)
 | 
				
			||||||
 | 
					        ctx.set_font_size(40)
 | 
				
			||||||
 | 
					        # Titel
 | 
				
			||||||
 | 
					        ctx.move_to(20,55)
 | 
				
			||||||
 | 
					        ctx.show_text("Temp")
 | 
				
			||||||
 | 
					        ctx.move_to(20,145)
 | 
				
			||||||
 | 
					        ctx.show_text("Humid")
 | 
				
			||||||
 | 
					        ctx.move_to(20, 235)
 | 
				
			||||||
 | 
					        ctx.show_text("Press")
 | 
				
			||||||
 | 
					        # Einheit
 | 
				
			||||||
 | 
					        ctx.set_font_size(24)
 | 
				
			||||||
 | 
					        ctx.move_to(20, 90)
 | 
				
			||||||
 | 
					        ctx.show_text("Deg C")
 | 
				
			||||||
 | 
					        ctx.move_to(20, 180)
 | 
				
			||||||
 | 
					        ctx.show_text("%")
 | 
				
			||||||
 | 
					        ctx.move_to(20, 270)
 | 
				
			||||||
 | 
					        ctx.show_text("hPa")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Meßwerte
 | 
				
			||||||
 | 
					        ctx.select_font_face("DSEG7 Classic")
 | 
				
			||||||
 | 
					        ctx.set_font_size(60) 
 | 
				
			||||||
 | 
					        # Temperatur °C
 | 
				
			||||||
 | 
					        ctx.move_to(180, 90)
 | 
				
			||||||
 | 
					        ctx.show_text("{:.1f}".format(self.bd.temp_air))
 | 
				
			||||||
 | 
					        # Feuchte %
 | 
				
			||||||
 | 
					        ctx.move_to(180, 180)
 | 
				
			||||||
 | 
					        ctx.show_text("{}".format(int(self.bd.humidity)))
 | 
				
			||||||
 | 
					        # Luftdruck hPa
 | 
				
			||||||
 | 
					        ctx.move_to(180, 270)
 | 
				
			||||||
 | 
					        ctx.show_text("{}".format(int(self.bd.pressure)))
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,188 @@
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Uhr
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TODO: Zeitzone anzeigen. Abhängig von Lat, Lon
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 Es sollen verschiedene Modi unterstützt werden
 | 
				
			||||||
 | 
					 - Analoguhr
 | 
				
			||||||
 | 
					 - Digitaluhr
 | 
				
			||||||
 | 
					 - Regattauhr / -timer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cairo
 | 
				
			||||||
 | 
					import math
 | 
				
			||||||
 | 
					from .page import Page
 | 
				
			||||||
 | 
					from datetime import datetime
 | 
				
			||||||
 | 
					import astral
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Clock(Page):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, pageno, cfg, boatdata):
 | 
				
			||||||
 | 
					        super().__init__(pageno, cfg, boatdata)
 | 
				
			||||||
 | 
					        self.buttonlabel[1] = 'MODE'
 | 
				
			||||||
 | 
					        self.buttonlabel[2] = 'TZ'
 | 
				
			||||||
 | 
					        self.mode = ('A', 'D', 'T') # (A)nalog (D)igital (T)imer
 | 
				
			||||||
 | 
					        self.modeindex = 1
 | 
				
			||||||
 | 
					        self.utc = True
 | 
				
			||||||
 | 
					        self.location =  astral.Location(('Norderstedt', 'Germany', 53.710105, 10.0574378, 'UTC'))
 | 
				
			||||||
 | 
					        self.location.astral = astral.Astral()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def handle_key(self, buttonid):
 | 
				
			||||||
 | 
					        if buttonid == 1:
 | 
				
			||||||
 | 
					            if self.modeindex < len(self.mode):
 | 
				
			||||||
 | 
					                self.modeindex += 1
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                self.modeindex = 0
 | 
				
			||||||
 | 
					            return True
 | 
				
			||||||
 | 
					        if buttonid == 2:
 | 
				
			||||||
 | 
					            self.utc = not self.utc
 | 
				
			||||||
 | 
					            return True
 | 
				
			||||||
 | 
					        return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def draw(self, ctx):
 | 
				
			||||||
 | 
					        ctx.select_font_face("Ubuntu", cairo.FontSlant.NORMAL, cairo.FontWeight.BOLD)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # akuellen Modus anzeigen
 | 
				
			||||||
 | 
					        if mode[modeindex] == 'A':
 | 
				
			||||||
 | 
					            self.draw_analog(ctx)
 | 
				
			||||||
 | 
					        if mode[modeindex] == 'D':
 | 
				
			||||||
 | 
					            self.draw_digital(ctx)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            self.draw_timer(ctx)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def draw_digital(self, ctx):
 | 
				
			||||||
 | 
					        ctx.set_font_size(24)
 | 
				
			||||||
 | 
					        ctx.move_to(10, 220)
 | 
				
			||||||
 | 
					        ctx.show_text("Digital clock")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def draw_timer(self, ctx):
 | 
				
			||||||
 | 
					        ctx.set_font_size(24)
 | 
				
			||||||
 | 
					        ctx.move_to(10, 220)
 | 
				
			||||||
 | 
					        ctx.show_text("Timer")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def draw_analog(self, ctx):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ts = datetime.now()
 | 
				
			||||||
 | 
					        sunrise = self.location.sunrise(ts)
 | 
				
			||||||
 | 
					        sunset = self.location.sunset(ts)
 | 
				
			||||||
 | 
					        #print(sunrise)
 | 
				
			||||||
 | 
					        #print(sunset)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Datum und Uhrzeit
 | 
				
			||||||
 | 
					        # Sonnenaufgang
 | 
				
			||||||
 | 
					        # Sonnenuntergang
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Wochentag
 | 
				
			||||||
 | 
					        # ts.strftime('%a')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Werte in den Ecken der Uhr 
 | 
				
			||||||
 | 
					        ctx.set_font_size(24)
 | 
				
			||||||
 | 
					        ctx.move_to(10, 220)
 | 
				
			||||||
 | 
					        ctx.show_text("Time")
 | 
				
			||||||
 | 
					        ctx.move_to(10, 95)
 | 
				
			||||||
 | 
					        ctx.show_text("Date")
 | 
				
			||||||
 | 
					        ctx.move_to(335, 95)
 | 
				
			||||||
 | 
					        ctx.show_text("SunR")
 | 
				
			||||||
 | 
					        ctx.move_to(335, 220)
 | 
				
			||||||
 | 
					        ctx.show_text("SunS")
 | 
				
			||||||
 | 
					        ctx.stroke()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ctx.set_font_size(16)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ctx.move_to(10, 65)
 | 
				
			||||||
 | 
					        ctx.show_text(ts.strftime("%d.%m.%Y"))
 | 
				
			||||||
 | 
					        ctx.move_to(10, 250)
 | 
				
			||||||
 | 
					        ctx.show_text(ts.strftime("%H:%M"))
 | 
				
			||||||
 | 
					        ctx.move_to(335, 65)
 | 
				
			||||||
 | 
					        ctx.show_text(sunrise.strftime("%H:%M"))
 | 
				
			||||||
 | 
					        ctx.move_to(335, 250)
 | 
				
			||||||
 | 
					        ctx.show_text(sunset.strftime("%H:%M"))
 | 
				
			||||||
 | 
					        ctx.stroke()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Horizontal separators
 | 
				
			||||||
 | 
					        ctx.rectangle(0, 149, 60, 3)
 | 
				
			||||||
 | 
					        ctx.rectangle(340, 149, 60, 3)
 | 
				
			||||||
 | 
					        ctx.fill()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Uhr
 | 
				
			||||||
 | 
					        cx = 200
 | 
				
			||||||
 | 
					        cy = 150
 | 
				
			||||||
 | 
					        r = 110
 | 
				
			||||||
 | 
					        ctx.arc(cx, cy, r + 10, 0, 2*math.pi)
 | 
				
			||||||
 | 
					        ctx.arc(cx, cy, r + 7, 0, 2*math.pi)
 | 
				
			||||||
 | 
					        ctx.stroke()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ctx.set_font_size(20)
 | 
				
			||||||
 | 
					        self.draw_text_center(ctx, cx, cy-40, 'UTC' if self.utc else 'LOT')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ctx.select_font_face("Ubuntu", cairo.FontSlant.NORMAL, cairo.FontWeight.BOLD)
 | 
				
			||||||
 | 
					        ctx.set_font_size(24)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for i in range(360):
 | 
				
			||||||
 | 
					            x = cx + (r - 30) * math.sin(i/180*math.pi)
 | 
				
			||||||
 | 
					            y = cy - (r - 30) * math.cos(i/180*math.pi)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            char = ""
 | 
				
			||||||
 | 
					            if i == 0:
 | 
				
			||||||
 | 
					                char = "12"
 | 
				
			||||||
 | 
					            elif i == 90:
 | 
				
			||||||
 | 
					                char = "3"
 | 
				
			||||||
 | 
					            elif i == 180:
 | 
				
			||||||
 | 
					                char = "6"
 | 
				
			||||||
 | 
					            elif i == 270:
 | 
				
			||||||
 | 
					                char = "9"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if i % 90 == 0:
 | 
				
			||||||
 | 
					                #ctx.move_to(x, y)
 | 
				
			||||||
 | 
					                self.draw_text_center(ctx, x, y, char)
 | 
				
			||||||
 | 
					                #ctx.stroke()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            ctx.set_line_width(3.0)
 | 
				
			||||||
 | 
					            if i % 6 == 0:
 | 
				
			||||||
 | 
					                if i % 30 == 0:
 | 
				
			||||||
 | 
					                    x0 = cx + (r - 10) * math.sin(i/180*math.pi)
 | 
				
			||||||
 | 
					                    y0 = cy - (r - 10) * math.cos(i/180*math.pi)
 | 
				
			||||||
 | 
					                    x1 = cx + (r + 10) * math.sin(i/180*math.pi)
 | 
				
			||||||
 | 
					                    y1 = cy - (r + 10) * math.cos(i/180*math.pi)
 | 
				
			||||||
 | 
					                    ctx.move_to(x0, y0)
 | 
				
			||||||
 | 
					                    ctx.line_to(x1, y1)
 | 
				
			||||||
 | 
					                    ctx.stroke()
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    x = cx + r * math.sin(i/180*math.pi)
 | 
				
			||||||
 | 
					                    y = cy - r * math.cos(i/180*math.pi)
 | 
				
			||||||
 | 
					                    ctx.arc(x, y, 2, 0, 2*math.pi)         
 | 
				
			||||||
 | 
					                    ctx.fill()
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        # Stundenzeiger
 | 
				
			||||||
 | 
					        p = ((cx - 2, cy - (r - 50)), (cx + 2, cy - (r - 50)), (cx + 6, cy + 16), (cx - 6, cy + 16))
 | 
				
			||||||
 | 
					        angle_h = (ts.hour % 12  + ts.minute / 60) * 30
 | 
				
			||||||
 | 
					        zeiger = self.rotate((cx, cy), p, angle_h)
 | 
				
			||||||
 | 
					        ctx.move_to(*zeiger[0])
 | 
				
			||||||
 | 
					        for point in zeiger[1:]:
 | 
				
			||||||
 | 
					            ctx.line_to(*point)
 | 
				
			||||||
 | 
					        ctx.fill()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Minutenzeiger
 | 
				
			||||||
 | 
					        p = ((cx - 1, cy - (r - 15)), (cx + 1, cy - (r - 15)), (cx + 6, cy + 20), (cx - 6, cy + 20))
 | 
				
			||||||
 | 
					        angle_m = ts.minute * 6
 | 
				
			||||||
 | 
					        zeiger = self.rotate((cx, cy), p, angle_m)
 | 
				
			||||||
 | 
					        ctx.move_to(*zeiger[0])
 | 
				
			||||||
 | 
					        for point in zeiger[1:]:
 | 
				
			||||||
 | 
					            ctx.line_to(*point)
 | 
				
			||||||
 | 
					        ctx.fill()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Zentraler Kreis
 | 
				
			||||||
 | 
					        ctx.set_source_rgb(0, 0, 0)
 | 
				
			||||||
 | 
					        ctx.arc(cx, cy, 12, 0, 2*math.pi)
 | 
				
			||||||
 | 
					        ctx.fill()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Wozu dieses?
 | 
				
			||||||
 | 
					        ctx.set_source_rgb(0.86, 0.86, 0.86)
 | 
				
			||||||
 | 
					        ctx.arc(cx, cy, 10, 0, 2*math.pi)
 | 
				
			||||||
 | 
					        ctx.fill()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ctx.set_source_rgb(0, 0, 0)
 | 
				
			||||||
 | 
					        ctx.arc(cx, cy, 2, 0, 2*math.pi)
 | 
				
			||||||
 | 
					        ctx.fill()
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,40 @@
 | 
				
			||||||
 | 
					import cairo
 | 
				
			||||||
 | 
					from .page import Page
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DST810(Page):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # DBT, STW, Log, WTemp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def draw(self, ctx):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Layout
 | 
				
			||||||
 | 
					        ctx.rectangle(0, 105, 400, 3)
 | 
				
			||||||
 | 
					        ctx.rectangle(0, 195, 400, 3)
 | 
				
			||||||
 | 
					        ctx.rectangle(200, 195, 3, 75)
 | 
				
			||||||
 | 
					        ctx.fill()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ctx.select_font_face("Ubuntu", cairo.FontSlant.NORMAL, cairo.FontWeight.BOLD)
 | 
				
			||||||
 | 
					        ctx.set_font_size(40) 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # titel
 | 
				
			||||||
 | 
					        ctx.move_to(20, 55)
 | 
				
			||||||
 | 
					        ctx.show_text("Depth")
 | 
				
			||||||
 | 
					        ctx.move_to(20, 145)
 | 
				
			||||||
 | 
					        ctx.show_text("Speed")
 | 
				
			||||||
 | 
					        ctx.move_to(20, 220)
 | 
				
			||||||
 | 
					        ctx.show_text("Log")
 | 
				
			||||||
 | 
					        ctx.move_to(220, 220)
 | 
				
			||||||
 | 
					        ctx.show_text("Temp")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Einheiten
 | 
				
			||||||
 | 
					        ctx.set_font_size(24) 
 | 
				
			||||||
 | 
					        ctx.move_to(20, 90)
 | 
				
			||||||
 | 
					        ctx.show_text("m")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Meßwerte
 | 
				
			||||||
 | 
					        ctx.select_font_face("DSEG7 Classic")
 | 
				
			||||||
 | 
					        ctx.set_font_size(60)
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        ctx.move_to(180, 90)
 | 
				
			||||||
 | 
					        ctx.show_text("m")
 | 
				
			||||||
 | 
					        ctx.set_font_size(40)
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,58 @@
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					XY-Graphik der Abgastemperatur
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cairo
 | 
				
			||||||
 | 
					import math
 | 
				
			||||||
 | 
					from .page import Page
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Exhaust(Page):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def draw(self, ctx):
 | 
				
			||||||
 | 
					        # Title
 | 
				
			||||||
 | 
					        ctx.select_font_face("Ubuntu", cairo.FontSlant.NORMAL, cairo.FontWeight.BOLD)
 | 
				
			||||||
 | 
					        ctx.set_font_size(24)
 | 
				
			||||||
 | 
					        ctx.move_to(10, 45)
 | 
				
			||||||
 | 
					        ctx.show_text("Exhaust Temperature")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Graph anzeigen X/Y
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        x0 = 55
 | 
				
			||||||
 | 
					        y0 = 255
 | 
				
			||||||
 | 
					        w = 300
 | 
				
			||||||
 | 
					        h = 200
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # X-Achse
 | 
				
			||||||
 | 
					        ctx.move_to(x0 - 20, y0)
 | 
				
			||||||
 | 
					        ctx.line_to(x0 + w, y0)
 | 
				
			||||||
 | 
					        # Y-Achse
 | 
				
			||||||
 | 
					        ctx.move_to(x0, y0 + 20)
 | 
				
			||||||
 | 
					        ctx.line_to(x0, y0 - h)
 | 
				
			||||||
 | 
					        ctx.stroke()
 | 
				
			||||||
 | 
					        # Pfeispitze X
 | 
				
			||||||
 | 
					        ctx.move_to(x0-4, y0 - h + 12)
 | 
				
			||||||
 | 
					        ctx.line_to(x0, y0 - h)
 | 
				
			||||||
 | 
					        ctx.line_to(x0 + 4, y0 - h + 12)
 | 
				
			||||||
 | 
					        ctx.fill()
 | 
				
			||||||
 | 
					        # Pfeilspitze Y
 | 
				
			||||||
 | 
					        ctx.move_to(x0 + w -12, y0 - 4)
 | 
				
			||||||
 | 
					        ctx.line_to(x0 + w, y0)
 | 
				
			||||||
 | 
					        ctx.line_to(x0 + w - 12, y0 + 4)
 | 
				
			||||||
 | 
					        ctx.fill()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Achsenbeschriftung
 | 
				
			||||||
 | 
					        ctx.set_font_size(16)
 | 
				
			||||||
 | 
					        ctx.move_to(x0 - 30, y0 - h + 20)
 | 
				
			||||||
 | 
					        ctx.show_text("°C")
 | 
				
			||||||
 | 
					        ctx.move_to(x0 + w - 10, y0 + 15)
 | 
				
			||||||
 | 
					        ctx.show_text("min")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Hier wird eine Reihe von Meßwerten erwartet
 | 
				
			||||||
 | 
					        # Aufgrund min und max kann die Y-Achse skaliert werden
 | 
				
			||||||
 | 
					        # Die X-Achse ist die Zeit
 | 
				
			||||||
 | 
					        self.draw_text_center(ctx, x0 - 30, y0 - h / 2, "Temperature", True, False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Einzeichnen von zwei Warnschwellen als horizontale 
 | 
				
			||||||
 | 
					        # Linie (gestrichelt)
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,137 @@
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Füllstandsanzeige Tank
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    0: "Fuel",
 | 
				
			||||||
 | 
					    1: "Water",
 | 
				
			||||||
 | 
					    2: "Gray Water",
 | 
				
			||||||
 | 
					    3: "Live Well",
 | 
				
			||||||
 | 
					    4: "Oil",
 | 
				
			||||||
 | 
					    5: "Black Water",
 | 
				
			||||||
 | 
					    6: "Fuel Gasoline",
 | 
				
			||||||
 | 
					    14: "Error",
 | 
				
			||||||
 | 
					    15: "Unavailable"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					import cairo
 | 
				
			||||||
 | 
					import math
 | 
				
			||||||
 | 
					from .page import Page
 | 
				
			||||||
 | 
					import nmea2000.lookup
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Fluid(Page):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, pageno, cfg, boatdata, fluidtype):
 | 
				
			||||||
 | 
					        super().__init__(pageno, cfg, boatdata)
 | 
				
			||||||
 | 
					        self.fluidtype = int(fluidtype)
 | 
				
			||||||
 | 
					        if self.fluidtype == 0:
 | 
				
			||||||
 | 
					            self.symbol = cairo.ImageSurface.create_from_png(os.path.join(cfg['imgpath'], "fuelpump.png"))
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            self.symbol = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def draw(self, ctx):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Zentrum Instrument
 | 
				
			||||||
 | 
					        cx = 200
 | 
				
			||||||
 | 
					        cy = 150
 | 
				
			||||||
 | 
					        # Radius
 | 
				
			||||||
 | 
					        r = 110 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Füllstand von 0 - 100%
 | 
				
			||||||
 | 
					        # 0 = -120°, 100 = +120°
 | 
				
			||||||
 | 
					        level = self.bd.tank[0].volume or 0
 | 
				
			||||||
 | 
					        angle = -120 + level * 2.4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Rahmen
 | 
				
			||||||
 | 
					        ctx.set_source_rgb(*self.fgcolor)
 | 
				
			||||||
 | 
					        ctx.set_line_width(3)
 | 
				
			||||||
 | 
					        ctx.arc(cx, cy, r, 0, 2*math.pi)
 | 
				
			||||||
 | 
					        ctx.stroke()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Symbol, sofern vorhanden
 | 
				
			||||||
 | 
					        if self.symbol:
 | 
				
			||||||
 | 
					            ctx.save()
 | 
				
			||||||
 | 
					            ctx.set_source_surface(self.symbol, cx - 8, cy - 50)
 | 
				
			||||||
 | 
					            ctx.paint()
 | 
				
			||||||
 | 
					            ctx.restore()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Fluidtype
 | 
				
			||||||
 | 
					        ctx.select_font_face("Ubuntu", cairo.FontSlant.NORMAL, cairo.FontWeight.BOLD)
 | 
				
			||||||
 | 
					        ctx.set_font_size(24)
 | 
				
			||||||
 | 
					        ctx.move_to(20, 60)
 | 
				
			||||||
 | 
					        ctx.show_text(nmea2000.lookup.fluidtype[self.fluidtype])
 | 
				
			||||||
 | 
					        ctx.stroke()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Zeigerrahmen im Zentrum
 | 
				
			||||||
 | 
					        ctx.arc(cx, cy, 8, 0, 2*math.pi)
 | 
				
			||||||
 | 
					        ctx.stroke()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Zeiger in Nullposition
 | 
				
			||||||
 | 
					        # Variante 1, einseitig
 | 
				
			||||||
 | 
					        #p = ((cx - 1, cy - (r - 20)), (cx + 1, cy - (r - 20)), (cx + 4, cy), (cx - 4, cy))
 | 
				
			||||||
 | 
					        # Variante 2, überstehend
 | 
				
			||||||
 | 
					        p = ((cx - 1, cy - (r - 20)), (cx + 1, cy - (r - 20)), (cx + 6, cy + 15), (cx - 6, cy + 15))
 | 
				
			||||||
 | 
					        # Zeiger für aktuellen Meßwert
 | 
				
			||||||
 | 
					        zeiger = self.rotate((cx, cy), p, angle)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Zeiger zeichnen
 | 
				
			||||||
 | 
					        ctx.move_to(*zeiger[0])
 | 
				
			||||||
 | 
					        for point in zeiger[1:]:
 | 
				
			||||||
 | 
					            ctx.line_to(*point)
 | 
				
			||||||
 | 
					        ctx.fill()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Lösche das Zentrum heraus
 | 
				
			||||||
 | 
					        ctx.set_source_rgb(*self.bgcolor)
 | 
				
			||||||
 | 
					        ctx.arc(cx, cy, 6, 0, 2*math.pi)
 | 
				
			||||||
 | 
					        ctx.fill()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ctx.set_source_rgb(*self.fgcolor)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Simple Skala direkt zeichnen
 | 
				
			||||||
 | 
					        # 50%-Wert oben in der Mitte
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ctx.select_font_face("Ubuntu", cairo.FontSlant.NORMAL, cairo.FontWeight.BOLD)
 | 
				
			||||||
 | 
					        ctx.set_font_size(16)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ctx.move_to(cx, cy -r)
 | 
				
			||||||
 | 
					        ctx.line_to(cx, cy -r + 16)
 | 
				
			||||||
 | 
					        # linker Anschlag 0%
 | 
				
			||||||
 | 
					        self.draw_text_center(ctx, cx, cy - r  + 30, "1/2")
 | 
				
			||||||
 | 
					        l = self.rotate((cx, cy), ((cx, cy - r + 16), (cx, cy - r)), -120)
 | 
				
			||||||
 | 
					        ctx.move_to(*l[0])
 | 
				
			||||||
 | 
					        ctx.line_to(*l[1])
 | 
				
			||||||
 | 
					        # rechter Anschlag 100%
 | 
				
			||||||
 | 
					        l = self.rotate((cx, cy), ((cx, cy - r + 16), (cx, cy - r)), 120)
 | 
				
			||||||
 | 
					        ctx.move_to(*l[0])
 | 
				
			||||||
 | 
					        ctx.line_to(*l[1])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # 25%
 | 
				
			||||||
 | 
					        l = self.rotate((cx, cy), ((cx, cy - r + 16), (cx, cy - r)), -60)
 | 
				
			||||||
 | 
					        ctx.move_to(*l[0])
 | 
				
			||||||
 | 
					        ctx.line_to(*l[1])
 | 
				
			||||||
 | 
					        tx, ty = self.rotate((cx, cy), ((cx, cy - r + 30),), -60)[0]
 | 
				
			||||||
 | 
					        self.draw_text_center(ctx, tx, ty, "1/4")
 | 
				
			||||||
 | 
					        # 75%
 | 
				
			||||||
 | 
					        l = self.rotate((cx, cy), ((cx, cy - r + 16), (cx, cy - r)), 60)
 | 
				
			||||||
 | 
					        ctx.move_to(*l[0])
 | 
				
			||||||
 | 
					        ctx.line_to(*l[1])
 | 
				
			||||||
 | 
					        tx, ty = self.rotate((cx, cy), ((cx, cy - r + 30),), 60)[0]
 | 
				
			||||||
 | 
					        self.draw_text_center(ctx, tx, ty, "3/4")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ctx.set_font_size(24)
 | 
				
			||||||
 | 
					        tx, ty = self.rotate((cx, cy), ((cx, cy - r + 30),), -130)[0]
 | 
				
			||||||
 | 
					        self.draw_text_center(ctx, tx, ty, "E")
 | 
				
			||||||
 | 
					        tx, ty = self.rotate((cx, cy), ((cx, cy - r + 30),), 130)[0]
 | 
				
			||||||
 | 
					        self.draw_text_center(ctx, tx, ty, "F")
 | 
				
			||||||
 | 
					        ctx.stroke()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.draw_text_center(ctx, cx, cy + r - 20, f"{level:.0f}%")
 | 
				
			||||||
 | 
					        ctx.stroke()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Skalenpunkte
 | 
				
			||||||
 | 
					        # Alle 5% ein Punkt aber nur da wo noch kein Strich ist
 | 
				
			||||||
 | 
					        for angle in [x for x in range(-120, 120, 12) if x not in (-120, -60, 0, 60, 120)]:
 | 
				
			||||||
 | 
					            x, y = self.rotate((cx, cy), ((cx, cy - r + 10),), angle)[0]
 | 
				
			||||||
 | 
					            ctx.arc(x, y, 2, 0, 2*math.pi)
 | 
				
			||||||
 | 
					            ctx.fill()
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,75 @@
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Vier frei wählbare Meßwerte
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Layout
 | 
				
			||||||
 | 
					+--------------------+
 | 
				
			||||||
 | 
					|         1          |
 | 
				
			||||||
 | 
					+--------------------+
 | 
				
			||||||
 | 
					|         2          |
 | 
				
			||||||
 | 
					+--------------------+
 | 
				
			||||||
 | 
					|         3          |
 | 
				
			||||||
 | 
					+--------------------+
 | 
				
			||||||
 | 
					|         4          |
 | 
				
			||||||
 | 
					+--------------------+
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cairo
 | 
				
			||||||
 | 
					from .page import Page
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class FourValues(Page):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, pageno, cfg, boatdata, boatvalue1, boatvalue2, boatvalue3, boatvalue4):
 | 
				
			||||||
 | 
					        super().__init__(pageno, cfg, boatdata)
 | 
				
			||||||
 | 
					        self.value1 = boatvalue1
 | 
				
			||||||
 | 
					        self.value2 = boatvalue2
 | 
				
			||||||
 | 
					        self.value3 = boatvalue3
 | 
				
			||||||
 | 
					        self.value4 = boatvalue4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def draw(self, ctx):
 | 
				
			||||||
 | 
					        # Seitenunterteilung
 | 
				
			||||||
 | 
					        ctx.rectangle(0, 80, 400, 3)
 | 
				
			||||||
 | 
					        ctx.rectangle(0, 146, 400, 3)
 | 
				
			||||||
 | 
					        ctx.rectangle(0, 214, 400, 3)
 | 
				
			||||||
 | 
					        ctx.fill()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # 
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        ctx.select_font_face("Ubuntu", cairo.FontSlant.NORMAL, cairo.FontWeight.BOLD)
 | 
				
			||||||
 | 
					        ctx.set_font_size(32) 
 | 
				
			||||||
 | 
					        ctx.move_to(20, 45)
 | 
				
			||||||
 | 
					        ctx.show_text("AWA")
 | 
				
			||||||
 | 
					        ctx.move_to(20, 113)
 | 
				
			||||||
 | 
					        ctx.show_text("AWS")
 | 
				
			||||||
 | 
					        ctx.move_to(20, 181)
 | 
				
			||||||
 | 
					        ctx.show_text("COG")
 | 
				
			||||||
 | 
					        ctx.move_to(20, 249)
 | 
				
			||||||
 | 
					        ctx.show_text("STW")
 | 
				
			||||||
 | 
					        ctx.stroke()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Units
 | 
				
			||||||
 | 
					        ctx.set_font_size(16)
 | 
				
			||||||
 | 
					        ctx.move_to(20, 65)
 | 
				
			||||||
 | 
					        ctx.show_text("Deg")
 | 
				
			||||||
 | 
					        ctx.move_to(20, 133)
 | 
				
			||||||
 | 
					        ctx.show_text("kn")
 | 
				
			||||||
 | 
					        ctx.move_to(20, 201)
 | 
				
			||||||
 | 
					        ctx.show_text("Deg")
 | 
				
			||||||
 | 
					        ctx.move_to(20, 269)
 | 
				
			||||||
 | 
					        ctx.show_text("kn")
 | 
				
			||||||
 | 
					        ctx.stroke()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Meßwerte
 | 
				
			||||||
 | 
					        ctx.select_font_face("DSEG7 Classic")
 | 
				
			||||||
 | 
					        ctx.set_font_size(40)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ctx.move_to(180, 65)
 | 
				
			||||||
 | 
					        ctx.show_text("150")
 | 
				
			||||||
 | 
					        ctx.move_to(180, 133)
 | 
				
			||||||
 | 
					        ctx.show_text("25.3")
 | 
				
			||||||
 | 
					        ctx.move_to(180, 201)
 | 
				
			||||||
 | 
					        ctx.show_text("146")
 | 
				
			||||||
 | 
					        ctx.move_to(180, 269)
 | 
				
			||||||
 | 
					        ctx.show_text("56.4")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,77 @@
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Vier frei auswählbare Meßwerte
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Layout
 | 
				
			||||||
 | 
					+--------------------+
 | 
				
			||||||
 | 
					|         1          |
 | 
				
			||||||
 | 
					+--------------------+
 | 
				
			||||||
 | 
					|         2          |
 | 
				
			||||||
 | 
					+--------------------+
 | 
				
			||||||
 | 
					|    3    |    4     |
 | 
				
			||||||
 | 
					+--------------------+
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cairo
 | 
				
			||||||
 | 
					from .page import Page
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class FourValues2(Page):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, pageno, cfg, boatdata, boatvalue1, boatvalue2, boatvalue3, boatvalue4):
 | 
				
			||||||
 | 
					        super().__init__(pageno, cfg, boatdata)
 | 
				
			||||||
 | 
					        self.value1 = boatvalue1
 | 
				
			||||||
 | 
					        self.value2 = boatvalue2
 | 
				
			||||||
 | 
					        self.value3 = boatvalue3
 | 
				
			||||||
 | 
					        self.value4 = boatvalue4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def draw(self, ctx):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Seitenunterteilung
 | 
				
			||||||
 | 
					        ctx.rectangle(0, 105, 400, 3)
 | 
				
			||||||
 | 
					        ctx.rectangle(0, 195, 400, 3)
 | 
				
			||||||
 | 
					        ctx.rectangle(200, 195, 3, 75)
 | 
				
			||||||
 | 
					        ctx.fill()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ctx.select_font_face("Ubuntu", cairo.FontSlant.NORMAL, cairo.FontWeight.BOLD)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Titel
 | 
				
			||||||
 | 
					        ctx.set_font_size(40) 
 | 
				
			||||||
 | 
					        ctx.move_to(20, 55)
 | 
				
			||||||
 | 
					        ctx.show_text("AWA")
 | 
				
			||||||
 | 
					        ctx.move_to(20, 145)
 | 
				
			||||||
 | 
					        ctx.show_text("AWS")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ctx.set_font_size(24)
 | 
				
			||||||
 | 
					        ctx.move_to(20, 220)
 | 
				
			||||||
 | 
					        ctx.show_text("COG")
 | 
				
			||||||
 | 
					        ctx.move_to(220, 220)
 | 
				
			||||||
 | 
					        ctx.show_text("STW")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Einheiten
 | 
				
			||||||
 | 
					        ctx.set_font_size(16) 
 | 
				
			||||||
 | 
					        ctx.move_to(20, 90)
 | 
				
			||||||
 | 
					        ctx.show_text("Deg")
 | 
				
			||||||
 | 
					        ctx.move_to(20, 180)
 | 
				
			||||||
 | 
					        ctx.show_text("kn")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ctx.set_font_size(16) 
 | 
				
			||||||
 | 
					        ctx.move_to(20, 240)
 | 
				
			||||||
 | 
					        ctx.show_text("Deg")
 | 
				
			||||||
 | 
					        ctx.move_to(220, 240)
 | 
				
			||||||
 | 
					        ctx.show_text("kn")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Meßwerte
 | 
				
			||||||
 | 
					        ctx.select_font_face("DSEG7 Classic")
 | 
				
			||||||
 | 
					        ctx.set_font_size(60)
 | 
				
			||||||
 | 
					        ctx.move_to(180, 90)
 | 
				
			||||||
 | 
					        ctx.show_text("150")
 | 
				
			||||||
 | 
					        ctx.move_to(180, 180)
 | 
				
			||||||
 | 
					        ctx.show_text("33.0")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ctx.set_font_size(40)
 | 
				
			||||||
 | 
					        ctx.move_to(80, 270)
 | 
				
			||||||
 | 
					        ctx.show_text("146")
 | 
				
			||||||
 | 
					        ctx.move_to(280, 270)
 | 
				
			||||||
 | 
					        ctx.show_text("50.5")
 | 
				
			||||||
 | 
					        ctx.stroke()
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,44 @@
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					WIP Mangels Generator keine Überprüfung möglich
 | 
				
			||||||
 | 
					Dies ist im Prinzip ein Platzhalter
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cairo
 | 
				
			||||||
 | 
					from .page import Page
 | 
				
			||||||
 | 
					import math
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Generator(Page):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def draw_generator(self, ctx, x, y, r):
 | 
				
			||||||
 | 
					        ctx.set_line_width(4.0)
 | 
				
			||||||
 | 
					        ctx.arc(x, y, r)
 | 
				
			||||||
 | 
					        ctx.set_font_size(60)
 | 
				
			||||||
 | 
					        self.draw_text_center(ctx, x, y, "G")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def draw(self, ctx):
 | 
				
			||||||
 | 
					        # Name
 | 
				
			||||||
 | 
					        ctx.select_font_face("Ubuntu", cairo.FontSlant.NORMAL, cairo.FontWeight.BOLD)
 | 
				
			||||||
 | 
					        ctx.set_font_size(40) 
 | 
				
			||||||
 | 
					        ctx.move_to(10, 65)
 | 
				
			||||||
 | 
					        ctx.show_text("Power")
 | 
				
			||||||
 | 
					        ctx.move_to(12, 82)
 | 
				
			||||||
 | 
					        ctx.show_text("Generator")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Voltage type
 | 
				
			||||||
 | 
					        ctx.select_font_face("DSEG7 Classic")
 | 
				
			||||||
 | 
					        ctx.move_to(10, 140)
 | 
				
			||||||
 | 
					        # 12 or 24
 | 
				
			||||||
 | 
					        ctx.show_text("12V")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Generator power
 | 
				
			||||||
 | 
					        # kW or W
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Show load level in percent
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Show sensor type info
 | 
				
			||||||
 | 
					        # INA219, INA226
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Current, A
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Consumption, W
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,114 @@
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Rotationssensor AS5600 mit Funktion "Kiel"
 | 
				
			||||||
 | 
					WIP
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Idee: 
 | 
				
			||||||
 | 
					  - Zusätzlich Anzeigemöglichkeit für die Tiefe eines variablen Kiels
 | 
				
			||||||
 | 
					  - Mode-Taste
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cairo
 | 
				
			||||||
 | 
					import math
 | 
				
			||||||
 | 
					from .page import Page
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Keel(Page):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, pageno, cfg, boatdata):
 | 
				
			||||||
 | 
					        super().__init__(pageno, cfg, boatdata)
 | 
				
			||||||
 | 
					        # Wert für Kielrotation
 | 
				
			||||||
 | 
					        self.valref = self.bd.getRef("xdrRotK")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def draw(self, ctx):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Mitte oben Instrument (Halbkreis)
 | 
				
			||||||
 | 
					        cx = 200
 | 
				
			||||||
 | 
					        cy = 150
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Radius Kielposition
 | 
				
			||||||
 | 
					        r = 110 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Titel
 | 
				
			||||||
 | 
					        ctx.select_font_face("Ubuntu", cairo.FontSlant.NORMAL, cairo.FontWeight.BOLD)
 | 
				
			||||||
 | 
					        ctx.set_font_size(32)
 | 
				
			||||||
 | 
					        ctx.move_to(100, 70)
 | 
				
			||||||
 | 
					        ctx.show_text("Keel Position")
 | 
				
			||||||
 | 
					        ctx.set_font_size(24)
 | 
				
			||||||
 | 
					        ctx.move_to(175, 110)
 | 
				
			||||||
 | 
					        ctx.show_text(self.valref.unit)
 | 
				
			||||||
 | 
					        ctx.stroke()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Halbkreis für Skala
 | 
				
			||||||
 | 
					        ctx.set_source_rgb(*self.fgcolor)
 | 
				
			||||||
 | 
					        ctx.set_line_width(3)
 | 
				
			||||||
 | 
					        ctx.arc(cx, cy, r + 10, 0, math.pi)
 | 
				
			||||||
 | 
					        ctx.stroke()
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        # Skala mit Strichen, Punkten und Beschriftung
 | 
				
			||||||
 | 
					        char = {
 | 
				
			||||||
 | 
					          90: "45",
 | 
				
			||||||
 | 
					          120: "30",
 | 
				
			||||||
 | 
					          150: "15",
 | 
				
			||||||
 | 
					          180: "0",
 | 
				
			||||||
 | 
					          210: "15",
 | 
				
			||||||
 | 
					          240: "30",
 | 
				
			||||||
 | 
					          270: "45"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        # Zeichnen in 10°-Schritten
 | 
				
			||||||
 | 
					        ctx.set_font_size(16)
 | 
				
			||||||
 | 
					        for i in range(90, 271, 10):
 | 
				
			||||||
 | 
					            fx = math.sin(i / 180 * math.pi)
 | 
				
			||||||
 | 
					            fy = math.cos(i / 180 * math.pi)
 | 
				
			||||||
 | 
					            if i in char:
 | 
				
			||||||
 | 
					                x = cx + (r - 30) * fx
 | 
				
			||||||
 | 
					                y = cy - (r - 30) * fy
 | 
				
			||||||
 | 
					                self.draw_text_center(ctx, x, y, char[i])
 | 
				
			||||||
 | 
					                ctx.stroke()
 | 
				
			||||||
 | 
					            if i % 30 == 0:
 | 
				
			||||||
 | 
					                ctx.move_to(cx + (r - 10) * fx, cy - (r - 10) * fy)
 | 
				
			||||||
 | 
					                ctx.line_to(cx + (r + 10) * fx, cy - (r + 10) * fy)
 | 
				
			||||||
 | 
					                ctx.stroke()
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                x = cx + r * fx
 | 
				
			||||||
 | 
					                y = cy - r * fy
 | 
				
			||||||
 | 
					                ctx.arc(x, y, 2, 0, 2*math.pi)
 | 
				
			||||||
 | 
					                ctx.fill()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Boot und Wasserlinie
 | 
				
			||||||
 | 
					        ctx.arc(cx, cy - 10, 28, 0, math.pi)
 | 
				
			||||||
 | 
					        ctx.fill()
 | 
				
			||||||
 | 
					        ctx.set_line_width(4)
 | 
				
			||||||
 | 
					        ctx.move_to(150, cy)
 | 
				
			||||||
 | 
					        ctx.line_to(250, cy)
 | 
				
			||||||
 | 
					        ctx.stroke()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        #ctx.arc(200, 150, r + 10, 0, 2*math.pi)
 | 
				
			||||||
 | 
					        #ctx.fill()
 | 
				
			||||||
 | 
					        #ctx.set_source_rgb(*self.bgcolor)
 | 
				
			||||||
 | 
					        #ctx.arc(200, 150, r + 7, 0, 2* math.pi)
 | 
				
			||||||
 | 
					        #ctx.rectangle(0, 30, 299, 122)
 | 
				
			||||||
 | 
					        #ctx.fill()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        angle = -15
 | 
				
			||||||
 | 
					        #angle = self.valref.value
 | 
				
			||||||
 | 
					        #TODO  Angle limits to +/-45°
 | 
				
			||||||
 | 
					        if angle < -45:
 | 
				
			||||||
 | 
					            angle = -45
 | 
				
			||||||
 | 
					        elif angle > 45:
 | 
				
			||||||
 | 
					            angle = 45
 | 
				
			||||||
 | 
					        angle *= 2 # stretched scale
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Kiel
 | 
				
			||||||
 | 
					        p = ((cx - 6, cy), (cx + 6, cy), (cx + 2, cy + r - 50), (cx - 2, cy + r - 50))
 | 
				
			||||||
 | 
					        keel = self.rotate((cx, cy), p, angle)
 | 
				
			||||||
 | 
					        ctx.move_to(*keel[0])
 | 
				
			||||||
 | 
					        for point in keel[1:]:
 | 
				
			||||||
 | 
					            ctx.line_to(*point)
 | 
				
			||||||
 | 
					        ctx.fill()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Kiel-Bombe
 | 
				
			||||||
 | 
					        x, y  = self.rotate((cx, cy), ((cx, cy + r -50),), angle)[0]
 | 
				
			||||||
 | 
					        ctx.arc(x, y, 5, 0, 2*math.pi)
 | 
				
			||||||
 | 
					        ctx.fill()
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,26 @@
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Frei auswählbaren Meßwert als Graphen anzeigen
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					YDGS01
 | 
				
			||||||
 | 
					   History Request PGN 61184
 | 
				
			||||||
 | 
					   History Data PGN 130816
 | 
				
			||||||
 | 
					   
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cairo
 | 
				
			||||||
 | 
					from .page import Page
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class OneGraph(Page):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def draw(self, ctx):
 | 
				
			||||||
 | 
					        # Name
 | 
				
			||||||
 | 
					        ctx.select_font_face("Ubuntu", cairo.FontSlant.NORMAL, cairo.FontWeight.BOLD)
 | 
				
			||||||
 | 
					        ctx.set_font_size(60) 
 | 
				
			||||||
 | 
					        ctx.move_to(20, 100)
 | 
				
			||||||
 | 
					        ctx.show_text("One Graph")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Graph anzeigen X/Y
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,31 @@
 | 
				
			||||||
 | 
					import cairo
 | 
				
			||||||
 | 
					from .page import Page
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class OneValue(Page):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, pageno, cfg, boatdata, boatvalue):
 | 
				
			||||||
 | 
					        super().__init__(pageno, cfg, boatdata)
 | 
				
			||||||
 | 
					        self.ref1 = self.bd.getRef(boatvalue)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def draw(self, ctx):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Bezeichnung
 | 
				
			||||||
 | 
					        ctx.select_font_face("Ubuntu", cairo.FontSlant.NORMAL, cairo.FontWeight.BOLD)
 | 
				
			||||||
 | 
					        ctx.set_font_size(60) 
 | 
				
			||||||
 | 
					        ctx.move_to(20, 100)
 | 
				
			||||||
 | 
					        ctx.show_text(self.ref1.valname)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Einheit
 | 
				
			||||||
 | 
					        ctx.set_font_size(40)
 | 
				
			||||||
 | 
					        ctx.move_to(270, 100)
 | 
				
			||||||
 | 
					        ctx.show_text(self.ref1.unit)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Meßwert
 | 
				
			||||||
 | 
					        ctx.select_font_face("DSEG7 Classic")
 | 
				
			||||||
 | 
					        ctx.set_font_size(100)
 | 
				
			||||||
 | 
					        ctx.move_to(40, 240)
 | 
				
			||||||
 | 
					        if self.ref1.value:
 | 
				
			||||||
 | 
					            ctx.show_text(self.ref1.format())
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            ctx.show_text(self.placeholder)
 | 
				
			||||||
 | 
					        ctx.stroke()
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,202 @@
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Basisklasse für alle Darstellungsseiten
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Hinweise zu Cairo:
 | 
				
			||||||
 | 
					Das Koordinatensystem geht von (0, 0) links oben bis (400, 300) rechts unten.
 | 
				
			||||||
 | 
					Um exakte Pixel zu treffen müssen Koordinaten mit Offset 0.5 verwendet werden.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					import cairo
 | 
				
			||||||
 | 
					import math
 | 
				
			||||||
 | 
					from datetime import datetime
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Page():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pageno = 1                # Nummer der aktuell sichtbaren Seite
 | 
				
			||||||
 | 
					    backlight = False
 | 
				
			||||||
 | 
					    color_normal = "dcdcdc"   # Standardhintergrund
 | 
				
			||||||
 | 
					    color_lighted = "d89090"  # Hintergrund im Nachtmodus
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bgcolor = (0.86, 0.86, 0.86)
 | 
				
			||||||
 | 
					    fgcolor = (0, 0, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @staticmethod
 | 
				
			||||||
 | 
					    def hexcolor(hexstr):
 | 
				
			||||||
 | 
					        if (len(hexstr) != 6) or (not all(c.lower in '0123456789abcdef' for c in hexstr)):
 | 
				
			||||||
 | 
					            raise ValueError('Not a valid RGB Hexstring')
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            return(int(hexstr[0:2], 16) / 255.0,
 | 
				
			||||||
 | 
					                   int(hexstr[2:4], 16) / 255.0,
 | 
				
			||||||
 | 
					                   int(hexstr[4:6], 16) / 255.0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @staticmethod
 | 
				
			||||||
 | 
					    def rotate (origin, points, angle):
 | 
				
			||||||
 | 
					        # operates on tuples, angle in degrees
 | 
				
			||||||
 | 
					        ox, oy = origin
 | 
				
			||||||
 | 
					        phi = math.radians(angle)
 | 
				
			||||||
 | 
					        fs = math.sin(phi)
 | 
				
			||||||
 | 
					        fc = math.cos(phi)
 | 
				
			||||||
 | 
					        rotated = []
 | 
				
			||||||
 | 
					        for x, y in points:
 | 
				
			||||||
 | 
					            dx = x - ox
 | 
				
			||||||
 | 
					            dy = y - oy
 | 
				
			||||||
 | 
					            rotated.append((ox + fc * dx - fs * dy, oy + fs * dx + fc * dy))
 | 
				
			||||||
 | 
					        return rotated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, pageno, cfg, boatdata):
 | 
				
			||||||
 | 
					        self.pageno = pageno
 | 
				
			||||||
 | 
					        self.cfg = cfg
 | 
				
			||||||
 | 
					        self.bd = boatdata
 | 
				
			||||||
 | 
					        self.header = True
 | 
				
			||||||
 | 
					        self.footer = True
 | 
				
			||||||
 | 
					        self.hbled = False # Heartbeat LED 
 | 
				
			||||||
 | 
					        self.hbfreq = 1000 # Heartbeat Frequenz in ms
 | 
				
			||||||
 | 
					        self.keylock = False
 | 
				
			||||||
 | 
					        self.icon = {}
 | 
				
			||||||
 | 
					        self.icon['PREV'] = cairo.ImageSurface.create_from_png(os.path.join(cfg['imgpath'], "arrow_l1.png"))
 | 
				
			||||||
 | 
					        self.icon['NEXT'] = cairo.ImageSurface.create_from_png(os.path.join(cfg['imgpath'], "arrow_r1.png"))
 | 
				
			||||||
 | 
					        self.icon['ILUM'] = cairo.ImageSurface.create_from_png(os.path.join(cfg['imgpath'], "lighton.png"))
 | 
				
			||||||
 | 
					        self.sym_lock = cairo.ImageSurface.create_from_png(os.path.join(cfg['imgpath'], "lock.png"))
 | 
				
			||||||
 | 
					        self.sym_swipe = cairo.ImageSurface.create_from_png(os.path.join(cfg['imgpath'], "swipe.png"))
 | 
				
			||||||
 | 
					        self.buttonlabel = {
 | 
				
			||||||
 | 
					            1: '',
 | 
				
			||||||
 | 
					            2: '',
 | 
				
			||||||
 | 
					            3: '#PREV',
 | 
				
			||||||
 | 
					            4: '#NEXT',
 | 
				
			||||||
 | 
					            5: '',
 | 
				
			||||||
 | 
					            6: '#ILUM'
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def handle_key(self, buttonid):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Diese Methode sollte in der Detailseite überladen werden
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        print(f"Button no. {buttonid} ignored")
 | 
				
			||||||
 | 
					        return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def heartbeat(self, ctx):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Wie ausschalten bei Seitenwechsel?
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        ctx.save()
 | 
				
			||||||
 | 
					        if self.hbled:
 | 
				
			||||||
 | 
					            ctx.set_source_rgb(0, 0, 0)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            ctx.set_source_rgb(0.86, 0.86, 0.86) # 0xdcdcdc
 | 
				
			||||||
 | 
					        ctx.arc(210, 9, 6, 0, math.pi*2)
 | 
				
			||||||
 | 
					        ctx.fill()
 | 
				
			||||||
 | 
					        ctx.restore()
 | 
				
			||||||
 | 
					        self.hbled = not self.hbled
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def draw_header(self, ctx):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Mögliche Zeichen für aktivierte Funktionen
 | 
				
			||||||
 | 
					          AP - Accesspoint ist aktiv
 | 
				
			||||||
 | 
					          WIFI - WIFI-Client
 | 
				
			||||||
 | 
					          TCP
 | 
				
			||||||
 | 
					          N2K - NMEA2000
 | 
				
			||||||
 | 
					          183 
 | 
				
			||||||
 | 
					          USB
 | 
				
			||||||
 | 
					          GPS - GPS Fix vorhanden
 | 
				
			||||||
 | 
					          # TODO Umstellung auf Symbole je 16 Pixel zum Platz sparen
 | 
				
			||||||
 | 
					          Neu: Nummer der aktiven Seite (1 - 10)
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        ctx.select_font_face("Ubuntu", cairo.FontSlant.NORMAL, cairo.FontWeight.BOLD)
 | 
				
			||||||
 | 
					        ctx.set_font_size(16)
 | 
				
			||||||
 | 
					        ctx.move_to(0.5, 14.5)
 | 
				
			||||||
 | 
					        ctx.show_text(f"N2K GPS")
 | 
				
			||||||
 | 
					        ctx.stroke()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Seitennummer neue Darstellung
 | 
				
			||||||
 | 
					        ctx.set_line_width(1)
 | 
				
			||||||
 | 
					        ctx.move_to(170.5, 1.5)
 | 
				
			||||||
 | 
					        ctx.line_to(190.5, 1.5)
 | 
				
			||||||
 | 
					        ctx.line_to(190.5, 16.5)
 | 
				
			||||||
 | 
					        ctx.line_to(170.5, 16.5)
 | 
				
			||||||
 | 
					        ctx.line_to(170.5, 1.5)
 | 
				
			||||||
 | 
					        ctx.stroke()
 | 
				
			||||||
 | 
					        self.draw_text_center(ctx, 180, 9.5, str(self.pageno))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Tastenstatus
 | 
				
			||||||
 | 
					        ctx.save()
 | 
				
			||||||
 | 
					        if self.keylock:
 | 
				
			||||||
 | 
					            ctx.set_source_surface(self.sym_lock, 150, 1)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            ctx.set_source_surface(self.sym_swipe, 150, 1)
 | 
				
			||||||
 | 
					        ctx.paint()
 | 
				
			||||||
 | 
					        ctx.restore()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Heartbeat
 | 
				
			||||||
 | 
					        self.heartbeat(ctx)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Datum und Uhrzeit
 | 
				
			||||||
 | 
					        ctx.move_to(230, 14.5)
 | 
				
			||||||
 | 
					        ctx.show_text(datetime.today().strftime('%H:%M %Y-%m-%d LOT'))
 | 
				
			||||||
 | 
					        ctx.stroke()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def draw_footer(self, ctx):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Nur Belegung der Buttons (label[1] bis label[6])
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        ctx.select_font_face("AtariST8x16SystemFont")
 | 
				
			||||||
 | 
					        #ctx.select_font_face("Ubuntu", cairo.FontSlant.NORMAL, cairo.FontWeight.BOLD)
 | 
				
			||||||
 | 
					        ctx.set_font_size(16)
 | 
				
			||||||
 | 
					        x = (35, 101, 167, 233, 299, 365) 
 | 
				
			||||||
 | 
					        y = 294
 | 
				
			||||||
 | 
					        for i in range(6):
 | 
				
			||||||
 | 
					            if len(self.buttonlabel[i+1]) > 0 :
 | 
				
			||||||
 | 
					                if self.buttonlabel[i+1][0] == "#":
 | 
				
			||||||
 | 
					                    # Symbol verwenden 16x16 Pixel
 | 
				
			||||||
 | 
					                    ctx.save()
 | 
				
			||||||
 | 
					                    key = self.buttonlabel[i+1][1:]
 | 
				
			||||||
 | 
					                    ctx.set_source_surface(self.icon[key], x[i]-8, y-13)
 | 
				
			||||||
 | 
					                    ctx.paint()
 | 
				
			||||||
 | 
					                    ctx.restore()
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    text = "[ {} ]".format(self.buttonlabel[i+1])
 | 
				
			||||||
 | 
					                    w = ctx.text_extents(text).width
 | 
				
			||||||
 | 
					                    ctx.move_to(x[i] - w/2, y)               
 | 
				
			||||||
 | 
					                    ctx.show_text(text)
 | 
				
			||||||
 | 
					        ctx.stroke()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def clear(self):
 | 
				
			||||||
 | 
					        ctx.set_source_rgb(1, 1, 1)
 | 
				
			||||||
 | 
					        ctx.rectangle(0, 0, 399, 299)
 | 
				
			||||||
 | 
					        ctx.fill()
 | 
				
			||||||
 | 
					        ctx.set_source_rgb(0, 0, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def draw_text_center(self, ctx, x, y, content, rotate=False, baseline=False, fill=False):
 | 
				
			||||||
 | 
					        ext = ctx.text_extents(content)
 | 
				
			||||||
 | 
					        if fill:
 | 
				
			||||||
 | 
					            ctx.set_source_rgb(*self.bgcolor)
 | 
				
			||||||
 | 
					            xf = x + ext.x_bearing - 2
 | 
				
			||||||
 | 
					            yf = y + ext.height / 2 + ext.y_bearing - 2
 | 
				
			||||||
 | 
					            wf = ext.width + 4
 | 
				
			||||||
 | 
					            hf = ext.height + 4
 | 
				
			||||||
 | 
					            ctx.rectangle(xf, yf, wf, hf)
 | 
				
			||||||
 | 
					            ctx.fill()
 | 
				
			||||||
 | 
					        ctx.set_source_rgb(*self.fgcolor)
 | 
				
			||||||
 | 
					        if rotate:
 | 
				
			||||||
 | 
					            if baseline:
 | 
				
			||||||
 | 
					                ctx.move_to(x - ext[3] / 2.0, y)
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                ctx.move_to(x - ext[3] / 2.0, y + ext[2] / 2.0)
 | 
				
			||||||
 | 
					            ctx.save()
 | 
				
			||||||
 | 
					            ctx.rotate(1.5 * math.pi)
 | 
				
			||||||
 | 
					            ctx.show_text(content)
 | 
				
			||||||
 | 
					            ctx.restore()
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            if baseline:
 | 
				
			||||||
 | 
					                ctx.move_to(x - ext[2] / 2.0, y)
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                ctx.move_to(x - ext[2] / 2.0, y + ext[3] / 2.0)
 | 
				
			||||||
 | 
					            ctx.show_text(content)
 | 
				
			||||||
 | 
					        ctx.stroke()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def draw_text_ralign(self, ctx, x, y, content):
 | 
				
			||||||
 | 
					        ext = ctx.text_extents(content)
 | 
				
			||||||
 | 
					        ctx.move_to(x - ext[2], y)
 | 
				
			||||||
 | 
					        ctx.show_text(content)
 | 
				
			||||||
 | 
					        ctx.stroke()
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,13 @@
 | 
				
			||||||
 | 
					import cairo
 | 
				
			||||||
 | 
					from .page import Page
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class RollPitch(Page):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def draw(self, ctx):
 | 
				
			||||||
 | 
					        # Name
 | 
				
			||||||
 | 
					        ctx.select_font_face("Ubuntu", cairo.FontSlant.NORMAL, cairo.FontWeight.BOLD)
 | 
				
			||||||
 | 
					        ctx.set_font_size(60) 
 | 
				
			||||||
 | 
					        ctx.move_to(20, 100)
 | 
				
			||||||
 | 
					        ctx.show_text("RollPitch")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Graph anzeigen X/Y
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,103 @@
 | 
				
			||||||
 | 
					import cairo
 | 
				
			||||||
 | 
					import math
 | 
				
			||||||
 | 
					from .page import Page
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Rudder(Page):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, pageno, cfg, boatdata):
 | 
				
			||||||
 | 
					        super().__init__(pageno, cfg, boatdata)
 | 
				
			||||||
 | 
					        self.buttonlabel[1] = 'MODE'
 | 
				
			||||||
 | 
					        self.mode = 'P'
 | 
				
			||||||
 | 
					        # Werte für Ruderausschlag
 | 
				
			||||||
 | 
					        self.valpri = self.bd.getRef("RPOS") # Primäres Ruder
 | 
				
			||||||
 | 
					        self.valsec = self.bd.getRef("PRPOS") # Sekundäres Ruder
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def handle_key(self, buttonid):
 | 
				
			||||||
 | 
					        if buttonid == 1:
 | 
				
			||||||
 | 
					            if self.mode == 'P':
 | 
				
			||||||
 | 
					                self.mode = 'S'
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                self.mode = 'P'
 | 
				
			||||||
 | 
					            return True
 | 
				
			||||||
 | 
					        return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def draw(self, ctx):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Ruder auswählen
 | 
				
			||||||
 | 
					        if self.mode == 'P':
 | 
				
			||||||
 | 
					            valref = self.valpri
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            valref = self.valsec
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Rotationszentrum
 | 
				
			||||||
 | 
					        cx = 200
 | 
				
			||||||
 | 
					        cy = 150
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Radius des Instruments
 | 
				
			||||||
 | 
					        r = 110
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Titel
 | 
				
			||||||
 | 
					        ctx.select_font_face("Ubuntu", cairo.FontSlant.NORMAL, cairo.FontWeight.BOLD)
 | 
				
			||||||
 | 
					        ctx.set_font_size(32) 
 | 
				
			||||||
 | 
					        ctx.move_to(80, 70)
 | 
				
			||||||
 | 
					        ctx.show_text("Rudder Position")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ctx.set_font_size(24)
 | 
				
			||||||
 | 
					        if valref.valid:
 | 
				
			||||||
 | 
					            ctx.move_to(175, 110)
 | 
				
			||||||
 | 
					            ctx.show_text(valref.unit)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            ctx.move_to(110, 110)
 | 
				
			||||||
 | 
					            ctx.show_text("no data available")
 | 
				
			||||||
 | 
					        ctx.stroke()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Debug 
 | 
				
			||||||
 | 
					        angle = 5
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Rahmen
 | 
				
			||||||
 | 
					        ctx.set_source_rgb(*self.fgcolor)
 | 
				
			||||||
 | 
					        ctx.set_line_width(3)
 | 
				
			||||||
 | 
					        ctx.arc(cx, cy, r + 10, 0, math.pi)
 | 
				
			||||||
 | 
					        ctx.stroke()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Zentrum
 | 
				
			||||||
 | 
					        ctx.arc(cx, cy, 8, 0, 2*math.pi)
 | 
				
			||||||
 | 
					        ctx.fill()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Skala mit Strichen, Punkten und Beschriftung
 | 
				
			||||||
 | 
					        char = {
 | 
				
			||||||
 | 
					          90: "45",
 | 
				
			||||||
 | 
					          120: "30",
 | 
				
			||||||
 | 
					          150: "15",
 | 
				
			||||||
 | 
					          180: "0",
 | 
				
			||||||
 | 
					          210: "15",
 | 
				
			||||||
 | 
					          240: "30",
 | 
				
			||||||
 | 
					          270: "45"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        # Zeichnen in 10°-Schritten
 | 
				
			||||||
 | 
					        ctx.set_font_size(16)
 | 
				
			||||||
 | 
					        for i in range(90, 271, 10):
 | 
				
			||||||
 | 
					            fx = math.sin(i / 180 * math.pi)
 | 
				
			||||||
 | 
					            fy = math.cos(i / 180 * math.pi)
 | 
				
			||||||
 | 
					            if i in char:
 | 
				
			||||||
 | 
					                x = cx + (r - 30) * fx
 | 
				
			||||||
 | 
					                y = cy - (r - 30) * fy
 | 
				
			||||||
 | 
					                self.draw_text_center(ctx, x, y, char[i])
 | 
				
			||||||
 | 
					                ctx.stroke()
 | 
				
			||||||
 | 
					            if i % 30 == 0:
 | 
				
			||||||
 | 
					                ctx.move_to(cx + (r - 10) * fx, cy - (r - 10) * fy)
 | 
				
			||||||
 | 
					                ctx.line_to(cx + (r + 10) * fx, cy - (r + 10) * fy)
 | 
				
			||||||
 | 
					                ctx.stroke()
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                x = cx + r * fx
 | 
				
			||||||
 | 
					                y = cy - r * fy
 | 
				
			||||||
 | 
					                ctx.arc(x, y, 2, 0, 2*math.pi)
 | 
				
			||||||
 | 
					                ctx.fill()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if valref.valid:
 | 
				
			||||||
 | 
					            p = ((cx - 6, cy), (cx + 6, cy), (cx + 2, cy + r - 50), (cx - 2, cy + r - 50))
 | 
				
			||||||
 | 
					            rudder = self.rotate((cx, cy), p, angle)
 | 
				
			||||||
 | 
					            ctx.move_to(*rudder[0])
 | 
				
			||||||
 | 
					            for point in rudder[1:]:
 | 
				
			||||||
 | 
					                ctx.line_to(*point)
 | 
				
			||||||
 | 
					            ctx.fill()
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,85 @@
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Satelliteninfos
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - Sat mit Fix: ausgefüllter Kreis
 | 
				
			||||||
 | 
					  - Sat ohne fix: leerer Kreis
 | 
				
			||||||
 | 
					  - Slots für 12 SNR-Balken
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cairo
 | 
				
			||||||
 | 
					import math
 | 
				
			||||||
 | 
					from .page import Page
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class SkyView(Page):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, pageno, cfg, boatdata):
 | 
				
			||||||
 | 
					        super().__init__(pageno, cfg, boatdata)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def pol2cart(azimuth, elevation):
 | 
				
			||||||
 | 
					        '''
 | 
				
			||||||
 | 
					        Polar to Cartesian coordinates within the horizon circle.
 | 
				
			||||||
 | 
					        azimuth in radians
 | 
				
			||||||
 | 
					        x = math.sin(azimuth) * elevation * self.radius
 | 
				
			||||||
 | 
					        y = math.cos(azimuth) * elevation * self.radius
 | 
				
			||||||
 | 
					        '''
 | 
				
			||||||
 | 
					        pass
 | 
				
			||||||
 | 
					        # (x, y) = self.pol2cart(sat.az, sat.el)
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					    def draw(self, ctx):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Name
 | 
				
			||||||
 | 
					        ctx.select_font_face("Ubuntu", cairo.FontSlant.NORMAL, cairo.FontWeight.BOLD)
 | 
				
			||||||
 | 
					        #ctx.set_font_size(32) 
 | 
				
			||||||
 | 
					        #self.draw_text_center(ctx, 200, 40, "Satellite Info")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Spezialseite
 | 
				
			||||||
 | 
					        cx = 130
 | 
				
			||||||
 | 
					        cy = 150
 | 
				
			||||||
 | 
					        r = 125
 | 
				
			||||||
 | 
					        r1 = r / 2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ctx.set_line_width(1.5)
 | 
				
			||||||
 | 
					        ctx.arc(cx, cy, r, 0, 2*math.pi)
 | 
				
			||||||
 | 
					        ctx.stroke()
 | 
				
			||||||
 | 
					        ctx.arc(cx, cy, r1, 0, 2*math.pi)
 | 
				
			||||||
 | 
					        ctx.stroke()
 | 
				
			||||||
 | 
					        ctx.set_dash([4, 4], 0)
 | 
				
			||||||
 | 
					        ctx.move_to(cx, cy - r)
 | 
				
			||||||
 | 
					        ctx.line_to(cx, cy + r)
 | 
				
			||||||
 | 
					        ctx.move_to(cx - r, cy)
 | 
				
			||||||
 | 
					        ctx.line_to(cx + r, cy)
 | 
				
			||||||
 | 
					        ctx.stroke()
 | 
				
			||||||
 | 
					        ctx.set_dash([], 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Signal/Noise Balken
 | 
				
			||||||
 | 
					        ctx.set_font_size(16) 
 | 
				
			||||||
 | 
					        ctx.move_to(325, 34)
 | 
				
			||||||
 | 
					        ctx.show_text("SNR")
 | 
				
			||||||
 | 
					        ctx.stroke()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ctx.set_line_width(1.5)
 | 
				
			||||||
 | 
					        ctx.rectangle(270, 20, 125, 257)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Beispieldaten
 | 
				
			||||||
 | 
					        ctx.set_line_width(0.5)
 | 
				
			||||||
 | 
					        for s in range(12):
 | 
				
			||||||
 | 
					            y = 30 + (s + 1) * 20
 | 
				
			||||||
 | 
					            ctx.move_to(275, y)
 | 
				
			||||||
 | 
					            ctx.show_text(f"{s:02d}")
 | 
				
			||||||
 | 
					            ctx.rectangle(305, y-12, 85, 14)
 | 
				
			||||||
 | 
					            ctx.stroke()
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					        ctx.set_line_width(1.0)
 | 
				
			||||||
 | 
					        for s in self.bd.sat.values():
 | 
				
			||||||
 | 
					            x = cx + math.sin(s.azimuth) * s.elevation * r
 | 
				
			||||||
 | 
					            y = cy + math.cos(s.azimuth) * s.elevation * r
 | 
				
			||||||
 | 
					            ctx.arc(x, y, 4, 0, 2*math.pi)
 | 
				
			||||||
 | 
					            ctx.move_to(x+4, y+4)
 | 
				
			||||||
 | 
					            ctx.show_text(f"{s.prn_num}")
 | 
				
			||||||
 | 
					            ctx.stroke()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Satellitenliste mit SNR-Balken sortiert nach nummer
 | 
				
			||||||
 | 
					        for prn_num in sorted(self.bd.sat):
 | 
				
			||||||
 | 
					            print(prn_num)           
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,40 @@
 | 
				
			||||||
 | 
					import cairo
 | 
				
			||||||
 | 
					from .page import Page
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Solar(Page):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def draw_solar(self, ctx, x, y, w, h):
 | 
				
			||||||
 | 
					        pass
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        // Solar graphic with fill level
 | 
				
			||||||
 | 
					        void solarGraphic(uint x, uint y, int pcolor, int bcolor){
 | 
				
			||||||
 | 
					            // Show solar modul
 | 
				
			||||||
 | 
					                int xb = x;     // X position
 | 
				
			||||||
 | 
					                int yb = y;     // Y position
 | 
				
			||||||
 | 
					                int t = 4;      // Line thickness
 | 
				
			||||||
 | 
					                int percent = 0;
 | 
				
			||||||
 | 
					                // Solar corpus 100x80
 | 
				
			||||||
 | 
					                int level = int((100.0 - percent) * (80-(2*t)) / 100.0);
 | 
				
			||||||
 | 
					                getdisplay().fillRect(xb, yb, 100, 80, pcolor);
 | 
				
			||||||
 | 
					                if(percent < 99){
 | 
				
			||||||
 | 
					                    getdisplay().fillRect(xb+t, yb+t, 100-(2*t), level, bcolor);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                // Draw horizontel lines
 | 
				
			||||||
 | 
					                getdisplay().fillRect(xb, yb+28-t, 100, t, pcolor);
 | 
				
			||||||
 | 
					                getdisplay().fillRect(xb, yb+54-t, 100, t, pcolor);
 | 
				
			||||||
 | 
					                // Draw vertical lines
 | 
				
			||||||
 | 
					                getdisplay().fillRect(xb+19+t, yb, t, 80, pcolor);
 | 
				
			||||||
 | 
					                getdisplay().fillRect(xb+39+2*t, yb, t, 80, pcolor);
 | 
				
			||||||
 | 
					                getdisplay().fillRect(xb+59+3*t, yb, t, 80, pcolor);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def draw(self, ctx):
 | 
				
			||||||
 | 
					        # Name
 | 
				
			||||||
 | 
					        ctx.select_font_face("Ubuntu", cairo.FontSlant.NORMAL, cairo.FontWeight.BOLD)
 | 
				
			||||||
 | 
					        ctx.set_font_size(60) 
 | 
				
			||||||
 | 
					        ctx.move_to(20, 100)
 | 
				
			||||||
 | 
					        ctx.show_text("Solar")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,107 @@
 | 
				
			||||||
 | 
					import cairo
 | 
				
			||||||
 | 
					from .page import Page
 | 
				
			||||||
 | 
					import datetime
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class System(Page):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, pageno, cfg, boatdata):
 | 
				
			||||||
 | 
					        super().__init__(pageno, cfg, boatdata)
 | 
				
			||||||
 | 
					        self.buttonlabel[1] = 'MODE'
 | 
				
			||||||
 | 
					        self.buttonlabel[2] = 'STBY'
 | 
				
			||||||
 | 
					        self.mode = ('I', 'N') # (I)nformation (N)MEA2000 Device List
 | 
				
			||||||
 | 
					        self.modeindex = 1
 | 
				
			||||||
 | 
					        self.standby = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def handle_key(self, buttonid):
 | 
				
			||||||
 | 
					        if self.standby and buttonid != 1:
 | 
				
			||||||
 | 
					            return True
 | 
				
			||||||
 | 
					        if buttonid == 1:
 | 
				
			||||||
 | 
					            if self.standby:
 | 
				
			||||||
 | 
					                self.standby = False
 | 
				
			||||||
 | 
					                self.buttonlabel[1] = 'MODE'
 | 
				
			||||||
 | 
					                self.buttonlabel[2] = 'STBY'
 | 
				
			||||||
 | 
					                self.header = True
 | 
				
			||||||
 | 
					                self.footer = True
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                if self.modeindex < len(self.mode):
 | 
				
			||||||
 | 
					                    self.modeindex += 1
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    self.modeindex = 0
 | 
				
			||||||
 | 
					            return True
 | 
				
			||||||
 | 
					        if buttonid == 2:
 | 
				
			||||||
 | 
					            self.buttonlabel[1] = None
 | 
				
			||||||
 | 
					            self.buttonlabel[2] = None
 | 
				
			||||||
 | 
					            self.header = False
 | 
				
			||||||
 | 
					            self.footer = False
 | 
				
			||||||
 | 
					            self.standby = True
 | 
				
			||||||
 | 
					        return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def draw_stby(self, ctx):
 | 
				
			||||||
 | 
					        # Standby
 | 
				
			||||||
 | 
					        # TODO Kopf und Fußzeile ausschalten
 | 
				
			||||||
 | 
					        # Ein Klick auf die Mode-Taste weckt das System wieder auf
 | 
				
			||||||
 | 
					        pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def draw_info(self, ctx):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Bezeichnung
 | 
				
			||||||
 | 
					        ctx.select_font_face("Ubuntu", cairo.FontSlant.NORMAL, cairo.FontWeight.BOLD)
 | 
				
			||||||
 | 
					        ctx.set_font_size(32)
 | 
				
			||||||
 | 
					        self.draw_text_center(ctx, 200, 40 , "System Info")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ctx.set_font_size(16)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # System name
 | 
				
			||||||
 | 
					        # Software version
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Linke Seite
 | 
				
			||||||
 | 
					        ctx.move_to(2, 80)
 | 
				
			||||||
 | 
					        ctx.show_text("Simulation: ")
 | 
				
			||||||
 | 
					        ctx.move_to(140, 80)
 | 
				
			||||||
 | 
					        ctx.show_text('On' if self.cfg['simulation'] else 'Off')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ctx.move_to(2, 100)
 | 
				
			||||||
 | 
					        ctx.show_text("BME280/BMP280: ")
 | 
				
			||||||
 | 
					        ctx.move_to(140, 100)
 | 
				
			||||||
 | 
					        ctx.show_text('On' if self.cfg['bme280'] else 'Off')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ctx.move_to(2, 120)
 | 
				
			||||||
 | 
					        ctx.show_text("GPS: ")
 | 
				
			||||||
 | 
					        ctx.move_to(140, 120)
 | 
				
			||||||
 | 
					        ctx.show_text('On' if self.cfg['gps'] else 'Off')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Rechte Seite
 | 
				
			||||||
 | 
					        ctx.move_to(202, 80)
 | 
				
			||||||
 | 
					        ctx.show_text("Wifi: ")
 | 
				
			||||||
 | 
					        ctx.move_to(340, 80)
 | 
				
			||||||
 | 
					        ctx.show_text('On')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ctx.move_to(202, 100)
 | 
				
			||||||
 | 
					        ctx.show_text("Buzzer: ")
 | 
				
			||||||
 | 
					        ctx.move_to(340, 100)
 | 
				
			||||||
 | 
					        ctx.show_text('60%')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ctx.move_to(202, 120)
 | 
				
			||||||
 | 
					        ctx.show_text("Timezone: ")
 | 
				
			||||||
 | 
					        ctx.move_to(340, 120)
 | 
				
			||||||
 | 
					        ctx.show_text(datetime.datetime.now().astimezone().tzname())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ctx.stroke()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Geräteliste
 | 
				
			||||||
 | 
					        ctx.move_to(2, 150)
 | 
				
			||||||
 | 
					        ctx.show_text("NMEA2000 Device List")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ctx.set_line_width(1.5)
 | 
				
			||||||
 | 
					        ctx.rectangle(2, 155, 394, 100)
 | 
				
			||||||
 | 
					        ctx.stroke()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def draw_devlist(self, ctx):
 | 
				
			||||||
 | 
					        # NMEA2000 Geräteliste, Vollbild
 | 
				
			||||||
 | 
					        # scrollen mit Up/Down
 | 
				
			||||||
 | 
					        pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def draw(self, ctx):
 | 
				
			||||||
 | 
					        if self.standby:
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        self.draw_info(ctx)
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,47 @@
 | 
				
			||||||
 | 
					import cairo
 | 
				
			||||||
 | 
					from .page import Page
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ThreeValues(Page):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, pageno, cfg, boatdata, boatvalue1, boatvalue2, boatvalue3):
 | 
				
			||||||
 | 
					        super().__init__(pageno, cfg, boatdata)
 | 
				
			||||||
 | 
					        self.ref1 = self.bd.getRef(boatvalue1)
 | 
				
			||||||
 | 
					        self.ref2 = self.bd.getRef(boatvalue2)
 | 
				
			||||||
 | 
					        self.ref3 = self.bd.getRef(boatvalue3)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def draw(self, ctx):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Seitenlayout
 | 
				
			||||||
 | 
					        ctx.rectangle(0, 105, 400, 3)
 | 
				
			||||||
 | 
					        ctx.rectangle(0, 195, 400, 3)
 | 
				
			||||||
 | 
					        ctx.fill()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ctx.select_font_face("Ubuntu", cairo.FontSlant.NORMAL, cairo.FontWeight.BOLD)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Titel
 | 
				
			||||||
 | 
					        ctx.set_font_size(40) 
 | 
				
			||||||
 | 
					        ctx.move_to(20, 55)
 | 
				
			||||||
 | 
					        ctx.show_text(self.ref1.valname)
 | 
				
			||||||
 | 
					        ctx.move_to(20, 145)
 | 
				
			||||||
 | 
					        ctx.show_text(self.ref2.valname)
 | 
				
			||||||
 | 
					        ctx.move_to(20, 235)
 | 
				
			||||||
 | 
					        ctx.show_text(self.ref3.valname)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Einheiten
 | 
				
			||||||
 | 
					        ctx.set_font_size(24) 
 | 
				
			||||||
 | 
					        ctx.move_to(20, 90)
 | 
				
			||||||
 | 
					        ctx.show_text(self.ref1.unit)
 | 
				
			||||||
 | 
					        ctx.move_to(20, 180)
 | 
				
			||||||
 | 
					        ctx.show_text(self.ref2.unit)
 | 
				
			||||||
 | 
					        ctx.move_to(20, 270)
 | 
				
			||||||
 | 
					        ctx.show_text(self.ref3.unit)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ctx.select_font_face("DSEG7 Classic")
 | 
				
			||||||
 | 
					        ctx.set_font_size(60)
 | 
				
			||||||
 | 
					        ctx.move_to(180, 90)
 | 
				
			||||||
 | 
					        ctx.show_text(self.ref1.format())
 | 
				
			||||||
 | 
					        ctx.move_to(180, 180)
 | 
				
			||||||
 | 
					        ctx.show_text(self.ref2.format())
 | 
				
			||||||
 | 
					        ctx.move_to(180, 270)
 | 
				
			||||||
 | 
					        ctx.show_text(self.ref3.format())
 | 
				
			||||||
 | 
					        ctx.stroke()
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,14 @@
 | 
				
			||||||
 | 
					import cairo
 | 
				
			||||||
 | 
					from .page import Page
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TwoGraphs(Page):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def draw(self, ctx):
 | 
				
			||||||
 | 
					        # Name
 | 
				
			||||||
 | 
					        ctx.select_font_face("Ubuntu", cairo.FontSlant.NORMAL, cairo.FontWeight.BOLD)
 | 
				
			||||||
 | 
					        ctx.set_font_size(60) 
 | 
				
			||||||
 | 
					        ctx.move_to(20, 100)
 | 
				
			||||||
 | 
					        ctx.show_text("Two Graphs")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Zwei Graphen anzeigen X/Y nebeneinander
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,67 @@
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Anzeige von zwei frei definierbaren Werten
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Layout
 | 
				
			||||||
 | 
					+--------------------+
 | 
				
			||||||
 | 
					|         1          |
 | 
				
			||||||
 | 
					+--------------------+
 | 
				
			||||||
 | 
					|         2          |
 | 
				
			||||||
 | 
					+--------------------+
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cairo
 | 
				
			||||||
 | 
					from .page import Page
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TwoValues(Page):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, pageno, cfg, boatdata, boatvalue1, boatvalue2):
 | 
				
			||||||
 | 
					        super().__init__(pageno, cfg, boatdata)
 | 
				
			||||||
 | 
					        self.ref1 = self.bd.getRef(boatvalue1)
 | 
				
			||||||
 | 
					        self.ref2 = self.bd.getRef(boatvalue2)
 | 
				
			||||||
 | 
					        #print(self.ref1.valname)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def draw(self, ctx):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Seitenunterteilung
 | 
				
			||||||
 | 
					        ctx.rectangle(0, 145, 400, 3)
 | 
				
			||||||
 | 
					        ctx.fill()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Name
 | 
				
			||||||
 | 
					        ctx.select_font_face("Ubuntu", cairo.FontSlant.NORMAL, cairo.FontWeight.BOLD)
 | 
				
			||||||
 | 
					        ctx.set_font_size(40) 
 | 
				
			||||||
 | 
					        ctx.move_to(20, 80)
 | 
				
			||||||
 | 
					        ctx.show_text(self.ref1.valname)
 | 
				
			||||||
 | 
					        ctx.move_to(20, 190)
 | 
				
			||||||
 | 
					        ctx.show_text(self.ref2.valname)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Einheit
 | 
				
			||||||
 | 
					        ctx.set_font_size(24)
 | 
				
			||||||
 | 
					        ctx.move_to(20, 130)
 | 
				
			||||||
 | 
					        ctx.show_text(self.ref1.unit)
 | 
				
			||||||
 | 
					        ctx.move_to(20, 240)
 | 
				
			||||||
 | 
					        ctx.show_text(self.ref2.unit)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Meßwerte
 | 
				
			||||||
 | 
					        if type(self.ref1 == 'BoatValueGeo'):
 | 
				
			||||||
 | 
					            ctx.select_font_face("Ubuntu", cairo.FontSlant.NORMAL, cairo.FontWeight.BOLD)
 | 
				
			||||||
 | 
					            ctx.set_font_size(40)
 | 
				
			||||||
 | 
					            ctx.move_to(140, 100)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            ctx.select_font_face("DSEG7 Classic")
 | 
				
			||||||
 | 
					            ctx.set_font_size(84)
 | 
				
			||||||
 | 
					            ctx.move_to(180, 130)
 | 
				
			||||||
 | 
					        ctx.show_text(self.ref1.format())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if type(self.ref2 == 'BoatValueGeo'):
 | 
				
			||||||
 | 
					            ctx.select_font_face("Ubuntu")
 | 
				
			||||||
 | 
					            ctx.set_font_size(40)
 | 
				
			||||||
 | 
					            ctx.move_to(140, 210)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            ctx.select_font_face("DSEG7 Classic")
 | 
				
			||||||
 | 
					            ctx.set_font_size(84)
 | 
				
			||||||
 | 
					            ctx.move_to(180, 240)
 | 
				
			||||||
 | 
					        ctx.show_text(self.ref2.format())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ctx.stroke()
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,216 @@
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Integrierte Spannungsmessung
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Ideen:
 | 
				
			||||||
 | 
					  - Umschaltung Datenquelle: intern, N2K
 | 
				
			||||||
 | 
					  - Umschaltung analog / digital / Graphik
 | 
				
			||||||
 | 
					  - Historische Werte vom YD-Batteriemonitor
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cairo
 | 
				
			||||||
 | 
					import math
 | 
				
			||||||
 | 
					from .page import Page
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Voltage(Page):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    avg = (1, 10, 60, 300);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, pageno, cfg, boatdata):
 | 
				
			||||||
 | 
					        super().__init__(pageno, cfg, boatdata)
 | 
				
			||||||
 | 
					        self.trend = True
 | 
				
			||||||
 | 
					        self.mode = 'A'
 | 
				
			||||||
 | 
					        self.avgindex = 0
 | 
				
			||||||
 | 
					        self.buttonlabel[1] = 'AVG'
 | 
				
			||||||
 | 
					        self.buttonlabel[2] = 'MODE'
 | 
				
			||||||
 | 
					        self.buttonlabel[5] = 'TRD'
 | 
				
			||||||
 | 
					        self.lastvalue = self.bd.voltage.value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def handle_key(self, buttonid):
 | 
				
			||||||
 | 
					        if buttonid == 1:
 | 
				
			||||||
 | 
					            if self.avgindex < len(self.avg) -1:
 | 
				
			||||||
 | 
					                self.avgindex += 1
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                self.avgindex = 0
 | 
				
			||||||
 | 
					        elif buttonid == 2:
 | 
				
			||||||
 | 
					            if self.mode == 'A':
 | 
				
			||||||
 | 
					                self.mode = 'D'
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                self.mode = 'A'
 | 
				
			||||||
 | 
					        elif buttonid == 5:
 | 
				
			||||||
 | 
					            self.trend = not self.trend
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def setBoatValue(self, boatvalue):
 | 
				
			||||||
 | 
					        # Einstellen welcher Wert dargestellt werden soll
 | 
				
			||||||
 | 
					        self.value1 = boatvalue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def draw(self, ctx):
 | 
				
			||||||
 | 
					        ctx.select_font_face("Ubuntu", cairo.FontSlant.NORMAL, cairo.FontWeight.BOLD)
 | 
				
			||||||
 | 
					        if self.mode == 'A':
 | 
				
			||||||
 | 
					            self.draw_analog(ctx)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            self.draw_digital(ctx)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def draw_analog(self, ctx):
 | 
				
			||||||
 | 
					        # TODO schönes Viertelkreis-Instrument
 | 
				
			||||||
 | 
					        # Skala von 9V bis 15V
 | 
				
			||||||
 | 
					        # d.h. 90° entsprechend unterteilen in 6 Stücke je 15°
 | 
				
			||||||
 | 
					        # Beschriftung 10, 11, 12, 13, 14
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Datenquelle
 | 
				
			||||||
 | 
					        ctx.set_font_size(16)
 | 
				
			||||||
 | 
					        ctx.move_to(300, 40)
 | 
				
			||||||
 | 
					        ctx.show_text("Source:")
 | 
				
			||||||
 | 
					        ctx.move_to(300, 60)
 | 
				
			||||||
 | 
					        ctx.show_text("VBat")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Batterietyp
 | 
				
			||||||
 | 
					        ctx.move_to(300, 90)
 | 
				
			||||||
 | 
					        ctx.show_text("Type:")
 | 
				
			||||||
 | 
					        ctx.move_to(300, 110)
 | 
				
			||||||
 | 
					        ctx.show_text("AGM")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Glättung
 | 
				
			||||||
 | 
					        ctx.move_to(300, 140)
 | 
				
			||||||
 | 
					        ctx.show_text("Avg:")
 | 
				
			||||||
 | 
					        ctx.move_to(300, 160)
 | 
				
			||||||
 | 
					        ctx.show_text("1s")
 | 
				
			||||||
 | 
					        ctx.stroke()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Gleichstromsymbol
 | 
				
			||||||
 | 
					        ctx.set_font_size(32)
 | 
				
			||||||
 | 
					        ctx.move_to(20, 80)
 | 
				
			||||||
 | 
					        ctx.show_text("V")
 | 
				
			||||||
 | 
					        ctx.set_line_width(3)
 | 
				
			||||||
 | 
					        ctx.move_to(20, 86.5) # obere Linie
 | 
				
			||||||
 | 
					        ctx.line_to(40, 86.5)
 | 
				
			||||||
 | 
					        ctx.move_to(20, 91.5) # untere drei kurzen Linien
 | 
				
			||||||
 | 
					        ctx.line_to(25, 91.5)
 | 
				
			||||||
 | 
					        ctx.move_to(27, 91.5)
 | 
				
			||||||
 | 
					        ctx.line_to(33, 91.5)
 | 
				
			||||||
 | 
					        ctx.move_to(35, 91.5)
 | 
				
			||||||
 | 
					        ctx.line_to(40, 91.5)
 | 
				
			||||||
 | 
					        ctx.stroke()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Kreis-segment 90°
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Rotationszentrum
 | 
				
			||||||
 | 
					        cx = 260
 | 
				
			||||||
 | 
					        cy = 270
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Radius des Instruments
 | 
				
			||||||
 | 
					        r = 240
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ctx.set_source_rgb(*self.fgcolor)
 | 
				
			||||||
 | 
					        ctx.set_line_width(2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ctx.arc(cx, cy, r, math.pi, 1.5*math.pi)
 | 
				
			||||||
 | 
					        ctx.stroke()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Beschriftung
 | 
				
			||||||
 | 
					        ctx.set_font_size(20)
 | 
				
			||||||
 | 
					        label = {285: "10", 300: "11", 315: "12", 330: "13", 345: "14"}
 | 
				
			||||||
 | 
					        for angle in label:
 | 
				
			||||||
 | 
					            x, y = self.rotate((cx, cy), ((cx, cy - r + 30),), angle)[0]
 | 
				
			||||||
 | 
					            self.draw_text_center(ctx, x, y, label[angle])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # grobe Skala
 | 
				
			||||||
 | 
					        p = ((cx, cy-r), (cx, cy - r + 12))
 | 
				
			||||||
 | 
					        ctx.set_line_width(2)
 | 
				
			||||||
 | 
					        for angle in label:
 | 
				
			||||||
 | 
					            line = self.rotate((cx, cy), p, angle)
 | 
				
			||||||
 | 
					            ctx.move_to(*line[0])
 | 
				
			||||||
 | 
					            ctx.line_to(*line[1])
 | 
				
			||||||
 | 
					        ctx.stroke()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # feine Skala
 | 
				
			||||||
 | 
					        p = ((cx, cy-r), (cx, cy - r + 5))
 | 
				
			||||||
 | 
					        ctx.set_line_width(1)
 | 
				
			||||||
 | 
					        for angle in [x for x in range(273, 360, 3)]:
 | 
				
			||||||
 | 
					            if angle in label:
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					            line = self.rotate((cx, cy), p, angle)
 | 
				
			||||||
 | 
					            ctx.move_to(*line[0])
 | 
				
			||||||
 | 
					            ctx.line_to(*line[1])
 | 
				
			||||||
 | 
					        ctx.stroke()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Zeiger auf 0-Position
 | 
				
			||||||
 | 
					        val = float(self.bd.voltage.format())
 | 
				
			||||||
 | 
					        if not val:
 | 
				
			||||||
 | 
					            angle = -0.5
 | 
				
			||||||
 | 
					        elif val > 15:
 | 
				
			||||||
 | 
					            angle = 91
 | 
				
			||||||
 | 
					        elif val <= 9:
 | 
				
			||||||
 | 
					            angle = -0.5
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            angle = (val - 9) * 15
 | 
				
			||||||
 | 
					        p = ((cx - 2, cy + 4),
 | 
				
			||||||
 | 
					             (cx - r + 35, cy + 2),
 | 
				
			||||||
 | 
					             (cx - r + 35, cy + 1),
 | 
				
			||||||
 | 
					             (cx - r + 5,  cy + 1),
 | 
				
			||||||
 | 
					             (cx - r + 5,  cy - 1),
 | 
				
			||||||
 | 
					             (cx - r + 35, cy - 1),
 | 
				
			||||||
 | 
					             (cx - r + 35, cy - 2),
 | 
				
			||||||
 | 
					             (cx - 2, cy - 4))
 | 
				
			||||||
 | 
					        zeiger = self.rotate((cx, cy), p, angle)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Zeiger zeichnen
 | 
				
			||||||
 | 
					        ctx.set_line_width(1)
 | 
				
			||||||
 | 
					        ctx.move_to(*zeiger[0])
 | 
				
			||||||
 | 
					        for point in zeiger[1:]:
 | 
				
			||||||
 | 
					            ctx.line_to(*point)
 | 
				
			||||||
 | 
					        ctx.fill()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Zeigerbasis 
 | 
				
			||||||
 | 
					        ctx.arc(cx, cy, 6, 0, 2*math.pi)
 | 
				
			||||||
 | 
					        ctx.set_source_rgb(*self.bgcolor)
 | 
				
			||||||
 | 
					        ctx.fill_preserve()
 | 
				
			||||||
 | 
					        ctx.set_line_width(2)
 | 
				
			||||||
 | 
					        ctx.set_source_rgb(*self.fgcolor)
 | 
				
			||||||
 | 
					        ctx.stroke()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def draw_digital(self, ctx):
 | 
				
			||||||
 | 
					        # Name
 | 
				
			||||||
 | 
					        ctx.set_font_size(60) 
 | 
				
			||||||
 | 
					        ctx.move_to(20, 100)
 | 
				
			||||||
 | 
					        ctx.show_text("VBat")
 | 
				
			||||||
 | 
					        # Unit
 | 
				
			||||||
 | 
					        ctx.set_font_size(40)
 | 
				
			||||||
 | 
					        ctx.move_to(270, 100)
 | 
				
			||||||
 | 
					        ctx.show_text("V")
 | 
				
			||||||
 | 
					        # Battery type
 | 
				
			||||||
 | 
					        ctx.set_font_size(16)
 | 
				
			||||||
 | 
					        ctx.move_to(294, 100)
 | 
				
			||||||
 | 
					        ctx.show_text("AGM")
 | 
				
			||||||
 | 
					        # Mittelwert
 | 
				
			||||||
 | 
					        ctx.move_to(320, 84)
 | 
				
			||||||
 | 
					        ctx.show_text("Avg: {}s".format(self.avg[self.avgindex]))
 | 
				
			||||||
 | 
					        ctx.stroke()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ctx.select_font_face("DSEG7 Classic")
 | 
				
			||||||
 | 
					        ctx.set_font_size(100)
 | 
				
			||||||
 | 
					        ctx.move_to(40, 240)
 | 
				
			||||||
 | 
					        ctx.show_text(self.bd.voltage.format())
 | 
				
			||||||
 | 
					        ctx.stroke()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Trendanzeige
 | 
				
			||||||
 | 
					        if self.trend and self.bd.voltage.value and self.lastvalue:
 | 
				
			||||||
 | 
					            ctx.rectangle(315, 183, 35, 4)
 | 
				
			||||||
 | 
					            ctx.fill()
 | 
				
			||||||
 | 
					            size = 11
 | 
				
			||||||
 | 
					            if self.lastvalue < self.bd.voltage.value:
 | 
				
			||||||
 | 
					                ctx.move_to(320, 174)
 | 
				
			||||||
 | 
					                ctx.line_to(320+size*2, 174)
 | 
				
			||||||
 | 
					                ctx.line_to(320+size, 174-size*2)
 | 
				
			||||||
 | 
					                ctx.line_to(320, 174)
 | 
				
			||||||
 | 
					                ctx.fill()
 | 
				
			||||||
 | 
					            elif self.lastvalue > self.bd.voltage.value:
 | 
				
			||||||
 | 
					                ctx.move_to(320, 195)
 | 
				
			||||||
 | 
					                ctx.line_to(320+size*2, 195)
 | 
				
			||||||
 | 
					                ctx.line_to(320+size, 195+size*2)
 | 
				
			||||||
 | 
					                ctx.line_to(320, 195)
 | 
				
			||||||
 | 
					                ctx.fill()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.lastvalue = self.bd.voltage.value
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,119 @@
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Windrose und Windroseflex
 | 
				
			||||||
 | 
					Benötigt 6 Werte
 | 
				
			||||||
 | 
					Hauptwerte: AWA, AWS, TWD, TWS
 | 
				
			||||||
 | 
					Nebenwerte: DBT, STW, oder COG, SOG
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cairo
 | 
				
			||||||
 | 
					import math
 | 
				
			||||||
 | 
					from .page import Page
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class WindRose(Page):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def draw(self, ctx):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Rahmen
 | 
				
			||||||
 | 
					        cx = 200
 | 
				
			||||||
 | 
					        cy = 150
 | 
				
			||||||
 | 
					        r = 110
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ctx.set_line_width(3)
 | 
				
			||||||
 | 
					        ctx.arc(cx, cy, r + 9, 0, 2*math.pi)
 | 
				
			||||||
 | 
					        ctx.stroke()
 | 
				
			||||||
 | 
					        ctx.arc(cx, cy, r - 11, 0, 2*math.pi)
 | 
				
			||||||
 | 
					        ctx.stroke()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for angle in range(0, 360, 10):
 | 
				
			||||||
 | 
					            if angle % 30 != 0:
 | 
				
			||||||
 | 
					                x, y = self.rotate((cx, cy), ((cx, cy - r),), angle)[0]
 | 
				
			||||||
 | 
					                ctx.arc(x, y, 2, 0, 2*math.pi)
 | 
				
			||||||
 | 
					                ctx.fill()
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                p = ((cx, cy - r + 10), (cx, cy - r - 10), (cx, cy - r + 30))
 | 
				
			||||||
 | 
					                pr = self.rotate((cx, cy), p, angle)
 | 
				
			||||||
 | 
					                ctx.move_to(*pr[0])
 | 
				
			||||||
 | 
					                ctx.line_to(*pr[1])
 | 
				
			||||||
 | 
					                ctx.stroke()
 | 
				
			||||||
 | 
					                self.draw_text_center(ctx, *pr[2], str(angle))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ctx.select_font_face("Ubuntu", cairo.FontSlant.NORMAL, cairo.FontWeight.BOLD)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Namen
 | 
				
			||||||
 | 
					        ctx.set_font_size(24) 
 | 
				
			||||||
 | 
					        ctx.move_to(10, 95)     # links oben
 | 
				
			||||||
 | 
					        ctx.show_text("AWA")
 | 
				
			||||||
 | 
					        ctx.move_to(335, 95)    # rechts oben
 | 
				
			||||||
 | 
					        ctx.show_text("TWD")
 | 
				
			||||||
 | 
					        ctx.move_to(10, 220)    # links unten
 | 
				
			||||||
 | 
					        ctx.show_text("AWS")
 | 
				
			||||||
 | 
					        ctx.move_to(335, 220)   # rechts unten
 | 
				
			||||||
 | 
					        ctx.show_text("TWS")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Units
 | 
				
			||||||
 | 
					        ctx.set_font_size(16)
 | 
				
			||||||
 | 
					        ctx.move_to(10, 115)     # links oben
 | 
				
			||||||
 | 
					        ctx.show_text("Deg")
 | 
				
			||||||
 | 
					        ctx.move_to(335, 115)    # rechts oben
 | 
				
			||||||
 | 
					        ctx.show_text("Deg")
 | 
				
			||||||
 | 
					        ctx.move_to(10, 190)    # links unten
 | 
				
			||||||
 | 
					        ctx.show_text("kn")
 | 
				
			||||||
 | 
					        ctx.move_to(335, 190)   # rechts unten
 | 
				
			||||||
 | 
					        ctx.show_text("kn")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Horiz. Trennlinien
 | 
				
			||||||
 | 
					        #ctx.rectangle(0, 149, 60, 3)
 | 
				
			||||||
 | 
					        #ctx.fill()
 | 
				
			||||||
 | 
					        ctx.set_line_width(3)
 | 
				
			||||||
 | 
					        # links
 | 
				
			||||||
 | 
					        ctx.move_to(0, 149)
 | 
				
			||||||
 | 
					        ctx.line_to(60, 149)
 | 
				
			||||||
 | 
					        # rechts
 | 
				
			||||||
 | 
					        ctx.move_to(340, 149)
 | 
				
			||||||
 | 
					        ctx.line_to(400, 149)
 | 
				
			||||||
 | 
					        ctx.stroke()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Meßwerte
 | 
				
			||||||
 | 
					        ctx.select_font_face("DSEG7 Classic")
 | 
				
			||||||
 | 
					        ctx.set_font_size(40) 
 | 
				
			||||||
 | 
					        # links oben
 | 
				
			||||||
 | 
					        ctx.move_to(10, 65)
 | 
				
			||||||
 | 
					        ctx.show_text("148")
 | 
				
			||||||
 | 
					         # rechts oben 
 | 
				
			||||||
 | 
					        ctx.move_to(295, 65)
 | 
				
			||||||
 | 
					        ctx.show_text("---")
 | 
				
			||||||
 | 
					        # links unten
 | 
				
			||||||
 | 
					        ctx.move_to(10, 270)
 | 
				
			||||||
 | 
					        ctx.show_text("46.7")
 | 
				
			||||||
 | 
					        # rechts unten
 | 
				
			||||||
 | 
					        ctx.move_to(295, 270)
 | 
				
			||||||
 | 
					        ctx.show_text("77.8")
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        ctx.set_font_size(32) 
 | 
				
			||||||
 | 
					        # innen oben
 | 
				
			||||||
 | 
					        ctx.move_to(160, 130)
 | 
				
			||||||
 | 
					        ctx.show_text("38.9")
 | 
				
			||||||
 | 
					        # innen unten  
 | 
				
			||||||
 | 
					        ctx.move_to(160, 200)
 | 
				
			||||||
 | 
					        ctx.show_text("19.9")
 | 
				
			||||||
 | 
					        ctx.stroke()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Zeiger
 | 
				
			||||||
 | 
					        angle = 148
 | 
				
			||||||
 | 
					        p = ((cx - 1, cy - (r - 15)), (cx + 1, cy - (r - 15)), (cx + 4, cy), (cx - 4, cy))
 | 
				
			||||||
 | 
					        zeiger = self.rotate((cx, cy), p, angle)
 | 
				
			||||||
 | 
					        ctx.move_to(*zeiger[0])
 | 
				
			||||||
 | 
					        for point in zeiger[1:]:
 | 
				
			||||||
 | 
					            ctx.line_to(*point)
 | 
				
			||||||
 | 
					        ctx.fill()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ctx.set_source_rgb(*self.bgcolor)
 | 
				
			||||||
 | 
					        ctx.arc(cx, cy, 8, 0, 2*math.pi)
 | 
				
			||||||
 | 
					        ctx.fill()
 | 
				
			||||||
 | 
					        ctx.set_source_rgb(*self.fgcolor)
 | 
				
			||||||
 | 
					        ctx.arc(cx, cy, 7, 0, 2*math.pi)
 | 
				
			||||||
 | 
					        ctx.fill()
 | 
				
			||||||