Skip to content

Commit 6ed86a4

Browse files
committed
added GetInfo request, % progress and speed Kb/s.
1 parent 26f6894 commit 6ed86a4

File tree

2 files changed

+210
-45
lines changed

2 files changed

+210
-45
lines changed

libcyphal_demo/src/file_downloader.hpp

Lines changed: 208 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,13 @@
1616
#include <libcyphal/types.hpp>
1717

1818
#include <uavcan/file/Error_1_0.hpp>
19+
#include <uavcan/file/GetInfo_0_2.hpp>
1920
#include <uavcan/file/Read_1_1.hpp>
21+
#include <uavcan/primitive/Unstructured_1_0.hpp>
2022

23+
#include <chrono>
24+
#include <cstddef>
25+
#include <iostream>
2126
#include <utility>
2227

2328
class FileDownloader final
@@ -34,9 +39,12 @@ class FileDownloader final
3439
FileDownloader(FileDownloader&& other) noexcept
3540
: presentation_{other.presentation_}
3641
, time_provider_{other.time_provider_}
42+
, get_info_client_{std::move(other.get_info_client_)}
43+
, get_info_promise_{std::move(other.get_info_promise_)}
3744
, read_client_{std::move(other.read_client_)}
38-
, response_promise_{std::move(other.response_promise_)}
39-
, current_request_{std::move(other.current_request_)}
45+
, read_promise_{std::move(other.read_promise_)}
46+
, read_request_{std::move(other.read_request_)}
47+
, file_stats_{other.file_stats_}
4048
{
4149
}
4250

@@ -48,88 +56,243 @@ class FileDownloader final
4856

4957
bool start(const libcyphal::transport::NodeId remote_node_id, const cetl::string_view file_path)
5058
{
51-
response_promise_.reset();
59+
get_info_promise_.reset();
60+
get_info_client_.reset();
61+
read_promise_.reset();
5262
read_client_.reset();
5363

54-
auto maybe_client = presentation_.makeClient<ReadService>(remote_node_id);
55-
if (const auto* const failure = cetl::get_if<Presentation::MakeFailure>(&maybe_client))
64+
file_stats_.file_size = 0;
65+
file_stats_.file_progress = 0;
66+
file_stats_.file_error.value = uavcan::file::Error_1_0::OK;
67+
68+
get_info_client_ = makeClient<Svc::GetInfo>("GetInfo", remote_node_id);
69+
read_client_ = makeClient<Svc::Read>("Read", remote_node_id);
70+
if (!get_info_client_ || !read_client_)
5671
{
57-
(void) failure;
72+
file_stats_.file_error.value = uavcan::file::Error_1_0::UNKNOWN_ERROR;
5873
return false;
5974
}
60-
read_client_.emplace(cetl::get<ReadClient>(std::move(maybe_client)));
6175

62-
current_request_.offset = 0;
63-
current_request_.path.path = {file_path.begin(), file_path.end(), &presentation_.memory()};
76+
read_request_.offset = 0;
77+
read_request_.path.path = {file_path.begin(), file_path.end(), &presentation_.memory()};
6478

65-
return initiateNextRequest();
79+
std::cout << "Getting file info (path='" << file_path << "')...\n";
80+
Svc::GetInfo::Request gi_request{&presentation_.memory()};
81+
gi_request.path.path = {file_path.begin(), file_path.end(), &presentation_.memory()};
82+
return makeRequest<Svc::GetInfo>("GetInfo",
83+
get_info_client_,
84+
get_info_promise_,
85+
gi_request,
86+
[this](const auto& arg) {
87+
//
88+
handleGetInfoPromiseResult(arg.result);
89+
});
6690
}
6791

6892
private:
69-
using Presentation = libcyphal::presentation::Presentation;
93+
using NodeId = libcyphal::transport::NodeId;
94+
using Presentation = libcyphal::presentation::Presentation;
95+
using UnstructuredData = uavcan::primitive::Unstructured_1_0;
96+
using ResponsePromiseFailure = libcyphal::presentation::ResponsePromiseFailure;
97+
98+
template <typename T>
99+
struct SvcSpec : T
100+
{
101+
using Client = libcyphal::presentation::ServiceClient<T>;
102+
using Promise = libcyphal::presentation::ResponsePromise<typename T::Response>;
103+
using Failure = typename Client::Failure;
104+
};
105+
struct Svc
106+
{
107+
using Read = SvcSpec<uavcan::file::Read_1_1>;
108+
using GetInfo = SvcSpec<uavcan::file::GetInfo_0_2>;
109+
110+
}; // Svc
111+
112+
struct FileStats
113+
{
114+
uavcan::file::Error_1_0 file_error;
115+
libcyphal::TimePoint start_time;
116+
std::size_t file_size{0};
117+
std::size_t file_progress{0};
70118

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;
119+
}; // FileStats
75120

76121
FileDownloader(Presentation& presentation, libcyphal::ITimeProvider& time_provider)
77122
: presentation_{presentation}
78123
, time_provider_{time_provider}
79124
{
80125
}
81126

82-
bool initiateNextRequest()
127+
template <typename Service>
128+
auto makeClient(const cetl::string_view role, const NodeId server_node_id)
129+
-> cetl::optional<typename Service::Client>
130+
{
131+
auto maybe_client = presentation_.makeClient<Service>(server_node_id);
132+
if (const auto* const failure = cetl::get_if<Presentation::MakeFailure>(&maybe_client))
133+
{
134+
(void) failure;
135+
std::cerr << "Can't make '" << role << "' client.\n";
136+
return cetl::nullopt;
137+
}
138+
return cetl::get<typename Service::Client>(std::move(maybe_client));
139+
}
140+
141+
template <typename Service, typename Handler>
142+
bool makeRequest( //
143+
const cetl::string_view role,
144+
cetl::optional<typename Service::Client>& client,
145+
cetl::optional<typename Service::Promise>& promise,
146+
const typename Service::Request& request,
147+
Handler&& handler,
148+
const libcyphal::Duration timeout = std::chrono::seconds{1})
83149
{
84-
response_promise_.reset();
150+
promise.reset();
85151

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))
152+
auto maybe_promise = client->request(time_provider_.now() + timeout, request);
153+
if (const auto* const failure = cetl::get_if<typename Service::Failure>(&maybe_promise))
89154
{
90155
(void) failure;
91-
read_client_.reset();
156+
file_stats_.file_error.value = uavcan::file::Error_1_0::UNKNOWN_ERROR;
157+
std::cerr << "Can't make '" << role << "' request.\n";
158+
complete();
92159
return false;
93160
}
94-
response_promise_.emplace(cetl::get<ResponsePromise>(std::move(maybe_promise)));
95-
response_promise_->setCallback([this](const auto& args) {
161+
162+
promise.emplace(cetl::get<typename Service::Promise>(std::move(maybe_promise)));
163+
promise->setCallback(std::forward<Handler>(handler));
164+
return true;
165+
}
166+
167+
void handleGetInfoPromiseResult(const Svc::GetInfo::Promise::Result& result)
168+
{
169+
if (const auto* const failure = cetl::get_if<ResponsePromiseFailure>(&result))
170+
{
171+
(void) failure;
172+
file_stats_.file_error.value = uavcan::file::Error_1_0::UNKNOWN_ERROR;
173+
std::cerr << "GetInfo request failed.\n";
174+
complete();
175+
return;
176+
}
177+
const auto success = cetl::get<Svc::GetInfo::Promise::Success>(result);
178+
179+
get_info_promise_.reset();
180+
get_info_client_.reset();
181+
182+
const auto& response = success.response;
183+
if (response._error.value == uavcan::file::Error_1_0::OK)
184+
{
185+
file_stats_.file_size = response.size;
186+
std::cout << "Downloading (size=" << file_stats_.file_size << ") ...\n";
187+
if (file_stats_.file_size > 0)
188+
{
189+
file_stats_.start_time = time_provider_.now();
190+
191+
printProgress();
192+
initiateNextReadRequest();
193+
return;
194+
}
195+
196+
file_stats_.file_error.value = uavcan::file::Error_1_0::OK;
197+
}
198+
else
199+
{
200+
file_stats_.file_error = response._error;
201+
std::cerr << "Can't get file info (err=" << response._error.value << ").\n";
202+
}
203+
204+
complete();
205+
}
206+
207+
bool initiateNextReadRequest()
208+
{
209+
return makeRequest<Svc::Read>("Read", read_client_, read_promise_, read_request_, [this](const auto& arg) {
96210
//
97-
handlePromiseResult(std::move(args.result));
211+
handleReadPromiseResult(arg.result);
98212
});
213+
}
99214

100-
return true;
215+
void handleReadPromiseResult(Svc::Read::Promise::Result result)
216+
{
217+
if (const auto* const failure = cetl::get_if<ResponsePromiseFailure>(&result))
218+
{
219+
(void) failure;
220+
file_stats_.file_error.value = uavcan::file::Error_1_0::UNKNOWN_ERROR;
221+
std::cerr << "Read request failed.\n";
222+
complete();
223+
return;
224+
}
225+
const auto success = cetl::get<Svc::Read::Promise::Success>(std::move(result));
226+
227+
const auto& response = success.response;
228+
if (response._error.value == uavcan::file::Error_1_0::OK)
229+
{
230+
const auto data_size = response.data.value.size();
231+
read_request_.offset += response.data.value.size();
232+
233+
printProgress();
234+
235+
// Are we done?
236+
if (data_size == UnstructuredData::_traits_::ArrayCapacity::value)
237+
{
238+
initiateNextReadRequest();
239+
return;
240+
}
241+
}
242+
else
243+
{
244+
file_stats_.file_error = response._error;
245+
std::cerr << "Can't read file (err=" << response._error.value << ").\n";
246+
}
247+
complete();
101248
}
102249

103-
void handlePromiseResult(ResponsePromise::Result result)
250+
void printProgress()
104251
{
105-
if (const auto* const success = cetl::get_if<ResponsePromise::Success>(&result))
252+
CETL_DEBUG_ASSERT(file_stats_.file_size > 0, "");
253+
CETL_DEBUG_ASSERT(read_request_.offset <= file_stats_.file_size, "");
254+
255+
const auto progress = (read_request_.offset * 100U) / file_stats_.file_size;
256+
CETL_DEBUG_ASSERT(progress <= 100U, "");
257+
258+
// Print progress only if its integer % has changed (or in the beginning).
259+
if ((progress != file_stats_.file_progress) || (read_request_.offset == 0))
106260
{
107-
const auto& response = success->response;
108-
if (response._error.value == uavcan::file::Error_1_0::OK)
261+
file_stats_.file_progress = progress;
262+
const auto duration = time_provider_.now() - file_stats_.start_time;
263+
if (const auto duration_us = std::chrono::duration_cast<std::chrono::microseconds>(duration).count())
264+
{
265+
const auto speed_kb_per_sec = (read_request_.offset * 1000000U) / (duration_us * 1024U);
266+
std::cout << "\r progress " << progress << "% (speed=" << speed_kb_per_sec << "KB/s) "
267+
<< std::flush;
268+
}
269+
else
109270
{
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-
}
271+
std::cout << "\r progress " << progress << "%" << std::flush;
119272
}
120273
}
121-
std::cout << "Download complete\n";
122-
response_promise_.reset();
274+
}
275+
276+
void complete()
277+
{
278+
std::cout << "\nDownload completed (err=" << file_stats_.file_error.value << ").\n" << std::flush;
279+
280+
get_info_promise_.reset();
281+
get_info_client_.reset();
282+
read_promise_.reset();
123283
read_client_.reset();
124284
}
125285

126286
// MARK: Data members:
127287

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()};
288+
Presentation& presentation_;
289+
libcyphal::ITimeProvider& time_provider_;
290+
cetl::optional<Svc::GetInfo::Client> get_info_client_;
291+
cetl::optional<Svc::GetInfo::Promise> get_info_promise_;
292+
cetl::optional<Svc::Read::Client> read_client_;
293+
cetl::optional<Svc::Read::Promise> read_promise_;
294+
Svc::Read::Request read_request_{&presentation_.memory()};
295+
FileStats file_stats_;
133296

134297
}; // FileDownloader
135298

libcyphal_demo/src/main.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,10 +133,12 @@ enum class ExitCode : std::uint8_t
133133

134134
void PrintUniqueIdTo(const std::array<std::uint8_t, 16>& unique_id, std::ostream& os)
135135
{
136+
const auto original_flags = os.flags();
136137
for (const auto byte : unique_id)
137138
{
138139
os << std::hex << std::setw(2) << std::setfill('0') << static_cast<std::uint32_t>(byte);
139140
}
141+
os.flags(original_flags);
140142
}
141143

142144
libcyphal::Expected<bool, ExitCode> run_application(const char* const root_path)

0 commit comments

Comments
 (0)