动手学ROS2-2节点通信-话题之RCLCPP实现

说明:本文基于ubuntu22.04和ros2 humble版本

目录

一、话题入门

1.节点通信

2.话题基础概念

2.1话题通信采取的是订阅发布模型

 2.2消息接口,同一个话题,所有的发布者和接收者必须使用相同消息接口。

2.3GUI-RQT工具

2.4CLI工具

二、话题之RCLCPP实现

1. 创建节点

 2.编写发布者

2.1导入消息接口

2.2确定话题名称和服务质量Qos

2.3使用定时器定时发布数据

 2.4运行测试

3编写订阅者

3.1创建订阅节点

​编辑

3.2查看订阅者API文档

3.3编写修改相关文件

3.4运行测试

 4订阅发布测试


一、话题入门

1.节点通信

通信的目的是在计算机系统中实现不同组件、进程或设备之间的信息和数据传递。

通信的原理涉及两个主要方面:通信协议和通信方式。

通信协议定义了数据的格式、传输方式、错误检测和纠正等规则,以确保可靠的数据传输。

通信方式涉及了不同的通信介质和技术,包括网络通信和进程间通信(IPC)。

基于TCP/UDP的网络通信方式:通过计算机网络进行信息交换。

基于共享内存的进程间通信(IPC)方式:通过共享内存区域在同一计算机系统内的不同进程之间进行通信。

ROS 2用于通讯的默认中间件是DDS。在DDS中,不同逻辑网络共享物理网络的主要机制称为域(Domain) ID。同一域上的ROS 2节点可以自由地相互发现并发送消息,而不同域上的ROS 2节点则不能。所有ROS 2节点默认使用域ID为0。为了避免在同一网络上运行ROS 2的不同计算机组之间互相干扰,应为每组设置不同的域ID。

2.话题基础概念

2.1话题通信采取的是订阅发布模型

节点发布数据到某个话题上,节点就可以通过订阅话题拿到数据。

 2.2消息接口,同一个话题,所有的发布者和接收者必须使用相同消息接口。

为了方便发送者和接收者进行数据的交换,ROS2帮我们在数据传递时做好了消息的序列化和反序列化。

2.3GUI-RQT工具

终端输入命令,可以显示节点间通信模型

rqt_graph

2.4CLI工具

ros2 topic -h

ros2 topic list      返回系统中当前活动的所有主题的列表

 ros2 topic list -t  返回系统中当前活动的所有主题的列表时,增加消息类型

ros2 topic echo <话题>    打印实时话题内容

ros2 topic info <话题>     查看话题属性信息

ros2 interface show <话题信息>   查看话题信息的消息类型

二、话题之RCLCPP实现

使用ROS2的RCLCPP客户端库来实现话题通信。

RCLCPP为Node类提供了丰富的API接口,其中就包括创建话题发布者和创建话题订阅者。

1. 创建节点

创建一个控制节点和一个被控节点。

控制节点创建一个话题发布者,发布控制命令(command)话题,接口类型为字符串(string),控制接点通过发布者发布控制命令(前进、后退、左转、右转、停止)。

被控节点创建一个订阅者,订阅控制命令,收到控制命令后根据命令内容打印对应速度出来。

在ros2test文件夹下开启终端,依次输入下面的命令,创建chapt3_ws工作空间、example_topic_rclcpp功能包和topic_publisher_01.cpp

mkdir -p chapt3_ws/src

cd chapt3_ws/src

ros2 pkg create example_topic_rclcpp --build-type ament_cmake --dependencies rclcpp touch example_topic_rclcpp/src/topic_publisher_01.cpp

  • pkg create 是创建包的意思
  • --build-type 用来指定该包的编译类型,一共有三个可选项ament_pythonament_cmakecmake
  • --dependencies 指的是这个功能包的依赖,这里小鱼给了一个ros2的C++客户端接口rclcpp

完成后的目录结构

接着采用面向对象方式写一个最简单的节点 

#include "rclcpp/rclcpp.hpp"

class TopicPublisher01 : public rclcpp::Node
{
public:
    // 构造函数,有一个参数为节点名称
    TopicPublisher01(std::string name) : Node(name)
    {
        RCLCPP_INFO(this->get_logger(), "%s节点已经启动.", name.c_str());
    }

private:
    // 声明节点
};

int main(int argc, char **argv)
{
    rclcpp::init(argc, argv);
    /*创建对应节点的共享指针对象*/
    auto node = std::make_shared<TopicPublisher01>("topic_publisher_01");
    /* 运行节点,并检测退出信号*/
    rclcpp::spin(node);
    rclcpp::shutdown();
    return 0;
}

在CMakeLists.txt中添加可执行文件,并使用install指令将其安装到install目录

add_executable(topic_publisher_01 src/topic_publisher_01.cpp)
ament_target_dependencies(topic_publisher_01 rclcpp)

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

编译在chapt3_wscpp目录下运行colcon测试

colcon build --packages-select example_topic_rclcpp        #编译节点

source install/setup.bash        #source环境

ros2 run example_topic_rclcpp topic_publisher_01

 2.编写发布者

学习使用API文档,创建发布者,需要调用node的成员函数create_publisher并传入对应的参数。

 根据API文档,创建发布者的函数,需要传入消息类型(msgT)、话题名称(topic_name)和 服务指令(qos)。

2.1导入消息接口

消息接口是ROS2通信时必须的一部分,通过消息接口ROS2才能完成消息的序列化和反序列化

ament_cmake类型功能包导入消息接口分为三步:

  1. CMakeLists.txt中导入,具体是先find_packagesament_target_dependencies
  2. packages.xml中导入,具体是添加depend标签并将消息接口写入。
  3. 在代码中导入,C++中是#include"消息功能包/xxx/xxx.hpp"

CMakeLists.txt

find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)

add_executable(topic_publisher_01 src/topic_publisher_01.cpp)
ament_target_dependencies(topic_publisher_01 rclcpp std_msgs)

packages.xml

  <buildtool_depend>ament_cmake</buildtool_depend>

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

  <test_depend>ament_lint_auto</test_depend>
  <test_depend>ament_lint_common</test_depend>

代码源文件topic_publisher_01.cpp

#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"

class TopicPublisher01 : public rclcpp::Node

2.2确定话题名称和服务质量Qos

  • 话题名称(topic_name)就用control_command
  • Qos,Qos支持直接指定一个数字,这个数字对应的是KeepLast队列长度。一般设置成10,即如果一次性有100条消息,默认保留最新的10个,其余的都扔掉。

 代码源文件 topic_publisher_01.cpp 

#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"

class TopicPublisher01 : public rclcpp::Node
{
public:
    // 构造函数,有一个参数为节点名称
    TopicPublisher01(std::string name) : Node(name)
    {
        RCLCPP_INFO(this->get_logger(), "大家好,我是%s.", name.c_str());
        // 创建发布者
        command_publisher_ = this->create_publisher<std_msgs::msg::String>("command", 10);
    }

private:
    // 声明话题发布者
    rclcpp::Publisher<std_msgs::msg::String>::SharedPtr command_publisher_;
};

2.3使用定时器定时发布数据

定时器是ROS2中的一个常用功能,通过定时器可以实现按照一定周期调用某个函数以实现定时发布等逻辑。

定时器对应的类是 rclcpp::TimerBase,调用create_wall_timer将返回其共享指针。

创建定时器时传入了两个参数,这两个参数都利用了C++11的新特性。

  • std::chrono::milliseconds(500),代表500ms,chrono是c++ 11中的时间库,提供计时,时钟等功能。
  • std::bind(&TopicPublisher01::timer_callback, this),bind() 函数的意义就像它的函数名一样,是用来绑定函数调用的某些参数的。

创建发布消息

std_msgs::msg::String是通过ROS2的消息文件自动生成的类,其原始消息文件内容可以通过命令行查询

ROS2会将消息文件转换成一个类,并把其中的定义转换成类的成员函数。

编写好发布者后,通过ROS2中的定时器来设置指定的周期调用回调函数,在回调函数里实现发布数据功能。

RCLCPP文档,找到创建定时器函数,观察参数

  • period,回调函数调用周期。
  • callback,回调函数。
  • group,调用回调函数所在的回调组,默认为nullptr。

 代码源文件 topic_publisher_01.cpp 

