Skip to content

Commit 26f6894

Browse files
committed
added first draft of file downloader on COMMAND_BEGIN_SOFTWARE_UPDATE
1 parent 3fca5a1 commit 26f6894

File tree

4 files changed

+182
-25
lines changed

4 files changed

+182
-25
lines changed

libcyphal_demo/src/application.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
#include "application.hpp"
88

9-
#include <libcyphal/application/registry/registry_impl.hpp>
109
#include <libcyphal/platform/storage.hpp>
1110

1211
#include <cetl/pf17/cetlpf.hpp>
@@ -24,15 +23,15 @@ namespace
2423
{
2524

2625
constexpr std::size_t HeapSize = 16ULL * 1024ULL;
27-
alignas(O1HEAP_ALIGNMENT) std::array<cetl::byte, HeapSize> s_heap_arena{};
26+
alignas(O1HEAP_ALIGNMENT) std::array<cetl::byte, HeapSize> s_heap_arena{}; // NOLINT
2827

2928
} // namespace
3029

3130
Application::Application(const char* const root_path)
3231
: o1_heap_mr_{s_heap_arena}
32+
, media_block_mr_{*cetl::pmr::new_delete_resource()}
3333
, storage_{root_path}
3434
, registry_{o1_heap_mr_}
35-
, media_block_mr_{*cetl::pmr::new_delete_resource()}
3635
, regs_{o1_heap_mr_, registry_, media_block_mr_}
3736
{
3837
cetl::pmr::set_default_resource(&o1_heap_mr_);

libcyphal_demo/src/exec_cmd_provider.hpp

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
#include "libcyphal/application/node.hpp"
1111
#include "libcyphal/presentation/presentation.hpp"
1212
#include "libcyphal/presentation/server.hpp"
13+
#include "libcyphal/time_provider.hpp"
14+
#include "libcyphal/transport/types.hpp"
1315
#include "libcyphal/types.hpp"
1416

1517
#include <cetl/pf17/cetlpf.hpp>
@@ -52,9 +54,12 @@ class ExecCmdProvider // NOSONAR cpp:S4963
5254
///
5355
/// @param node The application layer node instance. In use to access heartbeat producer.
5456
/// @param presentation The presentation layer instance. In use to create 'ExecuteCommand' service server.
57+
/// @param time_provider The time provider - in use to calculate RPC call deadlines.
5558
/// @return The ExecuteCommand provider instance or a failure.
5659
///
57-
static auto make(libcyphal::application::Node& node, libcyphal::presentation::Presentation& presentation)
60+
static auto make(libcyphal::application::Node& node,
61+
libcyphal::presentation::Presentation& presentation,
62+
libcyphal::ITimeProvider& time_provider)
5863
-> libcyphal::Expected<Derived, libcyphal::presentation::Presentation::MakeFailure>
5964
{
6065
auto maybe_srv = presentation.makeServer<Service>();
@@ -63,7 +68,7 @@ class ExecCmdProvider // NOSONAR cpp:S4963
6368
return std::move(*failure);
6469
}
6570

66-
return Derived{node, presentation, cetl::get<Server>(std::move(maybe_srv))};
71+
return Derived{node, presentation, time_provider, cetl::get<Server>(std::move(maybe_srv))};
6772
}
6873

6974
ExecCmdProvider(ExecCmdProvider&& other) noexcept
@@ -96,16 +101,19 @@ class ExecCmdProvider // NOSONAR cpp:S4963
96101
/// The user should override the method to handle custom commands.
97102
/// If the method returns `false`, the server will respond with a `STATUS_BAD_COMMAND` status.
98103
///
99-
/// @param command The command to be executed.
100-
/// @param parameter The command parameter.
101-
/// @param response The response to be sent back to the requester.
104+
/// @param command The command to be executed.
105+
/// @param parameter The command parameter.
106+
/// @param metadata The transport RX metadata.
107+
/// @param response The response to be sent back to the requester.
102108
///
103-
virtual bool onCommand(const Request::_traits_::TypeOf::command command,
104-
const cetl::string_view parameter,
105-
Response& response) noexcept
109+
virtual bool onCommand(const Request::_traits_::TypeOf::command command,
110+
const cetl::string_view parameter,
111+
const libcyphal::transport::ServiceRxMetadata& metadata,
112+
Response& response) noexcept
106113
{
107114
(void) command;
108115
(void) parameter;
116+
(void) metadata;
109117
(void) response;
110118

111119
return false;
@@ -126,7 +134,7 @@ class ExecCmdProvider // NOSONAR cpp:S4963
126134
server_.setOnRequestCallback([this](const auto& arg, auto continuation) {
127135
//
128136
Response response{alloc_};
129-
if (!onCommand(arg.request.command, makeStringView(arg.request.parameter), response))
137+
if (!onCommand(arg.request.command, makeStringView(arg.request.parameter), arg.metadata, response))
130138
{
131139
response.status = Response::STATUS_BAD_COMMAND;
132140
}
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
// This software is distributed under the terms of the MIT License.
2+
// Copyright (C) OpenCyphal Development Team <opencyphal.org>
3+
// Copyright Amazon.com Inc. or its affiliates.
4+
// SPDX-License-Identifier: MIT
5+
// Author: Sergei Shirokov <[email protected]>
6+
7+
#ifndef FILE_DOWNLOADER_HPP_INCLUDED
8+
#define FILE_DOWNLOADER_HPP_INCLUDED
9+
10+
#include <cetl/pf17/cetlpf.hpp>
11+
#include <libcyphal/presentation/client.hpp>
12+
#include <libcyphal/presentation/presentation.hpp>
13+
#include <libcyphal/presentation/response_promise.hpp>
14+
#include <libcyphal/time_provider.hpp>
15+
#include <libcyphal/transport/types.hpp>
16+
#include <libcyphal/types.hpp>
17+
18+
#include <uavcan/file/Error_1_0.hpp>
19+
#include <uavcan/file/Read_1_1.hpp>
20+
21+
#include <utility>
22+
23+
class FileDownloader final
24+
{
25+
public:
26+
/// Factory method to create a FileDownloader instance.
27+
///
28+
static FileDownloader make(libcyphal::presentation::Presentation& presentation,
29+
libcyphal::ITimeProvider& time_provider)
30+
{
31+
return FileDownloader{presentation, time_provider};
32+
}
33+
34+
FileDownloader(FileDownloader&& other) noexcept
35+
: presentation_{other.presentation_}
36+
, time_provider_{other.time_provider_}
37+
, read_client_{std::move(other.read_client_)}
38+
, response_promise_{std::move(other.response_promise_)}
39+
, current_request_{std::move(other.current_request_)}
40+
{
41+
}
42+
43+
~FileDownloader() = default;
44+
45+
FileDownloader(const FileDownloader&) = delete;
46+
FileDownloader& operator=(const FileDownloader&) = delete;
47+
FileDownloader& operator=(FileDownloader&&) noexcept = delete;
48+
49+
bool start(const libcyphal::transport::NodeId remote_node_id, const cetl::string_view file_path)
50+
{
51+
response_promise_.reset();
52+
read_client_.reset();
53+
54+
auto maybe_client = presentation_.makeClient<ReadService>(remote_node_id);
55+
if (const auto* const failure = cetl::get_if<Presentation::MakeFailure>(&maybe_client))
56+
{
57+
(void) failure;
58+
return false;
59+
}
60+
read_client_.emplace(cetl::get<ReadClient>(std::move(maybe_client)));
61+
62+
current_request_.offset = 0;
63+
current_request_.path.path = {file_path.begin(), file_path.end(), &presentation_.memory()};
64+
65+
return initiateNextRequest();
66+
}
67+
68+
private:
69+
using Presentation = libcyphal::presentation::Presentation;
70+
71+
using ReadService = uavcan::file::Read_1_1;
72+
using ReadClient = libcyphal::presentation::ServiceClient<ReadService>;
73+
using ResponsePromise = libcyphal::presentation::ResponsePromise<ReadService::Response>;
74+
using ResponseData = ReadService::Response::_traits_::TypeOf::data;
75+
76+
FileDownloader(Presentation& presentation, libcyphal::ITimeProvider& time_provider)
77+
: presentation_{presentation}
78+
, time_provider_{time_provider}
79+
{
80+
}
81+
82+
bool initiateNextRequest()
83+
{
84+
response_promise_.reset();
85+
86+
constexpr auto timeout = std::chrono::seconds{1};
87+
auto maybe_promise = read_client_->request(time_provider_.now() + timeout, current_request_);
88+
if (const auto* const failure = cetl::get_if<ReadClient::Failure>(&maybe_promise))
89+
{
90+
(void) failure;
91+
read_client_.reset();
92+
return false;
93+
}
94+
response_promise_.emplace(cetl::get<ResponsePromise>(std::move(maybe_promise)));
95+
response_promise_->setCallback([this](const auto& args) {
96+
//
97+
handlePromiseResult(std::move(args.result));
98+
});
99+
100+
return true;
101+
}
102+
103+
void handlePromiseResult(ResponsePromise::Result result)
104+
{
105+
if (const auto* const success = cetl::get_if<ResponsePromise::Success>(&result))
106+
{
107+
const auto& response = success->response;
108+
if (response._error.value == uavcan::file::Error_1_0::OK)
109+
{
110+
const auto data_size = response.data.value.size();
111+
current_request_.offset += response.data.value.size();
112+
113+
// Are we done?
114+
if (data_size == ResponseData::_traits_::ArrayCapacity::value)
115+
{
116+
initiateNextRequest();
117+
return;
118+
}
119+
}
120+
}
121+
std::cout << "Download complete\n";
122+
response_promise_.reset();
123+
read_client_.reset();
124+
}
125+
126+
// MARK: Data members:
127+
128+
Presentation& presentation_;
129+
libcyphal::ITimeProvider& time_provider_;
130+
cetl::optional<ReadClient> read_client_;
131+
cetl::optional<ResponsePromise> response_promise_;
132+
ReadService::Request current_request_{&presentation_.memory()};
133+
134+
}; // FileDownloader
135+
136+
#endif // FILE_DOWNLOADER_HPP_INCLUDED

libcyphal_demo/src/main.cpp

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,16 @@
66

77
#include "application.hpp"
88
#include "exec_cmd_provider.hpp"
9+
#include "file_downloader.hpp"
910
#include "transport_bag_can.hpp"
1011
#include "transport_bag_udp.hpp"
1112

1213
#include <cetl/pf17/cetlpf.hpp>
1314
#include <libcyphal/application/node.hpp>
1415
#include <libcyphal/presentation/presentation.hpp>
16+
#include <libcyphal/time_provider.hpp>
1517
#include <libcyphal/transport/transport.hpp>
18+
#include <libcyphal/transport/types.hpp>
1619
#include <libcyphal/types.hpp>
1720

1821
#include <uavcan/node/Health_1_0.hpp>
@@ -21,11 +24,10 @@
2124
#include <algorithm>
2225
#include <array>
2326
#include <cstdint>
24-
#include <cstring>
2527
#include <iomanip>
2628
#include <ios>
2729
#include <iostream>
28-
#include <unistd.h> // execve
30+
#include <unistd.h>
2931
#include <utility>
3032

3133
using namespace std::chrono_literals;
@@ -38,11 +40,14 @@ namespace
3840
class AppExecCmdProvider final : public ExecCmdProvider<AppExecCmdProvider>
3941
{
4042
public:
41-
AppExecCmdProvider(libcyphal::application::Node& node,
42-
const libcyphal::presentation::Presentation& presentation,
43-
Server&& server)
43+
AppExecCmdProvider(libcyphal::application::Node& node,
44+
libcyphal::presentation::Presentation& presentation,
45+
libcyphal::ITimeProvider& time_provider,
46+
Server&& server)
4447
: ExecCmdProvider{presentation, std::move(server)}
4548
, node_{node}
49+
, presentation_{presentation}
50+
, time_provider_{time_provider}
4651
{
4752
}
4853

@@ -57,9 +62,10 @@ class AppExecCmdProvider final : public ExecCmdProvider<AppExecCmdProvider>
5762
}
5863

5964
private:
60-
bool onCommand(const Request::_traits_::TypeOf::command command,
61-
const cetl::string_view parameter,
62-
Response& response) noexcept override
65+
bool onCommand(const Request::_traits_::TypeOf::command command,
66+
const cetl::string_view parameter,
67+
const libcyphal::transport::ServiceRxMetadata& metadata,
68+
Response& response) noexcept override
6369
{
6470
response.status = Response::STATUS_SUCCESS;
6571

@@ -92,17 +98,23 @@ class AppExecCmdProvider final : public ExecCmdProvider<AppExecCmdProvider>
9298
//
9399
std::cout << "🚧 COMMAND_BEGIN_SOFTWARE_UPDATE (file='" << parameter << "')\n";
94100
node_.heartbeatProducer().message().mode.value = uavcan::node::Mode_1_0::SOFTWARE_UPDATE;
101+
102+
file_downloader_.emplace(FileDownloader::make(presentation_, time_provider_));
103+
file_downloader_->start(metadata.remote_node_id, parameter);
95104
break;
96105

97106
default:
98-
return ExecCmdProvider::onCommand(command, parameter, response);
107+
return ExecCmdProvider::onCommand(command, parameter, metadata, response);
99108
}
100109
return true;
101110
}
102111

103-
libcyphal::application::Node& node_;
104-
bool should_power_off_{false};
105-
bool restart_required_{false};
112+
libcyphal::application::Node& node_;
113+
libcyphal::presentation::Presentation& presentation_;
114+
libcyphal::ITimeProvider& time_provider_;
115+
cetl::optional<FileDownloader> file_downloader_;
116+
bool should_power_off_{false};
117+
bool restart_required_{false};
106118

107119
}; // AppExecCmdProvider
108120

@@ -172,6 +184,7 @@ libcyphal::Expected<bool, ExitCode> run_application(const char* const root_path)
172184
auto maybe_node = libcyphal::application::Node::make(presentation);
173185
if (const auto* failure = cetl::get_if<libcyphal::application::Node::MakeFailure>(&maybe_node))
174186
{
187+
(void) failure;
175188
std::cerr << "❌ Failed to create node (iface='"
176189
<< static_cast<cetl::string_view>(iface_params.udp_iface.value()) << "').\n";
177190
return ExitCode::NodeCreationFailure;
@@ -211,9 +224,10 @@ libcyphal::Expected<bool, ExitCode> run_application(const char* const root_path)
211224

212225
// 6. Bring up the command execution provider.
213226
//
214-
auto maybe_exec_cmd_provider = AppExecCmdProvider::make(node, presentation);
227+
auto maybe_exec_cmd_provider = AppExecCmdProvider::make(node, presentation, executor);
215228
if (const auto* failure = cetl::get_if<libcyphal::application::Node::MakeFailure>(&maybe_exec_cmd_provider))
216229
{
230+
(void) failure;
217231
std::cerr << "❌ Failed to create exec cmd provider.\n";
218232
return ExitCode::ExecCmdProviderCreationFailure;
219233
}

0 commit comments

Comments
 (0)