dexcontrol 0.2.4__py3-none-any.whl → 0.2.10__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.

@@ -21,6 +21,8 @@ class ArmConfig:
21
21
  control_pub_topic: str = "control/arm/right"
22
22
  set_mode_query: str = "mode/arm/right"
23
23
  dof: int = 7
24
+ default_max_vel: float = 2.0 # do not go over 3.0
25
+ default_control_hz: int = 100
24
26
  joint_name: list[str] = field(
25
27
  default_factory=lambda: [f"R_arm_j{i + 1}" for i in range(7)]
26
28
  )
@@ -28,20 +28,9 @@ class HandConfig:
28
28
  ]
29
29
  )
30
30
 
31
- # Not to modify the following varaibles unless you change a different hand
32
- control_joint_names: list[str] = field(
33
- default_factory=lambda: ["th_j1", "ff_j1", "mf_j1", "rf_j1", "lf_j1", "th_j0"]
34
- )
35
- joint_pos_open: list[float] = field(
36
- default_factory=lambda: [0.1834, 0.2891, 0.2801, 0.284, 0.2811, -0.0158]
37
- )
38
- joint_pos_close: list[float] = field(
39
- default_factory=lambda: [
40
- -0.3468,
41
- -1.0946,
42
- -1.0844,
43
- -1.0154,
44
- -1.0118,
45
- 1.6,
46
- ]
31
+ pose_pool: dict[str, list[float]] = field(
32
+ default_factory=lambda: {
33
+ "open": [0.1834, 0.2891, 0.2801, 0.284, 0.2811, -0.0158],
34
+ "close": [-0.3468, -1.0946, -1.0844, -1.0154, -1.0118, 1.6],
35
+ }
47
36
  )
@@ -10,3 +10,5 @@
10
10
 
11
11
  from .rgb_camera import RGBCameraConfig
12
12
  from .zed_camera import ZedCameraConfig
13
+
14
+ __all__ = ["RGBCameraConfig", "ZedCameraConfig"]
@@ -13,16 +13,33 @@ from dataclasses import dataclass, field
13
13
 
14
14
  @dataclass
15
15
  class RGBCameraConfig:
16
+ """Configuration for the RGBCameraSensor.
17
+
18
+ Attributes:
19
+ _target_: The target class to instantiate.
20
+ name: A unique name for the sensor instance.
21
+ enable: Whether the sensor is enabled.
22
+ use_rtc: If True, uses the high-performance RTCSubscriber. If False,
23
+ uses the standard RGBCameraSubscriber with a Zenoh topic.
24
+ enable_fps_tracking: If True, tracks and logs the FPS of the stream.
25
+ fps_log_interval: The interval in seconds for logging FPS.
26
+ subscriber_config: A dictionary containing the configuration for the
27
+ underlying subscriber (either RTC or standard).
28
+ """
29
+
16
30
  _target_: str = "dexcontrol.sensors.camera.rgb_camera.RGBCameraSensor"
17
31
  name: str = "rgb_camera"
32
+ enable: bool = False
33
+ use_rtc: bool = True
18
34
  enable_fps_tracking: bool = False
19
35
  fps_log_interval: int = 30
20
- enable: bool = False
36
+
21
37
  subscriber_config: dict = field(
22
- default_factory=lambda: dict(
23
- rgb=dict(
24
- enable=True,
25
- info_key="camera/rgb/info",
26
- )
27
- )
38
+ default_factory=lambda: {
39
+ "rgb": {
40
+ "enable": True,
41
+ "info_key": "camera/rgb/info",
42
+ "topic": "camera/rgb",
43
+ }
44
+ }
28
45
  )
@@ -13,24 +13,43 @@ from dataclasses import dataclass, field
13
13
 
14
14
  @dataclass
15
15
  class ZedCameraConfig:
16
+ """Configuration for the ZedCameraSensor.
17
+
18
+ Attributes:
19
+ _target_: The target class to instantiate.
20
+ name: A unique name for the sensor instance.
21
+ enable: Whether the sensor is enabled.
22
+ use_rtc: If True, RGB streams use the high-performance RTCSubscriber.
23
+ If False, they use the standard RGBCameraSubscriber. Depth
24
+ always uses a standard Zenoh subscriber.
25
+ enable_fps_tracking: If True, tracks and logs the FPS of all streams.
26
+ fps_log_interval: The interval in seconds for logging FPS.
27
+ subscriber_config: A dictionary containing the configurations for each
28
+ individual stream (left_rgb, right_rgb, depth).
29
+ """
30
+
16
31
  _target_: str = "dexcontrol.sensors.camera.zed_camera.ZedCameraSensor"
17
32
  name: str = "zed_camera"
33
+ enable: bool = False
34
+ use_rtc: bool = True
18
35
  enable_fps_tracking: bool = False
19
36
  fps_log_interval: int = 30
20
- enable: bool = False
37
+
21
38
  subscriber_config: dict = field(
22
- default_factory=lambda: dict(
23
- left_rgb=dict(
24
- enable=True,
25
- info_key="camera/head/left_rgb/info",
26
- ),
27
- right_rgb=dict(
28
- enable=True,
29
- info_key="camera/head/right_rgb/info",
30
- ),
31
- depth=dict(
32
- enable=True,
33
- topic="camera/head/depth",
34
- ),
35
- )
39
+ default_factory=lambda: {
40
+ "left_rgb": {
41
+ "enable": True,
42
+ "info_key": "camera/head/left_rgb/info",
43
+ "topic": "camera/head/left_rgb",
44
+ },
45
+ "right_rgb": {
46
+ "enable": True,
47
+ "info_key": "camera/head/right_rgb/info",
48
+ "topic": "camera/head/right_rgb",
49
+ },
50
+ "depth": {
51
+ "enable": True,
52
+ "topic": "camera/head/depth",
53
+ },
54
+ }
36
55
  )
@@ -38,6 +38,7 @@ def _make_rgb_camera(name: str) -> Callable[[], RGBCameraConfig]:
38
38
  rgb=dict(
39
39
  enable=True,
40
40
  info_key=f"camera/base/{name}/rgb/info",
41
+ topic=f"camera/base/{name}/rgb",
41
42
  )
42
43
  ),
43
44
  name=f"base_{name}_camera",
dexcontrol/config/vega.py CHANGED
@@ -167,6 +167,12 @@ class Vega1Config(VegaConfig):
167
167
  wheels_dist=0.45,
168
168
  )
169
169
  )
170
+ head: HeadConfig = field(
171
+ default_factory=lambda: HeadConfig(
172
+ joint_limit_lower=[-1.483, -2.792, -1.378],
173
+ joint_limit_upper=[1.483, 2.792, 1.483],
174
+ )
175
+ )
170
176
 
171
177
 
172
178
  # Register variant configurations
dexcontrol/core/arm.py CHANGED
@@ -15,7 +15,7 @@ communication and the ArmWrenchSensor class for reading wrench sensor data.
15
15
  """
16
16
 
17
17
  import time
18
- from typing import Final, Literal
18
+ from typing import Any, Final, Literal
19
19
 
20
20
  import numpy as np
21
21
  import zenoh
@@ -72,6 +72,15 @@ class Arm(RobotJointComponent):
72
72
  configs.wrench_sub_topic, zenoh_session
73
73
  )
74
74
 
75
+ self._default_max_vel = configs.default_max_vel
76
+ self._default_control_hz = configs.default_control_hz
77
+ if self._default_max_vel > 3.0:
78
+ logger.warning(
79
+ f"Max velocity is set to {self._default_max_vel}, which is greater than 3.0. This is not recommended."
80
+ )
81
+ self._default_max_vel = 3.0
82
+ logger.warning("Max velocity is clamped to 3.0")
83
+
75
84
  def set_mode(self, mode: Literal["position", "disable"]) -> None:
76
85
  """Sets the operating mode of the arm.
