-
Notifications
You must be signed in to change notification settings - Fork 15
Feature/planar base #310
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
clementPene
wants to merge
10
commits into
agimus-project:humble-devel
Choose a base branch
from
Tiago-Pro-Harmonic:feature/planar_base
base: humble-devel
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+388
−64
Draft
Feature/planar base #310
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
74fc065
add function to convert ff message to planar base
07fe1a9
add planar base option to model
c2409f3
change actuation model depending the use of FF, planar base or fixed
0467590
add planar base to controller
6318617
update nodes reagrding modifs on controller
69f0722
try to fix blocking __init__ from get_param_from_node
916d12d
fix(trajectory_publisher): async non-blocking init with timeout/retry…
3232311
prevent user to use a specific cost on EE frame - for example with hp…
d1f00fc
prevent trajectory buffer to be empty at the end of the trajectory
1bac711
precommit update
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,8 +12,9 @@ | |
|
|
||
| import rclpy.time | ||
| from std_msgs.msg import Int32, String | ||
| from geometry_msgs.msg import Pose | ||
| from geometry_msgs.msg import Pose, Twist | ||
| from agimus_msgs.msg import MpcInput, MpcDebug | ||
| from rcl_interfaces.srv import GetParameters | ||
| import builtin_interfaces | ||
|
|
||
| from tf2_ros import TransformException | ||
|
|
@@ -43,8 +44,8 @@ | |
| mpc_msg_to_weighted_traj_point, | ||
| mpc_debug_data_to_msg, | ||
| transform_msg_to_se3, | ||
| get_param_from_node, | ||
| ros_pose_to_se3, | ||
| sensor_ff_to_planar_state, | ||
| ) | ||
|
|
||
|
|
||
|
|
@@ -59,10 +60,13 @@ def init_ros_robot_creation(self) -> None: | |
| self.environment_msg = None | ||
| self.robot_srdf_description_msg = None | ||
|
|
||
| # Get moving joint names from LFC | ||
| self.moving_joint_names = get_param_from_node( | ||
| self, "linear_feedback_controller", "moving_joint_names" | ||
| ).string_array_value | ||
| # moving_joint_names is fetched asynchronously in initialization_callback | ||
| # to avoid blocking __init__ when the LFC service is not yet available. | ||
| self.moving_joint_names = None | ||
| self._lfc_param_future = None | ||
| self._lfc_param_client = self.create_client( | ||
| GetParameters, "/linear_feedback_controller/get_parameters" | ||
| ) | ||
|
|
||
| self.subscriber_robot_description = self.create_subscription( | ||
| String, | ||
|
|
@@ -261,6 +265,14 @@ def initialize_ros_attributes(self) -> None: | |
| reliability=ReliabilityPolicy.BEST_EFFORT, | ||
| ), | ||
| ) | ||
| self.base_cmd_vel_publisher = self.create_publisher( | ||
| Twist, | ||
| "base_cmd_vel", | ||
| qos_profile=QoSProfile( | ||
| depth=10, | ||
| reliability=ReliabilityPolicy.BEST_EFFORT, | ||
| ), | ||
| ) | ||
| if self.params.publish_buffer_size: | ||
| self.ocp_buffer_size_pub = self.create_publisher( | ||
| Int32, | ||
|
|
@@ -362,11 +374,19 @@ def setup_mpc_initial_guess(self): | |
| ws_ref.setup(self.robot_models._robot_model) | ||
|
|
||
| np_sensor_msg: lfc_py_types.Sensor = sensor_msg_to_numpy(self.sensor_msg) | ||
| q_arm = np_sensor_msg.joint_state.position | ||
| v_arm = np_sensor_msg.joint_state.velocity | ||
| if self.params.planar_base: | ||
| q_sensor, v_sensor = sensor_ff_to_planar_state( | ||
| self.sensor_msg, q_arm, v_arm | ||
| ) | ||
| else: | ||
| q_sensor, v_sensor = q_arm, v_arm | ||
| initial_state = TrajectoryPoint( | ||
| time_ns=self.get_clock().now().nanoseconds, | ||
| robot_configuration=np_sensor_msg.joint_state.position, | ||
| robot_velocity=np_sensor_msg.joint_state.velocity, | ||
| robot_acceleration=np.zeros_like(np_sensor_msg.joint_state.velocity), | ||
| robot_configuration=q_sensor, | ||
| robot_velocity=v_sensor, | ||
| robot_acceleration=np.zeros_like(v_sensor), | ||
| ) | ||
| reference_trajectory = self.traj_buffer.horizon | ||
| if len(self.ocp.input_transforms) > 0: | ||
|
|
@@ -390,8 +410,9 @@ def mpc_input_callback(self, msg: MpcInput) -> None: | |
| msg, self.get_clock().now().nanoseconds | ||
| ) | ||
| self.traj_buffer.append(w_traj_point) | ||
| self.params.ocp.effector_frame_name = msg.ee_inputs[0].frame_id | ||
| self.effector_frame_name = msg.ee_inputs[0].frame_id | ||
| if msg.ee_inputs: | ||
| self.params.ocp.effector_frame_name = msg.ee_inputs[0].frame_id | ||
| self.effector_frame_name = msg.ee_inputs[0].frame_id | ||
|
|
||
| def update_geom_pose_callback(self, pose: Pose, geom_name: str): | ||
| """Updates pose of a geometry with a given name.""" | ||
|
|
@@ -418,13 +439,57 @@ def buffer_has_enough_data(self, ratio: float) -> bool: | |
| def send_control_msg(self, ocp_res: OCPResults) -> None: | ||
| """Get OCP control output and publish it.""" | ||
| assert self.np_sensor_msg is not None | ||
|
|
||
| # With a planar base (nu = 3 + n_arm), the feedforward and Ricatti gains | ||
| # include base DoFs that the LFC does not handle. | ||
| # Strip the base slice so the LFC receives arm-only commands (joint_nv). | ||
| # For free_flyer and fixed-base robots, base_nv = 0 or the actuation model | ||
| # already produces arm-only controls (ActuationModelFloatingBase), so no | ||
| # slicing is needed. | ||
| base_nv = 3 if self.params.planar_base else 0 | ||
| feedforward = ocp_res.feed_forward_terms[0][base_nv:] | ||
| ricatti_gain = ocp_res.ricatti_gains[0][base_nv:, :] | ||
|
|
||
| if self.params.planar_base: | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry but this sounds fishy. |
||
| # MPC gain is (n_arm, ndx_planar), LFC expects (n_arm, ndx_ff). | ||
| # Map planar tangent cols to FF tangent cols, zero-padding vz/wx/wy. | ||
| # Planar ndx: [q_base_diff(nv_base), q_arm(n_arm), v_base(nv_base), v_arm(n_arm)] | ||
| # LFC FF ndx: [q_ff_diff(nv_ff), q_arm(n_arm), v_ff(nv_ff), v_arm(n_arm)] | ||
| nv_base = base_nv # 3 for planar [vx, vy, wz] | ||
| nv_joints = feedforward.shape[0] | ||
| nv_ff = 6 # free-flyer nv, hardcoded in LFC (free_flyer_nv_ = 6) | ||
| nv_lfc = nv_ff + nv_joints | ||
|
|
||
| K_lfc = np.zeros((nv_joints, 2 * nv_lfc)) | ||
| # q_diff: planar [vx,vy,wz] → FF [0,1,5] (vz=2,wx=3,wy=4 stay zero) | ||
| K_lfc[:, [0, 1, 5]] = ricatti_gain[:, 0:nv_base] | ||
| K_lfc[:, nv_ff : nv_ff + nv_joints] = ricatti_gain[ | ||
| :, nv_base : nv_base + nv_joints | ||
| ] | ||
| # v_diff: same layout, offset by nv_lfc | ||
| K_lfc[:, [nv_lfc + 0, nv_lfc + 1, nv_lfc + 5]] = ricatti_gain[ | ||
| :, nv_base + nv_joints : 2 * nv_base + nv_joints | ||
| ] | ||
| K_lfc[:, nv_lfc + nv_ff : nv_lfc + nv_ff + nv_joints] = ricatti_gain[ | ||
| :, 2 * nv_base + nv_joints : 2 * nv_base + 2 * nv_joints | ||
| ] | ||
| ricatti_gain = K_lfc | ||
|
|
||
| ctrl_msg = lfc_py_types.Control( | ||
| feedback_gain=ocp_res.ricatti_gains[0], | ||
| feedforward=ocp_res.feed_forward_terms[0].reshape(self.rmodel.nv, 1), | ||
| feedback_gain=ricatti_gain, | ||
| feedforward=feedforward.reshape(len(feedforward), 1), | ||
| initial_state=self.np_sensor_msg, | ||
| ) | ||
| self.control_publisher.publish(control_numpy_to_msg(ctrl_msg)) | ||
|
|
||
| if self.params.planar_base: | ||
| base_u = ocp_res.feed_forward_terms[0][:3] | ||
| twist = Twist() | ||
| twist.linear.x = base_u[0] | ||
| twist.linear.y = base_u[1] | ||
| twist.angular.z = base_u[2] | ||
| self.base_cmd_vel_publisher.publish(twist) | ||
|
|
||
| def initialization_callback(self) -> None: | ||
| if self.sensor_msg is None: | ||
| self.get_logger().warn( | ||
|
|
@@ -436,9 +501,36 @@ def initialization_callback(self) -> None: | |
| if not self.ros_robot_ready(): | ||
| return | ||
|
|
||
| # Fetch moving_joint_names from LFC asynchronously (non-blocking). | ||
| if self.moving_joint_names is None: | ||
| if self._lfc_param_future is None: | ||
| if not self._lfc_param_client.service_is_ready(): | ||
| self.get_logger().warn( | ||
| "Waiting for LFC parameter service...", | ||
| throttle_duration_sec=5.0, | ||
| ) | ||
| return | ||
| req = GetParameters.Request() | ||
| req.names = ["moving_joint_names"] | ||
| self._lfc_param_future = self._lfc_param_client.call_async(req) | ||
| if not self._lfc_param_future.done(): | ||
| return | ||
| result = self._lfc_param_future.result() | ||
| self._lfc_param_future = None | ||
| if result is None: | ||
| self.get_logger().error( | ||
| "Failed to get moving_joint_names from LFC, retrying..." | ||
| ) | ||
| return | ||
| self.moving_joint_names = result.values[0].string_array_value | ||
| self.get_logger().info( | ||
| f"Got moving_joint_names from LFC: {self.moving_joint_names}" | ||
| ) | ||
|
|
||
| if self.mpc is None: | ||
| self.create_robot_models( | ||
| free_flyer=self.params.free_flyer, | ||
| planar_base=self.params.planar_base, | ||
| collision_as_capsule=self.params.collision_as_capsule, | ||
| self_collision=self.params.self_collision, | ||
| armature=self.params.ocp.armature, | ||
|
|
@@ -500,7 +592,9 @@ def run_callback(self, *args) -> None: | |
| f"MPC is running but the buffer does not have enough data. Current size {len(self.traj_buffer)}", | ||
| throttle_duration_sec=1.0, | ||
| ) | ||
| self.traj_buffer.append(self.traj_buffer[-1]) | ||
| required = self.traj_buffer.horizon_indexes[-1] + 1 | ||
| while len(self.traj_buffer) < required: | ||
| self.traj_buffer.append(self.traj_buffer[-1]) | ||
| if self.params.publish_debug_data: | ||
| # Do not use ROS time here because we want to measure the real computation time | ||
| start_compute_time = time.perf_counter() | ||
|
|
@@ -509,11 +603,20 @@ def run_callback(self, *args) -> None: | |
| # Update the input transforms required by the OCP, if any. | ||
| self.update_transforms() | ||
|
|
||
| q_arm = self.np_sensor_msg.joint_state.position | ||
| v_arm = self.np_sensor_msg.joint_state.velocity | ||
| if self.params.planar_base: | ||
| q_sensor, v_sensor = sensor_ff_to_planar_state( | ||
| self.sensor_msg, q_arm, v_arm | ||
| ) | ||
| else: | ||
| q_sensor, v_sensor = q_arm, v_arm | ||
|
|
||
| x0_traj_point = TrajectoryPoint( | ||
| time_ns=self.get_clock().now().nanoseconds, | ||
| robot_configuration=self.np_sensor_msg.joint_state.position, | ||
| robot_velocity=self.np_sensor_msg.joint_state.velocity, | ||
| robot_acceleration=np.zeros_like(self.np_sensor_msg.joint_state.velocity), | ||
| robot_configuration=q_sensor, | ||
| robot_velocity=v_sensor, | ||
| robot_acceleration=np.zeros_like(v_sensor), | ||
| ) | ||
| if self.params.constant_delay and control is not None: | ||
| # Compensate for delay by estimating the future state. | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.