apollo2.5源码解析之适配器模式

本博客主要参考https://blog.csdn.net/davidhopper/article/details/79197075https://zhuanlan.zhihu.com/p/50523482,在此表示感谢。

  • 适配器模式介绍

适配器模式的作用是将一个类的接口转换成客户希望的另外一个接口看,使得原本由于接口不兼容而不能一起工作的类可以一起工作。从实现上可以将适配器模式分为两种:类适配器(多继承方式)和对象适配器(对象组合方式)。关于适配器模式的详细介绍,可以参考博客https://blog.csdn.net/liang19890820/article/details/66973296

  • apollo2.5中适配器模式分析

在apollo2.5中,各模块内部的消息传递采用Google Protocol Buffer格式,各模块之间的底层消息传递目前采用ROS机制,后续版本好像已经替换为百度自研机制。不管apollo项目各模块之间的底层消息传递机制如何改变,我们必须确保各模块内部的消息传递格式不变,即采用Google Protocol Buffer格式,否则会因为底层消息传递机制的改变,导致各模块内部涉及消息处理的代码被迫修改,这既严重破坏各模块代码的独立性和正确性,又严重影响项目开发的效率。

一.适配器模板类Adapter

文件modules/common/adapters/adapter.h中定义了AdapterBase类和Adapter类,下面逐个来进行分析。AdapterBase类是一个抽象基类,定义了每个具体的适配器类对外的接口。Adapter类继承自AdapterBase类,并且是一个模板类,各种protobuf消息类型都可以用此模板类来生成一个消息适配器类。在文件modules/common/adapters/message_adapters.h中就用上述模板类生成了apollo2.5中所有消息类型对应的适配器类,如下所示:

using ChassisAdapter = Adapter<::apollo::canbus::Chassis>;
using ChassisDetailAdapter = Adapter<::apollo::canbus::ChassisDetail>;
using ControlCommandAdapter = Adapter<control::ControlCommand>;
using GpsAdapter = Adapter<apollo::localization::Gps>;
using ImuAdapter = Adapter<localization::Imu>;
using RawImuAdapter = Adapter<apollo::drivers::gnss::Imu>;
using LocalizationAdapter = Adapter<apollo::localization::LocalizationEstimate>;
using MonitorAdapter = Adapter<apollo::common::monitor::MonitorMessage>;
using PadAdapter = Adapter<control::PadMessage>;
using PerceptionObstaclesAdapter = Adapter<perception::PerceptionObstacles>;
using PlanningAdapter = Adapter<planning::ADCTrajectory>;
using PointCloudAdapter = Adapter<::sensor_msgs::PointCloud2>;
using ImageFrontAdapter = Adapter<::sensor_msgs::Image>;
using ImageShortAdapter = Adapter<::sensor_msgs::Image>;
using ImageLongAdapter = Adapter<::sensor_msgs::Image>;
using PredictionAdapter = Adapter<prediction::PredictionObstacles>;
using DriveEventAdapter = Adapter<DriveEvent>;
using TrafficLightDetectionAdapter = Adapter<perception::TrafficLightDetection>;
using RoutingRequestAdapter = Adapter<routing::RoutingRequest>;
using RoutingResponseAdapter = Adapter<routing::RoutingResponse>;
using RelativeOdometryAdapter =
    Adapter<calibration::republish_msg::RelativeOdometry>;
using InsStatAdapter = Adapter<drivers::gnss::InsStat>;
using InsStatusAdapter = Adapter<drivers::gnss_status::InsStatus>;
using GnssStatusAdapter = Adapter<drivers::gnss_status::GnssStatus>;
using SystemStatusAdapter = Adapter<apollo::monitor::SystemStatus>;
using StaticInfoAdapter = Adapter<apollo::data::StaticInfo>;
using MobileyeAdapter = Adapter<drivers::Mobileye>;
using DelphiESRAdapter = Adapter<drivers::DelphiESR>;
using ContiRadarAdapter = Adapter<drivers::ContiRadar>;
using UltrasonicAdapter = Adapter<drivers::Ultrasonic>;
using CompressedImageAdapter = Adapter<sensor_msgs::CompressedImage>;
using GnssRtkObsAdapter = Adapter<apollo::drivers::gnss::EpochObservation>;
using GnssRtkEphAdapter = Adapter<apollo::drivers::gnss::GnssEphemeris>;
using GnssBestPoseAdapter = Adapter<apollo::drivers::gnss::GnssBestPose>;
using LocalizationMsfGnssAdapter =
    Adapter<apollo::localization::LocalizationEstimate>;
