void joyCallback(const sensor_msgs::msg::Joy::ConstSharedPtr& msg)
// └────────────────┬────────────────┘
// 消息类型
// └─────────┬─────────┘
// 指针类型
// └───┬───┘
// 引用
// └─────┬─────┘
// 常量
1. sensor_msgs::msg::Joy
这是 ROS2 中手柄消息的标准数据类型 ,定义在 sensor_msgs 包中。
2. ConstSharedPtr
这是一个常量共享指针 (const std::shared_ptr<const Joy> 的别名),特点:
-
✅ 只读:不能修改消息内容
-
✅ 共享:多个地方可以同时持有
-
✅ 安全:自动管理内存
3. &(引用)
传递引用避免拷贝,提高性能。
二、Joy 消息结构
cpp
// sensor_msgs/msg/Joy.msg
Header header // 时间戳和坐标系
float32[] axes // 摇杆轴值数组(连续值)
int32[] buttons // 按钮状态数组(离散值)
详细说明
cpp
std_msgs/Header header // 消息头
uint32 seq // 序列号(自动递增)
time stamp // 时间戳
string frame_id // 坐标系ID(通常为空)
float32[] axes // 摇杆和扳机的值
// 范围: -1.0 ~ 1.0 (摇杆)
// 范围: 0.0 ~ 1.0 (扳机,默认在1.0位置)
// 索引含义取决于手柄类型
int32[] buttons // 按钮状态
// 值: 0 (未按下) 或 1 (按下)
// 索引含义取决于手柄类型
三、手柄数据映射(XBOX 手柄)
cpp
// 摇杆轴索引
enum Axis {
LEFT_STICK_X = 0, // 左摇杆水平 (-1.0 ~ 1.0)
LEFT_STICK_Y = 1, // 左摇杆垂直 (-1.0 ~ 1.0)
LEFT_TRIGGER = 2, // 左扳机 (0.0 ~ 1.0)
RIGHT_STICK_X = 3, // 右摇杆水平 (-1.0 ~ 1.0)
RIGHT_STICK_Y = 4, // 右摇杆垂直 (-1.0 ~ 1.0)
RIGHT_TRIGGER = 5, // 右扳机 (0.0 ~ 1.0)
D_PAD_X = 6, // 方向键水平 (-1.0, 0.0, 1.0)
D_PAD_Y = 7 // 方向键垂直 (-1.0, 0.0, 1.0)
};
// 按钮索引
enum Button {
A = 0, // A键
B = 1, // B键
X = 2, // X键
Y = 3, // Y键
LEFT_BUMPER = 4, // 左肩键 (LB)
RIGHT_BUMPER = 5, // 右肩键 (RB)
CHANGE_VIEW = 6, // 视图键 (Back)
MENU = 7, // 菜单键 (Start)
HOME = 8, // Xbox键
LEFT_STICK_CLICK = 9, // 左摇杆按下
RIGHT_STICK_CLICK = 10 // 右摇杆按下
};
四、如何访问数据
cpp
void joyCallback(const sensor_msgs::msg::Joy::ConstSharedPtr& msg)
{
// ============================================================
// 1. 访问摇杆数据 (axes数组)
// ============================================================
// 左摇杆
float left_x = msg->axes[0]; // 左右
float left_y = msg->axes[1]; // 前后
// 右摇杆
float right_x = msg->axes[3]; // 左右
float right_y = msg->axes[4]; // 前后
// 扳机
float left_trigger = msg->axes[2]; // 0.0 ~ 1.0
float right_trigger = msg->axes[5]; // 0.0 ~ 1.0
// 方向键
float d_pad_x = msg->axes[6]; // -1, 0, 1
float d_pad_y = msg->axes[7]; // -1, 0, 1
// ============================================================
// 2. 访问按钮数据 (buttons数组)
// ============================================================
// 检查按钮是否按下 (0=未按下, 1=按下)
bool a_pressed = msg->buttons[0] == 1;
bool b_pressed = msg->buttons[1] == 1;
bool x_pressed = msg->buttons[2] == 1;
bool y_pressed = msg->buttons[3] == 1;
bool lb_pressed = msg->buttons[4] == 1; // 左肩键
bool rb_pressed = msg->buttons[5] == 1; // 右肩键
// ============================================================
// 3. 访问消息头
// ============================================================
rclcpp::Time stamp = msg->header.stamp;
// 可以使用 stamp 进行时间同步
// ============================================================
// 4. 安全检查
// ============================================================
// 确保数组大小符合预期
if (msg->axes.size() < 8 || msg->buttons.size() < 11) {
RCLCPP_WARN(this->get_logger(), "Joy message size mismatch");
return;
}
}
五、实际应用示例
示例1:将手柄映射为 Twist 速度命令
cpp
void joyCallback(const sensor_msgs::msg::Joy::ConstSharedPtr& msg)
{
auto twist = geometry_msgs::msg::Twist();
// 安全检查
if (msg->axes.size() < 8) return;
// 死区处理(防止摇杆漂移)
double deadzone = 0.1;
// 左摇杆 → 线性速度 (前后左右)
twist.linear.x = (std::abs(msg->axes[1]) > deadzone) ? msg->axes[1] : 0.0;
twist.linear.y = (std::abs(msg->axes[0]) > deadzone) ? msg->axes[0] : 0.0;
// 右摇杆 → 角速度 (旋转)
twist.angular.z = (std::abs(msg->axes[3]) > deadzone) ? msg->axes[3] : 0.0;
// 速度缩放
double speed_scale = 0.5;
twist.linear.x *= speed_scale;
twist.linear.y *= speed_scale;
twist.angular.z *= speed_scale;
// 发布
cmd_pub_->publish(twist);
}
示例2:按钮控制(安全开关)
cpp
void joyCallback(const sensor_msgs::msg::Joy::ConstSharedPtr& msg)
{
if (msg->buttons.size() < 11) return;
// A 键:启用控制(安全开关)
if (msg->buttons[0] == 1) {
// 启用控制
enabled_ = true;
RCLCPP_INFO_ONCE(this->get_logger(), "Control enabled (A pressed)");
}
// B 键:禁用控制(紧急停止)
else if (msg->buttons[1] == 1) {
enabled_ = false;
RCLCPP_WARN_ONCE(this->get_logger(), "Control disabled (B pressed)");
}
if (!enabled_) {
// 发布零命令
publishZeroCommand();
return;
}
// ... 正常控制逻辑
}
示例3:组合键功能
cpp
void joyCallback(const sensor_msgs::msg::Joy::ConstSharedPtr& msg)
{
if (msg->buttons.size() < 11) return;
// 切换坐标系:VIEW 键 + MENU 键
if (msg->buttons[6] == 1 && msg->buttons[7] == 1) {
if (frame_ == "base_link") {
frame_ = "tool0";
} else {
frame_ = "base_link";
}
RCLCPP_INFO(this->get_logger(), "Switched to frame: %s", frame_.c_str());
}
// 速度调节:LB 减速,RB 加速
if (msg->buttons[4] == 1) {
speed_scale_ = std::max(0.1, speed_scale_ - 0.1);
}
if (msg->buttons[5] == 1) {
speed_scale_ = std::min(1.0, speed_scale_ + 0.1);
}
}
六、手柄类型适配
XBOX 手柄
cpp
// XBOX 手柄映射(如上所示)
enum XBOX_Axis { LEFT_X=0, LEFT_Y=1, L_TRIGGER=2, RIGHT_X=3, RIGHT_Y=4, R_TRIGGER=5, DPAD_X=6, DPAD_Y=7 };
enum XBOX_Button { A=0, B=1, X=2, Y=3, LB=4, RB=5, VIEW=6, MENU=7, HOME=8, L_CLICK=9, R_CLICK=10 };
PlayStation 手柄(DualShock 4)
cpp
enum PS4_Axis { LEFT_X=0, LEFT_Y=1, RIGHT_X=2, RIGHT_Y=3, L_TRIGGER=4, R_TRIGGER=5 };
enum PS4_Button { SQUARE=0, X=1, CIRCLE=2, TRIANGLE=3, L1=4, R1=5, L2=6, R2=7, SHARE=8, OPTIONS=9, L_CLICK=10, R_CLICK=11 };
通用 USB 手柄
cpp
// 不同手柄映射可能不同,建议先打印查看
void joyCallback(const sensor_msgs::msg::Joy::ConstSharedPtr& msg)
{
RCLCPP_INFO(this->get_logger(),
"Axes: [%f, %f, %f, %f, %f, %f, %f, %f]",
msg->axes[0], msg->axes[1], msg->axes[2], msg->axes[3],
msg->axes[4], msg->axes[5], msg->axes[6], msg->axes[7]);
}
七、调试技巧
打印所有数据
cpp
void joyCallback(const sensor_msgs::msg::Joy::ConstSharedPtr& msg)
{
std::string axes_str;
for (size_t i = 0; i < msg->axes.size(); ++i) {
axes_str += std::to_string(msg->axes[i]) + " ";
}
std::string buttons_str;
for (size_t i = 0; i < msg->buttons.size(); ++i) {
buttons_str += std::to_string(msg->buttons[i]) + " ";
}
RCLCPP_INFO(this->get_logger(),
"Axes: %s\nButtons: %s",
axes_str.c_str(), buttons_str.c_str());
}
查看手柄数据
bash
# 在终端查看
ros2 topic echo /joy
# 查看发布频率
ros2 topic hz /joy
# 查看话题信息
ros2 topic info /joy
八、总结
| 组件 | 类型 | 范围 | 说明 |
|---|---|---|---|
msg->axes |
float32[] |
-1.0 ~ 1.0 | 摇杆/扳机值 |
msg->buttons |
int32[] |
0 或 1 | 按钮状态 |
msg->header.stamp |
time |
- | 时间戳 |
| 访问方式 | -> |
- | 共享指针使用箭头 |
关键点:
-
✅
ConstSharedPtr保证只读安全 -
✅ 使用前检查数组大小
-
✅ 添加死区防止摇杆漂移
-
✅ 速度缩放保证安全
-
✅ 添加安全开关(如 A 键启用)