dexcontrol 0.3.0__py3-none-any.whl → 0.3.2__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 +16 -7
- 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/sensors/cameras/__init__.py +1 -2
- dexcontrol/config/sensors/cameras/zed_camera.py +2 -2
- dexcontrol/config/sensors/vega_sensors.py +12 -18
- dexcontrol/core/arm.py +29 -25
- dexcontrol/core/chassis.py +3 -12
- dexcontrol/core/component.py +68 -43
- dexcontrol/core/hand.py +50 -52
- dexcontrol/core/head.py +14 -26
- dexcontrol/core/misc.py +188 -166
- dexcontrol/core/robot_query_interface.py +140 -117
- dexcontrol/core/torso.py +0 -4
- dexcontrol/robot.py +15 -37
- 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 +8 -22
- {dexcontrol-0.3.0.dist-info → dexcontrol-0.3.2.dist-info}/METADATA +2 -1
- dexcontrol-0.3.2.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 -369
- dexcontrol-0.3.0.dist-info/RECORD +0 -76
- {dexcontrol-0.3.0.dist-info → dexcontrol-0.3.2.dist-info}/WHEEL +0 -0
- {dexcontrol-0.3.0.dist-info → dexcontrol-0.3.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -11,9 +11,8 @@
|
|
|
11
11
|
"""ZED IMU sensor implementation using Zenoh subscriber."""
|
|
12
12
|
|
|
13
13
|
import numpy as np
|
|
14
|
-
import zenoh
|
|
15
14
|
|
|
16
|
-
from dexcontrol.
|
|
15
|
+
from dexcontrol.comm import create_imu_subscriber
|
|
17
16
|
|
|
18
17
|
|
|
19
18
|
class ZedIMUSensor:
|
|
@@ -28,23 +27,17 @@ class ZedIMUSensor:
|
|
|
28
27
|
def __init__(
|
|
29
28
|
self,
|
|
30
29
|
configs,
|
|
31
|
-
zenoh_session: zenoh.Session,
|
|
32
30
|
) -> None:
|
|
33
31
|
"""Initialize the ZED IMU sensor.
|
|
34
32
|
|
|
35
33
|
Args:
|
|
36
34
|
configs: Configuration object containing topic, name, and other settings.
|
|
37
|
-
zenoh_session: Active Zenoh session for communication.
|
|
38
35
|
"""
|
|
39
36
|
self._name = configs.name
|
|
40
37
|
|
|
41
|
-
|
|
42
|
-
self._subscriber =
|
|
38
|
+
|
|
39
|
+
self._subscriber = create_imu_subscriber(
|
|
43
40
|
topic=configs.topic,
|
|
44
|
-
zenoh_session=zenoh_session,
|
|
45
|
-
name=f"{self._name}_subscriber",
|
|
46
|
-
enable_fps_tracking=configs.enable_fps_tracking,
|
|
47
|
-
fps_log_interval=configs.fps_log_interval,
|
|
48
41
|
)
|
|
49
42
|
|
|
50
43
|
def shutdown(self) -> None:
|
|
@@ -57,7 +50,8 @@ class ZedIMUSensor:
|
|
|
57
50
|
Returns:
|
|
58
51
|
True if receiving data, False otherwise.
|
|
59
52
|
"""
|
|
60
|
-
|
|
53
|
+
data = self._subscriber.get_latest()
|
|
54
|
+
return data is not None
|
|
61
55
|
|
|
62
56
|
def wait_for_active(self, timeout: float = 5.0) -> bool:
|
|
63
57
|
"""Wait for the ZED IMU sensor to start receiving data.
|
|
@@ -68,92 +62,109 @@ class ZedIMUSensor:
|
|
|
68
62
|
Returns:
|
|
69
63
|
True if sensor becomes active, False if timeout is reached.
|
|
70
64
|
"""
|
|
71
|
-
|
|
65
|
+
msg = self._subscriber.wait_for_message(timeout)
|
|
66
|
+
return msg is not None
|
|
72
67
|
|
|
73
68
|
def get_obs(self, obs_keys: list[str] | None = None) -> dict[str, np.ndarray] | None:
|
|
74
69
|
"""Get observation data for the ZED IMU sensor.
|
|
75
70
|
|
|
76
71
|
Args:
|
|
77
72
|
obs_keys: List of observation keys to retrieve. If None, returns all available data.
|
|
78
|
-
Valid keys: ['ang_vel', 'acc', 'quat', 'timestamp']
|
|
73
|
+
Valid keys: ['ang_vel', 'acc', 'quat', 'mag', 'timestamp']
|
|
79
74
|
|
|
80
75
|
Returns:
|
|
81
76
|
Dictionary with observation data including all IMU measurements.
|
|
82
77
|
Keys are mapped as follows:
|
|
83
|
-
- 'ang_vel': Angular velocity from '
|
|
84
|
-
- 'acc': Linear acceleration from '
|
|
85
|
-
- 'quat': Orientation quaternion from '
|
|
78
|
+
- 'ang_vel': Angular velocity from 'gyro'
|
|
79
|
+
- 'acc': Linear acceleration from 'acc'
|
|
80
|
+
- 'quat': Orientation quaternion from 'quat'
|
|
81
|
+
- 'mag': Magnetometer from 'mag' (if available)
|
|
86
82
|
- 'timestamp_ns': Timestamp from 'timestamp'
|
|
87
83
|
"""
|
|
88
84
|
if obs_keys is None:
|
|
89
85
|
obs_keys = ['ang_vel', 'acc', 'quat']
|
|
90
86
|
|
|
91
|
-
data = self._subscriber.
|
|
87
|
+
data = self._subscriber.get_latest()
|
|
92
88
|
if data is None:
|
|
93
89
|
return None
|
|
94
90
|
|
|
95
|
-
obs_out = {
|
|
91
|
+
obs_out = {}
|
|
92
|
+
|
|
93
|
+
# Add timestamp if available
|
|
94
|
+
if 'timestamp' in data:
|
|
95
|
+
obs_out['timestamp_ns'] = data['timestamp']
|
|
96
96
|
|
|
97
97
|
for key in obs_keys:
|
|
98
98
|
if key == 'ang_vel':
|
|
99
|
-
obs_out[key] = data
|
|
99
|
+
obs_out[key] = data.get('gyro', np.zeros(3))
|
|
100
100
|
elif key == 'acc':
|
|
101
|
-
obs_out[key] = data
|
|
101
|
+
obs_out[key] = data.get('acc', np.zeros(3))
|
|
102
102
|
elif key == 'quat':
|
|
103
|
-
obs_out[key] = data
|
|
104
|
-
|
|
105
|
-
|
|
103
|
+
obs_out[key] = data.get('quat', np.array([1.0, 0.0, 0.0, 0.0]))
|
|
104
|
+
elif key == 'mag' and 'mag' in data:
|
|
105
|
+
obs_out[key] = data['mag']
|
|
106
|
+
elif key == 'timestamp' and 'timestamp' in data:
|
|
107
|
+
obs_out['timestamp_ns'] = data['timestamp']
|
|
106
108
|
|
|
107
109
|
return obs_out
|
|
108
110
|
|
|
109
|
-
def
|
|
111
|
+
def get_acc(self) -> np.ndarray | None:
|
|
110
112
|
"""Get the latest linear acceleration from ZED IMU.
|
|
111
113
|
|
|
112
114
|
Returns:
|
|
113
115
|
Linear acceleration [x, y, z] in m/s² if available, None otherwise.
|
|
114
116
|
"""
|
|
115
|
-
|
|
117
|
+
data = self._subscriber.get_latest()
|
|
118
|
+
return data.get('acc') if data else None
|
|
116
119
|
|
|
117
|
-
def
|
|
120
|
+
def get_gyro(self) -> np.ndarray | None:
|
|
118
121
|
"""Get the latest angular velocity from ZED IMU.
|
|
119
122
|
|
|
120
123
|
Returns:
|
|
121
124
|
Angular velocity [x, y, z] in rad/s if available, None otherwise.
|
|
122
125
|
"""
|
|
123
|
-
|
|
126
|
+
data = self._subscriber.get_latest()
|
|
127
|
+
return data.get('gyro') if data else None
|
|
124
128
|
|
|
125
|
-
def
|
|
129
|
+
def get_quat(self) -> np.ndarray | None:
|
|
126
130
|
"""Get the latest orientation quaternion from ZED IMU.
|
|
127
131
|
|
|
128
132
|
Returns:
|
|
129
|
-
Orientation quaternion [x, y, z
|
|
133
|
+
Orientation quaternion [w, x, y, z] if available, None otherwise.
|
|
134
|
+
Note: dexcomm uses [w, x, y, z] quaternion format.
|
|
130
135
|
"""
|
|
131
|
-
|
|
136
|
+
data = self._subscriber.get_latest()
|
|
137
|
+
return data.get('quat') if data else None
|
|
132
138
|
|
|
133
|
-
def
|
|
139
|
+
def get_mag(self) -> np.ndarray | None:
|
|
134
140
|
"""Get the latest magnetometer reading from ZED IMU.
|
|
135
141
|
|
|
136
142
|
Returns:
|
|
137
143
|
Magnetic field [x, y, z] in µT if available, None otherwise.
|
|
138
144
|
"""
|
|
139
|
-
|
|
145
|
+
data = self._subscriber.get_latest()
|
|
146
|
+
if not data or not isinstance(data, dict):
|
|
147
|
+
return None
|
|
148
|
+
return data.get('mag', None)
|
|
140
149
|
|
|
141
|
-
def
|
|
150
|
+
def has_mag(self) -> bool:
|
|
142
151
|
"""Check if the ZED IMU has magnetometer data available.
|
|
143
152
|
|
|
144
153
|
Returns:
|
|
145
154
|
True if magnetometer data is available, False otherwise.
|
|
146
155
|
"""
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
156
|
+
data = self._subscriber.get_latest()
|
|
157
|
+
if not data or not isinstance(data, dict):
|
|
158
|
+
return False
|
|
159
|
+
return 'mag' in data and data['mag'] is not None
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
# Backward compatibility aliases
|
|
163
|
+
get_acceleration = get_acc
|
|
164
|
+
get_angular_velocity = get_gyro
|
|
165
|
+
get_orientation = get_quat
|
|
166
|
+
get_magnetometer = get_mag
|
|
167
|
+
has_magnetometer = has_mag
|
|
157
168
|
|
|
158
169
|
@property
|
|
159
170
|
def name(self) -> str:
|
|
@@ -17,9 +17,8 @@ subscriber for scan data.
|
|
|
17
17
|
from typing import Any
|
|
18
18
|
|
|
19
19
|
import numpy as np
|
|
20
|
-
import zenoh
|
|
21
20
|
|
|
22
|
-
from dexcontrol.
|
|
21
|
+
from dexcontrol.comm import create_lidar_subscriber
|
|
23
22
|
|
|
24
23
|
|
|
25
24
|
class RPLidarSensor:
|
|
@@ -32,26 +31,17 @@ class RPLidarSensor:
|
|
|
32
31
|
def __init__(
|
|
33
32
|
self,
|
|
34
33
|
configs,
|
|
35
|
-
zenoh_session: zenoh.Session,
|
|
36
34
|
) -> None:
|
|
37
35
|
"""Initialize the LIDAR sensor.
|
|
38
36
|
|
|
39
37
|
Args:
|
|
40
|
-
|
|
41
|
-
zenoh_session: Active Zenoh session for communication.
|
|
42
|
-
name: Name for the sensor instance.
|
|
43
|
-
enable_fps_tracking: Whether to track and log FPS metrics.
|
|
44
|
-
fps_log_interval: Number of frames between FPS calculations.
|
|
38
|
+
configs: Configuration for the LIDAR sensor.
|
|
45
39
|
"""
|
|
46
40
|
self._name = configs.name
|
|
47
41
|
|
|
48
42
|
# Create the LIDAR subscriber
|
|
49
|
-
self._subscriber =
|
|
50
|
-
topic=configs.topic
|
|
51
|
-
zenoh_session=zenoh_session,
|
|
52
|
-
name=f"{self._name}_subscriber",
|
|
53
|
-
enable_fps_tracking=configs.enable_fps_tracking,
|
|
54
|
-
fps_log_interval=configs.fps_log_interval,
|
|
43
|
+
self._subscriber = create_lidar_subscriber(
|
|
44
|
+
topic=configs.topic
|
|
55
45
|
)
|
|
56
46
|
|
|
57
47
|
|
|
@@ -65,7 +55,8 @@ class RPLidarSensor:
|
|
|
65
55
|
Returns:
|
|
66
56
|
True if receiving data, False otherwise.
|
|
67
57
|
"""
|
|
68
|
-
|
|
58
|
+
data = self._subscriber.get_latest()
|
|
59
|
+
return data is not None
|
|
69
60
|
|
|
70
61
|
def wait_for_active(self, timeout: float = 5.0) -> bool:
|
|
71
62
|
"""Wait for the LIDAR sensor to start receiving data.
|
|
@@ -76,7 +67,8 @@ class RPLidarSensor:
|
|
|
76
67
|
Returns:
|
|
77
68
|
True if sensor becomes active, False if timeout is reached.
|
|
78
69
|
"""
|
|
79
|
-
|
|
70
|
+
msg = self._subscriber.wait_for_message(timeout)
|
|
71
|
+
return msg is not None
|
|
80
72
|
|
|
81
73
|
def get_obs(self) -> dict[str, Any] | None:
|
|
82
74
|
"""Get the latest LIDAR scan data.
|
|
@@ -89,7 +81,8 @@ class RPLidarSensor:
|
|
|
89
81
|
- qualities: Array of quality values (0-255) if available, None otherwise
|
|
90
82
|
- timestamp: Timestamp in nanoseconds (int)
|
|
91
83
|
"""
|
|
92
|
-
|
|
84
|
+
data = self._subscriber.get_latest()
|
|
85
|
+
return data
|
|
93
86
|
|
|
94
87
|
def get_ranges(self) -> np.ndarray | None:
|
|
95
88
|
"""Get the latest range measurements.
|
|
@@ -97,7 +90,8 @@ class RPLidarSensor:
|
|
|
97
90
|
Returns:
|
|
98
91
|
Array of range measurements in meters if available, None otherwise.
|
|
99
92
|
"""
|
|
100
|
-
|
|
93
|
+
data = self._subscriber.get_latest()
|
|
94
|
+
return data.ranges if data else None
|
|
101
95
|
|
|
102
96
|
def get_angles(self) -> np.ndarray | None:
|
|
103
97
|
"""Get the latest angle measurements.
|
|
@@ -105,7 +99,8 @@ class RPLidarSensor:
|
|
|
105
99
|
Returns:
|
|
106
100
|
Array of angle measurements in radians if available, None otherwise.
|
|
107
101
|
"""
|
|
108
|
-
|
|
102
|
+
data = self._subscriber.get_latest()
|
|
103
|
+
return data.angles if data else None
|
|
109
104
|
|
|
110
105
|
def get_qualities(self) -> np.ndarray | None:
|
|
111
106
|
"""Get the latest quality measurements.
|
|
@@ -113,7 +108,8 @@ class RPLidarSensor:
|
|
|
113
108
|
Returns:
|
|
114
109
|
Array of quality values (0-255) if available, None otherwise.
|
|
115
110
|
"""
|
|
116
|
-
|
|
111
|
+
data = self._subscriber.get_latest()
|
|
112
|
+
return data.qualities if data else None
|
|
117
113
|
|
|
118
114
|
def get_point_count(self) -> int:
|
|
119
115
|
"""Get the number of points in the latest scan.
|
dexcontrol/sensors/manager.py
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
# 2. Commercial License
|
|
9
9
|
# For commercial licensing terms, contact: contact@dexmate.ai
|
|
10
10
|
|
|
11
|
-
"""Sensor manager for managing robot sensors
|
|
11
|
+
"""Sensor manager for managing robot sensors.
|
|
12
12
|
|
|
13
13
|
This module provides the Sensors class that manages multiple sensor instances
|
|
14
14
|
based on configuration and provides unified access to them.
|
|
@@ -19,7 +19,6 @@ import traceback
|
|
|
19
19
|
from typing import TYPE_CHECKING, Any
|
|
20
20
|
|
|
21
21
|
import hydra.utils as hydra_utils
|
|
22
|
-
import zenoh
|
|
23
22
|
from loguru import logger
|
|
24
23
|
from omegaconf import DictConfig, OmegaConf
|
|
25
24
|
|
|
@@ -44,7 +43,6 @@ class Sensors:
|
|
|
44
43
|
|
|
45
44
|
Attributes:
|
|
46
45
|
_sensors: List of instantiated sensor objects.
|
|
47
|
-
_zenoh_session: Active Zenoh session for communication.
|
|
48
46
|
"""
|
|
49
47
|
|
|
50
48
|
if TYPE_CHECKING:
|
|
@@ -61,16 +59,13 @@ class Sensors:
|
|
|
61
59
|
lidar: RPLidarSensor
|
|
62
60
|
ultrasonic: UltrasonicSensor
|
|
63
61
|
|
|
64
|
-
def __init__(self, configs: DictConfig | VegaSensorsConfig
|
|
65
|
-
zenoh_session: zenoh.Session) -> None:
|
|
62
|
+
def __init__(self, configs: DictConfig | VegaSensorsConfig) -> None:
|
|
66
63
|
"""Initialize sensors from configuration.
|
|
67
64
|
|
|
68
65
|
Args:
|
|
69
66
|
configs: Configuration for all sensors (VegaSensorsConfig or DictConfig).
|
|
70
|
-
zenoh_session: Active Zenoh session for communication.
|
|
71
67
|
"""
|
|
72
68
|
self._sensors: list[Any] = []
|
|
73
|
-
self._zenoh_session = zenoh_session
|
|
74
69
|
|
|
75
70
|
dict_configs = (OmegaConf.structured(configs) if isinstance(configs, VegaSensorsConfig)
|
|
76
71
|
else configs)
|
|
@@ -108,10 +103,8 @@ class Sensors:
|
|
|
108
103
|
'configs': {k: v for k, v in config.items() if k != '_target_'}
|
|
109
104
|
})
|
|
110
105
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
zenoh_session=self._zenoh_session,
|
|
114
|
-
)
|
|
106
|
+
# Note: zenoh_session no longer needed as DexComm handles sessions
|
|
107
|
+
sensor = hydra_utils.instantiate(temp_config)
|
|
115
108
|
|
|
116
109
|
if hasattr(sensor, "start"):
|
|
117
110
|
sensor.start()
|
dexcontrol/sensors/ultrasonic.py
CHANGED
|
@@ -8,21 +8,21 @@
|
|
|
8
8
|
# 2. Commercial License
|
|
9
9
|
# For commercial licensing terms, contact: contact@dexmate.ai
|
|
10
10
|
|
|
11
|
-
"""Ultrasonic sensor implementations using
|
|
11
|
+
"""Ultrasonic sensor implementations using DexComm subscribers.
|
|
12
12
|
|
|
13
|
-
This module provides ultrasonic sensor classes that use
|
|
14
|
-
|
|
13
|
+
This module provides ultrasonic sensor classes that use DexComm's
|
|
14
|
+
Raw API for distance measurements.
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
17
|
import numpy as np
|
|
18
|
-
import
|
|
18
|
+
from dexcomm.serialization import deserialize_protobuf
|
|
19
19
|
|
|
20
|
+
from dexcontrol.comm import create_subscriber
|
|
20
21
|
from dexcontrol.proto import dexcontrol_msg_pb2
|
|
21
|
-
from dexcontrol.utils.subscribers import ProtobufZenohSubscriber
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
class UltrasonicSensor:
|
|
25
|
-
"""Ultrasonic sensor using
|
|
25
|
+
"""Ultrasonic sensor using DexComm subscriber.
|
|
26
26
|
|
|
27
27
|
This sensor provides distance measurements from ultrasonic sensors
|
|
28
28
|
"""
|
|
@@ -30,24 +30,18 @@ class UltrasonicSensor:
|
|
|
30
30
|
def __init__(
|
|
31
31
|
self,
|
|
32
32
|
configs,
|
|
33
|
-
zenoh_session: zenoh.Session,
|
|
34
33
|
) -> None:
|
|
35
34
|
"""Initialize the ultrasonic sensor.
|
|
36
35
|
|
|
37
36
|
Args:
|
|
38
37
|
configs: Configuration for the ultrasonic sensor.
|
|
39
|
-
zenoh_session: Active Zenoh session for communication.
|
|
40
38
|
"""
|
|
41
39
|
self._name = configs.name
|
|
42
40
|
|
|
43
|
-
# Create the
|
|
44
|
-
self._subscriber =
|
|
41
|
+
# Create the protobuf subscriber using our clean DexComm integration
|
|
42
|
+
self._subscriber = create_subscriber(
|
|
45
43
|
topic=configs.topic,
|
|
46
|
-
|
|
47
|
-
message_type=dexcontrol_msg_pb2.UltrasonicState,
|
|
48
|
-
name=f"{self._name}_subscriber",
|
|
49
|
-
enable_fps_tracking=configs.enable_fps_tracking,
|
|
50
|
-
fps_log_interval=configs.fps_log_interval,
|
|
44
|
+
deserializer=lambda data: deserialize_protobuf(data, dexcontrol_msg_pb2.UltrasonicState),
|
|
51
45
|
)
|
|
52
46
|
|
|
53
47
|
|
|
@@ -61,7 +55,8 @@ class UltrasonicSensor:
|
|
|
61
55
|
Returns:
|
|
62
56
|
True if receiving data, False otherwise.
|
|
63
57
|
"""
|
|
64
|
-
|
|
58
|
+
data = self._subscriber.get_latest()
|
|
59
|
+
return data is not None
|
|
65
60
|
|
|
66
61
|
def wait_for_active(self, timeout: float = 5.0) -> bool:
|
|
67
62
|
"""Wait for the ultrasonic sensor to start receiving data.
|
|
@@ -72,7 +67,8 @@ class UltrasonicSensor:
|
|
|
72
67
|
Returns:
|
|
73
68
|
True if sensor becomes active, False if timeout is reached.
|
|
74
69
|
"""
|
|
75
|
-
|
|
70
|
+
msg = self._subscriber.wait_for_message(timeout)
|
|
71
|
+
return msg is not None
|
|
76
72
|
|
|
77
73
|
def get_obs(self) -> np.ndarray | None:
|
|
78
74
|
"""Get observation data for the ultrasonic sensor.
|
|
@@ -84,7 +80,7 @@ class UltrasonicSensor:
|
|
|
84
80
|
Numpy array of distances in meters with shape (4,) in the order:
|
|
85
81
|
[front_left, front_right, back_left, back_right].
|
|
86
82
|
"""
|
|
87
|
-
data = self._subscriber.
|
|
83
|
+
data = self._subscriber.get_latest()
|
|
88
84
|
if data is not None:
|
|
89
85
|
obs = [
|
|
90
86
|
data.front_left,
|
|
@@ -96,15 +92,6 @@ class UltrasonicSensor:
|
|
|
96
92
|
|
|
97
93
|
return None
|
|
98
94
|
|
|
99
|
-
@property
|
|
100
|
-
def fps(self) -> float:
|
|
101
|
-
"""Get the current FPS measurement.
|
|
102
|
-
|
|
103
|
-
Returns:
|
|
104
|
-
Current frames per second measurement.
|
|
105
|
-
"""
|
|
106
|
-
return self._subscriber.fps
|
|
107
|
-
|
|
108
95
|
@property
|
|
109
96
|
def name(self) -> str:
|
|
110
97
|
"""Get the sensor name.
|
dexcontrol/utils/__init__.py
CHANGED
|
@@ -7,14 +7,3 @@
|
|
|
7
7
|
#
|
|
8
8
|
# 2. Commercial License
|
|
9
9
|
# For commercial licensing terms, contact: contact@dexmate.ai
|
|
10
|
-
|
|
11
|
-
from .subscribers import (
|
|
12
|
-
BaseZenohSubscriber,
|
|
13
|
-
DecoderFunction,
|
|
14
|
-
GenericZenohSubscriber,
|
|
15
|
-
ProtobufZenohSubscriber,
|
|
16
|
-
json_decoder,
|
|
17
|
-
protobuf_decoder,
|
|
18
|
-
raw_bytes_decoder,
|
|
19
|
-
string_decoder,
|
|
20
|
-
)
|
|
@@ -0,0 +1,111 @@
|
|
|
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
|
+
"""Communication helper utilities for DexControl using DexComm.
|
|
12
|
+
|
|
13
|
+
This module provides simple helper functions for DexControl's communication
|
|
14
|
+
needs using the DexComm library's Raw API.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import time
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import Optional
|
|
21
|
+
|
|
22
|
+
from dexcomm import ZenohConfig, call_service
|
|
23
|
+
from dexcomm.serialization import deserialize_json
|
|
24
|
+
from loguru import logger
|
|
25
|
+
|
|
26
|
+
import dexcontrol
|
|
27
|
+
from dexcontrol.utils.os_utils import resolve_key_name
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def get_robot_config() -> ZenohConfig:
|
|
31
|
+
"""Get DexComm configuration for robot communication.
|
|
32
|
+
|
|
33
|
+
This checks for the robot's Zenoh configuration file and creates
|
|
34
|
+
appropriate DexComm config.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
ZenohConfig instance configured for the robot.
|
|
38
|
+
"""
|
|
39
|
+
config_path = dexcontrol.COMM_CFG_PATH
|
|
40
|
+
|
|
41
|
+
if config_path and config_path != Path("/tmp/no_config") and config_path.exists():
|
|
42
|
+
logger.debug(f"Loading config from: {config_path}")
|
|
43
|
+
return ZenohConfig.from_file(config_path)
|
|
44
|
+
else:
|
|
45
|
+
logger.debug("No config file found, using default peer mode")
|
|
46
|
+
return ZenohConfig.default_peer()
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def get_zenoh_config_path() -> Optional[Path]:
|
|
50
|
+
"""Get robot config only if not using default SessionManager.
|
|
51
|
+
|
|
52
|
+
DexComm's SessionManager will automatically use the config from
|
|
53
|
+
environment variables if available, so we only need to provide
|
|
54
|
+
config explicitly if we have a specific robot config file.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
path to the config file
|
|
58
|
+
"""
|
|
59
|
+
config_path = dexcontrol.COMM_CFG_PATH
|
|
60
|
+
return config_path
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def query_json_service(
|
|
64
|
+
topic: str,
|
|
65
|
+
timeout: float = 2.0,
|
|
66
|
+
max_retries: int = 1,
|
|
67
|
+
retry_delay: float = 0.5,
|
|
68
|
+
) -> dict | None:
|
|
69
|
+
"""Query for JSON information using DexComm with retry logic.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
topic: Topic to query (will be resolved with robot namespace).
|
|
73
|
+
timeout: Maximum time to wait for a response in seconds.
|
|
74
|
+
max_retries: Maximum number of retry attempts.
|
|
75
|
+
retry_delay: Initial delay between retries (doubles each retry).
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
Dictionary containing the parsed JSON response if successful, None otherwise.
|
|
79
|
+
"""
|
|
80
|
+
resolved_topic = resolve_key_name(topic)
|
|
81
|
+
logger.debug(f"Querying topic: {resolved_topic}")
|
|
82
|
+
|
|
83
|
+
current_delay = retry_delay
|
|
84
|
+
for attempt in range(max_retries + 1):
|
|
85
|
+
try:
|
|
86
|
+
data = call_service(
|
|
87
|
+
resolved_topic,
|
|
88
|
+
timeout=timeout,
|
|
89
|
+
config=get_zenoh_config_path(),
|
|
90
|
+
request_serializer=None,
|
|
91
|
+
response_deserializer=deserialize_json,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
if data:
|
|
95
|
+
logger.debug(f"Successfully received JSON data from {resolved_topic}")
|
|
96
|
+
return data
|
|
97
|
+
|
|
98
|
+
except json.JSONDecodeError as e:
|
|
99
|
+
logger.warning(f"Failed to parse JSON response from {resolved_topic}: {e}")
|
|
100
|
+
except Exception as e:
|
|
101
|
+
logger.warning(
|
|
102
|
+
f"Query failed for {resolved_topic} (attempt {attempt + 1}/{max_retries + 1}): {e}"
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
if attempt < max_retries:
|
|
106
|
+
logger.debug(f"Retrying in {current_delay:.1f} seconds...")
|
|
107
|
+
time.sleep(current_delay)
|
|
108
|
+
current_delay *= 2 # Exponential backoff
|
|
109
|
+
|
|
110
|
+
logger.error(f"Failed to query {resolved_topic} after {max_retries + 1} attempts")
|
|
111
|
+
return None
|
dexcontrol/utils/constants.py
CHANGED
|
@@ -16,7 +16,7 @@ from typing import Final
|
|
|
16
16
|
ROBOT_NAME_ENV_VAR: Final[str] = "ROBOT_NAME"
|
|
17
17
|
|
|
18
18
|
# Environment variable for communication config path
|
|
19
|
-
COMM_CFG_PATH_ENV_VAR: Final[str] = "
|
|
19
|
+
COMM_CFG_PATH_ENV_VAR: Final[str] = "ZENOH_CONFIG"
|
|
20
20
|
|
|
21
21
|
# Environment variable to disable heartbeat monitoring
|
|
22
22
|
DISABLE_HEARTBEAT_ENV_VAR: Final[str] = "DEXCONTROL_DISABLE_HEARTBEAT"
|
dexcontrol/utils/os_utils.py
CHANGED
|
@@ -137,28 +137,14 @@ def validate_server_version(version_info: dict[str, Any]) -> None:
|
|
|
137
137
|
return
|
|
138
138
|
|
|
139
139
|
# Check each component's software version
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
if software_version_int < dexcontrol.MIN_SOC_SOFTWARE_VERSION:
|
|
149
|
-
components_below_min.append(
|
|
150
|
-
(component_name, software_version_int)
|
|
151
|
-
)
|
|
152
|
-
except (ValueError, TypeError) as e:
|
|
153
|
-
logger.debug(
|
|
154
|
-
f"Could not parse software version for {component_name}: {e}"
|
|
155
|
-
)
|
|
156
|
-
|
|
157
|
-
# If any components are below minimum version, show warning
|
|
158
|
-
if components_below_min:
|
|
159
|
-
show_server_version_warning(
|
|
160
|
-
components_below_min, dexcontrol.MIN_SOC_SOFTWARE_VERSION
|
|
161
|
-
)
|
|
140
|
+
soc_info = server_info.get("soc", {})
|
|
141
|
+
software_version = soc_info.get("software_version", {})
|
|
142
|
+
if software_version is not None:
|
|
143
|
+
software_version_int = int(software_version)
|
|
144
|
+
if software_version_int < dexcontrol.MIN_SOC_SOFTWARE_VERSION:
|
|
145
|
+
show_server_version_warning(
|
|
146
|
+
[("soc", software_version_int)], dexcontrol.MIN_SOC_SOFTWARE_VERSION
|
|
147
|
+
)
|
|
162
148
|
|
|
163
149
|
|
|
164
150
|
def show_server_version_warning(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dexcontrol
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: A Python library of Sensing and Control for Dexmate's Robot
|
|
5
5
|
Project-URL: Repository, https://github.com/dexmate-ai/dexcontrol
|
|
6
6
|
Author-email: Dexmate <contact@dexmate.ai>
|
|
@@ -202,6 +202,7 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
202
202
|
Classifier: Typing :: Typed
|
|
203
203
|
Requires-Python: <3.14,>=3.10
|
|
204
204
|
Requires-Dist: aiortc
|
|
205
|
+
Requires-Dist: dexcomm>=0.1.2
|
|
205
206
|
Requires-Dist: eclipse-zenoh>=1.2.0
|
|
206
207
|
Requires-Dist: hydra-core==1.3.2
|
|
207
208
|
Requires-Dist: jaxtyping>=0.2.38
|