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
@@ -1,9 +1,14 @@
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
- """Nine-Axis IMU sensor implementation using Zenoh subscriber."""
11
+ """ZED IMU sensor implementation using Zenoh subscriber."""
7
12
 
8
13
  import numpy as np
9
14
  import zenoh
@@ -11,11 +16,13 @@ import zenoh
11
16
  from dexcontrol.utils.subscribers.imu import IMUSubscriber
12
17
 
13
18
 
14
- class NineAxisIMUSensor:
15
- """Nine-Axis IMU sensor using Zenoh subscriber.
19
+ class ZedIMUSensor:
20
+ """ZED IMU sensor using Zenoh subscriber.
16
21
 
17
- This sensor provides 9-axis IMU data including acceleration, angular velocity,
18
- orientation, and magnetometer data using the IMUSubscriber for efficient data handling.
22
+ This sensor provides IMU data from ZED cameras including acceleration, angular velocity,
23
+ orientation quaternion, and magnetometer data using the IMUSubscriber for efficient data handling.
24
+ The ZED IMU typically provides 9-axis data (accelerometer, gyroscope, magnetometer) with
25
+ quaternion orientation.
19
26
  """
20
27
 
21
28
  def __init__(
@@ -23,13 +30,11 @@ class NineAxisIMUSensor:
23
30
  configs,
24
31
  zenoh_session: zenoh.Session,
25
32
  ) -> None:
26
- """Initialize the Nine-Axis IMU sensor.
33
+ """Initialize the ZED IMU sensor.
27
34
 
28
35
  Args:
29
- topic: Zenoh topic to subscribe to for IMU data.
36
+ configs: Configuration object containing topic, name, and other settings.
30
37
  zenoh_session: Active Zenoh session for communication.
31
- name: Name for the sensor instance.
32
- enable_fps_tracking: Whether to track and log FPS metrics.
33
38
  """
34
39
  self._name = configs.name
35
40
 
@@ -42,13 +47,12 @@ class NineAxisIMUSensor:
42
47
  fps_log_interval=configs.fps_log_interval,
43
48
  )
44
49
 
45
-
46
50
  def shutdown(self) -> None:
47
- """Shutdown the IMU sensor."""
51
+ """Shutdown the ZED IMU sensor."""
48
52
  self._subscriber.shutdown()
49
53
 
50
54
  def is_active(self) -> bool:
51
- """Check if the IMU sensor is actively receiving data.
55
+ """Check if the ZED IMU sensor is actively receiving data.
52
56
 
53
57
  Returns:
54
58
  True if receiving data, False otherwise.
@@ -56,7 +60,7 @@ class NineAxisIMUSensor:
56
60
  return self._subscriber.is_active()
57
61
 
58
62
  def wait_for_active(self, timeout: float = 5.0) -> bool:
59
- """Wait for the IMU sensor to start receiving data.
63
+ """Wait for the ZED IMU sensor to start receiving data.
60
64
 
61
65
  Args:
62
66
  timeout: Maximum time to wait in seconds.
@@ -67,30 +71,33 @@ class NineAxisIMUSensor:
67
71
  return self._subscriber.wait_for_active(timeout)
68
72
 
69
73
  def get_obs(self, obs_keys: list[str] | None = None) -> dict[str, np.ndarray] | None:
70
- """Get observation data for the Nine-Axis IMU sensor.
74
+ """Get observation data for the ZED IMU sensor.
71
75
 
72
76
  Args:
73
77
  obs_keys: List of observation keys to retrieve. If None, returns all available data.
74
- Valid keys: ['ang_vel', 'acc', 'mag', 'quat']
78
+ Valid keys: ['ang_vel', 'acc', 'quat']
75
79
 
76
80
  Returns:
77
81
  Dictionary with observation data including all IMU measurements.
