dexcontrol 0.2.7__py3-none-any.whl → 0.2.12__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 +1 -0
- dexcontrol/config/core/arm.py +6 -0
- dexcontrol/config/core/hand.py +5 -16
- dexcontrol/config/core/head.py +7 -8
- dexcontrol/config/core/misc.py +14 -1
- dexcontrol/config/core/torso.py +8 -4
- dexcontrol/config/sensors/cameras/__init__.py +2 -1
- dexcontrol/config/sensors/cameras/luxonis_camera.py +51 -0
- dexcontrol/config/sensors/cameras/rgb_camera.py +1 -1
- dexcontrol/config/sensors/cameras/zed_camera.py +2 -2
- dexcontrol/config/sensors/vega_sensors.py +9 -1
- dexcontrol/config/vega.py +30 -2
- dexcontrol/core/arm.py +95 -43
- dexcontrol/core/component.py +92 -6
- dexcontrol/core/hand.py +81 -13
- dexcontrol/core/head.py +55 -26
- dexcontrol/core/misc.py +94 -13
- dexcontrol/core/torso.py +44 -11
- dexcontrol/proto/dexcontrol_msg_pb2.py +38 -36
- dexcontrol/proto/dexcontrol_msg_pb2.pyi +48 -20
- dexcontrol/proto/dexcontrol_query_pb2.py +7 -3
- dexcontrol/proto/dexcontrol_query_pb2.pyi +24 -0
- dexcontrol/robot.py +319 -75
- dexcontrol/sensors/__init__.py +2 -1
- dexcontrol/sensors/camera/__init__.py +2 -0
- dexcontrol/sensors/camera/luxonis_camera.py +169 -0
- dexcontrol/sensors/camera/rgb_camera.py +36 -0
- dexcontrol/sensors/camera/zed_camera.py +48 -8
- dexcontrol/sensors/imu/chassis_imu.py +5 -1
- dexcontrol/sensors/imu/zed_imu.py +3 -2
- dexcontrol/sensors/lidar/rplidar.py +1 -0
- dexcontrol/sensors/manager.py +3 -0
- dexcontrol/utils/constants.py +3 -0
- dexcontrol/utils/error_code.py +236 -0
- dexcontrol/utils/subscribers/lidar.py +1 -0
- dexcontrol/utils/trajectory_utils.py +17 -5
- dexcontrol/utils/viz_utils.py +86 -11
- dexcontrol/utils/zenoh_utils.py +39 -0
- {dexcontrol-0.2.7.dist-info → dexcontrol-0.2.12.dist-info}/METADATA +6 -4
- dexcontrol-0.2.12.dist-info/RECORD +75 -0
- dexcontrol-0.2.7.dist-info/RECORD +0 -72
- {dexcontrol-0.2.7.dist-info → dexcontrol-0.2.12.dist-info}/WHEEL +0 -0
- {dexcontrol-0.2.7.dist-info → dexcontrol-0.2.12.dist-info}/licenses/LICENSE +0 -0
dexcontrol/__init__.py
CHANGED
|
@@ -32,6 +32,7 @@ from dexcontrol.utils.constants import COMM_CFG_PATH_ENV_VAR
|
|
|
32
32
|
# Package-level constants
|
|
33
33
|
LIB_PATH: Final[Path] = Path(__file__).resolve().parent
|
|
34
34
|
CFG_PATH: Final[Path] = LIB_PATH / "config"
|
|
35
|
+
MIN_SOC_SOFTWARE_VERSION: int = 233
|
|
35
36
|
|
|
36
37
|
logger.configure(handlers=[{"sink": RichHandler(markup=True), "format": "{message}"}])
|
|
37
38
|
|
dexcontrol/config/core/arm.py
CHANGED
|
@@ -20,18 +20,24 @@ class ArmConfig:
|
|
|
20
20
|
wrench_sub_topic: str = "state/wrench/right"
|
|
21
21
|
control_pub_topic: str = "control/arm/right"
|
|
22
22
|
set_mode_query: str = "mode/arm/right"
|
|
23
|
+
ee_pass_through_pub_topic: str = "control/ee_pass_through/right"
|
|
23
24
|
dof: int = 7
|
|
25
|
+
default_control_hz: int = 100
|
|
24
26
|
joint_name: list[str] = field(
|
|
25
27
|
default_factory=lambda: [f"R_arm_j{i + 1}" for i in range(7)]
|
|
26
28
|
)
|
|
27
29
|
joint_limit: list[list[float]] = field(
|
|
28
30
|
default_factory=lambda: [[-np.pi, np.pi] for _ in range(7)]
|
|
29
31
|
)
|
|
32
|
+
joint_vel_limit: list[float] = field(
|
|
33
|
+
default_factory=lambda: [2.4, 2.4, 2.7, 2.7, 2.7, 2.7, 2.7]
|
|
34
|
+
)
|
|
30
35
|
pose_pool: dict[str, list[float]] = field(
|
|
31
36
|
default_factory=lambda: {
|
|
32
37
|
"folded": [-1.57079, 0.0, 0, -3.1, 0, 0, 0.69813],
|
|
33
38
|
"folded_closed_hand": [-1.57079, 0.0, 0, -3.1, 0, 0, 0.9],
|
|
34
39
|
"L_shape": [-0.064, 0.3, 0.0, -1.556, -1.271, 0.0, 0.0],
|
|
40
|
+
"lift_up": [-0.064, 0.3, 0.0, -2.756, -1.271, 0.0, 0.0],
|
|
35
41
|
"zero": [1.57079, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
|
|
36
42
|
}
|
|
37
43
|
)
|
dexcontrol/config/core/hand.py
CHANGED
|
@@ -28,20 +28,9 @@ class HandConfig:
|
|
|
28
28
|
]
|
|
29
29
|
)
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
default_factory=lambda: [0.1834, 0.2891, 0.2801, 0.284, 0.2811, -0.0158]
|
|
37
|
-
)
|
|
38
|
-
joint_pos_close: list[float] = field(
|
|
39
|
-
default_factory=lambda: [
|
|
40
|
-
-0.3468,
|
|
41
|
-
-1.0946,
|
|
42
|
-
-1.0844,
|
|
43
|
-
-1.0154,
|
|
44
|
-
-1.0118,
|
|
45
|
-
1.6,
|
|
46
|
-
]
|
|
31
|
+
pose_pool: dict[str, list[float]] = field(
|
|
32
|
+
default_factory=lambda: {
|
|
33
|
+
"open": [0.1834, 0.2891, 0.2801, 0.284, 0.2811, -0.0158],
|
|
34
|
+
"close": [-0.2668, -1.0946, -1.0844, -1.0154, -1.0118, 1.6],
|
|
35
|
+
}
|
|
47
36
|
)
|
dexcontrol/config/core/head.py
CHANGED
|
@@ -21,15 +21,14 @@ class HeadConfig:
|
|
|
21
21
|
default_factory=lambda: ["head_j1", "head_j2", "head_j3"]
|
|
22
22
|
)
|
|
23
23
|
dof: int = 3
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
joint_limit: list[list[float]] = field(
|
|
25
|
+
default_factory=lambda: [
|
|
26
|
+
[-1.2217, 1.2217],
|
|
27
|
+
[-2.7925, 2.7925],
|
|
28
|
+
[-1.396, 1.396],
|
|
29
|
+
]
|
|
27
30
|
)
|
|
28
|
-
|
|
29
|
-
default_factory=lambda: [1.2217, 2.7925, 1.396]
|
|
30
|
-
)
|
|
31
|
-
default_vel: float = 1.57
|
|
32
|
-
max_vel: float = 2.0
|
|
31
|
+
joint_vel_limit: list[float] = field(default_factory=lambda: [2.5, 2.5, 2.5])
|
|
33
32
|
pose_pool: dict[str, list[float]] = field(
|
|
34
33
|
default_factory=lambda: {
|
|
35
34
|
"home": [0.0, 0.0, 0.0],
|
dexcontrol/config/core/misc.py
CHANGED
|
@@ -13,7 +13,10 @@
|
|
|
13
13
|
import os
|
|
14
14
|
from dataclasses import dataclass
|
|
15
15
|
|
|
16
|
-
from dexcontrol.utils.constants import
|
|
16
|
+
from dexcontrol.utils.constants import (
|
|
17
|
+
DISABLE_ESTOP_CHECKING_ENV_VAR,
|
|
18
|
+
DISABLE_HEARTBEAT_ENV_VAR,
|
|
19
|
+
)
|
|
17
20
|
|
|
18
21
|
|
|
19
22
|
@dataclass
|
|
@@ -27,6 +30,16 @@ class EStopConfig:
|
|
|
27
30
|
_target_: str = "dexcontrol.core.misc.EStop"
|
|
28
31
|
state_sub_topic: str = "state/estop"
|
|
29
32
|
estop_query_name: str = "system/estop"
|
|
33
|
+
enabled: bool = True # Can be disabled via DEXCONTROL_DISABLE_ESTOP_CHECKING=1
|
|
34
|
+
|
|
35
|
+
def __post_init__(self):
|
|
36
|
+
"""Check environment variable to disable estop checking."""
|
|
37
|
+
if os.getenv(DISABLE_ESTOP_CHECKING_ENV_VAR, "0").lower() in (
|
|
38
|
+
"1",
|
|
39
|
+
"true",
|
|
40
|
+
"yes",
|
|
41
|
+
):
|
|
42
|
+
self.enabled = False
|
|
30
43
|
|
|
31
44
|
|
|
32
45
|
@dataclass
|
dexcontrol/config/core/torso.py
CHANGED
|
@@ -20,10 +20,14 @@ class TorsoConfig:
|
|
|
20
20
|
joint_name: list[str] = field(
|
|
21
21
|
default_factory=lambda: ["torso_j1", "torso_j2", "torso_j3"]
|
|
22
22
|
)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
joint_limit: list[list[float]] = field(
|
|
24
|
+
default_factory=lambda: [
|
|
25
|
+
[0, 1.570],
|
|
26
|
+
[0, 3.141],
|
|
27
|
+
[-1.570, 1.570],
|
|
28
|
+
]
|
|
29
|
+
)
|
|
30
|
+
joint_vel_limit: list[float] = field(default_factory=lambda: [0.6, 0.6, 0.6])
|
|
27
31
|
pose_pool: dict[str, list[float]] = field(
|
|
28
32
|
default_factory=lambda: {
|
|
29
33
|
"home": [0.0, 0.0, 0.0],
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
# 2. Commercial License
|
|
9
9
|
# For commercial licensing terms, contact: contact@dexmate.ai
|
|
10
10
|
|
|
11
|
+
from .luxonis_camera import LuxonisCameraConfig
|
|
11
12
|
from .rgb_camera import RGBCameraConfig
|
|
12
13
|
from .zed_camera import ZedCameraConfig
|
|
13
14
|
|
|
14
|
-
__all__ = ["RGBCameraConfig", "ZedCameraConfig"]
|
|
15
|
+
__all__ = ["RGBCameraConfig", "ZedCameraConfig", "LuxonisCameraConfig"]
|
|
@@ -0,0 +1,51 @@
|
|
|
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
|
+
from dataclasses import dataclass, field
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class LuxonisCameraConfig:
|
|
16
|
+
"""Configuration for LuxonisCameraSensor (wrist camera).
|
|
17
|
+
|
|
18
|
+
Attributes:
|
|
19
|
+
_target_: Target sensor class for Hydra instantiation.
|
|
20
|
+
name: Logical name of the camera.
|
|
21
|
+
enable: Whether this sensor is enabled.
|
|
22
|
+
enable_fps_tracking: Enable FPS tracking logs.
|
|
23
|
+
fps_log_interval: Frames between FPS logs.
|
|
24
|
+
subscriber_config: Topics for RGB and Depth Zenoh subscribers.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
_target_: str = "dexcontrol.sensors.camera.luxonis_camera.LuxonisCameraSensor"
|
|
28
|
+
name: str = "wrist_camera"
|
|
29
|
+
enable: bool = False
|
|
30
|
+
enable_fps_tracking: bool = False
|
|
31
|
+
fps_log_interval: int = 30
|
|
32
|
+
|
|
33
|
+
# Note: Resolution is set by the publisher (dexsensor). The publisher now defaults
|
|
34
|
+
# to 720p to match common Luxonis sensor capabilities (e.g., OV9782 supports 720p/800p).
|
|
35
|
+
# These subscribers only define topics to consume.
|
|
36
|
+
subscriber_config: dict = field(
|
|
37
|
+
default_factory=lambda: {
|
|
38
|
+
"left_rgb": {
|
|
39
|
+
"enable": True,
|
|
40
|
+
"topic": "camera/wrist/left_rgb",
|
|
41
|
+
},
|
|
42
|
+
"right_rgb": {
|
|
43
|
+
"enable": True,
|
|
44
|
+
"topic": "camera/wrist/right_rgb",
|
|
45
|
+
},
|
|
46
|
+
"depth": {
|
|
47
|
+
"enable": False,
|
|
48
|
+
"topic": "camera/wrist/depth",
|
|
49
|
+
},
|
|
50
|
+
}
|
|
51
|
+
)
|
|
@@ -19,7 +19,7 @@ class RGBCameraConfig:
|
|
|
19
19
|
_target_: The target class to instantiate.
|
|
20
20
|
name: A unique name for the sensor instance.
|
|
21
21
|
enable: Whether the sensor is enabled.
|
|
22
|
-
use_rtc: If True, uses the
|
|
22
|
+
use_rtc: If True, uses the RTCSubscriber. If False,
|
|
23
23
|
uses the standard RGBCameraSubscriber with a Zenoh topic.
|
|
24
24
|
enable_fps_tracking: If True, tracks and logs the FPS of the stream.
|
|
25
25
|
fps_log_interval: The interval in seconds for logging FPS.
|
|
@@ -19,7 +19,7 @@ class ZedCameraConfig:
|
|
|
19
19
|
_target_: The target class to instantiate.
|
|
20
20
|
name: A unique name for the sensor instance.
|
|
21
21
|
enable: Whether the sensor is enabled.
|
|
22
|
-
use_rtc: If True, RGB streams use the
|
|
22
|
+
use_rtc: If True, RGB streams use the RTCSubscriber.
|
|
23
23
|
If False, they use the standard RGBCameraSubscriber. Depth
|
|
24
24
|
always uses a standard Zenoh subscriber.
|
|
25
25
|
enable_fps_tracking: If True, tracks and logs the FPS of all streams.
|
|
@@ -31,7 +31,7 @@ class ZedCameraConfig:
|
|
|
31
31
|
_target_: str = "dexcontrol.sensors.camera.zed_camera.ZedCameraSensor"
|
|
32
32
|
name: str = "zed_camera"
|
|
33
33
|
enable: bool = False
|
|
34
|
-
use_rtc: bool =
|
|
34
|
+
use_rtc: bool = False
|
|
35
35
|
enable_fps_tracking: bool = False
|
|
36
36
|
fps_log_interval: int = 30
|
|
37
37
|
|
|
@@ -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 RGBCameraConfig, ZedCameraConfig
|
|
21
|
+
from .cameras import LuxonisCameraConfig, RGBCameraConfig, ZedCameraConfig
|
|
22
22
|
from .imu import ChassisIMUConfig, ZedIMUConfig
|
|
23
23
|
from .lidar import RPLidarConfig
|
|
24
24
|
from .ultrasonic import UltraSonicConfig
|
|
@@ -58,6 +58,12 @@ class VegaSensorsConfig:
|
|
|
58
58
|
"""
|
|
59
59
|
|
|
60
60
|
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
|
+
)
|
|
61
67
|
base_left_camera: RGBCameraConfig = field(default_factory=_make_rgb_camera("left"))
|
|
62
68
|
base_right_camera: RGBCameraConfig = field(
|
|
63
69
|
default_factory=_make_rgb_camera("right")
|
|
@@ -73,6 +79,8 @@ class VegaSensorsConfig:
|
|
|
73
79
|
|
|
74
80
|
def __post_init__(self) -> None:
|
|
75
81
|
self.head_camera.enable = False
|
|
82
|
+
self.left_wrist_camera.enable = False
|
|
83
|
+
self.right_wrist_camera.enable = False
|
|
76
84
|
self.base_left_camera.enable = False
|
|
77
85
|
self.base_right_camera.enable = False
|
|
78
86
|
self.base_front_camera.enable = False
|
dexcontrol/config/vega.py
CHANGED
|
@@ -52,6 +52,7 @@ class VegaConfig:
|
|
|
52
52
|
"folded": [1.57079, 0.0, 0, -3.1, 0, 0, -0.69813],
|
|
53
53
|
"folded_closed_hand": [1.57079, 0.0, 0, -3.1, 0, 0, -0.9],
|
|
54
54
|
"L_shape": [0.064, -0.3, 0.0, -1.556, 1.271, 0.0, 0.0],
|
|
55
|
+
"lift_up": [0.064, -0.3, 0.0, -2.756, 1.271, 0.0, 0.0],
|
|
55
56
|
"zero": [-1.57079, 0.0, 0, 0.0, 0, 0, 0.0],
|
|
56
57
|
},
|
|
57
58
|
)
|
|
@@ -67,6 +68,7 @@ class VegaConfig:
|
|
|
67
68
|
"folded": [-1.57079, 0.0, 0, -3.1, 0, 0, 0.69813],
|
|
68
69
|
"folded_closed_hand": [-1.57079, 0.0, 0, -3.1, 0, 0, 0.9],
|
|
69
70
|
"L_shape": [-0.064, 0.3, 0.0, -1.556, -1.271, 0.0, 0.0],
|
|
71
|
+
"lift_up": [-0.064, 0.3, 0.0, -2.756, -1.271, 0.0, 0.0],
|
|
70
72
|
"zero": [1.57079, 0.0, 0, 0.0, 0, 0, 0.0],
|
|
71
73
|
},
|
|
72
74
|
)
|
|
@@ -114,6 +116,7 @@ class VegaConfig:
|
|
|
114
116
|
reboot_query_name: str = "system/reboot"
|
|
115
117
|
clear_error_query_name: str = "system/clear_error"
|
|
116
118
|
led_query_name: str = "system/led"
|
|
119
|
+
soc_ntp_query_name: str = "time/soc"
|
|
117
120
|
|
|
118
121
|
@classmethod
|
|
119
122
|
def register_variant(
|
|
@@ -138,11 +141,22 @@ class Vega1Config(VegaConfig):
|
|
|
138
141
|
control_pub_topic="control/arm/left",
|
|
139
142
|
set_mode_query="mode/arm/left",
|
|
140
143
|
wrench_sub_topic="state/wrench/left",
|
|
144
|
+
ee_pass_through_pub_topic="control/ee_pass_through/left",
|
|
141
145
|
joint_name=[f"L_arm_j{i + 1}" for i in range(7)],
|
|
146
|
+
joint_limit=[
|
|
147
|
+
[-3.071, 3.071],
|
|
148
|
+
[-0.453, 1.553],
|
|
149
|
+
[-3.071, 3.071],
|
|
150
|
+
[-3.071, 0.244],
|
|
151
|
+
[-3.071, 3.071],
|
|
152
|
+
[-1.396, 1.396],
|
|
153
|
+
[-1.378, 1.117],
|
|
154
|
+
],
|
|
142
155
|
pose_pool={
|
|
143
156
|
"folded": [1.57079, 0.0, 0, -3.1, 0, 0, -0.69813],
|
|
144
157
|
"folded_closed_hand": [1.57079, 0.0, 0, -3.1, 0, 0, -0.9],
|
|
145
158
|
"L_shape": [0.064, 0.3, 0.0, -1.556, 1.271, 0.0, 0.0],
|
|
159
|
+
"lift_up": [0.064, 0.3, 0.0, -2.756, 1.271, 0.0, 0.0],
|
|
146
160
|
"zero": [-1.57079, 0.0, 0, 0.0, 0, 0, 0.0],
|
|
147
161
|
},
|
|
148
162
|
)
|
|
@@ -153,11 +167,22 @@ class Vega1Config(VegaConfig):
|
|
|
153
167
|
control_pub_topic="control/arm/right",
|
|
154
168
|
set_mode_query="mode/arm/right",
|
|
155
169
|
wrench_sub_topic="state/wrench/right",
|
|
170
|
+
ee_pass_through_pub_topic="control/ee_pass_through/right",
|
|
156
171
|
joint_name=[f"R_arm_j{i + 1}" for i in range(7)],
|
|
172
|
+
joint_limit=[
|
|
173
|
+
[-3.071, 3.071],
|
|
174
|
+
[-1.553, 0.453],
|
|
175
|
+
[-3.071, 3.071],
|
|
176
|
+
[-3.071, 0.244],
|
|
177
|
+
[-3.071, 3.071],
|
|
178
|
+
[-1.396, 1.396],
|
|
179
|
+
[-1.117, 1.378],
|
|
180
|
+
],
|
|
157
181
|
pose_pool={
|
|
158
182
|
"folded": [-1.57079, 0.0, 0, -3.1, 0, 0, 0.69813],
|
|
159
183
|
"folded_closed_hand": [-1.57079, 0.0, 0, -3.1, 0, 0, 0.9],
|
|
160
184
|
"L_shape": [-0.064, -0.3, 0.0, -1.556, -1.271, 0.0, 0.0],
|
|
185
|
+
"lift_up": [-0.064, -0.3, 0.0, -2.756, -1.271, 0.0, 0.0],
|
|
161
186
|
"zero": [1.57079, 0.0, 0, 0.0, 0, 0, 0.0],
|
|
162
187
|
},
|
|
163
188
|
)
|
|
@@ -169,8 +194,11 @@ class Vega1Config(VegaConfig):
|
|
|
169
194
|
)
|
|
170
195
|
head: HeadConfig = field(
|
|
171
196
|
default_factory=lambda: HeadConfig(
|
|
172
|
-
|
|
173
|
-
|
|
197
|
+
joint_limit=[
|
|
198
|
+
[-1.483, 1.483],
|
|
199
|
+
[-2.792, 2.792],
|
|
200
|
+
[-1.378, 1.378],
|
|
201
|
+
],
|
|
174
202
|
)
|
|
175
203
|
)
|
|
176
204
|
|
dexcontrol/core/arm.py
CHANGED
|
@@ -15,7 +15,7 @@ communication and the ArmWrenchSensor class for reading wrench sensor data.
|
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
17
|
import time
|
|
18
|
-
from typing import Final, Literal
|
|
18
|
+
from typing import Any, Final, Literal
|
|
19
19
|
|
|
20
20
|
import numpy as np
|
|
21
21
|
import zenoh
|
|
@@ -58,6 +58,12 @@ class Arm(RobotJointComponent):
|
|
|
58
58
|
state_message_type=dexcontrol_msg_pb2.ArmState,
|
|
59
59
|
zenoh_session=zenoh_session,
|
|
60
60
|
joint_name=configs.joint_name,
|
|
61
|
+
joint_limit=configs.joint_limit
|
|
62
|
+
if hasattr(configs, "joint_limit")
|
|
63
|
+
else None,
|
|
64
|
+
joint_vel_limit=configs.joint_vel_limit
|
|
65
|
+
if hasattr(configs, "joint_vel_limit")
|
|
66
|
+
else None,
|
|
61
67
|
pose_pool=configs.pose_pool,
|
|
62
68
|
)
|
|
63
69
|
|
|
@@ -72,6 +78,22 @@ class Arm(RobotJointComponent):
|
|
|
72
78
|
configs.wrench_sub_topic, zenoh_session
|
|
73
79
|
)
|
|
74
80
|
|
|
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
|
+
)
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
self._default_control_hz = configs.default_control_hz
|
|
89
|
+
if self._joint_vel_limit is not None:
|
|
90
|
+
if np.any(self._joint_vel_limit > 2.8):
|
|
91
|
+
logger.warning(
|
|
92
|
+
"Joint velocity limit is greater than 2.8. This is not recommended."
|
|
93
|
+
)
|
|
94
|
+
self._joint_vel_limit = np.clip(self._joint_vel_limit, 0, 2.8)
|
|
95
|
+
logger.warning("Joint velocity limit is clamped to 2.8")
|
|
96
|
+
|
|
75
97
|
def set_mode(self, mode: Literal["position", "disable"]) -> None:
|
|
76
98
|
"""Sets the operating mode of the arm.
|
|
77
99
|
|
|
@@ -112,6 +134,8 @@ class Arm(RobotJointComponent):
|
|
|
112
134
|
relative: bool = False,
|
|
113
135
|
wait_time: float = 0.0,
|
|
114
136
|
wait_kwargs: dict[str, float] | None = None,
|
|
137
|
+
exit_on_reach: bool = False,
|
|
138
|
+
exit_on_reach_kwargs: dict[str, Any] | None = None,
|
|
115
139
|
) -> None:
|
|
116
140
|
"""Controls the arm in joint position mode.
|
|
117
141
|
|
|
@@ -126,11 +150,19 @@ class Arm(RobotJointComponent):
|
|
|
126
150
|
immediately. If wait_time is greater than 0, the joint positions will
|
|
127
151
|
be interpolated between the current position and the target position,
|
|
128
152
|
and the function will wait for the specified time between each movement.
|
|
153
|
+
|
|
154
|
+
**IMPORTANT: When wait_time is 0, you MUST call this function repeatedly
|
|
155
|
+
in a high-frequency loop (e.g., 100 Hz). DO NOT call it just once!**
|
|
156
|
+
The function is designed for continuous control when wait_time=0.
|
|
157
|
+
The highest frequency that the user can call this function is 500 Hz.
|
|
158
|
+
|
|
159
|
+
|
|
129
160
|
wait_kwargs: Keyword arguments for the interpolation (only used if
|
|
130
161
|
wait_time > 0). Supported keys:
|
|
131
162
|
- control_hz: Control frequency in Hz (default: 100).
|
|
132
163
|
- max_vel: Maximum velocity in rad/s (default: 0.5).
|
|
133
|
-
|
|
164
|
+
exit_on_reach: If True, the function will exit when the joint positions are reached.
|
|
165
|
+
exit_on_reach_kwargs: Optional parameters for exit when the joint positions are reached.
|
|
134
166
|
Raises:
|
|
135
167
|
ValueError: If joint_pos dictionary contains invalid joint names.
|
|
136
168
|
"""
|
|
@@ -140,53 +172,58 @@ class Arm(RobotJointComponent):
|
|
|
140
172
|
resolved_joint_pos = (
|
|
141
173
|
self._resolve_relative_joint_cmd(joint_pos) if relative else joint_pos
|
|
142
174
|
)
|
|
175
|
+
resolved_joint_pos = self._convert_joint_cmd_to_array(resolved_joint_pos)
|
|
176
|
+
if self._joint_limit is not None:
|
|
177
|
+
resolved_joint_pos = np.clip(
|
|
178
|
+
resolved_joint_pos, self._joint_limit[:, 0], self._joint_limit[:, 1]
|
|
179
|
+
)
|
|
143
180
|
|
|
144
181
|
if wait_time > 0.0:
|
|
145
|
-
self._execute_trajectory_motion(
|
|
182
|
+
self._execute_trajectory_motion(
|
|
183
|
+
resolved_joint_pos,
|
|
184
|
+
wait_time,
|
|
185
|
+
wait_kwargs,
|
|
186
|
+
exit_on_reach,
|
|
187
|
+
exit_on_reach_kwargs,
|
|
188
|
+
)
|
|
146
189
|
else:
|
|
147
|
-
# Convert to array format
|
|
148
|
-
if isinstance(resolved_joint_pos, (list, dict)):
|
|
149
|
-
resolved_joint_pos = self._convert_joint_cmd_to_array(
|
|
150
|
-
resolved_joint_pos
|
|
151
|
-
)
|
|
152
190
|
self._send_position_command(resolved_joint_pos)
|
|
153
191
|
|
|
154
192
|
def _execute_trajectory_motion(
|
|
155
193
|
self,
|
|
156
|
-
|
|
194
|
+
target_joint_pos: Float[np.ndarray, " N"],
|
|
157
195
|
wait_time: float,
|
|
158
196
|
wait_kwargs: dict[str, float],
|
|
197
|
+
exit_on_reach: bool = False,
|
|
198
|
+
exit_on_reach_kwargs: dict[str, Any] | None = None,
|
|
159
199
|
) -> None:
|
|
160
200
|
"""Execute trajectory-based motion to target position.
|
|
161
201
|
|
|
162
202
|
Args:
|
|
163
|
-
|
|
203
|
+
target_joint_pos: Target joint positions as numpy array.
|
|
164
204
|
wait_time: Total time for the motion.
|
|
165
205
|
wait_kwargs: Parameters for trajectory generation.
|
|
206
|
+
exit_on_reach: If True, the function will exit when the joint positions are reached.
|
|
207
|
+
exit_on_reach_kwargs: Optional parameters for exit when the joint positions are reached.
|
|
166
208
|
"""
|
|
167
209
|
# Set default parameters
|
|
168
|
-
control_hz = wait_kwargs.get("control_hz",
|
|
169
|
-
max_vel = wait_kwargs.get("max_vel"
|
|
210
|
+
control_hz = wait_kwargs.get("control_hz", self._default_control_hz)
|
|
211
|
+
max_vel = wait_kwargs.get("max_vel")
|
|
212
|
+
if max_vel is None:
|
|
213
|
+
max_vel = (
|
|
214
|
+
self._joint_vel_limit if self._joint_vel_limit is not None else 2.8
|
|
215
|
+
)
|
|
216
|
+
exit_on_reach_kwargs = exit_on_reach_kwargs or {}
|
|
217
|
+
exit_on_reach_kwargs.setdefault("tolerance", 0.05)
|
|
170
218
|
|
|
171
219
|
# Create rate limiter and get current position
|
|
172
220
|
rate_limiter = RateLimiter(control_hz)
|
|
173
221
|
current_joint_pos = self.get_joint_pos().copy()
|
|
174
222
|
|
|
175
|
-
# Convert input to numpy array for trajectory generation
|
|
176
|
-
if isinstance(joint_pos, (list, dict)):
|
|
177
|
-
target_pos = (
|
|
178
|
-
self._convert_dict_to_array(joint_pos)
|
|
179
|
-
if isinstance(joint_pos, dict)
|
|
180
|
-
else np.array(joint_pos, dtype=np.float32)
|
|
181
|
-
)
|
|
182
|
-
else:
|
|
183
|
-
target_pos = joint_pos
|
|
184
|
-
|
|
185
223
|
# Generate trajectory using utility function
|
|
186
224
|
trajectory, _ = generate_linear_trajectory(
|
|
187
|
-
current_joint_pos,
|
|
225
|
+
current_joint_pos, target_joint_pos, max_vel, control_hz
|
|
188
226
|
)
|
|
189
|
-
|
|
190
227
|
# Execute trajectory with time limit
|
|
191
228
|
start_time = time.time()
|
|
192
229
|
for pos in trajectory:
|
|
@@ -197,8 +234,12 @@ class Arm(RobotJointComponent):
|
|
|
197
234
|
|
|
198
235
|
# Hold final position for remaining time
|
|
199
236
|
while time.time() - start_time < wait_time:
|
|
200
|
-
self._send_position_command(
|
|
237
|
+
self._send_position_command(target_joint_pos)
|
|
201
238
|
rate_limiter.sleep()
|
|
239
|
+
if exit_on_reach and self.is_joint_pos_reached(
|
|
240
|
+
target_joint_pos, **exit_on_reach_kwargs
|
|
241
|
+
):
|
|
242
|
+
break
|
|
202
243
|
|
|
203
244
|
def set_joint_pos_vel(
|
|
204
245
|
self,
|
|
@@ -208,6 +249,16 @@ class Arm(RobotJointComponent):
|
|
|
208
249
|
) -> None:
|
|
209
250
|
"""Controls the arm in joint position mode with a velocity feedforward term.
|
|
210
251
|
|
|
252
|
+
Warning:
|
|
253
|
+
The joint_vel parameter should be well-planned (such as from a trajectory planner).
|
|
254
|
+
Sending poorly planned or inappropriate joint velocity commands can cause the robot
|
|
255
|
+
to behave unexpectedly or potentially get damaged. Ensure velocity commands are
|
|
256
|
+
smooth, within safe limits, and properly coordinated across all joints.
|
|
257
|
+
|
|
258
|
+
Additionally, this command MUST be called at high frequency (e.g., 100Hz) to take
|
|
259
|
+
effect properly. DO NOT call this function just once or at low frequency, as this
|
|
260
|
+
can lead to unpredictable robot behavior.
|
|
261
|
+
|
|
211
262
|
Args:
|
|
212
263
|
joint_pos: Joint positions as either:
|
|
213
264
|
- List of joint values [j1, j2, ..., j7]
|
|
@@ -217,6 +268,7 @@ class Arm(RobotJointComponent):
|
|
|
217
268
|
- List of joint values [v1, v2, ..., v7]
|
|
218
269
|
- Numpy array with shape (7,), in radians/sec
|
|
219
270
|
- Dictionary of joint names and velocity values
|
|
271
|
+
|
|
220
272
|
relative: If True, the joint positions are relative to the current position.
|
|
221
273
|
|
|
222
274
|
Raises:
|
|
@@ -225,25 +277,15 @@ class Arm(RobotJointComponent):
|
|
|
225
277
|
resolved_joint_pos = (
|
|
226
278
|
self._resolve_relative_joint_cmd(joint_pos) if relative else joint_pos
|
|
227
279
|
)
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
self._convert_dict_to_array(resolved_joint_pos)
|
|
233
|
-
if isinstance(resolved_joint_pos, dict)
|
|
234
|
-
else np.array(resolved_joint_pos, dtype=np.float32)
|
|
235
|
-
)
|
|
236
|
-
else:
|
|
237
|
-
target_pos = resolved_joint_pos
|
|
238
|
-
|
|
239
|
-
if isinstance(joint_vel, (list, dict)):
|
|
240
|
-
target_vel = (
|
|
241
|
-
self._convert_dict_to_array(joint_vel)
|
|
242
|
-
if isinstance(joint_vel, dict)
|
|
243
|
-
else np.array(joint_vel, dtype=np.float32)
|
|
280
|
+
resolved_joint_pos = self._convert_joint_cmd_to_array(resolved_joint_pos)
|
|
281
|
+
if self._joint_limit is not None:
|
|
282
|
+
resolved_joint_pos = np.clip(
|
|
283
|
+
resolved_joint_pos, self._joint_limit[:, 0], self._joint_limit[:, 1]
|
|
244
284
|
)
|
|
245
|
-
|
|
246
|
-
|
|
285
|
+
target_pos = resolved_joint_pos
|
|
286
|
+
target_vel = self._convert_joint_cmd_to_array(
|
|
287
|
+
joint_vel, clip_value=self._joint_vel_limit
|
|
288
|
+
)
|
|
247
289
|
|
|
248
290
|
control_msg = dexcontrol_msg_pb2.ArmCommand(
|
|
249
291
|
command_type=dexcontrol_msg_pb2.ArmCommand.CommandType.VELOCITY_FEEDFORWARD,
|
|
@@ -269,6 +311,16 @@ class Arm(RobotJointComponent):
|
|
|
269
311
|
if self.wrench_sensor:
|
|
270
312
|
self.wrench_sensor.shutdown()
|
|
271
313
|
|
|
314
|
+
def send_ee_pass_through_message(self, message: bytes):
|
|
315
|
+
"""Send an end effector pass through message to the robot arm.
|
|
316
|
+
|
|
317
|
+
Args:
|
|
318
|
+
message: The message to send to the robot arm.
|
|
319
|
+
"""
|
|
320
|
+
control_msg = dexcontrol_msg_pb2.EndEffectorPassThroughCommand(data=message)
|
|
321
|
+
control_data = control_msg.SerializeToString()
|
|
322
|
+
self._ee_pass_through_publisher.put(control_data)
|
|
323
|
+
|
|
272
324
|
|
|
273
325
|
class ArmWrenchSensor(RobotComponent):
|
|
274
326
|
"""Wrench sensor reader for the robot arm.
|