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,316 +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
- import asyncio
12
- import json
13
- import threading
14
- import time
15
-
16
- import numpy as np
17
- import websockets
18
- from aiortc import RTCPeerConnection, RTCSessionDescription
19
- from loguru import logger
20
-
21
- # Try to import uvloop for better performance
22
- try:
23
- import uvloop # type: ignore[import-untyped]
24
-
25
- UVLOOP_AVAILABLE = True
26
- except ImportError:
27
- uvloop = None # type: ignore[assignment]
28
- UVLOOP_AVAILABLE = False
29
-
30
-
31
- class RTCSubscriber:
32
- """
33
- Subscriber for receiving video data via RTC.
34
-
35
- This class connects to a RTC peer through a signaling server,
36
- receives a video stream, and makes the latest frame available.
37
- """
38
-
39
- def __init__(
40
- self,
41
- url: str,
42
- name: str = "rtc_subscriber",
43
- enable_fps_tracking: bool = True,
44
- fps_log_interval: int = 100,
45
- ):
46
- """
47
- Initialize the RTC subscriber.
48
-
49
- Args:
50
- url: WebSocket URL of the signaling server.
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
- """
55
- self._url = url
56
- self._name = name
57
- self._pc = RTCPeerConnection()
58
- self._latest_frame: np.ndarray | None = None
59
- self._active = False
60
- self._data_lock = threading.Lock()
61
- self._stop_event = (
62
- threading.Event()
63
- ) # Use threading.Event for cross-thread communication
64
- self._async_stop_event = None # Will be created in the async context
65
- self._websocket = None # Store websocket reference for clean shutdown
66
-
67
- # FPS tracking
68
- self._enable_fps_tracking = enable_fps_tracking
69
- self._fps_log_interval = fps_log_interval
70
- self._frame_count = 0
71
- self._fps = 0.0
72
- self._last_fps_time = time.time()
73
-
74
- self._thread = threading.Thread(target=self._run_event_loop, daemon=True)
75
- self._thread.start()
76
-
77
- def _run_event_loop(self):
78
- """Run the asyncio event loop in a separate thread."""
79
- try:
80
- # Use uvloop if available for better performance
81
- if UVLOOP_AVAILABLE and uvloop is not None:
82
- # Create a new uvloop event loop for this thread
83
- loop = uvloop.new_event_loop()
84
- asyncio.set_event_loop(loop)
85
- logger.debug(f"Using uvloop for {self._name}")
86
-
87
- try:
88
- loop.run_until_complete(self._run())
89
- finally:
90
- loop.close()
91
- else:
92
- # Use default asyncio event loop
93
- asyncio.run(self._run())
94
-
95
- except Exception as e:
96
- logger.error(f"Event loop error for {self._name}: {e}")
97
- finally:
98
- with self._data_lock:
99
- self._active = False
100
-
101
- async def _run(self):
102
- """
103
- Connects to a RTC peer, receives video, and saves frames to disk.
104
- """
105
- # Create async stop event in the async context
106
- self._async_stop_event = asyncio.Event()
107
-
108
- # Start a task to monitor the threading stop event
109
- monitor_task = asyncio.create_task(self._monitor_stop_event())
110
-
111
- @self._pc.on("track")
112
- async def on_track(track):
113
- if track.kind == "video":
114
- while (
115
- self._async_stop_event is not None
116
- and not self._async_stop_event.is_set()
117
- ):
118
- try:
119
- frame = await asyncio.wait_for(
120
- track.recv(), timeout=1.0
121
- ) # Reduced timeout for faster shutdown response
122
- img = frame.to_ndarray(format="rgb24")
123
- with self._data_lock:
124
- self._latest_frame = img
125
- if not self._active:
126
- self._active = True
127
- self._update_fps_metrics()
128
- except asyncio.TimeoutError:
129
- # Check if we should stop before logging error
130
- if (
131
- self._async_stop_event is not None
132
- and not self._async_stop_event.is_set()
133
- ):
134
- logger.warning(
135
- f"Timeout: No frame received in 1 second from {self._url}"
136
- )
137
- continue
138
- except Exception as e:
139
- if (
140
- self._async_stop_event is not None
141
- and not self._async_stop_event.is_set()
142
- ):
143
- logger.error(f"Error receiving frame from {self._url}: {e}")
144
- break
145
-
146
- @self._pc.on("connectionstatechange")
147
- async def on_connectionstatechange():
148
- if self._pc.connectionState == "failed":
149
- logger.warning(f"RTC connection failed for {self._url}")
150
- await self._pc.close()
151
- if self._async_stop_event is not None:
152
- self._async_stop_event.set()
153
-
154
- try:
155
- async with websockets.connect(self._url) as websocket:
156
- self._websocket = websocket
157
-
158
- # Create an offer. The server's assertive codec control makes
159
- # client-side preferences redundant and potentially conflicting.
160
- self._pc.addTransceiver("video", direction="recvonly")
161
- offer = await self._pc.createOffer()
162
- await self._pc.setLocalDescription(offer)
163
-
164
- # Send the offer to the server
165
- await websocket.send(
166
- json.dumps(
167
- {
168
- "sdp": self._pc.localDescription.sdp,
169
- "type": self._pc.localDescription.type,
170
- }
171
- )
172
- )
173
-
174
- # Wait for the answer
175
- response = json.loads(await websocket.recv())
176
- if response["type"] == "answer":
177
- await self._pc.setRemoteDescription(
178
- RTCSessionDescription(
179
- sdp=response["sdp"], type=response["type"]
180
- )
181
- )
182
- else:
183
- logger.error(
184
- f"Received unexpected message type: {response['type']} from {self._url}"
185
- )
186
- if self._async_stop_event is not None:
187
- self._async_stop_event.set()
188
-
189
- # Wait until the stop event is set
190
- if self._async_stop_event is not None:
191
- await self._async_stop_event.wait()
192
-
193
- except websockets.exceptions.ConnectionClosed:
194
- logger.info(f"WebSocket connection closed for {self._url}")
195
- except Exception as e:
196
- if not self._async_stop_event.is_set():
197
- logger.error(f"Operation failed for {self._url}: {e}")
198
- finally:
199
- # Cancel the monitor task
200
- monitor_task.cancel()
201
- try:
202
- await monitor_task
203
- except asyncio.CancelledError:
204
- pass
205
-
206
- # Close websocket if still open
207
- if self._websocket:
208
- try:
209
- await self._websocket.close()
210
- except Exception as e:
211
- logger.debug(f"Error closing websocket for {self._url}: {e}")
212
-
213
- # Close peer connection if not already closed
214
- if self._pc.connectionState != "closed":
215
- try:
216
- await self._pc.close()
217
- except Exception as e:
218
- logger.debug(f"Error closing peer connection for {self._url}: {e}")
219
-
220
- with self._data_lock:
221
- self._active = False
222
-
223
- async def _monitor_stop_event(self):
224
- """Monitor the threading stop event and set the async stop event when needed."""
225
- while not self._stop_event.is_set():
226
- await asyncio.sleep(0.1) # Check every 100ms
227
- if self._async_stop_event is not None:
228
- self._async_stop_event.set()
229
-
230
- def _update_fps_metrics(self) -> None:
231
- """Update FPS tracking metrics.
232
-
233
- Increments frame counter and recalculates FPS at specified intervals.
234
- Only has an effect if fps_tracking was enabled during initialization.
235
- """
236
- if not self._enable_fps_tracking:
237
- return
238
-
239
- self._frame_count += 1
240
- if self._frame_count >= self._fps_log_interval:
241
- current_time = time.time()
242
- elapsed = current_time - self._last_fps_time
243
- self._fps = self._frame_count / elapsed
244
- logger.info(f"{self._name} frequency: {self._fps:.2f} Hz")
245
- self._frame_count = 0
246
- self._last_fps_time = current_time
247
-
248
- def get_latest_data(self) -> np.ndarray | None:
249
- """
250
- Get the latest video frame.
251
-
252
- Returns:
253
- Latest video frame as a numpy array (HxWxC RGB) if available, None otherwise.
254
- """
255
- with self._data_lock:
256
- return self._latest_frame.copy() if self._latest_frame is not None else None
257
-
258
- def is_active(self) -> bool:
259
- """Check if the subscriber is actively receiving data."""
260
- with self._data_lock:
261
- return self._active
262
-
263
- def wait_for_active(self, timeout: float = 5.0) -> bool:
264
- """
265
- Wait for the subscriber to start receiving data.
266
-
267
- Args:
268
- timeout: Maximum time to wait in seconds.
269
-
270
- Returns:
271
- True if subscriber becomes active, False if timeout is reached.
272
- """
273
- start_time = time.time()
274
- while not self.is_active():
275
- if time.time() - start_time > timeout:
276
- logger.error(
277
- f"No data received from {self._name} at {self._url} after {timeout}s"
278
- )
279
- return False
280
- time.sleep(0.1)
281
- return True
282
-
283
- def shutdown(self):
284
- """Stop the subscriber and release resources."""
285
-
286
- # Signal the async loop to stop
287
- self._stop_event.set()
288
-
289
- # Wait for the thread to finish with a reasonable timeout
290
- if self._thread.is_alive():
291
- self._thread.join(
292
- timeout=10.0
293
- ) # Increased timeout for more graceful shutdown
294
-
295
- if self._thread.is_alive():
296
- logger.warning(
297
- f"{self._name} thread did not shut down gracefully within timeout."
298
- )
299
-
300
- # Ensure active state is set to False
301
- with self._data_lock:
302
- self._active = False
303
-
304
- @property
305
- def name(self) -> str:
306
- """Get the subscriber name."""
307
- return self._name
308
-
309
- @property
310
- def fps(self) -> float:
311
- """Get the current FPS measurement.
312
-
313
- Returns:
314
- Current frames per second measurement.
315
- """
316
- return self._fps
@@ -1,122 +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
- """Zenoh utilities for dexcontrol.
12
-
13
- This module provides general utility functions for working with Zenoh
14
- communication framework.
15
- """
16
-
17
- import json
18
- import time
19
-
20
- import numpy as np
21
- import zenoh
22
- from loguru import logger
23
-
24
- from dexcontrol.utils.os_utils import resolve_key_name
25
-
26
-
27
- def query_zenoh_json(
28
- zenoh_session: zenoh.Session,
29
- topic: str,
30
- timeout: float = 2.0,
31
- max_retries: int = 1,
32
- retry_delay: float = 0.5,
33
- ) -> dict | None:
34
- """Query Zenoh for JSON information with retry logic.
35
-
36
- Args:
37
- zenoh_session: Active Zenoh session for communication.
38
- topic: Zenoh topic to query.
39
- timeout: Maximum time to wait for a response in seconds.
40
- max_retries: Maximum number of retry attempts.
41
- retry_delay: Initial delay between retries (doubles each retry).
42
-
43
- Returns:
44
- Dictionary containing the parsed JSON response if successful, None otherwise.
45
- """
46
- resolved_topic = resolve_key_name(topic)
47
- logger.debug(f"Querying Zenoh topic: {resolved_topic}")
48
-
49
- for attempt in range(max_retries + 1):
50
- try:
51
- # Add delay before retry (except first attempt)
52
- if attempt > 0:
53
- delay = retry_delay * (2 ** (attempt - 1)) # Exponential backoff
54
- logger.debug(f"Retry {attempt}/{max_retries} after {delay}s delay...")
55
- time.sleep(delay)
56
-
57
- # Try to get the info
58
- for reply in zenoh_session.get(resolved_topic, timeout=timeout):
59
- if reply.ok:
60
- response = json.loads(reply.ok.payload.to_bytes())
61
- return response
62
- else:
63
- # No valid reply received
64
- if attempt < max_retries:
65
- logger.debug(f"No reply on attempt {attempt + 1}, will retry...")
66
- else:
67
- logger.error(
68
- f"No valid reply received on topic '{resolved_topic}' after {max_retries + 1} attempts."
69
- )
70
-
71
- except StopIteration:
72
- if attempt < max_retries:
73
- logger.debug(f"Query timed out on attempt {attempt + 1}, will retry...")
74
- else:
75
- logger.error(f"Query timed out after {max_retries + 1} attempts.")
76
- except Exception as e:
77
- if attempt < max_retries:
78
- logger.debug(
79
- f"Query failed on attempt {attempt + 1}: {e}, will retry..."
80
- )
81
- else:
82
- logger.error(f"Query failed after {max_retries + 1} attempts: {e}")
83
-
84
- return None
85
-
86
-
87
- def compute_ntp_stats(offsets: list[float], rtts: list[float]) -> dict[str, float]:
88
- """Compute NTP statistics, removing outliers based on RTT median and std.
89
-
90
- Args:
91
- offsets: List of offset values (seconds).
92
- rtts: List of round-trip time values (seconds).
93
-
94
- Returns:
95
- Dictionary with computed statistics (mean, std, min, max, sample_count) for offset and rtt.
96
- """
97
- offsets_np = np.array(offsets)
98
- rtts_np = np.array(rtts)
99
- if len(rtts_np) < 3:
100
- mask = np.ones_like(rtts_np, dtype=bool)
101
- else:
102
- median = np.median(rtts_np)
103
- std = np.std(rtts_np)
104
- mask = np.abs(rtts_np - median) <= 2 * std
105
- offsets_filtered = offsets_np[mask]
106
- rtts_filtered = rtts_np[mask]
107
-
108
- def safe_stat(arr, func):
109
- return float(func(arr)) if len(arr) > 0 else 0.0
110
-
111
- stats = {
112
- "offset (mean)": safe_stat(offsets_filtered, np.mean),
113
- "offset (std)": safe_stat(offsets_filtered, np.std),
114
- "offset (min)": safe_stat(offsets_filtered, np.min),
115
- "offset (max)": safe_stat(offsets_filtered, np.max),
116
- "round_trip_time (mean)": safe_stat(rtts_filtered, np.mean),
117
- "round_trip_time (std)": safe_stat(rtts_filtered, np.std),
118
- "round_trip_time (min)": safe_stat(rtts_filtered, np.min),
119
- "round_trip_time (max)": safe_stat(rtts_filtered, np.max),
120
- "sample_count": int(len(offsets_filtered)),
121
- }
122
- return stats
@@ -1,75 +0,0 @@
1
- dexcontrol/__init__.py,sha256=9y7PGcWv9KYcLg7nH54FQv2vv0Q_GeVy3wcFa4Yf_gs,1692
2
- dexcontrol/robot.py,sha256=Ab-9HhfPiqKq4gGNoBUUMKqYsjPK4u9KW2ykPlbKe4A,51914
3
- dexcontrol/apps/dualsense_teleop_base.py,sha256=Dw1z-2HA5D7DPKutZxlOdXsN9vpk4gS6XzJsL5ZQLM0,12702
4
- dexcontrol/config/__init__.py,sha256=UVLNpzGD14e8g68rUZFXTh0B7FRx6uS0Eg_MecjinYM,520
5
- dexcontrol/config/vega.py,sha256=UE5XAdk5IpYj-LgLUzbAvr6Zi83e4XYPAvXdgM9z3VY,8239
6
- dexcontrol/config/core/__init__.py,sha256=Ym2R1hr1iMKQuXcg16BpZfQtTb0hQ5Q7smUIMlwKfho,637
7
- dexcontrol/config/core/arm.py,sha256=5hN1dQMe2H6oufaqgtZqx9vuB969DxM26leJqPsKEiA,1471
8
- dexcontrol/config/core/chassis.py,sha256=163hO4lVVaW7r9dvNleH0cdDds_GfO15EepnjloeT9U,868
9
- dexcontrol/config/core/hand.py,sha256=bGRO-_usaFP0GU6jgB6Lm3Z07DMqMVO_tiB5j4c8Cu4,954
10
- dexcontrol/config/core/head.py,sha256=SLwZE-lYEOk8XAmW-Ex7VkLF2w5HLItwsA3Dc7n5FtE,1061
11
- dexcontrol/config/core/misc.py,sha256=zHkJ144b6kbmMFE63wy_fgfo_6V-4XmM19hr6BUtQ0Y,1567
12
- dexcontrol/config/core/torso.py,sha256=DCTFgN1_Gn4THkKy23sIHOedACQtQ7cET3g4AmkVPco,1460
13
- dexcontrol/config/sensors/__init__.py,sha256=bYPMLxbbn5QeuPyA6OPGDS2JTYpnVvaZJT8PeILFjQY,252
14
- dexcontrol/config/sensors/vega_sensors.py,sha256=VTC8OPLEhBmnQpWdbPTOAr1JGeKOEekLc1Vew62yVtk,2969
15
- dexcontrol/config/sensors/cameras/__init__.py,sha256=UcY9soHfHTEZL4VX705yH7RM6jWf3Mqd8MWJMp59big,454
16
- dexcontrol/config/sensors/cameras/luxonis_camera.py,sha256=e3I5jiIbyAK2HjM8E0VRUOdYODTMjaA_zRH7Du9D8qI,1648
17
- dexcontrol/config/sensors/cameras/rgb_camera.py,sha256=MN4SjyZlfbrQ3JKDDkT8HhC0Aiyc0bWfDLt4ik0Xcvs,1448
18
- dexcontrol/config/sensors/cameras/zed_camera.py,sha256=cPXR84m2P3P2-qls1usHCbR6iz-l9gaBjjXyDmTXoiE,1836
19
- dexcontrol/config/sensors/imu/__init__.py,sha256=fW-DlevCvf_W8HV_fvLe9yIe-XL5op2mggoTKh-6fGQ,328
20
- dexcontrol/config/sensors/imu/chassis_imu.py,sha256=3OlTTBH6k1QGM5c5bcg8NL3XUXzYA8gCLM8lpCq2KFM,559
21
- dexcontrol/config/sensors/imu/zed_imu.py,sha256=y-dPI-XS6Kyq0WOf0wwuc2BgVnMN2hwCMxb0Vmwt4O4,550
22
- dexcontrol/config/sensors/lidar/__init__.py,sha256=j8vFkF675Z7zKtCztJcyG7oSA_XqrD8OeQLEK0GACug,288
23
- dexcontrol/config/sensors/lidar/rplidar.py,sha256=ybuT_f1ADWF3oGH1gi6D2F80TbJEm4vbm68Fe108OAA,541
24
- dexcontrol/config/sensors/ultrasonic/__init__.py,sha256=-q83RhIMZJGVFVPYaA4hOugoG6wZw8EL6wJg7-HTSxU,294
25
- dexcontrol/config/sensors/ultrasonic/ultrasonic.py,sha256=7b4dm1QOhy5_5RFVpY-frXZyDzqok0K1u7ed9gf3PL0,552
26
- dexcontrol/core/__init__.py,sha256=bYPMLxbbn5QeuPyA6OPGDS2JTYpnVvaZJT8PeILFjQY,252
27
- dexcontrol/core/arm.py,sha256=wbRm7G4v3ByVwscZ9rEu_IpmBdqZoI6gKXDphDeNN5Q,15360
28
- dexcontrol/core/chassis.py,sha256=tB-tsx6MjKJDOtQxBf7PIX0L1JwgnsXUzmNPC1VKsQU,23165
29
- dexcontrol/core/component.py,sha256=mkYH7W7tWIWnfWfXmCUQulzaZ7VfrwYI0ET_L7pPw2k,34779
30
- dexcontrol/core/hand.py,sha256=wd__YB5wcM-LlksEm3PatXhy665zLnu9nV_9x85WxIA,8649
31
- dexcontrol/core/head.py,sha256=iz9umFxiizYzK49BdML1i6pKM_i8kbk44tH0uu_TaVM,10512
32
- dexcontrol/core/misc.py,sha256=0YNJCrTJS1yCT9N-p9us5I7uSiAK470k8nWgJu638X4,25153
33
- dexcontrol/core/torso.py,sha256=4MB5EojHnlBpUaHzcZOh4IkKcjI6pSluXmpb5RpmdV4,8932
34
- dexcontrol/proto/dexcontrol_msg_pb2.py,sha256=YgPlHnOZbVfCuTdfK1DD0t4yf6qeDf-ximl4qY5YeWo,6397
35
- dexcontrol/proto/dexcontrol_msg_pb2.pyi,sha256=5sI3nH1hhLPAg_UpNk5Mgh5bt5YrzcoPj1tCL6bT45s,10865
36
- dexcontrol/proto/dexcontrol_query_pb2.py,sha256=n5EazrveRw2UXFM-J5n5c7NVfr8LzaPigmOns4UOI9k,5946
37
- dexcontrol/proto/dexcontrol_query_pb2.pyi,sha256=0Lr0wYPGHhhNeAK24JJ2A7bzZ7q-r5ssOI2_ZfoYzVk,7108
38
- dexcontrol/sensors/__init__.py,sha256=3DtVBwX5E3RPPjfCh2elnqk6uIWB_ukIqPQgBPZ7w7g,1008
39
- dexcontrol/sensors/manager.py,sha256=Qsbs9zZ4TvJgyn8yuI85wOU6mqgBQEus_MYSXj7wA9w,7025
40
- dexcontrol/sensors/ultrasonic.py,sha256=WAHvHh64iQ0HfqVf-Oo0Rg8R32Cdk5d8k8kDSwa3Xrc,3258
41
- dexcontrol/sensors/camera/__init__.py,sha256=w7-w58fAsV57lSb4ANC8tn8YbY5o1GVymw3p2agNJEE,684
42
- dexcontrol/sensors/camera/luxonis_camera.py,sha256=OeKoX7JL2ogbNJa7JPYkPT4LHKg8Wby5PPtoMQcFo8c,6307
43
- dexcontrol/sensors/camera/rgb_camera.py,sha256=UaEk8aszHeBRg5xMQlHxZk2KMP7wU3m6UnE_qgh6V4Q,5892
44
- dexcontrol/sensors/camera/zed_camera.py,sha256=hrnTrON0WieCkJ7i2pvO6nUjHOD_N8po9arl9jbqtuQ,15501
45
- dexcontrol/sensors/imu/__init__.py,sha256=bBC7_NSLJ5qLMvUYu2-9yXKO2bRpQLC0HyywBwnbM0A,768
46
- dexcontrol/sensors/imu/chassis_imu.py,sha256=a_PMH2I2UVHZ2xIqGac2M4xFIDgiUopjUOjD87xNFL0,5058
47
- dexcontrol/sensors/imu/zed_imu.py,sha256=kNXdOq0STucWG5Vvy6AWL3Hs4JZB6dxtfhCsjEWnRGY,5251
48
- dexcontrol/sensors/lidar/__init__.py,sha256=frF16HmeQnfbvH0dVJ4pPjD4TySF13wCk-O9L3Memeg,317
49
- dexcontrol/sensors/lidar/rplidar.py,sha256=HpPeO2lqGDGGrnR3j4rZ6suw_pc07Rhsfti9GCCi-1M,4499
50
- dexcontrol/utils/__init__.py,sha256=ayMZ6xNlA9xKfS_XRr8bWcoXW4-8Jg_25XSbMd5Jx58,468
51
- dexcontrol/utils/constants.py,sha256=6SG5HoSo7V-DlWH7cdNaMJtZs05dWrqYWIuKpmXfdI0,792
52
- dexcontrol/utils/error_code.py,sha256=iy840qnWn9wv_nVqyEDP8-l2CuXlPH2xXXW0k-5cHKk,7321
53
- dexcontrol/utils/io_utils.py,sha256=4TYV33ufECo8fuQivrZR9vtSdwWYUiPvpAUSneEzOOs,850
54
- dexcontrol/utils/motion_utils.py,sha256=p4kXQm_YorISDC2crrpY0gwCVw_yQCPv-acxPUSfh8w,7172
55
- dexcontrol/utils/os_utils.py,sha256=CC2st_Pb0C5cWfCg-i1P5bgBx3ZBX4LWVUiBq63dNeo,1852
56
- dexcontrol/utils/pb_utils.py,sha256=_6nFLKwAtq56vYXMZ6YmHnMfL5SarD96MVEA-9cxXns,3178
57
- dexcontrol/utils/rate_limiter.py,sha256=wFNaJ1fh-GO6zItuksKd_DSxLA1esE71WAiNDLpGsU0,6176
58
- dexcontrol/utils/rtc_utils.py,sha256=o2F9puC7CdAPnqiVq2vzomFZ7hMHljwtAbp9UiLhxJY,4426
59
- dexcontrol/utils/timer.py,sha256=1sOYYEapbZ5aBqJwknClsxgjDx0FDRQuGEdcTGnYTCI,3948
60
- dexcontrol/utils/trajectory_utils.py,sha256=TURFb0DeDey0416z4L7AXiWcKJYsgg_bB5AE_JPSpXY,1879
61
- dexcontrol/utils/viz_utils.py,sha256=rKtZfu32-9D9CS4cSiil-oLub_MiKTJV6hURvJbKd0s,6295
62
- dexcontrol/utils/zenoh_utils.py,sha256=83BSwWM0RZQP5Zey4Py83P_QC-vuk1qyk2Ac5iuschA,4264
63
- dexcontrol/utils/subscribers/__init__.py,sha256=Sqa-PPElwdUKxdh9BbU5MSqnf_i7BFqANrzVUXYUNuQ,1380
64
- dexcontrol/utils/subscribers/base.py,sha256=t4zcps_kjFG1wj6WSrZ4HJg0Bxcdnu-C8NkVVupmNVg,9769
65
- dexcontrol/utils/subscribers/camera.py,sha256=0kaeoKjKCxRQ5iaKK3r_94xEaVxNDBAMd2ZHL6X1kWU,11201
66
- dexcontrol/utils/subscribers/decoders.py,sha256=X39NY7zNEUlOaG0b1Eooc1T7U5SCQ3rZ2ddaz12fi0o,2134
67
- dexcontrol/utils/subscribers/generic.py,sha256=EKRutiu2zJBfzNIHCfYEfkcGE6QQTJmkOEMRRvEXJXA,3704
68
- dexcontrol/utils/subscribers/imu.py,sha256=Us4ZWzLfqujg16jvrNIoHcvUyxbRCv6mZlaAizvX6uo,5925
69
- dexcontrol/utils/subscribers/lidar.py,sha256=OlmORIf-dBO4LV4u6pWccoxh-nYTAtGlIfhjj-sG1F4,5691
70
- dexcontrol/utils/subscribers/protobuf.py,sha256=gtE2b9ZtR2UXftKA5nX7bvTLkj8AeXDYZMqe4B3t5BQ,3696
71
- dexcontrol/utils/subscribers/rtc.py,sha256=mxD-IIeQwluvq0_D63lQJJEXrJsIc6yxX7uD0PDh08k,11350
72
- dexcontrol-0.2.12.dist-info/METADATA,sha256=3O9vIN5nR0U_mpL2QAb7Enr_eRzUbSI3Zk49Vv_HFpI,36840
73
- dexcontrol-0.2.12.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
74
- dexcontrol-0.2.12.dist-info/licenses/LICENSE,sha256=0J2KCMNNnW5WZPK5x8xUiCxApBf7h83693ggSJYiue0,31745
75
- dexcontrol-0.2.12.dist-info/RECORD,,