Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions LibCarla/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,14 @@ if (BUILD_LIBCARLA_TESTS)
${LIBCARLA_SOURCE_PATH}/test/server/*.cpp
)

if (NOT ENABLE_ROS2)
list (
REMOVE_ITEM
LIBCARLA_TEST_SERVER_SOURCES
${LIBCARLA_SOURCE_PATH}/test/server/test_ros2_middleware.cpp
)
endif ()

carla_add_executable (
libcarla_test_server
"Build the LibCarla server-side gtest suite."
Expand Down Expand Up @@ -491,6 +499,37 @@ if (BUILD_LIBCARLA_TESTS)
LIBCARLA_TEST_CONTENT_FOLDER="${CARLA_WORKSPACE_PATH}/Build/test-content"
)

if (ENABLE_ROS2)
# The ROS2 middleware tests need the Fast-CDR and Fast-DDS headers and
# shared libs that the Ros2Native ExternalProject chain installs at build
# time (Fast-DDS for GenericCdrPubSubType's TopicDataType base and
# SerializedPayload_t). The path mirrors PROJECT_INSTALL_PATH in
# Ros2Native/CMakeLists.txt.
set (CARLA_ROS2_NATIVE_INSTALL_PATH ${CMAKE_BINARY_DIR}/Ros2Native/install)
target_include_directories (
libcarla_test_server SYSTEM PRIVATE
${CARLA_ROS2_NATIVE_INSTALL_PATH}/include
)
target_link_libraries (
libcarla_test_server PRIVATE
${CARLA_ROS2_NATIVE_INSTALL_PATH}/lib/libfastcdr.so
${CARLA_ROS2_NATIVE_INSTALL_PATH}/lib/libfastrtps.so
)
# libfastrtps.so privately depends on the foonathan_memory shared lib,
# whose file name is version- and config-dependent (e.g.
# libfoonathan_memory-0.7.4-dbg.so in Debug). -rpath-link lets the linker
# resolve those transitive symbols, and --disable-new-dtags emits DT_RPATH
# (instead of DT_RUNPATH) so the dynamic loader also searches this
# directory for libfastrtps.so's own dependencies at run time.
target_link_options (
libcarla_test_server PRIVATE
"LINKER:-rpath-link,${CARLA_ROS2_NATIVE_INSTALL_PATH}/lib"
"LINKER:-rpath,${CARLA_ROS2_NATIVE_INSTALL_PATH}/lib"
"LINKER:--disable-new-dtags"
)
add_dependencies (libcarla_test_server fastdds)
endif ()

endif ()

if (BUILD_CARLA_CLIENT)
Expand Down
7 changes: 7 additions & 0 deletions LibCarla/source/carla/ros2/ROS2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
#include "carla/sensor/s11n/ImageSerializer.h"
#include "carla/sensor/s11n/SensorHeaderSerializer.h"

#include "carla/ros2/middleware/ActiveMiddleware.h"

#include "publishers/BasePublisher.h"
#include "publishers/CarlaCameraPublisher.h"
#include "publishers/CarlaClockPublisher.h"
Expand Down Expand Up @@ -90,6 +92,11 @@ enum ESensors {
void ROS2::Enable(bool enable) {
_enabled = enable;
log_info("ROS2 enabled: ", _enabled);
// Select the ROS 2 middleware before any publisher or subscriber is created.
// FastDDS is the only middleware available today; runtime --rmw= selection
// arrives in a later PR. SetActiveMiddleware is the DDS-free bridge that keeps
// vendor headers out of carla-server.
SetActiveMiddleware(Middleware::FastDDS);
_clock_publisher = std::make_shared<CarlaClockPublisher>();
#if defined(WITH_ROS2_DEMO)
_basic_publisher = std::make_shared<BasicPublisher>();
Expand Down
6 changes: 3 additions & 3 deletions LibCarla/source/carla/ros2/listeners/BasicListener.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
#include <fastdds/dds/subscriber/DataReaderListener.hpp>
#include <fastdds/dds/core/status/SubscriptionMatchedStatus.hpp>
#include <fastdds/dds/subscriber/SampleInfo.hpp>
#include "carla/ros2/types/String.h"
#include "carla/ros2/types/msg/String.h"
#include "carla/ros2/subscribers/BasicSubscriber.h"
#include "carla/ros2/ROS2CallbackData.h"

Expand All @@ -24,7 +24,7 @@ namespace ros2 {

int _matched {0};
BasicSubscriber* _owner {nullptr};
std_msgs::msg::String _message {};
msg::String _message {};
};

void BasicListenerImpl::on_subscription_matched(efd::DataReader* reader, const efd::SubscriptionMatchedStatus& info)
Expand All @@ -47,7 +47,7 @@ namespace ros2 {
efd::SampleInfo info;
eprosima::fastrtps::types::ReturnCode_t rcode = reader->take_next_sample(&_message, &info);
if (rcode == erc::ReturnCodeValue::RETCODE_OK) {
_owner->ForwardMessage(_message.data());
_owner->ForwardMessage(_message.data);
}
if (rcode == erc::ReturnCodeValue::RETCODE_ERROR) {
std::cerr << "RETCODE_ERROR" << std::endl;
Expand Down
16 changes: 16 additions & 0 deletions LibCarla/source/carla/ros2/middleware/ActiveMiddleware.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright (c) 2026 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 <https://opensource.org/licenses/MIT>.

#include "carla/ros2/middleware/ActiveMiddleware.h"
#include "carla/ros2/middleware/MiddlewareFactory.h"

namespace carla {
namespace ros2 {

void SetActiveMiddleware(Middleware middleware) {
MiddlewareFactory::SetMiddleware(middleware);
}

} // namespace ros2
} // namespace carla
24 changes: 24 additions & 0 deletions LibCarla/source/carla/ros2/middleware/ActiveMiddleware.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright (c) 2026 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 <https://opensource.org/licenses/MIT>.

#pragma once

#include "carla/ros2/middleware/Middleware.h"

namespace carla {
namespace ros2 {

/// DDS-free bridge for selecting the active middleware across the shared-library
/// boundary.
///
/// ROS2.cpp is the only ROS 2 translation unit compiled into carla-server; every
/// other ROS 2 source (including MiddlewareFactory.h and the vendor middleware
/// headers) is compiled exclusively into libcarla-ros2-native.so. This declaration
/// includes only Middleware.h, so carla-server can select the middleware without
/// any DDS header crossing the boundary. The definition (which forwards to
/// MiddlewareFactory::SetMiddleware) is compiled into the shared library.
void SetActiveMiddleware(Middleware middleware);

} // namespace ros2
} // namespace carla
37 changes: 37 additions & 0 deletions LibCarla/source/carla/ros2/middleware/IPublisherMiddleware.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright (c) 2026 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 <https://opensource.org/licenses/MIT>.

#pragma once

#include <string>

namespace carla {
namespace ros2 {

/// Type-erased abstract interface for a publisher middleware.
/// Concrete implementations handle all vendor-specific entity creation,
/// type registration, and data writing.
class IPublisherMiddleware {
public:
virtual ~IPublisherMiddleware() = default;

/// Initialize the underlying middleware entities.
/// @param topic_name Full topic name including "rt/" prefix.
/// @return true on success.
virtual bool Init(const std::string& topic_name) = 0;

/// Serialize and write a message to the network.
/// @param message_data Pointer to the message object (type-erased, cast internally).
/// @return true if the write succeeded.
virtual bool Publish(void* message_data) = 0;

/// @return true if at least one subscriber is matched.
virtual bool IsAlive() const = 0;

/// @return The topic name this publisher is bound to.
virtual std::string GetTopicName() const = 0;
};

} // namespace ros2
} // namespace carla
38 changes: 38 additions & 0 deletions LibCarla/source/carla/ros2/middleware/ISubscriberMiddleware.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright (c) 2026 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 <https://opensource.org/licenses/MIT>.

#pragma once

#include <string>

namespace carla {
namespace ros2 {

/// Type-erased abstract interface for a subscriber middleware.
/// Concrete implementations write received messages directly into the caller-provided
/// storage (message_ptr / new_message_flag) to avoid an extra copy.
class ISubscriberMiddleware {
public:
virtual ~ISubscriberMiddleware() = default;

/// Initialize the underlying middleware entities.
/// The middleware writes incoming messages to *message_ptr and sets *new_message_flag = true.
/// @param topic_name Full topic name.
/// @param message_ptr Pointer to the message storage owned by the caller.
/// @param new_message_flag Pointer to the new-message flag owned by the caller.
/// @return true on success.
virtual bool Init(
const std::string& topic_name,
void* message_ptr,
bool* new_message_flag) = 0;

/// @return true if at least one publisher is matched.
virtual bool IsAlive() const = 0;

/// @return The topic name this subscriber is bound to.
virtual std::string GetTopicName() const = 0;
};

} // namespace ros2
} // namespace carla
80 changes: 80 additions & 0 deletions LibCarla/source/carla/ros2/middleware/Middleware.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Copyright (c) 2026 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 <https://opensource.org/licenses/MIT>.

#pragma once

#include <string>

namespace carla {
namespace ros2 {

/// Enumeration of available middleware implementations.
/// Passed to ROS2::Enable() to select the middleware at startup.
/// Once set, the middleware cannot be changed without restarting.
enum class Middleware {
FastDDS,
CycloneDDS
};

/// Convert a Middleware enum value to a readable string.
inline const char* MiddlewareToString(Middleware middleware) {
switch (middleware) {
case Middleware::FastDDS:
return "FastDDS";
case Middleware::CycloneDDS:
return "CycloneDDS";
}
return "Unknown";
}

/// Result of parsing a middleware name string.
struct MiddlewareParseResult {
bool valid;
Middleware middleware;
};

/// Parse a middleware name string (lowercase). Returns {true, middleware} on match,
/// {false, FastDDS} for unrecognized values.
inline MiddlewareParseResult MiddlewareFromString(const std::string& name) {
if (name == "fastdds") {
return {true, Middleware::FastDDS};
}
if (name == "cyclonedds") {
return {true, Middleware::CycloneDDS};
}
return {false, Middleware::FastDDS};
}

/// Return a readable list of middleware implementations compiled into this binary.
inline std::string GetAvailableMiddlewareString() {
std::string result;
#if defined(CARLA_ROS2_MIDDLEWARE_FASTDDS)
result += "FastDDS";
#endif
#if defined(CARLA_ROS2_MIDDLEWARE_CYCLONEDDS)
if (!result.empty()) {
result += ", ";
}
result += "CycloneDDS";
#endif
if (result.empty()) {
result = "none";
}
return result;
}

/// Mangle a 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 ToROS2TypeName(const std::string& type_name) {
auto pos = type_name.rfind("::");
if (pos == std::string::npos) {
return "dds_::" + type_name + "_";
}
return type_name.substr(0, pos) +
"::dds_::" + type_name.substr(pos + 2) + "_";
}

} // namespace ros2
} // namespace carla
Loading