matlab/matlab-cosimulate-sumo-simulink
> Build Simulink models that co-simulate with Eclipse SUMO traffic simulator. Use when creating SUMO-Simulink co-simulation, traffic simulation, TraCI connection, vehicle-in-the-loop testing, or ADAS scenario validation with SUMO. Covers Server/Client setup, Reader/Writer/Actor block configuration, random traffic generation, ego vehicle control, and SUMO file creation. Also use when the user mentions SumoInterfaceLibrary, .sumocfg files, or wants to connect Simulink to an external traffic simulator.
npx skills add https://github.com/matlab/matlab-agentic-toolkit --skill matlab-cosimulate-sumo-simulink
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.
SUMO_HOME environment variable setSUMO_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
getenv('SUMO_HOME') must return a valid path ending with a separator.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').
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);
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.
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.
See Block Reference and Patterns sections below. Always save_system(modelName) before sim(modelName) when the model has been modified programmatically.
| 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.
| Parameter | Purpose | Default |
|-----------|---------|---------|
| ClientAddress | Server IP | '127.0.0.1' |
| ClientPort | Must match ServerPort | '8813' |
| ClientOrder | Client priority | '1' |
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 |
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.
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)
| 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').
| 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
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.
% 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.
<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));
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.
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.
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';
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.
set_param([modelName '/SUMO Server'], 'EnableRandomTraffic','on', 'RandomTraffic','10');
No <route-files> needed. Random vehicles are named SpawnedVehicle_1, SpawnedVehicle_2, etc.
| 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)) |
| 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 |
step-length and all block SampleTime valuessave_system(modelName) before sim(modelName) after programmatic changesClientPort to ServerPort.rou.xml with depart="0" over Actor blockdepartSpeed='max' as the practical default for vehicles that should land movingEnablePacing='on') for demos so the GUI doesn't flash by.sumocfg with SUMO CLI before Simulink: system(sprintf('"%s" -c "%s" --end 0.1', sumoExe, cfgFile))mcp__matlab__model_edit, fall back to add_line for Enable port wiring----
Copyright 2026 The MathWorks, Inc.
----
Take matlab/matlab-cosimulate-sumo-simulink from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.