Skip to content

Commit ad441d9

Browse files
rwiltzkellyguo11
andauthored
Refactors teleop device factory to follow config class style (#3897)
# Description Refactors the teleop factory to shift declaration of teleop devices and retargeters out of the factory and into themselves. Fixes # (issue) ## Type of change - New feature (non-breaking change which adds functionality) ## Screenshots ## Checklist - [x] I have read and understood the [contribution guidelines](https://isaac-sim.github.io/IsaacLab/main/source/refs/contributing.html) - [x] I have run the [`pre-commit` checks](https://pre-commit.com/) with `./isaaclab.sh --format` - [x] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] I have updated the changelog and the corresponding version in the extension's `config/extension.toml` file - [x] I have added my name to the `CONTRIBUTORS.md` or my name already exists there --------- Signed-off-by: Kelly Guo <[email protected]> Co-authored-by: Kelly Guo <[email protected]>
1 parent fdadc90 commit ad441d9

24 files changed

+273
-291
lines changed

docs/source/how-to/cloudxr_teleoperation.rst

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -935,18 +935,15 @@ The retargeting system is designed to be extensible. You can create custom retar
935935
# Return control commands in appropriate format
936936
return torch.tensor([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0]) # Example output
937937
938-
3. Register your retargeter with the factory by adding it to the ``RETARGETER_MAP``:
938+
3. Register your retargeter by setting ``retargeter_type`` on the config class:
939939

940940
.. code-block:: python
941941
942942
# Import your retargeter at the top of your module
943943
from my_package.retargeters import MyCustomRetargeter, MyCustomRetargeterCfg
944944
945-
# Add your retargeter to the factory
946-
from isaaclab.devices.teleop_device_factory import RETARGETER_MAP
947-
948-
# Register your retargeter type with its constructor
949-
RETARGETER_MAP[MyCustomRetargeterCfg] = MyCustomRetargeter
945+
# Link the config to the implementation for factory construction
946+
MyCustomRetargeterCfg.retargeter_type = MyCustomRetargeter
950947
951948
4. Now you can use your custom retargeter in teleop device configurations:
952949

source/isaaclab/config/extension.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22

33
# Note: Semantic Versioning is used: https://semver.org/
4-
version = "0.48.2"
4+
version = "0.48.3"
55

66
# Description
77
title = "Isaac Lab framework for Robot Learning"

source/isaaclab/docs/CHANGELOG.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
Changelog
22
---------
33

4+
0.48.3 (2025-11-13)
5+
~~~~~~~~~~~~~~~~~~~
6+
7+
Changed
8+
^^^^^^^
9+
10+
* Moved retargeter and device declaration out of factory and into the devices/retargeters themselves.
11+
12+
413
0.48.2 (2025-11-13)
514
~~~~~~~~~~~~~~~~~~~
615

source/isaaclab/isaaclab/devices/device_base.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,14 @@
1818
class DeviceCfg:
1919
"""Configuration for teleoperation devices."""
2020

21+
# Whether teleoperation should start active by default
22+
teleoperation_active_default: bool = True
23+
# Torch device string to place output tensors on
2124
sim_device: str = "cpu"
25+
# Retargeters that transform device data into robot commands
2226
retargeters: list[RetargeterCfg] = field(default_factory=list)
27+
# Concrete device class to construct for this config. Set by each device module.
28+
class_type: type["DeviceBase"] | None = None
2329

2430

2531
@dataclass

source/isaaclab/isaaclab/devices/gamepad/se2_gamepad.py

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
"""Gamepad controller for SE(2) control."""
77

8+
from __future__ import annotations
9+
810
import numpy as np
911
import torch
1012
import weakref
@@ -18,16 +20,6 @@
1820
from ..device_base import DeviceBase, DeviceCfg
1921

2022

21-
@dataclass
22-
class Se2GamepadCfg(DeviceCfg):
23-
"""Configuration for SE2 gamepad devices."""
24-
25-
v_x_sensitivity: float = 1.0
26-
v_y_sensitivity: float = 1.0
27-
omega_z_sensitivity: float = 1.0
28-
dead_zone: float = 0.01
29-
30-
3123
class Se2Gamepad(DeviceBase):
3224
r"""A gamepad controller for sending SE(2) commands as velocity commands.
3325
@@ -209,3 +201,14 @@ def _resolve_command_buffer(self, raw_command: np.ndarray) -> np.ndarray:
209201
command[command_sign] *= -1
210202

211203
return command
204+
205+
206+
@dataclass
207+
class Se2GamepadCfg(DeviceCfg):
208+
"""Configuration for SE2 gamepad devices."""
209+
210+
v_x_sensitivity: float = 1.0
211+
v_y_sensitivity: float = 1.0
212+
omega_z_sensitivity: float = 1.0
213+
dead_zone: float = 0.01
214+
class_type: type[DeviceBase] = Se2Gamepad

source/isaaclab/isaaclab/devices/gamepad/se3_gamepad.py

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
"""Gamepad controller for SE(3) control."""
77

8+
from __future__ import annotations
9+
810
import numpy as np
911
import torch
1012
import weakref
@@ -18,17 +20,6 @@
1820
from ..device_base import DeviceBase, DeviceCfg
1921

2022

21-
@dataclass
22-
class Se3GamepadCfg(DeviceCfg):
23-
"""Configuration for SE3 gamepad devices."""
24-
25-
gripper_term: bool = True
26-
dead_zone: float = 0.01 # For gamepad devices
27-
pos_sensitivity: float = 1.0
28-
rot_sensitivity: float = 1.6
29-
retargeters: None = None
30-
31-
3223
class Se3Gamepad(DeviceBase):
3324
"""A gamepad controller for sending SE(3) commands as delta poses and binary command (open/close).
3425
@@ -264,3 +255,14 @@ def _resolve_command_buffer(self, raw_command: np.ndarray) -> np.ndarray:
264255
delta_command[delta_command_sign] *= -1
265256

266257
return delta_command
258+
259+
260+
@dataclass
261+
class Se3GamepadCfg(DeviceCfg):
262+
"""Configuration for SE3 gamepad devices."""
263+
264+
gripper_term: bool = True
265+
dead_zone: float = 0.01 # For gamepad devices
266+
pos_sensitivity: float = 1.0
267+
rot_sensitivity: float = 1.6
268+
class_type: type[DeviceBase] = Se3Gamepad

source/isaaclab/isaaclab/devices/haply/se3_haply.py

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
"""Haply device controller for SE3 control with force feedback."""
77

8+
from __future__ import annotations
9+
810
import asyncio
911
import json
1012
import numpy as np
@@ -25,23 +27,6 @@
2527
from ..retargeter_base import RetargeterBase
2628

2729

28-
@dataclass
29-
class HaplyDeviceCfg(DeviceCfg):
30-
"""Configuration for Haply device.
31-
32-
Attributes:
33-
websocket_uri: WebSocket URI for Haply SDK connection
34-
pos_sensitivity: Position sensitivity scaling factor
35-
data_rate: Data exchange rate in Hz
36-
limit_force: Maximum force magnitude in Newtons (safety limit)
37-
"""
38-
39-
websocket_uri: str = "ws://localhost:10001"
40-
pos_sensitivity: float = 1.0
41-
data_rate: float = 200.0
42-
limit_force: float = 2.0
43-
44-
4530
class HaplyDevice(DeviceBase):
4631
"""A Haply device controller for sending SE(3) commands with force feedback.
4732
@@ -387,3 +372,21 @@ async def _websocket_loop(self):
387372
await asyncio.sleep(2.0)
388373
else:
389374
break
375+
376+
377+
@dataclass
378+
class HaplyDeviceCfg(DeviceCfg):
379+
"""Configuration for Haply device.
380+
381+
Attributes:
382+
websocket_uri: WebSocket URI for Haply SDK connection
383+
pos_sensitivity: Position sensitivity scaling factor
384+
data_rate: Data exchange rate in Hz
385+
limit_force: Maximum force magnitude in Newtons (safety limit)
386+
"""
387+
388+
websocket_uri: str = "ws://localhost:10001"
389+
pos_sensitivity: float = 1.0
390+
data_rate: float = 200.0
391+
limit_force: float = 2.0
392+
class_type: type[DeviceBase] = HaplyDevice

source/isaaclab/isaaclab/devices/keyboard/se2_keyboard.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
"""Keyboard controller for SE(2) control."""
77

8+
from __future__ import annotations
9+
810
import numpy as np
911
import torch
1012
import weakref
@@ -17,15 +19,6 @@
1719
from ..device_base import DeviceBase, DeviceCfg
1820

1921

20-
@dataclass
21-
class Se2KeyboardCfg(DeviceCfg):
22-
"""Configuration for SE2 keyboard devices."""
23-
24-
v_x_sensitivity: float = 0.8
25-
v_y_sensitivity: float = 0.4
26-
omega_z_sensitivity: float = 1.0
27-
28-
2922
class Se2Keyboard(DeviceBase):
3023
r"""A keyboard controller for sending SE(2) commands as velocity commands.
3124
@@ -178,3 +171,13 @@ def _create_key_bindings(self):
178171
"NUMPAD_9": np.asarray([0.0, 0.0, -1.0]) * self.omega_z_sensitivity,
179172
"X": np.asarray([0.0, 0.0, -1.0]) * self.omega_z_sensitivity,
180173
}
174+
175+
176+
@dataclass
177+
class Se2KeyboardCfg(DeviceCfg):
178+
"""Configuration for SE2 keyboard devices."""
179+
180+
v_x_sensitivity: float = 0.8
181+
v_y_sensitivity: float = 0.4
182+
omega_z_sensitivity: float = 1.0
183+
class_type: type[DeviceBase] = Se2Keyboard

source/isaaclab/isaaclab/devices/keyboard/se3_keyboard.py

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
"""Keyboard controller for SE(3) control."""
77

8+
from __future__ import annotations
9+
810
import numpy as np
911
import torch
1012
import weakref
@@ -18,16 +20,6 @@
1820
from ..device_base import DeviceBase, DeviceCfg
1921

2022

21-
@dataclass
22-
class Se3KeyboardCfg(DeviceCfg):
23-
"""Configuration for SE3 keyboard devices."""
24-
25-
gripper_term: bool = True
26-
pos_sensitivity: float = 0.4
27-
rot_sensitivity: float = 0.8
28-
retargeters: None = None
29-
30-
3123
class Se3Keyboard(DeviceBase):
3224
"""A keyboard controller for sending SE(3) commands as delta poses and binary command (open/close).
3325
@@ -206,3 +198,14 @@ def _create_key_bindings(self):
206198
"C": np.asarray([0.0, 0.0, 1.0]) * self.rot_sensitivity,
207199
"V": np.asarray([0.0, 0.0, -1.0]) * self.rot_sensitivity,
208200
}
201+
202+
203+
@dataclass
204+
class Se3KeyboardCfg(DeviceCfg):
205+
"""Configuration for SE3 keyboard devices."""
206+
207+
gripper_term: bool = True
208+
pos_sensitivity: float = 0.4
209+
rot_sensitivity: float = 0.8
210+
retargeters: None = None
211+
class_type: type[DeviceBase] = Se3Keyboard

source/isaaclab/isaaclab/devices/openxr/manus_vive.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
Manus and Vive for teleoperation and interaction.
88
"""
99

10+
from __future__ import annotations
11+
1012
import contextlib
1113
import numpy as np
1214
from collections.abc import Callable
@@ -34,13 +36,6 @@
3436
from .manus_vive_utils import HAND_JOINT_MAP, ManusViveIntegration
3537

3638

37-
@dataclass
38-
class ManusViveCfg(DeviceCfg):
39-
"""Configuration for Manus and Vive."""
40-
41-
xr_cfg: XrCfg | None = None
42-
43-
4439
class ManusVive(DeviceBase):
4540
"""Manus gloves and Vive trackers for teleoperation and interaction.
4641
@@ -246,3 +241,11 @@ def _on_teleop_command(self, event: carb.events.IEvent):
246241
elif "reset" in msg:
247242
if "RESET" in self._additional_callbacks:
248243
self._additional_callbacks["RESET"]()
244+
245+
246+
@dataclass
247+
class ManusViveCfg(DeviceCfg):
248+
"""Configuration for Manus and Vive."""
249+
250+
xr_cfg: XrCfg | None = None
251+
class_type: type[DeviceBase] = ManusVive

0 commit comments

Comments
 (0)