Compare commits
65 Commits
e94f6df408
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| ba43c3147e | |||
| 1661c81dfa | |||
| cd04a5560d | |||
| 9ffb28d65b | |||
| 7613a5b7cb | |||
| f969df8cb8 | |||
| e7a2f4bb54 | |||
| e9d473fd10 | |||
| fb7c688a99 | |||
| 99dcf00242 | |||
| 2572d156e0 | |||
| de1cff974d | |||
| 8d75782ae8 | |||
| d4ac504e08 | |||
| fd5b2c96b9 | |||
| adb1b4e63a | |||
| a5baea524e | |||
| f6f515ea14 | |||
| f47379b6d3 | |||
| e7a96390f2 | |||
| f0ebdd0201 | |||
| e5646b6f27 | |||
| 84a99a747e | |||
| bcbaa25cf5 | |||
| 65335e3c91 | |||
| eab6cdf4c9 | |||
| d3a984075e | |||
| fc87a5959d | |||
| 9b4811a11d | |||
| eb41bdafa4 | |||
| ebb7b42d48 | |||
| 766b191109 | |||
| f3c9ced477 | |||
| 20882feeca | |||
| defe4e3c03 | |||
| 5b000f4f1f | |||
| 612783454e | |||
| b1d0687952 | |||
| acbcfac425 | |||
| fd673d5e55 | |||
| 4ba75b5686 | |||
| e738438a94 | |||
| 14108b1165 | |||
| 7d8ce221fd | |||
| be20826ab5 | |||
| d7136e38bf | |||
| dafcfaca6b | |||
| 986d222a98 | |||
| 664d6c7d49 | |||
| 5d8b33b086 | |||
| ae38d2b681 | |||
| 7f0a0f280b | |||
| 658806446e | |||
| 7d3a113ba3 | |||
| 46295f8139 | |||
| f7337a0c6c | |||
| 9e4622aeef | |||
| df04c9ad8e | |||
| b1fdb592b0 | |||
| 6214d08174 | |||
| aefd01e0f9 | |||
| b06d88d0ec | |||
| bae459c4d4 | |||
| 8b5168a83e | |||
| 5c2f058888 |
2
.gitignore
vendored
@@ -1,2 +1,4 @@
|
||||
*~
|
||||
__pycache__
|
||||
nmea2000
|
||||
obp60v.conf
|
||||
|
||||
16
INSTALL
@@ -4,7 +4,8 @@ erforderlich. Die unten angegebenen Abhängigkeiten müssen erfüllt sein.
|
||||
Python muß mindestens Version 3.10 sein.
|
||||
|
||||
apt-get install python3-cairo python3-gi python3-gi-cairo gir1.2-rsvg-2.0 \
|
||||
python3-serial python3-nmea2 python3-smbus2 python3-bme280 python3-astral
|
||||
python3-serial python3-nmea2 python3-smbus2 python3-bme280 python3-astral \
|
||||
python3-can python3-paho-mqtt python3-setproctitle gir1.2-gtk-3.0
|
||||
|
||||
Zusätzlich zu diesem Programm muß auch die zugehörige NMEA2000-Bibliothek
|
||||
für Python vorhanden sein. Diese kann momentan am besten parallel zu dem
|
||||
@@ -13,13 +14,20 @@ OBP60v-Verzeichnis installiert werden:
|
||||
cd OBP60v
|
||||
ln -s ../nmea2000 .
|
||||
|
||||
Das Programm wird über eine Konfigurationsdatei obp60.conf im gleichen
|
||||
Das Programm wird über eine Konfigurationsdatei obp60v.conf im gleichen
|
||||
Verzeichnis wie das Hauptprogramm gesteuert. Die Konfiguration wird
|
||||
einmalig beim Programmstart eingelesen.
|
||||
einmalig beim Programmstart eingelesen. Dazu kann die Vorlage kopiert
|
||||
werden:
|
||||
cp obp60v.conf-sample obp60v.conf
|
||||
|
||||
Die Schriftarten aus dem fonts-Verzeichnis müssen in /usr/local/share/fonts
|
||||
abgelegt werden.
|
||||
sudo cp -a fonts/* /usr/local/share/fonts
|
||||
|
||||
Meßdaten werden im Homeverzeichnis unter ~/.local/lib/obp60 gespeichert.
|
||||
Meßdaten werden im Homeverzeichnis unter ~/.local/lib/obp60v gespeichert.
|
||||
Dies betrifft momentan Luftdruckmessungen mit dem BME280.
|
||||
Das Verzeichnis wird automatisch angelegt.
|
||||
|
||||
Wenn das GUI-Fenster an den Ecken schwarze Stellen hat, dann ist vermutlich
|
||||
kein Kompositor vorhanden. Nachinstallieren z.B.:
|
||||
apt-get install xcompmgr
|
||||
|
||||
12
README
@@ -31,14 +31,20 @@ Abhängigkeiten
|
||||
- python3-heapdict
|
||||
- python3-setproctitle
|
||||
|
||||
Für GPS
|
||||
Für GPS und/oder NMEA0183
|
||||
- python3-serial
|
||||
- python3-nmea2
|
||||
Achtung: in Debian ist Version 1.15, die aktuelle Version 1.19 hat
|
||||
weitere Satzarten implementiert. Allerdings ist dort auch kein AIS
|
||||
vorhanden.
|
||||
|
||||
Für BME280
|
||||
- python3-smbus2
|
||||
- python3-bme280
|
||||
|
||||
Für den Tracker
|
||||
- python3-paho-mqtt
|
||||
|
||||
Die Konfiguration des virtuellen Geräts erfolgt über die Datei "obp60.conf".
|
||||
Die Einstellungen sollten vor dem ersten Start überprüft und ggf. angepaßt
|
||||
werden.
|
||||
@@ -48,7 +54,7 @@ Der Standardmodus sollte im Design möglichst nahme am Original OBP60 liegen.
|
||||
Es ist ein Fenster was immer im Vordergrund liegt, sich jedoch auf dem
|
||||
Bildschirm positionieren läßt.
|
||||
Der Vollbildmodus ist für das Display des OBP60P gedacht und setzt eine
|
||||
physische Bildschirmauflösung von 800x600 Pixeln voraus.
|
||||
physische Bildschirmauflösung von 800x480 Pixeln voraus.
|
||||
Der Modus kann vor dem Programmstart in der Konfigurationsdatei obp60.conf
|
||||
eingestellt werden:
|
||||
[system]
|
||||
@@ -73,7 +79,7 @@ Folgende Wischfunktionen sind implementiert:
|
||||
|
||||
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:
|
||||
Im System kann diese in der Datei z.B. 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 &
|
||||
|
||||
38
TODO
@@ -1,4 +1,26 @@
|
||||
Aufgaben- und Planungs- und Ideenliste
|
||||
Aufgaben, Planungs- und Ideenliste
|
||||
|
||||
- Tracker
|
||||
a) Regatta Hero
|
||||
python3-paho-mqtt benötigt
|
||||
b) lokal
|
||||
c) Server
|
||||
|
||||
- Internationalisierung
|
||||
1. EN (Standardsprache)
|
||||
2. DE
|
||||
3. FR
|
||||
4. ES
|
||||
5. IT
|
||||
|
||||
- Satelliten: SatelliteList verwenden und dieses auch in
|
||||
nmea2000 implementieren
|
||||
- Satellitenübersicht / SkyView
|
||||
Kreis mit Sats
|
||||
Rechtecke mit SNR (Signal/Noise)
|
||||
|
||||
- datareader für mehrere I²C-Sensoren. History muß entsprechend
|
||||
eine Liste sein.
|
||||
|
||||
- Barograph
|
||||
|
||||
@@ -6,8 +28,14 @@ Aufgaben- und Planungs- und Ideenliste
|
||||
Kreis um Position, Liste letzter Positionen
|
||||
Tasten: Set/Clear, Radius+, Radius-
|
||||
|
||||
- Satellitenübersicht / SkyView
|
||||
Kreis mit Sats
|
||||
Rechtecke mit SNR (Signal/Noise)
|
||||
|
||||
- Sea wave recorder (siehe Steamrock)
|
||||
|
||||
- Datenübertragung über Ethernet
|
||||
Achtung: Fast Packets
|
||||
- SeaSmart
|
||||
NMEA2000 in NMEA0183 gekapselt
|
||||
- Yacht Devices ASCII Raw Protokoll
|
||||
https://www.yachtd.com/downloads/ydnr02.pdf
|
||||
- Actisense?
|
||||
|
||||
- AIS
|
||||
|
||||
80
appdata.py
Normal file
@@ -0,0 +1,80 @@
|
||||
"""
|
||||
Generische Applikationsdaten
|
||||
|
||||
"""
|
||||
|
||||
import os
|
||||
from web import WebInterface
|
||||
from tracker import Tracker
|
||||
from navtex import NAVTEX
|
||||
from mapservice import MapService
|
||||
|
||||
class AppData():
|
||||
|
||||
def __init__(self, logger, cfg):
|
||||
self.shutdown = False # Globaler Ausschalter
|
||||
self.log = logger
|
||||
if cfg['navtex']:
|
||||
self.navtex = NAVTEX(logger, cfg)
|
||||
else:
|
||||
self.navtex = None
|
||||
if cfg['tide']:
|
||||
self.web = WebInterface(logger, cfg)
|
||||
else:
|
||||
self.web = None
|
||||
self.track = Tracker(logger, cfg)
|
||||
self.mapsrv = MapService(logger, cfg)
|
||||
self.frontend = None
|
||||
self.bv_lat = None
|
||||
self.bv_lon = None
|
||||
|
||||
# Für u.a. Header-Indikatoren
|
||||
# TODO
|
||||
self.status = {
|
||||
'AP': False, # Accesspoint
|
||||
'WIFI': False, # Wireless Client
|
||||
'TCP': False, # TCP Network
|
||||
'N2K': False, # NMEA2000
|
||||
'183': False, # NMEA0183
|
||||
'USB': False, # USB Datenverbindung
|
||||
'GPS': False, # GPS-Fix und -daten
|
||||
'TRK': False # Tracker
|
||||
}
|
||||
|
||||
def setFrontend(self, frontend):
|
||||
self.frontend = frontend # Referenz zur GUI
|
||||
self.bv_lat = frontend.boatdata.getRef("LAT")
|
||||
self.bv_lon = frontend.boatdata.getRef("LON")
|
||||
|
||||
def refreshStatus(self):
|
||||
self.status['AP'] = False # nicht implementiert
|
||||
|
||||
self.status['TCP'] = False
|
||||
self.status['WIFI'] = False
|
||||
for intf in os.listdir('/sys/class/net'):
|
||||
statefile = os.path.join('/sys/class/net', intf, 'operstate')
|
||||
wififile = os.path.join('/sys/class/net', intf, 'wireless')
|
||||
if os.path.exists(statefile):
|
||||
with open(statefile) as fh:
|
||||
state = fh.read().strip()
|
||||
if state == 'up':
|
||||
if os.path.exists(wififile):
|
||||
self.status['WIFI'] = True
|
||||
else:
|
||||
self.status['TCP'] = True
|
||||
|
||||
# TODO NMEA2000
|
||||
# can-Interface can0 im Netzwerk. Identifikation?
|
||||
|
||||
# TODO NMEA0183 tty auf Konfiguration
|
||||
# enabled in Konfiguration
|
||||
# port muß gültige Schnittstelle sein
|
||||
|
||||
# TODO USB /dev/ttyUSB0?
|
||||
|
||||
# GPS
|
||||
# Kann ein Empfänger am USB sein. Siehe Konfiguration
|
||||
self.status['GPS'] = self.bv_lat and self.bv_lon and self.bv_lat.valid and self.bv_lon.valid
|
||||
|
||||
# Tracker
|
||||
self.status['TRK'] = self.track.is_active()
|
||||
BIN
audio/30sek_alerter.mp3
Normal file
BIN
audio/30sek_alerter_en.mp3
Normal file
BIN
audio/abbruch.mp3
Normal file
BIN
audio/abbruch_en.mp3
Normal file
BIN
audio/acht.mp3
Normal file
BIN
audio/acht_en.mp3
Normal file
BIN
audio/alive.mp3
Normal file
BIN
audio/allgmRueck.mp3
Normal file
BIN
audio/allgmRueck_en.mp3
Normal file
BIN
audio/bahnVerk.mp3
Normal file
BIN
audio/bahnVerk_en.mp3
Normal file
BIN
audio/bahnmarke.mp3
Normal file
BIN
audio/bahnmarke_en.mp3
Normal file
BIN
audio/batteryLevel.mp3
Normal file
BIN
audio/batteryLevel_en.mp3
Normal file
BIN
audio/cellback.mp3
Normal file
BIN
audio/cellback_en.mp3
Normal file
BIN
audio/drei.mp3
Normal file
BIN
audio/dreiMin.mp3
Normal file
BIN
audio/dreiMin_en.mp3
Normal file
BIN
audio/drei_en.mp3
Normal file
BIN
audio/dreissig.mp3
Normal file
BIN
audio/dreissig_en.mp3
Normal file
BIN
audio/eineMin.mp3
Normal file
BIN
audio/eineMin_en.mp3
Normal file
BIN
audio/eins.mp3
Normal file
BIN
audio/eins_en.mp3
Normal file
BIN
audio/einzelRueck.mp3
Normal file
BIN
audio/einzelRueck_en.mp3
Normal file
BIN
audio/endeEineMinute.mp3
Normal file
BIN
audio/endeEineMinute_en.mp3
Normal file
BIN
audio/endeWettfahrt.mp3
Normal file
BIN
audio/endeWettfahrt_en.mp3
Normal file
BIN
audio/endstartVerschiebung.mp3
Normal file
BIN
audio/endstartVerschiebung_en.mp3
Normal file
BIN
audio/fuenf.mp3
Normal file
BIN
audio/fuenfMin.mp3
Normal file
BIN
audio/fuenfMin_en.mp3
Normal file
BIN
audio/fuenf_en.mp3
Normal file
BIN
audio/fuenfzehn.mp3
Normal file
BIN
audio/fuenfzehn_en.mp3
Normal file
BIN
audio/fuenfzig.mp3
Normal file
BIN
audio/fuenfzig_en.mp3
Normal file
BIN
audio/jumping.mp3
Normal file
BIN
audio/jumping_en.mp3
Normal file
BIN
audio/neueAnsage.mp3
Normal file
BIN
audio/neueAnsage_en.mp3
Normal file
BIN
audio/neun.mp3
Normal file
BIN
audio/neun_en.mp3
Normal file
BIN
audio/noconnection.mp3
Normal file
BIN
audio/noconnection_en.mp3
Normal file
BIN
audio/runde.mp3
Normal file
BIN
audio/runde_en.mp3
Normal file
BIN
audio/sechs.mp3
Normal file
BIN
audio/sechs_en.mp3
Normal file
BIN
audio/sieben.mp3
Normal file
BIN
audio/sieben_en.mp3
Normal file
BIN
audio/startErfolgt.mp3
Normal file
BIN
audio/startErfolgt_en.mp3
Normal file
BIN
audio/startVerschiebung.mp3
Normal file
BIN
audio/startVerschiebung_en.mp3
Normal file
BIN
audio/startlinie.mp3
Normal file
BIN
audio/startlinie_en.mp3
Normal file
BIN
audio/startnotready.mp3
Normal file
BIN
audio/startnotready_en.mp3
Normal file
BIN
audio/startready.mp3
Normal file
BIN
audio/startready_en.mp3
Normal file
BIN
audio/vier.mp3
Normal file
BIN
audio/vierMin.mp3
Normal file
BIN
audio/vierMin_en.mp3
Normal file
BIN
audio/vier_en.mp3
Normal file
BIN
audio/vierzig.mp3
Normal file
BIN
audio/vierzig_en.mp3
Normal file
BIN
audio/wartenAnkuend.mp3
Normal file
BIN
audio/wartenAnkuend_en.mp3
Normal file
BIN
audio/zehn.mp3
Normal file
BIN
audio/zehn_en.mp3
Normal file
BIN
audio/ziellinie.mp3
Normal file
BIN
audio/ziellinie_en.mp3
Normal file
BIN
audio/zwanzig.mp3
Normal file
BIN
audio/zwanzig_en.mp3
Normal file
BIN
audio/zwei.mp3
Normal file
BIN
audio/zweiMin.mp3
Normal file
BIN
audio/zweiMin_en.mp3
Normal file
BIN
audio/zwei_en.mp3
Normal file
98
cfgmenu.py
Normal file
@@ -0,0 +1,98 @@
|
||||
"""
|
||||
Menüsystem für Konfiguration(en)
|
||||
|
||||
"""
|
||||
|
||||
class MenuItem():
|
||||
|
||||
def __init__(self, valtype, label, value=None, unit=''):
|
||||
validtypes = ('int', 'bool')
|
||||
valtype = valtype.lower() # Groß- und Kleinschreibung lassen wir gemischt zu
|
||||
if valtype not in validtypes:
|
||||
raise TypeError(f"Invalid value type: '{valtype}'. Only supported: {validtypes}")
|
||||
self.label = label # Anzeigetext des Menüeintrag
|
||||
self.value = value # Zugeordneter Wert für diesen Eintrag
|
||||
self.unit = unit
|
||||
self._type = valtype
|
||||
self._min = 0
|
||||
self._max = 99999
|
||||
self.steps = (1, 10, 100, 1000) # Sprungmöglichkeiten für +/- Tasten
|
||||
self.step = 0 # index into tuple above
|
||||
self.position = None # Menüposition gezählt von 0 an
|
||||
|
||||
def setRange(self, valmin, valmax, steps):
|
||||
self.min = valmin
|
||||
self.max = valmax
|
||||
self.steps = steps
|
||||
|
||||
def setValue(self, val):
|
||||
if self._type == 'int':
|
||||
if val >= self._min and val <= self._max:
|
||||
self.value = val
|
||||
return True
|
||||
elif self.type == 'bool':
|
||||
self.value = val
|
||||
return True
|
||||
return False
|
||||
|
||||
class Menu():
|
||||
|
||||
def __init__(self, title, x, y):
|
||||
self.title = title
|
||||
self.items = {} # Items über Schlüssel zugreifbar
|
||||
self.activeitem = -1 # Noch nichts aktiv
|
||||
self._x = x
|
||||
self._y = y
|
||||
self._w = 100
|
||||
self._h = 20
|
||||
self._index = [] # Mapping zwischen Index(Position) und Schlüssel
|
||||
self._iter_index = 0
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
def __next__(self):
|
||||
if self._iter_index < len(self.items):
|
||||
itm = self.items[self._index[self._iter_index]]
|
||||
self._iter_index += 1
|
||||
return itm
|
||||
self._iter_index = 0
|
||||
raise StopIteration
|
||||
|
||||
def addItem(self, key, label, valtype, value=None, unit=''):
|
||||
if key in self.items.keys():
|
||||
raise KeyError(f"Duplicate menu item key: '{key}'")
|
||||
itm = MenuItem(valtype, label, value, unit)
|
||||
self.items[key] = itm
|
||||
self._index.append(key)
|
||||
itm.position = self._index.index(key)
|
||||
return itm
|
||||
|
||||
def setItemActive(self, key):
|
||||
self.activeitem = self._index.index(key)
|
||||
|
||||
def getActiveItem(self):
|
||||
return self.items[self._index[self.activeitem]]
|
||||
|
||||
def getItemByIndex(self, index):
|
||||
return self.items[self._index[index]]
|
||||
|
||||
def getItemByKey(self, key):
|
||||
return self.items[key]
|
||||
|
||||
def getItemCount(self):
|
||||
return len(self.items)
|
||||
|
||||
def setItemDimension(self, w, h):
|
||||
self._w = w
|
||||
self._h = h
|
||||
|
||||
def getXY(self):
|
||||
return (self._x, self._y)
|
||||
|
||||
def getRect(self):
|
||||
return (self._x, self._y, self._w, self._h * len(self.items))
|
||||
|
||||
def getItemRect(self, index):
|
||||
y = self._y + index * self._h
|
||||
return (self._x, y, self._w, self._h)
|
||||
21
graph.py
Normal file
@@ -0,0 +1,21 @@
|
||||
"""
|
||||
Generische Klasse zum Diagramme-Zeichnen
|
||||
"""
|
||||
|
||||
class Graph():
|
||||
|
||||
def __init__(self):
|
||||
self.title = "Title"
|
||||
self.subtitle = "Subtitle"
|
||||
self.title_x = "Title X"
|
||||
self.title_y = "Title Y"
|
||||
self.has_arrows = False
|
||||
|
||||
def paint(self, ctx):
|
||||
"""
|
||||
Output Graph to Cairo context
|
||||
"""
|
||||
pass
|
||||
|
||||
def add_series(self):
|
||||
pass
|
||||
BIN
images/arrow_dn.png
Normal file
|
After Width: | Height: | Size: 135 B |
BIN
images/arrow_up.png
Normal file
|
After Width: | Height: | Size: 138 B |
BIN
images/exclamation.png
Normal file
|
After Width: | Height: | Size: 203 B |
BIN
images/flags/alpha.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
images/flags/answer.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
images/flags/black.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
images/flags/blue.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
images/flags/charlie.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |