在ROS中,使用键盘控制turtle运动比较简单,下面主要对游戏手柄操控turtle进行代码说明和演示。
手柄如下:
上图中手柄上有两个操作杆。后面内容分为两个部分,一部分是单个操作杆(单轴)控制,另一部分是双操作杆(双轴)控制。但是这两部分内容主要不同在于轴的配置不同,体现在之后的.cpp代码有差异,其他并无差异。
一、基础的准备工作:
(1)创建工作空间
$ mkdir -p ~/catkin_workspace/src
$ cd ~/catkin_workspace/src
$ catkin_init_workspace
运行后在catkin_workspace--->src中会产生CMakeLists.txt文件,结果如下:
(2)编译工作空间
$ cd ~/catkin_workspace/
$ catkin_make
在终端中运行结果为:
在工作区间catkin_workspace中会产生build、devel文件夹和 .catkin_workspace文件,如下图所示:
(3)设置环境变量
$ source devel/setup.bash
运行结果为:
(4)检查环境变量
$ echo $ROS_PACKAGE_PATH
运行结果:
以上就是准备工作,在这个基础上,就可以在这个workspace上继续后续的代码编写了。
二、手柄连接测试
系统是Ubuntu18.04,在终端输入:
$ lsusb
运行结果:
红色框显示的正好是我的手柄的型号,说明手柄连接成功。
另一种测试是否连接成功的方法是,在终端输入:
$ ls /dev/input
运行结果为:
结果中出现了js0,也可能出现的是js1,不管是哪个,都说明手柄连接成功。
接下来,检验手柄上各个轴和键的对应关系,运行:
$ sudo jstest /dev/input/js0
运行结果为:
sudo 命令可以提高用户权限,因此要先输入密码。输入密码后,回车,就可以看到很多个Axes(轴)和Buttons(按钮),可以按手柄上的按钮或者摇动操作杆,就会看到终端上对应位置的数字的改变。
或者使用命令jstest-gtk,可以从可视化图上看到轴和按钮的个数以及对应关系。
运行结果为:
三、安装joy相关的包
需要订阅/joy话题,获取手柄上的信息,因此需要安装joy相关的包。
终端输入:
sudo apt-get install ros-melodic-joy
sudo apt-get install ros-melodic-joystick-drivers
注意要安装Ubuntu版本对应的Ros版本。本文中使用的Ubuntu18.04对应的Ros版本是melodic,可以在网上查询一下自己安装的Ubuntu对应的Ros版本,然后将其替换上述命令行中的“melodic”。
运行结果为:(因为我之前已经安装过,所以显示的是<ros-melodic-joy 已经是最新版>、<ros-melodic-joystick-drivers 已经是最新版> )
接下来,运行代码验证以下joy包是否安装成功。
运行命令:
$ roscore
启动Ros Master,也就是启动节点管理器,这是必要的,启动Master之后节点才可以在Master上注册节点信息、设置话题类型等。
启动Ros Master的界面如下:
设定joy节点所读取的手柄设备:
rosparam set joy_node/dev "/dev/input/js0"
之后,运行joy_node命令:
$ rosrun joy joy_node
四、(单操作杆)手柄控制海龟
在catkin_workspace的src目录下创建control_turtle功能包,使用命令为:
$ catkin_create_pkg control_turtle roscpp rospy std_msgs geometry_msgs turtlesim
roscpp rospy std_msgs geometry_msgs turtlesim均为所需的依赖包。
运行结果为:(successfully出现即创建成功)
单个遥杆控制的.cpp代码为:
Single_teleop_turtle.cpp代码:
#include <ros/ros.h>
#include <geometry_msgs/Twist.h>
#include <sensor_msgs/Joy.h>
class TeleopTurtle
{
public:
TeleopTurtle();
private:
// 处理手柄发送过来的信息
void callback(const sensor_msgs::Joy::ConstPtr &joy);
// 实例化ROS句柄
ros::NodeHandle nh;
// 定义订阅者对象,用来订阅手柄发送的数据
ros::Subscriber sub;
// 定义发布者对象,用来将手柄数据发布到乌龟控制话题上
ros::Publisher pub;
// 用来接收launch文件中设置的参数,绑定手柄摇杆、轴的映射
int axis_linear, axis_angular;
};
TeleopTurtle::TeleopTurtle()
{
// 从参数服务器读取的参数
nh.param<int>("axis_linear", axis_linear, 1);
nh.param<int>("axis_angular", axis_angular, 2);
pub = nh.advertise<geometry_msgs::Twist>("/turtle1/cmd_vel", 1);
sub = nh.subscribe<sensor_msgs::Joy>("joy", 10, &TeleopTurtle::callback, this);
}
void TeleopTurtle::callback(const sensor_msgs::Joy::ConstPtr &joy)
{
geometry_msgs::Twist vel;
// 将手柄摇杆轴拨动时值的输出赋值给乌龟的线速度和角速度
vel.linear.x = joy->axes[axis_linear];
vel.angular.z = joy->axes[axis_angular];
ROS_INFO("当前线速度为:%.3lf ; 角速度为:%.3lf", vel.linear.x, vel.angular.z);
pub.publish(vel);
}
int main(int argc, char **argv)
{
// 设置编码
setlocale(LC_ALL, "");
// 初始化ROS节点
ros::init(argc, argv, "teleop_turtle");
TeleopTurtle teleopTurtle;
ros::spin();
return 0;
}
双摇杆控制的.cpp代码为:
Double_teleop_turtle.cpp代码:
#include <ros/ros.h>
#include <geometry_msgs/Twist.h>
#include <sensor_msgs/Joy.h>
class TeleopTurtle
{
public:
TeleopTurtle();
private:
// 处理手柄发送过来的信息
void callback(const sensor_msgs::Joy::ConstPtr &joy);
// 实例化ROS句柄
ros::NodeHandle nh;
// 定义订阅者对象,用来订阅手柄发送的数据
ros::Subscriber sub;
// 定义发布者对象,用来将手柄数据发布到乌龟控制话题上
ros::Publisher pub;
// 用来接收launch文件中设置的参数,绑定手柄摇杆、轴的映射
int axis_linear, axis_angular;
int sticks_left, sticks_right;
};
TeleopTurtle::TeleopTurtle()
{
// 从参数服务器读取的参数
// 按手柄摇杆分配
nh.param<int>("sticks_left", sticks_left, 1);
nh.param<int>("sticks_right", sticks_right, 3);
// 按键分配
nh.param<int>("axis_linear", axis_linear, 1);
nh.param<int>("axis_angular", axis_angular, 2);
// Axes: 0: 0 1: 0 2: 0 3: 0 4: 0 5: 0 6: 0 7: 0
// 对应:0: 左摇杆轴左右
// 1: 左摇杆轴前后
// 2: 默认全不开放
// 3: 右摇杆轴左右
// 4: 右摇杆轴前后
// 5: 默认全不开放
// 6: 方向按键左+右
// 7: 方向按键前+后
pub = nh.advertise<geometry_msgs::Twist>("/turtle1/cmd_vel", 10);
sub = nh.subscribe<sensor_msgs::Joy>("joy", 10, &TeleopTurtle::callback, this);
}
void TeleopTurtle::callback(const sensor_msgs::Joy::ConstPtr &joy)
{
geometry_msgs::Twist vel;
// 将手柄摇杆轴拨动时值的输出赋值给乌龟的线速度和角速度
if(joy->axes[axis_linear] || joy->axes[axis_angular])
{
vel.linear.x = joy->axes[axis_linear];
vel.angular.z = joy->axes[axis_angular];
}
else if(joy->axes[sticks_left] || joy->axes[sticks_right])
{
vel.linear.x = joy->axes[sticks_left];
vel.angular.z = joy->axes[sticks_right];
}
// // 将手柄摇杆轴拨动时值的输出赋值给乌龟的线速度和角速度
// vel.linear.x = joy->axes[axis_linear];
// vel.angular.z = joy->axes[axis_angular];
// // 按键按动时也可以输出乌龟的线速度和角速度
// vel.linear.x = joy->axes[sticks_left];
// vel.angular.z = joy->axes[sticks_right];
ROS_INFO("当前线速度为:%.3lf ; 角速度为:%.3lf", vel.linear.x, vel.angular.z);
pub.publish(vel);
}
int main(int argc, char **argv)
{
// 设置编码
setlocale(LC_ALL, "");
// 初始化ROS节点
ros::init(argc, argv, "teleop_turtle");
TeleopTurtle teleopTurtle;
ros::spin();
return 0;
}
之后,在control_turtle--->src下创建Single_teleop_turtle.cpp / Double_teleop_turtle.cpp,命令为:
$ touch Single_teleop_turtle.cpp
结果为:
在catkin_workspace的src目录下创建launch_turtle功能包,
$ catkin_create_pkg launch_turtle
运行结果为:
看到 successfully ,则创建成功!
接着,在launch_turtle功能包下,右键创建launch文件夹(一般默认创建的文件夹为launch,其他当然也可以)
最后在launch文件夹下创建turtlelaunch.launch,命令为:
$ touch turtlelaunch.launch
turtlelaunch.launch中的代码为:
<launch>
<!-- 启动乌龟节点 -->
<node pkg="turtlesim" type="turtlesim_node" name="turtlesim_node"/>
<!-- 启动我们创建的手柄控制乌龟节点 -->
<node pkg="teleop" type="teleop_turtle" name="teleop_turtle" output="screen"/>
<!-- 向参数服务器写入参数 -->
<param name="axis_linear" value="1" type="int"/>
<param name="axis_angular" value="3" type="int"/>
<!-- 启动手柄节点,respawn="true"表示节点挂掉时会自动重启 -->
<node respawn="true" pkg="joy" type="joy_node" name="joystick" />
</launch>
修改CMakeLists.txt配置文件,将下面代码添加到该文件中:
# 节点构建选项,配置可执行文件
add_executable(Single_teleop_turtle src/Single_teleop_turtle.cpp)
# 节点构建选项,配置目标链接库
target_link_libraries(Single_teleop_turtle ${catkin_LIBRARIES}
)
五、结果
手柄单遥感控制turtle运动