Apollo如何通知/订阅主题topic

2023-11-05

转自:https://blog.csdn.net/u012423865/article/details/80024870

导读
众所周知,Apollo是基于ROS开发的,所以其底层也是基于消息的机制进行节点通信的。但是它在ROS的基础上做了一些改动,如下: 
P2P——由于原生ROS的消息机制是通过主节点(Master)分发数据来实现的,这样一个强中心化的结构始终存在一个Master意外导致系统奔溃的隐患,为了解决这个问题,Apollo使用了Fast-RTPS,用P2P的方式抛弃了主节点实现通信。
Protobuf——原生支持Google的Protobuf,完美的解决了原来ROS的MD5验证导致消息不能后向兼容的问题。
共享内存——使用共享内存传输,效率更高。
上面简单的介绍了一些Apollo和ROS的渊源,如果想详细了解可以看Apollo-ROS官方详细文档。鉴于与ROS的关系,那么就不得不说如何在Apollo里面利用topic进行通信了。虽然出自ROS,但是Apollo在ROS的基础上进行了大量的封装和改动,所以,较原生的ROS,在advertise/subscribe(通知/订阅)topic有了较大的区别。
本文我将按照操作步骤、代码分析、流程图的步骤简述一下Apollo的topic使用方法,如果你只想知道如何使用,那么看前面的使用方法即可,如果你想刨根问题,那么请耐心的看完,结合上下文一起观看,效果更好。还有,代码的注释不能放过,有一些上下文联系的地方,我可能放在代码注释里面来解释了。
使用方法
订阅
订阅已配置好的Topic

实现回调函数

void ZuoTestSubnode::ImgTestCallback(const sensor_msgs::Image &msg){
  AINFO << "ImgTestCallback";
  //-- do sth
}
1
2
3
4
在InitInternal()内将回调函数添加到对应Topic的回调函数队列,如下:

bool ZuoTestSubnode::InitInternal(){
  AdapterManager::AddImageShortCallback(&ZuoTestSubnode::ImgTestCallback, this);
}
1
2
3
订阅未配置好的Topic

如果用户需要新增Topic,那就需要重新配置,而配置,本质上就是调用subscribe/advertise函数,然后生成空的回调函数队列和消息发布句柄。这些是Apollo对ROS做的封装,主要目的是为了方便使用,以及管理众多的topic,这部分主要是在adapter_manager.h、message_manager.h等实现的。具体代码后面再讲,先看步骤:

在adapter_gflags.cc和adapter_gflags.h里面添加新topic。如下:

//-- adapter_gflags.h

//-- Zuo added on 2018-04-15 for testPublishSubnode
DECLARE_string(zuo_test_topic);
1
2
3
4
//-- adapter_gflags.cc

//-- Zuo added on 2018-04-15 for testPublishSubnode
DEFINE_string(zuo_test_topic, "/apollo/zuo/zuo_test", "Zuo added for testPublishMsg");
1
2
3
4
5
在adapter.conf文件内添加一个config标签,这个conf文件是用来配置对应的topic。而config中的type,是用来在后文的有限状态机中,进入我们新添的topic分支。

config {
type: ZUO_TEST
mode: PUBLISH_ONLY
message_history_limit: 5
}
1
2
3
4
5
在adapter_manager.h里面增加对ZuoTest的适配:

class AdapterManager {
public:
......
//-- Zuo added on 2018-04-15 for testPublishSubnode
//-- 这里的ZuoTest可以理解为对topic取了个别名。
REGISTER_ADAPTER(ZuoTest);
};
1
2
3
4
5
6
7
在adapter_manager.cc的Init()函数的有限状态机内添加一个分支,如下:

void AdapterManager::Init(const AdapterManagerConfig &configs) {
......
  for (const auto &config : configs.config()) {
      switch (config.type()) {
        ......
        case AdapterConfig::ZUO_TEST:
        EnableZuoTest(FLAGS_zuo_test_topic, config);
        break;
          default:
          AERROR << "Unknown adapter config type!";
          break;
      }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
在adapter_config.proto里面增加ZUO_TEST标签:(不然上述的有限状态机分支就无法进入)

message AdapterConfig {
enum MessageType {
    ......
  ZUO_TEST = 45;
}
1
2
3
4
5
在message_adapters.h里面增加对应消息的别名:

using ZuoTestAdapter = Adapter<sensor_msgs::Image>;
1
剩下的就和上述已配置好的Topic的使用方法相同了,我就不赘述了。

通知
通知已配置好的Topic

如果是已经配置好的topic,那就很简单,如下:

//-- 新建msg
PerceptionObstacles obstacles;
//-- publish msg
common::adapter::AdapterManager::PublishPerceptionObstacles(obstacles);
1
2
3
4
通知未配置好的Topic

其实在订阅未配置好的Topic里面的配置方法,是通用的,和这里的配置方法一样,我就不赘述了,在配置好以后参考上一步的通知方法即可。
代码分析
上一篇How_to_add_a_subnode已经讲到了如何添加一个subnode,但是还没有说到如何在这个subnode里面subscribe/publish(订阅/发布)消息,下面将根据上述的操作步骤讲解在Apollo里面为什么要这么用topic。

在说Apollo前,还是带一下ROS的通信机制,也方便我们后续的理解,详情可以了解ROS Wiki 

原生的ROS其实是如上图一个消息传递流程,简而言之就是,一个节点发,一个节点收,中间是由Master进行转发,具体操作如下:

//-- Zuo added on 2018-04-13
void chatterCallback(const std_msgs::String::ConstPtr& msg)  
{
    //-- do sth
    printf("msg's data = %s", msg.data);
}
int main(int argc, char **argv)  
{
    ros::init(argc, argv, "listener");
    ros::NodeHandle n;

    //-- 向Topic发布消息
    //-- 这里是告诉Master,我需要在'TopicfullName'上发布一个消息。
    //-- @param_0 Topic名字
    //-- @param_1 消息发布队列大小
    //-- 返回的句柄需要保存,用来调用publish函数
    ros::Publisher Zuo_pub = n.advertise<std_msgs::String>("TopicfullName", 1000);
    std_msgs::String msg;
    msg.data = "Hello World";
    //-- 用户根据返回的句柄调用publish函数,向对应的topic发送消息
    Zuo_pub.publish(msg);

    //-- 订阅一个Topic
    //-- 如果`TopicfullName`接收到消息,就会触发这里的回调函数chatterCallback()
    ros::Subscriber sub = n.subscribe("TopicfullName", 1000, chatterCallback);
    ros::spin();//ros::spin()进入自循环,循环获取事件
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
注意:上面的两个函数advertise/subscribe的方式我们可以多留意一下,之后在Apollo的代码讲解里面也会看到类似的方式。

在Apollo里面,为了降低subnode在通信过程中对Master节点的依赖,将上图的机制设计成两个subnode直接类似P2P通信,相当于对原来的网络做了一个去中心化的改变。但是这里我们不用管,对于用户来说,我们的使用方式没变,还是通过调用advertise/subscribe两个接口来进行节点通信。

诚然,在Apollo里面我们可以直接调用advertise/subscribe,但是可以用,不代表应该用。看代码我们会发现,在Apollo底层(adapter_manager.h)的确是调用了advertise/subscribe,但是在其之上还封装了几层,它将对不同Topic的advertise/subscribe使用封装成了对应的带有Topic名字标签的函数,例如:

common::adapter::AdapterManager::PublishPerceptionObstacles(obstacles);
1
上述这种本质上就是ROS里面的publish()函数,但是Apollo对publish()函数封装后,使得每个不同模块的publish都带有各自不同的标签,每一个PublishXXX()只能发送对应类型的Msg,也就是说,原生的ROS下,在大量的发送/订阅的场景下,需要用户管理大量的句柄(这里可以参考上述ROS的简例)。

上面是Publish,下面看看Apollo对subscribe是怎么封装的:
 AdapterManager::AddImageShortCallback(&ZuoTestSubnode::ZuoImgTestCallback,this);
1
Apollo将ROS的回调函数封装成一个函数队列,上面的函数就是往这个函数队列里添加回调函数。那么,这个函数在哪里定义的呢?如果直接搜索,是找不到的。这里Apollo用了一个技巧。AdapterManager::AddImageShortCallback()是在adapter_manager里面用宏展开和##拼接结合的方法而成。通过调用REGISTER_ADAPTER(name)来实现(REGISTER_ADAPTER的调用参考操作步骤里面的适配操作)。可以看到adapter_manager.h:#90开始,定义了三个不同参数的static void Add##name##Callback()函数。如下:
  static void Add##name##Callback(name##Adapter::Callback callback) {          \
    CHECK(instance()->name##_)                                                 \
        << "Initialize adapter before setting callback";                       \
    instance()->name##_->AddCallback(callback);                                \
  }                                                                            \
  template <class T>                                                           \
  static void Add##name##Callback(                                             \
      void (T::*fp)(const name##Adapter::DataType &data), T *obj) {            \
    Add##name##Callback(std::bind(fp, obj, std::placeholders::_1));            \
  }                                                                            \
  template <class T>                                                           \
  static void Add##name##Callback(                                             \
      void (T::*fp)(const name##Adapter::DataType &data)) {                    \
    Add##name##Callback(fp);                                                   \
  }  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
我们先不管函数具体作用,但是从上述代码我们可以看到,后两个函数最终也是调用了第一个函数,也就是
  static void Add##name##Callback(name##Adapter::Callback callback) {          \
1
接着跟进去,我们看到这个函数实际上就是调用了:
    instance()->name##_->AddCallback(callback);                                \
1
再跟进,可以看到在adapter.h里:
  /**
   * @brief registers the provided callback function to the adapter,
   * so that the callback function will be called once right after the
   * message hits the adapter.
   * @param callback the callback with signature void(const D &).
   */  
  void AddCallback(Callback callback) {
    receive_callbacks_.push_back(callback);
  } 
1
2
3
4
5
6
7
8
9
​ receive_callbacks_的定义如下:

  /// User defined function when receiving a message
  std::vector<Callback> receive_callbacks_;
1
2
其实到这里,Apollo订阅的封装实现很明显了,其在adapter_manager.h通过宏展开和拼接生成带有不同名字标签的函数群,这些函数群包含一系列topic相关操作的功能,如订阅,压入回调函数队列等等,然后在adapter_manager.cc里面通过有限状态机根据读入的config文件(参考dag_config_path变量)配置需要启动的subnode。这里调用的Enable##name(topicName,config)实现了对不同topic的订阅以及生成通知句柄,然后就可以调用Add##name##Callback来订阅指定的topic,以及调用Publish##name向指定topic发送消息。

那么Apollo这样绕了一圈将advertise/subscribe封装起来,有什么用呢?原因有如下两点:

当系统里面的相似类型多了,变量的管理会变得复杂起来,那么用这种带有名字标签的命名方式,可以让变量名能够做到顾名思义。
本质上,不同的subscribe和publish函数都是一些相同代码,Apollo通过##拼接和宏展开结合的方式,提高了这一块的代码复用率,也方便整个框架后续的延伸扩展。
还有一个要注意,name##Adapter是不同的消息格式,在message_adapters.h定义的别名,这是为了配合整套方案而将不同的消息都别名成了nameAdapter的形式。

using PerceptionObstaclesAdapter = Adapter<perception::PerceptionObstacles>;
1
类似上面的这样取别名,这里的perception::PerceptionObstacles是在perception_obstacle.pb.h的文件内定义的了,这个和protobuf有关,暂且不说,这是下一章How_to_add_a_new_msg的内容。

流程图
下面是整个topic配置的整体流程图,最终呈现在用户面前的是两句Publish##Name()和Add##Name##Callbackc()。 

--------------------- 
作者:Zuo丶 
来源:CSDN 
原文:https://blog.csdn.net/u012423865/article/details/80024870?utm_source=copy 
版权声明:本文为博主原创文章,转载请附上博文链接!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Apollo如何通知/订阅主题topic 的相关文章

  • ROS系统基本功能的使用详解(基本指令/节点/服务/启动文件/动态参数)

    ROS系统基本功能的使用详解 一 创建工作空间 二 创建与编译ROS功能包 三 ROS的基本命令 3 1 节点 3 2 主题 3 3 服务 3 4 参数服务器 四 节点的创建与运行 4 1 创建源文件 4 2 修改CMakeLists tx
  • 思岚RPLIDAR A2 在ubuntu 16.04上的测试

    1 下载雷达ROS包 首先在github上下载rplidar的ros包 下载指令为 默认安装了git git clone https github com Slamtec rplidar ros git 在ubuntu上创建工作空间 并将该
  • ROS诸多调试工具总结1

    ROS有许多调试工具来为ROS调试你的工具 1 rosnode 参数 用法 作用 list rosnode list 查看当前运行了哪些节点 info rosnode info node name 查看该节点发布 接受哪些话题以及服务 ki
  • 关于相机与激光雷达数据采集与标定

    最近在做一个关于车路协同的项目 需要做一个路侧系统 传感器有摄像头和激光雷达 相机和激光雷达联合标定费了老半天劲 在此记录一下 雷达时间戳不对 导致摄像头和雷达的数据无法对齐 解决办法 修改雷达驱动发布点云消息时的时间戳 相机内参标定可以使
  • ROS学习(1)——ROS1和ROS2的区别

    因为机器人是一个系统工程 它包括了机械臂结构 电子电路 驱动程序 通信框架 组装集成 调试和各种感知决策算法等方面 任何一个人甚至是一个公司都不可能完成机器人系统的研发工作 但是我们又希望自己能造出一个机器人跑一跑 验证一下自己的算法 所以
  • 1-如何安装ROS

    如何安装ROS 大家好 我是如何 今天尝试在Ubantu下安装ROS Robot Operating System 测试环境 虚拟机VMware Ubantu20 04 准备步骤 添加ROS软件源 sudo sh c echo deb ht
  • V2X应用场景之协同式自动驾驶

    转自 http zhidx com p 96637 html V2X应用场景之协同式自动驾驶 这个应用场景我觉得是比较典型的 也想多花点时间给大家介绍一下 就是关于V2X在自动驾驶里面很典型的应用 我们管它叫协同式自动驾驶车队 什么意思呢
  • Hypervisor介绍及在智能驾驶的应用

    转自Hypervisor 智能座舱和智能驾驶融合的关键技术 腾讯新闻
  • 激光雷达LMS111在ROS上的使用

    LMS111 10100 在ROS上的测试与使用 准备工作 设备 硬件 LMS111 101000激光雷达 软件 ubuntu16 04 ROS 开始 设备连接 将激光雷达与处理器 电脑 工控机等 通过以太网连接好 激光雷达默认的IP地址为
  • 在Ubuntu 14.04.2 LTS上安装Qt

    Qt是一个跨平台的应用程序框架 广泛用于开发具有GUI界面的应用软件以及命令行工具 几乎所有操作系统都可以使用Qt 如Windows Mac OS X Android等 用于开发Qt应用程序的主要编程语言是C 但是可以使用诸如Python
  • rosprofiler 安装和使用

    rosprofiler wiki 页面 http wiki ros org rosprofiler rosprofiler package 下载rosprofiler和ros statistics msgs 放到工程目录下编译 https
  • Ubuntu安装ROS

    原文链接 https blog csdn net qq 44830040 article details 106049992 这也是我在ubuntu里面安装ROS的第N次 以前每次安装过程都忘记总结了 导致每次安装ROS都浪费了很多的时间用
  • 【ROS】usb_cam相机标定

    1 唠叨两句 当我们要用相机做测量用途时 就需要做相机标定了 不然得到的计算结果会有很大误差 标定的内容包括三部分 内参 外参还有畸变参数 所以标定的过程就是要求得上面这些参数 以前弄这个事估计挺麻烦 需要做实验和计算才能得到 现在通过ro
  • ROS1 ROS2学习

    ROS1 ROS2学习 安装 ROS ROS1 ROS2 命令行界面 ROS2 功能包相关指令 ROS 命令行工具 ROS1 CLI工具 ROS2 CLI工具 ROS 通信核心概念 节点 Node 节点相关的CLI 话题 Topic 编写发
  • 什么是 void `std::allocator`?即:`std::allocator`

    自动生成ROS 机器人操作系统 message C 头文件包含如下类型定义 typedef std msgs Header
  • 将 CUDA 添加到 ROS 包

    我想在 ros 包中使用 cuda 有人给我一个简单的例子吗 我尝试使用 cuda 函数构建一个静态库并将该库添加到我的包中 但总是出现链接错误 未定义的引用 cuda 我已经构建了一个可执行文件而不是库并且它可以工作 请帮忙 我自己找到了
  • 不使用ros编译roscpp(使用g++)

    我正在尝试在不使用ROS其余部分的情况下编译roscpp 我只需要订阅一个节点 但该节点拥有使用旧版本ROS的节点 并且由于编译问题 我无法将我的程序与他的程序集成 我从git下载了源代码 https github com ros ros
  • 错误状态:平台不允许不安全的 HTTP:http://0.0.0.0:9090

    我正在尝试从我的 flutter 应用程序连接到 ws local host 9090 使用 rosbridge 运行 的 Ros WebSocket 服务 但我在 Flutter 中收到以下错误 错误状态 平台不允许不安全的 HTTP h
  • 在 Ubuntu 18.10 上安装 ROS Melodic

    I can t是唯一对 Cosmic 与 Wayland 和 Melodic 的组合感兴趣的人 我会坦白说 我似乎已经在 XPS 13 9370 上成功管理了此操作 或者至少安装脚本 最终 成功完成 然而 有一个非常棘手的解决方法 无论结果
  • 如何使用 PyQT5 连接和分离外部应用程序或对接外部应用程序?

    我正在使用 ROS 为多机器人系统开发 GUI 但我对界面中最不想做的事情感到困惑 在我的应用程序中嵌入 RVIZ GMAPPING 或其他屏幕 我已经在界面中放置了一个终端 但我无法解决如何向我的应用程序添加外部应用程序窗口的问题 我知道

随机推荐

  • Python->进程-线程->TCP服务器客户端-服务端->返回固定数据的静态web服务器-学习

    作者 芝士小熊饼干 系列专栏 Python 坚持天数 12天 获取进程id和进程的父id 进程名 导入包 import multiprocessing import time import os 创建任务 def task1 print f
  • Django-admin录入中文错误:Incorrect string value

    在Django自带后台中经常会出现编码错误 Incorrect string value xE7 xAE x80 xE5 x8D x95 for column message at row 1 需要修改admin表的中的编码 才能保证数据正
  • Rust- FFI (Foreign Function Interface)

    Foreign Function Interface FFI is a mechanism that allows code written in one language to call code written in another l
  • 手写ArrayStack底层 实现代码

    一 接口的定义 package p1 接口 public interface Stack
  • 华为OD机试真题B卷 Java 实现【统计字符】,附详细解题思路

    一 题目描述 输入一行字符 分别统计出包含英文字母 空格 数字和其它字符的个数 数据范围 输入的字符串长度满足 1 le n le 1000 1 n 1000 二 输入描述 输入一行字符串 可以有空格 三 输出描述 统计其中英文字符 空格字
  • ECharts画动态仪表盘+柱状图(ajax获取+循环画图)

    出来工作几个月了 整理下最近学的的东西 刚刚才开通博客 第一篇就先画仪表盘 柱状图 有什么写的不好的地方 请大家多多指教 jsp中首先要引用几个文件 xx xxx js 是我js代码存放的文件
  • C++标准库异常类

    C 标准库异常类继承层次中的根类为exception 其定义在exception头文件中 它是C 标准库所有函数抛出异常的基类 exception的接口定义如下 namespace std class exception public ex
  • 记一个复制黏贴的功能想法(黏贴剪切板中的数字自增,复制黏贴自增)

    起因 照例是要写起因的 起因非常之简单 不知道大家有没有遇到过需要输入连续的 id 101 id 110 这类数字的时候 这个时候能做的基本上是复制100 黏贴100 然后手动改 101 102 在我的脑海里 除了使用excel 其他没有很
  • 【华为机试真题 JAVA】执行时长-100

    编程题目 100分 执行时长 2021 2022 Q2考试题 时间限制 C C 1秒 其他语言 2秒 空间限制 C C 262144K 其他语言524288K 64bit IO Format lld 本题可使用本地IDE编码 不能使用本地已
  • 面对对象--结构体和类的区别

    一 面向对象 结构体和类的区别 结构体是一种值类型 而类是引用类型 值类型用于存储数据的值 引用类型用于存储对实际数据的引用 那么结构体就是当成值来使用的 类则通过引用来对实际数据操作 构使用栈存储 Stack Allocation 而类使
  • 利用腾讯云函数隐藏C2服务器

    1 简介 腾讯云函数 可以为企业和开发者提供无服务器执行环境 无需购买和管理服务器 只需要在腾讯云上使用平台支持的语言编写核心代码并设置代码运行的条件 即可在腾讯云基础设施上弹性 安全地运行代码 C2服务器所有流量通过腾讯云函数进行转发 由
  • Html04_input框中的value/key/placeholder到底是什么

    1 input框中的value值到底是什么 value 属性为 input 元素设定值 input标签有很多类型 也就是type 对于不同的输入类型 value 属性的用法也不同 以下是一些常用type的说明 text 文本框 input默
  • 513. Find Bottom Left Tree Value

    Given a binary tree find the leftmost value in the last row of the tree Example 1 Input 2 1 3 Output 1 Example 2 Input 1
  • Flutter:自定义组件的上下左右弹出层

    背景 最近要使用Flutter实现一个下拉菜单 需求就是 在当前组件下点击 其下方弹出一个菜单选项 如下图所示 实现起来 貌似没什么障碍 在Flutter中本身就提供了弹出层PopupMenuButton组件和showMenu方法 于是开搞
  • 复习之linux存储的基本管理

    一 实验环境的设定 1 实验环境的搭建 本节内容只需要一台虚拟机 westosa reset重置虚拟机 保证实验环境的纯净 配置网络实现ssh连接 重置虚拟机后 配置网络 设定ip 172 25 254 100 保证与主机可以通信 实现ss
  • Ant Design的layout布局 --- 根据路由配置渲染

  • python基础3——流程控制

    文章目录 一 操作符 1 1 比较操作符 1 2 逻辑操作符 1 3 成员操作符 1 4 身份操作符 二 流程控制 2 1 条件判断 2 2 循环语句 2 2 1 for循环 2 2 2 while循环 2 3 continue与break
  • 谷歌Colab云端部署Stable Diffusion 进行绘图

    系列文章目录 本地部署Stable Diffusion教程 亲测可以安装成功 Stable Diffusion界面参数及模型使用 文章目录 系列文章目录 前言 一 Colab是什么 二 操作步骤 1 找到对应的脚本 2 在谷歌Colab里执
  • wait WNOHANG 僵尸进程

    什么是僵尸进程 首先内核会释放终止进程 调用了exit系统调用 所使用的所有存储区 关闭所有打开的文件等 但内核为每一个终止子进程保存了一定量的信息 这些信息至少包括进程ID 进程的终止状态 以及该进程使用的CPU时间 所以当终止子进程的父
  • Apollo如何通知/订阅主题topic

    转自 https blog csdn net u012423865 article details 80024870 导读 众所周知 Apollo是基于ROS开发的 所以其底层也是基于消息的机制进行节点通信的 但是它在ROS的基础上做了一些