From e1412cce722095ba4c786f3c23e0e4f42d2d4ca4 Mon Sep 17 00:00:00 2001 From: mustafab0 Date: Sat, 7 Feb 2026 09:42:47 -0800 Subject: [PATCH 1/5] fix xarm adapter gripper method --- dimos/hardware/manipulators/xarm/adapter.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dimos/hardware/manipulators/xarm/adapter.py b/dimos/hardware/manipulators/xarm/adapter.py index 56b9d0357c..dd9f764031 100644 --- a/dimos/hardware/manipulators/xarm/adapter.py +++ b/dimos/hardware/manipulators/xarm/adapter.py @@ -351,8 +351,9 @@ def write_gripper_position(self, position: float) -> bool: if not self._arm: return False + self._arm.set_gripper_enable(True) pos_mm = position * M_TO_MM - code: int = self._arm.set_gripper_position(pos_mm) + code: int = self._arm.set_gripper_position(pos_mm, wait=True) return code == 0 # ========================================================================= From a63173f64319a0b75a83ae494bbcad3114d9f70e Mon Sep 17 00:00:00 2001 From: mustafab0 Date: Sat, 7 Feb 2026 09:43:56 -0800 Subject: [PATCH 2/5] exposed adapter as a property to control coordinator. This enables cusotm method implementation --- dimos/control/hardware_interface.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/dimos/control/hardware_interface.py b/dimos/control/hardware_interface.py index 2df1083511..9f6eb99851 100644 --- a/dimos/control/hardware_interface.py +++ b/dimos/control/hardware_interface.py @@ -70,6 +70,11 @@ def __init__( self._warned_unknown_joints: set[str] = set() self._current_mode: ControlMode | None = None + @property + def adapter(self) -> ManipulatorAdapter: + """The underlying hardware adapter.""" + return self._adapter + @property def hardware_id(self) -> HardwareId: """Unique ID for this hardware.""" From 507bb21d001e8d1433ba77dad0fbcf814067098f Mon Sep 17 00:00:00 2001 From: mustafab0 Date: Sat, 7 Feb 2026 09:45:15 -0800 Subject: [PATCH 3/5] rpc calls for gripper control added to control coordinator --- dimos/control/coordinator.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/dimos/control/coordinator.py b/dimos/control/coordinator.py index ae05d1de59..026a8c87e1 100644 --- a/dimos/control/coordinator.py +++ b/dimos/control/coordinator.py @@ -493,6 +493,38 @@ def task_invoke( return getattr(task, method)(**kwargs) + # ========================================================================= + # Gripper + # ========================================================================= + + @rpc + def set_gripper_position(self, hardware_id: str, position: float) -> bool: + """Set gripper position on a specific hardware device. + + Args: + hardware_id: ID of the hardware with the gripper + position: Gripper position in meters + """ + with self._hardware_lock: + hw = self._hardware.get(hardware_id) + if hw is None: + logger.warning(f"Hardware '{hardware_id}' not found for gripper command") + return False + return hw.adapter.write_gripper_position(position) + + @rpc + def get_gripper_position(self, hardware_id: str) -> float | None: + """Get gripper position from a specific hardware device. + + Args: + hardware_id: ID of the hardware with the gripper + """ + with self._hardware_lock: + hw = self._hardware.get(hardware_id) + if hw is None: + return None + return hw.adapter.read_gripper_position() + # ========================================================================= # Lifecycle # ========================================================================= From eaa50feec58ddfded23d8b1aa7a4ad0bb3801346 Mon Sep 17 00:00:00 2001 From: mustafab0 Date: Sat, 7 Feb 2026 09:47:57 -0800 Subject: [PATCH 4/5] Gripper RPC methods added to manipuilation module --- dimos/manipulation/manipulation_module.py | 74 +++++++++++++++++++++- dimos/manipulation/planning/spec/config.py | 1 + 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/dimos/manipulation/manipulation_module.py b/dimos/manipulation/manipulation_module.py index ccb120149c..a0b7b6889c 100644 --- a/dimos/manipulation/manipulation_module.py +++ b/dimos/manipulation/manipulation_module.py @@ -514,7 +514,9 @@ def get_robot_info(self, robot_name: RobotName | None = None) -> dict[str, objec def _get_coordinator_client(self) -> RPCClient | None: """Get or create coordinator RPC client (lazy init).""" - if not any(c.coordinator_task_name for _, c, _ in self._robots.values()): + if not any( + c.coordinator_task_name or c.gripper_hardware_id for _, c, _ in self._robots.values() + ): return None if self._coordinator_client is None: from dimos.control.coordinator import ControlCoordinator @@ -650,6 +652,76 @@ def remove_obstacle(self, obstacle_id: str) -> bool: return False return self._world_monitor.remove_obstacle(obstacle_id) + # ========================================================================= + # Gripper RPC Methods + # ========================================================================= + + def _get_gripper_hardware_id(self, robot_name: RobotName | None = None) -> str | None: + """Get gripper hardware ID for a robot.""" + robot = self._get_robot(robot_name) + if robot is None: + return None + _, _, config, _ = robot + if not config.gripper_hardware_id: + logger.warning(f"No gripper_hardware_id configured for '{config.name}'") + return None + return config.gripper_hardware_id + + @rpc + def set_gripper(self, position: float, robot_name: RobotName | None = None) -> bool: + """Set gripper position in meters. + + Args: + position: Gripper position in meters + robot_name: Robot to control (required if multiple robots configured) + """ + hw_id = self._get_gripper_hardware_id(robot_name) + if hw_id is None: + return False + client = self._get_coordinator_client() + if client is None: + logger.error("No coordinator client for gripper control") + return False + return bool(client.set_gripper_position(hw_id, position)) + + @rpc + def get_gripper(self, robot_name: RobotName | None = None) -> float | None: + """Get gripper position in meters. + + Args: + robot_name: Robot to query (required if multiple robots configured) + """ + hw_id = self._get_gripper_hardware_id(robot_name) + if hw_id is None: + return None + client = self._get_coordinator_client() + if client is None: + return None + result = client.get_gripper_position(hw_id) + return float(result) if result is not None else None + + @rpc + def open_gripper(self, robot_name: RobotName | None = None) -> bool: + """Open gripper fully (0.85m opening). + + Args: + robot_name: Robot to control (required if multiple robots configured) + """ + return bool(self.set_gripper(0.85, robot_name)) + + @rpc + def close_gripper(self, robot_name: RobotName | None = None) -> bool: + """Close gripper fully. + + Args: + robot_name: Robot to control (required if multiple robots configured) + """ + return bool(self.set_gripper(0.0, robot_name)) + + # ========================================================================= + # Lifecycle + # ========================================================================= + @rpc def stop(self) -> None: """Stop the manipulation module.""" diff --git a/dimos/manipulation/planning/spec/config.py b/dimos/manipulation/planning/spec/config.py index bcaeffc9da..2fdd29d8f3 100644 --- a/dimos/manipulation/planning/spec/config.py +++ b/dimos/manipulation/planning/spec/config.py @@ -73,6 +73,7 @@ class RobotModelConfig: # Coordinator integration joint_name_mapping: dict[str, str] = field(default_factory=dict) coordinator_task_name: str | None = None + gripper_hardware_id: str | None = None def get_urdf_joint_name(self, coordinator_name: str) -> str: """Translate coordinator joint name to URDF joint name.""" From 6c77e027d0fcaf90080e1d6e16345e705bc18233 Mon Sep 17 00:00:00 2001 From: mustafab0 Date: Sat, 7 Feb 2026 09:48:09 -0800 Subject: [PATCH 5/5] updated manipulation client --- .../planning/examples/manipulation_client.py | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/dimos/manipulation/planning/examples/manipulation_client.py b/dimos/manipulation/planning/examples/manipulation_client.py index ad5210b7e4..257932bd0f 100644 --- a/dimos/manipulation/planning/examples/manipulation_client.py +++ b/dimos/manipulation/planning/examples/manipulation_client.py @@ -223,6 +223,43 @@ def remove(self, obstacle_id: str) -> bool: """Remove obstacle.""" return cast("bool", self._call("remove_obstacle", obstacle_id)) + # ========================================================================= + # Gripper Methods + # ========================================================================= + + def set_gripper(self, position: float, robot_name: str | None = None) -> bool: + """Set gripper position in meters. + + Args: + position: Gripper position in meters + robot_name: Robot to control (required if multiple robots configured) + """ + return cast("bool", self._call("set_gripper", position, robot_name=robot_name)) + + def get_gripper(self, robot_name: str | None = None) -> float | None: + """Get gripper position in meters. + + Args: + robot_name: Robot to query (required if multiple robots configured) + """ + return cast("float | None", self._call("get_gripper", robot_name=robot_name)) + + def open_gripper(self, robot_name: str | None = None) -> bool: + """Open gripper fully. + + Args: + robot_name: Robot to control (required if multiple robots configured) + """ + return cast("bool", self._call("open_gripper", robot_name=robot_name)) + + def close_gripper(self, robot_name: str | None = None) -> bool: + """Close gripper fully. + + Args: + robot_name: Robot to control (required if multiple robots configured) + """ + return cast("bool", self._call("close_gripper", robot_name=robot_name)) + # ========================================================================= # Utility Methods # ========================================================================= @@ -289,6 +326,11 @@ def main() -> None: "sphere": c.sphere, "cylinder": c.cylinder, "remove": c.remove, + # Gripper methods + "set_gripper": c.set_gripper, + "get_gripper": c.get_gripper, + "open_gripper": c.open_gripper, + "close_gripper": c.close_gripper, # Utility methods "collision": c.collision, "reset": c.reset, @@ -321,6 +363,12 @@ def main() -> None: cylinder("name", x, y, z, radius, length) # Add cylinder remove("obstacle_id") # Remove obstacle +Gripper: + open_gripper() # Open gripper fully + close_gripper() # Close gripper fully + set_gripper(0.05) # Set gripper position (meters) + get_gripper() # Get gripper position (meters) + Utility: collision([0.1, ...]) # Check if config is collision-free reset() # Reset to IDLE state