Skip to content

Commit bc9b4ff

Browse files
Task/manipulation tests fixes (#1522)
* pyrealsense2 added as manipulation dependency * renamed manipulation_blueprint to blueprint for name standardization * added back manipulation client for users to interface without using agent cli * updated blueprints to run everything in 1 dimos run command * deleted the unitree-sdk submodule * added Jointstate type for manipulation client methods * fix mypy type checking for manipulation client * fix mymy test * missed blueprint added --------- Co-authored-by: stash <pomichterstash@gmail.com>
1 parent 9b9c817 commit bc9b4ff

File tree

9 files changed

+279
-37
lines changed

9 files changed

+279
-37
lines changed

dimos/hardware/sensors/camera/realsense/camera.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
from dimos.utils.reactive import backpressure
4545

4646
if TYPE_CHECKING:
47-
import pyrealsense2 as rs # type: ignore[import-not-found]
47+
import pyrealsense2 as rs # type: ignore[import-untyped,import-not-found]
4848

4949

5050
def default_base_transform() -> Transform:
@@ -119,7 +119,7 @@ def __init__(self, *args, **kwargs) -> None: # type: ignore[no-untyped-def]
119119

120120
@rpc
121121
def start(self) -> None:
122-
import pyrealsense2 as rs # type: ignore[import-not-found]
122+
import pyrealsense2 as rs # type: ignore[import-untyped,import-not-found]
123123

124124
self._pipeline = rs.pipeline()
125125
config = rs.config()
@@ -187,7 +187,7 @@ def _publish_camera_info(self) -> None:
187187
self.depth_camera_info.publish(self._depth_camera_info)
188188

189189
def _build_camera_info(self) -> None:
190-
import pyrealsense2 as rs # type: ignore[import-not-found]
190+
import pyrealsense2 as rs # type: ignore[import-untyped,import-not-found]
191191

192192
if self._profile is None:
193193
return
@@ -214,7 +214,7 @@ def _build_camera_info(self) -> None:
214214
)
215215

216216
def _intrinsics_to_camera_info(self, intrinsics: rs.intrinsics, frame_id: str) -> CameraInfo:
217-
import pyrealsense2 as rs # type: ignore[import-not-found]
217+
import pyrealsense2 as rs # type: ignore[import-untyped,import-not-found]
218218

219219
fx, fy = intrinsics.fx, intrinsics.fy
220220
cx, cy = intrinsics.ppx, intrinsics.ppy
@@ -243,7 +243,7 @@ def _intrinsics_to_camera_info(self, intrinsics: rs.intrinsics, frame_id: str) -
243243
)
244244

245245
def _get_extrinsics(self) -> None:
246-
import pyrealsense2 as rs # type: ignore[import-not-found]
246+
import pyrealsense2 as rs # type: ignore[import-untyped,import-not-found]
247247

248248
if self._profile is None or not self.config.enable_depth:
249249
return

dimos/manipulation/manipulation_blueprints.py renamed to dimos/manipulation/blueprints.py

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,26 @@
1313
# limitations under the License.
1414

1515
"""
16-
Blueprints for manipulation module integration with ControlCoordinator.
16+
Manipulation blueprints.
1717
18-
Usage:
19-
# Non-agentic (manual RPC):
20-
dimos run coordinator-mock
21-
dimos run xarm-perception
18+
Quick start:
19+
# 1. Verify manipulation deps load correctly (standalone, no hardware):
20+
dimos run xarm6-planner-only
2221
23-
# Agentic (LLM agent with skills):
24-
dimos run coordinator-mock
25-
dimos run xarm-perception-agent
22+
# 2. Keyboard teleop with mock arm:
23+
dimos run keyboard-teleop-xarm7
24+
25+
# 3. Interactive RPC client (plan, preview, execute from Python):
26+
dimos run xarm7-planner-coordinator
27+
python -i -m dimos.manipulation.planning.examples.manipulation_client
2628
"""
2729

2830
import math
2931
from pathlib import Path
3032

3133
from dimos.agents.agent import Agent
34+
from dimos.control.components import HardwareComponent, HardwareType, make_joints
35+
from dimos.control.coordinator import TaskConfig, control_coordinator
3236
from dimos.core.blueprints import autoconnect
3337
from dimos.core.transport import LCMTransport
3438
from dimos.hardware.sensors.camera.realsense import realsense_camera
@@ -316,12 +320,35 @@ def _make_piper_config(
316320
)
317321

318322

319-
# Single XArm7 planner for coordinator-mock
320-
# Usage: dimos run coordinator-mock, then dimos run xarm7-planner-coordinator
321-
xarm7_planner_coordinator = manipulation_module(
322-
robots=[_make_xarm7_config("arm", joint_prefix="arm_", coordinator_task="traj_arm")],
323-
planning_timeout=10.0,
324-
enable_viz=True,
323+
# Single XArm7 planner + mock coordinator (standalone, no external coordinator needed)
324+
# Usage: dimos run xarm7-planner-coordinator
325+
xarm7_planner_coordinator = autoconnect(
326+
manipulation_module(
327+
robots=[_make_xarm7_config("arm", joint_prefix="arm_", coordinator_task="traj_arm")],
328+
planning_timeout=10.0,
329+
enable_viz=True,
330+
),
331+
control_coordinator(
332+
tick_rate=100.0,
333+
publish_joint_state=True,
334+
joint_state_frame_id="coordinator",
335+
hardware=[
336+
HardwareComponent(
337+
hardware_id="arm",
338+
hardware_type=HardwareType.MANIPULATOR,
339+
joints=make_joints("arm", 7),
340+
adapter_type="mock",
341+
),
342+
],
343+
tasks=[
344+
TaskConfig(
345+
name="traj_arm",
346+
type="trajectory",
347+
joint_names=[f"arm_joint{i + 1}" for i in range(7)],
348+
priority=10,
349+
),
350+
],
351+
),
325352
).transports(
326353
{
327354
("joint_state", JointState): LCMTransport("/coordinator/joint_state", JointState),

dimos/manipulation/planning/README.md

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,21 @@ Motion planning for robotic manipulators. Backend-agnostic design with Drake imp
55
## Quick Start
66

77
```bash
8-
# Terminal 1: Mock coordinator
9-
dimos run coordinator-mock
8+
# 1. Verify manipulation dependencies load correctly (standalone, no hardware):
9+
dimos run xarm6-planner-only
1010

11-
# Terminal 2: Manipulation planner
12-
dimos run xarm7-planner-coordinator
11+
# 2. Keyboard teleop with mock arm (single command):
12+
dimos run keyboard-teleop-xarm7
1313

14-
# Terminal 3: IPython client
15-
python -m dimos.manipulation.planning.examples.manipulation_client
14+
# 3. Interactive RPC client (plan, preview, execute from Python):
15+
dimos run xarm7-planner-coordinator # terminal 1
16+
python -i -m dimos.manipulation.planning.examples.manipulation_client # terminal 2
1617
```
1718

18-
In IPython:
19+
In the interactive client:
1920
```python
20-
joints() # Get current joints
21+
commands() # List available commands
22+
joints() # Get current joint positions
2123
plan([0.1] * 7) # Plan to target
2224
preview() # Preview in Meshcat (url() for link)
2325
execute() # Execute via coordinator
@@ -146,8 +148,7 @@ planning/
146148
├── monitor/ # WorldMonitor (live state sync)
147149
├── trajectory_generator/ # Time-parameterized trajectories
148150
└── examples/
149-
├── planning_tester.py # Standalone CLI tester
150-
└── manipulation_client.py # IPython RPC client
151+
└── manipulation_client.py # Interactive RPC client (python -i)
151152
```
152153

153154
## Obstacle Types
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
# Copyright 2025-2026 Dimensional Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""
16+
RPC client for interacting with a running ManipulationModule.
17+
18+
Usage:
19+
# Start a manipulation blueprint in another terminal first:
20+
# dimos run xarm7-planner-coordinator
21+
#
22+
# Then run this client:
23+
python -i -m dimos.manipulation.planning.examples.manipulation_client
24+
25+
Available functions:
26+
joints() Get current joint positions
27+
ee() Get end-effector pose
28+
state() Get module state (IDLE, PLANNING, EXECUTING, ...)
29+
plan(joints) Plan to joint configuration, e.g. plan([0.1]*7)
30+
plan_pose(x,y,z) Plan to Cartesian pose
31+
preview(duration) Preview planned path in Meshcat
32+
execute() Execute planned trajectory via coordinator
33+
home() Move to home position
34+
url() Get Meshcat visualization URL
35+
robots() List configured robots
36+
info(robot) Get robot config details
37+
gripper(pos) Set gripper position (0.0=closed, 0.85=open)
38+
add_box(name,x,y,z) Add box obstacle
39+
add_sphere(name,x,y,z) Add sphere obstacle
40+
add_cylinder(name,x,y,z) Add cylinder obstacle
41+
remove(id) Remove obstacle by ID
42+
collision_free(joints) Check if config is collision-free
43+
"""
44+
45+
# mypy: disable-error-code=no-any-return
46+
from __future__ import annotations
47+
48+
from typing import Any
49+
50+
from dimos.core.rpc_client import RPCClient
51+
from dimos.manipulation.manipulation_module import ManipulationModule
52+
from dimos.msgs.geometry_msgs import Pose, Quaternion, Vector3
53+
54+
_client = RPCClient(None, ManipulationModule)
55+
56+
57+
def joints(robot_name: str | None = None) -> list[float] | None:
58+
"""Get current joint positions."""
59+
return _client.get_current_joints(robot_name)
60+
61+
62+
def ee(robot_name: str | None = None) -> Pose | None:
63+
"""Get end-effector pose."""
64+
return _client.get_ee_pose(robot_name)
65+
66+
67+
def state() -> str:
68+
"""Get module state."""
69+
return _client.get_state()
70+
71+
72+
def plan(target_joints: list[float], robot_name: str | None = None) -> bool:
73+
"""Plan to joint configuration. e.g. plan([0.1]*7)"""
74+
from dimos.msgs.sensor_msgs import JointState
75+
76+
js = JointState(position=target_joints)
77+
return _client.plan_to_joints(js, robot_name)
78+
79+
80+
def plan_pose(
81+
x: float,
82+
y: float,
83+
z: float,
84+
roll: float | None = None,
85+
pitch: float | None = None,
86+
yaw: float | None = None,
87+
robot_name: str | None = None,
88+
) -> bool:
89+
"""Plan to Cartesian pose. Preserves current orientation if rpy not given."""
90+
orientation = Quaternion(0, 0, 0, 1)
91+
if roll is not None or pitch is not None or yaw is not None:
92+
orientation = Quaternion.from_euler(Vector3(x=roll or 0, y=pitch or 0, z=yaw or 0))
93+
target = Pose(position=Vector3(x=x, y=y, z=z), orientation=orientation)
94+
return _client.plan_to_pose(target, robot_name)
95+
96+
97+
def preview(duration: float = 3.0, robot_name: str | None = None) -> bool:
98+
"""Preview planned path in Meshcat."""
99+
return _client.preview_path(duration, robot_name)
100+
101+
102+
def execute(robot_name: str | None = None) -> bool:
103+
"""Execute planned trajectory via coordinator."""
104+
return _client.execute(robot_name)
105+
106+
107+
def home(robot_name: str | None = None) -> bool:
108+
"""Plan and execute move to home position."""
109+
from dimos.msgs.sensor_msgs import JointState
110+
111+
home_joints = _client.get_robot_info(robot_name).get("home_joints", [0.0] * 7)
112+
success = _client.plan_to_joints(JointState(position=home_joints), robot_name)
113+
if success:
114+
return _client.execute(robot_name)
115+
return False
116+
117+
118+
def url() -> str | None:
119+
"""Get Meshcat visualization URL."""
120+
return _client.get_visualization_url()
121+
122+
123+
def robots() -> list[str]:
124+
"""List configured robots."""
125+
return _client.list_robots()
126+
127+
128+
def info(robot_name: str | None = None) -> dict[str, Any] | None:
129+
"""Get robot config details."""
130+
return _client.get_robot_info(robot_name)
131+
132+
133+
def gripper(position: float, robot_name: str | None = None) -> str:
134+
"""Set gripper position (0.0=closed, 0.85=open)."""
135+
return _client.set_gripper(position, robot_name)
136+
137+
138+
def add_box(
139+
name: str, x: float, y: float, z: float, w: float = 0.05, h: float = 0.05, d: float = 0.05
140+
) -> str | None:
141+
"""Add a box obstacle. e.g. add_box("cube", 0.3, 0, 0.2)"""
142+
pose = Pose(position=Vector3(x=x, y=y, z=z), orientation=Quaternion(0, 0, 0, 1))
143+
return _client.add_obstacle(name, pose, "box", [w, h, d], None)
144+
145+
146+
def add_sphere(name: str, x: float, y: float, z: float, radius: float = 0.05) -> str | None:
147+
"""Add a sphere obstacle. e.g. add_sphere("ball", 0.3, 0, 0.2)"""
148+
pose = Pose(position=Vector3(x=x, y=y, z=z), orientation=Quaternion(0, 0, 0, 1))
149+
return _client.add_obstacle(name, pose, "sphere", [radius], None)
150+
151+
152+
def add_cylinder(
153+
name: str, x: float, y: float, z: float, radius: float = 0.03, height: float = 0.1
154+
) -> str | None:
155+
"""Add a cylinder obstacle. e.g. add_cylinder("can", 0.3, 0, 0.2)"""
156+
pose = Pose(position=Vector3(x=x, y=y, z=z), orientation=Quaternion(0, 0, 0, 1))
157+
return _client.add_obstacle(name, pose, "cylinder", [radius, height], None)
158+
159+
160+
def remove(obstacle_id: str) -> bool:
161+
"""Remove an obstacle by ID (returned from add_*)."""
162+
return _client.remove_obstacle(obstacle_id)
163+
164+
165+
def collision_free(target_joints: list[float], robot_name: str | None = None) -> bool:
166+
"""Check if a joint configuration is collision-free."""
167+
return _client.is_collision_free(target_joints, robot_name)
168+
169+
170+
def commands() -> None:
171+
"""Print available functions and raw RPC methods."""
172+
print("=== Client Functions ===")
173+
for name, obj in sorted(globals().items()):
174+
if callable(obj) and not name.startswith("_") and obj.__module__ == __name__:
175+
doc = (obj.__doc__ or "").split("\n")[0]
176+
print(f" {name:25s} {doc}")
177+
178+
179+
def stop() -> None:
180+
"""Stop the RPC client."""
181+
_client.stop_rpc_client()
182+
183+
184+
if __name__ == "__main__":
185+
print("Manipulation RPC client ready.")
186+
print("Type commands() for available functions.")
187+
print("Try: joints(), plan([0.1]*7), preview(), execute()")

dimos/robot/all_blueprints.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
"demo-skill": "dimos.agents.skills.demo_skill:demo_skill",
5353
"drone-agentic": "dimos.robot.drone.blueprints.agentic.drone_agentic:drone_agentic",
5454
"drone-basic": "dimos.robot.drone.blueprints.basic.drone_basic:drone_basic",
55-
"dual-xarm6-planner": "dimos.manipulation.manipulation_blueprints:dual_xarm6_planner",
55+
"dual-xarm6-planner": "dimos.manipulation.blueprints:dual_xarm6_planner",
5656
"keyboard-teleop-piper": "dimos.robot.manipulators.piper.blueprints:keyboard_teleop_piper",
5757
"keyboard-teleop-xarm6": "dimos.robot.manipulators.xarm.blueprints:keyboard_teleop_xarm6",
5858
"keyboard-teleop-xarm7": "dimos.robot.manipulators.xarm.blueprints:keyboard_teleop_xarm7",
@@ -86,11 +86,11 @@
8686
"unitree-go2-spatial": "dimos.robot.unitree.go2.blueprints.smart.unitree_go2_spatial:unitree_go2_spatial",
8787
"unitree-go2-temporal-memory": "dimos.robot.unitree.go2.blueprints.agentic.unitree_go2_temporal_memory:unitree_go2_temporal_memory",
8888
"unitree-go2-vlm-stream-test": "dimos.robot.unitree.go2.blueprints.smart.unitree_go2_vlm_stream_test:unitree_go2_vlm_stream_test",
89-
"xarm-perception": "dimos.manipulation.manipulation_blueprints:xarm_perception",
90-
"xarm-perception-agent": "dimos.manipulation.manipulation_blueprints:xarm_perception_agent",
91-
"xarm6-planner-only": "dimos.manipulation.manipulation_blueprints:xarm6_planner_only",
92-
"xarm7-planner-coordinator": "dimos.manipulation.manipulation_blueprints:xarm7_planner_coordinator",
93-
"xarm7-planner-coordinator-agent": "dimos.manipulation.manipulation_blueprints:xarm7_planner_coordinator_agent",
89+
"xarm-perception": "dimos.manipulation.blueprints:xarm_perception",
90+
"xarm-perception-agent": "dimos.manipulation.blueprints:xarm_perception_agent",
91+
"xarm6-planner-only": "dimos.manipulation.blueprints:xarm6_planner_only",
92+
"xarm7-planner-coordinator": "dimos.manipulation.blueprints:xarm7_planner_coordinator",
93+
"xarm7-planner-coordinator-agent": "dimos.manipulation.blueprints:xarm7_planner_coordinator_agent",
9494
"xarm7-trajectory-sim": "dimos.simulation.sim_blueprints:xarm7_trajectory_sim",
9595
}
9696

dimos/robot/manipulators/xarm/blueprints.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
from dimos.control.coordinator import TaskConfig, control_coordinator
2828
from dimos.core.blueprints import autoconnect
2929
from dimos.core.transport import LCMTransport
30-
from dimos.manipulation.manipulation_blueprints import (
30+
from dimos.manipulation.blueprints import (
3131
_make_xarm6_config,
3232
_make_xarm7_config,
3333
)

docs/capabilities/manipulation/readme.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ KeyboardTeleopModule ──→ ControlCoordinator ──→ ManipulationModule
108108
| File | Description |
109109
|------|-------------|
110110
| [`manipulation_module.py`](/dimos/manipulation/manipulation_module.py) | Main module (RPC interface, state machine) |
111-
| [`manipulation_blueprints.py`](/dimos/manipulation/manipulation_blueprints.py) | Planner and perception blueprints |
111+
| [`manipulation/blueprints.py`](/dimos/manipulation/blueprints.py) | Planner and perception blueprints |
112112
| [`robot/manipulators/piper/blueprints.py`](/dimos/robot/manipulators/piper/blueprints.py) | Piper keyboard teleop blueprint |
113113
| [`robot/manipulators/xarm/blueprints.py`](/dimos/robot/manipulators/xarm/blueprints.py) | XArm keyboard teleop blueprints |
114114
| [`teleop/keyboard/keyboard_teleop_module.py`](/dimos/teleop/keyboard/keyboard_teleop_module.py) | Keyboard teleop module |

0 commit comments

Comments
 (0)