77
86
 
@@ -112,6 +121,8 @@ class Arm(RobotJointComponent):
112
121
  relative: bool = False,
113
122
  wait_time: float = 0.0,
114
123
  wait_kwargs: dict[str, float] | None = None,
124
+ exit_on_reach: bool = False,
125
+ exit_on_reach_kwargs: dict[str, Any] | None = None,
115
126
  ) -> None:
116
127
  """Controls the arm in joint position mode.
117
128
 
@@ -130,7 +141,8 @@ class Arm(RobotJointComponent):
130
141
  wait_time > 0). Supported keys:
131
142
  - control_hz: Control frequency in Hz (default: 100).
132
143
  - max_vel: Maximum velocity in rad/s (default: 0.5).
133
-
144
+ exit_on_reach: If True, the function will exit when the joint positions are reached.
145
+ exit_on_reach_kwargs: Optional parameters for exit when the joint positions are reached.
134
146
  Raises:
135
147
  ValueError: If joint_pos dictionary contains invalid joint names.
136
148
  """
@@ -142,7 +154,13 @@ class Arm(RobotJointComponent):
142
154
  )
143
155
 
144
156
  if wait_time > 0.0:
145
- self._execute_trajectory_motion(resolved_joint_pos, wait_time, wait_kwargs)
157
+ self._execute_trajectory_motion(
158
+ resolved_joint_pos,
159
+ wait_time,
160
+ wait_kwargs,
161
+ exit_on_reach,
162
+ exit_on_reach_kwargs,
163
+ )
146
164
  else:
147
165
  # Convert to array format
148
166
  if isinstance(resolved_joint_pos, (list, dict)):
@@ -156,6 +174,8 @@ class Arm(RobotJointComponent):
156
174
  joint_pos: Float[np.ndarray, " N"] | list[float] | dict[str, float],
157
175
  wait_time: float,
158
176
  wait_kwargs: dict[str, float],
177
+ exit_on_reach: bool = False,
178
+ exit_on_reach_kwargs: dict[str, Any] | None = None,
159
179
  ) -> None:
160
180
  """Execute trajectory-based motion to target position.
161
181
 
@@ -163,10 +183,14 @@ class Arm(RobotJointComponent):
163
183
  joint_pos: Target joint positions as list, numpy array, or dictionary.
164
184
  wait_time: Total time for the motion.
165
185
  wait_kwargs: Parameters for trajectory generation.
186
+ exit_on_reach: If True, the function will exit when the joint positions are reached.
187
+ exit_on_reach_kwargs: Optional parameters for exit when the joint positions are reached.
166
188
  """
167
189
  # Set default parameters
168
- control_hz = wait_kwargs.get("control_hz", 100)
169
- max_vel = wait_kwargs.get("max_vel", 0.5)
190
+ control_hz = wait_kwargs.get("control_hz", self._default_control_hz)
191
+ max_vel = wait_kwargs.get("max_vel", self._default_max_vel)
192
+ exit_on_reach_kwargs = exit_on_reach_kwargs or {}
193
+ exit_on_reach_kwargs.setdefault("tolerance", 0.05)
170
194
 
171
195
  # Create rate limiter and get current position
172
196
  rate_limiter = RateLimiter(control_hz)
@@ -186,7 +210,6 @@ class Arm(RobotJointComponent):
186
210
  trajectory, _ = generate_linear_trajectory(
187
211
  current_joint_pos, target_pos, max_vel, control_hz
188
212
  )
189
-
190
213
  # Execute trajectory with time limit
191
214
  start_time = time.time()
192
215
  for pos in trajectory:
@@ -199,6 +222,10 @@ class Arm(RobotJointComponent):
199
222
  while time.time() - start_time < wait_time:
200
223
  self._send_position_command(target_pos)
201
224
  rate_limiter.sleep()