82
+ Keys are mapped as follows:
83
+ - 'ang_vel': Angular velocity from 'angular_velocity'
84
+ - 'acc': Linear acceleration from 'acceleration'
85
+ - 'quat': Orientation quaternion from 'orientation'
78
86
  """
79
87
  if obs_keys is None:
80
- obs_keys = ['ang_vel', 'acc', 'mag', 'quat']
88
+ obs_keys = ['ang_vel', 'acc', 'quat']
81
89
 
82
90
  data = self._subscriber.get_latest_data()
83
91
  if data is None:
84
92
  return None
85
93
 
86
94
  obs_out = {}
95
+
87
96
  for key in obs_keys:
88
97
  if key == 'ang_vel':
89
98
  obs_out[key] = data['angular_velocity']
90
99
  elif key == 'acc':
91
100
  obs_out[key] = data['acceleration']
92
- elif key == 'mag':
93
- obs_out[key] = data['magnetometer']
94
101
  elif key == 'quat':
95
102
  obs_out[key] = data['orientation']
96
103
  else:
@@ -99,7 +106,7 @@ class NineAxisIMUSensor:
99
106
  return obs_out
100
107
 
101
108
  def get_acceleration(self) -> np.ndarray | None:
102
- """Get the latest linear acceleration.
109
+ """Get the latest linear acceleration from ZED IMU.
103
110
 
104
111
  Returns:
105
112
  Linear acceleration [x, y, z] in m/s² if available, None otherwise.
@@ -107,7 +114,7 @@ class NineAxisIMUSensor:
107
114
  return self._subscriber.get_acceleration()
108
115
 
109
116
  def get_angular_velocity(self) -> np.ndarray | None:
110
- """Get the latest angular velocity.
117
+ """Get the latest angular velocity from ZED IMU.
111
118
 
112
119
  Returns:
113
120
  Angular velocity [x, y, z] in rad/s if available, None otherwise.
@@ -115,7 +122,7 @@ class NineAxisIMUSensor:
115
122
  return self._subscriber.get_angular_velocity()
116
123
 
117
124
  def get_orientation(self) -> np.ndarray | None:
118
- """Get the latest orientation quaternion.
125
+ """Get the latest orientation quaternion from ZED IMU.
119
126
 
120
127
  Returns:
121
128
  Orientation quaternion [x, y, z, w] if available, None otherwise.
@@ -123,13 +130,21 @@ class NineAxisIMUSensor:
123
130
  return self._subscriber.get_orientation()
124
131
 
125
132
  def get_magnetometer(self) -> np.ndarray | None:
126
- """Get the latest magnetometer reading.
133
+ """Get the latest magnetometer reading from ZED IMU.
127
134
 
128
135
  Returns:
129
136
  Magnetic field [x, y, z] in µT if available, None otherwise.
130
137
  """
131
138
  return self._subscriber.get_magnetometer()
132
139
 
140
+ def has_magnetometer(self) -> bool:
141
+ """Check if the ZED IMU has magnetometer data available.
142
+
143
+ Returns:
144
+ True if magnetometer data is available, False otherwise.
145
+ """
146
+ return self._subscriber.has_magnetometer()
147
+
133
148
  @property
134
149
  def fps(self) -> float:
135
150
  """Get the current FPS measurement.
@@ -141,7 +156,7 @@ class NineAxisIMUSensor:
141
156
 
142
157
  @property
143
158
  def name(self) -> str:
144
- """Get the IMU name.
159
+ """Get the ZED IMU name.
145
160
 
146
161
  Returns:
147
162
  IMU name string.
@@ -1,3 +1,13 @@
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
+
1
11
  from .rplidar import RPLidarSensor
2
12
 
3
- __all__ = ['RPLidarSensor']
13
+ __all__ = ['RPLidarSensor']
@@ -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
  """LIDAR sensor implementations using Zenoh subscribers.
7
12
 
@@ -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 manager for managing robot sensors with Zenoh communication.
7
12
 
@@ -21,10 +26,10 @@ from omegaconf import DictConfig, OmegaConf
21
26
  from dexcontrol.config.sensors.vega_sensors import VegaSensorsConfig
22
27
 
23
28
  if TYPE_CHECKING:
24
- from dexcontrol.sensors.camera.gemini_camera import GeminiCameraSensor
25
29
  from dexcontrol.sensors.camera.rgb_camera import RGBCameraSensor
