dexcontrol 0.2.1__py3-none-any.whl → 0.2.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of dexcontrol might be problematic. Click here for more details.

Files changed (81) hide show
  1. dexcontrol/__init__.py +14 -3
  2. dexcontrol/apps/dualsense_teleop_base.py +16 -11
  3. dexcontrol/config/__init__.py +10 -5
  4. dexcontrol/config/core/__init__.py +8 -3
  5. dexcontrol/config/core/arm.py +8 -3
  6. dexcontrol/config/core/chassis.py +10 -5
  7. dexcontrol/config/core/hand.py +14 -9
  8. dexcontrol/config/core/head.py +8 -3
  9. dexcontrol/config/core/misc.py +8 -3
  10. dexcontrol/config/core/torso.py +8 -3
  11. dexcontrol/config/sensors/__init__.py +8 -3
  12. dexcontrol/config/sensors/cameras/__init__.py +9 -4
  13. dexcontrol/config/sensors/cameras/rgb_camera.py +18 -5
  14. dexcontrol/config/sensors/cameras/zed_camera.py +36 -0
  15. dexcontrol/config/sensors/imu/__init__.py +10 -5
  16. dexcontrol/config/sensors/imu/chassis_imu.py +21 -0
  17. dexcontrol/config/sensors/imu/zed_imu.py +21 -0
  18. dexcontrol/config/sensors/lidar/__init__.py +8 -3
  19. dexcontrol/config/sensors/lidar/rplidar.py +9 -3
  20. dexcontrol/config/sensors/ultrasonic/__init__.py +8 -3
  21. dexcontrol/config/sensors/ultrasonic/ultrasonic.py +9 -3
  22. dexcontrol/config/sensors/vega_sensors.py +34 -21
  23. dexcontrol/config/vega.py +14 -6
  24. dexcontrol/core/__init__.py +9 -0
  25. dexcontrol/core/arm.py +21 -6
  26. dexcontrol/core/chassis.py +8 -3
  27. dexcontrol/core/component.py +26 -6
  28. dexcontrol/core/hand.py +8 -3
  29. dexcontrol/core/head.py +18 -3
  30. dexcontrol/core/misc.py +94 -16
  31. dexcontrol/core/torso.py +8 -3
  32. dexcontrol/proto/dexcontrol_msg_pb2.py +17 -15
  33. dexcontrol/proto/dexcontrol_msg_pb2.pyi +24 -0
  34. dexcontrol/robot.py +82 -28
  35. dexcontrol/sensors/__init__.py +13 -8
  36. dexcontrol/sensors/camera/__init__.py +11 -6
  37. dexcontrol/sensors/camera/rgb_camera.py +33 -24
  38. dexcontrol/sensors/camera/zed_camera.py +364 -0
  39. dexcontrol/sensors/imu/__init__.py +13 -8
  40. dexcontrol/sensors/imu/chassis_imu.py +155 -0
  41. dexcontrol/sensors/imu/{nine_axis_imu.py → zed_imu.py} +41 -26
  42. dexcontrol/sensors/lidar/__init__.py +11 -1
  43. dexcontrol/sensors/lidar/rplidar.py +8 -3
  44. dexcontrol/sensors/manager.py +22 -9
  45. dexcontrol/sensors/ultrasonic.py +8 -3
  46. dexcontrol/utils/__init__.py +8 -3
  47. dexcontrol/utils/constants.py +10 -0
  48. dexcontrol/utils/io_utils.py +8 -3
  49. dexcontrol/utils/motion_utils.py +8 -3
  50. dexcontrol/utils/os_utils.py +23 -4
  51. dexcontrol/utils/pb_utils.py +8 -3
  52. dexcontrol/utils/rate_limiter.py +8 -3
  53. dexcontrol/utils/rtc_utils.py +144 -0
  54. dexcontrol/utils/subscribers/__init__.py +11 -3
  55. dexcontrol/utils/subscribers/base.py +26 -5
  56. dexcontrol/utils/subscribers/camera.py +10 -6
  57. dexcontrol/utils/subscribers/decoders.py +8 -3
  58. dexcontrol/utils/subscribers/generic.py +8 -3
  59. dexcontrol/utils/subscribers/imu.py +8 -3
  60. dexcontrol/utils/subscribers/lidar.py +8 -3
  61. dexcontrol/utils/subscribers/protobuf.py +8 -3
  62. dexcontrol/utils/subscribers/rtc.py +315 -0
  63. dexcontrol/utils/timer.py +8 -3
  64. dexcontrol/utils/trajectory_utils.py +8 -3
  65. dexcontrol/utils/viz_utils.py +8 -3
  66. dexcontrol/utils/zenoh_utils.py +83 -0
  67. dexcontrol-0.2.3.dist-info/METADATA +265 -0
  68. dexcontrol-0.2.3.dist-info/RECORD +72 -0
  69. {dexcontrol-0.2.1.dist-info → dexcontrol-0.2.3.dist-info}/WHEEL +1 -2
  70. dexcontrol-0.2.3.dist-info/licenses/LICENSE +184 -0
  71. dexcontrol/config/sensors/cameras/gemini_camera.py +0 -16
  72. dexcontrol/config/sensors/imu/gemini_imu.py +0 -15
  73. dexcontrol/config/sensors/imu/nine_axis_imu.py +0 -15
  74. dexcontrol/sensors/camera/gemini_camera.py +0 -139
  75. dexcontrol/sensors/imu/gemini_imu.py +0 -139
  76. dexcontrol/utils/reset_orbbec_camera_usb.py +0 -98
  77. dexcontrol-0.2.1.dist-info/METADATA +0 -369
  78. dexcontrol-0.2.1.dist-info/RECORD +0 -72
  79. dexcontrol-0.2.1.dist-info/licenses/LICENSE +0 -188
  80. dexcontrol-0.2.1.dist-info/licenses/NOTICE +0 -13
  81. dexcontrol-0.2.1.dist-info/top_level.txt +0 -1
