ROS 2 基本命令总结

ROS 2 基本命令总结

  • 1. 创建工作空间
  • 2. 实例测试
  • 3. terminal 命令
    • 3.1 run
    • 3.2 node
      • 3.2.1 node list
      • 3.2.2 Remapping
    • 3.2.3 node info
    • 3.3 topic
      • 3.3.1 rqt_graph
      • 3.3.2 topic list
      • 3.3.3 topic echo
      • 3.3.4 topic echo
      • 3.3.5 topic pub
      • 3.3.6 topic hz
    • 3.4 service
    • 3.5 param
      • 3.5.1 param list
      • 3.5.2 param get
      • 3.5.3 param set
      • 3.5.4 param dump
      • 3.5.5 load param file
      • 3.6 action
      • 3.6.1 action list
    • 3.7 bag
      • 3.7.1 bag record
      • 3.7.2 Record multiple topics
      • 3.7.3 bag info
      • 3.7.4 bag play
  • 4. Package
    • 4.1 创建 package
    • 4.2 编写一个简单的publisher subscriber程序
      • 4.2.1 编写publisher程序
      • 4.2.2 添加依赖项
      • 4.2.3 CMakeLists.txt
      • 4.2.4 编写subscriber程序
  • 5. 创建msg和srv
    • 5.1 生成功能包
      • 5.1.1 msg文件定义
      • 5.1.2 srv文件定义
      • 5.1.3 CMakeLists.txt
      • 5.1.4 package.xml
    • 5.2 测试接口
      • 5.2.1 测试 Num.msg
  • 6. Launch
    • 6.1 运行方式
  • 7. colcon
  • 8. ament_cmake
    • 8.1 基本的项目框架
    • 8.2 添加库文件和头文件
    • 8.3 添加依赖项

1. 创建工作空间

ROS相同,ROS 2也是建议创建一个工作空间,方便管理同一个项目的packages,而且也是将package源文件都放入src文件夹中。
创建工作空间:

mkdir -p ~/colcon_ws/src
cd colcon_ws/src

现在我们先关注 colcon 的编译过程,所以 package 源文件就先借用官网的。

git clone https://github.com/ros2/examples 
git checkout $ROS_DISTRO     # 切换到与本机版本对应的 branch 上
# 电脑上要是有两个ROS系统,需要先 source /opt/ros/eloquent/setup.bash

colcon 编译下载的 package

cd ..
colcon build

编译后,产生了 build, install, log 三个新文件夹。
如果要单独编译某一个 package,可以用如下命令:

colcon build --packages-select  PACKAGE_NAME

如果不希望编译某一个 package,可以在该 package 中创建名为 COLCON_IGNORE 的空文件,colcon 就会忽略掉该 package,不但不编译,连 colcon list 都不显示,这个 packagecolcon 就是透明的。

完成编译之后,要 source 一下 setup.bash 文件,确保系统能够找到当前编译生成的可执行文件和库:

source install/setup.bash

或者将 source 命令放入 .bashrc 文件,这样每次打开 terminal 就可以自动加载路径信息:

echo "source ~/colcon_ws/install/setup.bash" >> ~/.bashrc

我自己的.bashrc文件修改如下(针对电脑双ROS):
这里为老版本,使用起来比较方便,但是在部分程序的 build 等步骤会杂糅很多不需要的东西:

# version 1.0
CUR_PATH=$(pwd | cut -d / -f 4)
if [[ "$CUR_PATH" == "colcon_ws" ]];then
  source /opt/ros/eloquent/setup.bash
  source ~/colcon_ws/install/setup.bash
  echo 'ROS 2 sourced'
else
  source /opt/ros/melodic/setup.bash
  source ~/catkin_ws/devel/setup.bash
  echo 'ROS sourced'
fi

因此修正后的代码为:

# version 2.0
alias ros1_="source /opt/ros/melodic/setup.bash && source ~/catkin_ws/devel/setup.bash && echo 'ROS sourced' "
alias ros2_="source /opt/ros/eloquent/setup.bash && source ~/colcon_ws/install/setup.bash && echo 'ROS 2 sourced'"

2. 实例测试

首先启动一个 publisher

ros2 run examples_rclcpp_minimal_publisher publisher_member_function

再启动一个 subscriber

 ros2 run examples_rclcpp_minimal_subscriber subscriber_member_function 

终端显示如下:
ROS 2 基本命令总结_第1张图片ROS 2 基本命令总结_第2张图片这里与 ROS 最大的区别不需要启动 ROS master 节点,即不需要类似 roscore 的命令。ROS 2 是真正的分布式系统,不需要中心节点,这样系统的鲁棒性更强,不会因为中心节点失效而影响整个系统。

3. terminal 命令

ROS 2 根据命令的作用对象划分成多个类别,其中常用的几个类别:

Commands:
  launch     Run a launch file
  node       Various node related sub-commands
  param      Various param related sub-commands
  pkg        Various package related sub-commands
  run        Run a package specific executable
  service    Various service related sub-commands
  srv        Various srv related sub-commands
  topic      Various topic related sub-commands

在调用时都是采用如下命令格式:

ros2 COMMAND ...

我们可以对比 ROSROS 2 中的几个命令,应该很容易找到其中的规律

  • 运行 ROS node
    - ROS:rosrun
    - ROS 2:ros2 run

  • 查看当前运行的 node
    - ROS:rosrun
    - ROS 2:rosrun

3.1 run

命令 ros2 run 从一个 package 中情动可执行文件:

ros2 run  

3.2 node

3.2.1 node list

ros2 node list 将会展示你所有运行着的节点名称:

ros2 node list

3.2.2 Remapping

Remapping 允许你重新分配默认节点属性,像节点名, topic 名,service 名等等;
重新分配节点名 /turtlesim 的方式如下:

ros2 run turtlesim turtlesim_node --ros-args --remap __node:=my_turtle

此时会出现两个节点名:

/turtlesim
/my_turtle

3.2.3 node info

node info 的方式可以获取节点的更多信息:

ros2 node info 

3.3 topic

topic 不一定是一对一的通信,它也可以是一对多、多对一或者多对多的。

3.3.1 rqt_graph

rqt_graph 用来可视化正在改变的 nodetopic
要运行 rqt_graph,需打开一个新终端并输入命令:

rqt_graph

3.3.2 topic list

在新的终端下运行 ros2 topic list 命令会返回在系统内所有当前活跃的 topic

ros2 topic list

ros2 topic list -t 会返回相同的 topic 列表,这时候 topic 会额外显示其类型:

ros2 topic list -t

差异如下:

  • ros2 topic list
/parameter_events
/rosout
/turtle1/cmd_vel
  • ros2 topic list -t
/parameter_events [rcl_interfaces/msg/ParameterEvent]
/rosout [rcl_interfaces/msg/Log]
/turtle1/cmd_vel [geometry_msgs/msg/Twist]

3.3.3 topic echo

浏览数据在话题上发布:

ros2 topic echo 

3.3.4 topic echo

浏览数据在话题上发布:

ros2 topic echo 

3.3.5 topic pub

该命令可以用来直接从命令行向一个 topic 内发布数据:

ros2 topic pub   ''

参数是实际数据,需要以 YAML 语法输入,示例如下:

  • 例1:
ros2 topic pub --once /turtle1/cmd_vel geometry_msgs/msg/Twist "{linear: {x: 2.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 1.8}}"

其中 --once 是一个可选参数,其意味着 “发布一个消息然后退出”。

  • 例2:
ros2 topic pub --rate 1 /turtle1/cmd_vel geometry_msgs/msg/Twist "{linear: {x: 2.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 1.8}}"

这里 --rate 1 代替了 --once 选项,意思是以一个 1Hz 的稳定流发布命令。

3.3.6 topic hz

使用该命令可以发布数据的速率:

ros2 topic hz /turtle1/pose

输出类似如下:

average rate: 59.354
  min: 0.005s max: 0.027s std dev: 0.00284s window: 58

3.4 service

service 是另一种节点通信方法。service 基于 call-response 模式,与 publisher-subscriber 模式相对。topic 允许节点 suscribe 数据流并获得持续更新,而 service 仅在被 client 指定调用时才会提供数据
service 的使用方法与 topic 类似。

