Phase 0 · Community Bonding

Understanding the project, and setting up the workshop

Before writing pipeline code, two things had to be right: the architecture (how the pieces will talk), and the environment (getting heavyweight tools running under tight constraints).

1. Picking the architecture — and a key decision

The project moves data from CARLA to an AGL dashboard. Within the robotics stack, the nodes talk to each other over ROS2's native transport, DDS — that's normal and stays. The real architectural question is the last hop: how the data leaves the ROS world and reaches the AGL HMI.

The AGL HMI is a UI application, not a ROS node — it shouldn't carry a full DDS stack, message definitions, and a colcon build just to draw a screen. So the standard pattern is for it to consume ROS data over rosbridge: a server that exposes the ROS2 graph as plain WebSocket + JSON on TCP port 9090. The HMI is then simply a WebSocket client, fully decoupled from ROS internals. This is the design the project targets regardless of where it runs.

The decision: DDS inside the robotics stack; rosbridge (WebSocket/JSON) at the HMI boundary. This is the correct, intended pattern — and it also happens to make the emulated setup work for free: the AGL VM uses QEMU's user-mode (“slirp”) networking, which DDS multicast discovery can't cross, but rosbridge's plain TCP passes straight through. The right architecture and the practical one are the same.

The full intended pipeline — note DDS carries the data between the ROS2 nodes, and rosbridge appears only at the final hop to the HMI:

CARLA → carla_ros_bridge → PointCloud2 ┐ → lidar_detector (PCL perception) → 3D detections │ DDS (inside ROS2) → rosbridge_websocket :9090 ┘ → AGL HMI app (bird's-eye view) WebSocket/JSON

2. The CARLA→ROS2 bridge, and a Python version clash

A "bridge" is the translator that pulls state out of CARLA (over its RPC API) and publishes it onto ROS2 topics. CARLA ships an official, batteries-included one — carla_ros_bridge — that exposes every sensor and the vehicle state automatically. The problem is that it's a single program that must import carla and import rclpy in the same process — and those two cannot coexist here.

The reason is an ABI lock, not a preference: CARLA 0.9.15's Python bindings are a binary compiled against CPython 3.7 (a cp37 artifact that physically won't load on newer interpreters), while ROS2 Humble's rclpy needs Python 3.10+. There is no single interpreter where both imports succeed:

Run the bridge on…import carlaimport rclpy
Python 3.7✗ (Humble won't run)
Python 3.11✗ (no 3.7 binary loads)

Our approach — split the bridge in two

If both libraries can't live in one process, we put them in two — each in the Python version it needs, talking over a local UDP socket carrying JSON. One process imports only carla; the other imports only rclpy; neither ever sees the other's library, so the clash simply never arises. The JSON-over-UDP seam is an "airlock" between the two Python worlds (full implementation in Week 2).

To run two Python versions side by side cleanly, we use Miniforge — a conda installer living entirely in the home folder — giving a carla env (Python 3.7) and a ros2 env (Python 3.11, with ROS2 from the conda-packaged RoboStack distribution).

The correct long-term fix

The two-process bridge is a deliberate, constraint-driven shortcut — ideal for streaming one small topic, but a poor fit for large binary data like a LiDAR point cloud. The proper end state is to eliminate the clash so the official single-process bridge runs:

Practically, the right call differs by stage: the two-process bridge is correct for the lightweight, GPU-less odometry demo here; rebuilding the PythonAPI to run the official carla_ros_bridge is the right move once the GPU-bound LiDAR pipeline begins.

3. Standing up CARLA — without a GPU

CARLA is built on Unreal Engine and “recommends” an NVIDIA card. The server has neither a discrete GPU nor /dev/dri. The insight: CARLA can run with off-screen rendering, where the engine still launches and simulates physics on the CPU — only GPU sensors (cameras, LiDAR) need real graphics hardware. For vehicle physics and odometry, no GPU is required.

# CARLA 0.9.15 — the old AWS download is dead, use the mirror
mkdir -p ~/carla-sim && cd ~/carla-sim
wget https://carla-releases.s3.us-east-005.backblazeb2.com/Linux/CARLA_0.9.15.tar.gz
tar -xzf CARLA_0.9.15.tar.gz

# the Python-3.7 environment for the CARLA client
conda create -y -n carla python=3.7 && conda activate carla
pip install ~/carla-sim/PythonAPI/carla/dist/carla-0.9.15-cp37-cp37m-*.whl
# launch headless (no display), software-rendered
./CarlaUE4.sh -RenderOffScreen -nosound -carla-rpc-port=2000

4. The ROS2 environment

The second conda environment provides ROS2 Humble via RoboStack (ROS2 packaged for conda-forge):

conda create -y -n ros2 python=3.11 && conda activate ros2
conda config --env --add channels robostack-staging
conda config --env --add channels conda-forge
mamba install -y ros-humble-ros-base

The two environments — carla (3.7) and ros2 (3.11) — are the foundation of the bridge: one reads the simulator, the other publishes ROS2 topics, and they communicate over a local UDP socket.

5. The AGL + ROS2 build tree

AGL is assembled with the Yocto/BitBake build system; the ROS2 integration comes from the meta-ros layers. After evaluating the available release branches, the choice was the unagi release — it's internally consistent (Yocto scarthgap throughout) and ships the meta-agl-ros2 layer out of the box.

mkdir -p ~/AGL/master && cd ~/AGL/master
repo init -u https://gerrit.automotivelinux.org/gerrit/AGL/AGL-repo -b unagi
repo sync -j4

# configure a QEMU x86-64 build with the ROS2 feature
source meta-agl/scripts/aglsetup.sh -m qemux86-64 -b build-qemu-ros agl-ros2

End of community bonding  The pieces are chosen and installed: a clear architecture (rosbridge over slirp), two Python environments, CARLA running GPU-less, and an AGL/ROS2 build tree configured. Everything is in place to start building the actual pipeline — which is where the real bugs begin.

← Home
Week 1: Building AGL & proving the link →