Initial commit: Main Gate Controller HACS integration
Tests / pytest + ruff (3.12) (push) Has been cancelled
Validate / HACS validation (push) Has been cancelled
Validate / Hassfest validation (push) Has been cancelled

This commit is contained in:
bot
2026-09-07 23:19:12 +02:00
commit 988a30cbf7
27 changed files with 2456 additions and 0 deletions
+314
View File
@@ -0,0 +1,314 @@
# 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/<your-username>/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.<gate>_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.<gate>_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.<gate>_running` erzeugt. Das `running`-Attribut am
> Statussensor liefert dieselbe Information ohne eine weitere Entity, und
> `sensor.<gate>_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/<your-username>/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_<phone>` 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.<gate>_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.<gate>_open
├── sensor.py # sensor.<gate>_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.