..
# Copyright (c) 2026, Arm Limited.
#
# SPDX-License-Identifier: Apache-2.0
#########################
CAN and CARLA integration
#########################
Drive a CARLA ego vehicle from Safety Island control output over classic CAN.
CARLA has no native CAN interface, so a host bridge stands in for the vehicle
ECU: SocketCAN RX, decode the placeholder frames from :doc:`can_output`, apply
``carla.VehicleAckermannControl``.
This page is the design for the stacked work tracked in
https://github.com/autowarefoundation/autoware-safety-island/issues/43.
Implementation details, backend isolation, the Zephyr FVP UDP tunnel, and
the decoder/watchdog rules live in :doc:`can_carla_implementation_plan`.
.. mermaid::
graph LR
POSIX[freertos-posix]
VCAN[SocketCAN vcan0]
Bridge[Host CAN-CARLA bridge]
CARLA[CARLA ego]
POSIX -->|classic CAN 0x100 0x101 0x102| VCAN
VCAN --> Bridge
Bridge -->|VehicleAckermannControl| CARLA
**********************
Shared decisions
**********************
- **Actuation path is CAN.** The bridge talks to CARLA through the Python API,
not by republishing ROS control commands. That keeps CAN first-class: CARLA
is the simulated vehicle, not another ROS hop.
- **DDS inputs stay DDS.** Trajectory, odometry, steering, acceleration, and
operation mode are unchanged.
- **Placeholder frame contract.** Keep IDs ``0x100`` / ``0x101`` / ``0x102``
and the scaling in :doc:`can_output` until a vehicle DBC is agreed.
- **One host bridge, one sink.** Open-loop and closed-loop share the same
SocketCAN → decode → CARLA Python API process. Closed-loop does not add a
second command path through ``autoware_carla_interface``.
- **PR 1 local runtime is** ``freertos-posix`` **plus** ``vcan``.
- **PR 3 local runtime is** ``zephyr-fvp --network tap`` plus a UDP
batch tunnel onto the same ``vcan0`` decoder. Native FVP CAN stays
loopback-only. S32Z hardware CAN is out of this stack.
- **CI never launches CARLA.** GitHub Actions covers encode → transport →
decode. The visual CARLA loop is a documented host demo.
- **Mock is test-only.** Runtime CAN output does not fall back to the
in-memory recorder when no interface is configured.
- **CAN-FD is opt-in.** ``freertos-posix`` can send one 24-byte ``0x103``
frame instead of the classic three (``SAFETY_ISLAND_CAN_FORMAT=fd`` plus
``bridge.py --can-format fd``). Every other runtime stays classic; see
:doc:`can_fd`.
**********************
Open-loop (PR 1)
**********************
Goal: a Safety Island command visibly moves a CARLA ego without Autoware
planning or Open AD Kit.
Components
==========
1. **SocketCAN backend** for ``freertos-posix``. Isolated with
``PLATFORM_FREERTOS_POSIX`` so Linux ``PF_CAN`` never enters S32Z2/X5H
builds. ``SAFETY_ISLAND_CAN_IFACE`` (for example ``vcan0``) is required
at runtime; missing or unopenable interface fails ``can_init()``. The
in-memory mock is compiled only into ``--can-output-test``.
2. **Python host decoder** in ``demo/can_carla_bridge/``. ``0x102`` is the
commit marker: require fresh ``0x100`` and ``0x101``, wrapping sequence
(forward jumps accepted, replays/stale rejected), and a 0.5 s monotonic
receive-timeout safe stop. Python state-machine tests and a privileged
``vcan`` integration phase validate the decoder against the C++ sender.
3. **Host bridge** (``demo/can_carla_bridge/``): ``python-can`` on ``vcan0``,
assemble a command from the three frames, map to
``VehicleAckermannControl``, apply via the CARLA 0.9.16 client. Same
CARLA version as Open AD Kit.
4. **Inputs to the controller** from the existing rosbag (or a canned DDS
publisher) on domain 2. Do not start planning simulator or Open AD Kit in
this loop.
Control mapping
===============
``autoware_control_msgs/Control`` is kinematic and left-positive. CARLA
Ackermann control is right-positive. The bridge uses
``carla.VehicleAckermannControl``. Sign conversion is
``steer = -steering_tire_angle`` only: CARLA takes ``Abs(steer_speed)``.
Zero ``steer_speed`` snaps steer; zero ``acceleration`` means vehicle max,
not "hold". Safe stop is ``speed = 0`` with configured brake and last
steer, not all zeros. Details are in :doc:`can_carla_implementation_plan`.
Demo and validation
===================
- Host: ``ip link add vcan0 type vcan`` and bring it up.
- Build ``freertos-posix`` with ``--control-output CAN_ONLY`` or
``DDS_AND_CAN``, ``SAFETY_ISLAND_CAN_IFACE=vcan0``.
- Run the bridge against CARLA 0.9.16, confirm the ego steers/throttles.
- CI: privilege-free encoder/decoder/mock tests, plus a capability-gated
``vcan`` integration job. No GPU, no CARLA container.
Known limits
============
- Placeholder CAN IDs, not a production DBC.
- Open-loop CARLA motion will not match rosbag odometry.
- Mapping cannot recover jerk from the current frames.
- FVP TAP UDP is PR 3, not this demo.
- S32Z hardware CAN is not this demo.
Closed-loop with Open AD Kit is PR 2. Zephyr FVP is PR 3. CAN-FD is opt-in on
the POSIX path; see :doc:`can_fd`.
**********************
Closed-loop (PR 2)
**********************
Goal: Autoware planning against CARLA, Safety Island as the controller,
actuation still over CAN into the same host bridge.
This layer depends on the open-loop SocketCAN backend and bridge. It does
not add a second command sink.
.. mermaid::
graph LR
subgraph carla_stack [Open AD Kit CARLA]
Sensors[autoware_carla_interface
sensors only]
Planning[Autoware planning]
CARLA2[CARLA ego]
end
Bridge2[Host CAN-CARLA bridge]
DDS[domain bridge]
SI2[Safety Island
CAN_ONLY]
CARLA2 --> Sensors
Sensors --> Planning
Planning -->|trajectory odom steer accel| DDS
DDS --> SI2
SI2 -->|classic CAN| Bridge2
Bridge2 -->|VehicleAckermannControl| CARLA2
Rules
=====
- Safety Island builds with ``CAN_ONLY`` so
``/control/trajectory_follower/control_cmd`` is not also published over DDS.
- Autoware's onboard trajectory follower stays running so Auto/Engage can
set ``AUTONOMOUS``. It must not actuate CARLA: sensors-only
``autoware_carla_interface`` skips ``apply_control()`` and remaps command
topics off the live path.
- ``autoware_carla_interface`` keeps sensor and localization publication and
the CARLA world tick. It has no upstream sensor-only flag:
``SensorLoop`` calls ``ego.apply_control()`` every tick. The overlay
Open AD Kit's ``overlay/patch_sensors_only.py`` skips that call.
Remap the raw-vehicle converter off the live command topics. Do not fork
Open AD Kit or Autoware Universe.
- Same placeholder frames, same mapping, same ``vcan0`` iface.
- Autoware plus CARLA compose lives in Open AD Kit
``deployments/safety-island-carla-simulation/`` (PR #146). That deployment
also starts the domain-bridge. This repository runs SI POSIX ``CAN_ONLY``,
``vcan0``, and the CAN-CARLA bridge. Do not vendor Open AD Kit sources here.
Validation
==========
- Manual: start Open AD Kit ``safety-island-carla-simulation`` (no
``--drive``), then this repo's SI ``CAN_ONLY`` and
CAN-CARLA bridge. Town01, set a goal, engage, confirm the ego moves
under SI CAN commands and that ``candump vcan0`` shows ``0x100`` /
``0x101`` / ``0x102``.
- Confirm Autoware is not also applying ``control_cmd`` to CARLA (no dual
drive): ``apply_control`` is patched out and the converter is remapped.
- Still no CARLA job in this repository's GitHub Actions.
Known limits
============
- GPU host and Open AD Kit image pull required.
- Humble-only while Open AD Kit CARLA is Humble-only.
- Placeholder DBC and simple actuator mapping still apply.
- CES / FVP virtual-CAN is PR 3 below.
**********************
Zephyr FVP (PR 3)
**********************
Goal: the same open-loop and closed-loop CARLA demos as PR 1 and PR 2,
with Safety Island on ``zephyr-fvp --network tap`` instead of
``freertos-posix``. Frames still terminate on ``vcan0``. The host
decoder, watchdog, and CARLA bridge do not change.
Native FVP CAN stays ``zephyr,can-loopback`` (unit tests only). This PR
is a UDP batch tunnel over the existing TAP Ethernet path.
.. mermaid::
graph LR
FVP[zephyr-fvp TAP]
TAP[LAN91C111 tap0]
GW[UDP-to-CAN gateway]
VCAN[SocketCAN vcan0]
Bridge[Host CAN-CARLA bridge]
CARLA[CARLA ego]
FVP -->|48-byte UDP batch| TAP
TAP --> GW
GW -->|0x100 0x101 0x102| VCAN
VCAN --> Bridge
Bridge -->|VehicleAckermannControl| CARLA
Open-loop
=========
Same as PR 1, except the producer:
- Build ``zephyr-fvp`` with ``--network tap --control-output CAN_ONLY``
(or ``DDS_AND_CAN``).
- FVP ``192.168.10.2`` sends one UDP datagram per command to
``192.168.10.1:5555``.
- ``demo/can_tunnel_bridge/`` validates the datagram and injects classic
frames onto ``vcan0``.
- Rosbag or canned DDS inputs on domain 2. No Open AD Kit.
Closed-loop
===========
Same Open AD Kit rules as PR 2 (``CAN_ONLY``, sensors-only
``autoware_carla_interface``, no dual-drive). Safety Island is
``zephyr-fvp`` TAP plus the gateway, not ``freertos-posix``.
Kconfig
=======
``CONTROL_CMD_OUTPUT_CAN_ONLY`` / ``DDS_AND_CAN`` must not ``select CAN``.
Only ``CONFIG_CONTROL_CMD_CAN_TRANSPORT_NATIVE`` selects ``CAN``.
``CONFIG_CONTROL_CMD_CAN_TRANSPORT_UDP_TUNNEL`` selects ``NET_UDP`` /
``NET_IPV4`` and does not require ``zephyr,canbus``.
``platform_can.h`` includes ``zephyr_can_udp_tunnel.h`` on the TAP path
so ``zephyr_can.h`` is not compiled.
Watchdog
========
POSIX stays at 0.5 s. FVP is not real-time; the FVP demo and TAP
integration job use a much larger configured timeout.
Validation
==========
- Privilege-free: UDP pack/unpack unit tests; native loopback
``--can-output-test`` unchanged.
- Privileged: FVP TAP + gateway → ``vcan0`` → same decoder as PR 1.
No CARLA in CI.
- Manual: PR 1 and PR 2 host demos with ``zephyr-fvp`` as the producer.
Known limits
============
- CAN-over-UDP, not classic CAN end-to-end.
- S32Z hardware CAN remains out of this stack.
- CAN-FD is opt-in and POSIX-only; the FVP tunnel stays classic.