diff --git a/wii-ext/src/classic_sync.rs b/wii-ext/src/classic_sync.rs index e4fa5b2..2e3991d 100644 --- a/wii-ext/src/classic_sync.rs +++ b/wii-ext/src/classic_sync.rs @@ -11,12 +11,8 @@ // See `decode_classic_report` and `decode_classic_hd_report` for data format use crate::core::classic::*; -use crate::ControllerIdReport; +use crate::interface::Interface; use crate::ControllerType; -use crate::ExtHdReport; -use crate::ExtReport; -use crate::EXT_I2C_ADDR; -use crate::INTERMESSAGE_DELAY_MICROSEC_U32 as INTERMESSAGE_DELAY_MICROSEC; use embedded_hal::i2c::I2c; #[cfg(feature = "defmt_print")] @@ -30,24 +26,14 @@ pub enum ClassicError { ParseError, } -#[cfg_attr(feature = "defmt_print", derive(defmt::Format))] -/// Errors in this crate -#[derive(Debug)] -pub enum Error { - /// I²C bus communication error - I2C(E), - /// Invalid input data provided - InvalidInputData, -} +use crate::interface::Error; pub struct Classic { - i2cdev: I2C, + interface: Interface, hires: bool, calibration: CalibrationData, - delay: DELAY, } -// use crate::nunchuk; impl Classic where T: I2c, @@ -59,11 +45,11 @@ where /// send the required init sequence in order to read data in /// the future. pub fn new(i2cdev: T, delay: DELAY) -> Result, Error> { + let interface = Interface::new(i2cdev, delay); let mut classic = Classic { - i2cdev, + interface, hires: false, calibration: CalibrationData::default(), - delay, }; classic.init()?; Ok(classic) @@ -87,45 +73,6 @@ where Ok(()) } - /// Set the cursor position for the next i2c read - /// - /// This hardware has a range of 100 registers and automatically - /// increments the register read postion on each read operation, and also on - /// every write operation. - /// This should be called before a read operation to ensure you get the correct data - fn set_read_register_address(&mut self, byte0: u8) -> Result<(), Error> { - self.i2cdev - .write(EXT_I2C_ADDR as u8, &[byte0]) - .map_err(Error::I2C) - .and(Ok(())) - } - - /// Set a single register at target address - fn set_register(&mut self, addr: u8, byte1: u8) -> Result<(), Error> { - self.i2cdev - .write(EXT_I2C_ADDR as u8, &[addr, byte1]) - .map_err(Error::I2C) - .and(Ok(())) - } - - /// Read the button/axis data from the classic controller - fn read_report(&mut self) -> Result> { - let mut buffer: ExtReport = ExtReport::default(); - self.i2cdev - .read(EXT_I2C_ADDR as u8, &mut buffer) - .map_err(Error::I2C) - .and(Ok(buffer)) - } - - /// Read a high-resolution version of the button/axis data from the classic controller - fn read_hd_report(&mut self) -> Result> { - let mut buffer: ExtHdReport = ExtHdReport::default(); - self.i2cdev - .read(EXT_I2C_ADDR as u8, &mut buffer) - .map_err(Error::I2C) - .and(Ok(buffer)) - } - /// Send the init sequence to the Wii extension controller /// /// This could be a bit faster with DelayNs, but since you only init once we'll re-use delay_ms @@ -136,15 +83,9 @@ where // Reset to base register first - this should recover a controller in a weird state. // Use longer delays here than normal reads - the system seems more unreliable performing these commands - self.delay.delay_us(INTERMESSAGE_DELAY_MICROSEC * 2); - self.set_read_register_address(0)?; - self.delay.delay_us(INTERMESSAGE_DELAY_MICROSEC * 2); - self.set_register(0xF0, 0x55)?; - self.delay.delay_us(INTERMESSAGE_DELAY_MICROSEC * 2); - self.set_register(0xFB, 0x00)?; - self.delay.delay_us(INTERMESSAGE_DELAY_MICROSEC * 2); + self.interface.init()?; self.update_calibration()?; - self.delay.delay_us(INTERMESSAGE_DELAY_MICROSEC * 2); + // TODO: do we need more delay here? Ok(()) } @@ -154,9 +95,7 @@ where /// analogue axis as a u8, rather than packing smaller integers in a structure. /// If your controllers supports this mode, you should use it. It is much better. pub fn enable_hires(&mut self) -> Result<(), Error> { - self.delay.delay_us(INTERMESSAGE_DELAY_MICROSEC * 2); - self.set_register(0xFE, 0x03)?; - self.delay.delay_us(INTERMESSAGE_DELAY_MICROSEC * 2); + self.interface.enable_hires()?; self.hires = true; self.update_calibration()?; Ok(()) @@ -172,52 +111,36 @@ where /// TODO: work out why, make it public when it works #[allow(dead_code)] fn disable_hires(&mut self) -> Result<(), Error> { - self.delay.delay_us(INTERMESSAGE_DELAY_MICROSEC * 2); - self.set_register(0xFE, 0x01)?; - self.delay.delay_us(INTERMESSAGE_DELAY_MICROSEC * 2); + self.interface.disable_hires()?; self.hires = false; self.update_calibration()?; Ok(()) } - fn read_id(&mut self) -> Result> { - self.set_read_register_address(0xfa)?; - let i2c_id = self.read_report()?; - Ok(i2c_id) - } - pub fn identify_controller(&mut self) -> Result, Error> { - let i2c_id = self.read_id()?; - Ok(crate::common::identify_controller(i2c_id)) - } - - /// tell the extension controller to prepare a sample by setting the read cursor to 0 - fn start_sample(&mut self) -> Result<(), Error> { - self.set_read_register_address(0x00)?; - Ok(()) + self.interface.identify_controller() } /// poll the controller for the latest data fn read_classic_report(&mut self) -> Result> { if self.hires { - let buf = self.read_hd_report()?; + let buf = self.interface.read_hd_report()?; ClassicReading::from_data(&buf).ok_or(Error::InvalidInputData) } else { - let buf = self.read_report()?; + let buf = self.interface.read_report()?; ClassicReading::from_data(&buf).ok_or(Error::InvalidInputData) } } /// Simple read helper helper with no delay. Works for testing, not on real hardware pub fn read_classic_no_wait(&mut self) -> Result> { - self.start_sample()?; + self.interface.start_sample()?; self.read_classic_report() } /// Simple blocking read helper that will start a sample, wait 10ms, then read the value pub fn read_report_blocking(&mut self) -> Result> { - self.start_sample()?; - self.delay.delay_us(INTERMESSAGE_DELAY_MICROSEC); + self.interface.start_sample_and_wait()?; self.read_classic_report() } diff --git a/wii-ext/src/core.rs b/wii-ext/src/core.rs index 344729e..f32af63 100644 --- a/wii-ext/src/core.rs +++ b/wii-ext/src/core.rs @@ -1 +1,2 @@ pub mod classic; +pub mod nunchuk; diff --git a/wii-ext/src/core/nunchuk.rs b/wii-ext/src/core/nunchuk.rs new file mode 100644 index 0000000..fed2f97 --- /dev/null +++ b/wii-ext/src/core/nunchuk.rs @@ -0,0 +1,81 @@ +#[cfg(feature = "defmt_print")] +use defmt; + +#[cfg_attr(feature = "defmt_print", derive(defmt::Format))] +#[derive(Debug)] +pub struct NunchukReading { + pub joystick_x: u8, + pub joystick_y: u8, + pub accel_x: u16, // 10-bit + pub accel_y: u16, // 10-bit + pub accel_z: u16, // 10-bit + pub button_c: bool, + pub button_z: bool, +} + +impl NunchukReading { + pub fn from_data(data: &[u8]) -> Option { + if data.len() < 6 { + None + } else { + Some(NunchukReading { + joystick_x: data[0], + joystick_y: data[1], + accel_x: (u16::from(data[2]) << 2) | ((u16::from(data[5]) >> 6) & 0b11), + accel_y: (u16::from(data[3]) << 2) | ((u16::from(data[5]) >> 4) & 0b11), + accel_z: (u16::from(data[4]) << 2) | ((u16::from(data[5]) >> 2) & 0b11), + button_c: (data[5] & 0b10) == 0, + button_z: (data[5] & 0b01) == 0, + }) + } + } +} + +/// Relaxed/Center positions for each axis +/// +/// These are used to calculate the relative deflection of each access from their center point +#[derive(Default)] +pub struct CalibrationData { + pub joystick_x: u8, + pub joystick_y: u8, +} + +/// Data from a Nunchuk after calibration data has been applied +/// +/// Calibration is done by subtracting the resting values from the current +/// values, which means that going lower on the axis will go negative. +/// Due to this, we now store analog values as signed integers +/// +/// We'll only calibrate the joystick axes, leave accelerometer readings as-is +#[cfg_attr(feature = "defmt_print", derive(defmt::Format))] +#[derive(Debug, Default)] +pub struct NunchukReadingCalibrated { + pub joystick_x: i8, + pub joystick_y: i8, + pub accel_x: u16, // 10-bit + pub accel_y: u16, // 10-bit + pub accel_z: u16, // 10-bit + pub button_c: bool, + pub button_z: bool, +} + +impl NunchukReadingCalibrated { + pub fn new(r: NunchukReading, c: &CalibrationData) -> NunchukReadingCalibrated { + /// Just in case `data` minus `calibration data` is out of range, perform all operations + /// on i16 and clamp to i8 limits before returning + fn ext_u8_sub(a: u8, b: u8) -> i8 { + let res = (a as i16) - (b as i16); + res.clamp(i8::MIN as i16, i8::MAX as i16) as i8 + } + + NunchukReadingCalibrated { + joystick_x: ext_u8_sub(r.joystick_x, c.joystick_x), + joystick_y: ext_u8_sub(r.joystick_y, c.joystick_y), + accel_x: r.accel_x, + accel_y: r.accel_y, // 10-bit + accel_z: r.accel_z, // 10-bit + button_c: r.button_c, + button_z: r.button_z, + } + } +} diff --git a/wii-ext/src/interface.rs b/wii-ext/src/interface.rs new file mode 100644 index 0000000..f254f4a --- /dev/null +++ b/wii-ext/src/interface.rs @@ -0,0 +1,131 @@ +use crate::common::ControllerIdReport; +use crate::common::ExtHdReport; +use crate::common::ExtReport; +use crate::ControllerType; +use crate::EXT_I2C_ADDR; +use crate::INTERMESSAGE_DELAY_MICROSEC_U32 as INTERMESSAGE_DELAY_MICROSEC; +use embedded_hal::i2c::I2c; +use embedded_hal::i2c::SevenBitAddress; + +pub struct Interface { + i2cdev: I2C, + delay: Delay, +} + +#[cfg_attr(feature = "defmt_print", derive(defmt::Format))] +/// Errors in this crate +#[derive(Debug)] +pub enum Error { + /// I²C bus communication error + I2C(E), + /// Invalid input data provided + InvalidInputData, +} + +impl Interface +where + I2C: I2c, + Delay: embedded_hal::delay::DelayNs, +{ + pub fn new(i2cdev: I2C, delay: Delay) -> Interface { + Interface { i2cdev, delay } + } + + /// Send the init sequence to the Wii extension controller + pub(super) fn init(&mut self) -> Result<(), Error> { + // Extension controllers by default will use encrypted communication, as that is what the Wii does. + // We can disable this encryption by writing some magic values + // This is described at https://wiibrew.org/wiki/Wiimote/Extension_Controllers#The_New_Way + + // Reset to base register first - this should recover a controller in a weird state. + // Use longer delays here than normal reads - the system seems more unreliable performing these commands + self.delay.delay_us(INTERMESSAGE_DELAY_MICROSEC * 2); + self.set_read_register_address(0)?; + self.delay.delay_us(INTERMESSAGE_DELAY_MICROSEC * 2); + self.set_register(0xF0, 0x55)?; + self.delay.delay_us(INTERMESSAGE_DELAY_MICROSEC * 2); + self.set_register(0xFB, 0x00)?; + self.delay.delay_us(INTERMESSAGE_DELAY_MICROSEC * 2); + // TODO: move calibration to each impl + //self.update_calibration()?; + self.delay.delay_us(INTERMESSAGE_DELAY_MICROSEC * 2); + Ok(()) + } + + pub(super) fn read_id(&mut self) -> Result> { + self.set_read_register_address(0xfa)?; + let i2c_id = self.read_report()?; + Ok(i2c_id) + } + + pub(super) fn identify_controller(&mut self) -> Result, Error> { + let i2c_id = self.read_id()?; + Ok(crate::common::identify_controller(i2c_id)) + } + + /// tell the extension controller to prepare a sample by setting the read cursor to 0 + pub(super) fn start_sample(&mut self) -> Result<(), Error> { + self.set_read_register_address(0x00)?; + Ok(()) + } + + /// tell the extension controller to prepare a sample by setting the read cursor to 0 + pub(super) fn start_sample_and_wait(&mut self) -> Result<(), Error> { + self.set_read_register_address(0x00)?; + self.delay.delay_us(INTERMESSAGE_DELAY_MICROSEC); + Ok(()) + } + + /// Set the cursor position for the next i2c read + /// + /// This hardware has a range of 100 registers and automatically + /// increments the register read postion on each read operation, and also on + /// every write operation. + /// This should be called before a read operation to ensure you get the correct data + pub(super) fn set_read_register_address(&mut self, byte0: u8) -> Result<(), Error> { + self.i2cdev + .write(EXT_I2C_ADDR as u8, &[byte0]) + .map_err(Error::I2C) + .and(Ok(())) + } + + /// Set a single register at target address + pub(super) fn set_register(&mut self, addr: u8, byte1: u8) -> Result<(), Error> { + self.i2cdev + .write(EXT_I2C_ADDR as u8, &[addr, byte1]) + .map_err(Error::I2C) + .and(Ok(())) + } + + /// Read the button/axis data from the classic controller + pub(super) fn read_report(&mut self) -> Result> { + let mut buffer: ExtReport = ExtReport::default(); + self.i2cdev + .read(EXT_I2C_ADDR as u8, &mut buffer) + .map_err(Error::I2C) + .and(Ok(buffer)) + } + + pub(super) fn enable_hires(&mut self) -> Result<(), Error> { + self.delay.delay_us(INTERMESSAGE_DELAY_MICROSEC * 2); + self.set_register(0xFE, 0x03)?; + self.delay.delay_us(INTERMESSAGE_DELAY_MICROSEC * 2); + Ok(()) + } + + pub(super) fn disable_hires(&mut self) -> Result<(), Error> { + self.delay.delay_us(INTERMESSAGE_DELAY_MICROSEC * 2); + self.set_register(0xFE, 0x01)?; + self.delay.delay_us(INTERMESSAGE_DELAY_MICROSEC * 2); + Ok(()) + } + + /// Read a high-resolution version of the button/axis data from the classic controller + pub(super) fn read_hd_report(&mut self) -> Result> { + let mut buffer: ExtHdReport = ExtHdReport::default(); + self.i2cdev + .read(EXT_I2C_ADDR as u8, &mut buffer) + .map_err(Error::I2C) + .and(Ok(buffer)) + } +} diff --git a/wii-ext/src/lib.rs b/wii-ext/src/lib.rs index 567074e..c418a02 100644 --- a/wii-ext/src/lib.rs +++ b/wii-ext/src/lib.rs @@ -12,6 +12,9 @@ pub mod core; /// Anything common between nunchuk + classic pub mod common; +/// i2c interface code +pub mod interface; + pub mod nunchuk; /// Test data used by the integration tests to confirm that the driver is working. diff --git a/wii-ext/src/nunchuk.rs b/wii-ext/src/nunchuk.rs index f28705c..7db771e 100644 --- a/wii-ext/src/nunchuk.rs +++ b/wii-ext/src/nunchuk.rs @@ -7,15 +7,9 @@ // TODO: nunchuk technically supports HD report, but the last two bytes will be zeroes // work out if it's worth supporting that -use crate::ControllerIdReport; +use crate::core::nunchuk::{CalibrationData, NunchukReading, NunchukReadingCalibrated}; +use crate::interface::Interface; use crate::ControllerType; -use crate::ExtReport; -use crate::EXT_I2C_ADDR; -use crate::INTERMESSAGE_DELAY_MICROSEC_U32 as INTERMESSAGE_DELAY_MICROSEC; -use embedded_hal::delay::DelayNs; - -#[cfg(feature = "defmt_print")] -use defmt; use embedded_hal::i2c::{I2c, SevenBitAddress}; #[derive(Debug)] @@ -24,114 +18,30 @@ pub enum NunchukError { ParseError, } -/// Errors in this crate -#[derive(Debug)] -pub enum Error { - /// I²C bus communication error - I2C(E), - /// Invalid input data provided - InvalidInputData, -} - -#[cfg_attr(feature = "defmt_print", derive(defmt::Format))] -#[derive(Debug)] -pub struct NunchukReading { - pub joystick_x: u8, - pub joystick_y: u8, - pub accel_x: u16, // 10-bit - pub accel_y: u16, // 10-bit - pub accel_z: u16, // 10-bit - pub button_c: bool, - pub button_z: bool, -} - -impl NunchukReading { - pub fn from_data(data: &[u8]) -> Option { - if data.len() < 6 { - None - } else { - Some(NunchukReading { - joystick_x: data[0], - joystick_y: data[1], - accel_x: (u16::from(data[2]) << 2) | ((u16::from(data[5]) >> 6) & 0b11), - accel_y: (u16::from(data[3]) << 2) | ((u16::from(data[5]) >> 4) & 0b11), - accel_z: (u16::from(data[4]) << 2) | ((u16::from(data[5]) >> 2) & 0b11), - button_c: (data[5] & 0b10) == 0, - button_z: (data[5] & 0b01) == 0, - }) - } - } -} - -/// Relaxed/Center positions for each axis -/// -/// These are used to calculate the relative deflection of each access from their center point -#[derive(Default)] -pub struct CalibrationData { - pub joystick_x: u8, - pub joystick_y: u8, -} +use crate::interface::Error; -/// Data from a Nunchuk after calibration data has been applied -/// -/// Calibration is done by subtracting the resting values from the current -/// values, which means that going lower on the axis will go negative. -/// Due to this, we now store analog values as signed integers -/// -/// We'll only calibrate the joystick axes, leave accelerometer readings as-is -#[cfg_attr(feature = "defmt_print", derive(defmt::Format))] -#[derive(Debug, Default)] -pub struct NunchukReadingCalibrated { - pub joystick_x: i8, - pub joystick_y: i8, - pub accel_x: u16, // 10-bit - pub accel_y: u16, // 10-bit - pub accel_z: u16, // 10-bit - pub button_c: bool, - pub button_z: bool, -} - -impl NunchukReadingCalibrated { - pub fn new(r: NunchukReading, c: &CalibrationData) -> NunchukReadingCalibrated { - /// Just in case `data` minus `calibration data` is out of range, perform all operations - /// on i16 and clamp to i8 limits before returning - fn ext_u8_sub(a: u8, b: u8) -> i8 { - let res = (a as i16) - (b as i16); - res.clamp(i8::MIN as i16, i8::MAX as i16) as i8 - } - - NunchukReadingCalibrated { - joystick_x: ext_u8_sub(r.joystick_x, c.joystick_x), - joystick_y: ext_u8_sub(r.joystick_y, c.joystick_y), - accel_x: r.accel_x, - accel_y: r.accel_y, // 10-bit - accel_z: r.accel_z, // 10-bit - button_c: r.button_c, - button_z: r.button_z, - } - } -} - -pub struct Nunchuk { - i2cdev: I2C, +pub struct Nunchuk { + interface: Interface, calibration: CalibrationData, } -impl Nunchuk +impl Nunchuk where - T: I2c, + I2C: I2c, + DELAY: embedded_hal::delay::DelayNs, { /// Create a new Wii Nunchuk /// /// This method will open the provide i2c device file and will /// send the required init sequence in order to read data in /// the future. - pub fn new(i2cdev: T, delay: &mut D) -> Result, Error> { + pub fn new(i2cdev: I2C, delay: DELAY) -> Result, Error> { + let interface = Interface::new(i2cdev, delay); let mut nunchuk = Nunchuk { - i2cdev, + interface, calibration: CalibrationData::default(), }; - nunchuk.init(delay)?; + nunchuk.init()?; Ok(nunchuk) } @@ -139,8 +49,8 @@ where /// /// Since each device will have different tolerances, we take a snapshot of some analog data /// to use as the "baseline" center. - pub fn update_calibration(&mut self, delay: &mut D) -> Result<(), Error> { - let data = self.read_report_blocking(delay)?; + pub fn update_calibration(&mut self) -> Result<(), Error> { + let data = self.read_report_blocking()?; self.calibration = CalibrationData { joystick_x: data.joystick_x, @@ -149,98 +59,35 @@ where Ok(()) } - fn set_read_register_address(&mut self, address: u8) -> Result<(), Error> { - self.i2cdev - .write(EXT_I2C_ADDR as u8, &[address]) - .map_err(Error::I2C) - } - - fn set_register(&mut self, reg: u8, val: u8) -> Result<(), Error> { - self.i2cdev - .write(EXT_I2C_ADDR as u8, &[reg, val]) - .map_err(Error::I2C) - } - - fn read_report(&mut self) -> Result> { - let mut buffer: ExtReport = ExtReport::default(); - self.i2cdev - .read(EXT_I2C_ADDR as u8, &mut buffer) - .map_err(Error::I2C) - .and(Ok(buffer)) - } - /// Send the init sequence to the Wii extension controller - pub fn init(&mut self, delay: &mut D) -> Result<(), Error> { + pub fn init(&mut self) -> Result<(), Error> { // These registers must be written to disable encryption.; the documentation is a bit // lacking but it appears this is some kind of handshake to // perform unencrypted data tranfers - // Double all the delays here, we sometimes get connection issues otherwise - delay.delay_us(INTERMESSAGE_DELAY_MICROSEC * 2); - self.set_register(0xF0, 0x55)?; - delay.delay_us(INTERMESSAGE_DELAY_MICROSEC * 2); - self.set_register(0xFB, 0x00)?; - delay.delay_us(INTERMESSAGE_DELAY_MICROSEC * 2); - self.update_calibration(delay)?; - delay.delay_us(INTERMESSAGE_DELAY_MICROSEC * 2); - Ok(()) - } - - fn read_id(&mut self) -> Result> { - self.set_read_register_address(0xfa)?; - let i2c_id = self.read_report()?; - Ok(i2c_id) + self.interface.init()?; + self.update_calibration() } - pub fn identify_controller(&mut self) -> Result, Error> { - let i2c_id = self.read_id()?; - Ok(crate::common::identify_controller(i2c_id)) - } - - /// tell the extension controller to prepare a sample by setting the read cursor to 0 - fn start_sample(&mut self) -> Result<(), Error> { - self.set_read_register_address(0x00)?; - - Ok(()) + pub fn identify_controller(&mut self) -> Result, Error> { + self.interface.identify_controller() } /// Read the button/axis data from the nunchuk - fn read_nunchuk(&mut self) -> Result> { - let buf = self.read_report()?; + fn read_nunchuk(&mut self) -> Result> { + let buf = self.interface.read_report()?; NunchukReading::from_data(&buf).ok_or(Error::InvalidInputData) } - /// Simple helper with no delay. Should work for testing, not sure if it will function on hardware - pub fn read_no_wait(&mut self) -> Result> { - self.start_sample()?; - self.read_nunchuk() - } - - /// Simple helper with no delay. Should work for testing, not sure if it will function on hardware - pub fn read_calibrated_no_wait(&mut self) -> Result> { - Ok(NunchukReadingCalibrated::new( - self.read_no_wait()?, - &self.calibration, - )) - } - /// Simple blocking read helper that will start a sample, wait `INTERMESSAGE_DELAY_MICROSEC`, then read the value - pub fn read_report_blocking( - &mut self, - delay: &mut D, - ) -> Result> { - delay.delay_us(INTERMESSAGE_DELAY_MICROSEC); - self.start_sample()?; - delay.delay_us(INTERMESSAGE_DELAY_MICROSEC); + pub fn read_report_blocking(&mut self) -> Result> { + self.interface.start_sample()?; self.read_nunchuk() } /// Do a read, and report axis values relative to calibration - pub fn read_blocking( - &mut self, - delay: &mut D, - ) -> Result> { + pub fn read_blocking(&mut self) -> Result> { Ok(NunchukReadingCalibrated::new( - self.read_report_blocking(delay)?, + self.read_report_blocking()?, &self.calibration, )) } @@ -250,7 +97,11 @@ where mod tests { use super::*; use crate::test_data; - use embedded_hal_mock::eh1::i2c::{self, Transaction}; + use crate::EXT_I2C_ADDR; + use embedded_hal_mock::eh1::{ + delay::NoopDelay, + i2c::{self, Transaction}, + }; /// There's a certain amount of slop around the center position. /// Allow up to this range without it being an error const ZERO_SLOP: i8 = 5; @@ -262,15 +113,23 @@ mod tests { #[test] fn nunchuck_idle() { let expectations = vec![ + // Reset controller + Transaction::write(EXT_I2C_ADDR as u8, vec![0]), + // Init + Transaction::write(EXT_I2C_ADDR as u8, vec![240, 85]), + Transaction::write(EXT_I2C_ADDR as u8, vec![251, 0]), + // Calibration read + Transaction::write(EXT_I2C_ADDR as u8, vec![0]), + Transaction::read(EXT_I2C_ADDR as u8, test_data::NUNCHUCK_IDLE.to_vec()), + // Read Transaction::write(EXT_I2C_ADDR as u8, vec![0]), Transaction::read(EXT_I2C_ADDR as u8, test_data::NUNCHUCK_IDLE.to_vec()), ]; + let mut mock = i2c::Mock::new(&expectations); - let mut nc = Nunchuk { - i2cdev: mock.clone(), - calibration: CalibrationData::default(), - }; - let report = nc.read_no_wait().unwrap(); + let delay = NoopDelay::new(); + let mut nc = Nunchuk::new(mock.clone(), delay).unwrap(); + let report = nc.read_blocking().unwrap(); assert!(!report.button_c); assert!(!report.button_z); mock.done(); @@ -279,19 +138,22 @@ mod tests { #[test] fn nunchuck_idle_calibrated() { let expectations = vec![ + // Reset controller + Transaction::write(EXT_I2C_ADDR as u8, vec![0]), + // Init + Transaction::write(EXT_I2C_ADDR as u8, vec![240, 85]), + Transaction::write(EXT_I2C_ADDR as u8, vec![251, 0]), + // Calibration read + Transaction::write(EXT_I2C_ADDR as u8, vec![0]), + Transaction::read(EXT_I2C_ADDR as u8, test_data::NUNCHUCK_IDLE.to_vec()), + // Read Transaction::write(EXT_I2C_ADDR as u8, vec![0]), Transaction::read(EXT_I2C_ADDR as u8, test_data::NUNCHUCK_IDLE.to_vec()), ]; let mut mock = i2c::Mock::new(&expectations); - let idle = NunchukReading::from_data(&test_data::NUNCHUCK_IDLE).unwrap(); - let mut nc = Nunchuk { - i2cdev: mock.clone(), - calibration: CalibrationData { - joystick_x: idle.joystick_x, - joystick_y: idle.joystick_y, - }, - }; - let report = nc.read_calibrated_no_wait().unwrap(); + let delay = NoopDelay::new(); + let mut nc = Nunchuk::new(mock.clone(), delay).unwrap(); + let report = nc.read_blocking().unwrap(); assert!(!report.button_c); assert!(!report.button_z); assert_eq!(report.joystick_x, 0); @@ -302,19 +164,22 @@ mod tests { #[test] fn nunchuck_left_calibrated() { let expectations = vec![ + // Reset controller + Transaction::write(EXT_I2C_ADDR as u8, vec![0]), + // Init + Transaction::write(EXT_I2C_ADDR as u8, vec![240, 85]), + Transaction::write(EXT_I2C_ADDR as u8, vec![251, 0]), + // Calibration read + Transaction::write(EXT_I2C_ADDR as u8, vec![0]), + Transaction::read(EXT_I2C_ADDR as u8, test_data::NUNCHUCK_IDLE.to_vec()), + // Read Transaction::write(EXT_I2C_ADDR as u8, vec![0]), Transaction::read(EXT_I2C_ADDR as u8, test_data::NUNCHUCK_JOY_L.to_vec()), ]; let mut mock = i2c::Mock::new(&expectations); - let idle = NunchukReading::from_data(&test_data::NUNCHUCK_IDLE).unwrap(); - let mut nc = Nunchuk { - i2cdev: mock.clone(), - calibration: CalibrationData { - joystick_x: idle.joystick_x, - joystick_y: idle.joystick_y, - }, - }; - let report = nc.read_calibrated_no_wait().unwrap(); + let delay = NoopDelay::new(); + let mut nc = Nunchuk::new(mock.clone(), delay).unwrap(); + let report = nc.read_blocking().unwrap(); assert!(!report.button_c); assert!(!report.button_z); assert!(report.joystick_x < -AXIS_MAX, "x = {}", report.joystick_x); @@ -326,19 +191,22 @@ mod tests { #[test] fn nunchuck_right_calibrated() { let expectations = vec![ + // Reset controller + Transaction::write(EXT_I2C_ADDR as u8, vec![0]), + // Init + Transaction::write(EXT_I2C_ADDR as u8, vec![240, 85]), + Transaction::write(EXT_I2C_ADDR as u8, vec![251, 0]), + // Calibration read + Transaction::write(EXT_I2C_ADDR as u8, vec![0]), + Transaction::read(EXT_I2C_ADDR as u8, test_data::NUNCHUCK_IDLE.to_vec()), + // Read Transaction::write(EXT_I2C_ADDR as u8, vec![0]), Transaction::read(EXT_I2C_ADDR as u8, test_data::NUNCHUCK_JOY_R.to_vec()), ]; let mut mock = i2c::Mock::new(&expectations); - let idle = NunchukReading::from_data(&test_data::NUNCHUCK_IDLE).unwrap(); - let mut nc = Nunchuk { - i2cdev: mock.clone(), - calibration: CalibrationData { - joystick_x: idle.joystick_x, - joystick_y: idle.joystick_y, - }, - }; - let report = nc.read_calibrated_no_wait().unwrap(); + let delay = NoopDelay::new(); + let mut nc = Nunchuk::new(mock.clone(), delay).unwrap(); + let report = nc.read_blocking().unwrap(); assert!(!report.button_c); assert!(!report.button_z); assert!(report.joystick_x > AXIS_MAX, "x = {}", report.joystick_x); @@ -350,19 +218,22 @@ mod tests { #[test] fn nunchuck_up_calibrated() { let expectations = vec![ + // Reset controller + Transaction::write(EXT_I2C_ADDR as u8, vec![0]), + // Init + Transaction::write(EXT_I2C_ADDR as u8, vec![240, 85]), + Transaction::write(EXT_I2C_ADDR as u8, vec![251, 0]), + // Calibration read + Transaction::write(EXT_I2C_ADDR as u8, vec![0]), + Transaction::read(EXT_I2C_ADDR as u8, test_data::NUNCHUCK_IDLE.to_vec()), + // Read Transaction::write(EXT_I2C_ADDR as u8, vec![0]), Transaction::read(EXT_I2C_ADDR as u8, test_data::NUNCHUCK_JOY_U.to_vec()), ]; let mut mock = i2c::Mock::new(&expectations); - let idle = NunchukReading::from_data(&test_data::NUNCHUCK_IDLE).unwrap(); - let mut nc = Nunchuk { - i2cdev: mock.clone(), - calibration: CalibrationData { - joystick_x: idle.joystick_x, - joystick_y: idle.joystick_y, - }, - }; - let report = nc.read_calibrated_no_wait().unwrap(); + let delay = NoopDelay::new(); + let mut nc = Nunchuk::new(mock.clone(), delay).unwrap(); + let report = nc.read_blocking().unwrap(); assert!(!report.button_c); assert!(!report.button_z); assert!(report.joystick_y > AXIS_MAX, "y = {}", report.joystick_y); @@ -374,19 +245,22 @@ mod tests { #[test] fn nunchuck_down_calibrated() { let expectations = vec![ + // Reset controller + Transaction::write(EXT_I2C_ADDR as u8, vec![0]), + // Init + Transaction::write(EXT_I2C_ADDR as u8, vec![240, 85]), + Transaction::write(EXT_I2C_ADDR as u8, vec![251, 0]), + // Calibration read + Transaction::write(EXT_I2C_ADDR as u8, vec![0]), + Transaction::read(EXT_I2C_ADDR as u8, test_data::NUNCHUCK_IDLE.to_vec()), + // Read Transaction::write(EXT_I2C_ADDR as u8, vec![0]), Transaction::read(EXT_I2C_ADDR as u8, test_data::NUNCHUCK_JOY_D.to_vec()), ]; let mut mock = i2c::Mock::new(&expectations); - let idle = NunchukReading::from_data(&test_data::NUNCHUCK_IDLE).unwrap(); - let mut nc = Nunchuk { - i2cdev: mock.clone(), - calibration: CalibrationData { - joystick_x: idle.joystick_x, - joystick_y: idle.joystick_y, - }, - }; - let report = nc.read_calibrated_no_wait().unwrap(); + let delay = NoopDelay::new(); + let mut nc = Nunchuk::new(mock.clone(), delay).unwrap(); + let report = nc.read_blocking().unwrap(); assert!(!report.button_c); assert!(!report.button_z); assert!(report.joystick_y < -AXIS_MAX, "y = {}", report.joystick_y); @@ -398,20 +272,28 @@ mod tests { #[test] fn nunchuck_idle_repeat() { let expectations = vec![ + // Reset controller + Transaction::write(EXT_I2C_ADDR as u8, vec![0]), + // Init + Transaction::write(EXT_I2C_ADDR as u8, vec![240, 85]), + Transaction::write(EXT_I2C_ADDR as u8, vec![251, 0]), + // Calibration read + Transaction::write(EXT_I2C_ADDR as u8, vec![0]), + Transaction::read(EXT_I2C_ADDR as u8, test_data::NUNCHUCK_IDLE.to_vec()), + // Read Transaction::write(EXT_I2C_ADDR as u8, vec![0]), Transaction::read(EXT_I2C_ADDR as u8, test_data::NUNCHUCK_IDLE.to_vec()), Transaction::write(EXT_I2C_ADDR as u8, vec![0]), Transaction::read(EXT_I2C_ADDR as u8, test_data::NUNCHUCK_IDLE.to_vec()), ]; let mut mock = i2c::Mock::new(&expectations); - let mut nc = Nunchuk { - i2cdev: mock.clone(), - calibration: CalibrationData::default(), - }; - let report = nc.read_no_wait().unwrap(); + let delay = NoopDelay::new(); + let mut nc = Nunchuk::new(mock.clone(), delay).unwrap(); + + let report = nc.read_report_blocking().unwrap(); assert!(!report.button_c); assert!(!report.button_z); - let report = nc.read_no_wait().unwrap(); + let report = nc.read_report_blocking().unwrap(); assert!(!report.button_c); assert!(!report.button_z); mock.done(); @@ -420,15 +302,23 @@ mod tests { #[test] fn nunchuck_btn_c() { let expectations = vec![ + // Reset controller + Transaction::write(EXT_I2C_ADDR as u8, vec![0]), + // Init + Transaction::write(EXT_I2C_ADDR as u8, vec![240, 85]), + Transaction::write(EXT_I2C_ADDR as u8, vec![251, 0]), + // Calibration read + Transaction::write(EXT_I2C_ADDR as u8, vec![0]), + Transaction::read(EXT_I2C_ADDR as u8, test_data::NUNCHUCK_IDLE.to_vec()), + // Read Transaction::write(EXT_I2C_ADDR as u8, vec![0]), Transaction::read(EXT_I2C_ADDR as u8, test_data::NUNCHUCK_BTN_C.to_vec()), ]; let mut mock = i2c::Mock::new(&expectations); - let mut nc = Nunchuk { - i2cdev: mock.clone(), - calibration: CalibrationData::default(), - }; - let report = nc.read_no_wait().unwrap(); + let delay = NoopDelay::new(); + let mut nc = Nunchuk::new(mock.clone(), delay).unwrap(); + + let report = nc.read_report_blocking().unwrap(); assert!(report.button_c); assert!(!report.button_z); mock.done(); @@ -437,15 +327,23 @@ mod tests { #[test] fn nunchuck_btn_z() { let expectations = vec![ + // Reset controller + Transaction::write(EXT_I2C_ADDR as u8, vec![0]), + // Init + Transaction::write(EXT_I2C_ADDR as u8, vec![240, 85]), + Transaction::write(EXT_I2C_ADDR as u8, vec![251, 0]), + // Calibration read + Transaction::write(EXT_I2C_ADDR as u8, vec![0]), + Transaction::read(EXT_I2C_ADDR as u8, test_data::NUNCHUCK_IDLE.to_vec()), + // Read Transaction::write(EXT_I2C_ADDR as u8, vec![0]), Transaction::read(EXT_I2C_ADDR as u8, test_data::NUNCHUCK_BTN_Z.to_vec()), ]; let mut mock = i2c::Mock::new(&expectations); - let mut nc = Nunchuk { - i2cdev: mock.clone(), - calibration: CalibrationData::default(), - }; - let report = nc.read_no_wait().unwrap(); + let delay = NoopDelay::new(); + let mut nc = Nunchuk::new(mock.clone(), delay).unwrap(); + + let report = nc.read_report_blocking().unwrap(); assert!(!report.button_c); assert!(report.button_z); mock.done(); diff --git a/wii-ext/tests/nunchuk.rs b/wii-ext/tests/nunchuk.rs new file mode 100644 index 0000000..186d457 --- /dev/null +++ b/wii-ext/tests/nunchuk.rs @@ -0,0 +1,251 @@ +use embedded_hal_mock::eh1::{ + delay::NoopDelay, + i2c::{self, Transaction}, +}; +use wii_ext::{nunchuk::Nunchuk, test_data, EXT_I2C_ADDR}; +/// There's a certain amount of slop around the center position. +/// Allow up to this range without it being an error +const ZERO_SLOP: i8 = 5; +/// The max value at full deflection is ~100, but allow a bit less than that +const AXIS_MAX: i8 = 90; + +// TODO: work out how to test analogue values from joystick and gyro + +#[test] +fn nunchuck_idle() { + let expectations = vec![ + // Reset controller + Transaction::write(EXT_I2C_ADDR as u8, vec![0]), + // Init + Transaction::write(EXT_I2C_ADDR as u8, vec![240, 85]), + Transaction::write(EXT_I2C_ADDR as u8, vec![251, 0]), + // Calibration read + Transaction::write(EXT_I2C_ADDR as u8, vec![0]), + Transaction::read(EXT_I2C_ADDR as u8, test_data::NUNCHUCK_IDLE.to_vec()), + // Read + Transaction::write(EXT_I2C_ADDR as u8, vec![0]), + Transaction::read(EXT_I2C_ADDR as u8, test_data::NUNCHUCK_IDLE.to_vec()), + ]; + + let mut mock = i2c::Mock::new(&expectations); + let delay = NoopDelay::new(); + let mut nc = Nunchuk::new(mock.clone(), delay).unwrap(); + let report = nc.read_blocking().unwrap(); + assert!(!report.button_c); + assert!(!report.button_z); + mock.done(); +} + +#[test] +fn nunchuck_idle_calibrated() { + let expectations = vec![ + // Reset controller + Transaction::write(EXT_I2C_ADDR as u8, vec![0]), + // Init + Transaction::write(EXT_I2C_ADDR as u8, vec![240, 85]), + Transaction::write(EXT_I2C_ADDR as u8, vec![251, 0]), + // Calibration read + Transaction::write(EXT_I2C_ADDR as u8, vec![0]), + Transaction::read(EXT_I2C_ADDR as u8, test_data::NUNCHUCK_IDLE.to_vec()), + // Read + Transaction::write(EXT_I2C_ADDR as u8, vec![0]), + Transaction::read(EXT_I2C_ADDR as u8, test_data::NUNCHUCK_IDLE.to_vec()), + ]; + let mut mock = i2c::Mock::new(&expectations); + let delay = NoopDelay::new(); + let mut nc = Nunchuk::new(mock.clone(), delay).unwrap(); + let report = nc.read_blocking().unwrap(); + assert!(!report.button_c); + assert!(!report.button_z); + assert_eq!(report.joystick_x, 0); + assert_eq!(report.joystick_y, 0); + mock.done(); +} + +#[test] +fn nunchuck_left_calibrated() { + let expectations = vec![ + // Reset controller + Transaction::write(EXT_I2C_ADDR as u8, vec![0]), + // Init + Transaction::write(EXT_I2C_ADDR as u8, vec![240, 85]), + Transaction::write(EXT_I2C_ADDR as u8, vec![251, 0]), + // Calibration read + Transaction::write(EXT_I2C_ADDR as u8, vec![0]), + Transaction::read(EXT_I2C_ADDR as u8, test_data::NUNCHUCK_IDLE.to_vec()), + // Read + Transaction::write(EXT_I2C_ADDR as u8, vec![0]), + Transaction::read(EXT_I2C_ADDR as u8, test_data::NUNCHUCK_JOY_L.to_vec()), + ]; + let mut mock = i2c::Mock::new(&expectations); + let delay = NoopDelay::new(); + let mut nc = Nunchuk::new(mock.clone(), delay).unwrap(); + let report = nc.read_blocking().unwrap(); + assert!(!report.button_c); + assert!(!report.button_z); + assert!(report.joystick_x < -AXIS_MAX, "x = {}", report.joystick_x); + assert!(report.joystick_y > -ZERO_SLOP, "y = {}", report.joystick_y); + assert!(report.joystick_y < ZERO_SLOP, "y = {}", report.joystick_y); + mock.done(); +} + +#[test] +fn nunchuck_right_calibrated() { + let expectations = vec![ + // Reset controller + Transaction::write(EXT_I2C_ADDR as u8, vec![0]), + // Init + Transaction::write(EXT_I2C_ADDR as u8, vec![240, 85]), + Transaction::write(EXT_I2C_ADDR as u8, vec![251, 0]), + // Calibration read + Transaction::write(EXT_I2C_ADDR as u8, vec![0]), + Transaction::read(EXT_I2C_ADDR as u8, test_data::NUNCHUCK_IDLE.to_vec()), + // Read + Transaction::write(EXT_I2C_ADDR as u8, vec![0]), + Transaction::read(EXT_I2C_ADDR as u8, test_data::NUNCHUCK_JOY_R.to_vec()), + ]; + let mut mock = i2c::Mock::new(&expectations); + let delay = NoopDelay::new(); + let mut nc = Nunchuk::new(mock.clone(), delay).unwrap(); + let report = nc.read_blocking().unwrap(); + assert!(!report.button_c); + assert!(!report.button_z); + assert!(report.joystick_x > AXIS_MAX, "x = {}", report.joystick_x); + assert!(report.joystick_y > -ZERO_SLOP, "y = {}", report.joystick_y); + assert!(report.joystick_y < ZERO_SLOP, "y = {}", report.joystick_y); + mock.done(); +} + +#[test] +fn nunchuck_up_calibrated() { + let expectations = vec![ + // Reset controller + Transaction::write(EXT_I2C_ADDR as u8, vec![0]), + // Init + Transaction::write(EXT_I2C_ADDR as u8, vec![240, 85]), + Transaction::write(EXT_I2C_ADDR as u8, vec![251, 0]), + // Calibration read + Transaction::write(EXT_I2C_ADDR as u8, vec![0]), + Transaction::read(EXT_I2C_ADDR as u8, test_data::NUNCHUCK_IDLE.to_vec()), + // Read + Transaction::write(EXT_I2C_ADDR as u8, vec![0]), + Transaction::read(EXT_I2C_ADDR as u8, test_data::NUNCHUCK_JOY_U.to_vec()), + ]; + let mut mock = i2c::Mock::new(&expectations); + let delay = NoopDelay::new(); + let mut nc = Nunchuk::new(mock.clone(), delay).unwrap(); + let report = nc.read_blocking().unwrap(); + assert!(!report.button_c); + assert!(!report.button_z); + assert!(report.joystick_y > AXIS_MAX, "y = {}", report.joystick_y); + assert!(report.joystick_x > -ZERO_SLOP, "x = {}", report.joystick_x); + assert!(report.joystick_x < ZERO_SLOP, "x = {}", report.joystick_x); + mock.done(); +} + +#[test] +fn nunchuck_down_calibrated() { + let expectations = vec![ + // Reset controller + Transaction::write(EXT_I2C_ADDR as u8, vec![0]), + // Init + Transaction::write(EXT_I2C_ADDR as u8, vec![240, 85]), + Transaction::write(EXT_I2C_ADDR as u8, vec![251, 0]), + // Calibration read + Transaction::write(EXT_I2C_ADDR as u8, vec![0]), + Transaction::read(EXT_I2C_ADDR as u8, test_data::NUNCHUCK_IDLE.to_vec()), + // Read + Transaction::write(EXT_I2C_ADDR as u8, vec![0]), + Transaction::read(EXT_I2C_ADDR as u8, test_data::NUNCHUCK_JOY_D.to_vec()), + ]; + let mut mock = i2c::Mock::new(&expectations); + let delay = NoopDelay::new(); + let mut nc = Nunchuk::new(mock.clone(), delay).unwrap(); + let report = nc.read_blocking().unwrap(); + assert!(!report.button_c); + assert!(!report.button_z); + assert!(report.joystick_y < -AXIS_MAX, "y = {}", report.joystick_y); + assert!(report.joystick_x > -ZERO_SLOP, "x = {}", report.joystick_x); + assert!(report.joystick_x < ZERO_SLOP, "x = {}", report.joystick_x); + mock.done(); +} + +#[test] +fn nunchuck_idle_repeat() { + let expectations = vec![ + // Reset controller + Transaction::write(EXT_I2C_ADDR as u8, vec![0]), + // Init + Transaction::write(EXT_I2C_ADDR as u8, vec![240, 85]), + Transaction::write(EXT_I2C_ADDR as u8, vec![251, 0]), + // Calibration read + Transaction::write(EXT_I2C_ADDR as u8, vec![0]), + Transaction::read(EXT_I2C_ADDR as u8, test_data::NUNCHUCK_IDLE.to_vec()), + // Read + Transaction::write(EXT_I2C_ADDR as u8, vec![0]), + Transaction::read(EXT_I2C_ADDR as u8, test_data::NUNCHUCK_IDLE.to_vec()), + Transaction::write(EXT_I2C_ADDR as u8, vec![0]), + Transaction::read(EXT_I2C_ADDR as u8, test_data::NUNCHUCK_IDLE.to_vec()), + ]; + let mut mock = i2c::Mock::new(&expectations); + let delay = NoopDelay::new(); + let mut nc = Nunchuk::new(mock.clone(), delay).unwrap(); + + let report = nc.read_report_blocking().unwrap(); + assert!(!report.button_c); + assert!(!report.button_z); + let report = nc.read_report_blocking().unwrap(); + assert!(!report.button_c); + assert!(!report.button_z); + mock.done(); +} + +#[test] +fn nunchuck_btn_c() { + let expectations = vec![ + // Reset controller + Transaction::write(EXT_I2C_ADDR as u8, vec![0]), + // Init + Transaction::write(EXT_I2C_ADDR as u8, vec![240, 85]), + Transaction::write(EXT_I2C_ADDR as u8, vec![251, 0]), + // Calibration read + Transaction::write(EXT_I2C_ADDR as u8, vec![0]), + Transaction::read(EXT_I2C_ADDR as u8, test_data::NUNCHUCK_IDLE.to_vec()), + // Read + Transaction::write(EXT_I2C_ADDR as u8, vec![0]), + Transaction::read(EXT_I2C_ADDR as u8, test_data::NUNCHUCK_BTN_C.to_vec()), + ]; + let mut mock = i2c::Mock::new(&expectations); + let delay = NoopDelay::new(); + let mut nc = Nunchuk::new(mock.clone(), delay).unwrap(); + + let report = nc.read_report_blocking().unwrap(); + assert!(report.button_c); + assert!(!report.button_z); + mock.done(); +} + +#[test] +fn nunchuck_btn_z() { + let expectations = vec![ + // Reset controller + Transaction::write(EXT_I2C_ADDR as u8, vec![0]), + // Init + Transaction::write(EXT_I2C_ADDR as u8, vec![240, 85]), + Transaction::write(EXT_I2C_ADDR as u8, vec![251, 0]), + // Calibration read + Transaction::write(EXT_I2C_ADDR as u8, vec![0]), + Transaction::read(EXT_I2C_ADDR as u8, test_data::NUNCHUCK_IDLE.to_vec()), + // Read + Transaction::write(EXT_I2C_ADDR as u8, vec![0]), + Transaction::read(EXT_I2C_ADDR as u8, test_data::NUNCHUCK_BTN_Z.to_vec()), + ]; + let mut mock = i2c::Mock::new(&expectations); + let delay = NoopDelay::new(); + let mut nc = Nunchuk::new(mock.clone(), delay).unwrap(); + + let report = nc.read_report_blocking().unwrap(); + assert!(!report.button_c); + assert!(report.button_z); + mock.done(); +}