r_nsdのブログ

r_nsdのブログ

勉強したこと・調べたこと・思ったことを残しておくためのブログ

MENU

SUMOチュートリアル「TraCI4Traffic Lights」

オンラインでシミュレーションの値の取得や状態の変化を行うことができる機能「TraCI (Traffic Control Interface)」を使ったチュートリアルを行ったので記録しておきます.

TraCIは,TCPで制御する側(クライアントと言ったりコントローラと言ったりする)とSUMO間のメッセージを行います.

このチュートリアルでは制御する側はPythonスクリプトで書き,シミュレーションの値を取得したり,指示を送ったりします.

C++, Python, Javaにはデータからメッセージを作成するクラスが用意されています.

実際のコードは, <SUMO_HOME>/docs/tutorial/traci_tls もしくは,<SUMO_HOME>/tests/complex/tutorial/traci_tls にあります.(自分のSUMOのバージョン0.32.0では前者の方でした.)

やること

北から南へ電車が,東西を車が通行する状況において,「電車が通行するときは,東西の信号を赤にし,車の通行を止める」といった動作ができるようにします.

f:id:ryonsd:20190822100108p:plain

python runner.py

で,シミュレーションを実行することができます.

解説

runner.py のコードを見ていきます.

内容としては,

  • TraCIのインポート
  • ルートファイル(車両の発生,経路の設定が記述されている)の生成
  • TraCIによる通信(信号の制御)方法の定義
  • シミュレーションの設定,実行

となります.

TraCIのインポート

Python用に用意されているTraCIのライブラリへのパスの設定,インポートを行います.

from __future__ import absolute_import
from __future__ import print_function

import os
import sys
import optparse
import subprocess
import random

# Python用に用意されているTraCIのライブラリへのパスを設定する
try:
    sys.path.append(os.path.join(os.path.dirname(
        __file__), '..', '..', '..', '..', "tools"))  # tutorial in tests
    sys.path.append(os.path.join(os.environ.get("SUMO_HOME", os.path.join(
        os.path.dirname(__file__), "..", "..", "..")), "tools"))  # tutorial in docs
    from sumolib import checkBinary  # noqa
except ImportError:
    sys.exit(
        "please declare environment variable 'SUMO_HOME' as the root directory of your sumo installation (it should contain folders 'bin', 'tools' and 'docs')")

# TraCIの機能を使うためにインポート
import traci

ルートファイルrou.xmlの作成

SUMOは,道路ネットワークファイルのnet.xml と車両の経路を設定したルートファイルrou.xml を用いてシミュレーションを行います.net.xml は事前に作成されているので,rou.xml を作成します.

def generate_routefile():
    random.seed(42)  # make tests reproducible
    N = 3600  # number of time steps
    # demand per second from different directions
    pWE = 1. / 10
    pEW = 1. / 11
    pNS = 1. / 30 #30秒ごとに電車が発生する
    # xml形式での記述
    with open("data/cross.rou.xml", "w") as routes:
        print("""<routes>
        <vType id="typeWE" accel="0.8" decel="4.5" sigma="0.5" length="5" minGap="2.5" maxSpeed="16.67" guiShape="passenger"/>
        <vType id="typeNS" accel="0.8" decel="4.5" sigma="0.5" length="7" minGap="3" maxSpeed="25" guiShape="bus"/>

        <route id="right" edges="51o 1i 2o 52i" />
        <route id="left" edges="52o 2i 1o 51i" />
        <route id="down" edges="54o 4i 3o 53i" />""", file=routes)
        lastVeh = 0
        vehNr = 0
        for i in range(N):
            if random.uniform(0, 1) < pWE:
                print('    <vehicle id="right_%i" type="typeWE" route="right" depart="%i" />' % (
                    vehNr, i), file=routes)
                vehNr += 1
                lastVeh = i
            if random.uniform(0, 1) < pEW:
                print('    <vehicle id="left_%i" type="typeWE" route="left" depart="%i" />' % (
                    vehNr, i), file=routes)
                vehNr += 1
                lastVeh = i
            if random.uniform(0, 1) < pNS:
                print('    <vehicle id="down_%i" type="typeNS" route="down" depart="%i" color="1,0,0"/>' % (
                    vehNr, i), file=routes)
                vehNr += 1
                lastVeh = i
        print("</routes>", file=routes)

