Chapter 2: Analysis and Practice of Cyber RT Communication Mechanism

Cyber ​​RT analysis and practice

Chapter 2: Analysis and Practice of Cyber ​​RT Communication Mechanism



1. Introduction to Cyber ​​RT Communication Mechanism

1. Topics

insert image description here
We want to obtain the speed of the car all the time. This requirement does not require any message to be returned to the sender, nor does the sender need to further process the message. So we chose the Listener-Talker communication method to realize this function.
In Listener-Talker communication, one party actively sends messages, while the other party passively receives them. As shown in Figure 2-1, the Listener-Talker communication first creates two Nodes, namely the Talker Node and the Listener Node. Each Node instantiates the Writer class and the Reader class to read and write messages to the Channel. Writer and Reader are connected through Topic to read and write to the same shared memory (Channel). Here, in order to realize its "telling" function, Talker Node chooses to instantiate Writer, and writes messages to Channel through Writer
. In order to realize its "listening" function, the listener Node chooses to instantiate the reader class, and reads the channel through the Reader. Here, the definition of the data format used in the communication car message is defined in car.proto. This communication method is suitable for continuous communication application scenarios, such as the transmission of data such as radar signals and camera image information.

2. Service

insert image description here
We want to get the detailed information of the car, such as the license plate, but we don’t need to get the information all the time. We just want to request it when we need to know the information, so we consider using Server-Client communication to realize this Function. As shown in Figure 2-1, in Server-Client communication, when the client sends a message request, the server responds to the request and returns the data required by the client to the client. This communication mode is suitable for temporary message transmission and for scenarios that do not require continuous data transmission. The data definition it transmits is still in the corresponding proto file.

3. Parameters

insert image description here
There are some parameters such as the maximum speed limit of the vehicle, the maximum number of passengers, and whether to drive automatically, etc., which need to be used by each module. For example, whether to drive automatically or not may affect many modules at the same time, and may also be used by many modules. changed at runtime. We hope to have a method similar to "global variables" to store these parameters and define some custom parameters for use. A global parameter server is designed in Cyber ​​to realize this function, and its communication is still based on the RTPS protocol, as shown in Figure 2-8. Both the server and client of this communication method can set parameters and change parameters.

2. Data communication foundation Protobuf

1. Introduction to Protobuf

Protobuf is a cross-language and platform serialized data structure developed by Google. It is a flexible and efficient protocol for serialized data. Compared with XML and JSON formats, Protobuf is smaller, faster, and more convenient.
Protobuf is cross-language and comes with a compiler (protoc). You only need to compile it with protoc to compile it into Java, Python, C++, C#, Go and other language codes, and then you can use it directly without further Write other code, comes with parsing code. You only need to define the structured data to be serialized once (defined in the .proto file), and you can use the specially generated source code (using the generation tool provided by protobuf) to easily use different data streams to complete the reading and writing of the structured data operate. It is even possible to update the definition of data structures in the .proto file without breaking programs compiled against the old format.
Its advantages are as follows:

  • High performance efficiency: After serialization, the space occupied by bytes is 3-10 times less than that of XML, and the time efficiency of serialization is 20-100 times faster than that of XML.
  • Convenient and convenient to use: the operation of structured data is encapsulated into a class, which is easy to use.
  • High compatibility: Both communication parties use the same data protocol. When one party modifies the data structure, it will not affect the use of the other party.
  • Cross-language: Support Java, C++, Python, Go, Ruby and other languages.

2. Protobuf creation

For the convenience of explanation, use cyber/examples/proto/examples.protoa file to explain, syntaxwhich means the version of Protobuf is used. Currently Protobuf supports proto3, but proto2 is used in Apollo; package indicates the path of the file; each message indicates a data structure, followed by message is the name of the data structure, and the format of the field definition in brackets is: field rule data type field name field number.
There are three main types of field rules:

  • required: The value of this field must be provided when calling, otherwise the message is considered "uninitialized", which is not officially recommended, and there will be compatibility issues when changing the field rule to other rules.
  • optional: The value of this field can be set or not set, and a default value will be generated according to the data type.
  • repeated: Similar to a dynamic array, it can store multiple data of the same type.
# examples.proto
syntax = "proto2";
package apollo.cyber.examples.proto;
message SamplesTest1 {
    
    
  optional string class_name = 1;
  optional string case_name = 2;
};
message Chatter {
    
    
  optional uint64 timestamp = 1;
  optional uint64 lidar_timestamp = 2;
  optional uint64 seq = 3;
  optional bytes content = 4;
};
message Driver {
    
    
  optional string content = 1;
  optional uint64 msg_id = 2;
  optional uint64 timestamp = 3;
};

3. Protobuf compilation

The compilation of Protobuf is divided into two steps. First, the proto library is generated according to the .proto file, and then the C++ related source files are generated according to the produced proto library. This source file is automatically written in C++ language and can be automatically recognized by C++ programs. Each message will be parsed to generate a class, and the fields in it are equivalent to the attributes of this class. Additional members are also generated in source files based on properties, such as functions to get and set properties.

package(default_visibility = ["//visibility:public"])
#1、生成proto库
proto_library(
    name = "examples_proto",
    srcs = ["examples.proto"],
)
#2、生成源文件
cc_proto_library(
    name = "examples_cc_proto",
    deps = [
        ":examples_proto",
    ],
)

4. Protobuf case combat

Purpose : Use Protobuf to define the data format, set the data value in the main program and output it.
Process : 1. Create a directory and write related files; 2. Compile and execute.
The content is as follows:

1. Create the test function package structure:

test
|-- test_proto
    |-- BUILD
    |-- car.cc
|-- proto
    |-- BUILD
    |-- car_msg.proto

The contents of each structure are as follows:

2. test_proto/BUILD

load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library")
load("//tools/install:install.bzl", "install", "install_src_files")
load("//tools:cpplint.bzl", "cpplint")

package(default_visibility = ["//visibility:public"])

cc_binary(
    name = "car",
    srcs = ["car.cc"],
    deps = ["//test/proto:car_msg_cc_proto"], 
)

install(
    name = "install",
    runtime_dest = "test/bin",
    targets = [
        ":car"
    ],
)

install_src_files(
    name = "install_src",
    src_dir = ["."],
    dest = "test/src/cyberatest",
    filter = "*",
)

Code analysis:
This file is a Bazel build file, which describes how to build a binary executable file named "car".

  • First, several load statements are used in the code to load Bazel's built-in rules and custom rules. These rules are contained in different Bazel files and referenced by file paths. In this code, rules such as cc_binary, cc_library, install and install_src_files are loaded.
  • Next, the package statement is used to set the default visibility (visibility). Here, the visibility of the package is set to public, which means that all targets in the package can be accessed by other packages.
  • Then, the cc_binary rule is used to define a binary executable named "car". The executable's source code file is "car.cc", and the dependent library is //test/proto:car_msg_cc_proto, which means that the library needs to be built and linked before the executable can be built.
  • Next, the install rule is used to define an installation target named "install", which is used to install the built files into the specified runtime directory. Here the target is set to ":car", which means to install the built "car" target. The installation location is "test/bin".
  • Finally, the install_src_files rule is used to define an install source files target named "install_src". The purpose of this goal is to install the source code files in the current directory to the specified directory. Here, the source file directory is set as the current directory, the installation target path is set as "test/src/cyberatest", and the filter parameter specifies that only files matching the wildcard "*" will be installed.
    In general, this build file describes how to build and install a binary executable named "car" into the specified directory, and also install the source code files into the specified directory.

3. test_proto/car.cc

#include "test/proto/car_msg.pb.h"
using namespace std;

int main()
{
    
    
    apollo::cyber::test::proto::CarMsg car;

    car.set_owner("apollo");
    car.set_license_plate("京A88888");
    car.set_max_passenger(6);
    car.add_car_info("SUV"); //车型
    car.add_car_info("Red"); //车身颜色
    car.add_car_info("electric"); //电动

    string owner = car.owner();
    string license_plate = car.license_plate();
    uint64_t max_passenger = car.max_passenger();
    
    cout << "owner:" << owner << endl;
    cout << "license_plate:" << license_plate << endl;
    cout << "max_passenger:" << max_passenger << endl;

    for (int i = 0; i < car.car_info_size(); ++i){
    
    
        string info = car.car_info(i);
        cout << info << " ";
    }
    cout << endl;
    return 0;
}

4. proto/BUILD

load("@rules_proto//proto:defs.bzl", "proto_library")
load("@rules_cc//cc:defs.bzl", "cc_proto_library")
load("//tools:python_rules.bzl", "py_proto_library")

package(default_visibility = ["//visibility:public"])

proto_library(
    name = "car_msg_proto",
    srcs = ["car_msg.proto"],
)

cc_proto_library(
    name = "car_msg_cc_proto",
    deps = [":car_msg_proto"],
)

5. proto/car_msg.proto

syntax = "proto2";

package apollo.cyber.test.proto;

message CarMsg {
    
    
    required string owner = 1;
    optional string license_plate = 2;
    optional uint64 max_passenger = 3;
    repeated string car_info = 4;
}

Code analysis:

  • In this message definition, 1, 2, 3, and 4 are field identifiers. They are used to uniquely identify each field and are matched during serialization and deserialization.
  • For each field, the prefix of the identifier is used to indicate the type of the field (required, optional, repeated), and the following number is used to identify the position of the field in the message. Here, the identifier of the "owner" field is 1, the identifier of the "license_plate" field is 2, the identifier of the "max_passenger" field is 3, and the identifier of the "car_info" field is 4.
  • These identifiers are to ensure message consistency during serialization and deserialization, and they are unique within the message protocol.

6. BUILD of the data package

Code analysis:

  • This is part of a BUILD file that uses the install and install_src_files rules of the Bazel build system.
  • First, the install and install_src_files rules in the "//tools/install:install.bzl" file are loaded through the load function.
  • Then, an install rule named "install" is defined by the install rule. It specifies a list of files to install, including "test.BUILD" and "cyberfile.xml". It also specifies dependencies, which include the "//test/test_proto:install" rule.
  • Next, a source file installation rule called "install_src" is defined via the install_src_files rule. It specifies the directory of the source file (src_dir), in this case the current directory ("."). It also specifies the target directory (dest) of the installation, in this case "test/src". The filter parameter specifies the wildcard filter condition of the source files to be installed, where "*" means all files. It also specifies dependencies, which include the "//test/test_proto:install_src" rule.

7. cyberfile.xml

Code analysis:
This XML code looks like a manifest file describing the software package, which contains various information about the software package.

  • The front part contains: package type, source code path, license, author and other basic information;
  • The first tag, its type is "binary", the source code path is "//cyber", the warehouse name is "cyber", and the dependent package name is "cyber-dev".
  • The second tag does not specify a type, only the dependent package name "bazel-extend-tools-dev".
  • The third tag does not specify the type, but the lib_names attribute specifies that the dependent library name is "protobuf", the warehouse name is "com_google_protobuf", and the dependent package name is "3rd-protobuf-dev".
  • The : tag is used to specify the build tool used when building the package, here is "bazel".

6. Compile and run

The code can be compiled and run here:

// 编译
cd /apollo_workspace
buildtool build -p test

// 执行
cd /apollo_workspace/bazel-bin/test/test_proto
./car

The running results are shown in the figure below:
insert image description here
Owner: apollo
License plate number: Beijing A88888
Maximum number of passengers: 6
SUV red electric


3. Practice cases of Cyber ​​RT topic communication

1. Listener and Talker communication principle

In the first chapter, we have explained the basic concepts of Cyber ​​such as Node and Channel. In this chapter, based on these basic concepts, we further introduce the three communication methods in Cyber, that is, the three ways of sending and receiving information.
In order to better illustrate the usage of these three communication mechanisms, in this chapter we illustrate this part by implementing a case.
The case is as follows, assuming that we have our own "unmanned vehicle", Apollo number, now we want to obtain the vehicle's "real-time" speed, detailed information of the vehicle and general parameters of the vehicle through communication. So now there are a few questions:

  • What are the variables of the car itself, and where should they be defined?
  • What communication method should be used to obtain the "real-time" vehicle speed and realize the detailed information of the vehicle? Where is it implemented?
  • How should the implemented code run?
    Don't worry, we will start to implement this small demo with Cyber ​​and answer the above questions one by one.
    In this case, we created it under the /apollo/workspace/test file and named it communication. The overall directory structure is as follows:

Directory Structure:

/apollo_workspace/
  |--test
  |   |--communication
  |   |  |--BUILD //cyber_test编译文件
      |  |--talker.cc //talker-listener通信实现
      |  |--listener.cc
      |  |--server.cc //server-client通信实现
      |  |--client.cc
      |  |--param_server.cc //parameter server-client通信实现
      |  |--param_client.cc 
      |--proto 
         |--BUILD //car.proto 编译文件
         |--car.proto  //小车数据定义的文件

Through the content of the first section, we know how to use the proto file. In this chapter, we will write a proto file to realize the variable definition of our "car". In the cases of the three communication methods in this chapter, we use This data definition. Write the car.proto file, its content is as follows:

2. Communication implementation process explanation

3. Write C++ topic communication implementation

1. Proto file writing

First of all, we need to write our own proto file through the content of the first section to realize the variable definition of our "car". This data definition is used in the cases of the three communication methods in this section. Write the car.proto file, its content is as follows:

car.proto

// 定义proto使用的版本
syntax = "proto2";

//定义包名,在cc文件中调用
package apollo.cyber.test.proto;

//定义一个车的消息,车的型号,车主,车的车牌号,已跑公里数,车速
message Car{
    
    
    optional string plate = 1; 
    optional string type = 2;
    optional string owner = 3;
    optional uint64 kilometers = 4;
    optional uint64 speed = 5;
};

2. Write talker.cc file

Let's write a talker.cc to implement active dialogue

talker.cc

//头文件引用
#include "test/proto/car.pb.h"
#include "cyber/cyber.h"
#include "cyber/time/rate.h"

//car数据定义的引用,可以看出其定义来源于一个proto
using apollo::cyber::examples::cyber_test_proto::Car;

int main(int argc, char *argv[]) {
    
    
  // 初始化一个cyber框架
    apollo::cyber::Init(argv[0]);
  
  // 创建talker节点
    auto talker_node = apollo::cyber::CreateNode("talker");
    
    // 从节点创建一个Topic,来实现对车速的查看
    auto talker = talker_node->CreateWriter<Car>("car_speed");
    AINFO << "I'll start telling you the current speed of the car.";
    
    //设置初始速度为0,然后速度每秒增加5km/h
    uint64_t speed = 0;
    while (apollo::cyber::OK()) {
    
    
        auto msg = std::make_shared<Car>();
        msg->set_speed(speed);
        //假设车速持续增加
        speed += 5;
        talker->Write(msg);
        sleep(1);
    }
    return 0;
}

3. Listener.cc file writing

Write a listener to receive the content sent by the talker.

listener.cc

#include "test/proto/car.pb.h"
#include "cyber/cyber.h"

using apollo::cyber::examples::cyber_test_proto::Car;

//接收到消息后的响应函数
void message_callback(
        const std::shared_ptr<Car>& msg) {
    
    
    AINFO << "now speed is: " << msg->speed();
}

int main(int argc, char* argv[]) {
    
    
    //初始化cyber框架
    apollo::cyber::Init(argv[0]);
 
    //创建监听节点
    auto listener_node = apollo::cyber::CreateNode("listener");
   
    //创建监听响应进行消息读取
    auto listener = listener_node->CreateReader<Car>(
            "car_speed", message_callback);
    apollo::cyber::WaitForShutdown();
    return 0;
}

4. Run and test

1. Write bazel compilation file

Written in cyber/examples/cyber_test/BUILD.

talker and listener compile files.

load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library")
load("//tools/install:install.bzl", "install", "install_src_files")
load("//tools:cpplint.bzl", "cpplint")

package(default_visibility = ["//visibility:public"])

cc_binary(
    name = "talker",
    srcs = ["talker.cc"],
    deps = [
        "//cyber",
        "//test/proto:car_cc_proto",
    ],
    linkstatic = True,
)

cc_binary(
    name = "listener",
    srcs = ["listener.cc"],
    deps = [
        "//cyber",
        "//test/proto:car_cc_proto",
    ],
    linkstatic = True,
)

2. Compile:

aem start //启动docker环境
aem enter //进入docker环境

Provided by using the apollo package management development method

buildtool build -p test/communication/

Successful compilation shows:
insert image description here

3. Run:

First, change the output method to console output.

export GLOG_alsologtostderr=1

Open two terminals and enter Apollo's docker environment. One terminal runs talker and the other runs listener. You will find that after the listener runs, it starts to receive the message of the car speed sent by talker.

run talker

./bazel-bin/test/communication/talker

The terminal displays:
insert image description here

run listener

./bazel-bin/test/communication/listener

The result shows:
insert image description here
Congratulations, you have completed the C++ topic communication experiment of Apollo Cyber ​​RT! ! !

5. Write Python topic communication implementation (small exercise)

small test! ! !

6. Run and test

Looking forward to the result! ! !


4. Practice cases of topic communication in local deployment of Cyber ​​RT

1. Installation and deployment:

https://apollo.baidu.com/community/Apollo-Homepage-Document/Apollo_Doc_CN_8_0?doc=%2F%25E5%25AE%2589%25E8%25A3%2585%25E8%25AF%25B4%25E6%2598%258E%2F%25E8%25BD%25AF%25E4%25BB%25B6%25E5%258C%2585%25E5%25AE%2589%25E8%25A3%2585%2F%25E8%25BD%25AF%25E4%25BB%25B6%25E5%258C%2585%25E5%25AE%2589%25E8%25A3%2585%2F

2. Create and enter the Apollo environment container

1. Create a workspace

Create and enter the directory

mkdir application-demo
cd application-demo

2. Start the apollo environment container

aem start

If everything is normal, you will see a prompt similar to the following image:
insert image description here

3. Enter the apollo environment container

aem enter

After the script is successfully executed, the following information will be displayed, and you will enter the running container of Apollo:
insert image description here

user_name@in-dev-docker:/apollo_workspace# 

The workspace folder will be mounted in /apollo_workspace of the container.

4. Initialize the workspace

aem init

insert image description here
So far, the Apollo environment management tools and containers have been installed.

3. Protobuf, talker-listener practice case demonstration

1. Protobuf

insert image description here

2. talker-listener

insert image description here
insert image description here


Summarize

The above is what I want to talk about today. This article only briefly introduces the use of apollo talker and listener, and apollo provides a large number of cases and methods that enable us to learn autonomous driving quickly and easily.

Guess you like

Origin blog.csdn.net/yechen1/article/details/131707570