dexcontrol 0.2.7__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
  )
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:
dexcontrol/robot.py CHANGED
@@ -739,14 +739,18 @@ class Robot:
739
739
 
740
740
  def get_joint_pos_dict(
741
741
  self,
742
- component: Literal["left_arm", "right_arm", "torso", "head"]
743
- | list[Literal["left_arm", "right_arm", "torso", "head"]],
742
+ component: Literal[
743
+ "left_arm", "right_arm", "torso", "head", "left_hand", "right_hand"
744
+ ]
745
+ | list[
746
+ Literal["left_arm", "right_arm", "torso", "head", "left_hand", "right_hand"]
747
+ ],
744
748
  ) -> dict[str, float]:
745
749
  """Get the joint positions of one or more robot components.
746
750
 
747
751
  Args:
748
752
  component: Component name or list of component names to get joint positions for.
749
- Valid components are "left_arm", "right_arm", "torso", and "head".
753
+ Valid components are "left_arm", "right_arm", "torso", "head", "left_hand", and "right_hand".
750
754
 
751
755
  Returns:
752
756
  Dictionary mapping joint names to joint positions.
@@ -761,6 +765,8 @@ class Robot:
761
765
  "right_arm": self.right_arm,
762
766
  "torso": self.torso,
763
767
  "head": self.head,
768
+ "left_hand": self.left_hand,
769
+ "right_hand": self.right_hand,
764
770
  }
765
771
 
766
772
  try:
@@ -826,6 +832,8 @@ class Robot:
826
832
  relative: bool = False,
827
833
  wait_time: float = 0.0,
828
834
  wait_kwargs: dict[str, Any] | None = None,
835
+ exit_on_reach: bool = False,
836
+ exit_on_reach_kwargs: dict[str, Any] | None = None,
829
837
  ) -> None:
830
838
  """Set the joint positions of the robot.
831
839
 
@@ -836,7 +844,9 @@ class Robot:
836
844
  wait_time: Time to wait for movement completion in seconds.
837
845
  wait_kwargs: Additional parameters for trajectory generation.
838
846
  control_hz: Control frequency in Hz (default: 100).
839
- max_vel: Maximum velocity for trajectory (default: 0.5).
847
+ max_vel: Maximum velocity for trajectory (default: 3.).
848
+ exit_on_reach: If True, the function will exit when the joint positions are reached.
849
+ exit_on_reach_kwargs: Optional parameters for exit when the joint positions are reached.
840
850
 
841
851
  Raises:
842
852
  ValueError: If any component name is invalid.
@@ -846,6 +856,7 @@ class Robot:
846
856
  wait_kwargs = {}
847
857
 
848
858
  try:
859
+ start_time = time.time()
849
860
  component_map = self._get_component_map()
850
861
 
851
862
  # Validate component names
@@ -871,11 +882,69 @@ class Robot:
871
882
  relative,
872
883
  wait_time,
873
884
  wait_kwargs,
885
+ exit_on_reach=exit_on_reach,
886
+ exit_on_reach_kwargs=exit_on_reach_kwargs,
874
887
  )
888
+ remaining_time = wait_time - (time.time() - start_time)
889
+ if remaining_time <= 0:
890
+ return
891
+
892
+ self._wait_for_multi_component_positions(
893
+ component_map,
894
+ pv_components,
895
+ joint_pos,
896
+ start_time,
897
+ wait_time,
898
+ exit_on_reach,
899
+ exit_on_reach_kwargs,
900
+ )
875
901
 
876
902
  except Exception as e:
877
903
  raise RuntimeError(f"Failed to set joint positions: {e}") from e
878
904
 
905
+ def _wait_for_multi_component_positions(
906
+ self,
907
+ component_map: dict[str, Any],
908
+ components: list[str],
909
+ joint_pos: dict[str, list[float] | np.ndarray],
910
+ start_time: float,
911
+ wait_time: float,
912
+ exit_on_reach: bool = False,
913
+ exit_on_reach_kwargs: dict[str, Any] | None = None,
914
+ ) -> None:
915
+ """Wait for multiple components to reach target positions.
916
+
917
+ Args:
918
+ component_map: Mapping of component names to component instances.
919
+ components: List of component names to check.
920
+ joint_pos: Target joint positions for each component.
921
+ start_time: Time when the operation started.
922
+ wait_time: Maximum time to wait.
923
+ exit_on_reach: If True, exit early when all positions are reached.
924
+ exit_on_reach_kwargs: Optional parameters for position checking.
925
+ """
926
+ sleep_interval = 0.01 # Use consistent sleep interval
927
+
928
+ if exit_on_reach:
929
+ if components:
930
+ # Set default tolerance if not provided
931
+ exit_on_reach_kwargs = exit_on_reach_kwargs or {}
932
+
933
+ # Wait until all positions are reached or timeout
934
+ while time.time() - start_time < wait_time:
935
+ if all(
936
+ component_map[c].is_joint_pos_reached(
937
+ joint_pos[c], **exit_on_reach_kwargs
938
+ )
939
+ for c in components
940
+ ):
941
+ break
942
+ time.sleep(sleep_interval)
943
+ else:
944
+ # Simple wait without position checking
945
+ while time.time() - start_time < wait_time:
946
+ time.sleep(sleep_interval)
947
+
879
948
  def compensate_torso_pitch(self, joint_pos: np.ndarray, part: str) -> np.ndarray:
880
949
  """Compensate for torso pitch in joint positions.
881
950
 
@@ -1092,6 +1161,8 @@ class Robot:
1092
1161
  relative: bool,
1093
1162
  wait_time: float,
1094
1163
  wait_kwargs: dict[str, Any],
1164
+ exit_on_reach: bool = False,
1165
+ exit_on_reach_kwargs: dict[str, Any] | None = None,
1095
1166
  ) -> None:
1096
1167
  """Set non-PV components with smooth trajectory over wait_time.
1097
1168
 
@@ -1102,9 +1173,11 @@ class Robot:
1102
1173
  relative: Whether positions are relative.
1103
1174
  wait_time: Time to wait for movement completion.
1104
1175
  wait_kwargs: Additional trajectory parameters.
1176
+ exit_on_reach: If True, the function will exit when the joint positions are reached.
1177
+ exit_on_reach_kwargs: Optional parameters for exit when the joint positions are reached.
1105
1178
  """
1106
- control_hz = wait_kwargs.get("control_hz", 100)
1107
- max_vel = wait_kwargs.get("max_vel", 0.5)
1179
+ control_hz = wait_kwargs.get("control_hz", self.left_arm._default_control_hz)
1180
+ max_vel = wait_kwargs.get("max_vel", self.left_arm._default_max_vel)
1108
1181
 
1109
1182
  # Generate trajectories for smooth motion during wait_time
1110
1183
  rate_limiter = RateLimiter(control_hz)
@@ -1136,5 +1209,12 @@ class Robot:
1136
1209
  break
1137
1210
 
1138
1211
  # Wait for any remaining time
1139
- while time.time() - start_time < wait_time:
1140
- rate_limiter.sleep()
1212
+ self._wait_for_multi_component_positions(
1213
+ component_map,
1214
+ non_pv_components,
1215
+ joint_pos,
1216
+ start_time,
1217
+ wait_time,
1218
+ exit_on_reach,
1219
+ exit_on_reach_kwargs,
1220
+ )
@@ -138,3 +138,39 @@ class RGBCameraSensor:
138
138
  Sensor name string.
139
139
  """
140
140
  return self._name
141
+
142
+ @property
143
+ def height(self) -> int:
144
+ """Get the height of the camera image.
145
+
146
+ Returns:
147
+ Height of the camera image.
148
+ """
149
+ image = self.get_obs()
150
+ if image is None:
151
+ return 0
152
+ return image.shape[0]
153
+
154
+ @property
155
+ def width(self) -> int:
156
+ """Get the width of the camera image.
157
+
158
+ Returns:
159
+ Width of the camera image.
160
+ """
161
+ image = self.get_obs()
162
+ if image is None:
163
+ return 0
164
+ return image.shape[1]
165
+
166
+ @property
167
+ def resolution(self) -> tuple[int, int]:
168
+ """Get the resolution of the camera image.
169
+
170
+ Returns:
171
+ Resolution of the camera image.
172
+ """
173
+ image = self.get_obs()
174
+ if image is None:
175
+ return 0, 0
176
+ return image.shape[0], image.shape[1]
@@ -356,3 +356,34 @@ class ZedCameraSensor:
356
356
  Camera info dictionary if available, None otherwise.
357
357
  """
358
358
  return self._camera_info
359
+
360
+ @property
361
+ def height(self) -> dict[str, int]:
362
+ """Get the height of the camera image.
363
+
364
+ Returns:
365
+ Height of the camera image.
366
+ """
367
+ images = self.get_obs()
368
+ return {name: image.shape[0] if image is not None else 0 for name, image in images.items()}
369
+
370
+ @property
371
+ def width(self) -> dict[str, int]:
372
+ """Get the width of the camera image.
373
+
374
+ Returns:
375
+ Width of the camera image.
376
+ """
377
+ images = self.get_obs()
378
+ return {name: image.shape[1] if image is not None else 0 for name, image in images.items()}
379
+
380
+
381
+ @property
382
+ def resolution(self) -> dict[str, tuple[int, int]]:
383
+ """Get the resolution of the camera image.
384
+
385
+ Returns:
386
+ Resolution of the camera image.
387
+ """
388
+ images = self.get_obs()
389
+ return {name: (image.shape[0], image.shape[1]) if image is not None else (0, 0) for name, image in images.items()}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dexcontrol
3
- Version: 0.2.7
3
+ Version: 0.2.10
4
4
  Summary: A Python library of Sensing and Control for Dexmate's Robot
5
5
  Project-URL: Repository, https://github.com/dexmate-ai/dexcontrol
6
6
  Author-email: Dexmate <contact@dexmate.ai>
@@ -200,7 +200,7 @@ Classifier: Programming Language :: Python :: 3.11
200
200
  Classifier: Programming Language :: Python :: 3.12
201
201
  Classifier: Programming Language :: Python :: 3.13
202
202
  Classifier: Typing :: Typed
203
- Requires-Python: <3.13,>=3.10
203
+ Requires-Python: <3.14,>=3.10
204
204
  Requires-Dist: eclipse-zenoh>=1.2.0
205
205
  Requires-Dist: hydra-core==1.3.2
206
206
  Requires-Dist: jaxtyping>=0.2.38
@@ -225,7 +225,7 @@ Requires-Dist: sphinx-autodoc-typehints>=1.24.0; extra == 'docs'
225
225
  Requires-Dist: sphinx-rtd-theme>=1.3.0; extra == 'docs'
226
226
  Requires-Dist: sphinx>=7.0.0; extra == 'docs'
227
227
  Provides-Extra: example
228
- Requires-Dist: dexmotion>=0.2.0; extra == 'example'
228
+ Requires-Dist: dexmotion>=0.2.1; extra == 'example'
229
229
  Requires-Dist: dualsense-controller>=0.3.1; extra == 'example'
230
230
  Requires-Dist: matplotlib>=3.8.0; extra == 'example'
231
231
  Requires-Dist: pytransform3d==3.13.0; extra == 'example'
@@ -1,12 +1,12 @@
1
1
  dexcontrol/__init__.py,sha256=1UGkcmMHphLJM7MYIfSuWfSameV1XLBDVjEazl5hb1I,1656
2
- dexcontrol/robot.py,sha256=Kyp8VXcDM6PORlvWBQqH4JR-4Uu6ttn652gNX7t5fg8,41686
2
+ dexcontrol/robot.py,sha256=vFjrSHEJQaivieeDdRAfeUq39qhWOTmUN-2-pYnjWLE,45025
3
3
  dexcontrol/apps/dualsense_teleop_base.py,sha256=Dw1z-2HA5D7DPKutZxlOdXsN9vpk4gS6XzJsL5ZQLM0,12702
4
4
  dexcontrol/config/__init__.py,sha256=UVLNpzGD14e8g68rUZFXTh0B7FRx6uS0Eg_MecjinYM,520
5
5
  dexcontrol/config/vega.py,sha256=4RrP0V5bmH0OUM3V0gND6cpRuMqoziLTQdKiRey1_ag,7192
6
6
  dexcontrol/config/core/__init__.py,sha256=Ym2R1hr1iMKQuXcg16BpZfQtTb0hQ5Q7smUIMlwKfho,637
7
- dexcontrol/config/core/arm.py,sha256=bRemtNO_eQVUgcojw1ItwWiqOZVOaaIq72G5-QqFu-I,1183
7
+ dexcontrol/config/core/arm.py,sha256=G7k39vLx_PkctfZIaTMIAiJscpDAMo9B-ZLuy7xRsPo,1272
8
8
  dexcontrol/config/core/chassis.py,sha256=163hO4lVVaW7r9dvNleH0cdDds_GfO15EepnjloeT9U,868
9
- dexcontrol/config/core/hand.py,sha256=MONnh9rUmfDqUl3NMoJ6rOy8hZKvD5iv1egkYdIl1pQ,1272
9
+ dexcontrol/config/core/hand.py,sha256=gPIsYgm1wP_L4BWwVmnG8UGxM_a5J_HJDkIamhgl2n4,954
10
10
  dexcontrol/config/core/head.py,sha256=1eqsDPGBG8r8MocJwUMnDAQnOD152sl7P2k6Lg_ZKOA,1124
11
11
  dexcontrol/config/core/misc.py,sha256=KQstrVKFL-vdoaHVdj-VGwMrrhzm4pVz2g4A2etnav4,1169
12
12
  dexcontrol/config/core/torso.py,sha256=6B_-8LhT4rJ4K_oF9sjroOwIIj_JwlNS2JC8LmCCrSA,1456
@@ -23,13 +23,13 @@ dexcontrol/config/sensors/lidar/rplidar.py,sha256=ybuT_f1ADWF3oGH1gi6D2F80TbJEm4
23
23
  dexcontrol/config/sensors/ultrasonic/__init__.py,sha256=-q83RhIMZJGVFVPYaA4hOugoG6wZw8EL6wJg7-HTSxU,294
24
24
  dexcontrol/config/sensors/ultrasonic/ultrasonic.py,sha256=7b4dm1QOhy5_5RFVpY-frXZyDzqok0K1u7ed9gf3PL0,552
25
25
  dexcontrol/core/__init__.py,sha256=bYPMLxbbn5QeuPyA6OPGDS2JTYpnVvaZJT8PeILFjQY,252
26
- dexcontrol/core/arm.py,sha256=jeBpPILV_-n6r-tqFUUjQPSog1Jxd9HKki572S6e8qk,12467
26
+ dexcontrol/core/arm.py,sha256=6acUfzOTGEzPrdKDFcdMGgf5ovYNLP14PwGYkSnSpLM,13917
27
27
  dexcontrol/core/chassis.py,sha256=tB-tsx6MjKJDOtQxBf7PIX0L1JwgnsXUzmNPC1VKsQU,23165
28
- dexcontrol/core/component.py,sha256=KRlf6XR5vvki1N4EorIJzvc15_O8UZMHWa4khHTPDVk,31060
29
- dexcontrol/core/hand.py,sha256=swfWWM9h6o27i-41R-fw_JLfOzBHtpNIJDI1IgQPGIE,6541
30
- dexcontrol/core/head.py,sha256=V1ySWoFSPGCX65tbP-UHWiXgiJEtnEdiWLUDtvl-Sns,9227
28
+ dexcontrol/core/component.py,sha256=Z1-Q80yPJJ3njp45jrd51mEWKUtGgqq3oLYDTI4mBcw,33279
29
+ dexcontrol/core/hand.py,sha256=wd__YB5wcM-LlksEm3PatXhy665zLnu9nV_9x85WxIA,8649
30
+ dexcontrol/core/head.py,sha256=_COrqMmGzefK8lK38i6J3FqvcaeTAsY6Ss2rkHf1yoE,10097
31
31
  dexcontrol/core/misc.py,sha256=m7WtziXGyhM7z27MjxiIQfTMu0_z30ReLkUCHzaRk9E,22547
32
- dexcontrol/core/torso.py,sha256=N5XE_lY-i78iMFEjIxMJ9I6KWK4YgHv6VqVUKEWLrcg,7402
32
+ dexcontrol/core/torso.py,sha256=xJc8tTC5vbvT-pXz6fHgZEa17YNF-V8f9pHoEsUzn6s,8259
33
33
  dexcontrol/proto/dexcontrol_msg_pb2.py,sha256=iRvfKLjV7eRxb9jOB_KrCsd77GbgzByThW_JIKd_ibM,5691
34
34
  dexcontrol/proto/dexcontrol_msg_pb2.pyi,sha256=_vzo6hozKpC5jzdKUOTH81VXCNYO1awcgTFhVNo91N0,9371
35
35
  dexcontrol/proto/dexcontrol_query_pb2.py,sha256=Aa98bPmOuImBheN6fv4Ysy1Bmk5oX2tGatqcykDUz0E,5328
@@ -38,8 +38,8 @@ dexcontrol/sensors/__init__.py,sha256=Dp06cuO_3xC6i4u5rHqfK5NqlIC5kaCue_bAtTC6JE
38
38
  dexcontrol/sensors/manager.py,sha256=pBseqoAcrYdbBbSpFGChh_ROQlxnKDcolcKzKCEjpwM,6853
39
39
  dexcontrol/sensors/ultrasonic.py,sha256=WAHvHh64iQ0HfqVf-Oo0Rg8R32Cdk5d8k8kDSwa3Xrc,3258
40
40
  dexcontrol/sensors/camera/__init__.py,sha256=Vwe98I4Lvdv3F2UslOzKkeUkt5Rl2jSqbKlU6gIBeF0,609
41
- dexcontrol/sensors/camera/rgb_camera.py,sha256=ZyWS2FkEF7MYg5rlG_YXd8VpbBu--Y-1Rlb6uzYHNNo,5045
42
- dexcontrol/sensors/camera/zed_camera.py,sha256=7gVoRhwynCUjyTOtUBZCiNZS3MhXoQYBDrr_fGlho4g,14007
41
+ dexcontrol/sensors/camera/rgb_camera.py,sha256=UaEk8aszHeBRg5xMQlHxZk2KMP7wU3m6UnE_qgh6V4Q,5892
42
+ dexcontrol/sensors/camera/zed_camera.py,sha256=OrmyZmamM2tpZD4Wu66MJUjvX4zZPIWsl0DZZHhAloE,14964
43
43
  dexcontrol/sensors/imu/__init__.py,sha256=bBC7_NSLJ5qLMvUYu2-9yXKO2bRpQLC0HyywBwnbM0A,768
44
44
  dexcontrol/sensors/imu/chassis_imu.py,sha256=9gziCrtFKfWmeiDdbgD4aIkkdtjJahbckayYj_Lv5r4,4858
45
45
  dexcontrol/sensors/imu/zed_imu.py,sha256=VB0TfQYIqKNEPGPCbdAO9YtshWs4nE7y0y9FnWU-Mko,5148
@@ -66,7 +66,7 @@ dexcontrol/utils/subscribers/imu.py,sha256=Us4ZWzLfqujg16jvrNIoHcvUyxbRCv6mZlaAi
66
66
  dexcontrol/utils/subscribers/lidar.py,sha256=ZIUpzfb-n5zLnCfZBDxJMMI4FKmlwdOYHQzpVlfIGQw,5631
67
67
  dexcontrol/utils/subscribers/protobuf.py,sha256=gtE2b9ZtR2UXftKA5nX7bvTLkj8AeXDYZMqe4B3t5BQ,3696
68
68
  dexcontrol/utils/subscribers/rtc.py,sha256=mxD-IIeQwluvq0_D63lQJJEXrJsIc6yxX7uD0PDh08k,11350
69
- dexcontrol-0.2.7.dist-info/METADATA,sha256=9V7o2gUcFDBux04BFEqFSmYs0MCA8Pxqbvara3g9Fe8,36790
70
- dexcontrol-0.2.7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
71
- dexcontrol-0.2.7.dist-info/licenses/LICENSE,sha256=0J2KCMNNnW5WZPK5x8xUiCxApBf7h83693ggSJYiue0,31745
72
- dexcontrol-0.2.7.dist-info/RECORD,,
69
+ dexcontrol-0.2.10.dist-info/METADATA,sha256=VNSJzSGYSXAhUaKM3oj8Y7qy8hH1tkX4QAEGLV-mDDY,36791
70
+ dexcontrol-0.2.10.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
71
+ dexcontrol-0.2.10.dist-info/licenses/LICENSE,sha256=0J2KCMNNnW5WZPK5x8xUiCxApBf7h83693ggSJYiue0,31745
72
+ dexcontrol-0.2.10.dist-info/RECORD,,