From 454423e32b98062e72c4d41c5a9a5434e4c71769 Mon Sep 17 00:00:00 2001 From: Ilia Date: Mon, 17 Aug 2026 14:37:58 +0300 Subject: [PATCH 1/2] feat: add new sub --- include/libdcnode/sub.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/libdcnode/sub.hpp b/include/libdcnode/sub.hpp index a96f98f..7839e09 100644 --- a/include/libdcnode/sub.hpp +++ b/include/libdcnode/sub.hpp @@ -56,6 +56,7 @@ namespace libdcnode UAVCAN_EQUIPMENT_CAMERA_GIMBAL_ANGULARCOMMAND) LIBDCNODE_DEFINE_SUB_TRAITS(::uavcan_equipment_ahrs_Solution, UAVCAN_EQUIPMENT_AHRS_SOLUTION) LIBDCNODE_DEFINE_SUB_TRAITS(::uavcan_equipment_esc_RawCommand, UAVCAN_EQUIPMENT_ESC_RAWCOMMAND) + LIBDCNODE_DEFINE_SUB_TRAITS(::uavcan_equipment_ice_reciprocating_Status, UAVCAN_EQUIPMENT_ICE_RECIPROCATING_STATUS) LIBDCNODE_DEFINE_SUB_TRAITS(::uavcan_equipment_safety_ArmingStatus, UAVCAN_EQUIPMENT_SAFETY_ARMINGSTATUS) template From 613989287ff79554ccaa8b28869dd61e0213a234 Mon Sep 17 00:00:00 2001 From: Ilia Date: Tue, 18 Aug 2026 10:35:05 +0300 Subject: [PATCH 2/2] feat: use generated metadata --- .github/workflows/build.yml | 5 ++ CMakeLists.txt | 26 ++++++++ Libs/canard/cxx_wrappers.h | 93 ++++++++++++++++++++++++++ Makefile | 10 ++- include/libdcnode/pub.hpp | 74 +++++--------------- include/libdcnode/sub.hpp | 54 ++------------- tests/pub_sub_test.cpp | 130 ++++++++++++++++++++++++++++++++++++ 7 files changed, 283 insertions(+), 109 deletions(-) create mode 100644 Libs/canard/cxx_wrappers.h create mode 100644 tests/pub_sub_test.cpp diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 342bcc1..95ab4a0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -29,6 +29,11 @@ jobs: source ".venv/bin/activate" make ${{ matrix.target }}-build + - name: Run unit tests + run: | + source ".venv/bin/activate" + make tests + - name: Run SITL and smoke test timeout-minutes: 3 run: | diff --git a/CMakeLists.txt b/CMakeLists.txt index 539c13a..3f57bd9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,6 +38,10 @@ target_include_directories(libdcnode PUBLIC ${DSDL_OUT_DIR}/include ) +target_compile_definitions(libdcnode PUBLIC + DRONECAN_CXX_WRAPPERS=1 +) + set_source_files_properties(${SERIALIZATION_SOURCES} PROPERTIES @@ -47,3 +51,25 @@ set_source_files_properties(${SERIALIZATION_SOURCES} target_compile_options(${PROJECT_NAME} PRIVATE $<$:-Wno-address-of-packed-member> ) + +option(LIBDCNODE_BUILD_TESTS "Build libdcnode host tests" OFF) +if(LIBDCNODE_BUILD_TESTS) + enable_testing() + add_executable(libdcnode_pub_sub_test + ${CMAKE_CURRENT_LIST_DIR}/tests/pub_sub_test.cpp + ${CMAKE_CURRENT_LIST_DIR}/Libs/libcanard_v0/canard.c + ${DSDL_OUT_DIR}/src/uavcan.protocol.Panic.c + ) + target_include_directories(libdcnode_pub_sub_test PRIVATE + ${CMAKE_CURRENT_LIST_DIR}/include + ${CMAKE_CURRENT_LIST_DIR}/Libs + ${DSDL_OUT_DIR}/include + ) + target_compile_definitions(libdcnode_pub_sub_test PRIVATE + DRONECAN_CXX_WRAPPERS=1 + ) + target_compile_options(libdcnode_pub_sub_test PRIVATE + $<$:-Wall;-Wextra;-Werror> + ) + add_test(NAME libdcnode_pub_sub_test COMMAND libdcnode_pub_sub_test) +endif() diff --git a/Libs/canard/cxx_wrappers.h b/Libs/canard/cxx_wrappers.h new file mode 100644 index 0000000..a322712 --- /dev/null +++ b/Libs/canard/cxx_wrappers.h @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2022 Siddharth B Purohit, CubePilot Pty Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +// The upstream header includes helpers.h for the complete Canard C++ API. +// libdcnode uses only these generated type metadata macros, which do not need +// the allocator helpers. + +/// @brief Broadcast message interface. +/// @param MSGTYPE message type name +/// @param MSG_ID message id +/// @param MSG_SIGNATURE message signature +/// @param MSG_MAX_SIZE maximum message size +#if CANARD_ENABLE_CANFD || CANARD_ENABLE_TAO_OPTION +#define BROADCAST_MESSAGE_CXX_IFACE(MSGTYPE, MSG_ID, MSG_SIGNATURE, MSG_MAX_SIZE) \ + class MSGTYPE##_cxx_iface { \ + public: \ + typedef MSGTYPE msgtype; \ + static constexpr uint32_t (*encode)(msgtype*, uint8_t*, bool) = MSGTYPE##_encode; \ + static constexpr bool (*decode)(const CanardRxTransfer* transfer, msgtype*) = MSGTYPE##_decode; \ + static constexpr uint16_t ID = MSG_ID; \ + static constexpr uint64_t SIGNATURE = MSG_SIGNATURE; \ + static constexpr uint16_t MAX_SIZE = MSG_MAX_SIZE; \ + }; +#else +#define BROADCAST_MESSAGE_CXX_IFACE(MSGTYPE, MSG_ID, MSG_SIGNATURE, MSG_MAX_SIZE) \ + class MSGTYPE##_cxx_iface { \ + public: \ + typedef MSGTYPE msgtype; \ + static constexpr uint32_t (*encode)(msgtype*, uint8_t*) = MSGTYPE##_encode; \ + static constexpr bool (*decode)(const CanardRxTransfer* transfer, msgtype*) = MSGTYPE##_decode; \ + static constexpr uint16_t ID = MSG_ID; \ + static constexpr uint64_t SIGNATURE = MSG_SIGNATURE; \ + static constexpr uint16_t MAX_SIZE = MSG_MAX_SIZE; \ + }; +#endif + +#if CANARD_ENABLE_CANFD || CANARD_ENABLE_TAO_OPTION +#define SERVICE_MESSAGE_CXX_IFACE(SVCTYPE, SVC_ID, SVC_SIGNATURE, SVC_REQUEST_MAX_SIZE, SVC_RESPONSE_MAX_SIZE) \ + class SVCTYPE##_cxx_iface { \ + public: \ + typedef SVCTYPE##Request reqtype; \ + typedef SVCTYPE##Response rsptype; \ + static constexpr uint32_t (*req_encode)(reqtype*, uint8_t*, bool) = SVCTYPE##Request_encode; \ + static constexpr uint32_t (*rsp_encode)(rsptype*, uint8_t*, bool) = SVCTYPE##Response_encode; \ + static constexpr bool (*req_decode)(const CanardRxTransfer* transfer, reqtype*) = SVCTYPE##Request_decode; \ + static constexpr bool (*rsp_decode)(const CanardRxTransfer* transfer, rsptype*) = SVCTYPE##Response_decode; \ + static constexpr uint16_t ID = SVC_ID; \ + static constexpr uint64_t SIGNATURE = SVC_SIGNATURE; \ + static constexpr uint16_t REQ_MAX_SIZE = SVC_REQUEST_MAX_SIZE; \ + static constexpr uint16_t RSP_MAX_SIZE = SVC_RESPONSE_MAX_SIZE; \ + }; +#else +/// @brief Service message interface. +/// @param SVCTYPE message type name +/// @param SVC_ID message id +/// @param SVC_SIGNATURE message signature +/// @param SVC_REQUEST_MAX_SIZE maximum request size +/// @param SVC_RESPONSE_MAX_SIZE maximum response size +#define SERVICE_MESSAGE_CXX_IFACE(SVCTYPE, SVC_ID, SVC_SIGNATURE, SVC_REQUEST_MAX_SIZE, SVC_RESPONSE_MAX_SIZE) \ + class SVCTYPE##_cxx_iface { \ + public: \ + typedef SVCTYPE##Request reqtype; \ + typedef SVCTYPE##Response rsptype; \ + static constexpr uint32_t (*req_encode)(reqtype*, uint8_t*) = SVCTYPE##Request_encode; \ + static constexpr uint32_t (*rsp_encode)(rsptype*, uint8_t*) = SVCTYPE##Response_encode; \ + static constexpr bool (*req_decode)(const CanardRxTransfer* transfer, reqtype*) = SVCTYPE##Request_decode; \ + static constexpr bool (*rsp_decode)(const CanardRxTransfer* transfer, rsptype*) = SVCTYPE##Response_decode; \ + static constexpr uint16_t ID = SVC_ID; \ + static constexpr uint64_t SIGNATURE = SVC_SIGNATURE; \ + static constexpr uint16_t REQ_MAX_SIZE = SVC_REQUEST_MAX_SIZE; \ + static constexpr uint16_t RSP_MAX_SIZE = SVC_RESPONSE_MAX_SIZE; \ + }; +#endif diff --git a/Makefile b/Makefile index 3ea5f0a..0c90210 100644 --- a/Makefile +++ b/Makefile @@ -5,6 +5,7 @@ ROOT_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) BUILD_DIR:=$(ROOT_DIR)/build BUILD_EXAMPLES_DIR:=$(BUILD_DIR)/src/examples +BUILD_TESTS_DIR:=$(BUILD_DIR)/tests define build_sitl $(info Build example $(1)...) @@ -17,7 +18,7 @@ define run_sitl $(BUILD_EXAMPLES_DIR)/$(1)/ubuntu endef -.PHONY: ubuntu ubuntu-build ubuntu-run +.PHONY: ubuntu ubuntu-build ubuntu-run tests ubuntu-build: $(call build_sitl,ubuntu) @@ -31,6 +32,11 @@ ubuntu-run: ubuntu: ubuntu-build $(call run_sitl,ubuntu) +tests: + cmake -S $(ROOT_DIR) -B $(BUILD_TESTS_DIR) -DLIBDCNODE_BUILD_TESTS=ON + cmake --build $(BUILD_TESTS_DIR) --target libdcnode_pub_sub_test + ctest --test-dir $(BUILD_TESTS_DIR) --output-on-failure + clean: rm -rf build/examples/ @@ -38,7 +44,7 @@ code_style: cpplint cppcheck crlf astyle: ./scripts/code_style/check_astyle.py src include --astylerc scripts/code_style/astylerc cpplint: - cpplint src/*.cpp include/libdcnode/*.h include/libdcnode/*.hpp + cpplint src/*.cpp tests/*.cpp include/libdcnode/*.h include/libdcnode/*.hpp cppcheck: ./scripts/code_style/cppcheck.sh crlf: diff --git a/include/libdcnode/pub.hpp b/include/libdcnode/pub.hpp index f5cf60e..6578c9c 100644 --- a/include/libdcnode/pub.hpp +++ b/include/libdcnode/pub.hpp @@ -10,18 +10,10 @@ #include #include -#include -#include #include "libdcnode/dronecan.h" #include "libdcnode/platform.hpp" #include "dronecan_msgs.h" -// Max encoded message size (bytes) for publisher stack buffer. -// Override via -DLIBDCNODE_MAX_PUB_MESSAGE_SIZE=... if your DSDL set requires more. -#ifndef LIBDCNODE_MAX_PUB_MESSAGE_SIZE -#define LIBDCNODE_MAX_PUB_MESSAGE_SIZE 600U -#endif - // Initial delay before first publish after boot/reset (ms). // Intended to reduce CAN bus flooding during rapid reboot loops (e.g., watchdog resets). // Override via -DLIBDCNODE_INITIAL_PUB_DELAY_MS=... @@ -31,48 +23,6 @@ namespace libdcnode { - - template - struct DronecanPublisherTraits; - -#define LIBDCNODE_PUB_ENCODE(MessageType) MessageType##_encode -#define LIBDCNODE_PUB_BROADCAST(buffer, Prefix, inout_transfer_id, size) \ - uavcanPublish(Prefix##_SIGNATURE, Prefix##_ID, inout_transfer_id, CANARD_TRANSFER_PRIORITY_MEDIUM, \ - buffer, size) -#define LIBDCNODE_DEFINE_PUB_TRAITS(MessageType, MessagePrefix) \ - template <> \ - struct DronecanPublisherTraits \ - { \ - using EncodeRet = decltype(LIBDCNODE_PUB_ENCODE(MessageType)( \ - std::declval(), std::declval())); \ - static_assert(std::is_same_v, "*_encode() must return uint32_t exactly."); \ - static inline int16_t publish_once(MessageType *msg, uint8_t *inout_transfer_id) \ - { \ - uint8_t buffer[LIBDCNODE_MAX_PUB_MESSAGE_SIZE]; \ - auto bytes_needed = LIBDCNODE_PUB_ENCODE(MessageType)(msg, buffer); \ - if (bytes_needed == 0U || bytes_needed > LIBDCNODE_MAX_PUB_MESSAGE_SIZE) \ - return (int16_t)0; \ - return LIBDCNODE_PUB_BROADCAST(buffer, MessagePrefix, inout_transfer_id, bytes_needed); \ - } \ - }; - - LIBDCNODE_DEFINE_PUB_TRAITS(::uavcan_equipment_power_CircuitStatus, UAVCAN_EQUIPMENT_POWER_CIRCUITSTATUS) - LIBDCNODE_DEFINE_PUB_TRAITS(::uavcan_equipment_power_BatteryInfo, UAVCAN_EQUIPMENT_POWER_BATTERYINFO) - LIBDCNODE_DEFINE_PUB_TRAITS(::uavcan_equipment_ahrs_RawIMU, UAVCAN_EQUIPMENT_AHRS_RAWIMU) - LIBDCNODE_DEFINE_PUB_TRAITS(::uavcan_equipment_ahrs_MagneticFieldStrength2, - UAVCAN_EQUIPMENT_AHRS_MAGNETICFIELDSTRENGTH2) - LIBDCNODE_DEFINE_PUB_TRAITS(::uavcan_equipment_camera_gimbal_Status, UAVCAN_EQUIPMENT_CAMERA_GIMBAL_STATUS) - LIBDCNODE_DEFINE_PUB_TRAITS(::uavcan_equipment_actuator_ArrayCommand, - UAVCAN_EQUIPMENT_ACTUATOR_ARRAYCOMMAND) - LIBDCNODE_DEFINE_PUB_TRAITS(::uavcan_equipment_actuator_Status, UAVCAN_EQUIPMENT_ACTUATOR_STATUS) - LIBDCNODE_DEFINE_PUB_TRAITS(::uavcan_equipment_esc_Status, UAVCAN_EQUIPMENT_ESC_STATUS) - LIBDCNODE_DEFINE_PUB_TRAITS(::uavcan_equipment_hardpoint_Status, UAVCAN_EQUIPMENT_HARDPOINT_STATUS) - LIBDCNODE_DEFINE_PUB_TRAITS(::ardupilot_equipment_power_BatteryInfoAux, ARDUPILOT_EQUIPMENT_POWER_BATTERYINFOAUX) - LIBDCNODE_DEFINE_PUB_TRAITS(::uavcan_equipment_air_data_StaticPressure, - UAVCAN_EQUIPMENT_AIR_DATA_STATICPRESSURE) - LIBDCNODE_DEFINE_PUB_TRAITS(::uavcan_equipment_range_sensor_Measurement, - UAVCAN_EQUIPMENT_RANGE_SENSOR_MEASUREMENT) - template class DronecanPub { @@ -81,8 +31,23 @@ namespace libdcnode inline void publish() { - DronecanPublisherTraits::publish_once(&msg, &inout_transfer_id); - inout_transfer_id++; + using Interface = typename MessageType::cxx_iface; + uint8_t buffer[Interface::MAX_SIZE]; +#if CANARD_ENABLE_CANFD || CANARD_ENABLE_TAO_OPTION + const auto bytes_needed = Interface::encode(&msg, buffer, true); +#else + const auto bytes_needed = Interface::encode(&msg, buffer); +#endif + if (bytes_needed == 0U || bytes_needed > Interface::MAX_SIZE) + { + return; + } + uavcanPublish(Interface::SIGNATURE, + Interface::ID, + &inout_transfer_id, + CANARD_TRANSFER_PRIORITY_MEDIUM, + buffer, + bytes_needed); } MessageType msg{}; @@ -120,9 +85,4 @@ namespace libdcnode } // namespace libdcnode -// Undef internal macros -#undef LIBDCNODE_PUB_ENCODE -#undef LIBDCNODE_PUB_BROADCAST -#undef LIBDCNODE_DEFINE_PUB_TRAITS - #endif // LIBDCNODE_PUB_HPP_ diff --git a/include/libdcnode/sub.hpp b/include/libdcnode/sub.hpp index 7839e09..441ad00 100644 --- a/include/libdcnode/sub.hpp +++ b/include/libdcnode/sub.hpp @@ -13,52 +13,8 @@ #include "libdcnode/dronecan.h" #include "dronecan_msgs.h" -// Max encoded message size (bytes) for subscriber stack buffer. -// Override via -DLIBDCNODE_MAX_SUB_MESSAGE_SIZE=... if your DSDL set requires more. -#ifndef LIBDCNODE_MAX_SUB_MESSAGE_SIZE -#define LIBDCNODE_MAX_SUB_MESSAGE_SIZE 250 -#endif - -// Initial delay before first subscribe after boot/reset (ms). -// Intended to reduce CAN bus flooding during rapid reboot loops (e.g., watchdog resets). -// Override via -DLIBDCNODE_INITIAL_SUB_DELAY_MS=... -#ifndef LIBDCNODE_INITIAL_SUB_DELAY_MS -#define LIBDCNODE_INITIAL_SUB_DELAY_MS 500U -#endif - namespace libdcnode { - - template - struct DronecanSubscriberTraits; - -#define LIBDCNODE_DEFINE_SUB_TRAITS(MessageType, Prefix) \ - template <> \ - struct DronecanSubscriberTraits \ - { \ - static inline int8_t subscribe(void (*callback)(CanardRxTransfer *)) \ - { \ - return uavcanSubscribe(Prefix##_SIGNATURE, Prefix##_ID, callback); \ - } \ - static inline int8_t deserialize(CanardRxTransfer *transfer, MessageType *msg) \ - { \ - return MessageType##_decode(transfer, msg); \ - } \ - }; - - LIBDCNODE_DEFINE_SUB_TRAITS(::uavcan_equipment_hardpoint_Command, - UAVCAN_EQUIPMENT_HARDPOINT_COMMAND) - LIBDCNODE_DEFINE_SUB_TRAITS(::uavcan_equipment_hardpoint_Status, - UAVCAN_EQUIPMENT_HARDPOINT_STATUS) - LIBDCNODE_DEFINE_SUB_TRAITS(::uavcan_equipment_indication_LightsCommand, UAVCAN_EQUIPMENT_INDICATION_LIGHTSCOMMAND) - LIBDCNODE_DEFINE_SUB_TRAITS(::uavcan_equipment_actuator_ArrayCommand, UAVCAN_EQUIPMENT_ACTUATOR_ARRAYCOMMAND) - LIBDCNODE_DEFINE_SUB_TRAITS(::uavcan_equipment_camera_gimbal_AngularCommand, - UAVCAN_EQUIPMENT_CAMERA_GIMBAL_ANGULARCOMMAND) - LIBDCNODE_DEFINE_SUB_TRAITS(::uavcan_equipment_ahrs_Solution, UAVCAN_EQUIPMENT_AHRS_SOLUTION) - LIBDCNODE_DEFINE_SUB_TRAITS(::uavcan_equipment_esc_RawCommand, UAVCAN_EQUIPMENT_ESC_RAWCOMMAND) - LIBDCNODE_DEFINE_SUB_TRAITS(::uavcan_equipment_ice_reciprocating_Status, UAVCAN_EQUIPMENT_ICE_RECIPROCATING_STATUS) - LIBDCNODE_DEFINE_SUB_TRAITS(::uavcan_equipment_safety_ArmingStatus, UAVCAN_EQUIPMENT_SAFETY_ARMINGSTATUS) - template class DronecanSub { @@ -74,7 +30,8 @@ namespace libdcnode { user_callback = callback; filter = filter_; - auto sub_id = DronecanSubscriberTraits::subscribe(transfer_callback); + using Interface = typename MessageType::cxx_iface; + auto sub_id = uavcanSubscribe(Interface::SIGNATURE, Interface::ID, transfer_callback); if (sub_id < 0) { return sub_id; } @@ -84,8 +41,8 @@ namespace libdcnode static inline void transfer_callback(CanardRxTransfer *transfer) { - int8_t res = DronecanSubscriberTraits::deserialize(transfer, &msg); - if (res < 0) + using Interface = typename MessageType::cxx_iface; + if (Interface::decode(transfer, &msg)) { return; } @@ -112,7 +69,4 @@ namespace libdcnode } // namespace libdcnode -// Undef internal macros -#undef LIBDCNODE_DEFINE_SUB_TRAITS - #endif // LIBDCNODE_SUB_HPP_ diff --git a/tests/pub_sub_test.cpp b/tests/pub_sub_test.cpp new file mode 100644 index 0000000..28ea31c --- /dev/null +++ b/tests/pub_sub_test.cpp @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2026 Ilia Kliantsevich + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#include +#include +#include +#include +#include "libdcnode/pub.hpp" +#include "libdcnode/sub.hpp" + +namespace +{ +using Message = uavcan_protocol_Panic; +using Interface = Message::cxx_iface; + +uint64_t subscribed_signature = 0; +uint16_t subscribed_id = 0; +void (*subscribed_callback)(CanardRxTransfer *) = nullptr; +int8_t next_subscription_id = 0; + +std::array published_transfer_ids{}; +std::array published_payload{}; +uint64_t published_signature = 0; +uint16_t published_id = 0; +uint16_t published_payload_len = 0; +std::size_t publish_count = 0; + +std::size_t callback_count = 0; +Message received_message{}; + +void message_callback(const Message& msg) +{ + callback_count++; + received_message = msg; +} +} // namespace + +extern "C" int8_t uavcanSubscribe(uint64_t signature, + uint16_t id, + void (*callback)(CanardRxTransfer *transfer)) +{ + subscribed_signature = signature; + subscribed_id = id; + subscribed_callback = callback; + return next_subscription_id++; +} + +extern "C" int16_t uavcanPublish(uint64_t signature, + uint16_t id, + uint8_t *inout_transfer_id, + uint8_t priority, + const void *payload, + uint16_t payload_len) +{ + assert(publish_count < published_transfer_ids.size()); + assert(priority == CANARD_TRANSFER_PRIORITY_MEDIUM); + assert(payload_len <= published_payload.size()); + + published_signature = signature; + published_id = id; + published_payload_len = payload_len; + published_transfer_ids[publish_count++] = *inout_transfer_id; + std::memcpy(published_payload.data(), payload, payload_len); + + *inout_transfer_id = static_cast((*inout_transfer_id + 1U) & 31U); + return 1; +} + +int main() +{ + static_assert(Interface::ID == UAVCAN_PROTOCOL_PANIC_ID); + static_assert(Interface::SIGNATURE == UAVCAN_PROTOCOL_PANIC_SIGNATURE); + static_assert(Interface::MAX_SIZE == UAVCAN_PROTOCOL_PANIC_MAX_SIZE); + + libdcnode::DronecanSub sub; + const auto subscription_id = sub.init(message_callback); + assert(subscription_id >= 0); + assert(subscribed_signature == Interface::SIGNATURE); + assert(subscribed_id == Interface::ID); + assert(subscribed_callback != nullptr); + + Message source{}; + source.reason_text.len = 3; + source.reason_text.data[0] = 'b'; + source.reason_text.data[1] = 'a'; + source.reason_text.data[2] = 'd'; + + std::array encoded{}; +#if CANARD_ENABLE_CANFD || CANARD_ENABLE_TAO_OPTION + const auto encoded_size = Interface::encode(&source, encoded.data(), true); +#else + const auto encoded_size = Interface::encode(&source, encoded.data()); +#endif + assert(encoded_size > 0U); + + CanardRxTransfer transfer{}; + transfer.payload_head = encoded.data(); + transfer.payload_len = static_cast(encoded_size); + transfer.data_type_id = Interface::ID; + transfer.sub_id = static_cast(subscription_id); + subscribed_callback(&transfer); + + assert(callback_count == 1U); + assert(received_message.reason_text.len == source.reason_text.len); + assert(std::memcmp(received_message.reason_text.data, + source.reason_text.data, + source.reason_text.len) == 0); + + transfer.payload_len = static_cast(Interface::MAX_SIZE + 1U); + subscribed_callback(&transfer); + assert(callback_count == 1U); + + libdcnode::DronecanPub pub; + pub.msg = source; + pub.publish(); + pub.publish(); + + assert(publish_count == 2U); + assert(published_transfer_ids[0] == 0U); + assert(published_transfer_ids[1] == 1U); + assert(published_signature == Interface::SIGNATURE); + assert(published_id == Interface::ID); + assert(published_payload_len == encoded_size); + assert(std::memcmp(published_payload.data(), encoded.data(), encoded_size) == 0); + return 0; +}