dexcontrol 0.2.10__py3-none-any.whl → 0.3.0__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.
- dexcontrol/__init__.py +2 -0
- dexcontrol/config/core/arm.py +5 -1
- dexcontrol/config/core/chassis.py +9 -4
- dexcontrol/config/core/hand.py +2 -1
- 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 +34 -3
- dexcontrol/core/arm.py +103 -58
- dexcontrol/core/chassis.py +146 -115
- dexcontrol/core/component.py +83 -20
- dexcontrol/core/hand.py +74 -39
- dexcontrol/core/head.py +41 -28
- dexcontrol/core/misc.py +256 -25
- dexcontrol/core/robot_query_interface.py +440 -0
- dexcontrol/core/torso.py +28 -10
- dexcontrol/proto/dexcontrol_msg_pb2.py +27 -37
- dexcontrol/proto/dexcontrol_msg_pb2.pyi +111 -126
- dexcontrol/proto/dexcontrol_query_pb2.py +39 -35
- dexcontrol/proto/dexcontrol_query_pb2.pyi +41 -4
- dexcontrol/robot.py +266 -409
- dexcontrol/sensors/__init__.py +2 -1
- dexcontrol/sensors/camera/__init__.py +2 -0
- dexcontrol/sensors/camera/luxonis_camera.py +169 -0
- dexcontrol/sensors/camera/zed_camera.py +17 -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/os_utils.py +183 -1
- dexcontrol/utils/pb_utils.py +0 -22
- 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 +288 -2
- {dexcontrol-0.2.10.dist-info → dexcontrol-0.3.0.dist-info}/METADATA +15 -2
- dexcontrol-0.3.0.dist-info/RECORD +76 -0
- dexcontrol-0.2.10.dist-info/RECORD +0 -72
- {dexcontrol-0.2.10.dist-info → dexcontrol-0.3.0.dist-info}/WHEEL +0 -0
- {dexcontrol-0.2.10.dist-info → dexcontrol-0.3.0.dist-info}/licenses/LICENSE +0 -0
dexcontrol/robot.py
CHANGED
|
@@ -24,16 +24,11 @@ from __future__ import annotations
|
|
|
24
24
|
|
|
25
25
|
import os
|
|
26
26
|
import signal
|
|
27
|
+
import sys
|
|
27
28
|
import threading
|
|
28
29
|
import time
|
|
29
30
|
import weakref
|
|
30
|
-
from typing import
|
|
31
|
-
TYPE_CHECKING,
|
|
32
|
-
Any,
|
|
33
|
-
Final,
|
|
34
|
-
Literal,
|
|
35
|
-
cast,
|
|
36
|
-
)
|
|
31
|
+
from typing import TYPE_CHECKING, Any, Final, Literal, cast
|
|
37
32
|
|
|
38
33
|
import hydra.utils
|
|
39
34
|
import numpy as np
|
|
@@ -46,19 +41,19 @@ from rich.table import Table
|
|
|
46
41
|
import dexcontrol
|
|
47
42
|
from dexcontrol.config.vega import VegaConfig, get_vega_config
|
|
48
43
|
from dexcontrol.core.component import RobotComponent
|
|
49
|
-
from dexcontrol.
|
|
44
|
+
from dexcontrol.core.hand import HandType
|
|
45
|
+
from dexcontrol.core.misc import ServerLogSubscriber
|
|
46
|
+
from dexcontrol.core.robot_query_interface import RobotQueryInterface
|
|
50
47
|
from dexcontrol.sensors import Sensors
|
|
51
48
|
from dexcontrol.utils.constants import ROBOT_NAME_ENV_VAR
|
|
52
|
-
from dexcontrol.utils.os_utils import
|
|
53
|
-
from dexcontrol.utils.pb_utils import (
|
|
54
|
-
TYPE_SOFTWARE_VERSION,
|
|
55
|
-
ComponentStatus,
|
|
56
|
-
software_version_to_dict,
|
|
57
|
-
status_to_dict,
|
|
58
|
-
)
|
|
49
|
+
from dexcontrol.utils.os_utils import check_version_compatibility, get_robot_model
|
|
59
50
|
from dexcontrol.utils.rate_limiter import RateLimiter
|
|
60
51
|
from dexcontrol.utils.trajectory_utils import generate_linear_trajectory
|
|
61
|
-
from dexcontrol.utils.
|
|
52
|
+
from dexcontrol.utils.zenoh_utils import (
|
|
53
|
+
close_zenoh_session_with_timeout,
|
|
54
|
+
create_zenoh_session,
|
|
55
|
+
wait_for_zenoh_cleanup,
|
|
56
|
+
)
|
|
62
57
|
|
|
63
58
|
if TYPE_CHECKING:
|
|
64
59
|
from dexcontrol.core.arm import Arm
|
|
@@ -74,55 +69,25 @@ _active_robots: weakref.WeakSet[Robot] = weakref.WeakSet()
|
|
|
74
69
|
_signal_handlers_registered: bool = False
|
|
75
70
|
|
|
76
71
|
|
|
77
|
-
def _signal_handler(signum: int, frame: Any) -> None:
|
|
78
|
-
"""Signal handler to shutdown all active Robot instances.
|
|
79
|
-
|
|
80
|
-
Args:
|
|
81
|
-
signum: Signal number received (e.g., SIGINT, SIGTERM).
|
|
82
|
-
frame: Current stack frame (unused).
|
|
83
|
-
"""
|
|
84
|
-
logger.info(f"Received signal {signum}, shutting down all active robots...")
|
|
85
|
-
# Create a list copy to avoid modification during iteration
|
|
86
|
-
robots_to_shutdown = list(_active_robots)
|
|
87
|
-
for robot in robots_to_shutdown:
|
|
88
|
-
logger.info(f"Shutting down robot: {robot}")
|
|
89
|
-
try:
|
|
90
|
-
robot.shutdown()
|
|
91
|
-
except Exception as e: # pylint: disable=broad-except
|
|
92
|
-
logger.error(f"Error during robot shutdown: {e}", exc_info=True)
|
|
93
|
-
logger.info("All robots shutdown complete")
|
|
94
|
-
os._exit(1)
|
|
95
|
-
|
|
96
|
-
|
|
97
72
|
def _register_signal_handlers() -> None:
|
|
98
|
-
"""Register signal handlers for graceful shutdown.
|
|
99
|
-
|
|
100
|
-
This function ensures signal handlers are registered only once and sets up
|
|
101
|
-
handlers for SIGINT (Ctrl+C), SIGTERM, and SIGHUP (on Unix systems).
|
|
102
|
-
"""
|
|
73
|
+
"""Register signal handlers for graceful shutdown."""
|
|
103
74
|
global _signal_handlers_registered
|
|
104
75
|
if _signal_handlers_registered:
|
|
105
76
|
return
|
|
106
77
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
78
|
+
def signal_handler(signum: int, frame: Any) -> None:
|
|
79
|
+
sys.exit(0)
|
|
80
|
+
|
|
81
|
+
signal.signal(signal.SIGINT, signal_handler)
|
|
82
|
+
signal.signal(signal.SIGTERM, signal_handler)
|
|
110
83
|
|
|
111
|
-
# On Unix systems, also handle SIGHUP
|
|
112
84
|
if hasattr(signal, "SIGHUP"):
|
|
113
|
-
signal.signal(signal.SIGHUP,
|
|
85
|
+
signal.signal(signal.SIGHUP, signal_handler)
|
|
114
86
|
|
|
115
87
|
_signal_handlers_registered = True
|
|
116
88
|
|
|
117
89
|
|
|
118
|
-
class
|
|
119
|
-
"""Type hints for component configuration."""
|
|
120
|
-
|
|
121
|
-
_target_: str
|
|
122
|
-
configs: dict[str, Any]
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
class Robot:
|
|
90
|
+
class Robot(RobotQueryInterface):
|
|
126
91
|
"""Main interface class for robot control and monitoring.
|
|
127
92
|
|
|
128
93
|
This class serves as the primary interface for interacting with a robot system.
|
|
@@ -131,33 +96,32 @@ class Robot:
|
|
|
131
96
|
system-wide operations like status monitoring, trajectory execution, and component
|
|
132
97
|
control.
|
|
133
98
|
|
|
134
|
-
The Robot class supports context manager usage and automatic cleanup on program
|
|
135
|
-
interruption through signal handlers.
|
|
136
|
-
|
|
137
99
|
Example usage:
|
|
138
100
|
# Using context manager (recommended)
|
|
139
101
|
with Robot() as robot:
|
|
140
102
|
robot.set_joint_pos({"left_arm": [0, 0, 0, 0, 0, 0, 0]})
|
|
103
|
+
version_info = robot.get_version_info()
|
|
141
104
|
|
|
142
105
|
# Manual usage with explicit shutdown
|
|
143
106
|
robot = Robot()
|
|
144
107
|
try:
|
|
145
108
|
robot.set_joint_pos({"left_arm": [0, 0, 0, 0, 0, 0, 0]})
|
|
109
|
+
hand_types = robot.query_hand_type()
|
|
146
110
|
finally:
|
|
147
111
|
robot.shutdown()
|
|
148
112
|
|
|
149
113
|
Attributes:
|
|
150
|
-
left_arm: Left arm component interface.
|
|
151
|
-
right_arm: Right arm component interface.
|
|
152
|
-
left_hand: Left hand component interface.
|
|
153
|
-
right_hand: Right hand component interface.
|
|
154
|
-
head: Head component interface.
|
|
155
|
-
chassis: Chassis component interface.
|
|
156
|
-
torso: Torso component interface.
|
|
114
|
+
left_arm: Left arm component interface (7-DOF manipulator).
|
|
115
|
+
right_arm: Right arm component interface (7-DOF manipulator).
|
|
116
|
+
left_hand: Left hand component interface (conditional, based on hardware).
|
|
117
|
+
right_hand: Right hand component interface (conditional, based on hardware).
|
|
118
|
+
head: Head component interface (3-DOF pan-tilt-roll).
|
|
119
|
+
chassis: Chassis component interface (mobile base).
|
|
120
|
+
torso: Torso component interface (1-DOF pitch).
|
|
157
121
|
battery: Battery monitoring interface.
|
|
158
122
|
estop: Emergency stop interface.
|
|
159
123
|
heartbeat: Heartbeat monitoring interface.
|
|
160
|
-
sensors: Sensor systems interface.
|
|
124
|
+
sensors: Sensor systems interface (cameras, IMU, lidar, etc.).
|
|
161
125
|
"""
|
|
162
126
|
|
|
163
127
|
# Type annotations for dynamically created attributes
|
|
@@ -204,25 +168,20 @@ class Robot:
|
|
|
204
168
|
robot_model = get_robot_model()
|
|
205
169
|
self._robot_model: Final[str] = robot_model
|
|
206
170
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
raise ValueError(f"Failed to load robot configuration: {e}") from e
|
|
171
|
+
# Load configuration and initialize zenoh session
|
|
172
|
+
self._configs: Final[VegaConfig] = configs or get_vega_config(robot_model)
|
|
173
|
+
self._zenoh_session: zenoh.Session = create_zenoh_session(zenoh_config_file)
|
|
211
174
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
zenoh_config_file
|
|
215
|
-
)
|
|
216
|
-
except Exception as e:
|
|
217
|
-
raise RuntimeError(f"Failed to initialize zenoh session: {e}") from e
|
|
175
|
+
# Initialize ZenohQueryable parent class
|
|
176
|
+
super().__init__(self._zenoh_session, self._configs)
|
|
218
177
|
|
|
219
178
|
self._robot_name: Final[str] = os.getenv(ROBOT_NAME_ENV_VAR, "robot")
|
|
220
|
-
self._pv_components:
|
|
221
|
-
"left_hand",
|
|
222
|
-
"right_hand",
|
|
179
|
+
self._pv_components: list[str] = [
|
|
223
180
|
"head",
|
|
224
181
|
"torso",
|
|
225
182
|
]
|
|
183
|
+
self._log_subscriber = ServerLogSubscriber(self._zenoh_session)
|
|
184
|
+
self._hand_types: dict[str, HandType] = {}
|
|
226
185
|
|
|
227
186
|
# Register for automatic shutdown on signals if enabled
|
|
228
187
|
if auto_shutdown:
|
|
@@ -231,36 +190,11 @@ class Robot:
|
|
|
231
190
|
|
|
232
191
|
self._print_initialization_info(robot_model)
|
|
233
192
|
|
|
234
|
-
# Initialize robot
|
|
235
|
-
|
|
236
|
-
config_dict = omegaconf.OmegaConf.to_container(self._configs, resolve=True)
|
|
237
|
-
if not isinstance(config_dict, dict):
|
|
238
|
-
raise ValueError("Invalid configuration format")
|
|
239
|
-
self._init_components(cast(dict[str, Any], config_dict))
|
|
240
|
-
except Exception as e:
|
|
241
|
-
self.shutdown() # Clean up on initialization failure
|
|
242
|
-
raise RuntimeError(f"Failed to initialize robot components: {e}") from e
|
|
193
|
+
# Initialize robot components with safe error handling
|
|
194
|
+
self._safe_initialize_components()
|
|
243
195
|
|
|
244
|
-
#
|
|
245
|
-
|
|
246
|
-
self._wait_for_components()
|
|
247
|
-
except Exception as e:
|
|
248
|
-
self.shutdown() # Clean up on initialization failure
|
|
249
|
-
raise RuntimeError(f"Failed to activate components: {e}") from e
|
|
250
|
-
|
|
251
|
-
try:
|
|
252
|
-
self.sensors = Sensors(self._configs.sensors, self._zenoh_session)
|
|
253
|
-
self.sensors.wait_for_all_active()
|
|
254
|
-
except Exception as e:
|
|
255
|
-
self.shutdown() # Clean up on initialization failure
|
|
256
|
-
raise RuntimeError(f"Failed to initialize sensors: {e}") from e
|
|
257
|
-
|
|
258
|
-
# Set default modes
|
|
259
|
-
try:
|
|
260
|
-
self._set_default_modes()
|
|
261
|
-
except Exception as e:
|
|
262
|
-
self.shutdown() # Clean up on initialization failure
|
|
263
|
-
raise RuntimeError(f"Failed to set default modes: {e}") from e
|
|
196
|
+
# Check version compatibility using new JSON interface
|
|
197
|
+
self._check_version_compatibility()
|
|
264
198
|
|
|
265
199
|
@property
|
|
266
200
|
def robot_model(self) -> str:
|
|
@@ -280,33 +214,26 @@ class Robot:
|
|
|
280
214
|
"""
|
|
281
215
|
return self._robot_name
|
|
282
216
|
|
|
283
|
-
def __enter__(self) -> Robot:
|
|
284
|
-
"""
|
|
285
|
-
|
|
286
|
-
Returns:
|
|
287
|
-
Self reference for context management.
|
|
288
|
-
"""
|
|
217
|
+
def __enter__(self) -> "Robot":
|
|
218
|
+
"""Enter context manager."""
|
|
289
219
|
return self
|
|
290
220
|
|
|
291
|
-
def __exit__(
|
|
292
|
-
|
|
293
|
-
) -> None:
|
|
294
|
-
"""Context manager exit with automatic shutdown.
|
|
295
|
-
|
|
296
|
-
Args:
|
|
297
|
-
exc_type: Type of the exception that occurred, if any.
|
|
298
|
-
exc_val: Exception instance that occurred, if any.
|
|
299
|
-
exc_tb: Traceback of the exception that occurred, if any.
|
|
300
|
-
"""
|
|
221
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
222
|
+
"""Exit context manager and clean up resources."""
|
|
301
223
|
self.shutdown()
|
|
302
224
|
|
|
303
225
|
def __del__(self) -> None:
|
|
304
226
|
"""Destructor to ensure cleanup."""
|
|
305
227
|
if not self._shutdown_called:
|
|
306
|
-
logger
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
228
|
+
# Only log if the logger is still available (process might be terminating)
|
|
229
|
+
try:
|
|
230
|
+
logger.warning(
|
|
231
|
+
"Robot instance being destroyed without explicit shutdown call"
|
|
232
|
+
)
|
|
233
|
+
self.shutdown()
|
|
234
|
+
except Exception: # pylint: disable=broad-except
|
|
235
|
+
# During interpreter shutdown, some modules might not be available
|
|
236
|
+
pass
|
|
310
237
|
|
|
311
238
|
def _print_initialization_info(self, robot_model: str | None) -> None:
|
|
312
239
|
"""Print initialization information.
|
|
@@ -326,20 +253,65 @@ class Robot:
|
|
|
326
253
|
|
|
327
254
|
console.print(table)
|
|
328
255
|
|
|
329
|
-
def
|
|
330
|
-
"""
|
|
256
|
+
def _safe_initialize_components(self) -> None:
|
|
257
|
+
"""Safely initialize all robot components with consolidated error handling.
|
|
331
258
|
|
|
332
|
-
|
|
333
|
-
|
|
259
|
+
This method consolidates the initialization of components, sensors, and
|
|
260
|
+
default modes into a single method with unified error handling.
|
|
334
261
|
|
|
335
262
|
Raises:
|
|
336
|
-
RuntimeError: If
|
|
263
|
+
RuntimeError: If any critical initialization step fails.
|
|
337
264
|
"""
|
|
265
|
+
initialization_steps = [
|
|
266
|
+
("robot components", self._initialize_robot_components),
|
|
267
|
+
("component activation", self._wait_for_components),
|
|
268
|
+
("sensors", self._initialize_sensors),
|
|
269
|
+
("default state", self._set_default_state),
|
|
270
|
+
]
|
|
271
|
+
|
|
272
|
+
for step_name, step_function in initialization_steps:
|
|
273
|
+
try:
|
|
274
|
+
step_function()
|
|
275
|
+
except Exception as e:
|
|
276
|
+
self.shutdown()
|
|
277
|
+
raise RuntimeError(
|
|
278
|
+
f"Robot initialization failed at {step_name}: {e}"
|
|
279
|
+
) from e
|
|
280
|
+
|
|
281
|
+
def _initialize_sensors(self) -> None:
|
|
282
|
+
"""Initialize sensors and wait for activation."""
|
|
283
|
+
self.sensors = Sensors(self._configs.sensors, self._zenoh_session)
|
|
284
|
+
self.sensors.wait_for_all_active()
|
|
285
|
+
|
|
286
|
+
def _initialize_robot_components(self) -> None:
|
|
287
|
+
"""Initialize robot components from configuration."""
|
|
288
|
+
config_dict = omegaconf.OmegaConf.to_container(self._configs, resolve=True)
|
|
289
|
+
config_dict = cast(dict[str, Any], config_dict)
|
|
290
|
+
|
|
291
|
+
initialized_components = []
|
|
292
|
+
failed_components = []
|
|
293
|
+
|
|
338
294
|
for component_name, component_config in config_dict.items():
|
|
339
295
|
if component_name == "sensors":
|
|
340
296
|
continue
|
|
341
297
|
|
|
342
298
|
try:
|
|
299
|
+
# Skip hand initialization if the hand is not present on hardware or unknown
|
|
300
|
+
if (
|
|
301
|
+
component_name in ["left_hand", "right_hand"]
|
|
302
|
+
and self._hand_types == {}
|
|
303
|
+
):
|
|
304
|
+
self._hand_types = self.query_hand_type()
|
|
305
|
+
if (
|
|
306
|
+
component_name in ["left_hand", "right_hand"]
|
|
307
|
+
and self._hand_types.get(component_name.split("_")[0])
|
|
308
|
+
== HandType.UNKNOWN
|
|
309
|
+
):
|
|
310
|
+
logger.info(
|
|
311
|
+
f"Skipping {component_name} initialization, no known hand detected."
|
|
312
|
+
)
|
|
313
|
+
continue
|
|
314
|
+
|
|
343
315
|
component_config = getattr(self._configs, str(component_name))
|
|
344
316
|
if (
|
|
345
317
|
not hasattr(component_config, "_target_")
|
|
@@ -347,6 +319,7 @@ class Robot:
|
|
|
347
319
|
):
|
|
348
320
|
continue
|
|
349
321
|
|
|
322
|
+
# Create component configuration
|
|
350
323
|
temp_config = omegaconf.OmegaConf.create(
|
|
351
324
|
{
|
|
352
325
|
"_target_": component_config._target_,
|
|
@@ -355,16 +328,41 @@ class Robot:
|
|
|
355
328
|
},
|
|
356
329
|
}
|
|
357
330
|
)
|
|
331
|
+
|
|
332
|
+
# Handle different hand types
|
|
333
|
+
if component_name in ["left_hand", "right_hand"]:
|
|
334
|
+
hand_type = self._hand_types.get(component_name.split("_")[0])
|
|
335
|
+
temp_config["hand_type"] = hand_type
|
|
336
|
+
|
|
337
|
+
# Instantiate component with error handling
|
|
358
338
|
component_instance = hydra.utils.instantiate(
|
|
359
339
|
temp_config, zenoh_session=self._zenoh_session
|
|
360
340
|
)
|
|
341
|
+
|
|
342
|
+
# Store component instance both as attribute and in tracking dictionaries
|
|
361
343
|
setattr(self, str(component_name), component_instance)
|
|
344
|
+
initialized_components.append(component_name)
|
|
345
|
+
|
|
362
346
|
except Exception as e:
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
347
|
+
logger.error(f"Failed to initialize component {component_name}: {e}")
|
|
348
|
+
failed_components.append(component_name)
|
|
349
|
+
# Continue with other components rather than failing completely
|
|
350
|
+
|
|
351
|
+
# Report initialization summary
|
|
352
|
+
if failed_components:
|
|
353
|
+
logger.warning(
|
|
354
|
+
f"Failed to initialize components: {', '.join(failed_components)}"
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
# Raise error only if no critical components were initialized
|
|
358
|
+
critical_components = ["left_arm", "right_arm", "head", "torso", "chassis"]
|
|
359
|
+
initialized_critical = [
|
|
360
|
+
c for c in critical_components if c in initialized_components
|
|
361
|
+
]
|
|
362
|
+
if not initialized_critical:
|
|
363
|
+
raise RuntimeError("Failed to initialize any critical components")
|
|
366
364
|
|
|
367
|
-
def
|
|
365
|
+
def _set_default_state(self) -> None:
|
|
368
366
|
"""Set default control modes for robot components.
|
|
369
367
|
|
|
370
368
|
Raises:
|
|
@@ -372,21 +370,13 @@ class Robot:
|
|
|
372
370
|
"""
|
|
373
371
|
for arm in ["left_arm", "right_arm"]:
|
|
374
372
|
if component := getattr(self, arm, None):
|
|
375
|
-
|
|
376
|
-
component.set_mode("position")
|
|
377
|
-
except Exception as e:
|
|
378
|
-
raise RuntimeError(
|
|
379
|
-
f"Failed to set default mode for {arm}: {e}"
|
|
380
|
-
) from e
|
|
373
|
+
component.set_modes(["position"] * 7)
|
|
381
374
|
|
|
382
375
|
if head := getattr(self, "head", None):
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
head.set_joint_pos(home_pos)
|
|
388
|
-
except Exception as e:
|
|
389
|
-
raise RuntimeError(f"Failed to set default mode for head: {e}") from e
|
|
376
|
+
head.set_mode("enable")
|
|
377
|
+
home_pos = head.get_predefined_pose("home")
|
|
378
|
+
home_pos = self.compensate_torso_pitch(home_pos, "head")
|
|
379
|
+
head.set_joint_pos(home_pos)
|
|
390
380
|
|
|
391
381
|
def _wait_for_components(self) -> None:
|
|
392
382
|
"""Waits for all critical components to become active.
|
|
@@ -401,20 +391,25 @@ class Robot:
|
|
|
401
391
|
component_names: Final[list[str]] = [
|
|
402
392
|
"left_arm",
|
|
403
393
|
"right_arm",
|
|
404
|
-
"left_hand",
|
|
405
|
-
"right_hand",
|
|
406
394
|
"head",
|
|
407
395
|
"chassis",
|
|
408
396
|
"torso",
|
|
409
397
|
"battery",
|
|
410
398
|
"estop",
|
|
411
399
|
]
|
|
400
|
+
|
|
401
|
+
# Only add hands to component_names if they were actually initialized
|
|
402
|
+
if hasattr(self, "left_hand"):
|
|
403
|
+
component_names.append("left_hand")
|
|
404
|
+
if hasattr(self, "right_hand"):
|
|
405
|
+
component_names.append("right_hand")
|
|
406
|
+
|
|
412
407
|
if self._configs.heartbeat.enabled:
|
|
413
408
|
component_names.append("heartbeat")
|
|
414
409
|
|
|
415
410
|
console = Console()
|
|
416
411
|
actives: list[bool] = []
|
|
417
|
-
timeout_sec: Final[
|
|
412
|
+
timeout_sec: Final[float] = 5.0
|
|
418
413
|
check_interval: Final[float] = 0.1 # Check every 100ms
|
|
419
414
|
|
|
420
415
|
status = console.status(
|
|
@@ -445,7 +440,6 @@ class Robot:
|
|
|
445
440
|
|
|
446
441
|
# Check if we've exceeded timeout
|
|
447
442
|
if time.monotonic() - start_time >= timeout_sec:
|
|
448
|
-
console.log(f"{name} failed to become active")
|
|
449
443
|
actives.append(False)
|
|
450
444
|
break
|
|
451
445
|
|
|
@@ -454,51 +448,110 @@ class Robot:
|
|
|
454
448
|
finally:
|
|
455
449
|
status.stop()
|
|
456
450
|
|
|
457
|
-
if not
|
|
451
|
+
if not any(actives):
|
|
458
452
|
self.shutdown()
|
|
453
|
+
raise RuntimeError(f"No components activated within {timeout_sec}s")
|
|
454
|
+
|
|
455
|
+
if not all(actives):
|
|
459
456
|
inactive = [
|
|
460
457
|
name for name, active in zip(component_names, actives) if not active
|
|
461
458
|
]
|
|
462
|
-
|
|
463
|
-
f"Components failed to activate within {timeout_sec}s: {', '.join(inactive)}"
|
|
459
|
+
logger.error(
|
|
460
|
+
f"Components failed to activate within {timeout_sec}s: {', '.join(inactive)}.\n"
|
|
461
|
+
f"Other components may work, but some features, e.g. collision avoidance, may not work correctly."
|
|
462
|
+
f"Please check the robot status immediately."
|
|
464
463
|
)
|
|
464
|
+
else:
|
|
465
|
+
logger.info("All motor components are active")
|
|
465
466
|
|
|
466
|
-
|
|
467
|
+
def get_component_map(self) -> dict[str, Any]:
|
|
468
|
+
"""Get the component mapping dictionary.
|
|
467
469
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
+
Returns:
|
|
471
|
+
Dictionary mapping component names to component instances.
|
|
472
|
+
"""
|
|
473
|
+
component_map = {
|
|
474
|
+
"left_arm": getattr(self, "left_arm", None),
|
|
475
|
+
"right_arm": getattr(self, "right_arm", None),
|
|
476
|
+
"torso": getattr(self, "torso", None),
|
|
477
|
+
"head": getattr(self, "head", None),
|
|
478
|
+
}
|
|
470
479
|
|
|
471
|
-
|
|
472
|
-
|
|
480
|
+
# Only add hands if they were initialized
|
|
481
|
+
if hasattr(self, "left_hand"):
|
|
482
|
+
component_map["left_hand"] = self.left_hand
|
|
483
|
+
if hasattr(self, "right_hand"):
|
|
484
|
+
component_map["right_hand"] = self.right_hand
|
|
473
485
|
|
|
474
|
-
|
|
475
|
-
|
|
486
|
+
# Remove None values
|
|
487
|
+
return {k: v for k, v in component_map.items() if v is not None}
|
|
488
|
+
|
|
489
|
+
def validate_component_names(self, joint_pos: dict[str, Any]) -> None:
|
|
490
|
+
"""Validate that all component names are valid and initialized.
|
|
491
|
+
|
|
492
|
+
Args:
|
|
493
|
+
joint_pos: Joint position dictionary to validate.
|
|
476
494
|
|
|
477
495
|
Raises:
|
|
478
|
-
|
|
496
|
+
ValueError: If invalid component names are found with detailed guidance.
|
|
479
497
|
"""
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
if config_path is None:
|
|
483
|
-
logger.warning("Using default zenoh config settings")
|
|
484
|
-
return zenoh.open(zenoh.Config())
|
|
485
|
-
return zenoh.open(zenoh.Config.from_file(config_path))
|
|
486
|
-
except Exception as e:
|
|
487
|
-
raise RuntimeError(f"Failed to initialize zenoh session: {e}") from e
|
|
498
|
+
if not joint_pos:
|
|
499
|
+
raise ValueError("Joint position dictionary cannot be empty")
|
|
488
500
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
501
|
+
component_map = self.get_component_map()
|
|
502
|
+
valid_components = set(component_map.keys())
|
|
503
|
+
provided_components = set(joint_pos.keys())
|
|
504
|
+
invalid_components = provided_components - valid_components
|
|
492
505
|
|
|
493
|
-
|
|
494
|
-
|
|
506
|
+
if invalid_components:
|
|
507
|
+
available_msg = (
|
|
508
|
+
f"Available components: {', '.join(sorted(valid_components))}"
|
|
509
|
+
)
|
|
510
|
+
invalid_msg = (
|
|
511
|
+
f"Invalid component names: {', '.join(sorted(invalid_components))}"
|
|
512
|
+
)
|
|
513
|
+
|
|
514
|
+
# Provide helpful suggestions for common mistakes
|
|
515
|
+
suggestions = []
|
|
516
|
+
for invalid in invalid_components:
|
|
517
|
+
if invalid in ["left_hand", "right_hand"]:
|
|
518
|
+
suggestions.append(f"'{invalid}' may not be connected or detected")
|
|
519
|
+
elif invalid.replace("_", "") in [
|
|
520
|
+
c.replace("_", "") for c in valid_components
|
|
521
|
+
]:
|
|
522
|
+
close_match = next(
|
|
523
|
+
(
|
|
524
|
+
c
|
|
525
|
+
for c in valid_components
|
|
526
|
+
if c.replace("_", "") == invalid.replace("_", "")
|
|
527
|
+
),
|
|
528
|
+
None,
|
|
529
|
+
)
|
|
530
|
+
if close_match:
|
|
531
|
+
suggestions.append(
|
|
532
|
+
f"Did you mean '{close_match}' instead of '{invalid}'?"
|
|
533
|
+
)
|
|
534
|
+
|
|
535
|
+
error_msg = f"{invalid_msg}. {available_msg}."
|
|
536
|
+
if suggestions:
|
|
537
|
+
error_msg += f" Suggestions: {' '.join(suggestions)}"
|
|
538
|
+
|
|
539
|
+
raise ValueError(error_msg)
|
|
540
|
+
|
|
541
|
+
def _check_version_compatibility(self) -> None:
|
|
542
|
+
"""Check version compatibility between client and server.
|
|
543
|
+
|
|
544
|
+
This method uses the new JSON-based version interface to:
|
|
545
|
+
1. Compare client library version with server's minimum required version
|
|
546
|
+
2. Check server component versions for compatibility
|
|
547
|
+
3. Provide clear guidance for version mismatches
|
|
495
548
|
"""
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
549
|
+
try:
|
|
550
|
+
version_info = self.get_version_info(show=False)
|
|
551
|
+
check_version_compatibility(version_info)
|
|
552
|
+
except Exception as e:
|
|
553
|
+
# Log error but don't fail initialization for version check issues
|
|
554
|
+
logger.warning(f"Version compatibility check failed: {e}")
|
|
502
555
|
|
|
503
556
|
def shutdown(self) -> None:
|
|
504
557
|
"""Cleans up and closes all component connections.
|
|
@@ -511,7 +564,7 @@ class Robot:
|
|
|
511
564
|
logger.warning("Shutdown already called, skipping")
|
|
512
565
|
return
|
|
513
566
|
|
|
514
|
-
logger.info("Shutting down robot...")
|
|
567
|
+
logger.info("Shutting down robot components...")
|
|
515
568
|
self._shutdown_called = True
|
|
516
569
|
|
|
517
570
|
# Remove from active robots registry
|
|
@@ -533,9 +586,6 @@ class Robot:
|
|
|
533
586
|
f"Error stopping component {component.__class__.__name__}: {e}"
|
|
534
587
|
)
|
|
535
588
|
|
|
536
|
-
# Brief delay to ensure all stop operations complete
|
|
537
|
-
time.sleep(0.1)
|
|
538
|
-
|
|
539
589
|
# Shutdown sensors first (they may have background threads)
|
|
540
590
|
try:
|
|
541
591
|
if hasattr(self, "sensors") and self.sensors is not None:
|
|
@@ -558,40 +608,29 @@ class Robot:
|
|
|
558
608
|
# Brief delay to allow component shutdown to complete
|
|
559
609
|
time.sleep(0.1)
|
|
560
610
|
|
|
561
|
-
#
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
"
|
|
566
|
-
nonlocal zenoh_close_success
|
|
567
|
-
try:
|
|
568
|
-
# Brief delay for Zenoh cleanup
|
|
569
|
-
time.sleep(0.1)
|
|
570
|
-
self._zenoh_session.close()
|
|
571
|
-
zenoh_close_success = True
|
|
572
|
-
except Exception as e: # pylint: disable=broad-except
|
|
573
|
-
logger.warning(f"Zenoh session close error: {e}")
|
|
574
|
-
|
|
575
|
-
# Use non-daemon thread for proper cleanup
|
|
576
|
-
close_thread = threading.Thread(target=_close_zenoh_session, daemon=False)
|
|
577
|
-
close_thread.start()
|
|
578
|
-
|
|
579
|
-
# Wait for close with reasonable timeout
|
|
580
|
-
close_thread.join(timeout=3.0)
|
|
581
|
-
|
|
582
|
-
if close_thread.is_alive():
|
|
583
|
-
logger.warning(
|
|
584
|
-
"Zenoh session close timed out after 1s, continuing shutdown"
|
|
585
|
-
)
|
|
586
|
-
# Thread will be left to finish in background, but won't block shutdown
|
|
587
|
-
elif zenoh_close_success:
|
|
588
|
-
logger.debug("Zenoh session closed cleanly")
|
|
589
|
-
|
|
590
|
-
# Brief final delay for cleanup
|
|
591
|
-
time.sleep(0.5)
|
|
611
|
+
# Clean up log subscriber before closing zenoh session
|
|
612
|
+
try:
|
|
613
|
+
self._log_subscriber.shutdown()
|
|
614
|
+
except Exception as e: # pylint: disable=broad-except
|
|
615
|
+
logger.debug(f"Error shutting down log subscriber: {e}")
|
|
592
616
|
|
|
617
|
+
# Close Zenoh session
|
|
618
|
+
close_zenoh_session_with_timeout(self._zenoh_session)
|
|
619
|
+
lingering_threads = wait_for_zenoh_cleanup()
|
|
620
|
+
if lingering_threads:
|
|
621
|
+
self._assist_clean_exit_if_needed()
|
|
593
622
|
logger.info("Robot shutdown complete")
|
|
594
623
|
|
|
624
|
+
def _assist_clean_exit_if_needed(self) -> None:
|
|
625
|
+
"""Assist with clean exit if this is the last robot and we're exiting."""
|
|
626
|
+
if (
|
|
627
|
+
not _active_robots # No more active robots
|
|
628
|
+
and threading.current_thread() is threading.main_thread() # In main thread
|
|
629
|
+
and not hasattr(sys, "ps1") # Not in interactive mode
|
|
630
|
+
):
|
|
631
|
+
logger.debug("Assisting clean exit due to lingering Zenoh threads")
|
|
632
|
+
sys.exit(0)
|
|
633
|
+
|
|
595
634
|
def is_shutdown(self) -> bool:
|
|
596
635
|
"""Check if the robot has been shutdown.
|
|
597
636
|
|
|
@@ -600,143 +639,6 @@ class Robot:
|
|
|
600
639
|
"""
|
|
601
640
|
return self._shutdown_called
|
|
602
641
|
|
|
603
|
-
def get_software_version(
|
|
604
|
-
self, show: bool = True
|
|
605
|
-
) -> dict[str, TYPE_SOFTWARE_VERSION]:
|
|
606
|
-
"""Retrieve software version information for all components.
|
|
607
|
-
|
|
608
|
-
Args:
|
|
609
|
-
show: Whether to display the version information.
|
|
610
|
-
|
|
611
|
-
Returns:
|
|
612
|
-
Dictionary containing version information for all components.
|
|
613
|
-
|
|
614
|
-
Raises:
|
|
615
|
-
RuntimeError: If version information cannot be retrieved.
|
|
616
|
-
"""
|
|
617
|
-
try:
|
|
618
|
-
replies = self._zenoh_session.get(
|
|
619
|
-
resolve_key_name(self._configs.version_info_name)
|
|
620
|
-
)
|
|
621
|
-
version_dict = {}
|
|
622
|
-
for reply in replies:
|
|
623
|
-
if reply.ok and reply.ok.payload:
|
|
624
|
-
version_bytes = reply.ok.payload.to_bytes()
|
|
625
|
-
version_msg = cast(
|
|
626
|
-
dexcontrol_query_pb2.SoftwareVersion,
|
|
627
|
-
dexcontrol_query_pb2.SoftwareVersion.FromString(version_bytes),
|
|
628
|
-
)
|
|
629
|
-
version_dict = software_version_to_dict(version_msg)
|
|
630
|
-
break
|
|
631
|
-
|
|
632
|
-
if show:
|
|
633
|
-
show_software_version(version_dict)
|
|
634
|
-
return version_dict
|
|
635
|
-
except Exception as e:
|
|
636
|
-
raise RuntimeError(f"Failed to retrieve software versions: {e}") from e
|
|
637
|
-
|
|
638
|
-
def get_component_status(
|
|
639
|
-
self, show: bool = True
|
|
640
|
-
) -> dict[str, dict[str, bool | ComponentStatus]]:
|
|
641
|
-
"""Retrieve status information for all components.
|
|
642
|
-
|
|
643
|
-
Args:
|
|
644
|
-
show: Whether to display the status information.
|
|
645
|
-
|
|
646
|
-
Returns:
|
|
647
|
-
Dictionary containing status information for all components.
|
|
648
|
-
|
|
649
|
-
Raises:
|
|
650
|
-
RuntimeError: If status information cannot be retrieved.
|
|
651
|
-
"""
|
|
652
|
-
try:
|
|
653
|
-
replies = self._zenoh_session.get(
|
|
654
|
-
resolve_key_name(self._configs.status_info_name)
|
|
655
|
-
)
|
|
656
|
-
status_dict = {}
|
|
657
|
-
for reply in replies:
|
|
658
|
-
if reply.ok and reply.ok.payload:
|
|
659
|
-
status_bytes = reply.ok.payload.to_bytes()
|
|
660
|
-
status_msg = cast(
|
|
661
|
-
dexcontrol_query_pb2.ComponentStates,
|
|
662
|
-
dexcontrol_query_pb2.ComponentStates.FromString(status_bytes),
|
|
663
|
-
)
|
|
664
|
-
status_dict = status_to_dict(status_msg)
|
|
665
|
-
break
|
|
666
|
-
|
|
667
|
-
if show:
|
|
668
|
-
show_component_status(status_dict)
|
|
669
|
-
return status_dict
|
|
670
|
-
except Exception as e:
|
|
671
|
-
raise RuntimeError(f"Failed to retrieve component status: {e}") from e
|
|
672
|
-
|
|
673
|
-
def reboot_component(self, part: Literal["arm", "chassis", "torso"]) -> None:
|
|
674
|
-
"""Reboot a specific robot component.
|
|
675
|
-
|
|
676
|
-
Args:
|
|
677
|
-
part: Component to reboot ("arm", "chassis", or "torso").
|
|
678
|
-
|
|
679
|
-
Raises:
|
|
680
|
-
ValueError: If the specified component is invalid.
|
|
681
|
-
RuntimeError: If the reboot operation fails.
|
|
682
|
-
"""
|
|
683
|
-
component_map = {
|
|
684
|
-
"arm": dexcontrol_query_pb2.RebootComponent.Component.ARM,
|
|
685
|
-
"chassis": dexcontrol_query_pb2.RebootComponent.Component.CHASSIS,
|
|
686
|
-
"torso": dexcontrol_query_pb2.RebootComponent.Component.TORSO,
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
if part not in component_map:
|
|
690
|
-
raise ValueError(f"Invalid component: {part}")
|
|
691
|
-
|
|
692
|
-
try:
|
|
693
|
-
query_msg = dexcontrol_query_pb2.RebootComponent(
|
|
694
|
-
component=component_map[part]
|
|
695
|
-
)
|
|
696
|
-
self._zenoh_session.get(
|
|
697
|
-
resolve_key_name(self._configs.reboot_query_name),
|
|
698
|
-
payload=query_msg.SerializeToString(),
|
|
699
|
-
)
|
|
700
|
-
logger.info(f"Rebooting component: {part}")
|
|
701
|
-
except Exception as e:
|
|
702
|
-
raise RuntimeError(f"Failed to reboot component {part}: {e}") from e
|
|
703
|
-
|
|
704
|
-
def clear_error(
|
|
705
|
-
self, part: Literal["left_arm", "right_arm", "chassis", "head"] | str
|
|
706
|
-
) -> None:
|
|
707
|
-
"""Clear error state for a specific component.
|
|
708
|
-
|
|
709
|
-
Args:
|
|
710
|
-
part: Component to clear error state for.
|
|
711
|
-
|
|
712
|
-
Raises:
|
|
713
|
-
ValueError: If the specified component is invalid.
|
|
714
|
-
RuntimeError: If the error clearing operation fails.
|
|
715
|
-
"""
|
|
716
|
-
component_map = {
|
|
717
|
-
"left_arm": dexcontrol_query_pb2.ClearError.Component.LEFT_ARM,
|
|
718
|
-
"right_arm": dexcontrol_query_pb2.ClearError.Component.RIGHT_ARM,
|
|
719
|
-
"chassis": dexcontrol_query_pb2.ClearError.Component.CHASSIS,
|
|
720
|
-
"head": dexcontrol_query_pb2.ClearError.Component.HEAD,
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
if part not in component_map:
|
|
724
|
-
raise ValueError(f"Invalid component: {part}")
|
|
725
|
-
|
|
726
|
-
try:
|
|
727
|
-
query_msg = dexcontrol_query_pb2.ClearError(component=component_map[part])
|
|
728
|
-
self._zenoh_session.get(
|
|
729
|
-
resolve_key_name(self._configs.clear_error_query_name),
|
|
730
|
-
handler=lambda reply: logger.info(
|
|
731
|
-
f"Cleared error of {part}: {reply.ok}"
|
|
732
|
-
),
|
|
733
|
-
payload=query_msg.SerializeToString(),
|
|
734
|
-
)
|
|
735
|
-
except Exception as e:
|
|
736
|
-
raise RuntimeError(
|
|
737
|
-
f"Failed to clear error for component {part}: {e}"
|
|
738
|
-
) from e
|
|
739
|
-
|
|
740
642
|
def get_joint_pos_dict(
|
|
741
643
|
self,
|
|
742
644
|
component: Literal[
|
|
@@ -760,14 +662,7 @@ class Robot:
|
|
|
760
662
|
KeyError: If an invalid component name is provided.
|
|
761
663
|
RuntimeError: If joint position retrieval fails.
|
|
762
664
|
"""
|
|
763
|
-
component_map =
|
|
764
|
-
"left_arm": self.left_arm,
|
|
765
|
-
"right_arm": self.right_arm,
|
|
766
|
-
"torso": self.torso,
|
|
767
|
-
"head": self.head,
|
|
768
|
-
"left_hand": self.left_hand,
|
|
769
|
-
"right_hand": self.right_hand,
|
|
770
|
-
}
|
|
665
|
+
component_map = self.get_component_map()
|
|
771
666
|
|
|
772
667
|
try:
|
|
773
668
|
if isinstance(component, str):
|
|
@@ -857,10 +752,10 @@ class Robot:
|
|
|
857
752
|
|
|
858
753
|
try:
|
|
859
754
|
start_time = time.time()
|
|
860
|
-
component_map = self.
|
|
755
|
+
component_map = self.get_component_map()
|
|
861
756
|
|
|
862
757
|
# Validate component names
|
|
863
|
-
self.
|
|
758
|
+
self.validate_component_names(joint_pos)
|
|
864
759
|
|
|
865
760
|
# Separate position-velocity controlled components from others
|
|
866
761
|
pv_components = [c for c in joint_pos if c in self._pv_components]
|
|
@@ -986,6 +881,10 @@ class Robot:
|
|
|
986
881
|
|
|
987
882
|
return adjusted_positions
|
|
988
883
|
|
|
884
|
+
def have_hand(self, side: Literal["left", "right"]) -> bool:
|
|
885
|
+
"""Check if the robot has a hand."""
|
|
886
|
+
return self._hand_types.get(side) != HandType.UNKNOWN
|
|
887
|
+
|
|
989
888
|
def _process_trajectory(
|
|
990
889
|
self, trajectory: dict[str, np.ndarray | dict[str, np.ndarray]]
|
|
991
890
|
) -> dict[str, dict[str, np.ndarray]]:
|
|
@@ -1051,14 +950,7 @@ class Robot:
|
|
|
1051
950
|
ValueError: If invalid component is specified.
|
|
1052
951
|
"""
|
|
1053
952
|
rate_limiter = RateLimiter(control_hz)
|
|
1054
|
-
component_map =
|
|
1055
|
-
"left_arm": self.left_arm,
|
|
1056
|
-
"right_arm": self.right_arm,
|
|
1057
|
-
"torso": self.torso,
|
|
1058
|
-
"head": self.head,
|
|
1059
|
-
"left_hand": self.left_hand,
|
|
1060
|
-
"right_hand": self.right_hand,
|
|
1061
|
-
}
|
|
953
|
+
component_map = self.get_component_map()
|
|
1062
954
|
|
|
1063
955
|
first_component = next(iter(processed_trajectory))
|
|
1064
956
|
trajectory_length = len(processed_trajectory[first_component]["position"])
|
|
@@ -1080,41 +972,6 @@ class Robot:
|
|
|
1080
972
|
)
|
|
1081
973
|
rate_limiter.sleep()
|
|
1082
974
|
|
|
1083
|
-
def _get_component_map(self) -> dict[str, Any]:
|
|
1084
|
-
"""Get the component mapping dictionary.
|
|
1085
|
-
|
|
1086
|
-
Returns:
|
|
1087
|
-
Dictionary mapping component names to component instances.
|
|
1088
|
-
"""
|
|
1089
|
-
return {
|
|
1090
|
-
"left_arm": self.left_arm,
|
|
1091
|
-
"right_arm": self.right_arm,
|
|
1092
|
-
"torso": self.torso,
|
|
1093
|
-
"head": self.head,
|
|
1094
|
-
"left_hand": self.left_hand,
|
|
1095
|
-
"right_hand": self.right_hand,
|
|
1096
|
-
}
|
|
1097
|
-
|
|
1098
|
-
def _validate_component_names(
|
|
1099
|
-
self,
|
|
1100
|
-
joint_pos: dict[str, list[float] | np.ndarray],
|
|
1101
|
-
component_map: dict[str, Any],
|
|
1102
|
-
) -> None:
|
|
1103
|
-
"""Validate that all component names are valid.
|
|
1104
|
-
|
|
1105
|
-
Args:
|
|
1106
|
-
joint_pos: Joint position dictionary.
|
|
1107
|
-
component_map: Component mapping dictionary.
|
|
1108
|
-
|
|
1109
|
-
Raises:
|
|
1110
|
-
ValueError: If invalid component names are found.
|
|
1111
|
-
"""
|
|
1112
|
-
invalid_components = set(joint_pos.keys()) - set(component_map.keys())
|
|
1113
|
-
if invalid_components:
|
|
1114
|
-
raise ValueError(
|
|
1115
|
-
f"Invalid component names: {', '.join(invalid_components)}"
|
|
1116
|
-
)
|
|
1117
|
-
|
|
1118
975
|
def _set_pv_components(
|
|
1119
976
|
self,
|
|
1120
977
|
pv_components: list[str],
|
|
@@ -1177,7 +1034,7 @@ class Robot:
|
|
|
1177
1034
|
exit_on_reach_kwargs: Optional parameters for exit when the joint positions are reached.
|
|
1178
1035
|
"""
|
|
1179
1036
|
control_hz = wait_kwargs.get("control_hz", self.left_arm._default_control_hz)
|
|
1180
|
-
max_vel = wait_kwargs.get("max_vel", self.left_arm.
|
|
1037
|
+
max_vel = wait_kwargs.get("max_vel", self.left_arm._joint_vel_limit)
|
|
1181
1038
|
|
|
1182
1039
|
# Generate trajectories for smooth motion during wait_time
|
|
1183
1040
|
rate_limiter = RateLimiter(control_hz)
|