ROS2学习 2通信机制 话题通信

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

ROS2赵虚左老师课程第二章课程学习记录

一、ROS2通信机制简介

四部分组成:
1、节点(对应单一功能模块,例如雷达驱动节点、摄像头)
2、话题(具有相同话题的节点可以通信到一起)
3、通信模型(话题通信、服务通信、动作通信、服务通信)
4、接口(数据载体,是数据传输的特定格式 msg文件用于话题通信 srv文件用于服务通信 action文件用于动作通信)

二、话题通信

创建工作空间ws02_plumbing
编译
cd src
创建三个功能包base_interfaces_demo(自己设定接口格式,只能依赖c++) cpp01_topic(写c++例子) py01_topic(写python例子)

ros2 pkg create --build-type ament_cmake base_interfaces_demo
ros2 pkg create cpp01_topic --build-type ament_cmake --dependencies rclcpp std_msgs base_interfeces_demo --node-name demo01_talker_str
ros2 pkg create py01_topic --build-type ament_python --dependencies rclpy std_msgs base_interfaces_demo --node-name demo01_talker_str_py

2.1 发布方实现(C++)

/* 
需求:以某个固定频率发送文本“hello world!”,文本后缀编号,每发布一条,编号+1
流程:
  1.包含头文件
  2.初始化ROS2客户端
  3.自定义节点类
    3.1.创建消息发布方
    3.2.创建定时器
    3.3.组织并发布消息
  4.调用spin函数,传入自定义类对象指针
  5.释放资源 
  */
 //1.包含头文件
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"

using namespace std::chrono_literals;//可以在自变量后面跟上时间单位 用于后面定时器的定时设置1s

//3.自定义节点类
class Talker: public rclcpp::Node{
public:
  Talker():Node("talker_node_cpp"){
    RCLCPP_INFO(this->get_logger(),"发布节点创建!");
    // 3.1.创建消息发布方
    /* 
      模板:被发布的消息类型;
      参数:
        1.话题名称;
        2.QOS(消息队列长度)
      返回值:发布对象指针
     */
    publisher_ = this->create_publisher<std_msgs::msg::String>("chatter",10);//10代表消息队列长度
    // 3.2.创建定时器
    /*
      模板:可以不使用 
      参数:
        1.时间间隔;
        2.回调函数;
      返回值:定时器对象指针
     */
    timer_ = this->create_wall_timer(1s,std::bind(&Talker::on_timer,this));
  }
private:
  void on_timer(){
    // 3.3.组织并发布消息
    auto message = std_msgs::msg::String();
    message.data = "hello world!" + std::to_string(count++);
    RCLCPP_INFO(this->get_logger(),"发布方发布的消息:%s",message.data.c_str());
    publisher_->publish(message);
  }
  rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
  rclcpp::TimerBase::SharedPtr timer_;
  size_t count;
};
int main(int argc, char ** argv)
{
  //2.初始化ROS2客户端
  rclcpp::init(argc,argv);
  //4.调用spin函数,传入自定义类对象指针
  rclcpp::spin(std::make_shared<Talker>());
  //5.释放资源
  rclcpp::shutdown();
  return 0;
}

AI解释

    头文件引入:
        rclcpp/rclcpp.hpp: ROS2 C++库的主要头文件。
        std_msgs/msg/string.hpp: 包含用于发布字符串消息的头文件。

    命名空间:
        使用using namespace std::chrono_literals;引入chrono命名空间,用于后面设置定时器的时间间隔。

    自定义节点类 Talker:
        继承自rclcpp::Node类,表示这是一个ROS2节点。
        构造函数中初始化节点,并创建了消息发布者和定时器。

    消息发布者和定时器的创建:
        create_publisher创建了一个发布者,用于发布字符串消息到名为 "chatter" 的话题,消息队列长度为 10。
        create_wall_timer创建了一个定时器,每隔1秒触发一次,触发时调用on_timer成员函数。

    on_timer 成员函数:
        组织并发布带有编号的消息,编号每次递增。
        使用RCLCPP_INFO输出日志,显示发布的消息内容。

    main 函数:
        初始化ROS2客户端。
        创建Talker类的实例并传入spin函数,使节点进入主循环。
        最后释放ROS2资源并返回0。

运行结果

ros2 run cpp01_topic demo01_talker_str

话题发布1

ros2 topic echo /chatter

话题发布2

2.2 订阅方实现(C++)

c++如何修改配置文件

1.直接新建文件夹命名为 demo02_listener_str.cpp
2.package.xml主要添加依赖 CMakeList.txt中主要修改 add_executable(映射关系) /ament_target_dependencies/install三部分
以下是修改前的CMakeist.txt

add_executable(demo01_talker_str src/demo01_talker_str.cpp) #创建一个名为 "demo01_talker_str" 的可执行文件,并将源文件 "src/demo01_talker_str.cpp" 添加到这个可执行文件中
target_include_directories(demo01_talker_str PUBLIC
  $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
  $<INSTALL_INTERFACE:include>)
target_compile_features(demo01_talker_str PUBLIC c_std_99 cxx_std_17)  # Require C99 and C++17
ament_target_dependencies(
  demo01_talker_str
  "rclcpp"
  "std_msgs"
  "base_interfaces_demo"
)

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

以下是修改后的CMakeList.txt

add_executable(demo01_talker_str src/demo01_talker_str.cpp)
add_executable(demo02_listener_str src/demo02_listener_str.cpp) ###新增
target_include_directories(demo01_talker_str PUBLIC
  $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
  $<INSTALL_INTERFACE:include>)
target_compile_features(demo01_talker_str PUBLIC c_std_99 cxx_std_17)  # Require C99 and C++17
ament_target_dependencies(
  demo01_talker_str
  "rclcpp"
  "std_msgs"
  "base_interfaces_demo"
)
ament_target_dependencies(    ###新增
  demo02_listener_str
  "rclcpp"
  "std_msgs"
  "base_interfaces_demo"
)

install(TARGETS 
  demo01_talker_str
  demo02_listener_str    ###新增
  DESTINATION lib/${PROJECT_NAME})

代码实现

/* 
    需求:订阅发布方发布的消息,并在终端输出
    流程:
        1.包含头文件
        2.初始化ROS2客户端
        3.自定义节点类
            3.1.创建订阅方
            3.2.解析并输出数据
        4.调用spin函数,并传入节点对象指针
        5.资源释放
 */
//1.包含头文件
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"

//3.自定义节点类
class Listener: public rclcpp::Node{
public:
    Listener():Node("listener_node_cpp"){
        RCLCPP_INFO(this->get_logger(),"订阅方创建!");
        //3.1.创建订阅方
        /* 
            模板:消息类型;
            参数:
                1.话题名称
                2.QOS,队列长度
                3.回调函数
            返回值:订阅对象指针
         */
        subscription_ = this->create_subscription<std_msgs::msg::String>("chatter",10,std::bind(&Listener::do_cb,this,std::placeholders::_1));
    }
private:
    void do_cb(const std_msgs::msg::String &msg){
        //3.2.解析并输出数据
        RCLCPP_INFO(this->get_logger(),"订阅到的消息是:%s",msg.data.c_str());
    }
    rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;
};


int main(int argc, char const *argv[])
{
    //2.初始化ROS2客户端
    rclcpp::init(argc,argv);
    //4.调用spin函数,并传入节点对象指针
    rclcpp::spin(std::make_shared<Listener>());
    //5.资源释放
    rclcpp::shutdown();
    return 0;
}

编译
打开两个终端
分别更新环境变量
运行demo01_talker_str 和 demo02_listener_str两个文件
最终效果如下话题订阅

2.3 发布方实现(python)

文件名:demo01_talker_str_py.py(已创建)
代码如下

""" 
需求:以某个固定频率发送文本“hello world!”,文本后缀编号,每发布一条,编号+1
流程:
  1.导包
  2.初始化ROS2客户端
  3.自定义节点类
    3.1.创建消息发布方
    3.2.创建定时器
    3.3.组织并发布消息
  4.调用spin函数,传入自定义类对象(python中无所谓指针?)
  5.释放资源 
"""
# 1.导包
import rclpy
from rclpy.node import Node
from std_msgs.msg import String

# 3.自定义节点类
class Talker(Node):
    def __init__(self):
        super().__init__("talker_node_py")
        #初始化计数变量
        self.count = 0
        self.get_logger().info("发布方创建(python)!")
        # 3.1.创建消息发布方
        """
            参数:
                1.消息类型;
                2.话题名称;
                3.QOS,队列长度。
            返回值:
                发布对象 
        """
        self.publisher = self.create_publisher(String,"chatter",10)
        # 3.2.创建定时器
        """
            参数:
                1.时间间隔
                2.回调函数
            返回值:
                定时器对象 
        """
        self.timer = self.create_timer(1.0,self.on_timer)
    def on_timer(self):
        # 3.3.组织并发布消息
        message = String()
        message.data = "hello eorld(python)!" + str(self.count)
        self.publisher.publish(message)
        self.count += 1
        self.get_logger().info("发布的数据:%s" % message.data)

def main():
    # 2.初始化ROS2客户端
    rclpy.init()
    # 4.调用spin函数,传入自定义类对象
    rclpy.spin(Talker())
    # 5.释放资源 
    rclpy.shutdown()


if __name__ == '__main__':
    main()

AI解释

导包:使用import语句导入所需的ROS 2和消息相关的包和模块。
自定义节点类:创建了一个名为 Talker 的自定义节点类,继承自 Node 类。
	 __init__ 方法:初始化节点。在这里,它设置了节点名称、创建了一个发布者(用于发布消息)、创建了一个定时器(定时执行指定的回调函数)等。
	 on_timer 方法:定时器回调函数。每当定时器触发,就会调用这个方法,里面组织了一个 String 类型的消息并发布到名为 "chatter" 的话题上。
	 main 函数:初始化ROS 2客户端、调用 spin 函数,传入 Talker 类的实例,然后在程序结束时关闭ROS 2。
	 __name__ == '__main__' 的检查:确保代码在作为独立程序运行时才执行 main 函数。
整个代码的流程是初始化ROS 2,创建一个 Talker 节点,然后通过 spin 函数进入ROS 2的事件循环,等待消息的发布和接收。在 spin 函数中,Talker 节点的 on_timer 方法会根据定时器的设置定期发布带编号的字符串消息。最后,在程序结束时关闭ROS 2。

2.4 订阅方实现

与 demo01_talker_str_py.py 同目录下新创建一个python文件 demo02_listener_str_py.py

python如何修改配置文件

修改package.xml(添加依赖)以及setup.py,由于没有新的依赖需要添加只修改setup.py文件
修改前

    entry_points={
        'console_scripts': [
            'demo01_talker_str_py = py01_topic.demo01_talker_str_py:main'
        ],
    },

加上1行 ‘demo02_listener_str_py = py01_topic.demo02_listener_str_py:main’
修改后

    entry_points={
        'console_scripts': [
            'demo01_talker_str_py = py01_topic.demo01_talker_str_py:main',    #记得加上逗号隔开
            'demo02_listener_str_py = py01_topic.demo02_listener_str_py:main'
        ],
    },

代码实现

"""
    需求:订阅发布方发布的消息,并在终端输出
    流程:
        1.导包
        2.初始化ROS2客户端
        3.自定义节点类
            3.1.创建订阅方
            3.2.解析并输出数据
        4.调用spin函数,并传入节点对象指针
        5.资源释放
"""

# 1.导包
import rclpy
from rclpy.node import Node
from std_msgs.msg import String

# 3.自定义节点类
class Listener(Node):
    def __init__(self):
        super().__init__("listener_node_py")
        self.get_logger().info("订阅方创建(python)!")
        # 3.1.创建订阅方
        """
            参数:
                1.消息类型
                2.话题名称
                3.回调函数
                4.QOS 队列长度
        """
        self.subscription = self.create_subscription(String,"chatter",self.do_cb,10)
        
    def do_cb(self,msg):
        # 3.2.解析并输出数据
        self.get_logger().info("订阅到的数据:%s" %msg.data)


def main():
    # 2.初始化ROS2客户端
    rclpy.init()
    # 4.调用spin函数,并传入节点对象指针
    rclpy.spin(Listener())
    # 5.资源释放
    rclpy.shutdown()
if __name__ == '__main__':
    main()

编译
更新环境变量
运行
运行效果
话题接收python

2.5 自定义接口信息–接口文件

1.创建并编辑msg文件
创建功能包目录下创建msg文件夹
文件夹中创建msg文件,名称首字母大写(Student.msg)

string  name
int32   age
float64 height

2.编辑配置文件
终端输入筛选rosidl开头的功能包

ros2 pkg list | grep -i rosidl

