Cross-platform game controller library for robotics, drones, and edge computing
Project description
controlpad
A cross-platform Python library for integrating game controllers into robotics, drone, and edge computing projects.
from controlpad import Gamepad
gp = Gamepad()
@gp.on_axis("left_x", "left_y")
def on_left_stick(x, y):
robot.set_velocity(x, y)
@gp.on_button_press("cross")
def on_cross():
robot.jump()
gp.run()
Features
- Event-driven or polling — use decorator callbacks for clean application code, or poll in your own loop
- Named axes and buttons —
state.axis("left_x")instead ofjoystick.get_axis(0) - Built-in profiles — DualSense and Xbox out of the box; custom profiles in a few lines
- Auto-detection — profile is selected automatically from the controller's name string
- Radial deadzone — circular 2D deadzone for sticks, not a square
- Expo curves — standard RC-style centre softening for precise control
- Smoothing — configurable EMA low-pass filter to reduce stick jitter
- Auto-reconnect — survives USB wobbles and Bluetooth drops without crashing your application
- Headless Linux support — evdev backend requires no display, works inside ROS nodes and Docker containers
- CLI tools —
controlpad detect,controlpad monitorfor quick diagnostics
Installation
python3 -m venv my_env
source my_env/bin/activate
pip install controlpad
For headless Linux (Raspberry Pi, ROS, Docker — no display required):
python3 -m venv my_env
source my_env/bin/activate
pip install "controlpad[evdev]"
To check the installed version:
import controlpad
print(controlpad.__version__) # e.g. "0.1.0"
Quick start
Polling
import time
from controlpad import Gamepad
gp = Gamepad(deadzone=0.08)
gp.connect()
while True:
state = gp.read()
print(state.axis("left_x"), state.axis("left_y"))
print(state.button("cross"))
print(state.dpad) # (-1, 0), (0, 1), etc.
time.sleep(1 / 60)
Event-driven
from controlpad import Gamepad
gp = Gamepad(deadzone=0.08, expo=0.15)
@gp.on_axis("left_x", "left_y")
def on_left_stick(x, y):
drone.set_velocity(x, y)
@gp.on_axis("r2") # Trigger: 0.0 → 1.0
def on_throttle(value):
drone.set_throttle(value)
@gp.on_button_press("triangle")
def take_off():
drone.take_off()
@gp.on_button_press("cross")
def land():
drone.land()
@gp.on_disconnect()
def emergency():
drone.disarm()
gp.run()
Background thread
gp = Gamepad()
thread = gp.run_async() # Non-blocking — returns immediately
# Your main application continues here
do_other_things()
gp.stop()
thread.join()
Configuration
All options are passed to Gamepad():
| Parameter | Type | Default | Description |
|---|---|---|---|
profile |
str or ControllerProfile |
None |
Profile name or instance. None = auto-detect |
index |
int | 0 |
Which controller to open if multiple are connected |
deadzone |
float | 0.05 |
Stick deadzone radius [0, 1) |
expo |
float | 0.0 |
Expo curve strength [0, 1). 0 = linear |
smoothing |
float | 1.0 |
EMA alpha (0, 1]. Lower = smoother. 1.0 = off |
backend |
str | "auto" |
"auto", "pygame", or "evdev" |
headless |
bool | False |
Force SDL dummy video — no display required |
reconnect |
bool | True |
Auto-reconnect when controller is lost |
poll_rate |
int | 60 |
Polling frequency in Hz for run() |
Custom profiles
If your controller is not auto-detected, define your own profile using
controlpad detect to find the raw axis indices first:
controlpad detect
Then define and register the profile:
from controlpad import Gamepad, ControllerProfile, register_profile
my_stick = ControllerProfile(
name="MyJoystick",
axis_map={
"x": 0,
"y": 1,
"throttle": 2,
"twist": 3,
},
button_map={
"trigger": 0,
"thumb": 1,
},
invert_axes={"y"},
trigger_axes={"throttle"},
)
register_profile(my_stick)
gp = Gamepad(profile="myjoystick")
CLI tools
# Identify your controller and print its full axis/button mapping
controlpad detect
# List all built-in profiles
controlpad list
# Live axis/button stream in the terminal (no display needed)
controlpad monitor
controlpad monitor --rate 50 --deadzone 0.08
Headless / Raspberry Pi
On a Raspberry Pi running headless (no desktop), use either:
# Option 1: headless flag
gp = Gamepad(headless=True)
# Option 2: evdev backend (no pygame, no SDL)
gp = Gamepad(backend="evdev")
# Option 3: environment variable before import
export SDL_VIDEODRIVER=dummy
export SDL_AUDIODRIVER=dummy
python your_script.py
The evdev backend requires pip install "controlpad[evdev]" and the user to
be in the input group:
sudo usermod -aG input $USER
Supported controllers
| Controller | Profile name | Tested on |
|---|---|---|
| Sony DualSense (CFI-ZCT1W / CFI-ZCT1G) | dualsense |
Raspberry Pi 5 (Linux), macOS 14 |
| Xbox One / Series X|S | xbox |
Linux (xpadneo), macOS |
| Any HID gamepad | generic (auto-fallback) |
Access axes as axis_0, axis_1, … |
Contributions for other controllers are welcome — see CONTRIBUTING.md.
Development setup
Clone the repo and install in editable mode. The -e flag is important — it
registers the package with local metadata so controlpad.__version__
resolves correctly when running from source.
git clone https://github.com/ranaweerasupun/controlpad.git
cd controlpad
python3 -m venv my_env
source my_env/bin/activate
pip install -e ".[dev]"
Run the tests — no controller hardware required, the test suite uses a mock backend:
pytest
License
MIT — see LICENSE.
Author
Supun Sriyananda — github.com/ranaweerasupun
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file controlpad-0.1.1.tar.gz.
File metadata
- Download URL: controlpad-0.1.1.tar.gz
- Upload date:
- Size: 25.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
40a298e7a7e55cfd1bed07a239717eb553ea3f02b9299a97f0f48eeadff6e2b6
|
|
| MD5 |
717399f1d4b68562287a18de0ae9f70f
|
|
| BLAKE2b-256 |
ea324753d4be33d78fcb549b9fbca84eb106a9dabc6ecaa9556d410031af68cd
|
File details
Details for the file controlpad-0.1.1-py3-none-any.whl.
File metadata
- Download URL: controlpad-0.1.1-py3-none-any.whl
- Upload date:
- Size: 24.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
08271ee7f4d0b7a1966658674acdefb3537a6ab8cca2c5d7ed8ee08a3e4fc18b
|
|
| MD5 |
8a357e3ef5f4474cf2fe30a406b9902a
|
|
| BLAKE2b-256 |
2c1d81771f269124b72a561de9f1105c676c4c835f8b9f4618ed19b9704982ab
|