26
- from dexcontrol.sensors.imu.gemini_imu import GeminiIMUSensor
27
- from dexcontrol.sensors.imu.nine_axis_imu import NineAxisIMUSensor
30
+ from dexcontrol.sensors.camera.zed_camera import ZedCameraSensor
31
+ from dexcontrol.sensors.imu.chassis_imu import ChassisIMUSensor
32
+ from dexcontrol.sensors.imu.zed_imu import ZedIMUSensor
28
33
  from dexcontrol.sensors.lidar.rplidar import RPLidarSensor
29
34
  from dexcontrol.sensors.ultrasonic import UltrasonicSensor
30
35
 
@@ -43,13 +48,13 @@ class Sensors:
43
48
 
44
49
  if TYPE_CHECKING:
45
50
  # Type annotations for dynamically created sensor attributes
46
- head_camera: GeminiCameraSensor
51
+ head_camera: ZedCameraSensor
47
52
  base_left_camera: RGBCameraSensor
48
53
  base_right_camera: RGBCameraSensor
49
54
  base_front_camera: RGBCameraSensor
50
55
  base_back_camera: RGBCameraSensor
51
- base_imu: NineAxisIMUSensor
52
- head_imu: GeminiIMUSensor
56
+ base_imu: ChassisIMUSensor
57
+ head_imu: ZedIMUSensor
53
58
  lidar: RPLidarSensor
54
59
  ultrasonic: UltrasonicSensor
55
60
 
@@ -87,6 +92,10 @@ class Sensors:
87
92
  logger.debug(f"Sensor {name} config is None")
88
93
  return None
89
94
 
95
+ if not config.enable:
96
+ logger.debug(f"Sensor {name} is disabled")
97
+ return None
98
+
90
99
  if not (hasattr(config, '_target_') and config._target_):
91
100
  return None
92
101
 
@@ -118,9 +127,13 @@ class Sensors:
118
127
  try:
119
128
  if hasattr(sensor, "shutdown"):
120
129
  sensor.shutdown()
130
+ # Small delay between each sensor shutdown to prevent race conditions
131
+ time.sleep(0.05)
121
132
  except Exception as e:
122
133
  logger.error(f"Error shutting down sensor: {e}")
123
134
 
135
+ # Additional delay to ensure all sensor subscribers have been undeclared
136
+ time.sleep(0.1)
124
137
  logger.info("All sensors shut down")
125
138
 
126
139
  def get_active_sensors(self) -> list[str]:
