7. Python, Auto Flight Path
The code below will use Python to fly your Tello drone on a pre-determined flight path. There are four main Python files.
stats.py
defines theStats
object to store statistics on command request and responsetello.py
defines theTello
object to abstract and represent the Tello dronecommand.txt
stores the commands that will be sent to the Tello droneapp.py
is the application program, or thedriver
, that will be executed as an entrypoint to start sending commands
7.1. Stats
The Stats
class is used to log command requests and responses.
1from datetime import datetime
2
3class Stats(object):
4 def __init__(self, command, id):
5 """
6 Constructor.
7 :param command: The command sent.
8 :param id: The identifier.
9 """
10 self.command = command
11 self.response = None
12 self.id = id
13
14 self.start_time = datetime.now()
15 self.end_time = None
16 self.duration = None
17
18 def add_response(self, response):
19 """
20 Adds the response.
21 :param response: Response.
22 :return: None.
23 """
24 self.response = response
25 self.end_time = datetime.now()
26 self.duration = self.get_duration()
27
28 def get_duration(self):
29 """
30 Gets the duration.
31 :return: Duration.
32 """
33 diff = self.end_time - self.start_time
34 return diff.total_seconds()
35
36 def print_stats(self):
37 """
38 Prints the statistics.
39 :return: None.
40 """
41 print(f'\nid {self.id}')
42 print(f'command: {self.command}')
43 print(f'response: {self.response}')
44 print(f'start_time: {self.start_time}')
45 print(f'end_time: {self.end_time}')
46 print(f'duration: {self.duration}')
47
48 def got_response(self):
49 """
50 Returns a boolean if a response was received.
51 :return: Boolean.
52 """
53
54 if self.response is None:
55 return False
56 else:
57 return True
58
59 def return_stats(self):
60 """
61 Returns the statistics.
62 :return: Statistics.
63 """
64 str = ''
65 str += f'\nid: {self.id}\n'
66 str += f'command: {self.command}\n'
67 str += f'response: {self.response}\n'
68 str += f'start_time: {self.start_time}\n'
69 str += f'end_time: {self.end_time}\n'
70 str += f'duration: {self.duration}\n'
71 return str
7.2. Tello
The Tello
class is used to abstract the drone.
1import socket
2import threading
3import time
4from stats import Stats
5
6class Tello(object):
7 def __init__(self):
8 """
9 Constructor.
10 """
11 self.local_ip = ''
12 self.local_port = 8889
13 self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
14 self.socket.bind((self.local_ip, self.local_port))
15
16 # thread for receiving cmd ack
17 self.receive_thread = threading.Thread(target=self._receive_thread)
18 self.receive_thread.daemon = True
19 self.receive_thread.start()
20
21 self.tello_ip = '192.168.10.1'
22 self.tello_port = 8889
23 self.tello_address = (self.tello_ip, self.tello_port)
24 self.log = []
25
26 self.MAX_TIME_OUT = 15.0
27
28 def send_command(self, command):
29 """
30 Send a command to the ip address. Will be blocked until
31 the last command receives an 'OK'.
32 If the command fails (either b/c time out or error),
33 will try to resend the command
34 :param command: (str) the command to send
35 :param ip: (str) the ip of Tello
36 :return: The latest command response
37 """
38 self.log.append(Stats(command, len(self.log)))
39
40 self.socket.sendto(command.encode('utf-8'), self.tello_address)
41 print(f'sending command: {command} to {self.tello_ip}')
42
43 start = time.time()
44 while not self.log[-1].got_response():
45 now = time.time()
46 diff = now - start
47 if diff > self.MAX_TIME_OUT:
48 print(f'Max timeout exceeded... command {command}')
49 return
50 print(f'Done!!! sent command: {command} to {self.tello_ip}')
51
52 def _receive_thread(self):
53 """
54 Listen to responses from the Tello.
55 Runs as a thread, sets self.response to whatever the Tello last returned.
56 """
57 while True:
58 try:
59 self.response, ip = self.socket.recvfrom(1024)
60 print(f'from {ip}: {self.response}')
61
62 self.log[-1].add_response(self.response)
63 except Exception as exc:
64 print(f'Caught exception socket.error : {exc}')
65
66 def on_close(self):
67 """
68 On close.
69 :returns: None.
70 """
71 pass
72
73 def get_log(self):
74 """
75 Gets the logs.
76 :returns: Logs.
77 """
78 return self.log
7.3. Commands
The command.txt
file stores the sequence of string commands that we will send to the drone. Note how we have to put a delay
in between commands? This delay gives time for the drone and your program/computer to send, receive and process the data.
1command
2takeoff
3delay 5
4flip f
5delay 2
6flip b
7delay 2
8flip b
9delay 2
10flip f
11delay 3
12land
7.4. Application
The app.py
file is the application program entry point.
1from tello import Tello
2import sys
3from datetime import datetime
4import time
5import argparse
6
7
8def parse_args(args):
9 """
10 Parses arguments.
11 :param args: Arguments.
12 :return: Parsed arguments.
13 """
14 parser = argparse.ArgumentParser('Tello Flight Commander',
15 epilog='One-Off Coder https://www.oneoffcoder.com')
16
17 parser.add_argument('-f', '--file', help='command file', required=True)
18 return parser.parse_args(args)
19
20
21def start(file_name):
22 """
23 Starts sending commands to Tello.
24 :param file_name: File name where commands are located.
25 :return: None.
26 """
27 start_time = str(datetime.now())
28
29 with open(file_name, 'r') as f:
30 commands = f.readlines()
31
32 tello = Tello()
33 for command in commands:
34 if command != '' and command != '\n':
35 command = command.rstrip()
36
37 if command.find('delay') != -1:
38 sec = float(command.partition('delay')[2])
39 print(f'delay {sec}')
40 time.sleep(sec)
41 pass
42 else:
43 tello.send_command(command)
44
45 with open(f'log/{start_time}.txt', 'w') as out:
46 log = tello.get_log()
47
48 for stat in log:
49 stat.print_stats()
50 s = stat.return_stats()
51 out.write(s)
52
53
54if __name__ == '__main__':
55 args = parse_args(sys.argv[1:])
56 file_name = args.file
57 start(file_name)
7.5. Running the application
To run the program, type in the following from a terminal. The program will output the logs to the directory log/
.
python app.py -f command.txt