diff --git a/steckdose.md b/steckdose.md deleted file mode 100644 index 7b47b8d..0000000 --- a/steckdose.md +++ /dev/null @@ -1,1160 +0,0 @@ -# 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! 🚀