Skip to content

Create message types

Istvan Madari edited this page Sep 23, 2019 · 4 revisions

Only strongly-typed messages are allowed in the RIAPS C++ components. That means, you have to specify all the message types what you are planning to use in your application, with all their fields and their types.

Create strongly-typed messages

In the C++ component framework you have only one option to define structures of messages: use Cap'n Proto. Capnp is using schema file to define the message structure and then the Cap'n Proto compiler can produce the language-specific source code. First step: grab your favorite text editor (preferable with capnp syntax highlighter, like vscode) and create a file with .capnp extension.

code ./riaps_messages.capnp

In this example we are defining two messages: (1) Query (temperature), (2) Response (temperature value).

@0x84c91b33e534518f; # unique file ID, generated by `capnp id`

# struct timespec from <time.h>
struct Timespec {
    sec  @0 : UInt64;
    nsec @1 : UInt64;
}

struct QueryTemp {
    messageCounter @0 : UInt32;
    created        @1 : Timespec;
}

struct ResponseTemp {
    value   @0 : Float32;
    created @1 : Timespec;
}

We are ready with the message definition, now you have to turn this schema file into C++ code:

capnp compile ./riaps_messages.capnp -oc++:./

The -oc++ tells the capnp compiler to generate C++ code, while the :./ specifies that place the generated artifacts in the current folder. If everything goes well your directory contains a .c++ and a .h file.

At this point, please take your time and read about the Cap'n Proto schema language. On this page you will find more detailed and better walkthroughs about the capnp schema and its nuances.

Instantiate capnp messages in RIAPS

So far, the things went well. You defined your message structures and generated code which compiles successfully, but now you have to use this message in your RIAPS component. We built two wrappers around the capnp API, which makes easier to build/read RIAPS messages in C++. Let's see an example how to use them:

{
    // Wrapper around capnp to BUILD messages
    MessageBuilder<QueryTemp> builder;

    // Set the fields with `->` operator
    builder->setMessageCounter(counter_++);

    // Current time
    timespec ts;
    clock_gettime(CLOCK_REALTIME, &ts);
    
    // Set the timestamp 'created' field in the message
    auto created = builder->initCreated();
    created.setSec(ts.tv_sec);
    created.setNsec(ts.tv_nsec);
}

The message is ready to be sent! The Send() functions are explained under the API section.

Read capnp message in RIAPS

Reading happens in very similar way, however the MessageReader<ResponseTemp> is instantiated by the RIAPS framework (in the Recv() functions) and not by the developer. The MessageReader<> has two goals: (1) make the message reading easier for the developers, (2) hold the related pointers, structures together (and avoid memory leaks).

{
    // Type of msg will be MessageReader<ResponseTemp>
    auto [msg, err] = RecvTemp();
    if (!err) {
        // Access the message fields using the wrapper with '->getX()'
        auto value = msg->getValue();
        timespec created;

        // No wrapper here, this is a field in the message.
        // Access the fields with '.getX()'
        created.tv_sec  = msg->getCreated().getSec();
        created.tv_nsec = msg->getCreated().getNsec();
    } else {
        // Error in recv();
    }
}

The Recv() functions are explained under the API section.

Clone this wiki locally