Skip to content

Commit 5909831

Browse files
committed
Merge branch 'dev' into ruthwik_iphone_teleop
2 parents 9c1b68b + 71450f2 commit 5909831

16 files changed

Lines changed: 1650 additions & 718 deletions

File tree

dimos/core/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@
55
import time
66
from typing import TYPE_CHECKING, cast
77

8+
import lazy_loader as lazy
89
from rich.console import Console
910

1011
from dimos.core.core import rpc
1112
from dimos.utils.logging_config import setup_logger
12-
import lazy_loader as lazy
1313

1414
if TYPE_CHECKING:
1515
# Avoid runtime import to prevent circular import; ruff's TC001 would otherwise move it.

dimos/manipulation/control/coordinator_client.py

Lines changed: 36 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -123,20 +123,25 @@ def get_joint_positions(self) -> dict[str, float]:
123123
return self._rpc.get_joint_positions() or {}
124124

125125
def get_trajectory_status(self, task_name: str) -> dict[str, Any]:
126-
"""Get status of a trajectory task."""
127-
return self._rpc.get_trajectory_status(task_name) or {}
126+
"""Get status of a trajectory task via task_invoke."""
127+
result = self._rpc.task_invoke(task_name, "get_state", {})
128+
if result is not None:
129+
return {"state": int(result), "task": task_name}
130+
return {}
128131

129132
# =========================================================================
130-
# Trajectory execution (RPC calls)
133+
# Trajectory execution (via task_invoke)
131134
# =========================================================================
132135

133136
def execute_trajectory(self, task_name: str, trajectory: JointTrajectory) -> bool:
134-
"""Execute a trajectory on a task."""
135-
return self._rpc.execute_trajectory(task_name, trajectory) or False
137+
"""Execute a trajectory on a task via task_invoke."""
138+
result = self._rpc.task_invoke(task_name, "execute", {"trajectory": trajectory})
139+
return bool(result)
136140

137141
def cancel_trajectory(self, task_name: str) -> bool:
138-
"""Cancel an active trajectory."""
139-
return self._rpc.cancel_trajectory(task_name) or False
142+
"""Cancel an active trajectory via task_invoke."""
143+
result = self._rpc.task_invoke(task_name, "cancel", {})
144+
return bool(result)
140145

141146
# =========================================================================
142147
# Task selection and setup
@@ -320,26 +325,32 @@ def preview_trajectory(trajectory: JointTrajectory, joint_names: list[str]) -> N
320325

321326

322327
def wait_for_completion(client: CoordinatorClient, task_name: str, timeout: float = 60.0) -> bool:
323-
"""Wait for trajectory to complete with progress display."""
328+
"""Wait for trajectory to complete by polling task state.
329+
330+
TrajectoryState is an IntEnum: IDLE=0, EXECUTING=1, COMPLETED=2, ABORTED=3, FAULT=4.
331+
"""
324332
start = time.time()
325-
last_progress = -1.0
333+
_STATE_NAMES = {0: "IDLE", 1: "EXECUTING", 2: "COMPLETED", 3: "ABORTED", 4: "FAULT"}
326334

327335
while time.time() - start < timeout:
328336
status = client.get_trajectory_status(task_name)
329-
if not status.get("active", False):
330-
state: str = status.get("state", "UNKNOWN")
331-
print(f"\nTrajectory finished: {state}")
332-
return state == "COMPLETED"
337+
if not status:
338+
print("\nCould not get trajectory status")
339+
return False
333340

334-
progress = status.get("progress", 0.0)
335-
if progress != last_progress:
336-
bar_len = 30
337-
filled = int(bar_len * progress)
338-
bar = "=" * filled + "-" * (bar_len - filled)
339-
print(f"\r[{bar}] {progress * 100:.1f}%", end="", flush=True)
340-
last_progress = progress
341+
state_val = status.get("state")
342+
state_name = _STATE_NAMES.get(state_val, f"UNKNOWN({state_val})") # type: ignore[arg-type]
341343

342-
time.sleep(0.05)
344+
if state_val in (0, 2): # IDLE or COMPLETED
345+
print(f"\nTrajectory finished: {state_name}")
346+
return True
347+
if state_val in (3, 4): # ABORTED or FAULT
348+
print(f"\nTrajectory failed: {state_name}")
349+
return False
350+
# state_val == 1 means EXECUTING, keep polling
351+
elapsed = time.time() - start
352+
print(f"\r Executing... ({elapsed:.1f}s)", end="", flush=True)
353+
time.sleep(0.1)
343354

344355
print("\nTimeout waiting for trajectory")
345356
return False
@@ -471,12 +482,12 @@ def run(self) -> None:
471482

472483
def status(self) -> None:
473484
"""Show task status."""
485+
_STATE_NAMES = {0: "IDLE", 1: "EXECUTING", 2: "COMPLETED", 3: "ABORTED", 4: "FAULT"}
474486
status = self._client.get_trajectory_status(self._current_task)
487+
state_val = status.get("state")
488+
state_name = _STATE_NAMES.get(state_val, f"UNKNOWN({state_val})") # type: ignore[arg-type]
475489
print(f"\nTask: {self._current_task}")
476-
print(f" Active: {status.get('active', False)}")
477-
print(f" State: {status.get('state', 'UNKNOWN')}")
478-
if "progress" in status:
479-
print(f" Progress: {status['progress'] * 100:.1f}%")
490+
print(f" State: {state_name} ({state_val})")
480491

481492
def cancel(self) -> None:
482493
"""Cancel active trajectory."""

dimos/manipulation/manipulation_blueprints.py

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,19 @@
1616
Blueprints for manipulation module integration with ControlCoordinator.
1717
1818
Usage:
19-
# Start coordinator first, then planner:
20-
coordinator = xarm7_planner_coordinator.build()
21-
coordinator.loop()
22-
23-
# Plan and execute via RPC client:
24-
from dimos.manipulation.planning.examples.manipulation_client import ManipulationClient
25-
client = ManipulationClient()
26-
client.plan_to_joints([0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7])
27-
client.execute()
19+
# Non-agentic (manual RPC):
20+
dimos run coordinator-mock
21+
dimos run xarm-perception
22+
23+
# Agentic (LLM agent with skills):
24+
dimos run coordinator-mock
25+
dimos run xarm-perception-agent
2826
"""
2927

3028
import math
3129
from pathlib import Path
3230

31+
from dimos.agents.agent import Agent
3332
from dimos.core.blueprints import autoconnect
3433
from dimos.core.transport import LCMTransport
3534
from dimos.hardware.sensors.camera.realsense import realsense_camera
@@ -176,6 +175,7 @@ def _make_xarm6_config(
176175
max_acceleration=2.0,
177176
joint_name_mapping=joint_mapping,
178177
coordinator_task_name=coordinator_task,
178+
home_joints=[0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
179179
)
180180

181181

@@ -232,6 +232,8 @@ def _make_xarm7_config(
232232
coordinator_task_name=coordinator_task,
233233
gripper_hardware_id=gripper_hardware_id,
234234
tf_extra_links=tf_extra_links or [],
235+
# Home configuration: arm extended forward, elbow up (safe observe pose)
236+
home_joints=[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
235237
)
236238

237239

@@ -272,6 +274,7 @@ def _make_piper_config(
272274
max_acceleration=2.0,
273275
joint_name_mapping=joint_mapping,
274276
coordinator_task_name=coordinator_task,
277+
home_joints=[0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
275278
)
276279

277280

@@ -366,11 +369,42 @@ def _make_piper_config(
366369
)
367370

368371

372+
# XArm7 perception + LLM agent for agentic manipulation
373+
# Skills (pick, place, move_to_pose, etc.) auto-register with the agent's SkillCoordinator.
374+
# Usage: dimos run coordinator-mock, then dimos run xarm-perception-agent
375+
_MANIPULATION_AGENT_SYSTEM_PROMPT = """\
376+
You are a robotic manipulation assistant controlling an xArm7 robot arm.
377+
378+
Use ONLY these ManipulationModule skills for manipulation tasks:
379+
- scan_objects: Scan scene and list detected objects with 3D positions. Always call this first.
380+
- pick: Pick up an object by name. Requires scan_objects first.
381+
- place: Place a held object at x, y, z position.
382+
- place_back: Place a held object back at its original pick position.
383+
- pick_and_place: Pick an object and place it at a target location.
384+
- move_to_pose: Move end-effector to x, y, z with optional roll, pitch, yaw.
385+
- move_to_joints: Move to a joint configuration (comma-separated radians).
386+
- open_gripper / close_gripper / set_gripper: Control the gripper.
387+
- go_home: Move to the home/observe position.
388+
- go_init: Return to the startup position.
389+
- get_scene_info: Get full robot state, detected objects, and scene info.
390+
391+
Do NOT use the 'detect' or 'select' skills — use scan_objects instead.
392+
For robot_name parameters, always omit or pass None (single-arm setup).
393+
After pick or place, return to init with go_init unless another action follows immediately.
394+
"""
395+
396+
xarm_perception_agent = autoconnect(
397+
xarm_perception,
398+
Agent.blueprint(system_prompt=_MANIPULATION_AGENT_SYSTEM_PROMPT),
399+
)
400+
401+
369402
__all__ = [
370403
"PIPER_GRIPPER_COLLISION_EXCLUSIONS",
371404
"XARM_GRIPPER_COLLISION_EXCLUSIONS",
372405
"dual_xarm6_planner",
373406
"xarm6_planner_only",
374407
"xarm7_planner_coordinator",
375408
"xarm_perception",
409+
"xarm_perception_agent",
376410
]

0 commit comments

Comments
 (0)