From 3324de3e9d422532f232c706f2cde6da5f5de4ae Mon Sep 17 00:00:00 2001 From: Christoffer Martinsson Date: Tue, 16 Sep 2025 11:21:17 +0200 Subject: [PATCH] Added USB idle timeout --- rp2040/src/buttons.rs | 3 +- rp2040/src/calibration.rs | 18 ++--- rp2040/src/hardware.rs | 3 + rp2040/src/lib.rs | 8 +-- rp2040/src/main.rs | 63 ++++++++++++----- rp2040/src/mapping.rs | 1 - rp2040/src/status.rs | 113 ++++++++++++++++++++---------- rp2040/src/storage.rs | 53 +++++++------- rp2040/src/usb_joystick_device.rs | 10 ++- rp2040/src/usb_report.rs | 36 ++++++---- 10 files changed, 187 insertions(+), 121 deletions(-) diff --git a/rp2040/src/buttons.rs b/rp2040/src/buttons.rs index 5cda510..9b3b538 100644 --- a/rp2040/src/buttons.rs +++ b/rp2040/src/buttons.rs @@ -309,8 +309,7 @@ fn update_button_press_type(button: &mut Button, current_time: u32) { !button.pressed && elapsed >= USB_MIN_HOLD_MS }; - if button.usb_press_active && should_release - { + if button.usb_press_active && should_release { button.usb_changed = true; button.usb_press_active = false; button.active_usb_button = 0; diff --git a/rp2040/src/calibration.rs b/rp2040/src/calibration.rs index 2472575..d39ff54 100644 --- a/rp2040/src/calibration.rs +++ b/rp2040/src/calibration.rs @@ -11,7 +11,7 @@ //! behind simple `read_fn`/`write_fn` closures so the code can be unit‑tested on //! a host without hardware. -use crate::axis::{GIMBAL_MODE_M7, GIMBAL_MODE_M10, GimbalAxis}; +use crate::axis::{GimbalAxis, GIMBAL_MODE_M10, GIMBAL_MODE_M7}; use crate::hardware::NBR_OF_GIMBAL_AXIS; use crate::storage; use dyn_smooth::DynamicSmootherEcoI32; @@ -148,10 +148,8 @@ impl CalibrationManager { /// Load per‑axis calibration data from EEPROM. /// /// Updates the provided axes with loaded values; retains defaults on error. - pub fn load_axis_calibration( - axes: &mut [GimbalAxis; NBR_OF_GIMBAL_AXIS], - read_fn: &mut F, - ) where + pub fn load_axis_calibration(axes: &mut [GimbalAxis; NBR_OF_GIMBAL_AXIS], read_fn: &mut F) + where F: FnMut(u32) -> Result, { for (index, axis) in axes.iter_mut().enumerate() { @@ -176,7 +174,6 @@ impl CalibrationManager { storage::read_gimbal_mode(read_fn).unwrap_or(GIMBAL_MODE_M10) } - /// Reset each axis calibration to its current smoothed center. fn reset_axis_calibration( &self, @@ -342,11 +339,6 @@ mod tests { assert_eq!(manager.get_gimbal_mode(), GIMBAL_MODE_M10); } - - - - - #[test] fn test_load_axis_calibration_success() { let mut axes = [GimbalAxis::new(); NBR_OF_GIMBAL_AXIS]; @@ -362,7 +354,7 @@ mod tests { 4 => Ok(11), // max high byte 5 => Ok(208), // center low byte (2000 = 0x07D0, low byte = 208) 6 => Ok(7), // center high byte - _ => Err(()), // Other addresses fail + _ => Err(()), // Other addresses fail } }; @@ -402,7 +394,7 @@ mod tests { // Mock successful EEPROM read for M7 mode let mut read_fn = |addr: u32| { match addr { - 25 => Ok(GIMBAL_MODE_M7), // Gimbal mode stored at address 25 (EEPROM_DATA_LENGTH) + 25 => Ok(GIMBAL_MODE_M7), // Gimbal mode stored at address 25 (EEPROM_DATA_LENGTH) _ => Err(()), } }; diff --git a/rp2040/src/hardware.rs b/rp2040/src/hardware.rs index 5c112c6..713a440 100644 --- a/rp2040/src/hardware.rs +++ b/rp2040/src/hardware.rs @@ -95,6 +95,9 @@ pub mod timers { /// USB HID report interval (ms). pub const USB_UPDATE_INTERVAL_MS: u32 = 10; + + /// USB activity timeout (ms) - stop sending reports after this period of inactivity. + pub const USB_ACTIVITY_TIMEOUT_MS: u32 = 10_000; // 10 seconds } // ==================== USB DEVICE CONFIGURATION ==================== diff --git a/rp2040/src/lib.rs b/rp2040/src/lib.rs index 39b9ee3..a5164a9 100644 --- a/rp2040/src/lib.rs +++ b/rp2040/src/lib.rs @@ -8,8 +8,6 @@ /// Axis processing for gimbal and virtual axes (smoothing, expo, holds). pub mod axis; -/// Button-to-USB mapping tables and HAT constants. -pub mod mapping; /// Row/column scanned button matrix driver with debouncing. pub mod button_matrix; /// Button state machine (short/long press, timing, special actions). @@ -20,6 +18,8 @@ pub mod calibration; pub mod expo; /// Hardware constants, pin definitions, timing, and helper macros. pub mod hardware; +/// Button-to-USB mapping tables and HAT constants. +pub mod mapping; /// WS2812 status LED driver and status model. pub mod status; /// EEPROM storage layout and read/write helpers. @@ -32,9 +32,9 @@ pub mod usb_report; /// Re-exports for convenient access in `main` and downstream consumers. pub use axis::{AxisManager, GimbalAxis, VirtualAxis}; pub use calibration::CalibrationManager; -pub use expo::{ExpoLUT, apply_expo_curve, constrain, generate_expo_lut}; +pub use expo::{apply_expo_curve, constrain, generate_expo_lut, ExpoLUT}; pub use storage::{read_axis_calibration, read_gimbal_mode, write_calibration_data, StorageError}; -pub use usb_report::{get_joystick_report, axis_12bit_to_i16}; +pub use usb_report::{axis_12bit_to_i16, get_joystick_report}; /// Common ADC range constants used across modules. pub const ADC_MIN: u16 = 0; diff --git a/rp2040/src/main.rs b/rp2040/src/main.rs index 89fc742..b523ac6 100644 --- a/rp2040/src/main.rs +++ b/rp2040/src/main.rs @@ -233,6 +233,8 @@ fn main() -> ! { let mut usb_activity: bool = false; let mut usb_active: bool = false; let mut vt_enable: bool = false; + let mut idle_mode: bool = false; + let mut usb_activity_timeout_count: u32 = 0; let mut axis_manager = AxisManager::new(); let mut button_manager = ButtonManager::new(); @@ -285,6 +287,11 @@ fn main() -> ! { // Handle USB device polling and maintain connection state if usb_dev.poll(&mut [&mut usb_hid_joystick]) { + if !usb_active { + usb_activity = true; // Force initial report + idle_mode = false; + usb_activity_timeout_count = 0; + } usb_active = true; } @@ -329,6 +336,7 @@ fn main() -> ! { let system_state = SystemState { usb_active, + idle_mode, calibration_active: calibration_manager.is_active(), throttle_hold_enable: axis_manager.throttle_hold_enable, vt_enable, @@ -424,16 +432,22 @@ fn main() -> ! { // Process gimbal axes through calibration, expo curves, and scaling if axis_manager.process_axis_values(&smoother, &expo_lut) { usb_activity = true; + usb_activity_timeout_count = 0; // Reset timeout on real input activity + idle_mode = false; } // Update virtual axes based on front button states if axis_manager.update_virtual_axes(button_manager.buttons(), vt_enable) { usb_activity = true; + usb_activity_timeout_count = 0; // Reset timeout on real input activity + idle_mode = false; } // Process button logic (press types, timing, USB mapping) if button_manager.process_button_logic_with_timer(&timer) { usb_activity = true; + usb_activity_timeout_count = 0; // Reset timeout on real input activity + idle_mode = false; } } @@ -450,25 +464,38 @@ fn main() -> ! { // - Virtual throttle mode handling // Only transmit USB reports when input activity is detected - if usb_update_count_down.wait().is_ok() && usb_activity { - let virtual_ry_value = axis_manager.get_virtual_ry_value(&expo_lut_virtual); - let virtual_rz_value = axis_manager.get_virtual_rz_value(&expo_lut_virtual); + let usb_tick = usb_update_count_down.wait().is_ok(); - match usb_hid_joystick.device().write_report(&get_joystick_report( - button_manager.buttons_mut(), - &mut axis_manager.axes, - virtual_ry_value, - virtual_rz_value, - &vt_enable, - )) { - Err(UsbHidError::WouldBlock) => {} - Ok(_) => {} - Err(e) => { - status_led.update(StatusMode::Error); - core::panic!("Failed to write joystick report: {:?}", e) - } - }; - usb_activity = false; + if usb_tick && usb_activity { + // Check if we've exceeded the activity timeout + usb_activity_timeout_count += timers::USB_UPDATE_INTERVAL_MS; + if usb_activity_timeout_count >= timers::USB_ACTIVITY_TIMEOUT_MS { + usb_activity = false; // Stop sending reports after timeout + usb_activity_timeout_count = 0; + idle_mode = true; + } else { + let virtual_ry_value = axis_manager.get_virtual_ry_value(&expo_lut_virtual); + let virtual_rz_value = axis_manager.get_virtual_rz_value(&expo_lut_virtual); + + match usb_hid_joystick.device().write_report(&get_joystick_report( + button_manager.buttons_mut(), + &mut axis_manager.axes, + virtual_ry_value, + virtual_rz_value, + &vt_enable, + )) { + Err(UsbHidError::WouldBlock) => {} + Ok(_) => {} + Err(e) => { + status_led.update(StatusMode::Error); + core::panic!("Failed to write joystick report: {:?}", e) + } + }; + } + } else if usb_tick && usb_active { + idle_mode = true; + } else if usb_tick { + idle_mode = false; } } } diff --git a/rp2040/src/mapping.rs b/rp2040/src/mapping.rs index a934c95..c8adb7c 100644 --- a/rp2040/src/mapping.rs +++ b/rp2040/src/mapping.rs @@ -91,7 +91,6 @@ pub const USB_HAT_RIGHT: usize = 34; pub const USB_HAT_DOWN: usize = 35; pub const USB_HAT_LEFT: usize = 36; - use crate::buttons::Button; /// Configure USB button mappings for all buttons. diff --git a/rp2040/src/status.rs b/rp2040/src/status.rs index f54d92a..ae81bb3 100644 --- a/rp2040/src/status.rs +++ b/rp2040/src/status.rs @@ -6,9 +6,9 @@ use rp2040_hal::{ gpio::AnyPin, - pio::{PIO, PIOExt, StateMachineIndex, UninitStateMachine}, + pio::{PIOExt, StateMachineIndex, UninitStateMachine, PIO}, }; -use smart_leds::{RGB8, SmartLedsWrite}; +use smart_leds::{SmartLedsWrite, RGB8}; use ws2812_pio::Ws2812Direct; /// Status LED modes with clear semantics. @@ -20,36 +20,57 @@ pub enum StatusMode { NormalFlash = 2, Activity = 3, ActivityFlash = 4, - Other = 5, - OtherFlash = 6, - Warning = 7, - Error = 8, - Bootloader = 9, + Idle = 5, + Other = 6, + OtherFlash = 7, + Warning = 8, + Error = 9, + Bootloader = 10, } /// Aggregate system state for LED status indication. #[derive(Clone, Copy)] pub struct SystemState { pub usb_active: bool, + pub idle_mode: bool, pub calibration_active: bool, pub throttle_hold_enable: bool, pub vt_enable: bool, } /// Color definitions for different status modes. -const LED_COLORS: [RGB8; 10] = [ - RGB8 { r: 0, g: 0, b: 0 }, // Off - RGB8 { r: 10, g: 7, b: 0 }, // Normal (Green) - RGB8 { r: 10, g: 7, b: 0 }, // NormalFlash (Green) - RGB8 { r: 10, g: 4, b: 10 }, // Activity (Blue) - RGB8 { r: 10, g: 4, b: 10 }, // ActivityFlash (Blue) - RGB8 { r: 5, g: 10, b: 0 }, // Other (Orange) - RGB8 { r: 5, g: 10, b: 0 }, // OtherFlash (Orange) - RGB8 { r: 2, g: 20, b: 0 }, // Warning (Red) - RGB8 { r: 2, g: 20, b: 0 }, // Error (Red) - RGB8 { r: 0, g: 10, b: 10 }, // Bootloader (Purple) +const LED_COLORS: [RGB8; 11] = [ + RGB8 { r: 0, g: 0, b: 0 }, // Off + RGB8 { r: 10, g: 7, b: 0 }, // Normal (Green) + RGB8 { r: 10, g: 7, b: 0 }, // NormalFlash (Green) + RGB8 { r: 10, g: 4, b: 10 }, // Activity (Blue) + RGB8 { r: 10, g: 4, b: 10 }, // ActivityFlash (Blue) + RGB8 { r: 10, g: 7, b: 0 }, // Idle (Green flash) + RGB8 { r: 5, g: 10, b: 0 }, // Other (Orange) + RGB8 { r: 5, g: 10, b: 0 }, // OtherFlash (Orange) + RGB8 { r: 2, g: 20, b: 0 }, // Warning (Red) + RGB8 { r: 2, g: 20, b: 0 }, // Error (Red) + RGB8 { r: 0, g: 10, b: 10 }, // Bootloader (Purple) ]; +fn determine_mode(system_state: SystemState) -> StatusMode { + if system_state.calibration_active { + StatusMode::ActivityFlash + } else if system_state.idle_mode { + StatusMode::Idle + } else if !system_state.usb_active { + StatusMode::NormalFlash + } else if system_state.vt_enable { + StatusMode::Activity + } else if system_state.throttle_hold_enable { + StatusMode::Other + } else if system_state.usb_active { + StatusMode::Normal + } else { + StatusMode::Off + } +} + /// Status LED driver with self-contained state management. pub struct StatusLed where @@ -93,20 +114,7 @@ where /// Update LED based on system state and current time (ms). pub fn update_from_system_state(&mut self, system_state: SystemState, current_time: u32) { - let desired_mode = if system_state.calibration_active { - StatusMode::ActivityFlash - } else if !system_state.usb_active { - StatusMode::NormalFlash - } else if system_state.usb_active && system_state.vt_enable { - StatusMode::Activity - } else if system_state.usb_active && system_state.throttle_hold_enable { - StatusMode::Other - } else if system_state.usb_active { - StatusMode::Normal - } else { - StatusMode::Off - }; - + let desired_mode = determine_mode(system_state); self.set_mode(desired_mode, current_time); } @@ -133,6 +141,7 @@ where // Flashing modes - toggle between on and off StatusMode::NormalFlash | StatusMode::ActivityFlash + | StatusMode::Idle | StatusMode::OtherFlash | StatusMode::Warning => { if self.flash_state { @@ -143,7 +152,7 @@ where self.write_color(LED_COLORS[StatusMode::Off as usize]); } self.flash_state = !self.flash_state; - }, + } // Solid modes - just show the color _ => { self.write_color(LED_COLORS[self.current_mode as usize]); @@ -167,11 +176,10 @@ where match self.current_mode { StatusMode::NormalFlash | StatusMode::ActivityFlash + | StatusMode::Idle | StatusMode::OtherFlash - | StatusMode::Warning => { - current_time.saturating_sub(last_time) >= 500 - }, - _ => false // Non-flashing modes don't need periodic updates + | StatusMode::Warning => current_time.saturating_sub(last_time) >= 500, + _ => false, // Non-flashing modes don't need periodic updates } } } @@ -195,3 +203,34 @@ where self.set_mode(mode, 0); } } + +#[cfg(all(test, feature = "std"))] +mod tests { + use super::*; + + #[test] + fn idle_mode_selected_when_usb_idle() { + let state = SystemState { + usb_active: true, + idle_mode: true, + calibration_active: false, + throttle_hold_enable: false, + vt_enable: false, + }; + + assert_eq!(determine_mode(state), StatusMode::Idle); + } + + #[test] + fn calibration_has_priority_over_idle() { + let state = SystemState { + usb_active: true, + idle_mode: true, + calibration_active: true, + throttle_hold_enable: false, + vt_enable: false, + }; + + assert_eq!(determine_mode(state), StatusMode::ActivityFlash); + } +} diff --git a/rp2040/src/storage.rs b/rp2040/src/storage.rs index 21dad8f..d7e8eb4 100644 --- a/rp2040/src/storage.rs +++ b/rp2040/src/storage.rs @@ -28,7 +28,7 @@ pub enum StorageError { /// Returns (min, max, center) values as u16. pub fn read_axis_calibration( read_byte_fn: &mut dyn FnMut(u32) -> Result, - axis_index: usize + axis_index: usize, ) -> Result<(u16, u16, u16), StorageError> { // Original format uses: base = axis_index * 6, addresses = base+1,2,3,4,5,6 let base = axis_index as u32 * 6; @@ -42,10 +42,9 @@ pub fn read_axis_calibration( /// Read gimbal mode from EEPROM. pub fn read_gimbal_mode( - read_byte_fn: &mut dyn FnMut(u32) -> Result + read_byte_fn: &mut dyn FnMut(u32) -> Result, ) -> Result { - read_byte_fn(GIMBAL_MODE_OFFSET) - .map_err(|_| StorageError::ReadError) + read_byte_fn(GIMBAL_MODE_OFFSET).map_err(|_| StorageError::ReadError) } /// Write all calibration data and gimbal mode to EEPROM. @@ -53,7 +52,7 @@ pub fn read_gimbal_mode( pub fn write_calibration_data( write_page_fn: &mut dyn FnMut(u32, &[u8]) -> Result<(), ()>, axis_data: &[(u16, u16, u16)], - gimbal_mode: u8 + gimbal_mode: u8, ) -> Result<(), StorageError> { let mut eeprom_data: [u8; EEPROM_DATA_LENGTH] = [0; EEPROM_DATA_LENGTH]; @@ -73,28 +72,23 @@ pub fn write_calibration_data( eeprom_data[EEPROM_DATA_LENGTH - 1] = gimbal_mode; // Write entire page to EEPROM - write_page_fn(0x01, &eeprom_data) - .map_err(|_| StorageError::WriteError) + write_page_fn(0x01, &eeprom_data).map_err(|_| StorageError::WriteError) } // ==================== HELPER FUNCTIONS ==================== - /// Read a u16 value from EEPROM in little‑endian (low then high byte) format. fn read_u16_with_closure( read_byte_fn: &mut dyn FnMut(u32) -> Result, low_addr: u32, - high_addr: u32 + high_addr: u32, ) -> Result { - let low_byte = read_byte_fn(low_addr) - .map_err(|_| StorageError::ReadError)? as u16; - let high_byte = read_byte_fn(high_addr) - .map_err(|_| StorageError::ReadError)? as u16; + let low_byte = read_byte_fn(low_addr).map_err(|_| StorageError::ReadError)? as u16; + let high_byte = read_byte_fn(high_addr).map_err(|_| StorageError::ReadError)? as u16; Ok(low_byte | (high_byte << 8)) } - // ==================== TESTS ==================== #[cfg(all(test, feature = "std"))] @@ -120,14 +114,14 @@ mod tests { let mut buffer = [0u8; 4]; let value = 0x1234u16; - buffer[0] = value as u8; // 0x34 (low byte) - buffer[1] = (value >> 8) as u8; // 0x12 (high byte) + buffer[0] = value as u8; // 0x34 (low byte) + buffer[1] = (value >> 8) as u8; // 0x12 (high byte) assert_eq!(buffer[0], 0x34); assert_eq!(buffer[1], 0x12); let value = 0xABCDu16; - buffer[2] = value as u8; // 0xCD (low byte) - buffer[3] = (value >> 8) as u8; // 0xAB (high byte) + buffer[2] = value as u8; // 0xCD (low byte) + buffer[3] = (value >> 8) as u8; // 0xAB (high byte) assert_eq!(buffer[2], 0xCD); assert_eq!(buffer[3], 0xAB); } @@ -147,7 +141,12 @@ mod tests { #[test] fn test_calibration_data_format() { // Test that calibration data format matches original (addresses base+1,2,3,4,5,6) - let test_data = [(100, 3900, 2000), (150, 3850, 2048), (200, 3800, 1900), (250, 3750, 2100)]; + let test_data = [ + (100, 3900, 2000), + (150, 3850, 2048), + (200, 3800, 1900), + (250, 3750, 2100), + ]; let mut buffer = [0u8; EEPROM_DATA_LENGTH]; // Pack data using original format @@ -162,8 +161,8 @@ mod tests { } // Verify first axis data (addresses 1-6) - assert_eq!(buffer[1], 100 as u8); // min low - assert_eq!(buffer[2], 0); // min high + assert_eq!(buffer[1], 100 as u8); // min low + assert_eq!(buffer[2], 0); // min high assert_eq!(buffer[3], (3900 & 0xFF) as u8); // max low assert_eq!(buffer[4], (3900 >> 8) as u8); // max high assert_eq!(buffer[5], (2000 & 0xFF) as u8); // center low @@ -194,12 +193,12 @@ mod tests { // Mock EEPROM data: axis 0 with min=100, max=4000, center=2050 // Original format: addresses 1,2,3,4,5,6 for axis 0 let mut mock_data = [0u8; 7]; // Need indices 0-6 - mock_data[1] = 100; // min low byte - mock_data[2] = 0; // min high byte - mock_data[3] = 160; // max low byte (4000 = 0x0FA0) - mock_data[4] = 15; // max high byte - mock_data[5] = 2; // center low byte (2050 = 0x0802) - mock_data[6] = 8; // center high byte + mock_data[1] = 100; // min low byte + mock_data[2] = 0; // min high byte + mock_data[3] = 160; // max low byte (4000 = 0x0FA0) + mock_data[4] = 15; // max high byte + mock_data[5] = 2; // center low byte (2050 = 0x0802) + mock_data[6] = 8; // center high byte let mut read_fn = |addr: u32| -> Result { if addr < mock_data.len() as u32 { diff --git a/rp2040/src/usb_joystick_device.rs b/rp2040/src/usb_joystick_device.rs index bb7a7f2..51aa5b7 100644 --- a/rp2040/src/usb_joystick_device.rs +++ b/rp2040/src/usb_joystick_device.rs @@ -182,12 +182,10 @@ pub struct JoystickConfig<'a> { impl Default for JoystickConfig<'_> { fn default() -> Self { Self::new( - unwrap!( - unwrap!(InterfaceBuilder::new(JOYSTICK_DESCRIPTOR)) - .boot_device(InterfaceProtocol::None) - .description("Joystick") - .in_endpoint(10.millis()) - ) + unwrap!(unwrap!(InterfaceBuilder::new(JOYSTICK_DESCRIPTOR)) + .boot_device(InterfaceProtocol::None) + .description("Joystick") + .in_endpoint(10.millis())) .without_out_endpoint() .build(), ) diff --git a/rp2040/src/usb_report.rs b/rp2040/src/usb_report.rs index e0f5c47..e923d94 100644 --- a/rp2040/src/usb_report.rs +++ b/rp2040/src/usb_report.rs @@ -4,10 +4,13 @@ //! that matches the HID descriptor defined in `usb_joystick_device.rs`. //! Also contains support for virtual throttle mode and HAT directions. -use crate::axis::{GimbalAxis, remap, GIMBAL_AXIS_LEFT_X, GIMBAL_AXIS_LEFT_Y, GIMBAL_AXIS_RIGHT_X, GIMBAL_AXIS_RIGHT_Y}; +use crate::axis::{ + remap, GimbalAxis, GIMBAL_AXIS_LEFT_X, GIMBAL_AXIS_LEFT_Y, GIMBAL_AXIS_RIGHT_X, + GIMBAL_AXIS_RIGHT_Y, +}; use crate::buttons::{Button, TOTAL_BUTTONS}; -use crate::mapping::{USB_HAT_UP, USB_HAT_LEFT}; -use crate::hardware::{ADC_MIN, ADC_MAX, AXIS_CENTER}; +use crate::hardware::{ADC_MAX, ADC_MIN, AXIS_CENTER}; +use crate::mapping::{USB_HAT_LEFT, USB_HAT_UP}; use crate::usb_joystick_device::JoystickReport; // ==================== USB REPORT GENERATION ==================== @@ -130,7 +133,6 @@ pub fn get_joystick_report( } } - // ==================== TESTS ==================== #[cfg(all(test, feature = "std"))] @@ -176,7 +178,7 @@ mod tests { // Test reverse remapping behavior: when out_min > out_max, constrain will // clamp results to the "valid" range based on the constraint logic - assert_eq!(remap(0, 0, 1000, 2000, 0), 0); // Calculated 2000, constrained to 0 + assert_eq!(remap(0, 0, 1000, 2000, 0), 0); // Calculated 2000, constrained to 0 assert_eq!(remap(1000, 0, 1000, 2000, 0), 2000); // Calculated 0, constrained to 2000 } @@ -195,7 +197,8 @@ mod tests { let virtual_rz = 2000; let vt_enable = false; - let report = get_joystick_report(&mut buttons, &mut axes, virtual_ry, virtual_rz, &vt_enable); + let report = + get_joystick_report(&mut buttons, &mut axes, virtual_ry, virtual_rz, &vt_enable); // Verify axis conversions assert_eq!(report.x, axis_12bit_to_i16(2048)); @@ -221,7 +224,8 @@ mod tests { let virtual_rz = 0; let vt_enable = true; - let report = get_joystick_report(&mut buttons, &mut axes, virtual_ry, virtual_rz, &vt_enable); + let report = + get_joystick_report(&mut buttons, &mut axes, virtual_ry, virtual_rz, &vt_enable); // In VT mode, z should be 0 assert_eq!(report.z, 0); @@ -243,7 +247,8 @@ mod tests { let virtual_rz = 0; let vt_enable = true; - let report = get_joystick_report(&mut buttons, &mut axes, virtual_ry, virtual_rz, &vt_enable); + let report = + get_joystick_report(&mut buttons, &mut axes, virtual_ry, virtual_rz, &vt_enable); // In VT mode, z should be 0 assert_eq!(report.z, 0); @@ -269,7 +274,8 @@ mod tests { let virtual_rz = 0; let vt_enable = false; - let report = get_joystick_report(&mut buttons, &mut axes, virtual_ry, virtual_rz, &vt_enable); + let report = + get_joystick_report(&mut buttons, &mut axes, virtual_ry, virtual_rz, &vt_enable); // Check button bits are set correctly assert_eq!(report.buttons & (1 << 0), 1 << 0); // Button 1 @@ -290,7 +296,8 @@ mod tests { let virtual_rz = 0; let vt_enable = false; - let report = get_joystick_report(&mut buttons, &mut axes, virtual_ry, virtual_rz, &vt_enable); + let report = + get_joystick_report(&mut buttons, &mut axes, virtual_ry, virtual_rz, &vt_enable); // Check HAT direction is set correctly let expected_hat = (USB_HAT_UP as u8 - USB_HAT_UP as u8) * 2; // Should be 0 (up) @@ -311,7 +318,8 @@ mod tests { let virtual_rz = 0; let vt_enable = false; - let report = get_joystick_report(&mut buttons, &mut axes, virtual_ry, virtual_rz, &vt_enable); + let report = + get_joystick_report(&mut buttons, &mut axes, virtual_ry, virtual_rz, &vt_enable); // Check long press button is handled assert_eq!(report.buttons & (1 << 2), 1 << 2); // Button 3 @@ -350,7 +358,8 @@ mod tests { let virtual_rz = 0; let vt_enable = false; - let report = get_joystick_report(&mut buttons, &mut axes, virtual_ry, virtual_rz, &vt_enable); + let report = + get_joystick_report(&mut buttons, &mut axes, virtual_ry, virtual_rz, &vt_enable); // Check HAT left direction let expected_hat = (USB_HAT_LEFT as u8 - USB_HAT_UP as u8) * 2; @@ -374,7 +383,8 @@ mod tests { let virtual_rz = 0; let vt_enable = false; - let report = get_joystick_report(&mut buttons, &mut axes, virtual_ry, virtual_rz, &vt_enable); + let report = + get_joystick_report(&mut buttons, &mut axes, virtual_ry, virtual_rz, &vt_enable); // Check both regular buttons and HAT assert_eq!(report.buttons & (1 << 0), 1 << 0); // Button 1