dexcontrol 0.2.1__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 (72) hide show
  1. dexcontrol/__init__.py +45 -0
  2. dexcontrol/apps/dualsense_teleop_base.py +371 -0
  3. dexcontrol/config/__init__.py +14 -0
  4. dexcontrol/config/core/__init__.py +22 -0
  5. dexcontrol/config/core/arm.py +32 -0
  6. dexcontrol/config/core/chassis.py +22 -0
  7. dexcontrol/config/core/hand.py +42 -0
  8. dexcontrol/config/core/head.py +33 -0
  9. dexcontrol/config/core/misc.py +37 -0
  10. dexcontrol/config/core/torso.py +36 -0
  11. dexcontrol/config/sensors/__init__.py +4 -0
  12. dexcontrol/config/sensors/cameras/__init__.py +7 -0
  13. dexcontrol/config/sensors/cameras/gemini_camera.py +16 -0
  14. dexcontrol/config/sensors/cameras/rgb_camera.py +15 -0
  15. dexcontrol/config/sensors/imu/__init__.py +6 -0
  16. dexcontrol/config/sensors/imu/gemini_imu.py +15 -0
  17. dexcontrol/config/sensors/imu/nine_axis_imu.py +15 -0
  18. dexcontrol/config/sensors/lidar/__init__.py +6 -0
  19. dexcontrol/config/sensors/lidar/rplidar.py +15 -0
  20. dexcontrol/config/sensors/ultrasonic/__init__.py +6 -0
  21. dexcontrol/config/sensors/ultrasonic/ultrasonic.py +15 -0
  22. dexcontrol/config/sensors/vega_sensors.py +65 -0
  23. dexcontrol/config/vega.py +203 -0
  24. dexcontrol/core/__init__.py +0 -0
  25. dexcontrol/core/arm.py +324 -0
  26. dexcontrol/core/chassis.py +628 -0
  27. dexcontrol/core/component.py +834 -0
  28. dexcontrol/core/hand.py +170 -0
  29. dexcontrol/core/head.py +232 -0
  30. dexcontrol/core/misc.py +514 -0
  31. dexcontrol/core/torso.py +198 -0
  32. dexcontrol/proto/dexcontrol_msg_pb2.py +69 -0
  33. dexcontrol/proto/dexcontrol_msg_pb2.pyi +168 -0
  34. dexcontrol/proto/dexcontrol_query_pb2.py +73 -0
  35. dexcontrol/proto/dexcontrol_query_pb2.pyi +134 -0
  36. dexcontrol/robot.py +1091 -0
  37. dexcontrol/sensors/__init__.py +40 -0
  38. dexcontrol/sensors/camera/__init__.py +18 -0
  39. dexcontrol/sensors/camera/gemini_camera.py +139 -0
  40. dexcontrol/sensors/camera/rgb_camera.py +98 -0
  41. dexcontrol/sensors/imu/__init__.py +22 -0
  42. dexcontrol/sensors/imu/gemini_imu.py +139 -0
  43. dexcontrol/sensors/imu/nine_axis_imu.py +149 -0
  44. dexcontrol/sensors/lidar/__init__.py +3 -0
  45. dexcontrol/sensors/lidar/rplidar.py +164 -0
  46. dexcontrol/sensors/manager.py +185 -0
  47. dexcontrol/sensors/ultrasonic.py +110 -0
  48. dexcontrol/utils/__init__.py +15 -0
  49. dexcontrol/utils/constants.py +12 -0
  50. dexcontrol/utils/io_utils.py +26 -0
  51. dexcontrol/utils/motion_utils.py +194 -0
  52. dexcontrol/utils/os_utils.py +39 -0
  53. dexcontrol/utils/pb_utils.py +103 -0
  54. dexcontrol/utils/rate_limiter.py +167 -0
  55. dexcontrol/utils/reset_orbbec_camera_usb.py +98 -0
  56. dexcontrol/utils/subscribers/__init__.py +44 -0
  57. dexcontrol/utils/subscribers/base.py +260 -0
  58. dexcontrol/utils/subscribers/camera.py +328 -0
  59. dexcontrol/utils/subscribers/decoders.py +83 -0
  60. dexcontrol/utils/subscribers/generic.py +105 -0
  61. dexcontrol/utils/subscribers/imu.py +170 -0
  62. dexcontrol/utils/subscribers/lidar.py +195 -0
  63. dexcontrol/utils/subscribers/protobuf.py +106 -0
  64. dexcontrol/utils/timer.py +136 -0
  65. dexcontrol/utils/trajectory_utils.py +40 -0
  66. dexcontrol/utils/viz_utils.py +86 -0
  67. dexcontrol-0.2.1.dist-info/METADATA +369 -0
  68. dexcontrol-0.2.1.dist-info/RECORD +72 -0
  69. dexcontrol-0.2.1.dist-info/WHEEL +5 -0
  70. dexcontrol-0.2.1.dist-info/licenses/LICENSE +188 -0
  71. dexcontrol-0.2.1.dist-info/licenses/NOTICE +13 -0
  72. dexcontrol-0.2.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,164 @@
