Skip to content

feat(LibCarla/ros2): [3/3] publish traffic light info and status topics#9788

Draft
JArmandoAnaya wants to merge 16 commits into
carla-simulator:ue4-devfrom
JArmandoAnaya:feature/ros2-traffic-lights
Draft

feat(LibCarla/ros2): [3/3] publish traffic light info and status topics#9788
JArmandoAnaya wants to merge 16 commits into
carla-simulator:ue4-devfrom
JArmandoAnaya:feature/ros2-traffic-lights

Conversation

@JArmandoAnaya

@JArmandoAnaya JArmandoAnaya commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Description

With the map, the ego vehicle and the transform tree already covered by the previous pull requests of this series, one piece of the world remained invisible to the native ROS 2 interface: traffic lights. The simulator knows every light and its state at all times, but none of that reaches the ROS graph, so a planner that needs to stop at a red light has to fetch it through the Python API or fall back on the deprecated ros-bridge.

This pull request makes the simulator describe its traffic lights whenever ROS 2 is enabled, with no opt-in step, on two latched topics:

  • /carla/traffic_lights/info, published once per episode, carries the static picture of every light: its id, its pose in the world and its trigger volume, the same data the Python API exposes as actor.trigger_volume.
  • /carla/traffic_lights/status carries the current state of every light and is republished only when a state changes. Because the last sample is latched, a node that starts between changes still receives the current state immediately instead of waiting for the next transition.

A late joiner can check both at any time:

ros2 topic echo --qos-durability transient_local --once /carla/traffic_lights/info
ros2 topic echo --qos-durability transient_local --once /carla/traffic_lights/status

Both topics reuse the message definitions already published in ros-carla-msgs, the same contract the rest of the interface follows, and the state encoding matches what the bridge published. Nothing new is asked from the user: no flags, no settings, no attributes, and no ROS environment takes part in the build.

As the closing piece of the series, this pull request also adds Docs/ros2/topics.md, a reference of every topic the native interface publishes and subscribes to, and the consolidated changelog entry covering the three pull requests. It builds on top of #9787, whose commits it includes until that one merges; only the last commit is new here.

Closes #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 167 server and 66 client tests passing in debug and release, including new tests for the message types, the wire serialization and the change detection; make CarlaUE4Editor with no new warnings; a full make package build; the ROS 2 smoke tests against the packaged simulator (8 of 8 passing, including a new traffic light lifecycle test that forces state changes and reloads the world); and a check from a ROS 2 Humble container confirming both topics appear with the expected types and transient local durability.

Possible Drawbacks

  • The status list is keyed by actor id, which changes between episodes; consumers should pair it with the info topic of the same episode, exactly as they did with the bridge.
  • A frozen light publishes nothing after its last change, by design; the latched sample keeps its current state available to late joiners.

This change is Reviewable

@JArmandoAnaya
JArmandoAnaya marked this pull request as ready for review June 12, 2026 08:47
@JArmandoAnaya
JArmandoAnaya requested a review from a team as a code owner June 12, 2026 08:47
Copilot AI review requested due to automatic review settings June 12, 2026 08:47

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.

Extends CARLA’s native ROS 2 interface to publish the “core AD stack” dataset (latched map, REP-105 TF, ego-vehicle state, and traffic-light topics) with explicit publisher QoS support across middlewares, plus demo tooling and docs.

Changes:

  • Added new ROS 2 message types, publishers, and ROS2 API surface for map, ego vehicle state/info, tf_static, and traffic lights.
  • Introduced PublisherQos and propagated QoS into FastDDS/CycloneDDS/Zenoh publisher middlewares (including Zenoh transient-local via advanced publisher cache).
  • Added demo scripts/RViz preset/docs and expanded tests for new types, serialization, QoS, and lifecycle scenarios.

Reviewed changes

Copilot reviewed 54 out of 54 changed files in this pull request and generated no comments.

Show a summary per file
File Description
Util/ros2/compute_type_hash.sh Improves REP-2011 hash computation by packaging sibling .msg files and making the build more robust in Docker.
Unreal/CarlaUE4/Plugins/Carla/Source/Carla/Game/CarlaEngine.h Declares ROS 2 per-tick publishers for vehicle state and traffic lights.
Unreal/CarlaUE4/Plugins/Carla/Source/Carla/Game/CarlaEngine.cpp Publishes map on episode start; publishes vehicle odometry/status/TF and traffic-light status/info during post-tick.
Unreal/CarlaUE4/Plugins/Carla/Source/Carla/Actor/ActorDispatcher.cpp Publishes latched per-vehicle static info at vehicle registration time.
PythonAPI/test/smoke/test_ros2.py Adds smoke tests for map republish on reload and lifecycle stress for vehicle/traffic-light ROS 2 topics.
PythonAPI/examples/ros2/rviz/ros2_native.rviz Adds a transient-local MarkerArray display for map markers and switches fixed frame to map.
PythonAPI/examples/ros2/run_map_and_lidar_demo.sh New one-command Docker demo runner for map markers + lidar visualization pipeline.
PythonAPI/examples/ros2/ros2_native.py Updates shebang and adds SIGTERM handling for containerized shutdown cleanup.
PythonAPI/examples/ros2/map_and_lidar_demo/map_to_markers.py New node converting latched /carla/map OpenDRIVE into RViz lane markers.
PythonAPI/examples/ros2/map_and_lidar_demo/launcher.sh New container entrypoint orchestrating demo helpers and coordinated shutdown/cleanup.
PythonAPI/examples/ros2/map_and_lidar_demo/cleanup.py New cleanup utility to destroy leftover demo actors and restore async mode.
PythonAPI/examples/ros2/map_and_lidar_demo/build.sh New script to build the demo Docker image layering CARLA wheel + helpers onto RViz image.
PythonAPI/examples/ros2/map_and_lidar_demo/Dockerfile New Dockerfile to install CARLA wheel and copy demo helpers into the demo image.
PythonAPI/examples/ros2/README.md Expands documentation to cover the new map+lidar demo and helper scripts.
LibCarla/source/test/server/test_type_hash.cpp Extends REP-2011 type-hash tests to cover newly added message specializations.
LibCarla/source/test/server/test_ros2_middleware.cpp Adds tests for Publisher QoS behavior, type-name/package matching, and new CDR round-trips.
LibCarla/source/carla/ros2/types/msg/CarlaTrafficLightStatusList.h Adds native POD message type for traffic-light status lists.
LibCarla/source/carla/ros2/types/msg/CarlaTrafficLightStatus.h Adds native POD message type for per-light status encoding.
LibCarla/source/carla/ros2/types/msg/CarlaTrafficLightInfoList.h Adds native POD message type for latched traffic-light info lists.
LibCarla/source/carla/ros2/types/msg/CarlaTrafficLightInfo.h Adds native POD message type for per-light pose/trigger-volume info.
LibCarla/source/carla/ros2/types/msg/CarlaEgoVehicleStatus.h Adds native POD message type for per-vehicle “status” output.
LibCarla/source/carla/ros2/types/msg/CarlaEgoVehicleInfoWheel.h Adds native POD message type for per-wheel static vehicle description.
LibCarla/source/carla/ros2/types/msg/CarlaEgoVehicleInfo.h Adds native POD message type for latched vehicle static info.
LibCarla/source/carla/ros2/types/msg/CarlaBoundingBox.h Adds native POD message type for bounding boxes used by traffic-light info.
LibCarla/source/carla/ros2/types/msg/Accel.h Adds native POD geometry_msgs/Accel type used in ego status.
LibCarla/source/carla/ros2/types/CdrTopicInfo.h Adds CdrTopicInfo specializations (type_name/hash/max size) for new message types.
LibCarla/source/carla/ros2/types/CdrSerialization.h Adds CDR serializers/deserializers for new types and sequence-length sanity guards.
LibCarla/source/carla/ros2/publishers/UeToRosConversions.h Adds reusable UE→ROS coordinate conversions used by multiple publishers.
LibCarla/source/carla/ros2/publishers/PublisherImpl.h Extends publisher init to accept PublisherQos and pass it to middleware.
LibCarla/source/carla/ros2/publishers/CarlaTrafficLightStatusPublisher.h New publisher for on-change latched traffic-light status list topic.
LibCarla/source/carla/ros2/publishers/CarlaTrafficLightStatusPublisher.cpp Implements change detection and message rewrite for traffic-light status list.
LibCarla/source/carla/ros2/publishers/CarlaTrafficLightInfoPublisher.h New publisher for per-episode latched traffic-light info list topic.
LibCarla/source/carla/ros2/publishers/CarlaTrafficLightInfoPublisher.cpp Implements message writing and coordinate conversion for traffic-light info list.
LibCarla/source/carla/ros2/publishers/CarlaStaticTransformPublisher.h New transient-local /tf_static publisher for static REP-105 frames.
LibCarla/source/carla/ros2/publishers/CarlaStaticTransformPublisher.cpp Writes/publishes static transforms with UE→ROS conversion.
LibCarla/source/carla/ros2/publishers/CarlaOdometryPublisher.h New per-vehicle odometry publisher.
LibCarla/source/carla/ros2/publishers/CarlaOdometryPublisher.cpp Implements odometry pose/twist writing with correct frame conventions.
LibCarla/source/carla/ros2/publishers/CarlaMapPublisher.h New transient-local map publisher for OpenDRIVE on /carla/map.
LibCarla/source/carla/ros2/publishers/CarlaMapPublisher.cpp Implements OpenDRIVE string write for the map publisher.
LibCarla/source/carla/ros2/publishers/CarlaEgoVehicleStatusPublisher.h New per-vehicle status publisher (velocity/accel/orientation/control).
LibCarla/source/carla/ros2/publishers/CarlaEgoVehicleStatusPublisher.cpp Implements status message write and per-publisher acceleration history.
LibCarla/source/carla/ros2/publishers/CarlaEgoVehicleInfoPublisher.h New transient-local per-vehicle static info publisher.
LibCarla/source/carla/ros2/publishers/CarlaEgoVehicleInfoPublisher.cpp Implements vehicle info write including wheel position conversion into vehicle frame.
LibCarla/source/carla/ros2/middleware/zenoh/ZenohWireFormat.h Adds QoS encoding for Zenoh liveliness keyexprs to match rmw_zenoh expectations.
LibCarla/source/carla/ros2/middleware/zenoh/ZenohPublisherMiddleware.h Implements transient-local via Zenoh advanced publisher cache and advertises QoS in liveliness.
LibCarla/source/carla/ros2/middleware/fastdds/FastDDSPublisherMiddleware.h Applies keep-last depth and transient-local durability based on PublisherQos.
LibCarla/source/carla/ros2/middleware/cyclonedds/CycloneDDSPublisherMiddleware.h Applies keep-last depth and transient-local durability + durability service based on PublisherQos.
LibCarla/source/carla/ros2/middleware/PublisherQos.h Introduces a minimal publisher QoS model (durability + history depth).
LibCarla/source/carla/ros2/middleware/IPublisherMiddleware.h Updates middleware publisher init interface to accept PublisherQos.
LibCarla/source/carla/ros2/TrafficLightData.h Adds simulator-side traffic light state/info snapshot structs for publishing.
LibCarla/source/carla/ros2/ROS2.h Extends ROS2 API with map/vehicle/traffic-light processing and registration queries.
LibCarla/source/carla/ros2/ROS2.cpp Implements new processing paths, creates/destroys new publishers, and publishes REP-105 anchors.
Docs/ros2/topics.md New reference doc enumerating native ROS 2 topics, types, QoS, cadence, and TF tree.
CHANGELOG.md Adds release note entry describing the expanded native ROS 2 interface outputs.
Comments suppressed due to low confidence (6)

PythonAPI/examples/ros2/ros2_native.py:1

  • main() calls itself recursively (main(args) inside def main(args):), which will cause infinite recursion / stack overflow. The SIGTERM handler should be installed without re-invoking main() (typically by moving the handler setup to the if __name__ == '__main__': block or by ensuring the final top-level call is not indented inside main).
    Unreal/CarlaUE4/Plugins/Carla/Source/Carla/Game/CarlaEngine.cpp:1
  • The 'publish on change' behavior can be defeated if States order is not stable across frames: CarlaTrafficLightStatusPublisher::StatesEqual() compares by index, so a reordering (e.g., due to registry iteration order changes) will look like a state change and trigger republish every frame. Sort States (and ideally Info) by id before passing to ROS2 so change detection is order-invariant and deterministic.
    Unreal/CarlaUE4/Plugins/Carla/Source/Carla/Game/CarlaEngine.cpp:1
  • The 'publish on change' behavior can be defeated if States order is not stable across frames: CarlaTrafficLightStatusPublisher::StatesEqual() compares by index, so a reordering (e.g., due to registry iteration order changes) will look like a state change and trigger republish every frame. Sort States (and ideally Info) by id before passing to ROS2 so change detection is order-invariant and deterministic.
    Unreal/CarlaUE4/Plugins/Carla/Source/Carla/Game/CarlaEngine.cpp:1
  • The 'publish on change' behavior can be defeated if States order is not stable across frames: CarlaTrafficLightStatusPublisher::StatesEqual() compares by index, so a reordering (e.g., due to registry iteration order changes) will look like a state change and trigger republish every frame. Sort States (and ideally Info) by id before passing to ROS2 so change detection is order-invariant and deterministic.
    Unreal/CarlaUE4/Plugins/Carla/Source/Carla/Actor/ActorDispatcher.cpp:1
  • The ROS2 vehicle key is passed as static_cast<void*>(&Actor), while other call sites use an AActor* (e.g., static_cast<void*>(Actor)). Using UEActor (the AActor* already available in this scope) would remove ambiguity and avoid accidental pointer-to-pointer mismatches if the Actor identifier is (or becomes) a pointer type.
    Unreal/CarlaUE4/Plugins/Carla/Source/Carla/Game/CarlaEngine.cpp:1
  • This scans the entire actor registry every frame to find ROS2-registered vehicles. If large numbers of non-ROS actors exist, this can become a per-tick cost even though only a small subset is published. Consider iterating over the registered set directly (e.g., expose an iterator/list of registered vehicle handles from ROS2, or cache registered actors in the engine on register/unregister) to make per-frame work proportional to the number of published vehicles.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@JArmandoAnaya
