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.pydefines the- Statsobject to store statistics on command request and response
- tello.pydefines the- Telloobject to abstract and represent the Tello drone
- command.txtstores the commands that will be sent to the Tello drone
- app.pyis the application program, or the- driver, 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