From a6a46403e6f98c0a44ec43a66a8b02ea04f28162 Mon Sep 17 00:00:00 2001 From: M005A <114624212+M005A@users.noreply.github.com> Date: Thu, 17 Apr 2025 13:00:29 -0700 Subject: Create joystick_to_cursor.py --- src/pico/joystick_to_cursor.py | 177 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 src/pico/joystick_to_cursor.py diff --git a/src/pico/joystick_to_cursor.py b/src/pico/joystick_to_cursor.py new file mode 100644 index 0000000..361af68 --- /dev/null +++ b/src/pico/joystick_to_cursor.py @@ -0,0 +1,177 @@ +import pygame +import time +from pynput.mouse import Controller, Button + +pygame.init() +pygame.joystick.init() + +# Screen mapping size based on mouse position (my screen ins 2560 x 1600) +SCREEN_WIDTH = 1680 +SCREEN_HEIGHT = 1050 + +# Check for connected joystick +if pygame.joystick.get_count() == 0: + print("No joystick detected. Please connect a game controller.") + exit() + +joystick = pygame.joystick.Joystick(0) +joystick.init() +mouse = Controller() + +# Get controller information +button_count = joystick.get_numbuttons() +print(f"Joystick detected: {joystick.get_name()} with {button_count} buttons") + +# Initial calibration phase +print("Calibrating joystick. Please don't touch the controller...") +time.sleep(1) # Wait for 1 second to make sure joystick is at rest + +# Get initial position as offset +pygame.event.pump() +x_offset = joystick.get_axis(0) +y_offset = joystick.get_axis(1) +print(f"Center calibration complete. Offset values: X={x_offset}, Y={y_offset}") + +# Full calibration phase to get min/max values +print("\n--- FULL CALIBRATION ---") +print("Please move the joystick to its maximum extents in all directions.") +print("Move in circles covering the entire range for 5 seconds...") + +# Initialize min/max values +x_min, x_max = 0, 0 +y_min, y_max = 0, 0 + +# Calibration duration +cal_start_time = time.time() +cal_duration = 5 # seconds + +while time.time() - cal_start_time < cal_duration: + pygame.event.pump() + + # Get calibrated values + x_val = joystick.get_axis(0) - x_offset + y_val = joystick.get_axis(1) - y_offset + + # Update min/max + x_min = min(x_min, x_val) + x_max = max(x_max, x_val) + y_min = min(y_min, y_val) + y_max = max(y_max, y_val) + + # Show progress + progress = int((time.time() - cal_start_time) / cal_duration * 20) + print(f"\rCalibrating: [{'#' * progress}{' ' * (20-progress)}] Current ranges: X({x_min:.2f} to {x_max:.2f}) Y({y_min:.2f} to {y_max:.2f})", end="") + time.sleep(0.1) + +print("\nCalibration complete!") +print(f"X range: {x_min:.2f} to {x_max:.2f}") +print(f"Y range: {y_min:.2f} to {y_max:.2f}") + +# Dead zone +DEAD_ZONE = 0.05 # Ignore small movements (5% of range) +SMOOTHING_FACTOR = 0.85 # Higher values (closer to 1) make movement smoother but less responsive + +# Function to map joystick value to screen coordinates +def map_to_screen(value, in_min, in_max, out_min, out_max): + # Clamp input value to range + value = max(min(value, in_max), in_min) + # Map input range to output range + return (value - in_min) * (out_max - out_min) / (in_max - in_min) + out_min + +running = True +# Store the last mouse position to calculate deltas +last_screen_x, last_screen_y = SCREEN_WIDTH/2, SCREEN_HEIGHT/2 + +# Variables for smoothing +smooth_x = 0 +smooth_y = 0 + +# Set initial mouse position to center of screen +mouse.position = (int(last_screen_x), int(last_screen_y)) + +# Dictionary to track button states to detect changes +button_states = {i: False for i in range(button_count)} + +# Define controller buttons to mouse mapping +RIGHT_CLICK_BUTTON = 4 +LEFT_CLICK_BUTTON = 5 + +print(f"\nButton mapping:\n- Button {LEFT_CLICK_BUTTON}: Left mouse click\n- Button {RIGHT_CLICK_BUTTON}: Right mouse click") +print(f"Smoothing factor: {SMOOTHING_FACTOR} (higher = smoother but less responsive)") + +while running: + pygame.event.pump() + + # Process events to ensure button presses are detected + for event in pygame.event.get(): + if event.type == pygame.QUIT: + running = False + + # Read joystick axes and apply calibration + x_axis = joystick.get_axis(0) - x_offset + y_axis = joystick.get_axis(1) - y_offset + + # Apply dead zone + if abs(x_axis) < DEAD_ZONE: + x_axis = 0 + if abs(y_axis) < DEAD_ZONE: + y_axis = 0 + + # Apply smoothing with exponential moving average + smooth_x = SMOOTHING_FACTOR * smooth_x + (1 - SMOOTHING_FACTOR) * x_axis + smooth_y = SMOOTHING_FACTOR * smooth_y + (1 - SMOOTHING_FACTOR) * y_axis + + # Map smoothed joystick to screen coordinates + screen_x = map_to_screen(smooth_x, x_min, x_max, 0, SCREEN_WIDTH) + screen_y = map_to_screen(smooth_y, y_min, y_max, 0, SCREEN_HEIGHT) + + # Use absolute positioning instead of relative movement + if abs(x_axis) >= DEAD_ZONE or abs(y_axis) >= DEAD_ZONE: + # Set the mouse position directly to the calculated screen coordinates + mouse.position = (int(screen_x), int(screen_y)) + + # Check all button states + button_changes = False + button_status = [] + + for i in range(button_count): + button_value = joystick.get_button(i) + + # Handle mouse button clicks + if i == LEFT_CLICK_BUTTON: + if button_value and not button_states[i]: + mouse.press(Button.left) + button_status.append(f"Button {i}: LEFT CLICK") + elif not button_value and button_states[i]: + mouse.release(Button.left) + + elif i == RIGHT_CLICK_BUTTON: + if button_value and not button_states[i]: + mouse.press(Button.right) + button_status.append(f"Button {i}: RIGHT CLICK") + elif not button_value and button_states[i]: + mouse.release(Button.right) + + # Track button state changes for all buttons + if button_value != button_states[i]: + button_changes = True + button_states[i] = button_value + + # Add status for non-mouse buttons + if button_value and i != LEFT_CLICK_BUTTON and i != RIGHT_CLICK_BUTTON: + button_status.append(f"Button {i}: PRESSED") + + # Only display button information if there are changes or buttons pressed + if button_changes or any(button_states.values()): + print("\nButton Status:") + if not button_status: + print("No buttons currently pressed") + else: + for status in button_status: + print(status) + + # Display the scaled joystick position + print(f"Scaled position: ({int(screen_x)}, {int(screen_y)}) | Raw: ({x_axis:.2f}, {y_axis:.2f}) | Smoothed: ({smooth_x:.2f}, {smooth_y:.2f})") + time.sleep(0.05) + +pygame.quit() -- cgit v1.2.3