def generate_commands(program,
                      drone_config_map: Dict[str, DroneConfig] = None,
                      boundary_checker: BoundaryChecker = None,
                      collision_checker: CollisionChecker = None,
                      has_checks: bool = True,
                      symbol_table: SymbolTable = None,
                      function_table: FunctionTable = None):
    if drone_config_map is None:
        drone_config_map = {"DEFAULT": DefaultDroneConfig()}
    if boundary_checker is None:
        boundary_checker = BoundaryChecker(BoundaryConfig.no_limit())
    if collision_checker is None:
        collision_checker = CollisionChecker(drone_config_map,
                                             DefaultCollisionConfig())
    if symbol_table is None:
        symbol_table = SymbolTable()
    if function_table is None:
        function_table = FunctionTable()
    state_updater_map = {
        name: StateUpdater(config)
        for name, config in drone_config_map.items()
    }

    tree = _parse_program(program)

    drone_commands = Compiler(drone_config_map, symbol_table,
                              function_table).visit(tree)

    if has_checks:
        boundary_checker.check(drone_commands, state_updater_map)
        collision_checker.check(drone_commands, state_updater_map)

    return drone_commands
 def test_no_limit_values(self):
     boundary_config = BoundaryConfig.no_limit()
     self.assertEqual(float("inf"), boundary_config._max_seconds)
     self.assertEqual(float("inf"), boundary_config._max_x_meters)
     self.assertEqual(float("inf"), boundary_config._max_y_meters)
     self.assertEqual(float("inf"), boundary_config._max_z_meters)
     self.assertEqual(float("-inf"), boundary_config._min_x_meters)
     self.assertEqual(float("-inf"), boundary_config._min_y_meters)
     self.assertEqual(float("-inf"), boundary_config._min_z_meters)
    def test_init_with_wrong_parameter_should_give_error(self):
        with self.assertRaises(ValueError) as context:
            BoundaryConfig(max_seconds=-1)
        self.assertTrue("max_seconds should >= 0" in str(context.exception))

        with self.assertRaises(ValueError) as context:
            BoundaryConfig(max_x_meters=-1, min_x_meters=1)
        self.assertTrue(
            "max_x_meters should >= min_x_meters" in str(context.exception))

        with self.assertRaises(ValueError) as context:
            BoundaryConfig(max_y_meters=-1, min_y_meters=1)
        self.assertTrue(
            "max_y_meters should >= min_y_meters" in str(context.exception))

        with self.assertRaises(ValueError) as context:
            BoundaryConfig(max_z_meters=-1, min_z_meters=1)
        self.assertTrue(
            "max_z_meters should >= min_z_meters" in str(context.exception))
Beispiel #4
0
 def test_if_given_boundary_checker_should_use_it_to_check_safety(self):
     commands = "main() {takeoff(); up(1000); land();}"
     with self.assertRaises(SafetyCheckError) as context:
         generate_commands(commands,
                           boundary_checker=BoundaryChecker(
                               BoundaryConfig(max_seconds=10000,
                                              max_z_meters=1)))
     self.assertTrue(
         "Drone 'DEFAULT': the z coordinate 1001 will go beyond its upper limit 1"
         in str(context.exception))
 def test_str(self):
     boundary_config = BoundaryConfig(max_seconds=1,
                                      max_x_meters=2,
                                      max_y_meters=3,
                                      max_z_meters=4,
                                      min_x_meters=-2,
                                      min_y_meters=-3,
                                      min_z_meters=-4)
     self.assertEqual(
         "BoundaryConfig: { max_seconds: 1, x_range_meters: (-2, 2), " +
         "y_range_meters: (-3, 3), z_range_meters: (-4, 4) }",
         str(boundary_config))
 def test_parse_if_not_provided_configs_should_use_default_configs(self):
     config = "{}"
     with self.assertLogs(logging.getLogger()) as log:
         actual_drone_configs, actual_boundary_config, acutal_collision_config = ConfigParser.parse(config)
     expected_log = ["WARNING:root:'drones' missing when parsing configs, using default drone_config. " +
                     "Position estimation may be inaccurate.",
                     "WARNING:root:'boundary_config' missing when parsing configs, using unlimited boundary_config. " +
                     "Time and position will be unlimited.",
                     "WARNING:root:'collision_config' missing when parsing configs, using default collision_config."]
     self.assertEqual(expected_log, log.output)
     self.assertEqual({"DEFAULT": DefaultDroneConfig()}, actual_drone_configs)
     self.assertEqual(BoundaryConfig.no_limit(), actual_boundary_config)
     self.assertEqual(DefaultCollisionConfig(), acutal_collision_config)
Beispiel #7
0
 def test_if_given_state_updater_should_use_it_to_update_state(self):
     commands = "main() {takeoff(); land();}"
     with self.assertRaises(SafetyCheckError) as context:
         generate_commands(commands,
                           drone_config_map={
                               "DEFAULT":
                               DroneConfig(init_position=(0, 0, 0),
                                           speed_mps=1,
                                           rotate_speed_dps=90,
                                           takeoff_height_meters=10)
                           },
                           boundary_checker=BoundaryChecker(
                               BoundaryConfig(max_seconds=10,
                                              max_z_meters=1)))
     self.assertTrue(
         "Drone 'DEFAULT': the z coordinate 10 will go beyond its upper limit 1"
         in str(context.exception))
    def test_parse_if_name_repeated_should_ignore_second_appearance(self):
        config = """
            {
              "drones": [{
                "name": "DRONE1",
                "init_position": {"x": 1, "y": 2, "z": 3},
                "speed_mps": 2,
                "rotate_speed_dps": 180,
                "takeoff_height_meters": 2
              },{
                "name": "DRONE1",
                "init_position": {"x": 4, "y": 5, "z": 6},
                "speed_mps": 1,
                "rotate_speed_dps": 90,
                "takeoff_height_meters": 1
              }],
              "boundary_config": {
                "max_seconds": 100,
                "max_x_meters": 10,
                "max_y_meters": 20,
                "max_z_meters": 30,
                "min_x_meters": -10,
                "min_y_meters": -20,
                "min_z_meters": -30
              },
              "collision_config": {
                "collision_meters": 0.3,
                "time_interval_seconds": 0.5
              }
            }
            """

        with self.assertLogs(logging.getLogger()) as log:
            actual_drone_configs, actual_boundary_config, actual_collision_config = ConfigParser.parse(config)
        expected_log = ["WARNING:root:Drone name 'DRONE1' appeared more than ones in 'drones', ignored."]
        self.assertEqual(expected_log, log.output)
        expected_drone_configs = {"DRONE1": DroneConfig(init_position=(1, 2, 3), speed_mps=2,
                                                        rotate_speed_dps=180, takeoff_height_meters=2)}
        expected_boundary_config = BoundaryConfig(max_seconds=100, max_x_meters=10, max_y_meters=20, max_z_meters=30,
                                                  min_x_meters=-10, min_y_meters=-20, min_z_meters=-30)
        expected_collision_config = CollisionConfig(collision_meters=0.3, time_interval_seconds=0.5)
        self.assertEqual(expected_drone_configs, actual_drone_configs)
        self.assertEqual(expected_boundary_config, actual_boundary_config)
        self.assertEqual(expected_collision_config, actual_collision_config)
    def test_parse_if_init_position_missing_fields_should_use_default_value(self):
        config = """
            {
              "drones": [{
                "name": "DRONE1",
                "init_position": {},
                "speed_mps": 2,
                "rotate_speed_dps": 180,
                "takeoff_height_meters": 2
              }],
              "boundary_config": {
                "max_seconds": 100,
                "max_x_meters": 10,
                "max_y_meters": 20,
                "max_z_meters": 30,
                "min_x_meters": -10,
                "min_y_meters": -20,
                "min_z_meters": -30
              },
              "collision_config": {
                "collision_meters": 0.3,
                "time_interval_seconds": 0.5
              }
            }
            """

        with self.assertLogs(logging.getLogger()) as log:
            actual_drone_configs, actual_boundary_config, actual_collision_config = ConfigParser.parse(config)
        expected_log = ["WARNING:root:'x' missing when parsing drone 'DRONE1', using default value 0. " +
                        "Position estimation may be inaccurate.",
                        "WARNING:root:'y' missing when parsing drone 'DRONE1', using default value 0. " +
                        "Position estimation may be inaccurate.",
                        "WARNING:root:'z' missing when parsing drone 'DRONE1', using default value 0. " +
                        "Position estimation may be inaccurate."]
        self.assertEqual(expected_log, log.output)
        expected_drone_configs = {"DRONE1": DroneConfig(init_position=(0, 0, 0), speed_mps=2,
                                                        rotate_speed_dps=180, takeoff_height_meters=2)}
        expected_boundary_config = BoundaryConfig(max_seconds=100, max_x_meters=10, max_y_meters=20, max_z_meters=30,
                                                  min_x_meters=-10, min_y_meters=-20, min_z_meters=-30)
        expected_collision_config = CollisionConfig(collision_meters=0.3, time_interval_seconds=0.5)
        self.assertEqual(expected_drone_configs, actual_drone_configs)
        self.assertEqual(expected_boundary_config, actual_boundary_config)
        self.assertEqual(expected_collision_config, actual_collision_config)
 def test_parse_if_configs_missing_fields_should_use_default_value(self):
     config = """
         {
           "drones": [],
           "boundary_config": {
           },
           "collision_config": {
           }
         }
         """
     with self.assertLogs(logging.getLogger()) as log:
         actual_drone_configs, actual_boundary_config, actual_collision_config = ConfigParser.parse(config)
     expected_log = ["WARNING:root:'drones' is empty when parsing configs, using default drone_config. " +
                     "Position estimation may be inaccurate.",
                     "WARNING:root:'max_seconds' missing when parsing 'boundary_config', " +
                     "using default value inf. There will be no limit on 'max_seconds'.",
                     "WARNING:root:'max_x_meters' missing when parsing 'boundary_config', " +
                     "using default value inf. There will be no limit on 'max_x_meters'.",
                     "WARNING:root:'max_y_meters' missing when parsing 'boundary_config', " +
                     "using default value inf. There will be no limit on 'max_y_meters'.",
                     "WARNING:root:'max_z_meters' missing when parsing 'boundary_config', " +
                     "using default value inf. There will be no limit on 'max_z_meters'.",
                     "WARNING:root:'min_x_meters' missing when parsing 'boundary_config', " +
                     "using default value -inf. There will be no limit on 'min_x_meters'.",
                     "WARNING:root:'min_y_meters' missing when parsing 'boundary_config', " +
                     "using default value -inf. There will be no limit on 'min_y_meters'.",
                     "WARNING:root:'min_z_meters' missing when parsing 'boundary_config', " +
                     "using default value -inf. There will be no limit on 'min_z_meters'.",
                     "WARNING:root:'collision_meters' missing when parsing 'collision_config', " +
                     "using default value 0. There will be no limit on 'collision_meters'.",
                     "WARNING:root:'time_interval_seconds' missing when parsing 'collision_config', " +
                     "using default value 0.1."]
     self.assertEqual(expected_log, log.output)
     expected_drone_configs = {"DEFAULT": DefaultDroneConfig()}
     expected_boundary_config = BoundaryConfig(max_seconds=float("inf"), max_x_meters=float("inf"),
                                               max_y_meters=float("inf"), max_z_meters=float("inf"),
                                               min_x_meters=float("-inf"), min_y_meters=float("-inf"),
                                               min_z_meters=float("-inf"))
     expected_collision_config = DefaultCollisionConfig()
     self.assertEqual(expected_drone_configs, actual_drone_configs)
     self.assertEqual(expected_boundary_config, actual_boundary_config)
     self.assertEqual(expected_collision_config, actual_collision_config)
 def test_parse_if_provided_configs_should_parse_all_configs(self):
     config = """
         {
           "drones": [{
             "name": "DRONE1",
             "init_position": {"x": 1, "y": 2, "z": 3},
             "speed_mps": 2,
             "rotate_speed_dps": 180,
             "takeoff_height_meters": 2
           },{
             "name": "DRONE2",
             "init_position": {"x": 4, "y": 5, "z": 6},
             "speed_mps": 1,
             "rotate_speed_dps": 90,
             "takeoff_height_meters": 1
           }],
           "boundary_config": {
             "max_seconds": 100,
             "max_x_meters": 10,
             "max_y_meters": 20,
             "max_z_meters": 30,
             "min_x_meters": -10,
             "min_y_meters": -20,
             "min_z_meters": -30
           },
           "collision_config": {
             "collision_meters": 0.3,
             "time_interval_seconds": 0.5
           }
         }
         """
     actual_drone_config, actual_boundary_config, acutal_collision_config = ConfigParser.parse(config)
     expected_drone_configs = {"DRONE1": DroneConfig(init_position=(1, 2, 3), speed_mps=2,
                                                     rotate_speed_dps=180, takeoff_height_meters=2),
                               "DRONE2": DroneConfig(init_position=(4, 5, 6), speed_mps=1,
                                                     rotate_speed_dps=90, takeoff_height_meters=1)}
     expected_boundary_config = BoundaryConfig(max_seconds=100, max_x_meters=10, max_y_meters=20, max_z_meters=30,
                                               min_x_meters=-10, min_y_meters=-20, min_z_meters=-30)
     expected_collision_config = CollisionConfig(collision_meters=0.3, time_interval_seconds=0.5)
     self.assertEqual(expected_drone_configs, actual_drone_config)
     self.assertEqual(expected_boundary_config, actual_boundary_config)
     self.assertEqual(expected_collision_config, acutal_collision_config)
Beispiel #12
0
 def setUp(self) -> None:
     self.boundary_config = BoundaryConfig(max_seconds=10,
                                           max_x_meters=5,
                                           max_y_meters=5,
                                           max_z_meters=5,
                                           min_x_meters=-5,
                                           min_y_meters=-5,
                                           min_z_meters=-5)
     self.state_updater_map = {
         "DRONE1":
         StateUpdater(
             DroneConfig(init_position=(0, 0, 0),
                         speed_mps=1,
                         rotate_speed_dps=90,
                         takeoff_height_meters=1)),
         "DRONE2":
         StateUpdater(
             DroneConfig(init_position=(1, 0, 0),
                         speed_mps=2,
                         rotate_speed_dps=180,
                         takeoff_height_meters=2))
     }
 def test_parse_if_drone_object_missing_fields_should_use_default_value(self):
     config = """
         {
           "drones": [{}],
           "boundary_config": {
             "max_seconds": 100,
             "max_x_meters": 10,
             "max_y_meters": 20,
             "max_z_meters": 30,
             "min_x_meters": -10,
             "min_y_meters": -20,
             "min_z_meters": -30
           },
           "collision_config": {
             "collision_meters": 0.3,
             "time_interval_seconds": 0.5
           }
         }
         """
     with self.assertLogs(logging.getLogger()) as log:
         actual_drone_configs, actual_boundary_config, actual_collision_config = ConfigParser.parse(config)
     expected_log = ["WARNING:root:'name' missing when parsing object in 'drones', using default value 'DEFAULT'.",
                     "WARNING:root:'init_position' missing when parsing drone 'DEFAULT', " +
                     "using default value (0, 0, 0). Position estimation may be inaccurate.",
                     "WARNING:root:'speed_mps' missing when parsing drone 'DEFAULT', " +
                     "using default value 1. Position estimation may be inaccurate.",
                     "WARNING:root:'rotate_speed_dps' missing when parsing drone 'DEFAULT', " +
                     "using default value 90. Position estimation may be inaccurate.",
                     "WARNING:root:'takeoff_height_meters' missing when parsing drone 'DEFAULT', " +
                     "using default value 1. Position estimation may be inaccurate."]
     self.assertEqual(expected_log, log.output)
     expected_drone_configs = {"DEFAULT": DroneConfig(init_position=(0, 0, 0), speed_mps=1,
                                                      rotate_speed_dps=90, takeoff_height_meters=1)}
     expected_boundary_config = BoundaryConfig(max_seconds=100, max_x_meters=10, max_y_meters=20, max_z_meters=30,
                                               min_x_meters=-10, min_y_meters=-20, min_z_meters=-30)
     expected_collision_config = CollisionConfig(collision_meters=0.3, time_interval_seconds=0.5)
     self.assertEqual(expected_drone_configs, actual_drone_configs)
     self.assertEqual(expected_boundary_config, actual_boundary_config)
     self.assertEqual(expected_collision_config, actual_collision_config)
    def test_parse_if_name_is_empty_string_should_use_default_value(self):
        config = """
            {
              "drones": [{
                "name": "",
                "init_position": {"x": 1, "y": 2, "z": 3},
                "speed_mps": 2,
                "rotate_speed_dps": 180,
                "takeoff_height_meters": 2
              }],
              "boundary_config": {
                "max_seconds": 100,
                "max_x_meters": 10,
                "max_y_meters": 20,
                "max_z_meters": 30,
                "min_x_meters": -10,
                "min_y_meters": -20,
                "min_z_meters": -30
              },
              "collision_config": {
                "collision_meters": 0.3,
                "time_interval_seconds": 0.5
              }
            }
            """

        with self.assertLogs(logging.getLogger()) as log:
            actual_drone_configs, actual_boundary_config, actual_collision_config = ConfigParser.parse(config)
        expected_log = ["WARNING:root:'name' cannot be an empty string, using default value 'DEFAULT' instead."]
        self.assertEqual(expected_log, log.output)
        expected_drone_configs = {"DEFAULT": DroneConfig(init_position=(1, 2, 3), speed_mps=2,
                                                         rotate_speed_dps=180, takeoff_height_meters=2)}
        expected_boundary_config = BoundaryConfig(max_seconds=100, max_x_meters=10, max_y_meters=20, max_z_meters=30,
                                                  min_x_meters=-10, min_y_meters=-20, min_z_meters=-30)
        expected_collision_config = CollisionConfig(collision_meters=0.3, time_interval_seconds=0.5)
        self.assertEqual(expected_drone_configs, actual_drone_configs)
        self.assertEqual(expected_boundary_config, actual_boundary_config)
        self.assertEqual(expected_collision_config, actual_collision_config)
Beispiel #15
0
 def test_if_not_given_state_updater_should_use_default_to_update_state(
         self):
     commands = "main() {takeoff(); land();}"
     generate_commands(commands,
                       boundary_checker=BoundaryChecker(
                           BoundaryConfig(max_seconds=10, max_z_meters=1)))
    def test_check_state_if_beyond_limit_should_give_error(self):
        boundary_config = BoundaryConfig(max_seconds=0,
                                         max_x_meters=0,
                                         max_y_meters=0,
                                         max_z_meters=0,
                                         min_x_meters=0,
                                         min_y_meters=0,
                                         min_z_meters=0)
        with self.assertRaises(SafetyCheckError) as context:
            boundary_config.check_state('default', State(x_meters=10))
        self.assertTrue(
            "Drone 'default': the x coordinate 10 will go beyond its upper limit 0"
            in str(context.exception))

        with self.assertRaises(SafetyCheckError) as context:
            boundary_config.check_state('default', State(y_meters=10))
        self.assertTrue(
            "Drone 'default': the y coordinate 10 will go beyond its upper limit 0"
            in str(context.exception))

        with self.assertRaises(SafetyCheckError) as context:
            boundary_config.check_state('default', State(z_meters=10))
        self.assertTrue(
            "Drone 'default': the z coordinate 10 will go beyond its upper limit 0"
            in str(context.exception))

        with self.assertRaises(SafetyCheckError) as context:
            boundary_config.check_state('default', State(x_meters=-10))
        self.assertTrue(
            "Drone 'default': the x coordinate -10 will go beyond its lower limit 0"
            in str(context.exception))

        with self.assertRaises(SafetyCheckError) as context:
            boundary_config.check_state('default', State(y_meters=-10))
        self.assertTrue(
            "Drone 'default': the y coordinate -10 will go beyond its lower limit 0"
            in str(context.exception))

        with self.assertRaises(SafetyCheckError) as context:
            boundary_config.check_state('default', State(z_meters=-10))
        self.assertTrue(
            "Drone 'default': the z coordinate -10 will go beyond its lower limit 0"
            in str(context.exception))

        with self.assertRaises(SafetyCheckError) as context:
            boundary_config.check_state('default', State(time_used_seconds=10))
        self.assertTrue(
            "Drone 'default': the time used 10 seconds will go beyond the time limit 0 seconds"
            in str(context.exception))
 def test_eq(self):
     self.assertEqual(BoundaryConfig(), BoundaryConfig())
     self.assertNotEqual(BoundaryConfig(max_seconds=0),
                         BoundaryConfig(max_seconds=1))
     self.assertNotEqual(BoundaryConfig(max_x_meters=0),
                         BoundaryConfig(max_x_meters=1))
     self.assertNotEqual(BoundaryConfig(max_y_meters=0),
                         BoundaryConfig(max_y_meters=1))
     self.assertNotEqual(BoundaryConfig(max_z_meters=0),
                         BoundaryConfig(max_z_meters=1))
     self.assertNotEqual(BoundaryConfig(min_x_meters=0),
                         BoundaryConfig(min_x_meters=-1))
     self.assertNotEqual(BoundaryConfig(min_y_meters=0),
                         BoundaryConfig(min_y_meters=-1))
     self.assertNotEqual(BoundaryConfig(min_z_meters=0),
                         BoundaryConfig(min_z_meters=-1))
     self.assertNotEqual(None, BoundaryConfig())
 def _parse_boundary_config(data: dict) -> BoundaryConfig:
     if "boundary_config" in data:
         if "max_seconds" in data["boundary_config"]:
             max_seconds = data["boundary_config"]["max_seconds"]
         else:
             warning(
                 "'max_seconds' missing when parsing 'boundary_config', using default value inf. "
                 + "There will be no limit on 'max_seconds'.")
             max_seconds = float('inf')
         if "max_x_meters" in data["boundary_config"]:
             max_x_meters = data["boundary_config"]["max_x_meters"]
         else:
             warning(
                 "'max_x_meters' missing when parsing 'boundary_config', using default value inf. "
                 + "There will be no limit on 'max_x_meters'.")
             max_x_meters = float('inf')
         if "max_y_meters" in data["boundary_config"]:
             max_y_meters = data["boundary_config"]["max_y_meters"]
         else:
             warning(
                 "'max_y_meters' missing when parsing 'boundary_config', using default value inf. "
                 + "There will be no limit on 'max_y_meters'.")
             max_y_meters = float('inf')
         if "max_z_meters" in data["boundary_config"]:
             max_z_meters = data["boundary_config"]["max_z_meters"]
         else:
             warning(
                 "'max_z_meters' missing when parsing 'boundary_config', using default value inf. "
                 + "There will be no limit on 'max_z_meters'.")
             max_z_meters = float('inf')
         if "min_x_meters" in data["boundary_config"]:
             min_x_meters = data["boundary_config"]["min_x_meters"]
         else:
             warning(
                 "'min_x_meters' missing when parsing 'boundary_config', using default value -inf. "
                 + "There will be no limit on 'min_x_meters'.")
             min_x_meters = float('-inf')
         if "min_y_meters" in data["boundary_config"]:
             min_y_meters = data["boundary_config"]["min_y_meters"]
         else:
             warning(
                 "'min_y_meters' missing when parsing 'boundary_config', using default value -inf. "
                 + "There will be no limit on 'min_y_meters'.")
             min_y_meters = float('-inf')
         if "min_z_meters" in data["boundary_config"]:
             min_z_meters = data["boundary_config"]["min_z_meters"]
         else:
             warning(
                 "'min_z_meters' missing when parsing 'boundary_config', using default value -inf. "
                 + "There will be no limit on 'min_z_meters'.")
             min_z_meters = float('-inf')
         boundary_config = BoundaryConfig(max_seconds, max_x_meters,
                                          max_y_meters, max_z_meters,
                                          min_x_meters, min_y_meters,
                                          min_z_meters)
     else:
         warning(
             "'boundary_config' missing when parsing configs, using unlimited boundary_config. "
             + "Time and position will be unlimited.")
         boundary_config = BoundaryConfig.no_limit()
     return boundary_config
 def test_check_state_if_no_limit_should_not_give_error(self):
     boundary_config = BoundaryConfig()
     for number in [1e-5, 1e-3, 1e0, 1e3, 1e5, 1e10, float('inf')]:
         boundary_config.check_state('default', State(x_meters=number))
         boundary_config.check_state('default', State(y_meters=number))
         boundary_config.check_state('default', State(z_meters=number))
         boundary_config.check_state('default', State(x_meters=-number))
         boundary_config.check_state('default', State(y_meters=-number))
         boundary_config.check_state('default', State(z_meters=-number))
         boundary_config.check_state('default',
                                     State(time_used_seconds=number))