@@ -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
  """Ultrasonic sensor implementations using Zenoh subscribers.
7
12
 
@@ -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
  from .subscribers import (
7
12
  BaseZenohSubscriber,
@@ -1,3 +1,13 @@
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
+
1
11
  """Constants used throughout the dexcontrol package."""
2
12
 
3
13
  from typing import Final
@@ -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
  from typing import Any
7
12
 
@@ -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
 
7
12
  import numpy as np
@@ -1,10 +1,19 @@
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
+
1
11
  """Operating system utility functions."""
2
12
 
3
13
  import os
14
+ import re
4
15
  from typing import Final
5
16
 
6
- from loguru import logger
7
-
8
17
  from dexcontrol.utils.constants import ROBOT_NAME_ENV_VAR
9
18
 
10
19
 
@@ -23,6 +32,10 @@ def resolve_key_name(key: str) -> str:
23
32
  # Remove leading slash if present
24
33
  key = key.lstrip("/")
25
34
 
35
+ # Check if robot name is already present at the beginning
36
+ if key.startswith(f"{robot_name}/"):
37
+ return key
38
+
26
39
  # Combine robot name and key with single slash
27
40
  return f"{robot_name}/{key}"
28
41
 
@@ -30,10 +43,16 @@ def resolve_key_name(key: str) -> str:
30
43
  def get_robot_model() -> str:
31
44
  """Get the robot model from the environment variable."""
32
45
  robot_model_abb_mapping = dict(vg="vega")
33
- robot_name = os.getenv(ROBOT_NAME_ENV_VAR, "robot")
46
+ robot_name = os.getenv(ROBOT_NAME_ENV_VAR)
47
+ if robot_name is None:
48
+ raise ValueError(
49
+ f"Robot name is not set, please set the environment variable {ROBOT_NAME_ENV_VAR}"
50
+ )
51
+ if not re.match(r"^dm/[a-zA-Z0-9]{12}-(?:\d+|rc\d+)$", robot_name):
52
+ raise ValueError(f"Robot name is not in the correct format: {robot_name}")
53
+
34
54
  robot_model_abb = robot_name.split("/")[-1].split("-")[0][:2]
35
55
  if robot_model_abb not in robot_model_abb_mapping:
36
56
  raise ValueError(f"Unknown robot model: {robot_model_abb}")
37
57
  model = robot_model_abb_mapping[robot_model_abb] + "-" + robot_name.split("-")[-1]
38
- logger.info(f"The current robot model is: {model}")
39
58
  return model
@@ -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
  """Utility functions for Python data structures and protobuf messages."""
7
12
 
@@ -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
  """Rate limiter utility for maintaining consistent execution rates."""
7
12
 
@@ -0,0 +1,144 @@
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
+ """RTC utilities for dexcontrol.
12
+
13
+ This module provides utility functions for creating RTC subscribers
14
+ that first query Zenoh for connection information.
15
+ """
16
+
17
+ import zenoh
18
+ from loguru import logger
19
+
20
+ from dexcontrol.utils.subscribers.rtc import RTCSubscriber
21
+ from dexcontrol.utils.zenoh_utils import query_zenoh_json
22
+
23
+
24
+ def query_rtc_info(
25
+ zenoh_session: zenoh.Session,
26
+ info_topic: str,
27
+ timeout: float = 2.0,
28
+ max_retries: int = 1,
29
+ retry_delay: float = 0.5,
30
+ ) -> dict | None:
31
+ """Query Zenoh for RTC connection information.
32
+
33
+ Args:
34
+ zenoh_session: Active Zenoh session for communication.
35
+ info_topic: Zenoh topic to query for RTC info.
36
+ timeout: Maximum time to wait for a response in seconds.
37
+ max_retries: Maximum number of retry attempts.
38
+ retry_delay: Initial delay between retries (doubles each retry).
39
+
40
+ Returns:
41
+ Dictionary containing host and port information if successful, None otherwise.
42
+ """
43
+
44
+ # Use the general Zenoh query function
45
+ info = query_zenoh_json(
46
+ zenoh_session=zenoh_session,
47
+ topic=info_topic,
48
+ timeout=timeout,
49
+ max_retries=max_retries,
50
+ retry_delay=retry_delay,
51
+ )
52
+
53
+ return info
54
+
55
+
56
+ def create_rtc_subscriber_from_zenoh(
57
+ zenoh_session: zenoh.Session,
58
+ info_topic: str,
59
+ name: str = "rtc_subscriber",
60
+ enable_fps_tracking: bool = True,
61
+ fps_log_interval: int = 100,
62
+ query_timeout: float = 2.0,
63
+ max_retries: int = 1,
64
+ ) -> RTCSubscriber | None:
65
+ """Create a RTC subscriber by first querying Zenoh for connection info.
66
+
67
+ Args:
68
+ zenoh_session: Active Zenoh session for communication.
69
+ info_topic: Zenoh topic to query for RTC connection info.
70
+ name: Name for logging purposes.
71
+ enable_fps_tracking: Whether to track and log FPS metrics.
72
+ fps_log_interval: Number of frames between FPS calculations.
73
+ query_timeout: Maximum time to wait for Zenoh query response.
74
+ max_retries: Maximum number of retry attempts for Zenoh query.
75
+
76
+ Returns:
77
+ RTCSubscriber instance if successful, None otherwise.
78
+ """
79
+ # Query Zenoh for RTC connection information
80
+ rtc_info = query_rtc_info(zenoh_session, info_topic, query_timeout, max_retries)
81
+
82
+ if rtc_info is None:
83
+ logger.error("Failed to get RTC connection info from Zenoh")
84
+ return None
85
+
86
+ url = rtc_info.get("signaling_url")
87
+
88
+ if not url:
89
+ logger.error(f"Invalid RTC info: url={url}")
90
+ return None
91
+
92
+ # Construct WebSocket URL
93
+ ws_url = url
94
+ logger.info(f"Creating RTC subscriber with URL: {ws_url}")
95
+
96
+ try:
97
+ # Create and return the RTC subscriber
98
+ subscriber = RTCSubscriber(
99
+ url=ws_url,
100
+ name=name,
101
+ enable_fps_tracking=enable_fps_tracking,
102
+ fps_log_interval=fps_log_interval,
103
+ )
104
+ return subscriber
105
+ except Exception as e:
106
+ logger.error(f"Failed to create RTC subscriber: {e}")
107
+ return None
108
+
109
+
110
+ def create_rtc_subscriber_with_config(
111
+ zenoh_session: zenoh.Session,
112
+ config,
113
+ name: str = "rtc_subscriber",
114
+ enable_fps_tracking: bool = True,
115
+ fps_log_interval: int = 100,
116
+ ) -> RTCSubscriber | None:
117
+ """Create a RTC subscriber using configuration object.
118
+
119
+ Args:
120
+ zenoh_session: Active Zenoh session for communication.
121
+ config: Configuration object containing info_key.
122
+ name: Name for logging purposes.
123
+ enable_fps_tracking: Whether to track and log FPS metrics.
124
+ fps_log_interval: Number of frames between FPS calculations.
125
+ Returns:
126
+ RTCSubscriber instance if successful, None otherwise.
127
+ """
128
+ if "info_key" not in config:
129
+ logger.error("Config subscriber_config missing info_key")
130
+ return None
131
+
132
+ if not config["enable"]:
133
+ logger.info(f"Skipping {name} because it is disabled")
134
+ return None
135
+
136
+ info_topic = config["info_key"]
137
+
138
+ return create_rtc_subscriber_from_zenoh(
139
+ zenoh_session=zenoh_session,
140
+ info_topic=info_topic,
141
+ name=name,
142
+ enable_fps_tracking=enable_fps_tracking,
143
+ fps_log_interval=fps_log_interval,
144
+ )
@@ -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
  """Zenoh subscriber utilities for dexcontrol.
