特别说明
为什么我们需要首先连接蓝牙手柄呢?因为在 RoboTamerSdk4Qmini 代码库中,添加了一个手柄链接检测,如果没有连接手柄,程序会出错,所以我们需要先连接好手柄,才能进行后续的调试。
我们需要蓝牙手柄,来进行控制机器人,所以接下来我们进行一些操作。
如果家里实在是没有
PS4手柄,则可以购买一个兼容的蓝牙手柄,测试时使用的是这个手柄:蓝牙手柄购买链接
在程序中相关的按键定义:
特别说明
蓝牙手柄控制机器人的程序逻辑,已经内置在 RoboTamerSdk4Qmini 代码库中:
- 使用 pygame 库自动识别蓝牙手柄
- Linux 系统配对后,pygame 会自动检测到手柄设备(
pygame.joystick.Joystick(0)) - 不需要额外的蓝牙配对代码,系统级配对即可
蓝牙手柄 → Linux 蓝牙驱动 → pygame → Python 模块 → C++ (通过 Python C API) → 机器人控制
安装依赖
bash
sudo apt install -y python3-pygame bluetooth1
启动蓝牙服务:
bash
sudo systemctl start bluetooth
sudo systemctl enable bluetooth1
2
2
手柄配对
步骤1:将 PS4/5 手柄设置为配对模式:
同时按住手柄的 Share + PS 按钮 3 秒
指示灯条应开始快速闪烁(白色或蓝色)
步骤2:使用 bluetoothctl 进行配对 Wireless Controller 设备:
bash
# 进入蓝牙命令行
bluetoothctl
# 扫描周围设备
bluetooth> scan on
# 与手柄配对(将 MAC 替换为实际地址)
# 名字为:Wireless Controller
# 例如:pair A0:5A:5F:5C:67:74
bluetooth> pair XX:XX:XX:XX:XX:XX
# 信任设备
# 例如:trust A0:5A:5F:5C:67:74
bluetooth> trust XX:XX:XX:XX:XX:XX
# 连接设备
# 例如:connect A0:5A:5F:5C:67:74
bluetooth> connect XX:XX:XX:XX:XX:XX
# 手柄后面灯常量即是连接成功
# 退出
bluetooth> exit1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
配对一次之后,只要不删除相关的信息,每次双方开机都会自动连接。
手柄按键测试
注意
这一步很重要,如果你用的是第三方手柄的话,希望你能按照下方的步骤,测试一下手柄的按键是否正确映射,如果映射不正确的话,你需要修改 RoboTamerSdk4Qmini 代码库中相关的按键映射代码。
测试脚本代码【点击展开】
python
#!/usr/bin/env python3
"""
PS4 手柄按键映射测试工具 - 交互式引导版本
"""
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("❌ 未检测到手柄,请先连接蓝牙手柄")
sys.exit(1)
js = pygame.joystick.Joystick(0)
js.init()
print("=" * 60)
print(f"✅ 手柄已连接: {js.get_name()}")
print(f" 按键数量: {js.get_numbuttons()}")
print(f" 摇杆轴数: {js.get_numaxes()}")
print(f" 方向键数: {js.get_numhats()}")
print("=" * 60)
print()
# 测试项目列表
test_items = [
("×", "Cross/A", "站立模式", 0),
("○", "Circle/B", "退出程序", 1),
("□", "Square/X", "RL站立平衡", 3),
("△", "Triangle/Y", "RL行走", 4),
("L1", "L1", "侧向移动", 6),
("R1", "R1", "保留功能", 7),
("L2", "L2", "保留功能", 8),
("R2", "R2", "保留功能", 9),
("SHARE/SELECT", "SELECT", "正弦测试", 10),
("OPTIONS/START", "START", "折叠/就绪", 11),
]
results = {}
def wait_for_button():
"""等待按键按下,返回按键编号"""
pygame.event.clear()
print(" [等待按键...] ", end='', flush=True)
while True:
for event in pygame.event.get():
if event.type == pygame.JOYBUTTONDOWN:
return event.button
pygame.time.wait(50)
print("📋 现在开始测试,请按照提示逐个按下对应按键\n")
time.sleep(1)
for i, (symbol, name, function, expected) in enumerate(test_items, 1):
print(f"\n[{i}/{len(test_items)}] 请按下: {symbol} ({name})")
print(f" 功能: {function}")
print(f" SDK期望编号: button({expected})")
actual = wait_for_button()
if actual == expected:
print(f" ✅ 正确! button({actual})")
results[name] = (expected, actual, True)
else:
print(f" ⚠️ 不匹配! 实际是 button({actual}), 期望 button({expected})")
results[name] = (expected, actual, False)
time.sleep(0.5)
# 测试摇杆
print("\n" + "=" * 60)
print("🕹️ 摇杆测试")
print("=" * 60)
stick_tests = [
("左摇杆", "向上推", 1, -1.0),
("左摇杆", "向下推", 1, 1.0),
("右摇杆", "向左推", 2, -1.0),
("右摇杆", "向右推", 2, 1.0),
]
for stick_name, direction, expected_axis, expected_sign in stick_tests:
print(f"\n请 {stick_name} {direction},然后松开...")
print(" [等待摇杆动作...] ", 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"✅ 正确! axis({actual_axis}) = {actual_value:+.2f}")
else:
print(f"⚠️ 不匹配! 实际 axis({actual_axis}) = {actual_value:+.2f}, 期望 axis({expected_axis})")
detected = True
break
pygame.time.wait(50)
if not detected:
print("⏱️ 超时,未检测到摇杆动作")
time.sleep(0.5)
# 输出汇总报告
print("\n" + "=" * 60)
print("📊 测试结果汇总")
print("=" * 60)
all_correct = all(match for _, _, match in results.values())
if all_correct:
print("✅ 所有按键映射正确,你的手柄完全兼容!")
else:
print("⚠️ 发现按键映射不匹配,需要修改 joystick.py\n")
print("不匹配的按键:")
for name, (expected, actual, match) in results.items():
if not match:
print(f" - {name:15s}: 期望 button({expected}), 实际 button({actual})")
print("\n需要在 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}) → 改为 get_button({actual})")
print("\n测试完成!")
pygame.quit()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
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
测试效果如下:
修改后如下: