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