CyberRT-record-源码查看—cyber_py3

Rosbag ROS

 ROS中通过record.cpp 调用Recorder 类来进行bag的保存
   rosbag record 的代码位于 ros_comm\tools\rosbag\src\recorder.cpp 中。实现的类主要为Recorder。 
        Recorder的接口,一共有4个。总的来说,录制bag包的流程分为2个部分,
		          一个流程订阅消息,并且放入队列,
				  另一个流程从队列中读取消息,并且保存到bag文件中。
				  
				doTrigger  
			01.subscribe 订阅指定的topic消息,并且返回Subscriber	  
				  
            02.订阅的消息通过doQueue来放入队列。
			  void Recorder::doQueue(const ros::MessageEvent<topic_tools::ShapeShifter const>& msg_event, 
			                         string const& topic,
									 shared_ptr<ros::Subscriber> subscriber, shared_ptr<int> count) {
			03.int Recorder::run()       { 启动了另一个线程record_thread来进行录制
			04.void Recorder::doRecord() { doRecord  读取消息,并且写入bag文件,其中会检查磁盘和是否超过最大间隔还没接收消息

Record CyberRT

 1.录制数据-工具 
  cyber_recorder 
    cyber_recorder 是一个功能强大的工具,支持录制、回放、查询、分割和修复 Cyber RT 的通信数据
	       record  play   info    split  recover 
    name = "recorder",
    srcs = [
           "recorder.cc", "info.cc",  "recoverer.cc", 
           "spliter.cc", "player/play_task.cc", "player/play_task_buffer.cc", 
           "player/play_task_consumer.cc", "player/play_task_producer.cc", 
           "player/player.cc",
       ],
    hdrs = [
           "recorder.h", "info.h", "recoverer.h", "spliter.h", 
           "player/play_param.h", "player/play_task.h", 
           "player/play_task_buffer.h", "player/play_task_consumer.h", 
           "player/play_task_producer.h", "player/player.h",
       ],

命令行 
    cyber_recorder record -c  /front/image_raw_compresser  /lidar/rear  -i 30 -m 4096 -o ~/data/5260908.record
	基本概念 : 
		 -m    --segment-size 
         -i    --segment-interval
         -c,   --white-channel <name>		only play the specified channel
		  -a   --all
          -k,  --black-channel <name>		not play the specified channel			 
	入口函数 
	

				
	###配置信息			
        std::vector<std::string> opt_file_vec;
                 opt_file_vec.emplace_back(opt_file_abs_path);
        		 opt_file_vec.emplace_back(std::string(optarg)); opt_file_vec.emplace_back(std::string(argv[i]));
        std::vector<std::string> opt_output_vec;
                  opt_output_vec.push_back(opt_output_file_abs_path);  
        		   opt_output_vec.push_back(default_output_file);
        std::vector<std::string> opt_white_channels;
                  opt_white_channels.emplace_back(std::string(argv[i]));
        std::vector<std::string> opt_black_channels;
                  opt_black_channels.emplace_back(std::string(optarg));	
        auto opt_header = HeaderBuilder::GetHeader();
        	  opt_header.set_segment_interval(interval_s * 1000000000ULL);
        	  opt_header.set_segment_raw_size(size_mb * 1024 * 1024ULL);

    ###执行内容
       	cyber/tools/cyber_recorder/main.cc
    	auto recorder = std::make_shared<Recorder>(opt_output_vec[0], 
    			                               opt_all,
                                              opt_white_channels,
                                              opt_black_channels, opt_header);
        bool record_result = recorder->Start();		  	
		

    ###存储数据函数
	   变量 
	       std::shared_ptr<RecordWriter> writer_ = nullptr;
		   std::vector<std::string> white_channels_;
		   std::unordered_map<std::string, std::shared_ptr<ReaderBase>> channel_reader_map_;
		    Connection<const ChangeMsg&> change_conn_;
        channel_patterns->emplace_back(name); 
        writer_.reset(new RecordWriter(header_));
		Recorder::Start()
		    #### 流程--创建node--  node_ = ::apollo::cyber::CreateNode(node_name);
		    #### 初始化  读取 _init_readers
             InitReadersImpl() 多个主题topic
		        ##### bool Recorder::InitReadersImpl()
		         std::shared_ptr<ChannelManager> channel_manage
		          // 获取历史的write  InitReaderImpl()  单个主题topic ####void Recorder::FindNewChannel
		            FindNewChannel(role_attr); 
					  如果
					      writer_->WriteChannel(role_attr.channel_name(), role_attr.message_type(), role_attr.proto_desc())
					  否则###########  InitReaderImpl(role_attr.channel_name(), role_attr.message_type());
		          //发现 listen new writers in future	  channel_manager	
                     this->TopologyCallback(change_msg);
					       FindNewChannel(change_message.role_attr());
		    		 change_conn_ = channel_manager->AddChangeListener(topology_callback);
					 
		        FindNewChannel(role_attr);		 
		         bool Recorder::InitReaderImpl	
                    channel_name  message_type 主题名称和消息类型	
                     share_this->ReaderCallback(raw_message, channel_name); ##回调函数 ReaderCallback
		        	######  void Recorder::ReaderCallback(const std::shared_ptr<RawMessage>& message, const std::string& channel_name) 
		        	      writer_->WriteMessage(channel_name, message, message_time_) ###写数据 WriteMessage
    ####具体如下 		
    bool Recorder::Start() {
      get_patterns_func(black_channels_, &black_channel_patterns_);
    
      writer_.reset(new RecordWriter(header_));
      if (!InitReadersImpl()) {
        AERROR << " _init_readers error.";
        return false;
      }
    }
    #########
        bool Recorder::InitReadersImpl() {
          std::shared_ptr<ChannelManager> channel_manager =
		  //  GetWriters()都是用来感知对应的writer的 
		  //  直接获取已经感知到的writer
          for (auto role_attr : role_attr_vec) {
            FindNewChannel(role_attr);
          }
        // 是用来感知将来加入的writer,
          // listen new writers in future
          auto topology_callback =
              [this](const apollo::cyber::proto::ChangeMsg& change_msg) {
                apollo::cyber::Async(
                    [this, change_msg] { this->TopologyCallback(change_msg); });
              };
          change_conn_ = channel_manager->AddChangeListener(topology_callback);
         
           }
	################ 	
	            void Recorder::TopologyCallback(const ChangeMsg& change_message) {
                   FindNewChannel(change_message.role_attr());
                 }
				 
	            void Recorder::FindNewChannel(const RoleAttributes& role_attr) {
				  writer_->WriteChannel(role_attr.channel_name(), role_attr.message_type(),
                         role_attr.proto_desc())
				  InitReaderImpl(role_attr.channel_name(), role_attr.message_type());}	
	#######################
                 	   bool Recorder::InitReaderImpl(const std::string& channel_name, const std::string& message_type)
					         ReaderCallback(raw_message, channel_name);
	################################
                        void Recorder::ReaderCallback(const std::shared_ptr<RawMessage>& message,
                          const std::string& channel_name)	
                            writer_->WriteMessage(channel_name, message, message_time_)	
    
	### void Recorder::ShowProgress() {
 Recorder 
 RecordWriter是用于在网络框架中记录消息的组件。
    每个RecordWriter可以通过Open方法创建一个新的记录文件。用户只需执行 WriteMessage 和 WriteChannel 即可编写消息和通道信息,并且编写过程是异步的	
        writer_->WriteChannel(role_attr.channel_name(), role_attr.message_type(),
                         role_attr.proto_desc())		
		writer_->WriteMessage(channel_name, message, message_time_))  
		  

		
2.定义的数据结构和文件结构以及操作
    01.record 结构   
        cyber/proto/record.proto 
        cyber/proto/proto_desc.proto
        cyber/record/file/section.h   只包含类型和长度 struct Section { proto::SectionType type;int64_t size; };
    02.record 读写
        cyber/record/file/record_file_base.h 
        cyber/record/file/record_file_reader
        cyber/record/file/record_file_writer
        cyber/record/[header_build record_base]
        cyber/record/record_reader     回放
        cyber/record/record_viewer      多通道回放 RecordViewer
        cyber/record/record_writer	    录制文件:按时间分段也可以按大小分段
    apollo_cc_library(
        name = "cyber_record",
        srcs = [
            "header_builder.cc",
            "record_reader.cc",
            "record_viewer.cc",
            "record_writer.cc",
            "file/record_file_base.cc",
            "file/record_file_reader.cc",
            "file/record_file_writer.cc",
        ],
        hdrs = [
            "header_builder.h",
            "record_base.h",
            "record_message.h",
            "record_reader.h",
            "record_viewer.h",
            "record_writer.h",
            "file/record_file_base.h",
            "file/record_file_reader.h",
            "file/record_file_writer.h",
            "file/section.h",
        ],
        deps = [
            "//cyber/common:cyber_common",
            "//cyber/proto:record_cc_proto",
            "//cyber/time:cyber_time",
            "@com_google_protobuf//:protobuf",
            "//cyber/message:cyber_message",
        ],
    )	    

命令行工具集

tools 
   cyber_node cyber_channel cyber_launch  cyber_monitor
   cyber_service  cyber_recorder
分为两类
    一类是通过python脚本	
        cyber/tools/cyber_channel/cyber_channel.py
        cyber/tools/cyber_launch/cyber_launch.py
        cyber/tools/cyber_node/cyber_node.py
        cyber/tools/cyber_performance/cyber_performance.py
        cyber/tools/cyber_service/cyber_service.py
            from cyber.python.cyber_py3 import cyber_time
            from cyber.python.cyber_py3 import cyber
            from cyber.proto.role_attributes_pb2 import RoleAttributes				
    一类是通过C++源码	
        cyber/tools/cyber_monitor
        cyber/tools/cyber_recorder
		   /recorder.cc	
自定义命令行  
    01.框架搭建和功能实现
	 
	 
    02.修改 tools 下的 BUILD 与 cyber_tools_auto_complete.bash 这两个配置文件	 
       BUILD 文件添加 cyber_param 的相关依赖
       cyber_tools_auto_complete.bash 文件需要添加如下内容
    03.修改 /apollo/cyber/setup.bash  setup.bash 中需要设置 cyber_param 相关参数 cyber/setup.bash
	    recorder_path="${cyber_tool_path}/cyber_recorder"
        launch_path="${cyber_tool_path}/cyber_launch"
		
		for entry in "${mainboard_path}" \
		 
	04. 编译
	其他--命令说明
      命令补全 任意的一个shell里面输入 com紧接着【tab】,这个是命令补全,只要命令在系统的shell的环境变量里,就可以补全,无需特别设置。	
             bash 提供的补全功能。默认的补全脚本保存在 /etc/bash_completion.d 目录下 		  
      命令参数补全 实现自动补全的功能,涉及到两个两个命令 complete 和 compgen
	          设置 complete 参数补全
			      complete -d 命令---设置命令的参数为 文件夹
				  complete -W "词组" 命令 ---设置命令的参数为词组
				  complete -F function  执行function函数,把函数中生成COMPREPLY作为候选的补全结果
		compgen(筛选命令) 这个命令,用来筛选生成 匹配单词的 候选补全结果 
		        -W wordlist  分割 wordlist 中的单词,生成候选补全列表
	    compopt(修改补全命令设置)这个命令可以修改补全命令设置,注意了,这个命令必须在补全函数中使用,否则会报错
              设置命令补全后不要多加空格  compopt -o nospace			

Python API

C++中开发程序并提供Python API通常涉及以下步骤:
	使用C++编写你的核心功能。
    使用Python的C API或者现代的方法,如Boost.Python、pybind11来创建Python可调用的封装。
 CyberRT是使用了 cyber_py3 中使用了ctypes importlib	,即Python的C API

 Python API有 cyber record 

如何使用API接口	
 Cyber RT Python API 
     from cyber.python.cyber_py3 import cyber
	 cyber.init()
	 if not cyber.ok():
	     pass 
	 cyber.shutdown()
如何定义python API接口
    cyber/python/cyber_py3/cyber.py
	  import ctypes
      import importlib
      from google.protobuf.descriptor_pb2 import FileDescriptorProto
cyber/python/cyber/python/cyber_py3
//cyber/python/internal:_cyber_wrapper.so"
//cyber/python/internal:_cyber_timer_wrapper.so
//cyber/python/internal:_cyber_parameter_wrapper.so
//cyber/python/internal:_cyber_record_wrapper.so

#include <Python.h>  自定义扩展类型  使用 ctypes 模块或 cffi 库
     Python API(应用程序编程接口)定义了一系列函数、宏和变量,可以访问 Python 运行时系统的大部分内容。
	 Python 的 API 可以通过在一个 C 源文件中引用 "Python.h" 头文件来使用。
cmake_minimum_required(VERSION 3.1)
project(LatLon2UTM)
include_directories(./include/)
SET(LIBLatLon2UTM_SRC src/LatLon2UTM.cpp)
# C++ 库
ADD_LIBRARY(LatLon2UTM_core SHARED ${LIBLatLon2UTM_SRC})
SET_TARGET_PROPERTIES(LatLon2UTM_core PROPERTIES OUTPUT_NAME "LatLon2utm_core")

record和Cyber_recorder

 cyber/python/cyber_py3
    from cyber.python.cyber_py3 import cyber
	from cyber.python.cyber_py3 import record

Cyber RT Python API


cyber/python/cyber_py3/record.py
    sys.path.append(wrapper_lib_path)
	_CYBER_RECORD = importlib.import_module('_cyber_record_wrapper')
	
	
	cyber_py3文件夹:python使用cyber通讯的底层类和示例文件
      internal文件夹:C++实现用以和pyton通讯的底层类
	   将c++生成so供python调用
 cyber/python/cyber_py3
    from cyber.python.cyber_py3 import cyber
	from cyber.python.cyber_py3 import record

Cyber RT Python API


cyber/python/cyber_py3/record.py
    sys.path.append(wrapper_lib_path)
	_CYBER_RECORD = importlib.import_module('_cyber_record_wrapper')
	
	
	cyber_py3文件夹:python使用cyber通讯的底层类和示例文件
      internal文件夹:C++实现用以和pyton通讯的底层类
	   将c++生成so供python调用
1.编译代码共享仓库
   .so 系列
   Python.h: No such file or directory
2.cyber_py3-对应的包-inportlib 共享库
      python imporlib.import_module
     linux中分析动态库so常用命令:strings、file、ldd、nm、objdump

3.应用方调用 cyber_py3 

拓扑和读写相关的类

    Node是整个数据拓扑网络中的基本单元。一个Node中可以创建多个读者/写者,服务端/客户端。
       读者和写者分别对应Reader和Writer,用于Publish-Subscribe模式。
	   服务端和客户端分别对应Service和Client,用于Service-Client模式
   NodeManager和ChannelManager派生自Manager,
      NodeManager实现node加入、离开等信息的交换,
	  ChannelManager实现channel加入、离开等信息的交换。
    Node/NodeChannelImpl基于NodeChannelManager管理节点信息,它也负责创建Reader/Writer
       Reader/Writer基于ChannelManager管理channel信息;   		   
     基础与核心的服务发现与拓扑管理--通信网络的拓扑管理
       节点间通过读和写端建立数据通路。
	  cyber/service_discovery/ 以channel为边,这样可以得到一个数据流图络。由于节点可能退出,订阅情况也可能发生改变,
	   所以这个网络是动态的。因此需要对网络拓扑进行监控。
    主要负责数据结构是TopologyManager
	  TopologyManager 基于Participant 有三个子管理器,并有共同的基类Manager。  它们分别为:
           NodeManager用于管理网络拓扑中的节点。
           ChannelManager用于管理channel,即网络拓扑中的边。
           ServiceManager用于管理Service和Client。
          基于Fast RTPS的发现机制 它主要监视网络中是否有参与者加入或退出
	
	基于主动式的拓扑变更广播 TopologyManager::Init()函数中创建和初始化。在初始化时,会调用它们的StartDiscovery()函数开始启动自动发现机制。
      TopologyManager::Init中主要干三件事,对node、channel、service这三个manager的初始化。
	  init中创建了三个manager,node_manager_ 、channel_manager_ 、service_manager_ ,
	  这三个manager都是继承自Manager。
	      并且三个manager的init函数中做的东西很简单,都是调用了基类的 Manager::StartDiscovery
              StartDiscovery 中做的处理就是创建出fastRtps的pub和sub,这里我们也可以总结出cyber内部的发现机制通信都是通过fastRtps
	    Dispose和Publish这两个。Dispose调用的是各个子类的方法,比如channel拓扑节点变化了,就会调用 ChannelManager::Dispose来通过channelManager的节点变化		 
			ChannelManager::Dispose  Dispose会根据这个节点是join还是leave来调用子类的DisposeJoin或者DisposeLeave方法,
			 目的就是更新各个子类manager中保存的节点关系
                       DisposeJoin()
                       DisposeLeave() Notify(msg)						   
					   Notify,里面用了QT信号和槽的机制,其实就是触发了一个callback。
					   这个callback是使用方调用TopologyManager中的AddChangeListener设置的一个callback
			Manager::Publish函数,这函数就是将join或者leave消息通过fastRtps消息发给所有subscribe,让所有sub都去更新拓扑节点关系
			        到sub拓扑节点变更消息后,触发 Manager::OnRemoteChange 再调用 Dispose 来更新自己保存的拓扑关系
 
 每个 Writer 有 Transmitter,
 每个 Reader 有 Receiver。它们是负责消息发送与收取的类。
 Transmitter 与 Receiver 的基类为 Endpoint,代表一个通信的端点,	
        其类型为RoleAttributes(定义在role_attributes.proto)的成员attr_包含了host name,process id和一个根据uuid产生的hash值作为id
            通过它们就可以判断节点之间的相对位置关系。			
 Reader 和Writer 会调用 
    Transport的方法CreateTransmitter()和CreateReceiver()用于创建发送端的transmitter和接收端的receiver。
	创建时有四种模式可选,分别是INTRA,SHM和RTPS,和HYBRID	

   消息 写端的实现相对简单一些。在模块组件中,可以通过 CreateWriter()函数创建 Writer 对象,然后就可以通过该对象向指定channel发送消息了
           std::shared_ptr<RecordWriter> writer_ = nullptr;
		   writer_->WriteChannel(role_attr.channel_name(), role_attr.message_type(),
                         role_attr.proto_desc()))

    消息读端 Reader对象是针对单个channel的。然后针对所有channel创建DataVisitor对象							 
		channel多于一个时(组合消息),DataVisitor中还有一个DataFusion对象用于将多路channel的数据合并	
   /apollo/cyber/service_discovery/topology_manager.cc中 AddChangeListener() 
          ddChangeListener就是将 topology_callback 和chanel_signal关联起来了
		  
 缓存消息通常指的是将消息或数据存储在内存中,以便在需要时快速访问,而不必每次都从磁盘或网络等较慢的存储介质中读取。
    缓存可以显著提高程序的性能,特别是在处理大量数据或频繁访问相同数据的场景中。

Node和Schdule的关系

    调用LoadModule函数初始化component		
	    Component内部会使用Schdule创建一个根据Node name命名的Task。
	Scheduler 会将 node 与 CRoutine 建立联系,然后与 Processor 也建立联系
	
	boost::circular_buffer 是一种固定大小的缓冲区,它可以在其内部元素超过其容量时自动循环

参考

  rosbag record代码分析 https://zhuanlan.zhihu.com/p/438698296	
  百度Apollo智能驾驶之CyberRT__自定义cyber_param命令行工具 https://blog.csdn.net/qq_22701545/article/details/124539300
   rosbag record代码分析 https://zhuanlan.zhihu.com/p/438698296
   https://apollo.baidu.com/docs/apollo/latest/classapollo_1_1cyber_1_1record_1_1Recorder.html
   自动驾驶Apollo源码分析系统,CyberRT篇(一):简述CyberRT框架基础概念 https://cloud.tencent.com/developer/article/1999073
   自动驾驶平台Apollo 5.5阅读手记:Cyber RT中的通信传输 https://blog.csdn.net/jinzhuojun/article/details/108066714
   Apollo cyber_recorder & cyber_monitor https://zhuanlan.zhihu.com/p/441131103
posted @ 2025-01-16 17:16  辰令  阅读(231)  评论(0)    收藏  举报