def __init__( self, launch_delay_s=0.1, enable_thunderscope=True, runtime_dir="/tmp/tbots" ): """Initialize the TacticTestRunner :param launch_delay_s: How long to wait after launching the processes :param enable_thunderscope: If true, thunderscope opens and the test runs in realtime :param runtime_dir: Directory to open sockets, store logs and any output files """ # Setup runtime directory try: os.mkdir(runtime_dir) except: pass self.enable_thunderscope = enable_thunderscope if self.enable_thunderscope: self.thunderscope = Thunderscope() self.thunderscope.configure_default_layout() self.eventually_validation_sender = ThreadedUnixSender( runtime_dir + "/eventually_validation" ) self.always_validation_sender = ThreadedUnixSender( runtime_dir + "/always_validation" ) self.simulator = ErForceSimulator() self.yellow_full_system = FullSystem() time.sleep(launch_delay_s) self.last_exception = None
def __init__(self, runtime_dir="/tmp/tbots"): """Runs our standalone er-force simulator binary and sets up the unix sockets to communicate with it :param runtime_dir: The runtime directory """ # inputs to full_system self.robot_status_sender = ThreadedUnixSender(runtime_dir + ROBOT_STATUS_PATH) self.ssl_wrapper_sender = ThreadedUnixSender(runtime_dir + SSL_WRAPPER_PATH) self.ssl_referee_sender = ThreadedUnixSender(runtime_dir + SSL_REFEREE_PATH) self.sensor_proto_sender = ThreadedUnixSender(runtime_dir + SENSOR_PROTO_PATH) # outputs from full_system self.world_listener = ThreadedUnixListener(runtime_dir + WORLD_PATH, World) self.primitive_listener = ThreadedUnixListener( runtime_dir + PRIMITIVE_PATH, PrimitiveSet) # override the tactic self.tactic_override = ThreadedUnixSender(runtime_dir + TACTIC_OVERRIDE_PATH) # TODO (#2510) rename to full_system self.full_system_process = Popen(["software/unix_full_system"])
class TacticTestRunner(object): """Run a tactic""" def __init__( self, launch_delay_s=0.1, enable_thunderscope=True, runtime_dir="/tmp/tbots" ): """Initialize the TacticTestRunner :param launch_delay_s: How long to wait after launching the processes :param enable_thunderscope: If true, thunderscope opens and the test runs in realtime :param runtime_dir: Directory to open sockets, store logs and any output files """ # Setup runtime directory try: os.mkdir(runtime_dir) except: pass self.enable_thunderscope = enable_thunderscope if self.enable_thunderscope: self.thunderscope = Thunderscope() self.thunderscope.configure_default_layout() self.eventually_validation_sender = ThreadedUnixSender( runtime_dir + "/eventually_validation" ) self.always_validation_sender = ThreadedUnixSender( runtime_dir + "/always_validation" ) self.simulator = ErForceSimulator() self.yellow_full_system = FullSystem() time.sleep(launch_delay_s) self.last_exception = None def run_test( self, always_validation_sequence_set=[[]], eventually_validation_sequence_set=[[]], test_timeout_s=3, tick_duration_s=0.0166, # Default to 60hz ): """Run a test :param always_validation_sequence_set: Validation functions that should hold on every tick :param eventually_validation_sequence_set: Validation that should eventually be true, before the test ends :param test_timeout_s: The timeout for the test, if any eventually_validations remain after the timeout, the test fails. :param tick_duration_s: The simulation step duration """ def __stopper(delay=PROCESS_BUFFER_DELAY_S): """Stop running the test :param delay: How long to wait before closing everything, defaults to PROCESS_BUFFER_DELAY_S to minimize buffer warnings """ time.sleep(delay) # Close everything self.simulator.simulator_process.kill() self.yellow_full_system.full_system_process.kill() self.simulator.simulator_process.wait() self.yellow_full_system.full_system_process.wait() if self.enable_thunderscope: self.thunderscope.close() def __runner(): """Step simulation, full_system and run validation """ time_elapsed_s = 0 while time_elapsed_s < test_timeout_s: self.simulator.tick(tick_duration_s * MILLISECONDS_PER_SECOND) time_elapsed_s += tick_duration_s if self.enable_thunderscope: time.sleep(tick_duration_s) # Send the sensor_proto and get world ssl_wrapper = self.simulator.get_ssl_wrapper_packet(block=True) self.yellow_full_system.send_sensor_proto( self.simulator.get_yellow_sensor_proto(ssl_wrapper) ) world = self.yellow_full_system.get_world(block=True) # Validate ( eventually_validation_proto_set, always_validation_proto_set, ) = validation.run_validation_sequence_sets( world, eventually_validation_sequence_set, always_validation_sequence_set, ) if self.enable_thunderscope: # Send out the validation proto to thunderscope self.eventually_validation_sender.send( eventually_validation_proto_set ) self.always_validation_sender.send(always_validation_proto_set) # Check that all always validations are always valid validation.check_validation(always_validation_proto_set) # Step the primtives self.simulator.send_yellow_primitive_set_and_world( world, self.yellow_full_system.get_primitive_set(), ) # Check that all eventually validations are eventually valid validation.check_validation(eventually_validation_proto_set) __stopper() def excepthook(args): """This function is _critical_ for enable_thunderscope to work. If the test Thread will raises an exception we won't be able to close the window from the main thread. :param args: The args passed in from the hook """ __stopper(delay=PAUSE_AFTER_FAIL_DELAY_S) self.last_exception = args.exc_value raise self.last_exception threading.excepthook = excepthook # If thunderscope is enabled, run the test in a thread and show # thunderscope on this thread. The excepthook is setup to catch # any test failures and propagate them to the main thread if self.enable_thunderscope: run_sim_thread = threading.Thread(target=__runner, daemon=True) run_sim_thread.start() self.thunderscope.show() run_sim_thread.join() if self.last_exception: pytest.fail(str(self.last_exception)) # If thunderscope is disabled, just run the test else: __runner()
class FullSystem(object): def __init__(self, runtime_dir="/tmp/tbots"): """Runs our standalone er-force simulator binary and sets up the unix sockets to communicate with it :param runtime_dir: The runtime directory """ # inputs to full_system self.robot_status_sender = ThreadedUnixSender(runtime_dir + ROBOT_STATUS_PATH) self.ssl_wrapper_sender = ThreadedUnixSender(runtime_dir + SSL_WRAPPER_PATH) self.ssl_referee_sender = ThreadedUnixSender(runtime_dir + SSL_REFEREE_PATH) self.sensor_proto_sender = ThreadedUnixSender(runtime_dir + SENSOR_PROTO_PATH) # outputs from full_system self.world_listener = ThreadedUnixListener(runtime_dir + WORLD_PATH, World) self.primitive_listener = ThreadedUnixListener( runtime_dir + PRIMITIVE_PATH, PrimitiveSet) # override the tactic self.tactic_override = ThreadedUnixSender(runtime_dir + TACTIC_OVERRIDE_PATH) # TODO (#2510) rename to full_system self.full_system_process = Popen(["software/unix_full_system"]) def send_robot_status(self, robot_status): """Send the robot_status to full_system :param robot_status: The RobotStatus to send """ self.robot_status_sender.send(robot_status) def send_ssl_wrapper(self, ssl_wrapper_packet): """Send the ssl_wrapper_packet to full_system :param ssl_wrapper_packet: The packet to send """ self.ssl_wrapper_sender.send(ssl_wrapper_packet) def send_ssl_referee(self, ssl_referee_packet): """Send the ssl_referee_packet to full_system :param ssl_referee_packet: The packet to send """ self.ssl_referee_sender.send(ssl_referee_packet) def send_sensor_proto(self, sensor_proto): """Send a sensor msg to full system. :param sensor_proto: The sensor msg to send """ self.sensor_proto_sender.send(sensor_proto) def send_tactic_override(self, assigned_tactic_play_control_params): """Send the control params for the assigned tactic play to run specific tactics on assigned robots. :param assigned_tactic_play_control_params: The control params of the AssignedTacticPlay """ self.tactic_override.send(assigned_tactic_play_control_params) def get_world(self, block=False): """Grabs the world msg from the buffer if it exists, returns None if buffer is empty. :param block: Whether or not we should block until we receive a packet :return: World or None """ return self.world_listener.get_most_recent_message(block) def get_primitive_set(self): """Grabs the primitive msg set from the buffer if it exists, returns None if buffer is empty. :return: PrimitiveSet or None """ return self.primitive_listener.get_most_recent_message() def stop(): """Stop all listeners and senders. """ for unix_socket in [ self.robot_status_sender, self.ssl_wrapper_sender, self.ssl_referee_sender, self.tactic_override, self.sensor_proto_sender, self.world_listener, ]: unix_socket.force_stop() self.primitive_listener.force_stop()
def __init__(self, runtime_dir="/tmp/tbots"): """Runs our standalone er-force simulator binary and sets up the unix sockets to communicate with it :param runtime_dir: The unix path to run everything """ # inputs to er_force_simulator_main self.sim_tick_sender = ThreadedUnixSender(runtime_dir + SIMULATION_TICK_PATH) self.world_state_sender = ThreadedUnixSender(runtime_dir + WORLD_STATE_PATH) self.blue_world_sender = ThreadedUnixSender(runtime_dir + BLUE_WORLD_PATH) self.yellow_world_sender = ThreadedUnixSender(runtime_dir + YELLOW_WORLD_PATH) self.blue_primitive_set_sender = ThreadedUnixSender(runtime_dir + BLUE_PRIMITIVE_SET) self.yellow_primitive_set_sender = ThreadedUnixSender( runtime_dir + YELLOW_PRIMITIVE_SET) # outputs from er_force_sim_main self.ssl_wrapper_listener = ThreadedUnixListener( runtime_dir + SSL_WRAPPER_PACKET_PATH, SSL_WrapperPacket) self.blue_robot_status_listener = ThreadedUnixListener( runtime_dir + BLUE_ROBOT_STATUS_PATH, RobotStatus) self.yellow_robot_status_listener = ThreadedUnixListener( runtime_dir + YELLOW_ROBOT_STATUS_PATH, RobotStatus, ) self.world_state = WorldState() self.simulator_process = Popen(["software/er_force_simulator_main"])
class ErForceSimulator(object): def __init__(self, runtime_dir="/tmp/tbots"): """Runs our standalone er-force simulator binary and sets up the unix sockets to communicate with it :param runtime_dir: The unix path to run everything """ # inputs to er_force_simulator_main self.sim_tick_sender = ThreadedUnixSender(runtime_dir + SIMULATION_TICK_PATH) self.world_state_sender = ThreadedUnixSender(runtime_dir + WORLD_STATE_PATH) self.blue_world_sender = ThreadedUnixSender(runtime_dir + BLUE_WORLD_PATH) self.yellow_world_sender = ThreadedUnixSender(runtime_dir + YELLOW_WORLD_PATH) self.blue_primitive_set_sender = ThreadedUnixSender(runtime_dir + BLUE_PRIMITIVE_SET) self.yellow_primitive_set_sender = ThreadedUnixSender( runtime_dir + YELLOW_PRIMITIVE_SET) # outputs from er_force_sim_main self.ssl_wrapper_listener = ThreadedUnixListener( runtime_dir + SSL_WRAPPER_PACKET_PATH, SSL_WrapperPacket) self.blue_robot_status_listener = ThreadedUnixListener( runtime_dir + BLUE_ROBOT_STATUS_PATH, RobotStatus) self.yellow_robot_status_listener = ThreadedUnixListener( runtime_dir + YELLOW_ROBOT_STATUS_PATH, RobotStatus, ) self.world_state = WorldState() self.simulator_process = Popen(["software/er_force_simulator_main"]) def __setup_robots(self, robot_locations, team_colour): """Initializes the world from a list of robot locations :param robot_locations: A list of robot locations (index is robot id) :param team_colour: The color (either "blue" or "yellow") """ if "blue" in team_colour: robot_map = self.world_state.blue_robots else: robot_map = self.world_state.yellow_robots for robot_id, robot_location in enumerate(robot_locations): robot_map[robot_id].CopyFrom( RobotState( global_position=Point(x_meters=robot_location.x(), y_meters=robot_location.y()), global_orientation=Angle(radians=0), global_velocity=Vector(x_component_meters=0, y_component_meters=0), global_angular_velocity=AngularVelocity( radians_per_second=0), )) self.setup_world(self.world_state) def setup_blue_robots(self, robot_locations): """Initializes the world from a list of robot locations :param robot_locations: A list of robot locations (index is robot id) """ self.__setup_robots(robot_locations, "blue") def setup_yellow_robots(self, robot_locations): """Initializes the world from a list of robot locations :param robot_locations: A list of robot locations (index is robot id) """ self.__setup_robots(robot_locations, "yellow") def setup_ball(self, ball_position, ball_velocity, distance_from_ground=0): """Setup the ball with the x, y coordinates in meters :param ball_position: A tuple with the x,y coordinates :param ball_velocity: A tuple with the x,y velocity components :param distance_from_ground: How high up to start the ball """ self.world_state.ball_state.CopyFrom( BallState( global_position=Point( x_meters=ball_position.x(), y_meters=ball_position.y(), ), global_velocity=Vector( x_component_meters=ball_velocity.x(), y_component_meters=ball_velocity.y(), ), distance_from_ground=distance_from_ground, )) self.setup_world(self.world_state) def setup_world(self, world_state): """Pass in a world_state proto directly to setup the simulator :param world_state: The world state to initialize with """ self.world_state_sender.send(world_state) def __get_sensor_proto(self, ssl_wrapper, robot_status_listener): """Helper function to create a sensor proto :param ssl_wrapper: The ssl_wrapper packet to put in the sensor proto :param robot_status_listener: The robot status listener (blue or yellow) :returns: A sensor proto with the robot status from the listener """ sensor_proto = SensorProto() if ssl_wrapper: sensor_proto.ssl_vision_msg.CopyFrom(ssl_wrapper) robot_status = robot_status_listener.get_most_recent_message() packets = [] while robot_status is not None: packets.append(robot_status) robot_status = robot_status_listener.get_most_recent_message() sensor_proto.robot_status_msgs.extend(packets) return sensor_proto def get_blue_sensor_proto(self, ssl_wrapper): """Returns the blue sensor proto :param ssl_wrapper: The wrapper to pack in the sensor proto """ return self.__get_sensor_proto(ssl_wrapper, self.blue_robot_status_listener) def get_yellow_sensor_proto(self, ssl_wrapper): """Returns the yellow sensor proto :param ssl_wrapper: The wrapper to pack in the sensor proto """ return self.__get_sensor_proto(ssl_wrapper, self.yellow_robot_status_listener) def get_ssl_wrapper_packet(self, block=False): """Get wrapper packet :param block: If true, block until we receive a packet :return: SSL_WrapperPacket """ return self.ssl_wrapper_listener.get_most_recent_message(block) def tick(self, duration_ms): """Tick the simulator with the given duration :param duration_ms: The duration to step the sim """ tick = SimulatorTick() tick.milliseconds = duration_ms self.sim_tick_sender.send(tick) def send_blue_primitive_set_and_world(self, world, primitive_set): """Blue primitive set and world :param world: The world msg to send :param primitive_set: The primitive set to send """ self.blue_world_sender.send(world) self.blue_primitive_set_sender.send(primitive_set) def send_yellow_primitive_set_and_world(self, world, primitive_set): """Yellow primitive set and world :param world: The world msg to send :param primitive_set: The primitive set to send """ self.yellow_world_sender.send(world) self.yellow_primitive_set_sender.send(primitive_set) def stop(): """Stop all listeners and senders. """ for unix_socket in [ self.sim_tick_sender, self.world_state_sender, self.blue_world_sender, self.yellow_world_sender, self.blue_primitive_set_sender, self.yellow_primitive_set_sender, self.ssl_wrapper_listener, self.blue_robot_status_listener, self.yellow_robot_status_listener, ]: unix_socket.force_stop()