Special Note
Why do we need to connect the Bluetooth controller first? Because in the RoboTamerSdk4Qmini codebase, a controller connection check has been added. If the controller is not connected, the program will error. Therefore, we need to connect the controller first before proceeding with subsequent debugging.
We need a Bluetooth controller to control the robot, so let's proceed with some operations.
If you really don't have a
PS4controller at home, you can purchase a compatible Bluetooth controller. The one used during testing is this one: Bluetooth Controller Purchase Link
The relevant button definitions in the program:
Special Note
The Bluetooth controller robot control program logic is already built into the RoboTamerSdk4Qmini codebase:
- Uses the pygame library to automatically recognize Bluetooth controllers
- On Linux systems, after pairing, pygame will automatically detect the controller device (
pygame.joystick.Joystick(0)) - No additional Bluetooth pairing code is needed; system-level pairing is sufficient
Bluetooth Controller → Linux Bluetooth Driver → pygame → Python Module → C++ (via Python C API) → Robot Control
Install Dependencies
sudo apt install -y python3-pygame bluetoothStart the Bluetooth service:
sudo systemctl start bluetooth
sudo systemctl enable bluetooth2
Controller Pairing
Step 1: Set the PS4/5 controller to pairing mode:
Simultaneously press and hold the controller's Share + PS buttons for 3 seconds
The indicator light bar should start flashing quickly (white or blue)
Step 2: Use bluetoothctl to pair the Wireless Controller device:
# Enter Bluetooth command line
bluetoothctl
# Scan for nearby devices
bluetooth> scan on
# Pair with the controller (replace MAC with actual address)
# Name is: Wireless Controller
# Example: pair A0:5A:5F:5C:67:74
bluetooth> pair XX:XX:XX:XX:XX:XX
# Trust the device
# Example: trust A0:5A:5F:5C:67:74
bluetooth> trust XX:XX:XX:XX:XX:XX
# Connect the device
# Example: connect A0:5A:5F:5C:67:74
bluetooth> connect XX:XX:XX:XX:XX:XX
# The light on the back of the controller staying on means connection successful
# Exit
bluetooth> exit2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
After pairing once, as long as you don't delete the relevant information, both sides will automatically connect every time they are powered on.
Controller Button Testing
Note
This step is very important. If you are using a third-party controller, please follow the steps below to test whether the controller buttons are correctly mapped. If the mapping is incorrect, you need to modify the button mapping code in the RoboTamerSdk4Qmini codebase.
Test Script Code【Click to Expand】
#!/usr/bin/env python3
"""
PS4 Controller Button Mapping Test Tool - Interactive Guide Version
"""
import pygame
import sys
import os
import time
os.environ["SDL_VIDEODRIVER"] = "dummy"
pygame.init()
pygame.joystick.init()
if pygame.joystick.get_count() == 0:
print("❌ No controller detected, please connect the Bluetooth controller first")
sys.exit(1)
js = pygame.joystick.Joystick(0)
js.init()
print("=" * 60)
print(f"✅ Controller connected: {js.get_name()}")
print(f" Number of buttons: {js.get_numbuttons()}")
print(f" Number of axes: {js.get_numaxes()}")
print(f" Number of hats: {js.get_numhats()}")
print("=" * 60)
print()
# Test item list
test_items = [
("×", "Cross/A", "Stand mode", 0),
("○", "Circle/B", "Exit program", 1),
("□", "Square/X", "RL stand balance", 3),
("△", "Triangle/Y", "RL walk", 4),
("L1", "L1", "Lateral movement", 6),
("R1", "R1", "Reserved function", 7),
("L2", "L2", "Reserved function", 8),
("R2", "R2", "Reserved function", 9),
("SHARE/SELECT", "SELECT", "Sine test", 10),
("OPTIONS/START", "START", "Fold/Ready", 11),
]
results = {}
def wait_for_button():
"""Wait for button press, return button number"""
pygame.event.clear()
print(" [Waiting for button...] ", end='', flush=True)
while True:
for event in pygame.event.get():
if event.type == pygame.JOYBUTTONDOWN:
return event.button
pygame.time.wait(50)
print("📋 Now let's start testing, please press the corresponding buttons one by one according to the prompts\n")
time.sleep(1)
for i, (symbol, name, function, expected) in enumerate(test_items, 1):
print(f"\n[{i}/{len(test_items)}] Please press: {symbol} ({name})")
print(f" Function: {function}")
print(f" SDK expected number: button({expected})")
actual = wait_for_button()
if actual == expected:
print(f" ✅ Correct! button({actual})")
results[name] = (expected, actual, True)
else:
print(f" ⚠️ Mismatch! Actual is button({actual}), expected button({expected})")
results[name] = (expected, actual, False)
time.sleep(0.5)
# Test sticks
print("\n" + "=" * 60)
print("🕹️ Stick Test")
print("=" * 60)
stick_tests = [
("Left stick", "Push up", 1, -1.0),
("Left stick", "Push down", 1, 1.0),
("Right stick", "Push left", 2, -1.0),
("Right stick", "Push right", 2, 1.0),
]
for stick_name, direction, expected_axis, expected_sign in stick_tests:
print(f"\nPlease {stick_name} {direction}, then release...")
print(" [Waiting for stick action...] ", end='', flush=True)
pygame.event.clear()
detected = False
timeout = time.time() + 5
while time.time() < timeout and not detected:
for event in pygame.event.get():
if event.type == pygame.JOYAXISMOTION:
if abs(event.value) > 0.5:
actual_axis = event.axis
actual_value = event.value
if actual_axis == expected_axis and (actual_value * expected_sign > 0):
print(f"✅ Correct! axis({actual_axis}) = {actual_value:+.2f}")
else:
print(f"⚠️ Mismatch! Actual axis({actual_axis}) = {actual_value:+.2f}, expected axis({expected_axis})")
detected = True
break
pygame.time.wait(50)
if not detected:
print("⏱️ Timeout, no stick action detected")
time.sleep(0.5)
# Output summary report
print("\n" + "=" * 60)
print("📊 Test Results Summary")
print("=" * 60)
all_correct = all(match for _, _, match in results.values())
if all_correct:
print("✅ All button mappings are correct, your controller is fully compatible!")
else:
print("⚠️ Button mapping mismatches found, need to modify joystick.py\n")
print("Mismatched buttons:")
for name, (expected, actual, match) in results.items():
if not match:
print(f" - {name:15s}: expected button({expected}), actual button({actual})")
print("\nNeed to modify the following lines in joystick.py:")
for name, (expected, actual, match) in results.items():
if not match:
var_name = {
"Cross/A": "butA",
"Circle/B": "butB",
"Square/X": "butX",
"Triangle/Y": "butY",
"L1": "L1",
"R1": "R1",
"L2": "L2",
"R2": "R2",
"SELECT": "SELECT",
"START": "START",
}.get(name, name)
print(f" self.{var_name} = self.joystick.get_button({expected}) → change to get_button({actual})")
print("\nTest complete!")
pygame.quit()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
Test result is as follows:
After modification: