Skip to content

feat(LibCarla/ros2): [2/3] publish ego vehicle odometry, status, info and the TF tree - #9787

Draft
JArmandoAnaya wants to merge 13 commits into
carla-simulator:ue4-devfrom
JArmandoAnaya:feature/ros2-ego-vehicle-topics
Draft

feat(LibCarla/ros2): [2/3] publish ego vehicle odometry, status, info and the TF tree#9787
JArmandoAnaya wants to merge 13 commits into
carla-simulator:ue4-devfrom
JArmandoAnaya:feature/ros2-ego-vehicle-topics

Conversation

@JArmandoAnaya

@JArmandoAnaya JArmandoAnaya commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

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_name set to hero, 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.
  • A transform tree following REP 105: a latched map -> odom identity anchors the tree, a dynamic odom -> <vehicle> transform follows the vehicle every frame, and the existing sensor transforms complete the chain down to each sensor. RViz can render everything in the map frame, and tools like Nav2 find the frames they expect.

A short example of what a late joiner sees:

ros2 topic echo --once /carla/hero/vehicle_info
ros2 run tf2_tools view_frames   # map -> odom -> hero -> <sensors>

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-msgs package 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/ros2 also 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?

  • Platform(s): Linux (Ubuntu 20.04 build container, Ubuntu host)
  • Python version(s): 3.12
  • Unreal Engine version(s): 4.26 (CARLA fork)

Validation: make LibCarla ARGS="--ros2" and make 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 CarlaUE4Editor with no new warnings; a full make package build; 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

  • The topics follow the existing hero registration, so they cover one ego vehicle. Supporting several ego vehicles at once would need per-vehicle odom frames and is left for a future proposal.
  • The acceleration in the status message is derived from the velocity change between frames, so the first frame after spawning reports zero.
  • The odometry covariance fields are zero, as the simulator pose is ground truth.

This change is Reviewable

@update-docs

update-docs Bot commented Jun 12, 2026

Copy link
Copy Markdown

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.

@JArmandoAnaya JArmandoAnaya changed the title feat(LibCarla/ros2): publish ego vehicle odometry, status, info and the TF tree feat(LibCarla/ros2): [2/3] publish ego vehicle odometry, status, info and the TF tree Jun 12, 2026
@JArmandoAnaya
JArmandoAnaya marked this pull request as ready for review June 12, 2026 07:13
@JArmandoAnaya
JArmandoAnaya requested a review from a team as a code owner June 12, 2026 07:13
Copilot AI review requested due to automatic review settings June 12, 2026 07:13

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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->odom on /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 inside def main(args):, causing infinite recursion and preventing cleanup from ever running. Remove the recursive call and (if needed) install the SIGTERM handler in the if __name__ == '__main__': block before calling main(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.

Comment thread LibCarla/source/carla/ros2/publishers/UeToRosConversions.h Outdated
Comment thread LibCarla/source/carla/ros2/publishers/UeToRosConversions.h Outdated
Comment thread LibCarla/source/carla/ros2/types/CdrSerialization.h

@youtalk youtalk left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread LibCarla/source/test/server/test_type_hash.cpp
Comment thread LibCarla/source/carla/ros2/publishers/UeToRosConversions.h
…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.
@JArmandoAnaya
JArmandoAnaya force-pushed the feature/ros2-ego-vehicle-topics branch from aaf1f1c to 4f6b1a9 Compare July 7, 2026 06:08
youtalk added a commit to youtalk/carla that referenced this pull request Jul 24, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants