Files
linux-voice-assistant/docs/linux-voice-assistant-xvf3800-mute.md
T

224 lines
8.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# XVF3800 Mute Button & USB Control Integration
This document explains how the **ReSpeaker XVF3800 4‑Mic USB Array** integrates its
built‑in mute button and red mute LED with the **Linux Voice Assistant (LVA)**.
The goal is:
- Pressing the XVF3800’s hardware mute button updates the LVA mic state.
- Muting/unmuting the LVA (via Home Assistant, MQTT, or other inputs) updates
the XVF3800’s mic mute circuit and red LED.
- No extra binaries (like the vendor `xvf_host`) are required — LVA talks to
the XVF3800 directly via **USB control transfers** using `pyusb`.
---
## 1. How it Works (High‑Level)
The XVF3800 exposes a vendor control protocol over USB that can read and write
internal GPIO pins (GPO). One of these pins drives both:
- the microphone mute circuit
- the red mute LED on the XVF3800 front panel
LVA’s `XVF3800ButtonController` does three things:
1. **Reads hardware mute state periodically**
It polls the XVF3800’s GPO state (pin X0D30) in a background thread using
`pyusb`. If the value changes, LVA treats that as “the user pressed the
XVF3800 mute button” and publishes a `set_mic_mute` event.
2. **Mirrors LVA mute → XVF3800**
It subscribes to `mic_muted` / `mic_unmuted` events. Whenever LVA’s mute
state changes (for example, via MQTT, HA UI, or a GPIO button), the
controller writes the new state back to X0D30 so that the mic mute circuit
and red LED stay in sync.
3. **Initial sync on startup**
On first successful read, the controller treats the *hardware* mute state
as the source of truth and publishes `set_mic_mute` so LVA starts aligned
with whatever the XVF3800 is currently doing.
No PulseAudio/PipeWire configuration is required for this feature; it only
depends on **USB access to the XVF3800** and the `pyusb` library.
---
## 2. Prerequisites
- You are running a branch of **linux‑voice‑assistant** that includes:
- `linux_voice_assistant/xvf3800_button_controller.py`
- The updated `__main__.py` and `config.py` wiring for `XVF3800ButtonController`
- The ReSpeaker XVF3800 is connected over USB and appears in `lsusb` as:
- `2886:001a Seeed Technology Co., Ltd. reSpeaker XVF3800 4-Mic Array`
- The LVA `.venv` includes `pyusb` (already present in `pyproject.toml`):
```bash
source .venv/bin/activate
pip install pyusb
```
On most systems, user‑mode processes can talk to the XVF3800 over USB without
`sudo` once the device is enumerated. If you see `USBError: [Errno 13] Access denied`,
you may need a udev rule — that’s outside this document but worth noting.
---
## 3. Enabling the XVF3800 Mute Integration
The integration is controlled via the `button` section of `config.json`.
Two “modes” are supported:
- `"gpio"` – legacy Raspberry Pi GPIO button (ReSpeaker 2‑Mic HAT, etc.)
- `"xvf3800"` – USB‑based mute integration for the ReSpeaker XVF3800
### 3.1 Example `config.json` for XVF3800
Edit your LVA configuration file (usually `linux_voice_assistant/config.json`)
and add or adjust the `button` block like this:
```jsonc
{
"app": {
"name": "Linux Voice Assistant"
},
// ... other sections (audio, wake_word, esphome, led, mqtt, etc.) ...
"button": {
"enabled": true,
"mode": "xvf3800",
// Optional: how often to poll the XVF3800 GPO state (seconds)
"poll_interval_seconds": 0.05
}
}
```
Notes:
- `"enabled": true` turns on button support in general.
- `"mode": "xvf3800"` switches from the GPIO‑based button controller to the
USB‑based XVF3800 controller.
- `poll_interval_seconds` is optional and defaults to `0.01` if omitted. Values
between 0.02 and 0.1 are typically fine.
The GPIO‑specific fields (`pin`, `long_press_seconds`) are ignored in
`xvf3800` mode — the XVF3800’s onboard mute button is treated as a
**dedicated mute toggle only**, not a wake/stop button.
---
## 4. Runtime Behaviour
Once enabled and the LVA service is restarted, the mute integration behaves as follows:
### 4.1 Pressing the XVF3800 Mute Button
1. The XVF3800 firmware changes the GPO state of pin X0D30 and lights or clears
the red mute LED.
2. The `XVF3800ButtonController` sees the changed GPO values the next time it
polls USB.
3. It publishes a `set_mic_mute` event on the LVA `EventBus` with the new state.
4. The `MicMuteHandler`:
- Updates `ServerState.mic_muted` and `mic_muted_event` (pausing/resuming the
audio capture thread).
- Publishes `mic_muted` / `mic_unmuted` events used by other components
(LED controller, MQTT, etc.).
- Notifies the MQTT controller so the HA “Mute Microphone” entity stays in sync.
From the user’s perspective:
- The red LED on the XVF3800 tracks mic mute.
- The HA “Mute Microphone” switch also tracks the hardware button.
- The audio engine actually stops capturing when muted.
### 4.2 Toggling Mute from Home Assistant / MQTT / Other Inputs
1. Home Assistant toggles the LVA “Mute Microphone” switch (or another input
publishes a `set_mic_mute` event).
2. The `MicMuteHandler` applies the new mute state, as usual.
3. It emits a `mic_muted` or `mic_unmuted` event.
4. The `XVF3800ButtonController` receives this event and writes the new state
back to X0D30 via USB.
5. The XVF3800’s mic mute circuit and red LED update to match LVA.
This keeps **hardware**, **LVA state**, and **Home Assistant UI** aligned.
---
## 5. Coexistence with Other Buttons
The XVF3800 mute integration is intentionally narrow in scope:
- It does **not** implement short vs. long press behaviour.
- It does **not** trigger wake‑word, stop‑word, or playback controls.
- It is purely “mute on / mute off” with bidirectional sync.
If you also have a GPIO button (e.g. a ReSpeaker 2‑Mic HAT momentary switch)
wired for wake/stop behaviour, you can:
- Leave `button.mode: "gpio"` and **not** use the XVF3800 mute integration, or
- Move wake/stop to the GPIO button controller and leave the XVF3800 button
as mute‑only by setting `mode: "xvf3800"` and handling wake/stop elsewhere.
At the moment, `button.mode` is **single‑valued** — it selects either the
GPIO controller or the XVF3800 controller, not both at once. If you ever want
to support both concurrently, that would be a separate enhancement (e.g. a
list of button backends).
---
## 6. Troubleshooting
### 6.1 No USB Access / Permission Errors
Symptoms in the logs (journalctl or console):
```text
Failed to initialize XVF3800 USB client; mute button integration will be disabled
USBError: [Errno 13] Access denied (insufficient permissions)
```
Check:
- `lsusb` shows `2886:001a` while LVA is running.
- The LVA service is running as the user who owns the `.venv` and has
access to USB devices.
- If necessary, add a udev rule that grants access to `idVendor=0x2886`
and `idProduct=0x001a` for your LVA user, then reload udev and
replug the device.
The controller will keep retrying USB initialization; once permissions are
fixed, it should connect without restarting LVA.
### 6.2 Mute Button Works but HA Entity Doesn’t Update
- Verify MQTT is enabled and configured correctly in `config.json`.
- Confirm that the HA “Mute Microphone” entity exists and belongs to the
correct LVA device.
- Check logs for `MicMuteHandler` and `MqttController` messages when pressing
the XVF3800 mute button.
### 6.3 HA Mute Switch Works but XVF3800 LED Doesn’t Change
- Ensure `button.enabled` is `true` and `"mode": "xvf3800"` in `config.json`.
- Look for log lines like:
- `Initializing XVF3800ButtonController (mode=xvf3800)`
- `Connected to ReSpeaker XVF3800 for mute control`
- If you see only GPIO logs, confirm that the branch you’re running includes
`xvf3800_button_controller.py` and the updated `__main__.py` wiring.
---
## 7. Summary
With `mode: "xvf3800"`, the Linux Voice Assistant treats the ReSpeaker
XVF3800’s onboard mute button and red LED as a **first‑class mute control**:
- Pressing the XVF3800 mute button updates LVA and Home Assistant.
- Muting via Home Assistant or other inputs updates the XVF3800 hardware.
- The audio engine, LEDs, and MQTT entities stay in sync with the device’s
actual hardware mute state.
This integration is fully optional and coexists with existing audio and LED
behaviour — it simply adds a reliable, hardware‑based mute path for the
XVF3800‑based satellites.