respeaker 0.1.0

USB control library for Seeed ReSpeaker Mic Array v2.0 (LEDs + XVF-3000 DSP tuning)
Documentation
//! USB control for Seeed ReSpeaker Mic Array v2.0 (`2886:0018`).
//!
//! - [`PixelRing`] — RGB LED ring ([`pixel_ring`](https://github.com/respeaker/pixel_ring) protocol)
//! - [`Tuning`] — XVF-3000 DSP parameters ([`tuning.py`](https://github.com/respeaker/usb_4_mic_array))

mod error;
mod led;
mod params;
mod tuning;

pub use error::{Error, Result};
pub use led::{PixelRing, Rgb, VadLed};
pub use params::{
    find_parameter, Access, ParamKind, ParamValue, Parameter, PARAMETERS,
};
pub use tuning::Tuning;

use rusb::{Context, DeviceHandle, Direction, Recipient, RequestType, UsbContext};

/// Seeed Technology vendor id.
pub const VENDOR_ID: u16 = 0x2886;
/// ReSpeaker Mic Array v2.0 (UAC1.0) product id.
pub const PRODUCT_ID: u16 = 0x0018;

const LED_INDEX: u16 = 0x1C;
const LED_TIMEOUT: std::time::Duration = std::time::Duration::from_millis(8000);
const TUNING_TIMEOUT: std::time::Duration = std::time::Duration::from_millis(5000);

const VENDOR_OUT: u8 = rusb::request_type(Direction::Out, RequestType::Vendor, Recipient::Device);
const VENDOR_IN: u8 = rusb::request_type(Direction::In, RequestType::Vendor, Recipient::Device);

/// Open handle to a ReSpeaker Mic Array v2.0.
pub struct ReSpeaker<T: UsbContext = Context> {
    handle: DeviceHandle<T>,
}

impl ReSpeaker<Context> {
    /// Find and open the first ReSpeaker Mic Array v2.0 on the system.
    pub fn open() -> Result<Self> {
        let context = Context::new()?;
        Self::open_with_context(&context)
    }
}

impl<T: UsbContext> ReSpeaker<T> {
    /// Find and open the first matching device using an existing USB context.
    pub fn open_with_context(context: &T) -> Result<Self> {
        let devices = context.devices()?;
        let mut matched = None;
        for device in devices.iter() {
            let Ok(desc) = device.device_descriptor() else {
                continue;
            };
            if desc.vendor_id() == VENDOR_ID && desc.product_id() == PRODUCT_ID {
                matched = Some(device);
                break;
            }
        }

        let device = matched.ok_or(Error::NotFound {
            vid: VENDOR_ID,
            pid: PRODUCT_ID,
        })?;

        let handle = match device.open() {
            Ok(h) => h,
            Err(rusb::Error::Access) => return Err(Error::Access),
            Err(e) => return Err(Error::Usb(e)),
        };

        Ok(Self { handle })
    }

    /// Build from an already-opened handle (for tests / custom discovery).
    pub fn from_handle(handle: DeviceHandle<T>) -> Self {
        Self { handle }
    }

    /// LED ring controller.
    pub fn led(&self) -> PixelRing<'_, T> {
        PixelRing::new(&self.handle)
    }

    /// DSP tuning / DOA / VAD controller.
    pub fn tuning(&self) -> Tuning<'_, T> {
        Tuning::new(&self.handle)
    }

    /// Borrow the underlying USB handle.
    pub fn handle(&self) -> &DeviceHandle<T> {
        &self.handle
    }
}