Skip to content

Commit b39356a

Browse files
committed
Added the christmas tree script
1 parent d3a1276 commit b39356a

File tree

1 file changed

+185
-0
lines changed

1 file changed

+185
-0
lines changed

examples/swarm/christmas_tree.py

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# || ____ _ __
4+
# +------+ / __ )(_) /_______________ _____ ___
5+
# | 0xBC | / __ / / __/ ___/ ___/ __ `/_ / / _ \
6+
# +------+ / /_/ / / /_/ /__/ / / /_/ / / /_/ __/
7+
# || || /_____/_/\__/\___/_/ \__,_/ /___/\___/
8+
#
9+
# Copyright (C) 2025 Bitcraze AB
10+
#
11+
# This program is free software; you can redistribute it and/or
12+
# modify it under the terms of the GNU General Public License
13+
# as published by the Free Software Foundation; either version 2
14+
# of the License, or (at your option) any later version.
15+
#
16+
# This program is distributed in the hope that it will be useful,
17+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
18+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19+
# GNU General Public License for more details.
20+
# You should have received a copy of the GNU General Public License
21+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
22+
"""
23+
Script for flying a swarm of 8 Crazyflies performing a coordinated spiral choreography
24+
resembling a Christmas tree outline in 3D space. Each drone takes off to a different
25+
height, flies in spiraling circular layers, and changes radius as it rises and descends,
26+
forming the visual structure of a cone when viewed from outside.
27+
28+
The script is using the high level commanded and has been tested with 3 Crazyradios 2.0
29+
and the Lighthouse positioning system.
30+
"""
31+
import math
32+
import time
33+
34+
import cflib.crtp
35+
from cflib.crazyflie.swarm import CachedCfFactory
36+
from cflib.crazyflie.swarm import Swarm
37+
from cflib.crazyflie.syncCrazyflie import SyncCrazyflie
38+
39+
uri1 = 'radio://0/30/2M/E7E7E7E701'
40+
uri2 = 'radio://0/30/2M/E7E7E7E702'
41+
uri3 = 'radio://0/30/2M/E7E7E7E703'
42+
43+
uri4 = 'radio://1/55/2M/E7E7E7E704'
44+
uri5 = 'radio://1/55/2M/E7E7E7E705'
45+
uri6 = 'radio://1/55/2M/E7E7E7E706'
46+
47+
uri7 = 'radio://2/70/2M/E7E7E7E707'
48+
uri8 = 'radio://2/70/2M/E7E7E7E708'
49+
50+
uris = [
51+
uri1,
52+
uri2,
53+
uri3,
54+
uri4,
55+
uri5,
56+
uri6,
57+
uri7,
58+
uri8,
59+
# Add more URIs if you want more copters in the swarm
60+
]
61+
62+
63+
def arm(scf: SyncCrazyflie):
64+
scf.cf.platform.send_arming_request(True)
65+
time.sleep(1.0)
66+
67+
68+
# center of the spiral
69+
x0 = 0
70+
y0 = 0
71+
z0 = 0.5
72+
73+
x_offset = 0.4 # Vertical distance between 2 layers
74+
z_offset = 0.5 # Radius difference between 2 layers
75+
76+
starting_x = {
77+
uri1: x0 + x_offset,
78+
uri2: x0 + 2*x_offset,
79+
uri3: x0 + 3*x_offset,
80+
uri4: x0 + 4*x_offset,
81+
uri5: x0 - x_offset,
82+
uri6: x0 - 2*x_offset,
83+
uri7: x0 - 3*x_offset,
84+
uri8: x0 - 4*x_offset,
85+
}
86+
87+
starting_z = {
88+
uri1: z0 + 3*z_offset,
89+
uri2: z0 + 2*z_offset,
90+
uri3: z0 + z_offset,
91+
uri4: z0,
92+
uri5: z0 + 3*z_offset,
93+
uri6: z0 + 2*z_offset,
94+
uri7: z0 + z_offset,
95+
uri8: z0,
96+
}
97+
98+
starting_yaw = {
99+
uri1: math.pi/2,
100+
uri2: -math.pi/2,
101+
uri3: math.pi/2,
102+
uri4: -math.pi/2,
103+
uri5: -math.pi/2,
104+
uri6: math.pi/2,
105+
uri7: -math.pi/2,
106+
uri8: math.pi/2,
107+
108+
}
109+
110+
rotate_clockwise = {
111+
uri1: False,
112+
uri2: True,
113+
uri3: False,
114+
uri4: True,
115+
uri5: False,
116+
uri6: True,
117+
uri7: False,
118+
uri8: True,
119+
}
120+
121+
takeoff_dur = {
122+
uri: value / 0.4
123+
for uri, value in starting_z.items()
124+
}
125+
126+
127+
def x_from_z(z):
128+
cone_width = 4 # m
129+
cone_height = 5 # m
130+
"""
131+
Returns the radius of the tree with a given z.
132+
"""
133+
return cone_width/2 - (cone_width/cone_height) * z
134+
135+
136+
def run_shared_sequence(scf: SyncCrazyflie):
137+
circle_duration = 5 # Duration of a full-circle
138+
commander = scf.cf.high_level_commander
139+
uri = scf._link_uri
140+
141+
commander.takeoff(starting_z[uri], takeoff_dur[uri])
142+
time.sleep(max(takeoff_dur.values())+1)
143+
144+
# Go to the starting position
145+
commander.go_to(starting_x[uri], y0, starting_z[uri], starting_yaw[uri], 4)
146+
time.sleep(5)
147+
148+
# Full circle with ascent=0
149+
commander.spiral(2*math.pi, abs(starting_x[uri]), abs(starting_x[uri]),
150+
ascent=0, duration_s=circle_duration, sideways=False, clockwise=rotate_clockwise[uri])
151+
time.sleep(circle_duration+1)
152+
153+
# Half circle with ascent=-0.5m
154+
commander.spiral(math.pi, abs(starting_x[uri]), x_from_z(starting_z[uri]-0.5*z_offset),
155+
ascent=-0.5*z_offset, duration_s=0.5*circle_duration, sideways=False,
156+
clockwise=rotate_clockwise[uri])
157+
time.sleep(0.5*circle_duration+0.5)
158+
159+
# Full circle with ascent=+1.0m
160+
commander.spiral(2*math.pi, x_from_z(starting_z[uri]-0.5*z_offset), x_from_z(starting_z[uri]+0.5*z_offset),
161+
ascent=z_offset, duration_s=circle_duration, sideways=False,
162+
clockwise=rotate_clockwise[uri])
163+
time.sleep(circle_duration+1)
164+
165+
# Half circle with ascent=-0.5m
166+
commander.spiral(math.pi, x_from_z(starting_z[uri]+0.5*z_offset), x_from_z(starting_z[uri]),
167+
ascent=-0.5*z_offset, duration_s=0.5*circle_duration, sideways=False,
168+
clockwise=rotate_clockwise[uri])
169+
time.sleep(0.5*circle_duration+1)
170+
171+
commander.land(0, takeoff_dur[uri])
172+
time.sleep(max(takeoff_dur.values())+3)
173+
174+
175+
if __name__ == '__main__':
176+
cflib.crtp.init_drivers()
177+
factory = CachedCfFactory(rw_cache='./cache')
178+
with Swarm(uris, factory=factory) as swarm:
179+
# swarm.reset_estimators()
180+
time.sleep(0.5)
181+
print('arming...')
182+
swarm.parallel_safe(arm)
183+
print('starting sequence...')
184+
swarm.parallel_safe(run_shared_sequence)
185+
time.sleep(1)

0 commit comments

Comments
 (0)