mute funtion und warten bis nachricht gesendet
This commit is contained in:
parent
28d2bfe7d1
commit
095385a4a5
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Reference in New Issue