601 lines
21 KiB
Rust
601 lines
21 KiB
Rust
//! Button processing for CMDR Joystick
|
||
//!
|
||
//! Responsibilities
|
||
//! - Integrate button matrix results and extra pins
|
||
//! - Track pressed/previous states and USB change flags
|
||
//! - Detect short/long presses with minimal hold timing
|
||
//! - Filter HAT switches so only one direction remains active
|
||
//! - Evaluate special combinations (bootloader, calibration, etc.)
|
||
//! - Expose a compact state consumed by USB report generation
|
||
|
||
use crate::button_matrix::{ButtonMatrix, MatrixPins};
|
||
use crate::hardware::{AXIS_CENTER, BUTTON_COLS, BUTTON_ROWS, NUMBER_OF_BUTTONS};
|
||
use crate::mapping::*;
|
||
use embedded_hal::digital::InputPin;
|
||
use rp2040_hal::timer::Timer;
|
||
|
||
// Total buttons including the two extra (non‑matrix) buttons
|
||
pub const TOTAL_BUTTONS: usize = NUMBER_OF_BUTTONS + 2;
|
||
|
||
pub type JoystickButtonMatrix = ButtonMatrix<
|
||
MatrixPins<{ BUTTON_ROWS }, { BUTTON_COLS }>,
|
||
{ BUTTON_ROWS },
|
||
{ BUTTON_COLS },
|
||
{ NUMBER_OF_BUTTONS },
|
||
>;
|
||
|
||
// ==================== BUTTON STRUCT ====================
|
||
|
||
#[derive(Copy, Clone, Default)]
|
||
pub struct Button {
|
||
pub pressed: bool,
|
||
pub previous_pressed: bool,
|
||
pub usb_changed: bool,
|
||
pub usb_button: usize, // For short press
|
||
pub usb_button_long: usize, // For long press
|
||
pub enable_long_press: bool, // Flag to enable special behavior
|
||
pub enable_long_hold: bool, // Flag to enable special behavior
|
||
|
||
// Internals
|
||
pub press_start_time: u32, // When physical press started
|
||
pub long_press_handled: bool, // True if long press activated
|
||
pub active_usb_button: usize, // Currently active USB button
|
||
pub usb_press_active: bool, // Is USB press currently "down"
|
||
pub usb_press_start_time: u32, // When USB press was sent
|
||
}
|
||
|
||
// ==================== SPECIAL ACTIONS ====================
|
||
/// High‑level actions triggered by dedicated button combinations.
|
||
#[derive(Debug, PartialEq)]
|
||
pub enum SpecialAction {
|
||
None,
|
||
Bootloader,
|
||
StartCalibration,
|
||
CancelCalibration,
|
||
ThrottleHold(u16), // Value to hold
|
||
VirtualThrottleToggle,
|
||
CalibrationSetModeM10,
|
||
CalibrationSetModeM7,
|
||
CalibrationSave,
|
||
}
|
||
|
||
// ==================== BUTTON MANAGER ====================
|
||
/// Aggregates and processes all buttons, exposing a stable API to the rest of the firmware.
|
||
pub struct ButtonManager {
|
||
pub buttons: [Button; TOTAL_BUTTONS],
|
||
}
|
||
|
||
impl Default for ButtonManager {
|
||
fn default() -> Self {
|
||
// Build a button manager with default-initialized buttons and state flags.
|
||
Self::new()
|
||
}
|
||
}
|
||
|
||
impl ButtonManager {
|
||
pub fn new() -> Self {
|
||
let mut buttons = [Button::default(); TOTAL_BUTTONS];
|
||
|
||
// Configure button mappings using existing mapping functionality
|
||
configure_button_mappings(&mut buttons);
|
||
|
||
Self { buttons }
|
||
}
|
||
|
||
/// Update button states from the button matrix snapshot.
|
||
pub fn update_from_matrix(&mut self, matrix: &mut JoystickButtonMatrix) {
|
||
for (index, key) in matrix.buttons_pressed().iter().enumerate() {
|
||
self.buttons[index].pressed = *key;
|
||
}
|
||
}
|
||
|
||
/// Update extra (non‑matrix) button states from hardware pins.
|
||
pub fn update_extra_buttons<L, R>(&mut self, left_pin: &mut L, right_pin: &mut R)
|
||
where
|
||
L: InputPin,
|
||
R: InputPin,
|
||
L::Error: core::fmt::Debug,
|
||
R::Error: core::fmt::Debug,
|
||
{
|
||
self.buttons[BUTTON_FRONT_LEFT_EXTRA].pressed = left_pin.is_low().unwrap_or(false);
|
||
self.buttons[BUTTON_FRONT_RIGHT_EXTRA].pressed = right_pin.is_low().unwrap_or(false);
|
||
}
|
||
|
||
/// Filter HAT switches so only a single direction (or center) can be active.
|
||
pub fn filter_hat_switches(&mut self) {
|
||
const LEFT_HAT_DIRECTIONS: [usize; 4] = [
|
||
BUTTON_TOP_LEFT_HAT_UP,
|
||
BUTTON_TOP_LEFT_HAT_RIGHT,
|
||
BUTTON_TOP_LEFT_HAT_DOWN,
|
||
BUTTON_TOP_LEFT_HAT_LEFT,
|
||
];
|
||
|
||
const RIGHT_HAT_DIRECTIONS: [usize; 4] = [
|
||
BUTTON_TOP_RIGHT_HAT_UP,
|
||
BUTTON_TOP_RIGHT_HAT_RIGHT,
|
||
BUTTON_TOP_RIGHT_HAT_DOWN,
|
||
BUTTON_TOP_RIGHT_HAT_LEFT,
|
||
];
|
||
|
||
self.reconcile_hat(&LEFT_HAT_DIRECTIONS, BUTTON_TOP_LEFT_HAT);
|
||
self.reconcile_hat(&RIGHT_HAT_DIRECTIONS, BUTTON_TOP_RIGHT_HAT);
|
||
}
|
||
|
||
fn reconcile_hat(&mut self, directions: &[usize; 4], center: usize) {
|
||
// Normalize hat inputs by clearing the center and conflicting directions.
|
||
let pressed_count = directions
|
||
.iter()
|
||
.filter(|&&index| self.buttons[index].pressed)
|
||
.count();
|
||
|
||
if pressed_count == 0 {
|
||
return;
|
||
}
|
||
|
||
self.buttons[center].pressed = false;
|
||
|
||
if pressed_count >= 2 {
|
||
for &index in directions.iter() {
|
||
self.buttons[index].pressed = false;
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Update press types (short/long) and USB change flags; returns whether USB should be updated.
|
||
pub fn process_button_logic(&mut self, current_time: u32) -> bool {
|
||
let mut usb_activity = false;
|
||
|
||
for button in self.buttons.iter_mut() {
|
||
update_button_press_type(button, current_time);
|
||
|
||
if button.pressed != button.previous_pressed {
|
||
button.usb_changed = true;
|
||
}
|
||
|
||
if button.usb_changed {
|
||
usb_activity = true;
|
||
}
|
||
|
||
button.previous_pressed = button.pressed;
|
||
}
|
||
|
||
usb_activity
|
||
}
|
||
|
||
/// Convenience wrapper that derives `current_time` from the hardware timer.
|
||
pub fn process_button_logic_with_timer(&mut self, timer: &Timer) -> bool {
|
||
let current_time = (timer.get_counter().ticks() / 1000) as u32;
|
||
self.process_button_logic(current_time)
|
||
}
|
||
|
||
/// Check for special button combinations (bootloader, calibration state/mode, VT, etc.).
|
||
pub fn check_special_combinations(
|
||
&self,
|
||
unprocessed_axis_value: u16,
|
||
calibration_active: bool,
|
||
) -> SpecialAction {
|
||
// Secondary way to enter bootloader
|
||
if self.buttons[BUTTON_FRONT_LEFT_LOWER].pressed
|
||
&& self.buttons[BUTTON_TOP_LEFT_MODE].pressed
|
||
&& self.buttons[BUTTON_TOP_RIGHT_MODE].pressed
|
||
{
|
||
return SpecialAction::Bootloader;
|
||
}
|
||
|
||
// Calibration mode toggle (start/cancel with same button combination)
|
||
if self.buttons[BUTTON_FRONT_LEFT_UPPER].pressed
|
||
&& self.buttons[BUTTON_TOP_LEFT_MODE].pressed
|
||
&& self.buttons[BUTTON_TOP_RIGHT_MODE].pressed
|
||
{
|
||
if calibration_active {
|
||
return SpecialAction::CancelCalibration;
|
||
} else {
|
||
return SpecialAction::StartCalibration;
|
||
}
|
||
}
|
||
|
||
// Check for throttle hold button press
|
||
if let Some(th_button) = self.get_button_press_event(TH_BUTTON) {
|
||
if th_button {
|
||
if unprocessed_axis_value != AXIS_CENTER {
|
||
return SpecialAction::ThrottleHold(unprocessed_axis_value);
|
||
} else {
|
||
return SpecialAction::ThrottleHold(AXIS_CENTER);
|
||
}
|
||
}
|
||
}
|
||
|
||
// Check for virtual throttle button press
|
||
if let Some(vt_button) = self.get_button_press_event(VT_BUTTON) {
|
||
if vt_button {
|
||
return SpecialAction::VirtualThrottleToggle;
|
||
}
|
||
}
|
||
|
||
// Calibration mode selection (only during calibration)
|
||
if calibration_active {
|
||
if let Some(up_button) = self.get_button_press_event(BUTTON_TOP_LEFT_UP) {
|
||
if up_button {
|
||
return SpecialAction::CalibrationSetModeM10;
|
||
}
|
||
}
|
||
|
||
if let Some(down_button) = self.get_button_press_event(BUTTON_TOP_LEFT_DOWN) {
|
||
if down_button {
|
||
return SpecialAction::CalibrationSetModeM7;
|
||
}
|
||
}
|
||
|
||
if let Some(save_button) = self.get_button_press_event(BUTTON_TOP_RIGHT_HAT) {
|
||
if save_button {
|
||
return SpecialAction::CalibrationSave;
|
||
}
|
||
}
|
||
}
|
||
|
||
SpecialAction::None
|
||
}
|
||
|
||
/// Get a change event for a button: Some(true)=press, Some(false)=release, None=no change.
|
||
fn get_button_press_event(&self, button_index: usize) -> Option<bool> {
|
||
// Report the updated pressed state whenever it differs from the previous sample.
|
||
let button = &self.buttons[button_index];
|
||
if button.pressed != button.previous_pressed {
|
||
Some(button.pressed)
|
||
} else {
|
||
None
|
||
}
|
||
}
|
||
|
||
/// Get mutable access to the internal buttons array.
|
||
pub fn buttons_mut(&mut self) -> &mut [Button; TOTAL_BUTTONS] {
|
||
&mut self.buttons
|
||
}
|
||
|
||
/// Get read‑only access to the internal buttons array.
|
||
pub fn buttons(&self) -> &[Button; TOTAL_BUTTONS] {
|
||
&self.buttons
|
||
}
|
||
}
|
||
|
||
// ==================== BUTTON PRESS TYPE DETECTION ====================
|
||
|
||
/// Update button press type and manage USB press lifecycle.
|
||
///
|
||
/// Behavior
|
||
/// - On physical press: record start time and defer decision
|
||
/// - Long press: if enabled and held >= threshold, activate `usb_button_long`
|
||
/// - Short press: on release (and if long not activated), activate `usb_button`
|
||
/// - USB press lifetime: auto‑release after a minimal hold so the host sees a pulse
|
||
fn update_button_press_type(button: &mut Button, current_time: u32) {
|
||
// Transition a single button between short/long press USB outputs.
|
||
const LONG_PRESS_THRESHOLD: u32 = 200;
|
||
|
||
// Pressing button
|
||
if button.pressed && !button.previous_pressed {
|
||
button.press_start_time = current_time;
|
||
button.long_press_handled = false;
|
||
}
|
||
|
||
// While held: trigger long press if applicable
|
||
if button.pressed
|
||
&& button.enable_long_press
|
||
&& !button.long_press_handled
|
||
&& current_time - button.press_start_time >= LONG_PRESS_THRESHOLD
|
||
{
|
||
button.active_usb_button = button.usb_button_long;
|
||
button.usb_press_start_time = current_time;
|
||
button.usb_press_active = true;
|
||
button.usb_changed = true;
|
||
button.long_press_handled = true;
|
||
}
|
||
|
||
// Releasing button
|
||
if !button.pressed && button.previous_pressed {
|
||
// If long press wasn't triggered, it's a short press
|
||
if (!button.enable_long_press || !button.long_press_handled) && button.usb_button != 0 {
|
||
button.active_usb_button = button.usb_button;
|
||
button.usb_press_start_time = current_time;
|
||
button.usb_press_active = true;
|
||
button.usb_changed = true;
|
||
}
|
||
|
||
// If long press was active, release now
|
||
if button.long_press_handled && button.usb_press_active {
|
||
button.usb_changed = true;
|
||
button.usb_press_active = false;
|
||
button.active_usb_button = 0;
|
||
}
|
||
}
|
||
|
||
// Auto‑release generated USB press after minimum hold time
|
||
const USB_MIN_HOLD_MS: u32 = 50;
|
||
let elapsed = current_time.saturating_sub(button.usb_press_start_time);
|
||
let should_release = if button.long_press_handled {
|
||
!button.enable_long_hold && elapsed >= USB_MIN_HOLD_MS
|
||
} else {
|
||
!button.pressed && elapsed >= USB_MIN_HOLD_MS
|
||
};
|
||
|
||
if button.usb_press_active && should_release {
|
||
button.usb_changed = true;
|
||
button.usb_press_active = false;
|
||
button.active_usb_button = 0;
|
||
}
|
||
}
|
||
|
||
// ==================== CONSTANTS ====================
|
||
|
||
// Special button functions (from main.rs)
|
||
pub const TH_BUTTON: usize = BUTTON_TOP_LEFT_MODE;
|
||
pub const VT_BUTTON: usize = BUTTON_TOP_RIGHT_MODE;
|
||
|
||
// ==================== TESTS ====================
|
||
|
||
#[cfg(all(test, feature = "std"))]
|
||
mod tests {
|
||
use super::*;
|
||
|
||
#[test]
|
||
fn test_button_manager_creation() {
|
||
// Button manager should allocate an entry for every physical button.
|
||
let manager = ButtonManager::new();
|
||
assert_eq!(manager.buttons.len(), TOTAL_BUTTONS);
|
||
}
|
||
|
||
#[test]
|
||
fn test_button_default_state() {
|
||
// Default button instances start unpressed with no lingering USB state.
|
||
let button = Button::default();
|
||
assert!(!button.pressed);
|
||
assert!(!button.previous_pressed);
|
||
assert!(!button.usb_changed);
|
||
assert!(!button.long_press_handled);
|
||
}
|
||
|
||
#[test]
|
||
fn test_special_action_combinations() {
|
||
// The bootloader combo should trigger when all required buttons are pressed.
|
||
let mut manager = ButtonManager::new();
|
||
|
||
// Test bootloader combination
|
||
manager.buttons[BUTTON_FRONT_LEFT_LOWER].pressed = true;
|
||
manager.buttons[BUTTON_TOP_LEFT_MODE].pressed = true;
|
||
manager.buttons[BUTTON_TOP_RIGHT_MODE].pressed = true;
|
||
|
||
let action = manager.check_special_combinations(AXIS_CENTER, false);
|
||
assert_eq!(action, SpecialAction::Bootloader);
|
||
}
|
||
|
||
#[test]
|
||
fn test_calibration_combination() {
|
||
// Calibration combo should generate the start-calibration action.
|
||
let mut manager = ButtonManager::new();
|
||
|
||
// Test calibration combination
|
||
manager.buttons[BUTTON_FRONT_LEFT_UPPER].pressed = true;
|
||
manager.buttons[BUTTON_TOP_LEFT_MODE].pressed = true;
|
||
manager.buttons[BUTTON_TOP_RIGHT_MODE].pressed = true;
|
||
|
||
let action = manager.check_special_combinations(AXIS_CENTER, false);
|
||
assert_eq!(action, SpecialAction::StartCalibration);
|
||
}
|
||
|
||
#[test]
|
||
fn test_throttle_hold_center() {
|
||
// Throttle hold combo should capture the centered axis value when centered.
|
||
let mut manager = ButtonManager::new();
|
||
manager.buttons[TH_BUTTON].pressed = true;
|
||
manager.buttons[TH_BUTTON].previous_pressed = false;
|
||
|
||
let action = manager.check_special_combinations(AXIS_CENTER, false);
|
||
assert_eq!(action, SpecialAction::ThrottleHold(AXIS_CENTER));
|
||
}
|
||
|
||
#[test]
|
||
fn test_throttle_hold_value() {
|
||
// Off-center throttle hold should capture the live axis value for hold.
|
||
let mut manager = ButtonManager::new();
|
||
manager.buttons[TH_BUTTON].pressed = true;
|
||
manager.buttons[TH_BUTTON].previous_pressed = false;
|
||
|
||
let test_value = 3000u16;
|
||
let action = manager.check_special_combinations(test_value, false);
|
||
assert_eq!(action, SpecialAction::ThrottleHold(test_value));
|
||
}
|
||
|
||
#[test]
|
||
fn test_virtual_throttle_toggle() {
|
||
// Virtual throttle button should emit the toggle action when pressed.
|
||
let mut manager = ButtonManager::new();
|
||
manager.buttons[VT_BUTTON].pressed = true;
|
||
manager.buttons[VT_BUTTON].previous_pressed = false;
|
||
|
||
let action = manager.check_special_combinations(AXIS_CENTER, false);
|
||
assert_eq!(action, SpecialAction::VirtualThrottleToggle);
|
||
}
|
||
|
||
#[test]
|
||
fn test_hat_switch_filtering_left() {
|
||
// Left hat should clear center and conflicting directions when multiple inputs are active.
|
||
let mut manager = ButtonManager::new();
|
||
|
||
// Press multiple directional buttons on left hat
|
||
manager.buttons[BUTTON_TOP_LEFT_HAT_UP].pressed = true;
|
||
manager.buttons[BUTTON_TOP_LEFT_HAT_RIGHT].pressed = true;
|
||
|
||
manager.filter_hat_switches();
|
||
|
||
// Multiple directions cancel the hat output completely
|
||
let pressed_count = (BUTTON_TOP_LEFT_HAT_UP..=BUTTON_TOP_LEFT_HAT_LEFT)
|
||
.filter(|&i| manager.buttons[i].pressed)
|
||
.count();
|
||
assert_eq!(pressed_count, 0);
|
||
assert!(!manager.buttons[BUTTON_TOP_LEFT_HAT].pressed);
|
||
}
|
||
|
||
#[test]
|
||
fn test_hat_switch_filtering_right() {
|
||
// Right hat should behave the same way, disabling conflicts and the center button.
|
||
let mut manager = ButtonManager::new();
|
||
|
||
// Press multiple directional buttons on right hat
|
||
manager.buttons[BUTTON_TOP_RIGHT_HAT_UP].pressed = true;
|
||
manager.buttons[BUTTON_TOP_RIGHT_HAT_DOWN].pressed = true;
|
||
|
||
manager.filter_hat_switches();
|
||
|
||
// Multiple directions cancel the hat output completely
|
||
let pressed_count = (BUTTON_TOP_RIGHT_HAT_UP..=BUTTON_TOP_RIGHT_HAT_LEFT)
|
||
.filter(|&i| manager.buttons[i].pressed)
|
||
.count();
|
||
assert_eq!(pressed_count, 0);
|
||
assert!(!manager.buttons[BUTTON_TOP_RIGHT_HAT].pressed);
|
||
}
|
||
|
||
#[test]
|
||
fn test_hat_center_button_filtering() {
|
||
// Pressing a direction should suppress the corresponding hat center button.
|
||
let mut manager = ButtonManager::new();
|
||
|
||
// Press directional button and center button
|
||
manager.buttons[BUTTON_TOP_LEFT_HAT].pressed = true;
|
||
manager.buttons[BUTTON_TOP_LEFT_HAT_UP].pressed = true;
|
||
|
||
manager.filter_hat_switches();
|
||
|
||
// Center button should be disabled when directional is pressed
|
||
assert!(!manager.buttons[BUTTON_TOP_LEFT_HAT].pressed);
|
||
// But single direction should remain active
|
||
assert!(manager.buttons[BUTTON_TOP_LEFT_HAT_UP].pressed);
|
||
}
|
||
|
||
#[test]
|
||
fn test_hat_switch_single_direction_allowed() {
|
||
// A single direction press must remain active for both hats.
|
||
let mut manager = ButtonManager::new();
|
||
|
||
// Press only one directional button on left hat
|
||
manager.buttons[BUTTON_TOP_LEFT_HAT_UP].pressed = true;
|
||
|
||
manager.filter_hat_switches();
|
||
|
||
// Single direction should remain active
|
||
assert!(manager.buttons[BUTTON_TOP_LEFT_HAT_UP].pressed);
|
||
|
||
// Test same for right hat
|
||
let mut manager = ButtonManager::new();
|
||
manager.buttons[BUTTON_TOP_RIGHT_HAT_DOWN].pressed = true;
|
||
|
||
manager.filter_hat_switches();
|
||
|
||
// Single direction should remain active
|
||
assert!(manager.buttons[BUTTON_TOP_RIGHT_HAT_DOWN].pressed);
|
||
}
|
||
|
||
#[test]
|
||
fn test_hat_center_button_works_alone() {
|
||
// When no direction is pressed, the center button should report as pressed.
|
||
let mut manager = ButtonManager::new();
|
||
|
||
// Press only center button (no directions)
|
||
manager.buttons[BUTTON_TOP_LEFT_HAT].pressed = true;
|
||
|
||
manager.filter_hat_switches();
|
||
|
||
// Center button should remain active when no directions are pressed
|
||
assert!(manager.buttons[BUTTON_TOP_LEFT_HAT].pressed);
|
||
}
|
||
|
||
#[test]
|
||
fn test_button_press_type_short_press() {
|
||
// Short presses should emit the primary USB button and flag a USB change.
|
||
let mut button = Button::default();
|
||
button.usb_button = 1;
|
||
button.enable_long_press = false;
|
||
|
||
// Press button
|
||
button.pressed = true;
|
||
update_button_press_type(&mut button, 100);
|
||
button.previous_pressed = button.pressed; // Update state
|
||
|
||
// Release button quickly (before long press threshold)
|
||
button.pressed = false;
|
||
update_button_press_type(&mut button, 150);
|
||
|
||
assert_eq!(button.active_usb_button, 1);
|
||
assert!(button.usb_press_active);
|
||
assert!(button.usb_changed);
|
||
}
|
||
|
||
#[test]
|
||
fn test_button_press_type_long_press() {
|
||
// Long presses should switch to the alternate USB button and mark handled state.
|
||
let mut button = Button::default();
|
||
button.usb_button = 1;
|
||
button.usb_button_long = 2;
|
||
button.enable_long_press = true;
|
||
|
||
// Press button
|
||
button.pressed = true;
|
||
update_button_press_type(&mut button, 100);
|
||
button.previous_pressed = button.pressed; // Update state
|
||
|
||
// Hold for long press threshold
|
||
update_button_press_type(&mut button, 350); // 250ms later
|
||
|
||
assert_eq!(button.active_usb_button, 2); // Long press button
|
||
assert!(button.usb_press_active);
|
||
assert!(button.long_press_handled);
|
||
}
|
||
|
||
#[test]
|
||
fn test_button_press_type_long_press_auto_release_once() {
|
||
// Non-hold long presses should auto-release once after triggering the long press.
|
||
let mut button = Button::default();
|
||
button.usb_button_long = 2;
|
||
button.enable_long_press = true;
|
||
button.enable_long_hold = false;
|
||
|
||
// Press the button
|
||
button.pressed = true;
|
||
update_button_press_type(&mut button, 0);
|
||
button.previous_pressed = button.pressed;
|
||
|
||
// Hold long enough to trigger the long press path
|
||
update_button_press_type(&mut button, 250);
|
||
assert!(button.usb_press_active);
|
||
assert_eq!(button.active_usb_button, 2);
|
||
|
||
// Clear the changed flag to emulate USB stack observing it
|
||
button.usb_changed = false;
|
||
|
||
// Keep holding and ensure we auto-release exactly once
|
||
update_button_press_type(&mut button, 320);
|
||
assert!(!button.usb_press_active);
|
||
assert!(button.usb_changed);
|
||
|
||
button.usb_changed = false;
|
||
update_button_press_type(&mut button, 400);
|
||
assert!(!button.usb_press_active);
|
||
assert!(!button.usb_changed);
|
||
}
|
||
|
||
#[test]
|
||
fn test_timer_integration_method_exists() {
|
||
// Document that the timer-backed helper stays callable without hardware wiring.
|
||
let manager = ButtonManager::new();
|
||
|
||
// This test verifies the timer integration method signature and basic functionality
|
||
// without requiring actual hardware timer setup in the test environment.
|
||
// The method should delegate to process_button_logic with proper time calculation.
|
||
|
||
// Verify the ButtonManager exists and has the expected structure
|
||
assert_eq!(manager.buttons.len(), TOTAL_BUTTONS);
|
||
|
||
// The process_button_logic_with_timer method requires actual Timer hardware
|
||
// which isn't available in the test environment, but the method compilation
|
||
// is verified through the cargo check above.
|
||
}
|
||
}
|