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.

Files changed (51) hide show
  1. dexcontrol/__init__.py +16 -7
  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/sensors/cameras/__init__.py +1 -2
  8. dexcontrol/config/sensors/cameras/zed_camera.py +2 -2
  9. dexcontrol/config/sensors/vega_sensors.py +12 -18
  10. dexcontrol/core/arm.py +29 -25
  11. dexcontrol/core/chassis.py +3 -12
  12. dexcontrol/core/component.py +68 -43
  13. dexcontrol/core/hand.py +50 -52
  14. dexcontrol/core/head.py +14 -26
  15. dexcontrol/core/misc.py +188 -166
  16. dexcontrol/core/robot_query_interface.py +140 -117
  17. dexcontrol/core/torso.py +0 -4
  18. dexcontrol/robot.py +15 -37
  19. dexcontrol/sensors/__init__.py +1 -2
  20. dexcontrol/sensors/camera/__init__.py +0 -2
  21. dexcontrol/sensors/camera/base_camera.py +144 -0
  22. dexcontrol/sensors/camera/rgb_camera.py +67 -63
  23. dexcontrol/sensors/camera/zed_camera.py +89 -147
  24. dexcontrol/sensors/imu/chassis_imu.py +76 -56
  25. dexcontrol/sensors/imu/zed_imu.py +54 -43
  26. dexcontrol/sensors/lidar/rplidar.py +16 -20
  27. dexcontrol/sensors/manager.py +4 -11
  28. dexcontrol/sensors/ultrasonic.py +14 -27
  29. dexcontrol/utils/__init__.py +0 -11
  30. dexcontrol/utils/comm_helper.py +111 -0
  31. dexcontrol/utils/constants.py +1 -1
  32. dexcontrol/utils/os_utils.py +8 -22
  33. {dexcontrol-0.3.0.dist-info → dexcontrol-0.3.2.dist-info}/METADATA +2 -1
  34. dexcontrol-0.3.2.dist-info/RECORD +68 -0
  35. dexcontrol/config/sensors/cameras/luxonis_camera.py +0 -51
  36. dexcontrol/sensors/camera/luxonis_camera.py +0 -169
  37. dexcontrol/utils/rate_limiter.py +0 -172
  38. dexcontrol/utils/rtc_utils.py +0 -144
  39. dexcontrol/utils/subscribers/__init__.py +0 -52
  40. dexcontrol/utils/subscribers/base.py +0 -281
  41. dexcontrol/utils/subscribers/camera.py +0 -332
  42. dexcontrol/utils/subscribers/decoders.py +0 -88
  43. dexcontrol/utils/subscribers/generic.py +0 -110
  44. dexcontrol/utils/subscribers/imu.py +0 -175
  45. dexcontrol/utils/subscribers/lidar.py +0 -172
  46. dexcontrol/utils/subscribers/protobuf.py +0 -111
  47. dexcontrol/utils/subscribers/rtc.py +0 -316
  48. dexcontrol/utils/zenoh_utils.py +0 -369
  49. dexcontrol-0.3.0.dist-info/RECORD +0 -76
  50. {dexcontrol-0.3.0.dist-info → dexcontrol-0.3.2.dist-info}/WHEEL +0 -0
  51. {dexcontrol-0.3.0.dist-info → dexcontrol-0.3.2.dist-info}/licenses/LICENSE +0 -0
