def test_eq(self): commands1 = [ None, Command.takeoff(), Command.land(), Command.up(1), Command.down(1), Command.left(1), Command.right(1), Command.forward(1), Command.backward(1), Command.rotate_left(1), Command.rotate_right(1), Command.wait(1), Command.up(2), Command.down(2), Command.left(2), Command.right(2), Command.forward(2), Command.backward(2), Command.rotate_left(2), Command.rotate_right(2), Command.wait(2) ] commands2 = [ None, Command.takeoff(), Command.land(), Command.up(1), Command.down(1), Command.left(1), Command.right(1), Command.forward(1), Command.backward(1), Command.rotate_left(1), Command.rotate_right(1), Command.wait(1), Command.up(2), Command.down(2), Command.left(2), Command.right(2), Command.forward(2), Command.backward(2), Command.rotate_left(2), Command.rotate_right(2), Command.wait(2) ] for i in range(len(commands1)): for j in range(len(commands2)): if i == j: self.assertEqual(commands1[i], commands2[j]) else: self.assertNotEqual(commands1[i], commands2[j])
def test_multiple_commands_should_return_correct_command(self): actual = generate_commands(""" main() { takeoff(); up(1); down(2); left(3); right(4); forward(5); backward(6); rotate_left(7); rotate_right(8); wait(9); land(); } """) expected = [ SingleDroneCommand("DEFAULT", Command.takeoff()), SingleDroneCommand("DEFAULT", Command.up(1)), SingleDroneCommand("DEFAULT", Command.down(2)), SingleDroneCommand("DEFAULT", Command.left(3)), SingleDroneCommand("DEFAULT", Command.right(4)), SingleDroneCommand("DEFAULT", Command.forward(5)), SingleDroneCommand("DEFAULT", Command.backward(6)), SingleDroneCommand("DEFAULT", Command.rotate_left(7)), SingleDroneCommand("DEFAULT", Command.rotate_right(8)), SingleDroneCommand("DEFAULT", Command.wait(9)), SingleDroneCommand("DEFAULT", Command.land()) ] self.assertEqual(expected, actual)
def test_update_up_should_update_state(self): state = State(has_taken_off=True, z_meters=3) actual = self.state_updater.update(Command.up(1), state) expected = State(has_taken_off=True, time_used_seconds=1 / 0.5, z_meters=3 + 1) self.assertEqual(expected, actual)
def test_call_function_with_parameter_should_shadow_constant(self): commands = generate_commands(""" function func(drone DRONE2) return int { DRONE2.up(1); return 1; } main () { DRONE1.takeoff(); DRONE1.up(func(DRONE1)); DRONE1.land(); } """, drone_config_map={"DRONE1": DefaultDroneConfig(), "DRONE2": DefaultDroneConfig()}) expected_commands = [SingleDroneCommand("DRONE1", Command.takeoff()), SingleDroneCommand("DRONE1", Command.up(1)), SingleDroneCommand("DRONE1", Command.up(1)), SingleDroneCommand("DRONE1", Command.land())] self.assertEqual(expected_commands, commands)
def test_up_with_decimal_parameter_should_return_correct_command(self): actual = generate_commands(""" main() { takeoff(); up(1.0); land(); } """) expected = [ SingleDroneCommand("DEFAULT", Command.takeoff()), SingleDroneCommand("DEFAULT", Command.up(1.0)), SingleDroneCommand("DEFAULT", Command.land()) ] self.assertEqual(expected, actual)
def test_up_with_drone_name_should_return_correct_command(self): actual = generate_commands( """ main() { DRONE1.takeoff(); DRONE1.up(1); DRONE1.land(); } """, drone_config_map={"DRONE1": DefaultDroneConfig()}) expected = [ SingleDroneCommand("DRONE1", Command.takeoff()), SingleDroneCommand("DRONE1", Command.up(1)), SingleDroneCommand("DRONE1", Command.land()) ] self.assertEqual(expected, actual)
def visitUp(self, ctx: xDroneParser.UpContext) -> None: exprs = [self.visit(expr) for expr in ctx.expr()] if ctx.DOT(): drone_expr, expr = exprs else: drone_expr, expr = None, exprs[0] if expr.type != Type.int() and expr.type != Type.decimal(): raise CompileError( "Expression {} should have type int or decimal, but is {}". format(expr, expr.type)) drone_name = self._get_drone_name(drone_expr) self._get_latest_commands().append( SingleDroneCommand(drone_name, Command.up(expr.value)))
def test_to_command_str(self): parallel_commands = ParallelDroneCommands() parallel_commands.add([]) parallel_commands.add([ SingleDroneCommand("abc", Command.takeoff()), SingleDroneCommand("abc", Command.up(1)) ]) self.assertEqual("{ } || { abc.takeoff(); abc.up(1); };", parallel_commands.to_command_str()) outer_parallel_commands = ParallelDroneCommands() outer_parallel_commands.add([]) outer_parallel_commands.add([parallel_commands]) self.assertEqual("{ } || { { } || { abc.takeoff(); abc.up(1); }; };", outer_parallel_commands.to_command_str())
def test_call_procedure_should_run_commands(self): commands = generate_commands(""" procedure proc(int i, int j) { up(i + j); down(i); } main () { takeoff(); proc(100, 200); land(); } """) expected_commands = [SingleDroneCommand("DEFAULT", Command.takeoff()), SingleDroneCommand("DEFAULT", Command.up(300)), SingleDroneCommand("DEFAULT", Command.down(100)), SingleDroneCommand("DEFAULT", Command.land())] self.assertEqual(expected_commands, commands)
def test_execute_single_drone_command(self): single_commands = [ SingleDroneCommand("name1", Command.takeoff()), SingleDroneCommand("name1", Command.land()), SingleDroneCommand("name2", Command.up(1.1)), SingleDroneCommand("name2", Command.down(1.2)), SingleDroneCommand("name2", Command.left(1.3)), SingleDroneCommand("name2", Command.right(1.4)), SingleDroneCommand("name2", Command.forward(1.5)), SingleDroneCommand("name2", Command.backward(1.6)), SingleDroneCommand("name2", Command.rotate_left(91)), SingleDroneCommand("name2", Command.rotate_right(92)), SingleDroneCommand("name2", Command.wait(1)) ] with patch( 'xdrone.command_converters.dji_tello_edu_drone_executor.FlyTello', return_value=self.fly_tello): executor = DJITelloEduExecutor(self.name_id_map) executor.execute_drone_commands(single_commands) calls = [ call.wait_sync(), call.takeoff(tello=1), call.wait_sync(), call.land(tello=1), call.wait_sync(), call.up(110, tello=2), call.wait_sync(), call.down(120, tello=2), call.wait_sync(), call.left(130, tello=2), call.wait_sync(), call.right(140, tello=2), call.wait_sync(), call.forward(150, tello=2), call.wait_sync(), call.back(160, tello=2), call.wait_sync(), call.rotate_ccw(91, tello=2), call.wait_sync(), call.rotate_cw(92, tello=2), call.wait_sync(), call.pause(1) ] self.fly.assert_has_calls(calls)
def test_call_function_should_run_commands_and_return(self): commands = generate_commands(""" function func(int i, int j) return int { up(i + j); down(i); return i + j; } main () { takeoff(); forward(func(100, 200)); land(); } """) expected_commands = [SingleDroneCommand("DEFAULT", Command.takeoff()), SingleDroneCommand("DEFAULT", Command.up(300)), SingleDroneCommand("DEFAULT", Command.down(100)), SingleDroneCommand("DEFAULT", Command.forward(300)), SingleDroneCommand("DEFAULT", Command.land())] self.assertEqual(expected_commands, commands)
def test_check_single_drone_command_other_command_when_not_taken_off_should_give_error( self): commands = [ Command.land(), Command.up(1), Command.down(1), Command.left(1), Command.right(1), Command.forward(1), Command.backward(1), Command.rotate_left(90), Command.rotate_right(90) ] for command in commands: with self.assertRaises(SafetyCheckError) as context: drone_commands = [SingleDroneCommand("DRONE1", command)] BoundaryChecker(self.boundary_config).check( drone_commands, self.state_updater_map) self.assertTrue( "'{}' command used when drone 'DRONE1' has not been taken off". format(command.opcode) in str(context.exception))
def test_convert_command(self): commands = [ SingleDroneCommand("DRONE1", Command.takeoff()), SingleDroneCommand("DRONE1", Command.up(1)), SingleDroneCommand("DRONE1", Command.down(1)), SingleDroneCommand("DRONE1", Command.left(1)), SingleDroneCommand("DRONE1", Command.right(1)), SingleDroneCommand("DRONE1", Command.forward(1)), SingleDroneCommand("DRONE1", Command.backward(1)), SingleDroneCommand("DRONE1", Command.rotate_left(1)), SingleDroneCommand("DRONE1", Command.rotate_right(1)), ParallelDroneCommands( [[ ParallelDroneCommands( [[], [SingleDroneCommand("DRONE1", Command.up(1))]]) ], [ SingleDroneCommand("DRONE2", Command.takeoff()), SingleDroneCommand("DRONE2", Command.land()) ]]), SingleDroneCommand("DRONE1", Command.land()), SingleDroneCommand("DRONE1", Command.wait(1)) ] drone_config_map = { "DRONE1": DroneConfig((1, 2, 3), 1, 90, 2), "DRONE2": DroneConfig((0, 0, 0), 1, 90, 1) } expected = { "config": [{ "name": "DRONE1", "init_pos": [1, 2, 3], "speed": 1, "rotate_speed": 90, "takeoff_height": 2 }, { "name": "DRONE2", "init_pos": [0, 0, 0], "speed": 1, "rotate_speed": 90, "takeoff_height": 1 }], "commands": [{ "type": "single", "name": "DRONE1", "action": "takeoff", "values": [] }, { "type": "single", "name": "DRONE1", "action": "up", "values": [1] }, { "type": "single", "name": "DRONE1", "action": "down", "values": [1] }, { "type": "single", "name": "DRONE1", "action": "left", "values": [1] }, { "type": "single", "name": "DRONE1", "action": "right", "values": [1] }, { "type": "single", "name": "DRONE1", "action": "forward", "values": [1] }, { "type": "single", "name": "DRONE1", "action": "backward", "values": [1] }, { "type": "single", "name": "DRONE1", "action": "rotate_left", "values": [1] }, { "type": "single", "name": "DRONE1", "action": "rotate_right", "values": [1] }, { "type": "parallel", "branches": [[{ "type": "parallel", "branches": [[], [{ "type": "single", "name": "DRONE1", "action": "up", "values": [1] }]] }], [{ "type": "single", "name": "DRONE2", "action": "takeoff", "values": [] }, { "type": "single", "name": "DRONE2", "action": "land", "values": [] }]] }, { "type": "single", "name": "DRONE1", "action": "land", "values": [] }, { "type": "single", "name": "DRONE1", "action": "wait", "values": [1] }] } actual = SimulationConverter().convert(commands, drone_config_map) self.assertEqual(expected, json.loads(zlib.decompress(b64decode(actual))))
def _update_states(self, single_drone_command: SingleDroneCommand, state_updaters: Dict[str, StateUpdater], drone_trajectory_map: Dict[str, List[State]], drones_involved: Set[str]) -> float: time_interval = self.collision_config.time_interval_seconds drone_name = single_drone_command.drone_name command = single_drone_command.command if command.opcode == "takeoff": takeoff_distance = self.drone_config_map[ drone_name].takeoff_height_meters new_drone_command = SingleDroneCommand( drone_name, Command.up(takeoff_distance)) return self._update_states(new_drone_command, state_updaters, drone_trajectory_map, drones_involved) if command.opcode == "land": old_state = drone_trajectory_map[drone_name][-1] land_distance = old_state.z_meters new_drone_command = SingleDroneCommand(drone_name, Command.down(land_distance)) return self._update_states(new_drone_command, state_updaters, drone_trajectory_map, drones_involved) if command.opcode == "wait": seconds, = command.operands old_state = drone_trajectory_map[drone_name][-1] if seconds <= time_interval: new_state = state_updaters[drone_name].update( Command.wait(seconds), old_state) drone_trajectory_map[drone_name].append(new_state) for name in drones_involved.difference({drone_name}): state = drone_trajectory_map[name][-1] drone_trajectory_map[name].append( state.copy_and_set_time_used_seconds( state.time_used_seconds + seconds)) return seconds else: new_state = state_updaters[drone_name].update( Command.wait(time_interval), old_state) drone_trajectory_map[drone_name].append(new_state) for name in drones_involved.difference({drone_name}): state = drone_trajectory_map[name][-1] drone_trajectory_map[name].append( state.copy_and_set_time_used_seconds( state.time_used_seconds + time_interval)) new_drone_command = SingleDroneCommand( drone_name, Command.wait(seconds - time_interval)) return time_interval + self._update_states( new_drone_command, state_updaters, drone_trajectory_map, drones_involved) if command.opcode in ["rotate_left", "rotate_right"]: degrees, = command.operands old_state = drone_trajectory_map[drone_name][-1] rotate_speed = self.drone_config_map[drone_name].rotate_speed_dps seconds = degrees / rotate_speed if seconds <= time_interval: new_state = state_updaters[drone_name].update( Command(command.opcode, [rotate_speed * seconds]), old_state) drone_trajectory_map[drone_name].append(new_state) for name in drones_involved.difference({drone_name}): state = drone_trajectory_map[name][-1] drone_trajectory_map[name].append( state.copy_and_set_time_used_seconds( state.time_used_seconds + seconds)) return seconds else: new_state = state_updaters[drone_name].update( Command(command.opcode, [rotate_speed * time_interval]), old_state) drone_trajectory_map[drone_name].append(new_state) for name in drones_involved.difference({drone_name}): state = drone_trajectory_map[name][-1] drone_trajectory_map[name].append( state.copy_and_set_time_used_seconds( state.time_used_seconds + time_interval)) new_drone_command = SingleDroneCommand( drone_name, Command(command.opcode, [degrees - rotate_speed * time_interval])) return time_interval + self._update_states( new_drone_command, state_updaters, drone_trajectory_map, drones_involved) if command.opcode in [ "up", "down", "left", "right", "forward", "backward" ]: meters, = command.operands old_state = drone_trajectory_map[drone_name][-1] speed = self.drone_config_map[drone_name].speed_mps seconds = meters / speed if seconds <= time_interval: new_state = state_updaters[drone_name].update( Command(command.opcode, [speed * seconds]), old_state) drone_trajectory_map[drone_name].append(new_state) for name in drones_involved.difference({drone_name}): state = drone_trajectory_map[name][-1] drone_trajectory_map[name].append( state.copy_and_set_time_used_seconds( state.time_used_seconds + seconds)) return seconds else: new_state = state_updaters[drone_name].update( Command(command.opcode, [speed * time_interval]), old_state) drone_trajectory_map[drone_name].append(new_state) for name in drones_involved.difference({drone_name}): state = drone_trajectory_map[name][-1] drone_trajectory_map[name].append( state.copy_and_set_time_used_seconds( state.time_used_seconds + time_interval)) new_drone_command = SingleDroneCommand( drone_name, Command(command.opcode, [meters - speed * time_interval])) return time_interval + self._update_states( new_drone_command, state_updaters, drone_trajectory_map, drones_involved)
def test_to_command_str(self): drone_command = SingleDroneCommand("abc", Command.takeoff()) self.assertEqual("abc.takeoff();", drone_command.to_command_str()) drone_command = SingleDroneCommand("abc", Command.up(1)) self.assertEqual("abc.up(1);", drone_command.to_command_str())
def test_up(self): self.assertEqual(Command.up(1), Command.up(1)) self.assertEqual("up", Command.up(1).opcode) self.assertEqual([1], Command.up(1).operands) self.assertEqual("Command: { opcode: up, operands: [1] }", str(Command.up(1)))
def test_dji_tello_executor_should_send_correct_message(self) -> None: mocked_socket = Mock() mocked_socket.recvfrom = Mock(return_value=(b'ok', ('192.168.10.1', 8889))) mocked_socket.sendto = Mock() commands = [ Command.takeoff(), Command.up(1), Command.down(1), Command.left(1), Command.right(1), Command.forward(1), Command.backward(1), Command.rotate_left(90), Command.rotate_right(90), Command.wait(0), Command.land() ] executor = DJITelloExecutor() executor.sock.close() executor.sock = mocked_socket with self.assertLogs(logging.getLogger()) as log: executor.execute_commands(commands) calls = [ call(b"command", ('192.168.10.1', 8889)), call(b"takeoff", ('192.168.10.1', 8889)), call(b"up 100", ('192.168.10.1', 8889)), call(b"down 100", ('192.168.10.1', 8889)), call(b"left 100", ('192.168.10.1', 8889)), call(b"right 100", ('192.168.10.1', 8889)), call(b"forward 100", ('192.168.10.1', 8889)), call(b"back 100", ('192.168.10.1', 8889)), call(b"ccw 90", ('192.168.10.1', 8889)), call(b"cw 90", ('192.168.10.1', 8889)), call(b"land", ('192.168.10.1', 8889)) ] mocked_socket.sendto.assert_has_calls(calls) expected_log = [ "INFO:root:sent message: b'command'", "INFO:root:received response: ok", "INFO:root:sent message: b'takeoff'", "INFO:root:received response: ok", "INFO:root:sent message: b'up 100'", "INFO:root:received response: ok", "INFO:root:sent message: b'down 100'", "INFO:root:received response: ok", "INFO:root:sent message: b'left 100'", "INFO:root:received response: ok", "INFO:root:sent message: b'right 100'", "INFO:root:received response: ok", "INFO:root:sent message: b'forward 100'", "INFO:root:received response: ok", "INFO:root:sent message: b'back 100'", "INFO:root:received response: ok", "INFO:root:sent message: b'ccw 90'", "INFO:root:received response: ok", "INFO:root:sent message: b'cw 90'", "INFO:root:received response: ok", "INFO:root:sent message: b'land'", "INFO:root:received response: ok" ] self.assertEqual(expected_log, log.output)