Skip to main content

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 buttonsstate.axis("left_x") instead of joystick.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 toolscontrolpad detect, controlpad monitor for quick diagnostics

Installation

pip install controlpad

For headless Linux (Raspberry Pi, ROS, Docker — no display required):

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

Examples

The examples/ directory contains ready-to-run scripts:

File What it shows
basic_polling.py Manual polling loop
event_driven.py Decorator-based callbacks
drone_control.py Full drone input mapping
custom_profile.py Registering a custom controller layout
recording_playback.py Recording a session to JSON and replaying it without hardware

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
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 Sriyanandagithub.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

controlpad-0.2.0.tar.gz (38.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

controlpad-0.2.0-py3-none-any.whl (31.7 kB view details)

Uploaded Python 3

File details

Details for the file controlpad-0.2.0.tar.gz.

File metadata

  • Download URL: controlpad-0.2.0.tar.gz
  • Upload date:
  • Size: 38.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.15

File hashes

Hashes for controlpad-0.2.0.tar.gz
Algorithm Hash digest
SHA256 d8d2870e72c2f78cbf35c38c2bfb848ed6a8e0a7b449e5fe8f0ce1bd090ade43
MD5 ae450835ef61eccbb4378dce759f9ade
BLAKE2b-256 c4a0de5e6d1b615a7363a326101bf1c59f0c8c4a2ea8e39b6e6acaabaae9c387

See more details on using hashes here.

File details

Details for the file controlpad-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: controlpad-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 31.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.15

File hashes

Hashes for controlpad-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 73fdb0a2de403d5fdb38720c1cd8b8569ebf76ba1e02c6f36f6538e9f49b6e1e
MD5 635e8a3e66bf113d6dfd2fda485fd7a7
BLAKE2b-256 99101c4a2043839651060f6b552d25d1ef61c88f4f9780032c60bf2877ead2c4

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page