# Main Gate Controller Eine vollständig über die Home-Assistant-UI konfigurierbare Custom Integration für ein elektrisches Fußgängertor. Die Integration ersetzt eine bestehende Lösung aus Skripten, Timern und Helpern und steuert das Tor indirekt über eine bereits vorhandene Switch-Entity (z. B. `switch.shellyplusuni_8c4f00a6ce94_output_0`). > Vor der ersten Veröffentlichung müssen die Platzhalter in `manifest.json`, > `hacs.json`, dieser README und in `LICENSE` durch die tatsächlichen Werte > (GitHub-Benutzername, Klarname) ersetzt werden. Die Platzhalter tragen das > Präfix `YOUR_GITHUB_USERNAME`. ## Features - **Mehrere Tore** – pro Tor wird ein eigener Config Entry angelegt. - **Konfiguration über die UI** – inklusive modernem Entity-Selector für die Switch-Entity, NumberSelectorn für Zeiten und EntitySelector für Notify-Entities. - **Config Flow + Options Flow** – alle Parameter lassen sich nachträglich in `Settings → Devices & Services → Main Gate Controller → Configure` ändern. - **Mode: single** – ein laufender Ablauf kann nicht durch einen zweiten Button-Press unterbrochen werden. - **Robuste State Machine** – `closed → opening → open → closing → closed` mit Debug-Log auf jedem Übergang. Eine fehlgeschlagene Benachrichtigung führt nicht zum Abbruch. Ein fehlgeschlagener Switch-Service bricht den Ablauf ab und setzt den Status zurück auf `closed`. - **Synchronisation mit dem Sensor** – `remaining_seconds` wird während der Offen-Phase jede Sekunde aktualisiert und steht sofort im Dashboard zur Verfügung. - **Restart-sicher** – nach einem Home-Assistant-Neustart beginnt der Status mit `closed`, `running=false`. Ein ggf. unterbrochener Ablauf wird protokolliert. - **Diagnostics** – `home-assistant://developer-tools/...` Diagnose-Export ohne sensible Daten. ## Ablauf Wenn ein Tor geöffnet wird, führt die Integration exakt die folgende Sequenz aus und nutzt ausschließlich Service-Calls auf `switch.turn_on` / `switch.turn_off`: ``` 1. Status: opening 2. (optional) Notification „Opening Main Gate (Pedestrians)“ 3. switch.turn_off 4. warten (pulse_duration, Standard 1 s) 5. switch.turn_on 6. warten (pulse_duration) 7. switch.turn_off 8. Status: open 9. Countdown (open_duration, Standard 20 s), remaining_seconds zählt sekündlich herunter 10. Status: closing 11. (optional) Notification „Closing Main Gate“ 12. switch.turn_on 13. warten (pulse_duration) 14. switch.turn_off 15. warten (pulse_duration) 16. Status: closed ``` ## Installation via HACS (Custom Repository) 1. **HACS** in der Sidebar öffnen. 2. Auf das **⋮-Menü → Custom repositories** klicken. 3. Die URL dieses GitHub-Repositories eintragen, z. B. `https://github.com//ha-main-gate-controller`. 4. **Typ** auf `Integration` stehen lassen. 5. **Repository hinzufügen** klicken. 6. In der HACS-Übersicht erscheint *Main Gate Controller* und kann über **Download** installiert werden. 7. Falls erforderlich Home Assistant neu starten. 8. **Settings → Devices & Services → Add Integration** öffnen. 9. **Main Gate Controller** auswählen. 10. Das Gate konfigurieren (Name, Switch-Entity, Zeiten, optional Notifications). ## Manuelle Installation (ohne HACS) ``` custom_components/main_gate_controller/ ``` in das Home-Assistant-Konfigurationsverzeichnis kopieren, Home Assistant neu starten und denselben UI-Pfad ab Schritt 8 verwenden. ## Einrichtung ### Config Flow Der Config Flow ist ein einzelner Schritt. Die folgenden Felder werden abgefragt: | Feld | Pflicht | Default | Beschreibung | | ----------------------------- | :----: | -------------------------------------- | ----------------------------------------------------------------------- | | `Gate name` | ✅ | `Main Gate` | Anzeigename – wird als Device-Name und Sensor-Entity-ID-Basis verwendet | | `Gate switch` | ✅ | — | Switch-Entity zur Torsteuerung (Selector: `EntitySelector(domain="switch")`) | | `Open duration (seconds)` | ✅ | `20` | Wie lange das Tor offengehalten wird | | `Pulse duration (seconds)` | ✅ | `1.0` | Ein-/Aus-Pulsdauer der Switch-Impulse | | `Send notifications` | ✅ | `false` | Aktiviert/deaktiviert alle Benachrichtigungen | | `Notification targets` | ⛔ | leer | Notify-Entities (Selector: `EntitySelector(domain="notify", multiple=True)`) | | `Opening notification text` | ⛔ | `Opening Main Gate (Pedestrians)` | Text der Öffnungs-Benachrichtigung | | `Closing notification text` | ⛔ | `Closing Main Gate` | Text der Schließ-Benachrichtigung | Wird `Send notifications` aktiviert ohne dass Targets ausgewählt sind, meldet der Flow einen Fehler. Außerdem wird verhindert, dass dieselbe Switch-Entity an mehrere Config Entries gebunden wird (Unique-ID-Schutz). ### Options Flow Über **Settings → Devices & Services → Main Gate Controller → Configure** lassen sich `Open duration`, `Pulse duration`, `Send notifications`, `Notification targets` und beide Notification-Texte jederzeit nachjustieren. Der Name und die Switch-Entity sind im Options Flow bewusst nicht editierbar; dafür ist das Löschen und Neuanlegen eines Gates der vorgesehene Weg. ### Switch- und Notify-Domain Die Integration geht davon aus, dass die ausgewählte Switch-Entity und die ausgewählten Notify-Entities bereits in Home Assistant existieren (z. B. Shelly, Sonoff, MQTT, Companion-App). Es gibt **keine** direkte Geräte-Abhängigkeit. ## Entities Für jedes Gate (jeden Config Entry) wird ein gemeinsames Device angelegt mit `manufacturer = Custom` und `model = Main Gate Controller`. Folgende Entities gehören zum Device: ### Button `button._open` Startet den kompletten Ablauf. Friendly-Name: `Open`. Ist bereits ein Ablauf aktiv, wird der Druck ignoriert (logischer `mode: single`). Wird sauber protokolliert, nicht als Fehler. ### Sensor `sensor._status` Maschinenlesbarer State, standardmäßig einer von `closed`, `opening`, `open`, `closing`. Attribute (immer vorhanden): | Attribut | Typ | Beschreibung | | -------------------- | -------------- | --------------------------------------------------- | | `remaining_seconds` | `int \| null` | Sekunden bis zum automatischen Schließen | | `duration` | `number` | Konfigurierte `Open duration` | | `running` | `bool` | `True`, solange ein Ablauf läuft | | `started_at` | `string\|null` | ISO 8601 UTC – Zyklusstart | | `finishes_at` | `string\|null` | ISO 8601 UTC – berechnetes Ende der Öffnungsphase | Während der `open`-Phase wird `remaining_seconds` ungefähr jede Sekunde aktualisiert. > **Bewusste Designentscheidung:** Es wird kein zusätzlicher > `binary_sensor._running` erzeugt. Das `running`-Attribut am > Statussensor liefert dieselbe Information ohne eine weitere Entity, und > `sensor._status.options` enthält ohnehin alle Zustände. ## Beispiel Dashboard Eine kompakte, einzelne Tile-Card reicht aus. Die Tile-Card unterstützt das `state_content`-Feld, das beliebige Attribute der Entity anzeigen kann. Wir übergeben den maschinenlesbaren State **und** die verbleibenden Sekunden: ```yaml type: tile entity: sensor.main_gate_status name: Main Gate icon: mdi:gate state_content: - state - remaining_seconds ``` ### Wer das Ergebnis in der „sprechenden“ Variante sehen will Da der Sensor-State `closed/opening/open/closing` maschinenlesbar bleibt, werden die deutschen Bezeichnungen „Geschlossen/Öffnet/Offen/Schließt“ nicht im State selbst, sondern als Übersetzung im Frontend angezeigt. **Auf einer Tile-Card sieht man also je nach Frontend-Sprache entweder die englische oder die lokalisierte Variante – aber derselbe Sensor.** Wer auf den englischen Buttons-State explizit verzichten will, kann alternativ mit dem `mdi:clock-digital`-Icon und einer Text-Karte arbeiten: ```yaml type: conditional conditions: - entity: sensor.main_gate_status state: open card: type: markdown content: >- Open · {{ state_attr('sensor.main_gate_status', 'remaining_seconds') }}s ``` ## Update-Prozess HACS markiert neue Releases automatisch. Nach einer Aktualisierung empfiehlt sich ein Home-Assistant-Neustart, weil die Integration während eines laufenden Ablaufs nicht in-place ausgetauscht werden kann. > Aktuell ist keine Reload-Annotation aktiv: ein bereits laufender Zyklus geht > beim Update verloren und der Status wird auf `closed` zurückgesetzt. Das ist > mit einem schief gestellten Tor-Position fehlertolerant (siehe > *Einschränkungen*). ## Entwicklung ```bash git clone https://github.com//ha-main-gate-controller cd ha-main-gate-controller python3 -m venv .venv source .venv/bin/activate pip install --upgrade pip pip install -r requirements_test.txt # Statische Prüfung ruff check custom_components tests # Tests pytest ``` Die Tests verwenden das Standard-Testwerkzeug [`pytest-homeassistant-custom-component`](https://github.com/jaisenbe/pytest-homeassistant-custom-component), welches eine bestimmte Version von Home Assistant pinnt. Damit lassen sich Config-Flow, Entity-Lebenszyklus und asynchrone Service-Calls mit dem echten HA-Testkern testen. Die Integration wird über den `custom_components/`-Ordner direkt neben den Tests gefunden. ## Release-Prozess 1. Änderungen hochladen und prüfen, dass alle CI-Checks grün sind (HACS validation, Hassfest, pytest). 2. Im Repository die Versionsnummer in `manifest.json` und im Tag synchron anpassen. Der Release-Workflow (`.github/workflows/release.yml`) erzwingt das. Ziel: `version: 0.1.0` im Manifest ↔ Git-Tag `v0.1.0`. 3. Tag setzen und pushen: ```bash git tag v0.1.0 git push origin v0.1.0 ``` 4. GitHub Actions baut das Release-Artefakt (Zip der Integration) und veröffentlicht es auf der GitHub-Releases-Seite. ## Troubleshooting ### „cannot add entities“ / Integration lädt nicht - Home Assistant neu starten, damit `custom_components/main_gate_controller` einmal frisch gelesen wird. - Logs: ```yaml logger: logs: custom_components.main_gate_controller: debug ``` ### Button bleibt ohne Wirkung - Konfiguration prüfen: Settings → Devices & Services → Main Gate Controller → Gerät → Entity → Status sollte `closed` sein. Wenn nicht, einmal manuell über die Switch-Entity steuern, um sicher zu sein, dass das Relais überhaupt reagiert. ### Benachrichtigungen kommen nicht an - `Send notifications` aktiviert? Targets ausgewählt? - Die Entity-Selector-Suche zeigt nur `notify.*`-Entities; bei Companion-Apps darauf achten, dass die Mobile-App bereits ein `notify.mobile_app_` angelegt hat. ### Status bleibt nach Stromausfall auf einer Phase hängen - Die Integration ist **blind** für die reale Tor-Position: nach einem Neustart beginnt `sensor._status` mit `closed`. Wenn das Tor während des Ausfalls halb offen geblieben ist, muss es von Hand abgeschlossen werden. ## Architektur ``` custom_components/main_gate_controller/ ├── __init__.py # async_setup_entry, async_unload_entry, DeviceInfo ├── manifest.json # Domain, Version, Codeowners, iot_class=calculated ├── const.py # Konstanten / Defaults ├── coordinator.py # MainGateController ← Zustandsmaschine, Countdown, Service-Calls ├── config_flow.py # MainGateConfigFlow + MainGateOptionsFlow + Selectors ├── button.py # button._open ├── sensor.py # sensor._status + Attribute ├── diagnostics.py # anonymisierter Diagnose-Export ├── strings.json # Übersetzungs-Schlüssel └── translations/ ├── en.json └── de.json ``` Alle Service-Calls (`switch.turn_on/off`, `notify.send_message`) laufen ausschließlich gegen die ausgewählten Home-Assistant-Entities. Es findet kein direkter Zugriff auf Hersteller-Integrationen (z. B. Shelly) statt. ## Bekannte Einschränkungen / Design-Entscheidungen - **Kein Binary-Sensor „Running“.** Der Statussensor liefert `running` bereits als Attribut; eine zweite Entity würde keinen Mehrwert bieten. - **Blind-Betrieb.** Die Integration hat keine Rückmeldung über die tatsächliche Tor-Position. Wenn das Relais angezogen hat, heißt das nicht zwingend, dass das Tor offen ist. Wer eine echtzeitnahe Statusanzeige benötigt, sollte die Tor-Endlage zusätzlich an einen Home-Assistant-Sensor melden und diesen Automations-Auslöser verwenden. - **Restart mitten im Zyklus.** Beim Reload/Unload wird der laufende Zyklus abgebrochen und der Status auf `closed` zurückgesetzt. Die reale Tor-Position muss im Zweifel per Hand geprüft werden. - **Switch-Fehler → safe default.** Schlägt ein `switch.turn_*`-Aufruf fehl, wird der Zyklus abgebrochen und der Status auf `closed` gesetzt; ein Alarm bleibt im Log. Im Zweifel ist das Relais möglicherweise trotzdem ein- oder ausgeschaltet worden, das lässt sich aber nicht garantiert ermitteln.