3.5 param

3.5.1 param list

为了查看参数的节点归属,可以输入如下命令:

ros2 param list

就可以看到每个节点的子命名空间,紧接着的是每个节点的参数:

/teleop_turtle:
  scale_angular
  scale_linear
  use_sim_time
/turtlesim:
  background_b
  background_g
  background_r
  use_sim_time

需要注意的是,每个节点都有参数 use_sim_time,它并不是独一无二的。

3.5.2 param get

该命令用于获取当前参数的值:

ros2 param get  

例如探究上述 /turtlesim 内的参数 background_g

ros2 param get /turtlesim background_g

其参数值为:

Integer value is: 86

现在就可以知道 background_g 保留一个整数值。

3.5.3 param set

为了在运行时间内改变参数值,使用命令:

ros2 param set   

例如:

ros2 param set /turtlesim background_r 150

3.5.4 param dump

可以将一个节点的所有参数值 “dump” 进一个文件以储存,使得之后能使用该命令:

ros2 param dump 

例如,如果想储存节点 /turtlesim 当前的配置参数,可以输入命令:

ros2 param dump /turtlesim

终端将会返回消息:

Saving to:  ./turtlesim.yaml

你将发现在该工作空间目录有一个新文件被写入了。打开该文件,会显示以下内容:

turtlesim:
  ros__parameters:
    background_b: 255
    background_g: 86
    background_r: 150
    use_sim_time: false

该参数表需要手动加载。

3.5.5 load param file

为了使用在上一小节中保存的参数值,输入:

ros2 run   --ros-args --params-file 

与之前运行可执行文件方式一样,就是多了两个 flags: ‘–ros-args’ 和 ‘–params-file’,之后再接上想要加载的文件。
紧接着上一节的示例,想要读取该参数,需输入命令:

ros2 run turtlesim turtlesim_node --ros-args --params-file ./turtlesim.yaml

3.6 action

ROS 中常用的通信机制是话题(Topic)和服务(Service),但是在很多场景下,这两种通信机制往往满足不了所有需求。比如用话题发布运动目标,由于话题是单向通信,则需要另外订阅一个话题来获得机器人运动过程中的状态反馈。如果用服务发布运动目标,虽然可获得一次反馈信息,但是对于控制来说数据太少,而且当反馈迟迟没有收到时,就只能傻傻等待,做不了其他事情。
action 是一种类似于 Service 的问答通信机制,不同之处在于 action 带有连续反馈,可以不断反馈任务进度,也可以在任务过程中中止运行(actions are preemptable)。使用 action 发布机器人的运动目标,机器人在收到这个 action 后就开始运动,在运动过程中不断反馈当前的运动状态;过程中也可以随时取消运动,让机器人停止;当机器人完成运动目标后,action 返回任务完成的消息。

3.6.1 action list

为了识别在 ROS 中所有的 action,运行此命令:

ros2 action list

3.7 bag

3.7.1 bag record

记录发布到一个 topic 的消息可使用命令语句:

ros2 bag record 

3.7.2 Record multiple topics

也可以同时记录多个话题:

ros2 bag record -o subset /turtle1/cmd_vel /turtle1/pose

选项 -o 允许给 bag 文件定义一个独一无二的名称。紧接着的字符串,subset 就是文件名。

3.7.3 bag info

该命令用来查看记录好的 bag:

ros2 bag info 

3.7.4 bag play

该命令用于播放记录好的 bag:

ros2 bag play 

注意,与 ROS 不同的是,该命令并不会显示播放的时长以及总时长

4. Package

4.1 创建 package

在 ROS 2 内,创建一个新的功能包的命令语法为:

ros2 pkg create --build-type ament_cmake 

例如使用语句:

ros2 pkg create --build-type ament_cmake --node-name my_node my_package

上述语句多使用了一个可选参数 --node-name,它产生了一个叫 my_node 的 Hello World 类型可执行文件(在src中创建了名为my_node.cpp的文件)。

在 build 的时候要是只想搭建功能包 my_package,可以运行:

colcon build --packages-select my_package

一般来说,使用以下命令会更方便:

