10 sekunden warten nach neuer chat, instruktionen verbessert.
This commit is contained in:
parent
90791b9ff6
commit
38e653361e
|
|
@ -166,8 +166,9 @@ Claude verwendet diese Befehle in eckigen Klammern:
|
||||||
- **Echte Autonomie** - Claude entscheidet selbst was ihn interessiert
|
- **Echte Autonomie** - Claude entscheidet selbst was ihn interessiert
|
||||||
- **Paralelle Konversation** - Erkunden UND quatschen gleichzeitig
|
- **Paralelle Konversation** - Erkunden UND quatschen gleichzeitig
|
||||||
- **Sprachausgabe** - Claude redet mit dir (TTS)
|
- **Sprachausgabe** - Claude redet mit dir (TTS)
|
||||||
- **Spracheingabe** - Du redest mit Claude (STT)
|
- **Spracheingabe** - Du redest mit Claude (STT, 5s Stille = fertig)
|
||||||
- **Mute/Unmute** - Mikrofon per Tastendruck stummschalten
|
- **Mute/Unmute** - Mikrofon per Tastendruck stummschalten
|
||||||
|
- **Smart Recording** - Heartbeat pausiert automatisch während du sprichst
|
||||||
- **Hinderniserkennung** - Ultraschall & IMU
|
- **Hinderniserkennung** - Ultraschall & IMU
|
||||||
- **Touch-Display** - Notfall-Stopp & Status
|
- **Touch-Display** - Notfall-Stopp & Status
|
||||||
- **Termux Support** - Läuft auch auf Android!
|
- **Termux Support** - Läuft auch auf Android!
|
||||||
|
|
|
||||||
|
|
@ -245,6 +245,31 @@ python chat_audio_bridge.py -c config.local.yaml
|
||||||
- Mit **N** startest du einen neuen Chat und die Instruktionen werden erneut gesendet
|
- Mit **N** startest du einen neuen Chat und die Instruktionen werden erneut gesendet
|
||||||
- Bilder werden nur hochgeladen wenn sie sich geändert haben (spart Limit!)
|
- Bilder werden nur hochgeladen wenn sie sich geändert haben (spart Limit!)
|
||||||
|
|
||||||
|
### 2.7 Spracheingabe (STT) - Wie es funktioniert
|
||||||
|
|
||||||
|
Die Spracheingabe sammelt deine Worte intelligent:
|
||||||
|
|
||||||
|
1. **Aufnahme startet** sobald du sprichst
|
||||||
|
2. **Heartbeat pausiert** automatisch während du redest
|
||||||
|
3. **Mehrere Sätze** werden gesammelt und zusammengefügt
|
||||||
|
4. **Nach 5 Sekunden Stille** gilt die Eingabe als komplett
|
||||||
|
5. **Mit dem nächsten TICK** wird alles an Claude gesendet
|
||||||
|
|
||||||
|
**Beispiel-Ablauf:**
|
||||||
|
```
|
||||||
|
Du: "Hallo Claude..." → 🎤 Stefan spricht...
|
||||||
|
Du: "...kannst du mal..." → → Hallo Claude
|
||||||
|
Du: "...nach links schauen?" → → kannst du mal
|
||||||
|
→ → nach links schauen?
|
||||||
|
[5 Sekunden Stille] → ✓ Stefan (komplett): Hallo Claude kannst du mal nach links schauen?
|
||||||
|
[TICK wird gesendet] → Claude bekommt die komplette Nachricht
|
||||||
|
```
|
||||||
|
|
||||||
|
**Warum 5 Sekunden?**
|
||||||
|
- Gibt dir Zeit zum Nachdenken zwischen Sätzen
|
||||||
|
- Verhindert dass halbe Sätze gesendet werden
|
||||||
|
- Claude bekommt immer den kompletten Gedanken
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Teil 3: Hardware zusammenbauen
|
## Teil 3: Hardware zusammenbauen
|
||||||
|
|
|
||||||
|
|
@ -120,11 +120,19 @@ class ClaudesEyesAudioBridge:
|
||||||
self._sending = threading.Event()
|
self._sending = threading.Event()
|
||||||
self._sending.set() # Anfangs nicht am Senden (set = frei)
|
self._sending.set() # Anfangs nicht am Senden (set = frei)
|
||||||
|
|
||||||
|
# Recording-Flag: Wenn gesetzt, wird gerade aufgenommen
|
||||||
|
# Heartbeat pausiert während Aufnahme aktiv ist
|
||||||
|
self._recording = threading.Event()
|
||||||
|
self._recording.clear() # Anfangs nicht am Aufnehmen
|
||||||
|
|
||||||
# Mute-Flag: Wenn True, ignoriert STT alle Eingaben
|
# Mute-Flag: Wenn True, ignoriert STT alle Eingaben
|
||||||
# Startet gemutet um ungewollte Aufnahmen zu vermeiden
|
# Startet gemutet um ungewollte Aufnahmen zu vermeiden
|
||||||
self._muted = True
|
self._muted = True
|
||||||
self._mute_lock = threading.Lock()
|
self._mute_lock = threading.Lock()
|
||||||
|
|
||||||
|
# Silence-Timeout: Wie lange Stille bevor Aufnahme als fertig gilt
|
||||||
|
self._silence_timeout = 5.0 # Sekunden
|
||||||
|
|
||||||
def _load_config(self, config_path: str) -> dict:
|
def _load_config(self, config_path: str) -> dict:
|
||||||
"""Lädt die Konfiguration"""
|
"""Lädt die Konfiguration"""
|
||||||
path = Path(config_path)
|
path = Path(config_path)
|
||||||
|
|
@ -428,7 +436,8 @@ Die Befehle werden aus der TTS-Ausgabe rausgefiltert.
|
||||||
- Du musst nicht bei jedem TICK fahren - manchmal reicht auch schauen und kommentieren
|
- Du musst nicht bei jedem TICK fahren - manchmal reicht auch schauen und kommentieren
|
||||||
|
|
||||||
## WICHTIG: Bestätige mit [READY]
|
## WICHTIG: Bestätige mit [READY]
|
||||||
Wenn du diese Instruktionen verstanden hast, antworte mit **[READY]** am Ende deiner Nachricht.
|
Wenn du diese Instruktionen verstanden hast, antworte mit dem Tag `[READY]` (exakt so, in eckigen Klammern!) am Ende deiner Nachricht.
|
||||||
|
**Das Format muss EXAKT `[READY]` sein** - nicht fett, nicht anders formatiert, sondern genau so: [READY]
|
||||||
Erst dann starten die automatischen TICKs mit Bildern!"""
|
Erst dann starten die automatischen TICKs mit Bildern!"""
|
||||||
|
|
||||||
console.print("[cyan]→ Sende Instruktionen an Claude...[/cyan]")
|
console.print("[cyan]→ Sende Instruktionen an Claude...[/cyan]")
|
||||||
|
|
@ -500,6 +509,16 @@ Erst dann starten die automatischen TICKs mit Bildern!"""
|
||||||
if not self.running:
|
if not self.running:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
# Warte bis Recording fertig ist (5s Stille)
|
||||||
|
if self._recording.is_set():
|
||||||
|
logger.debug("Stefan spricht noch, warte auf Stille...")
|
||||||
|
while self.running and self._recording.is_set():
|
||||||
|
time.sleep(0.5)
|
||||||
|
logger.debug("Stefan fertig, fahre fort")
|
||||||
|
|
||||||
|
if not self.running:
|
||||||
|
break
|
||||||
|
|
||||||
# Zufällige Pause nach Claudes Antwort (natürlicheres Tempo)
|
# Zufällige Pause nach Claudes Antwort (natürlicheres Tempo)
|
||||||
pause = random.uniform(min_pause, max_pause)
|
pause = random.uniform(min_pause, max_pause)
|
||||||
time.sleep(pause)
|
time.sleep(pause)
|
||||||
|
|
@ -511,7 +530,7 @@ Erst dann starten die automatischen TICKs mit Bildern!"""
|
||||||
stefan_text = self._get_and_clear_stefan_buffer()
|
stefan_text = self._get_and_clear_stefan_buffer()
|
||||||
|
|
||||||
# Warte bis vorheriges Senden fertig ist
|
# Warte bis vorheriges Senden fertig ist
|
||||||
self._sending.wait()
|
self._sending.wait(timeout=30) # Max 30s warten
|
||||||
|
|
||||||
# Nächsten TICK senden (mit oder ohne Bild)
|
# Nächsten TICK senden (mit oder ohne Bild)
|
||||||
with self._lock:
|
with self._lock:
|
||||||
|
|
@ -622,40 +641,67 @@ Erst dann starten die automatischen TICKs mit Bildern!"""
|
||||||
"""
|
"""
|
||||||
Hört auf Stefan und sammelt seine Worte im Buffer.
|
Hört auf Stefan und sammelt seine Worte im Buffer.
|
||||||
|
|
||||||
Wenn Claude tippt → Buffer sammeln
|
Ablauf:
|
||||||
Wenn Claude fertig → Buffer wird mit nächstem TICK gesendet
|
1. Warte auf erste Spracheingabe
|
||||||
Wenn gemutet → Ignoriert alle Eingaben
|
2. Signalisiere Recording aktiv → Heartbeat pausiert
|
||||||
|
3. Sammle weitere Eingaben bis 5 Sekunden Stille
|
||||||
|
4. Recording fertig → Buffer wird mit nächstem TICK gesendet
|
||||||
|
|
||||||
So wird Claude nicht unterbrochen und bekommt alles gesammelt.
|
Wenn gemutet → Ignoriert alle Eingaben
|
||||||
"""
|
"""
|
||||||
if not self.stt:
|
if not self.stt:
|
||||||
logger.warning("STT nicht verfügbar")
|
logger.warning("STT nicht verfügbar")
|
||||||
return
|
return
|
||||||
|
|
||||||
logger.info("STT-Loop gestartet (mit Buffer)")
|
logger.info(f"STT-Loop gestartet (5s Stille = fertig)")
|
||||||
|
|
||||||
|
# Temporärer Buffer für aktuelle Aufnahme-Session
|
||||||
|
current_session_texts = []
|
||||||
|
last_speech_time = 0
|
||||||
|
|
||||||
while self.running:
|
while self.running:
|
||||||
try:
|
try:
|
||||||
# Wenn gemutet, kurz warten und überspringen
|
# Wenn gemutet, kurz warten und überspringen
|
||||||
if self.is_muted():
|
if self.is_muted():
|
||||||
|
# Falls wir mitten in einer Aufnahme waren, diese beenden
|
||||||
|
if self._recording.is_set():
|
||||||
|
self._finalize_recording(current_session_texts)
|
||||||
|
current_session_texts = []
|
||||||
time.sleep(0.5)
|
time.sleep(0.5)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Warte auf Sprache (mit Timeout)
|
# Warte auf Sprache (kurzer Timeout für schnelle Reaktion)
|
||||||
result = self.stt.listen_once(timeout=2)
|
result = self.stt.listen_once(timeout=1)
|
||||||
|
|
||||||
# Nochmal prüfen nach dem Hören (falls zwischendurch gemutet wurde)
|
# Nochmal prüfen nach dem Hören (falls zwischendurch gemutet wurde)
|
||||||
if self.is_muted():
|
if self.is_muted():
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if result and result.text and len(result.text) > 2:
|
if result and result.text and len(result.text) > 2:
|
||||||
# In Buffer speichern (thread-safe)
|
# Sprache erkannt!
|
||||||
with self._stefan_buffer_lock:
|
current_session_texts.append(result.text)
|
||||||
self._stefan_buffer.append(result.text)
|
last_speech_time = time.time()
|
||||||
self.stats.stefan_inputs += 1
|
self.stats.stefan_inputs += 1
|
||||||
|
|
||||||
console.print(f"\n[bold green]Stefan (gebuffert):[/bold green] {result.text}")
|
# Signalisiere dass Recording aktiv ist
|
||||||
logger.debug(f"Stefan-Buffer: {len(self._stefan_buffer)} Einträge")
|
if not self._recording.is_set():
|
||||||
|
self._recording.set()
|
||||||
|
console.print(f"\n[bold green]🎤 Stefan spricht...[/bold green]")
|
||||||
|
logger.debug("Recording gestartet")
|
||||||
|
|
||||||
|
console.print(f"[green] → {result.text}[/green]")
|
||||||
|
logger.debug(f"Stefan-Session: {len(current_session_texts)} Teile")
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Keine Sprache erkannt - prüfe auf Stille-Timeout
|
||||||
|
if self._recording.is_set() and last_speech_time > 0:
|
||||||
|
silence_duration = time.time() - last_speech_time
|
||||||
|
|
||||||
|
if silence_duration >= self._silence_timeout:
|
||||||
|
# 5 Sekunden Stille - Aufnahme beenden
|
||||||
|
self._finalize_recording(current_session_texts)
|
||||||
|
current_session_texts = []
|
||||||
|
last_speech_time = 0
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Timeout ist normal
|
# Timeout ist normal
|
||||||
|
|
@ -663,6 +709,30 @@ Erst dann starten die automatischen TICKs mit Bildern!"""
|
||||||
logger.error(f"STT-Loop-Fehler: {e}")
|
logger.error(f"STT-Loop-Fehler: {e}")
|
||||||
self.stats.errors += 1
|
self.stats.errors += 1
|
||||||
|
|
||||||
|
def _finalize_recording(self, texts: list):
|
||||||
|
"""
|
||||||
|
Beendet eine Aufnahme-Session und speichert im Buffer.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
texts: Liste der erkannten Texte in dieser Session
|
||||||
|
"""
|
||||||
|
if not texts:
|
||||||
|
self._recording.clear()
|
||||||
|
return
|
||||||
|
|
||||||
|
# Alle Texte zusammenfügen
|
||||||
|
full_text = " ".join(texts)
|
||||||
|
|
||||||
|
# In Haupt-Buffer speichern
|
||||||
|
with self._stefan_buffer_lock:
|
||||||
|
self._stefan_buffer.append(full_text)
|
||||||
|
|
||||||
|
console.print(f"\n[bold green]✓ Stefan (komplett):[/bold green] {full_text}")
|
||||||
|
logger.info(f"Recording beendet: {len(texts)} Teile → {len(full_text)} Zeichen")
|
||||||
|
|
||||||
|
# Recording-Flag zurücksetzen → Heartbeat kann weitermachen
|
||||||
|
self._recording.clear()
|
||||||
|
|
||||||
def _keyboard_loop(self):
|
def _keyboard_loop(self):
|
||||||
"""
|
"""
|
||||||
Hört auf Tastatureingaben für Mute-Toggle und andere Befehle.
|
Hört auf Tastatureingaben für Mute-Toggle und andere Befehle.
|
||||||
|
|
@ -730,6 +800,10 @@ Erst dann starten die automatischen TICKs mit Bildern!"""
|
||||||
# Neue URL in config.yaml speichern
|
# Neue URL in config.yaml speichern
|
||||||
self._save_chat_url_to_config(new_url)
|
self._save_chat_url_to_config(new_url)
|
||||||
|
|
||||||
|
# Warte bis der neue Chat vollständig geladen ist
|
||||||
|
console.print("[dim]Warte 10s bis Chat geladen...[/dim]")
|
||||||
|
time.sleep(10)
|
||||||
|
|
||||||
console.print("[cyan]Sende Instruktionen mit Referenz zum alten Chat...[/cyan]")
|
console.print("[cyan]Sende Instruktionen mit Referenz zum alten Chat...[/cyan]")
|
||||||
|
|
||||||
# Instruktionen erneut senden (mit Referenz zum alten Chat)
|
# Instruktionen erneut senden (mit Referenz zum alten Chat)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue