steeloxide 0.1.0

A rust library for interacting with SteelSeries devices.
Documentation
use hidapi::{HidApi, HidDevice};

use crate::{
    colour::Rgb, consts::STEELSERIES_APS, error::{DeviceError, MouseError, Result}, mouse::{packet::{ColourPacket, MousePacket, PersistPacket, PollingPacket}, polling_rate::PollingRate}
};

pub mod device_type;
pub(crate) mod consts;
pub(crate) mod packet;
pub mod polling_rate;

pub use device_type::MouseDevice;

pub struct Mouse {
    device: HidDevice,
    api: HidApi,
    device_type: MouseDevice,

    polling_rate: PollingRate,
    colours: ColourPacket,
}

impl std::fmt::Debug for Mouse {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Mouse")
            .field("device_type", &self.device_type)
            .field("polling_rate", &self.polling_rate)
            .field("colours", &self.colours)
            .finish_non_exhaustive() 
    }
}

impl Mouse {
    // TODO: more types
    pub fn new(device_type: MouseDevice) -> Result<Self> {
        let api = HidApi::new()?;

        let info = api
            .device_list()
            .find(|d| {
                d.vendor_id() == STEELSERIES_APS
                    && d.product_id() == device_type.product_id()
                    && d.interface_number() == device_type.interface()
            })
            .ok_or(DeviceError::NotFound)?;

        #[cfg(feature = "default")]
        tracing::info!(
            "found {} (VID=0x{:04X}, PID=0x{:04X}, interface={})",
            info.product_string().unwrap_or("Unknown"),
            info.vendor_id(),
            info.product_id(),
            info.interface_number(),
        );

        let device = info.open_device(&api)?;
        Ok(Self {
            api,
            device_type,
            device,
            polling_rate: 1000.into(),
            colours: ColourPacket::new(device_type)
        })
    }

    pub fn get_type(&self) -> MouseDevice {
        self.device_type
    }

    pub fn persist(&self) -> Result<usize> {
        self.send_packet(&PersistPacket)
    }

    pub fn polling_rate(&mut self, rate: PollingRate) -> Result<usize> {
        self.polling_rate = rate;
        self.send_packet(&PollingPacket(rate))
    }

    pub fn set_zone(&mut self, zone: usize, colour: Rgb) -> Result<usize> {
        self.colours.change_zone(zone, colour)?;
        self.send_packet(&self.colours)
    }

    pub fn set_colours(&mut self, colours: &[Rgb]) -> Result<usize> {
        if colours.len() > self.device_type.get_zones() {
            return Err(MouseError::ZoneOutOfRange.into());
        }

        for (i, zone) in colours.iter().enumerate() {
            self.colours.change_zone(i, *zone)?;
        }

        self.send_packet(&self.colours)
    }

    pub fn get_polling_rate(&self) -> u16 {
        self.polling_rate.0
    }

    pub fn get_colours(&self) -> Vec<Rgb> {
        self.colours.get_colours()
    }

    pub fn get_zone(&self, zone: usize) -> Result<Rgb> {
        self.colours.get_zone(zone)
    }

    fn send_packet<T: MousePacket>(&self, data: &T) -> Result<usize> {
        Ok(self.device.write(&data.serialize()?)?)
    }
}