zl程序教程

您现在的位置是:首页 >  工具

当前栏目

【ROS理论与实践】第4讲:ROS常用组件工具

组件工具 实践 常用 理论 ros
2023-09-11 14:20:36 时间

在这里插入图片描述

在这里插入图片描述

一、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 的参数,运行结果如下图:
在这里插入图片描述
不难发现,只有标签paramrosparam才能将参数存储到参数服务器ROS Master中。其中如果paramrosparam写到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_subscriberperson_publisher节点加了进来,并分别重命名为listenertalker。新开一个终端,输入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,下载好模型如下图所示:
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述