Skip to content

Commit 3db043b

Browse files
authored
Merge pull request #518 from bitcraze/Aris/swarm-sharing-data-example
Added swarm-sharing-data example
2 parents 0f1781c + 2a7dd80 commit 3db043b

File tree

1 file changed

+208
-0
lines changed

1 file changed

+208
-0
lines changed

examples/swarm/leader-follower.py

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# || ____ _ __
4+
# +------+ / __ )(_) /_______________ _____ ___
5+
# | 0xBC | / __ / / __/ ___/ ___/ __ `/_ / / _ \
6+
# +------+ / /_/ / / /_/ /__/ / / /_/ / / /_/ __/
7+
# || || /_____/_/\__/\___/_/ \__,_/ /___/\___/
8+
#
9+
# Copyright (C) 2017-2018 Bitcraze AB
10+
#
11+
# Crazyflie Nano Quadcopter Client
12+
#
13+
# This program is free software; you can redistribute it and/or
14+
# modify it under the terms of the GNU General Public License
15+
# as published by the Free Software Foundation; either version 2
16+
# of the License, or (at your option) any later version.
17+
#
18+
# This program is distributed in the hope that it will be useful,
19+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
20+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21+
# GNU General Public License for more details.
22+
# You should have received a copy of the GNU General Public License
23+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
24+
'''
25+
Example of a swarm sharing data and performing a leader-follower scenario
26+
using the motion commander.
27+
28+
The swarm takes off and the drones hover until the follower's local coordinate
29+
system is aligned with the global one. Then, the leader performs its own
30+
trajectory based on commands from the motion commander. The follower is
31+
constantly commanded to keep a defined distance from the leader, meaning that
32+
it is moving towards the leader when their current distance is larger than the
33+
defined one and away from the leader in the opposite scenario.
34+
All movements refer to the local coordinate system of each drone.
35+
36+
This example is intended to work with an absolute positioning system, it has
37+
been tested with the lighthouse positioning system.
38+
39+
This example aims at documenting how to use the collected data to define new
40+
trajectories in real-time. It also indicates how to use the swarm class to
41+
feed the Crazyflies completely different asynchronized trajectories in parallel.
42+
'''
43+
import math
44+
import time
45+
46+
import cflib.crtp
47+
from cflib.crazyflie.log import LogConfig
48+
from cflib.crazyflie.swarm import CachedCfFactory
49+
from cflib.crazyflie.swarm import Swarm
50+
from cflib.positioning.motion_commander import MotionCommander
51+
52+
# Change uris according to your setup
53+
# URIs in a swarm using the same radio must also be on the same channel
54+
URI1 = 'radio://0/80/2M/E7E7E7E7E7' # Follower
55+
URI2 = 'radio://0/80/2M/E7E7E7E7E8' # Leader
56+
57+
58+
DEFAULT_HEIGHT = 0.75
59+
DEFAULT_VELOCITY = 0.5
60+
x1 = [0]
61+
y1 = [0]
62+
z1 = [0]
63+
x2 = [0]
64+
y2 = [0]
65+
z2 = [0]
66+
yaw1 = [0]
67+
yaw2 = [0]
68+
d = 0
69+
70+
# List of URIs
71+
uris = {
72+
URI1,
73+
URI2,
74+
}
75+
76+
77+
def wait_for_param_download(scf):
78+
while not scf.cf.param.is_updated:
79+
time.sleep(1.0)
80+
print('Parameters downloaded for', scf.cf.link_uri)
81+
82+
83+
def arm(scf):
84+
scf.cf.platform.send_arming_request(True)
85+
time.sleep(1.0)
86+
87+
88+
def pos_to_vel(x1, y1, x2, y2, dist):
89+
'''
90+
This function takes two points on the x-y plane and outputs
91+
two components of the velocity vector: one along the x-axis
92+
and one along the y-axis. The combined vector represents the
93+
total velocity, pointing from point 1 to point 2, with a
94+
magnitude equal to the DEFAULT_VELOCITY. These 2 velocity
95+
vectors are meant to be used by the motion commander.
96+
The distance between them is taken as an argument because it
97+
is constanlty calculated by position_callback().
98+
'''
99+
if dist == 0:
100+
Vx = 0
101+
Vy = 0
102+
else:
103+
Vx = DEFAULT_VELOCITY * (x2-x1)/dist
104+
Vy = DEFAULT_VELOCITY * (y2-y1)/dist
105+
return Vx, Vy
106+
107+
108+
def position_callback(uri, data):
109+
global yaw1
110+
global x1, y1, z1, x2, y2, z2, d
111+
if uri == URI1: # Follower
112+
x1.append(data['stateEstimate.x'])
113+
y1.append(data['stateEstimate.y'])
114+
z1.append(data['stateEstimate.z'])
115+
yaw1.append(data['stateEstimate.yaw'])
116+
elif uri == URI2: # Leader
117+
x2.append(data['stateEstimate.x'])
118+
y2.append(data['stateEstimate.y'])
119+
z2.append(data['stateEstimate.z'])
120+
yaw2.append(data['stateEstimate.yaw'])
121+
122+
d = math.sqrt(pow((x1[-1]-x2[-1]), 2)+pow((y1[-1]-y2[-1]), 2))
123+
124+
125+
def start_position_printing(scf):
126+
log_conf1 = LogConfig(name='Position', period_in_ms=10)
127+
log_conf1.add_variable('stateEstimate.x', 'float')
128+
log_conf1.add_variable('stateEstimate.y', 'float')
129+
log_conf1.add_variable('stateEstimate.z', 'float')
130+
log_conf1.add_variable('stateEstimate.yaw', 'float')
131+
scf.cf.log.add_config(log_conf1)
132+
log_conf1.data_received_cb.add_callback(lambda _timestamp, data, _logconf: position_callback(scf.cf.link_uri, data))
133+
log_conf1.start()
134+
135+
136+
def leader_follower(scf):
137+
r_min = 0.8 # The minimum distance between the 2 drones
138+
r_max = 1.0 # The maximum distance between the 2 drones
139+
with MotionCommander(scf, default_height=DEFAULT_HEIGHT) as mc:
140+
141+
# The follower turns until it is aligned with the global coordinate system
142+
while abs(yaw1[-1]) > 2:
143+
if scf.__dict__['_link_uri'] == URI1: # Follower
144+
if yaw1[-1] > 0:
145+
mc.start_turn_right(36 if abs(yaw1[-1]) > 15 else 9)
146+
elif yaw1[-1] < 0:
147+
mc.start_turn_left(36 if abs(yaw1[-1]) > 15 else 9)
148+
149+
elif scf.__dict__['_link_uri'] == URI2: # Leader
150+
mc.stop()
151+
time.sleep(0.005)
152+
153+
time.sleep(0.5)
154+
155+
start_time = time.time()
156+
# Define the flight time after the follower is aligned
157+
end_time = time.time() + 20
158+
159+
while time.time() < end_time:
160+
161+
if scf.__dict__['_link_uri'] == URI1: # Follower
162+
if d > r_max:
163+
cmd_vel_x, cmd_vel_y = pos_to_vel(x1[-1], y1[-1], x2[-1], y2[-1], d)
164+
elif d >= r_min and d <= r_max:
165+
cmd_vel_x = 0
166+
cmd_vel_y = 0
167+
elif d < r_min:
168+
opp_cmd_vel_x, opp_cmd_vel_y = pos_to_vel(x1[-1], y1[-1], x2[-1], y2[-1], d)
169+
cmd_vel_x = -opp_cmd_vel_x
170+
cmd_vel_y = -opp_cmd_vel_y
171+
172+
mc.start_linear_motion(cmd_vel_x, cmd_vel_y, 0)
173+
174+
elif scf.__dict__['_link_uri'] == URI2: # Leader
175+
# Define the sequence of the leader
176+
if time.time() - start_time < 3:
177+
mc.start_forward(DEFAULT_VELOCITY)
178+
elif time.time() - start_time < 6:
179+
mc.start_back(DEFAULT_VELOCITY)
180+
elif time.time() - start_time < 20:
181+
mc.start_circle_right(0.9, DEFAULT_VELOCITY)
182+
else:
183+
mc.stop()
184+
185+
time.sleep(0.005)
186+
mc.land()
187+
188+
189+
if __name__ == '__main__':
190+
cflib.crtp.init_drivers()
191+
192+
factory = CachedCfFactory(rw_cache='./cache')
193+
with Swarm(uris, factory=factory) as swarm:
194+
195+
swarm.reset_estimators()
196+
197+
swarm.parallel_safe(arm)
198+
199+
print('Waiting for parameters to be downloaded...')
200+
swarm.parallel_safe(wait_for_param_download)
201+
202+
time.sleep(1)
203+
204+
swarm.parallel_safe(start_position_printing)
205+
time.sleep(0.5)
206+
207+
swarm.parallel_safe(leader_follower)
208+
time.sleep(1)

0 commit comments

Comments
 (0)