joycon-driver 0.0.0

Driver for Nintendo Switchâ„¢
Documentation
use crate::{
    button::{LeftButtons, RightButtons, SharedButtons, SimpleButtons},
    error::{JoyConDriverError, JoyConDriverResult},
    imu::IMUDataRaw,
    joycon::{DeviceType as DeviceType_, JoyConDevice},
    stick::{StickData, StickDirection},
};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BatteryLevel {
    Full,
    Medium,
    Low,
    Critical,
    Empty,
}

#[derive(Debug, Clone, Copy)]
pub struct BatteryStatus {
    pub level: BatteryLevel,
    pub charging: bool,
}

impl TryFrom<u8> for BatteryStatus {
    type Error = JoyConDriverError;

    fn try_from(data: u8) -> JoyConDriverResult<Self> {
        let level = match data / 2 {
            0 => BatteryLevel::Empty,
            1 => BatteryLevel::Critical,
            2 => BatteryLevel::Low,
            3 => BatteryLevel::Medium,
            4 => BatteryLevel::Full,
            _ => return Err(JoyConDriverError::InvalidResultBuffer),
        };
        Ok(Self {
            level,
            charging: data % 2 == 1,
        })
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DeviceType {
    JoyCon,
    ProCon,
}

#[derive(Debug, Clone, Copy)]
pub struct ConnectionInfo {
    pub device_type: DeviceType,
    pub powered: bool,
}

impl TryFrom<u8> for ConnectionInfo {
    type Error = JoyConDriverError;

    fn try_from(data: u8) -> JoyConDriverResult<Self> {
        let device_type = match (data >> 1) & 3 {
            3 => DeviceType::JoyCon,
            0 => DeviceType::ProCon,
            _ => return Err(JoyConDriverError::InvalidResultBuffer),
        };
        Ok(Self {
            device_type,
            powered: data % 2 == 1,
        })
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum InputReportMode {
    StandardFullMode = 0x30,
    SimpleHIDMode = 0x3F,
}

#[derive(Debug, Clone)]
pub struct StandardInputReportCommon {
    pub timer: u8,
    pub battery: BatteryStatus,
    pub connection_info: ConnectionInfo,
    pub right_button: RightButtons,
    pub shared_button: SharedButtons,
    pub left_button: LeftButtons,
    pub left_stick: StickData,
    pub right_stick: StickData,
}

impl TryFrom<&[u8]> for StandardInputReportCommon {
    type Error = JoyConDriverError;

    fn try_from(buf: &[u8]) -> JoyConDriverResult<Self> {
        Ok(Self {
            timer: buf[1],
            battery: BatteryStatus::try_from(buf[2] >> 4)?,
            connection_info: ConnectionInfo::try_from(buf[2] & 0xF)?,
            right_button: buf[3]
                .try_into()
                .map_err(|_| JoyConDriverError::InvalidResultBuffer)?,
            shared_button: buf[4]
                .try_into()
                .map_err(|_| JoyConDriverError::InvalidResultBuffer)?,
            left_button: buf[5]
                .try_into()
                .map_err(|_| JoyConDriverError::InvalidResultBuffer)?,
            left_stick: StickData::try_from(&buf[6..=8])?,
            right_stick: StickData::try_from(&buf[9..=11])?,
        })
    }
}

#[derive(Debug, Clone)]
pub struct StandardFullReport {
    pub common: StandardInputReportCommon,
    pub imu_raw: IMUDataRaw,
    pub imu_raw_5ms_ago: IMUDataRaw,
    pub imu_raw_10ms_ago: IMUDataRaw,
}

impl StandardFullReport {
    const REPORT_ID: u8 = 0x30;
}

impl TryFrom<&[u8]> for StandardFullReport {
    type Error = JoyConDriverError;

    fn try_from(buf: &[u8]) -> JoyConDriverResult<Self> {
        if buf[0] != Self::REPORT_ID {
            return Err(JoyConDriverError::InvalidResultBuffer);
        }
        Ok(Self {
            common: StandardInputReportCommon::try_from(buf)?,
            imu_raw: IMUDataRaw::try_from(&buf[13..=24])?,
            imu_raw_5ms_ago: IMUDataRaw::try_from(&buf[25..=36])?,
            imu_raw_10ms_ago: IMUDataRaw::try_from(&buf[37..=48])?,
        })
    }
}

#[derive(Debug, Clone)]
pub struct JoyConSimpleHIDReport {
    pub button_status: SimpleButtons,
    pub stick_direction: StickDirection,
}

impl JoyConSimpleHIDReport {
    const REPORT_ID: u8 = 0x3F;
}

impl TryFrom<&[u8]> for JoyConSimpleHIDReport {
    type Error = JoyConDriverError;

    fn try_from(buf: &[u8]) -> JoyConDriverResult<Self> {
        if buf[0] != Self::REPORT_ID {
            return Err(JoyConDriverError::InvalidResultBuffer);
        }
        Ok(Self {
            button_status: u16::from_le_bytes([buf[1], buf[2]])
                .try_into()
                .map_err(|_| JoyConDriverError::InvalidResultBuffer)?,
            stick_direction: buf[3].try_into()?,
        })
    }
}

#[derive(Debug, Clone)]
pub struct ProConSimpleHIDReport {
    pub button_status: SimpleButtons,
    pub stick_direction: StickDirection,
    pub left_stick: StickData,
    pub right_stick: StickData,
}

impl ProConSimpleHIDReport {
    const REPORT_ID: u8 = 0x3F;
}

impl TryFrom<&[u8]> for ProConSimpleHIDReport {
    type Error = JoyConDriverError;

    fn try_from(buf: &[u8]) -> JoyConDriverResult<Self> {
        if buf[0] != Self::REPORT_ID {
            return Err(JoyConDriverError::InvalidResultBuffer);
        }
        Ok(Self {
            button_status: u16::from_be_bytes([buf[1], buf[2]])
                .try_into()
                .map_err(|_| JoyConDriverError::InvalidResultBuffer)?,
            stick_direction: buf[3].try_into()?,
            left_stick: StickData::try_from(&buf[4..=7])?,
            right_stick: StickData::try_from(&buf[8..=11])?,
        })
    }
}

#[derive(Debug, Clone)]
pub enum InputReport {
    StandardFull(StandardFullReport),
    JoyConSimpleHID(JoyConSimpleHIDReport),
    ProConSimpleHID(ProConSimpleHIDReport),
}

impl InputReport {
    pub fn read(device: &JoyConDevice) -> JoyConDriverResult<Option<Self>> {
        let mut buf = [0u8; 50];
        device.read_timeout(&mut buf, 20)?;
        Ok(match buf[0] {
            0x30 => Some(InputReport::StandardFull(StandardFullReport::try_from(
                &buf[..],
            )?)),
            0x3F => match device.device_type {
                DeviceType_::JoyConL | DeviceType_::JoyConR => Some(InputReport::JoyConSimpleHID(
                    JoyConSimpleHIDReport::try_from(&buf[..])?,
                )),
                DeviceType_::ProCon => Some(InputReport::ProConSimpleHID(
                    ProConSimpleHIDReport::try_from(&buf[..])?,
                )),
            },
            _ => None,
        })
    }
}