feat(LibCarla/ros2): [3/3] publish traffic light info and status topics#9788
feat(LibCarla/ros2): [3/3] publish traffic light info and status topics#9788JArmandoAnaya wants to merge 16 commits into
Conversation
There was a problem hiding this comment.
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
PublisherQosand 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)insidedef main(args):), which will cause infinite recursion / stack overflow. The SIGTERM handler should be installed without re-invokingmain()(typically by moving the handler setup to theif __name__ == '__main__':block or by ensuring the final top-level call is not indented insidemain).
Unreal/CarlaUE4/Plugins/Carla/Source/Carla/Game/CarlaEngine.cpp:1- The 'publish on change' behavior can be defeated if
Statesorder 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. SortStates(and ideallyInfo) byidbefore 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
Statesorder 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. SortStates(and ideallyInfo) byidbefore 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
Statesorder 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. SortStates(and ideallyInfo) byidbefore 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 anAActor*(e.g.,static_cast<void*>(Actor)). UsingUEActor(theAActor*already available in this scope) would remove ambiguity and avoid accidental pointer-to-pointer mismatches if theActoridentifier 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.
youtalk
left a comment
There was a problem hiding this comment.
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.
…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.
13868ea to
4ef312a
Compare
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 asactor.trigger_volume./carla/traffic_lights/statuscarries 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:
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?
Validation:
make LibCarla ARGS="--ros2"andmake 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 CarlaUE4Editorwith no new warnings; a fullmake packagebuild; 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
This change is