mute funtion und warten bis nachricht gesendet

This commit is contained in:
2025-12-27 02:20:41 +01:00
parent 28d2bfe7d1
commit 095385a4a5
3 changed files with 146 additions and 23 deletions
+127 -23
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,33 +448,43 @@ 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:
# Erst Bild hochladen wenn aktiviert
if upload_images:
# Bild holen und hochladen
if not self.chat.fetch_image_from_esp32():
logger.warning("Konnte kein Bild vom ESP32 holen")
elif not self.chat.upload_image_to_chat():
logger.warning("Konnte Bild nicht hochladen")
# Signalisiere dass wir senden
self._sending.clear()
# Nachricht zusammenbauen
if stefan_text:
# Stefan hat was gesagt → Mit TICK senden
tick_message = f"[TICK]\n\nStefan sagt: {stefan_text}"
console.print(f"[cyan]→ TICK mit Stefan-Buffer: \"{stefan_text[:50]}...\"[/cyan]" if len(stefan_text) > 50 else f"[cyan]→ TICK mit Stefan-Buffer: \"{stefan_text}\"[/cyan]")
else:
# Nur TICK
tick_message = "[TICK]"
try:
# Erst Bild hochladen wenn aktiviert
if upload_images:
# Bild holen und hochladen
if not self.chat.fetch_image_from_esp32():
logger.warning("Konnte kein Bild vom ESP32 holen")
elif not self.chat.upload_image_to_chat():
logger.warning("Konnte Bild nicht hochladen")
success = self.chat.send_message(tick_message)
# Nachricht zusammenbauen
if stefan_text:
# Stefan hat was gesagt → Mit TICK senden
tick_message = f"[TICK]\n\nStefan sagt: {stefan_text}"
console.print(f"[cyan]→ TICK mit Stefan-Buffer: \"{stefan_text[:50]}...\"[/cyan]" if len(stefan_text) > 50 else f"[cyan]→ TICK mit Stefan-Buffer: \"{stefan_text}\"[/cyan]")
else:
# Nur TICK
tick_message = "[TICK]"
if success:
self.stats.ticks_sent += 1
self.stats.consecutive_errors = 0 # Reset
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")
success = self.chat.send_message(tick_message)
if success:
self.stats.ticks_sent += 1
self.stats.consecutive_errors = 0 # Reset
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.