Build SUMO-Simulink Co-simulation
Create Simulink models that run synchronized co-simulation with Eclipse SUMO using the SumoInterfaceLibrary blocks from the Automated Driving Toolbox Interface for Eclipse SUMO Traffic Simulator.
When to Use
- Building a Simulink model that connects to Eclipse SUMO
- Setting up traffic co-simulation for ADAS/AD controller testing
- Reading vehicle states (position, speed) from SUMO into Simulink
- Controlling an ego vehicle in SUMO from a Simulink controller
- Spawning or removing vehicles dynamically during simulation
- Generating random background traffic without writing route files
When NOT to Use
- Pure SUMO simulation without Simulink (use SUMO's CLI or Python TraCI directly)
- Vehicle dynamics modeling (use Vehicle Dynamics Blockset)
- Scenario design with Driving Scenario Designer (different workflow)
Prerequisites
- Eclipse SUMO installed with
SUMO_HOME environment variable set
- Support package installed: "Automated Driving Toolbox Interface for Eclipse SUMO Traffic Simulator"
- Windows: append trailing separator to
SUMO_HOME — The Server block's launcher concatenates SUMO_HOME + "bin\..." without a separator. If SUMO_HOME lacks a trailing \ or /, every sim() fails with "Failed to launch Eclipse SUMO simulator" even though SUMO is installed correctly. Always run before building/simulating:
if ~endsWith(getenv('SUMO_HOME'), filesep)
setenv('SUMO_HOME', [getenv('SUMO_HOME') filesep]);
end
- Verify:
getenv('SUMO_HOME') must return a valid path ending with a separator.
Preferred Construction Path
When mcp__matlab__model_edit (SATK) is available, prefer it over raw add_block/add_line. It applies auto-layout after every edit, produces clean wiring with no overlap, and lets you reference newly-added blocks by ref within the same call. The patterns below still apply — express them as add_block/connect/configure operations in a single JSON payload. Fall back to add_block/add_line only when SATK is unavailable or when the operation is not expressible in the schema (e.g., setting MATLAB Function Script via sfroot).
Note: When wiring EnablePort on a freshly-created Enabled Subsystem whose default In1/Out1 have been deleted, model_edit cannot resolve the Enable port. Workaround: add_line(model, 'Source/1', 'SubsystemName/Enable', 'autorouting','smart').
Workflow
Idempotent Build Template
modelName = 'sumo_cosim';
workDir = fileparts(mfilename('fullpath'));
% SUMO_HOME workaround (Windows)
sh = getenv('SUMO_HOME');
if ~endsWith(sh, filesep), setenv('SUMO_HOME', [sh filesep]); end
% Idempotent rebuild
if bdIsLoaded(modelName), close_system(modelName, 0); end
slxPath = fullfile(workDir, [modelName '.slx']);
if isfile(slxPath), delete(slxPath); end
new_system(modelName);
open_system(modelName);
set_param(modelName, 'StopTime','100', 'SolverType','Fixed-step', 'FixedStep','0.1');
% ... build model ...
save_system(modelName, slxPath);
1. Prepare SUMO Scenario Files
Ask the user for their existing .sumocfg. Only generate new files if explicitly requested. For quick networks use netgenerate:
sumoHome = getenv('SUMO_HOME');
cmd = sprintf('"%s" --grid --grid.number 2 --grid.length 200 --output-file "%s"', ...
fullfile(sumoHome,'bin','netgenerate'), fullfile(pwd,'network.net.xml'));
system(cmd);
Minimal .sumocfg (route-files optional when using EnableRandomTraffic):
<configuration>
<input><net-file value="network.net.xml"/></input>
<time><begin value="0"/><end value="100"/><step-length value="0.1"/></time>
</configuration>
Route file rules: All <vehicle>, <person>, and <personFlow> entries MUST be sorted by depart time (begin for flows). Out-of-order entries are silently ignored. Routes used by Actor blocks must be standalone <route> elements — not embedded in <flow>.
OpenDRIVE import: Use netconvert --opendrive-files file.xodr -o network.net.xml. Note: SUMO applies a netOffset (visible in .net.xml <location> element) — all SUMO coordinates = OpenDRIVE coordinates + netOffset.
2. Create Server and Client
add_block('SumoInterfaceLibrary/Server', [modelName '/SUMO Server']);
set_param([modelName '/SUMO Server'], 'ServerFile',fullfile(pwd,'cosim.sumocfg'), ...
'ServerPort','8813', 'ServerNumClients','1', 'EnablePacing','on', ...
'PacingRate','1', 'SampleTime','0.1');
add_block('SumoInterfaceLibrary/Client', [modelName '/SUMO Client']);
set_param([modelName '/SUMO Client'], 'ClientAddress','127.0.0.1', ...
'ClientPort','8813', 'ClientOrder','1', 'SampleTime','0.1');
Critical: ClientPort must equal ServerPort. FixedStep must match SUMO step-length. Enable pacing (EnablePacing='on') so the GUI doesn't flash by in 2 seconds.
3–6. Add Readers, Writers, Actors, Save and Run
See Block Reference and Patterns sections below. Always save_system(modelName) before sim(modelName) when the model has been modified programmatically.
Block Reference
Server Block
| Parameter | Purpose | Default |
|---|
ServerFile | Path to .sumocfg | — |
ServerPort | TraCI port | '8813' |
ServerNumClients | Number of clients | '1' |
EnableRandomTraffic | Spawn vehicles without route file | 'off' |
RandomTraffic | Vehicle count when random traffic enabled | '0' |
EnablePacing | Real-time pacing (recommended for demos) | 'off' |
PacingRate | Pacing multiplier | '1' |
SampleTime | Step interval | '-1' |
Note: SUMO default time-to-teleport=300s — any vehicle stuck >300s gets teleported. This can surprise when deliberately stopping an ego at a bus stop.
Client Block
| Parameter | Purpose | Default |
|---|
ClientAddress | Server IP | '127.0.0.1' |
ClientPort | Must match ServerPort | '8813' |
ClientOrder | Client priority | '1' |
Reader Block — Vehicle Topics
All Vehicle-topic Readers AND Writers crash if the target vehicle does not exist. Gate them in Enabled Subsystems when the vehicle spawns after t=0. See Safe Vehicle State Access.
| Topic | Input | Output |
|---|
Speed | Vehicle Id | Speed (scalar) |
Position | Vehicle Id | Position [x, y] |
Position3D | Vehicle Id | Position [x, y, z] |
Acceleration | Vehicle Id | Acceleration |
Angle | Vehicle Id | Heading (degrees, 0=north, CW) |
Vehicle Id inputs must come from a String Constant block — never from another Reader's output.
| Topic | Output |
|---|
ID list | Vehicle ID strings |
ID count | Scalar count |
Arrived list | IDs that reached destination |
Departed list | IDs that entered network |
Writer Block — Vehicle Topics
Writers crash if the target vehicle does not exist — same failure mode as Readers. Gate in Enabled Subsystems for delayed-spawn vehicles.
| Topic | Input Ports | Notes |
|---|
Speed | Vehicle Id, Speed | Sets desired speed; SUMO car-following still brakes for leaders |
Position (RouteFlag off) | Vehicle Id, X, Y, Angle | — |
Position (RouteFlag on) | Vehicle Id, Edge Id, Lane Index, X, Y, Angle, Keep Route | See type table below |
Min gap | Vehicle Id, Gap | — |
Speed mode | Vehicle Id, Mode | — |
Writer(Vehicle/Position, RouteFlag=on) port types — type mismatches silently fail:
| Port | Signal | Type |
|---|
| 1 | Vehicle Id | string |
| 2 | Edge Id | string |
| 3 | Lane Index | int32 |
| 4 | X | double |
| 5 | Y | double |
| 6 | Angle | double (degrees) |
| 7 | Keep Route | uint8 (use uint8(1)) |
Warning: Writer(Vehicle/Position) does no collision-checking. Teleporting near other vehicles causes pile-ups. Place spawned vehicles in a different lane from nearby traffic.
Writer Block — Visualization Topics
Use TopicCategory='Visualization' with GUITopic parameter. Visualization commands are sticky — once SUMO receives Schema/Track/Zoom, it retains the setting. Always wrap in a one-shot Enabled Subsystem. Do NOT leave running every step.
| GUITopic | Input Ports | Valid Values |
|---|
Track Vehicle | View Id, Vehicle Id | — |
Zoom factor | View Id, Zoom | — |
Schema | View Id, Schema | "standard", "real world", "fast standard", "fast real world" |
All Visualization topics require a View Id input on port 1. The default SUMO GUI view name is "View #0" — always use this exact string.
% Track Vehicle example (one-shot): camera follows ego
add_block('SumoInterfaceLibrary/Writer', [subsysPath '/Track Writer'], ...
'TopicCategory','Visualization', 'GUITopic','Track Vehicle', 'SampleTime','0.1');
add_block('simulink/String/String Constant', [subsysPath '/View Id'], ...
'String','"View #0"'); % Default SUMO GUI view — always "View #0"
add_block('simulink/String/String Constant', [subsysPath '/Vehicle Id'], ...
'String','"ego_vehicle"');
add_line(subsysPath, 'View Id/1', 'Track Writer/1');
add_line(subsysPath, 'Vehicle Id/1', 'Track Writer/2');
% Wrap in one-shot Enabled Subsystem (Constant(0)->IC(1)->Enable)
% Schema example (one-shot): switch GUI to "real world"
add_block('SumoInterfaceLibrary/Writer', [modelName '/Set Schema'], ...
'TopicCategory','Visualization', 'GUITopic','Schema', 'SampleTime','0.1');
add_block('simulink/String/String Constant', [modelName '/View Id'], ...
'String','"View #0"'); % Default SUMO GUI view — always "View #0"
add_block('simulink/String/String Constant', [modelName '/Schema Name'], ...
'String','"real world"');
% Wrap in one-shot Enabled Subsystem (Constant(0)->IC(1)->Enable)
Actor Block
| Parameter | Purpose |
|---|
VehicleState | 'Add' or 'Remove' |
SpawnMethod | 'Lane' (use this) or 'Position' (broken — see Known Issues) |
departLane | 'random', 'free', 'allowed', 'first', 'best' |
departSpeed | 'max' (recommended), 'random', 'desired', 'speedLimit' |
enableRouteID | 'on'/'off' |
routeID | Route ID from .rou.xml (requires enableRouteID='on') |
typeID | Vehicle type ID (default: 'DEFAULT_VEHTYPE') |
Always place Actor blocks inside Enabled Subsystems. Set Priority='1' on Spawn subsystem so it executes before Readers (Priority='10').
Patterns
Single-Shot Triggers
| Trigger | Pattern |
|---|
| Spawn at t=0 only | Constant(0) -> IC(Value=1) -> Enabled Subsystem |
| Spawn at simulation time T | Clock -> MATLAB Function (persistent fired) -> Enabled Subsystem |
| Continuous from T onward (e.g., speed writer) | Clock -> Compare To Constant (>=T) -> Enabled Subsystem |
| Spawn when condition true | Clock/sensor -> MATLAB Function (persistent fired) checking condition |
Time-based one-shot (spawn at t=1.2):
% MATLAB Function code:
% function pulse = fcn(t)
% %#codegen
% persistent fired
% if isempty(fired), fired = false; end
% if (t >= 1.2) && ~fired, pulse = 1.0; fired = true;
% else, pulse = 0.0; end
% end
Ego Vehicle Control (Route-File Approach — Recommended)
Define ego in .rou.xml with depart="0" so Readers work from t=0:
<routes>
<vType id="ego_type" accel="2.6" decel="4.5" sigma="0" length="5" maxSpeed="25"/>
<route id="ego_route" edges="B1B2 B2B3 B3B4 B4B1 B1B2 B2B3 B3B4 B4B1"/>
<vehicle id="ego_veh" type="ego_type" route="ego_route" depart="0"
departLane="free" departSpeed="max"/>
</routes>
If Simulink owns longitudinal control via Writer(Vehicle/Speed), do NOT put <stop> clauses in the ego's route — SUMO's stop directive overrides the Speed Writer.
Gated Writer(Vehicle/Speed) for Delayed-Spawn Vehicles
% Latched enable: HIGH from t >= spawnTime onwards
% Clock -> Compare To Constant (relop='>=', const=spawnTime) -> Enable
% Inside Enabled Subsystem:
% String Constant("veh_1") -> Writer(Vehicle/Speed) port 1
% Constant(desiredSpeed) -> Writer(Vehicle/Speed) port 2
Use Compare To Constant >= spawnTime (stays HIGH) rather than one-shot — speed must be continuously asserted while the vehicle is alive.
Distinct Routes Per Vehicle (Dynamic Spawning)
<routes>
<route id="ego_route" edges="A0A1 A1A2 A2B2 B2C2 C2C1 C1C0"/>
<route id="veh_1_route" edges="A1B1 B1B2 B2C2 C2C1 C1C0 C0B0"/>
<route id="veh_2_route" edges="B0B1 B1A1 A1A2 A2B2 B2C2 C2C1"/>
</routes>
% Per Actor block: set_param(actorPath, 'routeID', sprintf('veh_%d_route', k));
Spawn-Then-Teleport (Position Workaround)
Since SpawnMethod='Position' is broken (see Known Issues), use this two-step pattern:
Step N: Actor(Add, SpawnMethod='Lane', routeID=...) -> vehicle at edge start
Step N+1: Writer(Vehicle/Position, RouteFlag=on) with (VehId, EdgeId, int32(LaneIdx),
X, Y, Angle, uint8(1)) -> vehicle teleports to (X,Y)
A one-step delay between Spawn and Move is mandatory — use a Unit Delay on the enable signal. The vehicle does not exist on the SUMO side until one step after the Actor fires.
Periodic Batch Spawn Near Moving Reference
Clock -> PulseGen (persistent, fires every 5s, increments batch id)
-> SpawnBatch (Enabled SS, Priority=1): N IDGen -> N Actor(Add)
-> [Unit Delay z^-1]
-> MoveBatch (Enabled SS, Priority=10): N Writer(Vehicle/Position)
Reader(Vehicle/Position) on ego feeds PosGen so batch lands at egoPos + offset.
MATLAB Function with String Outputs
For dynamically generated vehicle IDs (e.g., "dyn_" + string(batch) + "_1"):
chart = sf.find('-isa','Stateflow.EMChart','Path',chartPath);
chart.ChartUpdate = 'DISCRETE';
chart.SampleTime = '0.1';
outData = chart.find('-isa','Stateflow.Data','Scope','Output');
outData.DataType = 'string';
outData.Props.Array.Size = '1';
Safe Vehicle State Access
All Vehicle-topic Readers AND Writers dereference the Vehicle Id at every step. They crash if the vehicle does not exist. This applies to Reader(Vehicle/...), Writer(Vehicle/...), AND Writer(Visualization/Track Vehicle).
For vehicles spawning after t=0, gate in an Enabled Subsystem:
% Clock -> Compare To Constant (>= spawnTime) -> Enabled Subsystem
% Inside: String Constant + Reader/Writer blocks
For ego defined in .rou.xml with depart="0" — no gating needed.
Random Traffic Quick-Start
set_param([modelName '/SUMO Server'], 'EnableRandomTraffic','on', 'RandomTraffic','10');
No <route-files> needed. Random vehicles are named SpawnedVehicle_1, SpawnedVehicle_2, etc.
Known Issues (Support Package Bugs)
| Issue | Impact | Workaround |
|---|
SUMO_HOME without trailing separator → launch failure (Windows) | P0 blocker | setenv('SUMO_HOME', [getenv('SUMO_HOME') filesep]) |
SpawnMethod='Position' passes wrong RouteFlag in internal moveToXY | Vehicle lands at edge start, not requested position | Use spawn-then-teleport pattern (Actor + Unit Delay + Writer(Vehicle/Position)) |
Common Mistakes
| Mistake | Why It Fails | Fix |
|---|
| Reader/Writer on vehicle before it exists | S-function crash in mdlOutputs | Gate in Enabled Subsystem; use depart="0" for ego |
| Reader output -> Reader input | ID List is variable-length; Vehicle readers need single string | Use String Constant for Vehicle Id |
| ClientPort != ServerPort | Client can't connect | Match both (default '8813') |
| Variable-step solver | SUMO requires lockstep | Fixed-step, match step-length |
| Actor fires repeatedly | "Vehicle already exists" | Enabled Subsystem with one-shot logic |
| Writer(Vehicle/Speed) ungated for delayed-spawn vehicle | Crash at t=0 | Gate with Clock >= spawnTime |
| Visualization Writer running every step | Wastes sim time; crashes if vehicle missing | One-shot Enabled Subsystem |
| Route entries not sorted by depart time | Silently dropped | Sort all entries by depart/begin |
Route embedded in <flow> | Actor can't find it | Define standalone <route> elements |
<stop> in ego route + Speed Writer | SUMO stop overrides Writer | Remove <stop>; use Simulink speed profile |
| Multiple Clock blocks | Clutter; all output same time | Single Clock, fan out to all consumers |
double for Lane Index or Keep Route in Position Writer | Type mismatch rejection | int32(laneIdx), uint8(keepRoute) |
| Teleport into occupied lane | Pile-ups, stuck vehicles | Teleport to different lane from nearby traffic |
Missing save_system before sim | "Unapplied changes" mask error | Always save after programmatic changes |
Ego depart="2" with Reader at t=0 | Garbage/crash from undefined vehicle | Set ego depart="0" |
| Spawning at fractional times with FixedStep=1.0 | Solver never hits target time | Match FixedStep, step-length, and all SampleTime |
Conventions
- Always use Fixed-step solver with step size matching SUMO
step-length and all block SampleTime values
- Always
save_system(modelName) before sim(modelName) after programmatic changes
- Always match
ClientPort to ServerPort
- Use a single Clock at model root — fan out to all comparators/pulse generators
- Prefer defining ego in
.rou.xml with depart="0" over Actor block
- Give ego a long/circular route to prevent mid-simulation removal
- Use
departSpeed='max' as the practical default for vehicles that should land moving
- Writer(Speed) sets desired speed — SUMO handles gap maintenance via car-following
- Enable pacing (
EnablePacing='on') for demos so the GUI doesn't flash by
- Wrap Visualization writers (Track, Schema, Zoom) in one-shot Enabled Subsystems — they are sticky
- Always place Actor blocks inside Enabled/Triggered Subsystems
- Every dynamically spawned vehicle needs a unique ID and a valid standalone route
- Validate
.sumocfg with SUMO CLI before Simulink: system(sprintf('"%s" -c "%s" --end 0.1', sumoExe, cfgFile))
- When using
mcp__matlab__model_edit, fall back to add_line for Enable port wiring
Copyright 2026 The MathWorks, Inc.