-
Notifications
You must be signed in to change notification settings - Fork 159
feat: integrate gripper into coordinator tick loop #1371
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
Changes from all commits
2b5abb5
7a82f0f
4e39bbe
279e024
7b823e2
3024b55
5617a62
32349c9
269be7d
3788d41
b3a0e98
210371b
7837c7f
dc0c21b
0ee6fa9
2de1e46
191ec86
4f7bb44
405aba6
fb45d3b
82cabb5
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 |
|---|---|---|
|
|
@@ -33,6 +33,7 @@ | |
| from dimos.control.components import ( | ||
| HardwareComponent, | ||
| HardwareType, | ||
| make_gripper_joints, | ||
| make_joints, | ||
| make_twist_base_joints, | ||
| ) | ||
|
|
@@ -45,6 +46,7 @@ | |
|
|
||
| _PIPER_MODEL_PATH = LfsPath("piper_description/mujoco_model/piper_no_gripper_description.xml") | ||
| _XARM6_MODEL_PATH = LfsPath("xarm_description/urdf/xarm6/xarm6.urdf") | ||
| _XARM7_MODEL_PATH = LfsPath("xarm_description/urdf/xarm7/xarm7.urdf") | ||
|
|
||
|
|
||
| # ============================================================================= | ||
|
|
@@ -473,30 +475,34 @@ | |
| # Teleop IK Blueprints (VR teleoperation with internal Pinocchio IK) | ||
| # ============================================================================= | ||
|
|
||
| # Single XArm6 with TeleopIK | ||
| coordinator_teleop_xarm6 = control_coordinator( | ||
| # Single XArm7 with TeleopIK | ||
| coordinator_teleop_xarm7 = control_coordinator( | ||
| tick_rate=100.0, | ||
| publish_joint_state=True, | ||
| joint_state_frame_id="coordinator", | ||
| hardware=[ | ||
| HardwareComponent( | ||
| hardware_id="arm", | ||
| hardware_type=HardwareType.MANIPULATOR, | ||
| joints=make_joints("arm", 6), | ||
| joints=make_joints("arm", 7), | ||
| adapter_type="xarm", | ||
| address="192.168.1.210", | ||
| address="192.168.2.235", | ||
| auto_enable=True, | ||
| gripper_joints=make_gripper_joints("arm"), | ||
| ), | ||
| ], | ||
| tasks=[ | ||
| TaskConfig( | ||
| name="teleop_xarm", | ||
| type="teleop_ik", | ||
| joint_names=[f"arm_joint{i + 1}" for i in range(6)], | ||
| joint_names=[f"arm_joint{i + 1}" for i in range(7)], | ||
| priority=10, | ||
| model_path=_XARM6_MODEL_PATH, | ||
| ee_joint_id=6, | ||
| model_path=_XARM7_MODEL_PATH, | ||
| ee_joint_id=7, | ||
| hand="right", | ||
| gripper_joint=make_gripper_joints("arm")[0], | ||
| gripper_open_pos=0.85, # xArm gripper range | ||
| gripper_closed_pos=0.0, | ||
| ), | ||
| ], | ||
| ).transports( | ||
|
|
@@ -715,6 +721,7 @@ | |
| "coordinator_teleop_dual", | ||
| "coordinator_teleop_piper", | ||
| "coordinator_teleop_xarm6", | ||
| "coordinator_teleop_xarm7", | ||
|
Comment on lines
721
to
+724
Contributor
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. This is okay and not a problem at all. But let's thin out control blueprints in the future to only have 1 example of each use cases and only with mock hardware For example control blueprints should have coordinator_teleop_mock, coordinator_visualservo-mock, coordinator-dual-mock.
Contributor
Author
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. Right, blueprints are overflowing on the coordinator/manipulators end. We can reduce the list and have smaller definitions. |
||
| "coordinator_velocity_xarm6", | ||
| "coordinator_xarm6", | ||
| "coordinator_xarm7", | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -65,7 +65,9 @@ def __init__( | |
|
|
||
| self._adapter = adapter | ||
| self._component = component | ||
| self._joint_names = component.joints | ||
| self._arm_joint_names: list[JointName] = list(component.joints) | ||
| self._gripper_joints: list[JointName] = list(component.gripper_joints) | ||
| self._joint_names: list[JointName] = component.all_joints | ||
|
|
||
| # Track last commanded values for hold-last behavior | ||
| self._last_commanded: dict[str, float] = {} | ||
|
|
@@ -114,15 +116,27 @@ def read_state(self) -> dict[JointName, JointState]: | |
| velocities = self._adapter.read_joint_velocities() | ||
| efforts = self._adapter.read_joint_efforts() | ||
|
|
||
| return { | ||
| result: dict[JointName, JointState] = { | ||
| name: JointState( | ||
| position=positions[i], | ||
| velocity=velocities[i], | ||
| effort=efforts[i], | ||
| ) | ||
| for i, name in enumerate(self._joint_names) | ||
| for i, name in enumerate(self._arm_joint_names) | ||
| } | ||
|
|
||
| # Append gripper joint(s) via adapter gripper method | ||
| if self._gripper_joints: | ||
| gripper_pos = self._adapter.read_gripper_position() | ||
| for gj in self._gripper_joints: | ||
| result[gj] = JointState( | ||
| position=gripper_pos if gripper_pos is not None else 0.0, | ||
| velocity=0.0, | ||
| effort=0.0, | ||
| ) | ||
|
|
||
| return result | ||
|
|
||
| def write_command(self, commands: dict[str, float], mode: ControlMode) -> bool: | ||
| """Write commands - allows partial joint sets, holds last for missing. | ||
|
|
||
|
|
@@ -153,8 +167,8 @@ def write_command(self, commands: dict[str, float], mode: ControlMode) -> bool: | |
| ) | ||
| self._warned_unknown_joints.add(joint_name) | ||
|
|
||
| # Build ordered list for adapter | ||
| ordered = self._build_ordered_command() | ||
| # Build ordered list for arm joints only | ||
| arm_ordered = [self._last_commanded[name] for name in self._arm_joint_names] | ||
|
Contributor
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. A bit confused about the semantic change. Is it because we want to make sure the gripper is added exactly after the specific ConnectedHardware manipulator instance? Or is it to distinguish between TwistBase Hardware and Manipulator Hardware?
Contributor
Author
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. yes, its to make sure we write joint commands to this ordered list - which only has arm_joints, and no grippers. The old |
||
|
|
||
| # Switch control mode if needed | ||
| if mode != self._current_mode: | ||
|
|
@@ -163,25 +177,43 @@ def write_command(self, commands: dict[str, float], mode: ControlMode) -> bool: | |
| return False | ||
| self._current_mode = mode | ||
|
|
||
| # Send to adapter | ||
| # Send arm joints to adapter | ||
| arm_ok: bool | ||
| match mode: | ||
| case ControlMode.POSITION | ControlMode.SERVO_POSITION: | ||
| return self._adapter.write_joint_positions(ordered) | ||
| arm_ok = self._adapter.write_joint_positions(arm_ordered) | ||
| case ControlMode.VELOCITY: | ||
| return self._adapter.write_joint_velocities(ordered) | ||
| arm_ok = self._adapter.write_joint_velocities(arm_ordered) | ||
| case ControlMode.TORQUE: | ||
| logger.warning(f"Hardware {self.hardware_id} does not support torque mode") | ||
| return False | ||
| arm_ok = False | ||
| case _: | ||
| return False | ||
| arm_ok = False | ||
|
|
||
| # Send gripper joints via adapter gripper method | ||
| gripper_ok = True | ||
| for gj in self._gripper_joints: | ||
| if gj in self._last_commanded: | ||
| gripper_ok = ( | ||
| self._adapter.write_gripper_position(self._last_commanded[gj]) and gripper_ok | ||
| ) | ||
|
|
||
| return arm_ok and gripper_ok | ||
|
|
||
| def _initialize_last_commanded(self) -> None: | ||
| """Initialize last_commanded with current hardware positions.""" | ||
| for _ in range(10): | ||
| try: | ||
| current = self._adapter.read_joint_positions() | ||
| for i, name in enumerate(self._joint_names): | ||
| for i, name in enumerate(self._arm_joint_names): | ||
| self._last_commanded[name] = current[i] | ||
|
|
||
| # Initialize gripper joint(s) from adapter | ||
| if self._gripper_joints: | ||
| gripper_pos = self._adapter.read_gripper_position() | ||
| for gj in self._gripper_joints: | ||
| self._last_commanded[gj] = gripper_pos if gripper_pos is not None else 0.0 | ||
|
|
||
| self._initialized = True | ||
| return | ||
| except Exception: | ||
|
|
||
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.
This is personal config, it doesn't belong in a blueprint. One idea would be to have
GlobalConfig.arm_ipwith a default of None. And you can putARM_IP=192.168.2.235in your.envfile.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.
True. Currently all manipulation blueprints hardcode the arm IP the same way. Moving these to
.envis a good idea - I can do a seperate PR cleaning up across all blueprints