55from typing import TYPE_CHECKING
66
77from array_api_compat import array_namespace
8+ from scipy .spatial .transform import Rotation as R
89
9- import drone_models .utils .cf2 as cf2
10- import drone_models .utils .rotation as R
10+ import drone_models .utils .rotation as rotation
1111from drone_models .transform import force2pwm , motor_force2rotor_speed , pwm2force
12- from drone_models .utils .constants_controllers import cntrl_const_mel
1312
1413if TYPE_CHECKING :
1514 from array_api_typing import Array
@@ -24,6 +23,7 @@ def pos2attitude(
2423 ang_vel : Array ,
2524 cmd : Array ,
2625 constants : Constants ,
26+ parameters : dict [str , Array | float ],
2727 dt : float = 1 / 500 ,
2828 i_error : Array | None = None ,
2929) -> tuple [Array , Array ]:
@@ -44,6 +44,7 @@ def pos2attitude(
4444 cmd: Full state command in SI units and rad with shape (..., 13). The entries are
4545 [x, y, z, vx, vy, vz, ax, ay, az, yaw, roll_rate, pitch_rate, yaw_rate].
4646 constants: Drone specific constants.
47+ parameters: Controller specific parameters. TODO make class?
4748 dt: Time since last call.
4849 i_error: Integral error of the position controller with shape (..., 3).
4950
@@ -63,17 +64,15 @@ def pos2attitude(
6364 # l.151 ff Integral Error
6465 if i_error is None :
6566 i_error = xp .zeros_like (pos )
66- i_error = xp .clip (
67- i_error + r_error * dt , - cntrl_const_mel ["i_range" ], cntrl_const_mel ["i_range" ]
68- )
67+ i_error = xp .clip (i_error + r_error * dt , - parameters ["i_range" ], parameters ["i_range" ])
6968 # l. 161 Desired thrust [F_des]
7069 # => only one case here, since setpoint is always in absolute mode
7170 # Note: since we've defined the gravity in z direction, a "-" needs to be added
7271 target_thrust = (
73- cntrl_const_mel ["mass" ] * (setpoint_acc - constants .GRAVITY_VEC )
74- + cntrl_const_mel ["kp" ] * r_error
75- + cntrl_const_mel ["kd" ] * v_error
76- + cntrl_const_mel ["ki" ] * i_error
72+ parameters ["mass" ] * (setpoint_acc - constants .GRAVITY_VEC )
73+ + parameters ["kp" ] * r_error
74+ + parameters ["kd" ] * v_error
75+ + parameters ["ki" ] * i_error
7776 )
7877 # l. 178 Rate-controlled YAW is moving YAW angle setpoint
7978 # => only one case here, since the setpoint is always in absolute mode
@@ -88,7 +87,7 @@ def pos2attitude(
8887 # Taking the dot product of the last axis:
8988 current_thrust = xp .vecdot (target_thrust , z_axis , axis = - 1 )
9089 # l. 207 Calculate axis [zB_des]
91- z_axis_desired = target_thrust / xp .linalg .norm (target_thrust )
90+ z_axis_desired = target_thrust / xp .linalg .vector_norm (target_thrust )
9291 # l. 210 [xC_des]
9392 # x_axis_desired = z_axis_desired x [sin(yaw), cos(yaw), 0]^T
9493 x_c_des_x = xp .cos (desiredYaw )
@@ -97,16 +96,16 @@ def pos2attitude(
9796 x_c_des = xp .stack ((x_c_des_x , x_c_des_y , x_c_des_z ), axis = - 1 )
9897 # [yB_des]
9998 y_axis_desired = xp .linalg .cross (z_axis_desired , x_c_des )
100- y_axis_desired = y_axis_desired / xp .linalg .norm (y_axis_desired )
99+ y_axis_desired = y_axis_desired / xp .linalg .vector_norm (y_axis_desired )
101100 # [xB_des]
102101 x_axis_desired = xp .linalg .cross (y_axis_desired , z_axis_desired )
103102 # converting desired axis to rotation matrix and then to RPY
104103 matrix = xp .stack ((x_axis_desired , y_axis_desired , z_axis_desired ), axis = - 1 )
105104 command_RPY = R .from_matrix (matrix ).as_euler ("xyz" , degrees = False )
106105 # l. 283
107- thrust = cntrl_const_mel ["massThrust" ] * current_thrust
106+ thrust = parameters ["massThrust" ] * current_thrust
108107 # Transform thrust into N to keep uniform interface
109- thrust = cf2 . pwm2force (thrust , constants , perMotor = False )
108+ thrust = pwm2force (thrust , constants . THRUST_MAX , constants . PWM_MAX ) * 4
110109 command_rpyt = xp .concat ((command_RPY , thrust [..., None ]), axis = - 1 )
111110 return command_rpyt , i_error
112111
@@ -116,8 +115,9 @@ def attitude2force_torque(
116115 quat : Array ,
117116 vel : Array ,
118117 ang_vel : Array ,
119- command_rpyt : Array ,
118+ cmd : Array ,
120119 constants : Constants ,
120+ parameters : dict [str , Array | float ],
121121 dt : float = 1 / 500 ,
122122 i_error_m : Array | None = None ,
123123 ang_vel_des : Array | None = None ,
@@ -131,8 +131,9 @@ def attitude2force_torque(
131131 quat: Drone orientation as xyzw quaternion with shape (..., 4).
132132 vel: Drone velocity with shape (..., 3).
133133 ang_vel: Drone angular drone velocity in rad/s with shape (..., 3).
134- command_rpyt : Commanded attitude (roll, pitch, yaw) and total thrust [rad, rad, rad, N]
134+ cmd : Commanded attitude (roll, pitch, yaw) and total thrust [rad, rad, rad, N]
135135 constants (Constants): Constants of the specific drone
136+ parameters: Controller specific parameters. TODO make class?
136137 dt: Time since last call.
137138 i_error_m: Integral error.
138139 ang_vel_des: Desired angular velocity in rad/s.
@@ -142,10 +143,10 @@ def attitude2force_torque(
142143 Returns:
143144 4 Motor forces [N], i_error_m
144145 """
145- xp = array_namespace (pos )
146- force_des = command_rpyt [..., - 1 ] # Total thrust in N
147- rpy_des = command_rpyt [..., :- 1 ]
148- axis_flip = xp .array ([1 , - 1 , 1 ]) # to change the direction of the y axis
146+ xp = array_namespace (quat )
147+ force_des = cmd [..., - 1 ] # Total thrust in N
148+ rpy_des = cmd [..., - 4 :- 1 ]
149+ axis_flip = xp .asarray ([1.0 , - 1.0 , 1.0 ]) # to change the direction of the y axis
149150 # l. 220 ff [eR]. We're using the "inefficient" code path from the firmware
150151 rot = R .from_quat (quat )
151152 rot_des = R .from_euler ("xyz" , rpy_des , degrees = False )
@@ -174,16 +175,18 @@ def attitude2force_torque(
174175 if i_error_m is None :
175176 i_error_m = xp .zeros_like (pos )
176177 i_error_m = i_error_m - eR * dt
177- i_error_m = xp .clip (i_error_m , - cntrl_const_mel ["i_range_m" ], cntrl_const_mel ["i_range_m" ])
178+ i_error_m = xp .clip (i_error_m , - parameters ["i_range_m" ], parameters ["i_range_m" ])
178179 # l. 278 ff Moment:
179180 torque_pwm = (
180- - cntrl_const_mel ["kR" ] * eR
181- + cntrl_const_mel ["kw" ] * ew
182- + cntrl_const_mel ["ki_m" ] * i_error_m
183- + cntrl_const_mel ["kd_omega" ] * err_d
181+ - parameters ["kR" ] * eR
182+ + parameters ["kw" ] * ew
183+ + parameters ["ki_m" ] * i_error_m
184+ + parameters ["kd_omega" ] * err_d
184185 )
185186 # l. 297 ff
186- torque_pwm = xp .clip (torque_pwm , - 32_000 , 32_000 )
187+ torque_pwm = xp .clip (
188+ torque_pwm , - parameters ["torque_pwm_range" ], parameters ["torque_pwm_range" ]
189+ )
187190 torque_pwm = xp .where ((force_des > 0 )[..., None ], torque_pwm , 0.0 )
188191 # Info: The following part is NOT part of the Mellinger controller, but of the firmware.
189192 # The mixing of force and torque is done on the PWM level. We therefore mix those here according
0 commit comments