# 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! 🚀