Skip to main content

joycon_driver/
report.rs

1use crate::{
2    button::{LeftButtons, RightButtons, SharedButtons, SimpleButtons},
3    error::{JoyConDriverError, JoyConDriverResult},
4    imu::IMUDataRaw,
5    joycon::{DeviceType as DeviceType_, JoyConDevice},
6    stick::{StickData, StickDirection},
7};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum BatteryLevel {
11    Full,
12    Medium,
13    Low,
14    Critical,
15    Empty,
16}
17
18#[derive(Debug, Clone, Copy)]
19pub struct BatteryStatus {
20    pub level: BatteryLevel,
21    pub charging: bool,
22}
23
24impl TryFrom<u8> for BatteryStatus {
25    type Error = JoyConDriverError;
26
27    fn try_from(data: u8) -> JoyConDriverResult<Self> {
28        let level = match data / 2 {
29            0 => BatteryLevel::Empty,
30            1 => BatteryLevel::Critical,
31            2 => BatteryLevel::Low,
32            3 => BatteryLevel::Medium,
33            4 => BatteryLevel::Full,
34            _ => return Err(JoyConDriverError::InvalidResultBuffer),
35        };
36        Ok(Self {
37            level,
38            charging: data % 2 == 1,
39        })
40    }
41}
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44pub enum DeviceType {
45    JoyCon,
46    ProCon,
47}
48
49#[derive(Debug, Clone, Copy)]
50pub struct ConnectionInfo {
51    pub device_type: DeviceType,
52    pub powered: bool,
53}
54
55impl TryFrom<u8> for ConnectionInfo {
56    type Error = JoyConDriverError;
57
58    fn try_from(data: u8) -> JoyConDriverResult<Self> {
59        let device_type = match (data >> 1) & 3 {
60            3 => DeviceType::JoyCon,
61            0 => DeviceType::ProCon,
62            _ => return Err(JoyConDriverError::InvalidResultBuffer),
63        };
64        Ok(Self {
65            device_type,
66            powered: data % 2 == 1,
67        })
68    }
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
72#[repr(u8)]
73pub enum InputReportMode {
74    StandardFullMode = 0x30,
75    SimpleHIDMode = 0x3F,
76}
77
78#[derive(Debug, Clone)]
79pub struct StandardInputReportCommon {
80    pub timer: u8,
81    pub battery: BatteryStatus,
82    pub connection_info: ConnectionInfo,
83    pub right_button: RightButtons,
84    pub shared_button: SharedButtons,
85    pub left_button: LeftButtons,
86    pub left_stick: StickData,
87    pub right_stick: StickData,
88}
89
90impl TryFrom<&[u8]> for StandardInputReportCommon {
91    type Error = JoyConDriverError;
92
93    fn try_from(buf: &[u8]) -> JoyConDriverResult<Self> {
94        Ok(Self {
95            timer: buf[1],
96            battery: BatteryStatus::try_from(buf[2] >> 4)?,
97            connection_info: ConnectionInfo::try_from(buf[2] & 0xF)?,
98            right_button: buf[3]
99                .try_into()
100                .map_err(|_| JoyConDriverError::InvalidResultBuffer)?,
101            shared_button: buf[4]
102                .try_into()
103                .map_err(|_| JoyConDriverError::InvalidResultBuffer)?,
104            left_button: buf[5]
105                .try_into()
106                .map_err(|_| JoyConDriverError::InvalidResultBuffer)?,
107            left_stick: StickData::try_from(&buf[6..=8])?,
108            right_stick: StickData::try_from(&buf[9..=11])?,
109        })
110    }
111}
112
113#[derive(Debug, Clone)]
114pub struct StandardFullReport {
115    pub common: StandardInputReportCommon,
116    pub imu_raw: IMUDataRaw,
117    pub imu_raw_5ms_ago: IMUDataRaw,
118    pub imu_raw_10ms_ago: IMUDataRaw,
119}
120
121impl StandardFullReport {
122    const REPORT_ID: u8 = 0x30;
123}
124
125impl TryFrom<&[u8]> for StandardFullReport {
126    type Error = JoyConDriverError;
127
128    fn try_from(buf: &[u8]) -> JoyConDriverResult<Self> {
129        if buf[0] != Self::REPORT_ID {
130            return Err(JoyConDriverError::InvalidResultBuffer);
131        }
132        Ok(Self {
133            common: StandardInputReportCommon::try_from(buf)?,
134            imu_raw: IMUDataRaw::try_from(&buf[13..=24])?,
135            imu_raw_5ms_ago: IMUDataRaw::try_from(&buf[25..=36])?,
136            imu_raw_10ms_ago: IMUDataRaw::try_from(&buf[37..=48])?,
137        })
138    }
139}
140
141#[derive(Debug, Clone)]
142pub struct JoyConSimpleHIDReport {
143    pub button_status: SimpleButtons,
144    pub stick_direction: StickDirection,
145}
146
147impl JoyConSimpleHIDReport {
148    const REPORT_ID: u8 = 0x3F;
149}
150
151impl TryFrom<&[u8]> for JoyConSimpleHIDReport {
152    type Error = JoyConDriverError;
153
154    fn try_from(buf: &[u8]) -> JoyConDriverResult<Self> {
155        if buf[0] != Self::REPORT_ID {
156            return Err(JoyConDriverError::InvalidResultBuffer);
157        }
158        Ok(Self {
159            button_status: u16::from_le_bytes([buf[1], buf[2]])
160                .try_into()
161                .map_err(|_| JoyConDriverError::InvalidResultBuffer)?,
162            stick_direction: buf[3].try_into()?,
163        })
164    }
165}
166
167#[derive(Debug, Clone)]
168pub struct ProConSimpleHIDReport {
169    pub button_status: SimpleButtons,
170    pub stick_direction: StickDirection,
171    pub left_stick: StickData,
172    pub right_stick: StickData,
173}
174
175impl ProConSimpleHIDReport {
176    const REPORT_ID: u8 = 0x3F;
177}
178
179impl TryFrom<&[u8]> for ProConSimpleHIDReport {
180    type Error = JoyConDriverError;
181
182    fn try_from(buf: &[u8]) -> JoyConDriverResult<Self> {
183        if buf[0] != Self::REPORT_ID {
184            return Err(JoyConDriverError::InvalidResultBuffer);
185        }
186        Ok(Self {
187            button_status: u16::from_be_bytes([buf[1], buf[2]])
188                .try_into()
189                .map_err(|_| JoyConDriverError::InvalidResultBuffer)?,
190            stick_direction: buf[3].try_into()?,
191            left_stick: StickData::try_from(&buf[4..=7])?,
192            right_stick: StickData::try_from(&buf[8..=11])?,
193        })
194    }
195}
196
197#[derive(Debug, Clone)]
198pub enum InputReport {
199    StandardFull(StandardFullReport),
200    JoyConSimpleHID(JoyConSimpleHIDReport),
201    ProConSimpleHID(ProConSimpleHIDReport),
202}
203
204impl InputReport {
205    pub fn read(device: &JoyConDevice) -> JoyConDriverResult<Option<Self>> {
206        let mut buf = [0u8; 50];
207        device.read_timeout(&mut buf, 20)?;
208        Ok(match buf[0] {
209            0x30 => Some(InputReport::StandardFull(StandardFullReport::try_from(
210                &buf[..],
211            )?)),
212            0x3F => match device.device_type {
213                DeviceType_::JoyConL | DeviceType_::JoyConR => Some(InputReport::JoyConSimpleHID(
214                    JoyConSimpleHIDReport::try_from(&buf[..])?,
215                )),
216                DeviceType_::ProCon => Some(InputReport::ProConSimpleHID(
217                    ProConSimpleHIDReport::try_from(&buf[..])?,
218                )),
219            },
220            _ => None,
221        })
222    }
223}