"""
This script runs an aribtrary sumo simulation and controls the specified vehicle
via keyboard input
"""
from __future__ import absolute_import
from __future__ import print_function
import os
import sys
import threading
import math
if sys.version_info.major == 3:
import queue as Queue
from tkinter import Button, Frame, Tk
else:
import Queue
from Tkinter import Button, Frame, Tk
GAME_DIR = os.path.dirname(__file__)
try:
sys.path.append(os.path.join(os.environ.get("SUMO_HOME", os.path.join(GAME_DIR, '..')), "tools"))
import sumolib
import traci
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')")
try:
import autopy
except ImportError:
sys.stderr.write("autopy not installed. Can only use keyboard control.\n")
autopy = None
try:
import playsound
except ImportError:
sys.stderr.write("playsound not installed. Sounds will not be played on collisions.\n")
playsound = None
eventQueue = Queue.Queue()
TS = 0.05
VERBOSE = False
MAX_STEER_ANGLE = 5
MIN_SPEED = -5
MAX_OFFROAD_SPEED = 7
OFFROAD_DECEL_TIME = 2
def leftKey(event):
eventQueue.put(('left', None))
if VERBOSE:
print("Left key pressed")
def rightKey(event):
eventQueue.put(('right', None))
if VERBOSE:
print("Right key pressed")
def upKey(event):
eventQueue.put(('up', None))
if VERBOSE:
print("Up key pressed")
def downKey(event):
eventQueue.put(('down', None))
if VERBOSE:
print("Down key pressed")
def mouseControl(master, speed, steerAngle, accel=2.6, decel=4.5):
try:
centerX = master.winfo_screenwidth() / 2.0
centerY = master.winfo_screenheight() / 2.0
x, y = autopy.mouse.location()
dx = (x - centerX) / centerX
dy = (y - centerY) / centerY
if dy < 0:
speed -= dy * TS * accel
else:
speed -= dy * TS * decel
steerAngle = MAX_STEER_ANGLE * dx
except Exception:
pass
return speed, steerAngle
class RacingClient:
"""
Launch the main part of the GUI and the worker thread. periodicCall and
endApplication could reside in the GUI part, but putting them here
means that you have all the thread controls in a single place.
"""
def __init__(self, master, sumocfg, egoID):
self.master = master
self.sumocfg = sumocfg
self.egoID = egoID
self.running = True
self.thread = threading.Thread(target=self.workerThread)
self.thread.start()
self.periodicCall()
def periodicCall(self):
if not self.running:
sys.exit(1)
self.master.after(100, self.periodicCall)
def workerThread(self):
try:
traci.start([sumolib.checkBinary("sumo-gui"), "-c", self.sumocfg,
"--lateral-resolution", "0.32",
"--collision.action", "warn",
"--step-length", str(TS)])
traci.simulationStep()
speed = traci.vehicle.getSpeed(self.egoID)
angle = traci.vehicle.getAngle(self.egoID)
traci.vehicle.setSpeedMode(self.egoID, 0)
steerAngle = 0
x, y = traci.vehicle.getPosition(self.egoID)
traci.gui.trackVehicle(traci.gui.DEFAULT_VIEW, self.egoID)
while traci.simulation.getMinExpectedNumber() > 0:
try:
if eventQueue.qsize() == 0:
if steerAngle > 0:
steerAngle = max(0, steerAngle - MAX_STEER_ANGLE * TS)
else:
steerAngle = min(0, steerAngle + MAX_STEER_ANGLE * TS)
while eventQueue.qsize():
try:
msg = eventQueue.get(0)
if len(msg) == 1:
direction = msg
val = None
else:
direction, val = msg
if direction == 'up':
if val is None:
val = 1
speed += val * TS * traci.vehicle.getAccel(self.egoID)
if direction == 'down':
if val is None:
val = 1
speed -= val * TS * traci.vehicle.getDecel(self.egoID)
if direction == 'left':
if val is None:
steerAngle -= TS * 5
else:
steerAngle = val
if direction == 'right':
if val is None:
steerAngle += TS * 5
else:
steerAngle = val
except Queue.Empty:
pass
if autopy:
speed, steerAngle = mouseControl(self.master, speed, steerAngle)
if traci.vehicle.getLaneID(self.egoID) == "":
if abs(speed) > MAX_OFFROAD_SPEED:
sign = 1 if speed > 0 else -1
speed -= TS * sign * (abs(speed) - MAX_OFFROAD_SPEED) / OFFROAD_DECEL_TIME
speed = max(MIN_SPEED, min(speed, traci.vehicle.getMaxSpeed(self.egoID)))
steerAngle = min(MAX_STEER_ANGLE, max(-MAX_STEER_ANGLE, steerAngle))
angle += steerAngle
angle = angle % 360
rad = -angle / 180 * math.pi + 0.5 * math.pi
x2 = x + math.cos(rad) * TS * speed
y2 = y + math.sin(rad) * TS * speed
traci.vehicle.moveToXY(self.egoID, "dummy", -1, x2, y2, angle, keepRoute=2)
traci.vehicle.setSpeed(self.egoID, speed)
traci.vehicle.setLine(self.egoID, str(speed))
x3, y3 = traci.vehicle.getPosition(self.egoID)
x, y = x2, y2
if playsound:
if self.egoID in traci.simulation.getCollidingVehiclesIDList():
playsound.playsound(os.path.join(GAME_DIR, "sounds", "car_horn1.wav"), False)
traci.simulationStep()
if VERBOSE:
print(("old=%.2f,%.2f new=%.2f,%.2f found=%.2f,%.2f speed=%.2f steer=%.2f " +
"angle=%s rad/pi=%.2f cos=%.2f sin=%.2f") % (
x, y, x2, y2, x3, y3, speed, steerAngle, angle, rad / math.pi,
math.cos(rad), math.sin(rad)))
except traci.TraCIException:
pass
traci.close()
except traci.FatalTraCIError:
pass
self.running = False
def main(sumocfg="racing/racing.sumocfg", egoID="ego"):
root = Tk()
root.geometry('180x100+0+0')
frame = Frame(root)
Button(frame, text="Click here.\nControl with arrow keys").grid(row=0)
root.bind('<Left>', leftKey)
root.bind('<Right>', rightKey)
root.bind('<Up>', upKey)
root.bind('<Down>', downKey)
root.winfo_screenwidth()
root.winfo_screenheight()
frame.pack()
RacingClient(root, sumocfg, egoID)
root.mainloop()
if __name__ == "__main__":
parser = sumolib.options.ArgumentParser()
parser.add_argument('--sumocfg', default=os.path.join(GAME_DIR, "racing", "racing.sumocfg"),
help=".sumocfg file path")
parser.add_argument('--ego', default="ego", help="vehicle ego id")
parser.add_argument("-v", "--verbose", action="store_true", default=False,
help="tell me what you are doing")
parser.add_argument("-m", "--mouse", action="store_true", default=False,
help="use mouse features")
args = parser.parse_args()
VERBOSE = args.verbose
if not args.mouse:
autopy = None
main(args.sumocfg, args.ego)