first commit

This commit is contained in:
Dieter Schlüter 2026-06-16 02:21:26 +02:00
commit 670c6082a6
5 changed files with 886 additions and 2 deletions

3
.gitignore vendored
View file

@ -38,3 +38,6 @@ env/
# Logs
*.log
# Ideen und Notizen
Ideen/

View file

@ -176,6 +176,8 @@ python3 assistant.py [OPTIONEN]
|---|---|---|
| `--backend NAME` | `llama` | Backend: `llama` \| `ollama` \| `openai` |
| `--model NAME` | auto | Modellname (bei llama.cpp: automatisch vom Server) |
| `--api-url URL` | — | Überschreibt die Backend-URL (beliebiger OpenAI-kompatibler Endpunkt) |
| `--api-key KEY` | — | API-Key (überschreibt `OPENAI_API_KEY` / Backend-Default) |
| `--system TEXT` | (eingebaut) | System-Prompt für den Assistenten |
| `--history N` | `10` | Maximale Anzahl gespeicherter Gesprächsrunden |
@ -265,6 +267,10 @@ Vollständige Sink-Namen aus `pactl list sinks short` können direkt verwendet w
## 9. LLM-Backend konfigurieren
Der Assistent spricht mit jedem LLM, das die **OpenAI Chat-Completions API**
implementiert. Mit `--api-url` und `--api-key` kann der Endpunkt frei
überschrieben werden — das `--backend`-Flag wählt nur den Verbindungs-Default.
### llama.cpp (Standard)
```bash
@ -273,8 +279,13 @@ python3 assistant.py --backend llama
```
Der Modellname wird automatisch vom Server abgefragt.
Anderen Port oder Host angeben:
### Ollama
```bash
python3 assistant.py --backend llama --api-url http://192.168.1.10:8001/v1
```
### Ollama (lokal)
```bash
# Ollama muss laufen: ollama serve
@ -282,6 +293,14 @@ python3 assistant.py --backend ollama --model llama3.2
python3 assistant.py --backend ollama --model mistral
```
### Ollama (remote / anderer Rechner)
```bash
python3 assistant.py --backend ollama \
--api-url http://mein-server:11434/v1 \
--model llama3.2
```
### OpenAI
```bash
@ -290,6 +309,66 @@ python3 assistant.py --backend openai --model gpt-4o-mini
python3 assistant.py --backend openai --model gpt-4o
```
### OpenRouter (viele Modelle über eine API)
[OpenRouter](https://openrouter.ai) bietet Zugang zu hunderten Modellen
(Mistral, Llama, Gemma, Claude, GPT-4 u. v. m.) über eine einheitliche API.
```bash
export OPENROUTER_API_KEY=sk-or-...
python3 assistant.py \
--backend openai \
--api-url https://openrouter.ai/api/v1 \
--model mistralai/mistral-7b-instruct
```
Andere beliebte OpenRouter-Modelle:
- `meta-llama/llama-3.1-8b-instruct:free` (kostenlos)
- `google/gemma-3-27b-it`
- `anthropic/claude-3.5-haiku`
Den API-Key unter `https://openrouter.ai/keys` erstellen.
### Groq (sehr schnelle Inferenz)
```bash
export GROQ_API_KEY=gsk_...
python3 assistant.py \
--backend openai \
--api-url https://api.groq.com/openai/v1 \
--model llama-3.1-70b-versatile
```
### LM Studio
```bash
# LM Studio muss mit aktiviertem lokalen Server laufen (Port 1234)
python3 assistant.py --backend llama --api-url http://localhost:1234/v1
```
### Together AI
```bash
export TOGETHER_API_KEY=...
python3 assistant.py \
--backend openai \
--api-url https://api.together.xyz/v1 \
--model meta-llama/Llama-3-8b-chat-hf
```
### Allgemeines Prinzip
Jeder Provider, der `/v1/chat/completions` mit `stream=true` unterstützt,
funktioniert mit:
```bash
python3 assistant.py \
--backend openai \
--api-url https://<provider>/v1 \
--model <modellname> \
--api-key <api-key>
```
---
## 10. Sprache und Stimme

View file

@ -0,0 +1,772 @@
# HOWTO: ReSpeaker XVF3800 mit XIAO ESP32S3 unter Ubuntu 24.04
Vollständige Schritt-für-Schritt-Anleitung für den Einsatz des
**Seeed Studio ReSpeaker XVF3800 4-Mic Array with XIAO ESP32S3**
als Mikrofon und Lautsprecher unter Ubuntu 24.04 mit PipeWire —
optimiert für Sprachassistenten auf Basis von Whisper und Piper.
---
## Inhaltsverzeichnis
1. [Hardware-Überblick](#1-hardware-überblick)
2. [Voraussetzungen](#2-voraussetzungen)
3. [Bekannter Bug: USB-Codec-Reset nach Neustart](#3-bekannter-bug-usb-codec-reset-nach-neustart)
4. [Systemabhängigkeiten installieren](#4-systemabhängigkeiten-installieren)
5. [USB-Codec-Reset-Dienst einrichten](#5-usb-codec-reset-dienst-einrichten)
6. [WirePlumber konfigurieren](#6-wireplumber-konfigurieren)
7. [ALSA konfigurieren](#7-alsa-konfigurieren)
8. [Autostart-Skript für die Desktop-Sitzung](#8-autostart-skript-für-die-desktop-sitzung)
9. [Python-Abhängigkeiten installieren](#9-python-abhängigkeiten-installieren)
10. [Verifikation: Alles testen](#10-verifikation-alles-testen)
11. [Verwendung mit dem Voice-Assistenten](#11-verwendung-mit-dem-voice-assistenten)
12. [Fehlerbehebung](#12-fehlerbehebung)
13. [Zusammenfassung aller erstellten Dateien](#13-zusammenfassung-aller-erstellten-dateien)
---
## 1. Hardware-Überblick
Das **ReSpeaker XVF3800 4-Mic Array** ist ein USB-Mikrofon-Array von Seeed Studio
mit integriertem **XIAO ESP32S3** Mikrocontroller. Es bietet:
| Eigenschaft | Wert |
|---|---|
| Mikrofone | 4× MEMS, Kreisanordnung |
| Nativrate | 48 000 Hz, Stereo (nach Beamforming) |
| USB-Klasse | USB Audio Class (kein Treiber nötig) |
| Hardware-AEC | Ja — Acoustic Echo Cancellation im DSP |
| Beamforming | Ja — richtet das Richtmikrofon auf den Sprecher aus |
| Ausgabe | 3,5-mm-Klinke (Stereo) |
| Vendor-ID | `2886` (Seeed Studio) |
**Wichtig für Whisper:** Der eingebaute DSP gibt nach Beamforming und
Rauschunterdrückung nur zwei Kanäle aus. sounddevice meldet für dieses
Gerät `max_input_channels = 0` beim Hardware-Device, obwohl es funktioniert.
Das ist ein Anzeigebug des Treibers, kein Fehler.
---
## 2. Voraussetzungen
- Ubuntu 24.04 LTS (frische Installation oder bestehendes System)
- PipeWire als Audio-Server (ab Ubuntu 22.04 Standard)
- Python 3.12+
- Internetzugang (für Downloads beim ersten Start)
- NVIDIA GPU mit CUDA (empfohlen für Whisper; CPU funktioniert ebenfalls, aber langsam)
PipeWire-Status prüfen:
```bash
systemctl --user status pipewire pipewire-pulse
# Beide müssen "active (running)" zeigen
```
Falls noch PulseAudio läuft:
```bash
systemctl --user disable --now pulseaudio.service pulseaudio.socket
systemctl --user enable --now pipewire pipewire-pulse
```
---
## 3. Bekannter Bug: USB-Codec-Reset nach Neustart
**Das Problem (XVF3800 Issue #20):**
Nach einem Software-Neustart (nicht nach Kaltstart) befindet sich der
TLV320-Audio-Codec auf dem XVF3800 in einem korrumpierten Zustand.
Ubuntu erkennt das Gerät zwar als USB-Gerät, aber PipeWire registriert
es nicht als Audio-Quelle. `arecord -l` zeigt die Karte, aber
`pactl list sources` kennt das Gerät nicht.
**Die Ursache:**
Der XIAO ESP32S3 initialisiert den Codec beim USB-Attach. Nach einem
`reboot`-Befehl bleibt die USB-Verbindung physisch bestehen — das
Attach-Event findet nicht statt, der Codec bleibt im alten Zustand.
**Der Fix:**
Das USB-Gerät kurz de-autorisieren und re-autorisieren — **ohne das
USB-Kabel abzuziehen**. Linux stellt über das Sysfs-Interface
(`/sys/bus/usb/devices/.../authorized`) einen Software-Disconnect/-Connect
bereit: Schreibe `0` → Gerät wird vom Kernel getrennt; schreibe `1`
Gerät wird neu erkannt. Dadurch löst Linux ein neues USB-Attach-Event aus,
der ESP32S3 reinitialisiert den Codec, und PipeWire erkennt das Gerät
korrekt — genau so wie nach einem physischen Einstecken des Kabels.
Dieser Reset wird automatisch durch den in Abschnitt 5 beschriebenen
systemd-Dienst durchgeführt.
---
## 4. Systemabhängigkeiten installieren
```bash
# PipeWire-Werkzeuge (meist schon vorhanden)
sudo apt install -y pipewire pipewire-pulse wireplumber \
libspa-0.2-bluetooth \
pavucontrol pulseaudio-utils
# Python-Entwicklungsumgebung
sudo apt install -y python3-pip python3-venv python3-dev
# ALSA-Werkzeuge (Diagnose)
sudo apt install -y alsa-utils
# Desktop-Benachrichtigungen (für das Autostart-Skript)
sudo apt install -y libnotify-bin
```
---
## 5. USB-Codec-Reset-Dienst einrichten
### 5.1 Reset-Skript erstellen
```bash
sudo nano /usr/local/bin/respeaker-reset
```
Inhalt:
```bash
#!/bin/bash
# Resets ReSpeaker XVF3800 TLV320 audio codec via USB authorized toggle.
# Fixes XVF3800 Issue #20: after soft reboot the codec state is corrupted.
USB_PATH=$(grep -rl "2886" /sys/bus/usb/devices/*/idVendor 2>/dev/null \
| head -1 | sed 's|/idVendor||; s|.*/||')
if [ -z "$USB_PATH" ]; then
echo "ReSpeaker not found at boot, skipping." >&2
exit 0
fi
echo "Resetting ReSpeaker at /sys/bus/usb/devices/$USB_PATH ..."
echo 0 > /sys/bus/usb/devices/"$USB_PATH"/authorized
sleep 2
echo 1 > /sys/bus/usb/devices/"$USB_PATH"/authorized
echo "Reset complete."
```
Ausführbar machen:
```bash
sudo chmod +x /usr/local/bin/respeaker-reset
```
**Wie das Skript funktioniert:**
1. Es sucht in `/sys/bus/usb/devices/` nach einem Gerät mit Vendor-ID `2886` (Seeed Studio).
2. Es schreibt `0` in die `authorized`-Datei des USB-Geräts → USB-Disconnect.
3. Nach 2 Sekunden schreibt es `1` → USB-Connect.
4. Der ESP32S3 läuft neu an und initialisiert den Codec korrekt.
### 5.2 systemd-Dienst erstellen
```bash
sudo nano /etc/systemd/system/respeaker-reset.service
```
Inhalt:
```ini
[Unit]
Description=ReSpeaker XVF3800 USB codec reset after boot
After=sound.target systemd-udevd.service
DefaultDependencies=no
[Service]
Type=oneshot
ExecStart=/usr/local/bin/respeaker-reset
RemainAfterExit=yes
[Install]
WantedBy=sound.target
```
Dienst aktivieren und starten:
```bash
sudo systemctl daemon-reload
sudo systemctl enable respeaker-reset.service
sudo systemctl start respeaker-reset.service
# Status prüfen
sudo systemctl status respeaker-reset.service
```
**Hinweis:** Der Dienst läuft mit Root-Rechten, weil das Schreiben in
`/sys/bus/usb/devices/*/authorized` Root-Rechte erfordert.
---
## 6. WirePlumber konfigurieren
WirePlumber ist der Session-Manager von PipeWire und steuert, wie
Geräte konfiguriert werden. Die Konfiguration liegt in
`~/.config/wireplumber/main.lua.d/`.
```bash
mkdir -p ~/.config/wireplumber/main.lua.d/
```
### 6.1 ALSA-Monitor-Eigenschaften (Bit-Tiefe und Sample-Rate)
```bash
nano ~/.config/wireplumber/main.lua.d/51-alsa-custom.lua
```
Inhalt:
```lua
alsa_monitor.properties = {
["audio.format"] = "S32LE",
["audio.rate"] = 48000,
["audio.channels"] = 2,
["audio.position"] = "FL,FR"
}
```
**Warum S32LE?** Das XVF3800 überträgt intern mit 32-Bit-Präzision.
S32LE verhindert unnötige Konvertierungen und damit leichte
Qualitätsverluste.
**Warum 48 000 Hz?** Das ist die native Samplerate des XVF3800.
Whisper benötigt 16 000 Hz; die Konvertierung übernimmt das Python-Skript
per `scipy.signal.resample_poly`.
### 6.2 Aufnahme-Lautstärke auf 150 % setzen
```bash
nano ~/.config/wireplumber/main.lua.d/52-respeaker-volume.lua
```
Inhalt:
```lua
-- ReSpeaker XVF3800: set capture volume to 150% on device connect.
-- The XVF3800 DSP (noise suppression + beamforming) outputs at a low level;
-- this software boost brings speech to ~-23 dBFS, which is optimal for Whisper.
table.insert(alsa_monitor.rules, {
matches = {
{
{ "node.name", "matches", "alsa_input.usb-Seeed_Studio_reSpeaker_XVF3800*" },
},
},
apply_properties = {
["node.volume"] = 1.5,
},
})
```
**Warum 150 %?** Der DSP des XVF3800 gibt nach Rauschunterdrückung und
Beamforming ein Signal mit relativ niedrigem Pegel aus. Bei 100 % landet
Sprache bei etwa 35 dBFS, was für Whisper zu leise ist (schlechtere
Transkription). 150 % bringt den Pegel auf ~23 dBFS, den Whisper als
optimal beschreibt.
WirePlumber neu starten, damit die Regeln aktiv werden:
```bash
systemctl --user restart wireplumber
```
Lautstärke sofort (ohne Neustart) setzen:
```bash
pactl set-source-volume \
alsa_input.usb-Seeed_Studio_reSpeaker_XVF3800_4-Mic_Array_114993700261100055-00.analog-stereo \
150%
```
---
## 7. ALSA konfigurieren
ALSA wird durch PipeWire unterstützt. Die `.asoundrc` stellt sicher,
dass ALSA-Anwendungen durch PipeWire geleitet werden und keine
„device busy"-Konflikte entstehen.
```bash
nano ~/.asoundrc
```
Inhalt:
```
# ALSA routes through PipeWire — avoids "device busy" conflicts.
# PipeWire default sink/source are set via:
# pactl set-default-sink <sink-name>
# pactl set-default-source <source-name>
pcm.!default {
type pipewire
playback_node "-1"
capture_node "-1"
}
ctl.!default {
type pipewire
}
# Named convenience aliases (use with: arecord -D respeaker / aplay -D motu_m2)
pcm.respeaker {
type pulse
device "alsa_input.usb-Seeed_Studio_reSpeaker_XVF3800_4-Mic_Array_114993700261100055-00.analog-stereo"
}
```
**Hinweis:** Den langen Gerätenamen immer aus der Ausgabe von
`pactl list sources short` kopieren — er enthält eine Seriennummer
(`114993700261100055`) die sich von Gerät zu Gerät unterscheiden kann.
Eigene Seriennummer ermitteln:
```bash
pactl list sources short | grep -i respeaker
```
---
## 8. Autostart-Skript für die Desktop-Sitzung
Nach dem Login prüft dieses Skript, ob das Gerät korrekt erkannt wurde,
und sendet eine Desktop-Benachrichtigung.
### 8.1 Skript erstellen
```bash
mkdir -p ~/.local/bin
nano ~/.local/bin/respeaker-init.sh
```
Inhalt:
```bash
#!/bin/bash
# ReSpeaker XVF3800 login health check.
# The systemd service respeaker-reset.service handles the USB codec reset at boot.
# This script only verifies the device is ready and notifies the user.
DEVICE="alsa_input.usb-Seeed_Studio_reSpeaker_XVF3800_4-Mic_Array_114993700261100055-00.analog-stereo"
# Wait for PipeWire + respeaker-reset.service to complete (max 30s)
sleep 5
for i in $(seq 25); do
pactl list sources short 2>/dev/null | grep -q "reSpeaker" && break
sleep 1
done
if ! pactl list sources short 2>/dev/null | grep -q "reSpeaker"; then
notify-send "ReSpeaker XVF3800" "Gerät nicht gefunden. USB-Kabel prüfen." \
--icon=audio-input-microphone --urgency=critical
exit 1
fi
# Fallback: ensure volume is 150% (WirePlumber rule should handle this)
pactl set-source-volume "$DEVICE" 150% 2>/dev/null
notify-send "ReSpeaker XVF3800" "Mikrofon bereit (150% Lautstärke, 48 kHz)." \
--icon=audio-input-microphone
```
```bash
chmod +x ~/.local/bin/respeaker-init.sh
```
**Wichtig:** Die Seriennummer im `DEVICE`-Wert anpassen (→ Abschnitt 7).
### 8.2 Autostart-Eintrag erstellen
```bash
mkdir -p ~/.config/autostart
nano ~/.config/autostart/respeaker-init.desktop
```
Inhalt:
```ini
[Desktop Entry]
Type=Application
Name=ReSpeaker XVF3800 Init
Comment=Reset ReSpeaker audio codec state after boot (XVF3800 USB Issue #20)
Exec=/home/BENUTZERNAME/.local/bin/respeaker-init.sh
Hidden=false
NoDisplay=true
X-GNOME-Autostart-enabled=true
```
`BENUTZERNAME` durch den eigenen Benutzernamen ersetzen, oder absoluten Pfad
nutzen:
```bash
# Pfad automatisch setzen
sed -i "s|BENUTZERNAME|$USER|g" ~/.config/autostart/respeaker-init.desktop
```
---
## 9. Python-Abhängigkeiten installieren
### 9.1 Conda-Umgebung (empfohlen für GPU-Projekte)
```bash
# Conda/Miniforge installieren falls nicht vorhanden
# https://github.com/conda-forge/miniforge
conda create -n voice python=3.12
conda activate voice
```
### 9.2 Python-Pakete
```bash
pip install \
faster-whisper \
openwakeword \
piper-tts \
sounddevice \
scipy \
numpy \
langdetect \
openai
```
**Paketbeschreibungen:**
| Paket | Funktion |
|---|---|
| `faster-whisper` | GPU-beschleunigte Spracherkennung (OpenAI Whisper) |
| `openwakeword` | Wake-Word-Erkennung offline (u. a. „Hey Jarvis") |
| `piper-tts` | Offline Text-to-Speech mit Piper-Stimmen |
| `sounddevice` | Python-Binding für PortAudio (Mikrofon-Zugriff) |
| `scipy` | Resampling: 48 kHz → 16 kHz für Whisper |
| `numpy` | Audiodaten-Verarbeitung |
| `langdetect` | Automatische Spracherkennung des TTS-Ausgabetexts |
| `openai` | HTTP-Client für llama.cpp / Ollama / OpenAI-APIs |
### 9.3 CUDA-Unterstützung prüfen
```bash
python3 -c "import torch; print(torch.cuda.is_available(), torch.cuda.device_count())"
# Sollte: True <Anzahl GPUs> ausgeben
# Whisper-Modell laden (lädt ~3 GB beim ersten Mal)
python3 -c "
from faster_whisper import WhisperModel
m = WhisperModel('large-v3', device='cuda', compute_type='float16')
print('Whisper OK')
"
```
Falls keine GPU vorhanden ist: `device='cpu'` und `compute_type='int8'` verwenden.
Die Transkription dauert dann mehrere Sekunden statt ~300 ms.
### 9.4 openwakeword initialisieren
Beim ersten Start lädt openwakeword die vortrainierten Modelle herunter:
```bash
python3 -c "
from openwakeword.model import Model
m = Model()
print('Wake-Word-Modelle:', list(m.models.keys()))
"
```
---
## 10. Verifikation: Alles testen
### Schritt 1: Gerät in PipeWire prüfen
```bash
pactl list sources short | grep -i respeaker
# Erwartete Ausgabe (eine Zeile):
# 123 alsa_input.usb-Seeed_Studio_reSpeaker_XVF3800_4-Mic_Array_... PipeWire s16le 2ch 48000Hz SUSPENDED
```
Falls leer: USB-Kabel abziehen, wieder einstecken, 5 Sekunden warten,
dann erneut prüfen.
### Schritt 2: Lautstärke prüfen
```bash
pactl get-source-volume \
alsa_input.usb-Seeed_Studio_reSpeaker_XVF3800_4-Mic_Array_114993700261100055-00.analog-stereo
# Sollte 150% / 1,50 zeigen
```
### Schritt 3: Aufnahme testen
```bash
# 5 Sekunden aufnehmen und abspielen
arecord -D respeaker -f S16_LE -r 48000 -c 2 -d 5 /tmp/test.wav && aplay /tmp/test.wav
```
Sprache sollte klar und ohne Echos zu hören sein.
### Schritt 4: sounddevice-Index ermitteln
```bash
python3 -c "
import sounddevice as sd
for i, d in enumerate(sd.query_devices()):
if 'respeaker' in d['name'].lower():
print(f'Index {i}: {d[\"name\"]}')
print(f' Kanäle: in={d[\"max_input_channels\"]} out={d[\"max_output_channels\"]}')
print(f' Rate: {int(d[\"default_samplerate\"])} Hz')
"
# Hinweis: max_input_channels kann 0 melden — das ist ein Anzeigebug,
# das Gerät funktioniert trotzdem als Eingabe.
```
### Schritt 5: Whisper-Transkription testen
```bash
# 5 Sekunden sprechen, dann automatische Transkription
python3 -c "
import sounddevice as sd
import numpy as np
from scipy.signal import resample_poly
from faster_whisper import WhisperModel
DEVICE = 16 # ← Index aus Schritt 4 einsetzen
RATE = 48000
SEC = 5
print('Sprechen ...')
audio = sd.rec(int(SEC * RATE), samplerate=RATE, channels=1,
dtype='float32', device=DEVICE)
sd.wait()
audio16k = resample_poly(audio[:,0], 16000, RATE).astype('float32')
model = WhisperModel('large-v3', device='cuda', compute_type='float16')
segs, _ = model.transcribe(audio16k, language='de')
print('Transkription:', ' '.join(s.text for s in segs))
"
```
### Schritt 6: TTS-Ausgabe testen
```bash
python3 -c "
import subprocess
from piper.voice import PiperVoice
from pathlib import Path
VOICES = Path.home() / '.local/share/piper/voices'
VOICES.mkdir(parents=True, exist_ok=True)
voice_name = 'de_DE-thorsten-high'
onnx = VOICES / f'{voice_name}.onnx'
if not onnx.exists():
from piper.download_voices import download_voice
print('Lade Stimme herunter ...')
download_voice(voice_name, VOICES)
voice = PiperVoice.load(str(onnx))
audio = b''.join(c.audio_int16_bytes for c in voice.synthesize('Hallo, ich bin dein Sprachassistent.'))
proc = subprocess.Popen(
['paplay', '--raw', f'--rate={voice.config.sample_rate}',
'--channels=1', '--format=s16le'],
stdin=subprocess.PIPE
)
proc.stdin.write(audio)
proc.stdin.close()
proc.wait()
print('TTS OK')
"
```
---
## 11. Verwendung mit dem Voice-Assistenten
Der Voice-Assistent in diesem Repository (`assistant.py`) nutzt alle
oben konfigurierten Komponenten.
### Starten
```bash
# Standard: Wake-Word "Hey Jarvis", llama.cpp auf localhost:8001
python3 assistant.py
# Entwicklung ohne Wake-Word
python3 assistant.py --no-wakeword
# MOTU M2 statt ReSpeaker als Mikrofon
python3 assistant.py --mic motu
# Alle verfügbaren Mikrofone anzeigen
python3 assistant.py --list-mics
```
### Mikrofon-Index im Code
Der Assistent verwendet den Kurznamen `respeaker` (→ `mic.py`), der
über Substring-Suche nach `reSpeaker` im Gerätenamen auflöst.
Der Hardware-Index (z. B. 16) kann auf anderen Systemen abweichen —
der Kurzname funktioniert unabhängig davon.
### GPU-Konfiguration
Das System wurde mit folgender GPU-Belegung entwickelt:
```
GPU 0 NVIDIA T600 (4 GB) — reserviert / Display
GPU 1 RTX 3090 (24 GB) — llama.cpp, Port 8001
GPU 2 RTX 3090 (24 GB) — Whisper
```
Whisper wird mit `CUDA_VISIBLE_DEVICES=1,2` gestartet und nutzt
physischen GPU-Index 2 (`--gpu 1` im Python-Code = Index 1
innerhalb von `CUDA_VISIBLE_DEVICES`).
Für Systeme mit einer einzelnen GPU:
```bash
python3 assistant.py --gpu 0
```
---
## 12. Fehlerbehebung
### Gerät nicht in `pactl list sources`
```bash
# 1. USB-Kabel abziehen und wieder einstecken
# 2. Manuellen Reset auslösen
sudo /usr/local/bin/respeaker-reset
# 3. Auf PipeWire-Erkennung warten
sleep 3 && pactl list sources short | grep -i respeaker
```
### Kein Ton bei der Aufnahme / Stille
```bash
# Lautstärke prüfen
pactl get-source-volume alsa_input.usb-Seeed_Studio_reSpeaker_XVF3800_4-Mic_Array_114993700261100055-00.analog-stereo
# Lautstärke manuell auf 150% setzen
pactl set-source-volume alsa_input.usb-Seeed_Studio_reSpeaker_XVF3800_4-Mic_Array_114993700261100055-00.analog-stereo 150%
# Mit alsamixer kontrollieren
alsamixer -c 5 # ← Kartennummer aus 'arecord -l'
```
### `max_input_channels = 0` in sounddevice
Das ist ein bekanntes Anzeigeproblem des ALSA-Treibers für dieses Gerät.
Das Gerät funktioniert trotzdem als Eingabe. Im Code muss die
Kanal-Filterung beim Auflösen des Gerätenamens deaktiviert sein
(→ `mic.py`, Funktion `resolve_mic`: kein Filter auf `max_input_channels`).
### Whisper versteht nichts / Transkription leer
1. Lautstärke zu niedrig → auf 150 % erhöhen (s. o.)
2. Falscher `--lang`-Parameter (z. B. `--lang en` bei deutschen Sätzen)
3. Zu kurze Stille-Erkennung → `--silence-sec 3.0` statt 5.0 testen
### `[audio] input overflow` im Terminal
Tritt auf, wenn Whisper die Verarbeitung verzögert und der PortAudio-Puffer
überläuft. Wird im Assistenten bewusst unterdrückt (ist kein Fehler).
Bei häufigem Auftreten GPU-Auslastung prüfen:
```bash
nvidia-smi
```
### systemd-Dienst schlägt fehl
```bash
sudo journalctl -u respeaker-reset.service -n 20
```
Häufige Ursache: Gerät ist beim Systemstart noch nicht angeschlossen.
In diesem Fall ist kein Fehler — das Skript prüft zuerst, ob die
Vendor-ID `2886` vorhanden ist, und beendet sich sauber wenn nicht.
### WirePlumber-Regeln greifen nicht
```bash
# WirePlumber neu starten
systemctl --user restart wireplumber
# Logs ansehen
journalctl --user -u wireplumber -n 30
```
---
## 13. Zusammenfassung aller erstellten Dateien
| Datei | Zweck |
|---|---|
| `/usr/local/bin/respeaker-reset` | USB-Codec-Reset (Root, ausführbar) |
| `/etc/systemd/system/respeaker-reset.service` | Automatischer Reset bei jedem Neustart |
| `~/.config/wireplumber/main.lua.d/51-alsa-custom.lua` | ALSA-Format und Sample-Rate für PipeWire |
| `~/.config/wireplumber/main.lua.d/52-respeaker-volume.lua` | Aufnahme-Lautstärke automatisch auf 150 % |
| `~/.asoundrc` | ALSA → PipeWire-Routing, benannte Gerät-Aliases |
| `~/.local/bin/respeaker-init.sh` | Login-Healthcheck mit Desktop-Benachrichtigung |
| `~/.config/autostart/respeaker-init.desktop` | Autostart des Healthchecks bei Desktop-Login |
### Kurzform: Komplette Installation auf einem neuen Rechner
```bash
# 1. Reset-Skript
sudo tee /usr/local/bin/respeaker-reset > /dev/null << 'EOF'
#!/bin/bash
USB_PATH=$(grep -rl "2886" /sys/bus/usb/devices/*/idVendor 2>/dev/null \
| head -1 | sed 's|/idVendor||; s|.*/||')
[ -z "$USB_PATH" ] && exit 0
echo 0 > /sys/bus/usb/devices/"$USB_PATH"/authorized
sleep 2
echo 1 > /sys/bus/usb/devices/"$USB_PATH"/authorized
EOF
sudo chmod +x /usr/local/bin/respeaker-reset
# 2. systemd-Dienst
sudo tee /etc/systemd/system/respeaker-reset.service > /dev/null << 'EOF'
[Unit]
Description=ReSpeaker XVF3800 USB codec reset after boot
After=sound.target systemd-udevd.service
DefaultDependencies=no
[Service]
Type=oneshot
ExecStart=/usr/local/bin/respeaker-reset
RemainAfterExit=yes
[Install]
WantedBy=sound.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now respeaker-reset.service
# 3. WirePlumber
mkdir -p ~/.config/wireplumber/main.lua.d
cat > ~/.config/wireplumber/main.lua.d/51-alsa-custom.lua << 'EOF'
alsa_monitor.properties = {
["audio.format"] = "S32LE",
["audio.rate"] = 48000,
["audio.channels"] = 2,
["audio.position"] = "FL,FR"
}
EOF
cat > ~/.config/wireplumber/main.lua.d/52-respeaker-volume.lua << 'EOF'
table.insert(alsa_monitor.rules, {
matches = {{ { "node.name", "matches", "alsa_input.usb-Seeed_Studio_reSpeaker_XVF3800*" } }},
apply_properties = { ["node.volume"] = 1.5 },
})
EOF
systemctl --user restart wireplumber
# 4. Python-Pakete
pip install faster-whisper openwakeword piper-tts sounddevice scipy langdetect openai
# 5. Prüfen
pactl list sources short | grep -i respeaker
```
Nach Abschluss aller Schritte und einem Neustart ist das ReSpeaker XVF3800
vollständig einsatzbereit für Whisper-Transkription und Piper-TTS.

View file

@ -55,6 +55,24 @@ python3 assistant.py --backend ollama --model llama3.2
python3 assistant.py --en-variant gb
```
## LLM-Backends
Der Assistent unterstützt jeden OpenAI-kompatiblen Endpunkt über `--api-url` und `--api-key`.
| Ziel | Befehl |
|---|---|
| llama.cpp lokal (Standard, Port 8001) | `python3 assistant.py` |
| llama.cpp auf anderem Port/Host | `--backend llama --api-url http://host:8080/v1` |
| Ollama lokal | `--backend ollama --model llama3.2` |
| Ollama remote | `--backend ollama --api-url http://server:11434/v1 --model llama3.2` |
| OpenAI | `--backend openai --model gpt-4o-mini` + `OPENAI_API_KEY` setzen |
| OpenRouter | `--backend openai --api-url https://openrouter.ai/api/v1 --model mistralai/mistral-7b-instruct --api-key $OPENROUTER_API_KEY` |
| Groq | `--backend openai --api-url https://api.groq.com/openai/v1 --model llama-3.1-70b-versatile --api-key $GROQ_API_KEY` |
| LM Studio | `--backend llama --api-url http://localhost:1234/v1` |
| Together AI | `--backend openai --api-url https://api.together.xyz/v1 --model meta-llama/Llama-3-8b-chat-hf --api-key $TOGETHER_API_KEY` |
Alle Provider, die die OpenAI Chat-Completions API implementieren, funktionieren mit `--backend openai --api-url <endpoint>`.
## Dateien
| Datei | Beschreibung |

View file

@ -278,7 +278,15 @@ def run(args: argparse.Namespace) -> None:
else:
ww_model = None
backend_cfg = BACKENDS[args.backend]
backend_cfg = dict(BACKENDS[args.backend]) # Kopie — kein Mutation globaler State
if args.api_url:
backend_cfg["base_url"] = args.api_url
if args.api_key:
backend_cfg["api_key"] = args.api_key
elif args.backend == "openai" and not backend_cfg["api_key"]:
backend_cfg["api_key"] = os.getenv("OPENAI_API_KEY", "")
if not backend_cfg["api_key"] and "openrouter" in (args.api_url or ""):
backend_cfg["api_key"] = os.getenv("OPENROUTER_API_KEY", "")
client = openai.OpenAI(
base_url=backend_cfg["base_url"],
api_key =backend_cfg["api_key"] or "none",
@ -834,6 +842,10 @@ def parse_args() -> argparse.Namespace:
choices=["llama", "ollama", "openai"], help="LLM-Backend")
p.add_argument("--model", default=None,
help="LLM-Modellname (default: auto-detect)")
p.add_argument("--api-url", default=None,
help="Überschreibt die Backend-URL (z. B. https://openrouter.ai/api/v1)")
p.add_argument("--api-key", default=None,
help="API-Key (überschreibt OPENAI_API_KEY / Backend-Default)")
p.add_argument("--whisper-model", default="large-v3")
p.add_argument("--gpu", type=int, default=1,
help="CUDA-Index für Whisper (in CUDA_VISIBLE_DEVICES)")