/**************************************************************************** * Copyright (c) 2024 PX4 Development Team. * SPDX-License-Identifier: BSD-3-Clause ****************************************************************************/ #pragma once #include "translations.h" #include "util.h" #include "template_util.h" #include #include class RegisteredTranslations { public: RegisteredTranslations(RegisteredTranslations const&) = delete; void operator=(RegisteredTranslations const&) = delete; static RegisteredTranslations& instance() { static RegisteredTranslations instance; return instance; } /** * @brief Register a translation class with 1 input and 1 output message. * * The translation class has the form: * * ``` * class MyTranslation { * public: * using MessageOlder = px4_msgs_old::msg::VehicleAttitudeV2; * * using MessageNewer = px4_msgs::msg::VehicleAttitude; * * static constexpr const char* kTopic = "fmu/out/vehicle_attitude"; * * static void fromOlder(const MessageOlder &msg_older, MessageNewer &msg_newer) { * // set msg_newer from msg_older * } * * static void toOlder(const MessageNewer &msg_newer, MessageOlder &msg_older) { * // set msg_older from msg_newer * } * }; * ``` */ template void registerDirectTranslation() { const std::string topic_name = T::kTopic; _topic_translations.addTopic(getTopicForMessageType(topic_name)); _topic_translations.addTopic(getTopicForMessageType(topic_name)); // Translation callbacks auto translation_cb_from_older = [](const std::vector& older_msg, std::vector& newer_msg) { T::fromOlder(*(const typename T::MessageOlder*)older_msg[0].get(), *(typename T::MessageNewer*)newer_msg[0].get()); }; auto translation_cb_to_older = [](const std::vector& newer_msg, std::vector& older_msg) { T::toOlder(*(const typename T::MessageNewer*)newer_msg[0].get(), *(typename T::MessageOlder*)older_msg[0].get()); }; _topic_translations.addTranslation({translation_cb_from_older, {MessageIdentifier{topic_name, T::MessageOlder::MESSAGE_VERSION}}, {MessageIdentifier{topic_name, T::MessageNewer::MESSAGE_VERSION}}}); _topic_translations.addTranslation({translation_cb_to_older, {MessageIdentifier{topic_name, T::MessageNewer::MESSAGE_VERSION}}, {MessageIdentifier{topic_name, T::MessageOlder::MESSAGE_VERSION}}}); } /** * @brief Register a translation class for a service. * * The translation class has the form: * * ``` * class MyServiceTranslation { * public: * using MessageOlder = px4_msgs_old::srv::VehicleCommandV0; * using MessageNewer = px4_msgs::srv::VehicleCommand; * * static constexpr const char* kTopic = "fmu/vehicle_command"; * * static void fromOlder(const MessageOlder::Request &msg_older, MessageNewer::Request &msg_newer) { * // set msg_newer from msg_older * } * * static void toOlder(const MessageNewer::Request &msg_newer, MessageOlder::Request &msg_older) { * // set msg_older from msg_newer * } * * static void fromOlder(const MessageOlder::Response &msg_older, MessageNewer::Response &msg_newer) { * // set msg_newer from msg_older * } * * static void toOlder(const MessageNewer::Response &msg_newer, MessageOlder::Response &msg_older) { * // set msg_older from msg_newer * } * }; * ``` */ template void registerServiceDirectTranslation() { const std::string topic_name = T::kTopic; _service_translations.addNode(getServiceForMessageType(topic_name)); _service_translations.addNode(getServiceForMessageType(topic_name)); // Add translations { // Request auto translation_cb_from_older = [](const std::vector &older_msg, std::vector &newer_msg) { T::fromOlder(*(const typename T::MessageOlder::Request *) older_msg[0].get(), *(typename T::MessageNewer::Request *) newer_msg[0].get()); }; auto translation_cb_to_older = [](const std::vector &newer_msg, std::vector &older_msg) { T::toOlder(*(const typename T::MessageNewer::Request *) newer_msg[0].get(), *(typename T::MessageOlder::Request *) older_msg[0].get()); }; _service_translations.addRequestTranslation({translation_cb_from_older, {MessageIdentifier{topic_name, T::MessageOlder::Request::MESSAGE_VERSION}}, {MessageIdentifier{topic_name, T::MessageNewer::Request::MESSAGE_VERSION}}}); _service_translations.addRequestTranslation({translation_cb_to_older, {MessageIdentifier{topic_name, T::MessageNewer::Request::MESSAGE_VERSION}}, {MessageIdentifier{topic_name, T::MessageOlder::Request::MESSAGE_VERSION}}}); } { // Response auto translation_cb_from_older = [](const std::vector &older_msg, std::vector &newer_msg) { T::fromOlder(*(const typename T::MessageOlder::Response *) older_msg[0].get(), *(typename T::MessageNewer::Response *) newer_msg[0].get()); }; auto translation_cb_to_older = [](const std::vector &newer_msg, std::vector &older_msg) { T::toOlder(*(const typename T::MessageNewer::Response *) newer_msg[0].get(), *(typename T::MessageOlder::Response *) older_msg[0].get()); }; _service_translations.addResponseTranslation({translation_cb_from_older, {MessageIdentifier{topic_name, T::MessageOlder::Request::MESSAGE_VERSION}}, {MessageIdentifier{topic_name, T::MessageNewer::Request::MESSAGE_VERSION}}}); _service_translations.addResponseTranslation({translation_cb_to_older, {MessageIdentifier{topic_name, T::MessageNewer::Request::MESSAGE_VERSION}}, {MessageIdentifier{topic_name, T::MessageOlder::Request::MESSAGE_VERSION}}}); } } /** * @brief Register a translation class with N input and M output messages. * * The translation class has the form: * ``` * class MyTranslation { * public: * using MessagesOlder = TypesArray; * static constexpr const char* kTopicsOlder[] = { * "fmu/out/vehicle_global_position", * "fmu/out/vehicle_local_position", * ... * }; * * using MessagesNewer = TypesArray; * static constexpr const char* kTopicsNewer[] = { * "fmu/out/vehicle_global_position", * "fmu/out/vehicle_local_position", * ... * }; * * static void fromOlder(const MessagesOlder::Type1 &msg_older1, const MessagesOlder::Type2 &msg_older2, ... * MessagesNewer::Type1 &msg_newer1, MessagesNewer::Type2 &msg_newer2, ...) { * // Set msg_newerX from msg_olderX * } * * static void toOlder(const MessagesNewer::Type1 &msg_newer1, const MessagesNewer::Type2 &msg_newer2, ... * MessagesOlder::Type1 &msg_older1, MessagesOlder::Type2 &msg_older2, ...) { * // Set msg_olderX from msg_newerX * } * }; * ``` */ template void registerTranslation() { const auto topics_older = getTopicsForMessageType(typename T::MessagesOlder::args(), T::kTopicsOlder); std::vector topics_older_identifiers; for (const auto& topic : topics_older) { _topic_translations.addTopic(topic); topics_older_identifiers.emplace_back(topic.id); } const auto topics_newer = getTopicsForMessageType(typename T::MessagesNewer::args(),T::kTopicsNewer); std::vector topics_newer_identifiers; for (const auto& topic : topics_newer) { _topic_translations.addTopic(topic); topics_newer_identifiers.emplace_back(topic.id); } // Translation callbacks const auto translation_cb_from_older = [](const std::vector& older_msgs, std::vector& newer_msgs) { call_translation_function(&T::fromOlder, typename T::MessagesOlder::args(), typename T::MessagesNewer::args(), older_msgs, newer_msgs); }; const auto translation_cb_to_older = [](const std::vector& newer_msgs, std::vector& older_msgs) { call_translation_function(&T::toOlder, typename T::MessagesNewer::args(), typename T::MessagesOlder::args(), newer_msgs, older_msgs); }; { // Older -> Newer Translation translation; translation.cb = translation_cb_from_older; translation.inputs = topics_older_identifiers; translation.outputs = topics_newer_identifiers; _topic_translations.addTranslation(std::move(translation)); } { // Newer -> Older Translation translation; translation.cb = translation_cb_to_older; translation.inputs = topics_newer_identifiers; translation.outputs = topics_older_identifiers; _topic_translations.addTranslation(std::move(translation)); } } const TopicTranslations& topicTranslations() const { return _topic_translations; } const ServiceTranslations& serviceTranslations() const { return _service_translations; } protected: RegisteredTranslations() = default; private: template static size_t getMaxSerializedMessageSize() { const auto type_handle = rclcpp::get_message_type_support_handle(); const auto fastrtps_handle = rosidl_typesupport_cpp::get_message_typesupport_handle_function(&type_handle, "rosidl_typesupport_fastrtps_cpp"); if (fastrtps_handle) { const auto *callbacks = static_cast(fastrtps_handle->data); char bound_info; return callbacks->max_serialized_size(bound_info); } return 0; } template static Topic getTopicForMessageType(const std::string& topic_name) { Topic ret{}; ret.id.topic_name = topic_name; ret.id.version = RosMessageType::MESSAGE_VERSION; auto message_buffer = std::make_shared(); ret.message_buffer = std::static_pointer_cast(message_buffer); // Subscription/Publication factory methods const std::string topic_name_versioned = getVersionedTopicName(topic_name, ret.id.version); ret.subscription_factory = [topic_name_versioned, message_buffer](rclcpp::Node& node, const std::function& on_topic_cb) -> rclcpp::SubscriptionBase::SharedPtr { return std::dynamic_pointer_cast( // Note: template instantiation of subscriptions slows down compilation considerably, see // https://github.com/ros2/rclcpp/issues/1949 node.create_subscription(topic_name_versioned, rclcpp::QoS(1).best_effort(), [on_topic_cb=on_topic_cb, message_buffer](typename RosMessageType::UniquePtr msg) -> void { *message_buffer = *msg; on_topic_cb(); })); }; ret.publication_factory = [topic_name_versioned](rclcpp::Node& node) -> rclcpp::PublisherBase::SharedPtr { return std::dynamic_pointer_cast( node.create_publisher(topic_name_versioned, rclcpp::QoS(1).best_effort())); }; ret.max_serialized_message_size = getMaxSerializedMessageSize(); return ret; } template static Service getServiceForMessageType(const std::string& topic_name) { Service ret{}; ret.id.topic_name = topic_name; ret.id.version = RosMessageType::Request::MESSAGE_VERSION; auto message_buffer_request = std::make_shared(); ret.message_buffer_request = std::static_pointer_cast(message_buffer_request); auto message_buffer_response = std::make_shared(); ret.message_buffer_response = std::static_pointer_cast(message_buffer_response); // Service/client factory methods const std::string topic_name_versioned = getVersionedTopicName(topic_name, ret.id.version); ret.service_factory = [topic_name_versioned, message_buffer_request](rclcpp::Node& node, const std::function req_id)>& on_request_cb) -> rclcpp::ServiceBase::SharedPtr { return std::dynamic_pointer_cast( node.create_service(topic_name_versioned, [on_request_cb=on_request_cb, message_buffer_request]( typename rclcpp::Service::SharedPtr service, std::shared_ptr req_id, const std::shared_ptr request ) -> void { *message_buffer_request = *request; on_request_cb(std::move(req_id)); })); }; ret.client_factory = [topic_name_versioned, message_buffer_response](rclcpp::Node& node, const std::function& on_response_cb) { auto client = node.create_client(topic_name_versioned); client->set_on_new_response_callback([client, message_buffer_response, on_response_cb](size_t num) { for (size_t i = 0; i < num; i++) { rmw_request_id_t request_id{}; if (client->take_response(*message_buffer_response, request_id)) { on_response_cb(request_id); } } }); const auto send_request = [client](MessageBuffer request) { auto result = client->async_send_request(std::static_pointer_cast(request)); // We don't need the client to keep track of ongoing requests, so we remove it right away // to prevent leaks client->remove_pending_request(result.request_id); return result.request_id; }; return std::make_tuple(std::dynamic_pointer_cast(client), send_request); }; ret.publication_factory_request = [](rclcpp::Node& node, const std::string& topic_name) -> rclcpp::PublisherBase::SharedPtr { return std::dynamic_pointer_cast( node.create_publisher( topic_name,rclcpp::QoS(1).best_effort().avoid_ros_namespace_conventions(true))); }; ret.publication_factory_response = [](rclcpp::Node& node, const std::string& topic_name) -> rclcpp::PublisherBase::SharedPtr { return std::dynamic_pointer_cast( node.create_publisher( topic_name,rclcpp::QoS(1).best_effort().avoid_ros_namespace_conventions(true))); }; ret.max_serialized_message_size_request = getMaxSerializedMessageSize(); ret.max_serialized_message_size_response = getMaxSerializedMessageSize(); return ret; } template static std::vector getTopicsForMessageTypeImpl(const char* const topics[], std::integer_sequence) { std::vector ret { getTopicForMessageType(topics[Is])... }; return ret; } template static std::vector getTopicsForMessageType(Pack, const char* const (&topics)[N]) { static_assert(N == sizeof...(RosMessageTypes), "Number of topics does not match number of message types"); return getTopicsForMessageTypeImpl(topics, std::index_sequence_for{}); } TopicTranslations _topic_translations; ServiceTranslations _service_translations; }; template class RegistrationHelperDirect { public: explicit RegistrationHelperDirect(const char* dummy) { // There's something strange: when there is no argument passed, the // compiler removes the static object completely. I don't know // why but this dummy variable prevents that. (void)dummy; RegisteredTranslations::instance().registerDirectTranslation(); } explicit RegistrationHelperDirect(const char* dummy, bool for_service) { (void)dummy; RegisteredTranslations::instance().registerServiceDirectTranslation(); } RegistrationHelperDirect(RegistrationHelperDirect const&) = delete; void operator=(RegistrationHelperDirect const&) = delete; }; #define REGISTER_TOPIC_TRANSLATION_DIRECT(class_name) \ RegistrationHelperDirect class_name##_registration_direct("dummy"); #define REGISTER_SERVICE_TRANSLATION_DIRECT(class_name) \ RegistrationHelperDirect class_name##_service_registration_direct("dummy", true); template class TopicRegistrationHelperGeneric { public: explicit TopicRegistrationHelperGeneric(const char* dummy) { (void)dummy; RegisteredTranslations::instance().registerTranslation(); } TopicRegistrationHelperGeneric(TopicRegistrationHelperGeneric const&) = delete; void operator=(TopicRegistrationHelperGeneric const&) = delete; }; #define REGISTER_TOPIC_TRANSLATION(class_name) \ TopicRegistrationHelperGeneric class_name##_registration_generic("dummy");