From ea82d5731ecd3a618b1d596a72a4c74dc388723f Mon Sep 17 00:00:00 2001 From: Thomas Hooge Date: Sat, 21 Jun 2025 19:45:08 +0200 Subject: [PATCH] Vollbildmodus eingebaut --- INSTALL | 5 +- README | 4 +- fullscreen.svg | 181 ++++++++++++++++++++++++++++++++++++++++++ images/front.png | Bin 0 -> 534 bytes obp60.conf | 5 +- obp60.py | 144 +++++++++++++++++++++++---------- pages/apparentwind.py | 15 ++-- pages/page.py | 11 +++ 8 files changed, 316 insertions(+), 49 deletions(-) create mode 100644 fullscreen.svg create mode 100644 images/front.png diff --git a/INSTALL b/INSTALL index a303fc6..4fcadeb 100644 --- a/INSTALL +++ b/INSTALL @@ -4,12 +4,15 @@ 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 \ - python-serial python3-nmea2 python3-smbus2 python3-bme280 + python-serial python3-nmea2 python3-smbus2 python3-bme280 python3-astral Das Programm wird über eine Konfigurationsdatei obp60.conf im gleichen Verzeichnis wie das Hauptprogramm gesteuert. Die Konfiguration wird einmalig beim Programmstart eingelesen. +Die Schriftarten aus dem fonts-Verzeichnis müssen in /usr/local/share/fonts +abgelegt werden. + Meßdaten werden im Homeverzeichnis unter ~/.local/lib/obp60 gespeichert. Dies betrifft momentan Luftdruckmessungen mit dem BME280. Das Verzeichnis wird automatisch angelegt. diff --git a/README b/README index 764d7a2..50ce5e5 100644 --- a/README +++ b/README @@ -11,13 +11,14 @@ Für Informationen zum OBP60 in Hardware siehe: Fehlermeldungen und Patches gerne an thomas@hoogi.de senden. Basishardware -- Raspberry Pi 4 +- Raspberry Pi 4 / 4GB Zusatzhardware: - NMEA2000 Interface - PiCAN-M (hiermit wird entwickelt) - Waveshare RS485 CAN HAT (ungetestet) - BME280-Sensor über I2C +- RTC mit DS3231 über I2C - GPS über USB/seriell angeschlossen Zusatzsoftware: @@ -26,6 +27,7 @@ Zusatzsoftware: Abhängigkeiten - python-can - heapdict +- python3-astral Für GPS - python-serial diff --git a/fullscreen.svg b/fullscreen.svg new file mode 100644 index 0000000..16d908f --- /dev/null +++ b/fullscreen.svg @@ -0,0 +1,181 @@ + + + +OBP60vVorabstudie diff --git a/images/front.png b/images/front.png new file mode 100644 index 0000000000000000000000000000000000000000..b08ee54ae7f24707ec5d6e046895e8b04cddadb4 GIT binary patch literal 534 zcmV+x0_pvUP)9Z!|OJ;M(#5@CEFtMFDcjrY54v;xQ&I?XReaNZH-w zz8FroxogudqTRY^+~v_O;8(F;W8bm6%~v2w8+<+&l6qG>*M)cZoRWinzXprem7CoL z{|@RGoVK{t>my{^=v9>%un(Q@#|ZeUBdq>#eY`# Y2aUX)Nr%^lP5=M^07*qoM6N<$g0O4*rT_o{ literal 0 HcmV?d00001 diff --git a/obp60.conf b/obp60.conf index bef17f9..bbcdf72 100644 --- a/obp60.conf +++ b/obp60.conf @@ -4,6 +4,7 @@ loglevel = 3 deviceid = 100 simulation = on histpath = ~/.local/lib/obp60 +guistyle = fullscreen [bme280] enabled = true @@ -57,7 +58,7 @@ number_of_pages = 10 start_page = 1 [page1] -type=ApparentWind +type=Clock [page2] type=Barograph @@ -69,7 +70,7 @@ type=Anchor type=Autobahn [page5] -type=Clock +type=ApparentWind [page6] type=TwoValues diff --git a/obp60.py b/obp60.py index 5805e76..53e5815 100755 --- a/obp60.py +++ b/obp60.py @@ -3,12 +3,18 @@ """ Virtuelles Multifunktionsgerät +Zwei Displayvarianten + 1. Fliegendes OBP60 auf großem Bildschirm. 400x300 Display + 2. Fullscreen Display skaliert mit 1,6 ergibt 640x480 + mit Platz für große Touch-Flächen am rechten Rand + NMEA2000 deviceclass: 120 - Display devicefunction: 130 - Display Benötigte Pakete: python3-cairo python3-gi python3-gi-cairo gir1.2-rsvg-2.0 + python3-astral Um transparente Darstellung unter Openbox zu erhalten muß xcompmgr installiert und konfiguriert werden. @@ -67,7 +73,7 @@ Button 5 wird für Trend TRND verwendet Version Datum Änderung(en) von -------- ----------- ------------------------------------------------------ ---- 0.1 2024-10-31 Entwicklung begonnen tho -0.2 2024-12-24 Veräffentlichung als Git-Repository tho +0.2 2024-12-24 Veröffentlichung als Git-Repository tho """ @@ -274,25 +280,44 @@ def datareader(histpath, history): 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): + def __init__(self, cfg, device, boatdata, profile): super().__init__() self.owndev = device self.boatdata = boatdata + self._fullscreen = cfg['guistyle'] == 'fullscreen' self.connect("destroy", self.on_destroy) - self.set_position(Gtk.WindowPosition.CENTER) - self.set_size_request(530, 555) + if self._fullscreen: + self.fullscreen() + #self.set_size_request(800, 480) + # Schaltflächen am unteren Bildschirmrand sind berührbar, + # zusätzlich gibt es die großen Flächen am rechten Rand + self.button_round = False + self.button = { # linke obere Ecke + 1: (640, 96), + 2: (640, 160), + 3: (640, 224), + 4: (640, 288), + 5: (640, 352), + 6: (640, 416) + } + self.button_w = 160 + self.button_h = 64 + else: + self.button_round = True + # Runde Tasten wie beim Original-Gerät + self.button = { # Mittelpunkt + 1: (75, 485), + 2: (150, 492), + 3: (227, 496), + 4: (306, 496), + 5: (382, 492), + 6: (459, 485) + } + self.button_radius = 30 + 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) @@ -304,7 +329,10 @@ class Frontend(Gtk.Window): self.set_visual(self.visual) handle = Rsvg.Handle() - self._svg = handle.new_from_file(os.path.join(sys.path[0], "obp60.svg")) + if self._fullscreen: + self._svg = handle.new_from_file(os.path.join(sys.path[0], "fullscreen2.svg")) + else: + self._svg = handle.new_from_file(os.path.join(sys.path[0], "obp60.svg")) self.connect("draw", self.on_draw) @@ -346,13 +374,20 @@ class Frontend(Gtk.Window): viewport = Rsvg.Rectangle() viewport.x = 0 viewport.y = 0 - viewport.width = 530 - viewport.height = 555 + if self._fullscreen: + viewport.width = 800 + viewport.height = 480 + else: + 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() + if not self._fullscreen: + ctx.translate(64, 95) # Koordinatenursprung auf virtuellen Displaybereich setzen + ctx.rectangle(0, 0, 400, 300) + ctx.clip() + else: + ctx.scale(1.6, 1.6) ctx.set_source_rgb(0, 0, 0) # Schwarz auf Weiß @@ -369,15 +404,28 @@ class Frontend(Gtk.Window): # 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 + if self.button_round: + # Horizontale runde Buttons + if (event.x < self.button[1][0] - self.button_radius or event.x > self.button[6][0] + self.button_radius): + return True + if (event.y < self.button[1][1] - self.button_radius or event.y > self.button[3][1] + self.button_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.button_radius: + self.button_clicked = b + break + else: + # Vertikale eckige Buttons + if (event.x < self.button[1][0]) or (event.x > self.button[1][0] + self.button_w): + return True + if (event.y < self.button[1][1]) or (event.y > self.button[6][1] + self.button_h): + return True + for b, v in self.button.items(): + if event.x >= v[0] and event.x <= v[0] + self.button_w and \ + event.y >= v[1] and event.y <= v[1] + self.button_h: + self.button_clicked = b + break return True def da_button_release(self, widget, event): @@ -388,16 +436,30 @@ class Frontend(Gtk.Window): # 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 + print("release") + if self.button_round: + 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 + else: + if event.x < self.button[1][0] or event.x > self.button[6][0] + self.button_w: + return True + if event.y < self.button[1][1] or event.y > self.button[6][1] + self.button_h: + return True + selected = 0 + for b, v in self.button.items(): + if event.x >= v[0] and event.x <= v[0] + self.button_w and \ + event.y >= v[1] and event.y <= v[1] + self.button_h: + selected = b + break + if self.keylock: # Bei Tastensperre einzige Möglichkeit: Tastensperre ausschalten if selected == 6 and self.button_clicked == 1: @@ -523,6 +585,8 @@ if __name__ == "__main__": cfg['deviceid'] = config.getint('system', 'deviceid') cfg['simulation'] = config.getboolean('system', 'simulation') cfg['histpath'] = os.path.expanduser(config.get('system', 'histpath')) + cfg['guistyle'] = config.get('system', 'guistyle') + print("Setting GUI style to '{}'".format(cfg['guistyle'])) cfg['gps'] = config.getboolean('gps', 'enabled') if cfg['gps']: @@ -552,7 +616,7 @@ if __name__ == "__main__": t_data = threading.Thread(target=datareader, args=(cfg['histpath'], history)) t_data.start() - app = Frontend(owndevice, boatdata, profile) + app = Frontend(cfg, owndevice, boatdata, profile) app.run() shutdown = True t_rxd_n2k.join() diff --git a/pages/apparentwind.py b/pages/apparentwind.py index 1ba5305..05a0dbb 100644 --- a/pages/apparentwind.py +++ b/pages/apparentwind.py @@ -9,7 +9,11 @@ class ApparentWind(Page): super().__init__(pageno, cfg, boatdata) self.buttonlabel[1] = 'MODE' self.mode = 'L' # (W)ind (L)ens - self.symbol = cairo.ImageSurface.create_from_png(os.path.join(cfg['imgpath'], "front.png")) + try: + self.symbol = cairo.ImageSurface.create_from_png(os.path.join(cfg['imgpath'], "front.png")) + except: + self.symbol = None + print("Warning: Missing image: {}".format(os.path.join(cfg['imgpath'], "front.png"))) def handle_key(self, buttonid): if buttonid == 1: @@ -27,10 +31,11 @@ class ApparentWind(Page): ctx.show_text("Apparent Wind") def draw_lens(self, ctx): - ctx.save() - ctx.set_source_surface(self.symbol, 140, 30) - ctx.paint() - ctx.restore() + if self.symbol: + ctx.save() + ctx.set_source_surface(self.symbol, 140, 30) + ctx.paint() + ctx.restore() ctx.set_line_width(2) diff --git a/pages/page.py b/pages/page.py index 6d4c2b3..afbd553 100644 --- a/pages/page.py +++ b/pages/page.py @@ -145,6 +145,11 @@ class Page(): ctx.set_font_size(16) x = (35, 101, 167, 233, 299, 365) y = 294 + # Fullscreen-Buttons + bx = 400 + by = (128, 192, 256, 320, 384, 448) + bw = 160 + bh = 64 for i in range(6): if len(self.buttonlabel[i+1]) > 0 : if self.buttonlabel[i+1][0] == "#": @@ -153,12 +158,18 @@ class Page(): key = self.buttonlabel[i+1][1:] ctx.set_source_surface(self.icon[key], x[i]-8, y-13) ctx.paint() + ctx.set_source_surface(self.icon[key], bx + bw / 3.2 - 8, by[i] / 1.6 - 6) + 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) + # Fullscreen + w = ctx.text_extents(self.buttonlabel[i+1]).width + ctx.move_to(bx + bw/3.2 - w/2, by[i] / 1.6 + 8) + ctx.show_text(self.buttonlabel[i+1]) ctx.stroke() def clear(self):