Sport Client (Joystick) — Python Example
Source: rbq_sdk/python/example/sport/rbq_sport_client_joy.py
Python port of the C++ rbq_sport_client_joy.
Drives the robot using a gamepad / joystick via the Python SportClient API.
Analog sticks stream continuous Move velocity commands at 10 Hz; buttons trigger discrete actions.
How it works
Unlike the C++ version's two-thread design, the Python example uses a single event loop at 10 Hz:
- Poll joystick events.
- Check button chords → discrete action or analog-stick
Move. - Call the matching
SportClientmethod. - Sleep for the remainder of the 100 ms tick.
This is simpler and sufficient for the 10 Hz control rate that the SportClient high-level interface operates at.
Prerequisites
rbq_sdk_pyinstalled — see Python SDK Overview.- A Linux joystick device at
/dev/input/js*. - Robot stack running (simulation or hardware).
Run
From rbq_sdk/python/example/sport/:
python3 rbq_sport_client_joy.py <networkInterface> [joystickPath]
# auto-detect joystick:
python3 rbq_sport_client_joy.py eth0
# explicit joystick:
python3 rbq_sport_client_joy.py eth0 /dev/input/js1Joystick auto-detection order:
argv[2]if given and readable.$JOYenvironment variable.- First
/dev/input/js*device whose name matcheslogitech,gamepad,x-box,xbox, orwireless. - First available
/dev/input/js*node. /dev/input/js0as a last resort.
Gamepad bindings
The mapping follows the Logitech F710 / Xbox layout on Linux.
Button chords (checked first)
| Buttons pressed | Action |
|---|---|
| L2 + B | Damp — emergency stop |
| L2 + A (1st press) | StandUp — lock joints standing |
| L2 + A (2nd press) | StandDown — go prone |
| START | SwitchGait(ID_DEFAULT_MODE) — default gait |
| L2 + START | SwitchGait(ID_RUNNING_MODE) — running mode |
| L2 + X | RecoveryStand |
| R1 + X | SwitchGait(ID_CLIMB_MODE) — climb mode |
| L1 + X | not supported on this robot |
| L1 + Y | not supported on this robot |
| BACK | StopMove |
Double-click bindings
| Button | Window | Action |
|---|---|---|
| Double X | 400 ms | LeftSideGait(1) |
| Double Y | 400 ms | RightSideGait(1) |
| Double R1 | 400 ms | not supported on this robot |
| Double R2 (trigger) | 400 ms | not supported on this robot |
Analog stick velocity (when no button is pressed)
| Axis constant | Physical | Maps to | Max |
|---|---|---|---|
AXIS_LY (1) | Left stick forward | vel_x | ±1.0 m/s |
AXIS_LX (0) | Left stick left | vel_y | ±0.5 m/s |
AXIS_RX (3) | Right stick left | vyaw | ±1.0 rad/s |
Deadzone: 0.12 on all axes.
Axis and button index reference
AXIS_LX, AXIS_LY, AXIS_RX = 0, 1, 3
AXIS_LT, AXIS_RT = 2, 5 # triggers; idle = -1, full press = +1
PAD_A, PAD_B, PAD_X, PAD_Y = 0, 1, 2, 3
PAD_LB, PAD_RB = 4, 5 # L1, R1
PAD_BACK, PAD_START = 6, 7These match the standard Xbox / Logitech F710 Linux js* mapping.
If buttons or axes appear swapped on your controller, check with jstest /dev/input/js0.
Key implementation notes
edgedetection: TheJoyclass storesprev[]andedge[]arrays.edge[i]is set toTrueonly on a rising transition (button newly pressed, not held), and cleared byend_frame()at the end of each tick.- R2 as virtual button:
r2_pressed = joy.ax[AXIS_RT] > 0.0; a rising edger2_edgeis computed manually and passed toconsume_double. lock_toggle_to_prone: AlternatesStandUp/StandDownon successiveL2+Apresses, matching the physical A2 controller behaviour.- Error streak: Consecutive non-zero return codes increment
err_streak; a warning is printed every 50 failures.
Differences from the C++ version
| Aspect | C++ | Python |
|---|---|---|
| Thread model | 2 threads (joy + control) | Single loop at 10 Hz |
| Control rate | 10 Hz | 10 Hz |
| Double-click tracking | std::chrono::steady_clock | time.monotonic() |
| Joystick API | Joystick C++ class | Self-contained Joy Python class (raw fcntl/struct) |
