7.2. Hello World with protobuf

Important

This tutorial will only work with eCAL 5.7.3 and upwards if you are using Windows. It will not work with older versions that were published as .msi installer (it missed some libraries).

Please switch to Ubuntu, if you are using an old eCAL Version.

In the last section you learned how to send strings to an eCAL Topic. Using strings is great for simple data that has a textual representation. Quite often however your data will be more complex, so you need some kind of protocol that defines how your data is structured.

Of course, you can define your own protocol and pass the raw memory to eCAL (you would use the raw eCAL::CPublisher() to do that). Our recommended way however is to use Google protobuf, because:

  • It solves the problem of how to serialize and de-serialize data for you

  • You get downward compatibility out of the box (if you follow the guidelines)

  • It is maintained by Google and the API is stable

  • The eCAL Monitor can display a nice reflection view of the data

Important

It is important to remember, that all your applications must agree on the data format. As protobuf messages are defined in .proto files, all of your applications should be compiled with the same files.

7.2.1. Protobuf sender

Let’s implement a small application, that lets the user input his name and send a message to an eCAL topic. As the sender and receiver need the same .proto files, we place them in a separate directory next to the source directories for the sender and the receiver:


├─  proto_messages
│  └─  hello_world.proto
├─  protobuf_snd
└─  protobuf_rec

Let’s start with the proto_messages/hello_world.proto file!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
syntax = "proto3";

package proto_messages;

message HelloWorld
{
  string name      = 1;
  uint32 id        = 2;
  string msg       = 3;
}

Note

What is happening here?

Line 3 assigns a package name (this will appear as C++ namespace later).

Line 5-10 Creates a message “HelloWorld”, that holds the fields “name”, “id” and “msg”.

Now start implementing the actual sender application. Just as in the last section create the CMakeLists.txt and main.cpp in the protobuf_snd directory and paste the following content:

  • CMakeLists.txt:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    cmake_minimum_required(VERSION 3.0)
    
    project(protobuf_snd)
    
    set(CMAKE_CXX_STANDARD 14)
    set(CMAKE_CXX_STANDARD_REQUIRED ON)
    
    if(MSVC)
      # CMake >= 3.15 will erroneously define PROTOBUF_USE_DLLS otherwise
      set (Protobuf_USE_STATIC_LIBS TRUE) 
    endif()
    
    find_package(eCAL REQUIRED)
    find_package(Protobuf REQUIRED)
    
    set(source_files
      main.cpp
    )
    
    set(protobuf_files
        ${CMAKE_CURRENT_SOURCE_DIR}/../proto_messages/hello_world.proto
    )
    
    add_executable(${PROJECT_NAME} ${source_files})
    
    PROTOBUF_TARGET_CPP(${PROJECT_NAME} ${CMAKE_CURRENT_SOURCE_DIR}/../proto_messages/ ${protobuf_files})
    
    target_link_libraries(${PROJECT_NAME}
      eCAL::core
      protobuf::libprotobuf
    )
    

    Note

    What is happening here?

    Line 14 adds Protobuf as dependency

    Line 20-22 Creates a list of .proto files. We only have one.

    Line 26 Compiles the .proto file to a C++ header file (hello_world.pb.h). The PROTOBUF_TARGET_CPP function is a convenience function from eCAL. If you have already worked with Protobuf and CMake, you may be more familiar with the following code, which basically does the same thing:

    include_directories(${CMAKE_CURRENT_BINARY_DIR})
    protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS ${protobuf_files})
    add_executable(${PROJECT_NAME} ${source_files} ${PROTO_SRCS} ${PROTO_HDRS})
    

    Line 30 links the executable against protobuf

  • main.cpp:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    #include <ecal/ecal.h>
    #include <ecal/msg/protobuf/publisher.h>
    
    #include <iostream>
    #include <thread>
    
    #include "hello_world.pb.h"
    
    int main(int argc, char** argv)
    {
      // Initialize eCAL and create a protobuf publisher
      eCAL::Initialize(argc, argv, "Hello World Protobuf Publisher");
      eCAL::protobuf::CPublisher<proto_messages::HelloWorld> publisher("hello_world_protobuf");
    
      // Ask the user to input his name
      std::cout << "Please enter your name: ";
      std::string name;
      std::getline(std::cin, name);
    
      unsigned int id = 0;
    
      // Infinite loop (using eCAL::Ok() will enable us to gracefully shutdown the
      // Process from another application)
      while (eCAL::Ok())
      {
        // Let the user input a message
        std::cout << "Type the message you want to send: ";
        std::string message;
        std::getline(std::cin, message);
    
        // Create a protobuf message object
        proto_messages::HelloWorld hello_world_message;
        hello_world_message.set_name(name);
        hello_world_message.set_msg (message);
        hello_world_message.set_id  (id++);
    
        // Send the message
        publisher.Send(hello_world_message);
        std::cout << "Sent message!" << std::endl << std::endl;
      }
    
      // finalize eCAL API
      eCAL::Finalize();
    }
    

    Note

    What is happening here?

    Line 2: This time, we include the protobuf publisher.

    Line7 includes the generated C++ file from the hello_world.proto

    Line 13 creates a protobuf publisher instance. Note that it is templated to proto_messages::HelloWorld, so if you would want to send different messages to different topics, you would create one publisher instance per topic. Sending different messages to the same topic is a bad idea and will break the de-serialization.

    Line 32-36 creates the message as protobuf object and sets the fields in it.

    Line 38 sends the protobuf object out to the topic.

