dexcontrol 0.2.12__py3-none-any.whl → 0.3.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.
- dexcontrol/__init__.py +18 -8
- dexcontrol/apps/dualsense_teleop_base.py +1 -1
- dexcontrol/comm/__init__.py +51 -0
- dexcontrol/comm/base.py +421 -0
- dexcontrol/comm/rtc.py +400 -0
- dexcontrol/comm/subscribers.py +329 -0
- dexcontrol/config/core/chassis.py +9 -4
- dexcontrol/config/core/hand.py +1 -0
- dexcontrol/config/sensors/cameras/__init__.py +1 -2
- dexcontrol/config/sensors/cameras/zed_camera.py +2 -2
- dexcontrol/config/sensors/vega_sensors.py +12 -18
- dexcontrol/config/vega.py +4 -1
- dexcontrol/core/arm.py +61 -37
- dexcontrol/core/chassis.py +141 -119
- dexcontrol/core/component.py +110 -59
- dexcontrol/core/hand.py +118 -85
- dexcontrol/core/head.py +18 -29
- dexcontrol/core/misc.py +327 -155
- dexcontrol/core/robot_query_interface.py +463 -0
- dexcontrol/core/torso.py +4 -8
- dexcontrol/proto/dexcontrol_msg_pb2.py +27 -39
- dexcontrol/proto/dexcontrol_msg_pb2.pyi +75 -118
- dexcontrol/proto/dexcontrol_query_pb2.py +39 -39
- dexcontrol/proto/dexcontrol_query_pb2.pyi +17 -4
- dexcontrol/robot.py +245 -574
- dexcontrol/sensors/__init__.py +1 -2
- dexcontrol/sensors/camera/__init__.py +0 -2
- dexcontrol/sensors/camera/base_camera.py +144 -0
- dexcontrol/sensors/camera/rgb_camera.py +67 -63
- dexcontrol/sensors/camera/zed_camera.py +89 -147
- dexcontrol/sensors/imu/chassis_imu.py +76 -56
- dexcontrol/sensors/imu/zed_imu.py +54 -43
- dexcontrol/sensors/lidar/rplidar.py +16 -20
- dexcontrol/sensors/manager.py +4 -11
- dexcontrol/sensors/ultrasonic.py +14 -27
- dexcontrol/utils/__init__.py +0 -11
- dexcontrol/utils/comm_helper.py +111 -0
- dexcontrol/utils/constants.py +1 -1
- dexcontrol/utils/os_utils.py +169 -1
- dexcontrol/utils/pb_utils.py +0 -22
- {dexcontrol-0.2.12.dist-info → dexcontrol-0.3.1.dist-info}/METADATA +13 -1
- dexcontrol-0.3.1.dist-info/RECORD +68 -0
- dexcontrol/config/sensors/cameras/luxonis_camera.py +0 -51
- dexcontrol/sensors/camera/luxonis_camera.py +0 -169
- dexcontrol/utils/rate_limiter.py +0 -172
- dexcontrol/utils/rtc_utils.py +0 -144
- dexcontrol/utils/subscribers/__init__.py +0 -52
- dexcontrol/utils/subscribers/base.py +0 -281
- dexcontrol/utils/subscribers/camera.py +0 -332
- dexcontrol/utils/subscribers/decoders.py +0 -88
- dexcontrol/utils/subscribers/generic.py +0 -110
- dexcontrol/utils/subscribers/imu.py +0 -175
- dexcontrol/utils/subscribers/lidar.py +0 -172
- dexcontrol/utils/subscribers/protobuf.py +0 -111
- dexcontrol/utils/subscribers/rtc.py +0 -316
- dexcontrol/utils/zenoh_utils.py +0 -122
- dexcontrol-0.2.12.dist-info/RECORD +0 -75
- {dexcontrol-0.2.12.dist-info → dexcontrol-0.3.1.dist-info}/WHEEL +0 -0
- {dexcontrol-0.2.12.dist-info → dexcontrol-0.3.1.dist-info}/licenses/LICENSE +0 -0
dexcontrol/core/hand.py
CHANGED
|
@@ -14,17 +14,24 @@ 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
|
|
17
|
+
from enum import Enum
|
|
18
|
+
from typing import Any, cast
|
|
18
19
|
|
|
19
20
|
import numpy as np
|
|
20
|
-
import zenoh
|
|
21
21
|
from jaxtyping import Float
|
|
22
|
+
from loguru import logger
|
|
22
23
|
|
|
23
24
|
from dexcontrol.config.core.hand import HandConfig
|
|
24
|
-
from dexcontrol.core.component import RobotJointComponent
|
|
25
|
+
from dexcontrol.core.component import RobotComponent, RobotJointComponent
|
|
25
26
|
from dexcontrol.proto import dexcontrol_msg_pb2
|
|
26
27
|
|
|
27
28
|
|
|
29
|
+
class HandType(Enum):
|
|
30
|
+
UNKNOWN = "UNKNOWN"
|
|
31
|
+
HandF5D6_V1 = "HandF5D6_V1"
|
|
32
|
+
HandF5D6_V2 = "HandF5D6_V2"
|
|
33
|
+
|
|
34
|
+
|
|
28
35
|
class Hand(RobotJointComponent):
|
|
29
36
|
"""Robot hand control class.
|
|
30
37
|
|
|
@@ -35,20 +42,18 @@ class Hand(RobotJointComponent):
|
|
|
35
42
|
def __init__(
|
|
36
43
|
self,
|
|
37
44
|
configs: HandConfig,
|
|
38
|
-
|
|
45
|
+
hand_type: HandType = HandType.HandF5D6_V1,
|
|
39
46
|
) -> None:
|
|
40
47
|
"""Initialize the hand controller.
|
|
41
48
|
|
|
42
49
|
Args:
|
|
43
50
|
configs: Hand configuration parameters containing communication topics
|
|
44
51
|
and predefined hand positions.
|
|
45
|
-
zenoh_session: Active Zenoh communication session for message passing.
|
|
46
52
|
"""
|
|
47
53
|
super().__init__(
|
|
48
54
|
state_sub_topic=configs.state_sub_topic,
|
|
49
55
|
control_pub_topic=configs.control_pub_topic,
|
|
50
|
-
state_message_type=dexcontrol_msg_pb2.
|
|
51
|
-
zenoh_session=zenoh_session,
|
|
56
|
+
state_message_type=dexcontrol_msg_pb2.MotorStateWithCurrent,
|
|
52
57
|
joint_name=configs.joint_name,
|
|
53
58
|
)
|
|
54
59
|
|
|
@@ -64,45 +69,11 @@ class Hand(RobotJointComponent):
|
|
|
64
69
|
Args:
|
|
65
70
|
joint_pos: Joint positions as list or numpy array.
|
|
66
71
|
"""
|
|
67
|
-
control_msg = dexcontrol_msg_pb2.
|
|
72
|
+
control_msg = dexcontrol_msg_pb2.MotorPosCommand()
|
|
68
73
|
joint_pos_array = self._convert_joint_cmd_to_array(joint_pos)
|
|
69
|
-
control_msg.
|
|
74
|
+
control_msg.pos.extend(joint_pos_array.tolist())
|
|
70
75
|
self._publish_control(control_msg)
|
|
71
76
|
|
|
72
|
-
def set_joint_pos(
|
|
73
|
-
self,
|
|
74
|
-
joint_pos: Float[np.ndarray, " N"] | list[float] | dict[str, float],
|
|
75
|
-
relative: bool = False,
|
|
76
|
-
wait_time: float = 0.0,
|
|
77
|
-
wait_kwargs: dict[str, float] | None = None,
|
|
78
|
-
exit_on_reach: bool = False,
|
|
79
|
-
exit_on_reach_kwargs: dict[str, Any] | None = None,
|
|
80
|
-
) -> None:
|
|
81
|
-
"""Send joint position control commands to the hand.
|
|
82
|
-
|
|
83
|
-
Args:
|
|
84
|
-
joint_pos: Joint positions as either:
|
|
85
|
-
- List of joint values [j1, j2, ..., jN]
|
|
86
|
-
- Numpy array with shape (N,)
|
|
87
|
-
- Dictionary mapping joint names to position values
|
|
88
|
-
relative: If True, the joint positions are relative to the current position.
|
|
89
|
-
wait_time: Time to wait after setting the joint positions.
|
|
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.
|
|
93
|
-
|
|
94
|
-
Raises:
|
|
95
|
-
ValueError: If joint_pos dictionary contains invalid joint names.
|
|
96
|
-
"""
|
|
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
|
-
)
|
|
105
|
-
|
|
106
77
|
def open_hand(
|
|
107
78
|
self,
|
|
108
79
|
wait_time: float = 0.0,
|
|
@@ -152,6 +123,34 @@ class HandF5D6(Hand):
|
|
|
152
123
|
the F5D6 hand model.
|
|
153
124
|
"""
|
|
154
125
|
|
|
126
|
+
def __init__(
|
|
127
|
+
self,
|
|
128
|
+
configs: HandConfig,
|
|
129
|
+
hand_type: HandType = HandType.HandF5D6_V1,
|
|
130
|
+
) -> None:
|
|
131
|
+
super().__init__(configs)
|
|
132
|
+
|
|
133
|
+
# Initialize touch sensor for F5D6_V2 hands
|
|
134
|
+
self._hand_type = hand_type
|
|
135
|
+
if self._hand_type == HandType.HandF5D6_V2:
|
|
136
|
+
self._touch_sensor = HandF5D6TouchSensor(configs.touch_sensor_sub_topic)
|
|
137
|
+
elif self._hand_type == HandType.HandF5D6_V1:
|
|
138
|
+
self._touch_sensor = None
|
|
139
|
+
else:
|
|
140
|
+
raise ValueError(f"Invalid hand type: {self._hand_type}")
|
|
141
|
+
|
|
142
|
+
def get_finger_tip_force(self) -> Float[np.ndarray, "5"]:
|
|
143
|
+
"""Get the force at the finger tips.
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
Array of force values at the finger tips.
|
|
147
|
+
"""
|
|
148
|
+
if self._touch_sensor is None:
|
|
149
|
+
raise ValueError(
|
|
150
|
+
f"Touch sensor not available for this hand type: {self._hand_type}"
|
|
151
|
+
)
|
|
152
|
+
return self._touch_sensor.get_fingertip_touch_net_force()
|
|
153
|
+
|
|
155
154
|
def close_hand(
|
|
156
155
|
self,
|
|
157
156
|
wait_time: float = 0.0,
|
|
@@ -159,27 +158,30 @@ class HandF5D6(Hand):
|
|
|
159
158
|
exit_on_reach_kwargs: dict[str, Any] | None = None,
|
|
160
159
|
) -> None:
|
|
161
160
|
"""Close the hand fully using a two-step approach for better control."""
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
self.
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
161
|
+
try:
|
|
162
|
+
if self.is_joint_pos_reached(self._joint_pos_close, tolerance=0.1):
|
|
163
|
+
return
|
|
164
|
+
|
|
165
|
+
# First step: Move to intermediate position
|
|
166
|
+
intermediate_pos = self._get_intermediate_close_position()
|
|
167
|
+
first_step_wait_time = 0.8
|
|
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
|
+
)
|
|
174
|
+
|
|
175
|
+
# Second step: Move to final closed position
|
|
176
|
+
remaining_wait_time = max(0.0, wait_time - first_step_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
|
+
)
|
|
183
|
+
except Exception as e:
|
|
184
|
+
logger.warning(f"Failed to close hand: {e}")
|
|
183
185
|
|
|
184
186
|
def open_hand(
|
|
185
187
|
self,
|
|
@@ -188,27 +190,30 @@ class HandF5D6(Hand):
|
|
|
188
190
|
exit_on_reach_kwargs: dict[str, Any] | None = None,
|
|
189
191
|
) -> None:
|
|
190
192
|
"""Open the hand fully using a two-step approach for better control."""
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
self.
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
193
|
+
try:
|
|
194
|
+
if self.is_joint_pos_reached(self._joint_pos_open, tolerance=0.1):
|
|
195
|
+
return
|
|
196
|
+
|
|
197
|
+
# First step: Move to intermediate position
|
|
198
|
+
intermediate_pos = self._get_intermediate_open_position()
|
|
199
|
+
first_step_wait_time = 0.3
|
|
200
|
+
self.set_joint_pos(
|
|
201
|
+
intermediate_pos,
|
|
202
|
+
wait_time=first_step_wait_time,
|
|
203
|
+
exit_on_reach=exit_on_reach,
|
|
204
|
+
exit_on_reach_kwargs=exit_on_reach_kwargs,
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
# Second step: Move to final open position
|
|
208
|
+
remaining_wait_time = max(0.0, wait_time - first_step_wait_time)
|
|
209
|
+
self.set_joint_pos(
|
|
210
|
+
self._joint_pos_open,
|
|
211
|
+
wait_time=remaining_wait_time,
|
|
212
|
+
exit_on_reach=exit_on_reach,
|
|
213
|
+
exit_on_reach_kwargs=exit_on_reach_kwargs,
|
|
214
|
+
)
|
|
215
|
+
except Exception as e:
|
|
216
|
+
logger.warning(f"Failed to open hand: {e}")
|
|
212
217
|
|
|
213
218
|
def _get_intermediate_close_position(self) -> np.ndarray:
|
|
214
219
|
"""Get intermediate position for closing hand.
|
|
@@ -241,3 +246,31 @@ class HandF5D6(Hand):
|
|
|
241
246
|
0
|
|
242
247
|
] * (1 - ratio)
|
|
243
248
|
return intermediate_pos
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
class HandF5D6TouchSensor(RobotComponent):
|
|
252
|
+
"""Wrench sensor reader for the robot arm.
|
|
253
|
+
|
|
254
|
+
This class provides methods to read wrench sensor data through Zenoh communication.
|
|
255
|
+
"""
|
|
256
|
+
|
|
257
|
+
def __init__(self, state_sub_topic: str) -> None:
|
|
258
|
+
"""Initialize the wrench sensor reader.
|
|
259
|
+
|
|
260
|
+
Args:
|
|
261
|
+
state_sub_topic: Topic to subscribe to for wrench sensor data.
|
|
262
|
+
"""
|
|
263
|
+
super().__init__(
|
|
264
|
+
state_sub_topic=state_sub_topic,
|
|
265
|
+
state_message_type=dexcontrol_msg_pb2.HandTouchSensorState,
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
def get_fingertip_touch_net_force(self) -> Float[np.ndarray, "5"]:
|
|
269
|
+
"""Get the complete wrench sensor state.
|
|
270
|
+
|
|
271
|
+
Returns:
|
|
272
|
+
Dictionary containing wrench values and button states.
|
|
273
|
+
"""
|
|
274
|
+
state = self._get_state()
|
|
275
|
+
hand_touch_state = cast(dexcontrol_msg_pb2.HandTouchSensorState, state)
|
|
276
|
+
return np.array(hand_touch_state.force)
|
dexcontrol/core/head.py
CHANGED
|
@@ -15,17 +15,16 @@ communication. It handles joint position and velocity control, mode setting, and
|
|
|
15
15
|
state monitoring.
|
|
16
16
|
"""
|
|
17
17
|
|
|
18
|
-
import
|
|
19
|
-
from typing import Final, Literal
|
|
18
|
+
from typing import Literal
|
|
20
19
|
|
|
21
20
|
import numpy as np
|
|
22
|
-
import
|
|
21
|
+
from dexcomm import call_service
|
|
23
22
|
from jaxtyping import Float
|
|
24
|
-
from loguru import logger
|
|
25
23
|
|
|
26
24
|
from dexcontrol.config.core import HeadConfig
|
|
27
25
|
from dexcontrol.core.component import RobotJointComponent
|
|
28
26
|
from dexcontrol.proto import dexcontrol_msg_pb2, dexcontrol_query_pb2
|
|
27
|
+
from dexcontrol.utils.comm_helper import get_zenoh_config_path
|
|
29
28
|
from dexcontrol.utils.os_utils import resolve_key_name
|
|
30
29
|
|
|
31
30
|
|
|
@@ -44,19 +43,16 @@ class Head(RobotJointComponent):
|
|
|
44
43
|
def __init__(
|
|
45
44
|
self,
|
|
46
45
|
configs: HeadConfig,
|
|
47
|
-
zenoh_session: zenoh.Session,
|
|
48
46
|
) -> None:
|
|
49
47
|
"""Initialize the head controller.
|
|
50
48
|
|
|
51
49
|
Args:
|
|
52
50
|
configs: Configuration parameters for the head including communication topics.
|
|
53
|
-
zenoh_session: Active Zenoh communication session for message passing.
|
|
54
51
|
"""
|
|
55
52
|
super().__init__(
|
|
56
53
|
state_sub_topic=configs.state_sub_topic,
|
|
57
54
|
control_pub_topic=configs.control_pub_topic,
|
|
58
|
-
state_message_type=dexcontrol_msg_pb2.
|
|
59
|
-
zenoh_session=zenoh_session,
|
|
55
|
+
state_message_type=dexcontrol_msg_pb2.MotorStateWithTorque,
|
|
60
56
|
joint_name=configs.joint_name,
|
|
61
57
|
joint_limit=configs.joint_limit
|
|
62
58
|
if hasattr(configs, "joint_limit")
|
|
@@ -67,9 +63,8 @@ class Head(RobotJointComponent):
|
|
|
67
63
|
pose_pool=configs.pose_pool,
|
|
68
64
|
)
|
|
69
65
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
)
|
|
66
|
+
# Store the query topic for later use with DexComm
|
|
67
|
+
self._mode_query_topic = resolve_key_name(configs.set_mode_query)
|
|
73
68
|
assert self._joint_vel_limit is not None, "joint_vel_limit is not set"
|
|
74
69
|
|
|
75
70
|
def set_joint_pos(
|
|
@@ -164,9 +159,9 @@ class Head(RobotJointComponent):
|
|
|
164
159
|
)
|
|
165
160
|
|
|
166
161
|
# Create and send control message
|
|
167
|
-
control_msg = dexcontrol_msg_pb2.
|
|
168
|
-
control_msg.
|
|
169
|
-
control_msg.
|
|
162
|
+
control_msg = dexcontrol_msg_pb2.MotorPosVelCommand()
|
|
163
|
+
control_msg.pos.extend(joint_pos.tolist())
|
|
164
|
+
control_msg.vel.extend(joint_vel.tolist())
|
|
170
165
|
self._publish_control(control_msg)
|
|
171
166
|
|
|
172
167
|
# Wait if specified
|
|
@@ -198,12 +193,15 @@ class Head(RobotJointComponent):
|
|
|
198
193
|
|
|
199
194
|
query_msg = dexcontrol_query_pb2.SetHeadMode()
|
|
200
195
|
query_msg.mode = mode_map[mode]
|
|
201
|
-
replies = self.mode_querier.get(payload=query_msg.SerializeToString())
|
|
202
196
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
197
|
+
call_service(
|
|
198
|
+
self._mode_query_topic,
|
|
199
|
+
request=query_msg,
|
|
200
|
+
timeout=1.0,
|
|
201
|
+
config=get_zenoh_config_path(),
|
|
202
|
+
request_serializer=lambda x: x.SerializeToString(),
|
|
203
|
+
response_deserializer=None,
|
|
204
|
+
)
|
|
207
205
|
|
|
208
206
|
def get_joint_limit(self) -> Float[np.ndarray, "3 2"] | None:
|
|
209
207
|
"""Get the joint limits of the head.
|
|
@@ -224,16 +222,7 @@ class Head(RobotJointComponent):
|
|
|
224
222
|
"""Clean up Zenoh resources for the head component."""
|
|
225
223
|
self.stop()
|
|
226
224
|
super().shutdown()
|
|
227
|
-
|
|
228
|
-
if hasattr(self, "mode_querier") and self.mode_querier:
|
|
229
|
-
self.mode_querier.undeclare()
|
|
230
|
-
except Exception as e:
|
|
231
|
-
# Don't log "Undeclared querier" errors as warnings - they're expected during shutdown
|
|
232
|
-
error_msg = str(e).lower()
|
|
233
|
-
if not ("undeclared" in error_msg or "closed" in error_msg):
|
|
234
|
-
logger.warning(
|
|
235
|
-
f"Error undeclaring mode querier for {self.__class__.__name__}: {e}"
|
|
236
|
-
)
|
|
225
|
+
# No need to undeclare queriers when using DexComm
|
|
237
226
|
|
|
238
227
|
def _process_joint_velocities(
|
|
239
228
|
self,
|