ROS2中Publisher和Subscriber的创建
ROS2的概念和术语与ROS1基本保持一致,对于消息话题,存在话题的发布方Publisher和接收方Subscriber
如果没有使用过ROS2,建议先把官网教程中Tutorials章节下的Beginner: CLI tools目录下的内容学习一遍,最好能将所有Demo跑通
具体网站链接如下;
在ROS2中自定义的Publisher类要继承自rclcpp::Node类,rclcpp::Node类中定义了create_publisher()模板函数,可以用来创建一个rclcpp::Publisher实例,本文以std_msgs::msg::String作为模板类型为例,对ros2源码中提供的minimal_publisher/lambda.cpp进行分析,该示例创建了一个publisher并使用lambda函数进行std_msg::msg::String格式的消息发送,该程序源码位于ros2源码src/ros2/examples/rclcpp/topics/目录下。
Node::create_publisher()中调用rclcpp::create_publisher()函数,调用detail::create_publiser()函数。上面的示例的模板类型std_msgs::msg::String对应MessageT模板。
detail::create_publiser()最终调用的是NodeTopics::create_publisher(),该函数实现是调用了第二个传入参数publisher_factory.create_typed_publisher()函数,publisher_factory由rlcpp::create_publisher_factory()创建,其中会将一个lambda匿名函数初始化给publisher_factory的create_typed_publisher成员变量,因此,publisher_factory.create_typed_publisher()调用的就是lambda匿名函数。
lambda匿名函数中创建了<PublisherT>的实例,该模板在这里对应rclcpp::Publisher<MessageT, AllocatorT>类型,上面示例得知这里的MessageT模板对应std_msgs::msg::String类型,因此rclcpp::Publisher的真正创建正是通过这个lambda匿名函数,lambda匿名函数最后将创建的rclcpp::Publisher实例指针返回给用户。
接下来看一下创建rclcpp::Publisher实例时,其构造函数都干了什么。 rclcpp::Publisher类继承自PublisherBase类,在rclcpp::Publisher类的构造函数中调用的rclcpp::get_message_type_support_handle<MessageT>()函数内部会调用rosidl_typesupport_cpp::get_message_type_support_handle<MessageT>(),该函数声明是一个函数模板,其实现位于msg消息生成的rosidl_typesupport_cpp/目录中的*__type_support.cpp。
上述示例中std_msgs::msg::String类型的get_message_type_support_handle()函数实现位于std_msgs/rosidl_typesupport_cpp/std_msgs/msg/string__type_support.cpp,函数返回一个static的rosidl_message_type_support_t实例,包含该消息类型支持的信息,作为PublisherBase类构造函数的参数。
上一步得到的rosidl_message_type_support_t结构体作为参数赋值给PublisherBase类,接着看PublisherBase类的构造函数,将上述结构体赋值到type_support_成员变量,并传入给rcl_publisher_init()函数,这里就从rclcpp层调用到rcl层的方法了。
rcl_publisher_init()也不处理该结构体,而是调用rmw_create_publisher()函数,这个函数就从rcl层深入到rmw层了。
注:rmw层直接与DDS对接的,ROS2面向不同厂家的DDS实现,都提供了一套rmw接口,因此rmw层的函数名虽然相同,但对于不同厂家的DDS有不同的实现,本文中涉及的rmw函数均以适配Fast DDS的rmw_fastrtps为例。
rmw_fastrtps_cpp::create_publisher()中调用get_message_typesupport_handle(),来获取对应的fastrtps_cpp类型的结构体实例,该函数中传入的type_supports参数就是之前rosidl_typesupport_cpp/目录中的get_message_type_support_handle()函数返回的static的rosidl_message_type_support_t实例。
接下来看一下get_message_typesupport_handle()函数实现,上图最后调用的func()对应rosidl_typesupport_cpp:: get_message_typesupport_handle_function(),进而调用了核心函数rosidl_typesupport_cpp::get_typesupport_handle_function()。
rosidl_typesupport_cpp::get_typesupport_handle_function()函数中,定义了动态库rcpputils::SharedLibrary的指针变量lib,并以“%s__%s”的格式由包名map->package_name和标识identifier合成动态库的名字,比如“std_msgs__rosidl_typesupport_fastrtps_cpp”。
接下来就可以使用动态库路径library_name,得到动态库实例赋值给lib变量,继而借助lib->get_symbol()函数从动态库中查找指定函数(例如rosidl_typesupport_fastrtps_cpp__get_message_type_support_handle__std_msgs__msg__String)。
指定函数会返回消息类型这一关键结构体的实例,其定义位于rosidl_typesupport_fastrtps_cpp/目录中的msg/detail/dds_fastrtps/目录的*__type_support.cpp文件。仍然以std_msgs::msg::String消息为例,看一下返回的rosidl_message_type_support_t类型的实例,其data成员被赋予了一个message_type_support_callbacks_t的实例,该实例有命名空间、消息类型名、消息序列化函数等信息。
让我们回到rmw_fastrtps_cpp::create_publisher()函数,将刚刚得到的rosidl_message_type_support_t类型实例赋给了type_support变量,其data成员包含了消息类型的关键信息。
之后就是借助type_support->data获取消息类型的关键信息,调用fastdds的相关接口进行DDS传输所需的操作。最后创建一个rmw_publisher_t的实例返回给rclcpp::PublisherBase 类型的publisher_handle_ ->impl->rmw_handle变量,type_support->data也会保存在rmw_publisher_t实例的data->type_support_impl_成员。至此,publisher创建流程结束。
rclcpp::Subscription创建流程
ROS2代码框架实现了底层代码在使用上的高度一致性和复用性,rclcpp::Subscription的创建流程与rclcpp::Publisher类似,有兴趣的coder可以尝试自己分析一下。