在package.xml里加入以下代码,并且删除原本已经存在的编译依赖

  <!-- 编译依赖 -->
  <build_depend>rosidl_default_generators</build_depend>
  <!-- 执行依赖 -->
  <exec_depend>rosidl_default_runtime</exec_depend>
  <!-- 声明当前包所属的功能包组 -->
  <member_of_group>rosidl_interface_packages</member_of_group>

在CMakeList.txt中加入find_package以及rosidl_generate_interfaces

# find dependencies
find_package(ament_cmake REQUIRED)
# uncomment the following section in order to fill in
# further dependencies manually.
# find_package(<dependency> REQUIRED)
find_package(rosidl_default_generators REQUIRED)  ###新增
# 为接口文件生成源码
rosidl_generate_interfaces( ${PROJECT_NAME}   ###新增
  "msg/Student.msg"
)

3.编译
4.测试
输入指令查看是否有对应的消息格式返回

ros2 interface show base_interfaces_demo/msg/Student

2.6 自定义话题通信(C++)

1.创建新的文件:demo03_talker_stu.cpp demo04_listener_stu.cpp
2.修改配置文件:如上
3.写代码、编译
4.运行

demo03_talker_stu.cpp代码如下

/* 
需求:以某个固定频率发送学生信息“姓名,年龄,身高”,每发布一条,;年龄+1
流程:
  1.包含头文件
  2.初始化ROS2客户端
  3.自定义节点类
    3.1.创建消息发布方
    3.2.创建定时器
    3.3.组织并发布消息
  4.调用spin函数,传入自定义类对象指针
  5.释放资源 
  */
 //1.包含头文件
#include "rclcpp/rclcpp.hpp"
#include "base_interfaces_demo/msg/student.hpp"

using namespace std::chrono_literals;//可以在自变量后面跟上时间单位 用于后面定时器的定时设置1s
using base_interfaces_demo::msg::Student;
//3.自定义节点类
class TalkerStu: public rclcpp::Node{
public:
  TalkerStu():Node("talkerstu_node_cpp"),count(0){
    RCLCPP_INFO(this->get_logger(),"发布节点创建!");
    // 3.1.创建消息发布方
    /* 
      模板:被发布的消息类型;
      参数:
        1.话题名称;
        2.QOS(消息队列长度)
      返回值:发布对象指针
     */
    publisher_ = this->create_publisher<Student>("chatterstu",10);//10代表消息队列长度
    // 3.2.创建定时器
    /*
      模板:可以不使用 
      参数:
        1.时间间隔;
        2.回调函数;
      返回值:定时器对象指针
     */
    timer_ = this->create_wall_timer(1s,std::bind(&TalkerStu::on_timer,this));
  }
private:
  void on_timer(){
    // 3.3.组织并发布消息
    auto message = Student();
    message.name = "huluwa";
    message.age = count;
    count += 1;
    message.height = 150.00;
    RCLCPP_INFO(this->get_logger(),"学生信息:%s,%d,%.2f",message.name.c_str(),message.age,message.height);
    publisher_->publish(message);
  }
  rclcpp::Publisher<Student>::SharedPtr publisher_;
  rclcpp::TimerBase::SharedPtr timer_;
  size_t count;
};
int main(int argc, char ** argv)
{
  //2.初始化ROS2客户端
  rclcpp::init(argc,argv);
  //4.调用spin函数,传入自定义类对象指针
  rclcpp::spin(std::make_shared<TalkerStu>());
  //5.释放资源
  rclcpp::shutdown();
  return 0;
}

demo04_listener_stu.cpp代码如下

/* 
    需求:订阅发布方发布的消息,并在终端输出
    流程:
        1.包含头文件
        2.初始化ROS2客户端
        3.自定义节点类
            3.1.创建订阅方
            3.2.解析并输出数据
        4.调用spin函数,并传入节点对象指针
        5.资源释放
 */
//1.包含头文件
#include "rclcpp/rclcpp.hpp"
#include "base_interfaces_demo/msg/student.hpp"

using base_interfaces_demo::msg::Student;

//3.自定义节点类
class ListenerStu: public rclcpp::Node{
public:
    ListenerStu():Node("listenerstu_node_cpp"){
        RCLCPP_INFO(this->get_logger(),"订阅方创建!");
        //3.1.创建订阅方
        /* 
            模板:消息类型;
            参数:
                1.话题名称
                2.QOS,队列长度
                3.回调函数
            返回值:订阅对象指针
         */
        subscription_ = this->create_subscription<Student>("chatterstu",10,std::bind(&ListenerStu::do_cb,this,std::placeholders::_1));
    }
private:
    void do_cb(const Student &msg){
        //3.2.解析并输出数据
        RCLCPP_INFO(this->get_logger(),"学生信息:%s,%d,%.2f",msg.name.c_str(),msg.age,msg.height);
    }
    rclcpp::Subscription<Student>::SharedPtr subscription_;
};