class TopicPublisher01 : public rclcpp::Node
{
public:
    // 构造函数,有一个参数为节点名称
    TopicPublisher01(std::string name) : Node(name)
    {
        RCLCPP_INFO(this->get_logger(), "大家好,我是%s.", name.c_str());
        // 创建发布者
        command_publisher_ = this->create_publisher<std_msgs::msg::String>("command", 10);
        // 创建定时器,500ms为周期,定时发布
        timer_ = this->create_wall_timer(std::chrono::milliseconds(500), std::bind(&TopicPublisher01::timer_callback, this));
    }

private:
    void timer_callback()
    {
        // 创建消息
        std_msgs::msg::String message;
        message.data = "forward";
        // 日志打印
        RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
        // 发布消息
        command_publisher_->publish(message);
    }
    // 声名定时器指针
    rclcpp::TimerBase::SharedPtr timer_;
    // 声明话题发布者指针
    rclcpp::Publisher<std_msgs::msg::String>::SharedPtr command_publisher_;
};

 2.4运行测试

在chapt3_wscpp工作空间目录下,打开终端运行

colcon build --packages-select example_topic_rclcpp

source install/setup.bash

ros2 run example_topic_rclcpp topic_publisher_01

#查看话题列表

ros2 topic list

#输出话题内容

ros2 topic echo /command

3编写订阅者

手动创建一个节点订阅/command指令,并处理数据。

3.1创建订阅节点

在chapt3_ws/src/example_topic_rclcpp目录下开启终端,输入以下命令创建源文件

touch src/topic_subscribe_01.cpp

编写代码源文件topic_subscribe_01.cpp

#include "rclcpp/rclcpp.hpp"

class TopicSubscribe01 : public rclcpp::Node
{
public:
    // 构造函数,有一个参数为节点名称
    TopicSubscribe01(std::string name) : Node(name)
    {
        RCLCPP_INFO(this->get_logger(), "大家好,我是%s.", name.c_str());
    }

private:
    // 声明节点
};

int main(int argc, char **argv)
{
    rclcpp::init(argc, argv);
    /*创建对应节点的共享指针对象*/
    auto node = std::make_shared<TopicSubscribe01>("topic_subscribe_01");
    /* 运行节点,并检测退出信号*/
    rclcpp::spin(node);
    rclcpp::shutdown();
    return 0;
}

CMakeLists.txt中增加编译链接

add_executable(topic_subscribe_01 src/topic_subscribe_01.cpp)
ament_target_dependencies(topic_subscribe_01 rclcpp)

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

在工作空间目录下进行编译测试

colcon build --packages-select example_topic_rclcpp

source install/setup.bash

ros2 run example_topic_rclcpp topic_subscribe_01  #首先会自动进入build文件下寻找,需要指出可执行文件所在文件夹

3.2查看订阅者API文档

 

五个参数,但后面两个都是默认的参数,我们只需要有话题名称、Qos和回调函数即可。

3.3编写修改相关文件

 编写源文件代码

#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"

class TopicSubscribe01 : public rclcpp::Node
{
public:
    TopicSubscribe01(std::string name) : Node(name)
    {
        RCLCPP_INFO(this->get_logger(), "大家好,我是%s.", name.c_str());
          // 创建一个订阅者订阅话题
        command_subscribe_ = this->create_subscription<std_msgs::msg::String>("command", 10, std::bind(&TopicSubscribe01::command_callback, this, std::placeholders::_1));
    }

private:
     // 声明一个订阅者
    rclcpp::Subscription<std_msgs::msg::String>::SharedPtr command_subscribe_;
     // 收到话题数据的回调函数
    void command_callback(const std_msgs::msg::String::SharedPtr msg)
    {
        double speed = 0.0f;
        if(msg->data == "forward")
        {
            speed = 0.2f;
        }
        RCLCPP_INFO(this->get_logger(), "收到[%s]指令,发送速度 %f", msg->data.c_str(),speed);
    }
};

CMakeLists.txt中添加std_msgs依赖

ament_target_dependencies(topic_subscribe_01 rclcpp std_msgs)

3.4运行测试

在工作空间目录下开启终端,编译运行订阅节点

colcon build --packages-select example_topic_rclcpp

source install/setup.bash

ros2 run example_topic_rclcpp topic_subscribe_01

手动发布数据测试订阅者

ros2 topic pub /command std_msgs/msg/String "{data: forward}"

 4订阅发布测试

开启节点的先后顺序无关,节点间能够相互发现

运行订阅节点

source install/setup.bash

ros2 run example_topic_rclcpp topic_subscribe_01

运行发布节点

source install/setup.bash

ros2 run example_topic_rclcpp topic_publisher_01

运行结果

 打开RQT查看计算图,终端输入 rqt

选择Node Graph