From 65ba02a2fb743c76b86a3d637c1b7cc780810ffc Mon Sep 17 00:00:00 2001 From: admin Date: Sun, 18 Jan 2026 16:27:21 +0000 Subject: [PATCH] =?UTF-8?q?steckdose.md=20hinzugef=C3=BCgt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- steckdose.md | 1160 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1160 insertions(+) create mode 100644 steckdose.md diff --git a/steckdose.md b/steckdose.md new file mode 100644 index 0000000..7b47b8d --- /dev/null +++ b/steckdose.md @@ -0,0 +1,1160 @@ +# Pi Zero 2W + ReSpeaker - IKEA TRETAKT Integration via FritzBox +## Sprachsteuerung von IKEA TRETAKT Smart Plugs + +**Hardware:** IKEA TRETAKT (Zigbee 3.0, €8) +**Integration:** FritzBox Smart Gateway (Zigbee Bridge) +**Steuerung:** Pi Zero 2W → FritzBox → TRETAKT Steckdose + +--- + +## UNTERSCHIED: TRETAKT vs. FRITZ!DECT + +| Aspekt | FRITZ!DECT 200 | IKEA TRETAKT | +|--------|----------------|-------------| +| **Funkstandard** | DECT ULE | Zigbee 3.0 | +| **Preis** | €25-35 | **€8 (super günstig!)** | +| **Reichweite** | ~300m | ~100m | +| **Netzwerk** | DECT Mesh | Zigbee Mesh | +| **Kompatibilität** | Nur FritzBox DECT | Philips Hue, Dirigera, FritzBox + Gateway | +| **API** | Direkt via TR-064 | Via FritzBox Zigbee Integration | + +**Vorteil TRETAKT:** Deutlich günstiger, Zigbee-Standard, erweiterbares Netzwerk! +**Nachteil:** Braucht **FRITZ!Smart Gateway** (~€100) wenn du nur DECT-Geräte hast. Mit FritzBox 7690 oder neuer schon integriert. + +--- + +## VORAUSSETZUNGEN + +1. **FritzBox mit Zigbee-Unterstützung:** + - ✅ FritzBox 7690 / 7680 / 7590 AX (ab 2021) + - ✅ **FRITZ!Smart Gateway** (~€100, zusätzliche Zigbee-Bridge) + - ❌ Ältere Modelle: Nicht unterstützt + +2. **IKEA TRETAKT Steckdosen** (€8 pro Stück) + - Zigbee 3.0 Standard + - Kein IKEA Hub nötig! + +Überprüfe dein FritzBox-Modell: +```bash +# Später vom Pi aus: +curl -s "http://192.168.178.1:49000/tr64desc.xml" | grep modelName +``` + +--- + +## TEIL 1: FritzBox vorbereiten + +### 1.1 FritzBox Zigbee aktivieren + +Je nach Modell unterschiedlich: + +#### Option A: FritzBox 7690/7680 (integriertes Zigbee) + +1. Öffne: **http://fritz.box** → Admin-Passwort eingeben +2. Gehe zu: **Heimnetz** → **Geräte und Sensoren** → **Smarthome Einstellungen** +3. Aktiviere: **"Zigbee aktivieren"** ✓ +4. Klicke: **"Speichern"** + +#### Option B: Mit FRITZ!Smart Gateway (für ältere Modelle) + +1. Gateway kaufen (~€100) +2. Mit Ethernet an FritzBox verbinden (nicht WiFi!) +3. FritzBox automatisch verbindet sich +4. Einrichtung ähnlich wie Option A + +Hinweis: Dieses Dokument geht von **Option A** aus (integriertes Zigbee). + +### 1.2 Benutzer für API-Zugriff erstellen + +1. FritzBox öffnen: **http://fritz.box** +2. **System** → **FRITZ!Box-Benutzer** +3. **Neuer Benutzer** klicken: + - **Benutzername:** `raspi_home` + - **Passwort:** Starkes Passwort (z.B. `TreTakt2025Pi`) +4. Haken setzen: + - ✅ **Zugriff auf Smart Home Geräte** (WICHTIG!) +5. **OK** klicken + +--- + +## TEIL 2: TRETAKT Steckdose koppeln + +### 2.1 Koppelmodus starten + +1. Öffne FritzBox: **http://fritz.box** +2. Gehe zu: **Heimnetz** → **Geräte und Sensoren** +3. Klicke auf: **"Neues Gerät anmelden"** oder **"Gerät koppeln"** +4. Die FritzBox sucht jetzt nach Zigbee-Geräten (30 Sekunden) + +### 2.2 TRETAKT Steckdose anmelden + +1. **TRETAKT Steckdose in die Steckdose stecken** +2. **Reset-Taste** auf der Rückseite **8-10 Sekunden halten** + - LED sollte **schnell blinken** (Kopplungsmodus aktiv) +3. FritzBox sollte das Gerät erkennen +4. Gib einen Namen ein (z.B. "Wohnzimmer Musik") +5. **Speichern** + +**Steckdose ist jetzt gekoppelt!** ✓ + +### 2.3 Gerät-ID (Identifier) ermitteln + +Dies brauchst du für die Python-Steuerung: + +1. FritzBox: **Heimnetz** → **Geräte und Sensoren** +2. Klicke auf die **TRETAKT Steckdose** +3. Suche nach **"Identifier"** oder **"ID"** + - Format: `zigbee:ABC123:1:on` oder ähnlich +4. **Notiere diese ID!** + +Alternative (via XML): +```bash +curl -s "http://192.168.178.1:49000/tr64desc.xml" | grep -i zigbee +``` + +Beispiel-IDs: +``` +zigbee:0013A20041C4ABCD:1:on +``` + +--- + +## TEIL 3: Python-Installation + +### 3.1 Bibliothek für FritzBox Zigbee installieren + +```bash +# SSH zum Pi +ssh pi@ + +# Installiere die FritzBox Bibliothek +pip3 install fritzconnection + +# Optional: für vollständigere Zigbee-Unterstützung +pip3 install pyfritz +``` + +### 3.2 Test durchführen + +Erstelle `test_tretakt.py`: + +```bash +nano ~/voice_assistant/test_tretakt.py +``` + +```python +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +Test-Skript: IKEA TRETAKT via FritzBox steuern +Zigbee-Steckdosen über REST API kontrollieren +""" + +import sys +from fritzconnection.lib.homeauto import FritzHomeAutomation + +# ============================================================================ +# KONFIGURATION +# ============================================================================ + +FRITZBOX_IP = "192.168.178.1" +FRITZBOX_USER = "raspi_home" +FRITZBOX_PASSWORD = "TreTakt2025Pi" + +# Die Identifier deiner TRETAKT Steckdosen (aus Schritt 2.3) +TRETAKT_DEVICES = { + "musik": { + "identifier": "zigbee:0013A20041C4ABCD:1:on", # ← DEINE ID! + "name": "Wohnzimmer Musik", + }, + "licht": { + "identifier": "zigbee:0013A20041C4ABCE:1:on", # ← DEINE ID! + "name": "Küche Licht", + }, + "stopp": { + "identifier": "zigbee:0013A20041C4ABCF:1:on", # ← DEINE ID! + "name": "Flur Steckdose", + }, +} + +# ============================================================================ +# FUNKTIONEN +# ============================================================================ + +def test_connection(): + """Teste Verbindung zur FritzBox""" + print("Verbinde zur FritzBox...") + try: + fha = FritzHomeAutomation( + avm_device_ip=FRITZBOX_IP, + user=FRITZBOX_USER, + password=FRITZBOX_PASSWORD + ) + print("✓ Verbindung erfolgreich!\n") + return fha + except Exception as e: + print(f"✗ Verbindungsfehler: {e}") + return None + +def list_all_devices(fha): + """Zeige alle Zigbee-Geräte""" + print("Verfügbare Zigbee-Geräte:") + try: + devices = fha.get_devices() + if not devices: + print("Keine Geräte gefunden") + return + + for device in devices: + print(f"\nGerät: {device.name}") + print(f" Identifier: {device.identifier}") + print(f" Typ: {device.device_type}") + print(f" Status: {'AN ✓' if device.state else 'AUS ✗'}") + + # Zusätzliche Infos + if hasattr(device, 'power'): + print(f" Leistung: {device.power / 100}W") + if hasattr(device, 'energy'): + print(f" Energie: {device.energy / 1000}kWh") + except Exception as e: + print(f"✗ Fehler: {e}") + +def toggle_device(fha, identifier): + """Schalte Gerät um""" + try: + device = fha.get_device_by_identifier(identifier) + if not device: + print(f"✗ Gerät nicht gefunden: {identifier}") + return False + + current_state = device.state + print(f"Status: {'AN' if current_state else 'AUS'}") + + if current_state: + print("Schalte AUS...") + device.turn_off() + else: + print("Schalte AN...") + device.turn_on() + + import time + time.sleep(1) + + device.update() + new_state = device.state + print(f"Neuer Status: {'AN ✓' if new_state else 'AUS ✗'}\n") + return True + except Exception as e: + print(f"✗ Fehler beim Schalten: {e}") + return False + +def get_device_info(fha, identifier, name): + """Zeige Geräteinformationen""" + try: + device = fha.get_device_by_identifier(identifier) + if not device: + print(f"✗ Gerät nicht gefunden") + return + + print(f"\n{'='*50}") + print(f"TRETAKT: {name}") + print(f"{'='*50}") + print(f"Identifier: {device.identifier}") + print(f"Typ: {device.device_type}") + print(f"Status: {'AN ✓' if device.state else 'AUS ✗'}") + + if hasattr(device, 'power'): + print(f"Leistung: {device.power / 100:.1f}W") + if hasattr(device, 'energy'): + print(f"Verbrauch: {device.energy / 1000:.2f}kWh") + + print(f"{'='*50}\n") + except Exception as e: + print(f"✗ Fehler: {e}") + +# ============================================================================ +# HAUPTPROGRAMM +# ============================================================================ + +if __name__ == "__main__": + print("="*60) + print("IKEA TRETAKT via FritzBox - Test") + print("="*60 + "\n") + + # Verbindung testen + fha = test_connection() + if not fha: + sys.exit(1) + + # Alle Geräte auflisten + list_all_devices(fha) + + # Info zu Testgerät + if "musik" in TRETAKT_DEVICES: + device_config = TRETAKT_DEVICES["musik"] + get_device_info(fha, device_config["identifier"], device_config["name"]) + + # Test schalten + print("Teste Schalten...\n") + toggle_device(fha, device_config["identifier"]) + + print("✓ Test erfolgreich!") +``` + +Speichere und teste: + +```bash +# Bearbeite die IDs +nano ~/voice_assistant/test_tretakt.py +# Trage deine Identifiers ein + +# Teste +python3 ~/voice_assistant/test_tretakt.py +``` + +**Erwartete Ausgabe:** + +``` +============================================================ +IKEA TRETAKT via FritzBox - Test +============================================================ + +Verbinde zur FritzBox... +✓ Verbindung erfolgreich! + +Verfügbare Zigbee-Geräte: + +Gerät: Wohnzimmer Musik + Identifier: zigbee:0013A20041C4ABCD:1:on + Typ: HAN_FUN + Status: AUS ✗ + +================================================== +TRETAKT: Wohnzimmer Musik +================================================== +Identifier: zigbee:0013A20041C4ABCD:1:on +Typ: HAN_FUN +Status: AUS ✗ +================================================== + +Teste Schalten... + +Status: AUS +Schalte AN... +Neuer Status: AN ✓ + +✓ Test erfolgreich! +``` + +--- + +## TEIL 4: Integration in Keyword Spotting + +### 4.1 Modifizierte `keyword_spotting.py` + +Beginne mit deiner bisherigen `keyword_spotting.py` und ersetze die Konfiguration: + +```bash +nano ~/voice_assistant/keyword_spotting.py +``` + +**Ersetze die Konfiguration:** + +```python +from fritzconnection.lib.homeauto import FritzHomeAutomation + +class Config: + # ... Audio-Config wie zuvor ... + + # === FritzBox Smart Home (Zigbee) === + FRITZBOX_IP = "192.168.178.1" + FRITZBOX_USER = "raspi_home" + FRITZBOX_PASSWORD = "TreTakt2025Pi" + + # === Deine IKEA TRETAKT Steckdosen === + TRETAKT_DEVICES = { + "musik": { + "identifier": "zigbee:0013A20041C4ABCD:1:on", # ← DEINE ID! + "name": "Wohnzimmer Musik", + }, + "stopp": { + "identifier": "zigbee:0013A20041C4ABCE:1:on", # ← DEINE ID! + "name": "Küche Musik", + }, + "licht": { + "identifier": "zigbee:0013A20041C4ABCF:1:on", # ← DEINE ID! + "name": "Flur Licht", + }, + } + + # Keywords (wie zuvor) + KEYWORDS = { + "musik": { + "sound": "music.wav", + "action": "toggle_tretakt", + "device": "musik", + "confidence": 0.65, + }, + "stopp": { + "sound": "stopped.wav", + "action": "toggle_tretakt", + "device": "stopp", + "confidence": 0.70, + }, + "licht": { + "sound": "light.wav", + "action": "toggle_tretakt", + "device": "licht", + "confidence": 0.68, + }, + } +``` + +### 4.2 FritzBox-Klasse für Zigbee hinzufügen + +Füge diese Klasse VOR `KeywordSpotter` ein: + +```python +# ============================================================================ +# FRITZBOX ZIGBEE SMART HOME (TRETAKT) +# ============================================================================ + +class FritzBoxZigbee: + """Steuere IKEA TRETAKT Steckdosen über FritzBox""" + + def __init__(self): + """Initialisiere FritzBox Verbindung""" + logger.info("Initialisiere FritzBox Zigbee-Verbindung...") + try: + self.fha = FritzHomeAutomation( + avm_device_ip=Config.FRITZBOX_IP, + user=Config.FRITZBOX_USER, + password=Config.FRITZBOX_PASSWORD + ) + logger.info("✓ FritzBox Zigbee verbunden") + except Exception as e: + logger.warning(f"⚠ FritzBox nicht verfügbar: {e}") + self.fha = None + + def toggle_tretakt(self, device_name): + """Schalte TRETAKT Steckdose um""" + if not self.fha: + logger.error("✗ FritzBox nicht verbunden") + return False + + if device_name not in Config.TRETAKT_DEVICES: + logger.error(f"✗ Gerät '{device_name}' nicht konfiguriert") + return False + + try: + device_config = Config.TRETAKT_DEVICES[device_name] + identifier = device_config["identifier"] + name = device_config["name"] + + # Hole Gerät + device = self.fha.get_device_by_identifier(identifier) + if not device: + logger.error(f"✗ Gerät {name} nicht gefunden") + return False + + current_state = device.state + + if current_state: + logger.info(f"🔴 Schalte '{name}' AUS") + device.turn_off() + else: + logger.info(f"🟢 Schalte '{name}' AN") + device.turn_on() + + # Status aktualisieren + import time + time.sleep(0.3) + device.update() + new_state = device.state + + logger.info(f"✓ '{name}' ist jetzt: {'AN' if new_state else 'AUS'}") + return True + + except Exception as e: + logger.error(f"✗ Fehler beim Schalten: {e}") + return False + + def get_status(self, device_name): + """Hole Status einer Steckdose""" + if not self.fha: + return None + + try: + device_config = Config.TRETAKT_DEVICES[device_name] + device = self.fha.get_device_by_identifier(device_config["identifier"]) + if device: + return device.state + except: + pass + + return None +``` + +### 4.3 ActionHandler aktualisieren + +**Ersetze die komplette `ActionHandler` Klasse:** + +```python +class ActionHandler: + """Führe erkannte Kommandos aus""" + + def __init__(self, sound_player, fritz_zigbee): + self.sound_player = sound_player + self.fritz_zigbee = fritz_zigbee + + def execute(self, keyword): + """Führe Aktion aus""" + if keyword not in Config.KEYWORDS: + return False + + config = Config.KEYWORDS[keyword] + logger.info(f"🎯 Erkannt: {keyword.upper()}") + + # Spiele Sound ab + if config.get("sound"): + self.sound_player.play_sound(config["sound"]) + + # Führe Aktion aus + action = config.get("action") + + if action == "toggle_tretakt": + device_name = config.get("device") + self.fritz_zigbee.toggle_tretakt(device_name) + + return True +``` + +### 4.4 VoiceControllerLite aktualisieren + +**Ersetze die `__init__` Methode:** + +```python +def __init__(self): + logger.info("=" * 70) + logger.info("Voice Controller - IKEA TRETAKT Integration") + logger.info("=" * 70) + + try: + self.spotter = KeywordSpotter() + self.sound_player = SoundPlayer() + self.fritz_zigbee = FritzBoxZigbee() # ← NEU! + self.action_handler = ActionHandler(self.sound_player, self.fritz_zigbee) # Geändert + + self.last_detection = 0 + self.detection_cooldown = 1.0 + except Exception as e: + logger.error(f"✗ Initialisierungsfehler: {e}") + raise +``` + +### 4.5 Kompletter Code + +Hier ist die **komplette modifizierte `keyword_spotting.py` mit TRETAKT-Integration:** + +```python +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +Keyword Spotting für Raspberry Pi Zero 2W ++ IKEA TRETAKT Zigbee Integration via FritzBox +""" + +import json +import os +import sys +import logging +import subprocess +import time +import numpy as np +import sounddevice as sd +from pathlib import Path +from collections import deque +from fritzconnection.lib.homeauto import FritzHomeAutomation + +# ============================================================================ +# KONFIGURATION +# ============================================================================ + +class Config: + BASE_DIR = Path(__file__).parent + SOUNDS_DIR = BASE_DIR / "sounds" + LOGS_DIR = BASE_DIR / "logs" + + SAMPLERATE = 16000 + CHUNK_SIZE = 512 + CHANNELS = 1 + DEVICE_INDEX = None + + # === FritzBox Smart Home (Zigbee) === + FRITZBOX_IP = "192.168.178.1" + FRITZBOX_USER = "raspi_home" + FRITZBOX_PASSWORD = "TreTakt2025Pi" + + # === Deine IKEA TRETAKT Steckdosen === + TRETAKT_DEVICES = { + "musik": { + "identifier": "zigbee:0013A20041C4ABCD:1:on", # ← DEINE ID! + "name": "Wohnzimmer Musik", + }, + "stopp": { + "identifier": "zigbee:0013A20041C4ABCE:1:on", # ← DEINE ID! + "name": "Küche Musik", + }, + "licht": { + "identifier": "zigbee:0013A20041C4ABCF:1:on", # ← DEINE ID! + "name": "Flur Licht", + }, + } + + # Keywords + KEYWORDS = { + "musik": { + "sound": "music.wav", + "action": "toggle_tretakt", + "device": "musik", + "confidence": 0.65, + }, + "stopp": { + "sound": "stopped.wav", + "action": "toggle_tretakt", + "device": "stopp", + "confidence": 0.70, + }, + "licht": { + "sound": "light.wav", + "action": "toggle_tretakt", + "device": "licht", + "confidence": 0.68, + }, + } + + LOG_FILE = LOGS_DIR / "keyword_spotting.log" + LOG_LEVEL = logging.INFO + +# ============================================================================ +# LOGGING +# ============================================================================ + +def setup_logging(): + """Einfaches Logging""" + Config.LOGS_DIR.mkdir(exist_ok=True) + + logger = logging.getLogger("KeywordSpotter") + logger.setLevel(Config.LOG_LEVEL) + + fh = logging.FileHandler(Config.LOG_FILE) + fh.setLevel(Config.LOG_LEVEL) + + ch = logging.StreamHandler() + ch.setLevel(Config.LOG_LEVEL) + + formatter = logging.Formatter( + '%(asctime)s - %(levelname)s - %(message)s', + datefmt='%Y-%m-%d %H:%M:%S' + ) + fh.setFormatter(formatter) + ch.setFormatter(formatter) + + logger.addHandler(fh) + logger.addHandler(ch) + + return logger + +logger = setup_logging() + +# ============================================================================ +# AUDIO-GERÄTE +# ============================================================================ + +def find_respeaker_device(): + """Finde ReSpeaker""" + logger.info("Suche ReSpeaker...") + try: + for index, name in enumerate(sd.query_devices()): + if isinstance(name, dict): + device_name = name.get('name', '') + else: + device_name = str(name) + + if 'seeed' in device_name.lower(): + logger.info(f"✓ ReSpeaker gefunden: Index {index}") + return index + except: + pass + + logger.warning("⚠ ReSpeaker nicht gefunden") + return None + +# ============================================================================ +# AKUSTISCHE FINGERPRINTS +# ============================================================================ + +class AudioFingerprint: + """Extrahiere akustische Features""" + + @staticmethod + def extract_features(audio_chunk): + """Extrahiere Features""" + audio = np.array(audio_chunk, dtype=np.float32) / 32768.0 + + zcr = np.mean(np.abs(np.diff(np.sign(audio)))) + energy = np.sqrt(np.mean(audio ** 2)) + + fft = np.abs(np.fft.fft(audio[:512])) + freq_energy = [ + np.sum(fft[0:50]), + np.sum(fft[50:150]), + np.sum(fft[150:256]), + ] + + return np.array([zcr, energy] + freq_energy, dtype=np.float32) + + @staticmethod + def compare_fingerprints(fp1, fp2): + """Vergleiche zwei Fingerprints""" + fp1_norm = (fp1 - np.mean(fp1)) / (np.std(fp1) + 1e-6) + fp2_norm = (fp2 - np.mean(fp2)) / (np.std(fp2) + 1e-6) + + similarity = np.dot(fp1_norm, fp2_norm) / ( + np.linalg.norm(fp1_norm) * np.linalg.norm(fp2_norm) + 1e-6 + ) + + similarity = (similarity + 1.0) / 2.0 + return max(0.0, min(1.0, similarity)) + +# ============================================================================ +# REFERENCE FINGERPRINTS +# ============================================================================ + +class ReferenceDatabase: + """Speichert Reference-Fingerprints""" + + def __init__(self): + self.db_file = Config.BASE_DIR / "reference_fingerprints.npy" + self.fingerprints = {} + self.load_or_create() + + def load_or_create(self): + """Lade oder erstelle Fingerprints""" + if self.db_file.exists(): + logger.info("Lade Reference-Fingerprints...") + try: + data = np.load(self.db_file, allow_pickle=True).item() + self.fingerprints = data + logger.info(f"✓ {len(self.fingerprints)} Keywords geladen") + except Exception as e: + logger.warning(f"Konnte nicht laden: {e}") + self.create_default_fingerprints() + else: + logger.info("Erstelle Default-Fingerprints...") + self.create_default_fingerprints() + + def create_default_fingerprints(self): + """Erstelle Default-Fingerprints""" + logger.warning("⚠ Benutze prepare_training.py für Training!") + + self.fingerprints = { + "musik": np.array([0.05, 0.3, 100, 500, 200], dtype=np.float32), + "stopp": np.array([0.02, 0.2, 150, 400, 300], dtype=np.float32), + "licht": np.array([0.04, 0.25, 120, 450, 250], dtype=np.float32), + } + + self.save() + + def save(self): + """Speichere Fingerprints""" + try: + np.save(self.db_file, self.fingerprints) + logger.info(f"✓ Reference-Fingerprints gespeichert") + except Exception as e: + logger.error(f"Fehler beim Speichern: {e}") + +# ============================================================================ +# FRITZBOX ZIGBEE (TRETAKT) ← NEU +# ============================================================================ + +class FritzBoxZigbee: + """Steuere IKEA TRETAKT Steckdosen über FritzBox""" + + def __init__(self): + """Initialisiere FritzBox Verbindung""" + logger.info("Initialisiere FritzBox Zigbee-Verbindung...") + try: + self.fha = FritzHomeAutomation( + avm_device_ip=Config.FRITZBOX_IP, + user=Config.FRITZBOX_USER, + password=Config.FRITZBOX_PASSWORD + ) + logger.info("✓ FritzBox Zigbee verbunden") + except Exception as e: + logger.warning(f"⚠ FritzBox nicht verfügbar: {e}") + self.fha = None + + def toggle_tretakt(self, device_name): + """Schalte TRETAKT Steckdose um""" + if not self.fha: + logger.error("✗ FritzBox nicht verbunden") + return False + + if device_name not in Config.TRETAKT_DEVICES: + logger.error(f"✗ Gerät '{device_name}' nicht konfiguriert") + return False + + try: + device_config = Config.TRETAKT_DEVICES[device_name] + identifier = device_config["identifier"] + name = device_config["name"] + + device = self.fha.get_device_by_identifier(identifier) + if not device: + logger.error(f"✗ Gerät {name} nicht gefunden") + return False + + current_state = device.state + + if current_state: + logger.info(f"🔴 Schalte '{name}' AUS") + device.turn_off() + else: + logger.info(f"🟢 Schalte '{name}' AN") + device.turn_on() + + time.sleep(0.3) + device.update() + new_state = device.state + + logger.info(f"✓ '{name}' ist jetzt: {'AN' if new_state else 'AUS'}") + return True + + except Exception as e: + logger.error(f"✗ Fehler beim Schalten: {e}") + return False + +# ============================================================================ +# KEYWORD SPOTTER +# ============================================================================ + +class KeywordSpotter: + """Höre nach Keywords""" + + def __init__(self): + logger.info("Initialisiere Keyword Spotter...") + + Config.DEVICE_INDEX = find_respeaker_device() + self.ref_db = ReferenceDatabase() + + self.stream = None + self.is_running = False + self.buffer = deque(maxlen=Config.SAMPLERATE) + + def audio_callback(self, indata, frames, time_info, status): + """Callback beim Audio-Input""" + if status: + logger.warning(f"Audio-Status: {status}") + + audio_data = indata[:, 0] + for sample in audio_data: + self.buffer.append(int(sample * 32767)) + + def start(self): + """Starte Audio-Listening""" + logger.info("Starte Audio-Listening...") + try: + self.stream = sd.InputStream( + samplerate=Config.SAMPLERATE, + blocksize=Config.CHUNK_SIZE, + channels=Config.CHANNELS, + device=Config.DEVICE_INDEX, + callback=self.audio_callback + ) + self.stream.start() + self.is_running = True + logger.info("✓ Audio-Listening aktiv") + except Exception as e: + logger.error(f"Fehler beim Starten: {e}") + raise + + def stop(self): + """Stoppe Audio-Listening""" + logger.info("Stoppe Audio-Listening...") + if self.stream: + self.stream.stop() + self.stream.close() + self.is_running = False + + def detect_keywords(self): + """Erkenne Keywords""" + if len(self.buffer) < Config.SAMPLERATE: + return None, 0 + + audio_chunk = list(self.buffer) + current_fp = AudioFingerprint.extract_features(audio_chunk) + + best_keyword = None + best_confidence = 0 + + for keyword, threshold_config in Config.KEYWORDS.items(): + ref_fp = self.ref_db.fingerprints.get(keyword) + + if ref_fp is None: + continue + + similarity = AudioFingerprint.compare_fingerprints(current_fp, ref_fp) + required_threshold = threshold_config.get("confidence", 0.7) + + if similarity > best_confidence and similarity >= required_threshold: + best_keyword = keyword + best_confidence = similarity + + return best_keyword, best_confidence + +# ============================================================================ +# SOUND-AUSGABE +# ============================================================================ + +class SoundPlayer: + """Spiele Sounds ab""" + + def __init__(self): + self.sounds_dir = Config.SOUNDS_DIR + self.sounds_dir.mkdir(exist_ok=True) + + def play_sound(self, filename): + """Spiele Sound ab""" + sound_path = self.sounds_dir / filename + + if not sound_path.exists(): + logger.warning(f"⚠ Sound nicht gefunden: {filename}") + return False + + try: + logger.info(f"♪ Spiele Sound ab: {filename}") + subprocess.run( + ['aplay', '-D', 'hw:1,0', str(sound_path)], + check=True, + capture_output=True, + timeout=10 + ) + return True + except Exception as e: + logger.error(f"✗ Fehler beim Abspielen: {e}") + return False + +# ============================================================================ +# AKTION-HANDLER ← GEÄNDERT +# ============================================================================ + +class ActionHandler: + """Führe Aktionen aus""" + + def __init__(self, sound_player, fritz_zigbee): + self.sound_player = sound_player + self.fritz_zigbee = fritz_zigbee + + def execute(self, keyword): + """Führe Aktion aus""" + if keyword not in Config.KEYWORDS: + return False + + config = Config.KEYWORDS[keyword] + logger.info(f"🎯 Erkannt: {keyword.upper()}") + + # Spiele Sound ab + if config.get("sound"): + self.sound_player.play_sound(config["sound"]) + + # Führe Aktion aus + action = config.get("action") + + if action == "toggle_tretakt": + device_name = config.get("device") + self.fritz_zigbee.toggle_tretakt(device_name) + + return True + +# ============================================================================ +# HAUPTPROGRAMM ← GEÄNDERT +# ============================================================================ + +class VoiceControllerLite: + """Hauptprogramm""" + + def __init__(self): + logger.info("=" * 70) + logger.info("Voice Controller - IKEA TRETAKT Integration") + logger.info("=" * 70) + + try: + self.spotter = KeywordSpotter() + self.sound_player = SoundPlayer() + self.fritz_zigbee = FritzBoxZigbee() # ← NEU! + self.action_handler = ActionHandler(self.sound_player, self.fritz_zigbee) # Geändert + + self.last_detection = 0 + self.detection_cooldown = 1.0 + except Exception as e: + logger.error(f"✗ Initialisierungsfehler: {e}") + raise + + def run(self): + """Hauptschleife""" + logger.info("Starte Hauptschleife...") + + try: + self.spotter.start() + + detection_count = 0 + + while True: + try: + keyword, confidence = self.spotter.detect_keywords() + + if keyword and confidence > 0.5: + current_time = time.time() + if current_time - self.last_detection > self.detection_cooldown: + detection_count += 1 + logger.info( + f"[#{detection_count}] ✓ {keyword.upper()} " + f"({confidence:.1%})" + ) + + self.action_handler.execute(keyword) + self.last_detection = current_time + + time.sleep(0.1) + + except KeyboardInterrupt: + logger.info("\n⏹ Unterbrochen") + break + except Exception as e: + logger.error(f"Fehler: {e}") + time.sleep(1) + + finally: + self.spotter.stop() + logger.info("✓ Voice Controller beendet") + +# ============================================================================ +# EINSTIEGSPUNKT +# ============================================================================ + +if __name__ == "__main__": + try: + controller = VoiceControllerLite() + controller.run() + except KeyboardInterrupt: + logger.info("Beendet") + sys.exit(0) + except Exception as e: + logger.error(f"✗ Fehler: {e}", exc_info=True) + sys.exit(1) +``` + +Speichere die Datei (Ctrl+X, Y, Enter). + +### 4.6 Konfiguration anpassen + +```bash +nano ~/voice_assistant/keyword_spotting.py +``` + +Trage deine Daten ein: + +```python +FRITZBOX_IP = "192.168.178.1" # Deine FritzBox IP +FRITZBOX_USER = "raspi_home" # Dein Benutzer +FRITZBOX_PASSWORD = "TreTakt2025Pi" # Dein Passwort + +TRETAKT_DEVICES = { + "musik": { + "identifier": "zigbee:0013A20041C4ABCD:1:on", # ← DEINE ID! + "name": "Wohnzimmer Musik", + }, + # ... weitere Geräte ... +} +``` + +### 4.7 Starten und testen + +```bash +cd ~/voice_assistant +python3 keyword_spotting.py +``` + +**Jetzt:** +1. Sprich: "musik" → TRETAKT schaltet! ✓ +2. Sprich: "stopp" → TRETAKT schaltet aus! +3. Sprich: "licht" → Andere TRETAKT schaltet! + +--- + +## VORTEIL TRETAKT vs. FRITZ!DECT + +| Feature | TRETAKT | FRITZ!DECT | +|---------|---------|-----------| +| **Preis** | €8 | €25-35 | +| **Netzwerk-Erweiterung** | Ja (Zigbee Mesh) | Nein (nur DECT) | +| **Mit Hue kompatibel** | ✅ Ja | ❌ Nein | +| **Weniger Strom** | ✅ Ja | ❌ Etwas mehr | +| **Physischer Schalter** | ✅ Ja | ✅ Ja | +| **Reichweite** | 100m | 300m | + +**Du sparst damit €17-27 pro Steckdose!** 🎉 + +--- + +## TROUBLESHOOTING + +**Problem: "Identifier nicht gefunden"** + +```bash +# Zeige alle Zigbee-Geräte mit Identifiers +python3 << 'EOF' +from fritzconnection.lib.homeauto import FritzHomeAutomation + +fha = FritzHomeAutomation( + avm_device_ip="192.168.178.1", + user="raspi_home", + password="TreTakt2025Pi" +) + +for device in fha.get_devices(): + if "tretakt" in device.name.lower() or "zigbee" in str(device.identifier): + print(f"Name: {device.name}") + print(f"Identifier: {device.identifier}\n") +EOF +``` + +**Problem: "FritzBox erkundet TRETAKT nicht"** + +1. Steckdose Reset (8-10 Sekunden Knopf drücken) +2. LED sollte schnell blinken +3. In FritzBox "Neues Gerät anmelden" starten +4. Warten (30 Sekunden) + +**Problem: "Schalten funktioniert nicht"** + +1. Test ob Verbindung funktioniert: `python3 test_tretakt.py` +2. Überprüfe Passwort in FritzBox +3. Überprüfe Identifier (muss exakt stimmen!) + +--- + +## ZUSAMMENFASSUNG + +✅ **IKEA TRETAKT (€8 pro Stück)** +✅ **FritzBox Zigbee Integration** +✅ **Sprachgesteuert über Pi Zero 2W** +✅ **Offline im heimischen LAN** +✅ **Super günstig und einfach** + +**Der perfekte Start in Smart Home!** 🏠 + +Viel Erfolg! 🚀