ROS中级教程知识--一篇完结

本文详细介绍了如何手动创建ROS package,包括创建package.xml和CMakeLists.txt文件,以及如何管理依赖项。ROS包是ROS系统中的基本单元,用于组织代码和资源。文中还提到了rosdep工具,用于处理系统依赖项。此外,文章讨论了roslaunch在大型项目中的使用,包括如何编写可重用的launch文件,使用machine tags和环境变量来控制节点的部署,以及参数、namespace和yaml文件的管理。整个教程旨在帮助读者深入理解ROS包的创建和管理过程。
摘要由CSDN通过智能技术生成

手动创建ROS package

我们可以使用(catkin_create_pkg)工具来自动创建ROS package,不过,接下来你就会发现,这不是什么难事。 roscreate-pkg节省精力避免错误,但package只不过就是一个文件夹外加一个XML文件。

现在,我们要手动创建一个名为foobarpackage。请转至你的catkin workspace,并确认你已经刷新了setup文件。

catkin_ws_top $ mkdir -p src/foobar

catkin_ws_top $ cd src/foobar

我们要做的第一件事就是添加配置文件。 package.xml 文件使得像rospack这类工具能够检测出我们所创建的package的依赖项.

 foobar/package.xml文件里添加如下语句:

<package>

  <name>foobar</name>

  <version>1.2.4</version>

  <description>

  This package provides foo capability.

  </description>

  <maintainer email="foobar@foo.bar.willowgarage.com">PR-foobar</maintainer>

  <license>BSD</license>

  <buildtool_depend>catkin</buildtool_depend>

  <build_depend>roscpp</build_depend>

  <build_depend>std_msgs</build_depend>

  <run_depend>roscpp</run_depend>

  <run_depend>std_msgs</run_depend>

</package>

同样,你可以参考 this page from catkin tutorial 以获得更多关于 catkin/package.xml的信息。

现在,你的package里已经包含配置文件(package.xml)ROS能够找到它。试一试,执行如下指令:

rospack find foobar

如果ROS配置正确,你将会看到类似如下的信息: /home/user/ros/catkin_ws_top/src/foobar. 这就是ROS如何在后台寻找package的机理。

应该注意到我们刚才所创建的package依赖于 roscpp  std_msgs.catkin恰恰是利用这些依赖项来配置所创建的package

至此,我们还需要一个 CMakeLists.txt 文件,这样 catkin_make才能够利用 CMake 强大的跨平台特性来编译所创建的package

 foobar/CMakeLists.txt文件里输入:

cmake_minimum_required(VERSION 2.8.3)

project(foobar)

find_package(catkin REQUIRED roscpp std_msgs)

catkin_package()

这就是你想在ROS下用catkin编译package所要做的全部工作。当然了,如果想要让它真正的编译一些东西,你还需要学习关于CMake宏的一些知识。 请参考 CMakeLists.txt 以获得更多信息。回顾初级教程如 (CreatingPackage ) 来修改你的 package.xml  CMakeLists.txt文件

管理系统依赖项

System Dependencies

ROS packages有时会需要操作系统提供一些外部函数库,这些函数库就是所谓的系统依赖项。在一些情况下,这些依赖项并没有被系统默认安装,因此,ROS提供了一个工具rosdep来下载并安装所需系统依赖项。

ROS packages必须在配置文件中声明他们需要哪些系统依赖项。下面我们来看看turtlesim package的配置文件:

$ roscd turtlesim

然后,

rosdep

rosdep 是一个能够下载并安装ROS packages所需要的系统依赖项的小工具 使用方法:

rosdep install [package]

turtlesim下载并安装系统依赖项:

$ rosdep install turtlesim

如果是按照教程的顺序学习,那么这很可能是你第一次使用rosdep工具,因此,当执行这条指令,你会看到如下的报错消息:

  • ERROR: your rosdep installation has not been initialized yet.  Please run:
  •         
  •     sudo rosdep init
  •     rosdep update

按照提示执行上边两条指令,并再次安装turtlesim的系统依赖项。

如果你安装的是二进制文件,那么会看到如下信息:

  • All required rosdeps installed successfully

不然,你将看到如下的turtlesim依赖项安装信息:

  • set -o errexit
  • set -o verbose
  • if [ ! -f /opt/ros/lib/libboost_date_time-gcc42-mt*-1_37.a ] ; then
  •   mkdir -p ~/ros/ros-deps
  •   cd ~/ros/ros-deps
  •   wget --tries=10 http://pr.willowgarage.com/downloads/boost_1_37_0.tar.gz
  •   tar xzf boost_1_37_0.tar.gz
  •   cd boost_1_37_0
  •   ./configure --prefix=/opt/ros
  •   make
  •   sudo make install
  • fi
  • if [ ! -f /opt/ros/lib/liblog4cxx.so.10 ] ; then
  •   mkdir -p ~/ros/ros-deps
  •   cd ~/ros/ros-deps
  •   wget --tries=10 http://pr.willowgarage.com/downloads/apache-log4cxx-0.10.0-wg_patched.tar.gz
  •   tar xzf apache-log4cxx-0.10.0-wg_patched.tar.gz
  •   cd apache-log4cxx-0.10.0
  •   ./configure --prefix=/opt/ros
  •   make
  •   sudo make install
  • fi

rosdep 运行上述bash脚本,并在完成后退出。

Roslaunch在大型项目中的使用技巧

Introduction

机器人上的应用经常涉及到nodes间的交互,每个node都有很多的parameters。在二维平面上的导航就是一个很好的例子。2dnav_pr2 包含了基本的运动节点、定位、地面识别、底层控制器和地图服务器。 同时, 还有几百个 ROS parameters 影响着这些node的行为模式。此外,也还存在着一些额外的约束。例如为了提高效率,地面识别节点应该跟倾斜的激光节点运行在同一台机器上。

一个roslaunch文件能够让你一次性将这些都配置好。在一个机器人上,roslaunch一下2dnav_pr2 package里的2dnav_pr2.launch文件就可以启动机器人导航时所需的所有东西。在本教程中,我们将仔细研究launch文件和它所具有的功能。

我们希望launch文件能够尽可能的重用,这样在不同的机器人平台上就不需要修改这些launch文件就能使用。即使是从真实的环境转到模拟环境中也只要稍微修改即可。接下来,我们将要研究如何构建launch文件才能实现其最大化的重用。

高层级的结构

这是一个高层级的launch文件 (利用指令 "rospack find 2dnav_pr2/move_base/2dnav_pr2.launch"可以找到).

<launch>

  <group name="wg">

    <include file="$(find pr2_alpha)/$(env ROBOT).machine" />

    <include file="$(find 2dnav_pr2)/config/new_amcl_node.xml" />

    <include file="$(find 2dnav_pr2)/config/base_odom_teleop.xml" />

    <include file="$(find 2dnav_pr2)/config/lasers_and_filters.xml" />

    <include file="$(find 2dnav_pr2)/config/map_server.xml" />

    <include file="$(find 2dnav_pr2)/config/ground_plane.xml" />

    <!-- The navigation stack and associated parameters -->

    <include file="$(find 2dnav_pr2)/move_base/move_base.xml" />

  </group>

</launch>

这个文件引用了其他的文件。在这些被引用的文件中都包含有与系统有关的nodeparameter(甚至是嵌套引用),比如定位、传感器处理和路径规划。

编写技巧: 高层级的launch文件应该简短,利用include指令将系统的组成部分和ROS parameter引用过来即可。

接下来我们将会看到,这种技巧使得我们可以很容易的替换掉系统的某个部分。

想要在PR2运行这个应用,我们需要启动core,接着roslaunch一个具体机器人的launch文件,例如在 pr2_alpha package里的pre.launch文件,最后roslaunch一下2dnav_pr2.launch。与其分开roslaunch这么多的文件,我们可以一次性将它们roslaunch起来。这会有一些利弊权衡:

  • 优点: 我们可以少做几个 "打开新终端, roslaunch" 的步骤。
  • 缺陷1: roslaunch一个launch文件会有一个持续一段时间的校准过程。如果2dnav_pr2 launch文件引用了机器人的launch文件,当我们用control-c终止这个roslaunch进程,然后又再次开启这个进程,校准过程还得再来一遍。
  • 缺陷2: 一些导航node要求校准过程必须在它启动之前完成。roslaunch目前还没有对节点的启动时间和顺序进行控制。最完美的方案当然是让导航节点等到校准过程完成后再启动,但就目前的情况来看,把他们分别放在两个launch文件里,直到校准过程结束再启动导航节点是个可行的方案。

因此,对于是否应该把多个启动项放到一个launch文件里并没有一个统一的标准。就本教程的案例而言,我们把他们放到两个launch文件里。

编写技巧:在决定应用需要多少个高层级的launch文件时,你要考虑利弊的权衡。

Machine tags and Environment Variables

为了平衡负载和管理带宽,我们需要控制哪些节点在哪个机器上运行。比如,我们希望amclbase laser在同一台机器上运行。同时,考虑到重用性,我们不希望把具体的机器名写入launch文件。roslaunch使用machine tags来解决这个问题。

第一个引用如下:

<include file="$(find pr2_alpha)/$(env ROBOT).machine" />

首先应该注意到这个文件使用 env 置换符来使用ROBOT变量的值。例如,在roslaunch指令前执行:

export ROBOT=pre

将会使得pre.machine 被引用。

编写技巧: 使用 env 置换符可以使得launch文件的一部分依赖于环境变量的值。

接下来,我们来看看pr2_alpha package里的 pre.machine 文件。

<launch>

  <machine name="c1" address="pre1" ros-root="$(env ROS_ROOT)" ros-package-path="$(env ROS_PACKAGE_PATH)" default="true" />

  <machine name="c2" address="pre2" ros-root="$(env ROS_ROOT)" ros-package-path="$(env ROS_PACKAGE_PATH)" />

</launch>

这个文件对本地机器名进行了一个映射,例如"c1" "c2"分别对应于机器名"pre1""pre2"。甚至可以控制你登录的用户名。(前提是你有ssh证书)

一旦这个映射建立好了之后,就可以用于控制节点的启动。比如,2dnav_pr2 package里所引用的config/new_amcl_node.xml文件包含这样的语句:

<node pkg="amcl" type="amcl" name="amcl" machine="c1">

这可以控制 amcl节点在机器名为c1的机器上运行。(查看其他的launch文件,你可以看到大多数激光传感器处理节点都在这个机器上运行)

当我们要在另一个机器人上运行程序,比如说机器人prf,我们只要修改ROBOT环境变量的值就可以了。相应的机器配置文件(pr2_alpha package里的prf.machine文件)就会被加载。我们甚至可以通过设置ROBOTsim从而是得程序可以在一个模拟机器人上运行。查看pr2_alpha package里的sim.machine文件,它只是将所有的机器名映射到了本地主机名

编写技巧: 使用machine tags来平衡负载并控制节点在机器上的启动,同时也因考虑将机器配置文件(.machine)跟系统变量关联起来以便于重复利用。

Parameters, namespaces, and yaml files

我们来看一下被引用的 move_base.xml文件。文件的一部分如下:

<node pkg="move_base" type="move_base" name="move_base" machine="c2">

  <remap from="odom" to="pr2_base_odometry/odom" />

  <param name="controller_frequency" value="10.0" />

  <param name="footprint_padding" value="0.015" />

  <param name="controller_patience" value="15.0" />

  <param name="clearing_radius" value="0.59" />

  <rosparam file="$(find 2dnav_pr2)/config/costmap_common_params.yaml" command="load" ns="global_costmap" />

  <rosparam file="$(find 2dnav_pr2)/config/costmap_common_params.yaml" command="load" ns="local_costmap" />

  <rosparam file="$(find 2dnav_pr2)/move_base/local_costmap_params.yaml" command="load" />

  <rosparam file="$(find 2dnav_pr2)/move_base/global_costmap_params.yaml" command="load" />

  <rosparam file="$(find 2dnav_pr2)/move_base/navfn_params.yaml" command="load" />

  <rosparam file="$(find 2dnav_pr2)/move_base/base_local_planner_params.yaml" command="load" />

</node>

这一小段代码负责启动move_base节点。 第一个引用元素是 remapping. 设计Move_base时是希望它从 "odom" topic 接收里程计信息的。在这个pr2案例里,里程计信息是发布在pr2_base_odometry topic,所以我们要重新映射一下。

编写技巧: 当一个给定类型的信息在不同的情况下发布在不同的topic上,我们可以使用topic remapping

这个文件有好几个<param>标签。这些参数是节点的内部元素(因为它们都写在</node>之前),因此它们是节点的私有参数私有参数。比如,第一个参数将move_base/controller_frequency设置为10.0

<param>元素之后,还有一些<rosparam>元素,它们将从yaml文件中读取参数。yaml是一种易于人类读取的文件格式,支持复杂数据的结构。这是第一个<rosparam>所加载的costmap_common_params.yaml文件一部分:

raytrace_range: 3.0

footprint: [[-0.325, -0.325], [-0.325, 0.325], [0.325, 0.325], [0.46, 0.0], [0.325, -0.325]]

inflation_radius: 0.55

# BEGIN VOXEL STUFF

observation_sources: base_scan_marking base_scan tilt_scan ground_object_cloud

base_scan_marking: {sensor_frame: base_laser, topic: /base_scan_marking, data_type: PointCloud, expected_update_rate: 0.2,

  observation_persistence: 0.0, marking: true, clearing: false, min_obstacle_height: 0.08, max_obstacle_height: 2.0}

我们看到yaml支持向量等数据结构(如上边的footprint就是向量)。它同样支持将嵌套的域名空间,比如base_laser被归属到了base_scan_marking/sensor_frame这样的嵌套域名下。注意这些域名都是归属于yaml文件自身域名global_costmap之下,而yaml文件的域名是由ns变量来控制的。 同样的, 由于 rosparam 都被包含在节点里, 所以参数的完整名称就是/move_base/global_costmap/base_scan_marking/sensor_frame.

接着一行是:

<rosparam file="$(find 2dnav_pr2)/config/costmap_common_params.yaml" command="load" ns="local_costmap" />

这跟上一行引用的是完全一样的yaml文件,只不过他们的域名空间不一样(local_costmap域名只影响运动路径控制器,而global_costmap影响到全局的导航规划)。这样可以避免重新给同样的变量再次赋值。

再下一行是:

<rosparam file="$(find 2dnav_pr2)/move_base/local_costmap_params.yaml" command="load"/>

跟上一行不同,这一行没有ns属性。因此这个yaml文件的域名就是/move_base。但是再仔细查看一下这个yaml文件的前几行:

local_costmap:

  #Independent settings for the local costmap

  publish_voxel_map: true

  global_frame: odom_combined

  robot_base_frame: base_link

最终我们可以确定参数都是归属于/move_base/local_costmap域名之下。

编写技巧: Yaml文件允许复杂的嵌套域名的参数,相同的参数值可以在多个地方重复使用。

launch文件的重用

