dexcontrol 0.2.12__py3-none-any.whl → 0.3.4__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.
Files changed (60) hide show
  1. dexcontrol/__init__.py +17 -8
  2. dexcontrol/apps/dualsense_teleop_base.py +1 -1
  3. dexcontrol/comm/__init__.py +51 -0
  4. dexcontrol/comm/rtc.py +401 -0
  5. dexcontrol/comm/subscribers.py +329 -0
  6. dexcontrol/config/core/chassis.py +9 -4
  7. dexcontrol/config/core/hand.py +1 -0
  8. dexcontrol/config/sensors/cameras/__init__.py +1 -2
  9. dexcontrol/config/sensors/cameras/zed_camera.py +2 -2
  10. dexcontrol/config/sensors/vega_sensors.py +12 -18
  11. dexcontrol/config/vega.py +4 -1
  12. dexcontrol/core/arm.py +66 -42
  13. dexcontrol/core/chassis.py +142 -120
  14. dexcontrol/core/component.py +107 -58
  15. dexcontrol/core/hand.py +119 -86
  16. dexcontrol/core/head.py +22 -33
  17. dexcontrol/core/misc.py +331 -158
  18. dexcontrol/core/robot_query_interface.py +467 -0
  19. dexcontrol/core/torso.py +5 -9
  20. dexcontrol/robot.py +245 -574
  21. dexcontrol/sensors/__init__.py +1 -2
  22. dexcontrol/sensors/camera/__init__.py +0 -2
  23. dexcontrol/sensors/camera/base_camera.py +150 -0
  24. dexcontrol/sensors/camera/rgb_camera.py +68 -64
  25. dexcontrol/sensors/camera/zed_camera.py +140 -164
  26. dexcontrol/sensors/imu/chassis_imu.py +81 -62
  27. dexcontrol/sensors/imu/zed_imu.py +54 -43
  28. dexcontrol/sensors/lidar/rplidar.py +16 -20
  29. dexcontrol/sensors/manager.py +4 -14
  30. dexcontrol/sensors/ultrasonic.py +15 -28
  31. dexcontrol/utils/__init__.py +0 -11
  32. dexcontrol/utils/comm_helper.py +110 -0
  33. dexcontrol/utils/constants.py +1 -1
  34. dexcontrol/utils/error_code.py +2 -4
  35. dexcontrol/utils/os_utils.py +172 -4
  36. dexcontrol/utils/pb_utils.py +6 -28
  37. {dexcontrol-0.2.12.dist-info → dexcontrol-0.3.4.dist-info}/METADATA +16 -3
  38. dexcontrol-0.3.4.dist-info/RECORD +62 -0
  39. {dexcontrol-0.2.12.dist-info → dexcontrol-0.3.4.dist-info}/WHEEL +1 -1
  40. dexcontrol/config/sensors/cameras/luxonis_camera.py +0 -51
  41. dexcontrol/proto/dexcontrol_msg_pb2.py +0 -73
  42. dexcontrol/proto/dexcontrol_msg_pb2.pyi +0 -220
  43. dexcontrol/proto/dexcontrol_query_pb2.py +0 -77
  44. dexcontrol/proto/dexcontrol_query_pb2.pyi +0 -162
  45. dexcontrol/sensors/camera/luxonis_camera.py +0 -169
  46. dexcontrol/utils/motion_utils.py +0 -199
  47. dexcontrol/utils/rate_limiter.py +0 -172
  48. dexcontrol/utils/rtc_utils.py +0 -144
  49. dexcontrol/utils/subscribers/__init__.py +0 -52
  50. dexcontrol/utils/subscribers/base.py +0 -281
  51. dexcontrol/utils/subscribers/camera.py +0 -332
  52. dexcontrol/utils/subscribers/decoders.py +0 -88
  53. dexcontrol/utils/subscribers/generic.py +0 -110
  54. dexcontrol/utils/subscribers/imu.py +0 -175
  55. dexcontrol/utils/subscribers/lidar.py +0 -172
  56. dexcontrol/utils/subscribers/protobuf.py +0 -111
  57. dexcontrol/utils/subscribers/rtc.py +0 -316
  58. dexcontrol/utils/zenoh_utils.py +0 -122
  59. dexcontrol-0.2.12.dist-info/RECORD +0 -75
  60. {dexcontrol-0.2.12.dist-info → dexcontrol-0.3.4.dist-info}/licenses/LICENSE +0 -0
@@ -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
@@ -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()