dexcontrol 0.2.12__py3-none-any.whl → 0.3.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 +18 -8
- dexcontrol/apps/dualsense_teleop_base.py +1 -1
- dexcontrol/comm/__init__.py +51 -0
- dexcontrol/comm/base.py +421 -0
- dexcontrol/comm/rtc.py +400 -0
- dexcontrol/comm/subscribers.py +329 -0
- dexcontrol/config/core/chassis.py +9 -4
- dexcontrol/config/core/hand.py +1 -0
- dexcontrol/config/sensors/cameras/__init__.py +1 -2
- dexcontrol/config/sensors/cameras/zed_camera.py +2 -2
- dexcontrol/config/sensors/vega_sensors.py +12 -18
- dexcontrol/config/vega.py +4 -1
- dexcontrol/core/arm.py +61 -37
- dexcontrol/core/chassis.py +141 -119
- dexcontrol/core/component.py +110 -59
- dexcontrol/core/hand.py +118 -85
- dexcontrol/core/head.py +18 -29
- dexcontrol/core/misc.py +327 -155
- dexcontrol/core/robot_query_interface.py +463 -0
- dexcontrol/core/torso.py +4 -8
- dexcontrol/proto/dexcontrol_msg_pb2.py +27 -39
- dexcontrol/proto/dexcontrol_msg_pb2.pyi +75 -118
- dexcontrol/proto/dexcontrol_query_pb2.py +39 -39
- dexcontrol/proto/dexcontrol_query_pb2.pyi +17 -4
- dexcontrol/robot.py +245 -574
- dexcontrol/sensors/__init__.py +1 -2
- dexcontrol/sensors/camera/__init__.py +0 -2
- dexcontrol/sensors/camera/base_camera.py +144 -0
- dexcontrol/sensors/camera/rgb_camera.py +67 -63
- dexcontrol/sensors/camera/zed_camera.py +89 -147
- dexcontrol/sensors/imu/chassis_imu.py +76 -56
- dexcontrol/sensors/imu/zed_imu.py +54 -43
- dexcontrol/sensors/lidar/rplidar.py +16 -20
- dexcontrol/sensors/manager.py +4 -11
- dexcontrol/sensors/ultrasonic.py +14 -27
- dexcontrol/utils/__init__.py +0 -11
- dexcontrol/utils/comm_helper.py +111 -0
- dexcontrol/utils/constants.py +1 -1
- dexcontrol/utils/os_utils.py +169 -1
- dexcontrol/utils/pb_utils.py +0 -22
- {dexcontrol-0.2.12.dist-info → dexcontrol-0.3.1.dist-info}/METADATA +13 -1
- dexcontrol-0.3.1.dist-info/RECORD +68 -0
- dexcontrol/config/sensors/cameras/luxonis_camera.py +0 -51
- dexcontrol/sensors/camera/luxonis_camera.py +0 -169
- dexcontrol/utils/rate_limiter.py +0 -172
- dexcontrol/utils/rtc_utils.py +0 -144
- dexcontrol/utils/subscribers/__init__.py +0 -52
- dexcontrol/utils/subscribers/base.py +0 -281
- dexcontrol/utils/subscribers/camera.py +0 -332
- dexcontrol/utils/subscribers/decoders.py +0 -88
- dexcontrol/utils/subscribers/generic.py +0 -110
- dexcontrol/utils/subscribers/imu.py +0 -175
- dexcontrol/utils/subscribers/lidar.py +0 -172
- dexcontrol/utils/subscribers/protobuf.py +0 -111
- dexcontrol/utils/subscribers/rtc.py +0 -316
- dexcontrol/utils/zenoh_utils.py +0 -122
- dexcontrol-0.2.12.dist-info/RECORD +0 -75
- {dexcontrol-0.2.12.dist-info → dexcontrol-0.3.1.dist-info}/WHEEL +0 -0
- {dexcontrol-0.2.12.dist-info → dexcontrol-0.3.1.dist-info}/licenses/LICENSE +0 -0
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
|
|
18
|
+
from .camera import RGBCameraSensor, ZedCameraSensor
|
|
19
19
|
|
|
20
20
|
# Import IMU sensors
|
|
21
21
|
from .imu import ChassisIMUSensor, ZedIMUSensor
|
|
@@ -31,7 +31,6 @@ __all__ = [
|
|
|
31
31
|
# Camera sensors
|
|
32
32
|
"RGBCameraSensor",
|
|
33
33
|
"ZedCameraSensor",
|
|
34
|
-
"LuxonisCameraSensor",
|
|
35
34
|
|
|
36
35
|
# IMU sensors
|
|
37
36
|
"ChassisIMUSensor",
|
|
@@ -14,12 +14,10 @@ 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
|
|
18
17
|
from .rgb_camera import RGBCameraSensor
|
|
19
18
|
from .zed_camera import ZedCameraSensor
|
|
20
19
|
|
|
21
20
|
__all__ = [
|
|
22
21
|
"RGBCameraSensor",
|
|
23
22
|
"ZedCameraSensor",
|
|
24
|
-
"LuxonisCameraSensor",
|
|
25
23
|
]
|
|
@@ -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
|
+
"""Base camera sensor class with common functionality for all camera sensors."""
|
|
12
|
+
|
|
13
|
+
from abc import ABC, abstractmethod
|
|
14
|
+
from typing import Any, Dict, Optional
|
|
15
|
+
|
|
16
|
+
from loguru import logger
|
|
17
|
+
|
|
18
|
+
from dexcontrol.utils.comm_helper import query_json_service
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class BaseCameraSensor(ABC):
|
|
22
|
+
"""Abstract base class for camera sensors.
|
|
23
|
+
|
|
24
|
+
Provides common functionality for querying camera info, extracting RTC URLs,
|
|
25
|
+
and managing camera metadata. Subclasses should implement the abstract methods
|
|
26
|
+
for their specific camera type.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(self, name: str):
|
|
30
|
+
"""Initialize the base camera sensor.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
name: Name of the camera sensor
|
|
34
|
+
"""
|
|
35
|
+
self._name = name
|
|
36
|
+
self._camera_info: Optional[Dict[str, Any]] = None
|
|
37
|
+
|
|
38
|
+
@abstractmethod
|
|
39
|
+
def shutdown(self) -> None:
|
|
40
|
+
"""Shutdown the camera sensor and release all resources."""
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
@abstractmethod
|
|
44
|
+
def is_active(self) -> bool:
|
|
45
|
+
"""Check if the camera sensor is actively receiving data."""
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
@abstractmethod
|
|
49
|
+
def wait_for_active(self, timeout: float = 5.0) -> bool:
|
|
50
|
+
"""Wait for the camera sensor to start receiving data."""
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
def _query_camera_info(self, info_endpoint: Optional[str]) -> None:
|
|
54
|
+
"""Query for camera metadata information.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
info_endpoint: The endpoint to query for camera info
|
|
58
|
+
"""
|
|
59
|
+
if not info_endpoint:
|
|
60
|
+
logger.debug(f"'{self._name}': No info endpoint provided for camera info query.")
|
|
61
|
+
return
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
logger.debug(f"'{self._name}': Querying camera info from '{info_endpoint}'.")
|
|
65
|
+
self._camera_info = query_json_service(info_endpoint, timeout=2.0)
|
|
66
|
+
|
|
67
|
+
if self._camera_info:
|
|
68
|
+
logger.info(f"'{self._name}': Successfully retrieved camera info.")
|
|
69
|
+
else:
|
|
70
|
+
logger.debug(f"'{self._name}': No camera info available at '{info_endpoint}'.")
|
|
71
|
+
|
|
72
|
+
except Exception as e:
|
|
73
|
+
logger.debug(f"'{self._name}': Failed to query camera info: {e}")
|
|
74
|
+
|
|
75
|
+
def _get_rtc_signaling_url(self, stream_name: str) -> Optional[str]:
|
|
76
|
+
"""Extract RTC signaling URL from camera_info for a specific stream.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
stream_name: Name of the stream (e.g., 'left_rgb', 'right_rgb', 'rgb')
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
Signaling URL if available, None otherwise
|
|
83
|
+
"""
|
|
84
|
+
if not self._camera_info:
|
|
85
|
+
return None
|
|
86
|
+
|
|
87
|
+
rtc_info = self._camera_info.get("rtc", {})
|
|
88
|
+
streams = rtc_info.get("streams", {})
|
|
89
|
+
stream_info = streams.get(stream_name, {})
|
|
90
|
+
return stream_info.get("signaling_url")
|
|
91
|
+
|
|
92
|
+
def _derive_info_endpoint_from_topic(self, topic: str) -> Optional[str]:
|
|
93
|
+
"""Derive camera info endpoint from a topic.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
topic: The camera topic, e.g.:
|
|
97
|
+
- 'camera/head/left_rgb' -> 'camera/head/info'
|
|
98
|
+
- 'camera/head/right_rgb' -> 'camera/head/info'
|
|
99
|
+
- 'camera/head/depth' -> 'camera/head/info'
|
|
100
|
+
- 'camera/base_left/rgb' -> 'camera/base_left/info'
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
Derived info endpoint or None
|
|
104
|
+
"""
|
|
105
|
+
if not topic:
|
|
106
|
+
return None
|
|
107
|
+
|
|
108
|
+
parts = topic.split("/")
|
|
109
|
+
if len(parts) < 2:
|
|
110
|
+
return None
|
|
111
|
+
|
|
112
|
+
# Check if the last part is a stream identifier
|
|
113
|
+
last_part = parts[-1].lower()
|
|
114
|
+
stream_identifiers = ['left_rgb', 'right_rgb', 'depth', 'rgb', 'left', 'right']
|
|
115
|
+
|
|
116
|
+
if last_part in stream_identifiers:
|
|
117
|
+
# Remove the stream identifier and add 'info'
|
|
118
|
+
# e.g., camera/head/left_rgb -> camera/head/info
|
|
119
|
+
# e.g., camera/base_left/rgb -> camera/base_left/info
|
|
120
|
+
return "/".join(parts[:-1]) + "/info"
|
|
121
|
+
else:
|
|
122
|
+
# The last part is not a stream identifier, just append '/info'
|
|
123
|
+
# e.g., camera/head -> camera/head/info
|
|
124
|
+
return topic + "/info"
|
|
125
|
+
|
|
126
|
+
@property
|
|
127
|
+
def name(self) -> str:
|
|
128
|
+
"""Get the sensor name.
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
Sensor name string
|
|
132
|
+
"""
|
|
133
|
+
return self._name
|
|
134
|
+
|
|
135
|
+
@property
|
|
136
|
+
def camera_info(self) -> Optional[Dict[str, Any]]:
|
|
137
|
+
"""Get the camera metadata information.
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
Camera info dictionary if available, None otherwise.
|
|
141
|
+
The dictionary typically contains intrinsic parameters,
|
|
142
|
+
resolution, and other camera metadata.
|
|
143
|
+
"""
|
|
144
|
+
return self._camera_info
|
|
@@ -8,82 +8,101 @@
|
|
|
8
8
|
# 2. Commercial License
|
|
9
9
|
# For commercial licensing terms, contact: contact@dexmate.ai
|
|
10
10
|
|
|
11
|
-
"""RGB camera sensor implementation using RTC or
|
|
11
|
+
"""RGB camera sensor implementation using RTC or DexComm subscriber."""
|
|
12
12
|
|
|
13
|
-
import
|
|
14
|
-
from typing import Optional, Union
|
|
13
|
+
from typing import Optional
|
|
15
14
|
|
|
16
15
|
import numpy as np
|
|
17
|
-
import
|
|
16
|
+
from loguru import logger
|
|
18
17
|
|
|
18
|
+
from dexcontrol.comm import create_camera_subscriber, create_rtc_camera_subscriber
|
|
19
19
|
from dexcontrol.config.sensors.cameras import RGBCameraConfig
|
|
20
|
-
from dexcontrol.
|
|
21
|
-
from dexcontrol.utils.subscribers.camera import RGBCameraSubscriber
|
|
22
|
-
from dexcontrol.utils.subscribers.rtc import RTCSubscriber
|
|
20
|
+
from dexcontrol.sensors.camera.base_camera import BaseCameraSensor
|
|
23
21
|
|
|
24
|
-
logger = logging.getLogger(__name__)
|
|
25
22
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
"""RGB camera sensor that supports both RTC and standard Zenoh subscribers.
|
|
23
|
+
class RGBCameraSensor(BaseCameraSensor):
|
|
24
|
+
"""RGB camera sensor that supports both RTC and standard DexComm subscribers.
|
|
29
25
|
|
|
30
26
|
This sensor provides RGB image data from a camera. It can be configured to use
|
|
31
27
|
either a high-performance RTC subscriber for real-time video streams or a
|
|
32
|
-
standard
|
|
28
|
+
standard DexComm subscriber for raw image topics. The mode is controlled by the
|
|
33
29
|
`use_rtc` flag in the `RGBCameraConfig`.
|
|
30
|
+
|
|
31
|
+
Both subscriber types provide the same API, making them interchangeable.
|
|
34
32
|
"""
|
|
35
33
|
|
|
36
34
|
def __init__(
|
|
37
35
|
self,
|
|
38
36
|
configs: RGBCameraConfig,
|
|
39
|
-
zenoh_session: zenoh.Session,
|
|
40
37
|
) -> None:
|
|
41
38
|
"""Initialize the RGB camera sensor based on the provided configuration.
|
|
42
39
|
|
|
43
40
|
Args:
|
|
44
41
|
configs: Configuration object for the RGB camera sensor.
|
|
45
|
-
zenoh_session: Active Zenoh session for communication.
|
|
46
42
|
"""
|
|
47
|
-
|
|
48
|
-
self.
|
|
43
|
+
super().__init__(configs.name)
|
|
44
|
+
self._configs = configs
|
|
45
|
+
self._subscriber = None # Will be either RTCSubscriberWrapper or DexComm Subscriber
|
|
49
46
|
|
|
50
47
|
subscriber_config = configs.subscriber_config.get("rgb", {})
|
|
51
48
|
if not subscriber_config or not subscriber_config.get("enable", False):
|
|
52
49
|
logger.info(f"RGBCameraSensor '{self._name}' is disabled in config.")
|
|
53
50
|
return
|
|
54
51
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
52
|
+
# Determine info endpoint and query camera info to potentially get RTC URLs
|
|
53
|
+
subscriber_config = configs.subscriber_config.get("rgb", {})
|
|
54
|
+
info_endpoint = self._determine_info_endpoint(subscriber_config)
|
|
55
|
+
self._query_camera_info(info_endpoint)
|
|
56
|
+
|
|
57
|
+
if configs.use_rtc:
|
|
58
|
+
logger.info(f"'{self._name}': Using RTC subscriber.")
|
|
59
|
+
|
|
60
|
+
# Try to get signaling URL from camera_info first
|
|
61
|
+
rtc_url = self._get_rtc_signaling_url("rgb")
|
|
62
|
+
if rtc_url:
|
|
63
|
+
logger.info(f"'{self._name}': Creating RTC subscriber with direct URL.")
|
|
64
|
+
self._subscriber = create_rtc_camera_subscriber(
|
|
65
|
+
signaling_url=rtc_url,
|
|
64
66
|
)
|
|
65
67
|
else:
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if
|
|
69
|
-
self.
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
name=f"{self._name}_subscriber",
|
|
73
|
-
enable_fps_tracking=configs.enable_fps_tracking,
|
|
74
|
-
fps_log_interval=configs.fps_log_interval,
|
|
68
|
+
# Fallback to using info_key
|
|
69
|
+
info_topic = subscriber_config.get("info_key") or subscriber_config.get("topic")
|
|
70
|
+
if info_topic:
|
|
71
|
+
logger.info(f"'{self._name}': Creating RTC subscriber with info_key.")
|
|
72
|
+
self._subscriber = create_rtc_camera_subscriber(
|
|
73
|
+
info_topic=info_topic,
|
|
75
74
|
)
|
|
76
75
|
else:
|
|
77
|
-
logger.warning(
|
|
78
|
-
|
|
79
|
-
|
|
76
|
+
logger.warning(f"No RTC URL or info_key for '{self._name}' RTC mode.")
|
|
77
|
+
else:
|
|
78
|
+
logger.info(f"'{self._name}': Using standard subscriber.")
|
|
79
|
+
topic = subscriber_config.get("topic")
|
|
80
|
+
if topic:
|
|
81
|
+
self._subscriber = create_camera_subscriber(
|
|
82
|
+
topic=topic,
|
|
83
|
+
)
|
|
84
|
+
else:
|
|
85
|
+
logger.warning(
|
|
86
|
+
f"No 'topic' specified for '{self._name}' in non-RTC mode."
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
if self._subscriber is None:
|
|
90
|
+
logger.warning(f"Failed to create subscriber for '{self._name}'.")
|
|
80
91
|
|
|
81
|
-
|
|
82
|
-
|
|
92
|
+
def _determine_info_endpoint(self, subscriber_config: dict) -> Optional[str]:
|
|
93
|
+
"""Determine the info endpoint for querying camera metadata.
|
|
83
94
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
95
|
+
Args:
|
|
96
|
+
subscriber_config: Subscriber configuration dict
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
Info endpoint or None
|
|
100
|
+
"""
|
|
101
|
+
if self._configs.use_rtc:
|
|
102
|
+
return subscriber_config.get("info_key")
|
|
103
|
+
else:
|
|
104
|
+
topic = subscriber_config.get("topic")
|
|
105
|
+
return self._derive_info_endpoint_from_topic(topic) if topic else None
|
|
87
106
|
|
|
88
107
|
def shutdown(self) -> None:
|
|
89
108
|
"""Shutdown the camera sensor and release all resources."""
|
|
@@ -97,7 +116,8 @@ class RGBCameraSensor:
|
|
|
97
116
|
Returns:
|
|
98
117
|
True if the subscriber exists and is receiving data, False otherwise.
|
|
99
118
|
"""
|
|
100
|
-
|
|
119
|
+
data = self._subscriber.get_latest()
|
|
120
|
+
return data is not None
|
|
101
121
|
|
|
102
122
|
def wait_for_active(self, timeout: float = 5.0) -> bool:
|
|
103
123
|
"""Wait for the camera sensor to start receiving data.
|
|
@@ -111,7 +131,8 @@ class RGBCameraSensor:
|
|
|
111
131
|
if not self._subscriber:
|
|
112
132
|
logger.warning(f"'{self._name}': Cannot wait, no subscriber initialized.")
|
|
113
133
|
return False
|
|
114
|
-
|
|
134
|
+
msg = self._subscriber.wait_for_message(timeout)
|
|
135
|
+
return msg is not None
|
|
115
136
|
|
|
116
137
|
def get_obs(self) -> np.ndarray | None:
|
|
117
138
|
"""Get the latest observation (RGB image) from the sensor.
|
|
@@ -119,25 +140,8 @@ class RGBCameraSensor:
|
|
|
119
140
|
Returns:
|
|
120
141
|
The latest RGB image as a numpy array (HxWxC) if available, otherwise None.
|
|
121
142
|
"""
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
@property
|
|
125
|
-
def fps(self) -> float:
|
|
126
|
-
"""Get the current FPS measurement.
|
|
127
|
-
|
|
128
|
-
Returns:
|
|
129
|
-
Current frames per second measurement.
|
|
130
|
-
"""
|
|
131
|
-
return self._subscriber.fps if self._subscriber else 0.0
|
|
132
|
-
|
|
133
|
-
@property
|
|
134
|
-
def name(self) -> str:
|
|
135
|
-
"""Get the sensor name.
|
|
136
|
-
|
|
137
|
-
Returns:
|
|
138
|
-
Sensor name string.
|
|
139
|
-
"""
|
|
140
|
-
return self._name
|
|
143
|
+
data = self._subscriber.get_latest()
|
|
144
|
+
return data if data is not None else None
|
|
141
145
|
|
|
142
146
|
@property
|
|
143
147
|
def height(self) -> int:
|