Skip to content

Commit

Permalink
Application layer libcyphal::application::Node and its `HeartbeatPr…
Browse files Browse the repository at this point in the history
…oducer` & `GetInfoProvider` sub-components. (#386)

- Implemented application layer `Node` class.
- Temporary switch to the specific Nunavut commit, where I've made
necessary fixes (see OpenCyphal/nunavut#346)
  - switch to "pmr" flavor of Nunavut c++ code generation
- modify libcyphal to pass now required pmr allocator to ctor-s of
Nunavut generated messages/requests/responses
- Added new application layer example:
"example_2_application_0_node_hb_getinfo_udp" - demonstrates how to make
a simple libcyphal node using application layer `Node` class.
- Addressed some todos
- A bit more docs
  • Loading branch information
serges147 authored Oct 7, 2024
1 parent f8451dd commit 318898e
Show file tree
Hide file tree
Showing 39 changed files with 1,692 additions and 121 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/sonar.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
- name: get nunavut
run: >
pip install nunavut
pip install git+https://github.com/OpenCyphal/nunavut.git@9b001f92122cf5ad838532eef520791fa387eb6d
- name: Install sonar-scanner and build-wrapper
uses: SonarSource/sonarcloud-github-c-cpp@v2
- name: Run tests
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
- name: get nunavut
# TODO: setup a venv, cache, and distribute to the other jobs.
run: >
pip install nunavut
pip install git+https://github.com/OpenCyphal/nunavut.git@9b001f92122cf5ad838532eef520791fa387eb6d
- name: configure
run: >
./build-tools/bin/verify.py
Expand Down Expand Up @@ -70,7 +70,7 @@ jobs:
key: ${{ runner.os }}-${{ hashFiles('cmake/modules/*.cmake') }}
- name: get nunavut
run: >
pip install nunavut
pip install git+https://github.com/OpenCyphal/nunavut.git@9b001f92122cf5ad838532eef520791fa387eb6d
- name: run tests
env:
GTEST_COLOR: yes
Expand Down Expand Up @@ -115,7 +115,7 @@ jobs:
key: ${{ runner.os }}-${{ hashFiles('cmake/modules/*.cmake') }}
- name: get nunavut
run: >
pip install nunavut
pip install git+https://github.com/OpenCyphal/nunavut.git@9b001f92122cf5ad838532eef520791fa387eb6d
- name: doc-gen
run: >
./build-tools/bin/verify.py
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,6 @@ external/**/*
# JetBrains
.idea/*
cmake-build-*/

# Python
.venv
7 changes: 6 additions & 1 deletion cmake/modules/Findnnvg.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,12 @@ function (_init_nnvg_command_args)
# Set up common args used for all invocations of nnvg
list(APPEND LOCAL_NNVG_CMD_ARGS "--experimental-languages" "--target-language=cpp")
list(APPEND LOCAL_NNVG_CMD_ARGS "-O" ${ARG_OUTPUT_FOLDER})
list(APPEND LOCAL_NNVG_CMD_ARGS "--language-standard=c++${CMAKE_CXX_STANDARD}")

if (CMAKE_CXX_STANDARD STREQUAL "14")
list(APPEND LOCAL_NNVG_CMD_ARGS "--language-standard=cetl++14-17")
else ()
list(APPEND LOCAL_NNVG_CMD_ARGS "--language-standard=c++${CMAKE_CXX_STANDARD}-pmr")
endif ()
# support files must be generated in a discrete rule to avoid multiple rules
# trying to generate the same file.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,11 @@ class Example_0_Transport_1_Heartbeat_GetInfo_Udp : public testing::Test

struct State
{
posix::UdpMedia::Collection media_collection_;
UdpTransportPtr transport_;
NodeHelpers::Heartbeat heartbeat_;
NodeHelpers::GetInfo get_info_;
cetl::pmr::memory_resource& mr_;
posix::UdpMedia::Collection media_collection_{};
UdpTransportPtr transport_{nullptr};
NodeHelpers::Heartbeat heartbeat_{mr_};
NodeHelpers::GetInfo get_info_{mr_};

}; // State

Expand All @@ -114,7 +115,7 @@ class Example_0_Transport_1_Heartbeat_GetInfo_Udp : public testing::Test

TEST_F(Example_0_Transport_1_Heartbeat_GetInfo_Udp, main)
{
State state;
State state{mr_};

// Make UDP transport with collection of media.
//
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,11 @@ class Example_0_Transport_2_Heartbeat_GetInfo_Can : public testing::Test

struct State
{
Linux::CanMedia::Collection media_collection_;
CanTransportPtr transport_;
NodeHelpers::Heartbeat heartbeat_;
NodeHelpers::GetInfo get_info_;
cetl::pmr::memory_resource& mr_;
Linux::CanMedia::Collection media_collection_{};
CanTransportPtr transport_{nullptr};
NodeHelpers::Heartbeat heartbeat_{mr_};
NodeHelpers::GetInfo get_info_{mr_};

}; // State

Expand All @@ -116,7 +117,7 @@ class Example_0_Transport_2_Heartbeat_GetInfo_Can : public testing::Test

TEST_F(Example_0_Transport_2_Heartbeat_GetInfo_Can, main)
{
State state;
State state{mr_};

// Make CAN transport with collection of media.
//
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ namespace UserService
template <bool IsRequest>
struct Ping final
{
using allocator_type = cetl::pmr::polymorphic_allocator<void>;

static constexpr PortId ServiceId = 147;

struct _traits_
Expand All @@ -89,6 +91,19 @@ struct Ping final
static constexpr std::size_t SerializationBufferSizeBytes = sizeof(std::uint64_t);
};

Ping() = default;

explicit Ping(const std::uint64_t _id)
: id{_id}
{
}

// Allocator constructor
explicit Ping(const allocator_type& allocator)
{
(void) allocator;
}

std::uint64_t id{}; // NOLINT

nunavut::support::SerializeResult serialize(nunavut::support::bitspan out_buffer) const
Expand Down Expand Up @@ -187,10 +202,11 @@ class Example_1_Presentation_1_PingUserService_Udp : public testing::Test

struct PingPongState
{
cetl::pmr::memory_resource& mr;
const std::string name;
CommonHelpers::RunningStats& stats;
TimePoint req_start;
UserService::PingRequest request{};
UserService::PingRequest request{UserService::PingRequest::allocator_type{&mr}};
cetl::optional<PongPromise> promise;
};
void processPingPongResult(PingPongState& state, const PongPromise::Callback::Arg& arg)
Expand Down Expand Up @@ -245,6 +261,7 @@ class Example_1_Presentation_1_PingUserService_Udp : public testing::Test

TEST_F(Example_1_Presentation_1_PingUserService_Udp, main)
{
using PingRequest = UserService::PingRequest;
using PingClient = Client<UserService::PingRequest, UserService::PongResponse>;
using PingServer = Server<UserService::PingRequest, UserService::PongResponse>;
using PongContinuation = PingServer::OnRequestCallback::Continuation;
Expand Down Expand Up @@ -297,8 +314,12 @@ TEST_F(Example_1_Presentation_1_PingUserService_Udp, main)
auto delay_cb = executor_.registerCallback([&ping_contexts, id = unique_request_id](const auto& cb_arg) {
//
auto& ping_context = ping_contexts[id];

UserService::PongResponse response{};
response.id = std::get<2>(ping_context).id;

auto& continuation = std::get<0>(ping_context);
continuation(cb_arg.approx_now + 1s, UserService::PongResponse{std::get<2>(ping_context).id});
continuation(cb_arg.approx_now + 1s, response);
ping_contexts.erase(id);
});
delay_cb.schedule(Callback::Schedule::Once{arg.approx_now + 10ms + (10ms * (arg.request.id % 3))});
Expand All @@ -322,9 +343,9 @@ TEST_F(Example_1_Presentation_1_PingUserService_Udp, main)
// (the `id` field), which will implicitly affect the order of responses (see server setup).
//
CommonHelpers::RunningStats ping_pong_stats;
std::array<PingPongState, 3> ping_pong_states{PingPongState{"A", ping_pong_stats, {}, {1000}, {}},
PingPongState{"B", ping_pong_stats, {}, {2000}, {}},
PingPongState{"C", ping_pong_stats, {}, {3000}, {}}};
std::array<PingPongState, 3> ping_pong_states{PingPongState{mr_, "A", ping_pong_stats, {}, PingRequest{1000}, {}},
PingPongState{mr_, "B", ping_pong_stats, {}, PingRequest{2000}, {}},
PingPongState{mr_, "C", ping_pong_stats, {}, PingRequest{3000}, {}}};
//
const auto make_ping_request = [this, &ping_client](PingPongState& state) {
//
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,32 +96,42 @@ class Example_1_Presentation_2_Heartbeat_GetInfo_Udp : public testing::Test
EXPECT_THAT(mr_.total_allocated_bytes, mr_.total_deallocated_bytes);
}

NodeHelpers::Heartbeat::Message makeHeartbeatMsg(const libcyphal::TimePoint now, const bool is_warn = false) const
NodeHelpers::Heartbeat::Message makeHeartbeatMsg(const libcyphal::TimePoint now, const bool is_warn = false)
{
using Message = NodeHelpers::Heartbeat::Message;

Message message{mr_alloc_};

const auto uptime_in_secs = std::chrono::duration_cast<std::chrono::seconds>(now - startup_time_);
return {static_cast<std::uint32_t>(uptime_in_secs.count()),
{is_warn ? uavcan::node::Health_1_0::WARNING : uavcan::node::Health_1_0::NOMINAL},
{is_warn ? uavcan::node::Mode_1_0::MAINTENANCE : uavcan::node::Mode_1_0::OPERATIONAL}};

message.uptime = static_cast<std::uint32_t>(uptime_in_secs.count());
message.health.value = is_warn ? uavcan::node::Health_1_0::WARNING : uavcan::node::Health_1_0::NOMINAL;
message.mode.value = is_warn ? uavcan::node::Mode_1_0::MAINTENANCE : uavcan::node::Mode_1_0::OPERATIONAL;

return message;
}

// MARK: Data members:
// NOLINTBEGIN

struct State
{
posix::UdpMedia::Collection media_collection_;
UdpTransportPtr transport_;
NodeHelpers::Heartbeat heartbeat_;
uavcan::node::GetInfo_1_0::Response get_info_response{{1, 0}};
cetl::pmr::memory_resource& mr_;
posix::UdpMedia::Collection media_collection_{};
UdpTransportPtr transport_{nullptr};
NodeHelpers::Heartbeat heartbeat_{mr_};
cetl::pmr::polymorphic_allocator<void> mr_alloc_{&mr_};
uavcan::node::GetInfo_1_0::Response get_info_response{mr_alloc_};

}; // State

TrackingMemoryResource mr_;
posix::PollSingleThreadedExecutor executor_{mr_};
TimePoint startup_time_{};
NodeId local_node_id_{42};
Duration run_duration_{10s};
std::vector<std::string> iface_addresses_{"127.0.0.1"};
TrackingMemoryResource mr_;
posix::PollSingleThreadedExecutor executor_{mr_};
TimePoint startup_time_{};
NodeId local_node_id_{42};
Duration run_duration_{10s};
std::vector<std::string> iface_addresses_{"127.0.0.1"};
cetl::pmr::polymorphic_allocator<void> mr_alloc_{&mr_};
// NOLINTEND

}; // Example_1_Presentation_2_Heartbeat_GetInfo_Udp
Expand All @@ -130,7 +140,8 @@ class Example_1_Presentation_2_Heartbeat_GetInfo_Udp : public testing::Test

TEST_F(Example_1_Presentation_2_Heartbeat_GetInfo_Udp, main)
{
State state;
State state{mr_};
state.get_info_response.protocol_version.major = 1;

// Make UDP transport with collection of media.
//
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ namespace UserService
template <bool IsRequest>
struct Ping final
{
using allocator_type = cetl::pmr::polymorphic_allocator<void>;

static constexpr PortId ServiceId = 147;

struct _traits_
Expand All @@ -97,6 +99,19 @@ struct Ping final
static constexpr std::size_t SerializationBufferSizeBytes = sizeof(std::uint64_t);
};

Ping() = default;

explicit Ping(const std::uint64_t _id)
: id{_id}
{
}

// Allocator constructor
explicit Ping(const allocator_type& allocator)
{
(void) allocator;
}

std::uint64_t id{}; // NOLINT

nunavut::support::SerializeResult serialize(nunavut::support::bitspan out_buffer) const
Expand Down Expand Up @@ -149,7 +164,8 @@ class Example_1_Presentation_3_HB_GetInfo_Ping_Can : public testing::Test
{
run_duration_ = std::chrono::duration<std::int64_t>{std::strtoll(run_duration_str, nullptr, 10)};
}
// Duration in seconds for which the test will run. Default is 10 seconds.
// Boolean (0 is `false`, otherwise `true`) which turns on/off printing of intermediate activities.
// Default is `true`.
if (const auto* const print_str = std::getenv("CYPHAL__PRINT"))
{
print_activities_ = 0 != std::strtoll(print_str, nullptr, 10);
Expand Down Expand Up @@ -181,12 +197,16 @@ class Example_1_Presentation_3_HB_GetInfo_Ping_Can : public testing::Test
EXPECT_THAT(mr_.total_allocated_bytes, mr_.total_deallocated_bytes);
}

uavcan::node::Heartbeat_1_0 makeHeartbeatMsg(const libcyphal::TimePoint now) const
uavcan::node::Heartbeat_1_0 makeHeartbeatMsg(const libcyphal::TimePoint now)
{
using Message = uavcan::node::Heartbeat_1_0;

Message message{{&mr_}};

const auto uptime_in_secs = std::chrono::duration_cast<std::chrono::seconds>(now - startup_time_);
return {static_cast<std::uint32_t>(uptime_in_secs.count()),
{uavcan::node::Health_1_0::NOMINAL},
{uavcan::node::Mode_1_0::OPERATIONAL}};
message.uptime = static_cast<std::uint32_t>(uptime_in_secs.count());

return message;
}

TimePoint now() const
Expand All @@ -201,9 +221,10 @@ class Example_1_Presentation_3_HB_GetInfo_Ping_Can : public testing::Test

struct PingPongState
{
cetl::pmr::memory_resource& mr;
const std::string name;
CommonHelpers::RunningStats& stats;
UserService::PingRequest request;
UserService::PingRequest request{UserService::PingRequest::allocator_type{&mr}};
Priority priority;
TimePoint req_start;
cetl::optional<PongPromise> promise;
Expand Down Expand Up @@ -261,6 +282,7 @@ class Example_1_Presentation_3_HB_GetInfo_Ping_Can : public testing::Test

TEST_F(Example_1_Presentation_3_HB_GetInfo_Ping_Can, main)
{
using PingRequest = UserService::PingRequest;
using PingClient = Client<UserService::PingRequest, UserService::PongResponse>;
using PingServer = Server<UserService::PingRequest, UserService::PongResponse>;
using PongContinuation = PingServer::OnRequestCallback::Continuation;
Expand Down Expand Up @@ -318,8 +340,12 @@ TEST_F(Example_1_Presentation_3_HB_GetInfo_Ping_Can, main)
auto delay_cb = executor_.registerCallback([&ping_contexts, id = unique_request_id](const auto& cb_arg) {
//
auto& ping_context = ping_contexts[id];

UserService::PongResponse response{};
response.id = std::get<2>(ping_context).id;

auto& continuation = std::get<0>(ping_context);
continuation(cb_arg.approx_now + 1s, UserService::PongResponse{std::get<2>(ping_context).id});
continuation(cb_arg.approx_now + 1s, response);
ping_contexts.erase(id);
});
delay_cb.schedule(Callback::Schedule::Once{arg.approx_now + 1us + (10us * (arg.request.id % 7))});
Expand All @@ -345,11 +371,11 @@ TEST_F(Example_1_Presentation_3_HB_GetInfo_Ping_Can, main)
constexpr std::size_t concurrent_requests = 5;
CommonHelpers::RunningStats ping_pong_stats;
std::array<PingPongState, concurrent_requests>
ping_pong_states{PingPongState{"A", ping_pong_stats, {1000}, Priority::Nominal, {}, {}},
PingPongState{"B", ping_pong_stats, {2000}, Priority::Nominal, {}, {}},
PingPongState{"C", ping_pong_stats, {3000}, Priority::Nominal, {}, {}},
PingPongState{"D", ping_pong_stats, {4000}, Priority::Nominal, {}, {}},
PingPongState{"E", ping_pong_stats, {5000}, Priority::Nominal, {}, {}}};
ping_pong_states{PingPongState{mr_, "A", ping_pong_stats, PingRequest{1000}, Priority::Nominal, {}, {}},
PingPongState{mr_, "B", ping_pong_stats, PingRequest{2000}, Priority::Nominal, {}, {}},
PingPongState{mr_, "C", ping_pong_stats, PingRequest{3000}, Priority::Nominal, {}, {}},
PingPongState{mr_, "D", ping_pong_stats, PingRequest{4000}, Priority::Nominal, {}, {}},
PingPongState{mr_, "E", ping_pong_stats, PingRequest{5000}, Priority::Nominal, {}, {}}};
//
const auto make_ping_request = [this, &ping_client](PingPongState& state) {
//
Expand Down Expand Up @@ -399,8 +425,9 @@ TEST_F(Example_1_Presentation_3_HB_GetInfo_Ping_Can, main)
// 7. Bring up 'GetInfo' server.
//
using GetInfo_1_0 = uavcan::node::GetInfo_1_0;
uavcan::node::GetInfo_1_0::Response get_info_response{{1, 0}};
const std::string node_name{"org.opencyphal.Ex_1_Pres_3_HB_GetInfo_Ping_CAN"};
uavcan::node::GetInfo_1_0::Response get_info_response{{&mr_}};
get_info_response.protocol_version.major = 1;
const std::string node_name{"org.opencyphal.Ex_1_Pres_3_HB_GetInfo_Ping_CAN"};
std::copy_n(node_name.begin(), std::min(node_name.size(), 50UL), std::back_inserter(get_info_response.name));
//
auto maybe_get_info_srv = presentation.makeServer<GetInfo_1_0>([&](const auto& arg, auto continuation) {
Expand Down
Loading

0 comments on commit 318898e

Please sign in to comment.