4. TraCI基础
各位同学好,我是老张。做了这么多年自动驾驶仿真,我越来越觉得TraCI是个好东西。说白了,TraCI就是SUMO留给我们的一个后门——通过它,我们可以用Python实时控制仿真里的每一辆车、每一个红绿灯。
这一章,我们来聊聊TraCI到底是什么,怎么用Python连上SUMO,怎么控制仿真一步步往前走,以及怎么获取和设置车辆的信息。嗯,这些都是基本功,但也是后面所有高级玩法的基石。
1.1 TraCI是什么?
TraCI的全称是Traffic Control Interface,翻译过来就是「交通控制接口」。它基于TCP协议,让外部程序(比如我们的Python脚本)可以跟SUMO仿真内核实时通信。
我刚开始接触TraCI时,总觉得它像个黑盒子。后来用多了才发现,它的设计思路其实很简单:
- 客户端-服务器模式:SUMO是服务器,Python脚本是客户端
- 请求-响应机制:你发一个命令,SUMO执行并返回结果
- 步进控制:仿真是一步一步走的,每一步你都可以干预
你可以把TraCI想象成SUMO的遥控器。没有它,SUMO只能按预设的路线跑;有了它,你就能在仿真运行时动态改变车辆的行为——比如让某辆车突然变道、减速,或者改变目的地。
核心要点:TraCI不是SUMO的一部分,它是SUMO对外开放的API。你写的Python代码通过TraCI控制SUMO,就像你用方向盘控制汽车一样。
下面这张图展示了TraCI在整个仿真系统中的位置:
1.2 Python连接SUMO
连接SUMO其实就两步:启动SUMO,然后让Python连上去。我习惯用traci.start()来搞定。
先看一个最简单的例子:
import traci
# 启动SUMO仿真
traci.start(["sumo-gui", "-c", "my_simulation.sumocfg"])
# 这里可以写你的控制逻辑...
# 关闭连接
traci.close()
这段代码做了三件事:
- 导入traci库
- 用sumo-gui启动仿真(带界面,方便观察)
- 最后关闭连接
小技巧:如果你不想看界面,只想跑数据,把sumo-gui换成sumo就行。我平时调试用gui,批量跑实验用sumo,省内存。
这里有个坑,我曾经踩过:一定要确保SUMO的bin目录在系统PATH里,否则Python找不到sumo-gui命令。如果你遇到FileNotFoundError,八成是这个原因。
1.3 TraCI控制仿真步进
仿真是一步一步走的。每一步,SUMO会计算所有车辆的位置、速度、加速度,然后更新状态。我们用traci.simulationStep()来控制这个节奏。
看代码:
import traci
traci.start(["sumo-gui", "-c", "my_simulation.sumocfg"])
# 跑100步仿真
for step in range(100):
traci.simulationStep() # 前进一个步长(默认1秒)
print(f"当前仿真时间: {traci.simulation.getTime()} 秒")
traci.close()
每次调用simulationStep(),仿真就前进一个时间步。默认步长是1秒,你也可以改成0.1秒:
traci.simulationStep(0.1) # 前进0.1秒
我个人习惯在自动驾驶场景中用0.1秒步长,这样控制更精细。但要注意,步长越小,计算量越大,仿真跑得越慢。
注意:不要在循环里忘记调用simulationStep()。我见过有人写了个死循环,只发命令不步进,结果SUMO卡死在那里。嗯,血的教训。
1.4 获取车辆信息
TraCI提供了丰富的API来获取车辆状态。常用的有这些:
| API | 功能 | 返回值 |
|---|---|---|
traci.vehicle.getSpeed(vehID) |
获取速度 | m/s(浮点数) |
traci.vehicle.getPosition(vehID) |
获取位置 | (x, y) 元组 |
traci.vehicle.getAngle(vehID) |
获取朝向角 | 度(0-360) |
traci.vehicle.getLaneID(vehID) |
获取当前车道 | 字符串 |
traci.vehicle.getRoadID(vehID) |
获取当前道路 | 字符串 |
traci.vehicle.getAcceleration(vehID) |
获取加速度 | m/s² |
来个完整的例子:
import traci
traci.start(["sumo-gui", "-c", "my_simulation.sumocfg"])
for step in range(50):
traci.simulationStep()
# 获取所有车辆ID
vehicle_ids = traci.vehicle.getIDList()
for veh_id in vehicle_ids:
speed = traci.vehicle.getSpeed(veh_id)
pos = traci.vehicle.getPosition(veh_id)
lane = traci.vehicle.getLaneID(veh_id)
print(f"车辆 {veh_id}: 速度={speed:.2f} m/s, "
f"位置=({pos[0]:.1f}, {pos[1]:.1f}), "
f"车道={lane}")
traci.close()
这里有个细节:getIDList()返回当前仿真中所有车辆的ID。如果仿真刚开始,车辆还没生成,返回的就是空列表。所以最好先跑几步再查。
1.5 设置车辆状态
光获取信息还不够,我们还要能控制车辆。TraCI允许你修改车辆的各种属性:
| API | 功能 | 示例 |
|---|---|---|
traci.vehicle.setSpeed(vehID, speed) |
设置速度 | 设为0表示停车 |
traci.vehicle.changeLane(vehID, laneIndex, duration) |
变道 | 持续变道到目标车道 |
traci.vehicle.setColor(vehID, color) |
设置颜色 | (R,G,B,A) 元组 |
traci.vehicle.setRouteID(vehID, routeID) |
切换路线 | 改变车辆行驶路径 |
traci.vehicle.moveTo(vehID, laneID, pos) |
瞬移 | 直接放到指定位置 |
来看一个实际场景:让车辆在遇到前方障碍时减速停车。
import traci
traci.start(["sumo-gui", "-c", "my_simulation.sumocfg"])
# 假设我们要控制车辆 "veh0"
target_veh = "veh0"
stop_position = 100.0 # 障碍物在100米处
for step in range(100):
traci.simulationStep()
# 获取车辆位置
pos = traci.vehicle.getPosition(target_veh)
current_x = pos[0] # 假设沿x轴行驶
# 如果距离障碍物小于20米,减速停车
distance_to_obstacle = stop_position - current_x
if distance_to_obstacle < 20.0 and distance_to_obstacle > 0:
# 速度随距离线性减小
new_speed = max(0, distance_to_obstacle / 20.0 * 10.0)
traci.vehicle.setSpeed(target_veh, new_speed)
print(f"减速中... 当前速度: {new_speed:.2f} m/s")
elif distance_to_obstacle <= 0:
traci.vehicle.setSpeed(target_veh, 0)
print("已停车")
traci.close()
关键点:setSpeed设置的是目标速度,不是加速度。SUMO的跟驰模型会自动计算加减速过程。如果你想完全接管车辆控制,可以用traci.vehicle.setSpeedMode(vehID, 0)关闭SUMO的速度控制,然后自己控制。
1.6 避坑指南
最后分享几个我踩过的坑:
- 车辆ID不存在:调用API前先检查车辆是否存在,用
traci.vehicle.getIDList()确认一下。我曾经直接写死ID,结果换了路网就报错。 - 步进顺序:先
simulationStep()再获取信息。如果你先获取再步进,拿到的还是上一帧的数据。 - 连接超时:如果仿真跑得慢,TraCI连接可能超时。可以适当调大超时时间:
traci.start([...], numRetries=10)。 - 不要忘记close():脚本异常退出时,SUMO进程可能还活着。我习惯用
try...finally确保关闭:
try:
traci.start([...])
# 你的逻辑
finally:
traci.close()
好了,这一章的内容就这些。TraCI的基础操作其实不难,多写几遍就熟了。下一章我们会深入TraCI的车辆控制API,到时候会讲怎么让多辆车协同工作——那才是真正有意思的地方。
可可精品资料库