Skip to content

Commit cda0b13

Browse files
authored
Custom namespaces for spacecraft mpc (DISCOWER#19)
* Added custom namespace argument * Updated README for namespace argument * Updated mpc parameters
1 parent aca13d5 commit cda0b13

9 files changed

+106
-58
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
build/*
2+
install/*
3+
log/*
24
dist/*
35
__pycache__/
46
poetry.lock

README.md

+5
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,8 @@ In order to launch the mpc quadrotor in a ros2 launchfile,
3939
```
4040
ros2 launch px4_mpc mpc_quadrotor_launch.py
4141
```
42+
43+
Use a custom namespace
44+
```
45+
ros2 launch px4_mpc mpc_spacecraft_launch.py namespace:=<namespace>
46+
```

px4_mpc/px4_mpc/controllers/spacecraft_direct_allocation_mpc.py

+10-7
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
class SpacecraftDirectAllocationMPC():
4040
def __init__(self, model):
4141
self.model = model
42-
self.Tf = 1.0
42+
self.Tf = 5.0
4343
self.N = 50
4444

4545
self.x0 = np.array([0.01, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0])
@@ -65,12 +65,12 @@ def setup(self, x0, N_horizon, Tf):
6565
ocp.dims.N = N_horizon
6666

6767
# set cost
68-
Q_mat = np.diag([3e1, 3e1, 3e1,
69-
2e1, 2e1, 2e2,
70-
1e2, 1e2, 1e2, 1e2,
71-
1e1, 1e1, 1e1])
68+
Q_mat = np.diag([5e1, 5e1, 5e1,
69+
2e3, 2e3, 2e3,
70+
5e2, 5e2, 5e2, 5e2,
71+
3e2, 3e2, 3e2])
7272
Q_e = 20 * Q_mat
73-
R_mat = np.diag([0.5e1] * 4)
73+
R_mat = np.diag([1e1] * 4)
7474

7575
ocp.cost.cost_type = 'NONLINEAR_LS'
7676
ocp.cost.cost_type_e = 'NONLINEAR_LS'
@@ -87,11 +87,14 @@ def setup(self, x0, N_horizon, Tf):
8787
ocp.constraints.lbu = np.array([-Fmax, -Fmax, -Fmax, -Fmax])
8888
ocp.constraints.ubu = np.array([+Fmax, +Fmax, +Fmax, +Fmax])
8989
ocp.constraints.idxbu = np.array([0, 1, 2, 3])
90+
# ocp.constraints.lbx = np.array([0, -1.54, -10 -0.05, -0.05, -10, -1.01, -1.01, -1.01, -1.01, -10, -10, -10])
91+
# ocp.constraints.ubx = np.array([4, 1.5, 10, 0.05, 0.05, 10, 1.01, 1.01, 1.01, 1.01, 10, 10, 10])
92+
# ocp.constraints.idxbx = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12])
9093

9194
ocp.constraints.x0 = x0
9295

9396
# set options
94-
ocp.solver_options.qp_solver = 'FULL_CONDENSING_DAQP' # FULL_CONDENSING_QPOASES
97+
ocp.solver_options.qp_solver = 'PARTIAL_CONDENSING_HPIPM' # FULL_CONDENSING_QPOASES
9598
# PARTIAL_CONDENSING_HPIPM, FULL_CONDENSING_QPOASES, FULL_CONDENSING_HPIPM,
9699
# PARTIAL_CONDENSING_QPDUNES, PARTIAL_CONDENSING_OSQP, FULL_CONDENSING_DAQP
97100
ocp.solver_options.hessian_approx = 'GAUSS_NEWTON' # 'GAUSS_NEWTON', 'EXACT'

px4_mpc/px4_mpc/controllers/spacecraft_rate_mpc.py

+6-8
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
class SpacecraftRateMPC():
4040
def __init__(self, model):
4141
self.model = model
42-
self.Tf = 1.0
42+
self.Tf = 5.0
4343
self.N = 50
4444

4545
self.x0 = np.array([0.01, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0])
@@ -66,11 +66,9 @@ def setup(self, x0, N_horizon, Tf):
6666
ocp.dims.N = N_horizon
6767

6868
# set cost
69-
Q_mat = 2*np.diag([10e1, 10e1, 10e1, 100e1, 100e1, 100e1, 1.0, 1.0, 1.0, 1.0])
70-
Q_e = 2*np.diag([30e2, 30e2, 30e2, 100e2, 100e2, 100e2, 10.0, 10.0, 10.0, 10.0])
71-
R_mat = 2*np.diag([1e1, 1e1, 1e1, 1e1, 1e1, 1e1])
72-
73-
# TODO: How do you add terminal costs?
69+
Q_mat = np.diag([5e1, 5e1, 5e1, 2e3, 2e3, 2e3, 5e2, 5e2, 5e2, 5e2])
70+
Q_e = 20 * Q_mat
71+
R_mat = 2*np.diag([1e1, 1e1, 1e1, 3e2, 3e2, 3e2])
7472

7573
# the 'EXTERNAL' cost type can be used to define general cost terms
7674
# NOTE: This leads to additional (exact) hessian contributions when using GAUSS_NEWTON hessian.
@@ -91,8 +89,8 @@ def setup(self, x0, N_horizon, Tf):
9189
ocp.cost.yref_e = np.array([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0])
9290

9391
# set constraints
94-
ocp.constraints.lbu = np.array([-Fmax, -Fmax, -Fmax, -wmax, -wmax, -0.5*wmax])
95-
ocp.constraints.ubu = np.array([+Fmax, +Fmax, +Fmax, wmax, wmax, 0.5*wmax])
92+
ocp.constraints.lbu = np.array([-Fmax, -Fmax, -Fmax, -wmax, -wmax, -wmax])
93+
ocp.constraints.ubu = np.array([+Fmax, +Fmax, +Fmax, wmax, wmax, wmax])
9694
ocp.constraints.idxbu = np.array([0, 1, 2, 3, 4, 5])
9795

9896
ocp.constraints.x0 = x0

px4_mpc/px4_mpc/controllers/spacecraft_wrench_mpc.py

+7-4
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
class SpacecraftWrenchMPC():
4040
def __init__(self, model):
4141
self.model = model
42-
self.Tf = 1.0
42+
self.Tf = 5.0
4343
self.N = 50
4444

4545
self.x0 = np.array([0.01, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0])
@@ -66,9 +66,12 @@ def setup(self, x0, N_horizon, Tf):
6666
ocp.dims.N = N_horizon
6767

6868
# set cost
69-
Q_mat = 2*np.diag([10e1, 10e1, 10e1, 1e1, 1e1, 1e1, 1e1, 1e1, 1e1, 0.0, 0.1, 0.1, 0.1])
70-
Q_e = 2*np.diag([30e2, 30e2, 30e2, 1e2, 1e2, 1e2, 0.0, 0.0, 0.0, 0.0, 1e2, 1e2, 1e2])
71-
R_mat = 2*np.diag([1e1, 1e1, 5e0])
69+
Q_mat = np.diag([5e1, 5e1, 5e1,
70+
2e3, 2e3, 2e3,
71+
5e2, 5e2, 5e2, 5e2,
72+
3e2, 3e2, 3e2])
73+
Q_e = 20 * Q_mat
74+
R_mat = 2*np.diag([1e1, 1e1, 1e1])
7275

7376
ocp.cost.cost_type = 'NONLINEAR_LS'
7477
ocp.cost.cost_type_e = 'NONLINEAR_LS'

px4_mpc/px4_mpc/launch/mpc_spacecraft_launch.py

+30-7
Original file line numberDiff line numberDiff line change
@@ -36,29 +36,49 @@
3636
__contact__ = "padr@kth.se, jalim@ethz.ch"
3737

3838
from launch import LaunchDescription
39+
from launch.actions import DeclareLaunchArgument
40+
from launch.substitutions import LaunchConfiguration, PathJoinSubstitution
3941
from launch_ros.actions import Node
4042
from ament_index_python.packages import get_package_share_directory
4143
import os
4244

4345

4446
def generate_launch_description():
47+
# Declare namespace argument
48+
namespace_arg = DeclareLaunchArgument(
49+
'namespace',
50+
default_value='', # Default namespace is empty
51+
description='Namespace for all nodes'
52+
)
53+
54+
# Get and process the namespace value
55+
namespace = LaunchConfiguration('namespace')
56+
4557
return LaunchDescription([
58+
namespace_arg,
59+
4660
Node(
4761
package='px4_mpc',
48-
namespace='px4_mpc',
62+
namespace=namespace,
4963
executable='mpc_spacecraft',
5064
name='mpc_spacecraft',
5165
output='screen',
5266
emulate_tty=True,
53-
parameters=[{'mode': 'wrench'}], # rate/wrench/direct_allocation
67+
parameters=[
68+
{'mode': 'direct_allocation'},
69+
{'namespace': namespace}
70+
]
5471
),
5572
Node(
5673
package='px4_mpc',
57-
namespace='px4_mpc',
74+
namespace=namespace,
5875
executable='rviz_pos_marker',
5976
name='rviz_pos_marker',
6077
output='screen',
6178
emulate_tty=True,
79+
parameters=[
80+
{'namespace': namespace}
81+
]
6282
),
6383
# Node(
6484
# package='micro_ros_agent',
@@ -68,15 +88,18 @@ def generate_launch_description():
6888
# output='screen'),
6989
Node(
7090
package='px4_offboard',
71-
namespace='px4_offboard',
91+
namespace=namespace,
7292
executable='visualizer',
73-
name='visualizer'
93+
name='visualizer',
94+
parameters=[
95+
{'namespace': namespace}
96+
]
7497
),
7598
Node(
7699
package='rviz2',
77100
namespace='',
78101
executable='rviz2',
79102
name='rviz2',
80-
arguments=['-d', [os.path.join(get_package_share_directory('px4_mpc'), 'config.rviz')]]
81-
)
103+
arguments=['-d', os.path.join(get_package_share_directory('px4_mpc'), 'config.rviz')],
104+
),
82105
])

px4_mpc/px4_mpc/models/spacecraft_rate_model.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ def __init__(self):
3939
self.name = 'spacecraft_rate_model'
4040

4141
# constants
42-
self.mass = 15.
43-
self.max_thrust = 1.
42+
self.mass = 15.0
43+
self.max_thrust = 1.5
4444
self.max_rate = 0.5
4545

4646
def get_acados_model(self) -> AcadosModel:

px4_mpc/px4_mpc/mpc_spacecraft.py

+37-29
Original file line numberDiff line numberDiff line change
@@ -79,54 +79,62 @@ def vector2PoseMsg(frame_id, position, attitude):
7979
class SpacecraftMPC(Node):
8080

8181
def __init__(self):
82-
super().__init__('minimal_publisher')
83-
qos_profile = QoSProfile(
84-
reliability=QoSReliabilityPolicy.RMW_QOS_POLICY_RELIABILITY_BEST_EFFORT,
85-
durability=QoSDurabilityPolicy.RMW_QOS_POLICY_DURABILITY_TRANSIENT_LOCAL,
86-
history=QoSHistoryPolicy.RMW_QOS_POLICY_HISTORY_KEEP_LAST,
87-
depth=1
82+
super().__init__('spacecraft_mpc')
83+
84+
# Declare and retrieve the namespace parameter
85+
self.declare_parameter('namespace', '') # Default to empty namespace
86+
self.namespace = self.get_parameter('namespace').value
87+
self.namespace_prefix = f'/{self.namespace}' if self.namespace else ''
88+
89+
# QoS profiles
90+
qos_profile_pub = QoSProfile(
91+
reliability=QoSReliabilityPolicy.BEST_EFFORT,
92+
durability=QoSDurabilityPolicy.TRANSIENT_LOCAL,
93+
history=QoSHistoryPolicy.KEEP_LAST,
94+
depth=0
95+
)
96+
97+
qos_profile_sub = QoSProfile(
98+
reliability=QoSReliabilityPolicy.BEST_EFFORT,
99+
durability=QoSDurabilityPolicy.VOLATILE,
100+
history=QoSHistoryPolicy.KEEP_LAST,
101+
depth=0
88102
)
89103

90104
# Get mode; rate, wrench, direct_allocation
91105
self.mode = self.declare_parameter('mode', 'rate').value
92106

93107
self.status_sub = self.create_subscription(
94108
VehicleStatus,
95-
'/fmu/out/vehicle_status',
109+
f'{self.namespace_prefix}/fmu/out/vehicle_status',
96110
self.vehicle_status_callback,
97-
qos_profile)
111+
qos_profile_sub)
98112

99113
self.attitude_sub = self.create_subscription(
100114
VehicleAttitude,
101-
'/fmu/out/vehicle_attitude',
115+
f'{self.namespace_prefix}/fmu/out/vehicle_attitude',
102116
self.vehicle_attitude_callback,
103-
qos_profile)
104-
self.angular_vel_sub = self.create_subscription(
105-
VehicleAngularVelocity,
106-
'/fmu/out/vehicle_angular_velocity',
107-
self.vehicle_angular_velocity_callback,
108-
qos_profile)
117+
qos_profile_sub)
109118
self.angular_vel_sub = self.create_subscription(
110119
VehicleAngularVelocity,
111-
'/fmu/out/vehicle_angular_velocity',
120+
f'{self.namespace_prefix}/fmu/out/vehicle_angular_velocity',
112121
self.vehicle_angular_velocity_callback,
113-
qos_profile)
122+
qos_profile_sub)
114123
self.local_position_sub = self.create_subscription(
115124
VehicleLocalPosition,
116-
'/fmu/out/vehicle_local_position',
125+
f'{self.namespace_prefix}/fmu/out/vehicle_local_position',
117126
self.vehicle_local_position_callback,
118-
qos_profile)
127+
qos_profile_sub)
119128

120-
self.set_pose_srv = self.create_service(SetPose, '/set_pose', self.add_set_pos_callback)
129+
self.set_pose_srv = self.create_service(SetPose, f'{self.namespace_prefix}/set_pose', self.add_set_pos_callback)
121130

122-
self.publisher_offboard_mode = self.create_publisher(OffboardControlMode, '/fmu/in/offboard_control_mode', qos_profile)
123-
self.publisher_rates_setpoint = self.create_publisher(VehicleRatesSetpoint, '/fmu/in/vehicle_rates_setpoint', qos_profile)
124-
self.publisher_direct_actuator = self.create_publisher(ActuatorMotors, '/fmu/in/actuator_motors', qos_profile)
125-
self.publisher_thrust_setpoint = self.create_publisher(VehicleThrustSetpoint, '/fmu/in/vehicle_thrust_setpoint', qos_profile)
126-
self.publisher_torque_setpoint = self.create_publisher(VehicleTorqueSetpoint, '/fmu/in/vehicle_torque_setpoint', qos_profile)
127-
self.predicted_path_pub = self.create_publisher(Path, '/px4_mpc/predicted_path', 10)
128-
self.reference_pub = self.create_publisher(Marker, "/px4_mpc/reference", 10)
129-
self.reference_pub = self.create_publisher(Marker, "/px4_mpc/reference", 10)
131+
self.publisher_offboard_mode = self.create_publisher(OffboardControlMode, f'{self.namespace_prefix}/fmu/in/offboard_control_mode', qos_profile_pub)
132+
self.publisher_rates_setpoint = self.create_publisher(VehicleRatesSetpoint, f'{self.namespace_prefix}/fmu/in/vehicle_rates_setpoint', qos_profile_pub)
133+
self.publisher_direct_actuator = self.create_publisher(ActuatorMotors, f'{self.namespace_prefix}/fmu/in/actuator_motors', qos_profile_pub)
134+
self.publisher_thrust_setpoint = self.create_publisher(VehicleThrustSetpoint, f'{self.namespace_prefix}/fmu/in/vehicle_thrust_setpoint', qos_profile_pub)
135+
self.publisher_torque_setpoint = self.create_publisher(VehicleTorqueSetpoint, f'{self.namespace_prefix}/fmu/in/vehicle_torque_setpoint', qos_profile_pub)
136+
self.predicted_path_pub = self.create_publisher(Path, f'{self.namespace_prefix}/px4_mpc/predicted_path', 10)
137+
self.reference_pub = self.create_publisher(Marker, f"{self.namespace_prefix}/px4_mpc/reference", 10)
130138

131139
timer_period = 0.02 # seconds
132140
self.timer = self.create_timer(timer_period, self.cmdloop_callback)
@@ -218,7 +226,7 @@ def publish_reference(self, pub, reference):
218226
def publish_rate_setpoint(self, u_pred):
219227
thrust_rates = u_pred[0, :]
220228
# Hover thrust = 0.73
221-
thrust_command = thrust_rates[0:3] * 0.07 # NOTE: Tune in thrust multiplier
229+
thrust_command = thrust_rates[0:3]
222230
rates_setpoint_msg = VehicleRatesSetpoint()
223231
rates_setpoint_msg.timestamp = int(Clock().now().nanoseconds / 1000)
224232
rates_setpoint_msg.roll = float(thrust_rates[3])

px4_mpc/px4_mpc/rviz_pos_marker.py

+7-1
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,13 @@ class MinimalClientAsync(Node):
158158

159159
def __init__(self):
160160
super().__init__('minimal_client_async')
161-
self.cli = self.create_client(SetPose, '/set_pose')
161+
162+
# Declare and retrieve the namespace parameter
163+
self.declare_parameter('namespace', '') # Default to empty namespace
164+
self.namespace = self.get_parameter('namespace').value
165+
self.namespace_prefix = f'/{self.namespace}' if self.namespace else ''
166+
167+
self.cli = self.create_client(SetPose, f'{self.namespace_prefix}/set_pose')
162168
while not self.cli.wait_for_service(timeout_sec=1.0):
163169
self.get_logger().info('service not available, waiting again...')
164170
self.req = SetPose.Request()

0 commit comments

Comments
 (0)