mute funtion und warten bis nachricht gesendet

This commit is contained in:
duffyduck 2025-12-27 02:20:41 +01:00
parent 28d2bfe7d1
commit 095385a4a5
3 changed files with 146 additions and 23 deletions

View File

@ -167,10 +167,19 @@ Claude verwendet diese Befehle in eckigen Klammern:
- **Paralelle Konversation** - Erkunden UND quatschen gleichzeitig
- **Sprachausgabe** - Claude redet mit dir (TTS)
- **Spracheingabe** - Du redest mit Claude (STT)
- **Mute/Unmute** - Mikrofon per Tastendruck stummschalten
- **Hinderniserkennung** - Ultraschall & IMU
- **Touch-Display** - Notfall-Stopp & Status
- **Termux Support** - Läuft auch auf Android!
## Keyboard-Shortcuts (Bridge)
| Taste | Funktion |
|-------|----------|
| **M** | Mikrofon Mute/Unmute |
| **Q** | Bridge beenden |
| **Ctrl+C** | Bridge beenden |
---
## Sicherheit

View File

@ -229,6 +229,16 @@ python chat_audio_bridge.py -d
python chat_audio_bridge.py -c config.local.yaml
```
### 2.6 Keyboard-Shortcuts während der Bridge läuft
| Taste | Funktion |
|-------|----------|
| **M** | Mikrofon Mute/Unmute - schaltet STT stumm |
| **Q** | Bridge beenden |
| **Ctrl+C** | Bridge beenden |
**Tipp:** Nutze Mute wenn du nicht willst dass Hintergrundgeräusche oder Gespräche mit anderen aufgenommen werden. Das Mikrofon nimmt sonst alles auf!
---
## Teil 3: Hardware zusammenbauen

View File

@ -114,6 +114,14 @@ class ClaudesEyesAudioBridge:
self._stefan_buffer: list = []
self._stefan_buffer_lock = threading.Lock()
# Send-Lock: Verhindert dass TICKs während Senden reinkommen
self._sending = threading.Event()
self._sending.set() # Anfangs nicht am Senden (set = frei)
# Mute-Flag: Wenn True, ignoriert STT alle Eingaben
self._muted = False
self._mute_lock = threading.Lock()
def _load_config(self, config_path: str) -> dict:
"""Lädt die Konfiguration"""
path = Path(config_path)
@ -252,8 +260,13 @@ class ClaudesEyesAudioBridge:
t3.start()
threads.append(t3)
# Thread 4: Keyboard-Listener für Mute-Toggle
t4 = threading.Thread(target=self._keyboard_loop, name="Keyboard", daemon=True)
t4.start()
threads.append(t4)
console.print("[cyan]Bridge läuft![/cyan]")
console.print("[dim]Drücke Ctrl+C zum Beenden[/dim]\n")
console.print("[dim]Drücke 'M' für Mute/Unmute, Ctrl+C zum Beenden[/dim]\n")
# Sende Startsignal an Claude und warte auf [READY]
if not self._send_start_signal():
@ -435,8 +448,15 @@ Erst dann starten die automatischen TICKs mit Bildern!"""
# Stefan-Buffer holen (falls er was gesagt hat)
stefan_text = self._get_and_clear_stefan_buffer()
# Warte bis vorheriges Senden fertig ist
self._sending.wait()
# Nächsten TICK senden (mit oder ohne Bild)
with self._lock:
# Signalisiere dass wir senden
self._sending.clear()
try:
# Erst Bild hochladen wenn aktiviert
if upload_images:
# Bild holen und hochladen
@ -462,6 +482,9 @@ Erst dann starten die automatischen TICKs mit Bildern!"""
logger.debug(f"TICK #{self.stats.ticks_sent}" + (" mit Bild" if upload_images else "") + (f" + Stefan: {stefan_text[:30]}" if stefan_text else ""))
else:
raise Exception("TICK fehlgeschlagen")
finally:
# Senden fertig - wieder freigeben
self._sending.set()
except Exception as e:
logger.error(f"Heartbeat-Fehler: {e}")
@ -525,6 +548,7 @@ Erst dann starten die automatischen TICKs mit Bildern!"""
Wenn Claude tippt Buffer sammeln
Wenn Claude fertig Buffer wird mit nächstem TICK gesendet
Wenn gemutet Ignoriert alle Eingaben
So wird Claude nicht unterbrochen und bekommt alles gesammelt.
"""
@ -536,9 +560,18 @@ Erst dann starten die automatischen TICKs mit Bildern!"""
while self.running:
try:
# Wenn gemutet, kurz warten und überspringen
if self.is_muted():
time.sleep(0.5)
continue
# Warte auf Sprache (mit Timeout)
result = self.stt.listen_once(timeout=2)
# Nochmal prüfen nach dem Hören (falls zwischendurch gemutet wurde)
if self.is_muted():
continue
if result and result.text and len(result.text) > 2:
# In Buffer speichern (thread-safe)
with self._stefan_buffer_lock:
@ -554,6 +587,52 @@ Erst dann starten die automatischen TICKs mit Bildern!"""
logger.error(f"STT-Loop-Fehler: {e}")
self.stats.errors += 1
def _keyboard_loop(self):
"""
Hört auf Tastatureingaben für Mute-Toggle.
Tasten:
- M: Mute/Unmute Toggle
- Q: Beenden (alternativ zu Ctrl+C)
"""
import sys
import tty
import termios
logger.info("Keyboard-Loop gestartet (M=Mute, Q=Quit)")
# Speichere ursprüngliche Terminal-Settings
old_settings = None
try:
old_settings = termios.tcgetattr(sys.stdin)
except:
logger.warning("Konnte Terminal-Settings nicht lesen (kein TTY?)")
return
try:
# Terminal in raw mode setzen (einzelne Tasten ohne Enter)
tty.setraw(sys.stdin.fileno())
while self.running:
# Lese einzelnes Zeichen (blockierend, aber mit select für Timeout)
import select
if select.select([sys.stdin], [], [], 0.5)[0]:
char = sys.stdin.read(1)
if char.lower() == 'm':
self.toggle_mute()
elif char.lower() == 'q' or char == '\x03': # q oder Ctrl+C
console.print("\n[yellow]Beende Bridge...[/yellow]")
self.running = False
break
except Exception as e:
logger.debug(f"Keyboard-Loop Fehler: {e}")
finally:
# Terminal-Settings wiederherstellen
if old_settings:
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings)
def _get_and_clear_stefan_buffer(self) -> Optional[str]:
"""
Holt den Stefan-Buffer und leert ihn.
@ -571,6 +650,31 @@ Erst dann starten die automatischen TICKs mit Bildern!"""
return text
def toggle_mute(self) -> bool:
"""
Schaltet Mute um.
Returns:
True wenn jetzt gemutet, False wenn ungemutet
"""
with self._mute_lock:
self._muted = not self._muted
status = "MUTED" if self._muted else "UNMUTED"
console.print(f"\n[bold {'red' if self._muted else 'green'}]🎤 Mikrofon {status}[/bold {'red' if self._muted else 'green'}]")
return self._muted
def set_mute(self, muted: bool):
"""Setzt Mute-Status direkt"""
with self._mute_lock:
self._muted = muted
status = "MUTED" if self._muted else "UNMUTED"
console.print(f"\n[bold {'red' if self._muted else 'green'}]🎤 Mikrofon {status}[/bold {'red' if self._muted else 'green'}]")
def is_muted(self) -> bool:
"""Prüft ob gemutet"""
with self._mute_lock:
return self._muted
def _clean_for_speech(self, text: str) -> str:
"""
Entfernt Befehle und technische Teile aus dem Text.