【ROS2笔记四】ROS2话题通信

4.ROS2话题通信

话题是ROS2中常用的通信方式之一,话题通信采取的是订阅发布模型,一个节点的数据会发布到某个话题之上,然后另一个节点可以订阅这个话题,这个节点就可以通过这个话题来拿到发布节点发布的数据了。

4.1订阅发布模型

订阅发布模型可以是:

  1. 一对一的
  2. 一对多的
  3. 多对一的
  4. 多对多的
  5. 节点可以订阅本身发布的话题内容

4.2ROS2话题工具

运行ROS2自带的demo我们可以简单地使用rqt_graph来查看topic的传递路径

ros2 run demo_nodes_cpp listener
ros2 run demo_nodes_cpp talker
rqt_graph

就可以看到下面的结果

Image

除了rqt_graph之外,ROS2还有一些CLI(Command Line Interface)命令行工具可以使用:

ros2 topic -h

可以查看所有有关topic的CLI的使用方法,这里不再赘述。

4.3rclcpp实现话题

4.3.1编写发布者

(1)创建新的节点

首先,创建一个新的工作空间colcon_test03_ws和新的功能包example_topic_rclcpp(这里旨在回顾工作空间和功能包的创建方法,你也可以在现有的功能包中直接创建脚本文件),然后新建脚本文件topic_publisher_01.cpp

mkdir -p ./colcon_test03_ws/src
cd ./colcon_test03_ws/src
ros2 pkg create example_topic_rclcpp --build-type ament_cmake --license Apache-2.0 --dependencies rclcpp
touch ./example_topic_rclcpp/src/topic_publisher_01.cpp

使用tree命令来查看目录结构(如果没有该命令则需提前安装sudo apt install tree),如下:

.
└── example_topic_rclcpp
    ├── CMakeLists.txt
    ├── include
    │   └── example_topic_rclcpp
    ├── LICENSE
    ├── package.xml
    └── src
        └── topic_publisher_01.cpp

4 directories, 4 files

现在我们需要在这个脚本文件中写入内容了,推荐使用vscode作为编辑器。我们使用面向对象的方式来编写一个最简单的节点,

#include "rclcpp/rclcpp.hpp"
#include <string>

class TopicPublisher01: public rclcpp::Node{
public:
    // 构造函数
    TopicPublisher01(std::string name) : Node(name){
        RCLCPP_INFO(this->get_logger(), "%s node has been launched", 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;
}

然后修改CMakeListst.txt,添加依赖

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}
)

(2)导入消息接口

消息接口是ROS2通信时必须的一部分,通过消息接口ROS2才能够完成消息的序列化和反序列化。ROS2为我们定义好了常用的消息接口,并生成了C++和Python的依赖文件,我们可以直接在程序中进行导入。

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

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

这三步修改的文件内容如下:

CMakeLists.txt

find_package(ament_cmake REQUIRED)
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)
install(TARGETS
  topic_publisher_01
  DESTINATION lib/${PROJECT_NAME}
)

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>

如果不想手动在CMakeLists.txtpackages.xml中添加包的依赖,我们可以在创建功能包的时候就添加上--dependencies std_msgs的依赖,这样就会为我们自动添加了。如下:

ros2 pkg create example_topic_rclcpp01 --build-type ament_cmake --license Apache-2.0 --dependencies rclcpp std_msgs

代码文件topic_publisher_01.cpp,包含消息头文件

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

class TopicPublisher01 : public rclcpp::Node

(3)创建发布者Publisher

相关API文档可以参考:https://docs.ros2.org/latest/api/rclcpp/

Image

我们需要指定的参数有:

  • 话题名称(topic_name),我们设置为control_command即可
  • QosQos指定一个数字,这个数字对应的是KeepLast消息队列的长度。
  • 还需要指定函数原型templateMessageT的消息类型(即示例中的MsgT),我们直接设置成std_msgs::msg::String即可。

编写如下的发布者代码

#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 node has been launched", name.c_str());
        // 创建发布者
        command_publisher_ = this->create_publisher<std_msgs::msg::String>('control_command', 10);
    }
private:
    // 声明话题发布者,在类的私有成员中再次声明发布者对象是为了在整个类中都能够访问和使用该对象。
    rclcpp::Publisher<std_msgs::msg::String>::SharedPtr command_publisher_;
};

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;
}

(4)创建定时器Timer

相关API文档可以参考:https://docs.ros2.org/latest/api/rclcpp/

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

编写如下的Timer代码

#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 node has been launched", name.c_str());
        // 创建发布者
        command_publisher_ = this->create_publisher<std_msgs::msg::String>('control_command', 10);
        // 创建定时器,500ms周期
        timer_ = this->create_wall_timer(std::chrono::milliseconds(500), std::bind(&TopicPublisher01::timer_callback, this));
    }
private:
    // 声明话题发布者,在类的私有成员中再次声明发布者对象是为了在整个类中都能够访问和使用该对象。
    rclcpp::Publisher<std_msgs::msg::String>::SharedPtr command_publisher_;
    // 声明定时器
    rclcpp::TimerBase::SharedPtr timer_;

    void timer_callback(){
        // 创建消息
        std_msgs::msg::String message;
        message.data = "forward";
        // 打印日志
        RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
        // 发布消息
        this->command_publisher_->publish(message);
    }
};

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;
}

测试一下是否正确

colcon build --packages-select example_topic_rclcpp
source ./intsall/setup.bash
ros2 run example_topic_rclcpp topic_publisher_01 

使用CLI查看消息内容

ros2 topic list
ros2 topic echo /control_command

4.4编写订阅者

(1)创建订阅节点

cd ./colcon_test03_ws/src
touch ./example_topic_rclcpp/src/topic_subscriber_01.cpp

编写以下内容:

#include "rclcpp/rclcpp.hpp"

class TopicSubscriber01: public rclcpp::Node{
public:
    TopicSubscriber01(std::string name): Node(name){
        RCLCPP_INFO(this->get_logger(), "%s has been launched", name.c_str());
    }

private:
};

int main(int argc, char **argv){
    rclcpp::init(argc, argv);
    auto node = std::make_shared<TopicSubscriber01>("topic_subscriber_01");
    rclcpp::spin(node);
    rclcpp::shutdown();
    return 0;
}

修改CMakeLists.txt文件

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

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

然后可以测试运行一下

colcon build --packages-select example_topic_rclcpp
source install/setup.bash
ros2 run example_topic_rclcpp topic_subscriber_01

(2)创建订阅者Subscriber

相关API文档可以参考:https://docs.ros2.org/latest/api/rclcpp/

Image

我们只关心前面三个参数,后面两个参数可以选择默认的参数

  • topic_name,指定订阅的话题名称
  • qos,指定消息队列长度
  • callback,指定订阅后消息处理的回调函数

编写以下代码:

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

class TopicSubscriber01: public rclcpp::Node{
public:
    TopicSubscriber01(std::string name): Node(name){
        RCLCPP_INFO(this->get_logger(), "%s has been launched", name.c_str());
        // 创建一个订阅者
        command_subscriber_ = this->create_subscription<std_msgs::msg::String>("control_command", 10, std::bind(&TopicSubscriber01::command_callback, this, std::placeholders::_1));
    }

private:
    // 声明一个订阅者
    rclcpp::Subscription<std_msgs::msg::String>::SharedPtr command_subscriber_;
    // 消息处理的回调函数
    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(), "Receive instruction [%s], send speed [%f]", msg->data.c_str(), speed);
    }

};

int main(int argc, char **argv){
    rclcpp::init(argc, argv);
    auto node = std::make_shared<TopicSubscriber01>("topic_subscriber_01");
    rclcpp::spin(node);
    rclcpp::shutdown();
    return 0;
}

构建然后运行

colcon build --packages-select example_topic_rclcpp
source install/setup.bash
ros2 run example_topic_rclcpp topic_publisher_01
ros2 run example_topic_rclcpp topic_subscriber_01

运行后的输出效果类似如下:

[INFO] [1712900021.099703358] [topic_publisher_01]: Publishing: 'forward'
[INFO] [1712900021.599713885] [topic_publisher_01]: Publishing: 'forward'
[INFO] [1712900022.099719858] [topic_publisher_01]: Publishing: 'forward'
[INFO] [1712900022.599664745] [topic_publisher_01]: Publishing: 'forward'


[INFO] [1712900027.600149013] [topic_subscriber_01]: Receive instruction [forward], send speed [0.200000]
[INFO] [1712900028.100186298] [topic_subscriber_01]: Receive instruction [forward], send speed [0.200000]
[INFO] [1712900028.600202891] [topic_subscriber_01]: Receive instruction [forward], send speed [0.200000]

Reference

d2lros2
ROS2 Tutorial

  • 12
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
ROS 2的Foxy版本是ROS(Robot Operating System)的一个稳定版本,其中包含了一些新的特性和改进。在ROS 2 Foxy中,话题通信仍然是一个重要的概念。 在ROS 2中,话题是一种用于传递消息的机制。通过发布者(Publisher)和订阅者(Subscriber)之间的话题通信,节点可以共享信息。话题的消息类型在编译时确定,并且使用ROS 2的接口定义语言(IDL)来描述。 要进行话题通信,首先需要定义消息类型。可以使用ROS 2提供的标准消息类型,也可以自定义消息类型。接下来,在发布者节点中创建一个发布者对象,并指定要发布的话题名称和消息类型。然后,在订阅者节点中创建一个订阅者对象,并指定要订阅的话题名称和消息类型。 发布者节点将消息发布到话题上,而订阅者节点将从话题上接收到发布者发送的消息。这样,节点之间就可以实现信息的传递和共享。 以下是一个简单的示例代码,演示了如何在ROS 2 Foxy中进行话题通信: 发布者节点代码: ```python import rclpy from std_msgs.msg import String def main(args=None): rclpy.init(args=args) node = rclpy.create_node('publisher') publisher = node.create_publisher(String, 'topic_name') msg = String() msg.data = 'Hello, ROS 2!' while rclpy.ok(): publisher.publish(msg) rclpy.spin_once(node) node.destroy_node() rclpy.shutdown() if __name__ == '__main__': main() ``` 订阅者节点代码: ```python import rclpy from std_msgs.msg import String def callback(msg): print('Received message:', msg.data) def main(args=None): rclpy.init(args=args) node = rclpy.create_node('subscriber') subscriber = node.create_subscription(String, 'topic_name', callback) rclpy.spin(node) node.destroy_node() rclpy.shutdown() if __name__ == '__main__': main() ``` 在上面的示例中,发布者节点创建了一个发布者对象,并使用`node.create_publisher`方法指定了要发布的话题名称。每次循环中,它都会发布一个包含字符串消息的消息对象。 订阅者节点创建了一个订阅者对象,并使用`node.create_subscription`方法指定了要订阅的话题名称和回调函数。每当收到来自发布者的消息时,回调函数将被调用,并打印出接收到的消息内容。 这只是一个简单的示例,实际中可能会涉及更复杂的消息类型和逻辑。但是,通过这个基本的框架,你可以开始在ROS 2 Foxy中进行话题通信

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

木心

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

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

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

打赏作者

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

抵扣说明:

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

余额充值