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.

Files changed (59) hide show
  1. dexcontrol/__init__.py +18 -8
  2. dexcontrol/apps/dualsense_teleop_base.py +1 -1
  3. dexcontrol/comm/__init__.py +51 -0
  4. dexcontrol/comm/base.py +421 -0
  5. dexcontrol/comm/rtc.py +400 -0
  6. dexcontrol/comm/subscribers.py +329 -0
  7. dexcontrol/config/core/chassis.py +9 -4
  8. dexcontrol/config/core/hand.py +1 -0
  9. dexcontrol/config/sensors/cameras/__init__.py +1 -2
  10. dexcontrol/config/sensors/cameras/zed_camera.py +2 -2
  11. dexcontrol/config/sensors/vega_sensors.py +12 -18
  12. dexcontrol/config/vega.py +4 -1
  13. dexcontrol/core/arm.py +61 -37
  14. dexcontrol/core/chassis.py +141 -119
  15. dexcontrol/core/component.py +110 -59
  16. dexcontrol/core/hand.py +118 -85
  17. dexcontrol/core/head.py +18 -29
  18. dexcontrol/core/misc.py +327 -155
  19. dexcontrol/core/robot_query_interface.py +463 -0
  20. dexcontrol/core/torso.py +4 -8
  21. dexcontrol/proto/dexcontrol_msg_pb2.py +27 -39
  22. dexcontrol/proto/dexcontrol_msg_pb2.pyi +75 -118
  23. dexcontrol/proto/dexcontrol_query_pb2.py +39 -39
  24. dexcontrol/proto/dexcontrol_query_pb2.pyi +17 -4
  25. dexcontrol/robot.py +245 -574
  26. dexcontrol/sensors/__init__.py +1 -2
  27. dexcontrol/sensors/camera/__init__.py +0 -2
  28. dexcontrol/sensors/camera/base_camera.py +144 -0
  29. dexcontrol/sensors/camera/rgb_camera.py +67 -63
  30. dexcontrol/sensors/camera/zed_camera.py +89 -147
  31. dexcontrol/sensors/imu/chassis_imu.py +76 -56
  32. dexcontrol/sensors/imu/zed_imu.py +54 -43
  33. dexcontrol/sensors/lidar/rplidar.py +16 -20
  34. dexcontrol/sensors/manager.py +4 -11
  35. dexcontrol/sensors/ultrasonic.py +14 -27
  36. dexcontrol/utils/__init__.py +0 -11
  37. dexcontrol/utils/comm_helper.py +111 -0
  38. dexcontrol/utils/constants.py +1 -1
  39. dexcontrol/utils/os_utils.py +169 -1
  40. dexcontrol/utils/pb_utils.py +0 -22
  41. {dexcontrol-0.2.12.dist-info → dexcontrol-0.3.1.dist-info}/METADATA +13 -1
  42. dexcontrol-0.3.1.dist-info/RECORD +68 -0
  43. dexcontrol/config/sensors/cameras/luxonis_camera.py +0 -51
  44. dexcontrol/sensors/camera/luxonis_camera.py +0 -169
  45. dexcontrol/utils/rate_limiter.py +0 -172
  46. dexcontrol/utils/rtc_utils.py +0 -144
  47. dexcontrol/utils/subscribers/__init__.py +0 -52
  48. dexcontrol/utils/subscribers/base.py +0 -281
  49. dexcontrol/utils/subscribers/camera.py +0 -332
  50. dexcontrol/utils/subscribers/decoders.py +0 -88
  51. dexcontrol/utils/subscribers/generic.py +0 -110
  52. dexcontrol/utils/subscribers/imu.py +0 -175
  53. dexcontrol/utils/subscribers/lidar.py +0 -172
  54. dexcontrol/utils/subscribers/protobuf.py +0 -111
  55. dexcontrol/utils/subscribers/rtc.py +0 -316
  56. dexcontrol/utils/zenoh_utils.py +0 -122
  57. dexcontrol-0.2.12.dist-info/RECORD +0 -75
  58. {dexcontrol-0.2.12.dist-info → dexcontrol-0.3.1.dist-info}/WHEEL +0 -0
  59. {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 typing import Any
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
- zenoh_session: zenoh.Session,
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.HandState,
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.HandCommand()
72
+ control_msg = dexcontrol_msg_pb2.MotorPosCommand()
68
73
  joint_pos_array = self._convert_joint_cmd_to_array(joint_pos)
69
- control_msg.joint_pos.extend(joint_pos_array.tolist())
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
- 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
- )
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
- if self.is_joint_pos_reached(self._joint_pos_open, tolerance=0.1):
192
- return
193
-
194
- # First step: Move to intermediate position
195
- intermediate_pos = self._get_intermediate_open_position()
196
- first_step_wait_time = 0.3
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
- )
203
-
204
- # Second step: Move to final open position
205
- remaining_wait_time = max(0.0, wait_time - first_step_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
- )
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 time
19
- from typing import Final, Literal
18
+ from typing import Literal
20
19
 
21
20
  import numpy as np
22
- import zenoh
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.HeadState,
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
- self.mode_querier: Final[zenoh.Querier] = zenoh_session.declare_querier(
71
- resolve_key_name(configs.set_mode_query), timeout=2.0
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.HeadCommand()
168
- control_msg.joint_pos.extend(joint_pos.tolist())
169
- control_msg.joint_vel.extend(joint_vel.tolist())
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
- for reply in replies:
204
- if reply.ok is not None and reply.ok.payload is not None:
205
- logger.info(reply.ok.payload.to_string())
206
- time.sleep(0.5)
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
- try:
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,