-
Notifications
You must be signed in to change notification settings - Fork 0
Cjans/wbc #1
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
base: devel
Are you sure you want to change the base?
Cjans/wbc #1
Changes from 11 commits
e65e0f6
1e13f04
09a2ba2
c6206d7
60c88cf
0d4c924
7c1c31c
bf5630b
8234e2a
24aa46e
87a93f1
097c137
5d14602
f783f51
c577bdb
b20a97a
17847bc
eb11383
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -46,7 +46,7 @@ | |
| from bosdyn.api.mobility_command_pb2 import MobilityCommand | ||
| from bosdyn.api.arm_command_pb2 import ArmCommand, ArmJointMoveCommand, ArmJointPosition | ||
| from bosdyn.client.frame_helpers import (ODOM_FRAME_NAME, GROUND_PLANE_FRAME_NAME, HAND_FRAME_NAME, BODY_FRAME_NAME, | ||
| GRAV_ALIGNED_BODY_FRAME_NAME, VISION_FRAME_NAME, get_a_tform_b) | ||
| GRAV_ALIGNED_BODY_FRAME_NAME, VISION_FRAME_NAME, get_a_tform_b, get_vision_tform_body) | ||
| from bosdyn.client.math_helpers import SE3Pose | ||
| from bosdyn.client.robot_command import (RobotCommandBuilder, blocking_command) | ||
| from bosdyn.client.exceptions import RpcError | ||
|
|
@@ -61,7 +61,19 @@ | |
|
|
||
| MAX_BODY_POSES = 100 | ||
| MAX_ARM_POINTS = 10 | ||
| MAX_MOBILE_MANIPULATION_POINTS = 10 | ||
| ARM_NUM_JOINTS = 6 | ||
| GRIPPER_NUM_JOINTS = 1 | ||
| BODY_MOBILE_NUM_JOINTS = 3 | ||
| MOBILE_MANIPULATION_NUM_JOINTS = ARM_NUM_JOINTS + BODY_MOBILE_NUM_JOINTS + GRIPPER_NUM_JOINTS | ||
| GRIPPER_CLOSE_TORQUE = 15.0 | ||
| MAX_GRIPPER_JOINT_VEL = 2.5 # rad/s | ||
| MAX_GRIPPER_JOINT_ACC = 15.0 # rad/s^2 | ||
| MAX_ARM_JOINT_VEL = 2.5 # rad/s | ||
| MAX_ARM_JOINT_ACC = 15.0 # rad/s^2 | ||
| MAX_BODY_VEL_LINEAR = 0.5 # m/s | ||
| MAX_BODY_VEL_ANGULAR = 0.5 # rad/s | ||
| BODY_HEIGHT_FOR_MOBILE_MANIPULATION = 0.0 # meters | ||
|
|
||
| class GraspStrategy(Enum): | ||
| TOP_DOWN_GRASP = 1 | ||
|
|
@@ -194,7 +206,7 @@ def verify_power_and_estop(self): | |
|
|
||
| self.verify_estop() | ||
|
|
||
| # Execute long trajectories | ||
| # Execute long arm trajectories | ||
| def arm_long_trajectory_executor( | ||
| self, traj_point_positions, traj_point_velocities, timepoints, cancel_event: threading.Event | ||
| ) -> None: | ||
|
|
@@ -464,6 +476,172 @@ def arm_and_gripper_long_trajectory_executor( | |
| # self._logger.info("Successfully executed arm and gripper trajectory") | ||
| # return True | ||
|
|
||
| def create_mobile_manipulation_command(self, traj_points: list, times: list[float], ref_time: float, vision_T_body: SE3Pose) -> robot_command_pb2.RobotCommand: | ||
|
||
| """ | ||
| Creates a synchronized command for mobile manipulation (i.e. body, arm, and gripper). | ||
|
|
||
| Args: | ||
| traj_point_positions: List of joint positions for each trajectory point in the mobile manipulation trajectory. Includes body and arm joints. | ||
| times: List of time offsets from reference time | ||
| ref_time: Reference timestamp for the trajectory | ||
| vision_T_body: Transform from body frame to vision frame | ||
|
|
||
| Returns: | ||
| RobotCommand with synchronized gripper, arm and mobility commands, or None on error | ||
| """ | ||
|
|
||
| # Validate length of times and trajectory points | ||
| if len(times) != len(traj_points): | ||
| self._logger.error("Length mismatch between times and trajectory points.") | ||
| return None | ||
|
|
||
| # Validate number of joints in each trajectory point | ||
| for i, positions in enumerate(traj_points): | ||
| if len(positions) != MOBILE_MANIPULATION_NUM_JOINTS: | ||
| self._logger.error(f"Invalid number of joints at index {i}: expected {MOBILE_MANIPULATION_NUM_JOINTS}, got {len(positions)}") | ||
| return None | ||
|
|
||
| # Separate body and arm points | ||
| body_points = [point[:BODY_MOBILE_NUM_JOINTS] for point in traj_points] | ||
| arm_positions = [point[BODY_MOBILE_NUM_JOINTS:BODY_MOBILE_NUM_JOINTS + ARM_NUM_JOINTS] for point in traj_points]# Next ARM_NUM_JOINTS in each point are arm joints | ||
| gripper_positions_list = [point[-GRIPPER_NUM_JOINTS:] for point in traj_points] # Last GRIPPER_NUM_JOINTS in each point are gripper joints | ||
| gripper_positions = [elem for sublist in gripper_positions_list for elem in sublist] # flatten the list | ||
|
|
||
| # Build gripper command | ||
| gripper_command = RobotCommandBuilder.claw_gripper_command_helper( | ||
| gripper_positions=gripper_positions, | ||
| times=times, | ||
| ref_time=ref_time, | ||
| max_vel=MAX_GRIPPER_JOINT_VEL, | ||
| max_acc=MAX_GRIPPER_JOINT_ACC | ||
| ) | ||
| # Build arm command | ||
| arm_command = RobotCommandBuilder.arm_joint_move_helper( | ||
| joint_positions=arm_positions, | ||
| times=times, | ||
| ref_time=ref_time, | ||
| max_vel=MAX_ARM_JOINT_VEL, | ||
| max_acc=MAX_ARM_JOINT_ACC | ||
| ) | ||
|
|
||
| # Build se2 trajectory for the mobility command | ||
| # Transform body points to vision frame | ||
| se2_trajectory_points = [] | ||
| for i in range(len(body_points)): | ||
|
||
| x_body = body_points[i][0] | ||
| y_body = body_points[i][1] | ||
| yaw_body = body_points[i][2] | ||
|
|
||
| # Transform to vision frame | ||
| x_vision, y_vision, _ = vision_T_body.transform_point(x_body, y_body, 0) | ||
| yaw_vision = vision_T_body.rot.to_yaw() + yaw_body | ||
|
|
||
| # Create SE2TrajectoryPoint | ||
| se2_pose = geometry_pb2.SE2Pose( | ||
| position=geometry_pb2.Vec2(x=x_vision, y=y_vision), | ||
| angle=yaw_vision | ||
| ) | ||
| se2_trajectory_points.append((times[i], se2_pose)) | ||
|
|
||
| # Build mobility params with velocity limits | ||
| speed_limit = geometry_pb2.SE2VelocityLimit( | ||
| max_vel=geometry_pb2.SE2Velocity( | ||
| linear=geometry_pb2.Vec2(x=MAX_BODY_VEL_LINEAR, y=MAX_BODY_VEL_LINEAR), | ||
| angular=MAX_BODY_VEL_ANGULAR | ||
| ), | ||
| min_vel=geometry_pb2.SE2Velocity( | ||
| linear=geometry_pb2.Vec2(x=-MAX_BODY_VEL_LINEAR, y=-MAX_BODY_VEL_LINEAR), | ||
| angular=-MAX_BODY_VEL_ANGULAR | ||
| ) | ||
| ) | ||
|
|
||
| mobility_params = spot_command_pb2.MobilityParams(vel_limit=speed_limit) | ||
|
|
||
| # Build the mobility command with mobility params and the final pose from the se2 trajectory | ||
| mobility_command = RobotCommandBuilder.synchro_se2_trajectory_command( | ||
| goal_se2=se2_trajectory_points[-1][1], # Final pose | ||
| frame_name=VISION_FRAME_NAME, | ||
| params=mobility_params, | ||
| body_height=BODY_HEIGHT_FOR_MOBILE_MANIPULATION, | ||
| locomotion_hint=spot_command_pb2.HINT_AUTO | ||
| ) | ||
|
|
||
| # Reset the se2 trajectory in the mobility command and manually add waypoints | ||
| se2_traj = mobility_command.synchronized_command.mobility_command.se2_trajectory_request.trajectory | ||
| del se2_traj.points[:] # Clear existing points - protobuf repeated field | ||
|
|
||
| for time_offset, se2_pose in se2_trajectory_points: | ||
| point = se2_traj.points.add() | ||
| point.pose.CopyFrom(se2_pose) | ||
| point.time_since_reference.CopyFrom(seconds_to_duration(time_offset)) | ||
|
|
||
| # Set reference time | ||
| if ref_time is not None: | ||
| mobility_command.synchronized_command.mobility_command.se2_trajectory_request.end_time.CopyFrom(ref_time) | ||
|
||
|
|
||
| # Combine gripper, arm and mobility commands into synchronized command | ||
| gripper_sync_command = robot_command_pb2.RobotCommand() | ||
| arm_sync_command = robot_command_pb2.RobotCommand() | ||
| mobility_sync_command = robot_command_pb2.RobotCommand() | ||
| gripper_sync_command.synchronized_command.gripper_command.CopyFrom(gripper_command.synchronized_command.gripper_command) | ||
| arm_sync_command.synchronized_command.arm_command.CopyFrom(arm_command.synchronized_command.arm_command) | ||
| mobility_sync_command.synchronized_command.mobility_command.CopyFrom(mobility_command.synchronized_command.mobility_command) | ||
| command = RobotCommandBuilder.build_synchro_command(mobility_sync_command, arm_sync_command, gripper_sync_command) | ||
|
|
||
| return command | ||
|
|
||
| # Execute long mobile manipulation trajectories | ||
| def mobile_manipulation_long_trajectory_executor( | ||
| self, traj_point_positions, traj_point_velocities, timepoints, cancel_event: threading.Event | ||
| ) -> None: | ||
|
|
||
| # Make sure the robot is powered on (which implicitly implies that it's estopped as well) | ||
| try: | ||
| if self._lease_manager.robot.is_powered_on(5): | ||
| self._logger.info("Spot is about to move its whole body.") | ||
| else: | ||
| self._logger.warn("Cannot execute mobile manipulation long trajectory, robot is not powered on") | ||
| return False | ||
| except RpcError as e: | ||
| self._logger.warn(f"Cannot execute mobile manipulation long trajectory, unable to communicate with robot\n\tMsg: {e}") | ||
| return False | ||
|
|
||
| start_time = time.time() | ||
| ref_time = seconds_to_timestamp(start_time) | ||
|
|
||
| # Get the latest robot state for transforms | ||
| robot_state = self._lease_manager._robot_state_client.get_robot_state() | ||
| vision_T_body = get_vision_tform_body(robot_state.kinematic_state.transforms_snapshot) | ||
|
|
||
| # Initialize trajectory manager to extract slices from the original trajectory for execution | ||
| mobile_manipulation_trajectory_manager = TrajectoryManager( | ||
| points=traj_point_positions, | ||
| times_since_ref=timepoints, | ||
| ref_time=start_time | ||
| ) | ||
|
|
||
| while not mobile_manipulation_trajectory_manager.done(): | ||
| if cancel_event.is_set(): | ||
| self.stop_robot() | ||
| self._logger.info("Mobile manipulation trajectory action cancelled early. Stopping robot") | ||
| return False | ||
|
|
||
| window, timestamps, sleep_time = mobile_manipulation_trajectory_manager.get_window(MAX_MOBILE_MANIPULATION_POINTS) | ||
|
|
||
| robot_cmd = self.create_mobile_manipulation_command(window, timestamps, ref_time, vision_T_body) | ||
|
|
||
| success, msg, _ = self._lease_manager.robot_command(robot_cmd, end_time_secs=time.time()+sleep_time) | ||
|
|
||
| if not success: | ||
| self._logger.warn(f"mobile_manipulation_long_trajectory_executor: Error executing robot command: {msg}") | ||
| return False | ||
|
|
||
| if mobile_manipulation_trajectory_manager.last(): | ||
| time.sleep(sleep_time) | ||
| else: | ||
| time.sleep(max(sleep_time - 0.2, 0)) | ||
| self._logger.info("Successfully executed mobile manipulation trajectory") | ||
| return True | ||
|
|
||
| def body_manipulation_trajectory_executor( | ||
| self, body_trajectory: List[SE3Pose], arm_trajectory: List[List[float]], timestamps: List[float] | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are these hardware limitations or "good values" based on testing? If it's the latter, then we'd probably want to make them parameters instead of hard-coding