dexcontrol 0.2.7__py3-none-any.whl → 0.2.12__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.
- dexcontrol/__init__.py +1 -0
- dexcontrol/config/core/arm.py +6 -0
- dexcontrol/config/core/hand.py +5 -16
- dexcontrol/config/core/head.py +7 -8
- dexcontrol/config/core/misc.py +14 -1
- dexcontrol/config/core/torso.py +8 -4
- dexcontrol/config/sensors/cameras/__init__.py +2 -1
- dexcontrol/config/sensors/cameras/luxonis_camera.py +51 -0
- dexcontrol/config/sensors/cameras/rgb_camera.py +1 -1
- dexcontrol/config/sensors/cameras/zed_camera.py +2 -2
- dexcontrol/config/sensors/vega_sensors.py +9 -1
- dexcontrol/config/vega.py +30 -2
- dexcontrol/core/arm.py +95 -43
- dexcontrol/core/component.py +92 -6
- dexcontrol/core/hand.py +81 -13
- dexcontrol/core/head.py +55 -26
- dexcontrol/core/misc.py +94 -13
- dexcontrol/core/torso.py +44 -11
- dexcontrol/proto/dexcontrol_msg_pb2.py +38 -36
- dexcontrol/proto/dexcontrol_msg_pb2.pyi +48 -20
- dexcontrol/proto/dexcontrol_query_pb2.py +7 -3
- dexcontrol/proto/dexcontrol_query_pb2.pyi +24 -0
- dexcontrol/robot.py +319 -75
- dexcontrol/sensors/__init__.py +2 -1
- dexcontrol/sensors/camera/__init__.py +2 -0
- dexcontrol/sensors/camera/luxonis_camera.py +169 -0
- dexcontrol/sensors/camera/rgb_camera.py +36 -0
- dexcontrol/sensors/camera/zed_camera.py +48 -8
- dexcontrol/sensors/imu/chassis_imu.py +5 -1
- dexcontrol/sensors/imu/zed_imu.py +3 -2
- dexcontrol/sensors/lidar/rplidar.py +1 -0
- dexcontrol/sensors/manager.py +3 -0
- dexcontrol/utils/constants.py +3 -0
- dexcontrol/utils/error_code.py +236 -0
- dexcontrol/utils/subscribers/lidar.py +1 -0
- dexcontrol/utils/trajectory_utils.py +17 -5
- dexcontrol/utils/viz_utils.py +86 -11
- dexcontrol/utils/zenoh_utils.py +39 -0
- {dexcontrol-0.2.7.dist-info → dexcontrol-0.2.12.dist-info}/METADATA +6 -4
- dexcontrol-0.2.12.dist-info/RECORD +75 -0
- dexcontrol-0.2.7.dist-info/RECORD +0 -72
- {dexcontrol-0.2.7.dist-info → dexcontrol-0.2.12.dist-info}/WHEEL +0 -0
- {dexcontrol-0.2.7.dist-info → dexcontrol-0.2.12.dist-info}/licenses/LICENSE +0 -0
dexcontrol/core/component.py
CHANGED
|
@@ -89,11 +89,11 @@ class RobotComponent:
|
|
|
89
89
|
Parsed protobuf state message.
|
|
90
90
|
|
|
91
91
|
Raises:
|
|
92
|
-
|
|
92
|
+
None: If no state data is available.
|
|
93
93
|
"""
|
|
94
94
|
state = self._subscriber.get_latest_data()
|
|
95
95
|
if state is None:
|
|
96
|
-
|
|
96
|
+
logger.error(f"No state data available for {self.__class__.__name__}")
|
|
97
97
|
return state
|
|
98
98
|
|
|
99
99
|
def wait_for_active(self, timeout: float = 5.0) -> bool:
|
|
@@ -133,6 +133,15 @@ class RobotComponent:
|
|
|
133
133
|
if hasattr(self, "_subscriber") and self._subscriber:
|
|
134
134
|
self._subscriber.shutdown()
|
|
135
135
|
|
|
136
|
+
def get_timestamp_ns(self) -> int:
|
|
137
|
+
"""Get the current timestamp (in nanoseconds) of the most recent state update.
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
The current timestamp in nanoseconds when driver updated the state.
|
|
141
|
+
We convert the time to client clock by adding the server time offset.
|
|
142
|
+
"""
|
|
143
|
+
return self._get_state().timestamp_ns
|
|
144
|
+
|
|
136
145
|
|
|
137
146
|
class RobotJointComponent(RobotComponent):
|
|
138
147
|
"""Base class for robot components with both state and control interfaces.
|
|
@@ -171,6 +180,8 @@ class RobotJointComponent(RobotComponent):
|
|
|
171
180
|
state_message_type: type[M],
|
|
172
181
|
zenoh_session: zenoh.Session,
|
|
173
182
|
joint_name: list[str] | None = None,
|
|
183
|
+
joint_limit: list[list[float]] | None = None,
|
|
184
|
+
joint_vel_limit: list[float] | None = None,
|
|
174
185
|
pose_pool: Mapping[str, list[float] | np.ndarray] | None = None,
|
|
175
186
|
) -> None:
|
|
176
187
|
"""Initializes RobotJointComponent.
|
|
@@ -190,6 +201,13 @@ class RobotJointComponent(RobotComponent):
|
|
|
190
201
|
resolved_topic
|
|
191
202
|
)
|
|
192
203
|
self._joint_name: list[str] | None = joint_name
|
|
204
|
+
self._joint_limit: np.ndarray | None = (
|
|
205
|
+
np.array(joint_limit) if joint_limit else None
|
|
206
|
+
)
|
|
207
|
+
self._joint_vel_limit: np.ndarray | None = (
|
|
208
|
+
np.array(joint_vel_limit) if joint_vel_limit else None
|
|
209
|
+
)
|
|
210
|
+
|
|
193
211
|
self._pose_pool: dict[str, np.ndarray] | None = (
|
|
194
212
|
self._convert_pose_pool_to_arrays(pose_pool)
|
|
195
213
|
)
|
|
@@ -231,6 +249,18 @@ class RobotJointComponent(RobotComponent):
|
|
|
231
249
|
raise ValueError("Joint names not available for this component")
|
|
232
250
|
return self._joint_name.copy()
|
|
233
251
|
|
|
252
|
+
@property
|
|
253
|
+
def joint_limit(self) -> np.ndarray | None:
|
|
254
|
+
"""Gets the joint limits of the component."""
|
|
255
|
+
return self._joint_limit.copy() if self._joint_limit is not None else None
|
|
256
|
+
|
|
257
|
+
@property
|
|
258
|
+
def joint_vel_limit(self) -> np.ndarray | None:
|
|
259
|
+
"""Gets the joint velocity limits of the component."""
|
|
260
|
+
return (
|
|
261
|
+
self._joint_vel_limit.copy() if self._joint_vel_limit is not None else None
|
|
262
|
+
)
|
|
263
|
+
|
|
234
264
|
def get_predefined_pose(self, pose_name: str) -> np.ndarray:
|
|
235
265
|
"""Gets a predefined pose from the pose pool.
|
|
236
266
|
|
|
@@ -466,7 +496,7 @@ class RobotJointComponent(RobotComponent):
|
|
|
466
496
|
def _convert_joint_cmd_to_array(
|
|
467
497
|
self,
|
|
468
498
|
joint_cmd: Float[np.ndarray, " N"] | list[float] | dict[str, float],
|
|
469
|
-
clip_value: float | None = None,
|
|
499
|
+
clip_value: float | np.ndarray | None = None,
|
|
470
500
|
) -> np.ndarray:
|
|
471
501
|
"""Convert joint command to numpy array format.
|
|
472
502
|
|
|
@@ -475,7 +505,9 @@ class RobotJointComponent(RobotComponent):
|
|
|
475
505
|
- List of joint values [j1, j2, ..., jN]
|
|
476
506
|
- Numpy array with shape (N,)
|
|
477
507
|
- Dictionary mapping joint names to values
|
|
478
|
-
clip_value: Optional value to clip the output array
|
|
508
|
+
clip_value: Optional value to clip the output array. Can be:
|
|
509
|
+
- float: symmetric clipping between [-clip_value, clip_value]
|
|
510
|
+
- numpy array: element-wise clipping between [-clip_value, clip_value]
|
|
479
511
|
|
|
480
512
|
Returns:
|
|
481
513
|
Joint command as numpy array.
|
|
@@ -634,6 +666,8 @@ class RobotJointComponent(RobotComponent):
|
|
|
634
666
|
relative: bool = False,
|
|
635
667
|
wait_time: float = 0.0,
|
|
636
668
|
wait_kwargs: dict[str, float] | None = None,
|
|
669
|
+
exit_on_reach: bool = False,
|
|
670
|
+
exit_on_reach_kwargs: dict[str, Any] | None = None,
|
|
637
671
|
) -> None:
|
|
638
672
|
"""Send joint position control commands.
|
|
639
673
|
|
|
@@ -645,6 +679,8 @@ class RobotJointComponent(RobotComponent):
|
|
|
645
679
|
relative: If True, the joint positions are relative to the current position.
|
|
646
680
|
wait_time: Time to wait after sending command in seconds.
|
|
647
681
|
wait_kwargs: Optional parameters for trajectory generation.
|
|
682
|
+
exit_on_reach: If True, the function will exit when the joint positions are reached.
|
|
683
|
+
exit_on_reach_kwargs: Optional parameters for exit when the joint positions are reached.
|
|
648
684
|
|
|
649
685
|
Raises:
|
|
650
686
|
ValueError: If joint_pos dictionary contains invalid joint names.
|
|
@@ -656,9 +692,49 @@ class RobotJointComponent(RobotComponent):
|
|
|
656
692
|
if isinstance(joint_pos, (list, dict)):
|
|
657
693
|
joint_pos = self._convert_joint_cmd_to_array(joint_pos)
|
|
658
694
|
|
|
695
|
+
if self._joint_limit is not None:
|
|
696
|
+
joint_pos = np.clip(
|
|
697
|
+
joint_pos, self._joint_limit[:, 0], self._joint_limit[:, 1]
|
|
698
|
+
)
|
|
699
|
+
|
|
659
700
|
self._send_position_command(joint_pos)
|
|
660
701
|
|
|
661
702
|
if wait_time > 0.0:
|
|
703
|
+
self._wait_for_position(
|
|
704
|
+
joint_pos, wait_time, exit_on_reach, exit_on_reach_kwargs
|
|
705
|
+
)
|
|
706
|
+
|
|
707
|
+
def _wait_for_position(
|
|
708
|
+
self,
|
|
709
|
+
joint_pos: Float[np.ndarray, " N"] | list[float] | dict[str, float],
|
|
710
|
+
wait_time: float,
|
|
711
|
+
exit_on_reach: bool = False,
|
|
712
|
+
exit_on_reach_kwargs: dict[str, Any] | None = None,
|
|
713
|
+
) -> None:
|
|
714
|
+
"""Wait for a specified time with optional early exit when position is reached.
|
|
715
|
+
|
|
716
|
+
Args:
|
|
717
|
+
joint_pos: Target joint positions to check against.
|
|
718
|
+
wait_time: Maximum time to wait in seconds.
|
|
719
|
+
exit_on_reach: If True, exit early when joint positions are reached.
|
|
720
|
+
exit_on_reach_kwargs: Optional parameters for position checking.
|
|
721
|
+
"""
|
|
722
|
+
if exit_on_reach:
|
|
723
|
+
# Set default tolerance if not provided
|
|
724
|
+
exit_on_reach_kwargs = exit_on_reach_kwargs or {}
|
|
725
|
+
exit_on_reach_kwargs.setdefault("tolerance", 0.05)
|
|
726
|
+
|
|
727
|
+
# Convert to expected format for is_joint_pos_reached
|
|
728
|
+
if isinstance(joint_pos, list):
|
|
729
|
+
joint_pos = np.array(joint_pos, dtype=np.float32)
|
|
730
|
+
|
|
731
|
+
# Wait until position is reached or timeout
|
|
732
|
+
start_time = time.time()
|
|
733
|
+
while time.time() - start_time < wait_time:
|
|
734
|
+
if self.is_joint_pos_reached(joint_pos, **exit_on_reach_kwargs):
|
|
735
|
+
break
|
|
736
|
+
time.sleep(0.01)
|
|
737
|
+
else:
|
|
662
738
|
time.sleep(wait_time)
|
|
663
739
|
|
|
664
740
|
def _send_position_command(self, joint_pos: Float[np.ndarray, " N"]) -> None:
|
|
@@ -679,12 +755,16 @@ class RobotJointComponent(RobotComponent):
|
|
|
679
755
|
self,
|
|
680
756
|
pose_name: str,
|
|
681
757
|
wait_time: float = 3.0,
|
|
758
|
+
exit_on_reach: bool = False,
|
|
759
|
+
exit_on_reach_kwargs: dict[str, float] | None = None,
|
|
682
760
|
) -> None:
|
|
683
761
|
"""Move the component to a predefined pose.
|
|
684
762
|
|
|
685
763
|
Args:
|
|
686
764
|
pose_name: Name of the pose to move to.
|
|
687
765
|
wait_time: Time to wait for the component to reach the pose.
|
|
766
|
+
exit_on_reach: If True, the function will exit when the joint positions are reached.
|
|
767
|
+
exit_on_reach_kwargs: Optional parameters for exit when the joint positions are reached.
|
|
688
768
|
|
|
689
769
|
Raises:
|
|
690
770
|
ValueError: If pose pool is not available or if an invalid pose name is provided.
|
|
@@ -696,7 +776,12 @@ class RobotJointComponent(RobotComponent):
|
|
|
696
776
|
f"Invalid pose name: {pose_name}. Available poses: {list(self._pose_pool.keys())}"
|
|
697
777
|
)
|
|
698
778
|
pose = self._pose_pool[pose_name]
|
|
699
|
-
self.set_joint_pos(
|
|
779
|
+
self.set_joint_pos(
|
|
780
|
+
pose,
|
|
781
|
+
wait_time=wait_time,
|
|
782
|
+
exit_on_reach=exit_on_reach,
|
|
783
|
+
exit_on_reach_kwargs=exit_on_reach_kwargs,
|
|
784
|
+
)
|
|
700
785
|
|
|
701
786
|
def is_joint_pos_reached(
|
|
702
787
|
self,
|
|
@@ -819,11 +904,12 @@ class RobotJointComponent(RobotComponent):
|
|
|
819
904
|
else:
|
|
820
905
|
# Check all joints, ensuring arrays are same length
|
|
821
906
|
min_len = min(len(current_pos), len(target_pos))
|
|
822
|
-
|
|
907
|
+
is_reached = bool(
|
|
823
908
|
np.all(
|
|
824
909
|
np.abs(current_pos[:min_len] - target_pos[:min_len]) <= tolerance
|
|
825
910
|
)
|
|
826
911
|
)
|
|
912
|
+
return is_reached
|
|
827
913
|
|
|
828
914
|
def is_pose_reached(
|
|
829
915
|
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.
|
|
55
|
-
self._joint_pos_close = np.array(configs.
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
@@ -58,74 +58,86 @@ class Head(RobotJointComponent):
|
|
|
58
58
|
state_message_type=dexcontrol_msg_pb2.HeadState,
|
|
59
59
|
zenoh_session=zenoh_session,
|
|
60
60
|
joint_name=configs.joint_name,
|
|
61
|
+
joint_limit=configs.joint_limit
|
|
62
|
+
if hasattr(configs, "joint_limit")
|
|
63
|
+
else None,
|
|
64
|
+
joint_vel_limit=configs.joint_vel_limit
|
|
65
|
+
if hasattr(configs, "joint_vel_limit")
|
|
66
|
+
else None,
|
|
61
67
|
pose_pool=configs.pose_pool,
|
|
62
68
|
)
|
|
63
69
|
|
|
64
70
|
self.mode_querier: Final[zenoh.Querier] = zenoh_session.declare_querier(
|
|
65
71
|
resolve_key_name(configs.set_mode_query), timeout=2.0
|
|
66
72
|
)
|
|
67
|
-
self.
|
|
68
|
-
self.max_vel: Final[float] = configs.max_vel
|
|
69
|
-
assert self.max_vel > self.default_vel, (
|
|
70
|
-
"max_vel must be greater than default_vel"
|
|
71
|
-
)
|
|
72
|
-
self._joint_limit: Float[np.ndarray, "3 2"] = np.stack(
|
|
73
|
-
[configs.joint_limit_lower, configs.joint_limit_upper], axis=1
|
|
74
|
-
)
|
|
73
|
+
assert self._joint_vel_limit is not None, "joint_vel_limit is not set"
|
|
75
74
|
|
|
76
75
|
def set_joint_pos(
|
|
77
76
|
self,
|
|
78
|
-
joint_pos: Float[np.ndarray, "
|
|
77
|
+
joint_pos: Float[np.ndarray, "3"] | list[float] | dict[str, float],
|
|
79
78
|
relative: bool = False,
|
|
80
79
|
wait_time: float = 0.0,
|
|
81
80
|
wait_kwargs: dict[str, float] | None = None,
|
|
81
|
+
exit_on_reach: bool = False,
|
|
82
|
+
exit_on_reach_kwargs: dict[str, float] | None = None,
|
|
82
83
|
) -> None:
|
|
83
84
|
"""Send joint position control commands to the head.
|
|
84
85
|
|
|
85
86
|
Args:
|
|
86
87
|
joint_pos: Joint positions as either:
|
|
87
88
|
- List of joint values [j1, j2]
|
|
88
|
-
- Numpy array with shape (
|
|
89
|
+
- Numpy array with shape (3,), in radians
|
|
89
90
|
- Dictionary mapping joint names to position values
|
|
90
91
|
relative: If True, the joint positions are relative to the current position.
|
|
91
92
|
wait_time: Time to wait after sending command in seconds. If 0, returns
|
|
92
93
|
immediately after sending command.
|
|
93
94
|
wait_kwargs: Optional parameters for trajectory generation (not used in Head).
|
|
95
|
+
exit_on_reach: If True, the function will exit when the joint positions are reached.
|
|
96
|
+
exit_on_reach_kwargs: Optional parameters for exit when the joint positions are reached.
|
|
94
97
|
|
|
95
98
|
Raises:
|
|
96
99
|
ValueError: If joint_pos dictionary contains invalid joint names.
|
|
97
100
|
"""
|
|
98
101
|
self.set_joint_pos_vel(
|
|
99
|
-
joint_pos,
|
|
102
|
+
joint_pos,
|
|
103
|
+
joint_vel=None,
|
|
104
|
+
relative=relative,
|
|
105
|
+
wait_time=wait_time,
|
|
106
|
+
exit_on_reach=exit_on_reach,
|
|
107
|
+
exit_on_reach_kwargs=exit_on_reach_kwargs,
|
|
100
108
|
)
|
|
101
109
|
|
|
102
110
|
def set_joint_pos_vel(
|
|
103
111
|
self,
|
|
104
|
-
joint_pos: Float[np.ndarray, "
|
|
105
|
-
joint_vel: Float[np.ndarray, "
|
|
112
|
+
joint_pos: Float[np.ndarray, "3"] | list[float] | dict[str, float],
|
|
113
|
+
joint_vel: Float[np.ndarray, "3"]
|
|
106
114
|
| list[float]
|
|
107
115
|
| dict[str, float]
|
|
108
116
|
| float
|
|
109
117
|
| None = None,
|
|
110
118
|
relative: bool = False,
|
|
111
119
|
wait_time: float = 0.0,
|
|
120
|
+
exit_on_reach: bool = False,
|
|
121
|
+
exit_on_reach_kwargs: dict[str, float] | None = None,
|
|
112
122
|
) -> None:
|
|
113
123
|
"""Send control commands to the head.
|
|
114
124
|
|
|
115
125
|
Args:
|
|
116
126
|
joint_pos: Joint positions as either:
|
|
117
127
|
- List of joint values [j1, j2]
|
|
118
|
-
- Numpy array with shape (
|
|
128
|
+
- Numpy array with shape (3,), in radians
|
|
119
129
|
- Dictionary mapping joint names to position values
|
|
120
130
|
joint_vel: Optional joint velocities as either:
|
|
121
131
|
- List of joint values [v1, v2]
|
|
122
|
-
- Numpy array with shape (
|
|
132
|
+
- Numpy array with shape (3,), in rad/s
|
|
123
133
|
- Dictionary mapping joint names to velocity values
|
|
124
134
|
- Single float value to be applied to all joints
|
|
125
135
|
If None, velocities are calculated based on default velocity setting.
|
|
126
136
|
relative: If True, the joint positions are relative to the current position.
|
|
127
137
|
wait_time: Time to wait after sending command in seconds. If 0, returns
|
|
128
138
|
immediately after sending command.
|
|
139
|
+
exit_on_reach: If True, the function will exit when the joint positions are reached.
|
|
140
|
+
exit_on_reach_kwargs: Optional parameters for exit when the joint positions are reached.
|
|
129
141
|
|
|
130
142
|
Raises:
|
|
131
143
|
ValueError: If wait_time is negative or joint_pos dictionary contains
|
|
@@ -142,6 +154,15 @@ class Head(RobotJointComponent):
|
|
|
142
154
|
joint_pos = self._convert_joint_cmd_to_array(joint_pos)
|
|
143
155
|
joint_vel = self._process_joint_velocities(joint_vel, joint_pos)
|
|
144
156
|
|
|
157
|
+
if self._joint_limit is not None:
|
|
158
|
+
joint_pos = np.clip(
|
|
159
|
+
joint_pos, self._joint_limit[:, 0], self._joint_limit[:, 1]
|
|
160
|
+
)
|
|
161
|
+
if self._joint_vel_limit is not None:
|
|
162
|
+
joint_vel = np.clip(
|
|
163
|
+
joint_vel, -self._joint_vel_limit, self._joint_vel_limit
|
|
164
|
+
)
|
|
165
|
+
|
|
145
166
|
# Create and send control message
|
|
146
167
|
control_msg = dexcontrol_msg_pb2.HeadCommand()
|
|
147
168
|
control_msg.joint_pos.extend(joint_pos.tolist())
|
|
@@ -149,8 +170,12 @@ class Head(RobotJointComponent):
|
|
|
149
170
|
self._publish_control(control_msg)
|
|
150
171
|
|
|
151
172
|
# Wait if specified
|
|
152
|
-
|
|
153
|
-
|
|
173
|
+
self._wait_for_position(
|
|
174
|
+
joint_pos=joint_pos,
|
|
175
|
+
wait_time=wait_time,
|
|
176
|
+
exit_on_reach=exit_on_reach,
|
|
177
|
+
exit_on_reach_kwargs=exit_on_reach_kwargs,
|
|
178
|
+
)
|
|
154
179
|
|
|
155
180
|
def set_mode(self, mode: Literal["enable", "disable"]) -> None:
|
|
156
181
|
"""Set the operating mode of the head.
|
|
@@ -180,12 +205,12 @@ class Head(RobotJointComponent):
|
|
|
180
205
|
logger.info(reply.ok.payload.to_string())
|
|
181
206
|
time.sleep(0.5)
|
|
182
207
|
|
|
183
|
-
def get_joint_limit(self) -> Float[np.ndarray, "3 2"]:
|
|
208
|
+
def get_joint_limit(self) -> Float[np.ndarray, "3 2"] | None:
|
|
184
209
|
"""Get the joint limits of the head.
|
|
185
210
|
|
|
186
211
|
Returns:
|
|
187
212
|
Array of joint limits with shape (3, 2), where the first column contains
|
|
188
|
-
lower limits and the second column contains upper limits.
|
|
213
|
+
lower limits and the second column contains upper limits, or None if not configured.
|
|
189
214
|
"""
|
|
190
215
|
return self._joint_limit
|
|
191
216
|
|
|
@@ -212,7 +237,7 @@ class Head(RobotJointComponent):
|
|
|
212
237
|
|
|
213
238
|
def _process_joint_velocities(
|
|
214
239
|
self,
|
|
215
|
-
joint_vel: Float[np.ndarray, "
|
|
240
|
+
joint_vel: Float[np.ndarray, "3"]
|
|
216
241
|
| list[float]
|
|
217
242
|
| dict[str, float]
|
|
218
243
|
| float
|
|
@@ -234,14 +259,18 @@ class Head(RobotJointComponent):
|
|
|
234
259
|
motion_norm = np.linalg.norm(joint_motion)
|
|
235
260
|
|
|
236
261
|
if motion_norm < 1e-6: # Avoid division by zero
|
|
237
|
-
return np.zeros(
|
|
262
|
+
return np.zeros(3, dtype=np.float32)
|
|
238
263
|
|
|
239
|
-
|
|
240
|
-
|
|
264
|
+
default_vel = (
|
|
265
|
+
2.5 if self._joint_vel_limit is None else np.min(self._joint_vel_limit)
|
|
266
|
+
)
|
|
267
|
+
return (joint_motion / motion_norm) * default_vel
|
|
241
268
|
|
|
242
269
|
if isinstance(joint_vel, (int, float)):
|
|
243
270
|
# Single value - apply to all joints
|
|
244
|
-
return np.full(
|
|
271
|
+
return np.full(3, joint_vel, dtype=np.float32)
|
|
245
272
|
|
|
246
|
-
# Convert to array and clip to
|
|
247
|
-
return self._convert_joint_cmd_to_array(
|
|
273
|
+
# Convert to array and clip to velocity limits
|
|
274
|
+
return self._convert_joint_cmd_to_array(
|
|
275
|
+
joint_vel, clip_value=self._joint_vel_limit
|
|
276
|
+
)
|