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
@@ -0,0 +1,170 @@
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
+ """Robot hand control module.
7
+
8
+ This module provides the Hand class for controlling a robotic hand through Zenoh
9
+ communication. It handles joint position control and state monitoring.
10
+ """
11
+
12
+ import numpy as np
13
+ import zenoh
14
+ from jaxtyping import Float
15
+
16
+ from dexcontrol.config.core.hand import HandConfig
17
+ from dexcontrol.core.component import RobotJointComponent
18
+ from dexcontrol.proto import dexcontrol_msg_pb2
19
+
20
+
21
+ class Hand(RobotJointComponent):
22
+ """Robot hand control class.
23
+
24
+ This class provides methods to control a robotic hand by publishing commands and
25
+ receiving state information through Zenoh communication.
26
+ """
27
+
28
+ def __init__(
29
+ self,
30
+ configs: HandConfig,
31
+ zenoh_session: zenoh.Session,
32
+ ) -> None:
33
+ """Initialize the hand controller.
34
+
35
+ Args:
36
+ configs: Hand configuration parameters containing communication topics
37
+ and predefined hand positions.
38
+ zenoh_session: Active Zenoh communication session for message passing.
39
+ """
40
+ super().__init__(
41
+ state_sub_topic=configs.state_sub_topic,
42
+ control_pub_topic=configs.control_pub_topic,
43
+ state_message_type=dexcontrol_msg_pb2.HandState,
44
+ zenoh_session=zenoh_session,
45
+ joint_name=configs.joint_name,
46
+ )
47
+
48
+ # Store predefined hand positions as private attributes
49
+ self._joint_pos_open = np.array(configs.joint_pos_open, dtype=np.float32)
50
+ self._joint_pos_close = np.array(configs.joint_pos_close, dtype=np.float32)
51
+
52
+ def _send_position_command(
53
+ self, joint_pos: Float[np.ndarray, " N"] | list[float]
54
+ ) -> None:
55
+ """Send joint position control commands to the hand.
56
+
57
+ Args:
58
+ joint_pos: Joint positions as list or numpy array.
59
+ """
60
+ control_msg = dexcontrol_msg_pb2.HandCommand()
61
+ joint_pos_array = self._convert_joint_cmd_to_array(joint_pos)
62
+ control_msg.joint_pos.extend(joint_pos_array.tolist())
63
+ self._publish_control(control_msg)
64
+
65
+ def set_joint_pos(
66
+ self,
67
+ joint_pos: Float[np.ndarray, " N"] | list[float] | dict[str, float],
68
+ relative: bool = False,
69
+ wait_time: float = 0.0,
70
+ wait_kwargs: dict[str, float] | None = None,
71
+ ) -> None:
72
+ """Send joint position control commands to the hand.
73
+
74
+ Args:
75
+ joint_pos: Joint positions as either:
76
+ - List of joint values [j1, j2, ..., jN]
77
+ - Numpy array with shape (N,)
78
+ - Dictionary mapping joint names to position values
79
+ relative: If True, the joint positions are relative to the current position.
80
+ wait_time: Time to wait after setting the joint positions.
81
+ wait_kwargs: Optional parameters for trajectory generation (not used in Hand).
82
+
83
+ Raises:
84
+ ValueError: If joint_pos dictionary contains invalid joint names.
85
+ """
86
+ super().set_joint_pos(joint_pos, relative, wait_time, wait_kwargs)
87
+
88
+ def open_hand(self, wait_time: float = 0.0) -> None:
89
+ """Open the hand to the predefined open position.
90
+
91
+ Args:
92
+ wait_time: Time to wait after opening the hand.
93
+ """
94
+ self.set_joint_pos(self._joint_pos_open, wait_time=wait_time)
95
+
96
+ def close_hand(self, wait_time: float = 0.0) -> None:
97
+ """Close the hand to the predefined closed position.
98
+
99
+ Args:
100
+ wait_time: Time to wait after closing the hand.
101
+ """
102
+ self.set_joint_pos(self._joint_pos_close, wait_time=wait_time)
103
+
104
+
105
+ class HandF5D6(Hand):
106
+ """Specialized hand class for the F5D6 hand model.
107
+
108
+ Extends the basic Hand class with additional control methods specific to
109
+ the F5D6 hand model.
110
+ """
111
+
112
+ def close_hand(self, wait_time: float = 0.0) -> None:
113
+ """Close the hand fully using a two-step approach for better control."""
114
+ if self.is_joint_pos_reached(self._joint_pos_close, tolerance=0.1):
115
+ return
116
+
117
+ # First step: Move to intermediate position
118
+ intermediate_pos = self._get_intermediate_close_position()
119
+ first_step_wait_time = 0.8
120
+ self.set_joint_pos(intermediate_pos, wait_time=first_step_wait_time)
121
+
122
+ # Second step: Move to final closed position
123
+ remaining_wait_time = max(0.0, wait_time - first_step_wait_time)
124
+ self.set_joint_pos(self._joint_pos_close, wait_time=remaining_wait_time)
125
+
126
+ def open_hand(self, wait_time: float = 0.0) -> None:
127
+ """Open the hand fully using a two-step approach for better control."""
128
+ if self.is_joint_pos_reached(self._joint_pos_open, tolerance=0.1):
129
+ return
130
+
131
+ # First step: Move to intermediate position
132
+ intermediate_pos = self._get_intermediate_open_position()
133
+ first_step_wait_time = 0.3
134
+ self.set_joint_pos(intermediate_pos, wait_time=first_step_wait_time)
135
+
136
+ # Second step: Move to final open position
137
+ remaining_wait_time = max(0.0, wait_time - first_step_wait_time)
138
+ self.set_joint_pos(self._joint_pos_open, wait_time=remaining_wait_time)
139
+
140
+ def _get_intermediate_close_position(self) -> np.ndarray:
141
+ """Get intermediate position for closing hand.
142
+
143
+ Returns:
144
+ Intermediate joint positions for smooth closing motion.
145
+ """
146
+ intermediate_pos = self._joint_pos_close.copy()
147
+ ratio = 0.2
148
+ # Adjust thumb opposition joint (last joint)
149
+ intermediate_pos[-1] = self._joint_pos_close[-1] * ratio + self._joint_pos_open[
150
+ -1
151
+ ] * (1 - ratio)
152
+ return intermediate_pos
153
+
154
+ def _get_intermediate_open_position(self) -> np.ndarray:
155
+ """Get intermediate position for opening hand.
156
+
157
+ Returns:
158
+ Intermediate joint positions for smooth opening motion.
159
+ """
160
+ intermediate_pos = self._joint_pos_close.copy()
161
+ ratio = 0.2
162
+ # Adjust thumb opposition joint (last joint)
163
+ intermediate_pos[-1] = self._joint_pos_close[-1] * ratio + self._joint_pos_open[
164
+ -1
165
+ ] * (1 - ratio)
166
+ # Adjust thumb flexion joint (first joint)
167
+ intermediate_pos[0] = self._joint_pos_close[0] * ratio + self._joint_pos_open[
168
+ 0
169
+ ] * (1 - ratio)
170
+ return intermediate_pos
@@ -0,0 +1,232 @@
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
+ """Robot head control module.
7
+
8
+ This module provides the Head class for controlling a robot head through Zenoh
9
+ communication. It handles joint position and velocity control, mode setting, and
10
+ state monitoring.
11
+ """
12
+
13
+ import time
14
+ from typing import Final, Literal
15
+
16
+ import numpy as np
17
+ import zenoh
18
+ from jaxtyping import Float
19
+ from loguru import logger
20
+
21
+ from dexcontrol.config.core import HeadConfig
22
+ from dexcontrol.core.component import RobotJointComponent
23
+ from dexcontrol.proto import dexcontrol_msg_pb2, dexcontrol_query_pb2
24
+ from dexcontrol.utils.os_utils import resolve_key_name
25
+
26
+
27
+ class Head(RobotJointComponent):
28
+ """Robot head control class.
29
+
30
+ This class provides methods to control a robot head by publishing commands and
31
+ receiving state information through Zenoh communication.
32
+
33
+ Attributes:
34
+ mode_querier: Zenoh querier for setting head mode.
35
+ default_vel: Default joint velocities for all joints.
36
+ max_vel: Maximum allowed joint velocities for all joints.
37
+ """
38
+
39
+ def __init__(
40
+ self,
41
+ configs: HeadConfig,
42
+ zenoh_session: zenoh.Session,
43
+ ) -> None:
44
+ """Initialize the head controller.
45
+
46
+ Args:
47
+ configs: Configuration parameters for the head including communication topics.
48
+ zenoh_session: Active Zenoh communication session for message passing.
49
+ """
50
+ super().__init__(
51
+ state_sub_topic=configs.state_sub_topic,
52
+ control_pub_topic=configs.control_pub_topic,
53
+ state_message_type=dexcontrol_msg_pb2.HeadState,
54
+ zenoh_session=zenoh_session,
55
+ joint_name=configs.joint_name,
56
+ pose_pool=configs.pose_pool,
57
+ )
58
+
59
+ self.mode_querier: Final[zenoh.Querier] = zenoh_session.declare_querier(
60
+ resolve_key_name(configs.set_mode_query), timeout=2.0
61
+ )
62
+ self.default_vel: Final[float] = configs.default_vel
63
+ self.max_vel: Final[float] = configs.max_vel
64
+ assert self.max_vel > self.default_vel, (
65
+ "max_vel must be greater than default_vel"
66
+ )
67
+ self._joint_limit: Float[np.ndarray, "3 2"] = np.stack(
68
+ [configs.joint_limit_lower, configs.joint_limit_upper], axis=1
69
+ )
70
+
71
+ def set_joint_pos(
72
+ self,
73
+ joint_pos: Float[np.ndarray, "2"] | list[float] | dict[str, float],
74
+ relative: bool = False,
75
+ wait_time: float = 0.0,
76
+ wait_kwargs: dict[str, float] | None = None,
77
+ ) -> None:
78
+ """Send joint position control commands to the head.
79
+
80
+ Args:
81
+ joint_pos: Joint positions as either:
82
+ - List of joint values [j1, j2]
83
+ - Numpy array with shape (2,), in radians
84
+ - Dictionary mapping joint names to position values
85
+ relative: If True, the joint positions are relative to the current position.
86
+ wait_time: Time to wait after sending command in seconds. If 0, returns
87
+ immediately after sending command.
88
+ wait_kwargs: Optional parameters for trajectory generation (not used in Head).
89
+
90
+ Raises:
91
+ ValueError: If joint_pos dictionary contains invalid joint names.
92
+ """
93
+ self.set_joint_pos_vel(
94
+ joint_pos, joint_vel=None, relative=relative, wait_time=wait_time
95
+ )
96
+
97
+ def set_joint_pos_vel(
98
+ self,
99
+ joint_pos: Float[np.ndarray, "2"] | list[float] | dict[str, float],
100
+ joint_vel: Float[np.ndarray, "2"]
101
+ | list[float]
102
+ | dict[str, float]
103
+ | float
104
+ | None = None,
105
+ relative: bool = False,
106
+ wait_time: float = 0.0,
107
+ ) -> None:
108
+ """Send control commands to the head.
109
+
110
+ Args:
111
+ joint_pos: Joint positions as either:
112
+ - List of joint values [j1, j2]
113
+ - Numpy array with shape (2,), in radians
114
+ - Dictionary mapping joint names to position values
115
+ joint_vel: Optional joint velocities as either:
116
+ - List of joint values [v1, v2]
117
+ - Numpy array with shape (2,), in rad/s
118
+ - Dictionary mapping joint names to velocity values
119
+ - Single float value to be applied to all joints
120
+ If None, velocities are calculated based on default velocity setting.
121
+ relative: If True, the joint positions are relative to the current position.
122
+ wait_time: Time to wait after sending command in seconds. If 0, returns
123
+ immediately after sending command.
124
+
125
+ Raises:
126
+ ValueError: If wait_time is negative or joint_pos dictionary contains
127
+ invalid joint names.
128
+ """
129
+ if wait_time < 0.0:
130
+ raise ValueError("wait_time must be greater than or equal to 0")
131
+
132
+ # Handle relative positioning
133
+ if relative:
134
+ joint_pos = self._resolve_relative_joint_cmd(joint_pos)
135
+
136
+ # Convert inputs to numpy arrays
137
+ joint_pos = self._convert_joint_cmd_to_array(joint_pos)
138
+ joint_vel = self._process_joint_velocities(joint_vel, joint_pos)
139
+
140
+ # Create and send control message
141
+ control_msg = dexcontrol_msg_pb2.HeadCommand()
142
+ control_msg.joint_pos.extend(joint_pos.tolist())
143
+ control_msg.joint_vel.extend(joint_vel.tolist())
144
+ self._publish_control(control_msg)
145
+
146
+ # Wait if specified
147
+ if wait_time > 0.0:
148
+ time.sleep(wait_time)
149
+
150
+ def set_mode(self, mode: Literal["enable", "disable"]) -> None:
151
+ """Set the operating mode of the head.
152
+
153
+ Args:
154
+ mode: Operating mode for the head. Must be either "enable" or "disable".
155
+
156
+ Raises:
157
+ ValueError: If an invalid mode is specified.
158
+ """
159
+ mode_map = {
160
+ "enable": dexcontrol_query_pb2.SetHeadMode.Mode.ENABLE,
161
+ "disable": dexcontrol_query_pb2.SetHeadMode.Mode.DISABLE,
162
+ }
163
+
164
+ if mode not in mode_map:
165
+ raise ValueError(
166
+ f"Invalid mode: {mode}. Must be one of {list(mode_map.keys())}"
167
+ )
168
+
169
+ query_msg = dexcontrol_query_pb2.SetHeadMode()
170
+ query_msg.mode = mode_map[mode]
171
+ replies = self.mode_querier.get(payload=query_msg.SerializeToString())
172
+
173
+ for reply in replies:
174
+ if reply.ok is not None and reply.ok.payload is not None:
175
+ logger.info(reply.ok.payload.to_string())
176
+ time.sleep(0.5)
177
+
178
+ def get_joint_limit(self) -> Float[np.ndarray, "3 2"]:
179
+ """Get the joint limits of the head.
180
+
181
+ Returns:
182
+ Array of joint limits with shape (3, 2), where the first column contains
183
+ lower limits and the second column contains upper limits.
184
+ """
185
+ return self._joint_limit
186
+
187
+ def stop(self) -> None:
188
+ """Stop the head by setting target position to current position with zero velocity."""
189
+ current_pos = self.get_joint_pos()
190
+ zero_vel = np.zeros(3, dtype=np.float32)
191
+ self.set_joint_pos_vel(current_pos, zero_vel, relative=False, wait_time=0.0)
192
+
193
+ def shutdown(self) -> None:
194
+ """Clean up Zenoh resources for the head component."""
195
+ self.stop()
196
+ super().shutdown()
197
+
198
+ def _process_joint_velocities(
199
+ self,
200
+ joint_vel: Float[np.ndarray, "2"]
201
+ | list[float]
202
+ | dict[str, float]
203
+ | float
204
+ | None,
205
+ joint_pos: np.ndarray,
206
+ ) -> np.ndarray:
207
+ """Process and validate joint velocities.
208
+
209
+ Args:
210
+ joint_vel: Joint velocities in various formats or None.
211
+ joint_pos: Target joint positions for velocity calculation.
212
+
213
+ Returns:
214
+ Processed joint velocities as numpy array.
215
+ """
216
+ if joint_vel is None:
217
+ # Calculate velocities based on motion direction and default velocity
218
+ joint_motion = joint_pos - self.get_joint_pos()
219
+ motion_norm = np.linalg.norm(joint_motion)
220
+
221
+ if motion_norm < 1e-6: # Avoid division by zero
222
+ return np.zeros(2, dtype=np.float32)
223
+
224
+ # Scale velocities by default velocity
225
+ return (joint_motion / motion_norm) * self.default_vel
226
+
227
+ if isinstance(joint_vel, (int, float)):
228
+ # Single value - apply to all joints
229
+ return np.full(2, joint_vel, dtype=np.float32)
230
+
231
+ # Convert to array and clip to max velocity
232
+ return self._convert_joint_cmd_to_array(joint_vel, clip_value=self.max_vel)