Skip to content

Commit 6ea97b2

Browse files
auto-detect spacemouse if id (product/vendor) is incorrect (#764)
* auto-detect spacemouse if id (product/vendor) is incorrect * Use simpler/concise auto-detection method * allow device_path as an option for space mouse; refactor detection code * Format * Only support direct control of torso if single joint * Use logging instead of prints * format
1 parent d1cab0b commit 6ea97b2

2 files changed

Lines changed: 44 additions & 18 deletions

File tree

robosuite/devices/device.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,9 @@ def input2action(self, mirror_actions=False, goal_update_mode="target") -> Optio
161161
device_torso_input = 0.0 # No torso input for non-mobile robots by default
162162

163163
if hasattr(robot, "torso") and robot.torso is not None:
164-
ac_dict["torso"] = self.get_torso_action(robot, device_torso_input)
164+
# only support single joint torso for now
165+
if self.env.robots[0].composite_controller.part_controllers["torso"].joint_dim == 1:
166+
ac_dict["torso"] = self.get_torso_action(robot, device_torso_input)
165167

166168
# populate action dict items for arm and grippers
167169
arm_action = self.get_arm_action(
@@ -190,7 +192,7 @@ def get_arm_action(self, robot, arm, norm_delta, goal_update_mode="target"):
190192
assert goal_update_mode in [
191193
"achieved",
192194
"target",
193-
], f"goal_update_mode must be either 'achieved' or 'target', got {goal_update_mode}" # update next target either based on achieved pose or current target pose
195+
], f"goal_update_mode must be either 'achieved' or 'target', got {goal_update_mode}" # update next target either based on achieved pose or current target pose
194196

195197
# TODO: the logic between OSC and while body based ik is fragmented right now. Unify
196198
if isinstance(robot.part_controllers[arm], OperationalSpaceController):

robosuite/devices/spacemouse.py

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -104,14 +104,14 @@ def convert(b1, b2):
104104
class SpaceMouse(Device):
105105
"""
106106
A minimalistic driver class for SpaceMouse with HID library.
107-
108-
Note: Use hid.enumerate() to view all USB human interface devices (HID).
109-
Make sure SpaceMouse is detected before running the script.
110-
You can look up its vendor/product id from this method.
107+
Auto-detects 3Dconnexion devices if default vendor/product IDs fail.
111108
112109
Args:
113110
env (RobotEnv): The environment which contains the robot(s) to control
114111
using this device.
112+
vendor_id (int): SpaceMouse vendor ID (will auto-detect if default fails)
113+
product_id (int): SpaceMouse product ID (will auto-detect if default fails)
114+
device_path (bytes): Specific device path (e.g., b'1-2:1.0') for reliable identification
115115
pos_sensitivity (float): Magnitude of input position command scaling
116116
rot_sensitivity (float): Magnitude of scale input rotation commands scaling
117117
"""
@@ -121,29 +121,39 @@ def __init__(
121121
env,
122122
vendor_id=macros.SPACEMOUSE_VENDOR_ID,
123123
product_id=macros.SPACEMOUSE_PRODUCT_ID,
124+
device_path=None,
124125
pos_sensitivity=1.0,
125126
rot_sensitivity=1.0,
126127
):
127128
super().__init__(env)
128129

129-
print("Opening SpaceMouse device")
130+
ROBOSUITE_DEFAULT_LOGGER.info("Opening SpaceMouse device")
130131
self.vendor_id = vendor_id
131132
self.product_id = product_id
132133
self.device = hid.device()
133-
try:
134-
self.device.open(self.vendor_id, self.product_id) # SpaceMouse
135-
except OSError as e:
136-
ROBOSUITE_DEFAULT_LOGGER.warning(
137-
"Failed to open SpaceMouse device"
138-
"Consider killing other processes that may be using the device such as 3DconnexionHelper (killall 3DconnexionHelper)"
139-
)
140-
raise
134+
135+
if device_path:
136+
try:
137+
self.device.open_path(device_path)
138+
ROBOSUITE_DEFAULT_LOGGER.info(f"Connected using path: {device_path}")
139+
except OSError:
140+
ROBOSUITE_DEFAULT_LOGGER.warning(f"Failed to open device at path: {device_path}")
141+
self._auto_detect_device()
142+
else:
143+
try:
144+
self.device.open(vendor_id, product_id)
145+
ROBOSUITE_DEFAULT_LOGGER.info(f"Connected using default IDs: {vendor_id:04x}:{product_id:04x}")
146+
except OSError:
147+
ROBOSUITE_DEFAULT_LOGGER.warning(
148+
f"Failed to open device with provided IDs: {vendor_id:04x}:{product_id:04x}"
149+
)
150+
self._auto_detect_device()
141151

142152
self.pos_sensitivity = pos_sensitivity
143153
self.rot_sensitivity = rot_sensitivity
144154

145-
print("Manufacturer: %s" % self.device.get_manufacturer_string())
146-
print("Product: %s" % self.device.get_product_string())
155+
ROBOSUITE_DEFAULT_LOGGER.info("Manufacturer: %s" % self.device.get_manufacturer_string())
156+
ROBOSUITE_DEFAULT_LOGGER.info("Product: %s" % self.device.get_product_string())
147157

148158
# 6-DOF variables
149159
self.x, self.y, self.z = 0, 0, 0
@@ -169,6 +179,18 @@ def __init__(
169179
# start listening
170180
self.listener.start()
171181

182+
def _auto_detect_device(self):
183+
"""Auto-detect and connect to first 3Dconnexion device."""
184+
devices = [d for d in hid.enumerate() if d.get("manufacturer_string") == "3Dconnexion"]
185+
if not devices:
186+
raise OSError("No 3Dconnexion devices found")
187+
188+
selected = devices[0]
189+
self.device.open_path(selected["path"])
190+
self.vendor_id = selected["vendor_id"]
191+
self.product_id = selected["product_id"]
192+
ROBOSUITE_DEFAULT_LOGGER.info(f"Auto-detected: {selected['product_string']} with path {selected['path']}")
193+
172194
@staticmethod
173195
def _display_controls():
174196
"""
@@ -191,6 +213,8 @@ def print_command(char, info):
191213
print_command("s", "switch active arm (if multi-armed robot)")
192214
print_command("=", "switch active robot (if multi-robot environment)")
193215
print("")
216+
print("NOTE: Auto-detects 3Dconnexion devices. Use device_path for specific device.")
217+
print("")
194218

195219
def _reset_internal_state(self):
196220
"""
@@ -375,5 +399,5 @@ def _postprocess_device_outputs(self, dpos, drotation):
375399

376400
space_mouse = SpaceMouse()
377401
for i in range(100):
378-
print(space_mouse.control, space_mouse.control_gripper)
402+
ROBOSUITE_DEFAULT_LOGGER.info(f"Control: {space_mouse.control}, Gripper: {space_mouse.control_gripper}")
379403
time.sleep(0.02)

0 commit comments

Comments
 (0)