diff --git a/50-allow-networkmanager.rules b/50-allow-networkmanager.rules deleted file mode 100644 index 387faa3..0000000 --- a/50-allow-networkmanager.rules +++ /dev/null @@ -1,9 +0,0 @@ -// Erlaubt koptikp (uid 1000) NetworkManager ohne Passwort zu steuern. -// Wir prüfen explizit auf uid, weil subject.local/active für SSH-Sessions -// oft nicht das tut, was man erwartet (logind behandelt SSH-Logins anders). -polkit.addRule(function(action, subject) { - if (action.id.indexOf("org.freedesktop.NetworkManager.") === 0 && - subject.user === "koptikp") { - return polkit.Result.YES; - } -}); diff --git a/idx.html b/idx.html deleted file mode 100644 index 31735c3..0000000 --- a/idx.html +++ /dev/null @@ -1,1042 +0,0 @@ - - - - - - epaper-dashboard - - - - -
- -
-
- -
-

epaper-dashboard

-
7.3" ACeP · 4×4 grid
-
-
-
-
-
-
-
-
- · -
-
-
-
- -
-
- -
- - - - -
- - -
-
-
-

Grid Layout 4×4 · 6 items

-
-
- -
-
- - - -
- - -
- -
-
- Position und Größe per Buttons am Item. Auto-Pack ordnet nach Größe (groß zuerst). -
-
- -
-
-

Live Preview

-
Auto-refresh alle 10s
-
- preview -
-
- - -
-
-
-

Plugin-Konfiguration

-
-
-
-
-
- - -
-
-
-

Refresh-Intervall

-
-
-
- - -
- -
-
- ACeP-Displays vertragen keine schnellen Refreshes. Hersteller empfiehlt ≥180s. -
-
- -
-
-

System

-
-
-
Service:
epaper-dashboard.service
-
Logs:
journalctl -u epaper-dashboard -f
-
Config:
config.json
-
Plugins:
plugins/
-
-
-
- - -
-
-
-

Aktueller Status

-
lade…
-
- - - - -
-
-
- -
-

WLAN verbinden / wechseln

- - -
- - -
- - - - - -
- -
-
-
- -
-

Gespeicherte Netzwerke

-
    -
    - -
    - Recovery-AP Info -

    Wenn das konfigurierte WLAN nicht erreichbar ist (z.B. Router defekt, falsches Passwort, Pi umgezogen), startet das System automatisch einen Recovery-Hotspot.

    - -
    -
    -
    - -
    - - - - - \ No newline at end of file diff --git a/install_polkit.sh b/install_polkit.sh deleted file mode 100644 index 32e92eb..0000000 --- a/install_polkit.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/bin/bash -# Erlaubt dem koptikp-User NetworkManager ohne Passwort zu steuern. -# Wird via polkit ausgewertet. -set -e - -RULE='polkit.addRule(function(action, subject) { - if (action.id.indexOf("org.freedesktop.NetworkManager.") === 0 && - subject.local === true && subject.active === true) { - return polkit.Result.YES; - } -});' - -RULE_FILE="/etc/polkit-1/rules.d/50-allow-networkmanager.rules" -echo "Schreibe $RULE_FILE..." -sudo -n bash -c "cat > $RULE_FILE <<'RULEEOF' -$POLKIT_RULE -RULEEOF -chmod 644 $RULE_FILE" - -echo "--- Inhalt: ---" -cat $RULE_FILE -echo -echo "--- Test: nmcli hotspot (kein pw mehr nötig) ---" -sudo -n nmcli device wifi hotspot ifname wlan0 ssid epaper-test password test1234 -sleep 3 -nmcli -t -f NAME,TYPE,DEVICE connection show --active -echo -echo "--- Cleanup ---" -sudo -n nmcli connection down epaper-test 2>&1 -sudo -n nmcli connection delete epaper-test 2>&1 -echo "Fertig." diff --git a/install_services.sh b/install_services.sh deleted file mode 100644 index bc08193..0000000 --- a/install_services.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/bash -# Installiert die systemd-Services fuer das ePaper-Dashboard. -set -e - -SERVICE_DIR=/etc/systemd/system -SERVICE_SRC=/home/koptikp/epaper-dashboard/port - -echo "=== installiere service units ===" -sudo -n install -m 644 "$SERVICE_SRC/epaper-dashboard.service" "$SERVICE_DIR/epaper-dashboard.service" -sudo -n install -m 644 "$SERVICE_SRC/epaper-admin.service" "$SERVICE_DIR/epaper-admin.service" - -echo -echo "=== stoppe alte Prozesse (manuell gestartet) ===" -pkill -f "python3.*admin.py" 2>/dev/null || true -pkill -f "python3.*dashboard.py" 2>/dev/null || true -sleep 2 - -echo "=== systemctl reload + enable + restart ===" -sudo -n systemctl daemon-reload -sudo -n systemctl enable epaper-dashboard.service -sudo -n systemctl enable epaper-admin.service -sudo -n systemctl restart epaper-dashboard.service -sudo -n systemctl restart epaper-admin.service - -sleep 3 - -echo -echo "=== status dashboard ===" -sudo -n systemctl is-active epaper-dashboard.service -sudo -n systemctl is-active epaper-admin.service -echo -echo "=== ports ===" -ss -ltn | grep -E "8080" | head -3 -echo -echo "Logs: journalctl -u epaper-dashboard -f" -echo " journalctl -u epaper-admin -f" diff --git a/render_test.py b/render_test.py deleted file mode 100644 index ad03d95..0000000 --- a/render_test.py +++ /dev/null @@ -1,8 +0,0 @@ -import sys -sys.path.insert(0, ".") -import dashboard -d = dashboard.Dashboard() -img = d.render_once() -img.save("/tmp/render_test.png") -print("rendered:", img.size) -print("widgets:", [(w.name if w else None) for w in d.widgets]) diff --git a/renderer.py b/renderer.py deleted file mode 100644 index bf96853..0000000 --- a/renderer.py +++ /dev/null @@ -1,502 +0,0 @@ -"""Render-Pipeline für das Dashboard. - -Unterstützte Display-Designs: - classic — Original flach, weiß, kein UI-Chrome (DEFAULT) - minimal — Schwarz/Weiß, bold, clean, viel Weißraum - minimal_color — Minimal + dezenter Akzent-Strich pro Kategorie - bold — Riesen-Typography, 1px-Rahmen, vollflächig - analog — 2×2-Uhr als Ziffernblatt, Rest clean Panels - magazine — Bold Typography, farbige Akzentstreifen links - cards — iOS-Widget-Style, Emoji-Icons, warme Paneele - kiosk — Dunkel, Daten-first, kompakte Info-Zellen - wireframe — Blueprint-Style, gestrichelte Rahmen, keine Füllungen - bauhaus — Geometrische Primärfarben, blockweise - poster — Farbige Flächen, bold, fast nur Content - retro_lcd — Alte LCD-Uhren-Ästhetik, grüner Hintergrund - glossy — Moderner iOS-Style, Emoji-Icons, Panels - data_board — Kontrollraum-Dashboard, dunkel, invertierte Karten - -Aufruf: - img = render_design(items, widgets, fonts, design="classic") -""" - -from PIL import Image, ImageDraw, ImageFont -from palette import FG, BG, WHITE, BLACK, GREEN, BLUE, RED, YELLOW, ORANGE, OK, WARN, ALERT, INFO, ACCENT -import math - -DISPLAY_W = 800 -DISPLAY_H = 480 -COLS, ROWS = 4, 4 -CELL_W = DISPLAY_W // COLS -CELL_H = DISPLAY_H // ROWS - - -def render_design(items, widgets, fonts, design="classic") -> Image.Image: - fn = { - "magazine": _render_magazine, - "cards": _render_cards, - "kiosk": _render_kiosk, - "wireframe": _render_wireframe, - "bauhaus": _render_bauhaus, - "poster": _render_poster, - "retro_lcd": _render_retro_lcd, - "glossy": _render_glossy, - "data_board": _render_data_board, - "minimal": _render_minimal, - "minimal_color": _render_minimal_color, - "bold": _render_bold, - "analog": _render_analog_clock, - "classic": _render_classic, - }.get(design, _render_classic) - return fn(items, widgets, fonts) - - -# ============================================================================ -# MAGAZINE — Bold Typography, Minimalist -# Idee: Wanduhr-Magazin / Bloomberg-Terminal-Ästhetik -# Schwarz/Weiß + eine Akzentfarbe pro Widget -# ============================================================================ - -def _render_magazine(items, widgets, fonts) -> Image.Image: - img = Image.new("RGB", (DISPLAY_W, DISPLAY_H), WHITE) - draw = ImageDraw.Draw(img) - - for item, widget in zip(items, widgets): - x, y, w, h = _pixels(item) - if widget is None: - continue - - # Jedes Widget bekommt einen vertikalen Akzent-Streifen links - accent = _widget_accent(widget.name) - draw.rectangle((x, y, x + 6, y + h - 1), fill=accent) - - # Widget-Name oben links, klein - label = (widget.label or widget.name or "").upper() - draw.text((x + 14, y + 6), label, font=fonts.get("16", fonts.get("default")), fill=(160, 160, 160)) - - # Horizontale Linie unter Label - draw.line((x + 14, y + 26, x + w - 10, y + 26), fill=(220, 220, 220), width=1) - - # Content-Bereich (ab y+32) - cy = y + 36 - ch = h - 42 - widget.render(draw, fonts, x + 14, cy, w - 20, ch) - - return img - - -# ============================================================================ -# CARDS — Background Panels + Emoji Icons + Vertical Accent Bars -# Idee: iOS Widgets / Google Smart Display -# Leicht grauer Hintergrund pro Karte, Emoji für Kategorie -# ============================================================================ - -def _render_cards(items, widgets, fonts) -> Image.Image: - img = Image.new("RGB", (DISPLAY_W, DISPLAY_H), (245, 244, 240)) # warm white - draw = ImageDraw.Draw(img) - - for item, widget in zip(items, widgets): - x, y, w, h = _pixels(item) - if widget is None: - continue - - # Card background - draw.rectangle((x + 2, y + 2, x + w - 3, y + h - 3), fill=WHITE) - - # Top accent bar - accent = _widget_accent(widget.name) - draw.rectangle((x + 2, y + 2, x + w - 3, y + 10), fill=accent) - - # Emoji icon (use emoji renderer via fallback) - icon = _widget_icon(widget.name) - _draw_emoji(draw, icon, x + 10, y + 18, 28) - - # Label - label = widget.label or widget.name or "" - draw.text((x + 44, y + 18), label, font=fonts.get("16", fonts.get("default")), fill=(80, 80, 80)) - - # Separator line - draw.line((x + 8, y + 52, x + w - 8, y + 52), fill=(230, 230, 230), width=1) - - # Content - widget.render(draw, fonts, x + 8, y + 58, w - 16, h - 64) - - return img - - -# ============================================================================ -# KIOSK — Data-Dense, Compact, Multi-Zone -# Idee: Control-Room / LoRa-Tracker / Science-Dashboard -# Viele kleine Info-Zellen, horizontale Teilung, monochrome Palette -# ============================================================================ - -def _render_kiosk(items, widgets, fonts) -> Image.Image: - img = Image.new("RGB", (DISPLAY_W, DISPLAY_H), BLACK) - draw = ImageDraw.Draw(img) - - for item, widget in zip(items, widgets): - x, y, w, h = _pixels(item) - if widget is None: - continue - - # Invertierte Box mit Akzent-Rand - draw.rectangle((x, y, x + w - 1, y + h - 1), fill=WHITE) - accent = _widget_accent(widget.name) - draw.rectangle((x, y, x + w - 1, y + 3), fill=accent) - - # Label in Akzentfarbe oben - label = (widget.label or widget.name or "").upper() - draw.text((x + 8, y + 8), label, font=fonts.get("14", fonts.get("default")), fill=accent) - - # Content - widget.render(draw, fonts, x + 6, y + 28, w - 12, h - 34) - - return img - - -# ============================================================================ -# Helper -# ============================================================================ - -def _pixels(item): - """Item (x,y,w,h) in Pixel (800x480, 4x4 grid)""" - COLS, ROWS = 4, 4 - CELL_W = DISPLAY_W // COLS - CELL_H = DISPLAY_H // ROWS - return item.x * CELL_W, item.y * CELL_H, item.w * CELL_W, item.h * CELL_H - - -# Alias für neue Designs -def _px(item): - return _pixels(item) - - -def _widget_accent(name): - accents = { - "clock": BLUE, - "weather": GREEN, - "system": ORANGE, - "hello": RED, - "spotify": (0, 200, 0), # spotify green - "strava": ORANGE, - "gmail": RED, - "minimax": (180, 100, 255), # purple - } - return accents.get(name, ORANGE) - - -def _widget_icon(name): - icons = { - "clock": "⏰", - "weather": "🌤️", - "system": "💻", - "hello": "👋", - "spotify": "🎵", - "strava": "🚴", - "gmail": "📧", - "minimax": "🤖", - } - return icons.get(name, "📟") - - -def _draw_emoji(draw, emoji, x, y, size): - """Draw emoji by using a fallback ImageFont trick with the emoji char.""" - # On systems without an emoji font, this just draws a rectangle placeholder - # The real implementation would need a font with emoji support - # For now draw a colored square as placeholder - colors = { - "⏰": (80, 160, 255), - "🌤️": (255, 200, 80), - "💻": (100, 100, 100), - "👋": (255, 160, 120), - "🎵": (0, 200, 100), - "🚴": (255, 140, 0), - "📧": (220, 80, 80), - "🤖": (180, 100, 255), - } - c = colors.get(emoji, (200, 200, 200)) - draw.ellipse((x, y, x + size, y + size), fill=c) - - -# ============================================================================ -# DESIGN 5: WIREFRAME -# Technisches Plan-Drawing. Blaue Linien, weiße Flächen. -# Blueprint-Ästhetik. -# ============================================================================ -def _render_wireframe(items, widgets, fonts) -> Image.Image: - img = Image.new("RGB", (DISPLAY_W, DISPLAY_H), (240, 248, 255)) - draw = ImageDraw.Draw(img) - for item, widget in zip(items, widgets): - x, y, w, h = _px(item) - if widget is None: - continue - draw.rectangle((x + 2, y + 2, x + w - 3, y + h - 3), fill=WHITE) - draw.rectangle((x + 2, y + 2, x + w - 3, y + h - 3), outline=BLUE, width=1) - draw.rectangle((x + 2, y + 2, x + w - 3, y + 10), fill=BLUE) - label = (widget.label or widget.name or "").upper() - draw.text((x + 10, y + 14), label, font=fonts.get("14", fonts.get("default")), fill=BLUE) - for row in range(item.y, item.y + item.h): - for col in range(item.x, item.x + item.w): - if (row + col) % 2 == 0: - cx = col * CELL_W + 4 - cy = row * CELL_H + 4 - cw = CELL_W - 6 - ch = CELL_H - 6 - draw.rectangle((cx, cy, cx + cw, cy + ch), fill=(240, 248, 255)) - widget.render(draw, fonts, x + 6, y + 32, w - 12, h - 38) - return img - - -# ============================================================================ -# DESIGN 6: BAUHAUS -# Geometrische Primärfarben. Jedes Widget bekommt eine der -# 7 ACeP-Farben als Hintergrund-Balken links. -# ============================================================================ -def _render_bauhaus(items, widgets, fonts) -> Image.Image: - img = Image.new("RGB", (DISPLAY_W, DISPLAY_H), (245, 245, 240)) - draw = ImageDraw.Draw(img) - PALETTE_7 = [RED, GREEN, BLUE, YELLOW, ORANGE, (0, 200, 200), (200, 100, 200)] - for i, (item, widget) in enumerate(zip(items, widgets)): - x, y, w, h = _px(item) - if widget is None: - continue - color = PALETTE_7[i % len(PALETTE_7)] - block_w = w // 3 - draw.rectangle((x + 2, y + 2, x + block_w - 2, y + h - 3), fill=color) - draw.rectangle((x + block_w, y + 2, x + w - 3, y + h - 3), fill=WHITE) - draw.rectangle((x + 2, y + 2, x + w - 3, y + h - 3), outline=FG, width=2) - label = (widget.label or widget.name or "").upper() - draw.text((x + 6, y + 8), label, font=fonts.get("14", fonts.get("default")), fill=WHITE) - widget.render(draw, fonts, x + block_w + 4, y + 4, w - block_w - 8, h - 8) - return img - - -# ============================================================================ -# DESIGN 7: POSTER -# Farbige Flächen, bold, fast nur Content. -# Jedes Widget in einer kräftigen Farbe. -# ============================================================================ -def _render_poster(items, widgets, fonts) -> Image.Image: - img = Image.new("RGB", (DISPLAY_W, DISPLAY_H), BLACK) - draw = ImageDraw.Draw(img) - colors = [RED, GREEN, BLUE, YELLOW, ORANGE, (0, 200, 200), (200, 100, 200)] - for i, (item, widget) in enumerate(zip(items, widgets)): - x, y, w, h = _px(item) - if widget is None: - continue - c = colors[i % len(colors)] - draw.rectangle((x, y, x + w - 1, y + h - 1), fill=c) - draw.rectangle((x, y, x + w - 1, y + h - 1), outline=WHITE, width=3) - label = (widget.label or widget.name or "").upper() - draw.text((x + 8, y + 6), label[:18], font=fonts.get("12", fonts.get("default")), fill=WHITE) - widget.render(draw, fonts, x + 6, y + 24, w - 12, h - 30) - return img - - -# ============================================================================ -# DESIGN 8: RETRO LCD -# Alte LCD-Uhren-Ästhetik. Dunkelgrüner Hintergrund, -# leuchtende grüne Ziffern, Fake-3D-Rahmen. -# ============================================================================ -def _render_retro_lcd(items, widgets, fonts) -> Image.Image: - img = Image.new("RGB", (DISPLAY_W, DISPLAY_H), (0, 20, 0)) - draw = ImageDraw.Draw(img) - LCD_GREEN = (0, 255, 120) - LCD_DARK = (0, 60, 30) - for item, widget in zip(items, widgets): - x, y, w, h = _px(item) - if widget is None: - continue - draw.rectangle((x + 3, y + 3, x + w - 4, y + h - 4), fill=LCD_DARK) - draw.rectangle((x + 1, y + 1, x + w - 2, y + h - 2), outline=LCD_GREEN, width=2) - label = (widget.label or widget.name or "").upper() - draw.text((x + 8, y + 6), label[:16], font=fonts.get("11", fonts.get("default")), fill=LCD_GREEN) - draw.line((x + 8, y + 22, x + w - 8, y + 22), fill=LCD_GREEN, width=1) - widget.render(draw, fonts, x + 6, y + 26, w - 12, h - 32) - return img - - -# ============================================================================ -# DESIGN 9: GLOSSY -# Moderne iOS-Widget-Ästhetik. Weiße Karten, Glossy-Header -# in Accent-Farbe, dezente Schatten-Rahmen. -# ============================================================================ -def _render_glossy(items, widgets, fonts) -> Image.Image: - img = Image.new("RGB", (DISPLAY_W, DISPLAY_H), (220, 228, 235)) - draw = ImageDraw.Draw(img) - WIDGET_ACCENTS = { - "clock": (80, 160, 255), - "weather": (60, 200, 100), - "system": (255, 160, 40), - "hello": (200, 80, 80), - "spotify": (0, 180, 80), - "strava": (255, 120, 0), - "gmail": (200, 60, 60), - "minimax": (160, 80, 255), - } - for item, widget in zip(items, widgets): - x, y, w, h = _px(item) - if widget is None: - continue - accent = WIDGET_ACCENTS.get(widget.name, (100, 100, 100)) - draw.rectangle((x + 2, y + 2, x + w - 2, y + h - 2), fill=WHITE) - draw.rectangle((x + 2, y + 2, x + w - 2, y + 14), fill=accent) - # Glossy highlight - for i in range(6): - alpha = int(80 - i * 12) - c = tuple(min(255, accent[j] + alpha) for j in range(3)) - draw.line((x + 2, y + 2 + i, x + w - 2, y + 2 + i), fill=c, width=1) - draw.rectangle((x + 2, y + 2, x + w - 2, y + h - 2), outline=(200, 210, 220), width=1) - draw.rectangle((x + 3, y + 3, x + w - 3, y + h - 3), outline=(220, 228, 235), width=1) - draw.rectangle((x + 4, y + 4, x + w - 4, y + h - 4), outline=(180, 190, 200), width=1) - label = (widget.label or widget.name or "").title() - draw.text((x + 10, y + 18), label, font=fonts.get("14", fonts.get("default")), fill=accent) - draw.line((x + 8, y + 36, x + w - 8, y + 36), fill=(220, 220, 220), width=1) - widget.render(draw, fonts, x + 6, y + 40, w - 12, h - 46) - return img - - -# ============================================================================ -# DESIGN 10: DATA BOARD -# Kontrollraum-Ästhetik. Winzige Zellen, viele Daten. -# Monochrome, Tick-Marks, Data-first. -# ============================================================================ -def _render_data_board(items, widgets, fonts) -> Image.Image: - img = Image.new("RGB", (DISPLAY_W, DISPLAY_H), (10, 10, 15)) - draw = ImageDraw.Draw(img) - for item, widget in zip(items, widgets): - x, y, w, h = _px(item) - if widget is None: - continue - draw.rectangle((x, y, x + w - 1, y + h - 1), fill=WHITE, width=2) - draw.rectangle((x, y, x + w - 1, y + 22), fill=WHITE) - draw.text((x + 6, y + 4), (widget.label or widget.name or "").upper(), - font=fonts.get("12", fonts.get("default")), fill=BLACK) - for tx in range(x + 10, x + w - 10, 20): - draw.line((tx, y + 24, tx, y + 28), fill=(180, 180, 180), width=1) - widget.render(draw, fonts, x + 4, y + 30, w - 8, h - 34) - return img - - -# ============================================================================ -# CLASSIC — Original flaches Design -# Weißer Hintergrund, kein UI-Chrome, widget füllt Zelle aus -# Das was auf deinem Referenz-Bild zu sehen ist -# ============================================================================ -def _render_classic(items, widgets, fonts) -> Image.Image: - img = Image.new("RGB", (DISPLAY_W, DISPLAY_H), WHITE) - draw = ImageDraw.Draw(img) - for item, widget in zip(items, widgets): - x, y, w, h = _px(item) - if widget is None: - continue - widget.render(draw, fonts, x + 8, y + 8, w - 16, h - 16) - return img - - -# ============================================================================ -# MINIMAL — das Referenz-Design -# Schwarz/Weiß, bold, clean, viel Weißraum. Kein UI-Chrome. -# ============================================================================ -def _render_minimal(items, widgets, fonts) -> Image.Image: - img = Image.new("RGB", (DISPLAY_W, DISPLAY_H), WHITE) - draw = ImageDraw.Draw(img) - for item, widget in zip(items, widgets): - x, y, w, h = _px(item) - if widget is None: - continue - widget.render(draw, fonts, x + 12, y + 12, w - 24, h - 24) - return img - - -# ============================================================================ -# MINIMAL COLOR — Minimal mit dezenter Akzentfarbe pro Kategorie -# Kleiner Farbbalken oben links, sonst weiß + schwarz -# ============================================================================ -def _render_minimal_color(items, widgets, fonts) -> Image.Image: - img = Image.new("RGB", (DISPLAY_W, DISPLAY_H), WHITE) - draw = ImageDraw.Draw(img) - CATEGORY_COLORS = { - "clock": (60, 80, 200), - "weather": (50, 160, 80), - "system": (200, 120, 30), - "hello": (180, 60, 60), - "spotify": (0, 160, 80), - "strava": (230, 110, 20), - "gmail": (200, 60, 60), - "minimax": (140, 80, 240), - } - for item, widget in zip(items, widgets): - x, y, w, h = _px(item) - if widget is None: - continue - color = CATEGORY_COLORS.get(widget.name, FG) - draw.rectangle((x + 12, y + 10, x + 16, y + 18), fill=color) - label = (widget.label or widget.name or "").upper() - draw.text((x + 22, y + 8), label, font=fonts.get("11", fonts.get("default")), fill=(160, 160, 160)) - widget.render(draw, fonts, x + 12, y + 24, w - 24, h - 36) - return img - - -# ============================================================================ -# BOLD — Riesen-Typography -# Zahlen und Text füllen die Zelle komplett. -# Kaum Weißraum, maximaler Informationsdichte. -# ============================================================================ -def _render_bold(items, widgets, fonts) -> Image.Image: - img = Image.new("RGB", (DISPLAY_W, DISPLAY_H), WHITE) - draw = ImageDraw.Draw(img) - for item, widget in zip(items, widgets): - x, y, w, h = _px(item) - if widget is None: - continue - draw.rectangle((x + 1, y + 1, x + w - 2, y + h - 2), outline=BLACK, width=1) - widget.render(draw, fonts, x + 6, y + 6, w - 12, h - 12) - return img - - -# ============================================================================ -# ANALOG CLOCK — Die Uhr als Mittelpunkt -# 2x2-Clock-Zellen zeigen ein analoges Ziffernblatt. -# Übrige Zellen: cleanes Panel mit Monospace. -# ============================================================================ -def _render_analog_clock(items, widgets, fonts) -> Image.Image: - img = Image.new("RGB", (DISPLAY_W, DISPLAY_H), (245, 245, 240)) - draw = ImageDraw.Draw(img) - import math - for item, widget in zip(items, widgets): - x, y, w, h = _px(item) - if widget is None: - continue - is_2x2 = item.w >= 2 and item.h >= 2 - is_clock = widget.name == "clock" and is_2x2 - if is_clock: - cx, cy = x + w // 2, y + h // 2 - r = min(w, h) // 2 - 16 - draw.ellipse((cx - r, cy - r, cx + r, cy + r), fill=WHITE, outline=BLACK, width=2) - for i in range(12): - angle = (i / 12) * 2 * math.pi - math.pi / 2 - mx = cx + int((r - 12) * math.cos(angle)) - my = cy + int((r - 12) * math.sin(angle)) - draw.ellipse((mx - 3, my - 3, mx + 3, my + 3), fill=BLACK) - from datetime import datetime - now = datetime.now() - hour_angle = ((now.hour % 12) / 12) * 2 * math.pi - math.pi / 2 - min_angle = (now.minute / 60) * 2 * math.pi - math.pi / 2 - hx = cx + int(r * 0.5 * math.cos(hour_angle)) - hy = cy + int(r * 0.5 * math.sin(hour_angle)) - draw.line((cx, cy, hx, hy), fill=BLACK, width=4) - mx = cx + int(r * 0.75 * math.cos(min_angle)) - my = cy + int(r * 0.75 * math.sin(min_angle)) - draw.line((cx, cy, mx, my), fill=BLACK, width=2) - draw.ellipse((cx - 4, cy - 4, cx + 4, cy + 4), fill=BLACK) - draw.text((cx - 40, cy + r + 8), now.strftime("%H:%M"), - font=fonts.get("20", fonts.get("default")), fill=BLACK) - else: - draw.rectangle((x + 2, y + 2, x + w - 2, y + h - 2), fill=WHITE) - draw.ellipse((x + 8, y + 8, x + 14, y + 14), fill=(60, 100, 200)) - label = (widget.label or widget.name or "").upper()[:12] - draw.text((x + 20, y + 6), label, font=fonts.get("12", fonts.get("default")), fill=(100, 100, 100)) - draw.line((x + 8, y + 22, x + w - 8, y + 22), fill=(200, 200, 200), width=1) - widget.render(draw, fonts, x + 6, y + 26, w - 12, h - 34) - return img diff --git a/renderer2.py b/renderer2.py deleted file mode 100644 index c751777..0000000 --- a/renderer2.py +++ /dev/null @@ -1,272 +0,0 @@ -"""Neue Display-Designs für ACeP 7-Farben e-Paper. - -Jedes Design definiert: - - Hintergrund / Rahmen - - Widget-Rahmen ( Frames) - - content_fn: wie das Widget gerendert wird -""" - -from PIL import Image, ImageDraw -from palette import FG, BG, WHITE, BLACK, GREEN, BLUE, RED, YELLOW, ORANGE, OK, WARN, ALERT, INFO, ACCENT -import math - -DISPLAY_W = 800 -DISPLAY_H = 480 -COLS, ROWS = 4, 4 -CELL_W = DISPLAY_W // COLS -CELL_H = DISPLAY_H // ROWS - - -# ============================================================================ -# DESIGN 5: WIREFRAME -# Technisches Plan-Drawing. Blaue Linien, weiße Flächen, -# schmale weiße Zellenrahmen. Blueprint-Ästhetik. -# ============================================================================ -def render_wireframe(items, widgets, fonts) -> Image.Image: - img = Image.new("RGB", (DISPLAY_W, DISPLAY_H), (240, 248, 255)) # aliceblue - draw = ImageDraw.Draw(img) - - for item, widget in zip(items, widgets): - x, y, w, h = _px(item) - if widget is None: - continue - - # Weißer Hintergrund pro Zelle - draw.rectangle((x + 2, y + 2, x + w - 3, y + h - 3), fill=WHITE) - - # Dünner Rahmen (blau) - draw.rectangle((x + 2, y + 2, x + w - 3, y + h - 3), outline=BLUE, width=1) - - # Obere Akzent-Linie (breit) - draw.rectangle((x + 2, y + 2, x + w - 3, y + 10), fill=BLUE) - - # Widget-Name oben links - label = (widget.label or widget.name or "").upper() - draw.text((x + 10, y + 14), label, font=fonts.get("14", fonts.get("default")), fill=BLUE) - - # Grid-Checkerboard im Hintergrund (subtle) - for row in range(item.y, item.y + item.h): - for col in range(item.x, item.x + item.w): - if (row + col) % 2 == 0: - cx = col * CELL_W + 4 - cy = row * CELL_H + 4 - cw = CELL_W - 6 - ch = CELL_H - 6 - draw.rectangle((cx, cy, cx + cw, cy + ch), fill=(240, 248, 255)) - - # Content - widget.render(draw, fonts, x + 6, y + 32, w - 12, h - 38) - - return img - - -# ============================================================================ -# DESIGN 6: BAUHAUS -# Geometrische Primärfarben. Jedes Widget bekommt eine der -# 7 ACeP-Farben als Hintergrund-Balken links. Reine Formen. -# ============================================================================ -def render_bauhaus(items, widgets, fonts) -> Image.Image: - img = Image.new("RGB", (DISPLAY_W, DISPLAY_H), (245, 245, 240)) # warm gray - draw = ImageDraw.Draw(img) - - PALETTE_7 = [RED, GREEN, BLUE, YELLOW, ORANGE, (0, 200, 200), (200, 100, 200)] - item_colors = {} - - for i, (item, widget) in enumerate(zip(items, widgets)): - x, y, w, h = _px(item) - if widget is None: - continue - color = PALETTE_7[i % len(PALETTE_7)] - item_colors[item.id] = color - - # Großer farbiger Block links (30% width) - block_w = w // 3 - draw.rectangle((x + 2, y + 2, x + block_w - 2, y + h - 3), fill=color) - - # Rest weiß - draw.rectangle((x + block_w, y + 2, x + w - 3, y + h - 3), fill=WHITE) - - # Rahmen - draw.rectangle((x + 2, y + 2, x + w - 3, y + h - 3), outline=FG, width=2) - - # Widget-Name in der farbigen Fläche - label = (widget.label or widget.name or "").upper() - draw.text((x + 6, y + 8), label, font=fonts.get("14", fonts.get("default")), fill=WHITE) - - # Content (rechte Seite) - widget.render(draw, fonts, x + block_w + 4, y + 4, w - block_w - 8, h - 8) - - return img - - -# ============================================================================ -# DESIGN 7: POSTER -# Überdimensionierte Typography. Jedes Widget großflächig. -# Widget-Hintergrund = monochrome Farbe aus Palette. -# Fast nur Text, kein UI-Chrome. -# ============================================================================ -def render_poster(items, widgets, fonts) -> Image.Image: - img = Image.new("RGB", (DISPLAY_W, DISPLAY_H), BLACK) - draw = ImageDraw.Draw(img) - - for item, widget in zip(items, widgets): - x, y, w, h = _px(item) - if widget is None: - continue - - # Zufällige kräftige Farbe pro widget (basierend auf name hash) - c = _hash_color(widget.name) - draw.rectangle((x, y, x + w - 1, y + h - 1), fill=c) - - # Weißer Rahmen drum - draw.rectangle((x, y, x + w - 1, y + h - 1), outline=WHITE, width=3) - - # Label oben Winzig - label = (widget.label or widget.name or "").upper() - draw.text((x + 8, y + 6), label[:18], font=fonts.get("12", fonts.get("default")), fill=WHITE) - - # Content — das Widget bekommt 90% der Fläche - # Wir übergeben ein invertiertes draw-Objekt (fg=WHITE, bg=c) - widget.render(draw, fonts, x + 6, y + 24, w - 12, h - 30) - - return img - - -# ============================================================================ -# DESIGN 8: RETRO LCD -# Alte LCD/Uhren-Ästhetik. Schwarzer Hintergrund, Ziffern in -# satten UnOld Green (LCD-STYLE). Rahmen mit abgerundeten Ecken, -# die wie eingeritzte Rillen aussehen. -# ============================================================================ -def render_retro_lcd(items, widgets, fonts) -> Image.Image: - img = Image.new("RGB", (DISPLAY_W, DISPLAY_H), (0, 20, 0)) # dunkles LCD-grün - draw = ImageDraw.Draw(img) - - LCD_GREEN = (0, 255, 120) - LCD_DARK = (0, 60, 30) - LCD_BG = (0, 30, 15) - - for item, widget in zip(items, widgets): - x, y, w, h = _px(item) - if widget is None: - continue - - # LCD-Zelle: dunkler als Hintergrund - draw.rectangle((x + 3, y + 3, x + w - 4, y + h - 4), fill=LCD_DARK) - - # Rahmen mit Fake-3D (oben/links hell, unten/rechts dunkel) - draw.rectangle((x + 1, y + 1, x + w - 2, y + h - 2), outline=LCD_GREEN, width=2) - - # Label in kleiner Schrift oben - label = (widget.label or widget.name or "").upper() - draw.text((x + 8, y + 6), label[:16], font=fonts.get("11", fonts.get("default")), fill=LCD_GREEN) - - # Separator-Linie - draw.line((x + 8, y + 22, x + w - 8, y + 22), fill=LCD_GREEN, width=1) - - # Content - widget.render(draw, fonts, x + 6, y + 26, w - 12, h - 32) - - return img - - -# ============================================================================ -# DESIGN 9: GLOSSY -# Moderner Glossy-Style. Weiße Karten mit leichtem Farbverlauf oben, -# ikonischer Rand, dezente Schatten durch mehrfache Ränder. -# ============================================================================ -def render_glossy(items, widgets, fonts) -> Image.Image: - img = Image.new("RGB", (DISPLAY_W, DISPLAY_H), (220, 228, 235)) # cool gray - draw = ImageDraw.Draw(img) - - WIDGET_ACCENTS = { - "clock": (80, 160, 255), - "weather": (60, 200, 100), - "system": (255, 160, 40), - "hello": (200, 80, 80), - "spotify": (0, 180, 80), - "strava": (255, 120, 0), - "gmail": (200, 60, 60), - "minimax": (160, 80, 255), - } - - for item, widget in zip(items, widgets): - x, y, w, h = _px(item) - if widget is None: - continue - - accent = WIDGET_ACCENTS.get(widget.name, (100, 100, 100)) - - # Karte weiß - draw.rectangle((x + 2, y + 2, x + w - 2, y + h - 2), fill=WHITE) - - # Glossy-Effekt: heller Balken oben - draw.rectangle((x + 2, y + 2, x + w - 2, y + 14), fill=accent) - # Weißer Gradient-Overlay oben - for i in range(6): - alpha = int(80 - i * 12) - c = tuple(min(255, accent[j] + alpha) for j in range(3)) - draw.line((x + 2, y + 2 + i, x + w - 2, y + 2 + i), fill=c, width=1) - - # Rahmeneffekt: 3-lagig für Schatten - draw.rectangle((x + 2, y + 2, x + w - 2, y + h - 2), outline=(200, 210, 220), width=1) - draw.rectangle((x + 3, y + 3, x + w - 3, y + h - 3), outline=(220, 228, 235), width=1) - draw.rectangle((x + 4, y + 4, x + w - 4, y + h - 4), outline=(180, 190, 200), width=1) - - # Label in Accent-Farbe - label = (widget.label or widget.name or "").title() - draw.text((x + 10, y + 18), label, font=fonts.get("14", fonts.get("default")), fill=accent) - - # Separator - draw.line((x + 8, y + 36, x + w - 8, y + 36), fill=(220, 220, 220), width=1) - - # Content - widget.render(draw, fonts, x + 6, y + 40, w - 12, h - 46) - - return img - - -# ============================================================================ -# DESIGN 10: DATA BOARD -# Kontrollraum-Ästhetik. Winzige Zellen, viele Daten. -# Horizontale Balken, Tick-Marks, monochrome Palette. -#=========================================================================== -def render_data_board(items, widgets, fonts) -> Image.Image: - img = Image.new("RGB", (DISPLAY_W, DISPLAY_H), (10, 10, 15)) - draw = ImageDraw.Draw(img) - - for item, widget in zip(items, widgets): - x, y, w, h = _px(item) - if widget is None: - continue - - # Weißer Rand - draw.rectangle((x, y, x + w - 1, y + h - 1), fill=WHITE, width=2) - - # Header-Balken (invertiert) - draw.rectangle((x, y, x + w - 1, y + 22), fill=WHITE) - draw.text((x + 6, y + 4), (widget.label or widget.name or "").upper(), - font=fonts.get("12", fonts.get("default")), fill=BLACK) - - # Kleine Tick-Marks oben (dekorativ) - for tx in range(x + 10, x + w - 10, 20): - draw.line((tx, y + 24, tx, y + 28), fill=(180, 180, 180), width=1) - - # Content - widget.render(draw, fonts, x + 4, y + 30, w - 8, h - 34) - - return img - - -# ============================================================================ -# Helper -# ============================================================================ -def _px(item): - return item.x * CELL_W, item.y * CELL_H, item.w * CELL_W, item.h * CELL_H - - -def _hash_color(name: str): - """Consistent color from name using simple hash.""" - colors = [RED, GREEN, BLUE, YELLOW, ORANGE, (0, 200, 200), (200, 0, 200)] - h = sum(ord(c) for c in name) - return colors[h % len(colors)] diff --git a/renderer3.py b/renderer3.py deleted file mode 100644 index 49ad9d5..0000000 --- a/renderer3.py +++ /dev/null @@ -1,203 +0,0 @@ -"""Minimal Display-Designs für ACeP 7-Farben. - -Inspiration von loiccoyle/tinyticker + inkflow-eink: -- Weißer Hintergrund, schwarze Geometrie -- Bold Typography, große Zahlen -- SVG-style rendering (saubere Linien, einheitliche Strichstärke) -- Dezente Akzentfarbe pro Widget-Kategorie -""" - -from PIL import Image, ImageDraw -from palette import FG, BG, WHITE, BLACK, GREEN, BLUE, RED, YELLOW, ORANGE, OK, WARN, ALERT, INFO, ACCENT - -DISPLAY_W = 800 -DISPLAY_H = 480 -COLS, ROWS = 4, 4 -CELL_W = DISPLAY_W // COLS -CELL_H = DISPLAY_H // ROWS - -# Einheitliche Strichstärke -STROKE = 2 # px für alle Linien -SMALL_STROKE = 1 - - -def render_design(items, widgets, fonts, design="minimal") -> Image.Image: - fn = { - "minimal": _render_minimal, - "minimal_color": _render_minimal_color, - "bold": _render_bold, - "analog": _render_analog_clock, - }.get(design, _render_minimal) - return fn(items, widgets, fonts) - - -# ============================================================================ -# MINIMAL — das Referenz-Design -# Schwarz/Weiß, bold, clean, viel Weißraum -# ============================================================================ -def _render_minimal(items, widgets, fonts) -> Image.Image: - img = Image.new("RGB", (DISPLAY_W, DISPLAY_H), WHITE) - draw = ImageDraw.Draw(img) - - # Keine sichtbaren Grid-Linien — jedes Widget steht für sich - for item, widget in zip(items, widgets): - x, y, w, h = _px(item) - if widget is None: - continue - - # Kein Rahmen, kein Hintergrund — reiner Content auf Weiß - # Content füllt die ganze Zelle - widget.render(draw, fonts, x + 12, y + 12, w - 24, h - 24) - - return img - - -# ============================================================================ -# MINIMAL COLOR — Minimal mit dezenter Akzentfarbe pro Kategorie -# Kleiner Farbbalken oben links, sonst weiß + schwarz -# ============================================================================ -def _render_minimal_color(items, widgets, fonts) -> Image.Image: - img = Image.new("RGB", (DISPLAY_W, DISPLAY_H), WHITE) - draw = ImageDraw.Draw(img) - - CATEGORY_COLORS = { - "clock": (60, 80, 200), # blau - "weather": (50, 160, 80), # grün - "system": (200, 120, 30), # orange - "hello": (180, 60, 60), # rot - "spotify": (0, 160, 80), # spotify-grün - "strava": (230, 110, 20), # strava-orange - "gmail": (200, 60, 60), # gmail-rot - "minimax": (140, 80, 240), # lila - } - - for item, widget in zip(items, widgets): - x, y, w, h = _px(item) - if widget is None: - continue - - color = CATEGORY_COLORS.get(widget.name, FG) - - # Winziger Akzent-Strich oben (8px hoch, 3px breit) - draw.rectangle((x + 12, y + 10, x + 16, y + 18), fill=color) - - # Widget-Name winzig darüber - label = (widget.label or widget.name or "").upper() - draw.text((x + 22, y + 8), label, font=fonts.get("11", fonts.get("default")), fill=(160, 160, 160)) - - # Content - widget.render(draw, fonts, x + 12, y + 24, w - 24, h - 36) - - return img - - -# ============================================================================ -# BOLD — Riesen-Typography -# Zahlen und Text füllen die Zelle komplett. -# Kaum Weißraum, maximaler Informationsdichte. -# ============================================================================ -def _render_bold(items, widgets, fonts) -> Image.Image: - img = Image.new("RGB", (DISPLAY_W, DISPLAY_H), WHITE) - draw = ImageDraw.Draw(img) - - for item, widget in zip(items, widgets): - x, y, w, h = _px(item) - if widget is None: - continue - - # Weißer Hintergrund, schwarzer Rahmen (1px) - draw.rectangle((x + 1, y + 1, x + w - 2, y + h - 2), outline=BLACK, width=1) - - # Content — groß - widget.render(draw, fonts, x + 6, y + 6, w - 12, h - 12) - - return img - - -# ============================================================================ -# ANALOG CLOCK — Die Uhr als Mittelpunkt -# Mittlere Zellen (2x2) zeigen die Uhr als analoges Ziffernblatt. -# Kleine Zellen (1x1) zeigen minimalistische Icons. -# Übrige Zellen: Daten-Widget mit Monospace-Text. -# ============================================================================ -def _render_analog_clock(items, widgets, fonts) -> Image.Image: - img = Image.new("RGB", (DISPLAY_W, DISPLAY_H), (245, 245, 240)) # warm white - draw = ImageDraw.Draw(img) - - for item, widget in zip(items, widgets): - x, y, w, h = _px(item) - if widget is None: - continue - - is_2x2 = item.w >= 2 and item.h >= 2 - is_clock = widget.name == "clock" and is_2x2 - - if is_clock: - # 2x2 Clock → Analoges Ziffernblatt - _draw_analog_clock(draw, x, y, w, h, fonts) - else: - # Andere Widgets: cleanes Panel - draw.rectangle((x + 2, y + 2, x + w - 2, y + h - 2), fill=WHITE) - # Kleiner farbiger Punkt oben links - color = (60, 100, 200) - draw.ellipse((x + 8, y + 8, x + 14, y + 14), fill=color) - # Label - label = (widget.label or widget.name or "").upper()[:12] - draw.text((x + 20, y + 6), label, font=fonts.get("12", fonts.get("default")), fill=(100, 100, 100)) - # Separator - draw.line((x + 8, y + 22, x + w - 8, y + 22), fill=(200, 200, 200), width=1) - # Content - widget.render(draw, fonts, x + 6, y + 26, w - 12, h - 34) - - return img - - -def _draw_analog_clock(draw, x, y, w, h, fonts): - """Zeichne ein analoges Ziffernblatt.""" - import math - cx, cy = x + w // 2, y + h // 2 - r = min(w, h) // 2 - 16 - - # Ziffernblatt-Kreis - draw.ellipse((cx - r, cy - r, cx + r, cy + r), fill=WHITE, outline=BLACK, width=STROKE) - - # Stunden-Markierungen (kleine punkte) - for i in range(12): - angle = (i / 12) * 2 * math.pi - math.pi / 2 - mx = cx + int((r - 12) * math.cos(angle)) - my = cy + int((r - 12) * math.sin(angle)) - draw.ellipse((mx - 3, my - 3, mx + 3, my + 3), fill=BLACK) - - # Zeiger - from datetime import datetime - now = datetime.now() - hour_angle = ((now.hour % 12) / 12) * 2 * math.pi - math.pi / 2 - min_angle = (now.minute / 60) * 2 * math.pi - math.pi / 2 - - # Stundenzeiger - hx = cx + int(r * 0.5 * math.cos(hour_angle)) - hy = cy + int(r * 0.5 * math.sin(hour_angle)) - draw.line((cx, cy, hx, hy), fill=BLACK, width=4) - - # Minutenzeiger - mx = cx + int(r * 0.75 * math.cos(min_angle)) - my = cy + int(r * 0.75 * math.sin(min_angle)) - draw.line((cx, cy, mx, my), fill=BLACK, width=2) - - # Mittelpunkt - draw.ellipse((cx - 4, cy - 4, cx + 4, cy + 4), fill=BLACK) - - # Digitale Zeit darunter - draw.text((cx - 40, cy + r + 8), - now.strftime("%H:%M"), - font=fonts.get("20", fonts.get("default")), fill=BLACK) - - -# ============================================================================ -# Helper -# ============================================================================ -def _px(item): - COLS, ROWS = 4, 4 - CELL_W = DISPLAY_W // COLS - CELL_H = DISPLAY_H // ROWS - return item.x * CELL_W, item.y * CELL_H, item.w * CELL_W, item.h * CELL_H diff --git a/socktest.py b/socktest.py deleted file mode 100644 index 92b9b57..0000000 --- a/socktest.py +++ /dev/null @@ -1,14 +0,0 @@ -import os, socket -p = "/tmp/test.sock" -if os.path.exists(p): - os.remove(p) -srv = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) -try: - srv.bind(p) - srv.listen(1) - print("bound, listening") - print("exists immediately after bind:", os.path.exists(p)) -finally: - srv.close() - if os.path.exists(p): - os.remove(p) diff --git a/start_admin.sh b/start_admin.sh deleted file mode 100644 index 246ed44..0000000 --- a/start_admin.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -# startet admin.py im Hintergrund mit korrektem CWD -cd /home/koptikp/epaper-dashboard/port -pkill -f admin.py 2>/dev/null -sleep 1 -nohup env EPAPER_ADMIN_PORT=8080 python3 admin.py > /tmp/admin.log 2>&1 & -echo "admin pid: $!" diff --git a/start_admin_remote.sh b/start_admin_remote.sh deleted file mode 100644 index 237f881..0000000 --- a/start_admin_remote.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash -chmod +x /home/koptikp/epaper-dashboard/port/start_admin.sh 2>/dev/null -# Sende das Script selbst -cat /home/koptikp/epaper-dashboard/port/start_admin.sh diff --git a/start_dashboard.sh b/start_dashboard.sh deleted file mode 100644 index cef9b2b..0000000 --- a/start_dashboard.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash -cd /home/koptikp/epaper-dashboard/port -pkill -f dashboard.py 2>/dev/null -sleep 1 -nohup env python3 dashboard.py > /tmp/dashboard.log 2>&1 & -echo "dashboard pid: $!" -sleep 2 -echo "--- log preview ---" -head -20 /tmp/dashboard.log diff --git a/templates/design_a_dark_minimal.html b/templates/design_a_dark_minimal.html deleted file mode 100644 index a7f74fd..0000000 --- a/templates/design_a_dark_minimal.html +++ /dev/null @@ -1,308 +0,0 @@ - - - - - epaper admin — Dark Minimal - - - -
    - -
    - - -
    -
    - -
    -

    Layout — 4×4 Grid

    -
    -
    - - -
    - - 4 items · OK -
    - -
    -
    -
    -
    Clock
    -
    2×2 · (0,0)
    -
    -
    - -
    -
    -
    -
    -
    Weather
    -
    2×2 · (2,0)
    -
    -
    - -
    -
    -
    -
    -
    -
    -
    System
    -
    2×2 · (0,2)
    -
    -
    -
    -
    -
    Hello
    -
    2×2 · (2,2)
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -

    Plugin Config

    -
    -

    Clock

    -
    System time and date
    -
    -
    - - -
    -
    - - -
    -
    -
    -
    -

    Weather

    -
    Open-Meteo — no API key needed
    -
    -
    - - -
    -
    - - -
    -
    -
    - -

    Live Preview

    -
    - -
    -
    - - diff --git a/templates/design_b_retro_terminal.html b/templates/design_b_retro_terminal.html deleted file mode 100644 index 9317e22..0000000 --- a/templates/design_b_retro_terminal.html +++ /dev/null @@ -1,328 +0,0 @@ - - - - - epaper admin — Retro Terminal - - - -
    - -
    - - -
    -
    - -
    -

    LAYOUT EDITOR [4x4]

    -
    -
    - - -
    - - STATUS: 4 ITEMS | GRID: OK -
    - -
    -
    -
    -
    CLOCK
    -
    2x2 @ (0,0)
    -
    - -
    -
    -
    -
    WEATHER
    -
    2x2 @ (2,0)
    -
    - -
    -
    -
    -
    -
    -
    SYSTEM
    -
    2x2 @ (0,2)
    -
    - -
    -
    -
    -
    HELLO
    -
    2x2 @ (2,2)
    -
    - -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -

    PLUGIN CONFIG

    -
    -

    [CLOCK]

    -
    System time and date display
    -
    -
    - - -
    -
    - - -
    -
    -
    -
    -

    [WEATHER]

    -
    Open-Meteo API — no key required
    -
    -
    - - -
    -
    - - -
    -
    -
    - -

    LIVE PREVIEW

    -
    - -
    -
    - - diff --git a/templates/design_c_warm_editorial.html b/templates/design_c_warm_editorial.html deleted file mode 100644 index 001a5c0..0000000 --- a/templates/design_c_warm_editorial.html +++ /dev/null @@ -1,381 +0,0 @@ - - - - - epaper admin — Warm Editorial - - - -
    -
    -

    epaper dashboard

    -
    7.3" ACeP · Raspberry Pi 4 · Waveshare
    -
    -
    - - -
    -
    - -
    -

    Layout — 4×4 Grid

    -
    -
    - - -
    - - 4 items configured -
    - -
    -
    -
    -
    -
    Clock
    -
    time
    -
    -
    2×2 · top-left
    -
    - -
    -
    -
    -
    -
    -
    -
    Weather
    -
    data
    -
    -
    2×2 · top-right
    -
    - -
    -
    -
    -
    -
    -
    -
    -
    -
    System
    -
    status
    -
    -
    2×2 · bottom-left
    -
    -
    -
    -
    -
    -
    Hello
    -
    message
    -
    -
    2×2 · bottom-right
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -

    Plugin Configuration

    -
    -

    Clock

    -
    Displays current time and date
    -
    -
    - - -
    -
    - - -
    -
    -
    -
    -

    Weather

    -
    Open-Meteo API · no API key required
    -
    -
    - - -
    -
    - - -
    -
    -
    - -

    Display Preview

    -
    - -
    -
    - - diff --git a/templates/preview_all.html b/templates/preview_all.html deleted file mode 100644 index 363c2e9..0000000 --- a/templates/preview_all.html +++ /dev/null @@ -1,48 +0,0 @@ - - - - - epaper dashboard — Design Preview - - - -
    -

    epaper-dashboard — Design Preview

    - Scrolle rechts für alle drei Varianten. Alle Designs haben das gleiche Backend — nur die Optik ist anders. -
    -
    -
    -
    -

    A — Dark Minimal

    -

    Linear/Vercel-inspiriert · Fast-Schwarz · Eine Akzentfarbe · Clean & Ruhe

    -
    - -
    -
    -
    -

    B — Retro Terminal

    -

    CRT/Phosphor · Grüner-Hintergrund · Scanlines · LoRes-Charme

    -
    - -
    -
    -
    -

    C — Warm Editorial

    -

    Zeitung/Magazin · Warme Töne · Serif-Fonts · Papier-Optik

    -
    - -
    -
    - - diff --git a/test_env_auth.sh b/test_env_auth.sh deleted file mode 100644 index 5b626a2..0000000 --- a/test_env_auth.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash -cat > /home/koptikp/epaper-dashboard/port/.env <<'ENVEOF' -EPAPER_ADMIN_USER=admin -EPAPER_ADMIN_PASSWORD=changeme123 -ENVEOF -chmod 600 /home/koptikp/epaper-dashboard/port/.env -echo "=== .env geschrieben ===" -ls -la /home/koptikp/epaper-dashboard/port/.env -echo -echo "=== restart admin ===" -bash /home/koptikp/epaper-dashboard/port/start_admin.sh -sleep 2 -echo -echo "=== test mit altem passwort (sollte 401) ===" -curl -s -u admin:admin -o /dev/null -w "admin:admin -> HTTP %{http_code}\n" http://127.0.0.1:8080/status.json -echo "=== test mit neuem passwort (sollte 200) ===" -curl -s -u admin:changeme123 -o /dev/null -w "admin:changeme123 -> HTTP %{http_code}\n" http://127.0.0.1:8080/status.json -echo -echo "=== sicherheits-check: passwort in logs? ===" -grep -E "changeme123|admin:" /tmp/admin.log || echo "(kein Treffer in logs)" diff --git a/test_gradient.py b/test_gradient.py deleted file mode 100644 index b61056d..0000000 --- a/test_gradient.py +++ /dev/null @@ -1,40 +0,0 @@ -"""Demo: Progress-Bars mit Gradient und konfigurierbaren Schwellen.""" -import sys; sys.path.insert(0, ".") -from PIL import Image, ImageDraw, ImageFont -from palette import hbar, parse_thresholds, FG, OK, WARN, ALERT, ORANGE, BLUE, GREEN, RED, YELLOW - -def font(s): return ImageFont.truetype("fnt/Aldrich-Regular.ttc", s) -fonts = {str(s): font(s) for s in [16, 20, 24]} - -img = Image.new("RGB", (800, 600), (255,255,255)) -draw = ImageDraw.Draw(img) - -# Title -draw.text((20, 10), "Gradient Bars - 3-stufig default (50/80/95)", font=font(20), fill=FG) - -# Row 1: gradient on (default thresholds) -draw.text((20, 50), "Gradient AN, Thresholds 50/80/95", font=font(16), fill=FG) -hbar(draw, 20, 80, 760, 30, 25, thresholds=parse_thresholds("ok@50,warn@80,alert@95"), gradient=True) -draw.text((20, 115), "25% (ok)", font=font(16), fill=FG) -hbar(draw, 20, 140, 760, 30, 65, thresholds=parse_thresholds("ok@50,warn@80,alert@95"), gradient=True) -draw.text((20, 175), "65% (warn)", font=font(16), fill=FG) -hbar(draw, 20, 200, 760, 30, 90, thresholds=parse_thresholds("ok@50,warn@80,alert@95"), gradient=True) -draw.text((20, 235), "90% (alert)", font=font(16), fill=FG) - -# Row 2: gradient off -draw.text((20, 280), "Gradient AUS (einfarbig in aktueller Schwelle)", font=font(20), fill=FG) -hbar(draw, 20, 310, 760, 30, 25, thresholds=parse_thresholds("ok@50,warn@80,alert@95"), gradient=False) -draw.text((20, 345), "25% einfarbig green", font=font(16), fill=FG) -hbar(draw, 20, 370, 760, 30, 65, thresholds=parse_thresholds("ok@50,warn@80,alert@95"), gradient=False) -draw.text((20, 405), "65% einfarbig yellow", font=font(16), fill=FG) -hbar(draw, 20, 430, 760, 30, 90, thresholds=parse_thresholds("ok@50,warn@80,alert@95"), gradient=False) -draw.text((20, 465), "90% einfarbig red", font=font(16), fill=FG) - -# Row 3: custom thresholds (mehr Stufen) -draw.text((20, 510), "Custom: 4 Stufen 30/60/80/95, custom colors", font=font(20), fill=FG) -spec = [(30, GREEN), (60, YELLOW), (80, ORANGE), (95, RED)] -hbar(draw, 20, 545, 760, 30, 50, thresholds=spec, gradient=True) -draw.text((20, 580), "50% → yellow segment", font=font(16), fill=FG) - -img.save("/tmp/gradient_demo.png") -print("saved /tmp/gradient_demo.png") diff --git a/test_grid_v2.py b/test_grid_v2.py deleted file mode 100644 index 916be9a..0000000 --- a/test_grid_v2.py +++ /dev/null @@ -1,32 +0,0 @@ -import sys; sys.path.insert(0, ".") -import json -from layout import Item -from dashboard import render_full, load_fonts - -cfg = json.load(open("config.json")) -items = [Item.from_dict(d) for d in cfg["layout"]["items"]] -plugins = cfg.get("plugin_configs", {}) - -from plugins.base import all_widget_classes -classes = {c.name: c for c in all_widget_classes()} -widgets = [] -for it in items: - cls = classes.get(it.plugin) - if cls: - try: - w = cls(plugins.get(it.plugin, {})) - w.fetch() - widgets.append(w) - except Exception as e: - print(f"widget {it.plugin} failed: {e}") - widgets.append(None) - else: - widgets.append(None) - -fonts = load_fonts() -print("items:", [(i.id, i.plugin, i.x, i.y, i.w, i.h) for i in items]) -print("widgets:", [w.name if w else None for w in widgets]) - -img = render_full(items, widgets, fonts) -img.save("/tmp/render_grid_v2.png") -print(f"saved /tmp/render_grid_v2.png size={img.size}") diff --git a/test_minimax_render.py b/test_minimax_render.py deleted file mode 100644 index dd1b988..0000000 --- a/test_minimax_render.py +++ /dev/null @@ -1,26 +0,0 @@ -import sys; sys.path.insert(0, ".") -from PIL import Image, ImageDraw, ImageFont -from plugins.minimax import Widget - -class FakeWidget(Widget): - def fetch(self): - return { - "windows": [ - {"label": "5-Hour", "used_pct": 47.0, "remaining_pct": 53.0, "reset_seconds": 1820, "raw": {}}, - {"label": "Weekly", "used_pct": 23.0, "remaining_pct": 77.0, "reset_seconds": 124800, "raw": {}}, - ], - "credits": {"points": 12345.0}, - } - -img = Image.new("RGB", (400, 240), (255, 255, 255)) -draw = ImageDraw.Draw(img) -fonts = { - "16": ImageFont.truetype("fnt/Aldrich-Regular.ttc", 16), - "20": ImageFont.truetype("fnt/Aldrich-Regular.ttc", 20), - "24": ImageFont.truetype("fnt/Aldrich-Regular.ttc", 24), - "28": ImageFont.truetype("fnt/Aldrich-Regular.ttc", 28), -} -w = FakeWidget({"show_credits": True, "label_5h": "5-Hour", "label_weekly": "Weekly"}) -w.render(draw, fonts, 0, 0, 400, 240) -img.save("/tmp/minimax_fake.png") -print("saved /tmp/minimax_fake.png") diff --git a/test_sizes.py b/test_sizes.py deleted file mode 100644 index a8e2562..0000000 --- a/test_sizes.py +++ /dev/null @@ -1,49 +0,0 @@ -"""Test: clock als 4x1-Strip (200x120), minimax als 2x2, weather als 2x2 (lower-right). -Zeigt ob Widgets auf verschiedene Groessen reagieren.""" -import sys; sys.path.insert(0, ".") -from PIL import Image, ImageDraw, ImageFont -from layout import Item, GRID_COLS, GRID_ROWS, CELL_W, CELL_H, pack -from plugins.clock import Widget as ClockW -from plugins.minimax import Widget as MiniMaxW -from plugins.weather import Widget as WeatherW -from plugins.hello import Widget as HelloW - -# Force-load fonts -def font(size): - return ImageFont.truetype("fnt/Aldrich-Regular.ttc", size) -fonts = { - "16": font(16), "20": font(20), "24": font(24), - "28": font(28), "32": font(32), "48": font(48), - "60": font(60), "80": font(80), - "clock": ImageFont.truetype("fnt/advanced_led_board-7.ttc", 100), -} - -# Layout: -# clock (4x1) | y=0 -# minimax (2x1) hello (2x1) | y=1 -# weather (4x2) | y=2..3 -items = [ - Item("a", "clock", 0, 0, 4, 1), - Item("b", "clock", 0, 0, 1, 1), # tiny - Item("c", "clock", 0, 0, 2, 2), # large - Item("d", "clock", 0, 0, 4, 4), # full -] - -print("Testing CLOCK widget at different sizes:") -print("="*60) -img = Image.new("RGB", (800, 480), (255,255,255)) -draw = ImageDraw.Draw(img) -# 4x1 strip top -w = ClockW({}) -w.fetch() -w.render(draw, fonts, 0, 0, 800, 120) -# 2x2 below -w = ClockW({"show_date": True}) -w.fetch() -w.render(draw, fonts, 0, 120, 400, 240) -# mini 1x1 bottom-right -w = ClockW({}) -w.fetch() -w.render(draw, fonts, 600, 360, 200, 120) -img.save("/tmp/sizes_clock.png") -print("saved /tmp/sizes_clock.png") diff --git a/test_sizes_mix.py b/test_sizes_mix.py deleted file mode 100644 index d5d6901..0000000 --- a/test_sizes_mix.py +++ /dev/null @@ -1,57 +0,0 @@ -"""Test alle Widgets in verschiedenen Groessen, um Layout-Probleme zu finden.""" -import sys; sys.path.insert(0, ".") -from PIL import Image, ImageDraw, ImageFont -from layout import Item -from plugins.clock import Widget as ClockW -from plugins.weather import Widget as WeatherW -from plugins.system import Widget as SystemW -from plugins.minimax import Widget as MiniMaxW -from plugins.hello import Widget as HelloW -import json - -def font(size): - return ImageFont.truetype("fnt/Aldrich-Regular.ttc", size) -fonts = { - "16": font(16), "20": font(20), "24": font(24), - "28": font(28), "32": font(32), "48": font(48), - "60": font(60), "80": font(80), - "clock": ImageFont.truetype("fnt/advanced_led_board-7.ttc", 100), -} - -plugins_cfg = json.load(open("config.json")).get("plugin_configs", {}) - -# Layout test grid: 4x4 -# (0,0)-(3,0): system 4x1 wide -# (0,1)-(1,1): weather 2x1 -# (2,1)-(3,1): minimax 2x1 -# (0,2)-(1,3): hello 2x2 -# (2,2)-(3,3): mix -img = Image.new("RGB", (800, 480), (255,255,255)) -draw = ImageDraw.Draw(img) - -# system 4x1 (full width strip) -w = SystemW(plugins_cfg.get("system", {})) -w.fetch() -w.render(draw, fonts, 0, 0, 800, 120) - -# weather 2x1 -w = WeatherW(plugins_cfg.get("weather", {"location": "52.52,13.41"})) -w.fetch() -w.render(draw, fonts, 0, 120, 400, 120) - -# minimax 2x1 -w = MiniMaxW(plugins_cfg.get("minimax", {})) -w.fetch() -w.render(draw, fonts, 400, 120, 400, 120) - -# hello 2x2 -w = HelloW({"text": "Layout Test", "color": "accent"}) -w.render(draw, fonts, 0, 240, 400, 240) - -# clock 2x2 -w = ClockW({"show_date": True}) -w.fetch() -w.render(draw, fonts, 400, 240, 400, 240) - -img.save("/tmp/sizes_mix.png") -print("saved /tmp/sizes_mix.png") diff --git a/test_system_thresholds.py b/test_system_thresholds.py deleted file mode 100644 index 7ffb2cf..0000000 --- a/test_system_thresholds.py +++ /dev/null @@ -1,29 +0,0 @@ -"""Test: System Widget mit verschiedenen Threshold-Konfigurationen.""" -import sys; sys.path.insert(0, ".") -from PIL import Image, ImageDraw, ImageFont -from plugins.system import Widget as SystemW - -def font(s): return ImageFont.truetype("fnt/Aldrich-Regular.ttc", s) -fonts = {str(s): font(s) for s in [16, 20, 24, 28, 32]} - -img = Image.new("RGB", (800, 480), (255,255,255)) -draw = ImageDraw.Draw(img) - -# Drei Konfigurationen nebeneinander -configs = [ - {"name": "Default (50/80/95, gradient)", - "cfg": {"bar_thresholds": "ok@50,warn@80,alert@95", "bar_gradient": True}}, - {"name": "Strenger (30/60/85, gradient)", - "cfg": {"bar_thresholds": "ok@30,warn@60,alert@85", "bar_gradient": True}}, - {"name": "Einfarbig (40/70/95)", - "cfg": {"bar_thresholds": "ok@40,warn@70,alert@95", "bar_gradient": False}}, -] -for i, c in enumerate(configs): - x = i * 267 - draw.text((x + 8, 5), c["name"], font=font(14), fill=(0,0,0)) - w = SystemW({**c["cfg"], "compact": False}) - w.fetch() - w.render(draw, fonts, x, 30, 260, 200) - -img.save("/tmp/system_thresholds.png") -print("saved /tmp/system_thresholds.png") diff --git a/waveshare_epd_init.py b/waveshare_epd_init.py deleted file mode 100644 index be4fe73..0000000 --- a/waveshare_epd_init.py +++ /dev/null @@ -1,3 +0,0 @@ -"""Waveshare 7.3-inch ACeP 7-Color e-Paper (F) — vendored driver.""" -from .epd7in3f import EPD -from .epdconfig import module_exit