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在整个仿真系统中的位置:

TraCI 在 SUMO 仿真中的角色 用户 Python 脚本 traci.start() traci.simulationStep() traci.vehicle.*() TraCI 接口层 TCP 通信协议 命令编码/解码 数据序列化 SUMO 仿真内核 车辆动力学 路网逻辑 交通流计算 命令 执行 ← 结果 → ← 状态 → 典型数据流 ① Python 脚本调用 traci.vehicle.getSpeed(vehID) ② TraCI 将命令编码为字节流,通过 TCP 发送给 SUMO ③ SUMO 解析命令,查询车辆速度,返回结果 ④ TraCI 解码结果,Python 脚本拿到速度值(如 13.5 m/s) 图:TraCI 作为中间层,连接用户脚本与 SUMO 仿真内核

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,到时候会讲怎么让多辆车协同工作——那才是真正有意思的地方。

可可精品资料库