ROS2从入门到精通1-1:详解ROS2话题通信机制与自定义消息

本文详细介绍了ROS2的话题通信机制,包括单向通信模型、C++和Python实现,以及如何创建自定义消息。通过实例展示了如何在ROS2中实现发布者和订阅者,并解释了多对多通信的灵活性。此外,还探讨了自定义消息的创建流程,以适应复杂数据需求。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

0 专栏介绍

本专栏旨在通过对ROS2的系统学习,掌握ROS2底层基本分布式原理,并具有机器人建模和应用ROS2进行实际项目的开发和调试的工程能力。

🚀详情:《ROS2从入门到精通》


1 话题通信模型

话题是一种单向通信通道,数据或信息以定义的格式传递其中。机器人组件可以订阅主题以接收信息,或者发布信息到主题以与其他组件分享。其中发布信息的节点称为发布者,接受信息的节点称为订阅者

在这里插入图片描述

举个例子,想象一个移动机器人,其中有一个负责读取传感器数据的组件,另一个负责控制电机。传感器组件可以在名为sensor_readings的话题中发布传感器读数信息。同时,控制组件可以订阅此话题以接收该信息并用于控制电机。

在 ROS 2 中,话题由中间件管理以确保消息可靠高效地传输。话题中的消息可以是任何可序列化的数据类型,例如整数、浮点数、字符串、矩阵等。

ROS 2 中的话题具有多对多通信的灵活性和可扩展性。例如,可以有多个组件订阅同一话题,或者多个组件发布数据到同一主题。此外,可以使用线程来连接单个机器人内部的组件,或者在网络中连接不同机器人之间的组件。

在这里插入图片描述

2 话题模型实现(C++)

实验目标:发布者发布控制消息到/turtle1/cmd_vel,控制乌龟其做圆周运动;订阅者订阅/turtle1/cmd_vel,在终端显示乌龟实时的位置坐标。

  • 发布者

    class PublisherNode : public rclcpp::Node
    {
        public:
            PublisherNode() : Node("lab_topic_pub") {
                publisher_ = create_publisher<geometry_msgs::msg::Twist>("/turtle1/cmd_vel", 10); 
            }
    
            void publish(geometry_msgs::msg::Twist msg) {
                publisher_->publish(msg);
            }
    
        private:
            rclcpp::Publisher<geometry_msgs::msg::Twist>::SharedPtr publisher_;
    };
    
    int main(int argc, char * argv[])                      
    {
        rclcpp::init(argc, argv); 
    
        auto node = std::make_shared<PublisherNode>();
        
        rclcpp::Rate loop_rate(10);
        while(rclcpp::ok()) {
            auto msg = geometry_msgs::msg::Twist();
            msg.linear.x = 0.5;
            msg.angular.z = 0.2;
            node->publish(msg);
            RCLCPP_INFO(node->get_logger(), "Publishing: x: %.2f, z: %.2f", msg.linear.x, msg.angular.z);
            loop_rate.sleep();
        }   
        
        rclcpp::shutdown();                                
        return 0;
    }
    
  • 订阅者(订阅的话题存在消息即触发回调函数)

    class SubscriberNode : public rclcpp::Node
    {
        public:
            SubscriberNode() : Node("lab_topic_sub")
            {
                subscriber_ = create_subscription<geometry_msgs::msg::Twist>(       
                    "/turtle1/cmd_vel", 10, std::bind(&SubscriberNode::OnPoseCallback, this, _1));
            }
    
        private:
            void OnPoseCallback(const geometry_msgs::msg::Twist & msg) const
            {
                RCLCPP_INFO(get_logger(), "Publishing: x: %.2f, z: %.2f", msg.linear.x, msg.angular.z);
            }
            
            rclcpp::Subscription<geometry_msgs::msg::Twist>::SharedPtr subscriber_; 
    };
    
    int main(int argc, char * argv[])                         
    {
        rclcpp::init(argc, argv);                 
        rclcpp::spin(std::make_shared<SubscriberNode>());     
        rclcpp::shutdown();                                  
        
        return 0;
    }
    

话题通信的效果如下所示:

在这里插入图片描述

计算图可视化为:

在这里插入图片描述

3 话题模型实现(Python)

