dexcontrol 0.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of dexcontrol might be problematic. Click here for more details.
- dexcontrol/__init__.py +45 -0
- dexcontrol/apps/dualsense_teleop_base.py +371 -0
- dexcontrol/config/__init__.py +14 -0
- dexcontrol/config/core/__init__.py +22 -0
- dexcontrol/config/core/arm.py +32 -0
- dexcontrol/config/core/chassis.py +22 -0
- dexcontrol/config/core/hand.py +42 -0
- dexcontrol/config/core/head.py +33 -0
- dexcontrol/config/core/misc.py +37 -0
- dexcontrol/config/core/torso.py +36 -0
- dexcontrol/config/sensors/__init__.py +4 -0
- dexcontrol/config/sensors/cameras/__init__.py +7 -0
- dexcontrol/config/sensors/cameras/gemini_camera.py +16 -0
- dexcontrol/config/sensors/cameras/rgb_camera.py +15 -0
- dexcontrol/config/sensors/imu/__init__.py +6 -0
- dexcontrol/config/sensors/imu/gemini_imu.py +15 -0
- dexcontrol/config/sensors/imu/nine_axis_imu.py +15 -0
- dexcontrol/config/sensors/lidar/__init__.py +6 -0
- dexcontrol/config/sensors/lidar/rplidar.py +15 -0
- dexcontrol/config/sensors/ultrasonic/__init__.py +6 -0
- dexcontrol/config/sensors/ultrasonic/ultrasonic.py +15 -0
- dexcontrol/config/sensors/vega_sensors.py +65 -0
- dexcontrol/config/vega.py +203 -0
- dexcontrol/core/__init__.py +0 -0
- dexcontrol/core/arm.py +324 -0
- dexcontrol/core/chassis.py +628 -0
- dexcontrol/core/component.py +834 -0
- dexcontrol/core/hand.py +170 -0
- dexcontrol/core/head.py +232 -0
- dexcontrol/core/misc.py +514 -0
- dexcontrol/core/torso.py +198 -0
- dexcontrol/proto/dexcontrol_msg_pb2.py +69 -0
- dexcontrol/proto/dexcontrol_msg_pb2.pyi +168 -0
- dexcontrol/proto/dexcontrol_query_pb2.py +73 -0
- dexcontrol/proto/dexcontrol_query_pb2.pyi +134 -0
- dexcontrol/robot.py +1091 -0
- dexcontrol/sensors/__init__.py +40 -0
- dexcontrol/sensors/camera/__init__.py +18 -0
- dexcontrol/sensors/camera/gemini_camera.py +139 -0
- dexcontrol/sensors/camera/rgb_camera.py +98 -0
- dexcontrol/sensors/imu/__init__.py +22 -0
- dexcontrol/sensors/imu/gemini_imu.py +139 -0
- dexcontrol/sensors/imu/nine_axis_imu.py +149 -0
- dexcontrol/sensors/lidar/__init__.py +3 -0
- dexcontrol/sensors/lidar/rplidar.py +164 -0
- dexcontrol/sensors/manager.py +185 -0
- dexcontrol/sensors/ultrasonic.py +110 -0
- dexcontrol/utils/__init__.py +15 -0
- dexcontrol/utils/constants.py +12 -0
- dexcontrol/utils/io_utils.py +26 -0
- dexcontrol/utils/motion_utils.py +194 -0
- dexcontrol/utils/os_utils.py +39 -0
- dexcontrol/utils/pb_utils.py +103 -0
- dexcontrol/utils/rate_limiter.py +167 -0
- dexcontrol/utils/reset_orbbec_camera_usb.py +98 -0
- dexcontrol/utils/subscribers/__init__.py +44 -0
- dexcontrol/utils/subscribers/base.py +260 -0
- dexcontrol/utils/subscribers/camera.py +328 -0
- dexcontrol/utils/subscribers/decoders.py +83 -0
- dexcontrol/utils/subscribers/generic.py +105 -0
- dexcontrol/utils/subscribers/imu.py +170 -0
- dexcontrol/utils/subscribers/lidar.py +195 -0
- dexcontrol/utils/subscribers/protobuf.py +106 -0
- dexcontrol/utils/timer.py +136 -0
- dexcontrol/utils/trajectory_utils.py +40 -0
- dexcontrol/utils/viz_utils.py +86 -0
- dexcontrol-0.2.1.dist-info/METADATA +369 -0
- dexcontrol-0.2.1.dist-info/RECORD +72 -0
- dexcontrol-0.2.1.dist-info/WHEEL +5 -0
- dexcontrol-0.2.1.dist-info/licenses/LICENSE +188 -0
- dexcontrol-0.2.1.dist-info/licenses/NOTICE +13 -0
- dexcontrol-0.2.1.dist-info/top_level.txt +1 -0
dexcontrol/__init__.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Copyright (c) 2025 Dexmate CORPORATION & AFFILIATES. All rights reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 with Commons Clause License
|
|
4
|
+
# Condition v1.0 [see LICENSE for details].
|
|
5
|
+
|
|
6
|
+
"""DexControl: Robot Control Interface Library.
|
|
7
|
+
|
|
8
|
+
This package provides interfaces for controlling and monitoring robot systems.
|
|
9
|
+
It serves as the primary API for interacting with Dexmate robots.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import os
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Final
|
|
17
|
+
|
|
18
|
+
# DO NOT REMOVE this following import, it is needed for hydra to find the config
|
|
19
|
+
import dexcontrol.config # pylint: disable=unused-import
|
|
20
|
+
from dexcontrol.robot import Robot
|
|
21
|
+
from dexcontrol.utils.constants import COMM_CFG_PATH_ENV_VAR
|
|
22
|
+
|
|
23
|
+
# Package-level constants
|
|
24
|
+
LIB_PATH: Final[Path] = Path(__file__).resolve().parent
|
|
25
|
+
CFG_PATH: Final[Path] = LIB_PATH / "config"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_comm_cfg_path() -> Path:
|
|
29
|
+
default_path = list(
|
|
30
|
+
Path("~/.dexmate/comm/zenoh/").expanduser().glob("**/zenoh_peer_config.json5")
|
|
31
|
+
)
|
|
32
|
+
if len(default_path) == 0:
|
|
33
|
+
raise FileNotFoundError(
|
|
34
|
+
"No zenoh_peer_config.json5 file found in ~/.dexmate/comm/zenoh/"
|
|
35
|
+
)
|
|
36
|
+
return default_path[0]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
COMM_CFG_PATH: Final[Path] = Path(
|
|
40
|
+
os.getenv(COMM_CFG_PATH_ENV_VAR, get_comm_cfg_path())
|
|
41
|
+
).expanduser()
|
|
42
|
+
|
|
43
|
+
ROBOT_CFG_PATH: Final[Path] = CFG_PATH
|
|
44
|
+
|
|
45
|
+
__all__ = ["Robot", "LIB_PATH", "CFG_PATH", "COMM_CFG_PATH", "ROBOT_CFG_PATH"]
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
# Copyright (c) 2025 Dexmate CORPORATION & AFFILIATES. All rights reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 with Commons Clause License
|
|
4
|
+
# Condition v1.0 [see LICENSE for details].
|
|
5
|
+
|
|
6
|
+
"""DualSense controller teleoperation base module.
|
|
7
|
+
|
|
8
|
+
This module provides a base class for controlling robots using a DualSense controller,
|
|
9
|
+
with safety features, emergency stop handling, and customizable control mappings.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import threading
|
|
13
|
+
from abc import ABC, abstractmethod
|
|
14
|
+
from enum import Enum
|
|
15
|
+
import time
|
|
16
|
+
|
|
17
|
+
from dualsense_controller import DualSenseController
|
|
18
|
+
from loguru import logger
|
|
19
|
+
|
|
20
|
+
from dexcontrol.robot import Robot
|
|
21
|
+
from dexcontrol.utils.rate_limiter import RateLimiter
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ControlMode(Enum):
|
|
25
|
+
"""Control modes for the teleop interface."""
|
|
26
|
+
|
|
27
|
+
POSITION = "position"
|
|
28
|
+
VELOCITY = "velocity"
|
|
29
|
+
TORQUE = "torque"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ButtonState:
|
|
33
|
+
"""Class to track button state with debouncing."""
|
|
34
|
+
|
|
35
|
+
def __init__(self, debounce_time: float = 0.05):
|
|
36
|
+
"""Initialize button state.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
debounce_time: Time in seconds to wait before considering a state change valid.
|
|
40
|
+
"""
|
|
41
|
+
self._raw_state = False
|
|
42
|
+
self._debounced_state = False
|
|
43
|
+
self._last_change_time = 0.0
|
|
44
|
+
self._debounce_time = debounce_time
|
|
45
|
+
self._lock = threading.Lock()
|
|
46
|
+
|
|
47
|
+
def update(self, new_state: bool) -> None:
|
|
48
|
+
"""Update the button state with debouncing.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
new_state: New raw button state.
|
|
52
|
+
"""
|
|
53
|
+
with self._lock:
|
|
54
|
+
current_time = time.time()
|
|
55
|
+
if new_state != self._raw_state:
|
|
56
|
+
self._last_change_time = current_time
|
|
57
|
+
self._raw_state = new_state
|
|
58
|
+
|
|
59
|
+
# Only update debounced state if enough time has passed
|
|
60
|
+
if current_time - self._last_change_time >= self._debounce_time:
|
|
61
|
+
self._debounced_state = self._raw_state
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def state(self) -> bool:
|
|
65
|
+
"""Get the current debounced button state."""
|
|
66
|
+
with self._lock:
|
|
67
|
+
return self._debounced_state
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class DualSenseTeleopBase(ABC):
|
|
71
|
+
"""Base class for DualSense controller teleoperation.
|
|
72
|
+
|
|
73
|
+
This class provides basic functionality for controlling a robot using a DualSense
|
|
74
|
+
controller, including safety features, e-stop handling, and button/stick mappings.
|
|
75
|
+
|
|
76
|
+
Attributes:
|
|
77
|
+
is_running: Flag indicating if the control loop is active.
|
|
78
|
+
control_hz: Control loop frequency in Hz.
|
|
79
|
+
button_update_hz: Button update frequency in Hz.
|
|
80
|
+
button_debounce_time: Time in seconds to wait before considering a button state change valid.
|
|
81
|
+
dualsense: Interface to the DualSense controller.
|
|
82
|
+
button_states: Dictionary to track button states with debouncing.
|
|
83
|
+
active_buttons: Currently pressed buttons.
|
|
84
|
+
safe_pressed: Safety button (L1) state.
|
|
85
|
+
estop_on: Emergency stop state.
|
|
86
|
+
estop_lock: Thread lock for e-stop state changes.
|
|
87
|
+
bot: Robot interface instance.
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
def __init__(
|
|
91
|
+
self,
|
|
92
|
+
control_hz: int = 200,
|
|
93
|
+
button_update_hz: int = 20,
|
|
94
|
+
device_index: int = 0,
|
|
95
|
+
button_debounce_time: float = 0.05,
|
|
96
|
+
) -> None:
|
|
97
|
+
"""Initialize the DualSense teleoperation base.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
control_hz: Control loop frequency in Hz. Defaults to 200.
|
|
101
|
+
button_update_hz: Button update frequency in Hz. Defaults to 20.
|
|
102
|
+
device_index: Index of the DualSense controller device. Defaults to 0.
|
|
103
|
+
button_debounce_time: Time in seconds to wait before considering a button state change valid.
|
|
104
|
+
"""
|
|
105
|
+
self.is_running = True
|
|
106
|
+
self.control_hz = control_hz
|
|
107
|
+
self.button_update_hz = button_update_hz
|
|
108
|
+
self.button_debounce_time = button_debounce_time
|
|
109
|
+
|
|
110
|
+
# Thread safety
|
|
111
|
+
self.button_lock = threading.Lock()
|
|
112
|
+
self.estop_lock = threading.Lock()
|
|
113
|
+
|
|
114
|
+
# Controller setup
|
|
115
|
+
try:
|
|
116
|
+
self.dualsense = DualSenseController(
|
|
117
|
+
device_index_or_device_info=device_index
|
|
118
|
+
)
|
|
119
|
+
self.dualsense.activate()
|
|
120
|
+
logger.info(
|
|
121
|
+
f"DualSense controller activated (device index: {device_index})"
|
|
122
|
+
)
|
|
123
|
+
except Exception as e:
|
|
124
|
+
logger.error(f"Failed to initialize DualSense controller: {e}")
|
|
125
|
+
raise
|
|
126
|
+
|
|
127
|
+
# Button state tracking
|
|
128
|
+
self.button_states: dict[str, ButtonState] = {}
|
|
129
|
+
self.active_buttons: set[str] = set()
|
|
130
|
+
|
|
131
|
+
# State flags
|
|
132
|
+
self.safe_pressed = False
|
|
133
|
+
self.estop_on = False
|
|
134
|
+
|
|
135
|
+
# Control state
|
|
136
|
+
self.control_mode = ControlMode.POSITION
|
|
137
|
+
self.rate_limiter = RateLimiter(self.button_update_hz)
|
|
138
|
+
|
|
139
|
+
# Robot interface
|
|
140
|
+
try:
|
|
141
|
+
self.bot = Robot()
|
|
142
|
+
logger.info(f"Robot '{self.bot.robot_model}' initialized")
|
|
143
|
+
except Exception as e:
|
|
144
|
+
logger.error(f"Failed to initialize robot: {e}")
|
|
145
|
+
self.dualsense.deactivate()
|
|
146
|
+
raise
|
|
147
|
+
|
|
148
|
+
# Configure button mappings
|
|
149
|
+
self._setup_button_mappings()
|
|
150
|
+
|
|
151
|
+
def _setup_button_mappings(self) -> None:
|
|
152
|
+
"""Set up controller button mappings and callbacks."""
|
|
153
|
+
# Safety and e-stop buttons
|
|
154
|
+
self.dualsense.btn_l1.on_down(self.safety_check)
|
|
155
|
+
self.dualsense.btn_l1.on_up(self.safety_check_release)
|
|
156
|
+
self.dualsense.btn_touchpad.on_down(self.toggle_estop)
|
|
157
|
+
|
|
158
|
+
# Standard button mappings
|
|
159
|
+
button_mapping = {
|
|
160
|
+
"btn_up": "dpad_up",
|
|
161
|
+
"btn_down": "dpad_down",
|
|
162
|
+
"btn_right": "dpad_right",
|
|
163
|
+
"btn_left": "dpad_left",
|
|
164
|
+
"btn_circle": "circle",
|
|
165
|
+
"btn_square": "square",
|
|
166
|
+
"btn_triangle": "triangle",
|
|
167
|
+
"btn_cross": "cross",
|
|
168
|
+
"btn_r1": "r1",
|
|
169
|
+
"btn_r2": "r2",
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
for btn_name, motion_name in button_mapping.items():
|
|
173
|
+
btn = getattr(self.dualsense, btn_name)
|
|
174
|
+
# Create button state tracker
|
|
175
|
+
self.button_states[motion_name] = ButtonState(self.button_debounce_time)
|
|
176
|
+
# Use default parameter to capture current value
|
|
177
|
+
btn.on_down(lambda m=motion_name: self._update_button_state(m, True))
|
|
178
|
+
btn.on_up(lambda m=motion_name: self._update_button_state(m, False))
|
|
179
|
+
|
|
180
|
+
# Setup additional mappings specific to the subclass
|
|
181
|
+
self._setup_additional_mappings()
|
|
182
|
+
|
|
183
|
+
def _setup_additional_mappings(self) -> None:
|
|
184
|
+
"""Set up additional controller mappings specific to subclasses.
|
|
185
|
+
|
|
186
|
+
This method can be overridden by subclasses to add more button mappings.
|
|
187
|
+
"""
|
|
188
|
+
pass
|
|
189
|
+
|
|
190
|
+
def _update_button_state(self, button: str, state: bool) -> None:
|
|
191
|
+
"""Update the state of a button with debouncing.
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
button: Name of the button being updated.
|
|
195
|
+
state: New raw button state.
|
|
196
|
+
"""
|
|
197
|
+
if button in self.button_states:
|
|
198
|
+
self.button_states[button].update(state)
|
|
199
|
+
if state:
|
|
200
|
+
self.add_button(button)
|
|
201
|
+
else:
|
|
202
|
+
self.remove_button(button)
|
|
203
|
+
|
|
204
|
+
def add_button(self, button: str) -> None:
|
|
205
|
+
"""Add a button to the set of active buttons if safety is enabled.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
button: Name of the button being pressed.
|
|
209
|
+
"""
|
|
210
|
+
if self.safe_pressed or button in ["l1", "touchpad"]:
|
|
211
|
+
with self.button_lock:
|
|
212
|
+
self.active_buttons.add(button)
|
|
213
|
+
logger.debug(f"Button added: {button}")
|
|
214
|
+
|
|
215
|
+
def remove_button(self, button: str) -> None:
|
|
216
|
+
"""Remove a button from the set of active buttons.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
button: Name of the button being released.
|
|
220
|
+
"""
|
|
221
|
+
with self.button_lock:
|
|
222
|
+
self.active_buttons.discard(button)
|
|
223
|
+
logger.debug(f"Button removed: {button}")
|
|
224
|
+
|
|
225
|
+
def toggle_estop(self) -> None:
|
|
226
|
+
"""Toggle the emergency stop state of the robot.
|
|
227
|
+
|
|
228
|
+
When activated, this will:
|
|
229
|
+
1. Toggle the robot's software e-stop
|
|
230
|
+
2. Stop any ongoing motion
|
|
231
|
+
3. Reset all motion commands to zero
|
|
232
|
+
"""
|
|
233
|
+
try:
|
|
234
|
+
with self.estop_lock:
|
|
235
|
+
self.estop_on = not self.estop_on
|
|
236
|
+
if self.estop_on:
|
|
237
|
+
self.stop_all_motion()
|
|
238
|
+
self.bot.estop.activate()
|
|
239
|
+
else:
|
|
240
|
+
self.bot.estop.deactivate()
|
|
241
|
+
|
|
242
|
+
logger.info(f"E-stop {'activated' if self.estop_on else 'deactivated'}")
|
|
243
|
+
|
|
244
|
+
# Visual feedback on controller
|
|
245
|
+
if self.estop_on:
|
|
246
|
+
self.dualsense.lightbar.set_color(255, 0, 0) # Red for e-stop
|
|
247
|
+
else:
|
|
248
|
+
self.update_controller_feedback()
|
|
249
|
+
|
|
250
|
+
except Exception as e:
|
|
251
|
+
logger.error(f"Error toggling e-stop: {e}")
|
|
252
|
+
|
|
253
|
+
def safety_check(self) -> None:
|
|
254
|
+
"""Enable safety mode (L1 button pressed).
|
|
255
|
+
|
|
256
|
+
Activates haptic feedback and enables safety-gated commands.
|
|
257
|
+
"""
|
|
258
|
+
self.dualsense.left_rumble.set(50)
|
|
259
|
+
self.safe_pressed = True
|
|
260
|
+
self.dualsense.left_rumble.set(0)
|
|
261
|
+
logger.debug("Safety mode enabled")
|
|
262
|
+
|
|
263
|
+
def safety_check_release(self) -> None:
|
|
264
|
+
"""Disable safety mode (L1 button released).
|
|
265
|
+
|
|
266
|
+
Deactivates haptic feedback and:
|
|
267
|
+
1. Clears all active buttons
|
|
268
|
+
2. Stops any ongoing motion
|
|
269
|
+
3. Resets all motion commands to zero
|
|
270
|
+
"""
|
|
271
|
+
self.safe_pressed = False
|
|
272
|
+
self.dualsense.left_rumble.set(0)
|
|
273
|
+
|
|
274
|
+
# Clear all active buttons with lock protection
|
|
275
|
+
with self.button_lock:
|
|
276
|
+
self.active_buttons.clear()
|
|
277
|
+
|
|
278
|
+
# Stop any ongoing motion
|
|
279
|
+
self.stop_all_motion()
|
|
280
|
+
logger.debug("Safety mode disabled, motion stopped")
|
|
281
|
+
|
|
282
|
+
def get_active_buttons(self) -> set[str]:
|
|
283
|
+
"""Get a copy of currently active buttons in a thread-safe way.
|
|
284
|
+
|
|
285
|
+
Returns:
|
|
286
|
+
A copy of the set of currently active buttons.
|
|
287
|
+
"""
|
|
288
|
+
with self.button_lock:
|
|
289
|
+
return self.active_buttons.copy()
|
|
290
|
+
|
|
291
|
+
def update_controller_feedback(self) -> None:
|
|
292
|
+
"""Update controller visual feedback based on current state.
|
|
293
|
+
|
|
294
|
+
This method can be overridden by subclasses to customize feedback.
|
|
295
|
+
"""
|
|
296
|
+
# Default is white lightbar
|
|
297
|
+
self.dualsense.lightbar.set_color_white()
|
|
298
|
+
|
|
299
|
+
def run_forever(self) -> None:
|
|
300
|
+
"""Run the control loop indefinitely until interrupted.
|
|
301
|
+
|
|
302
|
+
The loop runs at the specified button_update_hz frequency.
|
|
303
|
+
Handles KeyboardInterrupt for clean shutdown.
|
|
304
|
+
"""
|
|
305
|
+
logger.info("Starting teleop control loop")
|
|
306
|
+
rate_limiter = RateLimiter(self.button_update_hz)
|
|
307
|
+
try:
|
|
308
|
+
while self.is_running and not self.bot.is_shutdown():
|
|
309
|
+
self.update_motion()
|
|
310
|
+
rate_limiter.sleep()
|
|
311
|
+
except KeyboardInterrupt:
|
|
312
|
+
logger.info("Received keyboard interrupt, shutting down")
|
|
313
|
+
except Exception as e:
|
|
314
|
+
logger.error(f"Error in control loop: {e}")
|
|
315
|
+
raise
|
|
316
|
+
finally:
|
|
317
|
+
self.cleanup()
|
|
318
|
+
|
|
319
|
+
def cleanup(self) -> None:
|
|
320
|
+
"""Clean up resources before shutting down."""
|
|
321
|
+
self.is_running = False
|
|
322
|
+
|
|
323
|
+
# Reset controller
|
|
324
|
+
if hasattr(self, "dualsense"):
|
|
325
|
+
self.dualsense.lightbar.set_color_white()
|
|
326
|
+
self.dualsense.deactivate()
|
|
327
|
+
|
|
328
|
+
logger.info("Teleop node cleaned up and exiting")
|
|
329
|
+
|
|
330
|
+
@abstractmethod
|
|
331
|
+
def update_motion(self) -> None:
|
|
332
|
+
"""Update the robot's motion based on current controller state.
|
|
333
|
+
|
|
334
|
+
This method should be implemented by derived classes to define
|
|
335
|
+
specific motion control behavior.
|
|
336
|
+
"""
|
|
337
|
+
pass
|
|
338
|
+
|
|
339
|
+
@abstractmethod
|
|
340
|
+
def stop_all_motion(self) -> None:
|
|
341
|
+
"""Stop all ongoing robot motion and reset motion commands.
|
|
342
|
+
|
|
343
|
+
This method should be implemented by derived classes to properly
|
|
344
|
+
handle motion stopping for their specific use case.
|
|
345
|
+
"""
|
|
346
|
+
pass
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
class DummyDualSenseTeleop(DualSenseTeleopBase):
|
|
350
|
+
"""Simple implementation of DualSenseTeleopBase for testing purposes."""
|
|
351
|
+
|
|
352
|
+
def update_motion(self) -> None:
|
|
353
|
+
"""Dummy implementation of update_motion."""
|
|
354
|
+
if self.active_buttons:
|
|
355
|
+
logger.debug(f"Active buttons: {self.active_buttons}")
|
|
356
|
+
pass
|
|
357
|
+
|
|
358
|
+
def stop_all_motion(self) -> None:
|
|
359
|
+
"""Dummy implementation of stop_all_motion."""
|
|
360
|
+
logger.info("Stopping all motion (dummy implementation)")
|
|
361
|
+
pass
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
def test_dummy_dualsense_teleop() -> None:
|
|
365
|
+
"""Test function for the DummyDualSenseTeleop class."""
|
|
366
|
+
teleop = DummyDualSenseTeleop()
|
|
367
|
+
teleop.run_forever()
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
if __name__ == "__main__":
|
|
371
|
+
test_dummy_dualsense_teleop()
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Copyright (c) 2025 Dexmate CORPORATION & AFFILIATES. All rights reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 with Commons Clause License
|
|
4
|
+
# Condition v1.0 [see LICENSE for details].
|
|
5
|
+
|
|
6
|
+
from hydra.core.config_store import ConfigStore
|
|
7
|
+
|
|
8
|
+
from .vega import Vega1Config, VegaConfig
|
|
9
|
+
|
|
10
|
+
# Register the configs
|
|
11
|
+
cs = ConfigStore.instance()
|
|
12
|
+
cs.store(name="vega", node=VegaConfig)
|
|
13
|
+
cs.store(name="vega_rc2", node=VegaConfig)
|
|
14
|
+
cs.store(name="vega_1", node=Vega1Config)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Copyright (c) 2025 Dexmate CORPORATION & AFFILIATES. All rights reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 with Commons Clause License
|
|
4
|
+
# Condition v1.0 [see LICENSE for details].
|
|
5
|
+
|
|
6
|
+
from .arm import ArmConfig
|
|
7
|
+
from .chassis import ChassisConfig
|
|
8
|
+
from .hand import HandConfig
|
|
9
|
+
from .head import HeadConfig
|
|
10
|
+
from .misc import BatteryConfig, EStopConfig, HeartbeatConfig
|
|
11
|
+
from .torso import TorsoConfig
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"ArmConfig",
|
|
15
|
+
"ChassisConfig",
|
|
16
|
+
"HandConfig",
|
|
17
|
+
"HeadConfig",
|
|
18
|
+
"BatteryConfig",
|
|
19
|
+
"EStopConfig",
|
|
20
|
+
"HeartbeatConfig",
|
|
21
|
+
"TorsoConfig",
|
|
22
|
+
]
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Copyright (c) 2025 Dexmate CORPORATION & AFFILIATES. All rights reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 with Commons Clause License
|
|
4
|
+
# Condition v1.0 [see LICENSE for details].
|
|
5
|
+
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class ArmConfig:
|
|
13
|
+
_target_: str = "dexcontrol.core.arm.Arm"
|
|
14
|
+
state_sub_topic: str = "state/arm/right"
|
|
15
|
+
wrench_sub_topic: str = "state/wrench/right"
|
|
16
|
+
control_pub_topic: str = "control/arm/right"
|
|
17
|
+
set_mode_query: str = "mode/arm/right"
|
|
18
|
+
dof: int = 7
|
|
19
|
+
joint_name: list[str] = field(
|
|
20
|
+
default_factory=lambda: [f"R_arm_j{i + 1}" for i in range(7)]
|
|
21
|
+
)
|
|
22
|
+
joint_limit: list[list[float]] = field(
|
|
23
|
+
default_factory=lambda: [[-np.pi, np.pi] for _ in range(7)]
|
|
24
|
+
)
|
|
25
|
+
pose_pool: dict[str, list[float]] = field(
|
|
26
|
+
default_factory=lambda: {
|
|
27
|
+
"folded": [-1.57079, 0.0, 0, -3.1, 0, 0, 0.69813],
|
|
28
|
+
"folded_closed_hand": [-1.57079, 0.0, 0, -3.1, 0, 0, 0.9],
|
|
29
|
+
"L_shape": [-0.064, 0.3, 0.0, -1.556, -1.271, 0.0, 0.0],
|
|
30
|
+
"zero": [1.57079, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
|
|
31
|
+
}
|
|
32
|
+
)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Copyright (c) 2025 Dexmate CORPORATION & AFFILIATES. All rights reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 with Commons Clause License
|
|
4
|
+
# Condition v1.0 [see LICENSE for details].
|
|
5
|
+
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class ChassisConfig:
|
|
11
|
+
_target_: str = "dexcontrol.core.chassis.Chassis"
|
|
12
|
+
control_pub_topic: str = "control/chassis"
|
|
13
|
+
state_sub_topic: str = "state/chassis"
|
|
14
|
+
dof: int = 2
|
|
15
|
+
center_to_wheel_axis_dist: float = (
|
|
16
|
+
0.219 # the distance between base center and wheel axis in m
|
|
17
|
+
)
|
|
18
|
+
wheels_dist: float = 0.41 # the distance between two wheels in m (0.41 for vega_rc2, 0.45 for vega_1)
|
|
19
|
+
joint_name: list[str] = field(
|
|
20
|
+
default_factory=lambda: ["L_wheel_j1", "L_wheel_j2", "R_wheel_j1", "R_wheel_j2"]
|
|
21
|
+
)
|
|
22
|
+
max_vel: float = 0.6
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Copyright (c) 2025 Dexmate CORPORATION & AFFILIATES. All rights reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 with Commons Clause License
|
|
4
|
+
# Condition v1.0 [see LICENSE for details].
|
|
5
|
+
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class HandConfig:
|
|
11
|
+
_target_: str = "dexcontrol.core.hand.HandF5D6"
|
|
12
|
+
state_sub_topic: str = "state/hand/right"
|
|
13
|
+
control_pub_topic: str = "control/hand/right"
|
|
14
|
+
dof: int = 6
|
|
15
|
+
joint_name: list[str] = field(
|
|
16
|
+
default_factory=lambda: [
|
|
17
|
+
"R_th_j1",
|
|
18
|
+
"R_ff_j1",
|
|
19
|
+
"R_mf_j1",
|
|
20
|
+
"R_rf_j1",
|
|
21
|
+
"R_lf_j1",
|
|
22
|
+
"R_th_j0",
|
|
23
|
+
]
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
# Not to modify the following varaibles unless you change a different hand
|
|
27
|
+
control_joint_names: list[str] = field(
|
|
28
|
+
default_factory=lambda: ["th_j1", "ff_j1", "mf_j1", "rf_j1", "lf_j1", "th_j0"]
|
|
29
|
+
)
|
|
30
|
+
joint_pos_open: list[float] = field(
|
|
31
|
+
default_factory=lambda: [0.18313, 0.29012, 0.28084, 0.28498, 0.28204, -0.034]
|
|
32
|
+
)
|
|
33
|
+
joint_pos_close: list[float] = field(
|
|
34
|
+
default_factory=lambda: [
|
|
35
|
+
-0.64862,
|
|
36
|
+
-1.17584,
|
|
37
|
+
-1.16855,
|
|
38
|
+
-1.17493,
|
|
39
|
+
-1.17277,
|
|
40
|
+
1.6,
|
|
41
|
+
]
|
|
42
|
+
)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Copyright (c) 2025 Dexmate CORPORATION & AFFILIATES. All rights reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 with Commons Clause License
|
|
4
|
+
# Condition v1.0 [see LICENSE for details].
|
|
5
|
+
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class HeadConfig:
|
|
11
|
+
_target_: str = "dexcontrol.core.head.Head"
|
|
12
|
+
state_sub_topic: str = "state/head"
|
|
13
|
+
control_pub_topic: str = "control/head"
|
|
14
|
+
set_mode_query: str = "mode/head"
|
|
15
|
+
joint_name: list[str] = field(
|
|
16
|
+
default_factory=lambda: ["head_j1", "head_j2", "head_j3"]
|
|
17
|
+
)
|
|
18
|
+
dof: int = 3
|
|
19
|
+
# Use the following varaibles if you are using vega-rc2
|
|
20
|
+
joint_limit_lower: list[float] = field(
|
|
21
|
+
default_factory=lambda: [-1.2217, -2.7925, -1.396]
|
|
22
|
+
)
|
|
23
|
+
joint_limit_upper: list[float] = field(
|
|
24
|
+
default_factory=lambda: [1.2217, 2.7925, 1.396]
|
|
25
|
+
)
|
|
26
|
+
default_vel: float = 1.57
|
|
27
|
+
max_vel: float = 2.0
|
|
28
|
+
pose_pool: dict[str, list[float]] = field(
|
|
29
|
+
default_factory=lambda: {
|
|
30
|
+
"home": [0.0, 0.0, 0.0],
|
|
31
|
+
"tucked": [0.0, 0.0, -1.37],
|
|
32
|
+
}
|
|
33
|
+
)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Copyright (c) 2025 Dexmate CORPORATION & AFFILIATES. All rights reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 with Commons Clause License
|
|
4
|
+
# Condition v1.0 [see LICENSE for details].
|
|
5
|
+
|
|
6
|
+
"""Miscellaneous robot component configurations."""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
|
|
11
|
+
from dexcontrol.utils.constants import DISABLE_HEARTBEAT_ENV_VAR
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class BatteryConfig:
|
|
16
|
+
_target_: str = "dexcontrol.core.misc.Battery"
|
|
17
|
+
state_sub_topic: str = "state/bms"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class EStopConfig:
|
|
22
|
+
_target_: str = "dexcontrol.core.misc.EStop"
|
|
23
|
+
state_sub_topic: str = "state/estop"
|
|
24
|
+
estop_query_name: str = "system/estop"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class HeartbeatConfig:
|
|
29
|
+
_target_: str = "dexcontrol.core.misc.Heartbeat"
|
|
30
|
+
heartbeat_topic: str = "/heartbeat"
|
|
31
|
+
timeout_seconds: float = 1.0
|
|
32
|
+
enabled: bool = True # Can be disabled via DEXCONTROL_DISABLE_HEARTBEAT=1
|
|
33
|
+
|
|
34
|
+
def __post_init__(self):
|
|
35
|
+
"""Check environment variable to disable heartbeat monitoring."""
|
|
36
|
+
if os.getenv(DISABLE_HEARTBEAT_ENV_VAR, "0").lower() in ("1", "true", "yes"):
|
|
37
|
+
self.enabled = False
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Copyright (c) 2025 Dexmate CORPORATION & AFFILIATES. All rights reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 with Commons Clause License
|
|
4
|
+
# Condition v1.0 [see LICENSE for details].
|
|
5
|
+
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class TorsoConfig:
|
|
11
|
+
_target_: str = "dexcontrol.core.torso.Torso"
|
|
12
|
+
state_sub_topic: str = "state/torso"
|
|
13
|
+
control_pub_topic: str = "control/torso"
|
|
14
|
+
dof: int = 3
|
|
15
|
+
joint_name: list[str] = field(
|
|
16
|
+
default_factory=lambda: ["torso_j1", "torso_j2", "torso_j3"]
|
|
17
|
+
)
|
|
18
|
+
default_vel: float = 0.4 # rad/s, will be used if joint_vel is not provided
|
|
19
|
+
max_vel: float = 0.6 # max velocity of torso joint, will be used to clip
|
|
20
|
+
# the joint_vel, Highly recommended to set this value
|
|
21
|
+
# not higher than 0.6 rad/s
|
|
22
|
+
pose_pool: dict[str, list[float]] = field(
|
|
23
|
+
default_factory=lambda: {
|
|
24
|
+
"home": [0.0, 0.0, 0.0],
|
|
25
|
+
"folded": [0.0, 0.0, -1.5708],
|
|
26
|
+
"crouch20_low": [0.0, 0.0, -0.35],
|
|
27
|
+
"crouch20_medium": [0.52, 1.05, 0.18],
|
|
28
|
+
"crouch20_high": [0.78, 1.57, 0.44],
|
|
29
|
+
"crouch45_low": [0.0, 0.0, -0.79],
|
|
30
|
+
"crouch45_medium": [0.52, 1.05, -0.26],
|
|
31
|
+
"crouch45_high": [0.78, 1.57, 0],
|
|
32
|
+
"crouch90_low": [0.0, 0.0, -1.57],
|
|
33
|
+
"crouch90_medium": [0.52, 1.05, -1.04],
|
|
34
|
+
"crouch90_high": [0.78, 1.57, -0.78],
|
|
35
|
+
}
|
|
36
|
+
)
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Copyright (c) 2025 Dexmate CORPORATION & AFFILIATES. All rights reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 with Commons Clause License
|
|
4
|
+
# Condition v1.0 [see LICENSE for details].
|
|
5
|
+
|
|
6
|
+
from .gemini_camera import GeminiCameraConfig
|
|
7
|
+
from .rgb_camera import RGBCameraConfig
|