using LocalizationMsfLidarAdapter =
    Adapter<apollo::localization::LocalizationEstimate>;
using LocalizationMsfSinsPvaAdapter =
    Adapter<apollo::localization::IntegSinsPva>;
using LocalizationMsfStatusAdapter =
    Adapter<apollo::localization::LocalizationStatus>;
using RelativeMapAdapter = Adapter<apollo::relative_map::MapMsg>;
using NavigationAdapter = Adapter<apollo::relative_map::NavigationInfo>;
using VoiceDetectionRequestAdapter =
    Adapter<apollo::dreamview::VoiceDetectionRequest>;
using VoiceDetectionResponseAdapter =
    Adapter<apollo::dreamview::VoiceDetectionResponse>;
// for pandora
using PandoraPointCloudAdapter = Adapter<::sensor_msgs::PointCloud2>;
using PandoraCameraFrontColorAdapter = Adapter<::sensor_msgs::Image>;
using PandoraCameraRightGrayAdapter = Adapter<::sensor_msgs::Image>;
using PandoraCameraLeftGrayAdapter = Adapter<::sensor_msgs::Image>;
using PandoraCameraFrontGrayAdapter = Adapter<::sensor_msgs::Image>;
using PandoraCameraBackGrayAdapter = Adapter<::sensor_msgs::Image>;

各个消息适配器类是apollo2.5中各个模块和ROS通讯的一个抽象层,这样设计的一个好处就是,即使以后不用ROS作为底层消息传递机制了,也只需要修改Adapter中的相关代码,而各模块业务中的代码无需修改。

Adapter类中定义了一个私有成员变量receive_callbacks_,用来存储用户定义的消息处理函数也称回调函数Callback

/// User defined function when receiving a message
std::vector<Callback> receive_callbacks_;

其中,Callback的输入参数为D(模板参数),返回值为void,定义如下:

typedef typename std::function<void(const D&)> Callback;

另外,还定义了两个公有成员函数AddCallback和PopCallback,分别用来向receive_callbacks_中添加一个回调函数和从receive_callbacks_中删除最近添加的回调函数:

  /**
   * @brief Register 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);
  }

  /**
   * @brief Pop out the latest added callback.
   * @return false if there's no callback to pop out, true otherwise.
   */
  bool PopCallback() {
    if (receive_callbacks_.empty()) {
      return false;
    }
    receive_callbacks_.pop_back();
    return true;
  }

来看看公有成员函数OnReceive,OnReceive内部通过调用RosCallback来处理收到的消息:

  /**
   * @brief Data callback when receiving a message. Under the hood it calls
   * the callback ROS would invoke, useful when trying to emulate the callback
   * behavior when there's not a ROS.
   * @param message the newly received message.
   */
  void OnReceive(const D& message) {
    RosCallback(boost::make_shared<D const>(message));
  }

再来看看私有成员函数RosCallback,RosCallback内部先后调用EnqueueData和FireCallbacks,EnqueueData将接收到的消息存入data_queue_,FireCallbacks调用在receive_callbacks_中注册的每个回调函数来处理收到的消息:

  /**
   * @brief The ROS callback that will be invoked whenever a new
   * message is received.
   * @param message the newly received message.
   */
  void RosCallback(const DataPtr& message) {
    last_receive_time_ = apollo::common::time::Clock::NowInSeconds();
    EnqueueData(message);
    FireCallbacks(*message);
  }

 /**
   * @brief Push the shared-pointer-guarded data to the data queue of
   * the adapter.
   */
  void EnqueueData(const DataPtr& data) {
    // Dump the proto message to a file
    if (enable_dump_) {
      DumpMessage<D>(*data);
    }

    // Don't try to copy data and enqueue if the message_num is 0
    if (message_num_ == 0) {
      return;
    }

    // Lock the queue.
    std::lock_guard<std::mutex> lock(mutex_);
    if (data_queue_.size() + 1 > message_num_) {
      data_queue_.pop_back();
    }
    data_queue_.push_front(data);
  }

  /**
   * @brief Proactively invokes the callbacks one by one registered with the
   * specified data.
   * @param data the specified data.
   */
  void FireCallbacks(const D& data) {
    for (const auto& callback : receive_callbacks_) {
      callback(data);
    }
  }

在Adapter类中还定义了两个成员变量,数据队列data_queue_用于接收消息,数据队列observed_queue_用于保存历史消息:

  /// The received data. Its size is no more than message_num_
  std::list<DataPtr> data_queue_;

  /// It is the snapshot of the data queue. The snapshot is taken when
  /// Observe() is called.
  std::list<DataPtr> observed_queue_;

data_queue_的填充由公有成员函数OnReceive内部调用的RosCallback里的EnqueueData实现,而通过调用公有成员函数Observe,可以将data_queue_复制给observed_queue_:

/**
   * @brief Copy the data_queue_ into the observing queue to create a
   * view of data up to the call time for the user.
   */
  void Observe() override {
    std::lock_guard<std::mutex> lock(mutex_);
    observed_queue_ = data_queue_;
  }

关于observed_queue_的使用,一般情况是首先在AdapterManager单例初始化时通过Enable##name函数把对应消息适配器对象的Observe函数添加到AdapterManager单例的observers_中:

  void InternalEnable##name(const std::string &topic_name,                     \
                            const AdapterConfig &config) {                     \
    name##_.reset(                                                             \
        new name##Adapter(#name, topic_name, config.message_history_limit())); \
    /* Subscribe to a topic. */                                                \
    if (config.mode() != AdapterConfig::PUBLISH_ONLY && IsRos()) {             \
      name##subscriber_ =                                                      \
          node_handle_->subscribe(topic_name, config.message_history_limit(),  \
                                  &name##Adapter::RosCallback, name##_.get()); \
    }                                                                          \
    /* Advertise a topic. */                                                   \
    if (config.mode() != AdapterConfig::RECEIVE_ONLY && IsRos()) {             \
      name##publisher_ = node_handle_->advertise<name##Adapter::DataType>(     \
          topic_name, config.message_history_limit(), config.latch());         \
    }                                                                          \
    /* Lambda expression. */                                                   \
    observers_.push_back([this]() { name##_->Observe(); });                    \
    name##config_ = config;                                                    \
  }   

然后只要调用AdapterManager单例的Observe函数就能生成各个消息适配器对象中接收到的消息的一个快照:

void AdapterManager::Observe() {
  for (const auto observe : instance()->observers_) {
    observe();
  }
}

最后,关于Adapter类的作用总结如下:

apollo项目各模块之间必须相互通讯才能完成任务,为了使各模块彼此理解对方的消息,有必要使用统一的消息机制。apollo2.5采用ROS消息机制,适配器就是保证各模块正常接收、发送ROS消息的一种转接器。

Adapter class serves as the interface and a layer of abstraction for Apollo modules to interact with various I/O (e.g. ROS). The adapter will also store history data, so that other Apollo modules can get access to both the current and the past data without having to handle communication protocols directly.

Each Adapter instance only works with one single topic and its corresponding data type. Under the hood, a queue is used to store the current and historical messages. In most cases, the underlying data type is a proto, though this is not necessary.

Adapter::Observe() is thread-safe, but calling it from multiple threads may introduce unexpected behavior. Adapter is thread-safe w.r.t. data access and update.

二.适配器管理器类AdapterManager

文件modules/common/adapters/adapter_manager.h中定义了AdapterManager类,这是一个单例类,获取其对象请使用AdapterManager::instance():

DECLARE_SINGLETON(AdapterManager);

在AdapterManager类中使用宏REGISTER_ADAPTER来注册各个消息适配器对象:

  std::unique_ptr<name##Adapter> name##_;                                      \
  ros::Publisher name##publisher_;                                             \
  ros::Subscriber name##subscriber_;                                           \
  AdapterConfig name##config_;                                                 \

AdapterManager类的成员变量除了上述各个适配器对象及其关联的消息发布对象、消息订阅对象、适配器配置对象外,还有:

// The node handler of ROS, owned by the AdapterManager singleton.
std::unique_ptr<ros::NodeHandle> node_handle_;

/// Observe() callbacks that will be used to to call the Observe()
/// of enabled adapters.
std::vector<std::function<void()>> observers_;

// Indicate whether the AdapterManager singleton has been initialized.
bool initialized_ = false;

公有成员函数Init通过配置文件adapter.conf或者AdapterManagerConfig对象初始化AdapterManager单例,对于采用ROS底层消息传递机制的模块(即该模块进程为一个ROS节点),先重置AdapterManager单例的node_handle_成员变量(即AdapterManager单例持有的ROS node handler),然后对于模块中的每一个消息配置调用对应消息类型的Enable函数:

void AdapterManager::Init(const AdapterManagerConfig &configs) {
  if (Initialized()) {
    return;
  }
  instance()->initialized_ = true;
  if (configs.is_ros()) {
    instance()->node_handle_.reset(new ros::NodeHandle());
  }
  for (const auto &config : configs.config()) {
    switch (config.type()) {
      case AdapterConfig::POINT_CLOUD:
        EnablePointCloud(FLAGS_pointcloud_topic, config);
        break;
      case AdapterConfig::GPS:
        EnableGps(FLAGS_gps_topic, config);
        break;
      case AdapterConfig::IMU:
        EnableImu(FLAGS_imu_topic, config);
        break;
      // 略去中间的多个case
      default:
        AERROR << "Unknown adapter config type!";
        break;
    }
  }
}

这里以RoutingRequest类型消息为例说明一下Enable##name函数,InternalEnable##name函数首先新建一个对应的消息适配器对象用来侦听提前设定好的topic,然后通过node_handle_成员变量调用ros::NodeHandle的subscribe函数和advitise函数来创建对应的消息订阅器和消息发布器,最后把消息适配器对象的Observe函数添加到AdapterManager单例的observers_

  static void EnableRoutingRequest(const std::string &topic_name,                      
                           const AdapterConfig &config) {                      
    CHECK(config.message_history_limit() > 0)                                  
        << "Message history limit must be greater than 0";                     
    instance()->InternalEnableRoutingRequest(topic_name, config);                      
  }  

  void InternalEnableRoutingRequest(const std::string &topic_name,                     
                            const AdapterConfig &config) {                     
    RoutingRequest_.reset(                                                             
        new RoutingRequestAdapter(“RoutingRequest”, topic_name, config.message_history_limit())); 
    if (config.mode() != AdapterConfig::PUBLISH_ONLY && IsRos()) {             
      RoutingRequestsubscriber_ =                                                      
          node_handle_->subscribe(topic_name, config.message_history_limit(),  
                                  &RoutingRequestAdapter::RosCallback, RoutingRequest_.get());   
    }                                                                          
    if (config.mode() != AdapterConfig::RECEIVE_ONLY && IsRos()) {             
      RoutingRequestpublisher_ = node_handle_->advertise<RoutingRequestAdapter::DataType>(     
          topic_name, config.message_history_limit(), config.latch());         
    }                                                                          
    observers_.push_back([this]() { RoutingRequest_->Observe(); });                    
    RoutingRequestconfig_ = config;                                                    
  } 

接下来以RoutingRequest类型消息为例介绍两个非常重要的函数Add##name##Callback和Publish##name。

Add##name##Callback函数用来将消息响应函数注册到对应的消息适配器对象,消息相应函数在各个模块App类中:

static void AddRoutingRequestCallback(RoutingRequestAdapter::Callback callback) {          
    CHECK(instance()->RoutingRequest_)                                                 
        << "Initialize adapter before setting callback";                       
    instance()->RoutingRequest_->AddCallback(callback);                                
  }                                                                            
  template <class T>                                                           
  static void AddRoutingRequestCallback(                                             
      void (T::*fp)(const RoutingRequestAdapter::DataType &data), T *obj) {            
    AddRoutingRequestCallback(std::bind(fp, obj, std::placeholders::_1));            
  }                                                                            
  template <class T>                                                           
  static void AddRoutingRequestCallback(                                             
      void (T::*fp)(const RoutingRequestAdapter::DataType &data)) {                    
    AddRoutingRequestCallback(fp);                                                   
  }                            

Publish##name函数是消息发布函数,具体实现请看代码:

  static void PublishRoutingRequest(const RoutingRequestAdapter::DataType &data) {             
    instance()->InternalPublishRoutingRequest(data);                                   
  } 

  void InternalPublishRoutingRequest(const RoutingRequestAdapter::DataType &data) {            
    /* Only publish ROS msg if node handle is initialized. */                  
    if (IsRos()) {                                                             
      if (!RoutingRequestpublisher_.getTopic().empty()) {                              
        RoutingRequestpublisher_.publish(data);                                        
      } else {                                                                 
        AERROR << "RoutingRequest" << " is not valid.";                                   
      }                                                                        
    } else {                                                                   
      /* For non-ROS mode, just triggers the callback. */                      
      if (RoutingRequest_) {                                                           
        RoutingRequest_->OnReceive(data);                                              
      } else {                                                                 
        AERROR << "RoutingRequest" << " is null.";                                        
      }                                                                        
    }                                                                          
    RoutingRequest_->SetLatestPublished(data);                                         
  }

还有两个公有成员函数的作用不明,Tf2Buffer和CreateTimer,暂时先不管,后面再补上

/**
   * @brief Return a reference to static tf2 buffer.
   */
  static tf2_ros::Buffer &Tf2Buffer() {
    static tf2_ros::Buffer tf2_buffer;
    static TransformListener tf2Listener(&tf2_buffer,
                                         instance()->node_handle_.get());
    return tf2_buffer;
  }

  /**
   * @brief Create a timer which will call a callback at the specified
   * rate. It takes a class member function, and a bare pointer to the
   * object to call the method on.
   */
  template <class T>
  static ros::Timer CreateTimer(ros::Duration period,
                                void (T::*callback)(const ros::TimerEvent &),
                                T *obj, bool oneshot = false,
                                bool autostart = true) {
    if (IsRos()) {
      return instance()->node_handle_->createTimer(period, callback, obj,
                                                   oneshot, autostart);
    } else {
      AWARN << "ROS timer is only available in ROS mode, check your adapter "
               "config file! Return a dummy timer that won't function.";
      return ros::Timer();
    }
  }

关于AdapterManager类的作用总结如下:

AdapterManager class hosts all the specific adapters and manages them. It provides APIs for the users to initialize, access and interact with the adapters that they are interested in. Each (potentially) useful adapter needs to be registered here with the macro REGISTER_ADAPTER.

三.适配器配置

文件modules/common/adapters/proto/adapter_config.proto中定义了AdapterManagerConfig类和AdapterConfig类。AdapterManagerConfig类有两个成员config和is_ros,config是一个AdapterConfig对象,表示对应模块的一个消息配置,is_ros是一个bool变量,表示对应模块进程是一个ROS节点:

// A config to specify which messages a certain module would consume and
// produce.
message AdapterManagerConfig {
  repeated AdapterConfig config = 1;
  required bool is_ros = 2;  // Whether the message comes from ROS
}

AdapterConfig类有四个成员type、mode、message_history_limit和latch,具体请看代码及注释:

// Property of a certain Input/Output that will be used by a module.
message AdapterConfig {
  enum MessageType {
    POINT_CLOUD = 1;
    GPS = 2;
    IMU = 3;
  // 后面的均省略
 }
  enum Mode {
    RECEIVE_ONLY = 0;
    PUBLISH_ONLY = 1;
    DUPLEX = 2;
  }
  required MessageType type = 1;
  required Mode mode = 2;
  // The max number of received messages to keep in the adapter, this field
  // is not useful for PUBLISH_ONLY mode messages.
  optional int32 message_history_limit = 3 [default = 10];
 // If true, the last message published on this topic will be saved and sent to new 
  // subscribers when they connect
  optional bool latch = 4 [default=false];
}

最后来看一下canbus模块的配置文件adapter.conf的内容,其它模块的类似,都是在各模块的conf/adapter.conf文件中:

config {
  type: CONTROL_COMMAND
  mode: RECEIVE_ONLY
  message_history_limit: 10
}
config {
  type: CHASSIS
  mode: PUBLISH_ONLY
  message_history_limit: 10
}
config {
  type: CHASSIS_DETAIL
  mode: PUBLISH_ONLY
}
config {
  type: MONITOR
  mode: PUBLISH_ONLY
}
is_ros: true

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值