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.
- dexcontrol/__init__.py +17 -8
- dexcontrol/apps/dualsense_teleop_base.py +1 -1
- dexcontrol/comm/__init__.py +51 -0
- dexcontrol/comm/rtc.py +401 -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 +66 -42
- dexcontrol/core/chassis.py +142 -120
- dexcontrol/core/component.py +107 -58
- dexcontrol/core/hand.py +119 -86
- dexcontrol/core/head.py +22 -33
- dexcontrol/core/misc.py +331 -158
- dexcontrol/core/robot_query_interface.py +467 -0
- dexcontrol/core/torso.py +5 -9
- dexcontrol/robot.py +245 -574
- dexcontrol/sensors/__init__.py +1 -2
- dexcontrol/sensors/camera/__init__.py +0 -2
- dexcontrol/sensors/camera/base_camera.py +150 -0
- dexcontrol/sensors/camera/rgb_camera.py +68 -64
- dexcontrol/sensors/camera/zed_camera.py +140 -164
- dexcontrol/sensors/imu/chassis_imu.py +81 -62
- dexcontrol/sensors/imu/zed_imu.py +54 -43
- dexcontrol/sensors/lidar/rplidar.py +16 -20
- dexcontrol/sensors/manager.py +4 -14
- dexcontrol/sensors/ultrasonic.py +15 -28
- dexcontrol/utils/__init__.py +0 -11
- dexcontrol/utils/comm_helper.py +110 -0
- dexcontrol/utils/constants.py +1 -1
- dexcontrol/utils/error_code.py +2 -4
- dexcontrol/utils/os_utils.py +172 -4
- dexcontrol/utils/pb_utils.py +6 -28
- {dexcontrol-0.2.12.dist-info → dexcontrol-0.3.4.dist-info}/METADATA +16 -3
- dexcontrol-0.3.4.dist-info/RECORD +62 -0
- {dexcontrol-0.2.12.dist-info → dexcontrol-0.3.4.dist-info}/WHEEL +1 -1
- dexcontrol/config/sensors/cameras/luxonis_camera.py +0 -51
- dexcontrol/proto/dexcontrol_msg_pb2.py +0 -73
- dexcontrol/proto/dexcontrol_msg_pb2.pyi +0 -220
- dexcontrol/proto/dexcontrol_query_pb2.py +0 -77
- dexcontrol/proto/dexcontrol_query_pb2.pyi +0 -162
- dexcontrol/sensors/camera/luxonis_camera.py +0 -169
- dexcontrol/utils/motion_utils.py +0 -199
- 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.4.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
|
|
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: Callable[[Any], None] | None = None,
|
|
60
|
+
buffer_size: int = 1,
|
|
61
|
+
deserializer: Callable[[bytes], Any] | None = deserialize_auto,
|
|
62
|
+
config: Path | None = 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: Callable[[Any], None] | None = None,
|
|
108
|
+
buffer_size: int = 100,
|
|
109
|
+
deserializer: Callable[[bytes], Any] | None = deserialize_auto,
|
|
110
|
+
config: Path | None = 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: Callable[[Any], None] | 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: Callable[[Any], None] | 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: Callable[[Any], None] | 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: Callable[[Any], None] | 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: Callable[[Any], None] | 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) -> Any | None:
|
|
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
|
-
|
|
18
|
-
|
|
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
|
-
|
|
25
|
-
default_factory=lambda: ["L_wheel_j1", "
|
|
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
|
dexcontrol/config/core/hand.py
CHANGED
|
@@ -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"
|
|
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/
|
|
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/
|
|
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
|
|
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
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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/
|
|
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,
|
|
18
|
+
from typing import Any, Literal
|
|
19
19
|
|
|
20
20
|
import numpy as np
|
|
21
|
-
import
|
|
21
|
+
from dexcomm import Publisher, call_service
|
|
22
|
+
from dexcomm.serialization import serialize_protobuf
|
|
23
|
+
from dexcomm.serialization.protobuf import control_msg_pb2, control_query_pb2
|
|
24
|
+
from dexcomm.utils import RateLimiter
|
|
22
25
|
from jaxtyping import Float
|
|
23
26
|
from loguru import logger
|
|
24
27
|
|
|
25
28
|
from dexcontrol.config.core.arm import ArmConfig
|
|
26
29
|
from dexcontrol.core.component import RobotComponent, RobotJointComponent
|
|
27
|
-
from dexcontrol.
|
|
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=
|
|
59
|
-
zenoh_session=zenoh_session,
|
|
58
|
+
state_message_type=control_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
|
-
|
|
71
|
-
|
|
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
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
"position":
|
|
110
|
-
"disable":
|
|
121
|
+
"position": control_query_pb2.SetArmMode.Mode.POSITION,
|
|
122
|
+
"disable": control_query_pb2.SetArmMode.Mode.DISABLE,
|
|
123
|
+
"release": control_query_pb2.SetArmMode.Mode.CURRENT,
|
|
111
124
|
}
|
|
112
125
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
|
|
119
|
-
|
|
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 = control_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=3.0,
|
|
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 =
|
|
128
|
-
control_msg.
|
|
155
|
+
control_msg = control_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 =
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
joint_vel=list(target_vel),
|
|
318
|
+
control_msg = control_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
|
-
|
|
302
|
-
|
|
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()
|
|
@@ -317,9 +344,8 @@ class Arm(RobotJointComponent):
|
|
|
317
344
|
Args:
|
|
318
345
|
message: The message to send to the robot arm.
|
|
319
346
|
"""
|
|
320
|
-
control_msg =
|
|
321
|
-
|
|
322
|
-
self._ee_pass_through_publisher.put(control_data)
|
|
347
|
+
control_msg = control_msg_pb2.EndEffectorPassThroughCommand(data=message)
|
|
348
|
+
self._ee_pass_through_publisher.publish(control_msg)
|
|
323
349
|
|
|
324
350
|
|
|
325
351
|
class ArmWrenchSensor(RobotComponent):
|
|
@@ -328,17 +354,15 @@ 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
|
|
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
|
-
|
|
341
|
-
state_message_type=dexcontrol_msg_pb2.WrenchState,
|
|
365
|
+
state_message_type=control_msg_pb2.WrenchState,
|
|
342
366
|
)
|
|
343
367
|
|
|
344
368
|
def get_wrench_state(self) -> Float[np.ndarray, "6"]:
|