vlambda博客
学习文章列表

自动驾驶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 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 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# geometry_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' 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 needcatkin_package( INCLUDE_DIRS include  proto)
############# 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++ library# add_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 reconfigure# add_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 collide# add_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 above# add_dependencies(${PROJECT_NAME}_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
## Specify libraries to link a library or executable target against# target_link_libraries(${PROJECT_NAME}_node# ${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# DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}# )
## Mark executables for installation## See http://docs.ros.org/melodic/api/catkin/html/howto/format1/building_executables.html# install(TARGETS ${PROJECT_NAME}_node# RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}# )
## Mark libraries for installation## See http://docs.ros.org/melodic/api/catkin/html/howto/format1/building_libraries.html# install(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 installation# install(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# # myfile2# DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}# )
############### Testing ###############
## Add gtest based cpp test target and link libraries# catkin_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 nosetests# catkin_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轴的加速度,米/秒^2 optional float accel_y = 6; // 沿着车身Y轴的加速度,米/秒^2 optional float accel_z = 7; // 沿着车身Z轴的加速度,米/秒^2 /** * 使用车身坐标系: * 车身纵轴向前为X轴正方向,车身横轴向左为Y轴正方向,车身向上为Z轴正方向; * 角度方向逆时针为正. */}


(3)发布imu.proto消息

ros代码 talker.cpp:

#include "ros/ros.h"#include <std_msgs/String.h>#include <std_msgs/ByteMultiArray.h>#include <sstream>#include "header.pb.h"#include "imu.pb.h"
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:

#include "ros/ros.h"#include "chatter.pb.h"#include "header.pb.h"#include "imu.pb.h"#include <std_msgs/String.h>#include <std_msgs/ByteMultiArray.h>#include <string> 
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)后续细节待补充。