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.
- dexcontrol/__init__.py +45 -0
- dexcontrol/apps/dualsense_teleop_base.py +371 -0
- dexcontrol/config/__init__.py +14 -0
- dexcontrol/config/core/__init__.py +22 -0
- dexcontrol/config/core/arm.py +32 -0
- dexcontrol/config/core/chassis.py +22 -0
- dexcontrol/config/core/hand.py +42 -0
- dexcontrol/config/core/head.py +33 -0
- dexcontrol/config/core/misc.py +37 -0
- dexcontrol/config/core/torso.py +36 -0
- dexcontrol/config/sensors/__init__.py +4 -0
- dexcontrol/config/sensors/cameras/__init__.py +7 -0
- dexcontrol/config/sensors/cameras/gemini_camera.py +16 -0
- dexcontrol/config/sensors/cameras/rgb_camera.py +15 -0
- dexcontrol/config/sensors/imu/__init__.py +6 -0
- dexcontrol/config/sensors/imu/gemini_imu.py +15 -0
- dexcontrol/config/sensors/imu/nine_axis_imu.py +15 -0
- dexcontrol/config/sensors/lidar/__init__.py +6 -0
- dexcontrol/config/sensors/lidar/rplidar.py +15 -0
- dexcontrol/config/sensors/ultrasonic/__init__.py +6 -0
- dexcontrol/config/sensors/ultrasonic/ultrasonic.py +15 -0
- dexcontrol/config/sensors/vega_sensors.py +65 -0
- dexcontrol/config/vega.py +203 -0
- dexcontrol/core/__init__.py +0 -0
- dexcontrol/core/arm.py +324 -0
- dexcontrol/core/chassis.py +628 -0
- dexcontrol/core/component.py +834 -0
- dexcontrol/core/hand.py +170 -0
- dexcontrol/core/head.py +232 -0
- dexcontrol/core/misc.py +514 -0
- dexcontrol/core/torso.py +198 -0
- dexcontrol/proto/dexcontrol_msg_pb2.py +69 -0
- dexcontrol/proto/dexcontrol_msg_pb2.pyi +168 -0
- dexcontrol/proto/dexcontrol_query_pb2.py +73 -0
- dexcontrol/proto/dexcontrol_query_pb2.pyi +134 -0
- dexcontrol/robot.py +1091 -0
- dexcontrol/sensors/__init__.py +40 -0
- dexcontrol/sensors/camera/__init__.py +18 -0
- dexcontrol/sensors/camera/gemini_camera.py +139 -0
- dexcontrol/sensors/camera/rgb_camera.py +98 -0
- dexcontrol/sensors/imu/__init__.py +22 -0
- dexcontrol/sensors/imu/gemini_imu.py +139 -0
- dexcontrol/sensors/imu/nine_axis_imu.py +149 -0
- dexcontrol/sensors/lidar/__init__.py +3 -0
- dexcontrol/sensors/lidar/rplidar.py +164 -0
- dexcontrol/sensors/manager.py +185 -0
- dexcontrol/sensors/ultrasonic.py +110 -0
- dexcontrol/utils/__init__.py +15 -0
- dexcontrol/utils/constants.py +12 -0
- dexcontrol/utils/io_utils.py +26 -0
- dexcontrol/utils/motion_utils.py +194 -0
- dexcontrol/utils/os_utils.py +39 -0
- dexcontrol/utils/pb_utils.py +103 -0
- dexcontrol/utils/rate_limiter.py +167 -0
- dexcontrol/utils/reset_orbbec_camera_usb.py +98 -0
- dexcontrol/utils/subscribers/__init__.py +44 -0
- dexcontrol/utils/subscribers/base.py +260 -0
- dexcontrol/utils/subscribers/camera.py +328 -0
- dexcontrol/utils/subscribers/decoders.py +83 -0
- dexcontrol/utils/subscribers/generic.py +105 -0
- dexcontrol/utils/subscribers/imu.py +170 -0
- dexcontrol/utils/subscribers/lidar.py +195 -0
- dexcontrol/utils/subscribers/protobuf.py +106 -0
- dexcontrol/utils/timer.py +136 -0
- dexcontrol/utils/trajectory_utils.py +40 -0
- dexcontrol/utils/viz_utils.py +86 -0
- dexcontrol-0.2.1.dist-info/METADATA +369 -0
- dexcontrol-0.2.1.dist-info/RECORD +72 -0
- dexcontrol-0.2.1.dist-info/WHEEL +5 -0
- dexcontrol-0.2.1.dist-info/licenses/LICENSE +188 -0
- dexcontrol-0.2.1.dist-info/licenses/NOTICE +13 -0
- 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}")
|