关于 matlab-cosimulate-sumo-simulink
面向 Simulink 用户的 SUMO 联合仿真技能,指导使用 Automated Driving Toolbox Interface for Eclipse SUMO 搭建同步交通仿真。内容包括车辆状态读取、车辆控制及动态生成或移除车辆。
主要功能
- 01配置 SUMO Server 与 Client
- 02同步 SUMO 和 Simulink 仿真
- 03读取车辆状态和仿真信息
- 04控制车辆速度与位置
- 05动态添加或移除车辆
适用场景
- 01测试 ADAS 或 AD 控制器
- 02在 Simulink 中读取车辆位置和速度
- 03从控制器操纵 SUMO 中的车辆
- 04无需路线文件生成背景交通
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ matlab/ matlab-agentic-toolkit --skill matlab-cosimulate-sumo-simulink安装后在 Claude Code 中输入/matlab-cosimulate-sumo-simulink确认已加载。
附带文件manifest.yaml
详细介绍
构建 SUMO-Simulink 联合仿真
使用 Automated Driving Toolbox Interface for Eclipse SUMO Traffic Simulator 中的 SumoInterfaceLibrary 模块,创建与 Eclipse SUMO 同步运行的 Simulink 模型。
适用场景
- 构建连接到 Eclipse SUMO 的 Simulink 模型
- 设置用于 ADAS/AD 控制器测试的交通联合仿真
- 将车辆状态(位置、速度)从 SUMO 读入 Simulink
- 使用 Simulink 控制器控制 SUMO 中的自车
- 在仿真期间动态生成或移除车辆
- 无需编写路线文件即可生成随机背景交通
不适用场景
- 纯 SUMO 仿真,不使用 Simulink(直接使用 SUMO 的 CLI 或 Python TraCI)
- 车辆动力学建模(使用 Vehicle Dynamics Blockset)
- 使用 Driving Scenario Designer 设计场景(工作流不同)
前提条件
- 已安装 Eclipse SUMO,并设置了
SUMO_HOME环境变量 - 已安装支持包:“Automated Driving Toolbox Interface for Eclipse SUMO Traffic Simulator”
- Windows:为
SUMO_HOME添加尾随分隔符 — Server 模块的启动器会将SUMO_HOME + "bin\..."拼接起来,中间不添加分隔符。如果SUMO_HOME末尾没有\或/,即使 SUMO 已正确安装,每次sim()也都会失败并显示“Failed to launch Eclipse SUMO simulator”。在构建或仿真前务必运行:
if ~endsWith(getenv('SUMO_HOME'), filesep)
setenv('SUMO_HOME', [getenv('SUMO_HOME') filesep]);
end
- 验证:
getenv('SUMO_HOME')必须返回以分隔符结尾的有效路径。
首选构建方式
如果可以使用 mcp__matlab__model_edit(SATK),优先使用它,而不是直接使用 add_block/add_line。它会在每次编辑后自动布局,生成整洁且无重叠的连线,并允许在同一次调用中通过 ref 引用新添加的模块。以下模式仍然适用——在单个 JSON 负载中将其表示为 add_block/connect/configure 操作。仅当 SATK 不可用,或操作无法用架构表示时(例如通过 sfroot 设置 MATLAB Function 的 Script),才使用 add_block/add_line。
**注意:**为新创建的 Enabled Subsystem 的 EnablePort 接线时,如果其默认的 In1/Out1 已被删除,model_edit 无法解析 Enable 端口。解决方法: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);
1. 准备 SUMO 场景文件
请用户提供现有的 .sumocfg。只有在用户明确要求时才生成新文件。对于快速构建的网络,使用 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);
最简 .sumocfg(使用 EnableRandomTraffic 时可省略路由文件):
<configuration>
<input><net-file value="network.net.xml"/></input>
<time><begin value="0"/><end value="100"/><step-length value="0.1"/></time>
</configuration>
**路由文件规则:**所有 <vehicle>、<person> 和 <personFlow> 条目都必须按 depart 时间排序(流使用 begin)。顺序错误的条目会被静默忽略。Actor 模块使用的路线必须是独立的 <route> 元素,不能嵌入 <flow> 中。
**导入 OpenDRIVE:**使用 netconvert --opendrive-files file.xodr -o network.net.xml。注意:SUMO 会应用 netOffset(可在 .net.xml 的 <location> 元素中查看)——所有 SUMO 坐标 = OpenDRIVE 坐标 + netOffset。
2. 创建 Server 和 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');
重要:ClientPort 必须等于 ServerPort。FixedStep 必须与 SUMO 的 step-length 匹配。启用 pacing(EnablePacing='on'),避免 GUI 在 2 秒内快速闪过。
3–6. 添加读取器、写入器、Actor,保存并运行
请参阅下文的模块参考和模式部分。如果模型已通过编程方式修改,务必先执行 save_system(modelName),再执行 sim(modelName)。
模块参考
服务端模块
| 参数 | 用途 | 默认值 |
|---|---|---|
ServerFile | .sumocfg 的路径 | — |
ServerPort | TraCI 端口 | '8813' |
ServerNumClients | 客户端数量 | '1' |
EnableRandomTraffic | 不使用路线文件生成车辆 | 'off' |
RandomTraffic | 启用随机交通时的车辆数量 | '0' |
EnablePacing | 实时步进(演示时推荐) | 'off' |
PacingRate | 步进倍率 | '1' |
SampleTime | 步长间隔 | '-1' |
注意: SUMO 默认的 time-to-teleport=300s——任何停滞超过 300s 的车辆都会被传送。这可能会导致一个意外情况:有意让自车停在公交站时,车辆会被传送。
客户端模块
| 参数 | 用途 | 默认值 |
|---|---|---|
ClientAddress | 服务端 IP | '127.0.0.1' |
ClientPort | 必须与 ServerPort 匹配 | '8813' |
ClientOrder | 客户端优先级 | '1' |
Reader 模块——车辆话题
如果目标车辆不存在,所有车辆话题的 Reader 和 Writer 都会崩溃。 如果车辆在 t=0 之后才生成,请在 Enabled Subsystems 中控制它们的启用时机。请参阅 Safe Vehicle State Access。
| 话题 | 输入 | 输出 |
|---|---|---|
Speed | 车辆 ID | 速度(标量) |
Position | 车辆 ID | 位置 [x, y] |
Position3D | 车辆 ID | 位置 [x, y, z] |
Acceleration | 车辆 ID | 加速度 |
Angle | 车辆 ID | 朝向(度,0=北,顺时针) |
车辆 ID 输入必须来自 String Constant 模块——绝不能使用另一个 Reader 的输出。
Reader 模块——仿真话题(无输入端口)
| 话题 | 输出 |
|---|---|
ID list | 车辆 ID 字符串 |
ID count | 数量(标量) |
Arrived list | 已到达目的地的 ID |
Departed list | 已进入路网的 ID |
Writer 模块——车辆话题
如果目标车辆不存在,Writer 就会崩溃——与 Reader 的故障模式相同。对于延迟生成的车辆,请在 Enabled Subsystems 中控制其启用时机。
| 话题 | 输入端口 | 备注 |
|---|---|---|
Speed | 车辆 ID、速度 | 设置期望速度;SUMO 的跟驰模型仍会因前车而制动 |
Position(RouteFlag off) | 车辆 ID、X、Y、Angle | — |
Position(RouteFlag on) | 车辆 ID、Edge ID、Lane Index、X、Y、Angle、Keep Route | 请参阅下方的类型表 |
Min gap | 车辆 ID、间距 | — |
Speed mode | 车辆 ID、模式 | — |
Writer(Vehicle/Position, RouteFlag=on) 端口类型——类型不匹配会静默失败:
| 端口 | 信号 | 类型 |
|---|---|---|
| 1 | 车辆 ID | string |
| 2 | Edge ID | string |
| 3 | Lane Index | int32 |
| 4 | X | double |
| 5 | Y | double |
| 6 | Angle | double(度) |
| 7 | Keep Route | uint8(使用 uint8(1)) |
警告: Writer(Vehicle/Position) 不会进行碰撞检查。将车辆传送到其他车辆附近会造成车辆堆积。生成车辆时,请将其放在与附近交通不同的车道上。
Writer 模块——可视化话题
使用 TopicCategory='Visualization' 和 GUITopic 参数。可视化命令会持续生效——SUMO 收到 Schema/Track/Zoom 后,会保留该设置。务必将其封装在一次性 Enabled Subsystem 中。不要让它每个步长都运行。
| GUITopic | 输入端口 | 有效值 |
|---|---|---|
Track Vehicle | View Id、Vehicle Id | — |
Zoom factor | View Id、Zoom | — |
Schema | View Id、Schema | "standard"、"real world"、"fast standard"、"fast real world" |
所有 Visualization 话题都要求在端口 1 输入 View Id。SUMO GUI 的默认视图名称是 "View #0"——务必使用此确切字符串。
% 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 模块
| 参数 | 用途 |
|---|---|
VehicleState | 'Add' 或 'Remove' |
SpawnMethod | 'Lane'(使用此项)或 'Position'(存在缺陷——参见已知问题) |
departLane | 'random'、'free'、'allowed'、'first'、'best' |
departSpeed | 'max'(推荐)、'random'、'desired'、'speedLimit' |
enableRouteID | 'on'/'off' |
routeID | .rou.xml 中的路线 ID(需要 enableRouteID='on') |
typeID | 车辆类型 ID(默认:'DEFAULT_VEHTYPE') |
始终将 Actor 模块放在已启用子系统内。将 Spawn 子系统的 Priority='1',使其先于 Readers(Priority='10')执行。
模式
单次触发器
| 触发条件 | 模式 |
|---|---|
| 仅在 t=0 生成 | Constant(0) -> IC(Value=1) -> Enabled Subsystem |
| 在仿真时间 T 生成 | Clock -> MATLAB Function (persistent fired) -> Enabled Subsystem |
| 从 T 开始持续执行(例如速度写入器) | Clock -> Compare To Constant (>=T) -> Enabled Subsystem |
| 条件为真时生成 | Clock/传感器 -> MATLAB Function (persistent fired) 检查条件 |
基于时间的单次触发(在 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
自车控制(路线文件方案——推荐)
在 .rou.xml 中使用 depart="0" 定义自车,以便 Readers 从 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>
如果 Simulink 通过 Writer(Vehicle/Speed) 控制纵向运动,请勿在自车的路线中加入 <stop> 子句——SUMO 的停止指令会覆盖 Speed Writer。
延迟生成车辆的门控 Writer(Vehicle/Speed)
% 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
请使用 Compare To Constant >= spawnTime(保持为 HIGH),而不是单次触发——车辆存在期间必须持续设置速度。
每辆车使用独立路线(动态生成)
<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));
先生成后瞬移(Position 变通方案)
由于 SpawnMethod='Position' 存在缺陷(参见已知问题),请使用以下两步模式:
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)
Spawn 与 Move 之间必须延迟一个时间步——在 enable 信号上使用 Unit Delay。Actor 触发后,车辆要到下一个时间步才会在 SUMO 端创建。
在移动参考点附近定期批量生成
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
用于动态生成车辆 ID(例如 "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';
安全访问车辆状态
所有 Vehicle 主题的 Readers 和 Writers 都会在每个时间步解引用 Vehicle Id。车辆不存在时,它们会崩溃。此规则适用于 Reader(Vehicle/...)、Writer(Vehicle/...) 以及 Writer(Visualization/Track Vehicle)。
对于在 t=0 后生成的车辆,请在已启用子系统中进行门控:
% Clock -> Compare To Constant (>= spawnTime) -> Enabled Subsystem
% Inside: String Constant + Reader/Writer blocks
对于在 .rou.xml 中使用 depart="0" 定义的自车——无需门控。
随机交通快速入门
set_param([modelName '/SUMO Server'], 'EnableRandomTraffic','on', 'RandomTraffic','10');
无需 <route-files>。随机车辆的名称为 SpawnedVehicle_1、SpawnedVehicle_2 等。
已知问题(支持包缺陷)
| 问题 | 影响 | 变通方案 |
|---|---|---|
SUMO_HOME 末尾未带分隔符 → 启动失败(Windows) | P0 阻塞问题 | setenv('SUMO_HOME', [getenv('SUMO_HOME') filesep]) |
SpawnMethod='Position' 在内部 moveToXY 中传入错误的 RouteFlag | 车辆会出现在边缘起点,而非请求的位置 | 使用先生成后瞬移模式(Actor + Unit Delay + Writer(Vehicle/Position)) |
常见错误
| 错误 | 失败原因 | 修复方法 |
|---|---|---|
| 车辆尚不存在时就使用 Reader/Writer | mdlOutputs 中的 S-function 崩溃 | 放入 Enabled Subsystem 中进行门控;对于自车,使用 depart="0" |
| Reader 输出 -> Reader 输入 | ID List 是可变长度的;Vehicle reader 需要单个字符串 | 使用 String Constant 设置 Vehicle Id |
| ClientPort != ServerPort | 客户端无法连接 | 将两者设为相同值(默认值为 '8813') |
| 使用变步长求解器 | SUMO 要求锁步运行 | 使用固定步长,并与 step-length 保持一致 |
| Actor 重复触发 | “Vehicle already exists” | 使用带单次触发逻辑的 Enabled Subsystem |
| 延迟生成车辆的 Writer(Vehicle/Speed) 未进行门控 | 在 t=0 时崩溃 | 使用 Clock >= spawnTime 进行门控 |
| Visualization Writer 每个步长都运行 | 浪费仿真时间;车辆不存在时会崩溃 | 使用单次触发的 Enabled Subsystem |
| Route 条目未按 depart 时间排序 | 条目会被静默丢弃 | 按 depart/begin 对所有条目排序 |
Route 嵌入在 <flow> 中 | Actor 无法找到它 | 定义独立的 <route> 元素 |
自车路线中包含 <stop>,且使用了 Speed Writer | SUMO 的 stop 会覆盖 Writer | 移除 <stop>;使用 Simulink 速度曲线 |
| 存在多个 Clock 块 | 结构杂乱;所有块的输出时间都相同 | 使用单个 Clock,并将其分配给所有使用方 |
Lane Index 或 Keep Route 在 Position Writer 中使用 double | 类型不匹配,导致拒绝 | 使用 int32(laneIdx)、uint8(keepRoute) |
| 传送到被占用的车道 | 车辆堆积、卡住 | 传送到与附近车辆不同的车道 |
在 sim 前缺少 save_system | 出现“未应用更改”掩码错误 | 以编程方式更改后,始终保存 |
自车设置 depart="2",但 Reader 在 t=0 读取 | 车辆未定义,导致数据无效或崩溃 | 将自车的 depart 设为 "0" |
| FixedStep=1.0 时在非整数时间生成车辆 | 求解器不会到达目标时间 | 使 FixedStep、step-length 和所有 SampleTime 保持一致 |
约定
- 始终使用固定步长求解器,步长与 SUMO 的
step-length及所有块的SampleTime值保持一致 - 以编程方式更改模型后,在运行
sim(modelName)前始终调用save_system(modelName) - 始终确保
ClientPort与ServerPort匹配 - 在模型根级别使用单个 Clock,并将其分配给所有比较器/脉冲发生器
- 优先在
.rou.xml中使用depart="0"定义自车,而不是使用 Actor 块 - 为自车设置较长或环形路线,避免其在仿真过程中途被移除
- 对于应以行驶状态生成的车辆,使用
departSpeed='max'作为实用默认值 - Writer(Speed) 设置期望速度;SUMO 通过跟车行为处理间距维持
- 对演示启用 pacing(
EnablePacing='on'),避免 GUI 一闪而过 - 将 Visualization writers(Track、Schema、Zoom)放入单次触发的 Enabled Subsystems 中——它们具有粘滞性
- 始终将 Actor 块放在 Enabled/Triggered Subsystems 内
- 每辆动态生成的车辆都需要唯一 ID 和有效的独立路线
- 在 Simulink 中使用前,先通过 SUMO CLI 验证
.sumocfg:system(sprintf('"%s" -c "%s" --end 0.1', sumoExe, cfgFile)) - 使用
mcp__matlab__model_edit时,若 Enable 端口接线失败,则改用add_line
版权所有 2026 The MathWorks, Inc.