dexcontrol 0.2.12__py3-none-any.whl → 0.3.4__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.
Files changed (60) hide show
  1. dexcontrol/__init__.py +17 -8
  2. dexcontrol/apps/dualsense_teleop_base.py +1 -1
  3. dexcontrol/comm/__init__.py +51 -0
  4. dexcontrol/comm/rtc.py +401 -0
  5. dexcontrol/comm/subscribers.py +329 -0
  6. dexcontrol/config/core/chassis.py +9 -4
  7. dexcontrol/config/core/hand.py +1 -0
  8. dexcontrol/config/sensors/cameras/__init__.py +1 -2
  9. dexcontrol/config/sensors/cameras/zed_camera.py +2 -2
  10. dexcontrol/config/sensors/vega_sensors.py +12 -18
  11. dexcontrol/config/vega.py +4 -1
  12. dexcontrol/core/arm.py +66 -42
  13. dexcontrol/core/chassis.py +142 -120
  14. dexcontrol/core/component.py +107 -58
  15. dexcontrol/core/hand.py +119 -86
  16. dexcontrol/core/head.py +22 -33
  17. dexcontrol/core/misc.py +331 -158
  18. dexcontrol/core/robot_query_interface.py +467 -0
  19. dexcontrol/core/torso.py +5 -9
  20. dexcontrol/robot.py +245 -574
  21. dexcontrol/sensors/__init__.py +1 -2
  22. dexcontrol/sensors/camera/__init__.py +0 -2
  23. dexcontrol/sensors/camera/base_camera.py +150 -0
  24. dexcontrol/sensors/camera/rgb_camera.py +68 -64
  25. dexcontrol/sensors/camera/zed_camera.py +140 -164
  26. dexcontrol/sensors/imu/chassis_imu.py +81 -62
  27. dexcontrol/sensors/imu/zed_imu.py +54 -43
  28. dexcontrol/sensors/lidar/rplidar.py +16 -20
  29. dexcontrol/sensors/manager.py +4 -14
  30. dexcontrol/sensors/ultrasonic.py +15 -28
  31. dexcontrol/utils/__init__.py +0 -11
  32. dexcontrol/utils/comm_helper.py +110 -0
  33. dexcontrol/utils/constants.py +1 -1
  34. dexcontrol/utils/error_code.py +2 -4
  35. dexcontrol/utils/os_utils.py +172 -4
  36. dexcontrol/utils/pb_utils.py +6 -28
  37. {dexcontrol-0.2.12.dist-info → dexcontrol-0.3.4.dist-info}/METADATA +16 -3
  38. dexcontrol-0.3.4.dist-info/RECORD +62 -0
  39. {dexcontrol-0.2.12.dist-info → dexcontrol-0.3.4.dist-info}/WHEEL +1 -1
  40. dexcontrol/config/sensors/cameras/luxonis_camera.py +0 -51
  41. dexcontrol/proto/dexcontrol_msg_pb2.py +0 -73
  42. dexcontrol/proto/dexcontrol_msg_pb2.pyi +0 -220
  43. dexcontrol/proto/dexcontrol_query_pb2.py +0 -77
  44. dexcontrol/proto/dexcontrol_query_pb2.pyi +0 -162
  45. dexcontrol/sensors/camera/luxonis_camera.py +0 -169
  46. dexcontrol/utils/motion_utils.py +0 -199
  47. dexcontrol/utils/rate_limiter.py +0 -172
  48. dexcontrol/utils/rtc_utils.py +0 -144
  49. dexcontrol/utils/subscribers/__init__.py +0 -52
  50. dexcontrol/utils/subscribers/base.py +0 -281
  51. dexcontrol/utils/subscribers/camera.py +0 -332
  52. dexcontrol/utils/subscribers/decoders.py +0 -88
  53. dexcontrol/utils/subscribers/generic.py +0 -110
  54. dexcontrol/utils/subscribers/imu.py +0 -175
  55. dexcontrol/utils/subscribers/lidar.py +0 -172
  56. dexcontrol/utils/subscribers/protobuf.py +0 -111
  57. dexcontrol/utils/subscribers/rtc.py +0 -316
  58. dexcontrol/utils/zenoh_utils.py +0 -122
  59. dexcontrol-0.2.12.dist-info/RECORD +0 -75
  60. {dexcontrol-0.2.12.dist-info → dexcontrol-0.3.4.dist-info}/licenses/LICENSE +0 -0
dexcontrol/core/hand.py CHANGED
@@ -14,15 +14,22 @@ 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
+ from dexcomm.serialization.protobuf import control_msg_pb2
21
22
  from jaxtyping import Float
23
+ from loguru import logger
22
24
 
23
25
  from dexcontrol.config.core.hand import HandConfig
24
- from dexcontrol.core.component import RobotJointComponent
25
- from dexcontrol.proto import dexcontrol_msg_pb2
26
+ from dexcontrol.core.component import RobotComponent, RobotJointComponent
27
+
28
+
29
+ class HandType(Enum):
30
+ UNKNOWN = "UNKNOWN"
31
+ HandF5D6_V1 = "HandF5D6_V1"
32
+ HandF5D6_V2 = "HandF5D6_V2"
26
33
 
27
34
 
28
35
  class Hand(RobotJointComponent):
@@ -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=control_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 = control_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=control_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(control_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
22
+ from dexcomm.serialization.protobuf import control_msg_pb2, control_query_pb2
23
23
  from jaxtyping import Float
24
- from loguru import logger
25
24
 
26
25
  from dexcontrol.config.core import HeadConfig
27
26
  from dexcontrol.core.component import RobotJointComponent
28
- 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=control_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 = control_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
@@ -187,8 +182,8 @@ class Head(RobotJointComponent):
187
182
  ValueError: If an invalid mode is specified.
188
183
  """
189
184
  mode_map = {
190
- "enable": dexcontrol_query_pb2.SetHeadMode.Mode.ENABLE,
191
- "disable": dexcontrol_query_pb2.SetHeadMode.Mode.DISABLE,
185
+ "enable": control_query_pb2.SetHeadMode.Mode.ENABLE,
186
+ "disable": control_query_pb2.SetHeadMode.Mode.DISABLE,
192
187
  }
193
188
 
194
189
  if mode not in mode_map:
@@ -196,14 +191,17 @@ class Head(RobotJointComponent):
196
191
  f"Invalid mode: {mode}. Must be one of {list(mode_map.keys())}"
197
192
  )
198
193
 
199
- query_msg = dexcontrol_query_pb2.SetHeadMode()
194
+ query_msg = control_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=5.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,