dexcontrol/robot.py CHANGED
@@ -1,7 +1,12 @@
1
- # Copyright (c) 2025 Dexmate CORPORATION & AFFILIATES. All rights reserved.
1
+ # Copyright (C) 2025 Dexmate Inc.
2
2
  #
3
- # Licensed under the Apache License, Version 2.0 with Commons Clause License
4
- # Condition v1.0 [see LICENSE for details].
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
5
10
 
6
11
  """Main robot interface module.
7
12
 
@@ -36,6 +41,7 @@ import omegaconf
36
41
  import zenoh
37
42
  from loguru import logger
38
43
  from rich.console import Console
44
+ from rich.table import Table
39
45
 
40
46
  import dexcontrol
41
47
  from dexcontrol.config.vega import VegaConfig, get_vega_config
@@ -106,7 +112,6 @@ def _register_signal_handlers() -> None:
106
112
  signal.signal(signal.SIGHUP, _signal_handler)
107
113
 
108
114
  _signal_handlers_registered = True
109
- logger.debug("Signal handlers registered for graceful robot shutdown")
110
115
 
111
116
 
112
117
  class ComponentConfig(omegaconf.DictConfig):
@@ -178,7 +183,7 @@ class Robot:
178
183
  """Initializes the Robot with the given configuration.
179
184
 
180
185
  Args:
181
- robot_model: Optional robot variant name (e.g., "vega_rc2", "vega_1").
186
+ robot_model: Optional robot variant name (e.g., "vega-rc2", "vega-1").
182
187
  If configs is None, this will be used to get the appropriate config.
183
188
  Ignored if configs is provided.
184
189
  configs: Configuration parameters for all robot components.
@@ -192,6 +197,8 @@ class Robot:
192
197
  RuntimeError: If any critical component fails to become active within timeout.
193
198
  ValueError: If robot_model is invalid or configs cannot be loaded.
194
199
  """
200
+ self._components: list[RobotComponent] = []
201
+
195
202
  if robot_model is None:
196
203
  robot_model = get_robot_model()
197
204
  self._robot_model: Final[str] = robot_model
@@ -208,7 +215,6 @@ class Robot:
208
215
  except Exception as e:
209
216
  raise RuntimeError(f"Failed to initialize zenoh session: {e}") from e
210
217
 
211
- self._components: list[RobotComponent] = []
212
218
  self._robot_name: Final[str] = os.getenv(ROBOT_NAME_ENV_VAR, "robot")
213
219
  self._pv_components: Final[list[str]] = [
214
220
  "left_hand",
@@ -234,18 +240,19 @@ class Robot:
234
240
  self.shutdown() # Clean up on initialization failure
235
241
  raise RuntimeError(f"Failed to initialize robot components: {e}") from e
236
242
 
243
+ # Ensure all components are active
237
244
  try:
238
- self.sensors = Sensors(self._configs.sensors, self._zenoh_session)
245
+ self._wait_for_components()
239
246
  except Exception as e:
240
247
  self.shutdown() # Clean up on initialization failure
241
- raise RuntimeError(f"Failed to initialize sensors: {e}") from e
248
+ raise RuntimeError(f"Failed to activate components: {e}") from e
242
249
 
243
- # Ensure all components are active
244
250
  try:
245
- self._wait_for_components()
251
+ self.sensors = Sensors(self._configs.sensors, self._zenoh_session)
252
+ self.sensors.wait_for_all_active()
246
253
  except Exception as e:
247
254
  self.shutdown() # Clean up on initialization failure
248
- raise RuntimeError(f"Failed to activate components: {e}") from e
255
+ raise RuntimeError(f"Failed to initialize sensors: {e}") from e
249
256
 
250
257
  # Set default modes
251
258
  try:
@@ -306,12 +313,17 @@ class Robot:
306
313
  Args:
307
314
  robot_model: The robot model being initialized.
308
315
  """
309
- print("=" * 50)
310
- print(f"Robot name: {self._robot_name}")
316
+ console = Console()
317
+ table = Table(show_header=False)
318
+ table.add_column(style="cyan", no_wrap=True)
319
+ table.add_column(style="white")
320
+
321
+ table.add_row("Robot Name", str(self._robot_name))
311
322
  if robot_model:
312
- print(f"Robot model: {robot_model}")
313
- print(f"Communication config file: {dexcontrol.COMM_CFG_PATH}")
314
- print("=" * 50)
323
+ table.add_row("Robot Model", str(robot_model))
324
+ table.add_row("Communication Config", str(dexcontrol.COMM_CFG_PATH))
325
+
326
+ console.print(table)
315
327
 
316
328
  def _init_components(self, config_dict: dict[str, Any]) -> None:
317
329
  """Initialize robot components from configuration.
@@ -502,38 +514,80 @@ class Robot:
502
514
  except Exception: # pylint: disable=broad-except
503
515
  pass # WeakSet may already have removed it
504
516
 
505
- # Shutdown components in reverse order
506
- for component in reversed(self._components):
517
+ # First, stop all components that have stop methods to halt ongoing operations
518
+ for component in self._components:
507
519
  if component is not None:
508
520
  try:
509
- component.shutdown()
521
+ if hasattr(component, "stop"):
522
+ method = getattr(component, "stop")
523
+ if callable(method):
524
+ method()
510
525
  except Exception as e: # pylint: disable=broad-except
511
526
  logger.error(
512
- f"Error shutting down component {component.__class__.__name__}: {e}"
527
+ f"Error stopping component {component.__class__.__name__}: {e}"
513
528
  )
514
529
 
530
+ # Longer delay to ensure all stop operations complete and background threads settle
531
+ time.sleep(0.3)
532
+
533
+ # Shutdown sensors first (they may have background threads)
515
534
  try:
516
535
  self.sensors.shutdown()
536
+ # Give extra time for all sensor subscribers to undeclare cleanly
537
+ time.sleep(0.5)
517
538
  except Exception as e: # pylint: disable=broad-except
518
539
  logger.error(f"Error shutting down sensors: {e}")
519
540
 
520
- # Use timeout-based zenoh session close to avoid long waits
541
+ # Additional delay for sensor shutdown to complete
542
+ time.sleep(0.2)
543
+
544
+ # Shutdown components in reverse order
545
+ for component in reversed(self._components):
546
+ if component is not None:
547
+ try:
548
+ component.shutdown()
549
+ # Small delay between each component shutdown to prevent race conditions
550
+ time.sleep(0.05)
551
+ except Exception as e: # pylint: disable=broad-except
552
+ logger.error(
553
+ f"Error shutting down component {component.__class__.__name__}: {e}"
554
+ )
555
+
556
+ # Longer delay to allow all component shutdown to complete
557
+ time.sleep(0.5)
558
+
559
+ # Enhanced Zenoh session close with better synchronization
560
+ zenoh_close_success = False
561
+
521
562
  def _close_zenoh_session():
522
563
  """Close zenoh session in a separate thread."""
564
+ nonlocal zenoh_close_success
523
565
  try:
566
+ # Give Zenoh even more time to clean up internal resources and settle threads
567
+ time.sleep(0.5)
524
568
  self._zenoh_session.close()
569
+ zenoh_close_success = True
525
570
  except Exception as e: # pylint: disable=broad-except
526
- logger.debug(f"Zenoh session close error: {e}")
571
+ logger.warning(f"Zenoh session close error: {e}")
527
572
 
528
- close_thread = threading.Thread(target=_close_zenoh_session, daemon=True)
573
+ # Use non-daemon thread for proper cleanup
574
+ close_thread = threading.Thread(target=_close_zenoh_session, daemon=False)
529
575
  close_thread.start()
530
576
 
531
- # Wait for close with timeout
532
- close_thread.join(timeout=0.5) # 500ms timeout
577
+ # Wait for close with extended timeout to avoid race conditions
578
+ close_thread.join(timeout=3.0)
579
+
533
580
  if close_thread.is_alive():
534
- logger.warning("Zenoh session close timed out, continuing shutdown")
535
- else:
536
- logger.debug("Zenoh session closed successfully")
581
+ logger.warning(
582
+ "Zenoh session close timed out after 5s, continuing shutdown"
583
+ )
584
+ # Force daemon mode so it doesn't block process exit
585
+ close_thread.daemon = True
586
+ elif zenoh_close_success:
587
+ logger.debug("Zenoh session closed cleanly")
588
+
589
+ # Extended final delay to allow any remaining Zenoh background threads to finish
590
+ time.sleep(0.8)
537
591
 
538
592
  logger.info("Robot shutdown complete")
539
593
 
@@ -1,7 +1,12 @@
1
- # Copyright (c) 2025 Dexmate CORPORATION & AFFILIATES. All rights reserved.
1
+ # Copyright (C) 2025 Dexmate Inc.
2
2
  #
3
- # Licensed under the Apache License, Version 2.0 with Commons Clause License
4
- # Condition v1.0 [see LICENSE for details].
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
5
10
 
6
11
  """Sensor implementations for dexcontrol.