7
12
 
@@ -22,6 +27,7 @@ from .generic import GenericZenohSubscriber
22
27
  from .imu import IMUSubscriber
23
28
  from .lidar import LidarSubscriber
24
29
  from .protobuf import ProtobufZenohSubscriber
30
+ from .rtc import RTCSubscriber
25
31
 
26
32
  __all__ = [
27
33
  "BaseZenohSubscriber",
@@ -41,4 +47,6 @@ __all__ = [
41
47
  "LidarSubscriber",
42
48
  # IMU subscriber
43
49
  "IMUSubscriber",
50
+ # RTC subscriber
51
+ "RTCSubscriber",
44
52
  ]
@@ -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
  """Base Zenoh subscriber utilities.
7
12
 
@@ -193,8 +198,24 @@ class BaseZenohSubscriber(ABC):
193
198
 
194
199
  def shutdown(self) -> None:
195
200
  """Stop the subscriber and release resources."""
196
- if hasattr(self, "_subscriber") and self._subscriber:
197
- self._subscriber.undeclare()
201
+ # Mark as inactive first to prevent further data processing
202
+ with self._data_lock:
203
+ self._active = False
204
+
205
+ # Small delay to allow any ongoing data processing to complete
206
+ time.sleep(0.05)
207
+
208
+ try:
209
+ if hasattr(self, "_subscriber") and self._subscriber:
210
+ self._subscriber.undeclare()
211
+ except Exception as e:
212
+ # Don't log "Undeclared subscriber" errors as warnings - they're expected during shutdown
213
+ error_msg = str(e).lower()
214
+ if not ("undeclared" in error_msg or "closed" in error_msg):
215
+ logger.warning(f"Error undeclaring subscriber for {self._topic}: {e}")
216
+
217
+ # Additional delay to allow Zenoh to process the undeclare operation
218
+ time.sleep(0.02)
198
219
 
199
220
  @property
200
221
  def topic(self) -> str: