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 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300
|
# user-header
## Introduction
This example demonstrates how to leverage the user-header to send custom meta-information with each sample.
Specifically, we want to send a timestamp alongside the sample.
The example contains code for the typed and untyped C++ API as well as for the C language binding.
## Expected Output
[](https://asciinema.org/a/410691)
## Code walkthrough
The examples uses the user-header and user-payload which is defined in `user_header_and_payload_types.hpp`
for the C++ API and in `user_header_and_payload_types.h` for the C API. The user-header consists of a simple `uint64_t`
which is used to transmit a timestamp and the user-payload is a Fibonacci number.
This are the definitions for the C++ API:
<!-- [geoffrey] [iceoryx_examples/user_header/user_header_and_payload_types.hpp] [user-header] -->
```cpp
struct Header
{
uint64_t publisherTimestamp{0};
};
```
<!-- [geoffrey] [iceoryx_examples/user_header/user_header_and_payload_types.hpp] [user-payload] -->
```cpp
struct Data
{
uint64_t fibonacci{0};
};
```
This are the definitions for the C API:
<!-- [geoffrey] [iceoryx_examples/user_header/user_header_and_payload_types.h] [user-header] -->
```c
typedef struct
{
uint64_t publisherTimestamp;
} Header;
```
<!-- [geoffrey] [iceoryx_examples/user_header/user_header_and_payload_types.h] [user-payload] -->
```c
typedef struct
{
uint64_t fibonacci;
} Data;
```
### Publisher Typed C++ API
At first, there are includes for the user-header and user-payload types
and the iceoryx includes for publisher and runtime.
<!-- [geoffrey] [iceoryx_examples/user_header/publisher_cxx_api.cpp] [iceoryx includes] -->
```cpp
#include "user_header_and_payload_types.hpp"
#include "iceoryx_hoofs/posix_wrapper/signal_watcher.hpp"
#include "iceoryx_posh/popo/publisher.hpp"
#include "iceoryx_posh/runtime/posh_runtime.hpp"
```
Next, the iceoryx runtime is initialized. With this call, the application will be registered at `RouDi`,
the routing and discovery daemon.
<!-- [geoffrey] [iceoryx_examples/user_header/publisher_cxx_api.cpp] [initialize runtime] -->
```cpp
constexpr char APP_NAME[] = "iox-cpp-user-header-publisher";
iox::runtime::PoshRuntime::initRuntime(APP_NAME);
```
Now, we create the publisher. Unlike the other examples, this uses the second template parameter to define the user-header.
This is the only change compared to the other examples with the typed C++ API.
<!-- [geoffrey] [iceoryx_examples/user_header/publisher_cxx_api.cpp] [create publisher] -->
```cpp
iox::popo::Publisher<Data, Header> publisher({"Example", "User-Header", "Timestamp"});
```
In the main loop, a Fibonacci sequence is created and every second a number is published.
The Fibonacci number is passed to the `loan` method which construct the `Data` object if the loaning was successful.
This example uses the functional approach with `and_then` and `or_else`. Please have a look at the `icehello` example
if you prefer a more traditional approach.
<!-- [geoffrey] [iceoryx_examples/user_header/publisher_cxx_api.cpp] [[send samples in a loop] [loan sample]] -->
```cpp
uint64_t timestamp = 42;
uint64_t fibonacciLast = 0;
uint64_t fibonacciCurrent = 1;
while (!iox::posix::hasTerminationRequested())
{
auto fibonacciNext = fibonacciCurrent + fibonacciLast;
fibonacciLast = fibonacciCurrent;
fibonacciCurrent = fibonacciNext;
publisher.loan(Data{fibonacciCurrent})
.and_then([&](auto& sample) {
// ...
})
.or_else([&](auto& error) {
// ...
});
constexpr uint64_t MILLISECONDS_SLEEP{1000U};
std::this_thread::sleep_for(std::chrono::milliseconds(MILLISECONDS_SLEEP));
timestamp += MILLISECONDS_SLEEP;
}
```
When the loaning was successful, we get a sample and can access it in the `and_then` branch.
The sample has a `getUserHeader` method which returns a reference to the user-header we specified with the template parameter.
In this case it's the `Header` struct and we set the `publisherTimestamp`.
<!-- [geoffrey] [iceoryx_examples/user_header/publisher_cxx_api.cpp] [loan was successful] -->
```cpp
sample.getUserHeader().publisherTimestamp = timestamp;
sample.publish();
std::cout << APP_NAME << " sent data: " << fibonacciCurrent << " with timestamp " << timestamp << "ms"
<< std::endl;
```
If the loaning fails, the `or_else` branch is executed, which prints an error message
<!-- [geoffrey] [iceoryx_examples/user_header/publisher_cxx_api.cpp] [loan failed] -->
```cpp
std::cout << APP_NAME << " could not loan sample! Error code: " << error << std::endl;
```
### Publisher Untyped C++ API
The example with the untyped C++ publisher is quite similar to the one with the typed publisher.
The few differences will be discussed in this section.
At first there is a different include
<!-- [geoffrey] [iceoryx_examples/user_header/publisher_untyped_cxx_api.cpp] [include differs from typed C++ API] -->
```cpp
#include "iceoryx_posh/popo/untyped_publisher.hpp"
```
When the publisher is created, there is also no notion of a user-header and it looks exactly the same like already shown in other examples.
<!-- [geoffrey] [iceoryx_examples/user_header/publisher_untyped_cxx_api.cpp] [create publisher] -->
```cpp
iox::popo::UntypedPublisher publisher({"Example", "User-Header", "Timestamp"});
```
Variations come again into play when the chunk is loaned. Since the API is untyped,
the parameter for the user-header have to be provided with the `loan` call. These are optional parameter and
are set to values indicating no user-header by default.
<!-- [geoffrey] [iceoryx_examples/user_header/publisher_untyped_cxx_api.cpp] [[loan chunk]] -->
```cpp
publisher.loan(sizeof(Data), alignof(Data), sizeof(Header), alignof(Header))
.and_then([&](auto& userPayload) {
// ...
})
.or_else([&](auto& error) {
// ...
});
```
Lastly, since the untyped C++ API returns a `void` pointer to the user-payload, there is an intermediate step
to access the user-header by obtaining a `ChunkHeader` first, which provides a method to get the user-header.
This also return a `void` pointer, which makes a cast to the actual type necessary.
<!-- [geoffrey] [iceoryx_examples/user_header/publisher_untyped_cxx_api.cpp] [loan was successful] -->
```cpp
auto header = static_cast<Header*>(iox::mepoo::ChunkHeader::fromUserPayload(userPayload)->userHeader());
header->publisherTimestamp = timestamp;
auto data = static_cast<Data*>(userPayload);
data->fibonacci = fibonacciCurrent;
publisher.publish(userPayload);
std::cout << APP_NAME << " sent data: " << fibonacciCurrent << " with timestamp " << timestamp << "ms"
<< std::endl;
```
### Publisher C API
The example for the C API is similar to the one in `icedelivery_in_c` and therefore only the user-header related parts are looked into.
The overall structure is the same like in the typed and untyped C++ API counterparts.
The functions to access the user-header are located in the following include
<!-- [geoffrey] [iceoryx_examples/user_header/publisher_c_api.c] [additional include for user-header] -->
```cpp
#include "iceoryx_binding_c/chunk.h"
```
Similar to the untyped C++ API, the user-header parameter are specified with the loan function.
Since C does not have overloading, this is done by a different function
<!-- [geoffrey] [iceoryx_examples/user_header/publisher_c_api.c] [[loan chunk]] -->
```cpp
void* userPayload;
const uint32_t ALIGNMENT = 8;
enum iox_AllocationResult res = iox_pub_loan_aligned_chunk_with_user_header(
publisher, &userPayload, sizeof(Data), ALIGNMENT, sizeof(Header), ALIGNMENT);
```
Like with the untyped C++ API, the path to the user-header needs an intermediate step with the `iox_chunk_header_t` and
explicit casting to since the functions return `void` pointer.
<!-- [geoffrey] [iceoryx_examples/user_header/publisher_c_api.c] [[loan was successful]] -->
```cpp
iox_chunk_header_t* chunkHeader = iox_chunk_header_from_user_payload(userPayload);
Header* header = (Header*)iox_chunk_header_to_user_header(chunkHeader);
header->publisherTimestamp = timestamp;
Data* data = (Data*)userPayload;
data->fibonacci = fibonacciCurrent;
iox_pub_publish_chunk(publisher, userPayload);
// explicit cast to unsigned long since on macOS an uint64_t is a different built-in type than on Linux
printf("%s sent data: %lu with timestamp %ldms\n",
APP_NAME,
(unsigned long)fibonacciCurrent,
(unsigned long)timestamp);
fflush(stdout);
```
### Subscriber Typed C++ API
The boilerplate code for the subscriber is the same like for the publisher, therefore only the specific subscriber code is discussed in this section.
To use the subscriber, `subscriber.hpp` needs to be included.
Similar to the publisher, the subscriber requires the same additional template parameter when
it is used with a user header.
<!-- [geoffrey] [iceoryx_examples/user_header/subscriber_cxx_api.cpp] [create subscriber] -->
```cpp
iox::popo::Subscriber<Data, Header> subscriber({"Example", "User-Header", "Timestamp"});
```
The main loop is quite simple. The publisher is periodically polled and the data of the received sample is printed.
Again, the user-header is accessed by the `getUserHeader` method of the sample.
<!-- [geoffrey] [iceoryx_examples/user_header/subscriber_cxx_api.cpp] [poll subscriber for samples in a loop] -->
```cpp
while (!iox::posix::hasTerminationRequested())
{
subscriber.take().and_then([&](auto& sample) {
std::cout << APP_NAME << " got value: " << sample->fibonacci << " with timestamp "
<< sample.getUserHeader().publisherTimestamp << "ms" << std::endl;
});
constexpr std::chrono::milliseconds SLEEP_TIME{100U};
std::this_thread::sleep_for(SLEEP_TIME);
}
```
### Subscriber Untyped C++ API
On the subscriber side, the typed and untyped examples are even closer in similarity than the publisher example.
The notable difference is the `take` method, which returns a `void` pointer to the user-payload.
Like with the untyped publisher, in order to get the user-header the `ChunkHeader` must be obtained.
Contrary to the untyped publisher, we must cast to a `const T*` type, like `const Header*`
since we are not allowed to mutate the header from a subscriber.
At the end, the chunk must be released to prevent chunk leaks. This is done by calling the `release` method with user-payload pointer.
<!-- [geoffrey] [iceoryx_examples/user_header/subscriber_untyped_cxx_api.cpp] [take chunk] -->
```cpp
subscriber.take().and_then([&](auto& userPayload) {
auto header =
static_cast<const Header*>(iox::mepoo::ChunkHeader::fromUserPayload(userPayload)->userHeader());
auto data = static_cast<const Data*>(userPayload);
std::cout << APP_NAME << " got value: " << data->fibonacci << " with timestamp "
<< header->publisherTimestamp << "ms" << std::endl;
subscriber.release(userPayload);
});
```
### Subscriber C API
Finally there is the C API example. Like with the publisher example for the C API we just take a look at the user-header specific parts.
The overall structure is the same like in the typed and untyped C++ API counterparts.
As we already have seen with the untyped C++ example, the call to take the sample is independent of the usage of a user-header.
Likewise, the access to the user-header is done by the intermediate step of getting an `iox_chunk_header_t` first.
Since we are not allowed to mutate the user-header from a subscriber, the respective functions with a `_const` suffix must be used.
At last, the chunk is released in order to prevent chunk leaks.
<!-- [geoffrey] [iceoryx_examples/user_header/subscriber_c_api.c] [take chunk] -->
```cpp
const void* userPayload;
if (iox_sub_take_chunk(subscriber, &userPayload) == ChunkReceiveResult_SUCCESS)
{
const iox_chunk_header_t* chunkHeader = iox_chunk_header_from_user_payload_const(userPayload);
const Header* header = (const Header*)(iox_chunk_header_to_user_header_const(chunkHeader));
const Data* data = (const Data*)userPayload;
// explicit cast to unsigned long since on macOS an uint64_t is a different built-in type than on Linux
printf("%s got value: %lu with timestamp %ldms\n",
APP_NAME,
(unsigned long)data->fibonacci,
(unsigned long)header->publisherTimestamp);
fflush(stdout);
iox_sub_release_chunk(subscriber, userPayload);
}
```
<center>
[Check out User-Header on GitHub :fontawesome-brands-github:](https://github.com/eclipse-iceoryx/iceoryx/tree/v2.0.0/iceoryx_examples/user_header){ .md-button }
</center>
|