Skip to main content

joycon_driver/
joycon.rs

1use std::{
2    fmt,
3    sync::atomic::{AtomicU8, Ordering},
4};
5
6use hidapi::{DeviceInfo, HidApi, HidDevice};
7
8use crate::{
9    error::{JoyConDriverError, JoyConDriverResult},
10    imu::{IMUCalibration, IMUOffset},
11    report::{InputReport, InputReportMode},
12    stick::StickCalibration,
13};
14
15pub const VENDOR_ID: u16 = 0x057E;
16pub const PRODUCT_ID_JOYCON_L: u16 = 0x2006;
17pub const PRODUCT_ID_JOYCON_R: u16 = 0x2007;
18pub const PRODUCT_ID_PROCON: u16 = 0x2009;
19
20pub const SUB_COMMAND_IN_HEADER_BYTES: usize = 11;
21pub const SUB_COMMAND_OUT_HEADER_BYTES: usize = 15;
22
23pub const SUB_COMMAND_READ_ARGS_BYTES: usize = 5;
24pub const SUB_COMMAND_READ_HEADER_BYTES: usize =
25    SUB_COMMAND_OUT_HEADER_BYTES + SUB_COMMAND_READ_ARGS_BYTES;
26
27pub const SUB_COMMAND_SET_REPORT_MODE_ARGS_BYTES: usize = 1;
28
29pub const USER_CALIBRATION_DATA_MAGIC: [u8; 2] = [0xB2, 0xA1];
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum DeviceType {
33    JoyConL,
34    JoyConR,
35    ProCon,
36}
37
38impl TryFrom<&DeviceInfo> for DeviceType {
39    type Error = JoyConDriverError;
40
41    fn try_from(info: &DeviceInfo) -> JoyConDriverResult<Self> {
42        Ok(match (info.vendor_id(), info.product_id()) {
43            (VENDOR_ID, PRODUCT_ID_JOYCON_L) => DeviceType::JoyConL,
44            (VENDOR_ID, PRODUCT_ID_JOYCON_R) => DeviceType::JoyConR,
45            (VENDOR_ID, PRODUCT_ID_PROCON) => DeviceType::ProCon,
46            (VENDOR_ID, id) => return Err(JoyConDriverError::InvalidProductId(id)),
47            (id, _) => return Err(JoyConDriverError::InvalidVendorId(id)),
48        })
49    }
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53#[repr(u8)]
54pub enum Command {
55    RumbleAndSubCommand = 0x01,
56}
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq)]
59#[repr(u8)]
60pub enum SubCommand {
61    SetInputReportMode = 0x03,
62    SPIFlashRead = 0x10,
63    EnableIMU = 0x40,
64}
65
66#[derive(Debug, Clone)]
67pub enum SubCommandReply {
68    Ack(u8),
69    Nack,
70}
71
72impl From<u8> for SubCommandReply {
73    fn from(v: u8) -> Self {
74        if v & 0x80 > 0 {
75            SubCommandReply::Ack(v & 0x7F)
76        } else {
77            SubCommandReply::Nack
78        }
79    }
80}
81
82#[derive(Debug, Clone)]
83pub struct JoyConCalibrationData {
84    pub left_stick_factory_calib: Option<StickCalibration>,
85    pub left_stick_user_calib: Option<StickCalibration>,
86    pub right_stick_factory_calib: Option<StickCalibration>,
87    pub right_stick_user_calib: Option<StickCalibration>,
88    pub imu_factory_calib: IMUCalibration,
89    pub imu_user_calib: Option<IMUCalibration>,
90    pub imu_offset: IMUOffset,
91}
92
93impl JoyConCalibrationData {
94    pub fn read_data(device: &JoyConDevice) -> JoyConDriverResult<Self> {
95        Ok(Self {
96            left_stick_factory_calib: StickCalibration::read_left_factory_data(device)?,
97            left_stick_user_calib: StickCalibration::read_left_user_data(device)?,
98            right_stick_factory_calib: StickCalibration::read_right_factory_data(device)?,
99            right_stick_user_calib: StickCalibration::read_right_user_data(device)?,
100            imu_factory_calib: IMUCalibration::read_factory_data(device)?,
101            imu_user_calib: IMUCalibration::read_user_data(device)?,
102            imu_offset: IMUOffset::read_data(device)?,
103        })
104    }
105}
106
107pub struct JoyConDevice {
108    hid_device: HidDevice,
109    count: AtomicU8,
110    pub device_type: DeviceType,
111    pub serial_number: String,
112}
113
114impl JoyConDevice {
115    pub fn new(api: &HidApi, info: &DeviceInfo) -> JoyConDriverResult<Self> {
116        Ok(Self {
117            hid_device: api
118                .open_serial(
119                    info.vendor_id(),
120                    info.product_id(),
121                    info.serial_number().unwrap_or_default(),
122                )
123                .map_err(JoyConDriverError::from)?,
124            count: AtomicU8::default(),
125            device_type: DeviceType::try_from(info)?,
126            serial_number: info.serial_number().unwrap_or_default().to_string(),
127        })
128    }
129
130    pub fn read_calibration_data(&self) -> JoyConDriverResult<JoyConCalibrationData> {
131        JoyConCalibrationData::read_data(self)
132    }
133
134    pub fn input_report(&self) -> JoyConDriverResult<InputReport> {
135        loop {
136            match InputReport::read(self) {
137                Ok(Some(v)) => return Ok(v),
138                Ok(None) => continue,
139                Err(e) => return Err(e),
140            }
141        }
142    }
143
144    pub(crate) fn send_sub_command(
145        &self,
146        sub_command: SubCommand,
147        args: &[u8],
148        buf: &mut [u8],
149    ) -> JoyConDriverResult<()> {
150        let mut data = vec![0u8; SUB_COMMAND_IN_HEADER_BYTES + args.len()];
151        data[0] = Command::RumbleAndSubCommand as u8;
152        data[1] = self.count.fetch_add(1, Ordering::Relaxed);
153        data[10] = sub_command as u8;
154        data[SUB_COMMAND_IN_HEADER_BYTES..].copy_from_slice(args);
155        self.hid_device.write(&data[..])?;
156
157        loop {
158            self.hid_device.read_timeout(buf, 20)?;
159
160            if buf[0] == 0x21 && buf[14] == (sub_command as u8) {
161                return match SubCommandReply::from(buf[13]) {
162                    SubCommandReply::Ack(_) => Ok(()),
163                    SubCommandReply::Nack => Err(JoyConDriverError::SubCommandFailed),
164                };
165            }
166        }
167    }
168
169    pub(crate) fn read_timeout(&self, buf: &mut [u8], timeout: i32) -> JoyConDriverResult<()> {
170        self.hid_device.read_timeout(buf, timeout)?;
171        Ok(())
172    }
173
174    pub fn set_input_report_mode(&self, report_mode: InputReportMode) -> JoyConDriverResult<()> {
175        let mut buf = [0u8; SUB_COMMAND_OUT_HEADER_BYTES];
176        self.send_sub_command(
177            SubCommand::SetInputReportMode,
178            &[report_mode as u8],
179            &mut buf[..],
180        )
181    }
182
183    pub fn imu_feature(&self, enable: bool) -> JoyConDriverResult<()> {
184        let mut buf = [0u8; SUB_COMMAND_OUT_HEADER_BYTES];
185        let arg = if enable { 0x01 } else { 0x00 };
186        self.send_sub_command(SubCommand::EnableIMU, &[arg], &mut buf[..])
187    }
188
189    // https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/bluetooth_hid_subcommands_notes.md#subcommand-0x10-spi-flash-read
190    pub(crate) fn read(&self, buf: &mut [u8], address: u16) -> JoyConDriverResult<()> {
191        if buf.len() <= SUB_COMMAND_READ_HEADER_BYTES {
192            return Err(JoyConDriverError::BufferIsTooSmall(buf.len()));
193        }
194
195        let address_upper = ((address >> 8) & 0xFF) as u8;
196        let address_lower = (address & 0xFF) as u8;
197        let data_len = match (buf.len() - SUB_COMMAND_READ_HEADER_BYTES).try_into() {
198            Ok(v) => v,
199            Err(_) => {
200                return Err(JoyConDriverError::BufferIsTooLarge(buf.len()));
201            }
202        };
203
204        self.send_sub_command(
205            SubCommand::SPIFlashRead,
206            &[address_lower, address_upper, 0x00, 0x00, data_len],
207            buf,
208        )
209    }
210
211    fn list_devices_with_product_id(
212        api: &HidApi,
213        product_id: Option<u16>,
214    ) -> JoyConDriverResult<Vec<Self>> {
215        api.device_list()
216            .filter(|info| {
217                info.vendor_id() == VENDOR_ID
218                    && (product_id.is_none() || Some(info.product_id()) == product_id)
219            })
220            .map(|info| Self::new(api, info))
221            .collect::<JoyConDriverResult<Vec<_>>>()
222    }
223
224    pub fn list_devices(api: &HidApi) -> JoyConDriverResult<Vec<Self>> {
225        Self::list_devices_with_product_id(api, None)
226    }
227
228    pub fn list_joycon_l_devices(api: &HidApi) -> JoyConDriverResult<Vec<Self>> {
229        Self::list_devices_with_product_id(api, Some(PRODUCT_ID_JOYCON_L))
230    }
231
232    pub fn list_joycon_r_devices(api: &HidApi) -> JoyConDriverResult<Vec<Self>> {
233        Self::list_devices_with_product_id(api, Some(PRODUCT_ID_JOYCON_R))
234    }
235
236    pub fn list_procon_devices(api: &HidApi) -> JoyConDriverResult<Vec<Self>> {
237        Self::list_devices_with_product_id(api, Some(PRODUCT_ID_PROCON))
238    }
239}
240
241impl fmt::Debug for JoyConDevice {
242    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
243        f.debug_struct("JoyConDevice")
244            .field("device_type", &self.device_type)
245            .field("serial_number", &self.serial_number)
246            .finish()
247    }
248}