respeaker 0.3.0

USB control library for Seeed ReSpeaker Mic Array v2.0 and XVF3800 (LEDs + DSP tuning), with reSpeaker Lite identification
Documentation
//! reSpeaker Lite (XMOS XU316) — `2886:0019`.
//!
//! The USB firmware exposes the board as a plain UAC2 sound card plus a DFU
//! interface; unlike the Mic Array v2 and the XVF3800 it answers **no** vendor
//! control transfers, so LEDs, DoA/VAD and DSP parameters cannot be reached
//! over USB. Verified against firmware 2.0.5: every vendor request STALLs.
//!
//! What this module does offer is USB identification — model, firmware version
//! (from `bcdDevice`), product and serial strings.
//!
//! On-board DSP control is the "XMOS command transport over I2C" service
//! (device address `0x42`, resource id 240 for DFU and 241 for configuration),
//! which needs an I2C master such as a Raspberry Pi or the XIAO ESP32S3 — not
//! this USB host. Seeed's examples expose only four commands there: VNR value
//! (`0xf1`/`0x80`), mute-button status (`0xf1`/`0x81`), speaker mute
//! (`0xf1`/`0x10`) and firmware version (`0xf0`/`0xd8`).
//!
//! The board's WS2812 RGB LED and USR button are not reachable from either
//! transport. Per Seeed's firmware changelog the USB firmware sets `WS2812_PIN`
//! high "to give up the control of WS2812 after boot" (v2.0.6), leaving the LED
//! to an external MCU, and the USR/MUTE buttons are solder pads wired to the
//! XIAO header (`USR`→D2, `MUTE`→D3) rather than to anything the XU316 reports
//! over USB. Driving them needs a GPIO master on the expansion header.

use crate::error::Result;
use rusb::{DeviceHandle, UsbContext};

/// Firmware version triple reported in the USB device descriptor.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct FirmwareVersion {
    pub major: u8,
    pub minor: u8,
    pub patch: u8,
}

impl std::fmt::Display for FirmwareVersion {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
    }
}

/// I2C device address of the XU316 command-transport service.
pub const I2C_ADDRESS: u8 = 0x42;
/// Resource id of the I2C DFU service.
pub const I2C_RESID_DFU: u8 = 240;
/// Resource id of the I2C configuration service (DSP parameters).
pub const I2C_RESID_CONFIG: u8 = 241;

/// Explanation attached to every unsupported-over-USB operation.
pub(crate) const NO_USB_CONTROL: &str =
    "reSpeaker Lite exposes no USB control protocol (audio + DFU only); \
     use the XMOS I2C command transport at address 0x42 from an I2C master";

/// Borrowed reSpeaker Lite interface.
pub struct Lite<'a, T: UsbContext> {
    handle: &'a DeviceHandle<T>,
}

impl<'a, T: UsbContext> Lite<'a, T> {
    pub(crate) fn new(handle: &'a DeviceHandle<T>) -> Self {
        Self { handle }
    }

    /// Firmware version from `bcdDevice`.
    pub fn firmware_version(&self) -> Result<FirmwareVersion> {
        let desc = self.handle.device().device_descriptor()?;
        let v = desc.device_version();
        Ok(FirmwareVersion {
            major: v.major(),
            minor: v.minor(),
            patch: v.sub_minor(),
        })
    }

    pub fn version_string(&self) -> Result<String> {
        Ok(self.firmware_version()?.to_string())
    }

    /// USB product string, e.g. `ReSpeaker Lite`.
    pub fn product_name(&self) -> Result<String> {
        let desc = self.handle.device().device_descriptor()?;
        Ok(self.handle.read_product_string_ascii(&desc)?)
    }

    /// USB serial-number string (descriptor #3).
    ///
    /// Note that Seeed's firmware hardcodes this to the placeholder
    /// `"0000000001"` rather than deriving it from the XU316's unique id, so it
    /// does not identify an individual board. To tell two Lites apart on one
    /// host, use their USB bus number / address instead.
    pub fn serial_number(&self) -> Result<String> {
        let desc = self.handle.device().device_descriptor()?;
        Ok(self.handle.read_serial_number_string_ascii(&desc)?)
    }

    /// Always `false`: this firmware answers no vendor control transfers.
    pub const fn has_usb_control(&self) -> bool {
        false
    }
}