上述的编写技巧都是为了使得launch文件能再不同的环境下更易于重用。从上边的一个例子我们已经知道,使用env子变量可以在不改动launch文件的情况下就改变其行为模式。但是仍然在某些情况下,重用launch文件还是很麻烦甚至不肯能。我们来看一下pr2_2dnav_gazebo package。它有2d导航功能,但是只是为Gazebo模拟器而设计的。 对于导航来说,唯一改变了的就是我们所使用的Gazebo环境是一张静态地图,因此map_server节点必须重载其参数。当然这里我们可以使用另外一个env变量,但这会使得用户还得设置一大堆变量才能够roslaunch 因此,2dnav gazebo有它自己的高层级launch文件,叫'2dnav-stack-amcl.launch',如下所示:

<launch>

  <include file="$(find pr2_alpha)/sim.machine" />

  <include file="$(find 2dnav_pr2)/config/new_amcl_node.xml" />

  <include file="$(find 2dnav_pr2)/config/base_odom_teleop.xml" />

  <include file="$(find 2dnav_pr2)/config/lasers_and_filters.xml" />

  <node name="map_server" pkg="map_server" type="map_server" args="$(find gazebo_worlds)/Media/materials/textures/map3.png 0.1" respawn="true" machine="c1" />

  <include file="$(find 2dnav_pr2)/config/ground_plane.xml" />

  <!-- The naviagtion stack and associated parameters -->

  <include file="$(find 2dnav_pr2)/move_base/move_base.xml" />

</launch>

首先,因为我们知道这是一个模拟器,所以直接使用sim.machine文件,而不必再使用$(env ROBOT)变量来选择。其次,原来的

<include file="$(find 2dnav_pr2)/config/map_server.xml" />

已经被

<node name="map_server" pkg="map_server" type="map_server" args="$(find gazebo_worlds)/Media/materials/textures/map3.png 0.1" respawn="true" machine="c1" />

所替换。两者都包含了节点的声明,但他们来自不同的文件。

编写技巧: 想要改变应用的高层级功能,只要修改launch文件的相应部分即可。

参数重载

在某些情况下,上述技巧会很不方便。比如,使用2dnav_pr2但希望修改local costmap的分辨率为0.5。我们只需要修改local_costmap_params.yaml文件即可。我们本来只是想临时的修改,但这种方法却意味着它被永久修改了。也许我们可以将local_costmap_params.yaml文件拷贝一份并做修改,但这样我们还需要修改move_base.xml文件去引用修改后的yaml文件。 接着我们还得修改 2dnav_pr2.launch文件去引用被修改后的xml文件。这是很花时间的工作,而且假如我们使用了版本控制,我却看不到版本间有任何的改变。另外一种方法是新建立一个launch文件,这样就可以在2dnav_pr2.launch文件中定义move_base/local_costmap/resolution参数,修改这个参数就可以满足我们都要求。如果我们能够提前知道哪些参数有可能被修改,这会是一个很好的办法。

更好的方法是利用roslaunch的重载特性:参数按顺序赋值(在引用被执行之后)。这样,我们可以构建另外一个可以重载参数的高层级文件:

<launch>

<include file="$(find 2dnav_pr2)/move_base/2dnav_pr2.launch" />

<param name="move_base/local_costmap/resolution" value="0.5"/>

</launch>

这个方法的主要缺点在于它使得文件难以理解:想要知道一个参数的值需要追溯launch的引用。但它确实避免了多次拷贝文件然后修改它们。

编写技巧: 利用roslaunch重载功能来修改一个深深嵌套在launch文件分支中的参数。

Roslaunch arguments

CTurtle版本中,roslaunch还有一个和标签(tags)类似的参数替换特性,它允许依据变量的值来有条件启动某个launch文件。这比上述的参数重载和launch文件重用来得更简洁,适用性更强。但它需要一些额外操作来完成这个功能:修改原始launch文件来指定哪些变量是可变的。请参考roslaunch XML documentation.

编写技巧: 在能够修改原始launch文件的情况下,优先选择使用roslaunch变量,而不是参数重载和拷贝launch文件的方法。

