#32 Comment the client API

This commit is contained in:
nsubiron 2017-11-23 15:38:29 +01:00
parent 9f95e088c4
commit 1b621ded46
7 changed files with 82 additions and 42 deletions

View File

@ -21,44 +21,50 @@ except ImportError:
raise RuntimeError('cannot import "carla_server_pb2.py", run the protobuf compiler to generate this file')
VehicleControl = carla_protocol.Control
@contextmanager
def make_carla_client(host, world_port, timeout=15):
"""Context manager for creating and connecting a CarlaClient."""
with util.make_connection(CarlaClient, host, world_port, timeout) as client:
yield client
class CarlaClient(object):
"""The CARLA client. Manages communications with the CARLA server."""
def __init__(self, host, world_port, timeout=15):
self._world_client = tcp.TCPClient(host, world_port, timeout)
self._stream_client = tcp.TCPClient(host, world_port + 1, timeout)
self._control_client = tcp.TCPClient(host, world_port + 2, timeout)
self._current_settings = None
# Controls the state, if an episode is already started.
self._is_episode_requested = False
self._sensor_names = []
def connect(self):
self._world_client.connect()
def connect(self, connection_attempts=10):
"""
Try to establish a connection to a CARLA server at the given host:port.
"""
self._world_client.connect(connection_attempts)
def disconnect(self):
"""Disconnect from server."""
self._control_client.disconnect()
self._stream_client.disconnect()
self._world_client.disconnect()
def connected(self):
"""Return whether there is an active connection."""
return self._world_client.connected()
def load_settings(self, carla_settings):
"""
Load new settings and request a new episode based on these settings to
the server. carla_settings object must be convertible to a str holding a
CarlaSettings.ini.
Load new settings and request a new episode based on these settings.
carla_settings object must be convertible to a str holding the contents
of a CarlaSettings.ini file.
Returns a protobuf object holding the scene description.
Return a protobuf object holding the scene description.
"""
self._current_settings = carla_settings
return self._request_new_episode(carla_settings)
@ -68,8 +74,10 @@ class CarlaClient(object):
"""
Start the new episode at the player start given by the
player_start_index. The list of player starts is retrieved by
"load_settings". Requests a new episode based on the last settings
loaded by "load_settings".
"load_settings".
The new episode is started based on the last settings loaded by
"load_settings".
This function waits until the server answers with an EpisodeReady.
"""
@ -101,9 +109,9 @@ class CarlaClient(object):
def read_data(self):
"""
Read data sent from the server this frame. The episode must be started.
Return the protobuf object with the measurements followed by the raw
data of the sensors.
Read the data sent from the server this frame. The episode must be
started. Return a pair containing the protobuf object containing the
measurements followed by the raw data of the sensors.
"""
# Read measurements.
data = self._stream_client.read()
@ -116,7 +124,12 @@ class CarlaClient(object):
return pb_message, self._parse_raw_sensor_data(raw_sensor_data)
def send_control(self, *args, **kwargs):
"""Send vehicle control for the current frame."""
"""
Send the VehicleControl to be applied this frame.
If synchronous mode was requested, the server will pause the simulation
until this message is received.
"""
if isinstance(args[0] if args else None, carla_protocol.Control):
pb_message = args[0]
else:
@ -130,9 +143,8 @@ class CarlaClient(object):
def _request_new_episode(self, carla_settings):
"""
Request a new episode. Internal function to request information about a
new episode episode that is going to start. It also prepare the client
for reset by disconnecting stream and control clients.
Internal function to request a new episode. Prepare the client for a new
episode by disconnecting agent clients.
"""
# Disconnect agent clients.
self._stream_client.disconnect()
@ -154,6 +166,7 @@ class CarlaClient(object):
return pb_message
def _parse_raw_sensor_data(self, raw_data):
"""Return a dict of {'sensor_name': sensor_data, ...}."""
return dict((name, data) for name, data in zip(
self._sensor_names,
self._iterate_sensor_data(raw_data)))

View File

@ -15,11 +15,8 @@ provides considerably better performance.
try:
import numpy
except ImportError:
raise RuntimeError('cannot import numpy, make sure numpy package is installed')
@ -92,10 +89,10 @@ def depth_to_array(image):
return grayscale
def depth_to_grayscale(image):
def depth_to_logarithmic_grayscale(image):
"""
Convert an image containing CARLA encoded depth-map to logarithmic
grayscale.
Convert an image containing CARLA encoded depth-map to a logarithmic
grayscale image array.
"""
grayscale = depth_to_array(image)
# Convert to logarithmic depth.

View File

@ -17,14 +17,17 @@ import os
class Sensor(object):
"""
Base class for sensor descriptions. Used to add sensors to the
CarlaSettings.
Base class for sensor descriptions. Used to add sensors to CarlaSettings.
"""
pass
class Camera(Sensor):
"""Camera description. To be added to CarlaSettings."""
"""
Camera description. This class can be added to a CarlaSettings object to add
a camera to the player vehicle.
"""
def __init__(self, name, **kwargs):
self.CameraName = name
self.PostProcessing = 'SceneFinal'
@ -66,14 +69,13 @@ class Camera(Sensor):
class SensorData(object):
"""
Base class for sensor data returned from the server.
"""
"""Base class for sensor data returned from the server."""
pass
class Image(SensorData):
"""Data generated by a camera."""
"""Data generated by a Camera."""
def __init__(self, width, height, image_type, raw_data):
assert len(raw_data) == 4 * width * height
self.width = width

View File

@ -27,7 +27,11 @@ MAX_NUMBER_OF_WEATHER_IDS = 14
class CarlaSettings(object):
"""CARLA settings object. Convertible to str as CarlaSettings.ini."""
"""
The CarlaSettings object controls the settings of an episode. The __str__
method retrieves an str with a CarlaSettings.ini file contents.
"""
def __init__(self, **kwargs):
# [CARLA/Server]
self.SynchronousMode = True
@ -49,25 +53,27 @@ class CarlaSettings(object):
raise ValueError('CarlaSettings: no key named %r' % key)
setattr(self, key, value)
def get_number_of_agents(self):
if not self.SendNonPlayerAgentsInfo:
return 0
return self.NumberOfVehicles + self.NumberOfPedestrians
def randomize_seeds(self):
"""
Randomize the seeds of the new episode's pseudo-random number
generators.
"""
self.SeedVehicles = random.getrandbits(16)
self.SeedPedestrians = random.getrandbits(16)
def randomize_weather(self):
"""Randomized the WeatherId."""
self.WeatherId = random.randint(0, MAX_NUMBER_OF_WEATHER_IDS)
def add_sensor(self, sensor):
"""Add a sensor to the player vehicle (see sensor.py)."""
if isinstance(sensor, carla_sensor.Camera):
self._cameras.append(sensor)
else:
raise ValueError('Sensor not supported')
def __str__(self):
"""Converts this object to an INI formatted string."""
ini = ConfigParser()
ini.optionxform=str
S_SERVER = 'CARLA/Server'
@ -117,6 +123,10 @@ class CarlaSettings(object):
def _get_sensor_names(settings):
"""
Return a list with the names of the sensors defined in the settings object.
The settings object can be a CarlaSettings or an INI formatted string.
"""
if isinstance(settings, CarlaSettings):
return [camera.CameraName for camera in settings._cameras]
ini = ConfigParser()

View File

@ -16,6 +16,14 @@ class TCPConnectionError(Exception):
class TCPClient(object):
"""
Basic networking client for TCP connections. Errors occurred during
networking operations are raised as TCPConnectionError.
Received messages are expected to be prepended by a int32 defining the
message size. Messages are sent following this convention.
"""
def __init__(self, host, port, timeout):
self._host = host
self._port = port
@ -23,8 +31,10 @@ class TCPClient(object):
self._socket = None
self._logprefix = '(%s:%s) ' % (self._host, self._port)
def connect(self):
for attempt in range(10):
def connect(self, connection_attempts=10):
"""Try to establish a connection to the given host:port."""
connection_attempts = max(1, connection_attempts)
for attempt in range(1, connection_attempts + 1):
try:
self._socket = socket.create_connection(address=(self._host, self._port), timeout=self._timeout)
self._socket.settimeout(self._timeout)
@ -37,15 +47,18 @@ class TCPClient(object):
self._reraise_exception_as_tcp_error('failed to connect', exception)
def disconnect(self):
"""Disconnect any active connection."""
if self._socket is not None:
logging.debug(self._logprefix + 'disconnecting')
self._socket.close()
self._socket = None
def connected(self):
"""Return whether there is an active connection."""
return self._socket is not None
def write(self, message):
"""Send message to the server."""
if self._socket is None:
raise TCPConnectionError(self._logprefix + 'not connected')
header = struct.pack('<L', len(message))
@ -55,6 +68,7 @@ class TCPClient(object):
self._reraise_exception_as_tcp_error('failed to write data', exception)
def read(self):
"""Read a message from the server."""
header = self._read_n(4)
if not header:
raise TCPConnectionError(self._logprefix + 'connection closed')
@ -63,6 +77,7 @@ class TCPClient(object):
return data
def _read_n(self, length):
"""Read n bytes from the socket."""
if self._socket is None:
raise TCPConnectionError(self._logprefix + 'not connected')
buf = bytes()

View File

@ -12,7 +12,7 @@ from contextlib import contextmanager
@contextmanager
def make_connection(client_type, *args, **kwargs):
"""Context manager to create and connect a networking object."""
"""Context manager to create and connect a networking client object."""
client = None
try:
client = client_type(*args, **kwargs)

View File

@ -52,6 +52,7 @@ from carla.settings import CarlaSettings
from carla.tcp import TCPConnectionError
from carla.util import print_over_same_line
WINDOW_WIDTH = 800
WINDOW_HEIGHT = 600
MINI_WINDOW_WIDTH = 320
@ -59,6 +60,7 @@ MINI_WINDOW_HEIGHT = 180
def make_carla_settings():
"""Make a CarlaSettings object with the settings we need."""
settings = CarlaSettings()
settings.set(
SynchronousMode=False,
@ -118,6 +120,7 @@ class CarlaGame(object):
self._map = CarlaMap(city_name) if city_name is not None else None
def execute(self):
"""Launch the PyGame."""
pygame.init()
self._initialize_game()
try:
@ -264,7 +267,7 @@ class CarlaGame(object):
self._display.blit(surface, (0, 0))
if self._mini_view_image1 is not None:
array = image_converter.depth_to_grayscale(self._mini_view_image1)
array = image_converter.depth_to_logarithmic_grayscale(self._mini_view_image1)
surface = pygame.surfarray.make_surface(array.swapaxes(0, 1))
self._display.blit(surface, (gap_x, mini_image_y))
@ -304,10 +307,10 @@ def main():
type=int,
help='TCP port to listen to (default: 2000)')
argparser.add_argument(
'-m', '--city_name',
'-m', '--map-name',
metavar='M',
default=None,
help='Plot the carla town map of city sent as argument (default: None), needs to match city from server, Options: Town01 or Town02')
help='plot the map of the current city (needs to match active map in server, options: Town01 or Town02)')
args = argparser.parse_args()
log_level = logging.DEBUG if args.debug else logging.INFO