aboutsummaryrefslogtreecommitdiff
path: root/src/pico/joystick_to_cursor.py
blob: 361af689b112e7b91ae00ccc550f6e57b0da39cd (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
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()