自动驾驶ros系统中protobuf使用详解
1 引言
由于原生ros_msg扩展性较差,与其他系统对接时消息协议可能存在不兼容的情况,因此选择google protobuf替换。
2 系统环境
实验在nvidia_agx_xavier机器上进行。软件环境如下:
操作系统 |
linux内核 |
SDK | ROS |
Protobuf | C++ |
ubuntu18.04 |
4.9.1 |
jetpack4.4.1 |
melodic | 3.5.1 |
11 |
其中的jetpack4.4.1、ros、protobuf等安装不是本文的重点,略过。
3 开始使用
3.1 ROS工程创建
(1)创建Ros根目录
mkdir -p xdwh/src
cd ..
catkin_make
(2) 创建Ros包
cd xdwh/src
catkin_create_pkg pb_msgs_example message_generation geometry_msgs roscpp rospy sensor_msgs std_msgs
(3) 创建launch和proto文件夹
cd xdwh/src/pb_msgs_example
mkdir launch
mkdir proto
3.2 编程
(1) CMakeLists.txt 编写
A. Protobuf库引入
set(PROTO_INCLUDEDIR /usr/local/include)
set(PROTO_LINK_LIBRARAY /usr/local/lib/libprotobuf.so)
find_package(catkin REQUIRED COMPONENTS
Protobuf REQUIRED
geometry_msgs
message_generation
roscpp
rospy
sensor_msgs
std_msgs
)
B. Proto文件编译
add_subdirectory(./pb_msgs_example/proto)
catkin_package(
INCLUDE_DIRS include
proto
)
FILE(GLOB proto_files *.proto)
FOREACH(proto_file ${proto_files})
# STRING(REGEX REPLACE "[^/]proto" "" proto_file_name ${proto_file})
get_filename_component(proto_file_name ${proto_file} NAME_WE)
LIST(APPEND PROTO_SRCS "${proto_file_name}.pb.cc")
ADD_CUSTOM_COMMAND(
OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/${proto_file_name}.pb.h" "${CMAKE_CURRENT_SOURCE_DIR}/${proto_file_name}.pb.cc"
COMMAND protoc --proto_path=${CMAKE_CURRENT_SOURCE_DIR}/
--cpp_out=${CMAKE_CURRENT_SOURCE_DIR}/ ${proto_file}
DEPENDS ${proto_file}
)
ENDFOREACH(proto_file)
C. Proto文件添加入库
file(GLOB_RECURSE LIBAPOLLO_SRC_proto proto/*.cc)
add_library(s_proto_files STATIC ${LIBAPOLLO_SRC_proto})
include_directories(
# include
${catkin_INCLUDE_DIRS}
${PROTO_INCLUDEDIR}
proto
)
add_executable(pb_talker src/talker.cpp)
target_link_libraries(pb_talker ${catkin_LIBRARIES} protobuf s_proto_files ${PROTO_LINK_LIBRARAY})
add_executable(pb_listener src/listener.cpp)
target_link_libraries(pb_listener ${catkin_LIBRARIES} protobuf s_proto_files ${PROTO_LINK_LIBRARAY} )
D. 完整配置
CMakeLists.txt
cmake_minimum_required(VERSION 3.0.2)
project(pb_msgs_example)
## Compile as C++11, supported in ROS Kinetic and newer
add_compile_options(-std=c++11)
add_subdirectory(proto)
# Find catkin macros and libraries
# if COMPONENTS list like find_package(catkin REQUIRED COMPONENTS xyz)
# is used, also find other catkin packages
add_subdirectory(/home/xdwh/Workspace/xdwh_ros/src/xdwh_message/pb_msgs_example/proto)
set(PROTO_INCLUDEDIR /usr/local/include)
set(PROTO_LINK_LIBRARAY /usr/local/lib/libprotobuf.so)
find_package(catkin REQUIRED COMPONENTS
Protobuf REQUIRED
geometry_msgs
message_generation
roscpp
rospy
sensor_msgs
std_msgs
)
## System dependencies are found with CMake's conventions
find_package(Boost REQUIRED COMPONENTS system)
## Uncomment this if the package has a setup.py. This macro ensures
# modules and global scripts declared therein get installed
# See http://ros.org/doc/api/catkin/html/user_guide/setup_dot_py.html
catkin_python_setup()
################################################
# Declare ROS messages, services and actions ##
###############################################
## To declare and build messages, services or actions from within this
# package, follow these steps:
# * Let MSG_DEP_SET be the set of packages whose message types you use in
# your messages/services/actions (e.g. std_msgs, actionlib_msgs, ...).
# * In the file package.xml:
# * add a build_depend tag for "message_generation"
# * add a build_depend and a exec_depend tag for each package in MSG_DEP_SET
# * If MSG_DEP_SET isn't empty the following dependency has been pulled in
# but can be declared for certainty nonetheless:
# * add a exec_depend tag for "message_runtime"
# * In this file (CMakeLists.txt):
# * add "message_generation" and every package in MSG_DEP_SET to
# find_package(catkin REQUIRED COMPONENTS ...)
# * add "message_runtime" and every package in MSG_DEP_SET to
# catkin_package(CATKIN_DEPENDS ...)
# * uncomment the add_*_files sections below as needed
# and list every .msg/.srv/.action file to be processed
# * uncomment the generate_messages entry below
# * add every package in MSG_DEP_SET to generate_messages(DEPENDENCIES ...)
# add_proto_files(
DIRECTORY proto
FILES chatter.proto
)
generate_messages(DEPENDENCIES std_msgs)
# Generate messages in the 'msg' folder
add_message_files(
FILES
Message1.msg
Message2.msg
)
## Generate services in the 'srv' folder
add_service_files(
FILES
Service1.srv
Service2.srv
)
## Generate actions in the 'action' folder
add_action_files(
FILES
Action1.action
Action2.action
)
## Generate added messages and services with any dependencies listed here
generate_messages(
DEPENDENCIES
# sensor_msgs# std_msgs geometry_msgs
)
################################################
# Declare ROS dynamic reconfigure parameters ##
###############################################
## To declare and build dynamic reconfigure parameters within this
# package, follow these steps:
# * In the file package.xml:
# * add a build_depend and a exec_depend tag for "dynamic_reconfigure"
# * In this file (CMakeLists.txt):
# * add "dynamic_reconfigure" to
# find_package(catkin REQUIRED COMPONENTS ...)
# * uncomment the "generate_dynamic_reconfigure_options" section below
# and list every .cfg file to be processed
## Generate dynamic reconfigure parameters in the 'cfg' folder
generate_dynamic_reconfigure_options(
cfg/DynReconf1.cfg
cfg/DynReconf2.cfg
)
###################################
# catkin specific configuration ##
##################################
# The catkin_package macro generates cmake config files for your package
# Declare things to be passed to dependent projects
# INCLUDE_DIRS: uncomment this if your package contains header files
# LIBRARIES: libraries you create in this project that dependent projects also need
# CATKIN_DEPENDS: catkin_packages dependent projects also need
# DEPENDS: system dependencies of this project that dependent projects also need
catkin_package(
INCLUDE_DIRS include
proto
)
###########
# Build ##
##########
set(PROTO_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/proto)
${CMAKE_CURRENT_SOURCE_DIR}/proto/*.proto) FILE(GLOB proto_files
${proto_files}) FOREACH(proto_file
"[^/]proto" "" proto_file_name ${proto_file}) STRING(REGEX REPLACE
"${proto_file_name}.pb.cc") LIST(APPEND PROTO_SRCS
ADD_CUSTOM_COMMAND(
"${proto_file_name}.pb.h" "${proto_file_name}.pb.cc" OUTPUT
${CMAKE_CURRENT_SOURCE_DIR}/proto COMMAND protoc --proto_path=
${CMAKE_CURRENT_SOURCE_DIR}/proto/ ${proto_file} --cpp_out=
${proto_file} DEPENDS
)
ENDFOREACH(proto_file)
message("come into sub --------------------------------------------------------------- ")
FILE(GLOB proto_files *.proto)
FOREACH(proto_file ${proto_files})
# STRING(REGEX REPLACE "[^/]proto" "" proto_file_name ${proto_file})
get_filename_component(proto_file_name ${proto_file} NAME_WE)
LIST(APPEND PROTO_SRCS "${proto_file_name}.pb.cc")
ADD_CUSTOM_COMMAND(
OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/${proto_file_name}.pb.h" "${CMAKE_CURRENT_SOURCE_DIR}/${proto_file_name}.pb.cc"
COMMAND protoc --proto_path=${CMAKE_CURRENT_SOURCE_DIR}/
--cpp_out=${CMAKE_CURRENT_SOURCE_DIR}/ ${proto_file}
DEPENDS ${proto_file}
)
ENDFOREACH(proto_file)
file(GLOB_RECURSE LIBAPOLLO_SRC_proto proto/*.cc)
add_library(s_proto_files STATIC ${LIBAPOLLO_SRC_proto})
## Specify additional locations of header files
# Your package locations should be listed before other locations
include_directories(
include
{catkin_INCLUDE_DIRS}
{PROTO_INCLUDEDIR}
proto
)
add_executable(pb_talker src/talker.cpp)
target_link_libraries(pb_talker ${catkin_LIBRARIES} protobuf s_proto_files ${PROTO_LINK_LIBRARAY})
add_executable(pb_listener src/listener.cpp)
target_link_libraries(pb_listener ${catkin_LIBRARIES} protobuf s_proto_files ${PROTO_LINK_LIBRARAY} )
## Declare a C++ library
${PROJECT_NAME} add_library(
${PROJECT_NAME}/pb_msgs_example.cpp src/
)
## Add cmake target dependencies of the library
# as an example, code may need to be generated before libraries
# either from message generation or dynamic reconfigure
${PROJECT_NAME} ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS}) add_dependencies(
## Declare a C++ executable
# With catkin_make all packages are built within a single CMake context
# The recommended prefix ensures that target names across packages don't collide
${PROJECT_NAME}_node src/pb_msgs_example_node.cpp) add_executable(
## Rename C++ executable without prefix
# The above recommended prefix causes long target names, the following renames the
# target back to the shorter version for ease of user use
# e.g. "rosrun someones_pkg node" instead of "rosrun someones_pkg someones_pkg_node"
${PROJECT_NAME}_node PROPERTIES OUTPUT_NAME node PREFIX "") set_target_properties(
## Add cmake target dependencies of the executable
# same as for the library above
${PROJECT_NAME}_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS}) add_dependencies(
## Specify libraries to link a library or executable target against
${PROJECT_NAME}_node target_link_libraries(
${catkin_LIBRARIES}
)
#############
# Install ##
############
# all install targets should use catkin DESTINATION variables
See http://ros.org/doc/api/catkin/html/adv_user_guide/variables.html
## Mark executable scripts (Python etc.) for installation
# in contrast to setup.py, you can choose the destination
catkin_install_python(PROGRAMS
scripts/my_python_script
${CATKIN_PACKAGE_BIN_DESTINATION} DESTINATION
)
## Mark executables for installation
# See http://docs.ros.org/melodic/api/catkin/html/howto/format1/building_executables.html
${PROJECT_NAME}_node install(TARGETS
${CATKIN_PACKAGE_BIN_DESTINATION} RUNTIME DESTINATION
)
## Mark libraries for installation
# See http://docs.ros.org/melodic/api/catkin/html/howto/format1/building_libraries.html
${PROJECT_NAME} install(TARGETS
${CATKIN_PACKAGE_LIB_DESTINATION} ARCHIVE DESTINATION
${CATKIN_PACKAGE_LIB_DESTINATION} LIBRARY DESTINATION
${CATKIN_GLOBAL_BIN_DESTINATION} RUNTIME DESTINATION
)
## Mark cpp header files for installation
${PROJECT_NAME}/ install(DIRECTORY include/
${CATKIN_PACKAGE_INCLUDE_DESTINATION} DESTINATION
"*.h" FILES_MATCHING PATTERN
".svn" EXCLUDE PATTERN
)
## Mark other files for installation (e.g. launch and bag files, etc.)
install(FILES
# myfile1
# myfile2
${CATKIN_PACKAGE_SHARE_DESTINATION} DESTINATION
)
#############
# Testing ##
############
## Add gtest based cpp test target and link libraries
${PROJECT_NAME}-test test/test_pb_msgs_example.cpp) catkin_add_gtest(
if(TARGET ${PROJECT_NAME}-test)
${PROJECT_NAME}-test ${PROJECT_NAME}) target_link_libraries(
endif()
# Add folders to be run by python nosetests
test) catkin_add_nosetests(
(2)Proto文件定义
Proto文件目录:./pb_msgs_example/proto
以imu.proto为例,内容如下:
imu.proto:
syntax = "proto2";
package localization;
import "header.proto";
message Imu {
optional common.Header header = 1; // 报文头
optional float yaw_rate =2; // 绕车身Z轴的角速度(偏航角),弧度/秒
optional float pitch_rate =3; // 绕车身Y轴的角速度(俯仰角),弧度/秒
optional float roll_rate =4; // 绕车身X轴的角速度(横滚角),弧度/秒
optional float accel_x = 5; // 沿着车身X轴的加速度,米/秒^2
optional float accel_y = 6; // 沿着车身Y轴的加速度,米/秒^2
optional float accel_z = 7; // 沿着车身Z轴的加速度,米/秒^2
/**
* 使用车身坐标系:
* 车身纵轴向前为X轴正方向,车身横轴向左为Y轴正方向,车身向上为Z轴正方向;
* 角度方向逆时针为正.
*/
}
(3)发布imu.proto消息
ros代码 talker.cpp:
int main(int argc, char** argv)
{
ros::init(argc, argv, "talker");
ros::NodeHandle n;
ros::Publisher imu_pub = n.advertise<std_msgs::ByteMultiArray>("localization/imu", 1000);
ros::Rate loop_rate(10);
int count = 0;
while (ros::ok())
{
// pb_msgs::ShortMessage pb_msg;
ros::Time now = ros::Time::now();
ROS_INFO("count_%d \n",count);
std_msgs::ByteMultiArray proto_array;
localization::Imu imu;
common::Header *p_header = imu.mutable_header();
p_header->set_valid(1);
p_header->set_sequence(2);
p_header->set_timestamp(3);
p_header->set_src_module_id(4);
p_header->set_dst_module_id(5);
imu.set_yaw_rate(2);
imu.set_pitch_rate(3);
imu.set_roll_rate(4);
imu.set_accel_x(5);
imu.set_accel_y(6);
imu.set_accel_z(7);
int proto_size = imu.ByteSize();
proto_array.data.resize(proto_size);
bool is_ok = imu.SerializePartialToArray(proto_array.data.data(), proto_size);
if (!is_ok)
{
ROS_ERROR("Failed to serialize the proto of gnss_proto.");
}
else
{
// ROS_INFO("===========public proto");
imu_pub.publish(proto_array);
}
ROS_INFO("publish_%d \n",count);
ros::spinOnce();
loop_rate.sleep();
++count;
}
return 0;
}
(4)订阅imu.proto消息
listener.cpp:
struct AdImu {
/// 消息头
common::Header msg_head;
/// 航向角的角速度
float yaw_rate;
/// 俯仰角的角速度
float pitch_rate;
/// 横滚角的角速度
float roll_rate;
/// 沿着车身x轴的加速度
float accel_x;
/// 沿着车身y轴的加速度
float accel_y;
/// 沿着车身z轴的加速度
float accel_z;
/**
* @brief 清除内部数据
*/
void Clear() {
msg_head.Clear();
yaw_rate = 0;
pitch_rate = 0;
roll_rate = 0;
accel_x = 0;
accel_y = 0;
accel_z = 0;
}
/**
* @brief 构造函数
*/
AdImu() {
Clear();
}
};
bool DecodeImuMessage(
const char* msg, int msg_len, AdImu* data_out) {
// parse
localization::Imu imu;
if (!imu.ParseFromArray(msg, msg_len)) {
// LOG_ERR << "Failed to parse imu from array.";
return false;
}
data_out->yaw_rate = imu.yaw_rate();
data_out->pitch_rate = imu.pitch_rate();
data_out->roll_rate = imu.roll_rate();
data_out->accel_x = imu.accel_x();
data_out->accel_y = imu.accel_y();
data_out->accel_z = imu.accel_z();
return true;
}
void imuCallback(const std_msgs::ByteMultiArray& msg)
{
// ROS_INFO("I get counter message: [%d]", (int)msg->count());
if (msg.data.size() > 0)
{
AdImu adimu ;
if (DecodeImuMessage((const char*)(&msg.data[0]), msg.data.size(),&adimu))
{
ROS_INFO("yaw_rate: [%f]", adimu.yaw_rate);
ROS_INFO("pitch_rate: [%f]", adimu.pitch_rate);
ROS_INFO("roll_rate: [%f]", adimu.roll_rate);
ROS_INFO("accel_x: [%f]", adimu.accel_x);
ROS_INFO("accel_y: [%f]", adimu.accel_y);
ROS_INFO("accel_z: [%f]", adimu.accel_z);
}
}
}
int main(int argc, char** argv)
{
ros::init(argc, argv, "listener");
ros::NodeHandle n;
ros::Subscriber sub = n.subscribe("localization/imu", 1000, imuCallback);
ros::spin();
return 0;
}
3.3编译测试
(1) catkin编译
catkin_make -DPYTHON_EXECUTABLE=/usr/bin/python3
(2)运行测试
roslaunch ./launch/talker.launch
roslaunch ./launch/listener.launch
3.4 总结
(1)项目目录结构如下
(2)使用protobuf主要问题是定义proto文件、生成c++源文件及如何解析。
(3)后续细节待补充。