Added USB idle timeout

This commit is contained in:
Christoffer Martinsson 2025-09-16 11:21:17 +02:00
parent de138354b1
commit 3324de3e9d
10 changed files with 187 additions and 121 deletions

View File

@ -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;

View File

@ -11,7 +11,7 @@
//! behind simple `read_fn`/`write_fn` closures so the code can be unittested 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 peraxis calibration data from EEPROM.
///
/// Updates the provided axes with loaded values; retains defaults on error.
pub fn load_axis_calibration<F>(
axes: &mut [GimbalAxis; NBR_OF_GIMBAL_AXIS],
read_fn: &mut F,
) where
pub fn load_axis_calibration<F>(axes: &mut [GimbalAxis; NBR_OF_GIMBAL_AXIS], read_fn: &mut F)
where
F: FnMut(u32) -> Result<u8, ()>,
{
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(()),
}
};

View File

@ -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 ====================

View File

@ -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;

View File

@ -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;
}
}
}

View File

@ -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.

View File

@ -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<P, SM, I>
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);
}
}

View File

@ -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<u8, ()>,
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<u8, ()>
read_byte_fn: &mut dyn FnMut(u32) -> Result<u8, ()>,
) -> Result<u8, StorageError> {
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 littleendian (low then high byte) format.
fn read_u16_with_closure(
read_byte_fn: &mut dyn FnMut(u32) -> Result<u8, ()>,
low_addr: u32,
high_addr: u32
high_addr: u32,
) -> Result<u16, StorageError> {
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<u8, ()> {
if addr < mock_data.len() as u32 {

View File

@ -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(),
)

View File

@ -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