def __init__(self, robot): self._velocity_speed = VELOCITY_BASE_SPEED self._velocity_angular = VELOCITY_BASE_ANGULAR self.height = 0.5 self.roll = 0.0 self.pitch = 0.0 self.yaw = 0.0 self._robot = robot # Create clients -- do not use the for communication yet. self._lease_client = robot.ensure_client(LeaseClient.default_service_name) self._estop_client = robot.ensure_client(EstopClient.default_service_name) self._estop_endpoint = EstopEndpoint(self._estop_client, 'wasd', 9.0) self._power_client = robot.ensure_client(PowerClient.default_service_name) self._robot_state_client = robot.ensure_client(RobotStateClient.default_service_name) self._robot_command_client = robot.ensure_client(RobotCommandClient.default_service_name) self._robot_metrics_task = AsyncMetrics(self._robot_state_client) self._robot_state_task = AsyncRobotState(self._robot_state_client) self._image_task = AsyncImageCapture(robot) self._async_tasks = AsyncTasks( [self._robot_metrics_task, self._robot_state_task, self._image_task]) self._lock = threading.Lock() self._command_dictionary = { ord('\t'): self._quit_program, ord('T'): self._toggle_time_sync, ord('\\'): self._toggle_estop, ord('r'): self._self_right, ord('P'): self._toggle_power, ord('1'): self._sit, ord('2'): self._stand, ord('w'): self._move_forward, ord('s'): self._move_backward, ord('a'): self._strafe_left, ord('d'): self._strafe_right, ord('q'): self._turn_left, ord('e'): self._turn_right, ord('.'): self._speed_up, ord(','): self._slow_down, ord('>'): self._speed_up, ord('<'): self._slow_down, ord('z'): self._lean_left, ord('c'): self._lean_right, ord('f'): self._twist_left, ord('h'): self._twist_right, ord('t'): self._tilt_down, ord('g'): self._tilt_up, ord('3'): self._stand_lower, ord('4'): self._stand_higher, ord('I'): self._image_task.take_image, ord('O'): self._image_task.toggle_video_mode, } self._locked_messages = ['', '', ''] # string: displayed message for user self._estop_keepalive = None self._exit_check = None # Stuff that is set in start() self._robot_id = None self._lease = None
def __init__(self, robot): self._robot = robot # Create clients -- do not use the for communication yet. self._lease_client = robot.ensure_client( LeaseClient.default_service_name) try: self._estop_client = self._robot.ensure_client( EstopClient.default_service_name) self._estop_endpoint = EstopEndpoint(self._estop_client, 'GNClient', 9.0) except: # Not the estop. self._estop_client = None self._estop_endpoint = None self._power_client = robot.ensure_client( PowerClient.default_service_name) self._robot_state_client = robot.ensure_client( RobotStateClient.default_service_name) self._robot_command_client = robot.ensure_client( RobotCommandClient.default_service_name) self._robot_state_task = AsyncRobotState(self._robot_state_client) self._image_task = AsyncImageCapture(robot) self._async_tasks = AsyncTasks( [self._robot_state_task, self._image_task]) self._lock = threading.Lock() self._command_dictionary = { 27: self._stop, # ESC key ord('\t'): self._quit_program, ord('T'): self._toggle_time_sync, ord(' '): self._toggle_estop, ord('r'): self._self_right, ord('P'): self._toggle_power, ord('v'): self._sit, ord('b'): self._battery_change_pose, ord('f'): self._stand, ord('w'): self._move_forward, ord('s'): self._move_backward, ord('a'): self._strafe_left, ord('d'): self._strafe_right, ord('q'): self._turn_left, ord('e'): self._turn_right, ord('I'): self._image_task.take_image, ord('O'): self._image_task.toggle_video_mode, ord('u'): self._unstow, ord('j'): self._stow, ord('l'): self._toggle_lease } self._locked_messages = ['', '', ''] # string: displayed message for user self._estop_keepalive = None self._exit_check = None # Stuff that is set in start() self._robot_id = None self._lease = None self._lease_keepalive = None
def main(argv): # The last argument should be the IP address of the robot. The app will use the directory to find # the velodyne and start getting data from it. parser = argparse.ArgumentParser() bosdyn.client.util.add_common_arguments(parser) options = parser.parse_args(argv) sdk = bosdyn.client.create_standard_sdk('VelodyneClient') robot = sdk.create_robot(options.hostname) robot.authenticate(options.username, options.password) robot.sync_with_directory() _point_cloud_client = robot.ensure_client('velodyne-point-cloud') _robot_state_client = robot.ensure_client( RobotStateClient.default_service_name) _point_cloud_task = AsyncPointCloud(_point_cloud_client) _robot_state_task = AsyncRobotState(_robot_state_client) _task_list = [_point_cloud_task, _robot_state_task] _async_tasks = AsyncTasks(_task_list) print('Connected.') update_thread = threading.Thread(target=_update_thread, args=[_async_tasks]) update_thread.daemon = True update_thread.start() # Wait for the first responses. while any(task.proto is None for task in _task_list): time.sleep(0.1) fig = plt.figure() body_tform_butt = SE3Pose(-0.5, 0, 0, Quat()) body_tform_head = SE3Pose(0.5, 0, 0, Quat()) # Plot the point cloud as an animation. ax = fig.add_subplot(111, projection='3d') aggregate_data = collections.deque(maxlen=5) while True: if _point_cloud_task.proto[0].point_cloud: data = np.fromstring(_point_cloud_task.proto[0].point_cloud.data, dtype=np.float32) aggregate_data.append(data) plot_data = np.concatenate(aggregate_data) ax.clear() ax.set_xlabel('X (m)') ax.set_ylabel('Y (m)') ax.set_zlabel('Z (m)') # Draw robot position and orientation on plot if _robot_state_task.proto: odom_tform_body = get_odom_tform_body( _robot_state_task.proto.kinematic_state.transforms_snapshot ).to_proto() helper_se3 = SE3Pose.from_obj(odom_tform_body) odom_tform_butt = helper_se3.mult(body_tform_butt) odom_tform_head = helper_se3.mult(body_tform_head) ax.plot([odom_tform_butt.x], [odom_tform_butt.y], [odom_tform_butt.z], 'o', color=SPOT_YELLOW, markersize=7, label='robot_butt') ax.plot([odom_tform_head.x], [odom_tform_head.y], [odom_tform_head.z], 'o', color=SPOT_YELLOW, markersize=7, label='robot_head') ax.text(odom_tform_butt.x, odom_tform_butt.y, odom_tform_butt.z, 'Spot Butt', size=TEXT_SIZE, zorder=1, color='k') ax.text(odom_tform_head.x, odom_tform_head.y, odom_tform_head.z, 'Spot Head', size=TEXT_SIZE, zorder=1, color='k') ax.plot([odom_tform_butt.x, odom_tform_head.x], [odom_tform_butt.y, odom_tform_head.y], zs=[odom_tform_butt.z, odom_tform_head.z], linewidth=6, color=SPOT_YELLOW) # Plot point cloud data ax.plot(plot_data[0::3], plot_data[1::3], plot_data[2::3], '.') set_axes_equal(ax) plt.draw() plt.pause(0.016) if window_closed(ax): sys.exit(0)
def __init__(self, robot, download_filepath): self._robot = robot # Flag indicating whether mission is currently being recorded self._recording = False # Filepath for the location to put the downloaded graph and snapshots. self._download_filepath = download_filepath # List of visited waypoints in current mission self._waypoint_path = [] # Current waypoint id self._waypoint_id = 'NONE' # Create clients -- do not use the for communication yet. self._lease_client = robot.ensure_client( LeaseClient.default_service_name) try: self._estop_client = self._robot.ensure_client( EstopClient.default_service_name) self._estop_endpoint = EstopEndpoint(self._estop_client, 'GNClient', 9.0) except: # Not the estop. self._estop_client = None self._estop_endpoint = None self._power_client = robot.ensure_client( PowerClient.default_service_name) self._robot_state_client = robot.ensure_client( RobotStateClient.default_service_name) self._robot_command_client = robot.ensure_client( RobotCommandClient.default_service_name) self._world_object_client = robot.ensure_client( WorldObjectClient.default_service_name) # Setup the recording service client. self._recording_client = self._robot.ensure_client( GraphNavRecordingServiceClient.default_service_name) # Setup the graph nav service client. self._graph_nav_client = robot.ensure_client( GraphNavClient.default_service_name) self._robot_state_task = AsyncRobotState(self._robot_state_client) self._async_tasks = AsyncTasks([self._robot_state_task]) self._lock = threading.Lock() self._command_dictionary = { ord('\t'): self._quit_program, ord('T'): self._toggle_time_sync, ord(' '): self._toggle_estop, ord('r'): self._self_right, ord('P'): self._toggle_power, ord('v'): self._sit, ord('f'): self._stand, ord('w'): self._move_forward, ord('s'): self._move_backward, ord('a'): self._strafe_left, ord('d'): self._strafe_right, ord('q'): self._turn_left, ord('e'): self._turn_right, ord('m'): self._start_recording, ord('l'): self._relocalize, ord('g'): self._generate_mission, } self._locked_messages = ['', '', ''] # string: displayed message for user self._estop_keepalive = None self._exit_check = None # Stuff that is set in start() self._robot_id = None self._lease = None
def __init__(self, username, password, hostname, logger, rates={}, callbacks={}): self._username = username self._password = password self._hostname = hostname self._logger = logger self._rates = rates self._callbacks = callbacks self._keep_alive = True self._valid = True self._mobility_params = RobotCommandBuilder.mobility_params() self._is_standing = False self._is_sitting = True self._is_moving = False self._last_stand_command = None self._last_sit_command = None self._last_motion_command = None self._last_motion_command_time = None self._front_image_requests = [] for source in front_image_sources: self._front_image_requests.append( build_image_request( source, image_format=image_pb2.Image.Format.FORMAT_RAW)) self._side_image_requests = [] for source in side_image_sources: self._side_image_requests.append( build_image_request( source, image_format=image_pb2.Image.Format.FORMAT_RAW)) self._rear_image_requests = [] for source in rear_image_sources: self._rear_image_requests.append( build_image_request( source, image_format=image_pb2.Image.Format.FORMAT_RAW)) try: self._sdk = create_standard_sdk('ros_spot') except Exception as e: self._logger.error("Error creating SDK object: %s", e) self._valid = False return self._robot = self._sdk.create_robot(self._hostname) try: self._robot.authenticate(self._username, self._password) self._robot.start_time_sync() except RpcError as err: self._logger.error("Failed to communicate with robot: %s", err) self._valid = False return if self._robot: # Clients try: self._robot_state_client = self._robot.ensure_client( RobotStateClient.default_service_name) self._robot_command_client = self._robot.ensure_client( RobotCommandClient.default_service_name) self._graph_nav_client = self._robot.ensure_client( GraphNavClient.default_service_name) self._power_client = self._robot.ensure_client( PowerClient.default_service_name) self._lease_client = self._robot.ensure_client( LeaseClient.default_service_name) self._lease_wallet = self._lease_client.lease_wallet self._image_client = self._robot.ensure_client( ImageClient.default_service_name) self._estop_client = self._robot.ensure_client( EstopClient.default_service_name) except Exception as e: self._logger.error("Unable to create client service: %s", e) self._valid = False return # Store the most recent knowledge of the state of the robot based on rpc calls. self._current_graph = None self._current_edges = dict( ) #maps to_waypoint to list(from_waypoint) self._current_waypoint_snapshots = dict( ) # maps id to waypoint snapshot self._current_edge_snapshots = dict() # maps id to edge snapshot self._current_annotation_name_to_wp_id = dict() # Async Tasks self._async_task_list = [] self._robot_state_task = AsyncRobotState( self._robot_state_client, self._logger, max(0.0, self._rates.get("robot_state", 0.0)), self._callbacks.get("robot_state", lambda: None)) self._robot_metrics_task = AsyncMetrics( self._robot_state_client, self._logger, max(0.0, self._rates.get("metrics", 0.0)), self._callbacks.get("metrics", lambda: None)) self._lease_task = AsyncLease( self._lease_client, self._logger, max(0.0, self._rates.get("lease", 0.0)), self._callbacks.get("lease", lambda: None)) self._front_image_task = AsyncImageService( self._image_client, self._logger, max(0.0, self._rates.get("front_image", 0.0)), self._callbacks.get("front_image", lambda: None), self._front_image_requests) self._side_image_task = AsyncImageService( self._image_client, self._logger, max(0.0, self._rates.get("side_image", 0.0)), self._callbacks.get("side_image", lambda: None), self._side_image_requests) self._rear_image_task = AsyncImageService( self._image_client, self._logger, max(0.0, self._rates.get("rear_image", 0.0)), self._callbacks.get("rear_image", lambda: None), self._rear_image_requests) self._idle_task = AsyncIdle(self._robot_command_client, self._logger, 10.0, self) self._estop_endpoint = None self._async_tasks = AsyncTasks([ self._robot_state_task, self._robot_metrics_task, self._lease_task, self._front_image_task, self._side_image_task, self._rear_image_task, self._idle_task ]) self._robot_id = None self._lease = None
def main(argv): """Command line interface. Args: argv: List of command-line arguments passed to the program. """ parser = argparse.ArgumentParser() parser.add_argument( "--model-path", required=True, help= "Local file path to the Tensorflow model, example pre-trained models \ can be found at \ https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/detection_model_zoo.md" ) parser.add_argument( "--classes", default='/classes.json', type=str, help="File containing json mapping of object class IDs to class names") parser.add_argument( "--number-tensorflow-processes", default=1, type=int, help="Number of Tensorflow processes to run in parallel") parser.add_argument( "--detection-threshold", default=0.7, type=float, help="Detection threshold to use for Tensorflow detections") parser.add_argument( "--sleep-between-capture", default=1.0, type=float, help= "Seconds to sleep between each image capture loop iteration, which captures " + "an image from all cameras") parser.add_argument( "--detection-class", default=1, type=int, help="Detection classes to use in the" + "Tensorflow model; Default is to use 1, which is a person in the Coco dataset" ) parser.add_argument( "--max-processing-delay", default=7.0, type=float, help="Maximum allowed delay for processing an image; " + "any image older than this value will be skipped") bosdyn.client.util.add_common_arguments(parser) options = parser.parse_args(argv) try: # Make sure the model path is a valid file if not _check_model_path(options.model_path): return False # Check for classes json file, otherwise use the COCO class dictionary _check_and_load_json_classes(options.classes) global TENSORFLOW_PROCESS_BARRIER # pylint: disable=global-statement TENSORFLOW_PROCESS_BARRIER = Barrier( options.number_tensorflow_processes + 1) # Start Tensorflow processes start_tensorflow_processes(options.number_tensorflow_processes, options.model_path, options.detection_class, options.detection_threshold, options.max_processing_delay) # sleep to give the Tensorflow processes time to initialize try: TENSORFLOW_PROCESS_BARRIER.wait() except BrokenBarrierError as exc: print( f'Error waiting for Tensorflow processes to initialize: {exc}') return False # Start the API related things # Create robot object with a world object client sdk = bosdyn.client.create_standard_sdk('SpotFollowClient') robot = sdk.create_robot(options.hostname) robot.authenticate(options.username, options.password) #Time sync is necessary so that time-based filter requests can be converted robot.time_sync.wait_for_sync() # Verify the robot is not estopped and that an external application has registered and holds # an estop endpoint. verify_estop(robot) # Create the sdk clients robot_state_client = robot.ensure_client( RobotStateClient.default_service_name) robot_command_client = robot.ensure_client( RobotCommandClient.default_service_name) lease_client = robot.ensure_client(LeaseClient.default_service_name) image_client = robot.ensure_client(ImageClient.default_service_name) source_list = get_source_list(image_client) image_task = AsyncImage(image_client, source_list) robot_state_task = AsyncRobotState(robot_state_client) task_list = [image_task, robot_state_task] _async_tasks = AsyncTasks(task_list) print('Detect and follow client connected.') lease = lease_client.take() lease_keep = LeaseKeepAlive(lease_client) # Power on the robot and stand it up resp = robot.power_on() try: blocking_stand(robot_command_client) except CommandFailedError as exc: print( f'Error ({exc}) occurred while trying to stand. Check robot surroundings.' ) return False except CommandTimedOutError as exc: print(f'Stand command timed out: {exc}') return False print('Robot powered on and standing.') params_set = get_mobility_params() # This thread starts the async tasks for image and robot state retrieval update_thread = Thread(target=_update_thread, args=[_async_tasks]) update_thread.daemon = True update_thread.start() # Wait for the first responses. while any(task.proto is None for task in task_list): time.sleep(0.1) # Start image capture process image_capture_thread = Thread(target=capture_images, args=( image_task, options.sleep_between_capture, )) image_capture_thread.start() while True: # This comes from the tensorflow processes and limits the rate of this loop entry = PROCESSED_BOXES_QUEUE.get() # find the highest confidence bounding box highest_conf_source = _find_highest_conf_source(entry) if highest_conf_source is None: # no boxes or scores found continue capture_to_use = entry[highest_conf_source] raw_time = capture_to_use['raw_image_time'] time_gap = time.time() - raw_time if time_gap > options.max_processing_delay: continue # Skip image due to delay # Find the transform to highest confidence object using the depth sensor world_tform_object = get_object_position( capture_to_use['world_tform_cam'], capture_to_use['visual_image'], capture_to_use['depth_image'], capture_to_use['boxes'][0], ROTATION_ANGLES[capture_to_use['source']]) # get_object_position can fail if there is insufficient depth sensor information if not world_tform_object: continue scores = capture_to_use['scores'] print( f'Transform for object with confidence {scores[0]}: {world_tform_object}' ) print( f'Process latency: {time.time() - capture_to_use["system_cap_time"]}' ) tag_cmd = get_go_to(world_tform_object, robot_state_task.proto, params_set) end_time = 15.0 if tag_cmd is not None: robot_command_client.robot_command(lease=None, command=tag_cmd, end_time_secs=time.time() + end_time) return True except Exception as exc: # pylint: disable=broad-except LOGGER.error("Spot Tensorflow Detector threw an exception: %s", exc) return False
def main(argv): parser = argparse.ArgumentParser() bosdyn.client.util.add_common_arguments(parser) parser.add_argument('--protocol', default='tcp', type=str, choices=['tcp', 'udp'], help='IP Protocel to use, either tcp or udp.') parser.add_argument('--server-port', default=5201, type=int, help='Port number of iperf3 server') parser.add_argument('--server-hostname', default='127.0.0.1', type=str, help='IP address of ieprf3 server') options = parser.parse_args(argv) sdk = bosdyn.client.create_standard_sdk('CommsTestingClient', [MissionClient]) robot = sdk.create_robot(options.hostname) #ROBOT_IP robot.authenticate(options.username, options.password) robot.sync_with_directory() _robot_state_client = robot.ensure_client( RobotStateClient.default_service_name) _mission_client = robot.ensure_client(MissionClient.default_service_name) _robot_state_task = AsyncRobotState(_robot_state_client) _mission_state_task = AsyncMissionState(_mission_client) _task_list = [_robot_state_task, _mission_state_task] _async_tasks = AsyncTasks(_task_list) print('Connected.') update_thread = threading.Thread(target=_update_thread, args=[_async_tasks]) update_thread.daemon = True update_thread.start() # Wait for the first responses. while any(task.proto is None for task in _task_list): time.sleep(0.1) # list to hold all the data data_list = [] curr_fname = '' try: while True: if _mission_state_task.proto.status != mission_proto.State.STATUS_RUNNING: # Write out data if it exists if len(data_list) > 0: print( f'Finished a mission, data can be found in {curr_fname}' ) data_list.clear() print(_mission_state_task.proto) time.sleep(1) else: if _robot_state_task.proto: # This script currently uses odometry positioning, which is based on the robot's # position at boot time. Runs across boots will not be easily comparable odom_tform_body = get_odom_tform_body( _robot_state_task.proto.kinematic_state. transforms_snapshot).to_proto() helper_se3 = SE3Pose.from_obj(odom_tform_body) #check latency ping_ret = check_ping(options.server_hostname) ping = -1 if ping_ret is None else ping_ret['avg'] # Run iperf3 client client = iperf3.Client() client.duration = 1 client.server_hostname = options.server_hostname client.protocol = options.protocol client.port = options.server_port result = client.run() # update list of data points if result.error: print(result.error) else: data_entry = { 'loc_x': helper_se3.x, 'loc_y': helper_se3.y, 'loc_z': helper_se3.z, 'time': datetime.datetime.now(), 'latency(ms)': ping } if options.protocol == 'udp': data_entry.update({ 'send throughput(Mbps)': result.Mbps, 'recv throughput(Mbps)': -1, 'jitter(ms)': result.jitter_ms, 'lost(%)': result.lost_percent, 'retransmits': -1 }) elif options.protocol == 'tcp': data_entry.update({ 'send throughput(Mbps)': result.sent_Mbps, 'recv throughput(Mbps)': result.received_Mbps, 'jitter(ms)': -1, 'lost(%)': -1, 'retransmits': result.retransmits }) data_list.append(data_entry) print(data_list[-1]) # Create csv with header if it doesn't exist, then write latest row keys = data_list[0].keys() if curr_fname == '': curr_fname = create_csv_filename(options.protocol) with open(curr_fname, 'w') as output_file: header_writer = csv.DictWriter( output_file, keys) header_writer.writeheader() with open(curr_fname, 'a') as output_file: dict_writer = csv.DictWriter(output_file, keys) dict_writer.writerow(data_list[-1]) del client except KeyboardInterrupt: print("Caught KeyboardInterrupt, exiting") return True
def __init__(self, username, password, token, hostname, logger, rates={}, callbacks={}): self._username = username self._password = password self._token = token self._hostname = hostname self._logger = logger self._rates = rates self._callbacks = callbacks self._keep_alive = True self._valid = True self._mobility_params = RobotCommandBuilder.mobility_params() self._is_standing = False self._is_sitting = True self._is_moving = False self._last_stand_command = None self._last_sit_command = None self._last_motion_command = None self._last_motion_command_time = None self._front_image_requests = [] for source in front_image_sources: self._front_image_requests.append( build_image_request( source, image_format=image_pb2.Image.Format.FORMAT_RAW)) self._side_image_requests = [] for source in side_image_sources: self._side_image_requests.append( build_image_request( source, image_format=image_pb2.Image.Format.FORMAT_RAW)) self._rear_image_requests = [] for source in rear_image_sources: self._rear_image_requests.append( build_image_request( source, image_format=image_pb2.Image.Format.FORMAT_RAW)) try: self._sdk = create_standard_sdk('ros_spot') except Exception as e: self._logger.error("Error creating SDK object: %s", e) self._valid = False return try: self._sdk.load_app_token(self._token) except Exception as e: self._logger.error("Error loading developer token: %s", e) self._valid = False return self._robot = self._sdk.create_robot(self._hostname) try: self._robot.authenticate(self._username, self._password) self._robot.start_time_sync() except RpcError as err: self._logger.error("Failed to communicate with robot: %s", err) self._valid = False return if self._robot: # Clients try: self._robot_state_client = self._robot.ensure_client( RobotStateClient.default_service_name) self._robot_command_client = self._robot.ensure_client( RobotCommandClient.default_service_name) self._power_client = self._robot.ensure_client( PowerClient.default_service_name) self._lease_client = self._robot.ensure_client( LeaseClient.default_service_name) self._image_client = self._robot.ensure_client( ImageClient.default_service_name) self._estop_client = self._robot.ensure_client( EstopClient.default_service_name) except Exception as e: self._logger.error("Unable to create client service: %s", e) self._valid = False return # Async Tasks self._async_task_list = [] self._robot_state_task = AsyncRobotState( self._robot_state_client, self._logger, max(0.0, self._rates.get("robot_state", 0.0)), self._callbacks.get("robot_state", lambda: None)) self._robot_metrics_task = AsyncMetrics( self._robot_state_client, self._logger, max(0.0, self._rates.get("metrics", 0.0)), self._callbacks.get("metrics", lambda: None)) self._lease_task = AsyncLease( self._lease_client, self._logger, max(0.0, self._rates.get("lease", 0.0)), self._callbacks.get("lease", lambda: None)) self._front_image_task = AsyncImageService( self._image_client, self._logger, max(0.0, self._rates.get("front_image", 0.0)), self._callbacks.get("front_image", lambda: None), self._front_image_requests) self._side_image_task = AsyncImageService( self._image_client, self._logger, max(0.0, self._rates.get("side_image", 0.0)), self._callbacks.get("side_image", lambda: None), self._side_image_requests) self._rear_image_task = AsyncImageService( self._image_client, self._logger, max(0.0, self._rates.get("rear_image", 0.0)), self._callbacks.get("rear_image", lambda: None), self._rear_image_requests) self._idle_task = AsyncIdle(self._robot_command_client, self._logger, 10.0, self) self._estop_endpoint = EstopEndpoint(self._estop_client, 'ros', 9.0) self._async_tasks = AsyncTasks([ self._robot_state_task, self._robot_metrics_task, self._lease_task, self._front_image_task, self._side_image_task, self._rear_image_task, self._idle_task ]) self._robot_id = None self._lease = None