├── LICENSE ├── README.md ├── setup.py └── tello ├── __init__.py ├── demo.py ├── frame2html.py ├── index.html ├── stats.py └── tello.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 C灵C 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tello-Python 2 | Control DJI Tello drone with python 3 | 4 | 5 | ## Installation 6 | pip install tello-python 7 | 8 | 9 | ## How to import 10 | import tello 11 | 12 | drone = tello.Tello() 13 | 14 | 15 | ## Examples 16 | import tello 17 | 18 | drone = tello.Tello() 19 | 20 | drone.takeoff() 21 | 22 | drone.forward(100) 23 | 24 | drone.cw(90) 25 | 26 | drone.flip('r') 27 | 28 | drone.streamon() 29 | 30 | drone.land() 31 | 32 | 33 | ### Distance 34 | Required. The distance to fly forward in cm. Has to be between 20 and 500. 35 | 36 | 37 | ### Degrees 38 | Required. The number of degrees to rotate. Has to be between 1 and 360. 39 | 40 | 41 | ## More 42 | For more commands, please refer to the methods and comments in the source code tello.py file -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | with open('README.md', 'r', encoding='utf-8') as fh: 4 | long_description = fh.read() 5 | 6 | setuptools.setup( 7 | name='tello-python', 8 | version='1.1.6', 9 | author='C灵C', 10 | author_email='c0c@cocpy.com', 11 | description='Control DJI Tello drone with Python3', 12 | long_description=long_description, 13 | long_description_content_type='text/markdown', 14 | url='https://github.com/cocpy/Tello-Python', 15 | packages=setuptools.find_packages(), 16 | install_requires=[ 17 | 'opencv-python', 'flask', 'paddlepaddle', 'paddlehub' 18 | ], 19 | classifiers=[ 20 | 'Programming Language :: Python :: 3', 21 | 'License :: OSI Approved :: MIT License', 22 | 'Operating System :: OS Independent', 23 | ], 24 | ) -------------------------------------------------------------------------------- /tello/__init__.py: -------------------------------------------------------------------------------- 1 | __title__ = 'tello-python' 2 | __author__ = 'C灵C' 3 | __liscence__ = 'MIT' 4 | __copyright__ = 'Copyright 2021 C灵C' 5 | __version__ = '1.1.6' 6 | __all__ = ['tello', 'stats'] 7 | 8 | from .tello import Tello 9 | from .stats import Stats -------------------------------------------------------------------------------- /tello/demo.py: -------------------------------------------------------------------------------- 1 | from tello import tello 2 | 3 | 4 | drone = tello.Tello() 5 | 6 | # 起飞 7 | drone.takeoff() 8 | 9 | # 前进100cm 10 | drone.forward(100) 11 | 12 | # 旋转90° 13 | drone.cw(90) 14 | 15 | # 左翻滚 16 | drone.flip('l') 17 | 18 | # 打开视频流 19 | drone.streamon() 20 | 21 | # 降落 22 | drone.land() 23 | -------------------------------------------------------------------------------- /tello/frame2html.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, render_template, Response 2 | import cv2 3 | 4 | 5 | class VideoCamera(object): 6 | def __init__(self, frame): 7 | # 通过opencv获取实时视频流 8 | self.video = frame 9 | 10 | def __del__(self): 11 | self.video.release() 12 | 13 | def get_frame(self): 14 | success, image = self.video.read() 15 | # 因为opencv读取的图片并非jpeg格式,因此要用motion JPEG模式需要先将图片转码成jpg格式图片 16 | ret, jpeg = cv2.imencode('.jpg', image) 17 | return jpeg.tobytes() 18 | 19 | 20 | app = Flask(__name__) 21 | 22 | 23 | @app.route('/') # 主页 24 | def index(): 25 | # jinja2模板,具体格式保存在index.html文件中 26 | return render_template('index.html') 27 | 28 | 29 | def gen(camera): 30 | while True: 31 | frame = camera.get_frame() 32 | # 使用generator函数输出视频流, 每次请求输出的content类型是image/jpeg 33 | yield (b'--frame\r\n' 34 | b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n\r\n') 35 | 36 | 37 | @app.route('/video_feed') # 这个地址返回视频流响应 38 | def video_feed(): 39 | return Response(gen(VideoCamera()), mimetype='multipart/x-mixed-replace; boundary=frame') 40 | 41 | 42 | def run_app(): 43 | app.run(host='0.0.0.0', debug=True, port=5000) 44 | 45 | 46 | if __name__ == '__main__': 47 | run_app() 48 | -------------------------------------------------------------------------------- /tello/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Tello Video 4 | 5 | 6 |

