|
| 1 | +// |
| 2 | +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. |
| 3 | +// SPDX-License-Identifier: MIT |
| 4 | +// |
| 5 | + |
| 6 | +#ifndef OCVSMD_DAEMON_ENGINE_CYPHAL_CAN_TRANSPORT_BAG_HPP_INCLUDED |
| 7 | +#define OCVSMD_DAEMON_ENGINE_CYPHAL_CAN_TRANSPORT_BAG_HPP_INCLUDED |
| 8 | + |
| 9 | +#include "any_transport_bag.hpp" |
| 10 | +#include "config.hpp" |
| 11 | +#include "platform/can/can_media.hpp" |
| 12 | +#include "transport_helpers.hpp" |
| 13 | + |
| 14 | +#include <cetl/pf17/cetlpf.hpp> |
| 15 | +#include <libcyphal/executor.hpp> |
| 16 | +#include <libcyphal/transport/can/can_transport.hpp> |
| 17 | +#include <libcyphal/transport/can/can_transport_impl.hpp> |
| 18 | +#include <libcyphal/transport/errors.hpp> |
| 19 | +#include <libcyphal/types.hpp> |
| 20 | + |
| 21 | +#include <cstddef> |
| 22 | +#include <memory> |
| 23 | +#include <utility> |
| 24 | + |
| 25 | +namespace ocvsmd |
| 26 | +{ |
| 27 | +namespace daemon |
| 28 | +{ |
| 29 | +namespace engine |
| 30 | +{ |
| 31 | +namespace cyphal |
| 32 | +{ |
| 33 | + |
| 34 | +/// Holds (internally) instance of the CAN transport and its media (if any). |
| 35 | +/// |
| 36 | +class CanTransportBag final : public AnyTransportBag |
| 37 | +{ |
| 38 | + /// Defines private specification for making interface unique ptr. |
| 39 | + /// |
| 40 | + struct Spec |
| 41 | + { |
| 42 | + explicit Spec() = default; |
| 43 | + }; |
| 44 | + |
| 45 | +public: |
| 46 | + Transport& getTransport() const override |
| 47 | + { |
| 48 | + CETL_DEBUG_ASSERT(transport_, ""); |
| 49 | + return *transport_; |
| 50 | + } |
| 51 | + |
| 52 | + static Ptr make(cetl::pmr::memory_resource& memory, libcyphal::IExecutor& executor, const Config::Ptr& config) |
| 53 | + { |
| 54 | + CETL_DEBUG_ASSERT(config, ""); |
| 55 | + |
| 56 | + static const std::string can_prefix{"socketcan:"}; |
| 57 | + |
| 58 | + std::string can_ifaces; |
| 59 | + for (const auto& iface : config->getCyphalTransportInterfaces()) |
| 60 | + { |
| 61 | + if (0 == iface.compare(0, can_prefix.size(), can_prefix)) |
| 62 | + { |
| 63 | + can_ifaces += iface.substr(can_prefix.size()); |
| 64 | + can_ifaces += ","; |
| 65 | + } |
| 66 | + } |
| 67 | + common::getLogger("io")->trace("Attempting to create CAN transport (ifaces='{}')…", can_ifaces); |
| 68 | + |
| 69 | + auto transport_bag = std::make_unique<CanTransportBag>(Spec{}, memory, executor); |
| 70 | + |
| 71 | + auto& media_collection = transport_bag->media_collection_; |
| 72 | + media_collection.parse(can_ifaces); |
| 73 | + if (media_collection.count() == 0) |
| 74 | + { |
| 75 | + return nullptr; |
| 76 | + } |
| 77 | + |
| 78 | + auto maybe_transport = makeTransport({memory}, executor, media_collection.span(), TxQueueCapacity); |
| 79 | + if (const auto* failure = cetl::get_if<libcyphal::transport::FactoryFailure>(&maybe_transport)) |
| 80 | + { |
| 81 | + (void) failure; |
| 82 | + common::getLogger("io")->warn("Failed to create CAN transport."); |
| 83 | + return nullptr; |
| 84 | + } |
| 85 | + transport_bag->transport_ = cetl::get<TransportPtr>(std::move(maybe_transport)); |
| 86 | + |
| 87 | + // To support redundancy (multiple homogeneous interfaces), it's important to have a non-default |
| 88 | + // handler which "swallows" expected transient failures (by returning `nullopt` result). |
| 89 | + // Otherwise, the default Cyphal behavior will fail/interrupt current and future transfers |
| 90 | + // if some of its media encounter transient failures - thus breaking the whole redundancy goal, |
| 91 | + // namely, maintain communication if at least one of the interfaces is still up and running. |
| 92 | + // |
| 93 | + transport_bag->transport_->setTransientErrorHandler([](auto&) { return cetl::nullopt; }); |
| 94 | + // transport_bag->transport_->setTransientErrorHandler(TransportHelpers::CanTransientErrorReporter{}); |
| 95 | + |
| 96 | + common::getLogger("io")->debug("Created CAN transport (ifaces={}).", media_collection.count()); |
| 97 | + return transport_bag; |
| 98 | + } |
| 99 | + |
| 100 | + CanTransportBag(Spec, cetl::pmr::memory_resource& memory, libcyphal::IExecutor& executor) |
| 101 | + : memory_{memory} |
| 102 | + , executor_{executor} |
| 103 | + , media_collection_{memory, executor, memory} |
| 104 | + { |
| 105 | + } |
| 106 | + |
| 107 | +private: |
| 108 | + using TransportPtr = libcyphal::UniquePtr<libcyphal::transport::can::ICanTransport>; |
| 109 | + |
| 110 | + // Our current max `SerializationBufferSizeBytes` is 313 bytes (for `uavcan.node.GetInfo.Response.1.0`) |
| 111 | + // Assuming CAN classic presentation MTU of 7 bytes (plus a bit of overhead like CRC and stuff), |
| 112 | + // let's calculate the required TX queue capacity, and make it twice to accommodate 2 such messages. |
| 113 | + static constexpr std::size_t TxQueueCapacity = 2 * (313U + 8U) / 7U; |
| 114 | + |
| 115 | + cetl::pmr::memory_resource& memory_; |
| 116 | + libcyphal::IExecutor& executor_; |
| 117 | + platform::can::CanMediaCollection media_collection_; |
| 118 | + TransportPtr transport_; |
| 119 | + |
| 120 | +}; // CanTransportBag |
| 121 | + |
| 122 | +} // namespace cyphal |
| 123 | +} // namespace engine |
| 124 | +} // namespace daemon |
| 125 | +} // namespace ocvsmd |
| 126 | + |
| 127 | +#endif // OCVSMD_DAEMON_ENGINE_CYPHAL_CAN_TRANSPORT_BAG_HPP_INCLUDED |
0 commit comments