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}