225
+ if exit_on_reach and self.is_joint_pos_reached(
226
+ target_pos, **exit_on_reach_kwargs
227
+ ):
228
+ break
202
229
 
203
230
  def set_joint_pos_vel(
204
231
  self,
@@ -634,6 +634,8 @@ class RobotJointComponent(RobotComponent):
634
634
  relative: bool = False,
635
635
  wait_time: float = 0.0,
636
636
  wait_kwargs: dict[str, float] | None = None,
637
+ exit_on_reach: bool = False,
638
+ exit_on_reach_kwargs: dict[str, Any] | None = None,
637
639
  ) -> None:
638
640
  """Send joint position control commands.
639
641
 
@@ -645,6 +647,8 @@ class RobotJointComponent(RobotComponent):
645
647
  relative: If True, the joint positions are relative to the current position.
646
648
  wait_time: Time to wait after sending command in seconds.
647
649
  wait_kwargs: Optional parameters for trajectory generation.
650
+ exit_on_reach: If True, the function will exit when the joint positions are reached.
651
+ exit_on_reach_kwargs: Optional parameters for exit when the joint positions are reached.
648
652
 
649
653
  Raises:
650
654
  ValueError: If joint_pos dictionary contains invalid joint names.
@@ -659,6 +663,41 @@ class RobotJointComponent(RobotComponent):
659
663
  self._send_position_command(joint_pos)
660
664
 
661
665
  if wait_time > 0.0:
666
+ self._wait_for_position(
667
+ joint_pos, wait_time, exit_on_reach, exit_on_reach_kwargs
668
+ )
669
+
670
+ def _wait_for_position(
671
+ self,
672
+ joint_pos: Float[np.ndarray, " N"] | list[float] | dict[str, float],
673
+ wait_time: float,
674
+ exit_on_reach: bool = False,
675
+ exit_on_reach_kwargs: dict[str, Any] | None = None,
676
+ ) -> None:
677
+ """Wait for a specified time with optional early exit when position is reached.
678
+
679
+ Args:
680
+ joint_pos: Target joint positions to check against.
681
+ wait_time: Maximum time to wait in seconds.
682
+ exit_on_reach: If True, exit early when joint positions are reached.
683
+ exit_on_reach_kwargs: Optional parameters for position checking.
684
+ """
685
+ if exit_on_reach:
686
+ # Set default tolerance if not provided
687
+ exit_on_reach_kwargs = exit_on_reach_kwargs or {}
688
+ exit_on_reach_kwargs.setdefault("tolerance", 0.05)
689
+
690
+ # Convert to expected format for is_joint_pos_reached
691
+ if isinstance(joint_pos, list):
692
+ joint_pos = np.array(joint_pos, dtype=np.float32)
693
+
694
+ # Wait until position is reached or timeout
695
+ start_time = time.time()
696
+ while time.time() - start_time < wait_time:
697
+ if self.is_joint_pos_reached(joint_pos, **exit_on_reach_kwargs):
698
+ break
699
+ time.sleep(0.01)
700
+ else:
662
701
  time.sleep(wait_time)
663
702
 
664
703
  def _send_position_command(self, joint_pos: Float[np.ndarray, " N"]) -> None:
@@ -679,12 +718,16 @@ class RobotJointComponent(RobotComponent):
679
718
  self,
680
719
  pose_name: str,
681
720
  wait_time: float = 3.0,
721
+ exit_on_reach: bool = False,
722
+ exit_on_reach_kwargs: dict[str, float] | None = None,
682
723
  ) -> None:
683
724
  """Move the component to a predefined pose.
684
725
 
685
726
  Args:
686
727
  pose_name: Name of the pose to move to.
687
728
  wait_time: Time to wait for the component to reach the pose.
729
+ exit_on_reach: If True, the function will exit when the joint positions are reached.
730
+ exit_on_reach_kwargs: Optional parameters for exit when the joint positions are reached.
688
731
 
689
732
  Raises:
690
733
  ValueError: If pose pool is not available or if an invalid pose name is provided.
@@ -696,7 +739,12 @@ class RobotJointComponent(RobotComponent):
696
739
  f"Invalid pose name: {pose_name}. Available poses: {list(self._pose_pool.keys())}"
697
740
  )
698
741
  pose = self._pose_pool[pose_name]
699
- self.set_joint_pos(pose, wait_time=wait_time)
742
+ self.set_joint_pos(
743
+ pose,
744
+ wait_time=wait_time,
745
+ exit_on_reach=exit_on_reach,
746
+ exit_on_reach_kwargs=exit_on_reach_kwargs,
747
+ )
700
748
 
701
749
  def is_joint_pos_reached(
702
750
  self,
@@ -819,11 +867,12 @@ class RobotJointComponent(RobotComponent):
819
867
  else:
820
868
  # Check all joints, ensuring arrays are same length
821
869
  min_len = min(len(current_pos), len(target_pos))
822
- return bool(
870
+ is_reached = bool(
823
871
  np.all(
824
872
  np.abs(current_pos[:min_len] - target_pos[:min_len]) <= tolerance
825
873
  )
826
874
  )
875
+ return is_reached
827
876
 
828
877
  def is_pose_reached(
829
878
  self,
dexcontrol/core/hand.py CHANGED
@@ -14,6 +14,8 @@ This module provides the Hand class for controlling a robotic hand through Zenoh
14
14
  communication. It handles joint position control and state monitoring.
15
15
  """
16
16
 
17
+ from typing import Any
18
+
17
19
  import numpy as np
18
20
  import zenoh
19
21
  from jaxtyping import Float
@@ -51,8 +53,8 @@ class Hand(RobotJointComponent):
51
53
  )
52
54
 
53
55
  # Store predefined hand positions as private attributes
54
- self._joint_pos_open = np.array(configs.joint_pos_open, dtype=np.float32)
55
- self._joint_pos_close = np.array(configs.joint_pos_close, dtype=np.float32)
56
+ self._joint_pos_open = np.array(configs.pose_pool["open"], dtype=np.float32)
57
+ self._joint_pos_close = np.array(configs.pose_pool["close"], dtype=np.float32)
56
58
 
57
59
  def _send_position_command(
58
60
  self, joint_pos: Float[np.ndarray, " N"] | list[float]
@@ -73,6 +75,8 @@ class Hand(RobotJointComponent):
73
75
  relative: bool = False,
74
76
  wait_time: float = 0.0,
75
77
  wait_kwargs: dict[str, float] | None = None,
78
+ exit_on_reach: bool = False,
79
+ exit_on_reach_kwargs: dict[str, Any] | None = None,
76
80
  ) -> None:
77
81
  """Send joint position control commands to the hand.
78
82
 
@@ -84,27 +88,61 @@ class Hand(RobotJointComponent):
84
88
  relative: If True, the joint positions are relative to the current position.
85
89
  wait_time: Time to wait after setting the joint positions.
86
90
  wait_kwargs: Optional parameters for trajectory generation (not used in Hand).
91
+ exit_on_reach: If True, the function will exit when the joint positions are reached.
92
+ exit_on_reach_kwargs: Optional parameters for exit when the joint positions are reached.
87
93
 
88
94
  Raises:
89
95
  ValueError: If joint_pos dictionary contains invalid joint names.
90
96
  """
91
- super().set_joint_pos(joint_pos, relative, wait_time, wait_kwargs)
97
+ super().set_joint_pos(
98
+ joint_pos,
99
+ relative,
100
+ wait_time,
101
+ wait_kwargs,
102
+ exit_on_reach,
103
+ exit_on_reach_kwargs,
104
+ )
92
105
 
93
- def open_hand(self, wait_time: float = 0.0) -> None:
106
+ def open_hand(
107
+ self,
108
+ wait_time: float = 0.0,
109
+ exit_on_reach: bool = False,
110
+ exit_on_reach_kwargs: dict[str, Any] | None = None,
111
+ ) -> None:
94
112
  """Open the hand to the predefined open position.
95
113
 
96
114
  Args:
97
115
  wait_time: Time to wait after opening the hand.
116
+ exit_on_reach: If True, the function will exit when the joint positions are reached.
117
+ exit_on_reach_kwargs: Optional parameters for exit when the joint positions are reached.
98
118
  """
99
- self.set_joint_pos(self._joint_pos_open, wait_time=wait_time)
119
+ self.set_joint_pos(
120
+ self._joint_pos_open,
121
+ wait_time=wait_time,
122
+ exit_on_reach=exit_on_reach,
123
+ exit_on_reach_kwargs=exit_on_reach_kwargs,
124
+ )
100
125
 
101
- def close_hand(self, wait_time: float = 0.0) -> None:
126
+ def close_hand(
127
+ self,
128
+ wait_time: float = 0.0,
129
+ exit_on_reach: bool = False,
130
+ exit_on_reach_kwargs: dict[str, Any] | None = None,
131
+ ) -> None:
102
132
  """Close the hand to the predefined closed position.
103
133
 
104
134
  Args:
105
135
  wait_time: Time to wait after closing the hand.
136
+ exit_on_reach: If True, the function will exit when the joint positions are reached.
137
+ exit_on_reach_kwargs: Optional parameters for exit when the joint positions are reached.
138
+
106
139
  """
107
- self.set_joint_pos(self._joint_pos_close, wait_time=wait_time)
140
+ self.set_joint_pos(
141
+ self._joint_pos_close,
142
+ wait_time=wait_time,
143
+ exit_on_reach=exit_on_reach,
144
+ exit_on_reach_kwargs=exit_on_reach_kwargs,
145
+ )
108
146
 
109
147
 
110
148
  class HandF5D6(Hand):
@@ -114,7 +152,12 @@ class HandF5D6(Hand):
114
152
  the F5D6 hand model.
115
153
  """
116
154
 
117
- def close_hand(self, wait_time: float = 0.0) -> None:
155
+ def close_hand(
156
+ self,
157
+ wait_time: float = 0.0,
158
+ exit_on_reach: bool = False,
159
+ exit_on_reach_kwargs: dict[str, Any] | None = None,
160
+ ) -> None:
118
161
  """Close the hand fully using a two-step approach for better control."""
119
162
  if self.is_joint_pos_reached(self._joint_pos_close, tolerance=0.1):
120
163
  return
@@ -122,13 +165,28 @@ class HandF5D6(Hand):
122
165
  # First step: Move to intermediate position
123
166
  intermediate_pos = self._get_intermediate_close_position()
124
167
  first_step_wait_time = 0.8
125
- self.set_joint_pos(intermediate_pos, wait_time=first_step_wait_time)
168
+ self.set_joint_pos(
169
+ intermediate_pos,
170
+ wait_time=first_step_wait_time,
171
+ exit_on_reach=exit_on_reach,
172
+ exit_on_reach_kwargs=exit_on_reach_kwargs,
173
+ )
126
174
 
127
175
  # Second step: Move to final closed position
128
176
  remaining_wait_time = max(0.0, wait_time - first_step_wait_time)
129
- self.set_joint_pos(self._joint_pos_close, wait_time=remaining_wait_time)
177
+ self.set_joint_pos(
178
+ self._joint_pos_close,
179
+ wait_time=remaining_wait_time,
180
+ exit_on_reach=exit_on_reach,
181
+ exit_on_reach_kwargs=exit_on_reach_kwargs,
182
+ )
130
183
 
131
- def open_hand(self, wait_time: float = 0.0) -> None:
184
+ def open_hand(
185
+ self,
186
+ wait_time: float = 0.0,
187
+ exit_on_reach: bool = False,
188
+ exit_on_reach_kwargs: dict[str, Any] | None = None,
189
+ ) -> None:
132
190
  """Open the hand fully using a two-step approach for better control."""
133
191
  if self.is_joint_pos_reached(self._joint_pos_open, tolerance=0.1):
134
192
  return
@@ -136,11 +194,21 @@ class HandF5D6(Hand):
136
194
  # First step: Move to intermediate position
137
195
  intermediate_pos = self._get_intermediate_open_position()
138
196
  first_step_wait_time = 0.3
139
- self.set_joint_pos(intermediate_pos, wait_time=first_step_wait_time)
197
+ self.set_joint_pos(
198
+ intermediate_pos,
199
+ wait_time=first_step_wait_time,
200
+ exit_on_reach=exit_on_reach,
201
+ exit_on_reach_kwargs=exit_on_reach_kwargs,
202
+ )
140
203
 
141
204
  # Second step: Move to final open position
142
205
  remaining_wait_time = max(0.0, wait_time - first_step_wait_time)
143
- self.set_joint_pos(self._joint_pos_open, wait_time=remaining_wait_time)
206
+ self.set_joint_pos(
207
+ self._joint_pos_open,
208
+ wait_time=remaining_wait_time,
209
+ exit_on_reach=exit_on_reach,
210
+ exit_on_reach_kwargs=exit_on_reach_kwargs,
211
+ )
144
212
 
145
213
  def _get_intermediate_close_position(self) -> np.ndarray:
146
214
  """Get intermediate position for closing hand.
dexcontrol/core/head.py CHANGED
@@ -79,6 +79,8 @@ class Head(RobotJointComponent):
79
79
  relative: bool = False,
80
80
  wait_time: float = 0.0,
81
81
  wait_kwargs: dict[str, float] | None = None,
82
+ exit_on_reach: bool = False,
83
+ exit_on_reach_kwargs: dict[str, float] | None = None,
82
84
  ) -> None:
83
85
  """Send joint position control commands to the head.
84
86
 
@@ -91,12 +93,19 @@ class Head(RobotJointComponent):
91
93
  wait_time: Time to wait after sending command in seconds. If 0, returns
92
94
  immediately after sending command.
93
95
  wait_kwargs: Optional parameters for trajectory generation (not used in Head).
96
+ exit_on_reach: If True, the function will exit when the joint positions are reached.
97
+ exit_on_reach_kwargs: Optional parameters for exit when the joint positions are reached.
94
98
 
95
99
  Raises:
96
100
  ValueError: If joint_pos dictionary contains invalid joint names.
97
101
  """
98
102
  self.set_joint_pos_vel(
99
- joint_pos, joint_vel=None, relative=relative, wait_time=wait_time
103
+ joint_pos,
104
+ joint_vel=None,
105
+ relative=relative,
106
+ wait_time=wait_time,
107
+ exit_on_reach=exit_on_reach,
108
+ exit_on_reach_kwargs=exit_on_reach_kwargs,
100
109
  )
101
110
 
102
111
  def set_joint_pos_vel(
@@ -109,6 +118,8 @@ class Head(RobotJointComponent):
109
118
  | None = None,
110
119
  relative: bool = False,
111
120
  wait_time: float = 0.0,
121
+ exit_on_reach: bool = False,
122
+ exit_on_reach_kwargs: dict[str, float] | None = None,
112
123
  ) -> None:
113
124
  """Send control commands to the head.
114
125
 
@@ -126,6 +137,8 @@ class Head(RobotJointComponent):
126
137
  relative: If True, the joint positions are relative to the current position.
127
138
  wait_time: Time to wait after sending command in seconds. If 0, returns
128
139
  immediately after sending command.
140
+ exit_on_reach: If True, the function will exit when the joint positions are reached.
141
+ exit_on_reach_kwargs: Optional parameters for exit when the joint positions are reached.
129
142
 
130
143
  Raises:
131
144
  ValueError: If wait_time is negative or joint_pos dictionary contains
@@ -149,8 +162,12 @@ class Head(RobotJointComponent):
149
162
  self._publish_control(control_msg)
150
163
 
151
164
  # Wait if specified
152
- if wait_time > 0.0:
153
- time.sleep(wait_time)
165
+ self._wait_for_position(
166
+ joint_pos=joint_pos,
167
+ wait_time=wait_time,
168
+ exit_on_reach=exit_on_reach,
169
+ exit_on_reach_kwargs=exit_on_reach_kwargs,
170
+ )
154
171
 
155
172
  def set_mode(self, mode: Literal["enable", "disable"]) -> None:
156
173
  """Set the operating mode of the head.
dexcontrol/core/torso.py CHANGED
@@ -14,8 +14,6 @@ This module provides the Torso class for controlling a robot torso through Zenoh
14
14
  communication. It handles joint position and velocity control and state monitoring.
15
15
  """
16
16
 
17
- import time
18
-
19
17
  import numpy as np
20
18
  import zenoh
21
19
  from jaxtyping import Float
@@ -69,6 +67,8 @@ class Torso(RobotJointComponent):
69
67
  | None = None,
70
68
  relative: bool = False,
71
69
  wait_time: float = 0.0,
70
+ exit_on_reach: bool = False,
71
+ exit_on_reach_kwargs: dict[str, float] | None = None,
72
72
  ) -> None:
73
73
  """Send control commands to the torso.
74
74
 
@@ -86,6 +86,8 @@ class Torso(RobotJointComponent):
86
86
  relative: If True, the joint positions are relative to the current position.
87
87
  wait_time: Time to wait after sending command in seconds. If 0, returns
88
88
  immediately after sending command.
89
+ exit_on_reach: If True, the function will exit when the joint positions are reached.
90
+ exit_on_reach_kwargs: Optional parameters for exit when the joint positions are reached.
89
91
 
90
92
  Raises:
91
93
  ValueError: If wait_time is negative or joint_pos dictionary contains
@@ -109,8 +111,12 @@ class Torso(RobotJointComponent):
109
111
  self._publish_control(control_msg)
110
112
 
111
113
  # Wait if specified
112
- if wait_time > 0.0:
113
- time.sleep(wait_time)
114
+ self._wait_for_position(
115
+ joint_pos=joint_pos,
116
+ wait_time=wait_time,
117
+ exit_on_reach=exit_on_reach,
118
+ exit_on_reach_kwargs=exit_on_reach_kwargs,
119
+ )
114
120
 
115
121
  def set_joint_pos(
116
122
  self,
@@ -118,6 +124,8 @@ class Torso(RobotJointComponent):
118
124
  relative: bool = False,
119
125
  wait_time: float = 0.0,
120
126
  wait_kwargs: dict[str, float] | None = None,
127
+ exit_on_reach: bool = False,
128
+ exit_on_reach_kwargs: dict[str, float] | None = None,
121
129
  ) -> None:
122
130
  """Send joint position control commands to the torso.
123
131
 
@@ -130,12 +138,19 @@ class Torso(RobotJointComponent):
130
138
  wait_time: Time to wait after sending command in seconds. If 0, returns
131
139
  immediately after sending command.
132
140
  wait_kwargs: Optional parameters for trajectory generation (not used in Torso).
141
+ exit_on_reach: If True, the function will exit when the joint positions are reached.
142
+ exit_on_reach_kwargs: Optional parameters for exit when the joint positions are reached.
133
143
 
134
144
  Raises:
135
145
  ValueError: If joint_pos dictionary contains invalid joint names.
136
146
  """
137
147
  self.set_joint_pos_vel(
138
- joint_pos, joint_vel=None, relative=relative, wait_time=wait_time
148
+ joint_pos,
149
+ joint_vel=None,
150
+ relative=relative,
151
+ wait_time=wait_time,
152
+ exit_on_reach=exit_on_reach,
153
+ exit_on_reach_kwargs=exit_on_reach_kwargs,
139
154
  )
140
155
 
141
156
  def stop(self) -> None: