Skip to main content

wiimote_rs/
input.rs

1use crate::prelude::*;
2use bitflags::bitflags;
3
4const STATUS_ID: u8 = 0x20;
5const READ_MEMORY_ID: u8 = 0x21;
6const ACKNOWLEDGE_ID: u8 = 0x22;
7
8bitflags! {
9    #[derive(Debug, Clone, Copy)]
10    pub struct StatusFlags: u8 {
11        const BATTERY_LOW = 0b0000_0001;
12        const EXTENSION_CONTROLLER_CONNECTED = 0b0000_0010;
13        const SPEAKER_ENABLED = 0b0000_0100;
14        const IR_CAMERA_ENABLED = 0b0000_1000;
15        const LED_1 = 0b0001_0000;
16        const LED_2 = 0b0010_0000;
17        const LED_3 = 0b0100_0000;
18        const LED_4 = 0b1000_0000;
19    }
20}
21
22bitflags! {
23    #[derive(Debug, Clone, Copy)]
24    pub struct ButtonData: u16 {
25        const LEFT = 1 << 0;
26        const RIGHT = 1 << 1;
27        const DOWN = 1 << 2;
28        const UP = 1 << 3;
29        const PLUS = 1 << 4;
30
31        const TWO = 1 << 8;
32        const ONE = 1 << 9;
33        const B = 1 << 10;
34        const A = 1 << 11;
35        const MINUS = 1 << 12;
36
37        const HOME = 1 << 15;
38    }
39}
40
41#[repr(C, packed)]
42#[derive(Debug)]
43pub struct StatusData {
44    buttons: ButtonData,
45    flags: StatusFlags,
46    _reserved: [u8; 2],
47    battery_level: u8,
48}
49
50impl StatusData {
51    /// Returns the core button data.
52    #[must_use]
53    pub const fn buttons(&self) -> ButtonData {
54        self.buttons
55    }
56
57    /// Returns the status flags.
58    #[must_use]
59    pub const fn flags(&self) -> StatusFlags {
60        self.flags
61    }
62
63    /// Returns the battery level.
64    #[must_use]
65    pub const fn battery_level(&self) -> u8 {
66        self.battery_level
67    }
68}
69
70#[repr(C, packed)]
71#[derive(Debug)]
72pub struct MemoryData {
73    buttons: ButtonData,
74    size_error_flags: u8,
75    address: [u8; 2],
76    pub data: [u8; 16],
77}
78
79impl MemoryData {
80    /// Returns the core button data.
81    #[must_use]
82    pub const fn buttons(&self) -> ButtonData {
83        self.buttons
84    }
85
86    /// Returns the size of the data in bytes.
87    #[must_use]
88    pub const fn size(&self) -> u8 {
89        (self.size_error_flags >> 4) + 1
90    }
91
92    /// Returns the error flag.
93    ///
94    /// Known values:
95    /// - 0: No error
96    /// - 7: Attempted to read from write-only register or disconnected extension
97    /// - 8: Attempted to read from non-existing address
98    #[must_use]
99    pub const fn error_flag(&self) -> u8 {
100        self.size_error_flags & 0x0F
101    }
102
103    /// Returns the 2 least significant bytes of the address of the first byte.
104    #[must_use]
105    pub const fn address_offset(&self) -> u16 {
106        u16::from_be_bytes(self.address)
107    }
108}
109
110#[repr(C, packed)]
111#[derive(Debug)]
112pub struct AcknowledgeData {
113    buttons: ButtonData,
114    report_number: u8,
115    error_code: u8,
116}
117
118impl AcknowledgeData {
119    /// Returns the core button data.
120    #[must_use]
121    pub const fn buttons(&self) -> ButtonData {
122        self.buttons
123    }
124
125    /// Returns the report number.
126    #[must_use]
127    pub const fn report_number(&self) -> u8 {
128        self.report_number
129    }
130
131    /// Returns the error code.
132    #[must_use]
133    pub const fn error_code(&self) -> u8 {
134        self.error_code
135    }
136}
137
138#[repr(C, packed)]
139#[derive(Debug)]
140pub struct WiimoteData {
141    pub data: [u8; 21],
142}
143
144impl WiimoteData {
145    /// Returns the core button data.
146    ///
147    /// This is invalid for report type 0x3d that only contains extension data.
148    #[must_use]
149    pub const fn buttons(&self) -> ButtonData {
150        let bits = u16::from_le_bytes([self.data[0], self.data[1]]);
151        ButtonData::from_bits_retain(bits)
152    }
153}
154
155/// An input report represents the data sent from the Wii remote to the computer.
156#[derive(Debug)]
157pub enum InputReport {
158    /// Status information report (ID 0x20).
159    ///
160    /// Can be requested by sending an output report with ID 0x15 and is automatically
161    /// sent when the Extension is connected or disconnected.
162    ///
163    /// WiiBrew Documentation: <https://www.wiibrew.org/wiki/Wiimote#0x20:_Status>
164    StatusInformation(StatusData),
165    /// Read memory data report (ID 0x21).
166    ///
167    /// Result of a read memory request (output report ID 0x17).
168    ///
169    /// WiiBrew Documentation: <https://www.wiibrew.org/wiki/Wiimote#0x21:_Read_Memory_Data>
170    ReadMemory(MemoryData),
171    /// Acknowledge report (ID 0x22).
172    ///
173    /// Sent as a response to an output report with a corresponding result or error.
174    ///
175    /// WiiBrew Documentation: <https://www.wiibrew.org/wiki/Wiimote#0x22:_Acknowledge_output_report.2C_return_function_result>
176    Acknowledge(AcknowledgeData),
177    /// Data report (IDs 0x30-0x3F).
178    ///
179    /// Contains the data of the buttons, accelerometer, IR and Extension from the Wii remote.
180    /// The exact data depends on the report type requested by the output report 0x12.
181    /// Defaults to 0x30 which only contains the button data.
182    ///
183    /// WiiBrew Documentation: <https://www.wiibrew.org/wiki/Wiimote#Data_Reporting>
184    DataReport(u8, WiimoteData),
185}
186
187macro_rules! transmute_data {
188    ($value:expr, $type:ident) => {{
189        const DATA_SIZE: usize = std::mem::size_of::<$type>();
190        if $value.len() < DATA_SIZE {
191            return Err(WiimoteDeviceError::InvalidData.into());
192        }
193        let mut slice = [0u8; DATA_SIZE];
194        slice.copy_from_slice(&$value[1..=DATA_SIZE]);
195
196        unsafe { std::mem::transmute::<[u8; DATA_SIZE], $type>(slice) }
197    }};
198}
199
200impl InputReport {
201    fn from_status_information(value: &[u8]) -> WiimoteResult<Self> {
202        let data = transmute_data!(value, StatusData);
203        Ok(Self::StatusInformation(data))
204    }
205
206    fn from_read_memory_data(value: &[u8]) -> WiimoteResult<Self> {
207        let data = transmute_data!(value, MemoryData);
208        Ok(Self::ReadMemory(data))
209    }
210
211    fn from_acknowledge(value: &[u8]) -> WiimoteResult<Self> {
212        let data = transmute_data!(value, AcknowledgeData);
213        Ok(Self::Acknowledge(data))
214    }
215
216    fn from_data_report(value: &[u8]) -> Self {
217        const DATA_SIZE: usize = 21;
218        let mut data = [0u8; DATA_SIZE];
219        let bytes_to_copy = usize::min(value.len() - 1, DATA_SIZE);
220        data[..bytes_to_copy].copy_from_slice(&value[1..=bytes_to_copy]);
221
222        Self::DataReport(value[0], WiimoteData { data })
223    }
224}
225
226impl TryFrom<&[u8; WIIMOTE_DEFAULT_REPORT_BUFFER_SIZE]> for InputReport {
227    type Error = WiimoteError;
228
229    fn try_from(value: &[u8; WIIMOTE_DEFAULT_REPORT_BUFFER_SIZE]) -> Result<Self, Self::Error> {
230        let slice_without_length: &[u8] = value.as_slice();
231        Self::try_from(slice_without_length)
232    }
233}
234
235impl TryFrom<&[u8]> for InputReport {
236    type Error = WiimoteError;
237
238    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
239        if value.is_empty() {
240            return Err(WiimoteDeviceError::MissingData.into());
241        }
242        match value[0] {
243            STATUS_ID => Self::from_status_information(value),
244            READ_MEMORY_ID => Self::from_read_memory_data(value),
245            ACKNOWLEDGE_ID => Self::from_acknowledge(value),
246            0x30..=0x3F => Ok(Self::from_data_report(value)),
247            _ => Err(WiimoteDeviceError::InvalidData.into()),
248        }
249    }
250}
251
252#[cfg(test)]
253mod tests {
254    use super::*;
255
256    #[test]
257    fn test_status_report() {
258        let mut data = [0u8; WIIMOTE_DEFAULT_REPORT_BUFFER_SIZE];
259        data[0] = 0x20;
260        data[1] = 0b0001_0100; // Plus and D-Pad down
261        data[2] = 0b0000_0100; // B
262        data[3] = 0b0010_0101; // Status (battery low, speaker, led 2)
263
264        data[6] = 24; // Battery level
265
266        let report = InputReport::try_from(&data).unwrap();
267
268        assert!(matches!(report, InputReport::StatusInformation(_)));
269        if let InputReport::StatusInformation(data) = report {
270            assert_eq!(
271                data.buttons().bits(),
272                ButtonData::DOWN
273                    .union(ButtonData::PLUS)
274                    .union(ButtonData::B)
275                    .bits()
276            );
277            assert_eq!(
278                data.flags().bits(),
279                StatusFlags::BATTERY_LOW
280                    .union(StatusFlags::SPEAKER_ENABLED)
281                    .union(StatusFlags::LED_2)
282                    .bits()
283            );
284            assert_eq!(data.battery_level(), 24);
285        }
286    }
287
288    #[test]
289    fn test_read_memory_report() {
290        let mut data = [0u8; WIIMOTE_DEFAULT_REPORT_BUFFER_SIZE];
291        data[0] = 0x21;
292        data[1] = 0b0000_0000; // no button
293        data[2] = 0b1000_0000; // Home
294        data[3] = 0xF7; // Size and error flags
295        data[4] = 0x12; // Address
296        data[5] = 0xAB; // Address
297        data[6..22].copy_from_slice(b"1234567890123456"); // Data
298
299        let report = InputReport::try_from(&data).unwrap();
300
301        assert!(matches!(report, InputReport::ReadMemory(_)));
302        if let InputReport::ReadMemory(data) = report {
303            assert_eq!(data.buttons().bits(), ButtonData::HOME.bits());
304            assert_eq!(data.size(), 16);
305            assert_eq!(data.error_flag(), 7);
306            assert_eq!(data.address_offset(), 0x12AB);
307            assert_eq!(data.data, *b"1234567890123456");
308        }
309    }
310
311    #[test]
312    fn test_acknowledge_report() {
313        let data: &[u8] = &[
314            0x22,
315            0b0000_0000, // no button
316            0b0000_0000, // no button
317            0x12,        // report number
318            0xAB,        // error code
319        ];
320
321        let report = InputReport::try_from(data).unwrap();
322
323        assert!(matches!(report, InputReport::Acknowledge(_)));
324        if let InputReport::Acknowledge(data) = report {
325            assert_eq!(data.buttons().bits(), 0);
326            assert_eq!(data.report_number(), 0x12);
327            assert_eq!(data.error_code(), 0xAB);
328        }
329    }
330
331    #[test]
332    fn test_buttons_mode_0x30() {
333        let data: &[u8] = &[
334            0x30,
335            0b0000_0001, // D-Pad left
336            0b0000_0010, // One
337        ];
338
339        let report = InputReport::try_from(data).unwrap();
340
341        assert!(matches!(report, InputReport::DataReport(0x30, _)));
342        if let InputReport::DataReport(_, data) = report {
343            assert_eq!(
344                data.buttons().bits(),
345                ButtonData::LEFT.union(ButtonData::ONE).bits()
346            );
347        }
348    }
349}