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 the Stats object to store statistics on command request and response

  • tello.py defines the Tello object to abstract and represent the Tello drone

  • command.txt stores the commands that will be sent to the Tello drone

  • app.py is 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

Code

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

Code

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

Code

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)

Code

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