14 from .
import video_stream
16 from . protocol
import *
17 from .
import dispatcher
28 EVENT_LOG = EVENT_LOG_HEADER
44 CONNECTED_EVENT = EVENT_CONNECTED
45 WIFI_EVENT = EVENT_WIFI
46 LIGHT_EVENT = EVENT_LIGHT
47 FLIGHT_EVENT = EVENT_FLIGHT_DATA
49 TIME_EVENT = EVENT_TIME
50 VIDEO_FRAME_EVENT = EVENT_VIDEO_FRAME
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
75 self.
lock = threading.Lock()
97 self.
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
98 self.sock.bind((
'', self.
port))
99 self.sock.settimeout(2.0)
107 Set_loglevel controls the output messages. Valid levels are 108 LOG_ERROR, LOG_WARN, LOG_INFO, LOG_DEBUG and LOG_ALL. 114 Get_video_stream is used to prepare buffer object which receive video data from the drone. 116 newly_created =
False 118 log.info(
'get video stream')
134 """Connect is used to send the initial connection request to the drone.""" 138 """Wait_for_connection will block until the connection is established.""" 139 if not self.connected.wait(timeout):
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))
151 """Subscribe a event such as EVENT_CONNECTED, EVENT_FLIGHT_DATA, EVENT_VIDEO_FRAME and so on.""" 152 dispatcher.connect(handler, signal)
155 args.update({
'data': data})
160 log.debug(
'publish signal=%s, args=%s' % (event, args))
161 dispatcher.send(event, sender=self, **args)
164 """Takeoff tells the drones to liftoff and start flying.""" 165 log.info(
'set altitude limit 30m')
166 pkt =
Packet(SET_ALT_LIMIT_CMD)
170 log.info(
'takeoff (cmd=0x%02x seq=0x%04x)' % (TAKEOFF_CMD, self.
pkt_seq_num))
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)
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))
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)
200 """Quit stops the internal threads.""" 206 self.log.debug(
'get altitude limit (cmd=0x%02x seq=0x%04x)' % (
208 pkt =
Packet(ALT_LIMIT_MSG)
213 self.log.info(
'set altitude limit=%s (cmd=0x%02x seq=0x%04x)' % (
215 pkt =
Packet(SET_ALT_LIMIT_CMD)
216 pkt.add_byte(int(limit))
224 self.log.debug(
'get attitude limit (cmd=0x%02x seq=0x%04x)' % (
226 pkt =
Packet(ATT_LIMIT_MSG)
231 self.log.info(
'set attitude limit=%s (cmd=0x%02x seq=0x%04x)' % (
233 pkt =
Packet(ATT_LIMIT_CMD)
236 pkt.add_byte( int(
float_to_hex(float(limit))[4:6], 16) )
244 self.log.debug(
'get low battery threshold (cmd=0x%02x seq=0x%04x)' % (
246 pkt =
Packet(LOW_BAT_THRESHOLD_MSG)
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))
260 log.info(
'send_time (cmd=0x%02x seq=0x%04x)' % (TIME_CMD, self.
pkt_seq_num))
261 pkt =
Packet(TIME_CMD, 0x50)
268 pkt =
Packet(VIDEO_START_CMD, 0x60)
273 pkt =
Packet(VIDEO_MODE_CMD)
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)' % (
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))
295 """Set_exposure sets the drone camera exposure level. Valid levels are 0, 1, and 2.""" 296 if level < 0
or 2 < level:
298 log.info(
'set exposure (cmd=0x%02x seq=0x%04x)' % (EXPOSURE_CMD, self.
pkt_seq_num))
303 pkt =
Packet(EXPOSURE_CMD, 0x48)
309 """Set_video_encoder_rate sets the drone video encoder rate.""" 310 log.info(
'set video encoder rate (cmd=0x%02x seq=%04x)' %
316 pkt =
Packet(VIDEO_ENCODER_RATE_CMD, 0x68)
322 log.info(
'take picture')
326 """Up tells the drone to ascend. Pass in an int from 0-100.""" 327 log.info(
'up(val=%d)' % 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
336 """Forward tells the drone to go forward. Pass in an int from 0-100.""" 337 log.info(
'forward(val=%d)' % 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
346 """Right tells the drone to go right. Pass in an int from 0-100.""" 347 log.info(
'right(val=%d)' % 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
357 Clockwise tells the drone to rotate in a clockwise direction. 358 Pass in an int from 0-100. 360 log.info(
'clockwise(val=%d)' % val)
365 CounterClockwise tells the drone to rotate in a counter-clockwise direction. 366 Pass in an int from 0-100. 368 log.info(
'counter_clockwise(val=%d)' % val)
369 self.
left_x = val / 100.0 * -1
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)
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)
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)
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)
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)
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)
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)
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)
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) 448 log.info(
'set_throttle(val=%4.2f)' % throttle)
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) 457 log.info(
'set_yaw(val=%4.2f)' % yaw)
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) 466 log.info(
'set_pitch(val=%4.2f)' % pitch)
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) 475 log.info(
'set_roll(val=%4.2f)' % roll)
479 pkt =
Packet(STICK_CMD, 0x60)
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
486 11 bits (-1024 ~ +1023) x 4 axis = 44 bits 487 44 bits will be packed in to 6 bytes (48 bits) 489 axis4 axis3 axis2 axis1 492 98765432109876543210987654321098765432109876543210 494 byte5 byte4 byte3 byte2 byte1 byte0 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)
512 pkt =
Packet(LOG_HEADER_MSG, 0x50)
521 """Send_packet is used to send a command packet to the drone.""" 523 cmd = pkt.get_buffer()
526 except socket.error
as err:
528 log.error(
"send_packet: %s" % str(err))
530 log.info(
"send_packet: %s" % str(err))
536 pkt =
Packet(command, type, payload)
541 if isinstance(data, str):
542 data = bytearray([x
for x
in data])
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]))
555 if data[0] != START_OF_PACKET:
556 log.info(
'start of packet != %02x (%02x) (ignored)' % (START_OF_PACKET, data[0]))
558 log.info(
' %s' % str(map(chr, data))[1:-1])
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])))
570 self.log_data_file.write(data[12:-2])
572 elif cmd == LOG_DATA_MSG:
573 log.debug(
"recv: log_data: length=%d, %s" % (len(data[9:]),
byte_to_hexstring(data[9:])))
576 self.log_data.update(data[10:])
578 self.log_data_file.write(data[10:-2])
579 except Exception
as ex:
580 log.error(
'%s' % str(ex))
583 elif cmd == LOG_CONFIG_MSG:
584 log.debug(
"recv: log_config: length=%d, %s" % (len(data[9:]),
byte_to_hexstring(data[9:])))
586 elif cmd == WIFI_MSG:
590 elif cmd == ALT_LIMIT_MSG:
592 elif cmd == ATT_LIMIT_MSG:
594 elif cmd == LOW_BAT_THRESHOLD_MSG:
596 elif cmd == LIGHT_MSG:
599 elif cmd == FLIGHT_MSG:
602 log.debug(
"recv: flight data: %s" % str(flight_data))
604 elif cmd == TIME_CMD:
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" %
611 elif cmd == TELLO_CMD_FILE_SIZE:
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))
626 log.warn(
' file size: payload too small: %s' %
byte_to_hexstring(pkt.get_data()))
629 elif cmd == TELLO_CMD_FILE_DATA:
641 (filenum,chunk,fragment,size) = struct.unpack(
'<HLLH', data[0:12])
642 file = self.file_recv.get(filenum,
None)
648 if file.recvFragment(chunk, fragment, size, data[12:12+size]):
652 payload=struct.pack(
'<BHL', 0, filenum, chunk))
658 payload=struct.pack(
'<BHL', 1, filenum, chunk))
662 payload=struct.pack(
'<HL', filenum, file.size))
669 path =
'%s/Documents/tello-%s.dat' % (
671 datetime.datetime.now().strftime(
'%Y-%m-%d_%H%M%S'))
672 log.info(
'record log data in %s' % path)
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)))
688 event_disconnected =
True 694 event_connected =
True 706 event_disconnected =
True 710 event_disconnected =
True 716 if cur_state != self.
state:
717 log.info(
'state transit %s -> %s' % (cur_state, self.
state))
723 if event_disconnected:
725 self.connected.clear()
736 data, server = sock.recvfrom(self.
udpsize)
739 except socket.timeout
as ex:
741 log.error(
'recv: timeout')
743 except Exception
as ex:
744 log.error(
'recv: %s' % str(ex))
747 log.info(
'exit from the recv thread.')
750 log.info(
'start video thread')
752 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
754 sock.bind((
'', port))
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))
760 prev_video_data =
None 768 data, server = sock.recvfrom(self.
udpsize)
769 now = datetime.datetime.now()
775 loss = video_data.gap(prev_video_data)
780 prev_video_data = video_data
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))
790 history.append([now, len(data),
byte(data[0])*256 +
byte(data[1])])
791 if 100 < len(history):
792 history = history[1:]
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 '')))
804 history = history[-1:]
816 log.info((
'video data %d bytes %5.1fKB/sec' %
826 except socket.timeout
as ex:
827 log.error(
'video recv: timeout')
830 except Exception
as ex:
831 log.error(
'video recv: %s' % str(ex))
834 log.info(
'exit from the video thread.')
836 if __name__ ==
'__main__':
837 print(
'You can use test.py for testing.')
def get_video_stream(self)
def __publish(self, event, data=None, args)
def __fix_range(self, val, min=-1.0, max=1.0)
def subscribe(self, signal, handler)
def set_loglevel(self, level)
def set_alt_limit(self, limit)
def __state_machine(self, event, sender, data, args)
def send_packet(self, pkt)
def record_log_data(self, path=None)
def set_pitch(self, pitch)
def __send_time_command(self)
def wait_for_connection(self, timeout=None)
def __send_stick_command(self)
def get_low_bat_threshold(self)
def byte_to_hexstring(buf)
def set_throttle(self, throttle)
def send_packet_data(self, command, type=0x68, payload=[])
def __send_exposure(self)
def __process_packet(self, data)
def __send_ack_log(self, id)
def __send_conn_req(self)
def set_video_encoder_rate(self, rate)
def set_exposure(self, level)
def __init__(self, port=9000)
def flip_forwardleft(self)
def set_att_limit(self, limit)
def __send_video_mode(self, mode)
def __send_video_encoder_rate(self)
def recv_file_data(self, data)
def counter_clockwise(self, val)
def __send_start_video(self)
def set_video_mode(self, zoom=False)
def set_low_bat_threshold(self, threshold)
def flip_forwardright(self)