7
12
 
@@ -10,10 +15,10 @@ using Zenoh subscribers for data communication.
10
15
  """
11
16
 
12
17
  # Import camera sensors
13
- from .camera import GeminiCameraSensor, RGBCameraSensor
18
+ from .camera import RGBCameraSensor, ZedCameraSensor
14
19
 
15
20
  # Import IMU sensors
16
- from .imu import GeminiIMUSensor, NineAxisIMUSensor
21
+ from .imu import ChassisIMUSensor, ZedIMUSensor
17
22
 
18
23
  # Import other sensors
19
24
  from .lidar import RPLidarSensor
@@ -25,11 +30,11 @@ from .ultrasonic import UltrasonicSensor
25
30
  __all__ = [
26
31
  # Camera sensors
27
32
  "RGBCameraSensor",
28
- "GeminiCameraSensor",
33
+ "ZedCameraSensor",
29
34
 
30
35
  # IMU sensors
31
- "NineAxisIMUSensor",
32
- "GeminiIMUSensor",
36
+ "ChassisIMUSensor",
37
+ "ZedIMUSensor",
33
38
 
34
39
  # Other sensors
35
40
  "RPLidarSensor",
@@ -1,18 +1,23 @@
1
- # Copyright (c) 2025 Dexmate CORPORATION & AFFILIATES. All rights reserved.
1
+ # Copyright (C) 2025 Dexmate Inc.
2
2
  #
3
- # Licensed under the Apache License, Version 2.0 with Commons Clause License
4
- # Condition v1.0 [see LICENSE for details].
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
5
10
 
6
11
  """Camera sensor implementations using Zenoh subscribers.
7
12
 
8
13
  This module provides camera sensor classes that use the specialized camera
9
- subscribers for RGB and RGBD Gemini camera data, matching the dexsensor structure.
14
+ subscribers for RGB and RGBD camera data, matching the dexsensor structure.
10
15
  """
11
16
 
12
- from .gemini_camera import GeminiCameraSensor
13
17
  from .rgb_camera import RGBCameraSensor
18
+ from .zed_camera import ZedCameraSensor
14
19
 
15
20
  __all__ = [
16
21
  "RGBCameraSensor",
17
- "GeminiCameraSensor",
22
+ "ZedCameraSensor",
18
23
  ]
@@ -1,27 +1,39 @@
1
- # Copyright (c) 2025 Dexmate CORPORATION & AFFILIATES. All rights reserved.
1
+ # Copyright (C) 2025 Dexmate Inc.
2
2
  #
3
- # Licensed under the Apache License, Version 2.0 with Commons Clause License
4
- # Condition v1.0 [see LICENSE for details].
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
+ """RGB camera sensor implementation using RTC subscriber with Zenoh query."""
5
12
 
6
- """RGB camera sensor implementation using Zenoh subscriber."""
13
+ import logging
7
14
 
8
15
  import numpy as np
9
16
  import zenoh
10
17
 
11
- from dexcontrol.utils.subscribers.camera import RGBCameraSubscriber
18
+ from dexcontrol.utils.rtc_utils import create_rtc_subscriber_with_config
19
+
20
+ logger = logging.getLogger(__name__)
12
21
 
13
22
 
14
23
  class RGBCameraSensor:
15
- """RGB camera sensor using Zenoh subscriber.
24
+ """RGB camera sensor using RTC subscriber.
16
25
 
17
- This sensor provides RGB image data from a camera using the
18
- RGBCameraSubscriber for efficient data handling with lazy decoding.
26
+ This sensor provides RGB image data from a camera using RTC.
27
+ It first queries Zenoh for RTC connection information, then creates
28
+ a RTC subscriber for efficient data handling.
19
29
  """
20
30
 
21
31
  def __init__(
22
32
  self,
23
33
  configs,
24
34
  zenoh_session: zenoh.Session,
35
+ *args,
36
+ **kwargs,
25
37
  ) -> None:
26
38
  """Initialize the RGB camera sensor.
27
39
 
@@ -31,18 +43,23 @@ class RGBCameraSensor:
31
43
  """
32
44
  self._name = configs.name
33
45
 
34
- # Create the RGB camera subscriber
35
- self._subscriber = RGBCameraSubscriber(
36
- topic=configs.topic,
46
+ # Create the RTC subscriber using the utility function
47
+ self._subscriber = create_rtc_subscriber_with_config(
37
48
  zenoh_session=zenoh_session,
49
+ config=configs.subscriber_config.rgb,
38
50
  name=f"{self._name}_subscriber",
39
51
  enable_fps_tracking=configs.enable_fps_tracking,
40
52
  fps_log_interval=configs.fps_log_interval,
41
53
  )
42
54
 
55
+ if self._subscriber is None:
56
+ logger.warning(f"Failed to create RTC subscriber for {self._name}")
57
+ # Continue initialization without subscriber
58
+
43
59
  def shutdown(self) -> None:
44
60
  """Shutdown the camera sensor."""
45
- self._subscriber.shutdown()
61
+ if self._subscriber:
62
+ self._subscriber.shutdown()
46
63
 
47
64
  def is_active(self) -> bool:
48
65
  """Check if the camera sensor is actively receiving data.
@@ -50,7 +67,7 @@ class RGBCameraSensor:
50
67
  Returns:
51
68
  True if receiving data, False otherwise.
52
69
  """
53
- return self._subscriber.is_active()
70
+ return self._subscriber.is_active() if self._subscriber else False
54
71
 
55
72
  def wait_for_active(self, timeout: float = 5.0) -> bool:
56
73
  """Wait for the camera sensor to start receiving data.
@@ -61,7 +78,7 @@ class RGBCameraSensor:
61
78
  Returns:
62
79
  True if sensor becomes active, False if timeout is reached.
63
80
  """
64
- return self._subscriber.wait_for_active(timeout)
81
+ return self._subscriber.wait_for_active(timeout) if self._subscriber else False
65
82
 
66
83
  def get_obs(self) -> np.ndarray | None:
67
84
  """Get the latest RGB image data.
@@ -69,15 +86,7 @@ class RGBCameraSensor:
69
86
  Returns:
70
87
  Latest RGB image as numpy array (HxWxC) if available, None otherwise.
71
88
  """
72
- return self._subscriber.get_latest_data()
73
-
74
- def get_rgb_image(self) -> np.ndarray | None:
75
- """Get the latest RGB image.
76
-
77
- Returns:
78
- Latest RGB image as numpy array (HxWxC) if available, None otherwise.
79
- """
80
- return self._subscriber.get_latest_image()
89
+ return self._subscriber.get_latest_data() if self._subscriber else None
81
90
 
82
91
  @property
83
92
  def fps(self) -> float:
@@ -86,7 +95,7 @@ class RGBCameraSensor:
86
95
  Returns:
87
96
  Current frames per second measurement.
88
97
  """
89
- return self._subscriber.fps
98
+ return self._subscriber.fps if self._subscriber else 0.0
90
99
 
91
100
  @property
92
101
  def name(self) -> str: