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,
})
}
}