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.

Files changed (59) hide show
  1. dexcontrol/__init__.py +18 -8
  2. dexcontrol/apps/dualsense_teleop_base.py +1 -1
  3. dexcontrol/comm/__init__.py +51 -0
  4. dexcontrol/comm/base.py +421 -0
  5. dexcontrol/comm/rtc.py +400 -0
  6. dexcontrol/comm/subscribers.py +329 -0
  7. dexcontrol/config/core/chassis.py +9 -4
  8. dexcontrol/config/core/hand.py +1 -0
  9. dexcontrol/config/sensors/cameras/__init__.py +1 -2
  10. dexcontrol/config/sensors/cameras/zed_camera.py +2 -2
  11. dexcontrol/config/sensors/vega_sensors.py +12 -18
  12. dexcontrol/config/vega.py +4 -1
  13. dexcontrol/core/arm.py +61 -37
  14. dexcontrol/core/chassis.py +141 -119
  15. dexcontrol/core/component.py +110 -59
  16. dexcontrol/core/hand.py +118 -85
  17. dexcontrol/core/head.py +18 -29
  18. dexcontrol/core/misc.py +327 -155
  19. dexcontrol/core/robot_query_interface.py +463 -0
  20. dexcontrol/core/torso.py +4 -8
  21. dexcontrol/proto/dexcontrol_msg_pb2.py +27 -39
  22. dexcontrol/proto/dexcontrol_msg_pb2.pyi +75 -118
  23. dexcontrol/proto/dexcontrol_query_pb2.py +39 -39
  24. dexcontrol/proto/dexcontrol_query_pb2.pyi +17 -4
  25. dexcontrol/robot.py +245 -574
  26. dexcontrol/sensors/__init__.py +1 -2
  27. dexcontrol/sensors/camera/__init__.py +0 -2
  28. dexcontrol/sensors/camera/base_camera.py +144 -0
  29. dexcontrol/sensors/camera/rgb_camera.py +67 -63
  30. dexcontrol/sensors/camera/zed_camera.py +89 -147
  31. dexcontrol/sensors/imu/chassis_imu.py +76 -56
  32. dexcontrol/sensors/imu/zed_imu.py +54 -43
  33. dexcontrol/sensors/lidar/rplidar.py +16 -20
  34. dexcontrol/sensors/manager.py +4 -11
  35. dexcontrol/sensors/ultrasonic.py +14 -27
  36. dexcontrol/utils/__init__.py +0 -11
  37. dexcontrol/utils/comm_helper.py +111 -0
  38. dexcontrol/utils/constants.py +1 -1
  39. dexcontrol/utils/os_utils.py +169 -1
  40. dexcontrol/utils/pb_utils.py +0 -22
  41. {dexcontrol-0.2.12.dist-info → dexcontrol-0.3.1.dist-info}/METADATA +13 -1
  42. dexcontrol-0.3.1.dist-info/RECORD +68 -0
  43. dexcontrol/config/sensors/cameras/luxonis_camera.py +0 -51
  44. dexcontrol/sensors/camera/luxonis_camera.py +0 -169
  45. dexcontrol/utils/rate_limiter.py +0 -172
  46. dexcontrol/utils/rtc_utils.py +0 -144
  47. dexcontrol/utils/subscribers/__init__.py +0 -52
  48. dexcontrol/utils/subscribers/base.py +0 -281
  49. dexcontrol/utils/subscribers/camera.py +0 -332
  50. dexcontrol/utils/subscribers/decoders.py +0 -88
  51. dexcontrol/utils/subscribers/generic.py +0 -110
  52. dexcontrol/utils/subscribers/imu.py +0 -175
  53. dexcontrol/utils/subscribers/lidar.py +0 -172
  54. dexcontrol/utils/subscribers/protobuf.py +0 -111
  55. dexcontrol/utils/subscribers/rtc.py +0 -316
  56. dexcontrol/utils/zenoh_utils.py +0 -122
  57. dexcontrol-0.2.12.dist-info/RECORD +0 -75
  58. {dexcontrol-0.2.12.dist-info → dexcontrol-0.3.1.dist-info}/WHEEL +0 -0
  59. {dexcontrol-0.2.12.dist-info → dexcontrol-0.3.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,332 +0,0 @@
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
- """Camera Zenoh subscribers for RGB and depth data.
12
-
13
- This module provides specialized subscribers for camera data including RGB images
14
- and depth images, using the serialization formats from dexsensor.
15
- """
16
-
17
- import numpy as np
18
- import zenoh
19
- from loguru import logger
20
-
21
- from .base import BaseZenohSubscriber, CustomDataHandler
22
-
23
- # Import camera serialization functions from dexsensor
24
- try:
25
- from dexsensor.serialization.camera import decode_depth, decode_image
26
- except ImportError:
27
- logger.error(
28
- "Failed to import dexsensor camera serialization functions. Please install dexsensor."
29
- )
30
- decode_image = None
31
- decode_depth = None
32
-
33
-
34
- class RGBCameraSubscriber(BaseZenohSubscriber):
35
- """Zenoh subscriber for RGB camera data.
36
-
37
- This subscriber handles RGB image data encoded using the dexsensor
38
- camera serialization format with JPEG compression.
39
- Uses lazy decoding - data is only decoded when requested.
40
- """
41
-
42
- def __init__(
43
- self,
44
- topic: str,
45
- zenoh_session: zenoh.Session,
46
- name: str = "rgb_camera_subscriber",
47
- enable_fps_tracking: bool = True,
48
- fps_log_interval: int = 30,
49
- custom_data_handler: CustomDataHandler | None = None,
50
- ) -> None:
51
- """Initialize the RGB camera subscriber.
52
-
53
- Args:
54
- topic: Zenoh topic to subscribe to for RGB data.
55
- zenoh_session: Active Zenoh session for communication.
56
- name: Name for logging purposes.
57
- enable_fps_tracking: Whether to track and log FPS metrics.
58
- fps_log_interval: Number of frames between FPS calculations.
59
- custom_data_handler: Optional custom function to handle incoming data.
60
- If provided, this will replace the default data
61
- handling logic entirely.
62
- """
63
- super().__init__(
64
- topic,
65
- zenoh_session,
66
- name,
67
- enable_fps_tracking,
68
- fps_log_interval,
69
- custom_data_handler,
70
- )
71
- self._latest_raw_data: bytes | None = None
72
-
73
- def _data_handler(self, sample: zenoh.Sample) -> None:
74
- """Handle incoming RGB image data.
75
-
76
- Args:
77
- sample: Zenoh sample containing encoded RGB image data.
78
- """
79
- with self._data_lock:
80
- self._latest_raw_data = sample.payload.to_bytes()
81
- self._active = True
82
-
83
- self._update_fps_metrics()
84
-
85
- def get_latest_data(self) -> np.ndarray | None:
86
- """Get the latest RGB image.
87
-
88
- Returns:
89
- Latest RGB image as numpy array (HxWxC) if available, None otherwise.
90
- """
91
- with self._data_lock:
92
- if self._latest_raw_data is None:
93
- return None
94
-
95
- if decode_image is None:
96
- logger.error(
97
- f"Cannot decode RGB image for {self._name}: dexsensor not available"
98
- )
99
- return None
100
-
101
- try:
102
- # Decode the image, which is typically in BGR format
103
- image = decode_image(self._latest_raw_data)
104
- return image
105
- except Exception as e:
106
- logger.error(f"Failed to decode RGB image for {self._name}: {e}")
107
- return None
108
-
109
- def get_latest_image(self) -> np.ndarray | None:
110
- """Get the latest RGB image.
111
-
112
- Alias for get_latest_data() for clarity.
113
-
114
- Returns:
115
- Latest RGB image as numpy array (HxWxC) if available, None otherwise.
116
- """
117
- return self.get_latest_data()
118
-
119
-
120
- class DepthCameraSubscriber(BaseZenohSubscriber):
121
- """Zenoh subscriber for depth camera data.
122
-
123
- This subscriber handles depth image data encoded using the dexsensor
124
- camera serialization format with compression.
125
- Uses lazy decoding - data is only decoded when requested.
126
- """
127
-
128
- def __init__(
129
- self,
130
- topic: str,
131
- zenoh_session: zenoh.Session,
132
- name: str = "depth_camera_subscriber",
133
- enable_fps_tracking: bool = True,
134
- fps_log_interval: int = 30,
135
- custom_data_handler: CustomDataHandler | None = None,
136
- ) -> None:
137
- """Initialize the depth camera subscriber.
138
-
139
- Args:
140
- topic: Zenoh topic to subscribe to for depth data.
141
- zenoh_session: Active Zenoh session for communication.
142
- name: Name for logging purposes.
143
- enable_fps_tracking: Whether to track and log FPS metrics.
144
- fps_log_interval: Number of frames between FPS calculations.
145
- custom_data_handler: Optional custom function to handle incoming data.
146
- If provided, this will replace the default data
147
- handling logic entirely.
148
- """
149
- super().__init__(
150
- topic,
151
- zenoh_session,
152
- name,
153
- enable_fps_tracking,
154
- fps_log_interval,
155
- custom_data_handler,
156
- )
157
- self._latest_raw_data: bytes | None = None
158
-
159
- def _data_handler(self, sample: zenoh.Sample) -> None:
160
- """Handle incoming depth image data.
161
-
162
- Args:
163
- sample: Zenoh sample containing encoded depth image data.
164
- """
165
- with self._data_lock:
166
- self._latest_raw_data = sample.payload.to_bytes()
167
- self._active = True
168
-
169
- self._update_fps_metrics()
170
-
171
- def get_latest_data(self) -> np.ndarray | None:
172
- """Get the latest depth data.
173
-
174
- Returns:
175
- depth_image if available, None otherwise. Depth values in meters as numpy array (HxW)
176
- """
177
- with self._data_lock:
178
- if self._latest_raw_data is None:
179
- return None
180
-
181
- if decode_depth is None:
182
- logger.error(
183
- f"Cannot decode depth image for {self._name}: dexsensor not available"
184
- )
185
- return None
186
-
187
- try:
188
- # Decode the depth image
189
- depth = decode_depth(self._latest_raw_data)
190
- return depth
191
- except Exception as e:
192
- logger.error(f"Failed to decode depth image for {self._name}: {e}")
193
- return None
194
-
195
-
196
- class RGBDCameraSubscriber(BaseZenohSubscriber):
197
- """Zenoh subscriber for RGBD camera data.
198
-
199
- This subscriber handles both RGB and depth data from an RGBD camera,
200
- subscribing to separate topics for RGB and depth streams.
201
- """
202
-
203
- def __init__(
204
- self,
205
- rgb_topic: str,
206
- depth_topic: str,
207
- zenoh_session: zenoh.Session,
208
- name: str = "rgbd_camera_subscriber",
209
- enable_fps_tracking: bool = True,
210
- fps_log_interval: int = 30,
211
- custom_data_handler: CustomDataHandler | None = None,
212
- ) -> None:
213
- """Initialize the RGBD camera subscriber.
214
-
215
- Args:
216
- rgb_topic: Zenoh topic for RGB data.
217
- depth_topic: Zenoh topic for depth data.
218
- zenoh_session: Active Zenoh session for communication.
219
- name: Name for logging purposes.
220
- enable_fps_tracking: Whether to track and log FPS metrics.
221
- fps_log_interval: Number of frames between FPS calculations.
222
- custom_data_handler: Optional custom function to handle incoming data.
223
- If provided, this will replace the default data
224
- handling logic entirely.
225
- """
226
- # Initialize with RGB topic as primary
227
- super().__init__(
228
- rgb_topic,
229
- zenoh_session,
230
- name,
231
- enable_fps_tracking,
232
- fps_log_interval,
233
- custom_data_handler,
234
- )
235
-
236
- # Create separate subscribers for RGB and depth
237
- self._rgb_subscriber = RGBCameraSubscriber(
238
- rgb_topic,
239
- zenoh_session,
240
- f"{name}_rgb",
241
- enable_fps_tracking,
242
- fps_log_interval,
243
- custom_data_handler,
244
- )
245
- self._depth_subscriber = DepthCameraSubscriber(
246
- depth_topic,
247
- zenoh_session,
248
- f"{name}_depth",
249
- enable_fps_tracking,
250
- fps_log_interval,
251
- custom_data_handler,
252
- )
253
-
254
- def _data_handler(self, sample: zenoh.Sample) -> None:
255
- """Handle incoming data - not used as we use separate subscribers."""
256
- pass
257
-
258
- def get_latest_data(self) -> tuple[np.ndarray, np.ndarray] | None:
259
- """Get the latest RGBD data.
260
-
261
- Returns:
262
- Tuple of (rgb_image, depth_image, depth_min, depth_max) if both available, None otherwise.
263
- rgb_image: RGB image as numpy array (HxWxC)
264
- depth_image: Depth values in meters as numpy array (HxW)
265
- """
266
- rgb_image = self._rgb_subscriber.get_latest_data()
267
- depth_image = self._depth_subscriber.get_latest_data()
268
-
269
- if rgb_image is not None and depth_image is not None:
270
- return rgb_image, depth_image
271
- return None
272
-
273
- def get_latest_rgb(self) -> np.ndarray | None:
274
- """Get the latest RGB image.
275
-
276
- Returns:
277
- Latest RGB image as numpy array (HxWxC) if available, None otherwise.
278
- """
279
- return self._rgb_subscriber.get_latest_image()
280
-
281
- def get_latest_depth(self) -> np.ndarray | None:
282
- """Get the latest depth image.
283
-
284
- Returns:
285
- Latest depth image as numpy array (HxW) with values in meters if available, None otherwise.
286
- """
287
- return self._depth_subscriber.get_latest_data()
288
-
289
- def wait_for_active(self, timeout: float = 5.0) -> bool:
290
- """Wait for both RGB and depth subscribers to start receiving data.
291
-
292
- Args:
293
- timeout: Maximum time to wait in seconds.
294
-
295
- Returns:
296
- True if both subscribers become active, False if timeout is reached.
297
- """
298
- rgb_active = self._rgb_subscriber.wait_for_active(timeout)
299
- depth_active = self._depth_subscriber.wait_for_active(timeout)
300
- return rgb_active and depth_active
301
-
302
- def is_active(self) -> bool:
303
- """Check if both RGB and depth subscribers are actively receiving data.
304
-
305
- Returns:
306
- True if both subscribers are active, False otherwise.
307
- """
308
- return self._rgb_subscriber.is_active() and self._depth_subscriber.is_active()
309
-
310
- def shutdown(self) -> None:
311
- """Stop both subscribers and release resources."""
312
- self._rgb_subscriber.shutdown()
313
- self._depth_subscriber.shutdown()
314
- super().shutdown()
315
-
316
- @property
317
- def rgb_fps(self) -> float:
318
- """Get the RGB stream FPS measurement.
319
-
320
- Returns:
321
- Current RGB frames per second measurement.
322
- """
323
- return self._rgb_subscriber.fps
324
-
325
- @property
326
- def depth_fps(self) -> float:
327
- """Get the depth stream FPS measurement.
328
-
329
- Returns:
330
- Current depth frames per second measurement.
331
- """
332
- return self._depth_subscriber.fps
@@ -1,88 +0,0 @@
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
- """Decoder functions for Zenoh subscribers.
12
-
13
- This module provides common decoder functions that can be used with the
14
- GenericZenohSubscriber to transform raw Zenoh bytes into specific data formats.
15
- """
16
-
17
- import json
18
- from collections.abc import Callable
19
- from typing import Any, Type, TypeVar
20
-
21
- import zenoh
22
- from google.protobuf.message import Message
23
-
24
- M = TypeVar("M", bound=Message)
25
-
26
- # Type alias for decoder functions
27
- DecoderFunction = Callable[[zenoh.ZBytes], Any]
28
-
29
-
30
- def protobuf_decoder(message_type: Type[M]) -> DecoderFunction:
31
- """Create a decoder function for a specific protobuf message type.
32
-
33
- Args:
34
- message_type: Protobuf message class to decode to.
35
-
36
- Returns:
37
- Decoder function that parses bytes into the specified protobuf message.
38
- """
39
-
40
- def decode(data: zenoh.ZBytes) -> M:
41
- message = message_type()
42
- message.ParseFromString(data.to_bytes())
43
- return message
44
-
45
- return decode
46
-
47
-
48
- def raw_bytes_decoder(data: zenoh.ZBytes) -> bytes:
49
- """Decoder that returns raw bytes.
50
-
51
- Args:
52
- data: Zenoh bytes data.
53
-
54
- Returns:
55
- Raw bytes data.
56
- """
57
- return data.to_bytes()
58
-
59
-
60
- def json_decoder(data: zenoh.ZBytes) -> Any:
61
- """Decoder that parses JSON from bytes.
62
-
63
- Args:
64
- data: Zenoh bytes data containing JSON.
65
-
66
- Returns:
67
- Parsed JSON data.
68
-
69
- Raises:
70
- json.JSONDecodeError: If the data is not valid JSON.
71
- """
72
- return json.loads(data.to_bytes().decode("utf-8"))
73
-
74
-
75
- def string_decoder(data: zenoh.ZBytes, encoding: str = "utf-8") -> str:
76
- """Decoder that converts bytes to string.
77
-
78
- Args:
79
- data: Zenoh bytes data.
80
- encoding: Text encoding to use for decoding.
81
-
82
- Returns:
83
- Decoded string.
84
-
85
- Raises:
86
- UnicodeDecodeError: If the data cannot be decoded with the specified encoding.
87
- """
88
- return data.to_bytes().decode(encoding)
@@ -1,110 +0,0 @@
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
- """Generic Zenoh subscriber with configurable decoder functions.
12
-
13
- This module provides a flexible subscriber that can handle any type of data
14
- by accepting decoder functions that transform raw Zenoh bytes into the desired format.
15
- """
16
-
17
- from typing import Any
18
-
19
- import zenoh
20
- from loguru import logger
21
-
22
- from .base import BaseZenohSubscriber, CustomDataHandler
23
- from .decoders import DecoderFunction
24
-
25
-
26
- class GenericZenohSubscriber(BaseZenohSubscriber):
27
- """Generic Zenoh subscriber with configurable decoder function.
28
-
29
- This subscriber can handle any type of data by accepting a decoder function
30
- that transforms the raw Zenoh bytes into the desired data format.
31
- Uses lazy decoding - data is only decoded when requested.
32
- """
33
-
34
- def __init__(
35
- self,
36
- topic: str,
37
- zenoh_session: zenoh.Session,
38
- decoder: DecoderFunction | None = None,
39
- name: str = "generic_subscriber",
40
- enable_fps_tracking: bool = False,
41
- fps_log_interval: int = 100,
42
- custom_data_handler: CustomDataHandler | None = None,
43
- ) -> None:
44
- """Initialize the generic Zenoh subscriber.
45
-
46
- Args:
47
- topic: Zenoh topic to subscribe to for data.
48
- zenoh_session: Active Zenoh session for communication.
49
- decoder: Optional function to decode raw bytes into desired format.
50
- If None, raw bytes are returned.
51
- name: Name for logging purposes.
52
- enable_fps_tracking: Whether to track and log FPS metrics.
53
- fps_log_interval: Number of frames between FPS calculations.
54
- custom_data_handler: Optional custom function to handle incoming data.
55
- If provided, this will replace the default data
56
- handling logic entirely.
57
- """
58
- super().__init__(
59
- topic,
60
- zenoh_session,
61
- name,
62
- enable_fps_tracking,
63
- fps_log_interval,
64
- custom_data_handler,
65
- )
66
- self._decoder = decoder
67
- self._latest_raw_data: zenoh.ZBytes = zenoh.ZBytes("")
68
-
69
- def _data_handler(self, sample: zenoh.Sample) -> None:
70
- """Handle incoming data.
71
-
72
- Args:
73
- sample: Zenoh sample containing data.
74
- """
75
- with self._data_lock:
76
- self._latest_raw_data = sample.payload
77
- self._active = True
78
-
79
- self._update_fps_metrics()
80
-
81
- def get_latest_data(self) -> Any | None:
82
- """Get the latest data.
83
-
84
- Returns:
85
- Latest decoded data if decoder is provided and decoding succeeded,
86
- otherwise raw bytes. None if no data received.
87
- """
88
- with self._data_lock:
89
- if not self._active:
90
- return None
91
-
92
- if self._decoder is not None:
93
- try:
94
- return self._decoder(self._latest_raw_data)
95
- except Exception as e:
96
- logger.error(f"Failed to decode data for {self._name}: {e}")
97
- return None
98
- else:
99
- return self._latest_raw_data.to_bytes()
100
-
101
- def get_latest_raw_data(self) -> bytes | None:
102
- """Get the latest raw data bytes.
103
-
104
- Returns:
105
- Latest raw data bytes if available, None otherwise.
106
- """
107
- with self._data_lock:
108
- if not self._active:
109
- return None
110
- return self._latest_raw_data.to_bytes()
@@ -1,175 +0,0 @@
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
- """IMU Zenoh subscriber for inertial measurement data.
12
-
13
- This module provides a specialized subscriber for IMU data,
14
- using the serialization format from dexsensor.
15
- """
16
-
17
- from typing import Any
18
-
19
- import numpy as np
20
- import zenoh
21
- from loguru import logger
22
-
23
- from .base import BaseZenohSubscriber, CustomDataHandler
24
-
25
- # Import IMU serialization functions from dexsensor
26
- try:
27
- from dexsensor.serialization.imu import decode_imu_data
28
- except ImportError:
29
- logger.error(
30
- "Failed to import dexsensor IMU serialization functions. Please install dexsensor."
31
- )
32
- decode_imu_data = None
33
-
34
-
35
- class IMUSubscriber(BaseZenohSubscriber):
36
- """Zenoh subscriber for IMU data.
37
-
38
- This subscriber handles IMU data encoded using the dexsensor
39
- IMU serialization format with compression.
40
- Uses lazy decoding - data is only decoded when requested.
41
- """
42
-
43
- def __init__(
44
- self,
45
- topic: str,
46
- zenoh_session: zenoh.Session,
47
- name: str = "imu_subscriber",
48
- enable_fps_tracking: bool = True,
49
- fps_log_interval: int = 50,
50
- custom_data_handler: CustomDataHandler | None = None,
51
- ) -> None:
52
- """Initialize the IMU subscriber.
53
-
54
- Args:
55
- topic: Zenoh topic to subscribe to for IMU data.
56
- zenoh_session: Active Zenoh session for communication.
57
- name: Name for logging purposes.
58
- enable_fps_tracking: Whether to track and log FPS metrics.
59
- fps_log_interval: Number of frames between FPS calculations.
60
- custom_data_handler: Optional custom function to handle incoming data.
61
- If provided, this will replace the default data
62
- handling logic entirely.
63
- """
64
- super().__init__(
65
- topic,
66
- zenoh_session,
67
- name,
68
- enable_fps_tracking,
69
- fps_log_interval,
70
- custom_data_handler,
71
- )
72
- self._latest_raw_data: bytes | None = None
73
-
74
- def _data_handler(self, sample: zenoh.Sample) -> None:
75
- """Handle incoming IMU data.
76
-
77
- Args:
78
- sample: Zenoh sample containing encoded IMU data.
79
- """
80
- with self._data_lock:
81
- self._latest_raw_data = sample.payload.to_bytes()
82
- self._active = True
83
-
84
- self._update_fps_metrics()
85
-
86
- def get_latest_data(self) -> dict[str, Any] | None:
87
- """Get the latest IMU data.
88
-
89
- Returns:
90
- Latest IMU data dictionary if available, None otherwise.
91
- Dictionary contains:
92
- - acceleration: Linear acceleration [x, y, z] in m/s²
93
- - angular_velocity: Angular velocity [x, y, z] in rad/s
94
- - orientation: Orientation quaternion [x, y, z, w]
95
- - magnetometer: Magnetic field [x, y, z] in µT (if available)
96
- - timestamp: Timestamp of the measurement
97
- """
98
- with self._data_lock:
99
- if self._latest_raw_data is None:
100
- return None
101
-
102
- if decode_imu_data is None:
103
- logger.error(
104
- f"Cannot decode IMU data for {self._name}: dexsensor not available"
105
- )
106
- return None
107
-
108
- try:
109
- # Decode the IMU data
110
- imu_data = decode_imu_data(self._latest_raw_data)
111
- # Return a copy to avoid external modifications
112
- return {
113
- key: value.copy() if isinstance(value, np.ndarray) else value
114
- for key, value in imu_data.items()
115
- }
116
- except Exception as e:
117
- logger.error(f"Failed to decode IMU data for {self._name}: {e}")
118
- return None
119
-
120
- def get_acceleration(self) -> np.ndarray | None:
121
- """Get the latest linear acceleration.
122
-
123
- Returns:
124
- Linear acceleration [x, y, z] in m/s² if available, None otherwise.
125
- """
126
- imu_data = self.get_latest_data()
127
- if imu_data is not None:
128
- return imu_data["acceleration"]
129
- return None
130
-
131
- def get_angular_velocity(self) -> np.ndarray | None:
132
- """Get the latest angular velocity.
133
-
134
- Returns:
135
- Angular velocity [x, y, z] in rad/s if available, None otherwise.
136
- """
137
- imu_data = self.get_latest_data()
138
- if imu_data is not None:
139
- return imu_data["angular_velocity"]
140
- return None
141
-
142
- def get_orientation(self) -> np.ndarray | None:
143
- """Get the latest orientation quaternion.
144
-
145
- Returns:
146
- Orientation quaternion [x, y, z, w] if available, None otherwise.
147
- """
148
- imu_data = self.get_latest_data()
149
- if imu_data is not None:
150
- return imu_data["orientation"]
151
- return None
152
-
153
- def get_magnetometer(self) -> np.ndarray | None:
154
- """Get the latest magnetometer reading.
155
-
156
- Returns:
157
- Magnetic field [x, y, z] in µT if available, None otherwise.
158
- """
159
- imu_data = self.get_latest_data()
160
- if imu_data is not None and "magnetometer" in imu_data:
161
- magnetometer = imu_data["magnetometer"]
162
- return magnetometer if magnetometer is not None else None
163
- return None
164
-
165
- def has_magnetometer(self) -> bool:
166
- """Check if the latest IMU data includes magnetometer information.
167
-
168
- Returns:
169
- True if magnetometer data is available, False otherwise.
170
- """
171
- imu_data = self.get_latest_data()
172
- if imu_data is not None:
173
- magnetometer = imu_data.get("magnetometer")
174
- return magnetometer is not None and len(magnetometer) > 0
175
- return False