diff --git a/libcyphal_demo/src/application.cpp b/libcyphal_demo/src/application.cpp index 0949d02..edd765e 100644 --- a/libcyphal_demo/src/application.cpp +++ b/libcyphal_demo/src/application.cpp @@ -23,7 +23,7 @@ namespace { -constexpr std::size_t HeapSize = 32ULL * 1024ULL; +constexpr std::size_t HeapSize = 128ULL * 1024ULL; alignas(O1HEAP_ALIGNMENT) std::array s_heap_arena{}; // NOLINT constexpr std::size_t BlockHeapSize = 128ULL * 1024ULL; diff --git a/libcyphal_demo/src/file_downloader.hpp b/libcyphal_demo/src/file_downloader.hpp index fbd5049..6a48fca 100644 --- a/libcyphal_demo/src/file_downloader.hpp +++ b/libcyphal_demo/src/file_downloader.hpp @@ -37,19 +37,8 @@ class FileDownloader final return FileDownloader{presentation, time_provider}; } - FileDownloader(FileDownloader&& other) noexcept - : presentation_{other.presentation_} - , time_provider_{other.time_provider_} - , get_info_client_{std::move(other.get_info_client_)} - , get_info_promise_{std::move(other.get_info_promise_)} - , read_client_{std::move(other.read_client_)} - , read_promise_{std::move(other.read_promise_)} - , read_request_{std::move(other.read_request_)} - , file_stats_{other.file_stats_} - { - } - - ~FileDownloader() = default; + FileDownloader(FileDownloader&& other) noexcept = default; + ~FileDownloader() = default; FileDownloader(const FileDownloader&) = delete; FileDownloader& operator=(const FileDownloader&) = delete; @@ -62,33 +51,25 @@ class FileDownloader final read_promise_.reset(); read_client_.reset(); - file_stats_.file_size = 0; - file_stats_.file_progress = 0; - file_stats_.file_error.value = uavcan::file::Error_1_0::OK; + state_.file_size = 0; + state_.file_progress = 0; + state_.file_path = file_path; + state_.start_time = time_provider_.now(); + state_.file_error.value = uavcan::file::Error_1_0::OK; get_info_client_ = makeClient("GetInfo", remote_node_id); read_client_ = makeClient("Read", remote_node_id); if (!get_info_client_ || !read_client_) { - file_stats_.file_error.value = uavcan::file::Error_1_0::UNKNOWN_ERROR; + state_.file_error.value = uavcan::file::Error_1_0::UNKNOWN_ERROR; return false; } read_request_.offset = 0; - file_stats_.start_time = time_provider_.now(); read_request_.path.path = {file_path.begin(), file_path.end(), &presentation_.memory()}; std::cout << "Getting file info (path='" << file_path << "')...\n"; - Svc::GetInfo::Request gi_request{&presentation_.memory()}; - gi_request.path.path = {file_path.begin(), file_path.end(), &presentation_.memory()}; - return makeRequest("GetInfo", - get_info_client_, - get_info_promise_, - gi_request, - [this](const auto& arg) { - // - handleGetInfoPromiseResult(arg.result); - }); + return initiateGetInfoRequest(); } private: @@ -111,14 +92,16 @@ class FileDownloader final }; // Svc - struct FileStats + struct State { + std::string file_path; uavcan::file::Error_1_0 file_error; libcyphal::TimePoint start_time; std::size_t file_size{0}; std::size_t file_progress{0}; + int failed_requests{0}; - }; // FileStats + }; // State FileDownloader(Presentation& presentation, libcyphal::ITimeProvider& time_provider) : presentation_{presentation} @@ -155,7 +138,7 @@ class FileDownloader final if (const auto* const failure = cetl::get_if(&maybe_promise)) { (void) failure; - file_stats_.file_error.value = uavcan::file::Error_1_0::UNKNOWN_ERROR; + state_.file_error.value = uavcan::file::Error_1_0::UNKNOWN_ERROR; std::cerr << "Can't make '" << role << "' request.\n"; complete(); return false; @@ -166,17 +149,41 @@ class FileDownloader final return true; } + bool initiateGetInfoRequest() + { + Svc::GetInfo::Request request{&presentation_.memory()}; + request.path.path = {state_.file_path.begin(), state_.file_path.end(), &presentation_.memory()}; + return makeRequest("GetInfo", + get_info_client_, + get_info_promise_, + request, + [this](const auto& arg) { + // + handleGetInfoPromiseResult(arg.result); + }); + } + void handleGetInfoPromiseResult(const Svc::GetInfo::Promise::Result& result) { if (const auto* const failure = cetl::get_if(&result)) { (void) failure; - file_stats_.file_error.value = uavcan::file::Error_1_0::UNKNOWN_ERROR; - std::cerr << "GetInfo request failed.\n"; - complete(); + state_.failed_requests++; + if (state_.failed_requests >= MaxRetriesOnRequestFailure) + { + std::cerr << "'GetInfo' request failed (times=" << state_.failed_requests << ").\n"; + state_.file_error.value = uavcan::file::Error_1_0::UNKNOWN_ERROR; + complete(); + } + else + { + std::cerr << "'GetInfo' request failed (times=" << state_.failed_requests << "). Retrying…\n"; + initiateGetInfoRequest(); + } return; } - const auto success = cetl::get(result); + state_.failed_requests = 0; + const auto success = cetl::get(result); get_info_promise_.reset(); get_info_client_.reset(); @@ -184,22 +191,22 @@ class FileDownloader final const auto& response = success.response; if (response._error.value == uavcan::file::Error_1_0::OK) { - file_stats_.file_size = response.size; - std::cout << "Downloading (size=" << file_stats_.file_size << ") ...\n"; - if (file_stats_.file_size > 0) + state_.file_size = response.size; + std::cout << "Downloading (size=" << state_.file_size << ") ...\n"; + if (state_.file_size > 0) { - file_stats_.start_time = time_provider_.now(); + state_.start_time = time_provider_.now(); printProgress(); initiateNextReadRequest(); return; } - file_stats_.file_error.value = uavcan::file::Error_1_0::OK; + state_.file_error.value = uavcan::file::Error_1_0::OK; } else { - file_stats_.file_error = response._error; + state_.file_error = response._error; std::cerr << "Can't get file info (err=" << response._error.value << ").\n"; } @@ -219,11 +226,22 @@ class FileDownloader final if (const auto* const failure = cetl::get_if(&result)) { (void) failure; - file_stats_.file_error.value = uavcan::file::Error_1_0::UNKNOWN_ERROR; - std::cerr << "Read request failed.\n"; - complete(); + state_.failed_requests++; + if (state_.failed_requests >= MaxRetriesOnRequestFailure) + { + std::cerr << "'Read' request failed (times=" << state_.failed_requests << ").\n"; + state_.file_error.value = uavcan::file::Error_1_0::UNKNOWN_ERROR; + complete(); + } + else + { + std::cerr << "'Read' request failed (times=" << state_.failed_requests << "). Retrying…\n"; + initiateNextReadRequest(); + } return; } + state_.failed_requests = 0; + const auto success = cetl::get(std::move(result)); const auto& response = success.response; @@ -243,7 +261,7 @@ class FileDownloader final } else { - file_stats_.file_error = response._error; + state_.file_error = response._error; std::cerr << "Can't read file (err=" << response._error.value << ").\n"; } complete(); @@ -251,17 +269,17 @@ class FileDownloader final void printProgress() { - CETL_DEBUG_ASSERT(file_stats_.file_size > 0, ""); - CETL_DEBUG_ASSERT(read_request_.offset <= file_stats_.file_size, ""); + CETL_DEBUG_ASSERT(state_.file_size > 0, ""); + CETL_DEBUG_ASSERT(read_request_.offset <= state_.file_size, ""); - const auto progress = (read_request_.offset * 100U) / file_stats_.file_size; + const auto progress = (read_request_.offset * 100U) / state_.file_size; CETL_DEBUG_ASSERT(progress <= 100U, ""); // Print progress only if its integer % has changed (or in the beginning). - if ((progress != file_stats_.file_progress) || (read_request_.offset == 0)) + if ((progress != state_.file_progress) || (read_request_.offset == 0)) { - file_stats_.file_progress = progress; - const auto duration = time_provider_.now() - file_stats_.start_time; + state_.file_progress = progress; + const auto duration = time_provider_.now() - state_.start_time; if (const auto duration_us = std::chrono::duration_cast(duration).count()) { const auto speed_kb_per_sec = (read_request_.offset * 1000000U) / (duration_us * 1024U); @@ -277,9 +295,9 @@ class FileDownloader final void complete() { - const auto duration = time_provider_.now() - file_stats_.start_time; - std::cout << "\nDownload completed (err=" << file_stats_.file_error.value // - << ", time=" << std::fixed << std::setprecision(6) // NOLINT + const auto duration = time_provider_.now() - state_.start_time; + std::cout << "\nDownload completed (err=" << state_.file_error.value // + << ", time=" << std::fixed << std::setprecision(6) // NOLINT << std::chrono::duration_cast>(duration).count() << "s).\n" << std::flush; @@ -291,6 +309,8 @@ class FileDownloader final // MARK: Data members: + static constexpr int MaxRetriesOnRequestFailure = 10; + Presentation& presentation_; libcyphal::ITimeProvider& time_provider_; cetl::optional get_info_client_; @@ -298,7 +318,7 @@ class FileDownloader final cetl::optional read_client_; cetl::optional read_promise_; Svc::Read::Request read_request_{&presentation_.memory()}; - FileStats file_stats_; + State state_; }; // FileDownloader diff --git a/libcyphal_demo/src/transport_bag_can.hpp b/libcyphal_demo/src/transport_bag_can.hpp index 7c5dcad..f146f3f 100644 --- a/libcyphal_demo/src/transport_bag_can.hpp +++ b/libcyphal_demo/src/transport_bag_can.hpp @@ -65,14 +65,17 @@ struct TransportBagCan final const std::size_t block_size = mtu; const std::size_t pool_size = media_collection_.count() * TxQueueCapacity * block_size; media_block_mr_.setup(pool_size, block_size, block_alignment); - - transport_->setTransientErrorHandler(platform::CommonHelpers::Can::transientErrorReporter); + transport_->setTransientErrorHandler([](auto&) { return cetl::nullopt; }); + // transport_->setTransientErrorHandler(platform::CommonHelpers::Can::transientErrorReporter); return transport_.get(); } private: - static constexpr std::size_t TxQueueCapacity = 16; + // Our current max `SerializationBufferSizeBytes` is 515 bytes (for `uavcan.register.Access.Request.1.0`) + // Assuming CAN classic presentation MTU of 7 bytes (plus a bit of overhead like CRC and stuff), + // let's calculate the required TX queue capacity, and make it twice to accommodate 2 such messages. + static constexpr std::size_t TxQueueCapacity = 2 * (515U + 8U) / 7U; cetl::pmr::memory_resource& general_mr_; libcyphal::IExecutor& executor_; diff --git a/submodules/libcyphal b/submodules/libcyphal index 3d0320d..a3be9d3 160000 --- a/submodules/libcyphal +++ b/submodules/libcyphal @@ -1 +1 @@ -Subproject commit 3d0320dee3c64f59d8773bdc39e5bc3318bb40d8 +Subproject commit a3be9d3eef99c64d31adf23955ca2046c9a79f3b