int main(int argc, char const *argv[])
{
    //2.初始化ROS2客户端
    rclcpp::init(argc,argv);
    //4.调用spin函数,并传入节点对象指针
    rclcpp::spin(std::make_shared<ListenerStu>());
    //5.资源释放
    rclcpp::shutdown();
    return 0;
}

运行结果如下
自定义话题通信1

2.7 自定义话题通信

注意vscode中导入自定义文件会报错,需要ctrl+shift+p输入settings.json,打开编辑json文件加入编译后生成的文件,具体参考课件 https://www.bilibili.com/video/BV1ad4y1g71d/?spm_id_from=333.788&vd_source=5eb835f89e9c79ac93387bfdb1ad83cb
接下来步骤同2.6
demo03_talker_stu_py.py代码如下

""" 
需求:以某个固定频率发送学生信息“姓名,年龄,身高”,每发布一条,年龄+1
流程:
  1.导包
  2.初始化ROS2客户端
  3.自定义节点类
    3.1.创建消息发布方
    3.2.创建定时器
    3.3.组织并发布消息
  4.调用spin函数,传入自定义类对象
  5.释放资源 
"""
# 1.导包
import rclpy
from rclpy.node import Node
from base_interfaces_demo.msg import Student

# 3.自定义节点类
class TalkerStu(Node):
    def __init__(self):
        super().__init__("talkerstu_node_py")
        self.count = 0
        self.get_logger().info("发布方创建(python)!")
        # 3.1.创建消息发布方
        """
            参数:
                1.消息类型;
                2.话题名称;
                3.QOS,队列长度。
            返回值:
                发布对象 
        """
        self.publisher = self.create_publisher(Student,"chatterstu",10)
        # 3.2.创建定时器
        """
            参数:
                1.时间间隔
                2.回调函数
            返回值:
                定时器对象 
        """
        self.timer = self.create_timer(1.0,self.on_timer)
    def on_timer(self):
        # 3.3.组织并发布消息
        message = Student()
        message.name = "automan"
        message.age = self.count
        message.height = 1.85
        self.publisher.publish(message)
        self.count += 1
        self.get_logger().info("学生信息:%s, %d, %.2f" % (message.name, message.age, message.height))

def main():
    # 2.初始化ROS2客户端
    rclpy.init()
    # 4.调用spin函数,传入自定义类对象
    rclpy.spin(TalkerStu())
    # 5.释放资源 
    rclpy.shutdown()


if __name__ == '__main__':
    main()

demo04_listener_stu_py.py 代码如下

"""
    需求:订阅发布方发布的消息,并在终端输出
    流程:
        1.导包
        2.初始化ROS2客户端
        3.自定义节点类
            3.1.创建订阅方
            3.2.解析并输出数据
        4.调用spin函数,并传入节点对象指针
        5.资源释放
"""

# 1.导包
import rclpy
from rclpy.node import Node
from base_interfaces_demo.msg import Student

# 3.自定义节点类
class ListenerStu(Node):
    def __init__(self):
        super().__init__("listenerstu_node_py")
        self.get_logger().info("订阅方创建(python)!")
        # 3.1.创建订阅方
        """
            参数:
                1.消息类型
                2.话题名称
                3.回调函数
                4.QOS 队列长度
        """
        self.subscription = self.create_subscription(Student,"chatterstu",self.do_cb,10)
        
    def do_cb(self,msg):
        # 3.2.解析并输出数据
        self.get_logger().info("订阅到的数据:%s,%d,%.2f" % (msg.name, msg.age, msg.height))


def main():
    # 2.初始化ROS2客户端
    rclpy.init()
    # 4.调用spin函数,并传入节点对象指针
    rclpy.spin(ListenerStu())
    # 5.资源释放
    rclpy.shutdown()
if __name__ == '__main__':
    main()

运行效果如下
自定义话题通信2总运行效果如下
自定义话题通信3

总结

话题下的发布方与订阅方是多对多的关系

  • 19
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值