Skip to content

Commit 2592708

Browse files
committed
Merge branch 'multi_drone'
2 parents 14f0652 + 1a2975f commit 2592708

14 files changed

Lines changed: 469 additions & 47 deletions

File tree

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
[submodule "crazyflie/deps/crazyflie_tools"]
22
path = crazyflie/deps/crazyflie_tools
33
url = https://github.com/whoenig/crazyflie_tools.git
4+
[submodule "third_party/CrazySim"]
5+
path = third_party/CrazySim
6+
url = https://github.com/gtfactslab/CrazySim.git

crazyflie/config/crazyflies.yaml

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,41 @@
22
fileversion: 3
33

44
robots:
5-
cf231:
5+
cf_1:
6+
enabled: true
7+
uri: udp://0.0.0.0:19850
8+
initial_position: [0.0, 0.0, 0.0]
9+
type: cf_sim
10+
11+
cf_2:
612
enabled: true
7-
uri: radio://0/80/2M/E7E7E7E7E1
8-
initial_position: [-4.5, 0.0, 0.]
9-
# initial_position: [0., 0.3, 0.0]
10-
type: cf21_single_PID # see robot_types
11-
# firmware_params:
12-
# kalman:
13-
# pNAcc_xy: 1.0 # default 0.5
14-
# firmware_logging:
15-
# enabled: true
16-
# custom_topics:
17-
# topic_name3:
18-
# frequency: 1
19-
# vars: ["acc.x", "acc.y"]
20-
# reference_frame: "odom_cf231" # use a custom reference frame if no global coordinate system is available
13+
uri: udp://0.0.0.0:19851
14+
initial_position: [1.0, 4.0, 0.0]
15+
type: cf_sim
16+
17+
# cf_3:
18+
# enabled: false
19+
# uri: udp://0.0.0.0:19852
20+
# initial_position: [0.0, 1.0, 0.0]
21+
# type: cf_sim
22+
23+
# cf_4:
24+
# enabled: false
25+
# uri: udp://0.0.0.0:19853
26+
# initial_position: [1.0, 0.0, 0.0]
27+
# type: cf_sim
2128

2229
# Definition of the various robot types
2330
robot_types:
31+
cf_sim:
32+
motion_capture:
33+
enabled: false
34+
big_quad: false
35+
firmware_logging:
36+
enabled: true
37+
default_topics:
38+
pose:
39+
frequency: 10
2440
cf21_single:
2541
motion_capture:
2642
tracking: "librigidbodytracker" # one of "vendor", "librigidbodytracker"

crazyflie/config/server.yaml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@
1414
query_all_values_on_connect: False
1515
# simulation related
1616
sim:
17-
max_dt: 0.001 #0.1 # artificially limit the step() function (set to 0 to disable)
18-
backend: np # see backend folder for a list
17+
max_dt: 0.001 # artificially limit the step() function (set to 0 to disable) TODO: Rephrase
18+
backend: np # see backend folder for a list (dynobench, neuralswarm, none, np, pinocchio, custom)
19+
backend_dt: 0.0005 # time step of the backend (~ backend_dt / max_dt dictates sim-to-real time ratio)
20+
backend_time_for_stamp: true # if true, the backend will use the time from the stamp, otherwise rclpy get time now
1921
visualizations: # see visualization folder for a list
2022
rviz:
2123
enabled: true
@@ -40,4 +42,4 @@
4042
calibration:
4143
tvec: [0,0,0]
4244
rvec: [ 0.61394313, -0.61394313, 1.48218982] # 45 deg tilt
43-
controller: pid # none, pid, mellinger
45+
controller: pid # none, pid, mellinger, custom

crazyflie_sim/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
## Simulation backends for crazyswarm2
2+
3+
The following simulation backends are compatible with SASLab crazyswarm2
4+
- pinnochio: https://stack-of-tasks.github.io/pinocchio/download.html and pip3 install pinocchio
5+
- dynobench: pip3 install dynobench
6+
- np: Pure numpy based implementation
7+
- None: Next state is desired state (high level quick check of how things work)
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
from __future__ import annotations
2+
3+
import numpy as np
4+
from rclpy.node import Node
5+
from rclpy.time import Time
6+
from rosgraph_msgs.msg import Clock
7+
import rowan
8+
9+
from ..sim_data_types import Action, State
10+
11+
12+
class Backend:
13+
"""Backend that uses a SASlab custom dynamics model."""
14+
15+
def __init__(self, node: Node, names: list[str], states: list[State], dt: float):
16+
self.node = node
17+
self.names = names
18+
self.clock_publisher = node.create_publisher(Clock, "clock", 10)
19+
self.t = 0
20+
self.dt = dt
21+
22+
self.uavs = []
23+
self.uavs_takeoff = []
24+
for state in states:
25+
uav = Quadrotor(state)
26+
self.uavs.append(uav)
27+
uav_backup = QuadrotorNumpy(state)
28+
self.uavs_takeoff.append(uav_backup)
29+
30+
def time(self) -> float:
31+
return self.t
32+
33+
def step(
34+
self,
35+
states_desired: list[State],
36+
actions: list[Action],
37+
disturbances: list[State],
38+
) -> list[State]:
39+
# advance the time
40+
self.t += self.dt
41+
next_states = []
42+
for i, (uav, action, disturbance) in enumerate(zip(self.uavs, actions, disturbances)):
43+
backup_uav = self.uavs_takeoff[i]
44+
if isinstance(action, np.ndarray):
45+
uav.step(action, self.dt, disturbance=disturbance)
46+
next_states.append(uav.state)
47+
backup_uav.state = uav.state
48+
else:
49+
backup_uav.step(action, self.dt, disturbance=disturbance)
50+
next_states.append(backup_uav.state)
51+
uav.state = backup_uav.state
52+
53+
# print(states_desired, actions, next_states)
54+
# publish the current clock
55+
clock_message = Clock()
56+
clock_message.clock = Time(seconds=self.time()).to_msg()
57+
self.clock_publisher.publish(clock_message)
58+
59+
return next_states
60+
61+
def shutdown(self):
62+
pass
63+
64+
65+
class QuadrotorNumpy:
66+
"""Basic rigid body quadrotor model (no drag) using numpy and rowan.
67+
Assumes control is forces."""
68+
69+
def __init__(self, state):
70+
# parameters (Crazyflie 2.0 quadrotor)
71+
self.mass = 0.034 # kg
72+
# self.J = np.array([
73+
# [16.56,0.83,0.71],
74+
# [0.83,16.66,1.8],
75+
# [0.72,1.8,29.26]
76+
# ]) * 1e-6 # kg m^2
77+
self.J = np.array([16.571710e-6, 16.655602e-6, 29.261652e-6])
78+
79+
# Note: we assume here that our control is forces
80+
arm_length = 0.046 # m
81+
arm = 0.707106781 * arm_length
82+
t2t = 0.006 # thrust-to-torque ratio
83+
self.B0 = np.array([
84+
[1, 1, 1, 1],
85+
[-arm, -arm, arm, arm],
86+
[-arm, arm, arm, -arm],
87+
[-t2t, t2t, -t2t, t2t]
88+
])
89+
self.g = 9.81 # not signed
90+
91+
if self.J.shape == (3, 3):
92+
self.inv_J = np.linalg.pinv(self.J) # full matrix -> pseudo inverse
93+
else:
94+
self.inv_J = 1 / self.J # diagonal matrix -> division
95+
96+
self.state = state
97+
98+
def step(self, action, dt, disturbance, f_a=np.zeros(3)):
99+
100+
# convert RPM -> Force
101+
def rpm_to_force(rpm):
102+
# polyfit using data and scripts from https://github.com/IMRCLab/crazyflie-system-id
103+
p = [2.55077341e-08, -4.92422570e-05, -1.51910248e-01]
104+
force_in_grams = np.polyval(p, rpm)
105+
force_in_newton = force_in_grams * 9.81 / 1000.0
106+
return np.maximum(force_in_newton, 0)
107+
108+
force = rpm_to_force(action.rpm)
109+
110+
# compute next state
111+
eta = np.dot(self.B0, force)
112+
f_u = np.array([0, 0, eta[0]])
113+
tau_u = np.array([eta[1], eta[2], eta[3]])
114+
# dynamics
115+
# dot{p} = v
116+
pos_next = self.state.pos + self.state.vel * dt + disturbance.pos * dt
117+
# mv = mg + R f_u + f_a
118+
vel_next = self.state.vel + (
119+
np.array([0, 0, -self.g]) +
120+
(rowan.rotate(self.state.quat, f_u) + f_a) / self.mass) * dt + (
121+
disturbance.vel * dt
122+
)
123+
124+
# dot{R} = R S(w)
125+
# to integrate the dynamics, see
126+
# https://www.ashwinnarayan.com/post/how-to-integrate-quaternions/, and
127+
# https://arxiv.org/pdf/1604.08139.pdf
128+
# Sec 4.5, https://arxiv.org/pdf/1711.02508.pdf
129+
omega_global = rowan.rotate(self.state.quat, self.state.omega)
130+
q_next = rowan.normalize(
131+
rowan.calculus.integrate(
132+
self.state.quat, omega_global, dt))
133+
134+
# mJ = Jw x w + tau_u
135+
omega_next = self.state.omega + (
136+
self.inv_J * (np.cross(self.J * self.state.omega, self.state.omega) + tau_u)) * dt
137+
138+
self.state.pos = pos_next
139+
self.state.vel = vel_next
140+
self.state.quat = q_next
141+
self.state.omega = omega_next
142+
143+
# if we fall below the ground, set velocities to 0
144+
if self.state.pos[2] < 0:
145+
self.state.pos[2] = 0
146+
self.state.vel = [0, 0, 0]
147+
self.state.omega = [0, 0, 0]
148+
149+
150+
class Quadrotor:
151+
"""Basic quadrotor model assuming control is attitude + thrust"""
152+
153+
def __init__(self, state):
154+
self.g = 9.81 # not signed
155+
self.state = state
156+
self.internal_state = np.zeros(7)
157+
self.i = 0
158+
159+
def step(self, action, dt, disturbance, f_a=np.zeros(3)):
160+
# dot{p} = v
161+
if isinstance(action, np.ndarray):
162+
pos_next = self.state.pos + self.state.vel * dt + disturbance.pos * dt
163+
# mv = mg + R f_u + f_a
164+
vel_next = (
165+
self.state.vel
166+
+ dt
167+
* np.array([self.g * action[1], -self.g * action[0], action[3] - self.g])
168+
+ dt * disturbance.vel
169+
)
170+
ypr = rowan.to_euler(self.state.quat)
171+
yaw_next = ypr[0] + action[2] * dt
172+
eulerangles = np.array([yaw_next, action[1], action[0]])
173+
q_next = rowan.from_euler(*eulerangles)
174+
# omega_next = np.array([0, 0, action[2]])
175+
omega_next = np.zeros(3) # TODO: check if sufficient
176+
177+
self.state.pos = pos_next
178+
self.state.vel = vel_next
179+
self.state.quat = q_next
180+
self.state.omega = omega_next
181+
182+
# if we fall below the ground, set velocities to 0
183+
if self.state.pos[2] < 0:
184+
self.state.pos[2] = 0
185+
self.state.vel = [0, 0, 0]
186+
self.state.omega = [0, 0, 0]
187+
else:
188+
self.state = self.state

crazyflie_sim/crazyflie_sim/backend/dynobench.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import numpy as np
44
from rclpy.node import Node
55
from rclpy.time import Time
6-
import robot_python
6+
import dynobench
77
from rosgraph_msgs.msg import Clock
88

99
# import sys
@@ -15,12 +15,12 @@
1515
class Backend:
1616
"""Backend that uses newton-euler rigid-body dynamics implemented in numpy."""
1717

18-
def __init__(self, node: Node, names: list[str], states: list[State]):
18+
def __init__(self, node: Node, names: list[str], states: list[State], dt: float):
1919
self.node = node
2020
self.names = names
2121
self.clock_publisher = node.create_publisher(Clock, 'clock', 10)
2222
self.t = 0
23-
self.dt = 0.0005
23+
self.dt = dt
2424

2525
self.uavs = []
2626
for state in states:
@@ -30,14 +30,14 @@ def __init__(self, node: Node, names: list[str], states: list[State]):
3030
def time(self) -> float:
3131
return self.t
3232

33-
def step(self, states_desired: list[State], actions: list[Action]) -> list[State]:
33+
def step(self, states_desired: list[State], actions: list[Action], disturbances: list[State]) -> list[State]:
3434
# advance the time
3535
self.t += self.dt
3636

3737
next_states = []
3838

39-
for uav, action in zip(self.uavs, actions):
40-
uav.step(action, self.dt)
39+
for uav, action, disturbance in zip(self.uavs, actions, disturbances):
40+
uav.step(action, disturbance, self.dt)
4141
next_states.append(uav.state)
4242

4343
# print(states_desired, actions, next_states)
@@ -84,11 +84,11 @@ class Quadrotor:
8484
"""Basic rigid body quadrotor model (no drag) using numpy and rowan."""
8585

8686
def __init__(self, state):
87-
self.uav = robot_python.robot_factory(
87+
self.uav = dynobench.robot_factory(
8888
str((Path(__file__).parent / 'data/dynobench/crazyflie2.yaml').resolve()), [], [])
8989
self.state = state
9090

91-
def step(self, action, dt):
91+
def step(self, action, disturbance, dt):
9292

9393
# m: 0.034
9494
# max_f: 1.3
@@ -97,6 +97,8 @@ def step(self, action, dt):
9797

9898
xnext = np.zeros(13)
9999
self.uav.step(xnext, sim_state2dynobench_state(self.state), normalized_force, dt)
100+
xnext[0:3] += disturbance.pos * dt
101+
xnext[7:10] += disturbance.vel * dt
100102
self.state = dynobench_state2sim_state(xnext)
101103

102104
# if we fall below the ground, set velocities to 0

crazyflie_sim/crazyflie_sim/backend/neuralswarm.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,12 +103,12 @@ def compute_Fa(self, data_self, data_neighbors):
103103
class Backend:
104104
"""Backend that is based on the one defined in np.py."""
105105

106-
def __init__(self, node: Node, names: list[str], states: list[State]):
106+
def __init__(self, node: Node, names: list[str], states: list[State], dt: float):
107107
self.node = node
108108
self.names = names
109109
self.clock_publisher = node.create_publisher(Clock, 'clock', 10)
110110
self.t = 0
111-
self.dt = 0.0005
111+
self.dt = dt
112112

113113
self.uavs = []
114114
for state in states:

crazyflie_sim/crazyflie_sim/backend/none.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,17 @@
1010
class Backend:
1111
"""Tracks the desired state perfectly (no physics simulation)."""
1212

13-
def __init__(self, node: Node, names: list[str], states: list[State]):
13+
def __init__(self, node: Node, names: list[str], states: list[State], dt: float):
1414
self.node = node
1515
self.names = names
1616
self.clock_publisher = node.create_publisher(Clock, 'clock', 10)
1717
self.t = 0
18-
self.dt = 0.1
18+
self.dt = dt
1919

2020
def time(self) -> float:
2121
return self.t
2222

23-
def step(self, states_desired: list[State], actions: list[Action]) -> list[State]:
23+
def step(self, states_desired: list[State], actions: list[Action], disturbances: list[State]) -> list[State]:
2424
# advance the time
2525
self.t += self.dt
2626

0 commit comments

Comments
 (0)