Initial commit: Main Gate Controller HACS integration
This commit is contained in:
@@ -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.
|
||||
Reference in New Issue
Block a user