自动驾驶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 neweradd_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 packagesadd_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 COMPONENTSProtobuf REQUIREDgeometry_msgsmessage_generationroscpprospysensor_msgsstd_msgs)## System dependencies are found with CMake's conventionsfind_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.htmlcatkin_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 protoFILES chatter.proto)generate_messages(DEPENDENCIES std_msgs)# Generate messages in the 'msg' folderadd_message_files(FILESMessage1.msgMessage2.msg)## Generate services in the 'srv' folderadd_service_files(FILESService1.srvService2.srv)## Generate actions in the 'action' folderadd_action_files(FILESAction1.actionAction2.action)## Generate added messages and services with any dependencies listed heregenerate_messages(DEPENDENCIESgeometry_msgs# sensor_msgs# std_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' foldergenerate_dynamic_reconfigure_options(cfg/DynReconf1.cfgcfg/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 needcatkin_package(INCLUDE_DIRS includeproto)############ Build ############set(PROTO_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/proto)FILE(GLOB proto_files ${CMAKE_CURRENT_SOURCE_DIR}/proto/*.proto)FOREACH(proto_file ${proto_files})STRING(REGEX REPLACE "[^/]proto" "" proto_file_name ${proto_file})LIST(APPEND PROTO_SRCS "${proto_file_name}.pb.cc")ADD_CUSTOM_COMMAND(OUTPUT "${proto_file_name}.pb.h" "${proto_file_name}.pb.cc"COMMAND protoc --proto_path=${CMAKE_CURRENT_SOURCE_DIR}/proto--cpp_out=${CMAKE_CURRENT_SOURCE_DIR}/proto/ ${proto_file}DEPENDS ${proto_file})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 locationsinclude_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++ libraryadd_library(${PROJECT_NAME}src/${PROJECT_NAME}/pb_msgs_example.cpp)## Add cmake target dependencies of the library# as an example, code may need to be generated before libraries# either from message generation or dynamic reconfigureadd_dependencies(${PROJECT_NAME} ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})## 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 collideadd_executable(${PROJECT_NAME}_node src/pb_msgs_example_node.cpp)## 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"set_target_properties(${PROJECT_NAME}_node PROPERTIES OUTPUT_NAME node PREFIX "")## Add cmake target dependencies of the executable# same as for the library aboveadd_dependencies(${PROJECT_NAME}_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})## Specify libraries to link a library or executable target againsttarget_link_libraries(${PROJECT_NAME}_node${catkin_LIBRARIES})############## Install ############### all install targets should use catkin DESTINATION variablesSee 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 destinationcatkin_install_python(PROGRAMSscripts/my_python_scriptDESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION})## Mark executables for installation# See http://docs.ros.org/melodic/api/catkin/html/howto/format1/building_executables.htmlinstall(TARGETS ${PROJECT_NAME}_nodeRUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION})## Mark libraries for installation# See http://docs.ros.org/melodic/api/catkin/html/howto/format1/building_libraries.htmlinstall(TARGETS ${PROJECT_NAME}ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}RUNTIME DESTINATION ${CATKIN_GLOBAL_BIN_DESTINATION})## Mark cpp header files for installationinstall(DIRECTORY include/${PROJECT_NAME}/DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION}FILES_MATCHING PATTERN "*.h"PATTERN ".svn" EXCLUDE)## Mark other files for installation (e.g. launch and bag files, etc.)install(FILES# myfile1# myfile2DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION})############## Testing ################ Add gtest based cpp test target and link librariescatkin_add_gtest(${PROJECT_NAME}-test test/test_pb_msgs_example.cpp)if(TARGET ${PROJECT_NAME}-test)target_link_libraries(${PROJECT_NAME}-test ${PROJECT_NAME})endif()# Add folders to be run by python nosetestscatkin_add_nosetests(test)
(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轴的加速度,米/秒^2optional float accel_y = 6; // 沿着车身Y轴的加速度,米/秒^2optional 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) {// parselocalization::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)后续细节待补充。
