跳转至

ROS2 编程基础

Info

ROS2内容之广,并非一篇教程能够覆盖所有内容,因此仅列出一些有用的入口以便参考、学习,并且配有了基本的概念解读。

学习教程

节点 Node 和软件包 Package

Node

一个 节点(node) 是 ROS 2 图中的参与者,它使用客户端库与其他节点进行通信。节点之间可以在同一进程内通信,也可以在不同进程,甚至在不同机器之间通信。节点通常是 ROS 图中的计算单元;每个节点应当完成一个逻辑功能。

节点可以通过向命名的话题(topic)发布数据来向其他节点传递信息,或者订阅命名的话题以从其他节点获取数据。它们还可以作为 服务客户端(service client),请求其他节点代为执行某些计算;或者作为 服务端(service server),为其他节点提供功能。对于需要长时间运行的计算,节点可以作为 动作客户端(action client),让其他节点代为执行;或者作为 动作服务端(action server),为其他节点提供长时功能。节点还可以提供可配置的 参数(parameter),以便在运行时调整其行为。

节点通常是 发布者、订阅者、服务端、服务客户端、动作服务端、动作客户端 的复杂组合,往往同时扮演多种角色。

节点之间的连接是通过 分布式发现机制 建立的。

上述解释来源于官方文档:Node

ROS 和手机又很多相似之处,手机中的 APP 等同于 ROS 中的 Node。 Node

Package

虽然 ROS 程序的基础单元是节点,但我们并不能像手机安装 app 一样只安装单个节点,而是以 Package 为单位进行安装

Package

ROS 采用的模块化的设计理念,强调功能的相互独立,所以造成单个节点的功能比较单一,要想实现一个完整的任务,通常需要几个节点的配合才能完成。

bilibili: Node节点与package包

ROS package的框架,编程层面:

ROS Workspace

话题 Topic 和消息 Message

Topic->"频道"

ros2 run <your-package>之后,在终端输入rqt,你会看到类似下图的graph:

  • 一个 ROS 节点网络中,可以存在多个话题
  • 一个话题可以有多个发布者,也可以有多个订阅者
  • 一个节点可以对多个话题进行订阅,也可以发布多个话题
  • 不同的传感器消息通常会拥有各自独立话题名称,每个话题只有一个发布者
  • 机器人速度指令话题通常会有多个发布者,但同一时间只能有一个发言人

Message->"语言"

消息的作用是携带数据,从 Publisher 流动到 Subscriber。但不同数据的类型和容量都不一样,因此就需要提前指定数据类型进行传输

使用语言有两种方式:

  • 使用别人以及定义好的通用 Message Type,例如:std_msgs/msg/String
  • 欸,别人的都不太好用,我要自己独创一门方言 Message Type,专业点叫“自定义消息类型” 通用的 Message Type 也就是已经被 release 到 ROS2 官方消息类型列表 的各种 ​​​​*_msg​​​​。也可以使用以下命令查看已经安装到本机的 Message Type:
    1
    ros2 interface show <type>
    

实际开发中也会遇到的,自定义消息类型参考文档:Custom-ROS2-Interfaces

第一个Node

创建一个workspace:

1
mkdir -p ~/<your-ros-learning-folder>/ws
创建 src 文件夹并进入:

1
2
mkdir -p ~/<your-ros-learning-folder>/ws/src
cd ~/<your-ros-learning-folder>/ws/src   # 外层文件夹叫什么无所谓,但要保证当前你所在的是 src 文件夹
创建一个 Node,首先要创建一个 Package。

1
ros2 pkg create pub_sub_cmd_vel --license MIT --build-type ament_cmake --dependencies rclcpp geometry_msgs tf2_ros tf2_geometry_msgs
然后需要在/src 文件夹下创建源pub_sub_cmd_vel.cpp文件,这里您尝试引入头文件会发现 VSCode 并不能智能补全,找不到路径,于是您需要修改CMakeLists.txt文件,添加如下内容,具体在哪里添加、怎么添加,请自行GPT。
1
2
add_executable(pub_sub_cmd_vel src/pub_sub_cmd_vel.cpp)
ament_target_dependencies(pub_sub_cmd_vel rclcpp geometry_msgs tf2_ros tf2_geometry_msgs)
修改好之后,再次编译:
1
colcon build --symlink-install --cmake-args -DCMAKE_EXPORT_COMPILE_COMMANDS=ON

代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#include <geometry_msgs/msg/twist.hpp>
#include <rclcpp/rclcpp.hpp>

class VelocityPublisher : public rclcpp::Node
{
public:
  VelocityPublisher() : Node("velocity_publisher")
  {
    // 创建一个发布器,发布 geometry_msgs::msg::Twist 类型的消息到 cmd_vel 话题
    publisher_ = this->create_publisher<geometry_msgs::msg::Twist>("cmd_vel", 10);

    // 创建一个定时器,每隔 1000 毫秒调用一次 publish_velocity() 函数
    timer_ = this->create_wall_timer(
      std::chrono::milliseconds(1000), std::bind(&VelocityPublisher::publish_velocity, this));
  }

private:
  void publish_velocity()
  {
    // 创建并初始化 Twist 消息
    auto message = geometry_msgs::msg::Twist();
    message.linear.x = velocity_.linear.x;
    message.angular.z = velocity_.angular.z;

    // 打印并发布消息
    RCLCPP_INFO(
      this->get_logger(), "Publishing: linear.x: '%f', angular.z: '%f'", message.linear.x,
      message.angular.z);
    publisher_->publish(message);

    // 更新线速度和角速度
    update_velocity();
  }

  void update_velocity()
  {
    // 更新线速度和角速度
    velocity_.linear.x += 0.2;
    velocity_.angular.z += 0.2;

    // 如果速度超过1,则重置为-1
    if (velocity_.linear.x > 1.0) {
      velocity_.linear.x = -1.0;
    }
    if (velocity_.angular.z > 1.0) {
      velocity_.angular.z = -1.0;
    }
  }

  // 发布器,用于发布 Twist 消息
  rclcpp::Publisher<geometry_msgs::msg::Twist>::SharedPtr publisher_;

  // 定时器,用于定时调用 publish_velocity() 函数
  rclcpp::TimerBase::SharedPtr timer_;

  // 速度
  geometry_msgs::msg::Twist velocity_;
};

int main(int argc, char * argv[])
{
  // initializes ROS 2
  rclcpp::init(argc, argv);

  // Starts processing data from the node, including callbacks from the timer
  rclcpp::spin(std::make_shared<VelocityPublisher>());

  rclcpp::shutdown();
  return 0;
}

CMakeLists.txt

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
cmake_minimum_required(VERSION 3.8)
project(pub_sub_cmd_vel)

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

# find dependencies
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(geometry_msgs REQUIRED)

add_executable(pub_cmd_vel src/pub_cmd_vel.cpp)  # 这里的文件夹名称需要修该
ament_target_dependencies(pub_cmd_vel rclcpp geometry_msgs)

if(BUILD_TESTING)
  find_package(ament_lint_auto REQUIRED)
  set(ament_cmake_copyright_FOUND TRUE)
  set(ament_cmake_cpplint_FOUND TRUE)
  ament_lint_auto_find_test_dependencies()
endif()

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

ament_package()

除了上述方法之外,也可以模仿 ROS2 官方文档:Writing-A-Simple-Cpp-Publisher-And-Subscriber

ROS2其他通信机制

参考学习

推荐这个系列视频的原因在于讲的非常生动有趣,但可惜是 ROS1 的。 ROS1 和 ROS2 在核心概念上是几乎相同的,都有 Node,Topic,Action, Launch...。但 API 几乎被完全更改,使用起来有非常大的不同。 可以看个概念,了解 ROS 中的每个部件是设计用来干什么的。

开源工程: