Added USB idle timeout
This commit is contained in:
parent
de138354b1
commit
3324de3e9d
@ -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;
|
||||
|
||||
@ -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<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(()),
|
||||
}
|
||||
};
|
||||
|
||||
@ -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 ====================
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 little‑endian (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 {
|
||||
|
||||
@ -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(),
|
||||
)
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user