Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 40 additions & 40 deletions crazyflow/control/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,46 @@
R = TypeVar("R")


class Control(StrEnum):
"""Control type of the simulated onboard controller."""

state = "state"
"""State control takes [x, y, z, vx, vy, vz, ax, ay, az, qx, qy, qz, qw, wx, wy, wz].

Note:
Recommended frequency is >=20 Hz.

Warning:
Only the yaw of the attitude quaternion is used, as in the firmware. The so_rpy family
ignores the body rate setpoint.
"""
attitude = "attitude"
"""Attitude control takes [roll, pitch, yaw, collective thrust].

Note:
Recommended frequency is >=100 Hz.
"""
body_rate = "body_rate"
"""Body rate control takes [wx, wy, wz, collective thrust].

Note:
Recommended frequency is >=200 Hz.
"""
force_torque = "force_torque"
"""Force and torque control takes [fc, tx, ty, tz].

Note:
Recommended frequency is >=500 Hz.
"""
rotor_vel = "rotor_vel"
"""Rotor velocity control takes [w1, w2, w3, w4] in RPMs.

Note:
Recommended frequency is >=500 Hz.
"""
default = attitude


def parametrize(
fn: Callable[P, R], drone: str, xp: ModuleType | None = None, device: str | None = None
) -> Callable[P, R]:
Expand Down Expand Up @@ -83,46 +123,6 @@ def load_params(
return to_xp(filter_to_signature(merged, fn), xp=xp, device=device)


class Control(StrEnum):
"""Control type of the simulated onboard controller."""

state = "state"
"""State control takes [x, y, z, vx, vy, vz, ax, ay, az, qx, qy, qz, qw, wx, wy, wz].

Note:
Recommended frequency is >=20 Hz.

Warning:
Only the yaw of the attitude quaternion is used, as in the firmware. The so_rpy family
ignores the body rate setpoint.
"""
attitude = "attitude"
"""Attitude control takes [roll, pitch, yaw, collective thrust].

Note:
Recommended frequency is >=100 Hz.
"""
body_rate = "body_rate"
"""Body rate control takes [wx, wy, wz, collective thrust].

Note:
Recommended frequency is >=200 Hz.
"""
force_torque = "force_torque"
"""Force and torque control takes [fc, tx, ty, tz].

Note:
Recommended frequency is >=500 Hz.
"""
rotor_vel = "rotor_vel"
"""Rotor velocity control takes [w1, w2, w3, w4] in RPMs.

Note:
Recommended frequency is >=500 Hz.
"""
default = attitude


@jax.jit
def controllable(step: Array, freq: int, control_steps: Array, control_freq: int) -> Array:
"""Check which worlds can currently update their controllers.
Expand Down
48 changes: 44 additions & 4 deletions crazyflow/control/mellinger/params.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ pwm_max = 65535
thrust_min = 0.012817578393224994 # in N per motor
thrust_max = 0.12 # in N per motor
torque_pwm_max = [32000.0, 32000.0, 32000.0]
L = 0.03253
L = 0.03253 # Distance from axis to rotor, i.e., arm / sqrt(2)
rpm2thrust = [0.0, -5.382196214637237e-7, 2.4582929831265485e-10]
rpm2torque = [0.0, 1.410454111996297e-9, 1.4592584373980652e-12]
thrust2torque = 0.007350862856566459
Expand Down Expand Up @@ -47,7 +47,7 @@ pwm_max = 65535
thrust_min = 0.012817578393224994 # in N per motor
thrust_max = 0.12 # in N per motor
torque_pwm_max = [32000.0, 32000.0, 32000.0]
L = 0.03253
L = 0.03253 # Distance from axis to rotor, i.e., arm / sqrt(2)
rpm2thrust = [0.0, -3.6200226530383495e-7, 1.6060924304100328e-10] # Index is order
rpm2torque = [0.0, -2.2665265829562245e-9, 1.1149485566919186e-12] # Index is order
thrust2torque = 0.0069928948992470565
Expand Down Expand Up @@ -87,7 +87,7 @@ pwm_max = 65535
thrust_min = 0.01922636758983749 # in N per motor
thrust_max = 0.18 # in N per motor
torque_pwm_max = [32000.0, 32000.0, 32000.0]
L = 0.03253
L = 0.03253 # Distance from axis to rotor, i.e., arm / sqrt(2)
rpm2thrust = [0.0, -7.167227176573658e-7, 2.9401303690194613e-10] # Index is order
rpm2torque = [0.0, 5.815894847811497e-10, 1.331813874166509e-12] # Index is order
thrust2torque = 0.005355990836477486
Expand Down Expand Up @@ -122,7 +122,7 @@ int_err_max = [1.0, 1.0, 1500.0]
[cf21B_500.core]
gravity_vec = [0.0, 0.0, -9.81]
mass = 0.0393 # The controller is using the wrong mass by default
L = 0.035355
L = 0.035355 # Distance from axis to rotor, i.e., arm / sqrt(2)
rpm2thrust = [0.0, -3.133427287299859e-7, 4.407354891648379e-10]
rpm2torque = [0.0, 1.65886356219615e-9, 2.4693477924534137e-12]
thrust2torque = 0.00593893393599368
Expand Down Expand Up @@ -157,3 +157,43 @@ kw = [20000.0, 20000.0, 12000.0]
ki_m = [0.0, 0.0, 500.0]
kd_omega = [200.0, 200.0, 0.0]
int_err_max = [1.0, 1.0, 1500.0]

[hb_x500] # made up numbers, not used anyway
[hb_x500.core]
gravity_vec = [0.0, 0.0, -9.81]
mass = 2.28
L = 0.17678 # Distance from axis to rotor, i.e., arm / sqrt(2)
rpm2thrust = [0.0, 1e-6, 1e-9] # Unknown
rpm2torque = [0.0, 1e-9, 1e-12] # Unknown
thrust2torque = 0.01 # Unknown
mixing_matrix = [
[-1.0, -1.0, 1.0, 1.0],
[-1.0, 1.0, 1.0, -1.0],
[-1.0, 1.0, -1.0, 1.0]
]
pwm_min = 0
pwm_max = 1
thrust_min = 1 # in N per motor
thrust_max = 12.13 # in N per motor
mass_thrust = 0.02061006
torque_pwm_max = [0.4882887, 0.4882887, 0.4882887]

[hb_x500.state2attitude]
kp = [12.0, 12.0, 35.0]
kd = [6.0, 6.0, 8.0]
ki = [1.0, 1.0, 1.0]
int_err_max = [4.0, 4.0, 2.0]

[hb_x500.attitude2force_torque]
kR = [1.068132, 1.068132, 0.9155413]
kw = [0.3051804, 0.3051804, 0.1831083]
ki_m = [0.0, 0.0, 0.007629511]
kd_omega = [0.003051804, 0.003051804, 0.0]
int_err_max = [1.0, 1.0, 1500.0]

[hb_x500.body_rate2force_torque]
kR = [1.068132, 1.068132, 0.9155413]
kw = [0.3051804, 0.3051804, 0.1831083]
ki_m = [0.0, 0.0, 0.007629511]
kd_omega = [0.003051804, 0.003051804, 0.0]
int_err_max = [1.0, 1.0, 1500.0]
3 changes: 2 additions & 1 deletion crazyflow/drones/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
# * **cf2x_P250** — Crazyflie 2.x with plus propellers
# * **cf2x_T350** — Crazyflie 2.x with thrust upgrade kit
# * **cf21B_500** — Crazyflie 2.1 Brushless with 500 mAh battery
available_drones: tuple[str, ...] = ("cf2x_L250", "cf2x_P250", "cf2x_T350", "cf21B_500")
# * **hb_x500** — Holybro X500 V2
available_drones: tuple[str, ...] = ("cf2x_L250", "cf2x_P250", "cf2x_T350", "cf21B_500", "hb_x500")

__all__ = ["available_drones", "load_params"]

Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
189 changes: 189 additions & 0 deletions crazyflow/drones/hb_x500.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
<mujoco model="hb_x500">
<!-- Holybro X500 V2 (PX4 Development Kit). Meshes are converted from the official STEP file
(x500v2-frame.step, 2022-07-19, "3D CAD File" at
https://holybro.com/products/px4-development-kit-x500-v2). All STLs are binary, in millimetres.
The STEP is Y-up with the camera bracket on -X, so parts are rotated
(x, y, z) = (-x_step, z_step, y_step) into the MuJoCo convention. The CAD contains no
propellers. The props reconstruct Holybro 530084 from the product photographs at
https://www.premium-modellbau.de/holybro-s500-v2-1045-ersatzpropeller-2x-cw-2x-ccw
and https://holybro.com/products/spare-parts-s500-v2-kit (Propeller1045).
Diameter/pitch are nominal 10x4.5 inches; blade sections and hub dimensions are estimates.
The inner blade twist is relaxed to 8-14 degrees while retaining the blade outline.
PropL spins CCW seen from above, like the Crazyflie props. The body origin lies on the underside of the
bottom plate, in the plane of the arm clamps, which is roughly the CoM with a battery mounted
underneath. x points forward (camera bracket), y left, z up. -->
<compiler inertiafromgeom="false" meshdir="assets" autolimits="true" />

<asset>
<material name="black" rgba="0.102 0.102 0.102 1"/>
<material name="carbon" rgba="0.16 0.16 0.17 1"/>
<material name="gray" rgba="0.3 0.3 0.3 1"/>
<material name="dark-gray" rgba="0.2 0.2 0.22 1"/>
<material name="white" rgba="0.93 0.93 0.93 1"/>
<material name="green" rgba="0.13 0.62 0.28 1"/>
<material name="blue" rgba="0.13 0.33 0.64 1"/>

<mesh name="frame" file="hb_x500/hb_x500_frame.stl" scale="1e-3 1e-3 1e-3"/>
<mesh name="arm_plastics" file="hb_x500/hb_x500_arm_plastics.stl" scale="1e-3 1e-3 1e-3"/>
<mesh name="battery_holder" file="hb_x500/hb_x500_battery_holder.stl" scale="1e-3 1e-3 1e-3"/>
<mesh name="tubes" file="hb_x500/hb_x500_tubes.stl" scale="1e-3 1e-3 1e-3"/>
<!-- One shared mesh holding every screw and nut on the frame and the GPS mounts. -->
<mesh name="screws" file="hb_x500/hb_x500_screws.stl" scale="1e-3 1e-3 1e-3"/>
<mesh name="motors" file="hb_x500/hb_x500_motors.stl" scale="1e-3 1e-3 1e-3"/>
<mesh name="leg" file="hb_x500/hb_x500_leg.stl" scale="1e-3 1e-3 1e-3"/>
<mesh name="propguard_center" file="hb_x500/hb_x500_propguard_center.stl" scale="1e-3 1e-3 1e-3"/>
<mesh name="propguard_left" file="hb_x500/hb_x500_propguard_left.stl" scale="1e-3 1e-3 1e-3"/>
<mesh name="propguard_right" file="hb_x500/hb_x500_propguard_right.stl" scale="1e-3 1e-3 1e-3"/>
<mesh name="propguard_leg" file="hb_x500/hb_x500_propguard_leg.stl" scale="1e-3 1e-3 1e-3"/>
<mesh name="propL" file="hb_x500/hb_x500_PropL.stl" scale="1e-3 1e-3 1e-3" smoothnormal="true"/>
<mesh name="propR" file="hb_x500/hb_x500_PropR.stl" scale="1e-3 1e-3 1e-3" smoothnormal="true"/>
<mesh name="propHub" file="hb_x500/hb_x500_PropHub.stl" scale="1e-3 1e-3 1e-3" smoothnormal="true"/>
<!-- Holybro 30-degree H-RTK helical antenna mount:
https://www.unmannedtechshop.co.uk/products/holybro-30-gps-antenna-mount-h-rtk-helical
GPS arms follow the supplied 30-degree mount drawing (dimensions in mm):
https://cdn.shopify.com/s/files/1/0914/9972/5181/files/30_AntennaMount_Dimensions_1800x1800_47d5d653-a047-444d-a3a7-cb5a3b054260.webp
Antenna is an ArduSimple lightweight helical tripleband L-band IP67:
https://www.ardusimple.de/product/lightweight-helical-tripleband-l-band-antenna-ip67/
Housing: 40 diameter x 82.8 high, 34.3 bottom neck, simplified SMA adapter.
https://www.ardusimple.com/wp-content/uploads/2021/02/helical3b_dimensions.jpg -->
<mesh name="gps_mount" file="hb_x500/hb_x500_gps_mount.stl" scale="1e-3 1e-3 1e-3"/>
<mesh name="gps_tube" file="hb_x500/hb_x500_gps_tube.stl" scale="1e-3 1e-3 1e-3" smoothnormal="true"/>
<!-- SMA adapter collar between the mount and the antenna; GPS screws are in `screws`. -->
<mesh name="gps_sma" file="hb_x500/hb_x500_gps_sma.stl" scale="1e-3 1e-3 1e-3"/>
<mesh name="gps_antenna" file="hb_x500/hb_x500_gps_antenna.stl" scale="1e-3 1e-3 1e-3" smoothnormal="true"/>
<!-- The whole `drone` body fused into one mesh with transforms baked in (123408 faces). -->
<mesh name="hb_x500_fused" file="hb_x500/hb_x500_fused.stl" scale="1e-3 1e-3 1e-3"/>
</asset>

<default>
<default class="hb_x500">
<default class="visual">
<geom group="2" type="mesh" contype="0" conaffinity="0" />
</default>
<default class="collision">
<geom group="3" condim="1" />
</default>
<site group="5" />
</default>
</default>

<worldbody>
<body name="drone" pos="0 0 0.25" childclass="hb_x500">
<site name="CoM" />
<inertial pos="0.0 0.0 0.0" mass="2.28" diaginertia="0.024 0.024 0.044" />

<!-- Meshes -->
<geom mesh="frame" material="black" class="visual" />
<geom mesh="arm_plastics" material="blue" class="visual" />
<geom mesh="battery_holder" material="black" class="visual" pos="0.029 0 -0.013" quat="0 0.70710678 0.70710678 0"/>
<geom mesh="battery_holder" material="black" class="visual" pos="-0.041 0 -0.013" quat="0 0.70710678 0.70710678 0"/>
<!-- LemonRC 4300 mAh 14.8 V 35C, 136 x 43 x 33 mm, 422 g:
https://www.reichelt.de/de/de/shop/produkt/lipo_akku_lemonrc_4300_-_14_8v_35c_-385695 -->
<geom name="battery" type="box" size="0.068 0.0215 0.0165" pos="0 0 -0.0435" material="white" class="visual" />
<geom mesh="tubes" material="carbon" class="visual" />
<geom mesh="screws" material="gray" class="visual" />
<geom mesh="motors" material="black" class="visual" pos="0 0 0.0065" />

<!-- Collision geometry is estimated based on the visual mesh. We use a sphere for maximum
computational efficiency with larger swarms. Used by default-->
<geom name="col_sphere" type="sphere" size="0.41" pos="0 0 0.071" class="collision" contype="1" conaffinity="1" />
<!-- For higher accuracy with lower performance, you can use a box. By default off. -->
<geom name="col_box" type="box" size="0.327 0.327 0.1675" pos="0 0 0.0875" class="collision" rgba="0.5 0.5 0.5 0" contype="0" conaffinity="0" />

<!-- Motors and Propellers. Motor centres: 176.78 mm on both axes (500 mm wheelbase).
Hub underside is 4.5 mm below its origin and seats on the shaft shoulder at 64.5 mm. -->
<site name="motor0" pos="0.17678 -0.17678 0.069" />
<site name="motor1" pos="-0.17678 -0.17678 0.069" />
<site name="motor2" pos="-0.17678 0.17678 0.069" />
<site name="motor3" pos="0.17678 0.17678 0.069" />
<body name="prop0" pos="0.17678 -0.17678 0.069" euler="0 0 45">
<geom mesh="propL" material="black" class="visual"/>
<geom mesh="propHub" material="gray" class="visual"/>
</body>
<body name="prop1" pos="-0.17678 -0.17678 0.069" euler="0 0 135">
<geom mesh="propR" material="black" class="visual"/>
<geom mesh="propHub" material="gray" class="visual"/>
</body>
<body name="prop2" pos="-0.17678 0.17678 0.069" euler="0 0 225">
<geom mesh="propL" material="black" class="visual"/>
<geom mesh="propHub" material="gray" class="visual"/>
</body>
<body name="prop3" pos="0.17678 0.17678 0.069" euler="0 0 315">
<geom mesh="propR" material="black" class="visual"/>
<geom mesh="propHub" material="gray" class="visual"/>
</body>

<body name="propguard0" pos="0.17678 -0.17678 0.0245" euler="0 0 -135">
<geom mesh="propguard_center" material="white" class="visual" quat="0 0 0.70710678 0.70710678"/>
<geom mesh="propguard_left" material="white" class="visual" quat="0 0 0.70710678 0.70710678"/>
<geom mesh="propguard_right" material="white" class="visual" quat="0 0 0.70710678 0.70710678"/>
<geom mesh="propguard_leg" material="black" class="visual" quat="0 0 0.70710678 0.70710678"/>
<geom mesh="propguard_leg" material="black" class="visual" quat="0.27059805 0.27059805 -0.65328148 -0.65328148"/>
<geom mesh="propguard_leg" material="black" class="visual" quat="0.27059805 0.27059805 0.65328148 0.65328148"/>
</body>
<body name="propguard1" pos="-0.17678 -0.17678 0.0245" euler="0 0 -225">
<geom mesh="propguard_center" material="green" class="visual" quat="0 0 0.70710678 0.70710678"/>
<geom mesh="propguard_left" material="green" class="visual" quat="0 0 0.70710678 0.70710678"/>
<geom mesh="propguard_right" material="green" class="visual" quat="0 0 0.70710678 0.70710678"/>
<geom mesh="propguard_leg" material="black" class="visual" quat="0 0 0.70710678 0.70710678"/>
<geom mesh="propguard_leg" material="black" class="visual" quat="0.27059805 0.27059805 -0.65328148 -0.65328148"/>
<geom mesh="propguard_leg" material="black" class="visual" quat="0.27059805 0.27059805 0.65328148 0.65328148"/>
</body>
<body name="propguard2" pos="-0.17678 0.17678 0.0245" euler="0 0 45">
<geom mesh="propguard_center" material="green" class="visual" quat="0 0 0.70710678 0.70710678"/>
<geom mesh="propguard_left" material="green" class="visual" quat="0 0 0.70710678 0.70710678"/>
<geom mesh="propguard_right" material="green" class="visual" quat="0 0 0.70710678 0.70710678"/>
<geom mesh="propguard_leg" material="black" class="visual" quat="0 0 0.70710678 0.70710678"/>
<geom mesh="propguard_leg" material="black" class="visual" quat="0.27059805 0.27059805 -0.65328148 -0.65328148"/>
<geom mesh="propguard_leg" material="black" class="visual" quat="0.27059805 0.27059805 0.65328148 0.65328148"/>
</body>
<body name="propguard3" pos="0.17678 0.17678 0.0245" euler="0 0 -45">
<geom mesh="propguard_center" material="white" class="visual" quat="0 0 0.70710678 0.70710678"/>
<geom mesh="propguard_left" material="white" class="visual" quat="0 0 0.70710678 0.70710678"/>
<geom mesh="propguard_right" material="white" class="visual" quat="0 0 0.70710678 0.70710678"/>
<geom mesh="propguard_leg" material="black" class="visual" quat="0 0 0.70710678 0.70710678"/>
<geom mesh="propguard_leg" material="black" class="visual" quat="0.27059805 0.27059805 -0.65328148 -0.65328148"/>
<geom mesh="propguard_leg" material="black" class="visual" quat="0.27059805 0.27059805 0.65328148 0.65328148"/>
</body>
<body name="leg0" pos="0.17678 -0.17678 0.0245" euler="0 0 -135">
<geom mesh="leg" material="black" class="visual" quat="0 0 0.70710678 0.70710678"/>
</body>
<body name="leg1" pos="-0.17678 -0.17678 0.0245" euler="0 0 -225">
<geom mesh="leg" material="black" class="visual" quat="0 0 0.70710678 0.70710678"/>
</body>
<body name="leg2" pos="-0.17678 0.17678 0.0245" euler="0 0 45">
<geom mesh="leg" material="black" class="visual" quat="0 0 0.70710678 0.70710678"/>
</body>
<body name="leg3" pos="0.17678 0.17678 0.0245" euler="0 0 -45">
<geom mesh="leg" material="black" class="visual" quat="0 0 0.70710678 0.70710678"/>
</body>

<!-- Left/right GPS arms on the top plate: 164 mm between clamp pins,
12 mm tube diameter, 135.6 mm platform height, 436.2 mm antenna baseline. -->
<body name="gps_left" pos="0 0.05 0.032" euler="0 0 90">
<geom mesh="gps_mount" material="black" class="visual"/>
<geom mesh="gps_tube" material="carbon" class="visual"/>
<geom mesh="gps_sma" material="gray" class="visual"/>
<geom mesh="gps_antenna" material="black" class="visual" pos="0.1681 0 0.1396"/>
</body>
<body name="gps_right" pos="0 -0.05 0.032" euler="0 0 -90">
<geom mesh="gps_mount" material="black" class="visual"/>
<geom mesh="gps_tube" material="carbon" class="visual"/>
<geom mesh="gps_sma" material="gray" class="visual"/>
<geom mesh="gps_antenna" material="black" class="visual" pos="0.1681 0 0.1396"/>
</body>

<!-- Cameras -->
<camera name="track_cam" pos="-2.5 0 1.25" xyaxes="0 -1 0 1 0 2" mode="trackcom" />
<camera name="fpv_cam" pos="0.115 0 -0.03" euler="0 -90 -90" fovy="70" resolution="640 480" />
</body>

<body name="drone_fused" pos="0 0 0.25" childclass="hb_x500">
<inertial pos="0.0 0.0 0.0" mass="2.28" diaginertia="0.024 0.024 0.044" />
<geom mesh="hb_x500_fused" material="dark-gray" class="visual" />
<geom name="col_sphere_fused" type="sphere" size="0.41" pos="0 0 0.071" class="collision" contype="1" conaffinity="1" />
<geom name="col_box_fused" type="box" size="0.327 0.327 0.1675" pos="0 0 0.0875" class="collision" rgba="0.5 0.5 0.5 0" contype="0" conaffinity="0" />
</body>
</worldbody>

</mujoco>
Loading
Loading