OBP60v/pages/anchor.py

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)