-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathflight_modes.py
253 lines (217 loc) · 9.08 KB
/
flight_modes.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
"""
This file contains all the possible flight modes you can use to take control of the drone or making it executes missions
"""
import os
from abc import ABC, abstractmethod
from getch import getch
from time import sleep
from math import sqrt, ceil, cos, sin, radians
from toolbox import back_to_base, command_from_key
__all__ = ['OpenPipeMode', 'ReactiveMode', 'ActFromFileMode', 'ActFromActionListMode', 'PictureMission']
class AbstractFlightMode(ABC):
""" Abstract Base Class of the Strategy Pattern to create other flight modes """
def __init__(self, swarm):
super().__init__()
self.swarm = swarm
self.all_images = []
@abstractmethod
def start(self, **options):
"""Base abstract method which hould be overwritted to implement behaviour"""
class ReactiveMode(AbstractFlightMode):
"""Fast reacting mode with pre-binded keys"""
def __init__(self, swarm, **options):
super().__init__(swarm)
self.start(**options)
@back_to_base
def start(self, **options):
"""
On Windows getch() capture two 'keys' and first one is useless
On Unix special keys need 3 getch (Even arrows on Linux don't give the same keycode as Windows)
See : https://en.wikipedia.org/wiki/ANSI_escape_code
"""
# Ctrl + Z and Ctrl + C
exit_char = ['\x1a', '\x03']
while self.swarm.is_connected:
_input = getch()
try:
# Type conversion required on Windows
_input = _input.decode()
except AttributeError:
pass
if _input in exit_char:
for index in range(len(self.swarm)):
self.swarm.execute_actions([f'{index}-land'])
self.swarm.end_connection = True
self.swarm.command_socket.close()
break
# Useless escape chars
elif _input in ['[', '\x1b']:
continue
elif _input == 'p':
picture = self.swarm.take_picture()
self.all_images.append(picture)
elif command_from_key(_input) is not None:
for index in range(len(self.swarm)):
self.swarm.execute_actions([f'{index}-{command_from_key(_input)}'])
else:
print('Nothing attach to this key ' + _input)
self.swarm.save_pictures(self.all_images)
class OpenPipeMode(AbstractFlightMode):
"""Constant opened pipto communicate with the drone"""
def __init__(self, swarm, **options):
super().__init__(swarm)
self.start(**options)
@back_to_base
def start(self, **options):
"""Allow user to send command specifing drone command and drone id"""
user_command = ''
print('\nYou are using open pipe mode, enter "exit" to leave')
try :
#While last command wasn't land / flag isn't raised / at least one drone is connected
while self.swarm.is_connected:
try:
user_input = input('Enter the the index of the drone followed by command as "0-takeoff" or "1-cw 90" \n')
except KeyboardInterrupt :
# Prevent from socket being still alived at the end of the programm
break
# print(f'user input "{user_input}"')
if user_input == 'exit':
break
elif user_input == 'p':
picture = self.swarm.take_picture()
self.all_images.append(picture)
elif user_input: # user_input != ""
self.swarm.execute_actions([user_input])
self.swarm.save_pictures(self.all_images)
except EOFError:
print('User wants to disconnect')
class ActFromFileMode(AbstractFlightMode):
"""Read the whole content of a file an execute all actions contained in it"""
def __init__(self, swarm, **options):
super().__init__(swarm)
self.start(**options)
@back_to_base
def start(self, **options):
"""Read file and execute actions"""
filename = options.get('filename')
project_path = os.path.dirname(os.path.realpath(__file__))
dir_path = os.path.sep.join((project_path, 'missions_dir'))
path = os.path.sep.join((dir_path, filename))
try:
with open(path, 'r') as file:
content = file.read()
actions = content.split('\n')
self.swarm.execute_actions(actions)
except FileNotFoundError:
print(f'There is no file at {path}')
class ActFromActionListMode(AbstractFlightMode):
"""Excute a list of instructions"""
def __init__(self, swarm, **options):
super().__init__(swarm)
self.start(**options)
@back_to_base
def start(self, **options):
actions = options.get('actions')
self.swarm.execute_actions(actions)
class PictureMission(AbstractFlightMode):
"""🐧 Mode used for photogrametry purpose"""
def __init__(self, swarm, **options):
"""
:params: object_position is a tuple (x,y)
:params: object_dim is a tuple of the size (length, width, heigth)
"""
super().__init__(swarm)
if not self.swarm.video_stream:
print('You forgot to activate video stream on the drone')
self.swarm.end_connection = True
self.start(**options)
@back_to_base
def start(self, **options):
"""Need to be tested
Object coordinates :
length
←────────→
↑ ┌────────┐
│ │ │
width │ │ │
│ │ │
↓ └────────┼→ x
↓
y
"""
object_distance = options.get('object_distance')
object_dim = options.get('object_dim')
if object_distance is None or object_dim is None:
print('Please give object distance and dimensions')
return
center, pos = self.get_in_front(object_distance, object_dim)
self.move_around(center, pos, object_dim)
def get_in_front(self, object_distance: tuple, object_dim: tuple):
"""Move the drone just in front of the object"""
x, y = object_distance
length, width, _ = object_dim
radius = sqrt(length**2 + width**2)/2
center = (x - ceil(length/2), y + ceil(width/2))
print(f'center {center}')
x_pos = center[0] # - half drone width
# +/- marge
if center[1] >= 0:
y_pos = int(center[1] - radius - width)
else:
y_pos = int(center[1] + radius + width)
print(f'In front coords : {x_pos},{y_pos}')
return (center, (x_pos, y_pos))
def take_ground_angle_picture(self, hight: int):
"""Set of instructions to land the drone, take a picture and takeoff again"""
self.swarm.execute_actions(['0-land'])
sleep(3)
self.all_images.append(self.swarm.take_picture())
self.swarm.execute_actions(['0-takeoff'])
sleep(3)
if hight > 100:
self.swarm.execute_actions([f'0-up {hight-100}'])
else:
self.swarm.execute_actions([f'0-down {100-hight}'])
sleep(3)
def move_around(self, center: tuple, actual_pos: tuple, object_dim: tuple):
"""Navigate in circle around the object (+up/down)"""
x, y = actual_pos
heigth = object_dim[2]
actual_heigth = 10
circle_radius_coef = 1.2
number_of_points = 8
theta = 360/number_of_points
next_x = int((center[0] + (x - center[0])*cos(radians(theta)) - (y - center[1])*sin(radians(theta))))
next_y = int((center[1] + (y - center[1])*cos(radians(theta)) - (x - center[0])*sin(radians(theta))))
x_mvmt = (next_x - x) * circle_radius_coef
y_mvmt = (next_y - y) * circle_radius_coef
self.swarm.execute_actions(['0-takeoff'])
sleep(3)
self.swarm.execute_actions(['0-down 50'])
sleep(2)
# Get in front
self.swarm.execute_actions([f'0-forward {y}'])
sleep(3)
if x > 0:
self.swarm.execute_actions([f'0-right {x}'])
else:
self.swarm.execute_actions([f'0-left {abs(x)}'])
sleep(5)
while actual_heigth < heigth:
for _ in range(number_of_points):
# Only the first time
# ERROR
self.take_ground_angle_picture(actual_heigth)
self.all_images.append(self.swarm.take_picture())
self.swarm.execute_actions([f'0-right {x_mvmt}'])
sleep(3)
self.swarm.execute_actions([f'0-forward {y_mvmt}'])
sleep(3)
self.swarm.execute_actions([f'0-ccw {theta}'])
sleep(3)
actual_heigth += 20
self.swarm.execute_actions([f'0-up {20}'])
sleep(3)
self.swarm.execute_actions(['0-land'])
print(len(self.all_images))
self.swarm.save_pictures(self.all_images)