File: README.md

package info (click to toggle)
iceoryx 2.0.3%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 11,260 kB
  • sloc: cpp: 94,127; ansic: 1,443; sh: 1,436; python: 1,377; xml: 80; makefile: 61
file content (300 lines) | stat: -rw-r--r-- 12,670 bytes parent folder | download | duplicates (3)
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

[![asciicast](https://asciinema.org/a/410691.svg)](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>