Again, you can already watch the sender doing its work by inspecting the topic in the eCAL Monitor! (Compile the application just as in the last section)

Hello World Protobuf sender and eCAL Monitor

Important

The eCAL Monitor will not start listening to messages, until you open the reflection window. So, you will only see messages that were sent after you have opened the window.

7.2.2. Protobuf receiver

  • CMakeLists.txt:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    cmake_minimum_required(VERSION 3.0)
    
    project(protobuf_rec)
    
    set(CMAKE_CXX_STANDARD 14)
    set(CMAKE_CXX_STANDARD_REQUIRED ON)
    
    if(MSVC)
      # CMake >= 3.15 will erroneously define PROTOBUF_USE_DLLS otherwise
      set (Protobuf_USE_STATIC_LIBS TRUE) 
    endif()
    
    find_package(eCAL REQUIRED)
    find_package(Protobuf REQUIRED)
    
    set(source_files
      main.cpp
    )
    
    set(protobuf_files
        ${CMAKE_CURRENT_SOURCE_DIR}/../proto_messages/hello_world.proto
    )
    
    add_executable(${PROJECT_NAME} ${source_files})
    
    PROTOBUF_TARGET_CPP(${PROJECT_NAME} ${CMAKE_CURRENT_SOURCE_DIR}/../proto_messages/ ${protobuf_files})
    
    target_link_libraries(${PROJECT_NAME}
      eCAL::core
      protobuf::libprotobuf
    )
    
  • main.cpp:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    #include <ecal/ecal.h>
    #include <ecal/msg/protobuf/subscriber.h>
    
    #include <iostream>
    #include <thread>
    
    #include "hello_world.pb.h"
    
    void HelloWorldCallback(const proto_messages::HelloWorld& hello_world_msg)
    {
      std::cout << hello_world_msg.name() << " sent a message with ID "
                << hello_world_msg.id() << ":" << std::endl
                << hello_world_msg.msg() << std::endl << std::endl;
    }
    
    int main(int argc, char** argv)
    {
      // Initialize eCAL and create a protobuf subscriber
      eCAL::Initialize(argc, argv, "Hello World Protobuf Subscriber");
      eCAL::protobuf::CSubscriber<proto_messages::HelloWorld> subscriber("hello_world_protobuf");
    
      // Set the Callback
      subscriber.AddReceiveCallback(std::bind(&HelloWorldCallback, std::placeholders::_2));
    
      // Just don't exit
      while (eCAL::Ok())
      {
        std::this_thread::sleep_for(std::chrono::milliseconds(500));
      }
    
      // finalize eCAL API
      eCAL::Finalize();
    }
    

    Note

    What is happening here?

    Line 9 is our subscriber callback (you have already seen a callback in the last Hello World Tutorial). This time however it receives a protobuf object. Line 11-13 use the handy protobuf accessor methods to print the data to the terminal.

    Line 20 Creates an eCAL protobuf subscriber. Just like the publisher, it is templated to the proto_messages::HelloWorld message.

    Line 23 Sets the callback, so eCAL can call it whenever a new message is received.

Now compile and start both the sender and the receiver application and send some messages!

Hello World Protobuf sender and receiver

Congratulations, you have completed the Getting Started chapter! Now go ahead and use eCAL in your real-world scenario.

If you experience issues, you can create a GitHub issue, to get help.

7.2.3. Files


├─  proto_messages
│  └─  hello_world.proto
│
├─  protobuf_snd
│  ├─  CMakeLists.txt
│  └─  main.cpp
│
└─  protobuf_rec
   ├─  CMakeLists.txt
   └─  main.cpp