Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
a197163
Initial Commit: Added teleop module from quest_teleop_dev
ruthwikdasyam Jan 8, 2026
f0894f2
Fis: Generalizing controller module
ruthwikdasyam Jan 8, 2026
edf508f
Fix: debug statements, Feat: uses both right and left controllers
ruthwikdasyam Jan 8, 2026
2a00987
Fix: Added I/O types, lock for threading, X button press safety
ruthwikdasyam Jan 8, 2026
2abef27
Misc: Fix debug statements,
ruthwikdasyam Jan 8, 2026
f6e28c3
CI code cleanup
ruthwikdasyam Jan 8, 2026
d7010ba
Feat: Added pose visualization with rerun
ruthwikdasyam Jan 8, 2026
0032c87
Feat: Generalized it to work with all drivers
ruthwikdasyam Jan 8, 2026
0bf2e42
Misc: Added quest blueprint, and Modules
ruthwikdasyam Jan 8, 2026
69286b0
Feat: Removed xarm dependency - generalized it over drivers
ruthwikdasyam Jan 8, 2026
5426491
Fix: threading
ruthwikdasyam Jan 8, 2026
3e47fa2
Feat: Rate control - 90Hz max
ruthwikdasyam Jan 8, 2026
91354ac
Fix: changed file name
ruthwikdasyam Jan 8, 2026
57fd58b
Updates with base_module addition
ruthwikdasyam Jan 8, 2026
81aa3ca
Feat: Added base module: can be used to make device specific teleop m…
ruthwikdasyam Jan 8, 2026
118bc6e
Feat: Module updated to inherit form base class
ruthwikdasyam Jan 8, 2026
7e21067
docs: Readme Updated
ruthwikdasyam Jan 8, 2026
97c4e0b
Misc: Init updates
ruthwikdasyam Jan 8, 2026
8e55eba
Fix: Pre-commit fixes
ruthwikdasyam Jan 8, 2026
e527f22
Feat: Added Float32 to std_msgs
ruthwikdasyam Jan 8, 2026
2f1c182
Feat: Trigger value published in float
ruthwikdasyam Jan 8, 2026
5270825
Feat: Publishes in float32 + triggers viz in rerun
ruthwikdasyam Jan 8, 2026
c0f9edf
Bool -> Float32: change update
ruthwikdasyam Jan 8, 2026
495e669
Fix: No publishing before starting (pressing x to start) or after sto…
ruthwikdasyam Jan 8, 2026
7a860bc
Added folder structure
ruthwikdasyam Jan 8, 2026
aaef820
CI code cleanup
ruthwikdasyam Jan 8, 2026
7214540
docs: updated Readme
ruthwikdasyam Jan 8, 2026
9f724c8
Misc: init updates
ruthwikdasyam Jan 8, 2026
de6ea0f
Fix: comments update
ruthwikdasyam Jan 8, 2026
7045b3c
Fix: disconnect requires websocket input
ruthwikdasyam Jan 8, 2026
9e8cc39
Fix: update path for modules + File names
ruthwikdasyam Jan 8, 2026
5fd45b3
Fix: mypy errors
ruthwikdasyam Jan 8, 2026
3b741a9
CI code cleanup
ruthwikdasyam Jan 8, 2026
d1fdc10
Feat: Add sub: quaternions need subract method
ruthwikdasyam Jan 10, 2026
968fbf9
Fix: Modules are more generalized
ruthwikdasyam Jan 10, 2026
84b9bc0
Fix: updates with new method changes/renaming
ruthwikdasyam Jan 10, 2026
5ea3151
Fix: pre-commit errors
ruthwikdasyam Jan 10, 2026
1f8d255
CI code cleanup
ruthwikdasyam Jan 10, 2026
633fb73
Fix: Code Restructured
ruthwikdasyam Jan 10, 2026
9350c21
Feat: Websocket via LCM
ruthwikdasyam Jan 13, 2026
3dfacd2
Fix: pre-commit
ruthwikdasyam Jan 13, 2026
7724b23
Feat: Classes that connects with robots
ruthwikdasyam Jan 14, 2026
ea5b79b
Feat: Modules that receive data from any device and sends to any robot
ruthwikdasyam Jan 14, 2026
b5f0b2b
Misc: Yet to be updated
ruthwikdasyam Jan 14, 2026
99534fe
Feat: A model blueprint
ruthwikdasyam Jan 14, 2026
08329e3
Feat: Websocket sending msgs over LCM
ruthwikdasyam Jan 14, 2026
f508180
Misc: Ifnore certificates
ruthwikdasyam Jan 14, 2026
4396da9
Fix: updated addresses
ruthwikdasyam Jan 14, 2026
1484689
Fix: pre-commit
ruthwikdasyam Jan 14, 2026
7b4226e
Feat: new input format, methods from utils
ruthwikdasyam Jan 15, 2026
872116a
Feat: utils for transformations + optional rerun viz
ruthwikdasyam Jan 15, 2026
0364ace
docs: updated - new arch
ruthwikdasyam Jan 15, 2026
2c6f5e6
Misc: change of file location, required cert for ws
ruthwikdasyam Jan 15, 2026
2b1a90e
Fix: mypy fixes
ruthwikdasyam Jan 15, 2026
4d72043
CI code cleanup
ruthwikdasyam Jan 15, 2026
55e49a6
Fix: mypy errors
ruthwikdasyam Jan 15, 2026
d4e66b9
Fix: updating modules
ruthwikdasyam Jan 15, 2026
0bac311
Merge remote-tracking branch 'origin/dev' into quest_teleop_on_dev
ruthwikdasyam Jan 15, 2026
3511878
Fix: typechecking imports issue
ruthwikdasyam Jan 15, 2026
6093af5
Fex: Moved certs/ to assets/ + Readme update
ruthwikdasyam Jan 15, 2026
be07c99
Feat: Added ControllerPose msg type
ruthwikdasyam Jan 15, 2026
1df6a98
Misc: msg type updates
ruthwikdasyam Jan 15, 2026
5938363
CI code cleanup
ruthwikdasyam Jan 15, 2026
b9f8b13
Merge remote-tracking branch 'origin/dev' into quest_teleop_on_dev
ruthwikdasyam Jan 15, 2026
8880f7b
CI code cleanup
ruthwikdasyam Jan 15, 2026
9163e05
Fix: mypy errors
ruthwikdasyam Jan 16, 2026
8f747ff
Fix: removed to_ros methods
ruthwikdasyam Jan 16, 2026
10c7bc4
Fix: pr comments
ruthwikdasyam Jan 16, 2026
ba6924e
Fix: removed PoseConvertible from__sub__
ruthwikdasyam Jan 17, 2026
31cef1d
Feat: Added frame_id and ts publishing stamped msgs
ruthwikdasyam Jan 17, 2026
fc85862
Misc: updates after msg type changes
ruthwikdasyam Jan 17, 2026
6f3582c
Merge remote-tracking branch 'origin/dev' into quest_teleop_on_dev
ruthwikdasyam Jan 17, 2026
af8c25c
Misc: file location changes
ruthwikdasyam Jan 22, 2026
a7f7f3e
CI code cleanup
ruthwikdasyam Jan 22, 2026
df1d3bb
Merge branch 'dev' into quest_teleop_on_dev
ruthwikdasyam Jan 22, 2026
68171f2
Merge branch 'dev' into quest_teleop_on_dev
ruthwikdasyam Jan 22, 2026
64aeb4a
CI code cleanup
ruthwikdasyam Jan 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,8 @@ yolo11n.pt
/.mypy_cache*

*mobileclip*

/results

# Teleop SSL certificates
assets/teleop_certs/
13 changes: 13 additions & 0 deletions dimos/msgs/geometry_msgs/Pose.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,19 @@ def __add__(self, other: Pose | PoseConvertable | LCMTransform | Transform) -> P

return Pose(new_position, new_orientation)

def __sub__(self, other: Pose) -> Pose:
"""Compute the delta pose: self - other.

For position: simple subtraction.
For orientation: delta_quat = self.orientation * inverse(other.orientation)

Returns:
A new Pose representing the delta transformation
"""
delta_position = self.position - other.position
delta_orientation = self.orientation * other.orientation.inverse()
return Pose(delta_position, delta_orientation)

@classmethod
def from_ros_msg(cls, ros_msg: ROSPose) -> Pose:
"""Create a Pose from a ROS geometry_msgs/Pose message.
Expand Down
29 changes: 29 additions & 0 deletions dimos/msgs/std_msgs/Float32.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Copyright 2025-2026 Dimensional Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Float32 message type."""

from typing import ClassVar

from dimos_lcm.std_msgs import Float32 as LCMFloat32


class Float32(LCMFloat32): # type: ignore[misc]
"""ROS-compatible Float32 message."""

msg_name: ClassVar[str] = "std_msgs.Float32"

def __init__(self, data: float = 0.0) -> None:
"""Initialize Float32 with data value."""
self.data = data
3 changes: 2 additions & 1 deletion dimos/msgs/std_msgs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@
# limitations under the License.

from .Bool import Bool
from .Float32 import Float32
from .Header import Header
from .Int8 import Int8
from .Int32 import Int32

__all__ = ["Bool", "Header", "Int8", "Int32"]
__all__ = ["Bool", "Float32", "Header", "Int8", "Int32"]
2 changes: 2 additions & 0 deletions dimos/robot/all_blueprints.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@
"demo-google-maps-skill": "dimos.agents.skills.demo_google_maps_skill:demo_google_maps_skill",
"demo-object-scene-registration": "dimos.perception.demo_object_scene_registration:demo_object_scene_registration",
"demo-error-on-name-conflicts": "dimos.robot.unitree_webrtc.demo_error_on_name_conflicts:blueprint",
# Teleop blueprints
"quest3-teleop": "dimos.teleop.teleop_blueprints:quest3_teleop",
}


Expand Down
113 changes: 113 additions & 0 deletions dimos/teleop/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# Teleoperation

VR teleoperation system for controlling robots with Meta Quest controllers.

## Folder Structure

```
teleop/
├── __init__.py # Exports quest3_teleop blueprint
├── teleop_blueprints.py # Pre-built system configurations
├── devices/ # Teleop input modules
│ ├── base_teleop_module.py # BaseTeleopModule (calibration, deltas, transforms)
│ └── vr_teleop_module.py # VRTeleopModule (LCM inputs)
└── web/ # Quest WebXR interface
├── teleop_server.ts # Deno WebSocket-LCM bridge
├── static/index.html # WebXR client
└── certs/ # SSL certificates
```

## Architecture

```
┌─────────────────────────────────────────────────────────────────────┐
│ Quest 3 Headset │
│ ┌─────────────┐ │
│ │ WebXR App │ ──── Controller poses + triggers ────┐ │
│ └─────────────┘ │ │
└───────────────────────────────────────────────────────│─────────────┘
│ WebSocket
┌─────────────────────────────────────────────────────────────────────┐
│ Deno Bridge (teleop_server.ts) │
│ WebSocket ←→ LCM packet forwarding │
└───────────────────────────────────────────────────────│─────────────┘
│ LCM
┌─────────────────────────────────────────────────────────────────────┐
│ VRTeleopModule │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Calibrate │ → │ Compute Delta│ → │ Transform │ │
│ │ (X button) │ │ curr - init │ │ & Publish │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└───────────────────────────────────────────────────────│─────────────┘
┌───────────────────────────────────┴───────────┐
▼ ▼
controller_delta_0/1 controller_delta_2/3
(PoseStamped) (TwistStamped)
target = init + Δ vel = scale(Δ)
```

## Quick Start

```python
from dimos.teleop import quest3_teleop

coordinator = quest3_teleop.build()
coordinator.loop()
```

Then on Quest 3: Open `https://<your-ip>:8443`, press X to calibrate and start.

## Output Types Configuration

The `output_types` parameter determines how each controller input is mapped:

| Output Type | Indices | Use Case |
|-------------|---------|----------|
| `PoseStamped` | 0, 1 | Manipulators, end-effector control |
| `TwistStamped` | 2, 3 | Locomotion, velocity control |

The system auto-computes active indices from output types:
- `[PoseStamped, PoseStamped]` → indices `[0, 1]` (dual arm)
- `[TwistStamped, TwistStamped]` → indices `[2, 3]` (dual locomotion)
- `[PoseStamped, TwistStamped]` → indices `[0, 2]` (arm + quadruped)

## Custom Setup

```python
from dimos.teleop.devices import vr_teleop_module
from dimos.msgs.geometry_msgs import PoseStamped, TwistStamped
from dimos.core.blueprints import autoconnect

# Dual arm setup (both controllers → PoseStamped)
dual_arm = autoconnect(
vr_teleop_module(
output_types=[PoseStamped, PoseStamped],
input_labels=["left_arm", "right_arm"],
),
)

# Arm + Quadruped (left → PoseStamped index 0, right → TwistStamped index 2)
arm_and_quad = autoconnect(
vr_teleop_module(
output_types=[PoseStamped, TwistStamped],
input_labels=["left_arm", "right_quad"],
),
)

# With RPC for initial robot pose (arm calibration)
arm_with_rpc = autoconnect(
vr_teleop_module(
output_types=[PoseStamped, PoseStamped],
input_labels=["left_arm", "right_arm"],
robot_pose_rpc_methods=["LeftArm.get_ee_pose", "RightArm.get_ee_pose"],
),
)
```

## Submodules

- [devices/](devices/) - Teleop input modules
- [web/](web/) - Quest WebXR client and Deno server
21 changes: 21 additions & 0 deletions dimos/teleop/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Copyright 2025-2026 Dimensional Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Teleoperation module for dimos."""

from dimos.teleop.teleop_blueprints import quest3_teleop

__all__ = [
"quest3_teleop",
]
104 changes: 104 additions & 0 deletions dimos/teleop/devices/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# Teleop Devices

Input device modules that capture tracking data, compute deltas, and transform to robot commands.

## Modules

### BaseTeleopModule

Abstract base class providing:
- Multi-controller calibration (capture initial poses)
- Delta computation (current - initial)
- Transform to robot commands (PoseStamped or TwistStamped based on `output_types`)
- Publishing to `controller_delta_*` outputs
- Optional RPC calls for initial robot pose
- Rerun visualization

### VRTeleopModule

VR-specific implementation that:
- Subscribes to LCM Transform messages from Deno bridge
- Applies coordinate frame transformation (WebXR → Robot)
- Transforms deltas to robot commands based on output type
- Runs control loop at 50Hz

## Data Flow

```
LCM Input (Transform)
┌──────────────────┐
│ Coordinate Frame │ WebXR: X=right, Y=up, Z=back
│ Transformation │ Robot: X=forward, Y=left, Z=up
└──────────────────┘
┌──────────────────┐
│ Calibration │ On X button: capture initial controller pose
│ │ (optional) RPC call for initial robot pose
└──────────────────┘
┌──────────────────┐
│ Delta Compute │ delta = current_controller - initial_controller
└──────────────────┘
┌──────────────────┐
│ Transform │ PoseStamped: target = initial_robot + delta
│ │ TwistStamped: velocity = scale(delta)
└──────────────────┘
LCM Output (controller_delta_0/1/2/3)
```

## Configuration

```python
from dimos.teleop.devices import VRTeleopConfig
from dimos.msgs.geometry_msgs import PoseStamped, TwistStamped

config = VRTeleopConfig(
# Output types determine active indices:
# PoseStamped → indices 0,1 | TwistStamped → indices 2,3
output_types=[PoseStamped, TwistStamped],
input_labels=["left_vr", "right_vr"],

# Optional RPC methods for initial robot pose (per output)
robot_pose_rpc_methods=["ArmDriver.get_ee_pose", None],

# Visualization
visualize_in_rerun=True,

# Control
control_loop_hz=50.0,

# Transform settings
linear_scale=1.0,
angular_scale=1.0,
max_linear_velocity=0.5,
max_angular_velocity=1.0,
gripper_threshold=0.5,
)
```

## Output Indices

The `output_types` parameter auto-computes active indices:

| Configuration | Active Indices | Outputs |
|--------------|----------------|---------|
| `[PoseStamped, PoseStamped]` | `[0, 1]` | Dual arm |
| `[TwistStamped, TwistStamped]` | `[2, 3]` | Dual locomotion |
| `[PoseStamped, TwistStamped]` | `[0, 2]` | Arm + quadruped |

## Adding New Devices

1. Inherit from `BaseTeleopModule`
2. Override `start()` to set up input subscriptions
3. Transform input to 4x4 pose matrix in robot frame
4. Call `compute_deltas(poses, trigger_values)` to get delta poses
5. Use `transform_delta()` from utils to convert to command
6. Call `publish_command(index, command, aux_command)` to publish
35 changes: 35 additions & 0 deletions dimos/teleop/devices/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Copyright 2025-2026 Dimensional Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Teleoperation devices."""

from dimos.teleop.devices.base_teleop_module import (
BaseTeleopConfig,
BaseTeleopModule,
TeleopStatusKey,
)
from dimos.teleop.devices.vr_teleop_module import (
VRTeleopConfig,
VRTeleopModule,
vr_teleop_module,
)

__all__ = [
"BaseTeleopConfig",
"BaseTeleopModule",
"TeleopStatusKey",
"VRTeleopConfig",
"VRTeleopModule",
"vr_teleop_module",
]
Loading