实验目标:发布者发布控制消息到/turtle1/cmd_vel,控制乌龟其做圆周运动;订阅者订阅/turtle1/cmd_vel,在终端显示乌龟实时的位置坐标。

  • 发布者
    class PublisherNode(Node):
        def __init__(self, name):
            super().__init__(name) 
            self.publisher_ = self.create_publisher(Twist, '/turtle1/cmd_vel', 10) 
    
        def publish(self, msg: Twist):
            self.publisher_.publish(msg)
    
  • 订阅者
    class SubscriberNode(Node):
        def __init__(self, name):
            super().__init__(name)
            self.subscirber_ = self.create_subscription(
                Twist, '/turtle1/cmd_vel', self.OnPoseCallback, 10
            ) 
    
        def OnPoseCallback(self, msg):
            self.get_logger().info(f"Publishing: x: {msg.linear.x:.2f}, z: {msg.angular.z:.2f}")
    	
    def main(args=None):                             
        rclpy.init(args=args)                        
        node = SubscriberNode("lab_topic_sub")
        rclpy.spin(node)                                        
        node.destroy_node()                                     
        rclpy.shutdown()   
    

话题通信的效果如下所示:

在这里插入图片描述

4 自定义消息

ROS2系统通过std_msgs封装了一些常用的原生数据类型,比如StringInt32Int64等,对于一些复杂数据应用场景,往往需要在std_msgs或其他消息库的基础上继续封装更高级的数据类型

自定义消息的通用流程如下:

  • 功能包下新建msg文件夹,在其中添加自定义消息xxx.msg
  • 功能包package.xml中添加编译依赖与执行依赖
    <buildtool_depend>rosidl_default_generators</buildtool_depend>
    <exec_depend>rosidl_default_runtime</exec_depend>
    <member_of_group>rosidl_interface_packages</member_of_group>
    
  • 功能包CMakeLists.txt中添加编译消息相关依赖
    find_package(rosidl_default_generators REQUIRED)
    rosidl_generate_interfaces(${PROJECT_NAME}
    	"xxx.msg"
    	DEPENDENCIES xxx_msgs
    )
    
    ament_export_dependencies(rosidl_default_runtime)
    
  • 编译自定义消息,在install/<pkg_name>/include中生成由xxx.msg编译的C++可识别的xxx.hpp头文件
  • 引入xxx.hpp即可调用自定义消息

下面给出一个实例:

添加如下自定义消息,并按上面步骤配置依赖

# Person.msg
string name
string gender
uint8 age

geometry_msgs/Point position
    float64 x
    float64 y
    float64 z

定义一个发布者、一个订阅者测试自定义消息

  • 发布者
    // 初始化名为personPub的ROS节点, 该节点应在CMakeLists.txt中被构建为可执行文件
    ros::init(argc, argv, "personPub");
    // 创建节点句柄
    ros::NodeHandle pubNode;
    // 创建发布者, 该发布者属于pubNode节点, 发布话题为"/person/info",
    // 消息类型为"msgtest::Person", 发布队列长度为10
    ros::Publisher pub = pubNode.advertise<msg_lab::Person>("/person/info", 10);
    // 设置发布频率
    ros::Rate loopRate(10);
    while(ros::ok())
    {
        // 设置消息
        msg_lab::Person msg;
        msg.name = "winter";
        msg.gender = "man";
        msg.age = 20;
        msg.position.x = 10;
        msg.position.y = 20;
        msg.position.z = 30;
        // 发布消息
        pub.publish(msg);
        ROS_INFO("Publish Person Info[name: %s gender: %s age: %d pos: x-%.2f y-%.2f z-%.2f]", 
                msg.name.c_str(), msg.gender.c_str(), msg.age, msg.position.x, msg.position.y, msg.position.z);
        // 按循环频率延时
        loopRate.sleep();
    }
    
  • 订阅者
    void personInfoCallBack(const msg_lab::Person::ConstPtr &msg)
    {
        ROS_INFO("Subscribe Person Info[name: %s gender: %s age: %d pos: x-%.2f y-%.2f z-%.2f]", 
                    msg->name.c_str(), msg->gender.c_str(), msg->age, msg->position.x, msg->position.y, msg->position.z);
    }
    
    int main(int argc, char** argv)
    {
        // 初始化名为personSub的ROS节点, 该节点应在CMakeLists.txt中被构建为可执行文件
        ros::init(argc, argv, "personSub");
        // 创建节点句柄
        ros::NodeHandle subNode;
        // 创建订阅者, 该订阅者属于subNode节点, 订阅话题为"/person/info",
        // 订阅队列长度为10, 收到订阅消息后出发回调函数personInfoCallBack
        ros::Subscriber sub = subNode.subscribe("/person/info", 10, personInfoCallBack);
        // 循环等待回调函数
        ros::spin();
        return 0;
    }
    

实测效果如下:

在这里插入图片描述

完整代码通过下方博主名片联系获取


🔥 更多精彩专栏


👇源码获取 · 技术交流 · 抱团学习 · 咨询分享 请联系👇
评论 31
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Mr.Winter`

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

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

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

打赏作者

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

抵扣说明:

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

余额充值