JArmandoAnaya marked this pull request as draft June 12, 2026 08:54

@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.

Good closing piece: info latched once and status republished only on change is the right cadence, and Docs/ros2/topics.md is an excellent, much-needed contract reference. Same item as #9787 for the new CarlaTrafficLight* types (golden-hash pinning), and the same request to include Jazzy in validation. One more suggestion: add a smoke test in PythonAPI/test/smoke/test_ros2.py that subscribes to these topics with the real ros-carla-msgs package installed and asserts a successful decode — the deserialization check #9784 proposed; it catches wire drift a hash test alone won't.

Comment thread LibCarla/source/test/server/test_type_hash.cpp
…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.
… topics

A driving stack running against the native ROS 2 interface is blind to
traffic lights: the simulator knows every light and its state, but none
of it reaches the ROS graph, so planners fall back on the Python API or
on the deprecated external bridge.

The simulator now describes its traffic lights on two latched topics
whenever ROS 2 is enabled. /carla/traffic_lights/info is published once
per episode and carries the static picture of every light: its id, its
pose in the world and its trigger volume, the same data the Python API
exposes. /carla/traffic_lights/status carries the state of every light
and is republished only when a state changes; since the last sample is
latched, a subscriber that joins between changes still receives the
current state immediately. Both topics reuse the message definitions
already published in ros-carla-msgs, so nothing new is required on the
ROS side and no flag beyond --ros2 is involved.

As the closing piece of the data-output series, this also adds a
reference of every topic the native interface publishes and subscribes
to under Docs/ros2/topics.md, together with the consolidated changelog
entry for the series.
…ic light types

Pin the RIHS01 hashes of CarlaBoundingBox, CarlaTrafficLightInfo,
CarlaTrafficLightInfoList, CarlaTrafficLightStatus and
CarlaTrafficLightStatusList in TypeHash.GoldenValuesMatchCanonicalPackages,
so all 40 CdrTopicInfo specializations are verified against the canonical
packages. The golden values were computed by building ros-carla-msgs master
in the osrf/ros:jazzy-desktop image (the Util/ros2/compute_type_hash.sh
flow) and all five matched the values already pinned in CdrTopicInfo.h.
…arla-msgs package

Add test_ros2_traffic_light_decode_with_carla_msgs: subscribe to
/carla/traffic_lights/info and /carla/traffic_lights/status with the
canonical carla_msgs definitions, decode one sample of each and cross-check
the published ids against the client-visible traffic light actors. This
catches wire-format drift that a type hash comparison alone cannot. The
test skips when rclpy or carla_msgs is not importable, so plain smoke runs
are unaffected.
@JArmandoAnaya
JArmandoAnaya force-pushed the feature/ros2-traffic-lights branch from 13868ea to 4ef312a Compare July 7, 2026 06:24
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