Skip to content

Commit fec4d26

Browse files
authored
feat: manipulation demo improvements (#619)
1 parent 5d1026d commit fec4d26

File tree

6 files changed

+208
-9
lines changed

6 files changed

+208
-9
lines changed

docs/API_documentation/langchain_integration/ROS_2_tools.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,8 @@ RAI provides specific ROS 2 tools for certain ROS 2 packages.
8585

8686
### Custom Tools
8787

88-
| Tool Name | Description |
89-
| ------------------------ | -------------------------------------------- |
90-
| `MoveToPointTool` | Tool for moving to a point |
91-
| `GetObjectPositionsTool` | Tool for retrieving the positions of objects |
88+
| Tool Name | Description |
89+
| ------------------------ | --------------------------------------------------- |
90+
| `MoveToPointTool` | Tool for moving to a point |
91+
| `MoveObjectFromToTool` | Tool for moving an object from one point to another |
92+
| `GetObjectPositionsTool` | Tool for retrieving the positions of objects |

examples/manipulation-demo.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,11 @@
2424
from rai.agents.langchain.core import create_conversational_agent
2525
from rai.communication.ros2 import wait_for_ros2_services, wait_for_ros2_topics
2626
from rai.communication.ros2.connectors import ROS2Connector
27-
from rai.tools.ros2.manipulation import GetObjectPositionsTool, MoveToPointTool
27+
from rai.tools.ros2.manipulation import (
28+
GetObjectPositionsTool,
29+
MoveObjectFromToTool,
30+
ResetArmTool,
31+
)
2832
from rai.tools.ros2.simple import GetROS2ImageConfiguredTool
2933
from rai_open_set_vision.tools import GetGrabbingPointTool
3034

@@ -35,7 +39,7 @@
3539

3640
def create_agent():
3741
rclpy.init()
38-
connector = ROS2Connector()
42+
connector = ROS2Connector(executor_type="single_threaded")
3943

4044
required_services = ["/grounded_sam_segment", "/grounding_dino_classify"]
4145
required_topics = ["/color_image5", "/depth_image5", "/color_camera_info5"]
@@ -55,7 +59,8 @@ def create_agent():
5559
camera_info_topic="/color_camera_info5",
5660
get_grabbing_point_tool=GetGrabbingPointTool(connector=connector),
5761
),
58-
MoveToPointTool(connector=connector, manipulator_frame="panda_link0"),
62+
MoveObjectFromToTool(connector=connector, manipulator_frame="panda_link0"),
63+
ResetArmTool(connector=connector, manipulator_frame="panda_link0"),
5964
GetROS2ImageConfiguredTool(connector=connector, topic="/color_image5"),
6065
]
6166

src/rai_core/rai/tools/ros2/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@
5050
)
5151
from .manipulation.custom import (
5252
GetObjectPositionsTool,
53+
MoveObjectFromToTool,
54+
MoveObjectFromToToolInput,
5355
MoveToPointTool,
5456
MoveToPointToolInput,
5557
)
@@ -83,6 +85,8 @@
8385
"GetROS2TopicsNamesAndTypesTool",
8486
"GetROS2TransformConfiguredTool",
8587
"GetROS2TransformTool",
88+
"MoveObjectFromToTool",
89+
"MoveObjectFromToToolInput",
8690
"MoveToPointTool",
8791
"MoveToPointToolInput",
8892
"Nav2Toolkit",

src/rai_core/rai/tools/ros2/manipulation/__init__.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,20 @@
1919
"This is a ROS2 feature. Make sure ROS2 is installed and sourced."
2020
)
2121

22-
from .custom import GetObjectPositionsTool, MoveToPointTool, MoveToPointToolInput
22+
from .custom import (
23+
GetObjectPositionsTool,
24+
MoveObjectFromToTool,
25+
MoveObjectFromToToolInput,
26+
MoveToPointTool,
27+
MoveToPointToolInput,
28+
ResetArmTool,
29+
)
2330

2431
__all__ = [
2532
"GetObjectPositionsTool",
33+
"MoveObjectFromToTool",
34+
"MoveObjectFromToToolInput",
2635
"MoveToPointTool",
2736
"MoveToPointToolInput",
37+
"ResetArmTool",
2838
]

src/rai_core/rai/tools/ros2/manipulation/custom.py

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,132 @@ def _run(
127127
return f"Failed to position end effector at coordinates ({x:.2f}, {y:.2f}, {z:.2f})."
128128

129129

130+
class MoveObjectFromToToolInput(BaseModel):
131+
x: float = Field(description="The x coordinate of the point to move to")
132+
y: float = Field(description="The y coordinate of the point to move to")
133+
z: float = Field(description="The z coordinate of the point to move to")
134+
x1: float = Field(description="The x coordinate of the point to move to")
135+
y1: float = Field(description="The y coordinate of the point to move to")
136+
z1: float = Field(description="The z coordinate of the point to move to")
137+
138+
139+
class MoveObjectFromToTool(BaseROS2Tool):
140+
name: str = "move_object_from_to"
141+
description: str = (
142+
"Move an object from one point to another. "
143+
"The tool will grab the object from the first point and then release it at the second point. "
144+
"The tool will not confirm the success of grabbing or releasing objects. Use additional sensors (e.g. camera) or tools for that information."
145+
)
146+
147+
manipulator_frame: str = Field(..., description="Manipulator frame")
148+
min_z: float = Field(default=0.135, description="Minimum z coordinate [m]")
149+
calibration_x: float = Field(default=0.0, description="Calibration x [m]")
150+
calibration_y: float = Field(default=0.0, description="Calibration y [m]")
151+
calibration_z: float = Field(default=0.1, description="Calibration z [m]")
152+
additional_height: float = Field(
153+
default=0.05, description="Additional height for the place task [m]"
154+
)
155+
156+
# constant quaternion
157+
quaternion: Quaternion = Field(
158+
default=Quaternion(x=0.9238795325112867, y=-0.3826834323650898, z=0.0, w=0.0),
159+
description="Constant quaternion",
160+
)
161+
162+
args_schema: Type[MoveObjectFromToToolInput] = MoveObjectFromToToolInput
163+
164+
def _run(
165+
self,
166+
x: float,
167+
y: float,
168+
z: float,
169+
x1: float,
170+
y1: float,
171+
z1: float,
172+
) -> str:
173+
# NOTE: create_client could be refactored into self.connector.service_call
174+
self.connector.service_call
175+
client = self.connector.node.create_client(
176+
ManipulatorMoveTo,
177+
"/manipulator_move_to",
178+
)
179+
pose_stamped = PoseStamped()
180+
pose_stamped.header.frame_id = self.manipulator_frame
181+
pose_stamped.pose = Pose(
182+
position=Point(x=x, y=y, z=z),
183+
orientation=self.quaternion,
184+
)
185+
186+
pose_stamped1 = PoseStamped()
187+
pose_stamped1.header.frame_id = self.manipulator_frame
188+
pose_stamped1.pose = Pose(
189+
position=Point(x=x1, y=y1, z=z1),
190+
orientation=self.quaternion,
191+
)
192+
193+
pose_stamped.pose.position.x += self.calibration_x
194+
pose_stamped.pose.position.y += self.calibration_y
195+
pose_stamped.pose.position.z += self.calibration_z
196+
197+
pose_stamped.pose.position.z = np.max(
198+
[pose_stamped.pose.position.z, self.min_z]
199+
)
200+
201+
pose_stamped1.pose.position.x += self.calibration_x
202+
pose_stamped1.pose.position.y += self.calibration_y
203+
pose_stamped1.pose.position.z += self.calibration_z
204+
205+
pose_stamped1.pose.position.z = np.max(
206+
[pose_stamped1.pose.position.z, self.min_z]
207+
)
208+
209+
request = ManipulatorMoveTo.Request()
210+
request.target_pose = pose_stamped
211+
212+
request.initial_gripper_state = True # open
213+
request.final_gripper_state = False # closed
214+
215+
future = client.call_async(request)
216+
self.connector.node.get_logger().debug(
217+
f"Calling ManipulatorMoveTo service with request: x={request.target_pose.pose.position.x:.2f}, y={request.target_pose.pose.position.y:.2f}, z={request.target_pose.pose.position.z:.2f}"
218+
)
219+
response = get_future_result(future, timeout_sec=5.0)
220+
221+
if response is None:
222+
return f"Service call failed for point ({x:.2f}, {y:.2f}, {z:.2f})."
223+
224+
if response.success:
225+
self.connector.logger.info(
226+
f"End effector successfully positioned at coordinates ({x:.2f}, {y:.2f}, {z:.2f})."
227+
)
228+
else:
229+
self.connector.logger.error(
230+
f"Failed to position end effector at coordinates ({x:.2f}, {y:.2f}, {z:.2f})."
231+
)
232+
return "Failed to position end effector at coordinates ({x:.2f}, {y:.2f}, {z:.2f})."
233+
234+
request = ManipulatorMoveTo.Request()
235+
request.target_pose = pose_stamped1
236+
237+
request.initial_gripper_state = False # closed
238+
request.final_gripper_state = True # open
239+
240+
future = client.call_async(request)
241+
self.connector.node.get_logger().debug(
242+
f"Calling ManipulatorMoveTo service with request: x={request.target_pose.pose.position.x:.2f}, y={request.target_pose.pose.position.y:.2f}, z={request.target_pose.pose.position.z:.2f}"
243+
)
244+
245+
response = get_future_result(future, timeout_sec=20.0)
246+
247+
if response is None:
248+
return f"Service call failed for point ({x:.2f}, {y:.2f}, {z:.2f})."
249+
250+
if response.success:
251+
return f"End effector successfully positioned at coordinates ({x:.2f}, {y:.2f}, {z:.2f}). Note: The status of object interaction (grab/drop) is not confirmed by this movement."
252+
else:
253+
return f"Failed to position end effector at coordinates ({x:.2f}, {y:.2f}, {z:.2f})."
254+
255+
130256
class GetObjectPositionsToolInput(BaseModel):
131257
object_name: str = Field(
132258
..., description="The name of the object to get the positions of"
@@ -182,3 +308,56 @@ def _run(self, object_name: str):
182308
return f"No {object_name}s detected."
183309
else:
184310
return f"Centroids of detected {object_name}s in {self.target_frame} frame: [{', '.join(map(self.format_pose, mani_frame_poses))}]. Sizes of the detected objects are unknown."
311+
312+
313+
class ResetArmToolInput(BaseModel):
314+
pass
315+
316+
317+
class ResetArmTool(BaseROS2Tool):
318+
name: str = "reset_arm"
319+
description: str = "Reset the arm to the initial position. Use when the arm is stuck or when arm obstructs the objects."
320+
321+
args_schema: Type[ResetArmToolInput] = ResetArmToolInput
322+
323+
# constant quaternion
324+
quaternion: Quaternion = Field(
325+
default=Quaternion(x=0.9238795325112867, y=-0.3826834323650898, z=0.0, w=0.0),
326+
description="Constant quaternion",
327+
)
328+
manipulator_frame: str = Field(..., description="Manipulator frame")
329+
330+
def _run(self):
331+
client = self.connector.node.create_client(
332+
ManipulatorMoveTo,
333+
"/manipulator_move_to",
334+
)
335+
336+
x = 0.31
337+
y = 0.0
338+
z = 0.59
339+
340+
request = ManipulatorMoveTo.Request()
341+
request.target_pose = PoseStamped()
342+
request.target_pose.header.frame_id = self.manipulator_frame
343+
request.target_pose.pose = Pose(
344+
position=Point(x=x, y=y, z=z),
345+
orientation=self.quaternion,
346+
)
347+
348+
request.initial_gripper_state = True # open
349+
request.final_gripper_state = False # closed
350+
351+
future = client.call_async(request)
352+
self.connector.node.get_logger().debug(
353+
f"Calling ManipulatorMoveTo service with request: x={request.target_pose.pose.position.x:.2f}, y={request.target_pose.pose.position.y:.2f}, z={request.target_pose.pose.position.z:.2f}"
354+
)
355+
response = get_future_result(future, timeout_sec=5.0)
356+
357+
if response is None:
358+
return "Failed to reset the arm."
359+
360+
if response.success:
361+
return "Arm successfully reset."
362+
else:
363+
return "Failed to reset the arm."

src/rai_extensions/rai_open_set_vision/rai_open_set_vision/agents/base_vision_agent.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def __init__(
3636
os.makedirs(self._weights_path, exist_ok=True)
3737
self._init_weight_path()
3838
self.weight_path = self._weights_path
39-
self.ros2_connector = ROS2Connector(ros2_name)
39+
self.ros2_connector = ROS2Connector(ros2_name, executor_type="single_threaded")
4040

4141
def _init_weight_path(self):
4242
try:

0 commit comments

Comments
 (0)