ros_agent.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  1. #!/usr/bin/env python
  2. #
  3. # Copyright (c) 2019 Intel Corporation
  4. #
  5. # This work is licensed under the terms of the MIT license.
  6. # For a copy, see <https://opensource.org/licenses/MIT>.
  7. #
  8. """
  9. This module provides a ROS autonomous agent interface to control the ego vehicle via a ROS stack
  10. """
  11. import math
  12. import os
  13. import subprocess
  14. import signal
  15. import threading
  16. import time
  17. import numpy
  18. import carla
  19. import rospy
  20. from cv_bridge import CvBridge
  21. from geometry_msgs.msg import PoseStamped
  22. from nav_msgs.msg import Odometry, Path
  23. from rosgraph_msgs.msg import Clock
  24. from sensor_msgs.msg import Image, PointCloud2, NavSatFix, NavSatStatus, CameraInfo
  25. from sensor_msgs.point_cloud2 import create_cloud_xyz32
  26. from std_msgs.msg import Header, String
  27. import tf
  28. # pylint: disable=line-too-long
  29. from carla_msgs.msg import CarlaEgoVehicleStatus, CarlaEgoVehicleInfo, CarlaEgoVehicleInfoWheel, CarlaEgoVehicleControl, CarlaWorldInfo
  30. # pylint: enable=line-too-long
  31. from srunner.autoagents.autonomous_agent import AutonomousAgent
  32. class RosAgent(AutonomousAgent):
  33. """
  34. Base class for ROS-based stacks.
  35. Derive from it and implement the sensors() method.
  36. Please define TEAM_CODE_ROOT in your environment.
  37. The stack is started by executing $TEAM_CODE_ROOT/start.sh
  38. The sensor data is published on similar topics as with the carla-ros-bridge. You can find details about
  39. the utilized datatypes there.
  40. This agent expects a roscore to be running.
  41. """
  42. speed = None
  43. current_control = None
  44. stack_process = None
  45. timestamp = None
  46. current_map_name = None
  47. step_mode_possible = None
  48. vehicle_info_publisher = None
  49. global_plan_published = None
  50. def setup(self, path_to_conf_file):
  51. """
  52. setup agent
  53. """
  54. self.stack_thread = None
  55. # get start_script from environment
  56. team_code_path = os.environ['TEAM_CODE_ROOT']
  57. if not team_code_path or not os.path.exists(team_code_path):
  58. raise IOError("Path '{}' defined by TEAM_CODE_ROOT invalid".format(team_code_path))
  59. start_script = "{}/start.sh".format(team_code_path)
  60. if not os.path.exists(start_script):
  61. raise IOError("File '{}' defined by TEAM_CODE_ROOT invalid".format(start_script))
  62. # set use_sim_time via commandline before init-node
  63. process = subprocess.Popen(
  64. "rosparam set use_sim_time true", shell=True, stderr=subprocess.STDOUT, stdout=subprocess.PIPE)
  65. process.wait()
  66. if process.returncode:
  67. raise RuntimeError("Could not set use_sim_time")
  68. # initialize ros node
  69. rospy.init_node('ros_agent', anonymous=True)
  70. # publish first clock value '0'
  71. self.clock_publisher = rospy.Publisher('clock', Clock, queue_size=10, latch=True)
  72. self.clock_publisher.publish(Clock(rospy.Time.from_sec(0)))
  73. # execute script that starts the ad stack (remains running)
  74. rospy.loginfo("Executing stack...")
  75. self.stack_process = subprocess.Popen(start_script, shell=True, preexec_fn=os.setpgrp)
  76. self.vehicle_control_event = threading.Event()
  77. self.timestamp = None
  78. self.speed = 0
  79. self.global_plan_published = False
  80. self.vehicle_info_publisher = None
  81. self.vehicle_status_publisher = None
  82. self.odometry_publisher = None
  83. self.world_info_publisher = None
  84. self.map_file_publisher = None
  85. self.current_map_name = None
  86. self.tf_broadcaster = None
  87. self.step_mode_possible = False
  88. self.vehicle_control_subscriber = rospy.Subscriber(
  89. '/carla/ego_vehicle/vehicle_control_cmd', CarlaEgoVehicleControl, self.on_vehicle_control)
  90. self.current_control = carla.VehicleControl()
  91. self.waypoint_publisher = rospy.Publisher(
  92. '/carla/ego_vehicle/waypoints', Path, queue_size=1, latch=True)
  93. self.publisher_map = {}
  94. self.id_to_sensor_type_map = {}
  95. self.id_to_camera_info_map = {}
  96. self.cv_bridge = CvBridge()
  97. # setup ros publishers for sensors
  98. # pylint: disable=line-too-long
  99. for sensor in self.sensors():
  100. self.id_to_sensor_type_map[sensor['id']] = sensor['type']
  101. if sensor['type'] == 'sensor.camera.rgb':
  102. self.publisher_map[sensor['id']] = rospy.Publisher(
  103. '/carla/ego_vehicle/camera/rgb/' + sensor['id'] + "/image_color", Image, queue_size=1, latch=True)
  104. self.id_to_camera_info_map[sensor['id']] = self.build_camera_info(sensor)
  105. self.publisher_map[sensor['id'] + '_info'] = rospy.Publisher(
  106. '/carla/ego_vehicle/camera/rgb/' + sensor['id'] + "/camera_info", CameraInfo, queue_size=1, latch=True)
  107. elif sensor['type'] == 'sensor.lidar.ray_cast':
  108. self.publisher_map[sensor['id']] = rospy.Publisher(
  109. '/carla/ego_vehicle/lidar/' + sensor['id'] + "/point_cloud", PointCloud2, queue_size=1, latch=True)
  110. elif sensor['type'] == 'sensor.other.gnss':
  111. self.publisher_map[sensor['id']] = rospy.Publisher(
  112. '/carla/ego_vehicle/gnss/' + sensor['id'] + "/fix", NavSatFix, queue_size=1, latch=True)
  113. elif sensor['type'] == 'sensor.can_bus':
  114. if not self.vehicle_info_publisher:
  115. self.vehicle_info_publisher = rospy.Publisher(
  116. '/carla/ego_vehicle/vehicle_info', CarlaEgoVehicleInfo, queue_size=1, latch=True)
  117. if not self.vehicle_status_publisher:
  118. self.vehicle_status_publisher = rospy.Publisher(
  119. '/carla/ego_vehicle/vehicle_status', CarlaEgoVehicleStatus, queue_size=1, latch=True)
  120. elif sensor['type'] == 'sensor.hd_map':
  121. if not self.odometry_publisher:
  122. self.odometry_publisher = rospy.Publisher(
  123. '/carla/ego_vehicle/odometry', Odometry, queue_size=1, latch=True)
  124. if not self.world_info_publisher:
  125. self.world_info_publisher = rospy.Publisher(
  126. '/carla/world_info', CarlaWorldInfo, queue_size=1, latch=True)
  127. if not self.map_file_publisher:
  128. self.map_file_publisher = rospy.Publisher('/carla/map_file', String, queue_size=1, latch=True)
  129. if not self.tf_broadcaster:
  130. self.tf_broadcaster = tf.TransformBroadcaster()
  131. else:
  132. raise TypeError("Invalid sensor type: {}".format(sensor['type']))
  133. # pylint: enable=line-too-long
  134. def destroy(self):
  135. """
  136. Cleanup of all ROS publishers
  137. """
  138. if self.stack_process and self.stack_process.poll() is None:
  139. rospy.loginfo("Sending SIGTERM to stack...")
  140. os.killpg(os.getpgid(self.stack_process.pid), signal.SIGTERM)
  141. rospy.loginfo("Waiting for termination of stack...")
  142. self.stack_process.wait()
  143. time.sleep(5)
  144. rospy.loginfo("Terminated stack.")
  145. rospy.loginfo("Stack is no longer running")
  146. self.world_info_publisher.unregister()
  147. self.map_file_publisher.unregister()
  148. self.vehicle_status_publisher.unregister()
  149. self.vehicle_info_publisher.unregister()
  150. self.waypoint_publisher.unregister()
  151. self.stack_process = None
  152. rospy.loginfo("Cleanup finished")
  153. def on_vehicle_control(self, data):
  154. """
  155. callback if a new vehicle control command is received
  156. """
  157. cmd = carla.VehicleControl()
  158. cmd.throttle = data.throttle
  159. cmd.steer = data.steer
  160. cmd.brake = data.brake
  161. cmd.hand_brake = data.hand_brake
  162. cmd.reverse = data.reverse
  163. cmd.gear = data.gear
  164. cmd.manual_gear_shift = data.manual_gear_shift
  165. self.current_control = cmd
  166. if not self.vehicle_control_event.is_set():
  167. self.vehicle_control_event.set()
  168. # After the first vehicle control is sent out, it is possible to use the stepping mode
  169. self.step_mode_possible = True
  170. def build_camera_info(self, attributes): # pylint: disable=no-self-use
  171. """
  172. Private function to compute camera info
  173. camera info doesn't change over time
  174. """
  175. camera_info = CameraInfo()
  176. # store info without header
  177. camera_info.header = None
  178. camera_info.width = int(attributes['width'])
  179. camera_info.height = int(attributes['height'])
  180. camera_info.distortion_model = 'plumb_bob'
  181. cx = camera_info.width / 2.0
  182. cy = camera_info.height / 2.0
  183. fx = camera_info.width / (
  184. 2.0 * math.tan(float(attributes['fov']) * math.pi / 360.0))
  185. fy = fx
  186. camera_info.K = [fx, 0, cx, 0, fy, cy, 0, 0, 1]
  187. camera_info.D = [0, 0, 0, 0, 0]
  188. camera_info.R = [1.0, 0, 0, 0, 1.0, 0, 0, 0, 1.0]
  189. camera_info.P = [fx, 0, cx, 0, 0, fy, cy, 0, 0, 0, 1.0, 0]
  190. return camera_info
  191. def publish_plan(self):
  192. """
  193. publish the global plan
  194. """
  195. msg = Path()
  196. msg.header.frame_id = "/map"
  197. msg.header.stamp = rospy.Time.now()
  198. for wp in self._global_plan_world_coord:
  199. pose = PoseStamped()
  200. pose.pose.position.x = wp[0].location.x
  201. pose.pose.position.y = -wp[0].location.y
  202. pose.pose.position.z = wp[0].location.z
  203. quaternion = tf.transformations.quaternion_from_euler(
  204. 0, 0, -math.radians(wp[0].rotation.yaw))
  205. pose.pose.orientation.x = quaternion[0]
  206. pose.pose.orientation.y = quaternion[1]
  207. pose.pose.orientation.z = quaternion[2]
  208. pose.pose.orientation.w = quaternion[3]
  209. msg.poses.append(pose)
  210. rospy.loginfo("Publishing Plan...")
  211. self.waypoint_publisher.publish(msg)
  212. def sensors(self):
  213. """
  214. Define the sensor suite required by the agent
  215. :return: a list containing the required sensors
  216. """
  217. raise NotImplementedError(
  218. "This function has to be implemented by the derived classes")
  219. def get_header(self):
  220. """
  221. Returns ROS message header
  222. """
  223. header = Header()
  224. header.stamp = rospy.Time.from_sec(self.timestamp)
  225. return header
  226. def publish_lidar(self, sensor_id, data):
  227. """
  228. Function to publish lidar data
  229. """
  230. header = self.get_header()
  231. header.frame_id = 'ego_vehicle/lidar/{}'.format(sensor_id)
  232. lidar_data = numpy.frombuffer(
  233. data, dtype=numpy.float32)
  234. lidar_data = numpy.reshape(
  235. lidar_data, (int(lidar_data.shape[0] / 3), 3))
  236. # we take the oposite of y axis
  237. # (as lidar point are express in left handed coordinate system, and ros need right handed)
  238. # we need a copy here, because the data are read only in carla numpy
  239. # array
  240. lidar_data = -1.0 * lidar_data
  241. # we also need to permute x and y
  242. lidar_data = lidar_data[..., [1, 0, 2]]
  243. msg = create_cloud_xyz32(header, lidar_data)
  244. self.publisher_map[sensor_id].publish(msg)
  245. def publish_gnss(self, sensor_id, data):
  246. """
  247. Function to publish gnss data
  248. """
  249. msg = NavSatFix()
  250. msg.header = self.get_header()
  251. msg.header.frame_id = 'gps'
  252. msg.latitude = data[0]
  253. msg.longitude = data[1]
  254. msg.altitude = data[2]
  255. msg.status.status = NavSatStatus.STATUS_SBAS_FIX
  256. # pylint: disable=line-too-long
  257. msg.status.service = NavSatStatus.SERVICE_GPS | NavSatStatus.SERVICE_GLONASS | NavSatStatus.SERVICE_COMPASS | NavSatStatus.SERVICE_GALILEO
  258. # pylint: enable=line-too-long
  259. self.publisher_map[sensor_id].publish(msg)
  260. def publish_camera(self, sensor_id, data):
  261. """
  262. Function to publish camera data
  263. """
  264. msg = self.cv_bridge.cv2_to_imgmsg(data, encoding='bgra8')
  265. # the camera data is in respect to the camera's own frame
  266. msg.header = self.get_header()
  267. msg.header.frame_id = 'ego_vehicle/camera/rgb/{}'.format(sensor_id)
  268. cam_info = self.id_to_camera_info_map[sensor_id]
  269. cam_info.header = msg.header
  270. self.publisher_map[sensor_id + '_info'].publish(cam_info)
  271. self.publisher_map[sensor_id].publish(msg)
  272. def publish_can(self, sensor_id, data):
  273. """
  274. publish can data
  275. """
  276. if not self.vehicle_info_publisher:
  277. self.vehicle_info_publisher = rospy.Publisher(
  278. '/carla/ego_vehicle/vehicle_info', CarlaEgoVehicleInfo, queue_size=1, latch=True)
  279. info_msg = CarlaEgoVehicleInfo()
  280. for wheel in data['wheels']:
  281. wheel_info = CarlaEgoVehicleInfoWheel()
  282. wheel_info.tire_friction = wheel['tire_friction']
  283. wheel_info.damping_rate = wheel['damping_rate']
  284. wheel_info.steer_angle = wheel['steer_angle']
  285. wheel_info.disable_steering = wheel['disable_steering']
  286. info_msg.wheels.append(wheel_info)
  287. info_msg.max_rpm = data['max_rpm']
  288. info_msg.moi = data['moi']
  289. info_msg.damping_rate_full_throttle = data['damping_rate_full_throttle']
  290. info_msg.damping_rate_zero_throttle_clutch_disengaged = data['damping_rate_zero_throttle_clutch_disengaged']
  291. info_msg.use_gear_autobox = data['use_gear_autobox']
  292. info_msg.clutch_strength = data['clutch_strength']
  293. info_msg.mass = data['mass']
  294. info_msg.drag_coefficient = data['drag_coefficient']
  295. info_msg.center_of_mass.x = data['center_of_mass']['x']
  296. info_msg.center_of_mass.y = data['center_of_mass']['y']
  297. info_msg.center_of_mass.z = data['center_of_mass']['z']
  298. self.vehicle_info_publisher.publish(info_msg)
  299. msg = CarlaEgoVehicleStatus()
  300. msg.header = self.get_header()
  301. msg.velocity = data['speed']
  302. self.speed = data['speed']
  303. # todo msg.acceleration
  304. msg.control.throttle = self.current_control.throttle
  305. msg.control.steer = self.current_control.steer
  306. msg.control.brake = self.current_control.brake
  307. msg.control.hand_brake = self.current_control.hand_brake
  308. msg.control.reverse = self.current_control.reverse
  309. msg.control.gear = self.current_control.gear
  310. msg.control.manual_gear_shift = self.current_control.manual_gear_shift
  311. self.vehicle_status_publisher.publish(msg)
  312. def publish_hd_map(self, sensor_id, data):
  313. """
  314. publish hd map data
  315. """
  316. roll = -math.radians(data['transform']['roll'])
  317. pitch = -math.radians(data['transform']['pitch'])
  318. yaw = -math.radians(data['transform']['yaw'])
  319. quat = tf.transformations.quaternion_from_euler(roll, pitch, yaw)
  320. x = data['transform']['x']
  321. y = -data['transform']['y']
  322. z = data['transform']['z']
  323. if self.odometry_publisher:
  324. odometry = Odometry()
  325. odometry.header.frame_id = 'map'
  326. odometry.header.stamp = rospy.Time.from_sec(self.timestamp)
  327. odometry.child_frame_id = 'base_link'
  328. odometry.pose.pose.position.x = x
  329. odometry.pose.pose.position.y = y
  330. odometry.pose.pose.position.z = z
  331. odometry.pose.pose.orientation.x = quat[0]
  332. odometry.pose.pose.orientation.y = quat[1]
  333. odometry.pose.pose.orientation.z = quat[2]
  334. odometry.pose.pose.orientation.w = quat[3]
  335. odometry.twist.twist.linear.x = self.speed
  336. odometry.twist.twist.linear.y = 0
  337. odometry.twist.twist.linear.z = 0
  338. self.odometry_publisher.publish(odometry)
  339. if self.world_info_publisher:
  340. # extract map name
  341. map_name = os.path.basename(data['map_file'])[:-4]
  342. if self.current_map_name != map_name:
  343. self.current_map_name = map_name
  344. world_info = CarlaWorldInfo()
  345. world_info.map_name = self.current_map_name
  346. world_info.opendrive = data['opendrive']
  347. self.world_info_publisher.publish(world_info)
  348. if self.map_file_publisher:
  349. self.map_file_publisher.publish(data['map_file'])
  350. def use_stepping_mode(self): # pylint: disable=no-self-use
  351. """
  352. Overload this function to use stepping mode!
  353. """
  354. return False
  355. def run_step(self, input_data, timestamp):
  356. """
  357. Execute one step of navigation.
  358. """
  359. self.vehicle_control_event.clear()
  360. self.timestamp = timestamp
  361. self.clock_publisher.publish(Clock(rospy.Time.from_sec(timestamp)))
  362. # check if stack is still running
  363. if self.stack_process and self.stack_process.poll() is not None:
  364. raise RuntimeError("Stack exited with: {} {}".format(
  365. self.stack_process.returncode, self.stack_process.communicate()[0]))
  366. # publish global plan to ROS once
  367. if self._global_plan_world_coord and not self.global_plan_published:
  368. self.global_plan_published = True
  369. self.publish_plan()
  370. new_data_available = False
  371. # publish data of all sensors
  372. for key, val in input_data.items():
  373. new_data_available = True
  374. sensor_type = self.id_to_sensor_type_map[key]
  375. if sensor_type == 'sensor.camera.rgb':
  376. self.publish_camera(key, val[1])
  377. elif sensor_type == 'sensor.lidar.ray_cast':
  378. self.publish_lidar(key, val[1])
  379. elif sensor_type == 'sensor.other.gnss':
  380. self.publish_gnss(key, val[1])
  381. elif sensor_type == 'sensor.can_bus':
  382. self.publish_can(key, val[1])
  383. elif sensor_type == 'sensor.hd_map':
  384. self.publish_hd_map(key, val[1])
  385. else:
  386. raise TypeError("Invalid sensor type: {}".format(sensor_type))
  387. if self.use_stepping_mode():
  388. if self.step_mode_possible and new_data_available:
  389. self.vehicle_control_event.wait()
  390. # if the stepping mode is not used or active, there is no need to wait here
  391. return self.current_control