first commit
This commit is contained in:
parent
01ca5d7a00
commit
670c6082a6
5 changed files with 886 additions and 2 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -38,3 +38,6 @@ env/
|
|||
|
||||
# Logs
|
||||
*.log
|
||||
|
||||
# Ideen und Notizen
|
||||
Ideen/
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
772
HOWTO_SETUP_AND_INSTALL_RESPEAKER_XVF3800_XIAO_ESP32S3.md
Normal file
772
HOWTO_SETUP_AND_INSTALL_RESPEAKER_XVF3800_XIAO_ESP32S3.md
Normal 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.
|
||||
18
README.md
18
README.md
|
|
@ -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 |
|
||||
|
|
|
|||
14
assistant.py
14
assistant.py
|
|
@ -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)")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue