Skip to content

Feat: Arm teleop with Pinocchio - single & dual arm#1246

Merged
ruthwikdasyam merged 30 commits intodevfrom
ruthwik_arm_teleop
Feb 14, 2026
Merged

Feat: Arm teleop with Pinocchio - single & dual arm#1246
ruthwikdasyam merged 30 commits intodevfrom
ruthwik_arm_teleop

Conversation

@ruthwikdasyam
Copy link
Contributor

@ruthwikdasyam ruthwikdasyam commented Feb 12, 2026

Feature

  • Arm teleop support for Quest VR stack - any arm on DimOS can be teleoperated with Quest controllers
  • Blueprints added to test with Piper, XArm6, or both (dual-arm)

Problems

  • Safety Clamping is not a good idea, It might give out joint_positions that aren't IK valid. rejecting is better.
  • pinnocchio IK must be defined seperately in dimos/manipulation/. Can be used for any IK computation

Issue - Linear

closes DIM-446
closes DIM-421

closes #1186

Approach/Solution

  1. TeleopIKTask - new control task
    Accepts streaming cartesian delta poses from VR controllers, solves IK internally via Pinocchio at 100Hz.
    • Captures EE pose on engage; incoming poses applied as deltas from that reference
    • on_buttons() listens to QuestButtons - hold primary to track, release to stop
    • Safety: rejects solutions exceeding max_joint_delta_deg per tick, auto-disengages on stream timeout
    • Per-hand config ("left" / "right" / both) for dual-arm setups
  2. PinocchioIK - new standalone IK solver
    Lightweight Pinocchio-based solver for real-time control (unlike JacobianIK which requires full WorldSpec). Provides FK, damped least-squares IK, pose conversion helpers, and safety utilities (check_joint_delta, get_worst_joint_delta).
  3. CartesianIKTask refactored to use PinocchioIK - replaced inline IK/safety/pose code
  4. ControlCoordinator - new buttons: In[QuestButtons] stream, _on_buttons() handler, teleop_ik task type in factory, hand field on TaskConfig
  5. Teleop coordinator blueprints - coordinator_teleop_xarm6, coordinator_teleop_piper, coordinator_teleop_dual
  6. End-to-end blueprints - arm_teleop_xarm6, arm_teleop_piper, arm_teleop_dual
  7. ArmTeleopModule reworked - new ArmTeleopConfig.task_names for per-hand routing via frame_id, removed old toggle engage
  8. XArm6 URDF added to LFS (Piper already existed)

Breaking Changes/Info

  • Renamed CartesianIKTask.set_target_pose() -> on_cartesian_command()
  • Renamed CartesianIKTask.set_target_pose_dict() -> on_cartesian_command_dict()
  • CartesianIKTaskConfig removed: ik_max_iter, ik_eps, ik_damp, ik_dt, max_velocity.
    CartesianIKTask internals _solve_ik(), _safety_check(), _pose_to_se3() moved to PinocchioIK
  • CartesianIKTask._pose_to_se3() removed - Replaced by standalone pose_to_se3() in dimos.manipulation.planning.kinematics.pinocchio_ik. Import path changed.
  • CartesianIKTask safety: clamp -> reject - Previously, large joint deltas were clamped (interpolated toward target). Now the entire solution is rejected (returns None). Arm freezes instead of moving partially when delta exceeds limit.
  • All tasks inherit BaseControlTask instead of ControlTask

How to Test

Blueprint verification (required)

# Start the WebXR bridge (in a separate terminal)
./dimos/teleop/quest/web/teleop_server.ts

Step - 2
# Single XArm6 arm teleop
dimos run arm-teleop-xarm6

# Single Piper arm teleop
dimos run arm-teleop-piper

# Dual arm teleop (XArm6 + Piper)
dimos run arm-teleop-dual

Manual verification

  1. Start teleop_server.ts and launch a teleop blueprint above with hardware connected
  2. Open Quest 3 headset, navigate to WebXR teleop page
  3. Hold primary button (A on right / X on left) - arm should track controller deltas
  4. Release button - arm should freeze at last position
  5. Verify timeout: disconnect Quest mid-hold - arm should stop after 0.5s
  6. Dual arm: verify each controller independently drives its assigned arm

@ruthwikdasyam ruthwikdasyam marked this pull request as ready for review February 12, 2026 21:34
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 12, 2026

Greptile Overview

Greptile Summary

This PR adds VR teleop support for any arm on DimOS by introducing a new TeleopIKTask control task and refactoring IK solving into a standalone PinocchioIK utility.

Key changes:

  • New PinocchioIK solver - lightweight Pinocchio-based IK with damped least-squares, independent of WorldSpec, provides FK, safety utilities, and pose conversion helpers
  • New TeleopIKTask - accepts streaming cartesian delta poses from VR controllers, solves IK at 100Hz with button-based engage/disengage, timeout safety (0.5s), and per-hand config for dual-arm
  • CartesianIKTask refactored - replaced ~70 lines of inline IK/safety code with PinocchioIK calls, renamed methods from set_target_pose* to on_cartesian_command*, changed from clamping excessive joint deltas to rejecting them entirely
  • ControlCoordinator - added buttons input stream, _on_buttons() handler forwarding to all tasks, teleop_ik task type in factory, hand field on TaskConfig
  • ArmTeleopModule - refactored to use task_names dict for per-hand routing via frame_id, removed old toggle engage logic (now uses parent's press-and-hold)
  • Blueprints - added 3 coordinator blueprints (coordinator_teleop_xarm6, coordinator_teleop_piper, coordinator_teleop_dual) and 3 end-to-end blueprints (arm_teleop_xarm6, arm_teleop_piper, arm_teleop_dual)
  • XArm6 URDF added to LFS

Breaking changes:

  • CartesianIKTask.set_target_pose()on_cartesian_command()
  • CartesianIKTask.set_target_pose_dict()on_cartesian_command_dict()
  • CartesianIKTaskConfig removed fields: ik_max_iter, ik_eps, ik_damp, ik_dt, max_velocity (now in PinocchioIKConfig)
  • Safety behavior change: CartesianIK now rejects solutions instead of clamping deltas

Confidence Score: 4/5

  • This PR is mostly safe to merge with one logic issue that needs fixing
  • The implementation is well-structured with good separation of concerns (PinocchioIK extraction, new TeleopIKTask). However, there's a missing error handler in coordinator.py that could cause runtime errors, and the CartesianIKTask behavior change from clamping to rejecting may cause usability issues during rapid motions
  • dimos/control/coordinator.py needs the missing hasattr check restored to prevent AttributeError

Important Files Changed

Filename Overview
dimos/manipulation/planning/kinematics/pinocchio_ik.py New standalone Pinocchio-based IK solver with damped least-squares, forward kinematics, pose conversion utilities, and safety checking functions
dimos/control/tasks/teleop_task.py New TeleopIKTask implementing delta-pose teleop control with button-based engage, timeout safety, and internal IK solving
dimos/control/tasks/cartesian_ik_task.py Refactored to use PinocchioIK solver, replaced inline IK/safety code with shared utilities, renamed methods to on_cartesian_command* pattern
dimos/control/coordinator.py Added buttons input stream, teleop_ik task type factory, hand config field, and button forwarding to all tasks with on_buttons method
dimos/control/blueprints.py Added three teleop coordinator blueprints (xarm6, piper, dual) with TeleopIK tasks configured per hand, shared model path constants
dimos/teleop/blueprints.py Added three end-to-end teleop blueprints connecting ArmTeleopModule to coordinators with task_names routing for single/dual arm setups
dimos/teleop/quest/quest_extensions.py Refactored ArmTeleopModule to use task_names routing via frame_id instead of toggle engage (now uses parent's press-and-hold)

Sequence Diagram

sequenceDiagram
    participant Quest as Quest Controller
    participant ATM as ArmTeleopModule
    participant Coord as ControlCoordinator
    participant TIK as TeleopIKTask
    participant PIK as PinocchioIK
    participant HW as Hardware Adapter

    Quest->>ATM: Controller pose delta + buttons
    ATM->>ATM: Check task_names routing
    ATM->>Coord: PoseStamped (frame_id=task_name)
    ATM->>Coord: QuestButtons
    
    Coord->>TIK: on_buttons(QuestButtons)
    TIK->>TIK: Check hand-specific primary button
    alt Primary button pressed
        TIK->>TIK: Reset initial_ee_pose (engage)
    else Primary button released
        TIK->>TIK: Clear target_pose (disengage)
    end
    
    Coord->>TIK: on_cartesian_command(pose, t_now)
    TIK->>TIK: Store delta_pose, set active
    
    Coord->>Coord: Tick loop (100Hz)
    Coord->>TIK: compute(state)
    
    alt Active and not timed out
        TIK->>TIK: Capture initial_ee_pose (if first compute)
        TIK->>TIK: Apply delta to initial pose
        TIK->>PIK: solve(target_pose, q_current)
        PIK->>PIK: Damped least-squares IK
        PIK-->>TIK: q_solution, converged, error
        TIK->>TIK: Safety check: joint delta within limit
        alt Delta exceeds limit
            TIK-->>Coord: None (reject solution)
        else Delta OK
            TIK-->>Coord: JointCommandOutput(positions)
        end
    else Inactive or timed out
        TIK-->>Coord: None
    end
    
    Coord->>Coord: Arbitrate tasks by priority
    Coord->>HW: Send joint commands
    HW->>HW: Execute motion
Loading

Last reviewed commit: 1c08c76

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

11 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

task.set_target_pose(msg, t_now) # type: ignore[attr-defined]
else:
logger.warning(f"Task {task_name} does not support set_target_pose")
task.on_cartesian_command(msg, t_now) # type: ignore[attr-defined]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing error handling - if task lacks on_cartesian_command, AttributeError will be raised instead of graceful warning like the old code did with hasattr check.

Suggested change
task.on_cartesian_command(msg, t_now) # type: ignore[attr-defined]
if hasattr(task, "on_cartesian_command"):
task.on_cartesian_command(msg, t_now) # type: ignore[attr-defined]
else:
logger.warning(f"Task {task_name} does not support on_cartesian_command")

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed - Added error handling!

@ruthwikdasyam ruthwikdasyam changed the title Ruthwik arm teleop Feat: Arm teleop with Pinocchio IK — single & dual arm VR control Feb 12, 2026
@ruthwikdasyam ruthwikdasyam changed the title Feat: Arm teleop with Pinocchio IK — single & dual arm VR control Feat: Arm teleop with Pinocchio - single & dual arm Feb 12, 2026
def __init__(self, *args: Any, **kwargs: Any) -> None:
super().__init__(*args, **kwargs)
self._prev_primary: dict[Hand, bool] = {Hand.LEFT: False, Hand.RIGHT: False}
cfg: ArmTeleopConfig = self.config # type: ignore[assignment]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This ignore is not needed. You have to define config: ArmTeleopConfig after default_config = ArmTeleopConfig. Then remove this line.

Comment on lines +228 to +247
# =========================================================================
# Pose Conversion Helpers
# =========================================================================

@staticmethod
def pose_to_se3(pose: Pose | PoseStamped) -> pinocchio.SE3:
"""Convert Pose or PoseStamped to pinocchio SE3"""

position = np.array([pose.x, pose.y, pose.z])
quat = pose.orientation
rotation = pinocchio.Quaternion(quat.w, quat.x, quat.y, quat.z).toRotationMatrix()
return pinocchio.SE3(rotation, position)

@staticmethod
def pose_dict_to_se3(pose: dict[str, float]) -> pinocchio.SE3:
"""Convert a pose dict with RPY to pinocchio SE3"""

position = np.array([pose["x"], pose["y"], pose["z"]])
rotation = pinocchio.rpy.rpyToMatrix(pose["roll"], pose["pitch"], pose["yaw"])
return pinocchio.SE3(rotation, position)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These should be standalone functions too. They don't use PinocchioIK.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can write them outside the class. can they be in the same file, along with other standalone methods (check_joint_delta(), get_worst_joint_delta()), or should they go to any utils file, as this is a type conversion?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed it to standalone method

Comment on lines +623 to +629
# Also subscribe to buttons for engage/disengage
try:
if self.buttons.transport:
self._buttons_unsub = self.buttons.subscribe(self._on_buttons)
logger.info("Subscribed to buttons for engage/disengage")
except Exception as e:
logger.debug(f"Could not subscribe to buttons: {e}")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see this a lot (checking if transport is truthy) and I don't think it's good.

In this case, if the button transport isn't working, the application is completely broken, no? Does it actually happen? If it does, that's a problem and we need to fix it.

But also, it's not good to catch an exception and log it as debug. It should be at least a warning.

Copy link
Contributor Author

@ruthwikdasyam ruthwikdasyam Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check happens in if has_cartesian_ik or teleop_task. self.buttons.transport returns true for teleop_task and false for cartesian_ik (here, it should not fail, coz we dont need buttons for cartesian_ik).

I changed the structure now. Writing a different check for if has_teleop_task. So that it fails if there's no self.buttons.transport.

Copy link
Contributor Author

@ruthwikdasyam ruthwikdasyam Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are three input streams. Joint_command, cartesian_command and buttons.

missing .transport in xase of self.joint_command.transport and self.cartesian_command.transport doesn't mean its broken coz the task methods can be also called via task_invoke RPC, without needing a stream at all. But, if using .transport is bad, I can directly try to subscribe and return a warning in case its missing (and not fail).

But, this should fail for buttons as there is no RPC alternative. implemented this.
@mustafab0 please confirm this won’t affect the manipulation module.

I verified it and nothing should break.

"""Forward button state to all tasks that accept it."""
with self._task_lock:
for task in self._tasks.values():
if hasattr(task, "on_buttons"):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hasattr is almost always bad. Same with type: ignore[attr-defined]

The code is essentially saying "on the off chance that this object has a on_buttons method, call it with some random param. If the method is changed to require an additional param, mypy will never tell you because it doesn't know about it.

The simplest way to fix this is to define on_buttons in the ControlTask Protocol. This forces you to implement it on all tasks. In most cases, you can just implement the method as pass. (If this is too much code, you can define a BaseControlTask which implements all empty methods for such listeners, and if you inherit from it, only TeleopIKTask has to actually implement on_buttons.)

Copy link
Contributor Author

@ruthwikdasyam ruthwikdasyam Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, makes sense. So, remove all 'hasattr' from coordinator, which means implementing for all methods in the callbacks in either of ways you mentioned? Implementing this.

Added a BaseTask that implements all methods, and taks overrides and implements respective methods.

Comment on lines +490 to +491
if hasattr(task, "on_cartesian_command"):
task.on_cartesian_command(msg, t_now) # type: ignore[attr-defined]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as bellow.

from dimos.utils.data import get_data

_PIPER_MODEL_PATH = str(
get_data("piper_description") / "mujoco_model" / "piper_no_gripper_description.xml"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't notice this before, but this calls get_data at import time. Meaning if the user doesn't have the repo, the program blocks here until the repo is downloaded.

I think someone else handled this case before by passing a lambda which is evaluated by the module. @leshy What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was the previous implementation. But, this creates multiple methods everytime. What's the best way

def _get_piper_model_path() -> str
from dimos.utils.data import get_data
piper_path = get_data("piper_description")
return str(piper_path / "mujoco_model" / "piper_no_gripper_description.xml")

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. used @jeff-hykin LfsPath for lazy loading - get_data() now defers to build time. Fixed bugs in implementation and verified import no longer triggers download.

@jeff-hykin
Copy link
Member

In person notes:

  • AbsoluteDirectControl
  • RelativeDirectControl

@jeff-hykin
Copy link
Member

My bad @ruthwikdasyam looks like the LfsPath I added causes some breakage. I'll work with you on this today so we can it merged.

cartesian_command: In[PoseStamped]

# Input: Teleop buttons for engage/disengage signaling
buttons: In[QuestButtons]
Copy link
Member

@jeff-hykin jeff-hykin Feb 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quest = MetaQuest buttons right? I think this needs to be generic. What would it look like if we were to add PicoButtons?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

buttons stream name is generic, but the message type is QuestButtons since that's the only controller we support right now. Once we add a second device (Pico or VisionPro), the right generic interface will be clearer. abstracting before, without a second device/case usually leads to the wrong API. Keeping as-is for now.

@ruthwikdasyam ruthwikdasyam merged commit c119e5d into dev Feb 14, 2026
15 checks passed
@ruthwikdasyam ruthwikdasyam deleted the ruthwik_arm_teleop branch February 19, 2026 02:54
spomichter added a commit that referenced this pull request Feb 21, 2026
Release v0.0.10: Manipulation Stack, MuJoCo Simulation, DDS Transport, Web and Native Visualization via Rerun


## Highlights

88+ commits, 20 contributors, 700+ files changed.

The TLDR: **a complete manipulation stack**, **MuJoCo simulation**, **DDS transport**, and **a rewritten visualization pipeline**. Agents are no longer bolted on top — they're refactored as native modules with direct stream access. The entire ROS message dependency has been removed from core DimOS, and we've added VR, phone, and arm teleoperation stacks. You can now vibecode a pick-and-place task from natural language to motor commands. Installation has been significantly streamlined — no more direnv, simpler setup, and the web viewer is now the default.

---

## 🚀 New Features

### Simulation
- **MuJoCo simulation module** — Run any DimOS blueprint in simulation with no hardware. Supports xArm and Unitree embodiments, parses MJCF/URDF for robot properties, monotonic clock timing (no `time.sleep`). `dimos --simulation run unitree-go2` ([#1035](#1035)) by @jca0
- **Simulation teleop blueprints** — Added simulation teleop blueprints for Piper, xArm6, and xArm7. ([#1308](#1308)) by @mustafab0

### Manipulation
- **Modular manipulation stack** — Full planning stack with Drake: FK/IK solvers (Jacobian + Drake optimization), RRT path planning, world model with obstacle monitoring, multi-robot management. xArm6/7 and Piper support. ([#1079](#1079)) by @mustafab0
- **Joint servo and cartesian controllers** — Joint position/velocity controllers and cartesian IK task with Pinocchio solver. PoseStamped stream input for real-time control. ([#1116](#1116)) by @mustafab0
- **GraspGen integration** — Grasp generation via Docker-hosted GPU model. Lazy container startup, thread-safe init, RPC `generate_grasps()` returns ranked PoseArray. ([#1119](#1119), [#1234](#1234)) by @JalajShuklaSS
- **Gripper control** — Gripper RPC methods on control coordinator, exposed adapter property for custom implementations. ([#1213](#1213)) by @mustafab0
- **Detection3D and Object support** — Object input topics, TF support on manipulation module, pointcloud-to-convex-hull for Drake imports. ([#1236](#1236)) by @mustafab0
- **Agentic pick and place** — Reimplemented manipulation skills for agent-driven pick-and-place workflows. ([#1237](#1237)) by @mustafab0

### Teleoperation
- **Quest VR teleoperation** — Full WebXR + Deno bridge stack. Quest controller data (pose, trigger, grip) streamed to DimOS modules. Monitor-style locking for control loops. ([#1215](#1215)) by @ruthwikdasyam
- **Phone teleoperation** — Control Go2 from your phone with a web-based teleop interface. ([#1280](#1280)) by @ruthwikdasyam
- **Arm teleop with Pinocchio IK** — Single and dual arm teleoperation using Pinocchio inverse kinematics. Blueprints for xArm, Piper, and dual configurations. ([#1246](#1246)) by @ruthwikdasyam

### Transports & Infrastructure
- **DDS transport protocol** — CycloneDDS transport with configurable QoS (high-throughput and reliable profiles). Optional install, benchmark integration. ([#1174](#1174)) by @Kaweees
- **Pubsub pattern subscriptions** — Glob and regex pattern matching for topic subscriptions. `subscribe_all()` for bridge-style consumers. Topic type encoding in channel strings (`/topic#module.ClassName`). ([#1114](#1114)) by @leshy
- **LCM raw bytes passthrough** — Skip `lcm_encode()` when message is already bytes. ([#1223](#1223)) by @leshy
- **Unified TimeSeriesStore** — Pluggable backends (InMemory, SQLite, Pickle, PostgreSQL) with SortedKeyList for O(log n) operations. Replaces the old replay system and TimestampedCollection. Collection API with slice, range, and streaming methods. ([#1080](#1080)) by @leshy
- **DimosROS benchmark tests** — Benchmark suite for ROS transport performance. ([#1087](#1087)) by @leshy

### Navigation
- **FASTLIO2 support** — Hardware-verified localization with arm64 support. Docker deployment with FAR Planner, terrain analysis, and bagfile playback mode. Builds or-tools from source on arm64. ([#1149](#1149)) by @baishibona
- **Native Livox + FASTLIO2 module** — First-class DimOS native module for Livox Mid-360 lidar with FASTLIO2 localization. ([#1235](#1235)) by @leshy

### Visualization
- **RerunBridge module and CLI** — New bridge that subscribes to all LCM messages and logs those with `to_rerun()` to Rerun viewer. GlobalConfig singleton, web viewer support. Replaces the old rerun initialization system. ([#1154](#1154)) by @leshy
- **Webcam rerun visualization** — Camera module logs to Rerun with pinhole projection for 3D visualization. ([#1117](#1117)) by @ruthwikdasyam
- **Default viewer switched to rerun-web** — Browser-based viewer is now the default for broader compatibility. No native viewer install needed. ([#1324](#1324)) by @spomichter

### Agents
- **Agent refactor** — Restructured agent module with cleaner imports and global config integration. ([#1211](#1211)) by @paul-nechifor
- **Timestamp knowledge** — Agents now have timestamp awareness in prompts for temporal reasoning. ([#1093](#1093)) by @ClaireBookworm
- **Observe skill** — Go2 can now observe (capture and describe) its environment via agent skill. ([#1109](#1109)) by @paul-nechifor

### Platform & Hardware
- **G1 without ROS** — Unitree G1 blueprints decoupled from ROS dependency. Lazy imports for fast startup. ([#1221](#1221)) by @jeff-hykin
- **ARM (aarch64) support** — DimOS runs on ARM hardware. Platform-conditional dependencies, open3d source builds for arm64. ([#1229](#1229)) by @jeff-hykin
- **Universal joint/hardware schema** — `HardwareComponent` dataclass with `JointState`, `JointName` type aliases. Backend registry with auto-discovery for SDK adapters. ([#1040](#1040), [#1067](#1067)) by @mustafab0

---

## 🔧 Improvements

- **Optional Dask** — Start without Dask using `--no-dask` flag. Startup time reduced from ~60s to ~45s. ([#1111](#1111), [#1232](#1232)) by @paul-nechifor
- **RPC rework** — Renamed `ModuleBlueprint` → `_BlueprintAtom`, `ModuleBlueprintSet` → `Blueprint`, `ModuleConnection` → `Stream`. Added `ModuleRef`, improved type hints throughout. ([#1143](#1143)) by @jeff-hykin
- **Image class simplification** — Rewritten as pure NumPy dataclass. Removed CUDA backend, unused methods (solve_pnp, csrt_tracker), and image_impls/ directory. ([#1161](#1161)) by @leshy
- **Odometry message cleanup** — Simplified Odometry message type. ([#1256](#1256)) by @leshy
- **Remove all ROS message dependencies** — Purged ROS message types from core DimOS. Refactored rosnav to use ROSTransport. Removed dead ROS bridge code. ([#1230](#1230)) by @alexlin2
- **Removed bad function serialization** — Eliminated unnecessary serialization of Python functions. ([#1121](#1121)) by @paul-nechifor
- **Benchmark IEC units** — Switched bandwidth benchmarks from SI to IEC units for accuracy. ([#1147](#1147)) by @leshy
- **Pubsub typing improvements** — Thread-safety locks on `subscribe_new_topics` and `subscribe_all`. Proper type params across pubsub stack. ([#1153](#1153)) by @leshy
- **Autogenerated blueprint list** — Blueprints are now auto-discovered and listed. ([#1100](#1100)) by @paul-nechifor
- **Generic Buttons message** — Renamed `QuestButtons` to `Buttons` with generic field names for cross-platform teleop. ([#1261](#1261)) by @ruthwikdasyam
- **Dev container uses ros-dev image** — `./bin/dev` now runs the ROS-enabled dev image. ([#1170](#1170)) by @leshy
- **LSP support** — Added python-lsp-server and python-lsp-ruff to dev dependencies. ([#1169](#1169)) by @leshy
- **Lazy-load pyrealsense2** — RealSense camera module uses lazy imports to avoid errors in simulation environments without the SDK. ([#1309](#1309)) by @spomichter
- **Removed unused mmcv and mmengine** — Dead Detic dependencies removed, eliminating slow source builds from install. ([#1319](#1319)) by @spomichter
- **Simplified installation** — Removed direnv requirement, streamlined install instructions across all platforms. ([#1315](#1315)) by @spomichter
- **DDS extra excluded from --all-extras** — `cyclonedds` requires a source build, so `dds` is now excluded from `uv sync --all-extras` by default. ([#1318](#1318)) by @spomichter
- **Nix pre-commit skip** — Skip pre-commit install if hooks already exist. ([#1162](#1162)) by @leshy
- **Removed base-requirements** — Consolidated dependency management. ([#1098](#1098)) by @paul-nechifor
- **Removed old graspnet** — Cleaned up deprecated graspnet version. ([#1248](#1248)) by @paul-nechifor
- **Code cleanup** — Removed `tofix` markers ([#1216](#1216)), fixed ruff issues ([#1112](#1112)), removed old README_installation.md ([#1101](#1101)) by @paul-nechifor

---

## 🐛 Bug Fixes

- Fix LFS updating (move from .local to venv) ([#1090](#1090)) by @jeff-hykin
- Launch hotfixes: git clone HTTPS, get_data main branch ([#1091](#1091)) by @spomichter
- Fix camera demo not showing in Rerun ([#1148](#1148)) by @jeff-hykin
- Default to rerun native viewer ([#1099](#1099)) by @Nabla7
- Fix exploration blocking agent loop ([#1258](#1258)) by @paul-nechifor
- Fix person-follow blocking agent loop ([#1278](#1278)) by @paul-nechifor
- Skip metric3d tests on unsupported xformers GPUs (Blackwell compute capability >9.0) ([#1225](#1225)) by @leshy
- Fix manipulation tests ([#1218](#1218), [#1247](#1247)) by @jeff-hykin, @paul-nechifor
- Fix control coordinator e2e test ([#1212](#1212)) by @mustafab0
- Fix xarm7-sim broken e2e tests ([#1294](#1294)) by @paul-nechifor
- Pin langchain to restore supported providers ([#1241](#1241)) by @spomichter
- Fix missing library dependencies in Nix flake ([#1240](#1240)) by @Kaweees
- Fix discord invite link ([#1122](#1122)) by @spomichter
- macOS edgecase fix ([#1096](#1096)) by @jeff-hykin
- Fix second N in logo ([#1250](#1250)) by @jeff-hykin
- Fix Unitree Go2 minor issues ([#1307](#1307)) by @paul-nechifor
- Fix broken tests ([#1305](#1305)) by @ruthwikdasyam
- Fix `uv sync` for some macOS systems ([#1322](#1322)) by @jeff-hykin
- Fix mmcv install ([#1313](#1313)) by @paul-nechifor
- Fix mypy issues ([#1150](#1150), [#1167](#1167), [#1257](#1257)) by @leshy, @paul-nechifor, @jeff-hykin
- Fix Nix install uv pip extras ([#1321](#1321)) by @spomichter

---

## 📚 Documentation

- **Major docs overhaul** — New README with feature grid, hardware table, quickstart. Navigation, transports, data streams, and agent docs. ([#1295](#1295)) by @leshy
- **Day 1 docs** — Comprehensive getting started guide, development docs, contributing guide, architecture overview. Executable blueprint docs via md-babel-py. ([#1064](#1064)) by @jeff-hykin
- **Arm integration guide** — How-to for integrating new robotic arms with DimOS. ([#1238](#1238)) by @mustafab0
- **MCP documentation update** — Updated MCP install and usage instructions. ([#1251](#1251)) by @Kaweees
- **Docker docs** — First pass on Docker deployment documentation. ([#1151](#1151)) by @leshy
- **Transports documentation** — Encode/decode mixins, SHM examples, ROS/DDS transport docs. ([#1107](#1107)) by @leshy
- **Rerun API examples** — Updated examples for the new RerunBridge API. ([#1262](#1262)) by @jeff-hykin
- **PR template** added ([#1172](#1172)) by @christiefhyang
- **Simplified install instructions** — Removed direnv, streamlined across all platforms. ([#1315](#1315)) by @spomichter
- **Python example restored** — Added back the Python usage example. ([#1317](#1317)) by @jeff-hykin
- **Nix install updated** — Replaced uv with pip for Nix compatibility. ([#1326](#1326)) by @ruthwikdasyam
- **README improvements** ([#1311](#1311)) by @paul-nechifor
- **Simplified writing docs** — Consolidated writing_docs to a single markdown file. ([#1254](#1254)) by @jeff-hykin

---

## 🏗️ CI & Build

- **ci-complete gate** — Dynamic branch protection via single aggregated status check. MD-only PRs no longer blocked. ([#1279](#1279)) by @spomichter
- **Path-based test filtering** — Test jobs fully skip (no container spin-up) when no relevant code changed. ([#1284](#1284), [#1286](#1286)) by @spomichter
- **Navigation docker build workflow** — CI builds for the ROS navigation stack. ([#1259](#1259)) by @spomichter
- **CUDA test marker** — `@pytest.mark.cuda` for GPU-dependent tests. ([#1220](#1220)) by @jeff-hykin
- **e2e test marker** — Marked end-to-end tests for selective CI runs. ([#1110](#1110)) by @paul-nechifor
- **pytest stdin fix** — Added `-s` to default addopts for LCM autoconf compatibility. ([#1320](#1320)) by @spomichter

---

## ⚠️ Breaking Changes

- **RPC renames**: `ModuleBlueprint` → `_BlueprintAtom`, `ModuleBlueprintSet` → `Blueprint`, `ModuleConnection` → `Stream` ([#1143](#1143))
- **Image class rewrite**: `CudaImage` and `NumpyImage` removed. Image is now a pure NumPy dataclass. Methods like `solve_pnp`, `csrt_tracker`, `from_depth`, `to_depth_meters` removed. ([#1161](#1161))
- **ROS messages removed from core**: All `to_ros`/`from_ros` conversion methods removed. Use `ROSTransport` instead. ([#1230](#1230))
- **QuestButtons → Buttons**: Renamed with generic field names. ([#1261](#1261))
- **RerunBridge replaces old rerun init**: `dimos.dashboard.rerun_init` removed. Use `RerunBridgeModule` or the `rerun-bridge` CLI. ([#1154](#1154))
- **Unitree directory restructuring**: `unitree_go2` → `unitree/go2`, `unitree_g1` → `unitree/g1`. Blueprint names updated. ([#1221](#1221))
- **Default viewer is now rerun-web**: Use `--viewer-backend rerun` to restore native viewer. ([#1324](#1324))

---

## Quickstart

```bash
# Install
uv pip install dimos[base,unitree]

# Try it (no hardware needed)
# NOTE: First run downloads ~2.4 GB from LFS
dimos --replay run unitree-go2

# Simulate
uv pip install dimos[base,unitree,sim]
dimos --simulation run unitree-go2
```

---

## New Contributors 🎉

- @ruthwikdasyam — Quest VR teleoperation, phone teleop, arm teleop, webcam rerun viz
- @JalajShuklaSS — GraspGen integration
- @jca0 — MuJoCo simulation module
- @christiefhyang — PR template

---

**Full Changelog**: [v0.0.9...v0.0.10](v0.0.9...v0.0.10)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants