|
| 1 | +//! Sources: |
| 2 | +//! - https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/bluetooth_hid_notes.md |
| 3 | +//! - https://github.com/torvalds/linux/blob/master/drivers/hid/hid-nintendo.c |
| 4 | +//! - https://switchbrew.org/w/index.php?title=Joy-Con |
| 5 | +use packed_struct::prelude::*; |
| 6 | + |
| 7 | +#[derive(PrimitiveEnum_u8, Clone, Copy, PartialEq, Debug)] |
| 8 | +pub enum ReportType { |
| 9 | + CommandOutputReport = 0x01, |
| 10 | + McuUpdateOutputReport = 0x03, |
| 11 | + BasicOutputReport = 0x10, |
| 12 | + McuOutputReport = 0x11, |
| 13 | + AttachmentOutputReport = 0x12, |
| 14 | + CommandInputReport = 0x21, |
| 15 | + McuUpdateInputReport = 0x23, |
| 16 | + BasicInputReport = 0x30, |
| 17 | + McuInputReport = 0x31, |
| 18 | + AttachmentInputReport = 0x32, |
| 19 | + Unused1 = 0x33, |
| 20 | + GenericInputReport = 0x3F, |
| 21 | + OtaEnableFwuReport = 0x70, |
| 22 | + OtaSetupReadReport = 0x71, |
| 23 | + OtaReadReport = 0x72, |
| 24 | + OtaWriteReport = 0x73, |
| 25 | + OtaEraseReport = 0x74, |
| 26 | + OtaLaunchReport = 0x75, |
| 27 | + ExtGripOutputReport = 0x80, |
| 28 | + ExtGripInputReport = 0x81, |
| 29 | + Unused2 = 0x82, |
| 30 | +} |
| 31 | + |
| 32 | +impl TryFrom<u8> for ReportType { |
| 33 | + type Error = &'static str; |
| 34 | + |
| 35 | + fn try_from(value: u8) -> Result<Self, Self::Error> { |
| 36 | + match value { |
| 37 | + 0x01 => Ok(Self::CommandOutputReport), |
| 38 | + 0x03 => Ok(Self::McuUpdateOutputReport), |
| 39 | + 0x10 => Ok(Self::BasicOutputReport), |
| 40 | + 0x11 => Ok(Self::McuOutputReport), |
| 41 | + 0x12 => Ok(Self::AttachmentOutputReport), |
| 42 | + 0x21 => Ok(Self::CommandInputReport), |
| 43 | + 0x23 => Ok(Self::McuUpdateInputReport), |
| 44 | + 0x30 => Ok(Self::BasicInputReport), |
| 45 | + 0x31 => Ok(Self::McuInputReport), |
| 46 | + 0x32 => Ok(Self::AttachmentInputReport), |
| 47 | + 0x33 => Ok(Self::Unused1), |
| 48 | + 0x3F => Ok(Self::GenericInputReport), |
| 49 | + 0x70 => Ok(Self::OtaEnableFwuReport), |
| 50 | + 0x71 => Ok(Self::OtaSetupReadReport), |
| 51 | + 0x72 => Ok(Self::OtaReadReport), |
| 52 | + 0x73 => Ok(Self::OtaWriteReport), |
| 53 | + 0x74 => Ok(Self::OtaEraseReport), |
| 54 | + 0x75 => Ok(Self::OtaLaunchReport), |
| 55 | + 0x80 => Ok(Self::ExtGripOutputReport), |
| 56 | + 0x81 => Ok(Self::ExtGripInputReport), |
| 57 | + 0x82 => Ok(Self::Unused2), |
| 58 | + _ => Err("Invalid report type"), |
| 59 | + } |
| 60 | + } |
| 61 | +} |
| 62 | + |
| 63 | +#[derive(PrimitiveEnum_u8, Clone, Copy, PartialEq, Debug)] |
| 64 | +pub enum BatteryLevel { |
| 65 | + Empty = 0, |
| 66 | + Critical = 1, |
| 67 | + Low = 2, |
| 68 | + Medium = 3, |
| 69 | + Full = 4, |
| 70 | +} |
| 71 | + |
| 72 | +#[derive(PackedStruct, Debug, Copy, Clone, PartialEq)] |
| 73 | +#[packed_struct(bit_numbering = "msb0", size_bytes = "1")] |
| 74 | +pub struct BatteryConnection { |
| 75 | + /// Battery level. 8=full, 6=medium, 4=low, 2=critical, 0=empty. LSB=Charging. |
| 76 | + #[packed_field(bits = "0..=2", ty = "enum")] |
| 77 | + pub battery_level: BatteryLevel, |
| 78 | + #[packed_field(bits = "3")] |
| 79 | + pub charging: bool, |
| 80 | + /// Connection info. (con_info >> 1) & 3 - 3=JC, 0=Pro/ChrGrip. con_info & 1 - 1=Switch/USB powered. |
| 81 | + #[packed_field(bits = "4..=7")] |
| 82 | + pub conn_info: u8, |
| 83 | +} |
| 84 | + |
| 85 | +#[derive(PackedStruct, Debug, Copy, Clone, PartialEq)] |
| 86 | +#[packed_struct(bit_numbering = "msb0", size_bytes = "3")] |
| 87 | +pub struct ButtonStatus { |
| 88 | + // byte 0 (Right) |
| 89 | + #[packed_field(bits = "7")] |
| 90 | + pub y: bool, |
| 91 | + #[packed_field(bits = "6")] |
| 92 | + pub x: bool, |
| 93 | + #[packed_field(bits = "5")] |
| 94 | + pub b: bool, |
| 95 | + #[packed_field(bits = "4")] |
| 96 | + pub a: bool, |
| 97 | + #[packed_field(bits = "3")] |
| 98 | + pub sr_right: bool, |
| 99 | + #[packed_field(bits = "2")] |
| 100 | + pub sl_right: bool, |
| 101 | + #[packed_field(bits = "1")] |
| 102 | + pub r: bool, |
| 103 | + #[packed_field(bits = "0")] |
| 104 | + pub zr: bool, |
| 105 | + |
| 106 | + // byte 1 (Shared) |
| 107 | + #[packed_field(bits = "15")] |
| 108 | + pub minus: bool, |
| 109 | + #[packed_field(bits = "14")] |
| 110 | + pub plus: bool, |
| 111 | + #[packed_field(bits = "13")] |
| 112 | + pub r_stick: bool, |
| 113 | + #[packed_field(bits = "12")] |
| 114 | + pub l_stick: bool, |
| 115 | + #[packed_field(bits = "11")] |
| 116 | + pub home: bool, |
| 117 | + #[packed_field(bits = "10")] |
| 118 | + pub capture: bool, |
| 119 | + #[packed_field(bits = "9")] |
| 120 | + pub _unused: bool, |
| 121 | + #[packed_field(bits = "8")] |
| 122 | + pub charging_grip: bool, |
| 123 | + |
| 124 | + // byte 2 (Left) |
| 125 | + #[packed_field(bits = "23")] |
| 126 | + pub down: bool, |
| 127 | + #[packed_field(bits = "22")] |
| 128 | + pub up: bool, |
| 129 | + #[packed_field(bits = "21")] |
| 130 | + pub right: bool, |
| 131 | + #[packed_field(bits = "20")] |
| 132 | + pub left: bool, |
| 133 | + #[packed_field(bits = "19")] |
| 134 | + pub sr_left: bool, |
| 135 | + #[packed_field(bits = "18")] |
| 136 | + pub sl_left: bool, |
| 137 | + #[packed_field(bits = "17")] |
| 138 | + pub l: bool, |
| 139 | + #[packed_field(bits = "16")] |
| 140 | + pub zl: bool, |
| 141 | +} |
| 142 | + |
| 143 | +#[derive(PackedStruct, Debug, Copy, Clone, PartialEq)] |
| 144 | +#[packed_struct(bit_numbering = "msb0", size_bytes = "3")] |
| 145 | +pub struct StickData { |
| 146 | + /// Analog stick X-axis |
| 147 | + #[packed_field(bits = "0..=11", endian = "msb")] |
| 148 | + pub y: Integer<i16, packed_bits::Bits<12>>, |
| 149 | + /// Analog stick Y-axis |
| 150 | + #[packed_field(bits = "12..=23", endian = "msb")] |
| 151 | + pub x: Integer<i16, packed_bits::Bits<12>>, |
| 152 | +} |
| 153 | + |
| 154 | +/// The 6-Axis data is repeated 3 times. On Joy-con with a 15ms packet push, |
| 155 | +/// this is translated to 5ms difference sampling. E.g. 1st sample 0ms, 2nd 5ms, |
| 156 | +/// 3rd 10ms. Using all 3 samples let you have a 5ms precision instead of 15ms. |
| 157 | +#[derive(PackedStruct, Debug, Copy, Clone, PartialEq)] |
| 158 | +#[packed_struct(bit_numbering = "msb0", size_bytes = "12")] |
| 159 | +pub struct ImuData { |
| 160 | + #[packed_field(bytes = "0..=1", endian = "lsb")] |
| 161 | + pub accel_x: Integer<i16, packed_bits::Bits<16>>, |
| 162 | + #[packed_field(bytes = "2..=3", endian = "lsb")] |
| 163 | + pub accel_y: Integer<i16, packed_bits::Bits<16>>, |
| 164 | + #[packed_field(bytes = "4..=5", endian = "lsb")] |
| 165 | + pub accel_z: Integer<i16, packed_bits::Bits<16>>, |
| 166 | + #[packed_field(bytes = "6..=7", endian = "lsb")] |
| 167 | + pub gyro_x: Integer<i16, packed_bits::Bits<16>>, |
| 168 | + #[packed_field(bytes = "8..=9", endian = "lsb")] |
| 169 | + pub gyro_y: Integer<i16, packed_bits::Bits<16>>, |
| 170 | + #[packed_field(bytes = "10..=11", endian = "lsb")] |
| 171 | + pub gyro_z: Integer<i16, packed_bits::Bits<16>>, |
| 172 | +} |
| 173 | + |
| 174 | +#[derive(PackedStruct, Debug, Copy, Clone, PartialEq)] |
| 175 | +#[packed_struct(bit_numbering = "msb0", size_bytes = "64")] |
| 176 | +pub struct PackedInputDataReport { |
| 177 | + // byte 0-2 |
| 178 | + /// Input report ID |
| 179 | + #[packed_field(bytes = "0", ty = "enum")] |
| 180 | + pub id: ReportType, |
| 181 | + /// Timer. Increments very fast. Can be used to estimate excess Bluetooth latency. |
| 182 | + #[packed_field(bytes = "1")] |
| 183 | + pub timer: u8, |
| 184 | + /// Battery and connection information |
| 185 | + #[packed_field(bytes = "2")] |
| 186 | + pub info: BatteryConnection, |
| 187 | + |
| 188 | + // byte 3-5 |
| 189 | + /// Button status |
| 190 | + #[packed_field(bytes = "3..=5")] |
| 191 | + pub buttons: ButtonStatus, |
| 192 | + |
| 193 | + // byte 6-11 |
| 194 | + /// Left analog stick |
| 195 | + #[packed_field(bytes = "6..=8")] |
| 196 | + pub left_stick: StickData, |
| 197 | + /// Right analog stick |
| 198 | + #[packed_field(bytes = "9..=11")] |
| 199 | + pub right_stick: StickData, |
| 200 | + |
| 201 | + // byte 12 |
| 202 | + /// Vibrator input report. Decides if next vibration pattern should be sent. |
| 203 | + #[packed_field(bytes = "12")] |
| 204 | + pub vibrator_report: u8, |
| 205 | +} |
0 commit comments