@@ -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
@@ -1,172 +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
- """LIDAR Zenoh subscriber for scan data.
12
-
13
- This module provides a specialized subscriber for LIDAR scan 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 lidar serialization functions from dexsensor
26
- try:
27
- from dexsensor.serialization.lidar import decode_scan_data
28
- except ImportError:
29
- logger.error(
30
- "Failed to import dexsensor lidar serialization functions. Please install dexsensor."
31
- )
32
- decode_scan_data = None
33
-
34
-
35
- class LidarSubscriber(BaseZenohSubscriber):
36
- """Zenoh subscriber for LIDAR scan data.
37
-
38
- This subscriber handles LIDAR scan data encoded using the dexsensor
39
- lidar serialization format. Uses lazy decoding - data is only decoded
40
- when requested.
41
- """
42
-
43
- def __init__(
44
- self,
45
- topic: str,
46
- zenoh_session: zenoh.Session,
47
- name: str = "lidar_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 LIDAR subscriber.
53
-
54
- Args:
55
- topic: Zenoh topic to subscribe to for LIDAR 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 LIDAR scan data.
76
-
77
- Args:
78
- sample: Zenoh sample containing encoded LIDAR scan 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 LIDAR scan data.
88
-
89
- Returns:
90
- Latest scan data dictionary if available, None otherwise.
91
- Dictionary contains:
92
- - ranges: Array of range measurements in meters
93
- - angles: Array of corresponding angles in radians
94
- - qualities: Array of quality values (0-255) if available, None otherwise
95
- - timestamp: Timestamp in nanoseconds (int)
96
- """
97
- with self._data_lock:
98
- if self._latest_raw_data is None:
99
- return None
100
-
101
- if decode_scan_data is None:
102
- logger.error(
103
- f"Cannot decode LIDAR scan for {self._name}: dexsensor not available"
104
- )
105
- return None
106
-
107
- try:
108
- # Decode the LIDAR scan data
109
- scan_data = decode_scan_data(self._latest_raw_data)
110
- # Return a copy to avoid external modifications
111
- return {
112
- key: value.copy() if isinstance(value, np.ndarray) else value
113
- for key, value in scan_data.items()
114
- }
115
- except Exception as e:
116
- logger.error(f"Failed to decode LIDAR scan for {self._name}: {e}")
117
- return None
118
-
119
- def get_latest_scan(self) -> dict[str, Any] | None:
120
- """Get the latest LIDAR scan data.
121
-
122
- Alias for get_latest_data() for clarity.
123
-
124
- Returns:
125
- Latest scan data dictionary if available, None otherwise.
126
- """
127
- return self.get_latest_data()
128
-
129
- def get_ranges(self) -> np.ndarray | None:
130
- """Get the latest range measurements.
131
-
132
- Returns:
133
- Array of range measurements in meters if available, None otherwise.
134
- """
135
- scan_data = self.get_latest_data()
136
- if scan_data is not None:
137
- return scan_data["ranges"]
138
- return None
139
-
140
- def get_angles(self) -> np.ndarray | None:
141
- """Get the latest angle measurements.
142
-
143
- Returns:
144
- Array of angle measurements in radians if available, None otherwise.
145
- """
146
- scan_data = self.get_latest_data()
147
- if scan_data is not None:
148
- return scan_data["angles"]
149
- return None
150
-
151
- def get_qualities(self) -> np.ndarray | None:
152
- """Get the latest quality measurements.
153
-
154
- Returns:
155
- Array of quality values (0-255) if available, None otherwise.
156
- """
157
- scan_data = self.get_latest_data()
158
- if scan_data is not None:
159
- return scan_data.get("qualities")
160
- return None
161
-
162
- def has_qualities(self) -> bool:
163
- """Check if the latest scan data includes quality information.
164
-
165
- Returns:
166
- True if quality data is available, False otherwise.
167
- """
168
- scan_data = self.get_latest_data()
169
- if scan_data is not None:
170
- qualities = scan_data.get("qualities")
171
- return qualities is not None and len(qualities) > 0
172
- return False
@@ -1,111 +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
- """Protobuf-specific Zenoh subscriber.
12
-
13
- This module provides a subscriber specifically designed for handling protobuf messages
14
- with automatic parsing and type safety. Uses lazy decoding for efficiency.
15
- """
16
-
17
- from typing import TypeVar, cast
18
-
19
- import zenoh
20
- from google.protobuf.message import Message
21
- from loguru import logger
22
-
23
- from .base import BaseZenohSubscriber, CustomDataHandler
24
-
25
- M = TypeVar("M", bound=Message)
26
-
27
-
28
- class ProtobufZenohSubscriber(BaseZenohSubscriber):
29
- """Zenoh subscriber specifically for protobuf messages.
30
-
31
- This subscriber automatically handles protobuf message parsing and provides
32
- type-safe access to the latest message data. Uses lazy decoding - protobuf
33
- messages are only parsed when requested.
34
- """
35
-
36
- def __init__(
37
- self,
38
- topic: str,
39
- zenoh_session: zenoh.Session,
40
- message_type: type[M],
41
- name: str = "protobuf_subscriber",
42
- enable_fps_tracking: bool = False,
43
- fps_log_interval: int = 100,
44
- custom_data_handler: CustomDataHandler | None = None,
45
- ) -> None:
46
- """Initialize the protobuf Zenoh subscriber.
47
-
48
- Args:
49
- topic: Zenoh topic to subscribe to for protobuf messages.
50
- zenoh_session: Active Zenoh session for communication.
51
- message_type: Protobuf message class to parse incoming data.
52
- name: Name for logging purposes.
53
- enable_fps_tracking: Whether to track and log FPS metrics.
54
- fps_log_interval: Number of frames between FPS calculations.
55
- custom_data_handler: Optional custom function to handle incoming data.
56
- If provided, this will replace the default data
57
- handling logic entirely.
58
- """
59
- super().__init__(
60
- topic,
61
- zenoh_session,
62
- name,
63
- enable_fps_tracking,
64
- fps_log_interval,
65
- custom_data_handler,
66
- )
67
- self._message_type = message_type
68
- self._latest_raw_data: zenoh.ZBytes = zenoh.ZBytes("")
69
-
70
- def _data_handler(self, sample: zenoh.Sample) -> None:
71
- """Handle incoming protobuf data.
72
-
73
- Args:
74
- sample: Zenoh sample containing protobuf data.
75
- """
76
- with self._data_lock:
77
- self._latest_raw_data = sample.payload
78
- self._active = True
79
-
80
- self._update_fps_metrics()
81
-
82
- def get_latest_data(self) -> M | None:
83
- """Get the latest protobuf message.
84
-
85
- Returns:
86
- Latest parsed protobuf message if available and parsing succeeded,
87
- None otherwise.
88
- """
89
- with self._data_lock:
90
- if not self._active:
91
- return None
92
-
93
- # Parse protobuf message on demand
94
- try:
95
- message = self._message_type()
96
- message.ParseFromString(self._latest_raw_data.to_bytes())
97
- return cast(M, message)
98
- except Exception as e:
99
- logger.error(f"Failed to parse protobuf message for {self._name}: {e}")
100
- return None
101
-
102
- def get_latest_raw_data(self) -> bytes | None:
103
- """Get the latest raw protobuf data bytes.
104
-
105
- Returns:
106
- Latest raw protobuf data bytes if available, None otherwise.
107
- """
108
- with self._data_lock:
109
- if not self._active:
110
- return None
111
- return self._latest_raw_data.to_bytes()