tello.py
Go to the documentation of this file.
1 import threading
2 import socket
3 import time
4 import datetime
5 import struct
6 import sys
7 import os
8 
9 from . import crc
10 from . import logger
11 from . import event
12 from . import state
13 from . import error
14 from . import video_stream
15 from . utils import *
16 from . protocol import *
17 from . import dispatcher
18 
19 log = logger.Logger('Tello')
20 
21 
22 class Tello(object):
23  EVENT_CONNECTED = event.Event('connected')
24  EVENT_WIFI = event.Event('wifi')
25  EVENT_LIGHT = event.Event('light')
26  EVENT_FLIGHT_DATA = event.Event('fligt_data')
27  EVENT_LOG_HEADER = event.Event('log_header')
28  EVENT_LOG = EVENT_LOG_HEADER
29  EVENT_LOG_RAWDATA = event.Event('log_rawdata')
30  EVENT_LOG_DATA = event.Event('log_data')
31  EVENT_LOG_CONFIG = event.Event('log_config')
32  EVENT_TIME = event.Event('time')
33  EVENT_VIDEO_FRAME = event.Event('video frame')
34  EVENT_VIDEO_DATA = event.Event('video data')
35  EVENT_DISCONNECTED = event.Event('disconnected')
36  EVENT_FILE_RECEIVED = event.Event('file received')
37  # internal events
38  __EVENT_CONN_REQ = event.Event('conn_req')
39  __EVENT_CONN_ACK = event.Event('conn_ack')
40  __EVENT_TIMEOUT = event.Event('timeout')
41  __EVENT_QUIT_REQ = event.Event('quit_req')
42 
43  # for backward comaptibility
44  CONNECTED_EVENT = EVENT_CONNECTED
45  WIFI_EVENT = EVENT_WIFI
46  LIGHT_EVENT = EVENT_LIGHT
47  FLIGHT_EVENT = EVENT_FLIGHT_DATA
48  LOG_EVENT = EVENT_LOG
49  TIME_EVENT = EVENT_TIME
50  VIDEO_FRAME_EVENT = EVENT_VIDEO_FRAME
51 
52  STATE_DISCONNECTED = state.State('disconnected')
53  STATE_CONNECTING = state.State('connecting')
54  STATE_CONNECTED = state.State('connected')
55  STATE_QUIT = state.State('quit')
56 
57  LOG_ERROR = logger.LOG_ERROR
58  LOG_WARN = logger.LOG_WARN
59  LOG_INFO = logger.LOG_INFO
60  LOG_DEBUG = logger.LOG_DEBUG
61  LOG_ALL = logger.LOG_ALL
62 
63  def __init__(self, port=9000):
64  self.tello_addr = ('192.168.10.1', 8889)
65  self.debug = False
66  self.pkt_seq_num = 0x01e4
67  self.port = port
68  self.udpsize = 2000
69  self.left_x = 0.0
70  self.left_y = 0.0
71  self.right_x = 0.0
72  self.right_y = 0.0
73  self.sock = None
75  self.lock = threading.Lock()
76  self.connected = threading.Event()
77  self.video_enabled = False
79  self.video_data_size = 0
80  self.video_data_loss = 0
81  self.log = log
82  self.exposure = 0
84  self.video_stream = None
85  self.wifi_strength = 0
86  self.log_data = LogData(log)
87  self.log_data_file = None
89 
90  # video zoom state
91  self.zoom = False
92 
93  # File recieve state.
94  self.file_recv = {} # Map filenum -> protocol.DownloadedFile
95 
96  # Create a UDP socket
97  self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
98  self.sock.bind(('', self.port))
99  self.sock.settimeout(2.0)
100 
101  dispatcher.connect(self.__state_machine, dispatcher.signal.All)
102  threading.Thread(target=self.__recv_thread).start()
103  threading.Thread(target=self.__video_thread).start()
104 
105  def set_loglevel(self, level):
106  """
107  Set_loglevel controls the output messages. Valid levels are
108  LOG_ERROR, LOG_WARN, LOG_INFO, LOG_DEBUG and LOG_ALL.
109  """
110  log.set_level(level)
111 
112  def get_video_stream(self):
113  """
114  Get_video_stream is used to prepare buffer object which receive video data from the drone.
115  """
116  newly_created = False
117  self.lock.acquire()
118  log.info('get video stream')
119  try:
120  if self.video_stream is None:
122  newly_created = True
123  res = self.video_stream
124  finally:
125  self.lock.release()
126  if newly_created:
127  self.__send_exposure()
129  self.start_video()
130 
131  return res
132 
133  def connect(self):
134  """Connect is used to send the initial connection request to the drone."""
135  self.__publish(event=self.__EVENT_CONN_REQ)
136 
137  def wait_for_connection(self, timeout=None):
138  """Wait_for_connection will block until the connection is established."""
139  if not self.connected.wait(timeout):
140  raise error.TelloError('timeout')
141 
142  def __send_conn_req(self):
143  port = 9617
144  port0 = (int(port/1000) % 10) << 4 | (int(port/100) % 10)
145  port1 = (int(port/10) % 10) << 4 | (int(port/1) % 10)
146  buf = 'conn_req:%c%c' % (chr(port0), chr(port1))
147  log.info('send connection request (cmd="%s%02x%02x")' % (str(buf[:-2]), port0, port1))
148  return self.send_packet(Packet(buf))
149 
150  def subscribe(self, signal, handler):
151  """Subscribe a event such as EVENT_CONNECTED, EVENT_FLIGHT_DATA, EVENT_VIDEO_FRAME and so on."""
152  dispatcher.connect(handler, signal)
153 
154  def __publish(self, event, data=None, **args):
155  args.update({'data': data})
156  if 'signal' in args:
157  del args['signal']
158  if 'sender' in args:
159  del args['sender']
160  log.debug('publish signal=%s, args=%s' % (event, args))
161  dispatcher.send(event, sender=self, **args)
162 
163  def takeoff(self):
164  """Takeoff tells the drones to liftoff and start flying."""
165  log.info('set altitude limit 30m')
166  pkt = Packet(SET_ALT_LIMIT_CMD)
167  pkt.add_byte(0x1e) # 30m
168  pkt.add_byte(0x00)
169  self.send_packet(pkt)
170  log.info('takeoff (cmd=0x%02x seq=0x%04x)' % (TAKEOFF_CMD, self.pkt_seq_num))
171  pkt = Packet(TAKEOFF_CMD)
172  pkt.fixup()
173  return self.send_packet(pkt)
174 
175  def throw_and_go(self):
176  """Throw_and_go starts a throw and go sequence"""
177  log.info('throw_and_go (cmd=0x%02x seq=0x%04x)' % (THROW_AND_GO_CMD, self.pkt_seq_num))
178  pkt = Packet(THROW_AND_GO_CMD, 0x48)
179  pkt.add_byte(0x00)
180  pkt.fixup()
181  return self.send_packet(pkt)
182 
183  def land(self):
184  """Land tells the drone to come in for landing."""
185  log.info('land (cmd=0x%02x seq=0x%04x)' % (LAND_CMD, self.pkt_seq_num))
186  pkt = Packet(LAND_CMD)
187  pkt.add_byte(0x00)
188  pkt.fixup()
189  return self.send_packet(pkt)
190 
191  def palm_land(self):
192  """Tells the drone to wait for a hand underneath it and then land."""
193  log.info('palmland (cmd=0x%02x seq=0x%04x)' % (PALM_LAND_CMD, self.pkt_seq_num))
194  pkt = Packet(PALM_LAND_CMD)
195  pkt.add_byte(0x00)
196  pkt.fixup()
197  return self.send_packet(pkt)
198 
199  def quit(self):
200  """Quit stops the internal threads."""
201  log.info('quit')
202  self.__publish(event=self.__EVENT_QUIT_REQ)
203 
204  def get_alt_limit(self):
205  ''' ... '''
206  self.log.debug('get altitude limit (cmd=0x%02x seq=0x%04x)' % (
207  ALT_LIMIT_MSG, self.pkt_seq_num))
208  pkt = Packet(ALT_LIMIT_MSG)
209  pkt.fixup()
210  return self.send_packet(pkt)
211 
212  def set_alt_limit(self, limit):
213  self.log.info('set altitude limit=%s (cmd=0x%02x seq=0x%04x)' % (
214  int(limit), SET_ALT_LIMIT_CMD, self.pkt_seq_num))
215  pkt = Packet(SET_ALT_LIMIT_CMD)
216  pkt.add_byte(int(limit))
217  pkt.add_byte(0x00)
218  pkt.fixup()
219  self.send_packet(pkt)
220  self.get_alt_limit()
221 
222  def get_att_limit(self):
223  ''' ... '''
224  self.log.debug('get attitude limit (cmd=0x%02x seq=0x%04x)' % (
225  ATT_LIMIT_MSG, self.pkt_seq_num))
226  pkt = Packet(ATT_LIMIT_MSG)
227  pkt.fixup()
228  return self.send_packet(pkt)
229 
230  def set_att_limit(self, limit):
231  self.log.info('set attitude limit=%s (cmd=0x%02x seq=0x%04x)' % (
232  int(limit), ATT_LIMIT_CMD, self.pkt_seq_num))
233  pkt = Packet(ATT_LIMIT_CMD)
234  pkt.add_byte(0x00)
235  pkt.add_byte(0x00)
236  pkt.add_byte( int(float_to_hex(float(limit))[4:6], 16) ) # 'attitude limit' formatted in float of 4 bytes
237  pkt.add_byte(0x41)
238  pkt.fixup()
239  self.send_packet(pkt)
240  self.get_att_limit()
241 
243  ''' ... '''
244  self.log.debug('get low battery threshold (cmd=0x%02x seq=0x%04x)' % (
245  LOW_BAT_THRESHOLD_MSG, self.pkt_seq_num))
246  pkt = Packet(LOW_BAT_THRESHOLD_MSG)
247  pkt.fixup()
248  return self.send_packet(pkt)
249 
250  def set_low_bat_threshold(self, threshold):
251  self.log.info('set low battery threshold=%s (cmd=0x%02x seq=0x%04x)' % (
252  int(threshold), LOW_BAT_THRESHOLD_CMD, self.pkt_seq_num))
253  pkt = Packet(LOW_BAT_THRESHOLD_CMD)
254  pkt.add_byte(int(threshold))
255  pkt.fixup()
256  self.send_packet(pkt)
257  self.get_low_bat_threshold()
258 
260  log.info('send_time (cmd=0x%02x seq=0x%04x)' % (TIME_CMD, self.pkt_seq_num))
261  pkt = Packet(TIME_CMD, 0x50)
262  pkt.add_byte(0)
263  pkt.add_time()
264  pkt.fixup()
265  return self.send_packet(pkt)
266 
268  pkt = Packet(VIDEO_START_CMD, 0x60)
269  pkt.fixup()
270  return self.send_packet(pkt)
271 
272  def __send_video_mode(self, mode):
273  pkt = Packet(VIDEO_MODE_CMD)
274  pkt.add_byte(mode)
275  pkt.fixup()
276  return self.send_packet(pkt)
277 
278  def set_video_mode(self, zoom=False):
279  """Tell the drone whether to capture 960x720 4:3 video, or 1280x720 16:9 zoomed video.
280  4:3 has a wider field of view (both vertically and horizontally), 16:9 is crisper."""
281  log.info('set video mode zoom=%s (cmd=0x%02x seq=0x%04x)' % (
282  zoom, VIDEO_START_CMD, self.pkt_seq_num))
283  self.zoom = zoom
284  return self.__send_video_mode(int(zoom))
285 
286  def start_video(self):
287  """Start_video tells the drone to send start info (SPS/PPS) for video stream."""
288  log.info('start video (cmd=0x%02x seq=0x%04x)' % (VIDEO_START_CMD, self.pkt_seq_num))
289  self.video_enabled = True
290  self.__send_exposure()
292  return self.__send_start_video()
293 
294  def set_exposure(self, level):
295  """Set_exposure sets the drone camera exposure level. Valid levels are 0, 1, and 2."""
296  if level < 0 or 2 < level:
297  raise error.TelloError('Invalid exposure level')
298  log.info('set exposure (cmd=0x%02x seq=0x%04x)' % (EXPOSURE_CMD, self.pkt_seq_num))
299  self.exposure = level
300  return self.__send_exposure()
301 
302  def __send_exposure(self):
303  pkt = Packet(EXPOSURE_CMD, 0x48)
304  pkt.add_byte(self.exposure)
305  pkt.fixup()
306  return self.send_packet(pkt)
307 
308  def set_video_encoder_rate(self, rate):
309  """Set_video_encoder_rate sets the drone video encoder rate."""
310  log.info('set video encoder rate (cmd=0x%02x seq=%04x)' %
311  (VIDEO_ENCODER_RATE_CMD, self.pkt_seq_num))
312  self.video_encoder_rate = rate
313  return self.__send_video_encoder_rate()
314 
316  pkt = Packet(VIDEO_ENCODER_RATE_CMD, 0x68)
317  pkt.add_byte(self.video_encoder_rate)
318  pkt.fixup()
319  return self.send_packet(pkt)
320 
321  def take_picture(self):
322  log.info('take picture')
323  return self.send_packet_data(TAKE_PICTURE_COMMAND, type=0x68)
324 
325  def up(self, val):
326  """Up tells the drone to ascend. Pass in an int from 0-100."""
327  log.info('up(val=%d)' % val)
328  self.left_y = val / 100.0
329 
330  def down(self, val):
331  """Down tells the drone to descend. Pass in an int from 0-100."""
332  log.info('down(val=%d)' % val)
333  self.left_y = val / 100.0 * -1
334 
335  def forward(self, val):
336  """Forward tells the drone to go forward. Pass in an int from 0-100."""
337  log.info('forward(val=%d)' % val)
338  self.right_y = val / 100.0
339 
340  def backward(self, val):
341  """Backward tells the drone to go in reverse. Pass in an int from 0-100."""
342  log.info('backward(val=%d)' % val)
343  self.right_y = val / 100.0 * -1
344 
345  def right(self, val):
346  """Right tells the drone to go right. Pass in an int from 0-100."""
347  log.info('right(val=%d)' % val)
348  self.right_x = val / 100.0
349 
350  def left(self, val):
351  """Left tells the drone to go left. Pass in an int from 0-100."""
352  log.info('left(val=%d)' % val)
353  self.right_x = val / 100.0 * -1
354 
355  def clockwise(self, val):
356  """
357  Clockwise tells the drone to rotate in a clockwise direction.
358  Pass in an int from 0-100.
359  """
360  log.info('clockwise(val=%d)' % val)
361  self.left_x = val / 100.0
362 
363  def counter_clockwise(self, val):
364  """
365  CounterClockwise tells the drone to rotate in a counter-clockwise direction.
366  Pass in an int from 0-100.
367  """
368  log.info('counter_clockwise(val=%d)' % val)
369  self.left_x = val / 100.0 * -1
370 
371  def flip_forward(self):
372  """flip_forward tells the drone to perform a forwards flip"""
373  log.info('flip_forward (cmd=0x%02x seq=0x%04x)' % (FLIP_CMD, self.pkt_seq_num))
374  pkt = Packet(FLIP_CMD, 0x70)
375  pkt.add_byte(FlipFront)
376  pkt.fixup()
377  return self.send_packet(pkt)
378 
379  def flip_back(self):
380  """flip_back tells the drone to perform a backwards flip"""
381  log.info('flip_back (cmd=0x%02x seq=0x%04x)' % (FLIP_CMD, self.pkt_seq_num))
382  pkt = Packet(FLIP_CMD, 0x70)
383  pkt.add_byte(FlipBack)
384  pkt.fixup()
385  return self.send_packet(pkt)
386 
387  def flip_right(self):
388  """flip_right tells the drone to perform a right flip"""
389  log.info('flip_right (cmd=0x%02x seq=0x%04x)' % (FLIP_CMD, self.pkt_seq_num))
390  pkt = Packet(FLIP_CMD, 0x70)
391  pkt.add_byte(FlipRight)
392  pkt.fixup()
393  return self.send_packet(pkt)
394 
395  def flip_left(self):
396  """flip_left tells the drone to perform a left flip"""
397  log.info('flip_left (cmd=0x%02x seq=0x%04x)' % (FLIP_CMD, self.pkt_seq_num))
398  pkt = Packet(FLIP_CMD, 0x70)
399  pkt.add_byte(FlipLeft)
400  pkt.fixup()
401  return self.send_packet(pkt)
402 
403  def flip_forwardleft(self):
404  """flip_forwardleft tells the drone to perform a forwards left flip"""
405  log.info('flip_forwardleft (cmd=0x%02x seq=0x%04x)' % (FLIP_CMD, self.pkt_seq_num))
406  pkt = Packet(FLIP_CMD, 0x70)
407  pkt.add_byte(FlipForwardLeft)
408  pkt.fixup()
409  return self.send_packet(pkt)
410 
411  def flip_backleft(self):
412  """flip_backleft tells the drone to perform a backwards left flip"""
413  log.info('flip_backleft (cmd=0x%02x seq=0x%04x)' % (FLIP_CMD, self.pkt_seq_num))
414  pkt = Packet(FLIP_CMD, 0x70)
415  pkt.add_byte(FlipBackLeft)
416  pkt.fixup()
417  return self.send_packet(pkt)
418 
419  def flip_forwardright(self):
420  """flip_forwardright tells the drone to perform a forwards right flip"""
421  log.info('flip_forwardright (cmd=0x%02x seq=0x%04x)' % (FLIP_CMD, self.pkt_seq_num))
422  pkt = Packet(FLIP_CMD, 0x70)
423  pkt.add_byte(FlipForwardRight)
424  pkt.fixup()
425  return self.send_packet(pkt)
426 
427  def flip_backright(self):
428  """flip_backleft tells the drone to perform a backwards right flip"""
429  log.info('flip_backright (cmd=0x%02x seq=0x%04x)' % (FLIP_CMD, self.pkt_seq_num))
430  pkt = Packet(FLIP_CMD, 0x70)
431  pkt.add_byte(FlipBackRight)
432  pkt.fixup()
433  return self.send_packet(pkt)
434 
435  def __fix_range(self, val, min=-1.0, max=1.0):
436  if val < min:
437  val = min
438  elif val > max:
439  val = max
440  return val
441 
442  def set_throttle(self, throttle):
443  """
444  Set_throttle controls the vertical up and down motion of the drone.
445  Pass in an int from -1.0 ~ 1.0. (positive value means upward)
446  """
447  if self.left_y != self.__fix_range(throttle):
448  log.info('set_throttle(val=%4.2f)' % throttle)
449  self.left_y = self.__fix_range(throttle)
450 
451  def set_yaw(self, yaw):
452  """
453  Set_yaw controls the left and right rotation of the drone.
454  Pass in an int from -1.0 ~ 1.0. (positive value will make the drone turn to the right)
455  """
456  if self.left_x != self.__fix_range(yaw):
457  log.info('set_yaw(val=%4.2f)' % yaw)
458  self.left_x = self.__fix_range(yaw)
459 
460  def set_pitch(self, pitch):
461  """
462  Set_pitch controls the forward and backward tilt of the drone.
463  Pass in an int from -1.0 ~ 1.0. (positive value will make the drone move forward)
464  """
465  if self.right_y != self.__fix_range(pitch):
466  log.info('set_pitch(val=%4.2f)' % pitch)
467  self.right_y = self.__fix_range(pitch)
468 
469  def set_roll(self, roll):
470  """
471  Set_roll controls the the side to side tilt of the drone.
472  Pass in an int from -1.0 ~ 1.0. (positive value will make the drone move to the right)
473  """
474  if self.right_x != self.__fix_range(roll):
475  log.info('set_roll(val=%4.2f)' % roll)
476  self.right_x = self.__fix_range(roll)
477 
479  pkt = Packet(STICK_CMD, 0x60)
480 
481  axis1 = int(1024 + 660.0 * self.right_x) & 0x7ff
482  axis2 = int(1024 + 660.0 * self.right_y) & 0x7ff
483  axis3 = int(1024 + 660.0 * self.left_y) & 0x7ff
484  axis4 = int(1024 + 660.0 * self.left_x) & 0x7ff
485  '''
486  11 bits (-1024 ~ +1023) x 4 axis = 44 bits
487  44 bits will be packed in to 6 bytes (48 bits)
488 
489  axis4 axis3 axis2 axis1
490  | | | | |
491  4 3 2 1 0
492  98765432109876543210987654321098765432109876543210
493  | | | | | | |
494  byte5 byte4 byte3 byte2 byte1 byte0
495  '''
496  log.debug("stick command: yaw=%4d thr=%4d pit=%4d rol=%4d" %
497  (axis4, axis3, axis2, axis1))
498  log.debug("stick command: yaw=%04x thr=%04x pit=%04x rol=%04x" %
499  (axis4, axis3, axis2, axis1))
500  pkt.add_byte(((axis2 << 11 | axis1) >> 0) & 0xff)
501  pkt.add_byte(((axis2 << 11 | axis1) >> 8) & 0xff)
502  pkt.add_byte(((axis3 << 11 | axis2) >> 5) & 0xff)
503  pkt.add_byte(((axis4 << 11 | axis3) >> 2) & 0xff)
504  pkt.add_byte(((axis4 << 11 | axis3) >> 10) & 0xff)
505  pkt.add_byte(((axis4 << 11 | axis3) >> 18) & 0xff)
506  pkt.add_time()
507  pkt.fixup()
508  log.debug("stick command: %s" % byte_to_hexstring(pkt.get_buffer()))
509  return self.send_packet(pkt)
510 
511  def __send_ack_log(self, id):
512  pkt = Packet(LOG_HEADER_MSG, 0x50)
513  pkt.add_byte(0x00)
514  b0, b1 = le16(id)
515  pkt.add_byte(b0)
516  pkt.add_byte(b1)
517  pkt.fixup()
518  return self.send_packet(pkt)
519 
520  def send_packet(self, pkt):
521  """Send_packet is used to send a command packet to the drone."""
522  try:
523  cmd = pkt.get_buffer()
524  self.sock.sendto(cmd, self.tello_addr)
525  log.debug("send_packet: %s" % byte_to_hexstring(cmd))
526  except socket.error as err:
527  if self.state == self.STATE_CONNECTED:
528  log.error("send_packet: %s" % str(err))
529  else:
530  log.info("send_packet: %s" % str(err))
531  return False
532 
533  return True
534 
535  def send_packet_data(self, command, type=0x68, payload=[]):
536  pkt = Packet(command, type, payload)
537  pkt.fixup()
538  return self.send_packet(pkt)
539 
540  def __process_packet(self, data):
541  if isinstance(data, str):
542  data = bytearray([x for x in data])
543 
544  if str(data[0:9]) == 'conn_ack:' or data[0:9] == b'conn_ack:':
545  log.info('connected. (port=%2x%2x)' % (data[9], data[10]))
546  log.debug(' %s' % byte_to_hexstring(data))
547  if self.video_enabled:
548  self.__send_exposure()
550  self.__send_start_video()
551  self.__publish(self.__EVENT_CONN_ACK, data)
552 
553  return True
554 
555  if data[0] != START_OF_PACKET:
556  log.info('start of packet != %02x (%02x) (ignored)' % (START_OF_PACKET, data[0]))
557  log.info(' %s' % byte_to_hexstring(data))
558  log.info(' %s' % str(map(chr, data))[1:-1])
559  return False
560 
561  pkt = Packet(data)
562  cmd = uint16(data[5], data[6])
563  if cmd == LOG_HEADER_MSG:
564  id = uint16(data[9], data[10])
565  log.info("recv: log_header: id=%04x, '%s'" % (id, str(data[28:54])))
566  log.debug("recv: log_header: %s" % byte_to_hexstring(data[9:]))
567  self.__send_ack_log(id)
568  self.__publish(event=self.EVENT_LOG_HEADER, data=data[9:])
569  if self.log_data_file and not self.log_data_header_recorded:
570  self.log_data_file.write(data[12:-2])
571  self.log_data_header_recorded = True
572  elif cmd == LOG_DATA_MSG:
573  log.debug("recv: log_data: length=%d, %s" % (len(data[9:]), byte_to_hexstring(data[9:])))
574  self.__publish(event=self.EVENT_LOG_RAWDATA, data=data[9:])
575  try:
576  self.log_data.update(data[10:])
577  if self.log_data_file:
578  self.log_data_file.write(data[10:-2])
579  except Exception as ex:
580  log.error('%s' % str(ex))
581  self.__publish(event=self.EVENT_LOG_DATA, data=self.log_data)
582 
583  elif cmd == LOG_CONFIG_MSG:
584  log.debug("recv: log_config: length=%d, %s" % (len(data[9:]), byte_to_hexstring(data[9:])))
585  self.__publish(event=self.EVENT_LOG_CONFIG, data=data[9:])
586  elif cmd == WIFI_MSG:
587  log.debug("recv: wifi: %s" % byte_to_hexstring(data[9:]))
588  self.wifi_strength = data[9]
589  self.__publish(event=self.EVENT_WIFI, data=data[9:])
590  elif cmd == ALT_LIMIT_MSG:
591  log.info("recv: altitude limit: %s" % byte_to_hexstring(data[9:-2]))
592  elif cmd == ATT_LIMIT_MSG:
593  log.info("recv: attitude limit: %s" % byte_to_hexstring(data[9:-2]))
594  elif cmd == LOW_BAT_THRESHOLD_MSG:
595  log.info("recv: low battery threshold: %s" % byte_to_hexstring(data[9:-2]))
596  elif cmd == LIGHT_MSG:
597  log.debug("recv: light: %s" % byte_to_hexstring(data[9:-2]))
598  self.__publish(event=self.EVENT_LIGHT, data=data[9:])
599  elif cmd == FLIGHT_MSG:
600  flight_data = FlightData(data[9:])
601  flight_data.wifi_strength = self.wifi_strength
602  log.debug("recv: flight data: %s" % str(flight_data))
603  self.__publish(event=self.EVENT_FLIGHT_DATA, data=flight_data)
604  elif cmd == TIME_CMD:
605  log.debug("recv: time data: %s" % byte_to_hexstring(data))
606  self.__publish(event=self.EVENT_TIME, data=data[7:9])
607  elif cmd in (SET_ALT_LIMIT_CMD, ATT_LIMIT_CMD, LOW_BAT_THRESHOLD_CMD, TAKEOFF_CMD, LAND_CMD, VIDEO_START_CMD, VIDEO_ENCODER_RATE_CMD, PALM_LAND_CMD,
608  EXPOSURE_CMD, THROW_AND_GO_CMD, EMERGENCY_CMD):
609  log.debug("recv: ack: cmd=0x%02x seq=0x%04x %s" %
610  (uint16(data[5], data[6]), uint16(data[7], data[8]), byte_to_hexstring(data)))
611  elif cmd == TELLO_CMD_FILE_SIZE:
612  # Drone is about to send us a file. Get ready.
613  # N.b. one of the fields in the packet is a file ID; by demuxing
614  # based on file ID we can receive multiple files at once. This
615  # code doesn't support that yet, though, so don't take one photo
616  # while another is still being received.
617  log.info("recv: file size: %s" % byte_to_hexstring(data))
618  if len(pkt.get_data()) >= 7:
619  (size, filenum) = struct.unpack('<xLH', pkt.get_data())
620  log.info(' file size: num=%d bytes=%d' % (filenum, size))
621  # Initialize file download state.
622  self.file_recv[filenum] = DownloadedFile(filenum, size)
623  else:
624  # We always seem to get two files, one with most of the payload missing.
625  # Not sure what the second one is for.
626  log.warn(' file size: payload too small: %s' % byte_to_hexstring(pkt.get_data()))
627  # Ack the packet.
628  self.send_packet(pkt)
629  elif cmd == TELLO_CMD_FILE_DATA:
630  # log.info("recv: file data: %s" % byte_to_hexstring(data[9:21]))
631  # Drone is sending us a fragment of a file it told us to prepare
632  # for earlier.
633  self.recv_file_data(pkt.get_data())
634  else:
635  log.info('unknown packet: %04x %s' % (cmd, byte_to_hexstring(data)))
636  return False
637 
638  return True
639 
640  def recv_file_data(self, data):
641  (filenum,chunk,fragment,size) = struct.unpack('<HLLH', data[0:12])
642  file = self.file_recv.get(filenum, None)
643 
644  # Preconditions.
645  if file is None:
646  return
647 
648  if file.recvFragment(chunk, fragment, size, data[12:12+size]):
649  # Did this complete a chunk? Ack the chunk so the drone won't
650  # re-send it.
651  self.send_packet_data(TELLO_CMD_FILE_DATA, type=0x50,
652  payload=struct.pack('<BHL', 0, filenum, chunk))
653 
654  if file.done():
655  # We have the whole file! First, send a normal ack with the first
656  # byte set to 1 to indicate file completion.
657  self.send_packet_data(TELLO_CMD_FILE_DATA, type=0x50,
658  payload=struct.pack('<BHL', 1, filenum, chunk))
659  # Then send the FILE_COMPLETE packed separately telling it how
660  # large we thought the file was.
661  self.send_packet_data(TELLO_CMD_FILE_COMPLETE, type=0x48,
662  payload=struct.pack('<HL', filenum, file.size))
663  # Inform subscribers that we have a file and clean up.
664  self.__publish(event=self.EVENT_FILE_RECEIVED, data=file.data())
665  del self.file_recv[filenum]
666 
667  def record_log_data(self, path = None):
668  if path == None:
669  path = '%s/Documents/tello-%s.dat' % (
670  os.getenv('HOME'),
671  datetime.datetime.now().strftime('%Y-%m-%d_%H%M%S'))
672  log.info('record log data in %s' % path)
673  self.log_data_file = open(path, 'wb')
674 
675  def __state_machine(self, event, sender, data, **args):
676  self.lock.acquire()
677  cur_state = self.state
678  event_connected = False
679  event_disconnected = False
680  log.debug('event %s in state %s' % (str(event), str(self.state)))
681 
682  if self.state == self.STATE_DISCONNECTED:
683  if event == self.__EVENT_CONN_REQ:
684  self.__send_conn_req()
685  self.state = self.STATE_CONNECTING
686  elif event == self.__EVENT_QUIT_REQ:
687  self.state = self.STATE_QUIT
688  event_disconnected = True
689  self.video_enabled = False
690 
691  elif self.state == self.STATE_CONNECTING:
692  if event == self.__EVENT_CONN_ACK:
693  self.state = self.STATE_CONNECTED
694  event_connected = True
695  # send time
696  self.__send_time_command()
697  elif event == self.__EVENT_TIMEOUT:
698  self.__send_conn_req()
699  elif event == self.__EVENT_QUIT_REQ:
700  self.state = self.STATE_QUIT
701 
702  elif self.state == self.STATE_CONNECTED:
703  if event == self.__EVENT_TIMEOUT:
704  self.__send_conn_req()
705  self.state = self.STATE_CONNECTING
706  event_disconnected = True
707  self.video_enabled = False
708  elif event == self.__EVENT_QUIT_REQ:
709  self.state = self.STATE_QUIT
710  event_disconnected = True
711  self.video_enabled = False
712 
713  elif self.state == self.STATE_QUIT:
714  pass
715 
716  if cur_state != self.state:
717  log.info('state transit %s -> %s' % (cur_state, self.state))
718  self.lock.release()
719 
720  if event_connected:
721  self.__publish(event=self.EVENT_CONNECTED, **args)
722  self.connected.set()
723  if event_disconnected:
724  self.__publish(event=self.EVENT_DISCONNECTED, **args)
725  self.connected.clear()
726 
727  def __recv_thread(self):
728  sock = self.sock
729 
730  while self.state != self.STATE_QUIT:
731 
732  if self.state == self.STATE_CONNECTED:
733  self.__send_stick_command() # ignore errors
734 
735  try:
736  data, server = sock.recvfrom(self.udpsize)
737  log.debug("recv: %s" % byte_to_hexstring(data))
738  self.__process_packet(data)
739  except socket.timeout as ex:
740  if self.state == self.STATE_CONNECTED:
741  log.error('recv: timeout')
742  self.__publish(event=self.__EVENT_TIMEOUT)
743  except Exception as ex:
744  log.error('recv: %s' % str(ex))
745  show_exception(ex)
746 
747  log.info('exit from the recv thread.')
748 
749  def __video_thread(self):
750  log.info('start video thread')
751  # Create a UDP socket
752  sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
753  port = 6038
754  sock.bind(('', port))
755  sock.settimeout(1.0)
756  sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 512 * 1024)
757  log.info('video receive buffer size = %d' %
758  sock.getsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF))
759 
760  prev_video_data = None
761  prev_ts = None
762  history = []
763  while self.state != self.STATE_QUIT:
764  if not self.video_enabled:
765  time.sleep(1.0)
766  continue
767  try:
768  data, server = sock.recvfrom(self.udpsize)
769  now = datetime.datetime.now()
770  log.debug("video recv: %s %d bytes" % (byte_to_hexstring(data[0:2]), len(data)))
771  show_history = False
772 
773  # check video data loss
774  video_data = VideoData(data)
775  loss = video_data.gap(prev_video_data)
776  if loss != 0:
777  self.video_data_loss += loss
778  # enable this line to see packet history
779  # show_history = True
780  prev_video_data = video_data
781 
782  # check video data interval
783  if prev_ts is not None and 0.1 < (now - prev_ts).total_seconds():
784  log.info('video recv: %d bytes %02x%02x +%03d' %
785  (len(data), byte(data[0]), byte(data[1]),
786  (now - prev_ts).total_seconds() * 1000))
787  prev_ts = now
788 
789  # save video data history
790  history.append([now, len(data), byte(data[0])*256 + byte(data[1])])
791  if 100 < len(history):
792  history = history[1:]
793 
794  # show video data history
795  if show_history:
796  prev_ts = history[0][0]
797  for i in range(1, len(history)):
798  [ ts, sz, sn ] = history[i]
799  print(' %02d:%02d:%02d.%03d %4d bytes %04x +%03d%s' %
800  (ts.hour, ts.minute, ts.second, ts.microsecond/1000,
801  sz, sn, (ts - prev_ts).total_seconds()*1000,
802  (' *' if i == len(history) - 1 else '')))
803  prev_ts = ts
804  history = history[-1:]
805 
806  # deliver video frame to subscribers
807  self.__publish(event=self.EVENT_VIDEO_FRAME, data=data[2:])
808  self.__publish(event=self.EVENT_VIDEO_DATA, data=data)
809 
810  # show video frame statistics
811  if self.prev_video_data_time is None:
812  self.prev_video_data_time = now
813  self.video_data_size += len(data)
814  dur = (now - self.prev_video_data_time).total_seconds()
815  if 2.0 < dur:
816  log.info(('video data %d bytes %5.1fKB/sec' %
817  (self.video_data_size, self.video_data_size / dur / 1024)) +
818  ((' loss=%d' % self.video_data_loss) if self.video_data_loss != 0 else ''))
819  self.video_data_size = 0
820  self.prev_video_data_time = now
821  self.video_data_loss = 0
822 
823  # keep sending start video command
824  self.__send_start_video()
825 
826  except socket.timeout as ex:
827  log.error('video recv: timeout')
828  self.start_video()
829  data = None
830  except Exception as ex:
831  log.error('video recv: %s' % str(ex))
832  show_exception(ex)
833 
834  log.info('exit from the video thread.')
835 
836 if __name__ == '__main__':
837  print('You can use test.py for testing.')
def down(self, val)
Definition: tello.py:330
def __publish(self, event, data=None, args)
Definition: tello.py:154
def backward(self, val)
Definition: tello.py:340
def __fix_range(self, val, min=-1.0, max=1.0)
Definition: tello.py:435
def subscribe(self, signal, handler)
Definition: tello.py:150
def left(self, val)
Definition: tello.py:350
def set_loglevel(self, level)
Definition: tello.py:105
def set_alt_limit(self, limit)
Definition: tello.py:212
def __state_machine(self, event, sender, data, args)
Definition: tello.py:675
def send_packet(self, pkt)
Definition: tello.py:520
def record_log_data(self, path=None)
Definition: tello.py:667
def set_pitch(self, pitch)
Definition: tello.py:460
def forward(self, val)
Definition: tello.py:335
def __send_time_command(self)
Definition: tello.py:259
def wait_for_connection(self, timeout=None)
Definition: tello.py:137
def __send_stick_command(self)
Definition: tello.py:478
def right(self, val)
Definition: tello.py:345
def get_low_bat_threshold(self)
Definition: tello.py:242
def byte_to_hexstring(buf)
Definition: utils.py:26
def set_throttle(self, throttle)
Definition: tello.py:442
def uint16(val0, val1)
Definition: utils.py:15
def send_packet_data(self, command, type=0x68, payload=[])
Definition: tello.py:535
def __process_packet(self, data)
Definition: tello.py:540
def __send_ack_log(self, id)
Definition: tello.py:511
def clockwise(self, val)
Definition: tello.py:355
def set_video_encoder_rate(self, rate)
Definition: tello.py:308
def set_yaw(self, yaw)
Definition: tello.py:451
def set_roll(self, roll)
Definition: tello.py:469
def set_exposure(self, level)
Definition: tello.py:294
def __init__(self, port=9000)
Definition: tello.py:63
def set_att_limit(self, limit)
Definition: tello.py:230
def __send_video_mode(self, mode)
Definition: tello.py:272
def show_exception(ex)
Definition: utils.py:35
def __send_video_encoder_rate(self)
Definition: tello.py:315
def recv_file_data(self, data)
Definition: tello.py:640
def counter_clockwise(self, val)
Definition: tello.py:363
def __send_start_video(self)
Definition: tello.py:267
def set_video_mode(self, zoom=False)
Definition: tello.py:278
def set_low_bat_threshold(self, threshold)
Definition: tello.py:250


tello_driver
Author(s): Jordy van Appeven
autogenerated on Wed May 13 2020 03:34:54