3、Python与SUMO交互:TraCI接口介绍、Python连接SUMO、控制仿真步进
说实话,很多做自动驾驶仿真的朋友,一开始都卡在「怎么让Python和SUMO说话」这一步。SUMO本身是个独立的交通仿真引擎,它跑它的,你写你的Python脚本,两者怎么打通?
答案就是TraCI——Traffic Control Interface。说白了,这就是SUMO留给外界的「API窗口」。我最早接触TraCI的时候,觉得它像个遥控器:你按一下「开始」,SUMO就跑一步;你按一下「暂停」,它就停下来等你发指令。
3.1 TraCI是什么?
TraCI全称Traffic Control Interface,是基于TCP的客户端-服务器协议。SUMO作为服务器,Python脚本作为客户端。你通过TraCI发送命令,SUMO执行并返回结果。
我个人习惯把TraCI理解成「桥梁」。这座桥梁两端,一端是SUMO的仿真世界,另一端是你的Python逻辑。没有它,你只能看着SUMO自己跑,没法干预。
核心特点:
- 基于TCP通信,端口默认是8813
- 支持读取仿真状态(车辆位置、速度、信号灯状态等)
- 支持控制仿真对象(改变车辆路线、切换信号灯、插入车辆等)
- 每个仿真步进都可以交互
嗯,这里要注意:TraCI不是一次性把所有数据推给你的。它是「你问一句,它答一句」的模式。你想想看,如果你要实时获取1000辆车的状态,就得循环发1000次请求。所以性能优化是个坑,我后面会讲。
3.2 Python连接SUMO的两种方式
我在项目中遇到过两种连接场景,一种是「启动SUMO并自动连接」,另一种是「连接已经运行的SUMO」。两种方式各有用途。
方式一:通过Python启动SUMO并连接
这是最常用的方式。你写一个Python脚本,让它帮你启动SUMO,然后自动连上。
import traci
import sumolib
# SUMO的二进制路径
sumo_binary = "sumo-gui" # 或者用"sumo"(无界面)
# 配置文件路径
sumo_cfg = "my_scenario.sumocfg"
# 启动SUMO并连接
traci.start([sumo_binary, "-c", sumo_cfg, "--step-length", "0.1"])
print("SUMO已启动,TraCI连接成功!")
# 开始仿真步进
for step in range(100):
traci.simulationStep()
# 这里可以获取数据或控制对象
# 关闭连接
traci.close()
这段代码我用了无数次。注意--step-length参数,它控制仿真步长。0.1秒一步,意味着1秒仿真时间对应10个步进。我建议新手先用0.1秒,别太细,否则调试时眼睛跟不上。
方式二:连接已运行的SUMO
有时候你已经在命令行里手动启动了SUMO,或者SUMO是别人启动的。这时候可以用traci.init()来连接。
import traci
# 假设SUMO已经在端口8813上运行
traci.init(port=8813)
print("已连接到运行中的SUMO实例")
# 控制仿真步进
while traci.simulation.getMinExpectedNumber() > 0:
traci.simulationStep()
# 你的逻辑...
traci.close()
这种方式我一般在调试阶段用。先手动启动SUMO-GUI,看着界面调参数,然后另开一个终端跑Python脚本。这样能直观看到每一步的效果。
避坑指南:我曾经犯过一个低级错误——忘记关闭TraCI连接。如果你在脚本里开了TraCI但没调用traci.close(),SUMO进程会一直挂着,下次启动时会报端口被占用。解决办法:要么手动杀进程,要么在脚本里用try...finally保证关闭。
3.3 控制仿真步进
仿真步进,说白了就是「让仿真往前走一小步」。TraCI里用traci.simulationStep()来实现。但这里有个细节:你可以控制步进的时间长度。
基本步进控制
import traci
traci.start(["sumo", "-c", "demo.sumocfg"])
# 单步前进,默认步长由sumocfg决定
traci.simulationStep()
# 前进到指定时间点(单位:秒)
traci.simulationStep(10.0) # 直接跳到第10秒
# 连续步进
for t in range(0, 100):
traci.simulationStep(t * 0.1) # 每步0.1秒
# 获取当前仿真时间
current_time = traci.simulation.getTime()
print(f"当前仿真时间:{current_time}秒")
你想想看,为什么要有simulationStep(10.0)这种跳转?我在做大规模场景测试时,经常需要跳过前面的「预热阶段」。比如前100秒都是车辆初始化,我不想看,直接跳到第100秒开始采集数据。这样能节省大量仿真时间。
获取仿真状态
步进过程中,你肯定想知道当前仿真跑成什么样了。TraCI提供了一堆查询接口:
| 功能 | 代码示例 | 说明 |
|---|---|---|
| 获取当前时间 | traci.simulation.getTime() |
返回当前仿真时间(秒) |
| 获取车辆总数 | traci.simulation.getMinExpectedNumber() |
返回还在仿真中的车辆数 |
| 获取所有车辆ID | traci.vehicle.getIDList() |
返回当前所有车辆的ID列表 |
| 获取车辆位置 | traci.vehicle.getPosition(veh_id) |
返回(x, y)坐标 |
| 获取车辆速度 | traci.vehicle.getSpeed(veh_id) |
返回速度(m/s) |
我的小技巧:每次步进后,我习惯先调用traci.simulation.getMinExpectedNumber()检查是否还有车辆。如果返回0,说明仿真结束了,可以跳出循环。否则你可能会陷入死循环,一直步进到天荒地老。
3.4 一个完整的交互示例
把上面这些串起来,就是一个完整的TraCI交互流程。我写个简单例子:启动仿真,每步打印当前时间和车辆数,跑完自动关闭。
import traci
# 启动SUMO
traci.start(["sumo-gui", "-c", "demo.sumocfg", "--start"])
step = 0
max_steps = 500
while step < max_steps:
# 执行一步仿真
traci.simulationStep()
# 获取当前时间
sim_time = traci.simulation.getTime()
# 获取当前所有车辆
vehicles = traci.vehicle.getIDList()
print(f"步进{step}: 时间={sim_time:.1f}s, 车辆数={len(vehicles)}")
# 如果没车了,提前结束
if traci.simulation.getMinExpectedNumber() == 0:
print("所有车辆已离开仿真区域")
break
step += 1
# 关闭连接
traci.close()
print("仿真结束")
这段代码跑起来,你会看到控制台每0.1秒打印一行。嗯,这就是最基础的「Python控制SUMO」流程。你可以在while循环里加任何逻辑——改变车辆路线、触发事故、切换信号灯,都行。
3.5 知识体系总览
为了让你更直观地理解本章的知识结构,我画了一张流程图:
这张图把整个流程串起来了。你从左上角开始,先启动SUMO,再建立连接,然后进入步进循环——循环里你可以做三件事:获取状态、控制对象、执行步进。最后别忘了关闭连接。
我个人觉得,TraCI最强大的地方不在于它提供了多少接口,而在于它让你能在「每个仿真步进」里做决策。这意味着你可以写一个「智能体」,根据当前交通状况实时调整策略。这才是复杂交通场景仿真的精髓。
本章要点回顾:
- TraCI是Python与SUMO交互的桥梁,基于TCP协议
- 两种连接方式:
traci.start()启动并连接,traci.init()连接已有实例 - 仿真步进用
traci.simulationStep(),可以指定时间点跳转 - 步进循环中可查询车辆、信号灯、仿真时间等状态
- 务必在结束时调用
traci.close()释放资源
好了,这一章就到这里。你先把TraCI的基本用法跑通,后面章节我们会用它做更复杂的事情——比如动态插入车辆、实时切换信号灯方案、甚至模拟突发事故。路要一步一步走,先把步进控制练熟。