keyboard_and_video.py
Go to the documentation of this file.
1 """
2 tellopy sample using keyboard and video player
3 
4 Requires mplayer to record/save video.
5 
6 
7 Controls:
8 - tab to lift off
9 - WASD to move the drone
10 - space/shift to ascend/descent slowly
11 - Q/E to yaw slowly
12 - arrow keys to ascend, descend, or yaw quickly
13 - backspace to land, or P to palm-land
14 - enter to take a picture
15 - R to start recording video, R again to stop recording
16  (video and photos will be saved to a timestamped file in ~/Pictures/)
17 - Z to toggle camera zoom state
18  (zoomed-in widescreen or high FOV 4:3)
19 """
20 
21 import time
22 import sys
23 import tellopy
24 import pygame
25 import pygame.display
26 import pygame.key
27 import pygame.locals
28 import pygame.font
29 import os
30 import datetime
31 from subprocess import Popen, PIPE
32 # from tellopy import logger
33 
34 # log = tellopy.logger.Logger('TelloUI')
35 
36 prev_flight_data = None
37 video_player = None
38 video_recorder = None
39 font = None
40 wid = None
41 date_fmt = '%Y-%m-%d_%H%M%S'
42 
43 def toggle_recording(drone, speed):
44  global video_recorder
45  global date_fmt
46  if speed == 0:
47  return
48 
49  if video_recorder:
50  # already recording, so stop
51  video_recorder.stdin.close()
52  status_print('Video saved to %s' % video_recorder.video_filename)
53  video_recorder = None
54  return
55 
56  # start a new recording
57  filename = '%s/Pictures/tello-%s.mp4' % (os.getenv('HOME'),
58  datetime.datetime.now().strftime(date_fmt))
59  video_recorder = Popen([
60  'mencoder', '-', '-vc', 'x264', '-fps', '30', '-ovc', 'copy',
61  '-of', 'lavf', '-lavfopts', 'format=mp4',
62  # '-ffourcc', 'avc1',
63  # '-really-quiet',
64  '-o', filename,
65  ], stdin=PIPE)
66  video_recorder.video_filename = filename
67  status_print('Recording video to %s' % filename)
68 
69 def take_picture(drone, speed):
70  if speed == 0:
71  return
72  drone.take_picture()
73 
74 def palm_land(drone, speed):
75  if speed == 0:
76  return
77  drone.palm_land()
78 
79 def toggle_zoom(drone, speed):
80  # In "video" mode the drone sends 1280x720 frames.
81  # In "photo" mode it sends 2592x1936 (952x720) frames.
82  # The video will always be centered in the window.
83  # In photo mode, if we keep the window at 1280x720 that gives us ~160px on
84  # each side for status information, which is ample.
85  # Video mode is harder because then we need to abandon the 16:9 display size
86  # if we want to put the HUD next to the video.
87  if speed == 0:
88  return
89  drone.set_video_mode(not drone.zoom)
90  pygame.display.get_surface().fill((0,0,0))
91  pygame.display.flip()
92 
93 controls = {
94  'w': 'forward',
95  's': 'backward',
96  'a': 'left',
97  'd': 'right',
98  'space': 'up',
99  'left shift': 'down',
100  'right shift': 'down',
101  'q': 'counter_clockwise',
102  'e': 'clockwise',
103  # arrow keys for fast turns and altitude adjustments
104  'left': lambda drone, speed: drone.counter_clockwise(speed*2),
105  'right': lambda drone, speed: drone.clockwise(speed*2),
106  'up': lambda drone, speed: drone.up(speed*2),
107  'down': lambda drone, speed: drone.down(speed*2),
108  'tab': lambda drone, speed: drone.takeoff(),
109  'backspace': lambda drone, speed: drone.land(),
110  'p': palm_land,
111  'r': toggle_recording,
112  'z': toggle_zoom,
113  'enter': take_picture,
114  'return': take_picture,
115 }
116 
117 class FlightDataDisplay(object):
118  # previous flight data value and surface to overlay
119  _value = None
120  _surface = None
121  # function (drone, data) => new value
122  # default is lambda drone,data: getattr(data, self._key)
123  _update = None
124  def __init__(self, key, format, colour=(255,255,255), update=None):
125  self._key = key
126  self._format = format
127  self._colour = colour
128 
129  if update:
130  self._update = update
131  else:
132  self._update = lambda drone,data: getattr(data, self._key)
133 
134  def update(self, drone, data):
135  new_value = self._update(drone, data)
136  if self._value != new_value:
137  self._value = new_value
138  self._surface = font.render(self._format % (new_value,), True, self._colour)
139  return self._surface
140 
141 def flight_data_mode(drone, *args):
142  return (drone.zoom and "VID" or "PIC")
143 
145  return (video_recorder and "REC 00:00" or "") # TODO: duration of recording
146 
147 def update_hud(hud, drone, flight_data):
148  (w,h) = (158,0) # width available on side of screen in 4:3 mode
149  blits = []
150  for element in hud:
151  surface = element.update(drone, flight_data)
152  if surface is None:
153  continue
154  blits += [(surface, (0, h))]
155  # w = max(w, surface.get_width())
156  h += surface.get_height()
157  h += 64 # add some padding
158  overlay = pygame.Surface((w, h), pygame.SRCALPHA)
159  overlay.fill((0,0,0)) # remove for mplayer overlay mode
160  for blit in blits:
161  overlay.blit(*blit)
162  pygame.display.get_surface().blit(overlay, (0,0))
163  pygame.display.update(overlay.get_rect())
164 
165 def status_print(text):
166  pygame.display.set_caption(text)
167 
168 hud = [
169  FlightDataDisplay('height', 'ALT %3d'),
170  FlightDataDisplay('ground_speed', 'SPD %3d'),
171  FlightDataDisplay('battery_percentage', 'BAT %3d%%'),
172  FlightDataDisplay('wifi_strength', 'NET %3d%%'),
173  FlightDataDisplay(None, 'CAM %s', update=flight_data_mode),
174  FlightDataDisplay(None, '%s', colour=(255, 0, 0), update=flight_data_recording),
175 ]
176 
177 def flightDataHandler(event, sender, data):
178  global prev_flight_data
179  text = str(data)
180  if prev_flight_data != text:
181  update_hud(hud, sender, data)
182  prev_flight_data = text
183 
184 def videoFrameHandler(event, sender, data):
185  global video_player
186  global video_recorder
187  if video_player is None:
188  cmd = [ 'mplayer', '-fps', '35', '-really-quiet' ]
189  if wid is not None:
190  cmd = cmd + [ '-wid', str(wid) ]
191  video_player = Popen(cmd + ['-'], stdin=PIPE)
192 
193  try:
194  video_player.stdin.write(data)
195  except IOError as err:
196  status_print(str(err))
197  video_player = None
198 
199  try:
200  if video_recorder:
201  video_recorder.stdin.write(data)
202  except IOError as err:
203  status_print(str(err))
204  video_recorder = None
205 
206 def handleFileReceived(event, sender, data):
207  global date_fmt
208  # Create a file in ~/Pictures/ to receive image data from the drone.
209  path = '%s/Pictures/tello-%s.jpeg' % (
210  os.getenv('HOME'),
211  datetime.datetime.now().strftime('%Y-%m-%d_%H%M%S'))
212  with open(path, 'wb') as fd:
213  fd.write(data)
214  status_print('Saved photo to %s' % path)
215 
216 def main():
217  pygame.init()
218  pygame.display.init()
219  pygame.display.set_mode((1280, 720))
220  pygame.font.init()
221 
222  global font
223  font = pygame.font.SysFont("dejavusansmono", 32)
224 
225  global wid
226  if 'window' in pygame.display.get_wm_info():
227  wid = pygame.display.get_wm_info()['window']
228  print("Tello video WID:", wid)
229 
230  drone = tellopy.Tello()
231  drone.connect()
232  drone.start_video()
233  drone.subscribe(drone.EVENT_FLIGHT_DATA, flightDataHandler)
234  drone.subscribe(drone.EVENT_VIDEO_FRAME, videoFrameHandler)
235  drone.subscribe(drone.EVENT_FILE_RECEIVED, handleFileReceived)
236  speed = 30
237 
238  try:
239  while 1:
240  time.sleep(0.01) # loop with pygame.event.get() is too mush tight w/o some sleep
241  for e in pygame.event.get():
242  # WASD for movement
243  if e.type == pygame.locals.KEYDOWN:
244  print('+' + pygame.key.name(e.key))
245  keyname = pygame.key.name(e.key)
246  if keyname == 'escape':
247  drone.quit()
248  exit(0)
249  if keyname in controls:
250  key_handler = controls[keyname]
251  if type(key_handler) == str:
252  getattr(drone, key_handler)(speed)
253  else:
254  key_handler(drone, speed)
255 
256  elif e.type == pygame.locals.KEYUP:
257  print('-' + pygame.key.name(e.key))
258  keyname = pygame.key.name(e.key)
259  if keyname in controls:
260  key_handler = controls[keyname]
261  if type(key_handler) == str:
262  getattr(drone, key_handler)(0)
263  else:
264  key_handler(drone, 0)
265  except e:
266  print(str(e))
267  finally:
268  print('Shutting down connection to drone...')
269  if video_recorder:
270  toggle_recording(drone, 1)
271  drone.quit()
272  exit(1)
273 
274 if __name__ == '__main__':
275  main()
def __init__(self, key, format, colour=(255, 255, 255), update=None)
def handleFileReceived(event, sender, data)
def flightDataHandler(event, sender, data)
def update_hud(hud, drone, flight_data)
def videoFrameHandler(event, sender, data)


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