Skip to content

Traffic light outside a junction accepts set_state(), but get_state() always returns Red #9832

Description

@kawaeeeee

Setup

  • CARLA version: 0.9.16 (Python client and server)
  • Platform: Linux x86_64; packaged CARLA in an Ubuntu 20.04 Docker container on an Ubuntu 24.04.3 LTS host
  • Python version: 3.10.18
  • GPU: NVIDIA RTX PRO 6000 Blackwell Max-Q Workstation Edition
  • GPU Drivers: 590.48.01

The reproducer has only been runtime-tested with CARLA 0.9.16. The relevant fallback code also
appears to be present in the current master and ue5-dev branches, but I have not runtime-tested
those branches.

Describe the bug

When an OpenDRIVE dynamic traffic-light signal is placed on a non-junction road and its controller
is not referenced by a junction, CARLA creates a carla.TrafficLight actor and accepts
set_state(). The rendered bulb changes to the requested state, but the client-side actor snapshot
is not populated correctly:

  • traffic_light.get_state() always returns TrafficLightState.Red, including after setting Green
    or Yellow and waiting for a newer world snapshot.
  • traffic_light.get_opendrive_id() returns an empty string instead of the OpenDRIVE signal ID.
  • get_green_time(), get_yellow_time(), and get_red_time() all return 0.0.

In a larger custom map, a vehicle overlapping the signal trigger observed the requested state via
vehicle.get_traffic_light_state(). This and the visible bulb change indicate that the server-side
traffic-light component is updated, while the traffic-light actor snapshot exposed to the Python
client remains at its default values.

Steps to reproduce

The attached reproducer contains one 80 m straight road, one driving lane, one dynamic signal with
OpenDRIVE ID 10, and one controller that controls signal 10. It intentionally contains no
junction elements.

  1. Start a CARLA 0.9.16 server.

  2. Extract traffic_light_outside_junction_get_state_reproducer.zip.

  3. Run the following from the extracted directory:

    python3 reproduce.py --host 127.0.0.1 --port 2000

The script loads outside_junction_traffic_light.xodr using
client.generate_opendrive_world(), finds the single traffic-light actor, calls set_state() with
Green, Yellow, and Red, and waits for a new world snapshot after every call.

Expected behavior

After setting a traffic-light state and receiving a newer world snapshot, get_state() should
return the state that was set. The actor should also expose the OpenDRIVE signal ID:

set_state(Green)  -> get_state() == Green
set_state(Yellow) -> get_state() == Yellow
set_state(Red)    -> get_state() == Red
get_opendrive_id() == "10"

Logs

Client output:

CARLA client version: 0.9.16
CARLA server version: 0.9.16
Traffic light actor id: 25
OpenDRIVE signal id reported by CARLA: ''
Snapshot timings: green=0.0, yellow=0.0, red=0.0, frozen=False
set_state(Green) -> get_state() == Red
set_state(Yellow) -> get_state() == Red
set_state(Red) -> get_state() == Red
RESULT: BUG REPRODUCED
Green and Yellow were requested, but the client snapshot remained Red.

Relevant server output from the packaged UE4 server:

4.26.2-0+++UE4+Release-4.26 522 0
Disabling core dumps.
WARNING: cannot parse georeference: ''. Using default values.
Loading '/workspace/CarlaUE4/Content/Carla/Maps/Nav/OpenDriveMap.obj'...
Tiles 2 x 1
Max Tiles 2
Max Polys 2097152
Building...
TileRow 0/1: 2 tiles added (total 2)
Total tiles added: (2 / 2 max)
Saving...
Saving 2 tiles

The georeference warning is expected for this intentionally minimal XODR and does not prevent the
world or traffic-light actor from being created. No server-side error related to the set_state()
calls was emitted.

Scripts

traffic_light_outside_junction_get_state_reproducer.zip

It contains:

  • outside_junction_traffic_light.xodr: minimal OpenDRIVE 1.4 map
  • reproduce.py: standalone Python reproducer
  • README.md: run instructions and the expected/observed results

Additional investigation / suspected cause

The issue appears to come from the fallback path used for traffic-light signals that do not resolve
to a junction controller.

The normal path in TrafficLightManager.cpp calls:

TrafficLightGroup->AddController(NewTrafficLightController);

The fallback path instead directly appends to the controller array:

TrafficLightGroup->GetControllers().Add(NewTrafficLightController);

ATrafficLightGroup::AddController() also calls Controller->SetGroup(this). The direct array
append skips that call. As a result, TrafficLightComponent->GetController() is non-null while
TrafficLightComponent->GetGroup() is null.

WorldObserver.cpp serializes the traffic-light sign_id, state, timings, and frozen flag only when
both the controller and group are available. Otherwise, the zero-initialized snapshot is sent. Since
the enum value of TrafficLightState.Red is zero, get_state() always returns Red.

The same direct array append is visible in:

A possible minimal fix appears to be:

- TrafficLightGroup->GetControllers().Add(NewTrafficLightController);
+ TrafficLightGroup->AddController(NewTrafficLightController);

I have not rebuilt CARLA to validate this source change.

This may be related to
Discussion #6957: CARLA Traffic Light Data Missing,
which describes missing traffic-light snapshot data when a light has no group or controller. This
report adds a two-file minimal reproducer, demonstrates the set_state() versus get_state()
mismatch, and identifies the fallback path that leaves the group unset.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions