├── README.md └── tello.py /README.md: -------------------------------------------------------------------------------- 1 | DISCLAIMER 2 | 3 | I do not own a Tello. This code and documentation is based on the Tello SDK 4 | documentation as of 3/19/2018. 5 | 6 | WHAT THE BLEEP IS THIS 7 | 8 | A Python interface for the Ryze Tello drone. 9 | 10 | The tello module provides a Tello class, which interacts with the Tello API. 11 | 12 | The Tello has an IP of 192.168.10.1. The device sending commands must be 13 | connected to the Tello WiFi network and have an IP in the 192.168.10.0/24 14 | range. 15 | 16 | CREATE A MAGIC TELLO OBJECT 17 | 18 | Tello objects require a minimum of 2 parameters to initialize, the local IP 19 | address and port to bind. 20 | 21 | Ex. drone = tello.Tello('192.168.10.2', 8888) 22 | 23 | Methods that require distance or speed parameters expect feet or MPH. Include 24 | parameter imperial=False for meters and KPH. 25 | 26 | Ex. drone = tello.Tello('192.168.10.2', 8888, imperial=False) 27 | 28 | If you send a command to the Tello and it doesn't respond within .3 seconds, a 29 | RuntimeError is raised. You may specify the number of seconds to wait with the 30 | timeout parameter. 31 | 32 | Ex. drone = tello.Tello('192.168.10.2', 8888, imperial=False, timeout=.5) 33 | 34 | When you initialize a Tello object, it attempts to connect to the Tello and 35 | enter command mode. If this fails, a RuntimeError is raised. 36 | 37 | DO VARIOUS THINGS 38 | 39 | Once initialized, a number of methods are available to send commands to the 40 | Tello. It will respond with 'OK', 'FALSE' or a numeric value, which the methods 41 | return. 42 | 43 | These methods do what you'd expect. Responses are 'OK' or 'FALSE'. 44 | 45 | Tello.takeoff() 46 | Tello.land() 47 | 48 | Methods that perform vertical or horizontal movements require a single 49 | parameter, distance. Responses are 'OK' or 'FALSE'. 50 | 51 | The unit of distance is feet or meters. The SDK accepts distances of 1 to 500 52 | centimeters. Realistically, this translates to .1 - 5 meters or .7 - 16.4 feet. 53 | 54 | Tello.move_forward(distance) 55 | 56 | Tello.move_backward(distance) 57 | 58 | Tello.move_right(distance) 59 | 60 | Tello.move_left(distance) 61 | 62 | Tello.move_up(distance) 63 | 64 | Tello.move_down(distance) 65 | 66 | Methods that rotate require a single parameter, degrees. The SDK accepts values 67 | from 1 to 360. Responses are 'OK' or 'FALSE'. 68 | 69 | Tello.rotate_cw(degrees) 70 | 71 | Tello.rotate_ccw(degrees) 72 | 73 | The method to set speed requires a single parameter, speed. Responses are 'OK' 74 | or 'FALSE'. 75 | 76 | The unit of speed is KPH or MPH. The SDK accepts speeds from 1 to 100 77 | centimeters/second. Realistically, this translates to .1 to 3.6 KPH or .1 to 78 | 2.2 MPH. 79 | 80 | Tello.set_speed(speed) 81 | 82 | The method to flip requires a single parameter, direction. The SDK accepts 'l', 83 | 'r', 'f', 'b', 'lf', 'lb', 'rf' or 'rb'. Responses are 'OK' or 'FALSE'. 84 | 85 | Tello.flip(direction) 86 | 87 | Methods that retrieve information from the Tello take no parameters. Responses 88 | are numeric values. 89 | 90 | Get current speed in KPH or MPH: 91 | Tello.get_speed() 92 | 93 | Get percent battery life remaining: 94 | Tello.get_battery() 95 | 96 | Get elapsed flight time in seconds: 97 | Tello.get_flight_time() 98 | 99 | DO THE HOKEY POKEY 100 | 101 | Put it all together, and you might do something like this. 102 | 103 | import tello 104 | import time 105 | 106 | drone = tello.Tello('192.168.10.2', 8888) 107 | 108 | drone.takeoff() 109 | time.sleep(5) 110 | 111 | drone.set_speed(2) 112 | time.sleep(1) 113 | 114 | drone.move_up(3) 115 | time.sleep(5) 116 | 117 | drone.move_forward(10) 118 | time.sleep(10) 119 | 120 | drone.rotate_cw(180) 121 | time.sleep(5) 122 | 123 | drone.move_forward(10) 124 | time.sleep(10) 125 | 126 | drone.land() 127 | 128 | print 'Flight time: %s' % drone.get_flight_time() 129 | -------------------------------------------------------------------------------- /tello.py: -------------------------------------------------------------------------------- 1 | """License. 2 | 3 | Copyright 2018 Todd Mueller 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | 18 | """ 19 | 20 | import socket 21 | import threading 22 | import time 23 | import traceback 24 | 25 | class Tello: 26 | """Wrapper to simply interactions with the Ryze Tello drone.""" 27 | 28 | def __init__(self, local_ip, local_port, imperial=True, command_timeout=.3, tello_ip='192.168.10.1', tello_port=8889): 29 | """Binds to the local IP/port and puts the Tello into command mode. 30 | 31 | Args: 32 | local_ip (str): Local IP address to bind. 33 | local_port (int): Local port to bind. 34 | imperial (bool): If True, speed is MPH and distance is feet. 35 | If False, speed is KPH and distance is meters. 36 | command_timeout (int|float): Number of seconds to wait for a response to a command. 37 | tello_ip (str): Tello IP. 38 | tello_port (int): Tello port. 39 | 40 | Raises: 41 | RuntimeError: If the Tello rejects the attempt to enter command mode. 42 | 43 | """ 44 | 45 | self.abort_flag = False 46 | self.command_timeout = command_timeout 47 | self.imperial = imperial 48 | self.response = None 49 | self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 50 | self.tello_address = (tello_ip, tello_port) 51 | 52 | self.socket.bind((local_ip, local_port)) 53 | 54 | self.receive_thread = threading.Thread(target=self._receive_thread) 55 | self.receive_thread.daemon=True 56 | 57 | self.receive_thread.start() 58 | 59 | if self.send_command('command') != 'OK': 60 | raise RuntimeError('Tello rejected attempt to enter command mode') 61 | 62 | def __del__(self): 63 | """Closes the local socket.""" 64 | 65 | self.socket.close() 66 | 67 | def _receive_thread(self): 68 | """Listens for responses from the Tello. 69 | 70 | Runs as a thread, sets self.response to whatever the Tello last returned. 71 | 72 | """ 73 | while True: 74 | try: 75 | self.response, ip = self.socket.recvfrom(256) 76 | except Exception: 77 | break 78 | 79 | def flip(self, direction): 80 | """Flips. 81 | 82 | Args: 83 | direction (str): Direction to flip, 'l', 'r', 'f', 'b', 'lb', 'lf', 'rb' or 'rf'. 84 | 85 | Returns: 86 | str: Response from Tello, 'OK' or 'FALSE'. 87 | 88 | """ 89 | 90 | return self.send_command('flip %s' % direction) 91 | 92 | def get_battery(self): 93 | """Returns percent battery life remaining. 94 | 95 | Returns: 96 | int: Percent battery life remaining. 97 | 98 | """ 99 | 100 | battery = self.send_command('battery?') 101 | 102 | try: 103 | battery = int(battery) 104 | except: 105 | pass 106 | 107 | return battery 108 | 109 | 110 | def get_flight_time(self): 111 | """Returns the number of seconds elapsed during flight. 112 | 113 | Returns: 114 | int: Seconds elapsed during flight. 115 | 116 | """ 117 | 118 | flight_time = self.send_command('time?') 119 | 120 | try: 121 | flight_time = int(flight_time) 122 | except: 123 | pass 124 | 125 | return flight_time 126 | 127 | def get_speed(self): 128 | """Returns the current speed. 129 | 130 | Returns: 131 | int: Current speed in KPH or MPH. 132 | 133 | """ 134 | 135 | speed = self.send_command('speed?') 136 | 137 | try: 138 | speed = float(speed) 139 | 140 | if self.imperial is True: 141 | speed = round((speed / 44.704), 1) 142 | else: 143 | speed = round((speed / 27.7778), 1) 144 | except: 145 | pass 146 | 147 | return speed 148 | 149 | def land(self): 150 | """Initiates landing. 151 | 152 | Returns: 153 | str: Response from Tello, 'OK' or 'FALSE'. 154 | 155 | """ 156 | 157 | return self.send_command('land') 158 | 159 | def move(self, direction, distance): 160 | """Moves in a direction for a distance. 161 | 162 | This method expects meters or feet. The Tello API expects distances 163 | from 20 to 500 centimeters. 164 | 165 | Metric: .1 to 5 meters 166 | Imperial: .7 to 16.4 feet 167 | 168 | Args: 169 | direction (str): Direction to move, 'forward', 'back', 'right' or 'left'. 170 | distance (int|float): Distance to move. 171 | 172 | Returns: 173 | str: Response from Tello, 'OK' or 'FALSE'. 174 | 175 | """ 176 | 177 | distance = float(distance) 178 | 179 | if self.imperial is True: 180 | distance = int(round(distance * 30.48)) 181 | else: 182 | distance = int(round(distance * 100)) 183 | 184 | return self.send_command('%s %s' % (direction, distance)) 185 | 186 | def move_backward(self, distance): 187 | """Moves backward for a distance. 188 | 189 | See comments for Tello.move(). 190 | 191 | Args: 192 | distance (int): Distance to move. 193 | 194 | Returns: 195 | str: Response from Tello, 'OK' or 'FALSE'. 196 | 197 | """ 198 | 199 | return self.move('back', distance) 200 | 201 | def move_down(self, distance): 202 | """Moves down for a distance. 203 | 204 | See comments for Tello.move(). 205 | 206 | Args: 207 | distance (int): Distance to move. 208 | 209 | Returns: 210 | str: Response from Tello, 'OK' or 'FALSE'. 211 | 212 | """ 213 | 214 | return self.move('down', distance) 215 | 216 | def move_forward(self, distance): 217 | """Moves forward for a distance. 218 | 219 | See comments for Tello.move(). 220 | 221 | Args: 222 | distance (int): Distance to move. 223 | 224 | Returns: 225 | str: Response from Tello, 'OK' or 'FALSE'. 226 | 227 | """ 228 | return self.move('forward', distance) 229 | 230 | def move_left(self, distance): 231 | """Moves left for a distance. 232 | 233 | See comments for Tello.move(). 234 | 235 | Args: 236 | distance (int): Distance to move. 237 | 238 | Returns: 239 | str: Response from Tello, 'OK' or 'FALSE'. 240 | 241 | """ 242 | return self.move('left', distance) 243 | 244 | def move_right(self, distance): 245 | """Moves right for a distance. 246 | 247 | See comments for Tello.move(). 248 | 249 | Args: 250 | distance (int): Distance to move. 251 | 252 | """ 253 | return self.move('right', distance) 254 | 255 | def move_up(self, distance): 256 | """Moves up for a distance. 257 | 258 | See comments for Tello.move(). 259 | 260 | Args: 261 | distance (int): Distance to move. 262 | 263 | Returns: 264 | str: Response from Tello, 'OK' or 'FALSE'. 265 | 266 | """ 267 | 268 | return self.move('up', distance) 269 | 270 | def send_command(self, command): 271 | """Sends a command to the Tello and waits for a response. 272 | 273 | If self.command_timeout is exceeded before a response is received, 274 | a RuntimeError exception is raised. 275 | 276 | Args: 277 | command (str): Command to send. 278 | 279 | Returns: 280 | str: Response from Tello. 281 | 282 | Raises: 283 | RuntimeError: If no response is received within self.timeout seconds. 284 | 285 | """ 286 | 287 | self.abort_flag = False 288 | timer = threading.Timer(self.command_timeout, self.set_abort_flag) 289 | 290 | self.socket.sendto(command.encode('utf-8'), self.tello_address) 291 | 292 | timer.start() 293 | 294 | while self.response is None: 295 | if self.abort_flag is True: 296 | raise RuntimeError('No response to command') 297 | 298 | timer.cancel() 299 | 300 | response = self.response.decode('utf-8') 301 | self.response = None 302 | 303 | return response 304 | 305 | def set_abort_flag(self): 306 | """Sets self.abort_flag to True. 307 | 308 | Used by the timer in Tello.send_command() to indicate to that a response 309 | timeout has occurred. 310 | 311 | """ 312 | 313 | self.abort_flag = True 314 | 315 | def set_speed(self, speed): 316 | """Sets speed. 317 | 318 | This method expects KPH or MPH. The Tello API expects speeds from 319 | 1 to 100 centimeters/second. 320 | 321 | Metric: .1 to 3.6 KPH 322 | Imperial: .1 to 2.2 MPH 323 | 324 | Args: 325 | speed (int|float): Speed. 326 | 327 | Returns: 328 | str: Response from Tello, 'OK' or 'FALSE'. 329 | 330 | """ 331 | 332 | speed = float(speed) 333 | 334 | if self.imperial is True: 335 | speed = int(round(speed * 44.704)) 336 | else: 337 | speed = int(round(speed * 27.7778)) 338 | 339 | return self.send_command('speed %s' % speed) 340 | 341 | def takeoff(self): 342 | """Initiates take-off. 343 | 344 | Returns: 345 | str: Response from Tello, 'OK' or 'FALSE'. 346 | 347 | """ 348 | 349 | return self.send_command('takeoff') 350 | 351 | def rotate_cw(self, degrees): 352 | """Rotates clockwise. 353 | 354 | Args: 355 | degrees (int): Degrees to rotate, 1 to 360. 356 | 357 | Returns: 358 | str: Response from Tello, 'OK' or 'FALSE'. 359 | 360 | """ 361 | 362 | return self.send_command('cw %s' % degrees) 363 | 364 | def rotate_ccw(self, degrees): 365 | """Rotates counter-clockwise. 366 | 367 | Args: 368 | degrees (int): Degrees to rotate, 1 to 360. 369 | 370 | Returns: 371 | str: Response from Tello, 'OK' or 'FALSE'. 372 | 373 | """ 374 | return self.send_command('ccw %s' % degrees) 375 | --------------------------------------------------------------------------------