feat(LibCarla/ros2): [2/3] publish ego vehicle odometry, status, info and the TF tree - #9787
Conversation
|
Thanks for opening this pull request! The maintainers of this repository would appreciate it if you would update our CHANGELOG.md based on your changes. |
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
This PR extends CARLA’s native ROS 2 integration with latched map publishing, per-vehicle state topics (odometry/status/info + TF), and a Docker-based demo workflow for RViz visualization.
Changes:
- Add ROS 2 publishers for latched OpenDRIVE map, vehicle odometry/status/info, and REP-105 TF (including
map->odomon/tf_static). - Introduce Publisher QoS support (volatile vs transient_local + history depth) across FastDDS/CycloneDDS/Zenoh middlewares, and expand related CDR/type-hash tests.
- Add ROS 2 “map + lidar” demo scripts/config (Docker launcher, map-to-markers converter, RViz preset) plus new smoke tests.
Reviewed changes
Copilot reviewed 42 out of 42 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| Util/ros2/compute_type_hash.sh | Improves type-hash generation by packaging sibling .msg files and removing jq dependency. |
| Unreal/CarlaUE4/Plugins/Carla/Source/Carla/Game/CarlaEngine.h | Adds tick-time ROS 2 vehicle-state publish hook (ROS2 builds only). |
| Unreal/CarlaUE4/Plugins/Carla/Source/Carla/Game/CarlaEngine.cpp | Publishes latched map on episode start and per-tick vehicle state for registered vehicles. |
| Unreal/CarlaUE4/Plugins/Carla/Source/Carla/Actor/ActorDispatcher.cpp | Publishes latched static vehicle info at vehicle registration. |
| PythonAPI/test/smoke/test_ros2.py | Adds smoke tests for map re-publish on reload and vehicle topic lifecycle. |
| PythonAPI/examples/ros2/rviz/ros2_native.rviz | Adds map marker display and switches RViz fixed frame to map. |
| PythonAPI/examples/ros2/run_map_and_lidar_demo.sh | New one-command Docker runner for the demo stack. |
| PythonAPI/examples/ros2/ros2_native.py | Updates shebang and adds SIGTERM handling for container shutdown. |
| PythonAPI/examples/ros2/map_and_lidar_demo/map_to_markers.py | New ROS 2 node converting latched OpenDRIVE into RViz lane markers. |
| PythonAPI/examples/ros2/map_and_lidar_demo/launcher.sh | New container entrypoint orchestrating helper processes and cleanup. |
| PythonAPI/examples/ros2/map_and_lidar_demo/cleanup.py | New helper to remove leftover demo actors and restore async mode. |
| PythonAPI/examples/ros2/map_and_lidar_demo/build.sh | New script to build the demo Docker image with CARLA Python wheel. |
| PythonAPI/examples/ros2/map_and_lidar_demo/Dockerfile | New demo image layering helpers + CARLA wheel onto RViz base image. |
| PythonAPI/examples/ros2/README.md | Documents the new demo workflow, topics, and helper scripts. |
| LibCarla/source/test/server/test_type_hash.cpp | Updates type-hash tests for new message specializations. |
| LibCarla/source/test/server/test_ros2_middleware.cpp | Adds QoS plumbing tests, type-name assertions, and CDR round-trip tests for new ego-vehicle messages and conversions. |
| LibCarla/source/carla/ros2/types/msg/CarlaEgoVehicleStatus.h | Adds new ROS 2 message POD type. |
| LibCarla/source/carla/ros2/types/msg/CarlaEgoVehicleInfoWheel.h | Adds new ROS 2 message POD type. |
| LibCarla/source/carla/ros2/types/msg/CarlaEgoVehicleInfo.h | Adds new ROS 2 message POD type. |
| LibCarla/source/carla/ros2/types/msg/Accel.h | Adds geometry_msgs/Accel POD type. |
| LibCarla/source/carla/ros2/types/CdrTopicInfo.h | Adds type-name/hash/size specializations for new messages. |
| LibCarla/source/carla/ros2/types/CdrSerialization.h | Adds CDR serialization/deserialization for new messages with sequence-length sanity cap. |
| LibCarla/source/carla/ros2/publishers/UeToRosConversions.h | Adds shared UE→ROS conversion helpers used by the new publishers. |
| LibCarla/source/carla/ros2/publishers/PublisherImpl.h | Adds QoS parameter to publisher initialization. |
| LibCarla/source/carla/ros2/publishers/CarlaStaticTransformPublisher.h | New latched /tf_static publisher wrapper. |
| LibCarla/source/carla/ros2/publishers/CarlaStaticTransformPublisher.cpp | Implements static transform message fill. |
| LibCarla/source/carla/ros2/publishers/CarlaOdometryPublisher.h | New per-vehicle odometry publisher wrapper. |
| LibCarla/source/carla/ros2/publishers/CarlaOdometryPublisher.cpp | Implements odometry message fill. |
| LibCarla/source/carla/ros2/publishers/CarlaMapPublisher.h | New latched OpenDRIVE map publisher wrapper. |
| LibCarla/source/carla/ros2/publishers/CarlaMapPublisher.cpp | Implements map message fill. |
| LibCarla/source/carla/ros2/publishers/CarlaEgoVehicleStatusPublisher.h | New per-vehicle status publisher wrapper + acceleration helper. |
| LibCarla/source/carla/ros2/publishers/CarlaEgoVehicleStatusPublisher.cpp | Implements status message fill. |
| LibCarla/source/carla/ros2/publishers/CarlaEgoVehicleInfoPublisher.h | New latched per-vehicle info publisher wrapper. |
| LibCarla/source/carla/ros2/publishers/CarlaEgoVehicleInfoPublisher.cpp | Implements vehicle info message fill from physics control. |
| LibCarla/source/carla/ros2/middleware/zenoh/ZenohWireFormat.h | Adds QoS encoding helper for rmw_zenoh liveliness keys. |
| LibCarla/source/carla/ros2/middleware/zenoh/ZenohPublisherMiddleware.h | Adds QoS-aware init + transient_local caching via zenoh advanced publisher (with fallback). |
| LibCarla/source/carla/ros2/middleware/fastdds/FastDDSPublisherMiddleware.h | Applies requested durability/history depth to FastDDS writer QoS. |
| LibCarla/source/carla/ros2/middleware/cyclonedds/CycloneDDSPublisherMiddleware.h | Applies requested durability/history depth to CycloneDDS writer QoS. |
| LibCarla/source/carla/ros2/middleware/PublisherQos.h | Introduces shared Publisher QoS struct and defaults. |
| LibCarla/source/carla/ros2/middleware/IPublisherMiddleware.h | Extends publisher middleware interface to accept PublisherQos. |
| LibCarla/source/carla/ros2/ROS2.h | Adds new public APIs for map and per-vehicle publishing + registered-vehicle query. |
| LibCarla/source/carla/ros2/ROS2.cpp | Implements new publishers, per-vehicle publisher lifecycle, and latched map/static TF publishing. |
Comments suppressed due to low confidence (2)
PythonAPI/examples/ros2/ros2_native.py:1
- This calls
main(args)from insidedef main(args):, causing infinite recursion and preventing cleanup from ever running. Remove the recursive call and (if needed) install the SIGTERM handler in theif __name__ == '__main__':block before callingmain(args)once.
Unreal/CarlaUE4/Plugins/Carla/Source/Carla/Game/CarlaEngine.cpp:1 - This scans the entire actor registry every tick and calls
ROS2->IsVehicleRegistered(...)(which locks ROS2’s mutex) for each vehicle candidate. If maps routinely contain many actors, this can become a per-frame overhead; consider iterating the registered-vehicle set directly (e.g., expose a snapshot of registered actors from ROS2, or keep a local list updated from Register/Unregister events) to avoid N-per-tick registry scans and repeated locking.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
youtalk
left a comment
There was a problem hiding this comment.
The frame work is correct: REP-103 axis/unit conversion, a REP-105 map→odom→<vehicle>→<sensors> tree, and nav_msgs/Odometry twist expressed in the child frame — standard consumers (e.g. Nav2) find what they expect, and latched vehicle_info serves late joiners. The main thing I'd fix before merge is the type-hash test: it proves the hashes are well-formed and unique, but not that they equal the hashes the real ros-carla-msgs/std_msgs packages compute, so a layout drift would pass CI yet fail deserialization on a stock ROS 2 node — the exact silent-failure mode #9784 called out. Please also validate on Jazzy, not just Humble.
…map topic Publishers in the native ROS 2 layer can now request transient local durability, and each middleware (FastDDS, CycloneDDS, Zenoh) maps it to its own QoS so late-joining subscribers still receive the last sample. The first user is a new /carla/map topic that publishes the OpenDRIVE description of the current map on every map load, letting any ROS 2 node read the map at any time without connecting to the simulator.
…e markers in RViz A single command now runs a demo stack in Docker against a CARLA server started with --ros2. It spawns the hero vehicle on autopilot, converts the latched /carla/map OpenDRIVE into lane markers and broadcasts the map to hero transform, so RViz shows the camera, the lidar point cloud and the town lane network together in one world-fixed view. The stack cleans up after itself: stopping the container destroys the vehicle and its sensors, and a new run removes anything a previous unclean exit left behind.
Review feedback: history_depth was only honored for transient local publishers, while volatile ones silently kept a hardcoded depth of 1. The writer history is now set from the requested depth in every case, and the invalid depth 0 is clamped to 1 through a single helper used by all three middlewares. The defaults still produce the exact QoS every publisher had before.
Review feedback: python is not guaranteed to exist on modern systems, so the helpers now declare python3 directly. The waypoint cap in map_to_markers.py becomes a named constant and logs a warning when a lane is truncated by it.
…ensor streams Extend PublisherQos with a ReliabilityKind and a SensorData() profile. The default stays reliable, keeping the exact writer QoS every existing publisher had, while camera image/camera_info and point cloud topics (lidar, semantic lidar, radar, DVS) switch to best-effort delivery so a slow or vanished subscriber can never stall the publishing thread, the ROS 2 sensor-data idiom for high-rate streams. All three middlewares honor the new field: FastDDS and CycloneDDS set the writer reliability, Zenoh advertises it in the liveliness QoS segment (rmw enum value 2) that rmw_zenoh matches endpoints on. The bundled RViz preset switches its image and point cloud displays to best-effort so they keep matching.
std_msgs/String carries no Header and that is deliberate: it keeps rt/carla/map wire-compatible with the carla-ros-bridge /carla/map topic. Document the identity contract a late joiner can rely on instead of a stamp: the writer cache holds exactly one sample and the map is re-published on every episode start / map load, so the latched payload always describes the currently loaded map.
… config The zenoh-ext advanced-publisher cache backing transient_local latching only works on a session with timestamping enabled. The bundled session config already turns it on, but when that file cannot be loaded the zenoh default config does not, silently degrading latched topics such as rt/carla/map to volatile. Patch the fallback config so latching stays on by default.
…pe_hash.sh The hash tool built its temporary package from a single .msg file, so a message that references a sibling of its own package, like CarlaEgoVehicleStatus referencing CarlaEgoVehicleControl, could not be hashed. The tool now copies every .msg file found next to the input file into the temporary package and builds them together. It also repairs three breakages against the current osrf/ros:jazzy-desktop image: setup.bash no longer tolerates running under nounset, the --log-base option was being consumed by the greedy --cmake-args parser and forwarded to CMake, and jq is no longer preinstalled so the hash is now extracted with python3. Verified by recomputing the already pinned CarlaEgoVehicleControl hash, which matches byte for byte.
…he TF tree The native ROS 2 interface only published sensor streams, so standard driving stacks could not run against CARLA: there was no odometry, no information about the ego vehicle, and no transform tree connecting the vehicle to the world. Users had to fall back on the deprecated ros-bridge or write their own shim node. With this change, spawning a vehicle with role_name hero is enough for the simulator to publish everything a real drive-by-wire vehicle would report. Every frame it publishes the vehicle odometry (pose plus velocities in the vehicle frame) and a status message with the current speed, acceleration, orientation and applied control. Once at spawn it publishes a latched description of the vehicle (mass, wheels, physics limits), so a node can join at any time and still receive it. The transform tree follows REP 105: a latched map -> odom identity anchors the tree and a dynamic odom -> vehicle transform is published every frame, composing with the existing sensor transforms into map -> odom -> vehicle -> sensor. RViz and Nav2 can consume all of it directly, with no bridge and no extra node. Everything stays within the existing contract: the message types are wire-compatible with ros-carla-msgs and standard ROS 2 packages, the topics hang under the existing per-vehicle namespace, and no new flag, setting or attribute is introduced. As with the rest of the native interface, the types are serialized by CARLA itself and no ROS environment is involved in the build. Tested with the LibCarla unit suites in debug and release (156 server and 66 client tests passing, including new coverage for the message types, the coordinate conversions and the acceleration derivation), a full package build, and the ROS 2 smoke tests against the packaged simulator, including a new hero vehicle lifecycle test.
… demo The map and lidar demo shipped a helper that broadcast the map -> hero transform through the Python API, as a stopgap for the simulator not publishing any vehicle transform. The simulator now publishes the full map -> odom -> hero tree natively, so the helper is no longer needed and would compete with the simulator for the hero frame. The demo runs the remaining helpers unchanged on top of the native transforms.
…e lengths A code review flagged two rough edges in the ego vehicle work. The degree to radian conversion relied on M_PI together with a _USE_MATH_DEFINES macro defined inside a public header, which is non portable and leaks a global macro to every translation unit that includes it. It now uses the project's own pi constant from carla/geom/Math.h, so the header no longer defines any global macro. The CDR serializers also wrote a sequence length by casting the container size straight to a 32 bit field, which could in principle wrap and produce a stream that no longer matches its own length. The reader already rejects oversized sequences, so the writer now applies the same cap before emitting the length. A small shared helper carries that check for the vehicle wheels, point cloud fields and transform tree, keeping both sides of the wire format consistent.
…al packages The type hash tests only checked the RIHS01 shape and uniqueness, so a field-layout drift from the canonical message definition would pass CI and only surface as a decode failure on a real subscriber. Pin all 35 hashes to golden values computed by the canonical packages themselves: standard types from the REP-2011 JSON metadata installed by osrf/ros:jazzy-desktop, ackermann_msgs from its release package, and carla_msgs from building ros-carla-msgs master with the compute_type_hash.sh flow. The RIHS01 hash depends only on the message definition, so the values hold on every distro implementing REP-2011; Humble predates it and never validates hashes.
…onversion helpers CarlaTransformPublisher::ComputeTransform duplicated the axis and sign convention already provided by ue_rotation_to_ros_quaternion, and the static TF publisher hand-rolled the translation flip. Both now delegate to the UeToRosConversions.h helpers so the UE to ROS convention lives in a single place and the two paths cannot drift.
aaf1f1c to
4f6b1a9
Compare
…ity QoS Port the ue4-dev native ROS 2 work from upstream PRs carla-simulator#9786 and carla-simulator#9787 to the ue5-dev native ROS 2 stack (TF tree publishing excluded): * Publisher durability QoS: introduce PublisherQos descriptors and thread them through IPublisherMiddleware and the CycloneDDS, Fast DDS, and Zenoh backends, so publishers can request reliability/durability beyond the previous hardcoded defaults. * Latched map topic: CarlaMapPublisher publishes the OpenDRIVE map on /carla/map with TRANSIENT_LOCAL durability so late-joining subscribers still receive it. * Ego vehicle status/info and odometry: CarlaEgoVehicleStatusPublisher, CarlaEgoVehicleInfoPublisher, and CarlaOdometryPublisher with the carla_msgs CDR message types (CarlaEgoVehicleStatus/Info/InfoWheel, Accel), UE-to-ROS unit/frame conversions (UeToRosConversions.h), and DDS type hashes covered by test_type_hash.cpp. * Define the ego vehicle publisher constructors in the Ros2Native translation unit so middleware creation succeeds instead of failing at link/creation time. * Log a failed DDS writer creation at log_error in the new publishers, matching the severity every existing publisher uses for the identical condition; a nullptr DDS writer is a silent-failure risk. * Glob LibCarla test sources with CONFIGURE_DEPENDS so a newly added test file cannot be silently omitted by a stale CMake configure while gtest still exits 0 with a lower test count. Signed-off-by: Yutaka Kondo <yutaka.kondo@youtalk.jp>
Description
Today the native ROS 2 interface behaves as a one-way sensor pipe: cameras, lidar and the other sensors publish their streams, but the vehicle itself is silent. There is no odometry, no description of the ego vehicle, and no transform tree placing the vehicle in the world. In practice this means standard driving stacks cannot run against CARLA without the deprecated ros-bridge or a custom helper node sitting in between.
This pull request makes the ego vehicle speak for itself. Spawning a vehicle with
role_nameset tohero, exactly as users already do, is now enough for the simulator to publish the data a real drive-by-wire vehicle would report:/carla/<name>/odometry, published every frame, with the vehicle pose and its velocities expressed in the vehicle frame./carla/<name>/vehicle_status, published every frame, with the current speed, acceleration, orientation and the control being applied./carla/<name>/vehicle_info, published once when the vehicle spawns and kept latched, with its static description: mass, wheels, steering limits and other physics properties. A node that starts minutes later still receives it.map -> odomidentity anchors the tree, a dynamicodom -> <vehicle>transform follows the vehicle every frame, and the existing sensor transforms complete the chain down to each sensor. RViz can render everything in themapframe, and tools like Nav2 find the frames they expect.A short example of what a late joiner sees:
Nothing new is asked from the user. There are no new flags, settings or attributes, and the message types are wire-compatible with the
ros-carla-msgspackage and standard ROS 2 types, the same contract the existing control subscriber already uses. As with the rest of the native interface, CARLA serializes these messages itself and no ROS environment takes part in the build.Since the simulator now publishes the vehicle transforms natively, the map and lidar demo no longer needs the small helper it shipped to broadcast them from the client side, and that helper is removed.
This is the second pull request of the series planned in #9784 and builds on top of #9786, whose commits it includes until that one merges. The hash tool in
Util/ros2also receives a small extension so it can compute hashes for messages that reference siblings of their own package.Related to #9784
Where has this been tested?
Validation:
make LibCarla ARGS="--ros2"andmake check.LibCarla ARGS="--ros2"with 156 server and 66 client tests passing in debug and release, including new tests for the message types, the coordinate conversions and the acceleration derivation;make CarlaUE4Editorwith no new warnings; a fullmake packagebuild; and the ROS 2 smoke tests against the packaged simulator (7 of 7 passing, including a new hero vehicle lifecycle test that spawns, drives, destroys and respawns the hero).Possible Drawbacks
odomframes and is left for a future proposal.This change is