# 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.