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,40 @@
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 implementations for dexcontrol.
7
+
8
+ This module provides sensor classes for various robotic sensors
9
+ using Zenoh subscribers for data communication.
10
+ """
11
+
12
+ # Import camera sensors
13
+ from .camera import GeminiCameraSensor, RGBCameraSensor
14
+
15
+ # Import IMU sensors
16
+ from .imu import GeminiIMUSensor, NineAxisIMUSensor
17
+
18
+ # Import other sensors
19
+ from .lidar import RPLidarSensor
20
+
21
+ # Import sensor manager
22
+ from .manager import Sensors
23
+ from .ultrasonic import UltrasonicSensor
24
+
25
+ __all__ = [
26
+ # Camera sensors
27
+ "RGBCameraSensor",
28
+ "GeminiCameraSensor",
29
+
30
+ # IMU sensors
31
+ "NineAxisIMUSensor",
32
+ "GeminiIMUSensor",
33
+
34
+ # Other sensors
35
+ "RPLidarSensor",
36
+ "UltrasonicSensor",
37
+
38
+ # Sensor manager
39
+ "Sensors",
40
+ ]
@@ -0,0 +1,18 @@
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
+ """Camera sensor implementations using Zenoh subscribers.
7
+
8
+ This module provides camera sensor classes that use the specialized camera
9
+ subscribers for RGB and RGBD Gemini camera data, matching the dexsensor structure.
10
+ """
11
+
12
+ from .gemini_camera import GeminiCameraSensor
13
+ from .rgb_camera import RGBCameraSensor
14
+
15
+ __all__ = [
16
+ "RGBCameraSensor",
17
+ "GeminiCameraSensor",
18
+ ]
@@ -0,0 +1,139 @@
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
+ """Gemini RGBD camera sensor implementation using Zenoh subscriber."""
7
+
8
+ import numpy as np
9
+ import zenoh
10
+
11
+ from dexcontrol.utils.subscribers.camera import RGBDCameraSubscriber
12
+
13
+
14
+ class GeminiCameraSensor:
15
+ """Gemini RGBD camera sensor using Zenoh subscriber.
16
+
17
+ This sensor provides both RGB and depth image data from an Orbbec Gemini camera
18
+ using the RGBDCameraSubscriber for efficient data handling with lazy decoding.
19
+ """
20
+
21
+ def __init__(
22
+ self,
23
+ configs,
24
+ zenoh_session: zenoh.Session,
25
+ ) -> None:
26
+ """Initialize the Gemini RGBD camera sensor.
27
+
28
+ Args:
29
+ rgb_topic: Zenoh topic for RGB data.
30
+ depth_topic: Zenoh topic for depth data.
31
+ zenoh_session: Active Zenoh session for communication.
32
+ name: Name for the sensor instance.
33
+ enable_fps_tracking: Whether to track and log FPS metrics.
34
+ fps_log_interval: Number of frames between FPS calculations.
35
+ """
36
+ self._name = configs.name
37
+
38
+ # Create the RGBD camera subscriber
39
+ self._subscriber = RGBDCameraSubscriber(
40
+ rgb_topic=configs.rgb_topic,
41
+ depth_topic=configs.depth_topic,
42
+ zenoh_session=zenoh_session,
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
+ def shutdown(self) -> None:
49
+ """Shutdown the camera sensor."""
50
+ self._subscriber.shutdown()
51
+
52
+ def is_active(self) -> bool:
53
+ """Check if the camera sensor is actively receiving data.
54
+
55
+ Returns:
56
+ True if both RGB and depth are receiving data, False otherwise.
57
+ """
58
+ return self._subscriber.is_active()
59
+
60
+ def wait_for_active(self, timeout: float = 5.0) -> bool:
61
+ """Wait for the camera sensor to start receiving data.
62
+
63
+ Args:
64
+ timeout: Maximum time to wait in seconds.
65
+
66
+ Returns:
67
+ True if both RGB and depth become active, False if timeout is reached.
68
+ """
69
+ return self._subscriber.wait_for_active(timeout)
70
+
71
+ def get_obs(self, obs_keys: list[str] | None = None) -> dict[str, np.ndarray] | None:
72
+ """Get the latest RGBD data.
73
+
74
+ Returns:
75
+ Tuple of (rgb, depth) if both available, None otherwise.
76
+ """
77
+ if obs_keys is None:
78
+ obs_keys = ["rgb", "depth"]
79
+ obs_out = {}
80
+ for key in obs_keys:
81
+ if key == "rgb":
82
+ obs_out[key] = self._subscriber.get_latest_rgb()
83
+ elif key == "depth":
84
+ obs_out[key] = self._subscriber.get_latest_depth()
85
+ else:
86
+ raise ValueError(f"Invalid observation key: {key}")
87
+ return obs_out
88
+
89
+ def get_rgb_image(self) -> np.ndarray | None:
90
+ """Get the latest RGB image.
91
+
92
+ Returns:
93
+ Latest RGB image as numpy array (HxWxC) if available, None otherwise.
94
+ """
95
+ return self._subscriber.get_latest_rgb()
96
+
97
+ def get_depth_image(self) -> np.ndarray | None:
98
+ """Get the latest depth image.
99
+
100
+ Returns:
101
+ Latest depth image as numpy array (HxW) with values in meters if available, None otherwise.
102
+ """
103
+ return self._subscriber.get_latest_depth()
104
+
105
+ @property
106
+ def rgb_fps(self) -> float:
107
+ """Get the RGB stream FPS measurement.
108
+
109
+ Returns:
110
+ Current RGB frames per second measurement.
111
+ """
112
+ return self._subscriber.rgb_fps
113
+
114
+ @property
115
+ def depth_fps(self) -> float:
116
+ """Get the depth stream FPS measurement.
117
+
118
+ Returns:
119
+ Current depth frames per second measurement.
120
+ """
121
+ return self._subscriber.depth_fps
122
+
123
+ @property
124
+ def fps(self) -> float:
125
+ """Get the combined FPS measurement (minimum of RGB and depth).
126
+
127
+ Returns:
128
+ Current frames per second measurement.
129
+ """
130
+ return min(self.rgb_fps, self.depth_fps)
131
+
132
+ @property
133
+ def name(self) -> str:
134
+ """Get the sensor name.
135
+
136
+ Returns:
137
+ Sensor name string.
138
+ """
139
+ return self._name
@@ -0,0 +1,98 @@
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
+ """RGB camera sensor implementation using Zenoh subscriber."""
7
+
8
+ import numpy as np
9
+ import zenoh
10
+
11
+ from dexcontrol.utils.subscribers.camera import RGBCameraSubscriber
12
+
13
+
14
+ class RGBCameraSensor:
15
+ """RGB camera sensor using Zenoh subscriber.
16
+
17
+ This sensor provides RGB image data from a camera using the
18
+ RGBCameraSubscriber for efficient data handling with lazy decoding.
19
+ """
20
+
21
+ def __init__(
22
+ self,
23
+ configs,
24
+ zenoh_session: zenoh.Session,
25
+ ) -> None:
26
+ """Initialize the RGB camera sensor.
27
+
28
+ Args:
29
+ configs: Configuration for the RGB camera sensor.
30
+ zenoh_session: Active Zenoh session for communication.
31
+ """
32
+ self._name = configs.name
33
+
34
+ # Create the RGB camera subscriber
35
+ self._subscriber = RGBCameraSubscriber(
36
+ topic=configs.topic,
37
+ zenoh_session=zenoh_session,
38
+ name=f"{self._name}_subscriber",
39
+ enable_fps_tracking=configs.enable_fps_tracking,
40
+ fps_log_interval=configs.fps_log_interval,
41
+ )
42
+
43
+ def shutdown(self) -> None:
44
+ """Shutdown the camera sensor."""
45
+ self._subscriber.shutdown()
46
+
47
+ def is_active(self) -> bool:
48
+ """Check if the camera sensor is actively receiving data.
49
+
50
+ Returns:
51
+ True if receiving data, False otherwise.
52
+ """
53
+ return self._subscriber.is_active()
54
+
55
+ def wait_for_active(self, timeout: float = 5.0) -> bool:
56
+ """Wait for the camera sensor to start receiving data.
57
+
58
+ Args:
59
+ timeout: Maximum time to wait in seconds.
60
+
61
+ Returns:
62
+ True if sensor becomes active, False if timeout is reached.
63
+ """
64
+ return self._subscriber.wait_for_active(timeout)
65
+
66
+ def get_obs(self) -> np.ndarray | None:
67
+ """Get the latest RGB image data.
68
+
69
+ Returns:
70
+ Latest RGB image as numpy array (HxWxC) if available, None otherwise.
71
+ """
72
+ return self._subscriber.get_latest_data()
73
+
74
+ def get_rgb_image(self) -> np.ndarray | None:
75
+ """Get the latest RGB image.
76
+
77
+ Returns:
78
+ Latest RGB image as numpy array (HxWxC) if available, None otherwise.
79
+ """
80
+ return self._subscriber.get_latest_image()
81
+
82
+ @property
83
+ def fps(self) -> float:
84
+ """Get the current FPS measurement.
85
+
86
+ Returns:
87
+ Current frames per second measurement.
88
+ """
89
+ return self._subscriber.fps
90
+
91
+ @property
92
+ def name(self) -> str:
93
+ """Get the sensor name.
94
+
95
+ Returns:
96
+ Sensor name string.
97
+ """
98
+ return self._name
@@ -0,0 +1,22 @@
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
+ """IMU sensors package for dexcontrol.
7
+
8
+ This package provides sensor classes for various IMU (Inertial Measurement Unit)
9
+ sensors using Zenoh subscribers for data communication.
10
+
11
+ Available sensors:
12
+ - NineAxisIMUSensor: Standard 9-axis IMU with accelerometer, gyroscope, and magnetometer
13
+ - GeminiIMUSensor: IMU specific to Gemini hardware (6-axis: accelerometer + gyroscope)
14
+ """
15
+
16
+ from .gemini_imu import GeminiIMUSensor
17
+ from .nine_axis_imu import NineAxisIMUSensor
18
+
19
+ __all__ = [
20
+ "NineAxisIMUSensor",
21
+ "GeminiIMUSensor",
22
+ ]
@@ -0,0 +1,139 @@
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
+ """Gemini IMU sensor implementation using Zenoh subscriber."""
7
+
8
+ import numpy as np
9
+ import zenoh
10
+
11
+ from dexcontrol.utils.subscribers.imu import IMUSubscriber
12
+
13
+
14
+ class GeminiIMUSensor:
15
+ """Gemini IMU sensor using Zenoh subscriber.
16
+
17
+ This sensor provides 6-axis IMU data (accelerometer + gyroscope) from Gemini hardware
18
+ using the IMUSubscriber for efficient data handling. Note: Gemini IMU does not include
19
+ magnetometer data.
20
+ """
21
+
22
+ def __init__(
23
+ self,
24
+ configs,
25
+ zenoh_session: zenoh.Session,
26
+ ) -> None:
27
+ """Initialize the Gemini IMU sensor.
28
+
29
+ Args:
30
+ topic: Zenoh topic to subscribe to for IMU data.
31
+ zenoh_session: Active Zenoh session for communication.
32
+ name: Name for the sensor instance.
33
+ enable_fps_tracking: Whether to track and log FPS metrics.
34
+ """
35
+ self._name = configs.name
36
+
37
+ # Create the IMU subscriber
38
+ self._subscriber = IMUSubscriber(
39
+ topic=configs.topic,
40
+ zenoh_session=zenoh_session,
41
+ name=f"{self._name}_subscriber",
42
+ enable_fps_tracking=configs.enable_fps_tracking,
43
+ fps_log_interval=configs.fps_log_interval,
44
+ )
45
+
46
+
47
+ def shutdown(self) -> None:
48
+ """Shutdown the IMU sensor."""
49
+ self._subscriber.shutdown()
50
+
51
+ def is_active(self) -> bool:
52
+ """Check if the IMU sensor is actively receiving data.
53
+
54
+ Returns:
55
+ True if receiving data, False otherwise.
56
+ """
57
+ return self._subscriber.is_active()
58
+
59
+ def wait_for_active(self, timeout: float = 5.0) -> bool:
60
+ """Wait for the IMU sensor to start receiving data.
61
+
62
+ Args:
63
+ timeout: Maximum time to wait in seconds.
64
+
65
+ Returns:
66
+ True if sensor becomes active, False if timeout is reached.
67
+ """
68
+ return self._subscriber.wait_for_active(timeout)
69
+
70
+ def get_obs(self, obs_keys: list[str] | None = None) -> dict[str, np.ndarray] | None:
71
+ """Get observation data for the Gemini IMU sensor.
72
+
73
+ Args:
74
+ obs_keys: List of observation keys to retrieve. If None, returns available data.
75
+ Valid keys: ['ang_vel', 'acc', 'quat'] (no magnetometer for Gemini)
76
+
77
+ Returns:
78
+ Dictionary with observation data including IMU measurements.
79
+ Note: Magnetometer data is not available for Gemini IMU.
80
+ """
81
+ if obs_keys is None:
82
+ obs_keys = ['ang_vel', 'acc', 'quat']
83
+
84
+ obs_out = {}
85
+ data = self._subscriber.get_latest_data()
86
+
87
+ for key in obs_keys:
88
+ if key == 'ang_vel':
89
+ obs_out[key] = data['ang_vel']
90
+ elif key == 'acc':
91
+ obs_out[key] = data['acc']
92
+ elif key == 'quat':
93
+ obs_out[key] = data['quat']
94
+ else:
95
+ raise ValueError(f"Invalid observation key: {key}")
96
+
97
+ return obs_out
98
+
99
+ def get_acceleration(self) -> np.ndarray | None:
100
+ """Get the latest linear acceleration.
101
+
102
+ Returns:
103
+ Linear acceleration [x, y, z] in m/s² if available, None otherwise.
104
+ """
105
+ return self._subscriber.get_acceleration()
106
+
107
+ def get_angular_velocity(self) -> np.ndarray | None:
108
+ """Get the latest angular velocity.
109
+
110
+ Returns:
111
+ Angular velocity [x, y, z] in rad/s if available, None otherwise.
112
+ """
113
+ return self._subscriber.get_angular_velocity()
114
+
115
+ def get_orientation(self) -> np.ndarray | None:
116
+ """Get the latest orientation quaternion.
117
+
118
+ Returns:
119
+ Orientation quaternion [x, y, z, w] if available, None otherwise.
120
+ """
121
+ return self._subscriber.get_orientation()
122
+
123
+ @property
124
+ def fps(self) -> float:
125
+ """Get the current FPS measurement.
126
+
127
+ Returns:
128
+ Current frames per second measurement.
129
+ """
130
+ return self._subscriber.fps
131
+
132
+ @property
133
+ def name(self) -> str:
134
+ """Get the IMU name.
135
+
136
+ Returns:
137
+ IMU name string.
138
+ """
139
+ return self._name
@@ -0,0 +1,149 @@
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
+ """Nine-Axis IMU sensor implementation using Zenoh subscriber."""
7
+
8
+ import numpy as np
9
+ import zenoh
10
+
11
+ from dexcontrol.utils.subscribers.imu import IMUSubscriber
12
+
13
+
14
+ class NineAxisIMUSensor:
15
+ """Nine-Axis IMU sensor using Zenoh subscriber.
16
+
17
+ This sensor provides 9-axis IMU data including acceleration, angular velocity,
18
+ orientation, and magnetometer data using the IMUSubscriber for efficient data handling.
19
+ """
20
+
21
+ def __init__(
22
+ self,
23
+ configs,
24
+ zenoh_session: zenoh.Session,
25
+ ) -> None:
26
+ """Initialize the Nine-Axis IMU sensor.
27
+
28
+ Args:
29
+ topic: Zenoh topic to subscribe to for IMU data.
30
+ zenoh_session: Active Zenoh session for communication.
31
+ name: Name for the sensor instance.
32
+ enable_fps_tracking: Whether to track and log FPS metrics.
33
+ """
34
+ self._name = configs.name
35
+
36
+ # Create the IMU subscriber
37
+ self._subscriber = IMUSubscriber(
38
+ topic=configs.topic,
39
+ zenoh_session=zenoh_session,
40
+ name=f"{self._name}_subscriber",
41
+ enable_fps_tracking=configs.enable_fps_tracking,
42
+ fps_log_interval=configs.fps_log_interval,
43
+ )
44
+
45
+
46
+ def shutdown(self) -> None:
47
+ """Shutdown the IMU sensor."""
48
+ self._subscriber.shutdown()
49
+
50
+ def is_active(self) -> bool:
51
+ """Check if the IMU sensor is actively receiving data.
52
+
53
+ Returns:
54
+ True if receiving data, False otherwise.
55
+ """
56
+ return self._subscriber.is_active()
57
+
58
+ def wait_for_active(self, timeout: float = 5.0) -> bool:
59
+ """Wait for the IMU sensor to start receiving data.
60
+
61
+ Args:
62
+ timeout: Maximum time to wait in seconds.
63
+
64
+ Returns:
65
+ True if sensor becomes active, False if timeout is reached.
66
+ """
67
+ return self._subscriber.wait_for_active(timeout)
68
+
69
+ def get_obs(self, obs_keys: list[str] | None = None) -> dict[str, np.ndarray] | None:
70
+ """Get observation data for the Nine-Axis IMU sensor.
71
+
72
+ Args:
73
+ obs_keys: List of observation keys to retrieve. If None, returns all available data.
74
+ Valid keys: ['ang_vel', 'acc', 'mag', 'quat']
75
+
76
+ Returns:
77
+ Dictionary with observation data including all IMU measurements.
78
+ """
79
+ if obs_keys is None:
80
+ obs_keys = ['ang_vel', 'acc', 'mag', 'quat']
81
+
82
+ data = self._subscriber.get_latest_data()
83
+ if data is None:
84
+ return None
85
+
86
+ obs_out = {}
87
+ for key in obs_keys:
88
+ if key == 'ang_vel':
89
+ obs_out[key] = data['angular_velocity']
90
+ elif key == 'acc':
91
+ obs_out[key] = data['acceleration']
92
+ elif key == 'mag':
93
+ obs_out[key] = data['magnetometer']
94
+ elif key == 'quat':
95
+ obs_out[key] = data['orientation']
96
+ else:
97
+ raise ValueError(f"Invalid observation key: {key}")
98
+
99
+ return obs_out
100
+
101
+ def get_acceleration(self) -> np.ndarray | None:
102
+ """Get the latest linear acceleration.
103
+
104
+ Returns:
105
+ Linear acceleration [x, y, z] in m/s² if available, None otherwise.
106
+ """
107
+ return self._subscriber.get_acceleration()
108
+
109
+ def get_angular_velocity(self) -> np.ndarray | None:
110
+ """Get the latest angular velocity.
111
+
112
+ Returns:
113
+ Angular velocity [x, y, z] in rad/s if available, None otherwise.
114
+ """
115
+ return self._subscriber.get_angular_velocity()
116
+
117
+ def get_orientation(self) -> np.ndarray | None:
118
+ """Get the latest orientation quaternion.
119
+
120
+ Returns:
121
+ Orientation quaternion [x, y, z, w] if available, None otherwise.
122
+ """
123
+ return self._subscriber.get_orientation()
124
+
125
+ def get_magnetometer(self) -> np.ndarray | None:
126
+ """Get the latest magnetometer reading.
127
+
128
+ Returns:
129
+ Magnetic field [x, y, z] in µT if available, None otherwise.
130
+ """
131
+ return self._subscriber.get_magnetometer()
132
+
133
+ @property
134
+ def fps(self) -> float:
135
+ """Get the current FPS measurement.
136
+
137
+ Returns:
138
+ Current frames per second measurement.
139
+ """
140
+ return self._subscriber.fps
141
+
142
+ @property
143
+ def name(self) -> str:
144
+ """Get the IMU name.
145
+
146
+ Returns:
147
+ IMU name string.
148
+ """
149
+ return self._name
@@ -0,0 +1,3 @@
1
+ from .rplidar import RPLidarSensor
2
+
3
+ __all__ = ['RPLidarSensor']