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.

Files changed (72) hide show
  1. dexcontrol/__init__.py +45 -0
  2. dexcontrol/apps/dualsense_teleop_base.py +371 -0
  3. dexcontrol/config/__init__.py +14 -0
  4. dexcontrol/config/core/__init__.py +22 -0
  5. dexcontrol/config/core/arm.py +32 -0
  6. dexcontrol/config/core/chassis.py +22 -0
  7. dexcontrol/config/core/hand.py +42 -0
  8. dexcontrol/config/core/head.py +33 -0
  9. dexcontrol/config/core/misc.py +37 -0
  10. dexcontrol/config/core/torso.py +36 -0
  11. dexcontrol/config/sensors/__init__.py +4 -0
  12. dexcontrol/config/sensors/cameras/__init__.py +7 -0
  13. dexcontrol/config/sensors/cameras/gemini_camera.py +16 -0
  14. dexcontrol/config/sensors/cameras/rgb_camera.py +15 -0
  15. dexcontrol/config/sensors/imu/__init__.py +6 -0
  16. dexcontrol/config/sensors/imu/gemini_imu.py +15 -0
  17. dexcontrol/config/sensors/imu/nine_axis_imu.py +15 -0
  18. dexcontrol/config/sensors/lidar/__init__.py +6 -0
  19. dexcontrol/config/sensors/lidar/rplidar.py +15 -0
  20. dexcontrol/config/sensors/ultrasonic/__init__.py +6 -0
  21. dexcontrol/config/sensors/ultrasonic/ultrasonic.py +15 -0
  22. dexcontrol/config/sensors/vega_sensors.py +65 -0
  23. dexcontrol/config/vega.py +203 -0
  24. dexcontrol/core/__init__.py +0 -0
  25. dexcontrol/core/arm.py +324 -0
  26. dexcontrol/core/chassis.py +628 -0
  27. dexcontrol/core/component.py +834 -0
  28. dexcontrol/core/hand.py +170 -0
  29. dexcontrol/core/head.py +232 -0
  30. dexcontrol/core/misc.py +514 -0
  31. dexcontrol/core/torso.py +198 -0
  32. dexcontrol/proto/dexcontrol_msg_pb2.py +69 -0
  33. dexcontrol/proto/dexcontrol_msg_pb2.pyi +168 -0
  34. dexcontrol/proto/dexcontrol_query_pb2.py +73 -0
  35. dexcontrol/proto/dexcontrol_query_pb2.pyi +134 -0
  36. dexcontrol/robot.py +1091 -0
  37. dexcontrol/sensors/__init__.py +40 -0
  38. dexcontrol/sensors/camera/__init__.py +18 -0
  39. dexcontrol/sensors/camera/gemini_camera.py +139 -0
  40. dexcontrol/sensors/camera/rgb_camera.py +98 -0
  41. dexcontrol/sensors/imu/__init__.py +22 -0
  42. dexcontrol/sensors/imu/gemini_imu.py +139 -0
  43. dexcontrol/sensors/imu/nine_axis_imu.py +149 -0
  44. dexcontrol/sensors/lidar/__init__.py +3 -0
  45. dexcontrol/sensors/lidar/rplidar.py +164 -0
  46. dexcontrol/sensors/manager.py +185 -0
  47. dexcontrol/sensors/ultrasonic.py +110 -0
  48. dexcontrol/utils/__init__.py +15 -0
  49. dexcontrol/utils/constants.py +12 -0
  50. dexcontrol/utils/io_utils.py +26 -0
  51. dexcontrol/utils/motion_utils.py +194 -0
  52. dexcontrol/utils/os_utils.py +39 -0
  53. dexcontrol/utils/pb_utils.py +103 -0
  54. dexcontrol/utils/rate_limiter.py +167 -0
  55. dexcontrol/utils/reset_orbbec_camera_usb.py +98 -0
  56. dexcontrol/utils/subscribers/__init__.py +44 -0
  57. dexcontrol/utils/subscribers/base.py +260 -0
  58. dexcontrol/utils/subscribers/camera.py +328 -0
  59. dexcontrol/utils/subscribers/decoders.py +83 -0
  60. dexcontrol/utils/subscribers/generic.py +105 -0
  61. dexcontrol/utils/subscribers/imu.py +170 -0
  62. dexcontrol/utils/subscribers/lidar.py +195 -0
  63. dexcontrol/utils/subscribers/protobuf.py +106 -0
  64. dexcontrol/utils/timer.py +136 -0
  65. dexcontrol/utils/trajectory_utils.py +40 -0
  66. dexcontrol/utils/viz_utils.py +86 -0
  67. dexcontrol-0.2.1.dist-info/METADATA +369 -0
  68. dexcontrol-0.2.1.dist-info/RECORD +72 -0
  69. dexcontrol-0.2.1.dist-info/WHEEL +5 -0
  70. dexcontrol-0.2.1.dist-info/licenses/LICENSE +188 -0
  71. dexcontrol-0.2.1.dist-info/licenses/NOTICE +13 -0
  72. 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,4 @@
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].
@@ -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