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
@@ -0,0 +1,329 @@
1
+ # Copyright (C) 2025 Dexmate Inc.
2
+ #
3
+ # This software is dual-licensed:
4
+ #
5
+ # 1. GNU Affero General Public License v3.0 (AGPL-3.0)
6
+ # See LICENSE-AGPL for details
7
+ #
8
+ # 2. Commercial License
9
+ # For commercial licensing terms, contact: contact@dexmate.ai
10
+
11
+ """Core subscriber utilities using DexComm's Raw API.
12
+
13
+ Simple factory functions for creating DexComm subscribers with
14
+ DexControl's default configurations.
15
+ """
16
+
17
+ from pathlib import Path
18
+ from typing import Any, Callable, Optional
19
+
20
+ from dexcomm import BufferedSubscriber, Subscriber, wait_for_message
21
+ from dexcomm.serialization import deserialize_auto
22
+ from loguru import logger
23
+
24
+ from dexcontrol.utils.comm_helper import get_zenoh_config_path
25
+ from dexcontrol.utils.os_utils import resolve_key_name
26
+
27
+ # Import specialized deserializers
28
+ try:
29
+ from dexcomm.serialization import deserialize_depth, deserialize_image
30
+
31
+ IMAGE_DESERIALIZERS_AVAILABLE = True
32
+ except ImportError:
33
+ logger.debug("DexComm image deserializers not available")
34
+ deserialize_image = None
35
+ deserialize_depth = None
36
+ IMAGE_DESERIALIZERS_AVAILABLE = False
37
+
38
+ try:
39
+ from dexcomm.serialization import deserialize_imu
40
+
41
+ IMU_DESERIALIZER_AVAILABLE = True
42
+ except ImportError:
43
+ logger.debug("DexComm IMU deserializer not available")
44
+ deserialize_imu = None
45
+ IMU_DESERIALIZER_AVAILABLE = False
46
+
47
+ try:
48
+ from dexcomm.serialization.lidar import deserialize_lidar_2d
49
+
50
+ LIDAR_DESERIALIZER_AVAILABLE = True
51
+ except ImportError:
52
+ logger.debug("DexComm LiDAR deserializer not available")
53
+ deserialize_lidar_2d = None
54
+ LIDAR_DESERIALIZER_AVAILABLE = False
55
+
56
+
57
+ def create_subscriber(
58
+ topic: str,
59
+ callback: Optional[Callable[[Any], None]] = None,
60
+ buffer_size: int = 1,
61
+ deserializer: Optional[Callable[[bytes], Any]] = deserialize_auto,
62
+ config: Optional[Path] = None,
63
+ ) -> Subscriber:
64
+ """Create a DexComm Subscriber with DexControl defaults.
65
+
66
+ Simple wrapper that handles topic resolution and default configuration.
67
+
68
+ Args:
69
+ topic: Topic to subscribe to (will be resolved with robot namespace)
70
+ callback: Optional callback function for incoming messages
71
+ buffer_size: Number of messages to buffer (default 1 for latest only)
72
+ deserializer: Deserialization function (default: auto-detect)
73
+ config: Optional config path (default: use robot's config)
74
+
75
+ Returns:
76
+ DexComm Subscriber instance
77
+
78
+ Example:
79
+ ```python
80
+ # Simple subscription
81
+ sub = create_subscriber("sensors/imu")
82
+ data = sub.get_latest()
83
+
84
+ # With callback
85
+ sub = create_subscriber(
86
+ "robot/state",
87
+ callback=lambda msg: print(f"State: {msg}")
88
+ )
89
+ ```
90
+ """
91
+ full_topic = resolve_key_name(topic)
92
+ config_path = config or get_zenoh_config_path()
93
+
94
+ logger.debug(f"Creating subscriber for topic: {full_topic}")
95
+
96
+ return Subscriber(
97
+ topic=full_topic,
98
+ callback=callback,
99
+ deserializer=deserializer,
100
+ buffer_size=buffer_size,
101
+ config=config_path,
102
+ )
103
+
104
+
105
+ def create_buffered_subscriber(
106
+ topic: str,
107
+ callback: Optional[Callable[[Any], None]] = None,
108
+ buffer_size: int = 100,
109
+ deserializer: Optional[Callable[[bytes], Any]] = deserialize_auto,
110
+ config: Optional[Path] = None,
111
+ ) -> BufferedSubscriber:
112
+ """Create a DexComm BufferedSubscriber for data collection.
113
+
114
+ Args:
115
+ topic: Topic to subscribe to
116
+ callback: Optional callback function
117
+ buffer_size: Maximum messages to buffer (default 100)
118
+ deserializer: Deserialization function
119
+ config: Optional config path
120
+
121
+ Returns:
122
+ DexComm BufferedSubscriber instance
123
+
124
+ Example:
125
+ ```python
126
+ # Collect sensor data
127
+ sub = create_buffered_subscriber("sensors/lidar", buffer_size=500)
128
+ # ... wait for data ...
129
+ all_scans = sub.get_buffer()
130
+ ```
131
+ """
132
+ full_topic = resolve_key_name(topic)
133
+ config_path = config or get_zenoh_config_path()
134
+
135
+ logger.debug(f"Creating buffered subscriber for topic: {full_topic}")
136
+
137
+ return BufferedSubscriber(
138
+ topic=full_topic,
139
+ callback=callback,
140
+ deserializer=deserializer,
141
+ buffer_size=buffer_size,
142
+ config=config_path,
143
+ )
144
+
145
+
146
+ def create_camera_subscriber(
147
+ topic: str,
148
+ callback: Optional[Callable[[Any], None]] = None,
149
+ buffer_size: int = 1,
150
+ ) -> Subscriber:
151
+ """Create a subscriber for RGB camera data.
152
+
153
+ Uses DexComm's image deserializer for efficient image decompression.
154
+
155
+ Args:
156
+ topic: Camera topic
157
+ callback: Optional callback for images
158
+ buffer_size: Number of images to buffer
159
+
160
+ Returns:
161
+ Subscriber configured for RGB images
162
+ """
163
+ deserializer = (
164
+ deserialize_image if IMAGE_DESERIALIZERS_AVAILABLE else deserialize_auto
165
+ )
166
+
167
+ return create_subscriber(
168
+ topic=topic,
169
+ callback=callback,
170
+ buffer_size=buffer_size,
171
+ deserializer=deserializer,
172
+ )
173
+
174
+
175
+ def create_depth_subscriber(
176
+ topic: str,
177
+ callback: Optional[Callable[[Any], None]] = None,
178
+ buffer_size: int = 1,
179
+ ) -> Subscriber:
180
+ """Create a subscriber for depth camera data.
181
+
182
+ Uses DexComm's depth deserializer for 16-bit depth images.
183
+
184
+ Args:
185
+ topic: Depth camera topic
186
+ callback: Optional callback for depth images
187
+ buffer_size: Number of images to buffer
188
+
189
+ Returns:
190
+ Subscriber configured for depth images
191
+ """
192
+ deserializer = (
193
+ deserialize_depth if IMAGE_DESERIALIZERS_AVAILABLE else deserialize_auto
194
+ )
195
+
196
+ return create_subscriber(
197
+ topic=topic,
198
+ callback=callback,
199
+ buffer_size=buffer_size,
200
+ deserializer=deserializer,
201
+ )
202
+
203
+
204
+ def create_imu_subscriber(
205
+ topic: str,
206
+ callback: Optional[Callable[[Any], None]] = None,
207
+ buffer_size: int = 1,
208
+ ) -> Subscriber:
209
+ """Create a subscriber for IMU data.
210
+
211
+ Uses DexComm's IMU deserializer for 6-DOF/9-DOF IMU data.
212
+
213
+ Args:
214
+ topic: IMU topic
215
+ callback: Optional callback for IMU data
216
+ buffer_size: Number of samples to buffer
217
+
218
+ Returns:
219
+ Subscriber configured for IMU data
220
+ """
221
+ deserializer = deserialize_imu if IMU_DESERIALIZER_AVAILABLE else deserialize_auto
222
+
223
+ return create_subscriber(
224
+ topic=topic,
225
+ callback=callback,
226
+ buffer_size=buffer_size,
227
+ deserializer=deserializer,
228
+ )
229
+
230
+
231
+ def create_lidar_subscriber(
232
+ topic: str,
233
+ callback: Optional[Callable[[Any], None]] = None,
234
+ buffer_size: int = 1,
235
+ ) -> Subscriber:
236
+ """Create a subscriber for 2D LiDAR data.
237
+
238
+ Uses DexComm's LiDAR deserializer for laser scan data.
239
+
240
+ Args:
241
+ topic: LiDAR topic
242
+ callback: Optional callback for scan data
243
+ buffer_size: Number of scans to buffer
244
+
245
+ Returns:
246
+ Subscriber configured for LiDAR data
247
+ """
248
+ deserializer = (
249
+ deserialize_lidar_2d if LIDAR_DESERIALIZER_AVAILABLE else deserialize_auto
250
+ )
251
+
252
+ return create_subscriber(
253
+ topic=topic,
254
+ callback=callback,
255
+ buffer_size=buffer_size,
256
+ deserializer=deserializer,
257
+ )
258
+
259
+
260
+ def create_generic_subscriber(
261
+ topic: str,
262
+ callback: Optional[Callable[[Any], None]] = None,
263
+ buffer_size: int = 1,
264
+ raw_bytes: bool = False,
265
+ ) -> Subscriber:
266
+ """Create a generic subscriber with auto-detection or raw bytes.
267
+
268
+ Args:
269
+ topic: Topic to subscribe to
270
+ callback: Optional callback
271
+ buffer_size: Number of messages to buffer
272
+ raw_bytes: If True, return raw bytes without deserialization
273
+
274
+ Returns:
275
+ Generic subscriber instance
276
+ """
277
+ deserializer = None if raw_bytes else deserialize_auto
278
+
279
+ return create_subscriber(
280
+ topic=topic,
281
+ callback=callback,
282
+ buffer_size=buffer_size,
283
+ deserializer=deserializer,
284
+ )
285
+
286
+
287
+ def quick_subscribe(topic: str, callback: Callable[[Any], None]) -> Subscriber:
288
+ """Quick one-liner to subscribe with a callback.
289
+
290
+ Args:
291
+ topic: Topic to subscribe to
292
+ callback: Callback function
293
+
294
+ Returns:
295
+ Subscriber instance
296
+
297
+ Example:
298
+ ```python
299
+ sub = quick_subscribe("robot/state", lambda msg: print(msg))
300
+ ```
301
+ """
302
+ return create_subscriber(topic, callback=callback)
303
+
304
+
305
+ def wait_for_any_message(topic: str, timeout: float = 5.0) -> Optional[Any]:
306
+ """Wait for a message on a topic.
307
+
308
+ Convenience wrapper around DexComm's wait_for_message.
309
+
310
+ Args:
311
+ topic: Topic to wait on
312
+ timeout: Maximum wait time in seconds
313
+
314
+ Returns:
315
+ Received message or None if timeout
316
+
317
+ Example:
318
+ ```python
319
+ state = wait_for_any_message("robot/state", timeout=2.0)
320
+ if state:
321
+ print(f"Robot is in state: {state}")
322
+ ```
323
+ """
324
+ full_topic = resolve_key_name(topic)
325
+ return wait_for_message(
326
+ topic=full_topic,
327
+ timeout=timeout,
328
+ config=get_zenoh_config_path(),
329
+ )
@@ -14,14 +14,19 @@ from dataclasses import dataclass, field
14
14
  @dataclass
15
15
  class ChassisConfig:
16
16
  _target_: str = "dexcontrol.core.chassis.Chassis"
17
- control_pub_topic: str = "control/chassis"
18
- state_sub_topic: str = "state/chassis"
17
+ steer_control_pub_topic: str = "control/chassis/steer"
18
+ steer_state_sub_topic: str = "state/chassis/steer"
19
+ drive_control_pub_topic: str = "control/chassis/drive"
20
+ drive_state_sub_topic: str = "state/chassis/drive"
19
21
  dof: int = 2
20
22
  center_to_wheel_axis_dist: float = (
21
23
  0.219 # the distance between base center and wheel axis in m
22
24
  )
23
25
  wheels_dist: float = 0.45 # the distance between two wheels in m (0.41 for vega-rc2, 0.45 for vega-1)
24
- joint_name: list[str] = field(
25
- default_factory=lambda: ["L_wheel_j1", "L_wheel_j2", "R_wheel_j1", "R_wheel_j2"]
26
+ steer_joint_name: list[str] = field(
27
+ default_factory=lambda: ["L_wheel_j1", "R_wheel_j1"]
28
+ )
29
+ drive_joint_name: list[str] = field(
30
+ default_factory=lambda: ["L_wheel_j2", "R_wheel_j2"]
26
31
  )
27
32
  max_vel: float = 0.8
@@ -16,6 +16,7 @@ class HandConfig:
16
16
  _target_: str = "dexcontrol.core.hand.HandF5D6"
17
17
  state_sub_topic: str = "state/hand/right"
18
18
  control_pub_topic: str = "control/hand/right"
19
+ touch_sensor_sub_topic: str = "state/hand/right/touch" # Only for V2 hand
19
20
  dof: int = 6
20
21
  joint_name: list[str] = field(
21
22
  default_factory=lambda: [
@@ -8,8 +8,7 @@
8
8
  # 2. Commercial License
9
9
  # For commercial licensing terms, contact: contact@dexmate.ai
10
10
 
11
- from .luxonis_camera import LuxonisCameraConfig
12
11
  from .rgb_camera import RGBCameraConfig
13
12
  from .zed_camera import ZedCameraConfig
14
13
 
15
- __all__ = ["RGBCameraConfig", "ZedCameraConfig", "LuxonisCameraConfig"]
14
+ __all__ = ["RGBCameraConfig", "ZedCameraConfig"]
@@ -39,12 +39,12 @@ class ZedCameraConfig:
39
39
  default_factory=lambda: {
40
40
  "left_rgb": {
41
41
  "enable": True,
42
- "info_key": "camera/head/left_rgb/info",
42
+ "info_key": "camera/head/info", # Query the main camera info endpoint
43
43
  "topic": "camera/head/left_rgb",
44
44
  },
45
45
  "right_rgb": {
46
46
  "enable": True,
47
- "info_key": "camera/head/right_rgb/info",
47
+ "info_key": "camera/head/info", # Query the main camera info endpoint
48
48
  "topic": "camera/head/right_rgb",
49
49
  },
50
50
  "depth": {
@@ -18,7 +18,7 @@ LiDAR and ultrasonic sensors.
18
18
  from collections.abc import Callable
19
19
  from dataclasses import dataclass, field
20
20
 
21
- from .cameras import LuxonisCameraConfig, RGBCameraConfig, ZedCameraConfig
21
+ from .cameras import RGBCameraConfig, ZedCameraConfig
22
22
  from .imu import ChassisIMUConfig, ZedIMUConfig
23
23
  from .lidar import RPLidarConfig
24
24
  from .ultrasonic import UltraSonicConfig
@@ -33,15 +33,17 @@ def _make_rgb_camera(name: str) -> Callable[[], RGBCameraConfig]:
33
33
  Returns:
34
34
  Factory function that creates an RGBCameraConfig
35
35
  """
36
- return lambda: RGBCameraConfig(
37
- subscriber_config=dict(
38
- rgb=dict(
39
- enable=True,
40
- info_key=f"camera/base/{name}/rgb/info",
41
- topic=f"camera/base/{name}/rgb",
42
- )
43
- ),
44
- name=f"base_{name}_camera",
36
+ return (
37
+ lambda: RGBCameraConfig(
38
+ subscriber_config=dict(
39
+ rgb=dict(
40
+ enable=True,
41
+ info_key=f"camera/base_{name}/info", # Matches dexsensor: camera/base_{name}/info
42
+ topic=f"camera/base_{name}/rgb",
43
+ )
44
+ ),
45
+ name=f"base_{name}_camera",
46
+ )
45
47
  )
46
48
 
47
49
 
@@ -58,12 +60,6 @@ class VegaSensorsConfig:
58
60
  """
59
61
 
60
62
  head_camera: ZedCameraConfig = field(default_factory=ZedCameraConfig)
61
- left_wrist_camera: LuxonisCameraConfig = field(
62
- default_factory=lambda: LuxonisCameraConfig(name="left_wrist_camera")
63
- )
64
- right_wrist_camera: LuxonisCameraConfig = field(
65
- default_factory=lambda: LuxonisCameraConfig(name="right_wrist_camera")
66
- )
67
63
  base_left_camera: RGBCameraConfig = field(default_factory=_make_rgb_camera("left"))
68
64
  base_right_camera: RGBCameraConfig = field(
69
65
  default_factory=_make_rgb_camera("right")
@@ -79,8 +75,6 @@ class VegaSensorsConfig:
79
75
 
80
76
  def __post_init__(self) -> None:
81
77
  self.head_camera.enable = False
82
- self.left_wrist_camera.enable = False
83
- self.right_wrist_camera.enable = False
84
78
  self.base_left_camera.enable = False
85
79
  self.base_right_camera.enable = False
86
80
  self.base_front_camera.enable = False
dexcontrol/config/vega.py CHANGED
@@ -77,6 +77,7 @@ class VegaConfig:
77
77
  default_factory=lambda: HandConfig(
78
78
  state_sub_topic="state/hand/left",
79
79
  control_pub_topic="control/hand/left",
80
+ touch_sensor_sub_topic="state/hand/left/touch",
80
81
  joint_name=[
81
82
  "L_th_j1",
82
83
  "L_ff_j1",
@@ -91,6 +92,7 @@ class VegaConfig:
91
92
  default_factory=lambda: HandConfig(
92
93
  state_sub_topic="state/hand/right",
93
94
  control_pub_topic="control/hand/right",
95
+ touch_sensor_sub_topic="state/hand/right/touch",
94
96
  joint_name=[
95
97
  "R_th_j1",
96
98
  "R_ff_j1",
@@ -111,8 +113,9 @@ class VegaConfig:
111
113
  heartbeat: HeartbeatConfig = field(default_factory=HeartbeatConfig)
112
114
 
113
115
  # Queries
114
- version_info_name: str = "info/version"
116
+ version_info_name: str = "info/versions" # Updated to use JSON interface
115
117
  status_info_name: str = "info/status"
118
+ hand_info_query_name: str = "info/hand_type"
116
119
  reboot_query_name: str = "system/reboot"
117
120
  clear_error_query_name: str = "system/clear_error"
118
121
  led_query_name: str = "system/led"
dexcontrol/core/arm.py CHANGED
@@ -15,18 +15,20 @@ communication and the ArmWrenchSensor class for reading wrench sensor data.
15
15
  """
16
16
 
17
17
  import time
18
- from typing import Any, Final, Literal
18
+ from typing import Any, Literal
19
19
 
20
20
  import numpy as np
21
- import zenoh
21
+ from dexcomm import Publisher, call_service
22
+ from dexcomm.serialization import serialize_protobuf
23
+ from dexcomm.utils import RateLimiter
22
24
  from jaxtyping import Float
23
25
  from loguru import logger
24
26
 
25
27
  from dexcontrol.config.core.arm import ArmConfig
26
28
  from dexcontrol.core.component import RobotComponent, RobotJointComponent
27
29
  from dexcontrol.proto import dexcontrol_msg_pb2, dexcontrol_query_pb2
30
+ from dexcontrol.utils.comm_helper import get_zenoh_config_path
28
31
  from dexcontrol.utils.os_utils import resolve_key_name
29
- from dexcontrol.utils.rate_limiter import RateLimiter
30
32
  from dexcontrol.utils.trajectory_utils import generate_linear_trajectory
31
33
 
32
34
 
@@ -44,19 +46,16 @@ class Arm(RobotJointComponent):
44
46
  def __init__(
45
47
  self,
46
48
  configs: ArmConfig,
47
- zenoh_session: zenoh.Session,
48
49
  ) -> None:
49
50
  """Initialize the arm controller.
50
51
 
51
52
  Args:
52
53
  configs: Configuration parameters for the arm including communication topics.
53
- zenoh_session: Active Zenoh communication session for message passing.
54
54
  """
55
55
  super().__init__(
56
56
  state_sub_topic=configs.state_sub_topic,
57
57
  control_pub_topic=configs.control_pub_topic,
58
- state_message_type=dexcontrol_msg_pb2.ArmState,
59
- zenoh_session=zenoh_session,
58
+ state_message_type=dexcontrol_msg_pb2.MotorStateWithCurrent,
60
59
  joint_name=configs.joint_name,
61
60
  joint_limit=configs.joint_limit
62
61
  if hasattr(configs, "joint_limit")
@@ -67,22 +66,20 @@ class Arm(RobotJointComponent):
67
66
  pose_pool=configs.pose_pool,
68
67
  )
69
68
 
70
- self.mode_querier: Final[zenoh.Querier] = zenoh_session.declare_querier(
71
- resolve_key_name(configs.set_mode_query), timeout=2.0
72
- )
69
+ # Note: Mode querier functionality will need to be updated to use DexComm
70
+ # For now, we'll store the query topic for later use
71
+ self._mode_query_topic = resolve_key_name(configs.set_mode_query)
73
72
 
74
73
  # Initialize wrench sensor if configured
75
74
  self.wrench_sensor: ArmWrenchSensor | None = None
76
75
  if configs.wrench_sub_topic:
77
- self.wrench_sensor = ArmWrenchSensor(
78
- configs.wrench_sub_topic, zenoh_session
79
- )
76
+ self.wrench_sensor = ArmWrenchSensor(configs.wrench_sub_topic)
80
77
 
81
- # Initialize end effector pass through publisher
82
- self._ee_pass_through_publisher: Final[zenoh.Publisher] = (
83
- zenoh_session.declare_publisher(
84
- resolve_key_name(configs.ee_pass_through_pub_topic)
85
- )
78
+ # Initialize end effector pass through publisher using DexComm
79
+ self._ee_pass_through_publisher = Publisher(
80
+ topic=resolve_key_name(configs.ee_pass_through_pub_topic),
81
+ serializer=serialize_protobuf,
82
+ config=get_zenoh_config_path(),
86
83
  )
87
84
 
88
85
  self._default_control_hz = configs.default_control_hz
@@ -97,6 +94,9 @@ class Arm(RobotJointComponent):
97
94
  def set_mode(self, mode: Literal["position", "disable"]) -> None:
98
95
  """Sets the operating mode of the arm.
99
96
 
97
+ .. deprecated::
98
+ Use set_modes() instead for setting arm modes.
99
+
100
100
  Args:
101
101
  mode: Operating mode for the arm. Must be either "position" or "disable".
102
102
  "position": Enable position control
@@ -105,18 +105,46 @@ class Arm(RobotJointComponent):
105
105
  Raises:
106
106
  ValueError: If an invalid mode is specified.
107
107
  """
108
+ logger.warning("arm.set_mode() is deprecated, use set_modes() instead")
109
+ self.set_modes([mode] * 7)
110
+
111
+ def set_modes(self, modes: list[Literal["position", "disable", "release"]]) -> None:
112
+ """Sets the operating modes of the arm.
113
+
114
+ Args:
115
+ modes: List of operating modes for the arm. Each mode must be either "position", "disable", or "current".
116
+
117
+ Raises:
118
+ ValueError: If any mode in the list is invalid.
119
+ """
108
120
  mode_map = {
109
121
  "position": dexcontrol_query_pb2.SetArmMode.Mode.POSITION,
110
122
  "disable": dexcontrol_query_pb2.SetArmMode.Mode.DISABLE,
123
+ "release": dexcontrol_query_pb2.SetArmMode.Mode.CURRENT,
111
124
  }
112
125
 
113
- if mode not in mode_map:
114
- raise ValueError(
115
- f"Invalid mode: {mode}. Must be one of {list(mode_map.keys())}"
116
- )
126
+ for mode in modes:
127
+ if mode not in mode_map:
128
+ raise ValueError(
129
+ f"Invalid mode: {mode}. Must be one of {list(mode_map.keys())}"
130
+ )
117
131
 
118
- query_msg = dexcontrol_query_pb2.SetArmMode(mode=mode_map[mode])
119
- self.mode_querier.get(payload=query_msg.SerializeToString())
132
+ if len(modes) != 7:
133
+ raise ValueError("Arm modes length must match arm DoF (7).")
134
+
135
+ converted_modes = [mode_map[mode] for mode in modes]
136
+ query_msg = dexcontrol_query_pb2.SetArmMode(modes=converted_modes)
137
+ # Use DexComm's call_service for mode setting
138
+ from dexcontrol.utils.comm_helper import get_zenoh_config_path
139
+
140
+ call_service(
141
+ self._mode_query_topic,
142
+ request=query_msg,
143
+ timeout=0.05,
144
+ config=get_zenoh_config_path(),
145
+ request_serializer=lambda x: x.SerializeToString(),
146
+ response_deserializer=None,
147
+ )
120
148
 
121
149
  def _send_position_command(self, joint_pos: np.ndarray) -> None:
122
150
  """Send joint position command.
@@ -124,8 +152,8 @@ class Arm(RobotJointComponent):
124
152
  Args:
125
153
  joint_pos: Joint positions as numpy array.
126
154
  """
127
- control_msg = dexcontrol_msg_pb2.ArmCommand()
128
- control_msg.joint_pos.extend(joint_pos.tolist())
155
+ control_msg = dexcontrol_msg_pb2.MotorPosVelCurrentCommand()
156
+ control_msg.pos.extend(joint_pos.tolist())
129
157
  self._publish_control(control_msg)
130
158
 
131
159
  def set_joint_pos(
@@ -287,10 +315,9 @@ class Arm(RobotJointComponent):
287
315
  joint_vel, clip_value=self._joint_vel_limit
288
316
  )
289
317
 
290
- control_msg = dexcontrol_msg_pb2.ArmCommand(
291
- command_type=dexcontrol_msg_pb2.ArmCommand.CommandType.VELOCITY_FEEDFORWARD,
292
- joint_pos=list(target_pos),
293
- joint_vel=list(target_vel),
318
+ control_msg = dexcontrol_msg_pb2.MotorPosVelCurrentCommand(
319
+ pos=list(target_pos),
320
+ vel=list(target_vel),
294
321
  )
295
322
  self._publish_control(control_msg)
296
323
 
@@ -298,8 +325,8 @@ class Arm(RobotJointComponent):
298
325
  """Cleans up all Zenoh resources."""
299
326
  super().shutdown()
300
327
  try:
301
- if hasattr(self, "mode_querier") and self.mode_querier:
302
- self.mode_querier.undeclare()
328
+ # Mode querier cleanup no longer needed with DexComm
329
+ pass
303
330
  except Exception as e:
304
331
  # Don't log "Undeclared querier" errors as warnings - they're expected during shutdown
305
332
  error_msg = str(e).lower()
@@ -318,8 +345,7 @@ class Arm(RobotJointComponent):
318
345
  message: The message to send to the robot arm.
319
346
  """
320
347
  control_msg = dexcontrol_msg_pb2.EndEffectorPassThroughCommand(data=message)
321
- control_data = control_msg.SerializeToString()
322
- self._ee_pass_through_publisher.put(control_data)
348
+ self._ee_pass_through_publisher.publish(control_msg)
323
349
 
324
350
 
325
351
  class ArmWrenchSensor(RobotComponent):
@@ -328,16 +354,14 @@ class ArmWrenchSensor(RobotComponent):
328
354
  This class provides methods to read wrench sensor data through Zenoh communication.
329
355
  """
330
356
 
331
- def __init__(self, state_sub_topic: str, zenoh_session: zenoh.Session) -> None:
357
+ def __init__(self, state_sub_topic: str) -> None:
332
358
  """Initialize the wrench sensor reader.
333
359
 
334
360
  Args:
335
361
  state_sub_topic: Topic to subscribe to for wrench sensor data.
336
- zenoh_session: Active Zenoh communication session for message passing.
337
362
  """
338
363
  super().__init__(
339
364
  state_sub_topic=state_sub_topic,
340
- zenoh_session=zenoh_session,
341
365
  state_message_type=dexcontrol_msg_pb2.WrenchState,
342
366
  )
343
367