ros2 pkg create --build-type ament_cmake <pkg-name> --dependencies [deps]
# ros2 pkg create --build-type ament_cmake cpp_parameters --dependencies rclcpp

--dependencies 后接所需要的依赖项,这样依赖项会被自动添加进 package.xmlCMakeLists.txt 内。

4.2 编写一个简单的publisher subscriber程序

4.2.1 编写publisher程序

使用示例文件 publisher_member_function.cpp

#include  // C++11日期和时间库
#include 
#include 
#include 

#include "rclcpp/rclcpp.hpp" // 包含了 ROS2 系统的大多部分(rclcpp:ROS Client Library for C++)
#include "std_msgs/msg/string.hpp" // 这里跟 ROS1 区别不大

using namespace std::chrono_literals;

/* This example creates a subclass of Node and uses std::bind() to register a
* member function as a callback from the timer. */

// 通过从 rclcpp::Node 继承创建节点类。代码中的每一个this都指代这个node
class MinimalPublisher : public rclcpp::Node
{
  public:
    MinimalPublisher()
    : Node("minimal_publisher"), count_(0) //节点被命名为了 minimal_publisher,初始化count_为0
    {
      publisher_ = this->create_publisher<std_msgs::msg::String>("topic", 10);
      timer_ = this->create_wall_timer( // 初始化timer_
      500ms, std::bind(&MinimalPublisher::timer_callback, this)); // 一秒执行两次timer_callback
    }

  private:
    void timer_callback()
    {
      auto message = std_msgs::msg::String();
      message.data = "Hello, world! " + std::to_string(count_++);
      RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());// 确保每一个被发布的消息都被打印到控制台
      publisher_->publish(message);
    }
    rclcpp::TimerBase::SharedPtr timer_;
    rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
    size_t count_;
};

  int main(int argc, char * argv[])
  {
    rclcpp::init(argc, argv); // 初始化 ROS2
    rclcpp::spin(std::make_shared<MinimalPublisher>()); // 开始从节点处理数据
    rclcpp::shutdown();
    return 0;
  }

4.2.2 添加依赖项

使用文本编译器打开 package.xml 文件,复制以下命令:

<depend>rclcpp</depend>
<depend>std_msgs</depend>

这声明了功能包在它的代码执行过程中需要 rclppstd_msgs

4.2.3 CMakeLists.txt

打开文件 CMakeLists.txt,添加依赖项:

find_package(ament_cmake REQUIRED)

find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)

之后,添加可执行文件并命名为 talker 这样就可以使用节点运行 ros2 run:

add_executable(talker src/publisher_member_function.cpp)
ament_target_dependencies(talker rclcpp std_msgs)

ament_target_dependencies 的用法为:

ament_target_dependencies( [dependencies])

该 CMake 宏用来创建可执行节点并且与依赖项关联起来。

最后,添加 install(TARGETS…) 部分,这样 ros2 run 可以找到该可执行文件:

install(TARGETS
  talker
  DESTINATION lib/${PROJECT_NAME})

为了 install 启动文件和节点,可以使用 install() 宏放置在文件最后,但在宏 ament_package() 之后, 示例如下:

# Install launch files
install(
  DIRECTORY launch
  DESTINATION share/${PROJECT_NAME}
)

# Install nodes
install(
  TARGETS [node-names]
  DESTINATION lib/${PROJECT_NAME}
)

在精简了 CMakeLists.txt 后,它的样子如下:

cmake_minimum_required(VERSION 3.5)
project(cpp_pubsub)

# Default to C++14
if(NOT CMAKE_CXX_STANDARD)
  set(CMAKE_CXX_STANDARD 14)
endif()

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  add_compile_options(-Wall -Wextra -Wpedantic)
endif()

find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)

add_executable(talker src/publisher_member_function.cpp)
ament_target_dependencies(talker rclcpp std_msgs)

install(TARGETS
  talker
  DESTINATION lib/${PROJECT_NAME})

ament_package()

4.2.4 编写subscriber程序

使用示例文件 subscriber_member_function.cpp

#include 

#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
using std::placeholders::_1;

class MinimalSubscriber : public rclcpp::Node
{
  public:
    MinimalSubscriber()
    : Node("minimal_subscriber")
    {
      subscription_ = this->create_subscription<std_msgs::msg::String>(
      "topic", 10, std::bind(&MinimalSubscriber::topic_callback, this, _1));
    }

  private:
    void topic_callback(const std_msgs::msg::String::SharedPtr msg) const
    {
      RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg->data.c_str());
    }
    rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;
};

int main(int argc, char * argv[])
{
  rclcpp::init(argc, argv);
  rclcpp::spin(std::make_shared<MinimalSubscriber>());
  rclcpp::shutdown();
  return 0;
}

在这里程序与 Publisher 程序基本相同,除了 spin 的含义。对于 publisher 节点,spin 意味着启动计时器,但对于 subscriber 来说,它只是意味着准备在消息到来时接收消息。

5. 创建msg和srv

5.1 生成功能包

msg 和 srv 文件内构造与 ROS1 相同。

5.1.1 msg文件定义

创建一个称作 Num.msg 的新文件,里面写入代码:

int64 num

该自定义消息,传输一个名为num的64位整数。

5.1.2 srv文件定义

创建一个称作 AddThreeInts.srv 的新文件,里面写入代码:

int64 a
int64 b
int64 c
---
int64 sum

该自定义消息,它 request 三个名为 a、b 和 c 的整数,并用一个名为 sum 的整数 respond。

5.1.3 CMakeLists.txt

要将您定义的接口转换为特定于语言的代码(如c++和Python),以便在这些语言中使用它们,请向CMakeLists.txt添加以下代码行:

find_package(rosidl_default_generators REQUIRED)

rosidl_generate_interfaces(${PROJECT_NAME}
  "msg/Num.msg"
  "srv/AddThreeInts.srv"
 )

5.1.4 package.xml

由于接口依赖 rosidl_default_generators 来生成语言指定代码,需要声明一个依赖性:

rosidl_default_generators

rosidl_default_runtime

rosidl_interface_packages

5.2 测试接口

5.2.1 测试 Num.msg

与之前的程序相比,微调了一些地方:

  • Publisher:
#include 
#include 

#include "rclcpp/rclcpp.hpp"
#include "tutorial_interfaces/msg/num.hpp"     // CHANGE

using namespace std::chrono_literals;

class MinimalPublisher : public rclcpp::Node
{
public:
  MinimalPublisher()
  : Node("minimal_publisher"), count_(0)
  {
    publisher_ = this->create_publisher<tutorial_interfaces::msg::Num>("topic", 10);    // CHANGE
    timer_ = this->create_wall_timer(
      500ms, std::bind(&MinimalPublisher::timer_callback, this));
  }

private:
  void timer_callback()
  {
    auto message = tutorial_interfaces::msg::Num();                               // CHANGE
    message.num = this->count_++;                                        // CHANGE
    RCLCPP_INFO(this->get_logger(), "Publishing: '%d'", message.num);    // CHANGE
    publisher_->publish(message);
  }
  rclcpp::TimerBase::SharedPtr timer_;
  rclcpp::Publisher<tutorial_interfaces::msg::Num>::SharedPtr publisher_;         // CHANGE
  size_t count_;
};

int main(int argc, char * argv[])
{
  rclcpp::init(argc, argv);
  rclcpp::spin(std::make_shared<MinimalPublisher>());
  rclcpp::shutdown();
  return 0;
}
  • Subscriber:
#include 

#include "rclcpp/rclcpp.hpp"
#include "tutorial_interfaces/msg/num.hpp"     // CHANGE
using std::placeholders::_1;

class MinimalSubscriber : public rclcpp::Node
{
public:
  MinimalSubscriber()
  : Node("minimal_subscriber")
  {
    subscription_ = this->create_subscription<tutorial_interfaces::msg::Num>(          // CHANGE
      "topic", 10, std::bind(&MinimalSubscriber::topic_callback, this, _1));
  }

private:
  void topic_callback(const tutorial_interfaces::msg::Num::SharedPtr msg) const       // CHANGE
  {
    RCLCPP_INFO(this->get_logger(), "I heard: '%d'", msg->num);              // CHANGE
  }
  rclcpp::Subscription<tutorial_interfaces::msg::Num>::SharedPtr subscription_;       // CHANGE
};

int main(int argc, char * argv[])
{
  rclcpp::init(argc, argv);
  rclcpp::spin(std::make_shared<MinimalSubscriber>());
  rclcpp::shutdown();
  return 0;
}
  • CMakeLists.txt:
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(tutorial_interfaces REQUIRED)                         # CHANGE

add_executable(talker src/publisher_member_function.cpp)
ament_target_dependencies(talker rclcpp tutorial_interfaces)         # CHANGE

add_executable(listener src/subscriber_member_function.cpp)
ament_target_dependencies(listener rclcpp tutorial_interfaces)     # CHANGE

install(TARGETS
  talker
  listener
  DESTINATION lib/${PROJECT_NAME})

ament_package()
  • package.xml:
tutorial_interfaces

6. Launch

使用 ROS2 启动命令运行一个单独的启动文件将会立即启动整个系统——所有节点及其配置。这在程序的运行中是非常方便的。需要注意的是,ROS2 与 ROS1 在 Launch 文件上有很大的不同。ROS1 使用的是 .launch 文件,ROS2 使用的是 .py 文件。
示例文件 turtlesim_mimic_launch.py(版本:Eloquent)

// 引入python的launch模块
from launch import LaunchDescription
from launch_ros.actions import Node

def generate_launch_description():
    return LaunchDescription([
        Node(
            package='turtlesim',
            node_namespace='turtlesim1',
            node_executable='turtlesim_node',
            node_name='sim'
        ),
        Node(
            package='turtlesim',
            node_namespace='turtlesim2',
            node_executable='turtlesim_node',
            node_name='sim'
        ),
        Node(
            package='turtlesim',
            node_executable='mimic',
            node_name='mimic',
            remappings=[
                ('/input/pose', '/turtlesim1/turtle1/pose'),
                ('/output/cmd_vel', '/turtlesim2/turtle1/cmd_vel'),
            ]
        )
    ])

这里程序创建了三个节点,所有的都来自于 turtlesim 功能包。系统的目标是打开两个 turtlesim 窗口,其中一只乌龟模仿另一只乌龟的移动。
其中,如下语句为launch文件描述的开始:

def generate_launch_description():
   return LaunchDescription([
   ])

下述的两个节点:

Node(
    package='turtlesim',
    node_namespace='turtlesim1',
    node_executable='turtlesim_node', // 可执行程序的名称
    node_name='sim'
),
Node(
    package='turtlesim',
    node_namespace='turtlesim2',
    node_executable='turtlesim_node',
    node_name='sim'
),

注意,这两个节点之间的唯一区别是它们的 namespace 值。独一无二的 namespace 允许系统在没有 node name 或 topic name 冲突的情况下启动两个模拟器。

在这个系统中,两只海龟都接收到关于同一主题的命令,并发布它们关于同一主题的姿态。如果没有独一无二的 namespace,就没有办法区分属于某个turtle的消息和属于另一个turtle的消息。

最后的一个节点也来自于 turtlesim 功能包,但是有不同的可执行文件:

Node(
    package='turtlesim',
    node_executable='mimic',
    node_name='mimic',
    remappings=[
      ('/input/pose', '/turtlesim1/turtle1/pose'),
      ('/output/cmd_vel', '/turtlesim2/turtle1/cmd_vel'),
    ]
)

mimic/input/pose topic 被重映射为了 /turtlesim1/turtle1/pose/output/cmd_vel 话题被重映射为了 /turtlesim2/turtle1/cmd_vel。这意味着 mimic 会订阅 /turtlesim1/sim 的位姿 topic 并重新发布它给 /turtlesim2/sim 的速度命令 topic 来订阅。

这意味着mimic将订阅/turtlesim1/sim的pose主题,并为/turtlesim2/sim的velocity命令主题重新发布要订阅的主题。

综上,大致的节点间通信如下:
在这里插入图片描述

6.1 运行方式

Lauch 文件有两种运行方法:

    1. 在当前路径下打开:
ros2 launch <launch_file_name> # ros2 launch turtlesim_mimic_launch.py
    1. 提供功能包名称打开:
