317 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			317 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, appdata, boatdata):
 | |
|         super().__init__(pageno, cfg, appdata, 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)
 | |
|         if self._bd.alarm:
 | |
|             self.draw_alarm(ctx, self._bd.alarm_src, self._bd.alarm_id, self._bd.alarm_msg)
 |