TraCIによる通信の定義

シミュレーションのステップ毎にTraCIによりSUMOと通信を行い,車両の位置情報の受信,信号の制御命令の送信を行います.

net.xmlの中で次のように信号の状態を定義しています.この信号はID"0"が割り振られいます.Gが緑,rが赤,yが黄色で,信号の色を表しており,左から順に北東南西の信号です.

<tlLogic id="0" type="static" programID="0" offset="0">
    <phase duration="31" state="GrGr"/> <!-- index=0 -->
    <phase duration="6" state="yryr"/> <!-- index=1 -->
    <phase duration="31" state="rGrG"/> <!-- index=2 -->
    <phase duration="6" state="ryry"/> <!-- index=3 -->
</tlLogic>

この信号の定義をもとに,信号を制御しています.TraCIのPythonAPIについては,こちらに詳しく載っています.

def run():
    """execute the TraCI control loop"""
    step = 0
    # ID"0"のこの信号のindex=2の状態に信号を設定しています.つまり東西の信号を青にしています.  
    traci.trafficlight.setPhase("0", 2)

    # traci.simulation.getMinExpectedNumber()で,道路ネットワーク上にある車両の数と,まだ開始を待っている車両の数を返します.
    # これが0より大きいときに traci.simulationStep()でシミュレーションを1ステップごと実行します.
    while traci.simulation.getMinExpectedNumber() > 0:
        traci.simulationStep()
        # 現在の信号の状態 (indexの値) が2の場合に
        if traci.trafficlight.getPhase("0") == 2:
            # 信号のある交差点上に車両が
            if traci.inductionloop.getLastStepVehicleNumber("0") > 0:
                # いる場合は
                traci.trafficlight.setPhase("0", 3)
            else:
                # それ以外の場合は,東西の信号が青(index=2) の状態を維持する
                traci.trafficlight.setPhase("0", 2)
        step += 1
    traci.close()
    sys.stdout.flush()

このようにTraCIを使い,現在のシミュレーション上の車両の状態を取得し,それによって信号を制御しています.

シミュレーションの実行

シミュレーション時にGUIを使用するかや,TraCIを起動したときに実行するファイルの設定などを行い,シミュレーションを実行します.

def get_options():
    optParser = optparse.OptionParser()
    optParser.add_option("--nogui", action="store_true",
                         default=False, help="run the commandline version of sumo")
    options, args = optParser.parse_args()
    return options


# this is the main entry point of this script
if __name__ == "__main__":
    options = get_options()

    # this script has been called from the command line. It will start sumo as a
    # server, then connect and run
    if options.nogui:
        sumoBinary = checkBinary('sumo')
    else:
        sumoBinary = checkBinary('sumo-gui')

    # first, generate the route file for this simulation
    generate_routefile()

    # this is the normal way of using traci. sumo is started as a
    # subprocess and then the python script connects and runs
    traci.start([sumoBinary, "-c", "data/cross.sumocfg",
                             "--tripinfo-output", "tripinfo.xml"])
    run()

まとめ

TraCIでは,こちらのようにTCPベースで通信の規定が細かく決まっていますが,Python用に用意されたライブラリを用いることで,直感的に使うことができます. C++, Python, Java以外のTraCIのクラスが設定されていない言語を使うときは,TCPの通信のプロトコルをチェックして使う必要があります. TraCIを使うことで,オンラインでシミュレーションの状態の把握や制御ができるようになります.