Tello Video

7 | 8 | 9 | -------------------------------------------------------------------------------- /tello/stats.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | 4 | class Stats: 5 | def __init__(self, command: str, idi: int): 6 | self.command = command 7 | self.response = None 8 | self.id = idi 9 | 10 | self.start_time = datetime.now() 11 | self.end_time = None 12 | self.duration = None 13 | 14 | def add_response(self, response: str): 15 | self.response = str(response) 16 | # 计算执行命令所需的总时间 17 | self.end_time = datetime.now() 18 | self.duration = (self.end_time-self.start_time).total_seconds() 19 | 20 | def got_response(self): 21 | if self.response is None: 22 | return False 23 | else: 24 | return True 25 | 26 | def get_raw_response(self): 27 | return self.response 28 | 29 | @staticmethod 30 | def numeric_response(data: str): 31 | num_val = ''.join(i for i in data if i.isdigit() or i == '-' or i == '.') 32 | return num_val 33 | 34 | def int_response(self, data: str): 35 | return int(self.numeric_response(data)) 36 | 37 | def float_response(self, data: str): 38 | return float(self.numeric_response(data)) 39 | 40 | def attitude_response(self): 41 | raw_att = self.response.split(';') 42 | att_data = (self.int_response(raw_att[0]), self.int_response(raw_att[1]), self.int_response(raw_att[2])) 43 | return att_data 44 | 45 | def acceleration_response(self): 46 | raw_acc = self.response.split(';') 47 | acc_data = (self.float_response(raw_acc[0]), self.float_response(raw_acc[1]), self.float_response(raw_acc[2])) 48 | return acc_data 49 | 50 | def temp_response(self): 51 | raw_temp = self.response.split('~') 52 | temp = (self.int_response(raw_temp[0]) + self.int_response(raw_temp[1]))/2 53 | return temp 54 | 55 | def get_response(self): 56 | if 'attitude?' in self.command: 57 | return self.attitude_response() 58 | elif 'acceleration?' in self.command: 59 | return self.acceleration_response() 60 | elif 'temp?' in self.command: 61 | return self.temp_response() 62 | elif 'baro?' in self.command or 'speed?' in self.command: 63 | return self.float_response(self.response) 64 | elif '?' not in self.command: 65 | return self.get_raw_response() 66 | else: 67 | return self.int_response(self.response) 68 | -------------------------------------------------------------------------------- /tello/tello.py: -------------------------------------------------------------------------------- 1 | import os 2 | import socket 3 | import queue 4 | import threading 5 | import time 6 | import datetime 7 | 8 | import cv2 9 | import numpy as np 10 | import paddlehub as hub 11 | from PIL import Image 12 | 13 | from .stats import Stats 14 | from .frame2html import VideoCamera, run_app 15 | 16 | q = queue.Queue() 17 | 18 | 19 | # q.queue.clear() 20 | 21 | 22 | class Tello: 23 | def __init__(self, te_ip: str = '192.168.10.1', debug: bool = True): 24 | # 在8889上打开本地UDP端口以进行无人机通信 25 | self.local_ip = '' 26 | self.local_port = 8889 27 | self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 28 | self.socket.bind((self.local_ip, self.local_port)) 29 | 30 | # 设置无人机IP和端口信息 31 | self.te_ip = te_ip 32 | self.te_port = 8889 33 | self.te_address = (self.te_ip, self.te_port) 34 | self.log = [] 35 | self.picture_path = '' 36 | self.file_path = '' 37 | self.frame = None 38 | 39 | # 加载动物识别模型 40 | self.module = hub.Module(name="resnet50_vd_animals") 41 | 42 | # 初始化响应线程 43 | self.receive_thread = threading.Thread(target=self._receive_thread) 44 | self.receive_thread.daemon = True 45 | self.receive_thread.start() 46 | 47 | # 本项目运行时选项 48 | self.stream_state = False 49 | self.camera_state = False 50 | self.color_state = False 51 | self.video_state = False 52 | self.save_state = False 53 | self.picture_state = False 54 | self.animal_state = False 55 | self.flip_frame = False 56 | self.now_color = 0 57 | self.MAX_TIME_OUT = 15.0 58 | self.debug = debug 59 | 60 | # 将无人机设置为命令模式 61 | self.command() 62 | 63 | def send_command(self, command: str, query: bool = False): 64 | # 为出站命令创建新的日志条目 65 | self.log.append(Stats(command, len(self.log))) 66 | 67 | # 向无人机发送命令 68 | self.socket.sendto(command.encode('utf-8'), self.te_address) 69 | # 显示确认消息 70 | if self.debug is True: 71 | print('Send Command: {}'.format(command)) 72 | 73 | # 检查命令是否超时(基于MAX_TIME_OUT中的值) 74 | start = time.time() 75 | while not self.log[-1].got_response(): # 在日志中未收到任何响应的情况下运行 76 | now = time.time() 77 | difference = now - start 78 | if difference > self.MAX_TIME_OUT: 79 | print('Connect Time Out!') 80 | break 81 | 82 | # 打印出无人机响应 83 | if self.debug is True and query is False: 84 | print('Response: {}'.format(self.log[-1].get_response())) 85 | 86 | def _receive_thread(self): 87 | while True: 88 | # 检查无人机响应,引发套接字错误 89 | try: 90 | self.response, ip = self.socket.recvfrom(1024) 91 | self.log[-1].add_response(self.response) 92 | except socket.error as exc: 93 | print('Error: {}'.format(exc)) 94 | 95 | def _cap_video_thread(self): 96 | # 创建流捕获对象 97 | cap = cv2.VideoCapture('udp://' + self.te_ip + ':11111') 98 | # cap.set(cv2.CAP_PROP_BUFFERSIZE, 2) 99 | while self.stream_state: 100 | ret, frame = cap.read() 101 | while ret: 102 | ret, frame = cap.read() 103 | if self.flip_frame: 104 | frame = cv2.flip(frame, 0) 105 | cv2.imshow("DJI Tello", frame) 106 | q.put(frame) 107 | k = cv2.waitKey(1) & 0xFF 108 | # 如果按Esc键,视频流关闭 109 | if k == 27: 110 | break 111 | cap.release() 112 | cv2.destroyAllWindows() 113 | 114 | def _service_video_thread(self): 115 | while True: 116 | self.frame = q.get() 117 | # k = cv2.waitKey(1) & 0xFF 118 | # 如果按F1键,截图到当前位置 119 | # if k == 0 or self.camera_state: 120 | if self.camera_state: 121 | self.file_path = self.picture_path + '\\' + datetime.datetime.now().strftime('%Y%m%d_%H%M%S') + '.png' 122 | print('图片路径为:', self.file_path) 123 | try: 124 | cv2.imwrite(self.file_path, self.frame) 125 | except Exception as e: 126 | print('保存图片失败') 127 | self.camera_state = False 128 | 129 | # 识别动物 130 | if self.animal_state: 131 | results = self.module.classification(images=[self.frame]) 132 | # print(results) 133 | key_value_list = list(results[0].items()) 134 | key_first, value_first = key_value_list[0][0], key_value_list[0][1] 135 | if '非动物' != key_first: 136 | # print('检测结果是:', key_first, ',相似度为:', value_first) 137 | cv2.imshow(key_first, self.frame) 138 | self.animal_state = False 139 | 140 | # 显示照片 141 | if self.picture_state: 142 | file = self.file_path 143 | f = Image.open(file).show() 144 | self.picture_state = False 145 | 146 | # 识别当前颜色 147 | if self.color_state: 148 | self.detect_color(self.frame) 149 | self.color_state = False 150 | 151 | # 将视频流发送至http 152 | if self.video_state: 153 | self.video_http(self.frame) 154 | self.video_state = False 155 | 156 | # 保存视频流至本地 157 | if self.save_state: 158 | self.video_save(self.frame) 159 | self.save_state = False 160 | 161 | def wait(self, delay: float): 162 | # 显示等待消息 163 | if self.debug is True: 164 | print('Wait {} Seconds...'.format(delay)) 165 | 166 | # 日志条目增加了延迟 167 | self.log.append(Stats('wait', len(self.log))) 168 | # 延迟激活 169 | time.sleep(delay) 170 | 171 | @staticmethod 172 | def video_http(frame): 173 | vc = VideoCamera(frame) 174 | run_app() 175 | 176 | @staticmethod 177 | def video_save(frame): 178 | force = cv2.VideoWriter_fourcc(*'XVID') 179 | out = cv2.VideoWriter('output.avi', force, 20.0, (640, 480)) 180 | frame = cv2.flip(frame, 0) 181 | # write the flipped frame 182 | out.write(frame) 183 | 184 | def get_log(self): 185 | return self.log 186 | 187 | def take_picture(self, path=os.getcwd()): 188 | """拍照""" 189 | self.camera_state = True 190 | self.picture_path = path 191 | 192 | def show_picture(self): 193 | """显示照片""" 194 | self.picture_state = True 195 | 196 | def flip_video(self): 197 | """翻转视频,在加装下视镜片的情况下开启""" 198 | self.flip_frame = True 199 | 200 | def identify_animal(self): 201 | """识别动物""" 202 | self.animal_state = True 203 | 204 | def identify_color(self): 205 | """识别当前颜色(红色或绿色)""" 206 | self.color_state = True 207 | time.sleep(0.5) 208 | return self.now_color 209 | 210 | # 以下命令强烈建议配合官方SDK食用 211 | # https://www.ryzerobotics.com/cn/tello/downloads 212 | 213 | # 控制命令 214 | def command(self): 215 | """进入SDK命令模式""" 216 | self.send_command('command') 217 | 218 | def takeoff(self): 219 | """自动起飞,1米左右""" 220 | self.send_command('takeoff') 221 | 222 | def land(self): 223 | """自动降落""" 224 | self.send_command('land') 225 | 226 | def streamon(self): 227 | """打开视频流""" 228 | self.send_command('streamon') 229 | self.stream_state = True 230 | 231 | self.cap_video_thread = threading.Thread(target=self._cap_video_thread) 232 | self.cap_video_thread.daemon = True 233 | self.cap_video_thread.start() 234 | 235 | def streamoff(self): 236 | """关闭视频流""" 237 | self.stream_state = False 238 | self.send_command('streamoff') 239 | 240 | def stream_service_on(self): 241 | """是否开启视频流附加功能,开启视频流会卡顿""" 242 | self.service_video_thread = threading.Thread(target=self._service_video_thread) 243 | self.service_video_thread.daemon = True 244 | self.service_video_thread.start() 245 | 246 | def detect_color(self, frame): 247 | """颜色识别""" 248 | # frame = cv2.imread("test.jpg") 249 | hue_image = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) 250 | 251 | low_red_range1 = np.array([110, 43, 0]) 252 | high_red_range1 = np.array([180, 255, 255]) 253 | threshold_red1 = cv2.inRange(hue_image, low_red_range1, high_red_range1) 254 | res_red1 = cv2.bitwise_and(frame, frame, mask=threshold_red1) 255 | 256 | low_red_range2 = np.array([0, 43, 0]) 257 | high_red_range2 = np.array([10, 255, 255]) 258 | threshold_red2 = cv2.inRange(hue_image, low_red_range2, high_red_range2) 259 | res_red2 = cv2.bitwise_and(frame, frame, mask=threshold_red2) 260 | 261 | threshold_red = threshold_red1 + threshold_red2 262 | res_red = res_red1 + res_red2 263 | 264 | low_green_range = np.array([35, 43, 46]) 265 | high_green_range = np.array([77, 255, 255]) 266 | threshold_green = cv2.inRange(hue_image, low_green_range, high_green_range) 267 | res_green = cv2.bitwise_and(frame, frame, mask=threshold_green) 268 | 269 | res = res_red + res_green 270 | if cv2.countNonZero(threshold_green) > 0.5 * np.size(threshold_green): 271 | self.now_color = 'green' 272 | elif ((cv2.countNonZero(threshold_red) > 0.5 * np.size(threshold_red)) & ( 273 | cv2.countNonZero(threshold_red) < 0.7 * np.size(threshold_red))): 274 | self.now_color = 'red' 275 | else: 276 | self.now_color = 'none' 277 | # color = cv2.cvtColor(binary, cv2.COLOR_GRAY2BGR) 278 | return self.now_color, res 279 | 280 | def emergency(self): 281 | """停止电机转动""" 282 | self.send_command('emergency') 283 | 284 | def up(self, x: int): 285 | """向上飞x(20-500)厘米""" 286 | self.send_command('up {}'.format(x)) 287 | 288 | def down(self, x: int): 289 | """向下飞x(20-500)厘米""" 290 | self.send_command('down {}'.format(x)) 291 | 292 | def left(self, x: int): 293 | """向左飞x(20-500)厘米""" 294 | self.send_command('left {}'.format(x)) 295 | 296 | def right(self, x: int): 297 | """向右飞x(20-500)厘米""" 298 | self.send_command('right {}'.format(x)) 299 | 300 | def forward(self, x: int): 301 | """向前飞x(20-500)厘米""" 302 | self.send_command('forward {}'.format(x)) 303 | 304 | def back(self, x: int): 305 | """向后飞x(20-500)厘米""" 306 | self.send_command('back {}'.format(x)) 307 | 308 | def cw(self, angle: int): 309 | """顺时针旋转angle°(1-360)""" 310 | self.send_command('cw {}'.format(angle)) 311 | 312 | def ccw(self, angle: int): 313 | """逆时针旋转angle°(1-360)""" 314 | self.send_command('ccw {}'.format(angle)) 315 | 316 | def flip(self, direction: str): 317 | """朝direction方向翻滚,左侧(left)缩写为l,同理right=r,forward=f,back=b""" 318 | self.send_command('flip {}'.format(direction)) 319 | 320 | def go(self, x: int, y: int, z: int, speed: int): 321 | """以设置速度speed(cm / s)飞往坐标(x, y, z) 322 | x: -500 - 500 323 | y: -500 - 500 324 | z: -500 - 500 325 | speed: 10 - 100(cm / s) 326 | x、y、z不能同时在 -20 ~ 20 之间""" 327 | self.send_command('go {} {} {} {}'.format(x, y, z, speed)) 328 | 329 | def stop(self): 330 | """"停止运动并悬停,任何时候都可以""" 331 | self.send_command('stop') 332 | 333 | def curve(self, x1: int, y1: int, z1: int, x2: int, y2: int, z2: int, speed: int): 334 | """以设置速度speed( cm/s )飞弧线,经过(x1,y1,z1)到(x2,y2,z2) 335 | 如果弧线半径不在 0.5-10 米范围内,则返回相应提醒 336 | x1, x2: -500 - 500 337 | y1, y2: -500 - 500 338 | z1, z2: -500 - 500 339 | speed: 10-60 340 | x、y、z 不能同时在 -20 ~ 20 之间""" 341 | self.send_command('curve {} {} {} {} {} {} {}'.format(x1, y1, z1, x2, y2, z2, speed)) 342 | 343 | def go_mid(self, x: int, y: int, z: int, speed: int, mid: str): 344 | """以设置速度speed(m/s)飞往设置 id 的挑战卡坐标系的(x,y,z)坐标点 345 | mid: 346 | m1/m2/~/m8:对应挑战卡上的挑战卡ID 347 | m-1: 无人机内部算法最快识别到的挑战卡,随机选择一个探测到的挑战卡 348 | m-2: 距离无人机中心距离最近的挑战卡 349 | x: -500 - 500 350 | y: -500 - 500 351 | z: 0 - 500 352 | speed: 10-100 (cm/s) 353 | x、y、z 不能同时在 -20 ~ 20 之间""" 354 | self.send_command('go {} {} {} {} {}'.format(x, y, z, speed, mid)) 355 | 356 | def curve_mid(self, x1: int, y1: int, z1: int, x2: int, y2: int, z2: int, speed: int, mid: str): 357 | """以设置速度speed( cm/s )飞弧线,经过设置 mid 的挑战卡坐标系中的(x1,y1,z1)点到(x2,y2,z2)点 358 | 如果弧线半径不在 0.5-10 米范围内,则返回相应提醒 359 | x1, x2: -500 - 500 360 | y1, y2: -500 - 500 361 | z1, z2: 0 - 500 362 | speed: 10-60 363 | x、y、z 不能同时在 -20 ~ 20 之间""" 364 | self.send_command('curve {} {} {} {} {} {} {} {}'.format(x1, y1, z1, x2, y2, z2, speed, mid)) 365 | 366 | def jump_mid(self, x: int, y: int, z: int, speed: int, yaw: int, mid1: str, mid2: str): 367 | """飞往 mid1 坐标系的(x,y,z)点后悬停,识别 mid2 的挑战卡 368 | 并在 mid2 坐标系下 (0,0,z) 的位置并旋转向到设置的 偏航yaw 值,( z>0 )""" 369 | self.send_command('jump {} {} {} {} {} {} {}'.format(x, y, z, speed, yaw, mid1, mid2)) 370 | 371 | # 设置命令 372 | def set_speed(self, speed: int): 373 | """将当前速度设为 speed cm/s,speed = 10-100""" 374 | self.send_command('speed {}'.format(speed)) 375 | 376 | def rc_control(self, a: int, b: int, c: int, d: int): 377 | """设置遥控器的 4 个通道杆量 378 | a: 横滚 (-100~100) 379 | b: 俯仰 (-100~100) 380 | c: 油门 (-100~100) 381 | d: 偏航 (-100~100) 382 | """ 383 | self.send_command('rc {} {} {} {}'.format(a, b, c, d)) 384 | 385 | def set_wifi(self, ssid: str, passwrd: str): 386 | """更改 无人机 Wi-Fi 密码 387 | ssid: 更改后的 Wi-Fi 账号 388 | passwrd: 更改后的 Wi-Fi 密码 389 | """ 390 | self.send_command('wifi {} {}'.format(ssid, passwrd)) 391 | 392 | def mon(self): 393 | """"打开挑战卡探测,默认同时打开前视和下视探测""" 394 | self.send_command('mon') 395 | 396 | def moff(self): 397 | """"关闭挑战卡探测""" 398 | self.send_command('moff') 399 | 400 | def mdirection(self, mdir: int): 401 | """mdir=0/1/2 402 | 0 打开下视探测 403 | 1 打开前视探测 404 | 2 同时打开前视和下视探测 405 | * 使用前必须使用 mon 命令打开探测功能 406 | * 单独打开前视或者下视探测时,探测频率为20Hz,同时打开前视和下视时,将交替探测,单个反向的探测频率为 10Hz""" 407 | self.send_command('mdirection {}'.format(mdir)) 408 | 409 | def ap2sta(self, ssid: str, passwrd: str): 410 | """将Tello转为 station 模式,并连入到 AP 411 | ssid: 要连接的 Wi-Fi 账号 412 | passwrd: 要连接的 Wi-Fi 密码""" 413 | self.send_command('ap {} {}'.format(ssid, passwrd)) 414 | 415 | # 读取命令 416 | def get_speed(self): 417 | """获取当前设置速度speed(cm/s),speed(10-100)""" 418 | self.send_command('speed?', True) 419 | return self.log[-1].get_response() 420 | 421 | def get_battery(self): 422 | """获取当前电池剩余电量的百分比值 x,x = (10-100)""" 423 | self.send_command('battery?', True) 424 | return self.log[-1].get_response() 425 | 426 | def get_time(self): 427 | """获取电机运转时间(s)""" 428 | self.send_command('time?', True) 429 | return self.log[-1].get_response() 430 | 431 | def get_wifi(self): 432 | """获得 Wi-Fi 信噪比""" 433 | self.send_command('wifi?', True) 434 | return self.log[-1].get_response() 435 | 436 | def get_sdk(self): 437 | """获得 无人机 SDK 版本号 xx(>=20)""" 438 | self.send_command('sdk?', True) 439 | return self.log[-1].get_response() 440 | 441 | def get_sn(self): 442 | """获得 无人机 SN 码 生产序列号""" 443 | self.send_command('sn?', True) 444 | return self.log[-1].get_response() 445 | 446 | def get_height(self): 447 | """获取高度,新版本中已停用""" 448 | self.send_command('height?', True) 449 | return self.log[-1].get_response() 450 | 451 | def get_temp(self): 452 | """获取温度,新版本中已停用""" 453 | self.send_command('temp?', True) 454 | return self.log[-1].get_response() 455 | 456 | def get_attitude(self): 457 | """获取飞行姿态,新版本中已停用""" 458 | self.send_command('attitude?', True) 459 | return self.log[-1].get_response() 460 | 461 | def get_baro(self): 462 | """获取压力,新版本中已停用""" 463 | self.send_command('baro?', True) 464 | return self.log[-1].get_response() 465 | 466 | def get_acceleration(self): 467 | """获取加速度,新版本中已停用""" 468 | self.send_command('acceleration?', True) 469 | return self.log[-1].get_response() 470 | 471 | def get_tof(self): 472 | """获取飞行时间,新版本中已停用""" 473 | self.send_command('tof?', True) 474 | return self.log[-1].get_response() 475 | --------------------------------------------------------------------------------