15 KiB
HomeKit Bridge
HomeKit Accessory Protocol (HAP) Bridge für das Home Automation System.
Überblick
Diese HomeKit-Bridge ermöglicht die Steuerung aller Geräte über Apple HomeKit / Home-App auf iPhone/iPad/Mac.
Architektur:
- Läuft auf Raspberry Pi im lokalen Netzwerk
- Kommuniziert ausschließlich über REST-API (kein direkter MQTT/Redis-Zugriff)
- Verwendet HAP-Python für HomeKit-Protokoll
- Unterstützt Echtzeit-Updates über Server-Sent Events (SSE)
Implementierungsanleitung für Copilot
Copilot-Aufgabe: Implementiere die HomeKit-Bridge (HAP-Python) für unser System
Kontext
- Wir verwenden HAP-Python als HomeKit-Bridge.
- Die Bridge läuft auf einem Raspberry Pi im gleichen LAN wie die iPhones.
- Die Bridge kommuniziert AUSSCHLIESSLICH über die REST-API mit dem restlichen System (KEIN direkter MQTT-/Redis-Zugriff).
- Geräte- und Raum-Infos kommen aus:
GET /devicesGET /layoutund werden überdevice_idgejoint.
- Die Abbildung von abstrakten Gerätetypen/Features auf HomeKit-Services/Characteristics steht in der Datei
apps/homekit/homekit_mapping.md(Markdown-Tabelle). Bitte orientiere dich daran.
Ziel
Implementiere eine lauffähige HomeKit-Bridge in apps/homekit, die:
- beim Start Geräte & Räume über die API lädt,
- daraus anhand der Mapping-Tabelle HomeKit-Accessories erzeugt,
- Zustandsänderungen über einen Realtime-Endpoint der API empfängt und in HomeKit spiegeln kann,
- Set-Kommandos aus HomeKit (z.B. Licht an/aus, Helligkeit, Zieltemperatur) per
POST /devices/{id}/setan die API sendet, - saubere Start-/Stop-Logs ausgibt,
- einfache Test-/Startanweisungen für den Raspberry Pi und die Home-App (iPhone) in Kommentaren dokumentiert.
WICHTIG
- Bitte benutze HAP-Python (pyhap).
- Keine direkte MQTT- oder Redis-Kommunikation in der Bridge.
- Nutze HTTP(S) zur API (z.B. mit requests oder httpx; gerne sync, HAP-Python ist selbst eventloop-orientiert).
- Nutze die Mapping-Tabelle in
apps/homekit/homekit_mapping.mdals Leitlinie für die Accessory-Typen.
Paketstruktur
apps/homekit/
├── __init__.py
├── main.py # Einstiegspunkt
├── api_client.py # REST-Client zur API
├── device_registry.py # Join /devices + /layout → internes Device-Modell
├── accessories/
│ ├── base.py # gemeinsame Basisklasse/n für Accessories
│ ├── light.py # Light-Accessories
│ ├── thermostat.py
│ ├── contact.py
│ ├── sensor.py # temp_humidity etc.
│ └── cover.py # optional, Rollladen
└── tests/ # rudimentäre Tests/Checks
Teil 1: Internes Gerätemodell / Device-Registry
1.1 Erstelle in apps/homekit/device_registry.py:
Dataklasse/Model Device:
device_id: strtype: str- "light","thermostat","outlet","contact","temp_humidity","cover",...name: str- Kurzname ausGET /devices.namefriendly_name: str- title ausGET /layout(fallback name)room: str | None- Raumname aus layoutfeatures: dict[str, bool]read_only: bool- heuristisch: Sensor-/Kontakt-Typen sind read_only
Klasse DeviceRegistry mit Funktionen:
def load_from_api(api: ApiClient) -> DeviceRegistry:- ruft
GET /devicesundGET /layoutauf, - joint über
device_id, - erstellt Device-Instanzen.
- ruft
get_all(): list[Device]get_by_id(device_id: str) -> Device | None
Akzeptanz:
load_from_apifunktioniert mit der bestehenden Struktur von/devicesund/layout:/devicesliefert mindestens{device_id, type, name, features}/layoutliefert eine Struktur, aus derdevice_id → room + titleableitbar ist
- Der Join über
device_idklappt; fehlende Layout-Einträge werden toleriert
Teil 2: API-Client
2.1 Erstelle in apps/homekit/api_client.py:
Klasse ApiClient mit:
__init__(self, base_url: str, token: str | None = None, timeout: int = 5)- Methoden:
get_devices() -> list[dict]: GET /devicesget_layout() -> dict: GET /layoutget_device_state(device_id: str) -> dict: GET /devices/{id}/statepost_device_set(device_id: str, type: str, payload: dict) -> None: POST /devices/{id}/setstream_realtime() -> Iterator[dict]:- GET /realtime als SSE, yield jedes Event als dict:
{"type":"state","device_id":...,"payload":{...},"ts":...}
- GET /realtime als SSE, yield jedes Event als dict:
Auth:
- Wenn ein API-Token via ENV
HOMEKIT_API_TOKENgesetzt ist, nutze HTTP-Header:Authorization: Bearer <token>
Akzeptanz:
- ApiClient ist robust:
- bei Netzwerkfehlern gibt es sinnvolle Exceptions/Logs,
stream_realtimebehandelt Reconnect (z.B. einfache Endlosschleife mit Backoff).
- Es werden keine MQTT-Details verwendet, nur HTTP.
Teil 3: HomeKit-Accessory-Klassen (HAP-Python)
3.1 Erstelle in apps/homekit/accessories/base.py:
Basisklasse BaseDeviceAccessory(Accessory) mit:
- Referenz auf Device (aus DeviceRegistry)
- Referenz auf ApiClient
- Methoden zum:
- Registrieren von HAP-Characteristics und Set-Handlern
- Aktualisieren von Characteristics bei eingehenden Events
- Logging
3.2 Erstelle spezifische Accessory-Klassen basierend auf homekit_mapping.md:
LightAccessories (apps/homekit/accessories/light.py):
- On/Off (nur power)
- Dimmable (power + brightness)
- Color (power + brightness + color_hsb)
ThermostatAccessory:
- CurrentTemperature, TargetTemperature, Mode (so weit in Mapping definiert)
ContactAccessory:
- ContactSensorState (open/closed)
TempHumidityAccessory:
- TemperatureSensor (CurrentTemperature)
- HumiditySensor (CurrentRelativeHumidity)
OutletAccessory:
- On/Off
CoverAccessory (optional):
- WindowCovering mit CurrentPosition/TargetPosition
Die Mapping-Tabelle in homekit_mapping.md ist die normative Referenz:
- Bitte lies die Tabelle und mappe
abstract_type + Features → passende Accessory-Klasse und Characteristics - Wo die Tabelle
Status=TODO/REVIEWhat:- Implementiere nur das, was eindeutig ist,
- lasse TODO-Kommentare an den entsprechenden Stellen im Code.
Akzeptanz:
- Für die abstrakten Typen, die bereits in
devices.yamlund Mapping-Tabelle klar definiert sind (z.B. light, thermostat, contact, temp_humidity), existieren passende Accessory-Klassen. - Set-Operationen erzeugen korrekte Payloads für
POST /devices/{id}/set:- Light:
{"type":"light","payload":{"power":"on"/"off", "brightness":..., "hue":..., "sat":...}} - Thermostat:
{"type":"thermostat","payload":{"target":...}} - Contact: read_only → keine Set-Handler
- Temp/Humidity: read_only → keine Set-Handler
- Light:
Teil 4: Bridge-Setup mit HAP-Python
4.1 Implementiere in apps/homekit/main.py:
env-Konfiguration:
HOMEKIT_NAME(default: "Home Automation Bridge")HOMEKIT_PIN(z.B. "031-45-154")HOMEKIT_PORT(default 51826)API_BASE(z.B. "http://api:8001" oder extern)HOMEKIT_API_TOKEN(optional)
Funktion build_bridge(driver, api_client: ApiClient) -> Bridge:
- DeviceRegistry via
load_from_api(api_client)laden. - Für jedes Device anhand Mapping-Tabelle die passende Accessory-Klasse instanziieren.
- Einen Bridge-Accessory (
pyhap.accessory.Bridge) erstellen. - Alle Device-Accessories der Bridge hinzufügen.
Realtime-Event-Loop:
- In einem Hintergrund-Thread oder ThreadPool:
api_client.stream_realtime()iterieren,- für jedes Event
device_id → Accessoryfinden, - Characteristics updaten.
- Thread wird beim Shutdown sauber beendet.
main():
- Logging einrichten.
- ApiClient erstellen.
AccessoryDriver(port=HOMEKIT_PORT, persist_file="homekit.state")erstellen.- Bridge via
build_bridge(driver, api_client)bauen. - Bridge dem Driver hinzufügen.
- Realtime-Thread starten.
driver.start()aufrufen.- Auf KeyboardInterrupt reagieren und sauber stoppen.
Akzeptanz:
- Beim Start loggt die Bridge:
- Anzahl Devices,
- auf welchem Port sie als HomeKit-Bridge lauscht,
- welches API_BASE verwendet wird.
- Die Datei
homekit.statewird im Arbeitsverzeichnis bzw. einem konfigurierbaren Ordner abgelegt (um Pairing-Info persistent zu halten). - Die Bridge übersteht API-Neustarts (Realtime-Loop reconnectet) und Netzwerkflaps.
Teil 5: Tests & Testanweisungen
5.1 Lege in apps/homekit/tests/ einfache Tests/Checks an:
Unit-Tests (pytest), soweit ohne echtes HomeKit möglich:
- Test für
DeviceRegistry.load_from_api()mit Mock-Antworten aus/devicesund/layout:- Korrekte Join-Logik,
- Korrekte room/friendly_name-Zuordnung.
- Test für set-Payload-Erzeugung pro Accessory:
- z.B. LightAccessory: On=True → POST /devices/{id}/set wird mit korrektem Payload aufgerufen (über Mock ApiClient).
Allgemein
- Nutze möglichst sinnvolle Typannotationen und Docstrings.
- Hinterlasse TODO-Kommentare an Stellen, wo die Mapping-Tabelle explizit Status=TODO/REVIEW hat.
- Ändere KEINE bestehenden API-Endpunkte; verlasse dich nur auf deren aktuelles Verhalten (GET /devices, GET /layout, /realtime, POST /devices/{id}/set).
Installation & Setup
Voraussetzungen
- Python 3.9+
- Raspberry Pi im gleichen LAN wie iPhone/iPad
- API-Server erreichbar (z.B.
http://api:8001)
Installation
cd apps/homekit
pip install -r requirements.txt
Umgebungsvariablen
Erstelle eine .env Datei oder setze folgende Variablen:
export API_BASE="http://YOUR_API_IP:8001"
export HOMEKIT_API_TOKEN="your-token-if-needed" # optional
export HOMEKIT_PIN="031-45-154"
export HOMEKIT_NAME="Home Automation Bridge"
export HOMEKIT_PORT="51826"
export HOMEKIT_PERSIST_FILE="homekit.state"
Start
python -m apps.homekit.main
Erwartete Logs:
Loading devices from API...
Loaded X devices from API
Bridge built with Y accessories
HomeKit Bridge started on port 51826
Starting realtime event loop...
Pairing mit iPhone (Home-App)
-
Voraussetzungen:
- iPhone im gleichen WLAN wie Raspberry Pi
- Bridge läuft und zeigt "started on port 51826"
-
Home-App öffnen:
- Öffne die Home-App auf dem iPhone
- Tippe auf "+" → "Gerät hinzufügen"
- Wähle "Weitere Optionen..." oder "Code fehlt oder kann nicht gescannt werden"
-
Bridge auswählen:
- Die Bridge sollte in der Nähe-Liste erscheinen (z.B. "Home Automation Bridge")
- Tippe auf die Bridge
-
PIN eingeben:
- Gib den PIN ein:
031-45-154(oder deinHOMEKIT_PIN) - Format:
XXX-XX-XXX
- Gib den PIN ein:
-
Konfiguration abschließen:
- Geräte werden geladen
- Räume werden automatisch aus
layout.yamlübernommen - Geräte können nun über Home-App gesteuert werden
Funktionstests
Test 1: Licht einschalten
Aktion: Lampe in Home-App antippen (On)
Erwartung:
- API-Log zeigt:
POST /devices/{id}/setmit{"type":"light","payload":{"power":"on"}} - Physische Lampe oder Simulator schaltet ein
Test 2: Helligkeit ändern (dimmbare Lampe)
Aktion: Helligkeits-Slider bewegen (z.B. 75%)
Erwartung:
POST /devices/{id}/setmitbrightnessca. 75- Lampe dimmt entsprechend
Test 3: Farbe ändern (Farblampe)
Aktion: Farbe in Home-App wählen
Erwartung:
POST /devices/{id}/setmithue/satWerten- Lampe wechselt Farbe
Test 4: Thermostat Zieltemperatur
Aktion: Zieltemperatur auf 22°C setzen
Erwartung:
POST /devices/{id}/setmittarget=22CurrentTemperaturewird über/realtimeaktualisiert
Test 5: Kontaktsensor (read-only)
Aktion: Fenster physisch öffnen/schließen
Erwartung:
/realtimesendet Event- Home-App zeigt "Offen" oder "Geschlossen"
Test 6: Temperatur-/Feuchtigkeitssensor
Aktion: Werte ändern sich (z.B. Heizung an)
Erwartung:
/realtimeEvents aktualisieren Werte- Home-App zeigt aktuelle Temperatur/Luftfeuchtigkeit
Troubleshooting
Bridge erscheint nicht in Home-App
- Netzwerk prüfen: iPhone und RPi im gleichen Subnet?
- Firewall prüfen: Port 51826 muss erreichbar sein
- Logs prüfen: Fehler beim Start?
- mDNS/Bonjour: Funktioniert Bonjour im Netzwerk?
Geräte reagieren nicht
- API-Logs prüfen: Kommen POST-Requests an?
- Realtime-Verbindung: Läuft
/realtimeEvent-Loop? (Log-Meldungen) - API-Endpoints testen: Manuell mit
curltesten
Pairing schlägt fehl
- State-Datei löschen:
rm homekit.stateund Bridge neu starten - PIN-Format prüfen: Muss
XXX-XX-XXXFormat haben - Alte Pairings löschen: In Home-App unter "Home-Einstellungen" → "HomeKit-Geräte zurücksetzen"
Realtime-Updates funktionieren nicht
- SSE-Verbindung prüfen: Logs zeigen "Starting realtime event loop..."?
- API-Endpoint testen:
curl -N http://api:8001/realtime - Firewall/Proxy: Blockiert etwas SSE-Streams?
Docker Deployment
Dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "-m", "apps.homekit.main"]
docker-compose.yml
services:
homekit:
build: ./apps/homekit
environment:
- API_BASE=http://api:8001
- HOMEKIT_PIN=031-45-154
- HOMEKIT_NAME=Home Automation Bridge
ports:
- "51826:51826"
volumes:
- ./data/homekit:/data
network_mode: "host" # Wichtig für mDNS/Bonjour Discovery
restart: unless-stopped
Wichtig: network_mode: "host" ist erforderlich für mDNS/Bonjour Discovery, damit die Bridge im lokalen Netzwerk gefunden werden kann.
Architektur
┌─────────────┐
│ iPhone/ │
│ Home-App │
└──────┬──────┘
│ HomeKit (HAP)
│ Port 51826
┌──────▼──────────────────┐
│ HomeKit Bridge │
│ (HAP-Python) │
│ - Device Registry │
│ - Accessory Mapping │
│ - SSE Event Loop │
└──────┬──────────────────┘
│ HTTP REST API
│ (GET /devices, POST /set, SSE /realtime)
┌──────▼──────────────────┐
│ API Server │
│ (FastAPI) │
└──────┬──────────────────┘
│ MQTT
┌──────▼──────────────────┐
│ Abstraction Layer │
│ (Zigbee2MQTT, MAX!) │
└─────────────────────────┘
Weitere Dokumentation
- API-Mapping: Siehe
homekit_mapping.mdfür Device-Type → HomeKit-Service Mapping - API-Dokumentation: Siehe API-Server README für Endpoint-Dokumentation
- HAP-Python Docs: https://github.com/ikalchev/HAP-python
Entwicklung
Tests ausführen
pytest apps/homekit/tests/
Logs
Die Bridge gibt detaillierte Logs aus:
INFO: Normale Betriebsmeldungen (Start, Device-Anzahl, etc.)DEBUG: Detaillierte State-Updates und API-CallsERROR: Fehler bei API-Kommunikation oder Accessory-Updates
Log-Level über Environment-Variable steuern:
export LOG_LEVEL=DEBUG
Lizenz
Siehe Hauptprojekt-Lizenz.