ROS在多机器人上的使用

概述

ROS设计的灵魂就在于其分布式计算。一个优秀的节点不需要考虑在哪台机器上运行,它允许实时分配计算量以最大化的利用系统资源。(有一个特例——驱动节点必须运行在跟硬件设备有物理连接的机器上)。在多个机器人上使用ROS是一件很简单的事,你只需要记住一下几点:

  • 你只需要一个master,只要在一个机器上运行它就可以了。
  • 所有节点都必须通过配置 ROS_MASTER_URI连接到同一个master。
  • 任意两台机器间任意两端口都必须要有完整的、双向连接的网络。(参考ROS/NetworkSetup).
  • 每台机器都必须向其他机器广播其能够解析的名字。(参考 ROS/NetworkSetup).

跨机器运行的 Talker / listener

假如说我们希望在两台机器上分别运行talker / listener 主机名分别为 marvin  hal.登陆主机名为marvin的机器,你只要:

ssh marvin

同样的方法可以登陆hal.

启动 master

我们需要选择一台机器运行master,这里我们选hal启动master的第一步是:

ssh hal

roscore

启动 listener

接下来我们在机器hal上启动listener, 并配置ROS_MASTER_URI,这样就可以使用刚刚启动的master:

ssh hal

export ROS_MASTER_URI=http://hal:11311

rosrun rospy_tutorials listener.py

启动 talker

现在我们要在marvin 机器上启动talker,同样通过配置ROS_MASTER_URI来使用hal机器上的master:

ssh marvin

export ROS_MASTER_URI=http://hal:11311

rosrun rospy_tutorials talker.py

小惊喜: 现在你可以看到机器hal上的listener正在接收来自marvin机器上talker发布的消息。

请注意,talker / listener启动的顺序是没有要求的, 唯一的要求就是master必须先于节点启动。

反向测试

现在我们来尝试一下反向测试。终止talkerlistener的运行,但仍然保留master在机器 hal上,然后让talkerlisterner交换机器运行。

首先,在机器marvin启动listerner:

ssh marvin

export ROS_MASTER_URI=http://hal:11311

rosrun rospy_tutorials listener.py

然后在机器hal上启动talker:

ssh hal

export ROS_MASTER_URI=http://hal:11311

rosrun rospy_tutorials talker.py

运行出错

如果没有取得如上预期的效果,那么很有可能是你的网络配置出错了。参考ROS/NetworkSetup重新配置你的网络。

译者注

根据译者的尝试,如果你想取得如上预期效果,你还需配置ROS_IP为当前的局域网ip地址。(利用ifconfig指令可以查看你当前的ip地址)。其次,很有可能你的主机名不能够被其他机器解析,所以保险的方法是利用 ssh hostname@local_ip的方式进行登陆(ssh turtlebot@192.168.1.100)。再者,ROS_MASTER_URI最好也用运行master的那台机器的ip地址来替换主机名(如:export ROS_MASTER_URI=http://192.168.1.100:11311)

自定义消息

自定义消息

自定义一个消息类型很简单,只要将.msg文件放到一个packagemsg文件夹下即可。请参考创建.msg 文件 (不要忘记选择相应的编译构建系统)

引用和输出消息类型

消息类型都被归属到与package相对应的域名空间下,例如:

C++

切换行号显示

   1 #include <std_msgs/String.h>

   2

   3 std_msgs::String msg;

Python

切换行号显示

   1 from std_msgs.msg import String

   2

   3 msg = String()

依赖项

如果你要使用在其他package里定义的消息类型,不要忘记添加以下语句:

<build_depend>name_of_package_containing_custom_msg</build_depend>

<run_depend>name_of_package_containing_custom_msg</run_depend>

 package.xml

教程ROSNodeTutorialPython展示了使用自定义消息类型来创建talkerlistenerC++Python实现。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

申扬

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值