315 lines
10 KiB
Python
315 lines
10 KiB
Python
"""
|
|
|
|
Ankerinfo / -alarm
|
|
|
|
Daten
|
|
- Position des Ankers
|
|
- Wassertiefe beim Ankern
|
|
- Gesteckte Kettenlänge
|
|
- aktuelle Position des Schiffs
|
|
- aktuelle Wassertiefe an Schiffsposition
|
|
- aktuelle Schiffsausrichtung bearing/heading
|
|
- aktuelle Windrichtung (wahr)
|
|
- aktuelle Windstärke (wahr)
|
|
- Alarm aktiv J/N
|
|
- Alarmradius
|
|
- GPS Abweichung/ Fehler in der Horizontalen
|
|
- Zeitpunkt des Ankeraus
|
|
|
|
Darstellung:
|
|
- Modi: Normal / Konfiguration
|
|
- Anker oben / unten
|
|
- Nord ist oben
|
|
|
|
Es gibt verschiedene Unterseiten
|
|
- Normalansicht
|
|
- Konfigurationsseite
|
|
|
|
"""
|
|
|
|
import os
|
|
import cairo
|
|
import math
|
|
import time
|
|
from cfgmenu import Menu
|
|
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] = 'MODE'
|
|
self.buttonlabel[2] = 'DROP'
|
|
self.buttonlabel[5] = '' # ALARM erst möglich wenn der Anker unten ist
|
|
|
|
self.mode = 'N' # (N)ormal, (C)onfiguration
|
|
self.scale = 50 # Radius of display circle in meter
|
|
|
|
self._bd = boatdata
|
|
|
|
# Der sinnvolle Abstand ist abhängig von der Länge der gesteckten Kette
|
|
# Die initial eingegebene Position des Ankers sollte nactträglich justiert
|
|
# werden können
|
|
|
|
self.chain_length = 60 # maximale Länge die ausgesteckt werden kann
|
|
self.chain = 35 # aktuell gesteckte Länge
|
|
self.anchor_set = False
|
|
self.anchor_lat = 0
|
|
self.anchor_lon = 0
|
|
self.anchor_depth = -1
|
|
self.anchor_ts = None # Timestamp of dropped anchor
|
|
self.lat = 0
|
|
self.lon = 0
|
|
self.heading = -1
|
|
self.depth = -1
|
|
self.alarm_range = 20
|
|
self.alarm_enabled = False
|
|
self.alarm = False # Alarm ist ausgelöst und aktiv
|
|
self.wind_angle = -1
|
|
|
|
# Menüsteuerung für Konfiguration
|
|
self._menu = Menu("Options", 20, 80)
|
|
self._menu.setItemDimension(120, 20)
|
|
newitem = self._menu.addItem("chain", "Chain out", "int", 0, "m")
|
|
newitem.setRange(0, 200, (1, 5, 10))
|
|
newitem = self._menu.addItem("chainmax", "Chain max", "int", self.chain_length, "m")
|
|
newitem.setRange(0, 200, (1, 5, 10))
|
|
newitem = self._menu.addItem("zoom", "Zoom", "int", 2)
|
|
newitem.setRange(1, 8, (1,))
|
|
newitem = self._menu.addItem("range", "Alarm range", "int", 40, "m")
|
|
newitem.setRange(1, 200, (1, 5, 10))
|
|
self._menu.setItemActive("chain")
|
|
|
|
self._test = 0
|
|
|
|
def handle_key(self, buttonid):
|
|
if buttonid == 1:
|
|
if self.mode == 'N':
|
|
self.mode = 'C'
|
|
self.buttonlabel[2] = '#UP'
|
|
self.buttonlabel[3] = '#DOWN'
|
|
itm = self._menu.getActiveItem()
|
|
stepwidth = itm.steps[itm.step]
|
|
self.buttonlabel[4] = f"-{stepwidth}"
|
|
self.buttonlabel[5] = f"+{stepwidth}"
|
|
self.buttonlabel[6] = 'STEP'
|
|
else:
|
|
self.mode = 'N'
|
|
self.buttonlabel[2] = 'RISE' if self.anchor_set else 'DROP'
|
|
self.buttonlabel[3] = '#PREV'
|
|
self.buttonlabel[4] = '#NEXT'
|
|
self.buttonlabel[5] = 'ALARM' if not self.alarm_enabled else 'OFF'
|
|
return True
|
|
if self.mode == 'N':
|
|
# Normal
|
|
if buttonid == 2:
|
|
if not self.anchor_set:
|
|
self.anchor_lat = self._bd.lat
|
|
self.anchor_lon = self._bd.lon
|
|
self.anchor_set = True
|
|
self.anchor_ts = time.time()
|
|
self.buttonlabel[2] = 'RISE'
|
|
self.buttonlabel[5] = 'ALARM'
|
|
else:
|
|
self.anchor_set = False
|
|
self.alarm = False
|
|
self.alarm_enabled = False
|
|
self.anchor_ts = None
|
|
self.buttonlabel[2] = 'DROP'
|
|
self.buttonlabel[5] = ''
|
|
return True
|
|
elif buttonid == 5:
|
|
# Bei aktivem Alarm kann mit dieser Taste der Alarm zurückgesetzt
|
|
# werden. Die Tastenbeschriftung wechselt zwischen ALARM und OFF.
|
|
if self.alarm:
|
|
self.alarm = False
|
|
self.buttonlabel[5] = 'ALARM'
|
|
if self.alarm_enabled:
|
|
self.alarm_enabled = False
|
|
self.buttonlabel[5] = 'ALARM'
|
|
else:
|
|
self.alarm_enabled = True
|
|
self.buttonlabel[5] = 'OFF'
|
|
return True
|
|
else:
|
|
# Konfiguration
|
|
itm = self._menu.getActiveItem()
|
|
if buttonid == 2:
|
|
if self._menu.activeitem == 0:
|
|
self._menu.activeitem = self._menu.getItemCount() - 1
|
|
else:
|
|
self._menu.activeitem -= 1
|
|
elif buttonid == 3:
|
|
if self._menu.activeitem == self._menu.getItemCount() - 1:
|
|
self._menu.activeitem = 0
|
|
else:
|
|
self._menu.activeitem += 1
|
|
elif buttonid == 4:
|
|
# decrease value by step
|
|
stepwidth = itm.steps[itm.step]
|
|
itm.setValue(itm.value - stepwidth)
|
|
elif buttonid == 5:
|
|
# increase value by step
|
|
stepwidth = itm.steps[itm.step]
|
|
itm.setValue(itm.value + stepwidth)
|
|
elif buttonid == 6:
|
|
ns = len(itm.steps)
|
|
if ns > 1:
|
|
if itm.step < ns - 1:
|
|
itm.step += 1
|
|
else:
|
|
itm.step = 0
|
|
stepwidth = itm.steps[itm.step]
|
|
self.buttonlabel[4] = f"-{stepwidth}"
|
|
self.buttonlabel[5] = f"+{stepwidth}"
|
|
return True
|
|
return False
|
|
|
|
def draw_normal(self, ctx):
|
|
|
|
"""
|
|
value1 = LAT
|
|
value2 = LON
|
|
value3 = HDOP
|
|
value4 = DBS
|
|
value5 = TWD
|
|
value6 = TWS
|
|
"""
|
|
|
|
# self.anchor_lat =
|
|
|
|
# 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(2, 200)
|
|
ctx.show_text("Depth")
|
|
ctx.move_to(320, 50)
|
|
ctx.show_text("Chain")
|
|
|
|
ctx.set_font_size(16)
|
|
ctx.move_to(2, 70)
|
|
ctx.show_text("Alarm: ")
|
|
if self.alarm_enabled:
|
|
ctx.show_text("On")
|
|
else:
|
|
ctx.show_text("Off")
|
|
ctx.move_to(320, 70)
|
|
ctx.show_text(f"{self.chain} m")
|
|
|
|
ctx.move_to(10, 220)
|
|
if self._bd.dbs.valid:
|
|
ctx.show_text(self._bd.dbs.format())
|
|
|
|
ctx.stroke()
|
|
|
|
# Spezialseite
|
|
cx = 200
|
|
cy = 150
|
|
r = 125
|
|
|
|
# Skala
|
|
ctx.set_line_width(1)
|
|
ctx.arc(cx, cy, r, 0, 2*math.pi)
|
|
ctx.move_to(cx + 10, cy + 0.5)
|
|
ctx.line_to(cx + r - 4, cy + 0.5)
|
|
ctx.move_to(cx + r / 2, cy + 20)
|
|
# Pfeil links
|
|
ctx.move_to(cx + 10, cy + 0.5)
|
|
ctx.line_to(cx + 16, cy - 4 + 0.5)
|
|
ctx.move_to(cx + 10, cy + 0.5)
|
|
ctx.line_to(cx + 16, cy + 4 + 0.5)
|
|
# Pfeil rechts
|
|
ctx.move_to(cx + r - 4, cy + 0.5)
|
|
ctx.line_to(cx + r - 10, cy - 4 + 0.5)
|
|
ctx.move_to(cx + r - 4, cy + 0.5)
|
|
ctx.line_to(cx + r - 10, cy + 4 + 0.5)
|
|
|
|
ctx.set_font_size(16)
|
|
self.draw_text_center(ctx, cx + r / 2, cy + 8, str(self.scale) + " m")
|
|
ctx.stroke()
|
|
|
|
ctx.set_line_width(1.5)
|
|
# Ankersymbol falls Anker fallen gelassen wurde
|
|
# Ansonsten ist das Boot-Symbol im Mittelpunkt
|
|
if self.anchor_set:
|
|
ctx.save()
|
|
ctx.set_source_surface(self.sym_anchor, cx-8, cy-8)
|
|
ctx.paint()
|
|
ctx.restore()
|
|
|
|
# Boot zeichnen
|
|
# Heading beachten
|
|
# Wir arbeiten mit einer Kopie, weil bx/by im Laufe der Zeit von
|
|
# cx/cy abweichen werden
|
|
if self.anchor_set:
|
|
bx = cx
|
|
by = cy + 30
|
|
else:
|
|
bx = cx
|
|
by = cy + 8
|
|
p = ((bx - 5, by), (bx - 5, by - 10), (bx, by - 16), (bx + 5, by - 10), (bx + 5, by), (bx - 6, by))
|
|
if self._bd.hdt.value:
|
|
# Rotiere Boot-Symbol um eigenes Zentrum
|
|
boat = self.rotate((bx, by - 8), p, self._bd.hdt.value)
|
|
else:
|
|
# Kein Heading, Boot immer zeichnen
|
|
boat = p
|
|
ctx.move_to(*boat[0])
|
|
for point in boat[1:]:
|
|
ctx.line_to(*point)
|
|
ctx.stroke()
|
|
|
|
# Windpfeil zeichnen
|
|
if self._bd.awa.value:
|
|
p = ((cx, cy - r + 25), (cx - 12, cy - r - 4), (cx, cy - r + 6), (cx + 12, cy - r - 4), (cx, cy - r + 25))
|
|
wind = self.rotate((cx, cy), p, self._bd.awa.value)
|
|
ctx.move_to(*wind[0])
|
|
for point in wind[1:]:
|
|
ctx.line_to(*point)
|
|
ctx.fill()
|
|
|
|
def draw_config(self, ctx):
|
|
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 configuration")
|
|
|
|
# Menü zeichnen
|
|
ctx.save()
|
|
ctx.set_font_size(16)
|
|
x, y, w, h = self._menu.getRect()
|
|
ctx.set_line_width(1)
|
|
x += 0.5 # Cairo-Fix for single pixel line
|
|
y += 0.5
|
|
ctx.save()
|
|
ctx.rectangle(x, y, w, h)
|
|
ctx.clip_preserve()
|
|
ctx.stroke()
|
|
for m in self._menu:
|
|
ix, iy, iw, ih = self._menu.getItemRect(m.position)
|
|
inverted = (m.position == self._menu.activeitem)
|
|
self.draw_text_boxed(ctx, ix, iy, iw, ih, m.label, inverted, False)
|
|
ctx.stroke()
|
|
# Werte neben dem Menü
|
|
ctx.restore()
|
|
ctx.rectangle(0, 20, 400, 360) # new clipping
|
|
ctx.clip()
|
|
self._test += 1
|
|
for m in self._menu:
|
|
ix, iy, iw, ih = self._menu.getItemRect(m.position)
|
|
ctx.move_to(ix + iw + 20, iy + ih - 4) # 5 für Unterlängen
|
|
ctx.show_text(f"{m.value} {m.unit}")
|
|
ctx.stroke()
|
|
ctx.restore()
|
|
|
|
def draw(self, ctx):
|
|
if self.mode == 'N':
|
|
self.draw_normal(ctx)
|
|
else:
|
|
self.draw_config(ctx)
|