CAN and CARLA implementation plan
Corrected dual-runtime plan for classic CAN from the Safety Island into a CARLA ego. This page is the implementation source of truth for the stacked work in https://github.com/autowarefoundation/autoware-safety-island/issues/43. It supersedes the original open-loop sketch in CAN and CARLA integration where the two disagree.
The placeholder frame contract in CAN output is unchanged: IDs
0x100 / 0x101 / 0x102, classic 8-byte DLC, existing scaling.
Why this plan
The original PR 1 design assumed freertos-posix plus vcan only, with
the in-memory mock as the unset-interface fallback, and treated Zephyr FVP as
unreachable. Cross-check against the current code and issue #43 found that
that sketch cannot ship as written:
platform_can.hdispatches every FreeRTOS target through one header. LinuxPF_CANmust never enter the S32Z2/X5Harm-none-eabibuilds.The mock always reports successful
can_init(). If it remains the runtime fallback,CAN_ONLYcannot fail-fast and commands disappear into memory.freertos-posixcurrently defaults toDDS_AND_CANonly to exercise that mock. Once the mock is test-only, the unconfigured runtime default must beDDS_ONLY.Sequence lives only in
0x102. One UDP datagram per CAN frame lets loss or reordering pair stale0x100/0x101data with a later commit.Existing CAN CI records mock frames. It does not create
vcan0, send SocketCAN, or run a decoder. Creatingvcan0needsCAP_NET_ADMIN; FVP TAP also needs/dev/net/tun. That path is not privilege-free.Autoware steering is CCW/left-positive. CARLA Ackermann steering is right-positive. Velocity and steering rate must not be discarded.
0x102is the only cycle marker. The decoder must require fresh0x100and0x101, wrapping sequence (accept forward jumps, reject replays and stale values), and a receive-timeout safe stop.
Zephyr FVP still has no host-reachable CAN IP. The existing TAP Ethernet
path (192.168.10.2 on FVP, 192.168.10.1 on the host) is the only
virtual way to reach the host. Native zephyr-s32z already speaks real
CAN and stays on that path.
Common transport contract
Keep encode_control_command() and the three-frame layout. Change how
frames leave the controller.
ControlCommandCanOutput::send() today encodes, then calls
platform::can_send() once per frame while holding a mutex, and
increments sequence_ before encoding can fail
(control_command_can_output.hpp). Replace that with a batch send:
Encode the complete command.
Call
platform::can_send_batch(frames, count).Increment
sequence_only after the batch succeeds.
can_send_batch() is the platform boundary. Semantics:
Never send
0x102unless0x100and0x101were handed to the transport. A later write failure may still leave the data frames visible onvcan0; only the UDP datagram is all-or-nothing.Sends are nonblocking, or bounded to a short deadline. Do not stall the controller timer callback on SocketCAN or UDP backpressure.
A failed batch leaves
sequence_unchanged so the next successful command stays contiguous. Unit-test this: encode or mid-batch send fails, then the next successful command reuses the same sequence.
Keep platform::can_send(frame) only where a backend truly is
frame-at-a-time (native Zephyr CAN, the test mock). The POSIX SocketCAN
and FVP UDP backends implement the batch entry point directly.
Decoder and CARLA mapping
The host’s Python decoder in demo/can_carla_bridge/decoder.py is the only
assembler. The Safety Island encoder remains C++; both runtimes inject classic
frames onto vcan0 and the bridge never sees UDP.
Assembly
0x102is the commit marker. Do not apply a command until a commit arrives.Require a fresh
0x100and a fresh0x101since the previous accepted commit. Duplicate IDs in the same cycle replace the pending frame; they do not count as a second cycle.Sequence is the little-endian
uint16at bytes 2-3 of0x102. Accept the next expected value and forward jumps, wrapping modulo-65536 (including 65535 to 0). A forward jump resyncs the expected sequence and the watchdog anchor. Reject replays and stale sequences (wrapping signed delta< 0).On first command after start or after a watchdog safe-stop, accept the first valid complete cycle as the baseline. Do not require sequence 0.
After a rejected commit, drop pending
0x100/0x101.Unknown CAN IDs are ignored and do not affect the cycle.
IDs
0x100/0x101/0x102with DLC other than 8, extended ID, or non-classic flags are discarded: that ID is not fresh. They do not themselves trigger safe-stop.
Watchdog
Use host monotonic receive time, not the payload timestamp.
timestamp_ms_modulo()is producer metadata. FreeRTOS POSIX uses host epoch time; Zephyr FVP defaults to time since boot. Those clocks are not comparable.If no accepted commit arrives within the configured timeout, apply a safe stop and hold it until a new valid cycle is accepted.
POSIX default timeout is 0.5 s, matching controller
timeout_thr_secand covering a skipped 150 ms tick (~300 ms to the next frame). Do not use 200 ms: that is tighter than one missed period.FVP is not real-time. The FVP demo and TAP integration job use a much larger configured timeout; they do not share the 0.5 s POSIX default.
CARLA boundary
Autoware Lateral.idl is CCW/left-positive. CARLA Ackermann steering is
right-positive. Use carla.VehicleAckermannControl, not
VehicleControl.
CARLA 0.9.16 AckermannController does not treat zeros as “hold”:
Abs(steer_speed) < 0.001snaps steer to the target immediately.Abs(acceleration) < 0.0001clips the speed PID with the vehicle’s max accel/decel, not “no acceleration”.The controller takes
Abs(steer_speed), so negating Autoware’s rate does not reverse steering direction. Sign conversion is onlysteer = -steering_tire_angle.
Apply:
steer = -steering_tire_anglesteer_speed = abs(steering_tire_rotation_rate)when the rate flag is set. If unset, use a configured slew (not 0) so CARLA does not snap.speed = velocityaccelerationwhen the acceleration flag is set. If unset, use a configured magnitude (not 0).jerk = 0: the current frames do not carry a scaled jerk field.
Safe stop: speed = 0, a configured braking acceleration (not 0),
hold last steer, and a configured slew. Do not command
steer = 0, steer_speed = 0 (that snaps wheels to center).
Constants remain bridge configuration, defaulted to the Autoware sample-vehicle ballpark. This is not an OEM vehicle model.
FreeRTOS POSIX
Backend isolation
platform_network.h already special-cases PLATFORM_FREERTOS_S32Z2
and PLATFORM_FREERTOS_X5H before the generic FreeRTOS header.
platform_can.h must do the same:
actuation_module/freertos/CMakeLists.txtdefinesPLATFORM_FREERTOS_POSIX=1in addition toPLATFORM_FREERTOS.platform_can.hincludes a POSIX-only SocketCAN header whenPLATFORM_FREERTOS_POSIXis set, a test mock when the CAN-output-test target is built, and keeps embedded targets off LinuxPF_CAN.freertos-s32z2andfreertos-x5hCMake mustFATAL_ERRORifCONFIG_CONTROL_CMD_OUTPUT_MODEis notDDS_ONLY.
SocketCAN backend
Runtime CAN output requires
SAFETY_ISLAND_CAN_IFACE(for examplevcan0). Missing, empty, or unopenable interface:can_init()returns false.CAN_ONLYthen exits incontroller_node.cpp;DDS_AND_CANlogs and continues on DDS.Open
PF_CAN/SOCK_RAW/CAN_RAW, bind to the named interface, setO_NONBLOCK.can_send_batch()writes0x100,0x101, then0x102. If a write fails before the commit, do not send0x102.Default
CONFIG_CONTROL_CMD_OUTPUT_MODEforfreertos-posixbecomesDDS_ONLY. Demo and integration builds pass--control-output CAN_ONLYorDDS_AND_CAN.
Mock backend
Compile the in-memory recorder only into BUILD_TEST=4
(--can-output-test). Keep reset_recorded_can_frames() and friends
behind that target so the existing encoder and recording assertions stay
privilege-free.
Zephyr FVP UDP tunnel
Native FVP CAN remains zephyr,can-loopback and is still the
--can-output-test path. It cannot reach the host. The CARLA path is a
separate transport over the existing TAP network.
Selection
Do not key both Zephyr backends on PLATFORM_ZEPHYR alone.
zephyr_can.h requires chosen { zephyr,canbus } and must stay the
S32Z / loopback-test backend.
Today CONTROL_CMD_OUTPUT_CAN_ONLY and
CONTROL_CMD_OUTPUT_DDS_AND_CAN both select CAN.
zephyr_can.h then #error``s unless ``chosen { zephyr,canbus }
exists. FVP TAP has no canbus node unless the loopback overlay is
applied, so a transport choice that “does not select CAN” is not enough:
the output-mode select still pulls in the CAN stack.
Change Kconfig:
Remove
select CANfrom the output-mode choice.CONFIG_CONTROL_CMD_CAN_TRANSPORT_NATIVE(default)select CANand is the currentzephyr,canbuspath.CONFIG_CONTROL_CMD_CAN_TRANSPORT_UDP_TUNNELselectsNET_UDP/NET_IPV4, does notselect CAN, and does not requirezephyr,canbus.platform_can.hincludeszephyr_can_udp_tunnel.hvszephyr_can.hfrom the transport choice so the native header is not compiled on the TAP path.
build.sh --platform zephyr-fvp --network tap --control-output CAN_ONLY
(or DDS_AND_CAN) applies a new conf fragment
fvp_baser_aemv8r_smp_can_udp_tunnel.conf on top of
fvp_baser_aemv8r_smp_tap_network.conf. Reject the UDP tunnel without
--network tap. Extend --control-output to zephyr-fvp; keep it
invalid for zephyr-s32z in this stack.
Wire format
One datagram is one command. Do not serialize CanFrame as a C++
object: native endianness, bool, and padding are not a protocol.
Fixed little-endian layout, exact length 48 bytes:
Offset |
Size |
Field |
|---|---|---|
0 |
2 |
magic bytes |
2 |
1 |
version |
3 |
1 |
flags (bit 0 = classic batch; other bits reserved 0) |
4 |
2 |
batch sequence (same value encoded in |
6 |
2 |
reserved 0 |
8 |
13 |
frame 0: |
21 |
13 |
frame 1: same |
34 |
13 |
frame 2: same |
47 |
1 |
reserved 0 |
Frame order in the datagram is 0x100, 0x101, 0x102. The
gateway rejects any other length, magic, version, flags, DLC, ID, or
frame order, and does not inject a partial cycle.
Sender
New header
zephyr_can_udp_tunnel.h, selected by the transport choice.can_init()creates a UDPnet_context(or BSD socket) toCONFIG_CONTROL_CMD_CAN_TUNNEL_PEERdefault192.168.10.1andCONFIG_CONTROL_CMD_CAN_TUNNEL_PORTdefault5555.can_send_batch()packs the 48-byte datagram and sends once, nonblocking. Do not retry inside the controller callback.main.cppcurrently ignoresconfigure_network()failures. For the UDP tunnel, a failed TAP/IP bring-up must exit beforecan_init(). The TAP conf already setsCONFIG_NET_IFACE1_ADDR="192.168.10.2".
Host gateway
demo/can_tunnel_bridge/ binds UDP 192.168.10.1:5555, validates a
complete datagram, and injects the three classic frames onto vcan0 in
order. If SocketCAN injection fails before 0x102, do not send the
commit. This process is not the CARLA bridge; it only creates the same
SocketCAN stream that freertos-posix writes natively.
Host CARLA bridge
demo/can_carla_bridge/ stays on vcan0 via python-can. It does
not know which runtime produced the frames.
Receive classic frames.
Run the assembler, sequence check, and watchdog above.
Map to
VehicleAckermannControland apply through the CARLA 0.9.16 client, same version as Open AD Kit.Inputs to the Safety Island still come from the existing rosbag or a canned DDS publisher on domain 2. Open-loop does not start planning simulator or Open AD Kit.
Work items
Land in this order so each step is testable without CARLA.
PR 1 (#49) — freertos-posix + vcan
Common batch API and sequence fix
can_send_batch(), increment sequence only on success, keep per-framecan_send()as a backend helper. Test: failed encode or mid-batch send does not advancesequence_.POSIX backend isolation
PLATFORM_FREERTOS_POSIX, test-only mock, SocketCAN source that cannot compile into S32Z2/X5H,DDS_ONLYdefault, CMakeFATAL_ERRORon embedded CAN modes.Host decoder Use the Python decoder directly in the bridge and in the SocketCAN roundtrip test against the C++ sender. Cover startup, duplicates, missing/reordered frames, bad DLC, wrap
65535 -> 0, ignored unknown IDs, forward-jump accept, stale/replay reject, timeout safe-stop, and re-baseline after timeout.FreeRTOS ``vcan`` integration Privileged test: create
vcan0, setSAFETY_ISLAND_CAN_IFACE, send a real command, decode on the host. No CARLA.CARLA bridge (POSIX demo) Ackermann mapping, 0.5 s watchdog, documented host demo for
freertos-posix.Docs Keep this page, CAN and CARLA integration, and CAN output aligned. Closed-loop is PR 2. Zephyr FVP is PR 3. CAN-FD is opt-in (PR 4).
PR 2 (#50) — Open AD Kit closed-loop
Autoware planning against CARLA, Safety Island as the controller, actuation
still over classic CAN into the same vcan0 bridge as PR 1. Autoware plus
CARLA compose lives in Open AD Kit
(deployments/safety-island-carla-simulation/, PR #146), including the
domain-bridge. This repository keeps the SI binary, vcan0, and
demo/can_carla_bridge.
Sensors-only CARLA interface
autoware_carla_interfacehas no upstream sensor-only flag.SensorLoopcallsego_actor.apply_control()every tick. Overlay Open AD Kit’soverlay/patch_sensors_only.pyskips that call so the CAN bridge is the sole CARLA driver. Remapinput_control_cmd/output_actuation_cmdoff the live topics.Stub Autoware follower Open AD Kit mounts its stub
control.launch.xmlinto thecontrolservice, same pattern as the planning-simulator demo.SI ``CAN_ONLY`` and domain-bridge Build
freertos-posix --control-output CAN_ONLY. Bridge the five DDS inputs domain 1 → 2. Do not rely oncontrol_cmd2 → 1. Closed-loop CycloneDDS pins both domains tolo(Open AD Kit default).Host CAN-CARLA bridge Same
demo/can_carla_bridge/as PR 1. Pass--ego-role ego_vehiclematches Open AD Kit.VehicleAckermannControlmapping unchanged.Pins and topic contract Open AD Kit’s deployment
config.envpins CARLA and component images by digest. Record the Open AD Kit and Safety Island commits when you run the demo. Its privilege-free tests check the five-input topic matrix againstbridge-config.yaml.Docs Closed-loop launch is two-repo. CI never starts CARLA or Open AD Kit.
PR 3 (#53) — Zephyr FVP TAP tunnel
Open-loop and closed-loop with zephyr-fvp --network tap. Same
vcan0 decoder as PR 1 / PR 2.
Zephyr UDP transport and gateway
Kconfig as above (output modes do not
select CAN), TAP conf fragment,build.shflags, 48-byte pack/unpack unit tests, host gateway.
Zephyr FVP TAP integration
Run FVP with hostbridge TAP, gateway,
vcan0, same decoder, FVP watchdog timeout. No CARLA.
CARLA bridge (FVP demo)
Same bridge as PR 1, documented TAP + gateway host demo.
PR 4 (#54) — CAN-FD (opt-in)
One 24-byte 0x103 frame replaces the classic batch on the same vcan0
path. Classic stays the default; Zephyr, the FVP tunnel, and S32Z hardware stay
classic. FD is not a second command path.
FD frame and encoder
CanFdFrame(64-byte capacity, bytelength,brs) plusencode_control_command_fd(), which packs the classic payloads without duplicating scaling. Test: frame id, length 24, BRS off, payload offsets.Opt-in selection and bridge decode
SAFETY_ISLAND_CAN_FORMAT=fdon POSIX SocketCAN (MTU below 72 fails init) andbridge.py --can-format fd. The decoder gainsfeed_fd()and reuses the commit, replay, and watchdog rules.
Tests and CI
Privilege-free (existing job, no extra capabilities)
Encoder payload, mode helpers, non-finite rejection.
PR 4: FD encoder frame id, byte length, BRS off, and payload offsets.
Mock recorder on
freertos-posix --can-output-test.Failed encode or mid-batch send does not advance
sequence_.Zephyr FVP
zephyr,can-loopbackon--can-output-test.Python decoder state-machine unit tests, including wrap, ignored unknown IDs, forward-jump accept, stale/replay reject, and timeout.
PR 3: UDP datagram pack/unpack and rejection of malformed lengths.
Privileged integration (explicit capabilities)
Do not fold this into the unprivileged container job. On CI, load
vcan on the runner host and run the test with host networking;
fail if vcan0 cannot be created. Local runs may skip (exit 77)
when the kernel has no vcan.
PR 1: FreeRTOS POSIX
ip link add vcan0 type vcan,SAFETY_ISLAND_CAN_IFACE=vcan0, encode → SocketCAN → decode. Watchdog: stop the producer and assert safe-stop (0.5 s).PR 3: Zephyr FVP TAP
tap0at192.168.10.1/24, FVP at192.168.10.2, gateway →vcan0→ same decoder, with the FVP timeout.PR 4: the same
vcan0roundtrip also sends one CAN-FD0x103frame (SAFETY_ISLAND_CAN_FORMAT=fd) and decodes it.
Never launch CARLA, a GPU job, or an Open AD Kit compose in GitHub
Actions. The Open AD Kit companion PR tests the sensors-only overlay and
five-input topic matrix; this repository tests ego role_name in the bridge.
Known limits
Placeholder CAN IDs, not a production DBC.
Open-loop CARLA motion will not match rosbag odometry.
FVP CARLA path is CAN-over-UDP, not classic CAN end-to-end. No arbitration, error frames, or bit timing. Message semantics (ID, DLC, data) are preserved.
Jerk is flagged in
0x102but not carried as a scaled field.zephyr-s32zhardware CAN to a host USB-CAN adapter is possible and out of this stack.freertos-s32z2/freertos-x5hCAN is out of this stack.