### Custom Targets for Building and Running Examples Source: https://github.com/uavcan/libuavcan/blob/main/docs/examples/CMakeLists.txt Defines custom CMake targets for building and running all defined examples. 'build_examples' depends on all example executables, while 'run_examples' depends on their corresponding test reports. ```cmake add_custom_target( build_examples DEPENDS ${ALL_EXAMPLES} ) add_custom_target( run_examples DEPENDS ${ALL_EXAMPLE_RUNS} ) ``` -------------------------------- ### Platform-Specific Library Setup Source: https://github.com/uavcan/libuavcan/blob/main/docs/examples/CMakeLists.txt Configures platform-specific libraries for examples, including POSIX UDP and Linux SocketCAN. It conditionally adds libraries based on the operating system and links against necessary dependencies like 'canard'. ```cmake if (UNIX AND NOT (CMAKE_SYSTEM_NAME STREQUAL "Windows")) set(IS_POSIX TRUE) else () set(IS_POSIX FALSE) endif () if (UNIX AND CMAKE_SYSTEM_NAME STREQUAL "Linux") set(IS_LINUX TRUE) else () set(IS_LINUX FALSE) endif () message(STATUS "IS_POSIX=${IS_POSIX}") message(STATUS "IS_LINUX=${IS_LINUX}") set(EXAMPLES_PLATFORM_LIBS "") if (IS_POSIX) add_library(examples_platform_posix "platform/posix/udp/udp.c" ) list(APPEND EXAMPLES_PLATFORM_LIBS "examples_platform_posix") endif () if (IS_LINUX) add_library(examples_platform_linux "platform/linux/can/socketcan.c" ) target_link_libraries(examples_platform_linux PUBLIC canard) list(APPEND EXAMPLES_PLATFORM_LIBS "examples_platform_linux") endif () message(STATUS "EXAMPLES_PLATFORM_LIBS=${EXAMPLES_PLATFORM_LIBS}") ``` -------------------------------- ### Redundant Transport Configuration Example Source: https://github.com/uavcan/libuavcan/blob/main/docs/design/readme.md This diagram illustrates a redundant transport configuration using LibCANard for Cyphal/CAN interfaces and LibUDPard for Cyphal/UDP interfaces, both aggregating into a LibCyphal RedundantTransport. ```mermaid flowchart BT c00("Cyphal/CAN\n#0") --> libcanard0("LibCANard #0") c01("Cyphal/CAN\n#1") --> libcanard0 u00("Cyphal/UDP\n#0") --> libudpard0("LibUDPard #0") u01("Cyphal/UDP\n#1") --> libudpard0 libudpard0 --> red0("LibCyphal\nRedundantTransport") libcanard0 --> red0 ``` -------------------------------- ### Native Example Test Target Definition Source: https://github.com/uavcan/libuavcan/blob/main/docs/examples/CMakeLists.txt Defines native test targets for C++ examples using CMake. It uses a macro to generate unittest targets, specifying source files, extra libraries, and output variables for the library, executable, and report. ```cmake file(GLOB_RECURSE NATIVE_EXAMPLES LIST_DIRECTORIES false CONFIGURE-DEPENDS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} example_*.cpp ) set(ALL_EXAMPLES "") set(ALL_EXAMPLE_RUNS "") foreach (NATIVE_EXAMPLE ${NATIVE_EXAMPLES}) message(STATUS "Example: ${NATIVE_EXAMPLE}") # Skip linux specific examples on non-linux platforms # string(FIND "${NATIVE_EXAMPLE}" "linux" LINUX_WORD_POSITION) if ((LINUX_WORD_POSITION GREATER -1) AND NOT IS_LINUX) message(WARNING "skipping linux specific example: ${NATIVE_EXAMPLE}") continue() endif () define_native_gtest_unittest_targets( TEST_SOURCE ${NATIVE_EXAMPLE} EXTRA_TEST_LIBS cyphal cetl dsdl_support dsdl_example_types ${EXAMPLES_PLATFORM_LIBS} LINK_TO_MAIN OUT_TEST_LIB_VARIABLE LOCAL_TEST_LIB OUT_TEST_EXE_VARIABLE LOCAL_TEST_TARGET OUT_TEST_REPORT_VARIABLE LOCAL_TEST_REPORT ) target_include_directories(${LOCAL_TEST_LIB} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ) # We need to exclude the "external", "nunavut" & "uavcan" directories from coverage reports. cmake_path(APPEND LIBCYPHAL_ROOT "external" OUTPUT_VARIABLE LIBCYPHAL_EXTERNAL_PATH) cmake_path(APPEND LIBCYPHAL_ROOT "${CMAKE_INSTALL_PREFIX}/nunavut" OUTPUT_VARIABLE LIBCYPHAL_NUNAVUT_PATH) cmake_path(APPEND LIBCYPHAL_ROOT "${CMAKE_INSTALL_PREFIX}/uavcan" OUTPUT_VARIABLE LIBCYPHAL_UAVCAN_PATH) if (CMAKE_BUILD_TYPE STREQUAL "Coverage") define_gcovr_tracefile_target( TARGET ${LOCAL_TEST_TARGET} ROOT_DIRECTORY ${LIBCYPHAL_ROOT} TARGET_EXECUTION_DEPENDS ${LOCAL_TEST_REPORT} OBJECT_LIBRARY ${LOCAL_TEST_LIB} EXCLUDE_PATHS ${LIBCYPHAL_EXTERNAL_PATH} ${LIBCYPHAL_NUNAVUT_PATH} ${LIBCYPHAL_UAVCAN_PATH} EXCLUDE_TEST_FRAMEWORKS EXCLUDE_TARGET ENABLE_INSTRUMENTATION ) endif () list(APPEND ALL_EXAMPLES "${LOCAL_TEST_TARGET}") list(APPEND ALL_EXAMPLE_RUNS "${LOCAL_TEST_REPORT}") endforeach () ``` -------------------------------- ### Basic Registry Interface (IRegistry) Source: https://github.com/uavcan/libuavcan/blob/main/docs/design/readme.md Defines the fundamental interface for interacting with registers, including getting values and setting them. Assumes Value and DSDLValue types are defined elsewhere. ```c++ /// This is the main, basic interface for the application to its registers. /// The registry is just a collection of named getters[/setters]. class IRegistry { public: IRegistry() = default; virtual ~IRegistry() = default; IRegistry(const IRegistry&) = delete; IRegistry(IRegistry&&) = delete; IRegistry& operator=(const IRegistry&) = delete; IRegistry& operator=(IRegistry&&) = delete; /// Reads the current value of the register. Empty if nonexistent. /// The worst-case complexity is log(n), where n is the number of registers. [[nodiscard]] virtual std::optional get(const std::string_view name) const = 0; /// Assign the register with the specified value. /// The worst-case complexity is log(n), where n is the number of registers. [[nodiscard]] virtual std::optional set(const std::string_view name, const DSDLValue& val) = 0; }; ``` -------------------------------- ### IKeyValue Interface Definition Source: https://github.com/uavcan/libuavcan/blob/main/docs/design/readme.md Defines the interface for key-value storage, including methods for getting, putting, and dropping data. This interface is intended for power-loss tolerant storage with data integrity validation. ```C++ enum class Error : std::uint8_t { Existence, ///< Entry does not exist but should; or exists but shouldn't. API, ///< Bad API invocation (e.g., null pointer). Capacity, ///< No space left on the storage device. IO, ///< Device input/output error. Internal, ///< Internal failure in the filesystem (storage corruption or logic error). }; /// Key-value storage provides a very simple API for storing and retrieving named blobs. /// The underlying storage implementation is required to be power-loss tolerant and to /// validate data integrity per key (e.g., using CRC and such). /// This interface is fully blocking and should only be used during initialization and shutdown, /// never during normal operation. Non-blocking adapters can be built on top of it. class IKeyValue { public: IKeyValue() = default; IKeyValue(const IKeyValue&) = delete; IKeyValue(IKeyValue&&) = delete; auto operator=(const IKeyValue&) -> IKeyValue& = delete; auto operator=(IKeyValue&&) -> IKeyValue& = delete; virtual ~IKeyValue() = default; /// The return value is the number of bytes read into the buffer or the error. [[nodiscard]] virtual auto get(const std::string_view key, const std::size_t size, void* const data) const -> std::variant = 0; /// Existing data, if any, is replaced entirely. New file and its parent directories created implicitly. /// Either all or none of the data bytes are written. [[nodiscard]] virtual auto put(const std::string_view key, const std::size_t size, const void* const data) -> std::optional = 0; /// Remove key. If the key does not exist, the existence error is returned. [[nodiscard]] virtual auto drop(const std::string_view key) -> std::optional = 0; }; ``` -------------------------------- ### Demonstrating Dynamic Linking Issue with getTypeID Source: https://github.com/uavcan/libuavcan/blob/main/docs/design/readme.md This example demonstrates the problem of static address-based type identification when a shared object is dynamically linked. The output shows duplicated type IDs for 'long double' and 'void', indicating separate instantiations. ```c++ // Compile the executable with: c++ main.c++ -std=c++14 #include "typeid.hpp" // this header contains getTypeID #include #include int main() { std::cout << getTypeID() << std::endl; std::cout << getTypeID() << std::endl; reinterpret_cast(dlsym(dlopen("liblib.so", RTLD_LAZY), "execute"))(); return 0; } ``` ```c++ // Compile this file into a shared object as follows: c++ -shared -fPIC -o liblib.so lib.c++ #include "typeid.hpp" // this header contains getTypeID #include extern "C" void execute() { std::cout << getTypeID() << std::endl; std::cout << getTypeID() << std::endl; std::cout << getTypeID() << std::endl; std::cout << getTypeID() << std::endl; } ``` -------------------------------- ### Introspectable Registry Interface (IIntrospectableRegistry) Source: https://github.com/uavcan/libuavcan/blob/main/docs/design/readme.md Extends the basic registry interface with methods for introspection, such as getting register names by index and the total size. Assumes DSDLName type is defined elsewhere. ```c++ /// Extends the basic registry interface with additional methods that enable introspection. class IIntrospectableRegistry : public IRegistry { public: /// Gets the name of the register at the specified index. Empty name if the index is out of range. /// The ordering is arbitrary but stable as long as the register set is not modified. /// The worst-case complexity may be up to linear of the number of registers. /// Keep in mind that the register may cease to exist at any time (this is why the name is returned by value). [[nodiscard]] virtual DSDLName index(const std::size_t index) const = 0; /// The worst-case complexity may be up to linear of the number of registers. [[nodiscard]] virtual std::size_t size() const = 0; }; ``` -------------------------------- ### Libcyphal Demonstration Applications Structure Source: https://github.com/uavcan/libuavcan/blob/main/CONTRIBUTING.md This snippet outlines the directory structure for demonstration applications within the libcyphal project. It lists various platform-specific demonstrations like 'linux-posix', 'linux-coroutine', and 'freeRTOS', along with their respective CMakeLists.txt files and validation subdirectories. ```plaintext + demonstration/ <-- Platform-specific demonstration software. In the future | | this will be external and will stay in the garage. We'll | | keep it here for now to accelerate development. | | | + linux-posix/ <-- Demonstrates using libcyphal in a single, multi-threaded | | linux application. | | + CMakeLists.txt | | + validation/ <-- reification of libcyphal/validation for linux | | | + linux-coroutine/ <-- Demonstrates using libcyphal with C++20 coroutines. | | + CMakeLists.txt | | | + linux-amp/ <-- Demonstrates using libcyphal on a multi-core system using | | openAMP to coordinate work between cores. | | + CMakeLists.txt | | | + mac/ <-- Love to have this. not sure what the priority is though. | | + CMakeLists.txt | | + validation/ <-- reification of libcyphal/validation for mac | | | + windows/ <-- contributions welcome! | | + CMakeLists.txt | | + validation/ <-- reification of libcyphal/validation for windows | | | + freeRTOS/ <-- at some point this is important given how many MCUs it | | unlocks. | | + CMakeLists.txt | | | + [on-metal]/ <-- TDB bare-metal firmware demonstration to show that | | libcyphal works on a resource-constrained target without | | preemption. | | + CMakeLists.txt | | + validation/ <-- reification of libcyphal/validation for [TBD] | | | + [other]/ contributions welcome! | + CMakeLists.txt | + validation/ <-- reification of libcyphal/validation for [other] | ``` -------------------------------- ### Docker Command-Line Workflow for Libuavcan Source: https://github.com/uavcan/libuavcan/blob/main/CONTRIBUTING.md This command sequence outlines the steps to pull the necessary Docker image, clone the repository, and run the build process for libuavcan using the command line. ```bash docker pull ghcr.io/opencyphal/toolshed:ts24.4.3 git clone {this repo} cd {this repo} docker run --rm -it -v ${PWD}:/repo ghcr.io/opencyphal/toolshed:ts24.4.3 mkdir build cd build cmake .. cmake --build . ``` -------------------------------- ### Registry Class Definition Source: https://github.com/uavcan/libuavcan/blob/main/docs/design/readme.md Manages a collection of registers, providing get, set, and index operations. Ensures registers do not outlive the registry. ```cpp /// Each register occupies one small fragment on the heap to facilitate type erasure. /// Worst-case access complexity is log(n) where n is the number of registers. class Registry final : public IIntrospectableRegistry { public: Registry() = default; ~Registry() override // NOLINT(hicpp-use-equals-default,modernize-use-equals-default) { assert(tree_.empty()); // If it fails here, there are registers that have outlived the registry. } Registry(const Registry&) = delete; Registry(Registry&&) = delete; Registry& operator=(const Registry&) = delete; Registry& operator=(Registry&&) = delete; [[nodiscard]] std::optional get(const std::string_view name) const override { if (const auto* const reg = find(name)) { return reg->get(); } return std::nullopt; } [[nodiscard]] std::optional set(const std::string_view name, const Value& val) override { if (auto* const reg = find(name)) { return reg->set(val); } return SetError::Existence; } [[nodiscard]] Name index(const std::size_t index) const override { if (const auto* const reg = tree_[index]) { return reg->getName(); } return {}; } [[nodiscard]] std::size_t size() const override { return tree_.size(); } /// Options used when creating a new register. struct Options final { /// True if the register value is retained across application restarts. bool persistent = false; }; /// Create a new register with the specified getter and, optionally, setter. /// This operation incurs one small heap allocation per register to facilitate type erasure (usually under 0.5 KiB); /// the callables are moved to the heap. /// /// The name object is moved to the heap as well; if this is an std::string_view or some other reference, /// then it has to outlive the register. If this cannot be ensured, use some type that owns the string; /// e.g., dyshlo::sitl::String. /// The name shall be convertible to std::string_view. /// /// The getter should simply return anything that can be converted to Value. It can be a primitive type as well. /// /// The setter accepts a Value which is guaranteed to have the same type and dimensionality as that returned by the /// getter; the type and dimensionality consistency is enforced by the registry implementation. /// The setter should return true if the value was accepted, false otherwise. /// /// Destroy the returned unique pointer to remove the register. /// Returns nullptr if the register already exists or if there is not enough memory. template >> [[nodiscard]] RegisterPtr route(N&& name, const Options& opt, G&& getter) { return add(std::forward(name), opt, std::forward(getter), std::monostate{}); } template , std::string_view>>, typename = decltype(makeValue(std::declval()())), typename = decltype(std::declval()(std::declval()))> [[nodiscard]] RegisterPtr route(N&& name, const Options& opt, G&& getter, S&& setter) { return add(std::forward(name), opt, std::forward(getter), std::forward(setter)); } ``` -------------------------------- ### CMake Build Kit Information Source: https://github.com/uavcan/libuavcan/blob/main/CMakeLists.txt Displays essential build kit information, including the toolchain file, compiler details, and linker utilities. ```cmake message(STATUS "[ Build Kit ]-------------------------------------------\n CMAKE_TOOLCHAIN_FILE: ${LOCAL_CMAKE_TOOLCHAIN_FILE} CETLVAST_FLAG_SET: ${CETLVAST_FLAG_SET} CMAKE_CXX_COMPILER_[ID-VER]: ${CMAKE_CXX_COMPILER_ID}-${CMAKE_CXX_COMPILER_VERSION} CMAKE_C_COMPILER_[ID-VER]: ${CMAKE_CXX_COMPILER_ID}-${CMAKE_C_COMPILER_VERSION} CMAKE_AR: ${CMAKE_AR} CMAKE_LINKER: ${CMAKE_LINKER} CMAKE_RANLIB: ${CMAKE_RANLIB} CMAKE_[lang]_PLATFORM_ID: ${CMAKE_CXX_PLATFORM_ID} -----------------------------------------------------------\n") ``` -------------------------------- ### Private Register Implementation Source: https://github.com/uavcan/libuavcan/blob/main/docs/design/readme.md Internal implementation of a register, handling get and set operations. It manages the getter, setter, name, and options for a register. ```C++ private: template > class Reg : public Register { public: explicit Reg(cavl::Tree& tree, const N& name, const Options& opt, G&& getter, S&& setter) : Register(tree, static_cast(static_cast(name))), getter_(std::forward(getter)), setter_(std::forward(setter)), name_(name), opt_(opt) { } ~Reg() override = default; Reg(const Reg&) = delete; Reg(Reg&&) = delete; Reg& operator=(const Reg&) = delete; Reg& operator=(Reg&&) = delete; [[nodiscard]] ValueWithMetadata get() const override { ValueWithMetadata out; registry::set(out.value, getter_()); out.flags.mutable_ = IsMutable; out.flags.persistent = opt_.persistent; return out; } [[nodiscard]] std::optional set(const Value& val) override { if constexpr (IsMutable) { auto converted = get().value; if (coerce(converted, val)) { if (setter_(converted)) { return std::nullopt; } return SetError::Semantics; } return SetError::Coercion; } return SetError::Mutability; } [[nodiscard]] Name getName() const override { return makeName(static_cast(name_)); } private: const G getter_; [[no_unique_address]] const S setter_; ///< Empty unless IsMutable. const N name_; const Options opt_; }; template [[nodiscard]] RegisterPtr add(const N& name, const Options& opt, G&& getter, S&& setter) { if (find(static_cast(name)) == nullptr) { return detail::makeUnique>(tree_, name, opt, std::forward(getter), std::forward(setter)); } return nullptr; // Out of memory or name conflict. } // This overload is provided to convert the name to a string_view to allow invocation with string literals. template [[nodiscard]] RegisterPtr add(const char* const name, const Options& opt, G&& getter, S&& setter) { return add(static_cast(name), opt, std::forward(getter), std::forward(setter)); } [[nodiscard]] Register* find(const std::string_view name) { return tree_.search([k = detail::Key(name)](const Register& x) { return x.key_.compare(k); }); } [[nodiscard]] const Register* find(const std::string_view name) const { return tree_.search([k = detail::Key(name)](const Register& x) { return x.key_.compare(k); }); } cavl::Tree tree_; }; ``` -------------------------------- ### Build LibCyphal with CMake Source: https://github.com/uavcan/libuavcan/blob/main/README.md Instructions for building libcyphal for development purposes using CMake. It shows how to create a build directory, configure CMake with an option to disable static analysis, and run the make command. ```shell mkdir build cd build cmake .. -DNO_STATIC_ANALYSIS=1 make -j16 ``` -------------------------------- ### Configure SocketCAN Interface Source: https://github.com/uavcan/libuavcan/blob/main/CONTRIBUTING.md Use these commands to configure a SocketCAN interface for a specific bitrate, sample point, and FD mode. Ensure the interface is brought down before configuration and up afterwards. Verify the configuration using `ip -details link show`. ```bash sudo ip link set can0 down sudo ip link set can0 type can bitrate 1000000 sample-point 0.875 dbitrate 4000000 dsample-point 0.75 fd on sudo tc qdisc replace dev can0 root pfifo_fast sudo ip link set can0 up sudo ip -details link show can0 ``` -------------------------------- ### Registry Key Hashing (detail::Key) Source: https://github.com/uavcan/libuavcan/blob/main/docs/design/readme.md Internal detail class for generating a 64-bit hash key from register names for efficient lookup. Uses CRC64WE for hashing. ```c++ namespace detail { /// The registers are accessed by key, which is a name hash. /// A 64-bit hash yields a negligible collision probability even for a very large set of registers: /// /// >>> n=10_000 /// >>> d=Decimal(2**64) /// >>> 1 - ((d-1)/d) ** ((n*(n-1))//2) /// Decimal('2.7102343794533273E-12') class Key final { public: explicit Key(const std::string_view name) : val_(hash(name)) {} explicit Key(const Name& name) : val_(hash({reinterpret_cast(name.name.data()), name.name.size()})) {} /// Positive if this one is greater than the other. [[nodiscard]] std::int8_t compare(const Key other) const noexcept { return static_cast((val_ == other.val_) ? 0 : ((val_ > other.val_) ? +1 : -1)); } private: [[nodiscard]] static std::uint64_t hash(const std::string_view name) noexcept { return CRC64WE(name.begin(), name.end()).get(); } std::uint64_t val_; }; } // namespace detail ``` -------------------------------- ### ResponsePromise::setCallback Source: https://github.com/uavcan/libuavcan/blob/main/docs/design/readme.md Sets a callback function to be invoked when the response is received or the deadline is reached. The callback is executed within the IRunnable::run context. This method should be used instead of get() when a callback is desired. ```APIDOC ## ResponsePromise::setCallback ### Description Sets a callback function to be invoked when the response is received or the deadline is reached. The callback is executed within the IRunnable::run context. This method should be used instead of get() when a callback is desired. ### Method `void setCallback(const Callback& cb, const TimePoint deadline)` ### Parameters #### Callback Function - **cb** (const Callback&) - Required - The callback function to execute. It accepts an `std::optional` containing a tuple of the response and metadata, or `std::nullopt` if timed out. - **deadline** (const TimePoint) - Required - The time point at which the callback should be considered timed out if no response is received. ``` -------------------------------- ### Create Register with Default Options Source: https://github.com/uavcan/libuavcan/blob/main/docs/design/readme.md Convenience method for creating getters with default options. Use this for simple read-only register definitions. ```C++ /// Convenience method for creating getters with default options. template , std::string_view>>, typename = decltype(makeValue(std::declval()()))> [[nodiscard]] RegisterPtr route(N&& name, G&& getter) { return add(std::forward(name), Options{}, std::forward(getter), std::monostate{}); } ``` -------------------------------- ### CMake Project Status Message Source: https://github.com/uavcan/libuavcan/blob/main/CMakeLists.txt Displays comprehensive build configuration details, including library paths, versions, and various CMake variables. ```cmake message(STATUS "-------------------------------------------------------------------\n\| LIBCYPHAL_ROOT: ${LIBCYPHAL_ROOT} LIBCYPHAL_INCLUDE: ${LIBCYPHAL_INCLUDE} LIBCYPHAL_VERSION: ${LIBCYPHAL_VERSION} LIBCYPHAL_INTROSPECTION_TRACE_ENABLE: ${LIBCYPHAL_INTROSPECTION_TRACE_ENABLE} LIBCYPHAL_INTROSPECTION_ENABLE_ASSERT: ${LIBCYPHAL_INTROSPECTION_ENABLE_ASSERT} CETLVAST_CPP_STANDARD: ${CETLVAST_CPP_STANDARD} CMAKE_PROJECT_NAME: ${CMAKE_PROJECT_NAME} CMAKE_SOURCE_DIR: ${CMAKE_SOURCE_DIR} CMAKE_CURRENT_SOURCE_DIR: ${CMAKE_CURRENT_SOURCE_DIR} CMAKE_BINARY_DIR: ${CMAKE_BINARY_DIR} CMAKE_CURRENT_BINARY_DIR: ${CMAKE_CURRENT_BINARY_DIR} CMAKE_MODULE_PATH: ${CMAKE_MODULE_PATH} CMAKE_MESSAGE_LOG_LEVEL: ${CMAKE_MESSAGE_LOG_LEVEL} CMAKE_INSTALL_PREFIX: ${CMAKE_INSTALL_PREFIX} CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE} FETCHCONTENT_FULLY_DISCONNECTED: ${FETCHCONTENT_FULLY_DISCONNECTED} CETL CETL_ENABLE_DEBUG_ASSERT: ${CETL_ENABLE_DEBUG_ASSERT} Standard Library LIBCXX_ENABLE_ASSERTIONS: ${LIBCXX_ENABLE_ASSERTIONS} -- ------------------------------------------------------------------\n") ``` -------------------------------- ### Cyphal/CAN Media Layer Interface Source: https://github.com/uavcan/libuavcan/blob/main/docs/design/readme.md Defines the non-blocking media layer API for Cyphal/CAN, operating on extended CAN frames. It includes methods for getting MTU, setting filters, pushing frames for transmission, and popping received frames. ```c++ class IMedia { public: /// This value may change arbitrarily at runtime. The transport implementation will query it before every /// transmission on the port. This value has no effect on the reception pipeline as it can accept arbitrary MTU. [[nodiscard]] virtual std::uint16_t getMTU() const noexcept = 0; /// If there are fewer hardware filters available than requested, the configuration will be coalesced as described /// in the Cyphal/CAN Specification. If zero filters are requested, all incoming traffic will be rejected. /// While reconfiguration is in progress, incoming frames may be lost and/or unwanted frames may be received. /// The lifetime of the filter array may end upon return (no references retained). /// Returns true on success, false in case of a low-level error (e.g., IO error). struct Filter final { std::uint32_t id; std::uint32_t mask; }; [[nodiscard]] virtual bool setFilters(const std::span id_mask) noexcept = 0; /// Schedule the frame for transmission asynchronously and return immediately. /// Returns true if accepted or already timed out; false to try again later. [[nodiscard]] virtual std::expected> push(const TimePoint deadline, const std::uint32_t can_id, const std::span payload) noexcept = 0; /// Return the next frame from the reception queue unless empty; otherwise, return an empty option immediately. /// The payload of the frame will be written into the span. struct Rx final { TimePoint timestamp; std::uint32_t extended_can_id; std::size_t payload_size; }; [[nodiscard]] virtual std::optional pop(const std::span payload_buffer) noexcept = 0; }; ``` -------------------------------- ### ScatteredBuffer Class Definition Source: https://github.com/uavcan/libuavcan/blob/main/docs/design/readme.md Defines the ScatteredBuffer class, which manages potentially fragmented memory buffers for Cyphal transfers. It provides a simplified interface for copying data and getting the buffer size, while handling memory management automatically. ```c++ /// The buffer is movable but not copyable because copying the contents of a buffer is considered wasteful. /// The buffer behaves as if it's empty if the underlying implementation is moved away. class ScatteredBuffer final { public: static constexpr std::size_t ImplementationFootprint = sizeof(void*) * 8; class Iface /// Lizard-specific implementation hidden from the user. { public: [[nodiscard]] virtual std::size_t copy(const std::size_t offset_bytes, void* const destination, const std::size_t length_bytes) const = 0; [[nodiscard]] virtual std::size_t size() const = 0; protected: Iface() = default; Iface(const Iface&) = delete; Iface(Iface&&) = default; Iface& operator=(const Iface&) = delete; Iface& operator=(Iface&&) = delete; virtual ~Iface() = default; }; /// Accepts a Lizard-specific implementation of Iface and moves it into the internal storage. template::value>> explicit ScatteredBuffer(T&& source) : impl_(std::move(source)) {} /// Copies a fragment of the specified size at the specified offset out of the buffer. /// The request is truncated to prevent out-of-range memory access. /// Returns the number of bytes copied. /// Does nothing and returns zero if the instance has been moved away. [[nodiscard]] std::size_t copy(const std::size_t offset_bytes, void* const destination, const std::size_t length_bytes) const { return impl_ ? impl_->copy(offset_bytes, destination, length_bytes) : 0; } /// The number of bytes stored in the buffer (possibly scattered, but this is hidden from the user). /// Returns zero if the buffer is moved away. [[nodiscard]] std::size_t size() const { return impl_ ? impl_->size() : 0; } private: ImplementationCell> impl_; }; ``` -------------------------------- ### Libcyphal Folder Structure Overview Source: https://github.com/uavcan/libuavcan/blob/main/CONTRIBUTING.md This snippet details the directory layout of the libcyphal project, highlighting key folders such as 'build', 'cmake', 'demonstration', 'docs', 'external', 'include', and 'test'. It explains the purpose of each directory within the project's organization. ```plaintext + build*/ <-- reserved (and git-ignored) for build output. | + cmake/ <-- cmake build system stuff common to all sub-directories. | + demonstration/ <-- fully functional applications to demonstrate use of | libcyphal on various target systems. | + docs/ <-- doxygen and example code source | + CMakeLists.txt <-- doxygen and example code build | + examples/ <-- Example code to include in doxygen (also built to verify | its validity) | + external/ <-- external project mount-point. Sub-folders are not | included in git history. + include/ | + libcyphal/ <-- where the magic happens! | | test/ | + unittest/ <-- googletest unit tests of libcyphal code. | + compile/ <-- compile-time tests of libcyphal code. | + .github/ <-- github actions CI + .devcontainer/ <-- vscode/github developer container integration | (OpenCyphal toolshed image) | + .vscode/ <-- common vscode development settings ``` -------------------------------- ### Registry::get Source: https://github.com/uavcan/libuavcan/blob/main/docs/design/readme.md Retrieves the value and metadata of a register by its name. Returns std::nullopt if the register does not exist. ```APIDOC ## Registry::get ### Description Retrieves the value and metadata of a register by its name. Returns std::nullopt if the register does not exist. ### Method GET (conceptual) ### Endpoint N/A (SDK method) ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Request Example ``` registry.get("some.register.name"); ``` ### Response #### Success Response (std::optional) - **value** (Value) - The current value of the register. - **metadata** (Metadata) - Metadata associated with the register value. #### Response Example ```json { "value": "some_value", "metadata": { "type": "string", "persistent": true } } ``` ``` -------------------------------- ### Enable Coverage Report Generation Source: https://github.com/uavcan/libuavcan/blob/main/test/unittest/CMakeLists.txt Enables the generation of coverage reports in specified formats (e.g., HTML, SonarQube). This requires a root directory and outputs report indices. ```cmake enable_coverage_report(COVERAGE_REPORT_FORMATS html sonarqube ROOT_DIRECTORY ${LIBCYPHAL_ROOT} OUT_REPORT_INDICIES LOCAL_COVERAGE_REPORT_INDICIES ) ``` -------------------------------- ### CMake Toolchain File Initialization Source: https://github.com/uavcan/libuavcan/blob/main/CMakeLists.txt Initializes a variable to display the toolchain file status, preventing warnings about uninitialized variables. ```cmake if (NOT CMAKE_TOOLCHAIN_FILE) set(LOCAL_CMAKE_TOOLCHAIN_FILE "(not set)") else() set(LOCAL_CMAKE_TOOLCHAIN_FILE ${CMAKE_TOOLCHAIN_FILE}) endif() ``` -------------------------------- ### Subscriber Base Class Constructor and Virtual Methods Source: https://github.com/uavcan/libuavcan/blob/main/docs/design/readme.md Protected constructor for SubscriberBase and virtual methods for handling incoming data. Concrete subscribers implement doAccept for deserialization. ```C++ protected: explicit SubscriberBase(const cetl::shared_ptr& impl); [[nodiscard]] virtual nunavut::support::SerializeResult doAccept(const Metadata& meta, const ScatteredBuffer& data) noexcept = 0; private: /// This is invoked from SubscriberImpl when a new transfer for this subscription is received. /// The concrete subscriber implements this by invoking the auto-generated deserialization function. [[nodiscard]] nunavut::support::SerializeResult accept(const Metadata& meta, const ScatteredBuffer& data) noexcept; cetl::shared_ptr impl_; ``` -------------------------------- ### Running Clang-Format via Docker Source: https://github.com/uavcan/libuavcan/blob/main/CONTRIBUTING.md This command executes Clang-Format within the specified Docker container to ensure code formatting adheres to the project's standards. It mounts the current directory to the container for in-place formatting. ```bash docker run --rm -v ${PWD}:/repo ghcr.io/opencyphal/toolshed:ts24.4.3 ./build-tools/bin/verify.py build-danger-danger-repo-clang-format-in-place ``` -------------------------------- ### Executable for SonarQube Coverage Analysis Source: https://github.com/uavcan/libuavcan/blob/main/test/unittest/CMakeLists.txt An executable target is created to ensure SonarQube analyzes the libcyphal headers. It links against the cyphal and dsdl_support libraries. ```cmake add_executable( cyphal_sonar "sonar.cpp" ) target_link_libraries(cyphal_sonar PRIVATE cyphal dsdl_support ) ``` -------------------------------- ### Coverage Report Configuration Source: https://github.com/uavcan/libuavcan/blob/main/docs/examples/CMakeLists.txt Enables coverage reporting for the project, generating reports in HTML and SonarQube formats. It specifies the root directory for coverage analysis and excludes specific paths. ```cmake if (CMAKE_BUILD_TYPE STREQUAL "Coverage") enable_coverage_report(COVERAGE_REPORT_FORMATS html sonarqube ROOT_DIRECTORY ${LIBCYPHAL_ROOT} ) endif () ``` -------------------------------- ### Presentation Class Interface Source: https://github.com/uavcan/libuavcan/blob/main/docs/design/readme.md The Presentation class provides the main interface for interacting with the UAVCAN network. It allows for the creation of publishers, subscribers, clients, and servers for various message and service types. It also manages the underlying transport layer. ```c++ class Presentation final { public: explicit Presentation(std::pmr::memory_resource& memory, transport::ITransport& tr) noexcept; // We could make Publisher a non-generic method, such that it operates on IComposite instead of Message. // We choose not to do that because that would make the size of the serialization buffer unknown at compile time, // necessitating the use of heap for message serialization. // Between the use of heap and generics we choose the lesser evil. // // The hidden implementation object stores the transfer-ID counter. This means that the destruction of the last publisher // on a given subject-ID causes the transfer-ID state to be lost. If seen as practical, it can be easily avoided by // moving the transfer-ID counters into a separate PMR-aware dynamic container (subject-ID -> transfer-ID), like // std::unordered_map. // If not, the application can always ensure that the transfer-ID // state is not lost by creating a dummy publisher instance whose only purpose is to keep the counter alive. // // If the message is void, a specialized publisher is constructed that operates on pre-serialized raw blobs. // See the specialization for usage details. template [[nodiscard]] std::expected, Error> makePublisher(const std::uint16_t subject_id); // An additional template parameter may be added later specifying the depth of the FIFO queue. // If the queue is full, the oldest messages are dropped (newer messages take precedence). // For now, the queue depth is 1, meaning that subscribers behave like sampling ports. // // If the message is of type cetl::VariableLengthArray, where Allocator may be arbitrary, // a specialized subscriber is constructed that does not deserialize messages but returns the serialzied // representation as-is after copying it into a new instance of the specified array type. // Note that we can't just deliver ScatteredBuffer to the application because it is non-copyable. // See the specialization for usage details. template [[nodiscard]] std::expected, Error> makeSubscriber(const std::uint16_t subject_id); // Notice that a client is bound to a specific remote node. // To query multiple nodes one has to create multiple clients. // // The hidden implementation object stores the transfer-ID counter. This means that the destruction of the last client // on a given service-ID causes the transfer-ID state to be lost. If seen as practical, it can be easily avoided by // moving the transfer-ID counters into a separate PMR-aware dynamic container (service-ID -> transfer-ID), like // std::unordered_map. // If not, the application can always ensure that the transfer-ID // state is not lost by creating a dummy client instance whose only purpose is to keep the counter alive. // // If the service is of type void, a specialized client is constructed that does not serialize requests nor // deserializes responses, but accepts raw pre-serialized requests and returns responses contained in ScatteredBuffer // as received from the transport layer. See the specialization for usage details. template [[nodiscard]] std::expected, Error> makeClient(const std::uint16_t service_id, const std::uint16_t server_node_id); // If such a server already exists, an error will be returned (error type TBD). // // If the service is of type void, a specialized server is constructed that operates on pre-serialized raw blobs. // See the specialization for usage details. template [[nodiscard]] std::expected>, Error> makeServer(const std::uint16_t service_id); [[nodiscard]] transport::ITransport& getTransport() noexcept; [[nodiscard]] const transport::ITransport& getTransport() const noexcept; }; /// The hidden per-port-unique implementation classes (not visible to the user, managed automatically). class PublisherImpl; class SubscriberImpl : public IRunnable; class ClientImpl : public IRunnable; ``` -------------------------------- ### Registry::route (getter and setter) Source: https://github.com/uavcan/libuavcan/blob/main/docs/design/readme.md Creates a new register with a specified name, options, a getter function, and a setter function. The register is automatically removed when the returned RegisterPtr is destroyed. Returns nullptr if the register already exists or if memory allocation fails. ```APIDOC ## Registry::route (getter and setter) ### Description Creates a new register with a specified name, options, a getter function, and a setter function. The register is automatically removed when the returned RegisterPtr is destroyed. Returns nullptr if the register already exists or if memory allocation fails. ### Method POST (conceptual) ### Endpoint N/A (SDK method) ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body - **name** (N&&) - The name for the new register. Must be convertible to std::string_view. - **opt** (const Options&) - Options for the register, e.g., persistence. - **getter** (G&&) - A callable that returns the register's value. - **setter** (S&&) - A callable that accepts a Value and sets the register's value. ### Request Example ```cpp auto reg_ptr = registry.route("my.setting", {}, []() { return get_setting(); }, [](const Value& v) { return set_setting(v); }); ``` ### Response #### Success Response (RegisterPtr) - A unique pointer to the created Register object. Destroying this pointer removes the register. #### Response Example ``` // A valid RegisterPtr object ``` ``` -------------------------------- ### Server Class Source: https://github.com/uavcan/libuavcan/blob/main/docs/design/readme.md A non-typed specialization of the Server class for handling raw data buffers as requests and responses. ```APIDOC ## Class Server ### Description A non-typed specialization of the Server class. It handles incoming requests as raw `ScatteredBuffer` and accepts responses as raw blobs. ### Methods - `get()`: Retrieves the next pending request context, including the raw request buffer, metadata, and a continuation to send the response. The continuation can be used at most once. ### RequestContext Structure - `request`: The incoming request as a `ScatteredBuffer`. - `meta`: Metadata associated with the request. - `continuation`: A closure that accepts a span of byte spans to send the response. It can be used at most once. ```