ros2 launch <package_name> <launch_file_name>

7. colcon

默认情况下,它将创建以下目录作为src目录的对等目录:

  • build:用来存储中间文件。对于每一个功能包,将创建一个子文件夹,如 CMake 被调用的子文件夹。
  • install:每个功能包被配置到的位置。在默认情况下,每一个功能包都会被配置到一个独立的子目录中。
  • log:包含关于每次 colcon 调用的各种日志信息。

安装目录是每个包将被安装到的位置。默认情况下,每个包将安装到单独的子目录中。
日志目录包含关于每次colcon调用的各种日志信息。

需要注意的是,与 ROS1 使用到的 catkin 不同,colcon 没有 devel 目录。

8. ament_cmake

在命令行上使用 ros2 pkg create 可以生成一个基本的 CMake 框架,然后将基本构建信息收集到两个文件中:package.xmlCMakeLists.txt
package.xml 必须包含所有依赖项和一些元数据,以便让 colcon 为这个功能包找到正确的构建顺序,在CI中安装所需的依赖项,并为bloom发行版提供信息。
CMakeLists.txt 包含构建+功能包的可执行文件和库 的命令,这将是本届主要的关注点。

8.1 基本的项目框架

一个 ament 功能包的 CMakeLists.txt 的基本框架包含:

cmake_minimum_required(VERSION 3.5)
project(my_project)

ament_package()

参数 project 表示功能包名称,必须要与在 package.xml 内的功能包名称相同。

项目设置由 ament_package() 完成,每个功能包它必须也只能被调用一次。ament_package() 配置 package.xml,注册带 ament 索引的功能包,并为 CMake 搭建配置文件,以便其他使用 find_package 的功能包可以找到它。因为 ament_package()CMakeLists.txt 收集了很多信息,它需要被 CMakeLists.txt 最后调用。尽管可以在调用 install 函数复制文件和目录之后调用 ament_package(),但是将 ament_package() 恒作为最后的调用更简便。

ament_package() 可以被提供的额外参数:

  • CONFIG_EXTRAS
  • CONFIG_EXTRAS_POST

8.2 添加库文件和头文件

有两个主要的目标需要搭建:由 add_library 搭建的库文件以及由 add_executable 搭建的可执行文件。

由于C/ c++中头文件和实现文件的分离,所以并不总是需要将这两个文件作为参数添加到 add_library/ add_executable 中。

以下是建议的最佳做法:

  • 如果您正在构建一个库,请将所有 clinet 应该会使用到的头文件放在一边,因此必须安装到 include 文件夹的子目录中,而所有其他文件(不应该导出的.c/.cpp和头文件)都放在src文件夹中
  • 只有 cpp 文件在 add_libraryadd_executable 调用中被显示引用。
  • 允许通过以下方式查找头文件:
target_include_directories(my_target
  PUBLIC
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
    $<INSTALL_INTERFACE:include>)

这个命令在 build 期间内和被 installed 时添加了所有在文件夹 ${CMAKE_CURRENT_SOURCE_DIR}/include 内的文件到公共接口。

原则上,如果${CMAKE_CURRENT_SOURCE_DIR}${CMAKE_INSTALL_DIR}同时调用了include和顶层文件夹,那么使用生成器表达式在这里是没有必要的,但它是非常常见的。

8.3 添加依赖项

有两种方式来连接功能包和新的依赖项:

第一种为推荐的方式,使用 ament 宏 ament_target_dependencies。举个栗子,假设想连接 my_target 和线性几何库 Eigen3。

find_package(Eigen3 REQUIRED)
ament_target_dependencies(my_target Eigen3)

这包含了必须的头文件和库文件,并且它们的依赖项会被项目正确找到。它也确保了所有依赖项的 include 目录顺序正确,在使用 overlay 工作空间的时候。

第二种方式是使用 target_link_libraries

find_package(Eigen3 REQUIRED)
target_link_libraries(my_target Eigen3::Eigen)

这也将包括必要的头文件、库和它们的依赖关系,但是与 ament_target_dependencies 相比,在使用 overlay 工作区时,它可能不会正确地对依赖关系排序。

你可能感兴趣的:(ROS,linux,ubuntu)