Skip to content

Commit f0237c6

Browse files
authored
Implementation of SDK Publisher (#25)
- Added new `ocvsmd::sdk::Publisher` interface and implementation. - Added corresponding internal `RawPublisher` IPC service and its client - mainly serves `rawPublish` method, where is `publish` method is just implemented using the raw variant. - Implemented strong-typed message `Subscriber::receive` method by using `rawReceive` and transformation `then` operator. Also: - Removed "Raw" word from various pub/sub types - now they support both raw and strong-typed messages. - more docs
1 parent 5d2be62 commit f0237c6

33 files changed

+1613
-255
lines changed

include/ocvsmd/sdk/daemon.hpp

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -80,29 +80,51 @@ class Daemon
8080
///
8181
virtual NodeRegistryClient::Ptr getNodeRegistryClient() const = 0;
8282

83-
/// Defines the result type of the raw subscriber creation.
83+
/// Defines the result type of the publisher creation.
8484
///
85-
/// On success, the result is a smart pointer to a raw subscriber with the required parameters.
85+
/// On success, the result is a smart pointer to a publisher with the required parameters.
8686
/// On failure, the result is an SDK error.
8787
///
88-
struct MakeRawSubscriber final
88+
struct MakePublisher final
8989
{
90-
using Success = RawSubscriber::Ptr;
90+
using Success = Publisher::Ptr;
9191
using Failure = Error;
9292
using Result = cetl::variant<Success, Failure>;
9393
};
94-
/// Makes a new raw subscriber for the specified subject.
94+
/// Makes a new publisher for the specified subject.
95+
///
96+
/// The server-side (the daemon) of SDK will create the corresponding Cyphal network publisher,
97+
/// and then publish messages which are passed from to the client-side of SDK.
98+
/// See also `Publisher` docs for how to publish the outgoing messages.
99+
///
100+
/// @param subject_id The subject ID to publish to.
101+
/// @return An execution sender which emits the async result of the operation.
102+
///
103+
virtual SenderOf<MakePublisher::Result>::Ptr makePublisher(const CyphalPortId subject_id) = 0;
104+
105+
/// Defines the result type of the subscriber creation.
106+
///
107+
/// On success, the result is a smart pointer to a subscriber with the required parameters.
108+
/// On failure, the result is an SDK error.
109+
///
110+
struct MakeSubscriber final
111+
{
112+
using Success = Subscriber::Ptr;
113+
using Failure = Error;
114+
using Result = cetl::variant<Success, Failure>;
115+
};
116+
/// Makes a new subscriber for the specified subject.
95117
///
96118
/// The server-side (the daemon) of SDK will create the corresponding Cyphal network subscriber,
97-
/// subscribe to its raw (aka `void`) messages, and then forward them to the client-side of SDK.
98-
/// See also `RawSubscriber` docs for how to consume the incoming messages.
119+
/// subscribe to its messages, and then forward them to the client-side of SDK.
120+
/// See also `Subscriber` docs for how to consume the incoming messages.
99121
///
100122
/// @param subject_id The subject ID to subscribe to.
101-
/// @param extent_bytes The "extent" size of raw messages (see Cyphal spec).
123+
/// @param extent_bytes The "extent" size of messages (see Cyphal spec).
102124
/// @return An execution sender which emits the async result of the operation.
103125
///
104-
virtual SenderOf<MakeRawSubscriber::Result>::Ptr makeRawSubscriber(const CyphalPortId subject_id,
105-
const std::size_t extent_bytes) = 0;
126+
virtual SenderOf<MakeSubscriber::Result>::Ptr makeSubscriber(const CyphalPortId subject_id,
127+
const std::size_t extent_bytes) = 0;
106128

107129
protected:
108130
Daemon() = default;

include/ocvsmd/sdk/defines.hpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010
#include <cetl/pf20/cetlpf.hpp>
1111

1212
#include <cerrno>
13+
#include <cstddef>
1314
#include <cstdint>
15+
#include <memory>
1416

1517
namespace ocvsmd
1618
{
@@ -102,6 +104,11 @@ using CyphalNodeIds = cetl::span<CyphalNodeId>;
102104
///
103105
using CyphalPortId = std::uint16_t;
104106

107+
/// Defines priorities for Cyphal network messages.
108+
///
109+
/// Note, raw values are exactly the same as defined in the Cyphal specification -
110+
/// this is done to simplify the conversion between the SDK and the Cyphal network.
111+
///
105112
enum class CyphalPriority : std::uint8_t
106113
{
107114
Exceptional = 0,
@@ -115,6 +122,18 @@ enum class CyphalPriority : std::uint8_t
115122

116123
}; // CyphalPriority
117124

125+
/// Defines helper which owns mutable raw data buffer.
126+
///
127+
struct OwnMutablePayload
128+
{
129+
/// Holds the size of the raw data buffer. It could be less than it was allocated.
130+
std::size_t size;
131+
132+
/// Holds smart pointer to the raw data buffer.
133+
std::unique_ptr<cetl::byte[]> data; // NOLINT(*-avoid-c-arrays)
134+
135+
}; // OwnMutablePayload
136+
118137
} // namespace sdk
119138
} // namespace ocvsmd
120139

include/ocvsmd/sdk/node_pub_sub.hpp

Lines changed: 185 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,50 +6,153 @@
66
#ifndef OCVSMD_SDK_NODE_PUB_SUB_HPP_INCLUDED
77
#define OCVSMD_SDK_NODE_PUB_SUB_HPP_INCLUDED
88

9+
#include "defines.hpp"
910
#include "execution.hpp"
1011

1112
#include <cetl/pf17/cetlpf.hpp>
1213

14+
#include <chrono>
1315
#include <memory>
1416

1517
namespace ocvsmd
1618
{
1719
namespace sdk
1820
{
1921

20-
/// Defines the interface for the Raw Messages Subscriber.
22+
/// Defines the interface of Messages Publisher.
2123
///
22-
class RawSubscriber
24+
class Publisher
25+
{
26+
public:
27+
/// Defines a smart pointer type for the interface.
28+
///
29+
/// It's made "shared" b/c execution sender (see `publish` method) implicitly
30+
/// holds reference to its publisher.
31+
///
32+
using Ptr = std::shared_ptr<Publisher>;
33+
34+
virtual ~Publisher() = default;
35+
36+
// No copy/move semantics.
37+
Publisher(Publisher&&) = delete;
38+
Publisher(const Publisher&) = delete;
39+
Publisher& operator=(Publisher&&) = delete;
40+
Publisher& operator=(const Publisher&) = delete;
41+
42+
/// Publishes the next raw message using this publisher.
43+
///
44+
/// The client-side (the SDK) will forward the raw data to the corresponding Cyphal network publisher
45+
/// on the server-side (the daemon). The raw data is forwarded as is, without any interpretation or validation.
46+
///
47+
/// Note, only one operation can be active at a time (per publisher).
48+
/// In the case of multiple "concurrent" operations, only the last one will report the publishing result.
49+
/// Any previous still existing operations will be "stalled" and never complete.
50+
///
51+
/// @param raw_payload The raw message data to publish.
52+
/// @param timeout The maximum time to keep the published raw message as valid in the Cyphal network.
53+
/// @return An execution sender which emits the async result of the operation.
54+
///
55+
virtual SenderOf<OptError>::Ptr rawPublish(OwnMutablePayload&& raw_payload,
56+
const std::chrono::microseconds timeout) = 0;
57+
58+
/// Sets priority for messages to be issued by this publisher.
59+
///
60+
/// The next and following `publish` operations will use this priority.
61+
///
62+
virtual OptError setPriority(const CyphalPriority priority) = 0;
63+
64+
/// Publishes the next message using this publisher.
65+
///
66+
/// The client-side (the SDK) will forward the serialized message to the corresponding Cyphal network publisher
67+
/// on the server-side (the daemon).
68+
///
69+
/// Note, only one operation can be active at a time (per publisher).
70+
/// In the case of multiple "concurrent" operations, only the last one will report the publishing result.
71+
/// Any previous still existing operations will be "stalled" and never complete.
72+
///
73+
/// @param message The message to publish.
74+
/// @param timeout The maximum time to keep the published message as valid in the Cyphal network.
75+
/// @return An execution sender which emits the async result of the operation.
76+
///
77+
template <typename Message>
78+
SenderOf<OptError>::Ptr publish(const Message& message, const std::chrono::microseconds timeout)
79+
{
80+
return tryPerformOnSerialized(message, [this, timeout](auto raw_payload) {
81+
//
82+
return rawPublish(std::move(raw_payload), timeout);
83+
});
84+
}
85+
86+
protected:
87+
Publisher() = default;
88+
89+
private:
90+
template <typename Message, typename Action>
91+
CETL_NODISCARD static SenderOf<OptError>::Ptr tryPerformOnSerialized(const Message& msg, Action&& action)
92+
{
93+
#if defined(__cpp_exceptions)
94+
try
95+
{
96+
#endif
97+
// Try to serialize the message to raw payload buffer.
98+
//
99+
constexpr std::size_t BufferSize = Message::_traits_::SerializationBufferSizeBytes;
100+
// NOLINTNEXTLINE(*-avoid-c-arrays)
101+
OwnMutablePayload payload{BufferSize, std::make_unique<cetl::byte[]>(BufferSize)};
102+
//
103+
// No lint b/c of integration with Nunavut.
104+
// NOLINTNEXTLINE(*-pro-type-reinterpret-cast)
105+
auto* const payload_data = reinterpret_cast<std::uint8_t*>(payload.data.get());
106+
const auto result_size = serialize(msg, {payload_data, payload.size});
107+
if (result_size)
108+
{
109+
payload.size = result_size.value();
110+
return std::forward<Action>(action)(std::move(payload));
111+
}
112+
return just<OptError>(Error{Error::Code::InvalidArgument});
113+
114+
#if defined(__cpp_exceptions)
115+
} catch (const std::bad_alloc&)
116+
{
117+
return just<OptError>(Error{Error::Code::OutOfMemory});
118+
}
119+
#endif
120+
}
121+
122+
}; // Publisher
123+
124+
/// Defines the interface of Messages Subscriber.
125+
///
126+
class Subscriber
23127
{
24128
public:
25129
/// Defines a smart pointer type for the interface.
26130
///
27131
/// It's made "shared" b/c execution sender (see `receive` method) implicitly
28132
/// holds reference to its subscriber.
29133
///
30-
using Ptr = std::shared_ptr<RawSubscriber>;
134+
using Ptr = std::shared_ptr<Subscriber>;
31135

32-
virtual ~RawSubscriber() = default;
136+
virtual ~Subscriber() = default;
33137

34138
// No copy/move semantics.
35-
RawSubscriber(RawSubscriber&&) = delete;
36-
RawSubscriber(const RawSubscriber&) = delete;
37-
RawSubscriber& operator=(RawSubscriber&&) = delete;
38-
RawSubscriber& operator=(const RawSubscriber&) = delete;
139+
Subscriber(Subscriber&&) = delete;
140+
Subscriber(const Subscriber&) = delete;
141+
Subscriber& operator=(Subscriber&&) = delete;
142+
Subscriber& operator=(const Subscriber&) = delete;
39143

40-
/// Defines the result type of the raw subscriber message reception.
144+
/// Defines the result type of the subscriber raw message reception.
41145
///
42146
/// On success, the result is a raw data buffer, its size, and extra metadata.
43147
/// On failure, the result is an SDK error.
44148
///
45-
struct Receive final
149+
struct RawReceive final
46150
{
47151
struct Success
48152
{
49-
std::size_t size;
50-
std::unique_ptr<cetl::byte[]> data; // NOLINT(*-avoid-c-arrays)
51-
CyphalPriority priority;
52-
cetl::optional<CyphalNodeId> publisher_node_id;
153+
OwnMutablePayload payload;
154+
CyphalPriority priority;
155+
cetl::optional<CyphalNodeId> publisher_node_id;
53156
};
54157
using Failure = Error;
55158
using Result = cetl::variant<Success, Failure>;
@@ -67,12 +170,77 @@ class RawSubscriber
67170
///
68171
/// @return An execution sender which emits the async result of the operation.
69172
///
70-
virtual SenderOf<Receive::Result>::Ptr receive() = 0;
173+
virtual SenderOf<RawReceive::Result>::Ptr rawReceive() = 0;
174+
175+
/// Defines the result type of the subscriber message reception.
176+
///
177+
/// On success, the result is a deserialized message, and its extra metadata.
178+
/// On failure, the result is an SDK error.
179+
///
180+
struct Receive final
181+
{
182+
template <typename Message>
183+
struct Success
184+
{
185+
Message message;
186+
CyphalPriority priority;
187+
cetl::optional<CyphalNodeId> publisher_node_id;
188+
};
189+
190+
using Failure = Error;
191+
192+
template <typename Message>
193+
using Result = cetl::variant<Success<Message>, Failure>;
194+
};
195+
/// Receives the next message from this subscriber.
196+
///
197+
/// The server-side (the daemon) will forward the observed raw data on the corresponding Cyphal network subscriber.
198+
/// The received raw data is then deserialized into the strong-typed message.
199+
///
200+
/// Note, only one `receive` operation can be active at a time (per subscriber).
201+
/// In the case of multiple "concurrent" operations, only the last one will receive the result.
202+
/// Any previous still existing operations will be "stalled" and never complete.
203+
/// Also, to not miss any new message, user should immediately initiate
204+
/// a new `receive` operation after getting the success result of the previous one.
205+
///
206+
/// @return An execution sender which emits the async result of the operation.
207+
///
208+
template <typename Message>
209+
typename SenderOf<Receive::Result<Message>>::Ptr receive(cetl::pmr::memory_resource& memory)
210+
{
211+
using ResultMsg = Receive::Result<Message>;
212+
213+
return then<ResultMsg, RawReceive::Result>(rawReceive(), [&memory](auto raw_result) -> ResultMsg {
214+
//
215+
if (const auto* const failure = cetl::get_if<RawReceive::Failure>(&raw_result))
216+
{
217+
return *failure;
218+
}
219+
auto raw_msg = cetl::get<RawReceive::Success>(std::move(raw_result));
220+
221+
// No lint b/c of integration with Nunavut.
222+
// NOLINTNEXTLINE(*-pro-type-reinterpret-cast)
223+
const auto* const raw_payload = reinterpret_cast<const std::uint8_t*>(raw_msg.payload.data.get());
224+
Message message{&memory};
225+
const auto deser_result = deserialize(message, {raw_payload, raw_msg.payload.size});
226+
if (!deser_result)
227+
{
228+
// Invalid message payload.
229+
return Error{Error::Code::InvalidArgument};
230+
}
231+
232+
return Receive::Success<Message>{
233+
std::move(message),
234+
raw_msg.priority,
235+
raw_msg.publisher_node_id,
236+
};
237+
});
238+
}
71239

72240
protected:
73-
RawSubscriber() = default;
241+
Subscriber() = default;
74242

75-
}; // RawSubscriber
243+
}; // Subscriber
76244

77245
} // namespace sdk
78246
} // namespace ocvsmd

src/cli/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ add_cyphal_library(
99
NAME cli
1010
DSDL_FILES
1111
"uavcan/node/7509.Heartbeat.1.0.dsdl"
12+
"uavcan/time/7168.Synchronization.1.0.dsdl"
1213
ALLOW_EXPERIMENTAL_LANGUAGES
1314
LANGUAGE cpp
1415
LANGUAGE_STANDARD ${CYPHAL_LANGUAGE_STANDARD}

src/cli/dsdl_helpers.hpp

Lines changed: 0 additions & 23 deletions
This file was deleted.

0 commit comments

Comments
 (0)