1
+ # Copyright (c) 2025 Dexmate CORPORATION & AFFILIATES. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 with Commons Clause License
4
+ # Condition v1.0 [see LICENSE for details].
5
+
6
+ """LIDAR sensor implementations using Zenoh subscribers.
7
+
8
+ This module provides LIDAR sensor classes that use the specialized LIDAR
9
+ subscriber for scan data.
10
+ """
11
+
12
+ from typing import Any
13
+
14
+ import numpy as np
15
+ import zenoh
16
+
17
+ from dexcontrol.utils.subscribers.lidar import LidarSubscriber
18
+
19
+
20
+ class RPLidarSensor:
21
+ """LIDAR sensor using Zenoh subscriber.
22
+
23
+ This sensor provides LIDAR scan data using the LidarSubscriber
24
+ for efficient data handling with lazy decoding.
25
+ """
26
+
27
+ def __init__(
28
+ self,
29
+ configs,
30
+ zenoh_session: zenoh.Session,
31
+ ) -> None:
32
+ """Initialize the LIDAR sensor.
33
+
34
+ Args:
35
+ topic: Zenoh topic to subscribe to for LIDAR data.
36
+ zenoh_session: Active Zenoh session for communication.
37
+ name: Name for the sensor instance.
38
+ enable_fps_tracking: Whether to track and log FPS metrics.
39
+ fps_log_interval: Number of frames between FPS calculations.
40
+ """
41
+ self._name = configs.name
42
+
43
+ # Create the LIDAR subscriber
44
+ self._subscriber = LidarSubscriber(
45
+ topic=configs.topic,
46
+ zenoh_session=zenoh_session,
47
+ name=f"{self._name}_subscriber",
48
+ enable_fps_tracking=configs.enable_fps_tracking,
49
+ fps_log_interval=configs.fps_log_interval,
50
+ )
51
+
52
+
53
+ def shutdown(self) -> None:
54
+ """Shutdown the LIDAR sensor."""
55
+ self._subscriber.shutdown()
56
+
57
+ def is_active(self) -> bool:
58
+ """Check if the LIDAR sensor is actively receiving data.
59
+
60
+ Returns:
61
+ True if receiving data, False otherwise.
62
+ """
63
+ return self._subscriber.is_active()
64
+
65
+ def wait_for_active(self, timeout: float = 5.0) -> bool:
66
+ """Wait for the LIDAR sensor to start receiving data.
67
+
68
+ Args:
69
+ timeout: Maximum time to wait in seconds.
70
+
71
+ Returns:
72
+ True if sensor becomes active, False if timeout is reached.
73
+ """
74
+ return self._subscriber.wait_for_active(timeout)
75
+
76
+ def get_obs(self) -> dict[str, Any] | None:
77
+ """Get the latest LIDAR scan data.
78
+
79
+ Returns:
80
+ Latest scan data dictionary if available, None otherwise.
81
+ Dictionary contains:
82
+ - ranges: Array of range measurements
83
+ - angles: Array of corresponding angles
84
+ - intensities: Array of intensity values (if available)
85
+ - angle_min: Minimum angle of the scan
86
+ - angle_max: Maximum angle of the scan
87
+ - angle_increment: Angular distance between measurements
88
+ - scan_time: Time for a complete scan
89
+ - time_increment: Time between measurements
90
+ - range_min: Minimum range value
91
+ - range_max: Maximum range value
92
+ """
93
+ return self._subscriber.get_latest_data()
94
+
95
+ def get_ranges(self) -> np.ndarray | None:
96
+ """Get the latest range measurements.
97
+
98
+ Returns:
99
+ Array of range measurements if available, None otherwise.
100
+ """
101
+ return self._subscriber.get_ranges()
102
+
103
+ def get_angles(self) -> np.ndarray | None:
104
+ """Get the latest angle measurements.
105
+
106
+ Returns:
107
+ Array of angle measurements if available, None otherwise.
108
+ """
109
+ return self._subscriber.get_angles()
110
+
111
+ def get_intensities(self) -> np.ndarray | None:
112
+ """Get the latest intensity measurements.
113
+
114
+ Returns:
115
+ Array of intensity measurements if available, None otherwise.
116
+ """
117
+ return self._subscriber.get_intensities()
118
+
119
+ def get_scan_info(self) -> dict[str, float] | None:
120
+ """Get scan metadata information.
121
+
122
+ Returns:
123
+ Dictionary with scan metadata if available, None otherwise.
124
+ Contains: angle_min, angle_max, angle_increment, scan_time,
125
+ time_increment, range_min, range_max
126
+ """
127
+ return self._subscriber.get_scan_info()
128
+
129
+ def get_point_count(self) -> int:
130
+ """Get the number of points in the latest scan.
131
+
132
+ Returns:
133
+ Number of points in the scan, 0 if no data available.
134
+ """
135
+ ranges = self.get_ranges()
136
+ if ranges is not None:
137
+ return len(ranges)
138
+ return 0
139
+
140
+ def has_intensities(self) -> bool:
141
+ """Check if the latest scan data includes intensity information.
142
+
143
+ Returns:
144
+ True if intensity data is available, False otherwise.
145
+ """
146
+ return self._subscriber.has_intensities()
147
+
148
+ @property
149
+ def fps(self) -> float:
150
+ """Get the current FPS measurement.
151
+
152
+ Returns:
153
+ Current frames per second measurement.
154
+ """
155
+ return self._subscriber.fps
156
+
157
+ @property
158
+ def name(self) -> str:
159
+ """Get the LIDAR name.
160
+
161
+ Returns:
162
+ LIDAR name string.
163
+ """
164
+ return self._name
@@ -0,0 +1,185 @@
1
+ # Copyright (c) 2025 Dexmate CORPORATION & AFFILIATES. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 with Commons Clause License
4
+ # Condition v1.0 [see LICENSE for details].
5
+
6
+ """Sensor manager for managing robot sensors with Zenoh communication.
7
+
8
+ This module provides the Sensors class that manages multiple sensor instances
9
+ based on configuration and provides unified access to them.
10
+ """
11
+
12
+ import time
13
+ import traceback
14
+ from typing import TYPE_CHECKING, Any
15
+
16
+ import hydra.utils as hydra_utils
17
+ import zenoh
18
+ from loguru import logger
19
+ from omegaconf import DictConfig, OmegaConf
20
+
21
+ from dexcontrol.config.sensors.vega_sensors import VegaSensorsConfig
22
+
23
+ if TYPE_CHECKING:
24
+ from dexcontrol.sensors.camera.gemini_camera import GeminiCameraSensor
25
+ 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
28
+ from dexcontrol.sensors.lidar.rplidar import RPLidarSensor
29
+ from dexcontrol.sensors.ultrasonic import UltrasonicSensor
30
+
31
+
32
+ class Sensors:
33
+ """Manages robot sensors including cameras, IMUs, LiDAR, and ultrasonic sensors.
34
+
35
+ Provides a unified interface for creating, managing, and shutting down all types of
36
+ sensors used by the robot. Instantiates sensors based on configuration and provides
37
+ access to them as attributes.
38
+
39
+ Attributes:
40
+ _sensors: List of instantiated sensor objects.
41
+ _zenoh_session: Active Zenoh session for communication.
42
+ """
43
+
44
+ if TYPE_CHECKING:
45
+ # Type annotations for dynamically created sensor attributes
46
+ head_camera: GeminiCameraSensor
47
+ base_left_camera: RGBCameraSensor
48
+ base_right_camera: RGBCameraSensor
49
+ base_front_camera: RGBCameraSensor
50
+ base_back_camera: RGBCameraSensor
51
+ base_imu: NineAxisIMUSensor
52
+ head_imu: GeminiIMUSensor
53
+ lidar: RPLidarSensor
54
+ ultrasonic: UltrasonicSensor
55
+
56
+ def __init__(self, configs: DictConfig | VegaSensorsConfig,
57
+ zenoh_session: zenoh.Session) -> None:
58
+ """Initialize sensors from configuration.
59
+
60
+ Args:
61
+ configs: Configuration for all sensors (VegaSensorsConfig or DictConfig).
62
+ zenoh_session: Active Zenoh session for communication.
63
+ """
64
+ self._sensors: list[Any] = []
65
+ self._zenoh_session = zenoh_session
66
+
67
+ dict_configs = (OmegaConf.structured(configs) if isinstance(configs, VegaSensorsConfig)
68
+ else configs)
69
+
70
+ for sensor_name, sensor_config in dict_configs.items():
71
+ sensor = self._create_sensor(sensor_config, str(sensor_name))
72
+ if sensor is not None:
73
+ setattr(self, str(sensor_name), sensor)
74
+ self._sensors.append(sensor)
75
+
76
+ def _create_sensor(self, config: DictConfig | None, name: str) -> Any | None:
77
+ """Creates and initializes a sensor from config.
78
+
79
+ Args:
80
+ config: Sensor configuration.
81
+ name: Name of the sensor for logging.
82
+
83
+ Returns:
84
+ Initialized sensor object or None if creation fails.
85
+ """
86
+ if config is None:
87
+ logger.debug(f"Sensor {name} config is None")
88
+ return None
89
+
90
+ if not (hasattr(config, '_target_') and config._target_):
91
+ return None
92
+
93
+ try:
94
+ temp_config = OmegaConf.create({
95
+ '_target_': config._target_,
96
+ 'configs': {k: v for k, v in config.items() if k != '_target_'}
97
+ })
98
+
99
+ sensor = hydra_utils.instantiate(
100
+ temp_config,
101
+ zenoh_session=self._zenoh_session,
102
+ )
103
+
104
+ if hasattr(sensor, "start"):
105
+ sensor.start()
106
+
107
+ return sensor
108
+
109
+ except Exception as e:
110
+ logger.error(f"Failed to instantiate sensor {name}: {e}")
111
+ logger.debug(f"Full traceback:\n{traceback.format_exc()}")
112
+ return None
113
+
114
+ def shutdown(self) -> None:
115
+ """Shuts down all active sensors."""
116
+ logger.info("Shutting down all sensors...")
117
+ for sensor in self._sensors:
118
+ try:
119
+ if hasattr(sensor, "shutdown"):
120
+ sensor.shutdown()
121
+ except Exception as e:
122
+ logger.error(f"Error shutting down sensor: {e}")
123
+
124
+ logger.info("All sensors shut down")
125
+
126
+ def get_active_sensors(self) -> list[str]:
127
+ """Gets list of active sensor names.
128
+
129
+ Returns:
130
+ List of sensor names that are currently active.
131
+ """
132
+ excluded_attrs = {'shutdown', 'get_active_sensors', 'wait_for_sensors'}
133
+ active_sensors = []
134
+
135
+ for attr_name in dir(self):
136
+ if (not attr_name.startswith('_') and
137
+ attr_name not in excluded_attrs):
138
+ sensor = getattr(self, attr_name)
139
+ if (sensor is not None and
140
+ hasattr(sensor, 'is_active') and
141
+ sensor.is_active()):
142
+ active_sensors.append(attr_name)
143
+ return active_sensors
144
+
145
+ def wait_for_sensors(self, timeout: float = 5.0) -> bool:
146
+ """Waits for all enabled sensors to become active.
147
+
148
+ Args:
149
+ timeout: Maximum time to wait for sensors to become active.
150
+
151
+ Returns:
152
+ True if all sensors became active, False otherwise.
153
+ """
154
+ start_time = time.time()
155
+
156
+ while time.time() - start_time < timeout:
157
+ if all(sensor.is_active() for sensor in self._sensors
158
+ if hasattr(sensor, 'is_active')):
159
+ logger.info("All sensors are active")
160
+ return True
161
+ time.sleep(0.1)
162
+
163
+ logger.warning(f"Not all sensors became active within {timeout}s")
164
+ return False
165
+
166
+ def wait_for_all_active(self, timeout: float = 5.0) -> bool:
167
+ """Waits for all enabled sensors to become active.
168
+
169
+ Alias for wait_for_sensors() for backward compatibility.
170
+
171
+ Args:
172
+ timeout: Maximum time to wait for sensors to become active.
173
+
174
+ Returns:
175
+ True if all sensors became active, False otherwise.
176
+ """
177
+ return self.wait_for_sensors(timeout)
178
+
179
+ def get_sensor_count(self) -> int:
180
+ """Gets the total number of instantiated sensors.
181
+
182
+ Returns:
183
+ Number of sensors that were successfully created.
184
+ """
185
+ return len(self._sensors)
@@ -0,0 +1,110 @@
1
+ # Copyright (c) 2025 Dexmate CORPORATION & AFFILIATES. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 with Commons Clause License
4
+ # Condition v1.0 [see LICENSE for details].
5
+
6
+ """Ultrasonic sensor implementations using Zenoh subscribers.
7
+
8
+ This module provides ultrasonic sensor classes that use the generic
9
+ subscriber for distance measurements.
10
+ """
11
+
12
+ import numpy as np
13
+ import zenoh
14
+
15
+ from dexcontrol.proto import dexcontrol_msg_pb2
16
+ from dexcontrol.utils.subscribers import ProtobufZenohSubscriber
17
+
18
+
19
+ class UltrasonicSensor:
20
+ """Ultrasonic sensor using Zenoh subscriber.
21
+
22
+ This sensor provides distance measurements from ultrasonic sensors
23
+ """
24
+
25
+ def __init__(
26
+ self,
27
+ configs,
28
+ zenoh_session: zenoh.Session,
29
+ ) -> None:
30
+ """Initialize the ultrasonic sensor.
31
+
32
+ Args:
33
+ configs: Configuration for the ultrasonic sensor.
34
+ zenoh_session: Active Zenoh session for communication.
35
+ """
36
+ self._name = configs.name
37
+
38
+ # Create the generic subscriber with JSON decoder
39
+ self._subscriber = ProtobufZenohSubscriber(
40
+ topic=configs.topic,
41
+ zenoh_session=zenoh_session,
42
+ message_type=dexcontrol_msg_pb2.UltrasonicState,
43
+ name=f"{self._name}_subscriber",
44
+ enable_fps_tracking=configs.enable_fps_tracking,
45
+ fps_log_interval=configs.fps_log_interval,
46
+ )
47
+
48
+
49
+ def shutdown(self) -> None:
50
+ """Shutdown the ultrasonic sensor."""
51
+ self._subscriber.shutdown()
52
+
53
+ def is_active(self) -> bool:
54
+ """Check if the ultrasonic sensor is actively receiving data.
55
+
56
+ Returns:
57
+ True if receiving data, False otherwise.
58
+ """
59
+ return self._subscriber.is_active()
60
+
61
+ def wait_for_active(self, timeout: float = 5.0) -> bool:
62
+ """Wait for the ultrasonic sensor to start receiving data.
63
+
64
+ Args:
65
+ timeout: Maximum time to wait in seconds.
66
+
67
+ Returns:
68
+ True if sensor becomes active, False if timeout is reached.
69
+ """
70
+ return self._subscriber.wait_for_active(timeout)
71
+
72
+ def get_obs(self) -> np.ndarray | None:
73
+ """Get observation data for the ultrasonic sensor.
74
+
75
+ This method provides a standardized observation format
76
+ that can be used in robotics applications.
77
+
78
+ Returns:
79
+ Numpy array of distances in meters with shape (4,) in the order:
80
+ [front_left, front_right, back_left, back_right].
81
+ """
82
+ data = self._subscriber.get_latest_data()
83
+ if data is not None:
84
+ obs = [
85
+ data.front_left,
86
+ data.front_right,
87
+ data.back_left,
88
+ data.back_right,
89
+ ]
90
+ return np.array(obs, dtype=np.float32)
91
+
92
+ return None
93
+
94
+ @property
95
+ def fps(self) -> float:
96
+ """Get the current FPS measurement.
97
+
98
+ Returns:
99
+ Current frames per second measurement.
100
+ """
101
+ return self._subscriber.fps
102
+
103
+ @property
104
+ def name(self) -> str:
105
+ """Get the sensor name.
106
+
107
+ Returns:
108
+ Sensor name string.
109
+ """
110
+ return self._name
@@ -0,0 +1,15 @@
1
+ # Copyright (c) 2025 Dexmate CORPORATION & AFFILIATES. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 with Commons Clause License
4
+ # Condition v1.0 [see LICENSE for details].
5
+
6
+ from .subscribers import (
7
+ BaseZenohSubscriber,
8
+ DecoderFunction,
9
+ GenericZenohSubscriber,
10
+ ProtobufZenohSubscriber,
11
+ json_decoder,
12
+ protobuf_decoder,
13
+ raw_bytes_decoder,
14
+ string_decoder,
15
+ )
@@ -0,0 +1,12 @@
1
+ """Constants used throughout the dexcontrol package."""
2
+
3
+ from typing import Final
4
+
5
+ # Environment variable for robot name
6
+ ROBOT_NAME_ENV_VAR: Final[str] = "ROBOT_NAME"
7
+
8
+ # Environment variable for communication config path
9
+ COMM_CFG_PATH_ENV_VAR: Final[str] = "DEXMATE_COMM_CFG_PATH"
10
+
11
+ # Environment variable to disable heartbeat monitoring
12
+ DISABLE_HEARTBEAT_ENV_VAR: Final[str] = "DEXCONTROL_DISABLE_HEARTBEAT"
@@ -0,0 +1,26 @@
1
+ # Copyright (c) 2025 Dexmate CORPORATION & AFFILIATES. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 with Commons Clause License
4
+ # Condition v1.0 [see LICENSE for details].
5
+
6
+ from typing import Any
7
+
8
+ from loguru import logger
9
+
10
+
11
+ def print_dict(d: dict[str, Any], indent: int = 0) -> None:
12
+ """Print a nested dictionary with nice formatting.
13
+
14
+ Recursively prints dictionary contents with proper indentation.
15
+
16
+ Args:
17
+ d: Dictionary to print.
18
+ indent: Current indentation level in spaces.
19
+ """
20
+ indent_str = " " * indent
21
+ for key, value in d.items():
22
+ if isinstance(value, dict):
23
+ logger.info(f"{indent_str}{key}:")
24
+ print_dict(value, indent + 4)
25
+ else:
26
+ logger.info(f"{indent_str}{key}: {value}")