# πŸš‡ mvg-departures Kompakter Abfahrtsmonitor fuer MVG U-Bahn und S-Bahn (Muenchen), primaer fuer mobile Nutzung optimiert. ![Screenshot der Webseite](docs/screenshot.png) ## ✨ Features - πŸ—ΊοΈ Konfigurierbare Stationsliste (`config.yaml`) mit Typ (UBAHN/SBAHN/TRAM/BUS) - 🚫 Ausschluss-Filter pro Station: nur unerwuenschte Zielrichtungen werden ausgeblendet, neue/unbekannte Ziele werden automatisch weiter angezeigt - πŸ‘€ Profile zur Gruppierung von Stationen (z.B. "Hinfahrt" und "RΓΌckfahrt") - πŸ–±οΈ Klickbare Kacheln zum Profilwechsel (nur bei >1 Profil) - πŸ”— URL-Param `?profile=` + Auto-Refresh - 🌐 Zwei Endpunkte: - `/` β€” mobile-optimierte HTML-Ansicht mit Auto-Refresh (alle 60s per Meta-Refresh) - `/api/departures` β€” JSON-Ausgabe derselben Daten - πŸ“Š Anzeige von Linie, Ziel, Abfahrtszeit, Verspaetung und Stoerungsmeldungen - 🎨 Visuelle Unterscheidung von U-Bahn (blau) und S-Bahn (gruen) per Icon ## βš™οΈ Konfiguration Siehe `config.yaml`: ```yaml refresh_seconds: 60 cache_seconds: 20 departures_limit: 10 profiles: - name: "Hinfahrt" stations: - name: "Josephsburg, MΓΌnchen" type: "UBAHN" exclude_destinations: - "Messestadt Ost" - "Messestadt West" - name: "Berg am Laim, MΓΌnchen" type: "SBAHN" exclude_destinations: - "Erding" - "Markt Schwaben" - name: "RΓΌckfahrt" stations: - name: "Grosshadern, MΓΌnchen" type: "UBAHN" exclude_destinations: [] ``` `exclude_destinations` filtert per exaktem `destination`-Stringvergleich. Alles was NICHT in der Liste steht, wird angezeigt β€” so fallen Linienaenderungen/Umleitungen sofort auf, statt lautlos verschwunden zu sein. ## πŸƒ Lokal starten ```bash pip install -r requirements.txt uvicorn app.main:app --reload ``` ## 🐳 Docker ```bash docker build -t mvg-departures . docker run -p 8000:8000 -e TZ=Europe/Berlin -v $(pwd)/config.yaml:/app/config.yaml mvg-departures ``` ## πŸš€ Deployment Siehe `docker-compose.yml` im Repo als Referenz. Beispiel (Owner anpassen): ```yaml services: mvg-departures: image: ghcr.io/skoelle/mvg-departures:latest container_name: mvg-departures ports: - "8000:8000" volumes: - ./config.yaml:/app/config.yaml:ro environment: - TZ=Europe/Berlin restart: unless-stopped labels: - "com.centurylinklabs.watchtower.enable=true" ``` ## πŸ”„ CI/CD `.github/workflows/build.yml`: - πŸ—οΈ Baut bei jedem Push auf `main` das Docker-Image und pusht es nach `ghcr.io//:latest` sowie mit Short-SHA-Tag - 🧹 Anschliessender Cleanup-Job loescht alte Image-Versionen in der GHCR-Package-Registry und behaelt nur die letzten 4 Versionen (`min-versions-to-keep: 4`) ### πŸ“‹ Voraussetzung Repo-Settings β†’ Actions β†’ General β†’ Workflow permissions auf "Read and write permissions" stellen, sonst schlaegt der Push nach GHCR fehl. ## πŸ“‘ mvg api https://www.mvg.de/api/bgw-pt/v3/locations?query=Josephsburg \ https://www.mvg.de/api/bgw-pt/v3/locations?query=Berg%20am \ https://www.mvg.de/api/bgw-pt/v3/departures?globalId=de:09162:1220&limit=10&transportTypes=UBAHN \ https://www.mvg.de/api/bgw-pt/v3/departures?globalId=de:09162:910&limit=10&transportTypes=SBAHN ## πŸ”Œ Endpunkte | Route | Beschreibung | |---|---| | `/` | HTML-Ansicht, mobile-optimiert, Auto-Refresh alle 60s | | `/?profile=` | HTML-Ansicht fΓΌr ein bestimmtes Profil | | `/api/departures` | JSON-Liste aller gefilterten Abfahrten (erstes Profil als Fallback) | | `/api/departures?profile=` | JSON-Liste fΓΌr ein bestimmtes Profil | | `/healthz` | Healthcheck fuer Docker/Watchtower | ## πŸ“ Projektstruktur ``` mvg-departures/ β”œβ”€β”€ app/ β”‚ β”œβ”€β”€ __init__.py β”‚ β”œβ”€β”€ main.py # FastAPI-App, Endpunkte, Caching β”‚ β”œβ”€β”€ config.py # Config-Loader (YAML β†’ Dataclasses) β”‚ └── templates/ β”‚ └── index.html # Jinja2-Template (Mobile-UI) β”œβ”€β”€ config.yaml # Stationskonfiguration β”œβ”€β”€ docker-compose.yml # Deployment-Beispiel β”œβ”€β”€ requirements.txt # Python-Dependencies β”œβ”€β”€ Dockerfile β”œβ”€β”€ .dockerignore β”œβ”€β”€ .gitignore β”œβ”€β”€ AGENTS.md β”œβ”€β”€ SPEC.md β”œβ”€β”€ renovate.json └── .github/workflows/ └── build.yml # CI/CD: Docker-Build + GHCR-Push ``` ## πŸ› οΈ Tech-Stack - ⚑ **Runtime**: Python 3.14 - πŸš€ **Framework**: FastAPI + Uvicorn - 🎨 **Templating**: Jinja2 (server-seitig) - πŸš‡ **MVG-API**: `mvg` Python-Client (oeffentlich, kein Auth noetig) - βš™οΈ **Config**: YAML via `pyyaml` - 🐳 **Container**: Docker (python:3.14-slim) ## πŸ’Ύ Caching - πŸ†” **Station-ID**: In-memory Dict, lifetime=Session (loest Station-Namen auf) - ⏱️ **Abfahrten**: In-memory Dict, TTL=`cache_seconds` (Default: 20s) pro Station+Typ - πŸ”„ Keine Persistenz β†’ Neustart = Cache-Verlust ## ⚠️ Einschraenkungen - πŸ“‘ Kein WebSocket/Live-Updates (nur periodischer Meta-Refresh) - πŸ’½ Keine Datenbank (nur In-Memory) - πŸ§ͺ Kein Test-Framework vorhanden - πŸ“„ Ein `config.yaml` pro Deployment (kein Multi-Tenancy) ## πŸ“œ Lizenz MIT License - Copyright (c) 2026 Stefan Koelle (https://stefankoelle.de)