【ROS理论与实践】第4讲:ROS常用组件工具
一、Launch启动文件
从前面几篇文章可以看到,运行节点时需要手动在终端输入rosrun命令,而rosrun命令每次只能运行一个节点,且需要预先运行roscore命令。复杂机器人系统必然是有多个节点的,而多个节点运行时更是需要启动多个终端,可以看到该种启动方式甚是繁琐。那么有没有一种优雅的启动方式呢?答案是有的,就是接下来要介绍的Launch启动文件!
类似Linux中的启动脚本,不需要单独启动一个终端运行roscore命令来启动ROS Master,它会自动启动。
- name:会取代程序中ros::init初始化的节点名,当然name可以省略就默认是ros::init初始化的节点名
- output:节点的日志是否要在终端中去做输出,即日志使能开关,默认不会打印任何日志信息。
- respawn:节点运行失败,让其自动重启
- required:整个launch文件启动中,某个节点是必须的,如果这个节点启动不起来,程序就会报错。
- ns:设置命名空间(namespace)
- args:当做输入参数传递给可执行文件,通过main函数中的argc、argv去做读取
- pararm和是把参数存到ROS Master中的。而区别是:param每次只加载一个参数;rosparam可以加载整个文件中的所有参数。
- arg是仅限launch文件内部使用的局部变量。
turtlesim_parameter_config.launch
<launch>
<param name="/turtle_number" value="2"/>
<arg name="TurtleName1" default="Tom" />
<arg name="TurtleName2" default="Jerry" />
<node pkg="turtlesim" type="turtlesim_node" name="turtlesim_node">
<param name="turtle_name1" value="$(arg TurtleName1)"/>
<param name="turtle_name2" value="$(arg TurtleName2)"/>
<rosparam file="$(find learning_launch)/config/param.yaml" command="load"/>
</node>
<node pkg="turtlesim" type="turtle_teleop_key" name="turtle_teleop_key" output="screen"/>
</launch>
理解launch文件,首先找node节点。
- 第一个node节点
turtlesim_node
主要功能是启动了一个海龟仿真器。- 第二个node节点
turtle_teleop_key
主要功能是启动了一个海龟键盘控制器,其中还加了output
参数,代表要把这个node节点的日志信息,打印到终端上面!launch文件自动后,就可以通过键盘上的方向键控制海龟的移动。<param name="/turtle_number" value="2"/>
这一行的turtle_number前面加上了/
,表示是全局命名。
新开一个终端,输入
rosnode list
可以列出当前ROS中的所有节点,这个节点名称可以跟launch文件中node标签下的那么属性对应,如:<node pkg="turtlesim" type="turtlesim_node" name="turtlesim_node">
,那么必有一个节点名叫turtlesim_node
,运行结果如下图:
注:可以自行更改一下node标签的name属性,验证一下节点名是否会改变!
新开一个终端,输入
rosparam list
可以列出当前ROS中的所有参数名,这些都是存储在参数服务器 ROS Master 的参数,运行结果如下图:
不难发现,只有标签param
和rosparam
才能将参数存储到参数服务器ROS Master中。其中如果param
和rosparam
写到node
标签下,还会将该节点名称默认当做参数命名空间。如上图中的turtle_num
写到了node
标签外,所以没有前面参数命名空间;而turtle_name1
写到了节点turtlesim_node
下,所以前面加上了turtlesim_node
参数命名空间。
如果想看
rosparam list
中的各个参数值,可以使用rosparam get 参数名
,如:rosparam get /turtle_number
,运行结果如下图:
当然这种一次只能查看一个参数值,如何查看到全部的参数值呢?一种方法是直接看launch文件的启动日志信息,具体启动方式见下文,这里截取启动的部分日志信息:
param.yaml
A: 123
B: "hello"
group:
C: 456
D: "hello"
param.yaml
中的group
表示为C和D设置的命名空间。
在终端输入
roslaunch learning_launch turtlesim_parameter_config.launch
可以启动launch文件,log信息如下图所示:
turtlesim_remap.launch
<launch>
<include file="$(find learning_launch)/launch/simple.launch" />
<node pkg="turtlesim" type="turtlesim_node" name="turtlesim_node">
<remap from="/turtle1/cmd_vel" to="/cmd_vel"/>
</node>
</launch>
simple.launch
<launch>
<node pkg="learning_communication" type="person_subscriber" name="listener" />
<node pkg="learning_communication" type="person_publisher" name="talker" />
</launch>
在终端输入
roslaunch learning_launch turtlesim_remap.launch
可以启动launch文件,log信息如下图所示:
新开一个终端,输入
rostopic list
可以列出当前ROS中的所有话题,如下图所示,可以看到通过重映射remap
将话题名/turtle1/cmd_vel
改成了/cmd_vel
:
include
标签的作用是将learning_launch
功能包下simple.launch
加了进来,该launch主要作用是将learning_communication
功能包下的person_subscriber
和person_publisher
节点加了进来,并分别重命名为listener
和talker
。新开一个终端,输入rosnode list
可以看到这两个节点,如下图:
此时想要海龟再动起来,主要需要发布的话题是
/cmd_vel
,全部命令如下:rostopic pub -r 10 /cmd_vel geometry_msgs/Twist ...[Tab补全]
,如下图所示:
二、TF坐标变换
两个坐标系的变换(旋转和平移),可以通过4x4的矩阵来描述!
ROS 提供的TF功能包可以方便我们获取任意两个坐标系之间的位置关系,TF功能包具有时间属性,可以保存10s内的任意坐标系之间的位置关系。在ROS当中,所有的坐标系关系都是通过树形结构来做管理的,可以通过树形结构来查询与根节点之间的位置关系,TF底层有一个tf_tree来保存说有坐标系的位置关系。
上图是TF的一个典型应用场景,当我们知道一个坐标系的数据,可以通过TF坐标变换快速得到另一个坐标系下的数据。
rosrun tf view_frames
命令会监听五秒钟之内两只海龟的坐标系变化关系(TF),并把监听的数据保存到当前路径下的一个pdf文件中!通过PDF文件中的图可以直观的看到TF坐标系关系,首先在该仿真器中存在三个坐标系,分别是:world(全局坐标系)、turtle1(海龟1坐标系)和turtle2(海龟2坐标系),图中world是树的根节点,turtle1和turtle2通过箭头来描述与根节点world的位置关系。该程序底层规则是:海龟1在运动时,会不断广播Turtle1相对于world坐标系的位置关系,对应的箭头数据会不断发生变化。海龟2可以查询到海龟1相对于world坐标系的位置变化,并通过海龟2相对world的位置关系,从而得到海龟2相对海龟1的位置关系!
TF使用首先先让系统知道有Turtle1、Turtle2 和 world 这三个坐标系,以及Turtle1相对于world,以及Turtle2相对于world,它俩分别的关系是什么样的,然后才能查询Turtle1和Turtle2这两个坐标系之间的关系!查询之后,才能做速度指令的计算,这是一个基本的实验流程。
/**
* 该例程产生tf数据,并计算、发布turtle2的速度指令
*/
#include <ros/ros.h>
#include <tf/transform_broadcaster.h>
#include <turtlesim/Pose.h>
std::string turtle_name;
void poseCallback(const turtlesim::PoseConstPtr& msg)
{
// 创建tf的广播器
static tf::TransformBroadcaster br;
// 初始化tf数据
tf::Transform transform;
transform.setOrigin( tf::Vector3(msg->x, msg->y, 0.0) ); // 平移变换 x y z坐标;海龟在world坐标系下的坐标就相当于是海龟坐标系相对于world坐标系的坐标变化关系!
tf::Quaternion q;// 旋转
q.setRPY(0, 0, msg->theta); // 围绕z轴旋转,故x和y都为0 ;setRPY 可以把围绕三轴的旋转变成一个四元数,setRotation可以把四元数信息设置到坐标变换中来!
transform.setRotation(q);
// 广播world与海龟坐标系之间的tf数据
br.sendTransform(tf::StampedTransform(transform, ros::Time::now(), "world", turtle_name));
}
int main(int argc, char** argv)
{
// 初始化ROS节点
ros::init(argc, argv, "my_tf_broadcaster");
// 输入参数作为海龟的名字
if (argc != 2)
{
ROS_ERROR("need turtle name as argument");
return -1;
}
turtle_name = argv[1];
// 订阅海龟的位姿话题
ros::NodeHandle node;
ros::Subscriber sub = node.subscribe(turtle_name+"/pose", 10, &poseCallback);
// 循环等待回调函数
ros::spin();
return 0;
};
/**
* 该例程监听tf数据,并计算、发布turtle2的速度指令
*/
#include <ros/ros.h>
#include <tf/transform_listener.h>
#include <geometry_msgs/Twist.h>
#include <turtlesim/Spawn.h>
int main(int argc, char** argv)
{
// 初始化ROS节点
ros::init(argc, argv, "my_tf_listener");
// 创建节点句柄
ros::NodeHandle node;
// 请求产生turtle2
ros::service::waitForService("/spawn");
ros::ServiceClient add_turtle = node.serviceClient<turtlesim::Spawn>("/spawn");
turtlesim::Spawn srv;
add_turtle.call(srv);
// 创建发布turtle2速度控制指令的发布者
ros::Publisher turtle_vel = node.advertise<geometry_msgs::Twist>("/turtle2/cmd_vel", 10);
// 创建tf的监听器
tf::TransformListener listener;
ros::Rate rate(10.0);
while (node.ok())
{
// 获取turtle1与turtle2坐标系之间的tf数据
tf::StampedTransform transform;
try
{
listener.waitForTransform("/turtle2", "/turtle1", ros::Time(0), ros::Duration(3.0)); // 查询是否存在这两个坐标系
listener.lookupTransform("/turtle2", "/turtle1", ros::Time(0), transform); // 得到这两个坐标系的坐标变换结果
}
catch (tf::TransformException &ex)
{
ROS_ERROR("%s",ex.what());
ros::Duration(1.0).sleep();
continue;
}
// 根据turtle1与turtle2坐标系之间的位置关系,发布turtle2的速度控制指令
geometry_msgs::Twist vel_msg;
vel_msg.angular.z = 4.0 * atan2(transform.getOrigin().y(),
transform.getOrigin().x());//角速度
vel_msg.linear.x = 0.5 * sqrt(pow(transform.getOrigin().x(), 2) +
pow(transform.getOrigin().y(), 2)); // 线速度;sqrt计算欧氏距离,除以2s,相当于乘以0.5,得到速度
turtle_vel.publish(vel_msg);
rate.sleep();
}
return 0;
};
add_executable(turtle_tf_broadcaster src/turtle_tf_broadcaster.cpp)
target_link_libraries(turtle_tf_broadcaster ${catkin_LIBRARIES})
add_executable(turtle_tf_listener src/turtle_tf_listener.cpp)
target_link_libraries(turtle_tf_listener ${catkin_LIBRARIES})
<launch>
<!-- Turtlesim Node-->
<node pkg="turtlesim" type="turtlesim_node" name="sim"/>
<node pkg="turtlesim" type="turtle_teleop_key" name="teleop" output="screen"/>
<node pkg="learning_tf" type="turtle_tf_broadcaster" args="/turtle1" name="turtle1_tf_broadcaster" />
<node pkg="learning_tf" type="turtle_tf_broadcaster" args="/turtle2" name="turtle2_tf_broadcaster" />
<node pkg="learning_tf" type="turtle_tf_listener" name="listener" />
</launch>
三、可视化显示与仿真工具
上述模型下载网址已更改,如果下载可到我的 gitee 上,下载命令:
git clone https://gitee.com/ReCclay/gazebo_models.git
另外需要注意,默认不存在
models
文件夹,需要自己手动创建下!在资源管理器中显示隐藏文件快捷键是ctrl+H
,下载好模型如下图所示: