diff --git a/LibCarla/cmake/fast_dds/CMakeLists.txt b/LibCarla/cmake/fast_dds/CMakeLists.txt index 4ff74c21d57..f51bbc430f7 100644 --- a/LibCarla/cmake/fast_dds/CMakeLists.txt +++ b/LibCarla/cmake/fast_dds/CMakeLists.txt @@ -9,6 +9,7 @@ file(GLOB libcarla_carla_fastdds_headers "${libcarla_source_path}/carla/ros2/subscribers/*.h" "${libcarla_source_path}/carla/ros2/listeners/*.h" "${libcarla_source_path}/carla/ros2/types/*.h" + "${libcarla_source_path}/carla/ros2/types/msg/*.h" "${libcarla_source_path}/carla/ros2/dds/*.h" "${libcarla_source_path}/carla/ros2/dds/fastdds/*.h" ) diff --git a/LibCarla/source/carla/ros2/dds/DDSMiddleware.h b/LibCarla/source/carla/ros2/dds/DDSMiddleware.h index 35a4d89be47..20f8804c628 100644 --- a/LibCarla/source/carla/ros2/dds/DDSMiddleware.h +++ b/LibCarla/source/carla/ros2/dds/DDSMiddleware.h @@ -52,5 +52,17 @@ inline std::string GetAvailableMiddlewareString() { return result; } +/// Mangle a DDS type name into the ROS2-compatible format. +/// "sensor_msgs::msg::Image" becomes "sensor_msgs::msg::dds_::Image_". +/// A bare name like "Image" becomes "dds_::Image_". +inline std::string ToROS2DDSTypeName(const std::string& dds_type_name) { + auto pos = dds_type_name.rfind("::"); + if (pos == std::string::npos) { + return "dds_::" + dds_type_name + "_"; + } + return dds_type_name.substr(0, pos) + + "::dds_::" + dds_type_name.substr(pos + 2) + "_"; +} + } // namespace ros2 } // namespace carla diff --git a/LibCarla/source/carla/ros2/dds/DDSMiddlewareFactory.h b/LibCarla/source/carla/ros2/dds/DDSMiddlewareFactory.h index 5b7b2adcd9a..51cc8f81143 100644 --- a/LibCarla/source/carla/ros2/dds/DDSMiddlewareFactory.h +++ b/LibCarla/source/carla/ros2/dds/DDSMiddlewareFactory.h @@ -12,7 +12,7 @@ #include "carla/ros2/dds/IDDSSubscriberMiddleware.h" #include "carla/Logging.h" -#if defined(CARLA_ROS2_DDS_FASTDDS) +#if defined(CARLA_ROS2_DDS_FASTDDS) && !defined(CARLA_ROS2_DDS_TESTING) # include "carla/ros2/dds/fastdds/FastDDSPublisherMiddleware.h" # include "carla/ros2/dds/fastdds/FastDDSSubscriberMiddleware.h" #endif @@ -77,7 +77,7 @@ class DDSMiddlewareFactory { static std::unique_ptr CreatePublisher() { switch (GetActiveMiddleware()) { case DDSMiddleware::FastDDS: -#if defined(CARLA_ROS2_DDS_FASTDDS) +#if defined(CARLA_ROS2_DDS_FASTDDS) && !defined(CARLA_ROS2_DDS_TESTING) return std::unique_ptr( new FastDDSPublisherMiddleware()); #else @@ -95,7 +95,7 @@ class DDSMiddlewareFactory { static std::unique_ptr CreateSubscriber() { switch (GetActiveMiddleware()) { case DDSMiddleware::FastDDS: -#if defined(CARLA_ROS2_DDS_FASTDDS) +#if defined(CARLA_ROS2_DDS_FASTDDS) && !defined(CARLA_ROS2_DDS_TESTING) return std::unique_ptr( new FastDDSSubscriberMiddleware()); #else diff --git a/LibCarla/source/carla/ros2/dds/fastdds/FastDDSTypeMap.h b/LibCarla/source/carla/ros2/dds/fastdds/FastDDSTypeMap.h index 7a292f3a2b1..1a9ad1935a1 100644 --- a/LibCarla/source/carla/ros2/dds/fastdds/FastDDSTypeMap.h +++ b/LibCarla/source/carla/ros2/dds/fastdds/FastDDSTypeMap.h @@ -2,22 +2,42 @@ // This work is licensed under the terms of the MIT license. // For a copy, see . -// Identity-mapping version: msg_type IS the FastDDS type, so each -// specialization maps a type to itself. - #pragma once +#include "carla/ros2/types/FastDDSConversions.h" + // PubSubType headers for type registration +#include "carla/ros2/types/TimePubSubTypes.h" +#include "carla/ros2/types/HeaderPubSubTypes.h" +#include "carla/ros2/types/Vector3PubSubTypes.h" +#include "carla/ros2/types/QuaternionPubSubTypes.h" +#include "carla/ros2/types/PointPubSubTypes.h" +#include "carla/ros2/types/Point32PubSubTypes.h" +#include "carla/ros2/types/PosePubSubTypes.h" +#include "carla/ros2/types/PoseWithCovariancePubSubTypes.h" +#include "carla/ros2/types/TwistPubSubTypes.h" +#include "carla/ros2/types/TwistWithCovariancePubSubTypes.h" +#include "carla/ros2/types/TransformPubSubTypes.h" +#include "carla/ros2/types/TransformStampedPubSubTypes.h" +#include "carla/ros2/types/OdometryPubSubTypes.h" +#include "carla/ros2/types/RegionOfInterestPubSubTypes.h" +#include "carla/ros2/types/PointFieldPubSubTypes.h" +#include "carla/ros2/types/NavSatStatusPubSubTypes.h" #include "carla/ros2/types/NavSatFixPubSubTypes.h" +#include "carla/ros2/types/ClockPubSubTypes.h" +#include "carla/ros2/types/Float32PubSubTypes.h" +#include "carla/ros2/types/StringPubSubTypes.h" +#include "carla/ros2/types/ImuPubSubTypes.h" #include "carla/ros2/types/ImagePubSubTypes.h" #include "carla/ros2/types/CameraInfoPubSubTypes.h" -#include "carla/ros2/types/ImuPubSubTypes.h" #include "carla/ros2/types/PointCloud2PubSubTypes.h" -#include "carla/ros2/types/ClockPubSubTypes.h" #include "carla/ros2/types/TFMessagePubSubTypes.h" +#include "carla/ros2/types/TF2ErrorPubSubTypes.h" +#include "carla/ros2/types/AckermannDrivePubSubTypes.h" +#include "carla/ros2/types/AckermannDriveStampedPubSubTypes.h" #include "carla/ros2/types/CarlaCollisionEventPubSubTypes.h" #include "carla/ros2/types/CarlaEgoVehicleControlPubSubTypes.h" -#include "carla/ros2/types/AckermannDriveStampedPubSubTypes.h" +#include "carla/ros2/types/CarlaLineInvasionPubSubTypes.h" namespace carla { namespace ros2 { @@ -26,9 +46,11 @@ namespace ros2 { /// Primary template is intentionally undefined — only specializations are valid. template struct FastDDSTypeMap; -// --- Identity specializations --- -// In the current codebase, msg_type is already the FastDDS-generated type. -// Each specialization maps the type to itself and its corresponding PubSubType. +// ============================================================ +// Identity specializations (msg_type IS the FastDDS type) +// Used by current publishers/subscribers until they are migrated +// to POD message types in PR #2b. +// ============================================================ template<> struct FastDDSTypeMap { using fastdds_type = sensor_msgs::msg::NavSatFix; @@ -81,7 +103,7 @@ template<> struct FastDDSTypeMap { }; /// Identity conversion: copy src to dst when both types are the same. -/// In the next phase, we will replace these with real POD-to-FastDDS conversions. +/// Used by publishers/subscribers that have not yet migrated to POD types. template inline void to_fastdds(const T& src, T& dst) { dst = src; @@ -92,5 +114,166 @@ inline void from_fastdds(const T& src, T& dst) { dst = src; } +// ============================================================ +// Real specializations (POD msg types -> FastDDS types) +// These map backend-neutral POD structs to FastDDS-generated types. +// Conversion functions are in types/FastDDSConversions.h. +// ============================================================ + +template<> struct FastDDSTypeMap { + using fastdds_type = builtin_interfaces::msg::Time; + using fastdds_pubsub_type = builtin_interfaces::msg::TimePubSubType; +}; + +template<> struct FastDDSTypeMap { + using fastdds_type = std_msgs::msg::Header; + using fastdds_pubsub_type = std_msgs::msg::HeaderPubSubType; +}; + +template<> struct FastDDSTypeMap { + using fastdds_type = geometry_msgs::msg::Vector3; + using fastdds_pubsub_type = geometry_msgs::msg::Vector3PubSubType; +}; + +template<> struct FastDDSTypeMap { + using fastdds_type = geometry_msgs::msg::Quaternion; + using fastdds_pubsub_type = geometry_msgs::msg::QuaternionPubSubType; +}; + +template<> struct FastDDSTypeMap { + using fastdds_type = geometry_msgs::msg::Point; + using fastdds_pubsub_type = geometry_msgs::msg::PointPubSubType; +}; + +template<> struct FastDDSTypeMap { + using fastdds_type = geometry_msgs::msg::Point32; + using fastdds_pubsub_type = geometry_msgs::msg::Point32PubSubType; +}; + +template<> struct FastDDSTypeMap { + using fastdds_type = geometry_msgs::msg::Pose; + using fastdds_pubsub_type = geometry_msgs::msg::PosePubSubType; +}; + +template<> struct FastDDSTypeMap { + using fastdds_type = geometry_msgs::msg::PoseWithCovariance; + using fastdds_pubsub_type = geometry_msgs::msg::PoseWithCovariancePubSubType; +}; + +template<> struct FastDDSTypeMap { + using fastdds_type = geometry_msgs::msg::Twist; + using fastdds_pubsub_type = geometry_msgs::msg::TwistPubSubType; +}; + +template<> struct FastDDSTypeMap { + using fastdds_type = geometry_msgs::msg::TwistWithCovariance; + using fastdds_pubsub_type = geometry_msgs::msg::TwistWithCovariancePubSubType; +}; + +template<> struct FastDDSTypeMap { + using fastdds_type = geometry_msgs::msg::Transform; + using fastdds_pubsub_type = geometry_msgs::msg::TransformPubSubType; +}; + +template<> struct FastDDSTypeMap { + using fastdds_type = geometry_msgs::msg::TransformStamped; + using fastdds_pubsub_type = geometry_msgs::msg::TransformStampedPubSubType; +}; + +template<> struct FastDDSTypeMap { + using fastdds_type = nav_msgs::msg::Odometry; + using fastdds_pubsub_type = nav_msgs::msg::OdometryPubSubType; +}; + +template<> struct FastDDSTypeMap { + using fastdds_type = sensor_msgs::msg::RegionOfInterest; + using fastdds_pubsub_type = sensor_msgs::msg::RegionOfInterestPubSubType; +}; + +template<> struct FastDDSTypeMap { + using fastdds_type = sensor_msgs::msg::PointField; + using fastdds_pubsub_type = sensor_msgs::msg::PointFieldPubSubType; +}; + +template<> struct FastDDSTypeMap { + using fastdds_type = sensor_msgs::msg::NavSatStatus; + using fastdds_pubsub_type = sensor_msgs::msg::NavSatStatusPubSubType; +}; + +template<> struct FastDDSTypeMap { + using fastdds_type = sensor_msgs::msg::NavSatFix; + using fastdds_pubsub_type = sensor_msgs::msg::NavSatFixPubSubType; +}; + +template<> struct FastDDSTypeMap { + using fastdds_type = rosgraph::msg::Clock; + using fastdds_pubsub_type = rosgraph::msg::ClockPubSubType; +}; + +template<> struct FastDDSTypeMap { + using fastdds_type = std_msgs::msg::Float32; + using fastdds_pubsub_type = std_msgs::msg::Float32PubSubType; +}; + +template<> struct FastDDSTypeMap { + using fastdds_type = std_msgs::msg::String; + using fastdds_pubsub_type = std_msgs::msg::StringPubSubType; +}; + +template<> struct FastDDSTypeMap { + using fastdds_type = sensor_msgs::msg::Imu; + using fastdds_pubsub_type = sensor_msgs::msg::ImuPubSubType; +}; + +template<> struct FastDDSTypeMap { + using fastdds_type = sensor_msgs::msg::Image; + using fastdds_pubsub_type = sensor_msgs::msg::ImagePubSubType; +}; + +template<> struct FastDDSTypeMap { + using fastdds_type = sensor_msgs::msg::CameraInfo; + using fastdds_pubsub_type = sensor_msgs::msg::CameraInfoPubSubType; +}; + +template<> struct FastDDSTypeMap { + using fastdds_type = sensor_msgs::msg::PointCloud2; + using fastdds_pubsub_type = sensor_msgs::msg::PointCloud2PubSubType; +}; + +template<> struct FastDDSTypeMap { + using fastdds_type = tf2_msgs::msg::TFMessage; + using fastdds_pubsub_type = tf2_msgs::msg::TFMessagePubSubType; +}; + +template<> struct FastDDSTypeMap { + using fastdds_type = tf2_msgs::msg::TF2Error; + using fastdds_pubsub_type = tf2_msgs::msg::TF2ErrorPubSubType; +}; + +template<> struct FastDDSTypeMap { + using fastdds_type = ackermann_msgs::msg::AckermannDrive; + using fastdds_pubsub_type = ackermann_msgs::msg::AckermannDrivePubSubType; +}; + +template<> struct FastDDSTypeMap { + using fastdds_type = ackermann_msgs::msg::AckermannDriveStamped; + using fastdds_pubsub_type = ackermann_msgs::msg::AckermannDriveStampedPubSubType; +}; + +template<> struct FastDDSTypeMap { + using fastdds_type = carla_msgs::msg::CarlaCollisionEvent; + using fastdds_pubsub_type = carla_msgs::msg::CarlaCollisionEventPubSubType; +}; + +template<> struct FastDDSTypeMap { + using fastdds_type = carla_msgs::msg::CarlaEgoVehicleControl; + using fastdds_pubsub_type = carla_msgs::msg::CarlaEgoVehicleControlPubSubType; +}; + +template<> struct FastDDSTypeMap { + using fastdds_type = carla_msgs::msg::LaneInvasionEvent; + using fastdds_pubsub_type = carla_msgs::msg::LaneInvasionEventPubSubType; +}; + } // namespace ros2 } // namespace carla diff --git a/LibCarla/source/carla/ros2/publishers/PublisherImpl.h b/LibCarla/source/carla/ros2/publishers/PublisherImpl.h index 9d46419eab7..63495013725 100644 --- a/LibCarla/source/carla/ros2/publishers/PublisherImpl.h +++ b/LibCarla/source/carla/ros2/publishers/PublisherImpl.h @@ -19,11 +19,17 @@ namespace ros2 { using msg_type = typename T::msg_type; bool Init(std::string topic_name) { - _middleware = DDSMiddlewareFactory::CreatePublisher(); +#ifdef LIBCARLA_WITH_GTEST if (!_middleware) { - log_error("PublisherImpl: Failed to create middleware publisher"); - return false; +#endif + _middleware = DDSMiddlewareFactory::CreatePublisher(); + if (!_middleware) { + log_error("PublisherImpl: Failed to create middleware publisher"); + return false; + } +#ifdef LIBCARLA_WITH_GTEST } +#endif return _middleware->Init(topic_name); } @@ -53,6 +59,12 @@ namespace ros2 { return _middleware->Publish(&_message); } +#ifdef LIBCARLA_WITH_GTEST + void SetMiddlewareForTesting(std::unique_ptr middleware) { + _middleware = std::move(middleware); + } +#endif + private: std::unique_ptr _middleware; msg_type _message; diff --git a/LibCarla/source/carla/ros2/subscribers/SubscriberImpl.h b/LibCarla/source/carla/ros2/subscribers/SubscriberImpl.h index 5d1f21ea5c5..8938496d64e 100644 --- a/LibCarla/source/carla/ros2/subscribers/SubscriberImpl.h +++ b/LibCarla/source/carla/ros2/subscribers/SubscriberImpl.h @@ -20,11 +20,17 @@ namespace ros2 { using msg_type = typename S::msg_type; bool Init(std::string topic_name) { - _middleware = DDSMiddlewareFactory::CreateSubscriber(); +#ifdef LIBCARLA_WITH_GTEST if (!_middleware) { - log_error("SubscriberImpl: Failed to create middleware subscriber"); - return false; +#endif + _middleware = DDSMiddlewareFactory::CreateSubscriber(); + if (!_middleware) { + log_error("SubscriberImpl: Failed to create middleware subscriber"); + return false; + } +#ifdef LIBCARLA_WITH_GTEST } +#endif return _middleware->Init(topic_name, &_message, &_new_message); } @@ -49,6 +55,17 @@ namespace ros2 { bool HasNewMessage() { return _new_message; } +#ifdef LIBCARLA_WITH_GTEST + void SetMiddlewareForTesting(std::unique_ptr middleware) { + _middleware = std::move(middleware); + } + + void SimulateMessageReceiptForTesting(const msg_type& msg) { + _message = msg; + _new_message = true; + } +#endif + private: std::unique_ptr _middleware; msg_type _message; diff --git a/LibCarla/source/carla/ros2/types/FastDDSConversions.h b/LibCarla/source/carla/ros2/types/FastDDSConversions.h new file mode 100644 index 00000000000..9fdb7739ed7 --- /dev/null +++ b/LibCarla/source/carla/ros2/types/FastDDSConversions.h @@ -0,0 +1,623 @@ +// Copyright (c) 2025 Computer Vision Center (CVC) at the Universitat Autonoma de Barcelona (UAB). +// This work is licensed under the terms of the MIT license. +// For a copy, see . + +#pragma once + +// POD message types +#include "carla/ros2/types/msg/Time.h" +#include "carla/ros2/types/msg/Header.h" +#include "carla/ros2/types/msg/Vector3.h" +#include "carla/ros2/types/msg/Quaternion.h" +#include "carla/ros2/types/msg/Point.h" +#include "carla/ros2/types/msg/Point32.h" +#include "carla/ros2/types/msg/Pose.h" +#include "carla/ros2/types/msg/PoseWithCovariance.h" +#include "carla/ros2/types/msg/Twist.h" +#include "carla/ros2/types/msg/TwistWithCovariance.h" +#include "carla/ros2/types/msg/Transform.h" +#include "carla/ros2/types/msg/TransformStamped.h" +#include "carla/ros2/types/msg/Odometry.h" +#include "carla/ros2/types/msg/RegionOfInterest.h" +#include "carla/ros2/types/msg/PointField.h" +#include "carla/ros2/types/msg/NavSatStatus.h" +#include "carla/ros2/types/msg/NavSatFix.h" +#include "carla/ros2/types/msg/Clock.h" +#include "carla/ros2/types/msg/Float32.h" +#include "carla/ros2/types/msg/String.h" +#include "carla/ros2/types/msg/Imu.h" +#include "carla/ros2/types/msg/Image.h" +#include "carla/ros2/types/msg/CameraInfo.h" +#include "carla/ros2/types/msg/PointCloud2.h" +#include "carla/ros2/types/msg/TFMessage.h" +#include "carla/ros2/types/msg/TF2Error.h" +#include "carla/ros2/types/msg/AckermannDrive.h" +#include "carla/ros2/types/msg/AckermannDriveStamped.h" +#include "carla/ros2/types/msg/CarlaCollisionEvent.h" +#include "carla/ros2/types/msg/CarlaEgoVehicleControl.h" +#include "carla/ros2/types/msg/CarlaLineInvasion.h" + +// FastDDS-generated types (current flat layout) +#include "carla/ros2/types/Time.h" +#include "carla/ros2/types/Header.h" +#include "carla/ros2/types/Vector3.h" +#include "carla/ros2/types/Quaternion.h" +#include "carla/ros2/types/Point.h" +#include "carla/ros2/types/Point32.h" +#include "carla/ros2/types/Pose.h" +#include "carla/ros2/types/PoseWithCovariance.h" +#include "carla/ros2/types/Twist.h" +#include "carla/ros2/types/TwistWithCovariance.h" +#include "carla/ros2/types/Transform.h" +#include "carla/ros2/types/TransformStamped.h" +#include "carla/ros2/types/Odometry.h" +#include "carla/ros2/types/RegionOfInterest.h" +#include "carla/ros2/types/PointField.h" +#include "carla/ros2/types/NavSatStatus.h" +#include "carla/ros2/types/NavSatFix.h" +#include "carla/ros2/types/Clock.h" +#include "carla/ros2/types/Float32.h" +#include "carla/ros2/types/String.h" +#include "carla/ros2/types/Imu.h" +#include "carla/ros2/types/Image.h" +#include "carla/ros2/types/CameraInfo.h" +#include "carla/ros2/types/PointCloud2.h" +#include "carla/ros2/types/TFMessage.h" +#include "carla/ros2/types/TF2Error.h" +#include "carla/ros2/types/AckermannDrive.h" +#include "carla/ros2/types/AckermannDriveStamped.h" +#include "carla/ros2/types/CarlaCollisionEvent.h" +#include "carla/ros2/types/CarlaEgoVehicleControl.h" +#include "carla/ros2/types/CarlaLineInvasion.h" + +#include + +namespace carla { +namespace ros2 { + +// ============================================================ +// Conversion functions: POD msg types <-> FastDDS-generated types +// Order matters — composites call primitives. +// ============================================================ + +// --- Primitives --- + +inline void to_fastdds(const msg::Time& src, builtin_interfaces::msg::Time& dst) { + dst.sec(src.sec); + dst.nanosec(src.nanosec); +} + +inline void from_fastdds(const builtin_interfaces::msg::Time& src, msg::Time& dst) { + dst.sec = src.sec(); + dst.nanosec = src.nanosec(); +} + +inline void to_fastdds(const msg::Header& src, std_msgs::msg::Header& dst) { + to_fastdds(src.stamp, dst.stamp()); + dst.frame_id(src.frame_id); +} + +inline void from_fastdds(const std_msgs::msg::Header& src, msg::Header& dst) { + from_fastdds(src.stamp(), dst.stamp); + dst.frame_id = src.frame_id(); +} + +inline void to_fastdds(const msg::Vector3& src, geometry_msgs::msg::Vector3& dst) { + dst.x(src.x); + dst.y(src.y); + dst.z(src.z); +} + +inline void from_fastdds(const geometry_msgs::msg::Vector3& src, msg::Vector3& dst) { + dst.x = src.x(); + dst.y = src.y(); + dst.z = src.z(); +} + +inline void to_fastdds( + const msg::Quaternion& src, + geometry_msgs::msg::Quaternion& dst) { + dst.x(src.x); + dst.y(src.y); + dst.z(src.z); + dst.w(src.w); +} + +inline void from_fastdds( + const geometry_msgs::msg::Quaternion& src, + msg::Quaternion& dst) { + dst.x = src.x(); + dst.y = src.y(); + dst.z = src.z(); + dst.w = src.w(); +} + +inline void to_fastdds(const msg::Point& src, geometry_msgs::msg::Point& dst) { + dst.x(src.x); + dst.y(src.y); + dst.z(src.z); +} + +inline void from_fastdds(const geometry_msgs::msg::Point& src, msg::Point& dst) { + dst.x = src.x(); + dst.y = src.y(); + dst.z = src.z(); +} + +inline void to_fastdds(const msg::Point32& src, geometry_msgs::msg::Point32& dst) { + dst.x(src.x); + dst.y(src.y); + dst.z(src.z); +} + +inline void from_fastdds(const geometry_msgs::msg::Point32& src, msg::Point32& dst) { + dst.x = src.x(); + dst.y = src.y(); + dst.z = src.z(); +} + +// --- Geometry composites --- + +inline void to_fastdds(const msg::Pose& src, geometry_msgs::msg::Pose& dst) { + to_fastdds(src.position, dst.position()); + to_fastdds(src.orientation, dst.orientation()); +} + +inline void from_fastdds(const geometry_msgs::msg::Pose& src, msg::Pose& dst) { + from_fastdds(src.position(), dst.position); + from_fastdds(src.orientation(), dst.orientation); +} + +inline void to_fastdds( + const msg::PoseWithCovariance& src, + geometry_msgs::msg::PoseWithCovariance& dst) { + to_fastdds(src.pose, dst.pose()); + dst.covariance(src.covariance); +} + +inline void from_fastdds( + const geometry_msgs::msg::PoseWithCovariance& src, + msg::PoseWithCovariance& dst) { + from_fastdds(src.pose(), dst.pose); + dst.covariance = src.covariance(); +} + +inline void to_fastdds(const msg::Twist& src, geometry_msgs::msg::Twist& dst) { + to_fastdds(src.linear, dst.linear()); + to_fastdds(src.angular, dst.angular()); +} + +inline void from_fastdds(const geometry_msgs::msg::Twist& src, msg::Twist& dst) { + from_fastdds(src.linear(), dst.linear); + from_fastdds(src.angular(), dst.angular); +} + +inline void to_fastdds( + const msg::TwistWithCovariance& src, + geometry_msgs::msg::TwistWithCovariance& dst) { + to_fastdds(src.twist, dst.twist()); + dst.covariance(src.covariance); +} + +inline void from_fastdds( + const geometry_msgs::msg::TwistWithCovariance& src, + msg::TwistWithCovariance& dst) { + from_fastdds(src.twist(), dst.twist); + dst.covariance = src.covariance(); +} + +inline void to_fastdds( + const msg::Transform& src, + geometry_msgs::msg::Transform& dst) { + to_fastdds(src.translation, dst.translation()); + to_fastdds(src.rotation, dst.rotation()); +} + +inline void from_fastdds( + const geometry_msgs::msg::Transform& src, + msg::Transform& dst) { + from_fastdds(src.translation(), dst.translation); + from_fastdds(src.rotation(), dst.rotation); +} + +inline void to_fastdds( + const msg::TransformStamped& src, + geometry_msgs::msg::TransformStamped& dst) { + to_fastdds(src.header, dst.header()); + dst.child_frame_id(src.child_frame_id); + to_fastdds(src.transform, dst.transform()); +} + +inline void from_fastdds( + const geometry_msgs::msg::TransformStamped& src, + msg::TransformStamped& dst) { + from_fastdds(src.header(), dst.header); + dst.child_frame_id = src.child_frame_id(); + from_fastdds(src.transform(), dst.transform); +} + +// --- Sensor primitives --- + +inline void to_fastdds( + const msg::RegionOfInterest& src, + sensor_msgs::msg::RegionOfInterest& dst) { + dst.x_offset(src.x_offset); + dst.y_offset(src.y_offset); + dst.height(src.height); + dst.width(src.width); + dst.do_rectify(src.do_rectify); +} + +inline void from_fastdds( + const sensor_msgs::msg::RegionOfInterest& src, + msg::RegionOfInterest& dst) { + dst.x_offset = src.x_offset(); + dst.y_offset = src.y_offset(); + dst.height = src.height(); + dst.width = src.width(); + dst.do_rectify = src.do_rectify(); +} + +inline void to_fastdds( + const msg::PointField& src, + sensor_msgs::msg::PointField& dst) { + dst.name(src.name); + dst.offset(src.offset); + dst.datatype(src.datatype); + dst.count(src.count); +} + +inline void from_fastdds( + const sensor_msgs::msg::PointField& src, + msg::PointField& dst) { + dst.name = src.name(); + dst.offset = src.offset(); + dst.datatype = src.datatype(); + dst.count = src.count(); +} + +inline void to_fastdds( + const msg::NavSatStatus& src, + sensor_msgs::msg::NavSatStatus& dst) { + dst.status(src.status); + dst.service(src.service); +} + +inline void from_fastdds( + const sensor_msgs::msg::NavSatStatus& src, + msg::NavSatStatus& dst) { + dst.status = src.status(); + dst.service = src.service(); +} + +// --- Simple messages --- + +inline void to_fastdds(const msg::Clock& src, rosgraph::msg::Clock& dst) { + to_fastdds(src.clock, dst.clock()); +} + +inline void from_fastdds(const rosgraph::msg::Clock& src, msg::Clock& dst) { + from_fastdds(src.clock(), dst.clock); +} + +inline void to_fastdds(const msg::Float32& src, std_msgs::msg::Float32& dst) { + dst.data(src.data); +} + +inline void from_fastdds(const std_msgs::msg::Float32& src, msg::Float32& dst) { + dst.data = src.data(); +} + +inline void to_fastdds(const msg::String& src, std_msgs::msg::String& dst) { + dst.data(src.data); +} + +inline void from_fastdds(const std_msgs::msg::String& src, msg::String& dst) { + dst.data = src.data(); +} + +// --- Complex sensor messages --- + +inline void to_fastdds(const msg::Imu& src, sensor_msgs::msg::Imu& dst) { + to_fastdds(src.header, dst.header()); + to_fastdds(src.orientation, dst.orientation()); + dst.orientation_covariance(src.orientation_covariance); + to_fastdds(src.angular_velocity, dst.angular_velocity()); + dst.angular_velocity_covariance(src.angular_velocity_covariance); + to_fastdds(src.linear_acceleration, dst.linear_acceleration()); + dst.linear_acceleration_covariance(src.linear_acceleration_covariance); +} + +inline void from_fastdds(const sensor_msgs::msg::Imu& src, msg::Imu& dst) { + from_fastdds(src.header(), dst.header); + from_fastdds(src.orientation(), dst.orientation); + dst.orientation_covariance = src.orientation_covariance(); + from_fastdds(src.angular_velocity(), dst.angular_velocity); + dst.angular_velocity_covariance = src.angular_velocity_covariance(); + from_fastdds(src.linear_acceleration(), dst.linear_acceleration); + dst.linear_acceleration_covariance = src.linear_acceleration_covariance(); +} + +inline void to_fastdds( + const msg::NavSatFix& src, + sensor_msgs::msg::NavSatFix& dst) { + to_fastdds(src.header, dst.header()); + to_fastdds(src.status, dst.status()); + dst.latitude(src.latitude); + dst.longitude(src.longitude); + dst.altitude(src.altitude); + dst.position_covariance(src.position_covariance); + dst.position_covariance_type(src.position_covariance_type); +} + +inline void from_fastdds( + const sensor_msgs::msg::NavSatFix& src, + msg::NavSatFix& dst) { + from_fastdds(src.header(), dst.header); + from_fastdds(src.status(), dst.status); + dst.latitude = src.latitude(); + dst.longitude = src.longitude(); + dst.altitude = src.altitude(); + dst.position_covariance = src.position_covariance(); + dst.position_covariance_type = src.position_covariance_type(); +} + +// Image: const-ref overload (copies data vector) +inline void to_fastdds(const msg::Image& src, sensor_msgs::msg::Image& dst) { + to_fastdds(src.header, dst.header()); + dst.height(src.height); + dst.width(src.width); + dst.encoding(src.encoding); + dst.is_bigendian(src.is_bigendian); + dst.step(src.step); + dst.data(src.data); +} + +// Image: non-const overload (moves data vector) +inline void to_fastdds(msg::Image& src, sensor_msgs::msg::Image& dst) { + to_fastdds(src.header, dst.header()); + dst.height(src.height); + dst.width(src.width); + dst.encoding(std::move(src.encoding)); + dst.is_bigendian(src.is_bigendian); + dst.step(src.step); + dst.data(std::move(src.data)); +} + +inline void from_fastdds(const sensor_msgs::msg::Image& src, msg::Image& dst) { + from_fastdds(src.header(), dst.header); + dst.height = src.height(); + dst.width = src.width(); + dst.encoding = src.encoding(); + dst.is_bigendian = src.is_bigendian(); + dst.step = src.step(); + dst.data = src.data(); +} + +inline void to_fastdds( + const msg::CameraInfo& src, + sensor_msgs::msg::CameraInfo& dst) { + to_fastdds(src.header, dst.header()); + dst.height(src.height); + dst.width(src.width); + dst.distortion_model(src.distortion_model); + dst.D(src.d); + dst.k(src.k); + dst.r(src.r); + dst.p(src.p); + dst.binning_x(src.binning_x); + dst.binning_y(src.binning_y); + to_fastdds(src.roi, dst.roi()); +} + +inline void from_fastdds( + const sensor_msgs::msg::CameraInfo& src, + msg::CameraInfo& dst) { + from_fastdds(src.header(), dst.header); + dst.height = src.height(); + dst.width = src.width(); + dst.distortion_model = src.distortion_model(); + dst.d = src.D(); + dst.k = src.k(); + dst.r = src.r(); + dst.p = src.p(); + dst.binning_x = src.binning_x(); + dst.binning_y = src.binning_y(); + from_fastdds(src.roi(), dst.roi); +} + +// PointCloud2: const-ref overload (copies data vector) +inline void to_fastdds( + const msg::PointCloud2& src, + sensor_msgs::msg::PointCloud2& dst) { + to_fastdds(src.header, dst.header()); + dst.height(src.height); + dst.width(src.width); + std::vector fields(src.fields.size()); + for (size_t i = 0; i < src.fields.size(); ++i) { + to_fastdds(src.fields[i], fields[i]); + } + dst.fields(std::move(fields)); + dst.is_bigendian(src.is_bigendian); + dst.point_step(src.point_step); + dst.row_step(src.row_step); + dst.data(src.data); + dst.is_dense(src.is_dense); +} + +// PointCloud2: non-const overload (moves data vector) +inline void to_fastdds( + msg::PointCloud2& src, + sensor_msgs::msg::PointCloud2& dst) { + to_fastdds(src.header, dst.header()); + dst.height(src.height); + dst.width(src.width); + std::vector fields(src.fields.size()); + for (size_t i = 0; i < src.fields.size(); ++i) { + to_fastdds(src.fields[i], fields[i]); + } + dst.fields(std::move(fields)); + dst.is_bigendian(src.is_bigendian); + dst.point_step(src.point_step); + dst.row_step(src.row_step); + dst.data(std::move(src.data)); + dst.is_dense(src.is_dense); +} + +inline void from_fastdds( + const sensor_msgs::msg::PointCloud2& src, + msg::PointCloud2& dst) { + from_fastdds(src.header(), dst.header); + dst.height = src.height(); + dst.width = src.width(); + dst.fields.resize(src.fields().size()); + for (size_t i = 0; i < src.fields().size(); ++i) { + from_fastdds(src.fields()[i], dst.fields[i]); + } + dst.is_bigendian = src.is_bigendian(); + dst.point_step = src.point_step(); + dst.row_step = src.row_step(); + dst.data = src.data(); + dst.is_dense = src.is_dense(); +} + +// --- tf2 messages --- + +inline void to_fastdds(const msg::TFMessage& src, tf2_msgs::msg::TFMessage& dst) { + std::vector transforms(src.transforms.size()); + for (size_t i = 0; i < src.transforms.size(); ++i) { + to_fastdds(src.transforms[i], transforms[i]); + } + dst.transforms(std::move(transforms)); +} + +inline void from_fastdds(const tf2_msgs::msg::TFMessage& src, msg::TFMessage& dst) { + dst.transforms.resize(src.transforms().size()); + for (size_t i = 0; i < src.transforms().size(); ++i) { + from_fastdds(src.transforms()[i], dst.transforms[i]); + } +} + +inline void to_fastdds(const msg::TF2Error& src, tf2_msgs::msg::TF2Error& dst) { + dst.error(src.error); + dst.error_string(src.error_string); +} + +inline void from_fastdds(const tf2_msgs::msg::TF2Error& src, msg::TF2Error& dst) { + dst.error = src.error(); + dst.error_string = src.error_string(); +} + +// --- Navigation --- + +inline void to_fastdds(const msg::Odometry& src, nav_msgs::msg::Odometry& dst) { + to_fastdds(src.header, dst.header()); + dst.child_frame_id(src.child_frame_id); + to_fastdds(src.pose, dst.pose()); + to_fastdds(src.twist, dst.twist()); +} + +inline void from_fastdds(const nav_msgs::msg::Odometry& src, msg::Odometry& dst) { + from_fastdds(src.header(), dst.header); + dst.child_frame_id = src.child_frame_id(); + from_fastdds(src.pose(), dst.pose); + from_fastdds(src.twist(), dst.twist); +} + +// --- Ackermann --- + +inline void to_fastdds( + const msg::AckermannDrive& src, + ackermann_msgs::msg::AckermannDrive& dst) { + dst.steering_angle(src.steering_angle); + dst.steering_angle_velocity(src.steering_angle_velocity); + dst.speed(src.speed); + dst.acceleration(src.acceleration); + dst.jerk(src.jerk); +} + +inline void from_fastdds( + const ackermann_msgs::msg::AckermannDrive& src, + msg::AckermannDrive& dst) { + dst.steering_angle = src.steering_angle(); + dst.steering_angle_velocity = src.steering_angle_velocity(); + dst.speed = src.speed(); + dst.acceleration = src.acceleration(); + dst.jerk = src.jerk(); +} + +inline void to_fastdds( + const msg::AckermannDriveStamped& src, + ackermann_msgs::msg::AckermannDriveStamped& dst) { + to_fastdds(src.header, dst.header()); + to_fastdds(src.drive, dst.drive()); +} + +inline void from_fastdds( + const ackermann_msgs::msg::AckermannDriveStamped& src, + msg::AckermannDriveStamped& dst) { + from_fastdds(src.header(), dst.header); + from_fastdds(src.drive(), dst.drive); +} + +// --- CARLA custom messages --- + +inline void to_fastdds( + const msg::CarlaCollisionEvent& src, + carla_msgs::msg::CarlaCollisionEvent& dst) { + to_fastdds(src.header, dst.header()); + dst.other_actor_id(src.other_actor_id); + to_fastdds(src.normal_impulse, dst.normal_impulse()); +} + +inline void from_fastdds( + const carla_msgs::msg::CarlaCollisionEvent& src, + msg::CarlaCollisionEvent& dst) { + from_fastdds(src.header(), dst.header); + dst.other_actor_id = src.other_actor_id(); + from_fastdds(src.normal_impulse(), dst.normal_impulse); +} + +inline void to_fastdds( + const msg::CarlaEgoVehicleControl& src, + carla_msgs::msg::CarlaEgoVehicleControl& dst) { + to_fastdds(src.header, dst.header()); + dst.throttle(src.throttle); + dst.steer(src.steer); + dst.brake(src.brake); + dst.hand_brake(src.hand_brake); + dst.reverse(src.reverse); + dst.gear(src.gear); + dst.manual_gear_shift(src.manual_gear_shift); +} + +inline void from_fastdds( + const carla_msgs::msg::CarlaEgoVehicleControl& src, + msg::CarlaEgoVehicleControl& dst) { + from_fastdds(src.header(), dst.header); + dst.throttle = src.throttle(); + dst.steer = src.steer(); + dst.brake = src.brake(); + dst.hand_brake = src.hand_brake(); + dst.reverse = src.reverse(); + dst.gear = src.gear(); + dst.manual_gear_shift = src.manual_gear_shift(); +} + +// POD is CarlaLineInvasion, FastDDS class is LaneInvasionEvent +inline void to_fastdds( + const msg::CarlaLineInvasion& src, + carla_msgs::msg::LaneInvasionEvent& dst) { + to_fastdds(src.header, dst.header()); + dst.crossed_lane_markings(src.crossed_lane_markings); +} + +inline void from_fastdds( + const carla_msgs::msg::LaneInvasionEvent& src, + msg::CarlaLineInvasion& dst) { + from_fastdds(src.header(), dst.header); + dst.crossed_lane_markings = src.crossed_lane_markings(); +} + +} // namespace ros2 +} // namespace carla diff --git a/LibCarla/source/carla/ros2/types/msg/AckermannDrive.h b/LibCarla/source/carla/ros2/types/msg/AckermannDrive.h new file mode 100644 index 00000000000..259021b6f96 --- /dev/null +++ b/LibCarla/source/carla/ros2/types/msg/AckermannDrive.h @@ -0,0 +1,21 @@ +// Copyright (c) 2025 Computer Vision Center (CVC) at the Universitat Autonoma de Barcelona (UAB). +// This work is licensed under the terms of the MIT license. +// For a copy, see . + +#pragma once + +namespace carla { +namespace ros2 { +namespace msg { + +struct AckermannDrive { + float steering_angle = 0.0f; + float steering_angle_velocity = 0.0f; + float speed = 0.0f; + float acceleration = 0.0f; + float jerk = 0.0f; +}; + +} // namespace msg +} // namespace ros2 +} // namespace carla diff --git a/LibCarla/source/carla/ros2/types/msg/AckermannDriveStamped.h b/LibCarla/source/carla/ros2/types/msg/AckermannDriveStamped.h new file mode 100644 index 00000000000..003fe6823c1 --- /dev/null +++ b/LibCarla/source/carla/ros2/types/msg/AckermannDriveStamped.h @@ -0,0 +1,20 @@ +// Copyright (c) 2025 Computer Vision Center (CVC) at the Universitat Autonoma de Barcelona (UAB). +// This work is licensed under the terms of the MIT license. +// For a copy, see . + +#pragma once +#include "carla/ros2/types/msg/Header.h" +#include "carla/ros2/types/msg/AckermannDrive.h" + +namespace carla { +namespace ros2 { +namespace msg { + +struct AckermannDriveStamped { + Header header; + AckermannDrive drive; +}; + +} // namespace msg +} // namespace ros2 +} // namespace carla diff --git a/LibCarla/source/carla/ros2/types/msg/CameraInfo.h b/LibCarla/source/carla/ros2/types/msg/CameraInfo.h new file mode 100644 index 00000000000..1bc8fbf3a73 --- /dev/null +++ b/LibCarla/source/carla/ros2/types/msg/CameraInfo.h @@ -0,0 +1,33 @@ +// Copyright (c) 2025 Computer Vision Center (CVC) at the Universitat Autonoma de Barcelona (UAB). +// This work is licensed under the terms of the MIT license. +// For a copy, see . + +#pragma once +#include +#include +#include +#include +#include "carla/ros2/types/msg/Header.h" +#include "carla/ros2/types/msg/RegionOfInterest.h" + +namespace carla { +namespace ros2 { +namespace msg { + +struct CameraInfo { + Header header; + uint32_t height = 0; + uint32_t width = 0; + std::string distortion_model; + std::vector d; + std::array k = {}; + std::array r = {}; + std::array p = {}; + uint32_t binning_x = 0; + uint32_t binning_y = 0; + RegionOfInterest roi; +}; + +} // namespace msg +} // namespace ros2 +} // namespace carla diff --git a/LibCarla/source/carla/ros2/types/msg/CarlaCollisionEvent.h b/LibCarla/source/carla/ros2/types/msg/CarlaCollisionEvent.h new file mode 100644 index 00000000000..791cc9f90b9 --- /dev/null +++ b/LibCarla/source/carla/ros2/types/msg/CarlaCollisionEvent.h @@ -0,0 +1,22 @@ +// Copyright (c) 2025 Computer Vision Center (CVC) at the Universitat Autonoma de Barcelona (UAB). +// This work is licensed under the terms of the MIT license. +// For a copy, see . + +#pragma once +#include +#include "carla/ros2/types/msg/Header.h" +#include "carla/ros2/types/msg/Vector3.h" + +namespace carla { +namespace ros2 { +namespace msg { + +struct CarlaCollisionEvent { + Header header; + uint32_t other_actor_id = 0; + Vector3 normal_impulse; +}; + +} // namespace msg +} // namespace ros2 +} // namespace carla diff --git a/LibCarla/source/carla/ros2/types/msg/CarlaEgoVehicleControl.h b/LibCarla/source/carla/ros2/types/msg/CarlaEgoVehicleControl.h new file mode 100644 index 00000000000..9c7b90c4b96 --- /dev/null +++ b/LibCarla/source/carla/ros2/types/msg/CarlaEgoVehicleControl.h @@ -0,0 +1,26 @@ +// Copyright (c) 2025 Computer Vision Center (CVC) at the Universitat Autonoma de Barcelona (UAB). +// This work is licensed under the terms of the MIT license. +// For a copy, see . + +#pragma once +#include +#include "carla/ros2/types/msg/Header.h" + +namespace carla { +namespace ros2 { +namespace msg { + +struct CarlaEgoVehicleControl { + Header header; + float throttle = 0.0f; + float steer = 0.0f; + float brake = 0.0f; + bool hand_brake = false; + bool reverse = false; + int32_t gear = 0; + bool manual_gear_shift = false; +}; + +} // namespace msg +} // namespace ros2 +} // namespace carla diff --git a/LibCarla/source/carla/ros2/types/msg/CarlaLineInvasion.h b/LibCarla/source/carla/ros2/types/msg/CarlaLineInvasion.h new file mode 100644 index 00000000000..758ace94505 --- /dev/null +++ b/LibCarla/source/carla/ros2/types/msg/CarlaLineInvasion.h @@ -0,0 +1,21 @@ +// Copyright (c) 2025 Computer Vision Center (CVC) at the Universitat Autonoma de Barcelona (UAB). +// This work is licensed under the terms of the MIT license. +// For a copy, see . + +#pragma once +#include +#include +#include "carla/ros2/types/msg/Header.h" + +namespace carla { +namespace ros2 { +namespace msg { + +struct CarlaLineInvasion { + Header header; + std::vector crossed_lane_markings; +}; + +} // namespace msg +} // namespace ros2 +} // namespace carla diff --git a/LibCarla/source/carla/ros2/types/msg/Clock.h b/LibCarla/source/carla/ros2/types/msg/Clock.h new file mode 100644 index 00000000000..c3aeb22ef23 --- /dev/null +++ b/LibCarla/source/carla/ros2/types/msg/Clock.h @@ -0,0 +1,18 @@ +// Copyright (c) 2025 Computer Vision Center (CVC) at the Universitat Autonoma de Barcelona (UAB). +// This work is licensed under the terms of the MIT license. +// For a copy, see . + +#pragma once +#include "carla/ros2/types/msg/Time.h" + +namespace carla { +namespace ros2 { +namespace msg { + +struct Clock { + Time clock; +}; + +} // namespace msg +} // namespace ros2 +} // namespace carla diff --git a/LibCarla/source/carla/ros2/types/msg/Float32.h b/LibCarla/source/carla/ros2/types/msg/Float32.h new file mode 100644 index 00000000000..6452f152256 --- /dev/null +++ b/LibCarla/source/carla/ros2/types/msg/Float32.h @@ -0,0 +1,17 @@ +// Copyright (c) 2025 Computer Vision Center (CVC) at the Universitat Autonoma de Barcelona (UAB). +// This work is licensed under the terms of the MIT license. +// For a copy, see . + +#pragma once + +namespace carla { +namespace ros2 { +namespace msg { + +struct Float32 { + float data = 0.0f; +}; + +} // namespace msg +} // namespace ros2 +} // namespace carla diff --git a/LibCarla/source/carla/ros2/types/msg/Header.h b/LibCarla/source/carla/ros2/types/msg/Header.h new file mode 100644 index 00000000000..7def89c2f96 --- /dev/null +++ b/LibCarla/source/carla/ros2/types/msg/Header.h @@ -0,0 +1,20 @@ +// Copyright (c) 2025 Computer Vision Center (CVC) at the Universitat Autonoma de Barcelona (UAB). +// This work is licensed under the terms of the MIT license. +// For a copy, see . + +#pragma once +#include +#include "carla/ros2/types/msg/Time.h" + +namespace carla { +namespace ros2 { +namespace msg { + +struct Header { + Time stamp; + std::string frame_id; +}; + +} // namespace msg +} // namespace ros2 +} // namespace carla diff --git a/LibCarla/source/carla/ros2/types/msg/Image.h b/LibCarla/source/carla/ros2/types/msg/Image.h new file mode 100644 index 00000000000..6f106c1735e --- /dev/null +++ b/LibCarla/source/carla/ros2/types/msg/Image.h @@ -0,0 +1,27 @@ +// Copyright (c) 2025 Computer Vision Center (CVC) at the Universitat Autonoma de Barcelona (UAB). +// This work is licensed under the terms of the MIT license. +// For a copy, see . + +#pragma once +#include +#include +#include +#include "carla/ros2/types/msg/Header.h" + +namespace carla { +namespace ros2 { +namespace msg { + +struct Image { + Header header; + uint32_t height = 0; + uint32_t width = 0; + std::string encoding; + uint8_t is_bigendian = 0; + uint32_t step = 0; + std::vector data; +}; + +} // namespace msg +} // namespace ros2 +} // namespace carla diff --git a/LibCarla/source/carla/ros2/types/msg/Imu.h b/LibCarla/source/carla/ros2/types/msg/Imu.h new file mode 100644 index 00000000000..357c4e8fe07 --- /dev/null +++ b/LibCarla/source/carla/ros2/types/msg/Imu.h @@ -0,0 +1,27 @@ +// Copyright (c) 2025 Computer Vision Center (CVC) at the Universitat Autonoma de Barcelona (UAB). +// This work is licensed under the terms of the MIT license. +// For a copy, see . + +#pragma once +#include +#include "carla/ros2/types/msg/Header.h" +#include "carla/ros2/types/msg/Quaternion.h" +#include "carla/ros2/types/msg/Vector3.h" + +namespace carla { +namespace ros2 { +namespace msg { + +struct Imu { + Header header; + Quaternion orientation; + std::array orientation_covariance = {}; + Vector3 angular_velocity; + std::array angular_velocity_covariance = {}; + Vector3 linear_acceleration; + std::array linear_acceleration_covariance = {}; +}; + +} // namespace msg +} // namespace ros2 +} // namespace carla diff --git a/LibCarla/source/carla/ros2/types/msg/NavSatFix.h b/LibCarla/source/carla/ros2/types/msg/NavSatFix.h new file mode 100644 index 00000000000..842b777faef --- /dev/null +++ b/LibCarla/source/carla/ros2/types/msg/NavSatFix.h @@ -0,0 +1,32 @@ +// Copyright (c) 2025 Computer Vision Center (CVC) at the Universitat Autonoma de Barcelona (UAB). +// This work is licensed under the terms of the MIT license. +// For a copy, see . + +#pragma once +#include +#include +#include "carla/ros2/types/msg/Header.h" +#include "carla/ros2/types/msg/NavSatStatus.h" + +namespace carla { +namespace ros2 { +namespace msg { + +struct NavSatFix { + static const uint8_t COVARIANCE_TYPE_UNKNOWN = 0; + static const uint8_t COVARIANCE_TYPE_APPROXIMATED = 1; + static const uint8_t COVARIANCE_TYPE_DIAGONAL_KNOWN = 2; + static const uint8_t COVARIANCE_TYPE_KNOWN = 3; + + Header header; + NavSatStatus status; + double latitude = 0.0; + double longitude = 0.0; + double altitude = 0.0; + std::array position_covariance = {}; + uint8_t position_covariance_type = 0; +}; + +} // namespace msg +} // namespace ros2 +} // namespace carla diff --git a/LibCarla/source/carla/ros2/types/msg/NavSatStatus.h b/LibCarla/source/carla/ros2/types/msg/NavSatStatus.h new file mode 100644 index 00000000000..2470b2e9968 --- /dev/null +++ b/LibCarla/source/carla/ros2/types/msg/NavSatStatus.h @@ -0,0 +1,28 @@ +// Copyright (c) 2025 Computer Vision Center (CVC) at the Universitat Autonoma de Barcelona (UAB). +// This work is licensed under the terms of the MIT license. +// For a copy, see . + +#pragma once +#include + +namespace carla { +namespace ros2 { +namespace msg { + +struct NavSatStatus { + static const uint8_t STATUS_NO_FIX = 255; + static const uint8_t STATUS_FIX = 0; + static const uint8_t STATUS_SBAS_FIX = 1; + static const uint8_t STATUS_GBAS_FIX = 2; + static const uint16_t SERVICE_GPS = 1; + static const uint16_t SERVICE_GLONASS = 2; + static const uint16_t SERVICE_COMPASS = 4; + static const uint16_t SERVICE_GALILEO = 8; + + uint8_t status = 0; + uint16_t service = 0; +}; + +} // namespace msg +} // namespace ros2 +} // namespace carla diff --git a/LibCarla/source/carla/ros2/types/msg/Odometry.h b/LibCarla/source/carla/ros2/types/msg/Odometry.h new file mode 100644 index 00000000000..4d4e2d0a3e8 --- /dev/null +++ b/LibCarla/source/carla/ros2/types/msg/Odometry.h @@ -0,0 +1,24 @@ +// Copyright (c) 2025 Computer Vision Center (CVC) at the Universitat Autonoma de Barcelona (UAB). +// This work is licensed under the terms of the MIT license. +// For a copy, see . + +#pragma once +#include +#include "carla/ros2/types/msg/Header.h" +#include "carla/ros2/types/msg/PoseWithCovariance.h" +#include "carla/ros2/types/msg/TwistWithCovariance.h" + +namespace carla { +namespace ros2 { +namespace msg { + +struct Odometry { + Header header; + std::string child_frame_id; + PoseWithCovariance pose; + TwistWithCovariance twist; +}; + +} // namespace msg +} // namespace ros2 +} // namespace carla diff --git a/LibCarla/source/carla/ros2/types/msg/Point.h b/LibCarla/source/carla/ros2/types/msg/Point.h new file mode 100644 index 00000000000..b731e4a19df --- /dev/null +++ b/LibCarla/source/carla/ros2/types/msg/Point.h @@ -0,0 +1,19 @@ +// Copyright (c) 2025 Computer Vision Center (CVC) at the Universitat Autonoma de Barcelona (UAB). +// This work is licensed under the terms of the MIT license. +// For a copy, see . + +#pragma once + +namespace carla { +namespace ros2 { +namespace msg { + +struct Point { + double x = 0.0; + double y = 0.0; + double z = 0.0; +}; + +} // namespace msg +} // namespace ros2 +} // namespace carla diff --git a/LibCarla/source/carla/ros2/types/msg/Point32.h b/LibCarla/source/carla/ros2/types/msg/Point32.h new file mode 100644 index 00000000000..f3ee652542d --- /dev/null +++ b/LibCarla/source/carla/ros2/types/msg/Point32.h @@ -0,0 +1,19 @@ +// Copyright (c) 2025 Computer Vision Center (CVC) at the Universitat Autonoma de Barcelona (UAB). +// This work is licensed under the terms of the MIT license. +// For a copy, see . + +#pragma once + +namespace carla { +namespace ros2 { +namespace msg { + +struct Point32 { + float x = 0.0f; + float y = 0.0f; + float z = 0.0f; +}; + +} // namespace msg +} // namespace ros2 +} // namespace carla diff --git a/LibCarla/source/carla/ros2/types/msg/PointCloud2.h b/LibCarla/source/carla/ros2/types/msg/PointCloud2.h new file mode 100644 index 00000000000..4cd62edb0d7 --- /dev/null +++ b/LibCarla/source/carla/ros2/types/msg/PointCloud2.h @@ -0,0 +1,29 @@ +// Copyright (c) 2025 Computer Vision Center (CVC) at the Universitat Autonoma de Barcelona (UAB). +// This work is licensed under the terms of the MIT license. +// For a copy, see . + +#pragma once +#include +#include +#include "carla/ros2/types/msg/Header.h" +#include "carla/ros2/types/msg/PointField.h" + +namespace carla { +namespace ros2 { +namespace msg { + +struct PointCloud2 { + Header header; + uint32_t height = 0; + uint32_t width = 0; + std::vector fields; + bool is_bigendian = false; + uint32_t point_step = 0; + uint32_t row_step = 0; + std::vector data; + bool is_dense = false; +}; + +} // namespace msg +} // namespace ros2 +} // namespace carla diff --git a/LibCarla/source/carla/ros2/types/msg/PointField.h b/LibCarla/source/carla/ros2/types/msg/PointField.h new file mode 100644 index 00000000000..85d9f8da983 --- /dev/null +++ b/LibCarla/source/carla/ros2/types/msg/PointField.h @@ -0,0 +1,31 @@ +// Copyright (c) 2025 Computer Vision Center (CVC) at the Universitat Autonoma de Barcelona (UAB). +// This work is licensed under the terms of the MIT license. +// For a copy, see . + +#pragma once +#include +#include + +namespace carla { +namespace ros2 { +namespace msg { + +struct PointField { + static const uint8_t INT8 = 1; + static const uint8_t UINT8 = 2; + static const uint8_t INT16 = 3; + static const uint8_t UINT16 = 4; + static const uint8_t INT32 = 5; + static const uint8_t UINT32 = 6; + static const uint8_t FLOAT32 = 7; + static const uint8_t FLOAT64 = 8; + + std::string name; + uint32_t offset = 0; + uint8_t datatype = 0; + uint32_t count = 0; +}; + +} // namespace msg +} // namespace ros2 +} // namespace carla diff --git a/LibCarla/source/carla/ros2/types/msg/Pose.h b/LibCarla/source/carla/ros2/types/msg/Pose.h new file mode 100644 index 00000000000..bf3c81b869a --- /dev/null +++ b/LibCarla/source/carla/ros2/types/msg/Pose.h @@ -0,0 +1,20 @@ +// Copyright (c) 2025 Computer Vision Center (CVC) at the Universitat Autonoma de Barcelona (UAB). +// This work is licensed under the terms of the MIT license. +// For a copy, see . + +#pragma once +#include "carla/ros2/types/msg/Point.h" +#include "carla/ros2/types/msg/Quaternion.h" + +namespace carla { +namespace ros2 { +namespace msg { + +struct Pose { + Point position; + Quaternion orientation; +}; + +} // namespace msg +} // namespace ros2 +} // namespace carla diff --git a/LibCarla/source/carla/ros2/types/msg/PoseWithCovariance.h b/LibCarla/source/carla/ros2/types/msg/PoseWithCovariance.h new file mode 100644 index 00000000000..3570f8f0a50 --- /dev/null +++ b/LibCarla/source/carla/ros2/types/msg/PoseWithCovariance.h @@ -0,0 +1,20 @@ +// Copyright (c) 2025 Computer Vision Center (CVC) at the Universitat Autonoma de Barcelona (UAB). +// This work is licensed under the terms of the MIT license. +// For a copy, see . + +#pragma once +#include +#include "carla/ros2/types/msg/Pose.h" + +namespace carla { +namespace ros2 { +namespace msg { + +struct PoseWithCovariance { + Pose pose; + std::array covariance = {}; +}; + +} // namespace msg +} // namespace ros2 +} // namespace carla diff --git a/LibCarla/source/carla/ros2/types/msg/Quaternion.h b/LibCarla/source/carla/ros2/types/msg/Quaternion.h new file mode 100644 index 00000000000..59cecdf4725 --- /dev/null +++ b/LibCarla/source/carla/ros2/types/msg/Quaternion.h @@ -0,0 +1,20 @@ +// Copyright (c) 2025 Computer Vision Center (CVC) at the Universitat Autonoma de Barcelona (UAB). +// This work is licensed under the terms of the MIT license. +// For a copy, see . + +#pragma once + +namespace carla { +namespace ros2 { +namespace msg { + +struct Quaternion { + double x = 0.0; + double y = 0.0; + double z = 0.0; + double w = 0.0; +}; + +} // namespace msg +} // namespace ros2 +} // namespace carla diff --git a/LibCarla/source/carla/ros2/types/msg/RegionOfInterest.h b/LibCarla/source/carla/ros2/types/msg/RegionOfInterest.h new file mode 100644 index 00000000000..0a21608c655 --- /dev/null +++ b/LibCarla/source/carla/ros2/types/msg/RegionOfInterest.h @@ -0,0 +1,22 @@ +// Copyright (c) 2025 Computer Vision Center (CVC) at the Universitat Autonoma de Barcelona (UAB). +// This work is licensed under the terms of the MIT license. +// For a copy, see . + +#pragma once +#include + +namespace carla { +namespace ros2 { +namespace msg { + +struct RegionOfInterest { + uint32_t x_offset = 0; + uint32_t y_offset = 0; + uint32_t height = 0; + uint32_t width = 0; + bool do_rectify = false; +}; + +} // namespace msg +} // namespace ros2 +} // namespace carla diff --git a/LibCarla/source/carla/ros2/types/msg/String.h b/LibCarla/source/carla/ros2/types/msg/String.h new file mode 100644 index 00000000000..81a50e40248 --- /dev/null +++ b/LibCarla/source/carla/ros2/types/msg/String.h @@ -0,0 +1,18 @@ +// Copyright (c) 2025 Computer Vision Center (CVC) at the Universitat Autonoma de Barcelona (UAB). +// This work is licensed under the terms of the MIT license. +// For a copy, see . + +#pragma once +#include + +namespace carla { +namespace ros2 { +namespace msg { + +struct String { + std::string data; +}; + +} // namespace msg +} // namespace ros2 +} // namespace carla diff --git a/LibCarla/source/carla/ros2/types/msg/TF2Error.h b/LibCarla/source/carla/ros2/types/msg/TF2Error.h new file mode 100644 index 00000000000..33f4bf46e8f --- /dev/null +++ b/LibCarla/source/carla/ros2/types/msg/TF2Error.h @@ -0,0 +1,20 @@ +// Copyright (c) 2025 Computer Vision Center (CVC) at the Universitat Autonoma de Barcelona (UAB). +// This work is licensed under the terms of the MIT license. +// For a copy, see . + +#pragma once +#include +#include + +namespace carla { +namespace ros2 { +namespace msg { + +struct TF2Error { + uint8_t error = 0; + std::string error_string; +}; + +} // namespace msg +} // namespace ros2 +} // namespace carla diff --git a/LibCarla/source/carla/ros2/types/msg/TFMessage.h b/LibCarla/source/carla/ros2/types/msg/TFMessage.h new file mode 100644 index 00000000000..54636dffa79 --- /dev/null +++ b/LibCarla/source/carla/ros2/types/msg/TFMessage.h @@ -0,0 +1,19 @@ +// Copyright (c) 2025 Computer Vision Center (CVC) at the Universitat Autonoma de Barcelona (UAB). +// This work is licensed under the terms of the MIT license. +// For a copy, see . + +#pragma once +#include +#include "carla/ros2/types/msg/TransformStamped.h" + +namespace carla { +namespace ros2 { +namespace msg { + +struct TFMessage { + std::vector transforms; +}; + +} // namespace msg +} // namespace ros2 +} // namespace carla diff --git a/LibCarla/source/carla/ros2/types/msg/Time.h b/LibCarla/source/carla/ros2/types/msg/Time.h new file mode 100644 index 00000000000..4183173ee83 --- /dev/null +++ b/LibCarla/source/carla/ros2/types/msg/Time.h @@ -0,0 +1,19 @@ +// Copyright (c) 2025 Computer Vision Center (CVC) at the Universitat Autonoma de Barcelona (UAB). +// This work is licensed under the terms of the MIT license. +// For a copy, see . + +#pragma once +#include + +namespace carla { +namespace ros2 { +namespace msg { + +struct Time { + int32_t sec = 0; + uint32_t nanosec = 0; +}; + +} // namespace msg +} // namespace ros2 +} // namespace carla diff --git a/LibCarla/source/carla/ros2/types/msg/Transform.h b/LibCarla/source/carla/ros2/types/msg/Transform.h new file mode 100644 index 00000000000..99ec1d2b9c7 --- /dev/null +++ b/LibCarla/source/carla/ros2/types/msg/Transform.h @@ -0,0 +1,20 @@ +// Copyright (c) 2025 Computer Vision Center (CVC) at the Universitat Autonoma de Barcelona (UAB). +// This work is licensed under the terms of the MIT license. +// For a copy, see . + +#pragma once +#include "carla/ros2/types/msg/Vector3.h" +#include "carla/ros2/types/msg/Quaternion.h" + +namespace carla { +namespace ros2 { +namespace msg { + +struct Transform { + Vector3 translation; + Quaternion rotation; +}; + +} // namespace msg +} // namespace ros2 +} // namespace carla diff --git a/LibCarla/source/carla/ros2/types/msg/TransformStamped.h b/LibCarla/source/carla/ros2/types/msg/TransformStamped.h new file mode 100644 index 00000000000..cb9d471c3bd --- /dev/null +++ b/LibCarla/source/carla/ros2/types/msg/TransformStamped.h @@ -0,0 +1,22 @@ +// Copyright (c) 2025 Computer Vision Center (CVC) at the Universitat Autonoma de Barcelona (UAB). +// This work is licensed under the terms of the MIT license. +// For a copy, see . + +#pragma once +#include +#include "carla/ros2/types/msg/Header.h" +#include "carla/ros2/types/msg/Transform.h" + +namespace carla { +namespace ros2 { +namespace msg { + +struct TransformStamped { + Header header; + std::string child_frame_id; + Transform transform; +}; + +} // namespace msg +} // namespace ros2 +} // namespace carla diff --git a/LibCarla/source/carla/ros2/types/msg/Twist.h b/LibCarla/source/carla/ros2/types/msg/Twist.h new file mode 100644 index 00000000000..ed2d8d42e36 --- /dev/null +++ b/LibCarla/source/carla/ros2/types/msg/Twist.h @@ -0,0 +1,19 @@ +// Copyright (c) 2025 Computer Vision Center (CVC) at the Universitat Autonoma de Barcelona (UAB). +// This work is licensed under the terms of the MIT license. +// For a copy, see . + +#pragma once +#include "carla/ros2/types/msg/Vector3.h" + +namespace carla { +namespace ros2 { +namespace msg { + +struct Twist { + Vector3 linear; + Vector3 angular; +}; + +} // namespace msg +} // namespace ros2 +} // namespace carla diff --git a/LibCarla/source/carla/ros2/types/msg/TwistWithCovariance.h b/LibCarla/source/carla/ros2/types/msg/TwistWithCovariance.h new file mode 100644 index 00000000000..6e00857d6c1 --- /dev/null +++ b/LibCarla/source/carla/ros2/types/msg/TwistWithCovariance.h @@ -0,0 +1,20 @@ +// Copyright (c) 2025 Computer Vision Center (CVC) at the Universitat Autonoma de Barcelona (UAB). +// This work is licensed under the terms of the MIT license. +// For a copy, see . + +#pragma once +#include +#include "carla/ros2/types/msg/Twist.h" + +namespace carla { +namespace ros2 { +namespace msg { + +struct TwistWithCovariance { + Twist twist; + std::array covariance = {}; +}; + +} // namespace msg +} // namespace ros2 +} // namespace carla diff --git a/LibCarla/source/carla/ros2/types/msg/Vector3.h b/LibCarla/source/carla/ros2/types/msg/Vector3.h new file mode 100644 index 00000000000..1b879020e11 --- /dev/null +++ b/LibCarla/source/carla/ros2/types/msg/Vector3.h @@ -0,0 +1,19 @@ +// Copyright (c) 2025 Computer Vision Center (CVC) at the Universitat Autonoma de Barcelona (UAB). +// This work is licensed under the terms of the MIT license. +// For a copy, see . + +#pragma once + +namespace carla { +namespace ros2 { +namespace msg { + +struct Vector3 { + double x = 0.0; + double y = 0.0; + double z = 0.0; +}; + +} // namespace msg +} // namespace ros2 +} // namespace carla diff --git a/LibCarla/source/test/server/test_dds_middleware.cpp b/LibCarla/source/test/server/test_dds_middleware.cpp new file mode 100644 index 00000000000..a4563b94c81 --- /dev/null +++ b/LibCarla/source/test/server/test_dds_middleware.cpp @@ -0,0 +1,490 @@ +// Copyright (c) 2025 Computer Vision Center (CVC) at the Universitat Autonoma de Barcelona (UAB). +// This work is licensed under the terms of the MIT license. +// For a copy, see . + +// Must be defined before any includes to suppress real DDS auto-includes +// while keeping CARLA_ROS2_DDS_FASTDDS compile-time checks active. +#define CARLA_ROS2_DDS_TESTING +#define CARLA_ROS2_DDS_FASTDDS + +#include "test.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace carla::ros2; + +// ========================================================================== +// Test infrastructure +// ========================================================================== + +struct TestMsg { + int value{0}; +}; + +struct TestPubTraits { + using msg_type = TestMsg; +}; + +struct TestSubTraits { + using msg_type = TestMsg; +}; + +// -- Mock publisher middleware ------------------------------------------------ + +class MockPublisherMiddleware : public IDDSPublisherMiddleware { + public: + bool init_return_value{true}; + bool publish_return_value{true}; + bool alive{true}; + + bool init_called{false}; + bool publish_called{false}; + std::string last_topic_name; + void* last_published_data{nullptr}; + + bool Init(const std::string& topic_name) override { + init_called = true; + last_topic_name = topic_name; + return init_return_value; + } + + bool Publish(void* message_data) override { + publish_called = true; + last_published_data = message_data; + return publish_return_value; + } + + bool IsAlive() const override { return alive; } + std::string GetTopicName() const override { return last_topic_name; } +}; + +// -- Mock subscriber middleware ----------------------------------------------- + +class MockSubscriberMiddleware : public IDDSSubscriberMiddleware { + public: + bool init_return_value{true}; + bool alive{true}; + + bool init_called{false}; + std::string last_topic_name; + void* stored_message_ptr{nullptr}; + bool* stored_flag_ptr{nullptr}; + + bool Init( + const std::string& topic_name, + void* message_ptr, + bool* new_message_flag) override { + init_called = true; + last_topic_name = topic_name; + stored_message_ptr = message_ptr; + stored_flag_ptr = new_message_flag; + return init_return_value; + } + + bool IsAlive() const override { return alive; } + std::string GetTopicName() const override { return last_topic_name; } +}; + +// -- Factory fixture (resets static state) ------------------------------------ + +class DDSMiddlewareFactoryFixture : public ::testing::Test { + protected: + void SetUp() override { + DDSMiddlewareFactory::SetMiddleware(DDSMiddleware::FastDDS); + } +}; + +// ========================================================================== +// Group 1: dds_middleware_enum (2 tests) +// ========================================================================== + +TEST(dds_middleware_enum, values_exist) { + DDSMiddleware mw = DDSMiddleware::FastDDS; + EXPECT_EQ(static_cast(mw), 0); +} + +TEST(dds_middleware_enum, switch_covers_all) { + DDSMiddleware mw = DDSMiddleware::FastDDS; + bool covered = false; + switch (mw) { + case DDSMiddleware::FastDDS: + covered = true; + break; + } + EXPECT_TRUE(covered); +} + +// ========================================================================== +// Group 2: dds_middleware_to_string (3 tests) +// ========================================================================== + +TEST(dds_middleware_to_string, fastdds_returns_correct_string) { + EXPECT_STREQ(DDSMiddlewareToString(DDSMiddleware::FastDDS), "FastDDS"); +} + +TEST(dds_middleware_to_string, result_is_not_null) { + const char* result = DDSMiddlewareToString(DDSMiddleware::FastDDS); + ASSERT_NE(result, nullptr); +} + +TEST(dds_middleware_to_string, result_is_not_empty) { + const char* result = DDSMiddlewareToString(DDSMiddleware::FastDDS); + EXPECT_STRNE(result, ""); +} + +// ========================================================================== +// Group 3: dds_middleware_from_string (5 tests) +// ========================================================================== + +TEST(dds_middleware_from_string, fastdds_lowercase_valid) { + auto result = DDSMiddlewareFromString("fastdds"); + EXPECT_TRUE(result.valid); + EXPECT_EQ(result.middleware, DDSMiddleware::FastDDS); +} + +TEST(dds_middleware_from_string, unknown_string_invalid) { + auto result = DDSMiddlewareFromString("cyclonedds"); + EXPECT_FALSE(result.valid); +} + +TEST(dds_middleware_from_string, empty_string_invalid) { + auto result = DDSMiddlewareFromString(""); + EXPECT_FALSE(result.valid); +} + +TEST(dds_middleware_from_string, uppercase_rejected) { + auto result = DDSMiddlewareFromString("FastDDS"); + EXPECT_FALSE(result.valid); +} + +TEST(dds_middleware_from_string, partial_match_rejected) { + auto result = DDSMiddlewareFromString("fast"); + EXPECT_FALSE(result.valid); +} + +// ========================================================================== +// Group 4: dds_middleware_available (2 tests) +// ========================================================================== + +TEST(dds_middleware_available, fastdds_available) { + EXPECT_TRUE( + DDSMiddlewareFactory::IsMiddlewareAvailable(DDSMiddleware::FastDDS)); +} + +TEST(dds_middleware_available, available_string_contains_fastdds) { + std::string available = GetAvailableMiddlewareString(); + EXPECT_NE(available.find("FastDDS"), std::string::npos); +} + +// ========================================================================== +// Group 5: dds_middleware_type_name (4 tests) +// ========================================================================== + +TEST(dds_middleware_type_name, bare_name) { + EXPECT_EQ(ToROS2DDSTypeName("Image"), "dds_::Image_"); +} + +TEST(dds_middleware_type_name, fully_qualified) { + EXPECT_EQ( + ToROS2DDSTypeName("sensor_msgs::msg::Image"), + "sensor_msgs::msg::dds_::Image_"); +} + +TEST(dds_middleware_type_name, single_namespace) { + EXPECT_EQ(ToROS2DDSTypeName("msg::Image"), "msg::dds_::Image_"); +} + +TEST(dds_middleware_type_name, empty_string) { + EXPECT_EQ(ToROS2DDSTypeName(""), "dds_::_"); +} + +// ========================================================================== +// Group 6: DDSMiddlewareFactoryFixture (5 tests) +// ========================================================================== + +TEST_F(DDSMiddlewareFactoryFixture, set_and_get_middleware) { + DDSMiddlewareFactory::SetMiddleware(DDSMiddleware::FastDDS); + EXPECT_EQ(DDSMiddlewareFactory::GetMiddleware(), DDSMiddleware::FastDDS); +} + +TEST_F(DDSMiddlewareFactoryFixture, default_is_fastdds) { + EXPECT_EQ(DDSMiddlewareFactory::GetMiddleware(), DDSMiddleware::FastDDS); +} + +TEST_F(DDSMiddlewareFactoryFixture, is_middleware_available_fastdds) { + EXPECT_TRUE( + DDSMiddlewareFactory::IsMiddlewareAvailable(DDSMiddleware::FastDDS)); +} + +TEST_F(DDSMiddlewareFactoryFixture, resolve_available_middleware) { + auto resolution = + DDSMiddlewareFactory::ResolveMiddleware(DDSMiddleware::FastDDS); + EXPECT_TRUE(resolution.success); + EXPECT_EQ(resolution.middleware, DDSMiddleware::FastDDS); +} + +TEST_F(DDSMiddlewareFactoryFixture, factory_available_string) { + std::string available = DDSMiddlewareFactory::GetAvailableMiddlewareString(); + EXPECT_NE(available.find("FastDDS"), std::string::npos); +} + +// ========================================================================== +// Group 7: dds_publisher_interface (5 tests) +// ========================================================================== + +TEST(dds_publisher_interface, mock_init_success) { + MockPublisherMiddleware mock; + mock.init_return_value = true; + EXPECT_TRUE(mock.Init("rt/test_topic")); + EXPECT_TRUE(mock.init_called); + EXPECT_EQ(mock.last_topic_name, "rt/test_topic"); +} + +TEST(dds_publisher_interface, mock_init_failure) { + MockPublisherMiddleware mock; + mock.init_return_value = false; + EXPECT_FALSE(mock.Init("rt/test_topic")); + EXPECT_TRUE(mock.init_called); +} + +TEST(dds_publisher_interface, mock_publish_records_data) { + MockPublisherMiddleware mock; + TestMsg msg; + msg.value = 42; + EXPECT_TRUE(mock.Publish(&msg)); + EXPECT_TRUE(mock.publish_called); + EXPECT_EQ(mock.last_published_data, &msg); +} + +TEST(dds_publisher_interface, mock_topic_name) { + MockPublisherMiddleware mock; + mock.Init("rt/camera/image"); + EXPECT_EQ(mock.GetTopicName(), "rt/camera/image"); +} + +TEST(dds_publisher_interface, mock_is_alive) { + MockPublisherMiddleware mock; + mock.alive = true; + EXPECT_TRUE(mock.IsAlive()); + mock.alive = false; + EXPECT_FALSE(mock.IsAlive()); +} + +// ========================================================================== +// Group 8: dds_subscriber_interface (5 tests) +// ========================================================================== + +TEST(dds_subscriber_interface, mock_init_stores_pointers) { + MockSubscriberMiddleware mock; + TestMsg msg; + bool flag = false; + EXPECT_TRUE(mock.Init("rt/test_topic", &msg, &flag)); + EXPECT_TRUE(mock.init_called); + EXPECT_EQ(mock.stored_message_ptr, &msg); + EXPECT_EQ(mock.stored_flag_ptr, &flag); +} + +TEST(dds_subscriber_interface, mock_init_failure) { + MockSubscriberMiddleware mock; + mock.init_return_value = false; + TestMsg msg; + bool flag = false; + EXPECT_FALSE(mock.Init("rt/test_topic", &msg, &flag)); +} + +TEST(dds_subscriber_interface, mock_topic_name) { + MockSubscriberMiddleware mock; + TestMsg msg; + bool flag = false; + mock.Init("rt/lidar/points", &msg, &flag); + EXPECT_EQ(mock.GetTopicName(), "rt/lidar/points"); +} + +TEST(dds_subscriber_interface, mock_is_alive) { + MockSubscriberMiddleware mock; + mock.alive = true; + EXPECT_TRUE(mock.IsAlive()); + mock.alive = false; + EXPECT_FALSE(mock.IsAlive()); +} + +TEST(dds_subscriber_interface, mock_simulates_message_receipt) { + MockSubscriberMiddleware mock; + TestMsg msg; + bool flag = false; + mock.Init("rt/test", &msg, &flag); + ASSERT_NE(mock.stored_message_ptr, nullptr); + ASSERT_NE(mock.stored_flag_ptr, nullptr); + auto* typed_ptr = static_cast(mock.stored_message_ptr); + typed_ptr->value = 99; + *mock.stored_flag_ptr = true; + EXPECT_EQ(msg.value, 99); + EXPECT_TRUE(flag); +} + +// ========================================================================== +// Group 9: publisher_impl (7 tests) +// ========================================================================== + +TEST(publisher_impl, get_message_returns_pointer) { + PublisherImpl pub; + TestMsg* msg = pub.GetMessage(); + ASSERT_NE(msg, nullptr); + msg->value = 7; + EXPECT_EQ(pub.GetMessage()->value, 7); +} + +TEST(publisher_impl, init_delegates_to_middleware) { + PublisherImpl pub; + auto* mock = new MockPublisherMiddleware(); + pub.SetMiddlewareForTesting( + std::unique_ptr(mock)); + EXPECT_TRUE(pub.Init("rt/test_topic")); + EXPECT_TRUE(mock->init_called); + EXPECT_EQ(mock->last_topic_name, "rt/test_topic"); +} + +TEST(publisher_impl, publish_delegates_to_middleware) { + PublisherImpl pub; + auto* mock = new MockPublisherMiddleware(); + pub.SetMiddlewareForTesting( + std::unique_ptr(mock)); + pub.Init("rt/test_topic"); + EXPECT_TRUE(pub.Publish()); + EXPECT_TRUE(mock->publish_called); + EXPECT_EQ(mock->last_published_data, pub.GetMessage()); +} + +TEST(publisher_impl, publish_before_init_fails) { + PublisherImpl pub; + ::testing::internal::CaptureStderr(); + EXPECT_FALSE(pub.Publish()); + ::testing::internal::GetCapturedStderr(); +} + +TEST(publisher_impl, is_alive_delegates) { + PublisherImpl pub; + auto* mock = new MockPublisherMiddleware(); + mock->alive = true; + pub.SetMiddlewareForTesting( + std::unique_ptr(mock)); + pub.Init("rt/test_topic"); + EXPECT_TRUE(pub.IsAlive()); + mock->alive = false; + EXPECT_FALSE(pub.IsAlive()); +} + +TEST(publisher_impl, topic_name_delegates) { + PublisherImpl pub; + auto* mock = new MockPublisherMiddleware(); + pub.SetMiddlewareForTesting( + std::unique_ptr(mock)); + pub.Init("rt/camera/image"); + EXPECT_EQ(pub.GetTopicName(), "rt/camera/image"); +} + +TEST(publisher_impl, data_flows_through_publish) { + PublisherImpl pub; + auto* mock = new MockPublisherMiddleware(); + pub.SetMiddlewareForTesting( + std::unique_ptr(mock)); + pub.Init("rt/test_topic"); + + pub.GetMessage()->value = 42; + pub.Publish(); + + ASSERT_NE(mock->last_published_data, nullptr); + auto* published = static_cast(mock->last_published_data); + EXPECT_EQ(published->value, 42); +} + +// ========================================================================== +// Group 10: subscriber_impl (7 tests) +// ========================================================================== + +TEST(subscriber_impl, has_new_message_initially_false) { + SubscriberImpl sub; + EXPECT_FALSE(sub.HasNewMessage()); +} + +TEST(subscriber_impl, init_delegates_to_middleware) { + SubscriberImpl sub; + auto* mock = new MockSubscriberMiddleware(); + sub.SetMiddlewareForTesting( + std::unique_ptr(mock)); + EXPECT_TRUE(sub.Init("rt/test_topic")); + EXPECT_TRUE(mock->init_called); + EXPECT_EQ(mock->last_topic_name, "rt/test_topic"); + EXPECT_NE(mock->stored_message_ptr, nullptr); + EXPECT_NE(mock->stored_flag_ptr, nullptr); +} + +TEST(subscriber_impl, get_message_clears_flag) { + SubscriberImpl sub; + auto* mock = new MockSubscriberMiddleware(); + sub.SetMiddlewareForTesting( + std::unique_ptr(mock)); + sub.Init("rt/test_topic"); + + TestMsg msg; + msg.value = 77; + sub.SimulateMessageReceiptForTesting(msg); + EXPECT_TRUE(sub.HasNewMessage()); + + TestMsg retrieved = sub.GetMessage(); + EXPECT_EQ(retrieved.value, 77); + EXPECT_FALSE(sub.HasNewMessage()); +} + +TEST(subscriber_impl, is_alive_delegates) { + SubscriberImpl sub; + auto* mock = new MockSubscriberMiddleware(); + mock->alive = true; + sub.SetMiddlewareForTesting( + std::unique_ptr(mock)); + sub.Init("rt/test_topic"); + EXPECT_TRUE(sub.IsAlive()); + mock->alive = false; + EXPECT_FALSE(sub.IsAlive()); +} + +TEST(subscriber_impl, topic_name_delegates) { + SubscriberImpl sub; + auto* mock = new MockSubscriberMiddleware(); + sub.SetMiddlewareForTesting( + std::unique_ptr(mock)); + sub.Init("rt/lidar/points"); + EXPECT_EQ(sub.GetTopicName(), "rt/lidar/points"); +} + +TEST(subscriber_impl, simulate_message_receipt) { + SubscriberImpl sub; + auto* mock = new MockSubscriberMiddleware(); + sub.SetMiddlewareForTesting( + std::unique_ptr(mock)); + sub.Init("rt/test_topic"); + + EXPECT_FALSE(sub.HasNewMessage()); + TestMsg msg; + msg.value = 123; + sub.SimulateMessageReceiptForTesting(msg); + EXPECT_TRUE(sub.HasNewMessage()); + EXPECT_EQ(sub.GetMessage().value, 123); +} + +TEST(subscriber_impl, init_failure_propagated) { + SubscriberImpl sub; + auto* mock = new MockSubscriberMiddleware(); + mock->init_return_value = false; + sub.SetMiddlewareForTesting( + std::unique_ptr(mock)); + EXPECT_FALSE(sub.Init("rt/test_topic")); +}