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/robot.py
CHANGED
|
@@ -27,13 +27,7 @@ import signal
|
|
|
27
27
|
import threading
|
|
28
28
|
import time
|
|
29
29
|
import weakref
|
|
30
|
-
from typing import
|
|
31
|
-
TYPE_CHECKING,
|
|
32
|
-
Any,
|
|
33
|
-
Final,
|
|
34
|
-
Literal,
|
|
35
|
-
cast,
|
|
36
|
-
)
|
|
30
|
+
from typing import TYPE_CHECKING, Any, Final, Literal, cast
|
|
37
31
|
|
|
38
32
|
import hydra.utils
|
|
39
33
|
import numpy as np
|
|
@@ -58,7 +52,12 @@ from dexcontrol.utils.pb_utils import (
|
|
|
58
52
|
)
|
|
59
53
|
from dexcontrol.utils.rate_limiter import RateLimiter
|
|
60
54
|
from dexcontrol.utils.trajectory_utils import generate_linear_trajectory
|
|
61
|
-
from dexcontrol.utils.viz_utils import
|
|
55
|
+
from dexcontrol.utils.viz_utils import (
|
|
56
|
+
show_component_status,
|
|
57
|
+
show_ntp_stats,
|
|
58
|
+
show_software_version,
|
|
59
|
+
)
|
|
60
|
+
from dexcontrol.utils.zenoh_utils import compute_ntp_stats
|
|
62
61
|
|
|
63
62
|
if TYPE_CHECKING:
|
|
64
63
|
from dexcontrol.core.arm import Arm
|
|
@@ -84,14 +83,25 @@ def _signal_handler(signum: int, frame: Any) -> None:
|
|
|
84
83
|
logger.info(f"Received signal {signum}, shutting down all active robots...")
|
|
85
84
|
# Create a list copy to avoid modification during iteration
|
|
86
85
|
robots_to_shutdown = list(_active_robots)
|
|
86
|
+
|
|
87
|
+
# Set a total timeout for all shutdowns
|
|
88
|
+
shutdown_start = time.time()
|
|
89
|
+
max_shutdown_time = 5.0 # Maximum 5 seconds for all shutdowns
|
|
90
|
+
|
|
87
91
|
for robot in robots_to_shutdown:
|
|
92
|
+
if time.time() - shutdown_start > max_shutdown_time:
|
|
93
|
+
logger.warning("Shutdown timeout reached, forcing exit")
|
|
94
|
+
break
|
|
95
|
+
|
|
88
96
|
logger.info(f"Shutting down robot: {robot}")
|
|
89
97
|
try:
|
|
90
98
|
robot.shutdown()
|
|
91
99
|
except Exception as e: # pylint: disable=broad-except
|
|
92
100
|
logger.error(f"Error during robot shutdown: {e}", exc_info=True)
|
|
101
|
+
|
|
93
102
|
logger.info("All robots shutdown complete")
|
|
94
|
-
|
|
103
|
+
# Force exit to ensure the process terminates
|
|
104
|
+
os._exit(0)
|
|
95
105
|
|
|
96
106
|
|
|
97
107
|
def _register_signal_handlers() -> None:
|
|
@@ -255,6 +265,23 @@ class Robot:
|
|
|
255
265
|
self.shutdown() # Clean up on initialization failure
|
|
256
266
|
raise RuntimeError(f"Failed to initialize sensors: {e}") from e
|
|
257
267
|
|
|
268
|
+
# Check soc software version before proceeding
|
|
269
|
+
version_dict = self.get_software_version(show=False)
|
|
270
|
+
min_soc_version = dexcontrol.MIN_SOC_SOFTWARE_VERSION
|
|
271
|
+
soc_version = version_dict.get("soc", {}).get("software_version")
|
|
272
|
+
if soc_version is None:
|
|
273
|
+
logger.warning(
|
|
274
|
+
"Could not determine the software version of the remote driver ('soc'). "
|
|
275
|
+
"Please ensure the remote server is running and reachable."
|
|
276
|
+
)
|
|
277
|
+
elif soc_version < min_soc_version:
|
|
278
|
+
logger.warning(
|
|
279
|
+
f"The remote driver ('soc') software version is too old: {soc_version}. "
|
|
280
|
+
f"Minimum required version is {min_soc_version}.\n"
|
|
281
|
+
f"Please update the remote server or downgrade this project code to match the server version. "
|
|
282
|
+
f"Otherwise, some features may not work correctly or may encounter errors."
|
|
283
|
+
)
|
|
284
|
+
|
|
258
285
|
# Set default modes
|
|
259
286
|
try:
|
|
260
287
|
self._set_default_modes()
|
|
@@ -303,10 +330,50 @@ class Robot:
|
|
|
303
330
|
def __del__(self) -> None:
|
|
304
331
|
"""Destructor to ensure cleanup."""
|
|
305
332
|
if not self._shutdown_called:
|
|
306
|
-
logger
|
|
307
|
-
|
|
333
|
+
# Only log if the logger is still available (process might be terminating)
|
|
334
|
+
try:
|
|
335
|
+
logger.warning(
|
|
336
|
+
"Robot instance being destroyed without explicit shutdown call"
|
|
337
|
+
)
|
|
338
|
+
self.shutdown()
|
|
339
|
+
except Exception: # pylint: disable=broad-except
|
|
340
|
+
# During interpreter shutdown, some modules might not be available
|
|
341
|
+
pass
|
|
342
|
+
|
|
343
|
+
@staticmethod
|
|
344
|
+
def cleanup_all() -> None:
|
|
345
|
+
"""Force cleanup of all Zenoh resources and threads.
|
|
346
|
+
|
|
347
|
+
This method can be called between Robot instances to ensure a clean state.
|
|
348
|
+
It's especially useful when creating multiple Robot instances in the same script.
|
|
349
|
+
|
|
350
|
+
Example:
|
|
351
|
+
robot1 = Robot()
|
|
352
|
+
robot1.shutdown()
|
|
353
|
+
Robot.cleanup_all() # Ensure clean state
|
|
354
|
+
|
|
355
|
+
robot2 = Robot() # Safe to create new instance
|
|
356
|
+
robot2.shutdown()
|
|
357
|
+
"""
|
|
358
|
+
import gc
|
|
359
|
+
|
|
360
|
+
# Force garbage collection to clean up any remaining resources
|
|
361
|
+
gc.collect()
|
|
362
|
+
|
|
363
|
+
# Give threads time to terminate
|
|
364
|
+
time.sleep(1.0)
|
|
365
|
+
|
|
366
|
+
# Log any remaining pyo3-closure threads
|
|
367
|
+
lingering = []
|
|
368
|
+
for thread in threading.enumerate():
|
|
369
|
+
if "pyo3-closure" in thread.name and thread.is_alive():
|
|
370
|
+
lingering.append(thread.name)
|
|
371
|
+
|
|
372
|
+
if lingering:
|
|
373
|
+
logger.debug(
|
|
374
|
+
f"Zenoh threads still active after cleanup: {lingering}. "
|
|
375
|
+
"This is normal and shouldn't affect new Robot instances."
|
|
308
376
|
)
|
|
309
|
-
self.shutdown()
|
|
310
377
|
|
|
311
378
|
def _print_initialization_info(self, robot_model: str | None) -> None:
|
|
312
379
|
"""Print initialization information.
|
|
@@ -339,30 +406,25 @@ class Robot:
|
|
|
339
406
|
if component_name == "sensors":
|
|
340
407
|
continue
|
|
341
408
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
"
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
setattr(self, str(component_name), component_instance)
|
|
362
|
-
except Exception as e:
|
|
363
|
-
raise RuntimeError(
|
|
364
|
-
f"Failed to initialize component {component_name}: {e}"
|
|
365
|
-
) from e
|
|
409
|
+
component_config = getattr(self._configs, str(component_name))
|
|
410
|
+
if (
|
|
411
|
+
not hasattr(component_config, "_target_")
|
|
412
|
+
or not component_config._target_
|
|
413
|
+
):
|
|
414
|
+
continue
|
|
415
|
+
|
|
416
|
+
temp_config = omegaconf.OmegaConf.create(
|
|
417
|
+
{
|
|
418
|
+
"_target_": component_config._target_,
|
|
419
|
+
"configs": {
|
|
420
|
+
k: v for k, v in component_config.items() if k != "_target_"
|
|
421
|
+
},
|
|
422
|
+
}
|
|
423
|
+
)
|
|
424
|
+
component_instance = hydra.utils.instantiate(
|
|
425
|
+
temp_config, zenoh_session=self._zenoh_session
|
|
426
|
+
)
|
|
427
|
+
setattr(self, str(component_name), component_instance)
|
|
366
428
|
|
|
367
429
|
def _set_default_modes(self) -> None:
|
|
368
430
|
"""Set default control modes for robot components.
|
|
@@ -372,21 +434,13 @@ class Robot:
|
|
|
372
434
|
"""
|
|
373
435
|
for arm in ["left_arm", "right_arm"]:
|
|
374
436
|
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
|
|
437
|
+
component.set_mode("position")
|
|
381
438
|
|
|
382
439
|
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
|
|
440
|
+
head.set_mode("enable")
|
|
441
|
+
home_pos = head.get_predefined_pose("home")
|
|
442
|
+
home_pos = self.compensate_torso_pitch(home_pos, "head")
|
|
443
|
+
head.set_joint_pos(home_pos)
|
|
390
444
|
|
|
391
445
|
def _wait_for_components(self) -> None:
|
|
392
446
|
"""Waits for all critical components to become active.
|
|
@@ -414,7 +468,7 @@ class Robot:
|
|
|
414
468
|
|
|
415
469
|
console = Console()
|
|
416
470
|
actives: list[bool] = []
|
|
417
|
-
timeout_sec: Final[
|
|
471
|
+
timeout_sec: Final[float] = 5.0
|
|
418
472
|
check_interval: Final[float] = 0.1 # Check every 100ms
|
|
419
473
|
|
|
420
474
|
status = console.status(
|
|
@@ -445,7 +499,6 @@ class Robot:
|
|
|
445
499
|
|
|
446
500
|
# Check if we've exceeded timeout
|
|
447
501
|
if time.monotonic() - start_time >= timeout_sec:
|
|
448
|
-
console.log(f"{name} failed to become active")
|
|
449
502
|
actives.append(False)
|
|
450
503
|
break
|
|
451
504
|
|
|
@@ -454,13 +507,18 @@ class Robot:
|
|
|
454
507
|
finally:
|
|
455
508
|
status.stop()
|
|
456
509
|
|
|
457
|
-
if not
|
|
510
|
+
if not any(actives):
|
|
458
511
|
self.shutdown()
|
|
512
|
+
raise RuntimeError(f"No components activated within {timeout_sec}s")
|
|
513
|
+
|
|
514
|
+
if not all(actives):
|
|
459
515
|
inactive = [
|
|
460
516
|
name for name, active in zip(component_names, actives) if not active
|
|
461
517
|
]
|
|
462
|
-
|
|
463
|
-
f"Components failed to activate within {timeout_sec}s: {', '.join(inactive)}"
|
|
518
|
+
logger.error(
|
|
519
|
+
f"Components failed to activate within {timeout_sec}s: {', '.join(inactive)}.\n"
|
|
520
|
+
f"Other components may work, but some features, e.g. collision avoidance, may not work correctly."
|
|
521
|
+
f"Please check the robot status immediately."
|
|
464
522
|
)
|
|
465
523
|
|
|
466
524
|
logger.info("All components activated successfully")
|
|
@@ -560,35 +618,75 @@ class Robot:
|
|
|
560
618
|
|
|
561
619
|
# Enhanced Zenoh session close with better synchronization
|
|
562
620
|
zenoh_close_success = False
|
|
621
|
+
zenoh_close_exception = None
|
|
563
622
|
|
|
564
623
|
def _close_zenoh_session():
|
|
565
624
|
"""Close zenoh session in a separate thread."""
|
|
566
|
-
nonlocal zenoh_close_success
|
|
625
|
+
nonlocal zenoh_close_success, zenoh_close_exception
|
|
567
626
|
try:
|
|
568
627
|
# Brief delay for Zenoh cleanup
|
|
569
628
|
time.sleep(0.1)
|
|
570
629
|
self._zenoh_session.close()
|
|
571
630
|
zenoh_close_success = True
|
|
572
631
|
except Exception as e: # pylint: disable=broad-except
|
|
632
|
+
zenoh_close_exception = e
|
|
573
633
|
logger.warning(f"Zenoh session close error: {e}")
|
|
574
634
|
|
|
575
|
-
#
|
|
576
|
-
close_thread = threading.Thread(target=_close_zenoh_session, daemon=
|
|
635
|
+
# Try to close zenoh session with timeout
|
|
636
|
+
close_thread = threading.Thread(target=_close_zenoh_session, daemon=True)
|
|
577
637
|
close_thread.start()
|
|
578
638
|
|
|
579
639
|
# Wait for close with reasonable timeout
|
|
580
|
-
|
|
640
|
+
close_timeout = 2.0
|
|
641
|
+
close_thread.join(timeout=close_timeout)
|
|
581
642
|
|
|
582
643
|
if close_thread.is_alive():
|
|
583
|
-
logger.warning(
|
|
584
|
-
|
|
585
|
-
)
|
|
586
|
-
# Thread will be left to finish in background, but won't block shutdown
|
|
644
|
+
logger.warning(f"Zenoh session close timed out after {close_timeout}s")
|
|
645
|
+
# The thread is daemon, so it won't block the main thread
|
|
587
646
|
elif zenoh_close_success:
|
|
588
647
|
logger.debug("Zenoh session closed cleanly")
|
|
648
|
+
elif zenoh_close_exception:
|
|
649
|
+
logger.debug(
|
|
650
|
+
f"Zenoh session close completed with error: {zenoh_close_exception}"
|
|
651
|
+
)
|
|
589
652
|
|
|
590
|
-
#
|
|
591
|
-
|
|
653
|
+
# Give Zenoh threads more time to clean up properly
|
|
654
|
+
# This helps ensure threads terminate naturally
|
|
655
|
+
time.sleep(0.8)
|
|
656
|
+
|
|
657
|
+
# Check for lingering pyo3-closure threads
|
|
658
|
+
# These are internal Zenoh library threads
|
|
659
|
+
lingering_threads = []
|
|
660
|
+
for thread in threading.enumerate():
|
|
661
|
+
if (
|
|
662
|
+
"pyo3-closure" in thread.name
|
|
663
|
+
and thread.is_alive()
|
|
664
|
+
and not thread.daemon
|
|
665
|
+
):
|
|
666
|
+
lingering_threads.append(thread.name)
|
|
667
|
+
|
|
668
|
+
if lingering_threads:
|
|
669
|
+
logger.debug(
|
|
670
|
+
f"Note: Zenoh library threads still active: {lingering_threads}. "
|
|
671
|
+
"These are internal library threads that should not prevent creating new Robot instances."
|
|
672
|
+
)
|
|
673
|
+
|
|
674
|
+
# If this is the last robot being shutdown and we're in the main module,
|
|
675
|
+
# help the script exit cleanly
|
|
676
|
+
import sys
|
|
677
|
+
|
|
678
|
+
if (
|
|
679
|
+
not _active_robots
|
|
680
|
+
and threading.current_thread() is threading.main_thread()
|
|
681
|
+
):
|
|
682
|
+
# Check if we're likely at the end of the main script
|
|
683
|
+
frame = sys._getframe()
|
|
684
|
+
while frame.f_back:
|
|
685
|
+
frame = frame.f_back
|
|
686
|
+
# If we're at the top level of a script (not in interactive mode)
|
|
687
|
+
if frame.f_code.co_name == "<module>" and not hasattr(sys, "ps1"):
|
|
688
|
+
logger.debug("Main script ending, forcing clean exit")
|
|
689
|
+
os._exit(0)
|
|
592
690
|
|
|
593
691
|
logger.info("Robot shutdown complete")
|
|
594
692
|
|
|
@@ -600,6 +698,74 @@ class Robot:
|
|
|
600
698
|
"""
|
|
601
699
|
return self._shutdown_called
|
|
602
700
|
|
|
701
|
+
def query_ntp(
|
|
702
|
+
self,
|
|
703
|
+
sample_count: int = 30,
|
|
704
|
+
show: bool = False,
|
|
705
|
+
timeout: float = 1.0,
|
|
706
|
+
device: Literal["soc", "jetson"] = "soc",
|
|
707
|
+
) -> dict[Literal["success", "offset", "rtt"], bool | float]:
|
|
708
|
+
"""Query the NTP server via zenoh for time synchronization and compute robust statistics.
|
|
709
|
+
|
|
710
|
+
Args:
|
|
711
|
+
sample_count: Number of NTP samples to request (default: 50).
|
|
712
|
+
show: Whether to print summary statistics using a rich table.
|
|
713
|
+
timeout: Timeout for the zenoh querier in seconds (default: 2.0).
|
|
714
|
+
device: Which device to query for NTP ("soc" or "jetson").
|
|
715
|
+
|
|
716
|
+
Returns:
|
|
717
|
+
Dictionary with keys:
|
|
718
|
+
- "success": True if any replies were received, False otherwise.
|
|
719
|
+
- "offset": Mean offset (in seconds) after removing RTT outliers.
|
|
720
|
+
- "rtt": Mean round-trip time (in seconds) after removing RTT outliers.
|
|
721
|
+
"""
|
|
722
|
+
if device == "soc":
|
|
723
|
+
ntp_key = resolve_key_name(self._configs.soc_ntp_query_name)
|
|
724
|
+
elif device == "jetson":
|
|
725
|
+
raise NotImplementedError("Jetson NTP query is not implemented yet")
|
|
726
|
+
time_offset = []
|
|
727
|
+
time_rtt = []
|
|
728
|
+
|
|
729
|
+
querier = self._zenoh_session.declare_querier(ntp_key, timeout=timeout)
|
|
730
|
+
time.sleep(0.1)
|
|
731
|
+
|
|
732
|
+
reply_count = 0
|
|
733
|
+
for i in range(sample_count):
|
|
734
|
+
request = dexcontrol_query_pb2.NTPRequest()
|
|
735
|
+
request.client_send_time_ns = time.time_ns()
|
|
736
|
+
request.sample_count = sample_count
|
|
737
|
+
request.sample_index = i
|
|
738
|
+
replies = querier.get(payload=request.SerializeToString())
|
|
739
|
+
|
|
740
|
+
for reply in replies:
|
|
741
|
+
reply_count += 1
|
|
742
|
+
if reply.ok and reply.ok.payload:
|
|
743
|
+
client_receive_time_ns = time.time_ns()
|
|
744
|
+
response = dexcontrol_query_pb2.NTPResponse()
|
|
745
|
+
response.ParseFromString(reply.ok.payload.to_bytes())
|
|
746
|
+
t0 = request.client_send_time_ns
|
|
747
|
+
t1 = response.server_receive_time_ns
|
|
748
|
+
t2 = response.server_send_time_ns
|
|
749
|
+
t3 = client_receive_time_ns
|
|
750
|
+
offset = ((t1 - t0) + (t2 - t3)) // 2 / 1e9
|
|
751
|
+
rtt = (t3 - t0) / 1e9
|
|
752
|
+
time_offset.append(offset)
|
|
753
|
+
time_rtt.append(rtt)
|
|
754
|
+
if i < sample_count - 1:
|
|
755
|
+
time.sleep(0.01)
|
|
756
|
+
|
|
757
|
+
querier.undeclare()
|
|
758
|
+
if reply_count == 0:
|
|
759
|
+
return {"success": False, "offset": 0, "rtt": 0}
|
|
760
|
+
|
|
761
|
+
stats = compute_ntp_stats(time_offset, time_rtt)
|
|
762
|
+
offset = stats["offset (mean)"]
|
|
763
|
+
rtt = stats["round_trip_time (mean)"]
|
|
764
|
+
if show:
|
|
765
|
+
show_ntp_stats(stats)
|
|
766
|
+
|
|
767
|
+
return {"success": True, "offset": offset, "rtt": rtt}
|
|
768
|
+
|
|
603
769
|
def get_software_version(
|
|
604
770
|
self, show: bool = True
|
|
605
771
|
) -> dict[str, TYPE_SOFTWARE_VERSION]:
|
|
@@ -727,9 +893,7 @@ class Robot:
|
|
|
727
893
|
query_msg = dexcontrol_query_pb2.ClearError(component=component_map[part])
|
|
728
894
|
self._zenoh_session.get(
|
|
729
895
|
resolve_key_name(self._configs.clear_error_query_name),
|
|
730
|
-
handler=lambda reply: logger.info(
|
|
731
|
-
f"Cleared error of {part}: {reply.ok}"
|
|
732
|
-
),
|
|
896
|
+
handler=lambda reply: logger.info(f"Cleared error of {part}"),
|
|
733
897
|
payload=query_msg.SerializeToString(),
|
|
734
898
|
)
|
|
735
899
|
except Exception as e:
|
|
@@ -739,14 +903,18 @@ class Robot:
|
|
|
739
903
|
|
|
740
904
|
def get_joint_pos_dict(
|
|
741
905
|
self,
|
|
742
|
-
component: Literal[
|
|
743
|
-
|
|
906
|
+
component: Literal[
|
|
907
|
+
"left_arm", "right_arm", "torso", "head", "left_hand", "right_hand"
|
|
908
|
+
]
|
|
909
|
+
| list[
|
|
910
|
+
Literal["left_arm", "right_arm", "torso", "head", "left_hand", "right_hand"]
|
|
911
|
+
],
|
|
744
912
|
) -> dict[str, float]:
|
|
745
913
|
"""Get the joint positions of one or more robot components.
|
|
746
914
|
|
|
747
915
|
Args:
|
|
748
916
|
component: Component name or list of component names to get joint positions for.
|
|
749
|
-
Valid components are "left_arm", "right_arm", "torso", and "
|
|
917
|
+
Valid components are "left_arm", "right_arm", "torso", "head", "left_hand", and "right_hand".
|
|
750
918
|
|
|
751
919
|
Returns:
|
|
752
920
|
Dictionary mapping joint names to joint positions.
|
|
@@ -761,6 +929,8 @@ class Robot:
|
|
|
761
929
|
"right_arm": self.right_arm,
|
|
762
930
|
"torso": self.torso,
|
|
763
931
|
"head": self.head,
|
|
932
|
+
"left_hand": self.left_hand,
|
|
933
|
+
"right_hand": self.right_hand,
|
|
764
934
|
}
|
|
765
935
|
|
|
766
936
|
try:
|
|
@@ -826,6 +996,8 @@ class Robot:
|
|
|
826
996
|
relative: bool = False,
|
|
827
997
|
wait_time: float = 0.0,
|
|
828
998
|
wait_kwargs: dict[str, Any] | None = None,
|
|
999
|
+
exit_on_reach: bool = False,
|
|
1000
|
+
exit_on_reach_kwargs: dict[str, Any] | None = None,
|
|
829
1001
|
) -> None:
|
|
830
1002
|
"""Set the joint positions of the robot.
|
|
831
1003
|
|
|
@@ -836,7 +1008,9 @@ class Robot:
|
|
|
836
1008
|
wait_time: Time to wait for movement completion in seconds.
|
|
837
1009
|
wait_kwargs: Additional parameters for trajectory generation.
|
|
838
1010
|
control_hz: Control frequency in Hz (default: 100).
|
|
839
|
-
max_vel: Maximum velocity for trajectory (default:
|
|
1011
|
+
max_vel: Maximum velocity for trajectory (default: 3.).
|
|
1012
|
+
exit_on_reach: If True, the function will exit when the joint positions are reached.
|
|
1013
|
+
exit_on_reach_kwargs: Optional parameters for exit when the joint positions are reached.
|
|
840
1014
|
|
|
841
1015
|
Raises:
|
|
842
1016
|
ValueError: If any component name is invalid.
|
|
@@ -846,6 +1020,7 @@ class Robot:
|
|
|
846
1020
|
wait_kwargs = {}
|
|
847
1021
|
|
|
848
1022
|
try:
|
|
1023
|
+
start_time = time.time()
|
|
849
1024
|
component_map = self._get_component_map()
|
|
850
1025
|
|
|
851
1026
|
# Validate component names
|
|
@@ -871,11 +1046,69 @@ class Robot:
|
|
|
871
1046
|
relative,
|
|
872
1047
|
wait_time,
|
|
873
1048
|
wait_kwargs,
|
|
1049
|
+
exit_on_reach=exit_on_reach,
|
|
1050
|
+
exit_on_reach_kwargs=exit_on_reach_kwargs,
|
|
874
1051
|
)
|
|
1052
|
+
remaining_time = wait_time - (time.time() - start_time)
|
|
1053
|
+
if remaining_time <= 0:
|
|
1054
|
+
return
|
|
1055
|
+
|
|
1056
|
+
self._wait_for_multi_component_positions(
|
|
1057
|
+
component_map,
|
|
1058
|
+
pv_components,
|
|
1059
|
+
joint_pos,
|
|
1060
|
+
start_time,
|
|
1061
|
+
wait_time,
|
|
1062
|
+
exit_on_reach,
|
|
1063
|
+
exit_on_reach_kwargs,
|
|
1064
|
+
)
|
|
875
1065
|
|
|
876
1066
|
except Exception as e:
|
|
877
1067
|
raise RuntimeError(f"Failed to set joint positions: {e}") from e
|
|
878
1068
|
|
|
1069
|
+
def _wait_for_multi_component_positions(
|
|
1070
|
+
self,
|
|
1071
|
+
component_map: dict[str, Any],
|
|
1072
|
+
components: list[str],
|
|
1073
|
+
joint_pos: dict[str, list[float] | np.ndarray],
|
|
1074
|
+
start_time: float,
|
|
1075
|
+
wait_time: float,
|
|
1076
|
+
exit_on_reach: bool = False,
|
|
1077
|
+
exit_on_reach_kwargs: dict[str, Any] | None = None,
|
|
1078
|
+
) -> None:
|
|
1079
|
+
"""Wait for multiple components to reach target positions.
|
|
1080
|
+
|
|
1081
|
+
Args:
|
|
1082
|
+
component_map: Mapping of component names to component instances.
|
|
1083
|
+
components: List of component names to check.
|
|
1084
|
+
joint_pos: Target joint positions for each component.
|
|
1085
|
+
start_time: Time when the operation started.
|
|
1086
|
+
wait_time: Maximum time to wait.
|
|
1087
|
+
exit_on_reach: If True, exit early when all positions are reached.
|
|
1088
|
+
exit_on_reach_kwargs: Optional parameters for position checking.
|
|
1089
|
+
"""
|
|
1090
|
+
sleep_interval = 0.01 # Use consistent sleep interval
|
|
1091
|
+
|
|
1092
|
+
if exit_on_reach:
|
|
1093
|
+
if components:
|
|
1094
|
+
# Set default tolerance if not provided
|
|
1095
|
+
exit_on_reach_kwargs = exit_on_reach_kwargs or {}
|
|
1096
|
+
|
|
1097
|
+
# Wait until all positions are reached or timeout
|
|
1098
|
+
while time.time() - start_time < wait_time:
|
|
1099
|
+
if all(
|
|
1100
|
+
component_map[c].is_joint_pos_reached(
|
|
1101
|
+
joint_pos[c], **exit_on_reach_kwargs
|
|
1102
|
+
)
|
|
1103
|
+
for c in components
|
|
1104
|
+
):
|
|
1105
|
+
break
|
|
1106
|
+
time.sleep(sleep_interval)
|
|
1107
|
+
else:
|
|
1108
|
+
# Simple wait without position checking
|
|
1109
|
+
while time.time() - start_time < wait_time:
|
|
1110
|
+
time.sleep(sleep_interval)
|
|
1111
|
+
|
|
879
1112
|
def compensate_torso_pitch(self, joint_pos: np.ndarray, part: str) -> np.ndarray:
|
|
880
1113
|
"""Compensate for torso pitch in joint positions.
|
|
881
1114
|
|
|
@@ -1092,6 +1325,8 @@ class Robot:
|
|
|
1092
1325
|
relative: bool,
|
|
1093
1326
|
wait_time: float,
|
|
1094
1327
|
wait_kwargs: dict[str, Any],
|
|
1328
|
+
exit_on_reach: bool = False,
|
|
1329
|
+
exit_on_reach_kwargs: dict[str, Any] | None = None,
|
|
1095
1330
|
) -> None:
|
|
1096
1331
|
"""Set non-PV components with smooth trajectory over wait_time.
|
|
1097
1332
|
|
|
@@ -1102,9 +1337,11 @@ class Robot:
|
|
|
1102
1337
|
relative: Whether positions are relative.
|
|
1103
1338
|
wait_time: Time to wait for movement completion.
|
|
1104
1339
|
wait_kwargs: Additional trajectory parameters.
|
|
1340
|
+
exit_on_reach: If True, the function will exit when the joint positions are reached.
|
|
1341
|
+
exit_on_reach_kwargs: Optional parameters for exit when the joint positions are reached.
|
|
1105
1342
|
"""
|
|
1106
|
-
control_hz = wait_kwargs.get("control_hz",
|
|
1107
|
-
max_vel = wait_kwargs.get("max_vel",
|
|
1343
|
+
control_hz = wait_kwargs.get("control_hz", self.left_arm._default_control_hz)
|
|
1344
|
+
max_vel = wait_kwargs.get("max_vel", self.left_arm._joint_vel_limit)
|
|
1108
1345
|
|
|
1109
1346
|
# Generate trajectories for smooth motion during wait_time
|
|
1110
1347
|
rate_limiter = RateLimiter(control_hz)
|
|
@@ -1136,5 +1373,12 @@ class Robot:
|
|
|
1136
1373
|
break
|
|
1137
1374
|
|
|
1138
1375
|
# Wait for any remaining time
|
|
1139
|
-
|
|
1140
|
-
|
|
1376
|
+
self._wait_for_multi_component_positions(
|
|
1377
|
+
component_map,
|
|
1378
|
+
non_pv_components,
|
|
1379
|
+
joint_pos,
|
|
1380
|
+
start_time,
|
|
1381
|
+
wait_time,
|
|
1382
|
+
exit_on_reach,
|
|
1383
|
+
exit_on_reach_kwargs,
|
|
1384
|
+
)
|
dexcontrol/sensors/__init__.py
CHANGED
|
@@ -15,7 +15,7 @@ using Zenoh subscribers for data communication.
|
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
17
|
# Import camera sensors
|
|
18
|
-
from .camera import RGBCameraSensor, ZedCameraSensor
|
|
18
|
+
from .camera import LuxonisCameraSensor, RGBCameraSensor, ZedCameraSensor
|
|
19
19
|
|
|
20
20
|
# Import IMU sensors
|
|
21
21
|
from .imu import ChassisIMUSensor, ZedIMUSensor
|
|
@@ -31,6 +31,7 @@ __all__ = [
|
|
|
31
31
|
# Camera sensors
|
|
32
32
|
"RGBCameraSensor",
|
|
33
33
|
"ZedCameraSensor",
|
|
34
|
+
"LuxonisCameraSensor",
|
|
34
35
|
|
|
35
36
|
# IMU sensors
|
|
36
37
|
"ChassisIMUSensor",
|
|
@@ -14,10 +14,12 @@ This module provides camera sensor classes that use the specialized camera
|
|
|
14
14
|
subscribers for RGB and RGBD camera data, matching the dexsensor structure.
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
|
+
from .luxonis_camera import LuxonisCameraSensor
|
|
17
18
|
from .rgb_camera import RGBCameraSensor
|
|
18
19
|
from .zed_camera import ZedCameraSensor
|
|
19
20
|
|
|
20
21
|
__all__ = [
|
|
21
22
|
"RGBCameraSensor",
|
|
22
23
|
"ZedCameraSensor",
|
|
24
|
+
"LuxonisCameraSensor",
|
|
23
25
|
]
|