dexcontrol 0.2.10__py3-none-any.whl → 0.3.0__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.
- dexcontrol/__init__.py +2 -0
- dexcontrol/config/core/arm.py +5 -1
- dexcontrol/config/core/chassis.py +9 -4
- dexcontrol/config/core/hand.py +2 -1
- dexcontrol/config/core/head.py +7 -8
- dexcontrol/config/core/misc.py +14 -1
- dexcontrol/config/core/torso.py +8 -4
- dexcontrol/config/sensors/cameras/__init__.py +2 -1
- dexcontrol/config/sensors/cameras/luxonis_camera.py +51 -0
- dexcontrol/config/sensors/cameras/rgb_camera.py +1 -1
- dexcontrol/config/sensors/cameras/zed_camera.py +2 -2
- dexcontrol/config/sensors/vega_sensors.py +9 -1
- dexcontrol/config/vega.py +34 -3
- dexcontrol/core/arm.py +103 -58
- dexcontrol/core/chassis.py +146 -115
- dexcontrol/core/component.py +83 -20
- dexcontrol/core/hand.py +74 -39
- dexcontrol/core/head.py +41 -28
- dexcontrol/core/misc.py +256 -25
- dexcontrol/core/robot_query_interface.py +440 -0
- dexcontrol/core/torso.py +28 -10
- dexcontrol/proto/dexcontrol_msg_pb2.py +27 -37
- dexcontrol/proto/dexcontrol_msg_pb2.pyi +111 -126
- dexcontrol/proto/dexcontrol_query_pb2.py +39 -35
- dexcontrol/proto/dexcontrol_query_pb2.pyi +41 -4
- dexcontrol/robot.py +266 -409
- dexcontrol/sensors/__init__.py +2 -1
- dexcontrol/sensors/camera/__init__.py +2 -0
- dexcontrol/sensors/camera/luxonis_camera.py +169 -0
- dexcontrol/sensors/camera/zed_camera.py +17 -8
- dexcontrol/sensors/imu/chassis_imu.py +5 -1
- dexcontrol/sensors/imu/zed_imu.py +3 -2
- dexcontrol/sensors/lidar/rplidar.py +1 -0
- dexcontrol/sensors/manager.py +3 -0
- dexcontrol/utils/constants.py +3 -0
- dexcontrol/utils/error_code.py +236 -0
- dexcontrol/utils/os_utils.py +183 -1
- dexcontrol/utils/pb_utils.py +0 -22
- dexcontrol/utils/subscribers/lidar.py +1 -0
- dexcontrol/utils/trajectory_utils.py +17 -5
- dexcontrol/utils/viz_utils.py +86 -11
- dexcontrol/utils/zenoh_utils.py +288 -2
- {dexcontrol-0.2.10.dist-info → dexcontrol-0.3.0.dist-info}/METADATA +15 -2
- dexcontrol-0.3.0.dist-info/RECORD +76 -0
- dexcontrol-0.2.10.dist-info/RECORD +0 -72
- {dexcontrol-0.2.10.dist-info → dexcontrol-0.3.0.dist-info}/WHEEL +0 -0
- {dexcontrol-0.2.10.dist-info → dexcontrol-0.3.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,440 @@
|
|
|
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 query utilities for robot communication.
|
|
12
|
+
|
|
13
|
+
This module provides the ZenohQueryable class that encapsulates all zenoh-based
|
|
14
|
+
queries and communication with the robot server. It handles various query types
|
|
15
|
+
including hand type detection, version information, status queries, and control operations.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import json
|
|
19
|
+
import time
|
|
20
|
+
from typing import TYPE_CHECKING, Any, Literal, cast
|
|
21
|
+
|
|
22
|
+
import zenoh
|
|
23
|
+
from loguru import logger
|
|
24
|
+
|
|
25
|
+
from dexcontrol.config.vega import VegaConfig, get_vega_config
|
|
26
|
+
from dexcontrol.core.hand import HandType
|
|
27
|
+
from dexcontrol.proto import dexcontrol_query_pb2
|
|
28
|
+
from dexcontrol.utils.os_utils import resolve_key_name
|
|
29
|
+
from dexcontrol.utils.pb_utils import (
|
|
30
|
+
ComponentStatus,
|
|
31
|
+
status_to_dict,
|
|
32
|
+
)
|
|
33
|
+
from dexcontrol.utils.viz_utils import show_component_status
|
|
34
|
+
from dexcontrol.utils.zenoh_utils import compute_ntp_stats, create_zenoh_session
|
|
35
|
+
|
|
36
|
+
if TYPE_CHECKING:
|
|
37
|
+
from dexcontrol.config.vega import VegaConfig
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class RobotQueryInterface:
|
|
41
|
+
"""Base class for zenoh query operations.
|
|
42
|
+
|
|
43
|
+
This class provides a clean interface for all zenoh-based queries and
|
|
44
|
+
communication operations. It maintains references to the zenoh session
|
|
45
|
+
and configuration needed for queries.
|
|
46
|
+
|
|
47
|
+
Can be used as a context manager for automatic resource cleanup:
|
|
48
|
+
>>> with RobotQueryInterface.create() as interface:
|
|
49
|
+
... version_info = interface.get_version_info()
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
def __init__(self, zenoh_session: zenoh.Session, configs: "VegaConfig"):
|
|
53
|
+
"""Initialize the RobotQueryInterface with session and config.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
zenoh_session: Active zenoh session for communication.
|
|
57
|
+
configs: Robot configuration containing query names.
|
|
58
|
+
"""
|
|
59
|
+
self._zenoh_session: zenoh.Session = zenoh_session
|
|
60
|
+
self._configs = configs
|
|
61
|
+
self._owns_session = False
|
|
62
|
+
|
|
63
|
+
@classmethod
|
|
64
|
+
def create(
|
|
65
|
+
cls,
|
|
66
|
+
zenoh_config_file: str | None = None,
|
|
67
|
+
) -> "RobotQueryInterface":
|
|
68
|
+
"""Create a standalone RobotQueryInterface with its own zenoh session.
|
|
69
|
+
|
|
70
|
+
This class method provides a convenient way to create a RobotQueryInterface
|
|
71
|
+
without requiring the full Robot class. The created interface will manage
|
|
72
|
+
its own zenoh session and configuration.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
zenoh_config_file: Path to zenoh configuration file. If None,
|
|
76
|
+
uses the default configuration path.
|
|
77
|
+
robot_config_path: Path to robot configuration file. If None,
|
|
78
|
+
uses default configuration for detected robot model.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
RobotQueryInterface instance ready for use.
|
|
82
|
+
|
|
83
|
+
Raises:
|
|
84
|
+
RuntimeError: If zenoh session initialization fails.
|
|
85
|
+
ValueError: If robot configuration cannot be loaded.
|
|
86
|
+
|
|
87
|
+
Example:
|
|
88
|
+
>>> query_interface = RobotQueryInterface.create_standalone()
|
|
89
|
+
>>> version_info = query_interface.get_version_info()
|
|
90
|
+
>>> query_interface.close()
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
session = create_zenoh_session(zenoh_config_file)
|
|
94
|
+
config: VegaConfig = get_vega_config()
|
|
95
|
+
|
|
96
|
+
instance = cls(session, config)
|
|
97
|
+
instance._owns_session = True
|
|
98
|
+
return instance
|
|
99
|
+
|
|
100
|
+
def close(self) -> None:
|
|
101
|
+
"""Close the zenoh session if owned by this instance.
|
|
102
|
+
|
|
103
|
+
This method should be called when done using a standalone
|
|
104
|
+
RobotQueryInterface to properly clean up resources.
|
|
105
|
+
"""
|
|
106
|
+
if self._owns_session and self._zenoh_session:
|
|
107
|
+
logger.debug("Closing zenoh session")
|
|
108
|
+
self._zenoh_session.close()
|
|
109
|
+
|
|
110
|
+
def __enter__(self) -> "RobotQueryInterface":
|
|
111
|
+
"""Enter context manager."""
|
|
112
|
+
return self
|
|
113
|
+
|
|
114
|
+
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
|
|
115
|
+
"""Exit context manager and clean up resources."""
|
|
116
|
+
self.close()
|
|
117
|
+
|
|
118
|
+
def query_hand_type(self) -> dict[str, HandType]:
|
|
119
|
+
"""Query the hand type information from the server.
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
Dictionary containing hand type information for left and right hands.
|
|
123
|
+
Format: {"left": hand_type_name, "right": hand_type_name}
|
|
124
|
+
Possible hand types: "UNKNOWN", "HandF5D6_V1", "HandF5D6_V2"
|
|
125
|
+
UNKNOWN means not connected or unknown end effector connected.
|
|
126
|
+
|
|
127
|
+
Raises:
|
|
128
|
+
RuntimeError: If hand type information cannot be retrieved.
|
|
129
|
+
"""
|
|
130
|
+
try:
|
|
131
|
+
# Sleep more time to avoice zenoh query timeout
|
|
132
|
+
time.sleep(2)
|
|
133
|
+
replies = self._zenoh_session.get(
|
|
134
|
+
resolve_key_name(self._configs.hand_info_query_name), timeout=5.0
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
for reply in replies:
|
|
138
|
+
if reply.ok and reply.ok.payload:
|
|
139
|
+
# Parse JSON response (not protobuf)
|
|
140
|
+
payload_bytes = reply.ok.payload.to_bytes()
|
|
141
|
+
payload_str = payload_bytes.decode("utf-8")
|
|
142
|
+
hand_info = json.loads(payload_str)
|
|
143
|
+
|
|
144
|
+
# Validate the expected format
|
|
145
|
+
if isinstance(hand_info, dict):
|
|
146
|
+
logger.info(f"End effector hand types: {hand_info}")
|
|
147
|
+
return {
|
|
148
|
+
"left": HandType(hand_info["left"]),
|
|
149
|
+
"right": HandType(hand_info["right"]),
|
|
150
|
+
}
|
|
151
|
+
else:
|
|
152
|
+
logger.warning(
|
|
153
|
+
f"Invalid hand type format received: {hand_info}"
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
# If no valid response received, assume v1 for backward compatibility
|
|
157
|
+
return {"left": HandType.HandF5D6_V1, "right": HandType.HandF5D6_V1}
|
|
158
|
+
|
|
159
|
+
except Exception as e:
|
|
160
|
+
logger.warning(f"Failed to query hand type: {e}. Assuming v1 hand types.")
|
|
161
|
+
return {"left": HandType.HandF5D6_V1, "right": HandType.HandF5D6_V1}
|
|
162
|
+
|
|
163
|
+
def query_ntp(
|
|
164
|
+
self,
|
|
165
|
+
sample_count: int = 30,
|
|
166
|
+
show: bool = False,
|
|
167
|
+
timeout: float = 1.0,
|
|
168
|
+
device: Literal["soc", "jetson"] = "soc",
|
|
169
|
+
) -> dict[Literal["success", "offset", "rtt"], bool | float]:
|
|
170
|
+
"""Query the NTP server via zenoh for time synchronization and compute robust statistics.
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
sample_count: Number of NTP samples to request (default: 50).
|
|
174
|
+
show: Whether to print summary statistics using a rich table.
|
|
175
|
+
timeout: Timeout for the zenoh querier in seconds (default: 2.0).
|
|
176
|
+
device: Which device to query for NTP ("soc" or "jetson").
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
Dictionary with keys:
|
|
180
|
+
- "success": True if any replies were received, False otherwise.
|
|
181
|
+
- "offset": Mean offset (in seconds) after removing RTT outliers.
|
|
182
|
+
- "rtt": Mean round-trip time (in seconds) after removing RTT outliers.
|
|
183
|
+
"""
|
|
184
|
+
if device == "soc":
|
|
185
|
+
ntp_key = resolve_key_name(self._configs.soc_ntp_query_name)
|
|
186
|
+
elif device == "jetson":
|
|
187
|
+
raise NotImplementedError("Jetson NTP query is not implemented yet")
|
|
188
|
+
|
|
189
|
+
time_offset = []
|
|
190
|
+
time_rtt = []
|
|
191
|
+
|
|
192
|
+
querier = self._zenoh_session.declare_querier(ntp_key, timeout=timeout)
|
|
193
|
+
time.sleep(0.1)
|
|
194
|
+
|
|
195
|
+
reply_count = 0
|
|
196
|
+
for i in range(sample_count):
|
|
197
|
+
request = dexcontrol_query_pb2.NTPRequest()
|
|
198
|
+
request.client_send_time_ns = time.time_ns()
|
|
199
|
+
request.sample_count = sample_count
|
|
200
|
+
request.sample_index = i
|
|
201
|
+
replies = querier.get(payload=request.SerializeToString())
|
|
202
|
+
|
|
203
|
+
for reply in replies:
|
|
204
|
+
reply_count += 1
|
|
205
|
+
if reply.ok and reply.ok.payload:
|
|
206
|
+
client_receive_time_ns = time.time_ns()
|
|
207
|
+
response = dexcontrol_query_pb2.NTPResponse()
|
|
208
|
+
response.ParseFromString(reply.ok.payload.to_bytes())
|
|
209
|
+
t0 = request.client_send_time_ns
|
|
210
|
+
t1 = response.server_receive_time_ns
|
|
211
|
+
t2 = response.server_send_time_ns
|
|
212
|
+
t3 = client_receive_time_ns
|
|
213
|
+
offset = ((t1 - t0) + (t2 - t3)) // 2 / 1e9
|
|
214
|
+
rtt = (t3 - t0) / 1e9
|
|
215
|
+
time_offset.append(offset)
|
|
216
|
+
time_rtt.append(rtt)
|
|
217
|
+
if i < sample_count - 1:
|
|
218
|
+
time.sleep(0.01)
|
|
219
|
+
|
|
220
|
+
querier.undeclare()
|
|
221
|
+
if reply_count == 0:
|
|
222
|
+
return {"success": False, "offset": 0, "rtt": 0}
|
|
223
|
+
|
|
224
|
+
stats = compute_ntp_stats(time_offset, time_rtt)
|
|
225
|
+
offset = stats["offset (mean)"]
|
|
226
|
+
rtt = stats["round_trip_time (mean)"]
|
|
227
|
+
if show:
|
|
228
|
+
from dexcontrol.utils.viz_utils import show_ntp_stats
|
|
229
|
+
|
|
230
|
+
show_ntp_stats(stats)
|
|
231
|
+
|
|
232
|
+
return {"success": True, "offset": offset, "rtt": rtt}
|
|
233
|
+
|
|
234
|
+
def get_version_info(self, show: bool = True) -> dict[str, Any]:
|
|
235
|
+
"""Retrieve comprehensive version information using JSON interface.
|
|
236
|
+
|
|
237
|
+
This method queries the new JSON-based version endpoint that provides:
|
|
238
|
+
- Server component versions (hardware, software, compile_time, hashes)
|
|
239
|
+
- Minimum required client version
|
|
240
|
+
- Version compatibility information
|
|
241
|
+
|
|
242
|
+
Args:
|
|
243
|
+
show: Whether to display the version information.
|
|
244
|
+
|
|
245
|
+
Returns:
|
|
246
|
+
Dictionary containing comprehensive version information with structure:
|
|
247
|
+
{
|
|
248
|
+
"server": {
|
|
249
|
+
"component_name": {
|
|
250
|
+
"hardware_version": int,
|
|
251
|
+
"software_version": int,
|
|
252
|
+
"compile_time": str,
|
|
253
|
+
"main_hash": str,
|
|
254
|
+
"sub_hash": str
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
"client": {
|
|
258
|
+
"minimal_version": str
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
Raises:
|
|
263
|
+
RuntimeError: If version information cannot be retrieved.
|
|
264
|
+
"""
|
|
265
|
+
try:
|
|
266
|
+
replies = self._zenoh_session.get(
|
|
267
|
+
resolve_key_name(self._configs.version_info_name), timeout=5.0
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
for reply in replies:
|
|
271
|
+
if reply.ok and reply.ok.payload:
|
|
272
|
+
try:
|
|
273
|
+
# Parse JSON response
|
|
274
|
+
payload_bytes = reply.ok.payload.to_bytes()
|
|
275
|
+
payload_str = payload_bytes.decode("utf-8")
|
|
276
|
+
version_info = json.loads(payload_str)
|
|
277
|
+
|
|
278
|
+
# Validate expected structure
|
|
279
|
+
if (
|
|
280
|
+
isinstance(version_info, dict)
|
|
281
|
+
and "server" in version_info
|
|
282
|
+
and "client" in version_info
|
|
283
|
+
):
|
|
284
|
+
if show:
|
|
285
|
+
self._show_version_info(version_info)
|
|
286
|
+
return version_info
|
|
287
|
+
else:
|
|
288
|
+
logger.warning(
|
|
289
|
+
f"Invalid version info format received: {version_info}"
|
|
290
|
+
)
|
|
291
|
+
except (json.JSONDecodeError, UnicodeDecodeError) as e:
|
|
292
|
+
logger.warning(f"Failed to parse version info response: {e}")
|
|
293
|
+
continue
|
|
294
|
+
|
|
295
|
+
raise RuntimeError("No valid version information received from server")
|
|
296
|
+
|
|
297
|
+
except Exception as e:
|
|
298
|
+
raise RuntimeError(f"Failed to retrieve version information: {e}") from e
|
|
299
|
+
|
|
300
|
+
def get_component_status(
|
|
301
|
+
self, show: bool = True
|
|
302
|
+
) -> dict[str, dict[str, bool | ComponentStatus]]:
|
|
303
|
+
"""Retrieve status information for all components.
|
|
304
|
+
|
|
305
|
+
Args:
|
|
306
|
+
show: Whether to display the status information.
|
|
307
|
+
|
|
308
|
+
Returns:
|
|
309
|
+
Dictionary containing status information for all components.
|
|
310
|
+
|
|
311
|
+
Raises:
|
|
312
|
+
RuntimeError: If status information cannot be retrieved.
|
|
313
|
+
"""
|
|
314
|
+
try:
|
|
315
|
+
replies = self._zenoh_session.get(
|
|
316
|
+
resolve_key_name(self._configs.status_info_name)
|
|
317
|
+
)
|
|
318
|
+
status_dict = {}
|
|
319
|
+
for reply in replies:
|
|
320
|
+
if reply.ok and reply.ok.payload:
|
|
321
|
+
status_bytes = reply.ok.payload.to_bytes()
|
|
322
|
+
status_msg = cast(
|
|
323
|
+
dexcontrol_query_pb2.ComponentStates,
|
|
324
|
+
dexcontrol_query_pb2.ComponentStates.FromString(status_bytes),
|
|
325
|
+
)
|
|
326
|
+
status_dict = status_to_dict(status_msg)
|
|
327
|
+
break
|
|
328
|
+
|
|
329
|
+
if show:
|
|
330
|
+
show_component_status(status_dict)
|
|
331
|
+
return status_dict
|
|
332
|
+
except Exception as e:
|
|
333
|
+
raise RuntimeError(f"Failed to retrieve component status: {e}") from e
|
|
334
|
+
|
|
335
|
+
def reboot_component(
|
|
336
|
+
self,
|
|
337
|
+
part: Literal["arm", "chassis", "torso"],
|
|
338
|
+
) -> None:
|
|
339
|
+
"""Reboot a specific robot component.
|
|
340
|
+
|
|
341
|
+
Args:
|
|
342
|
+
part: Component to reboot ("arm", "chassis", or "torso").
|
|
343
|
+
|
|
344
|
+
Raises:
|
|
345
|
+
ValueError: If the specified component is invalid.
|
|
346
|
+
RuntimeError: If the reboot operation fails.
|
|
347
|
+
"""
|
|
348
|
+
component_map = {
|
|
349
|
+
"arm": dexcontrol_query_pb2.RebootComponent.Component.ARM,
|
|
350
|
+
"chassis": dexcontrol_query_pb2.RebootComponent.Component.CHASSIS,
|
|
351
|
+
"torso": dexcontrol_query_pb2.RebootComponent.Component.TORSO,
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if part not in component_map:
|
|
355
|
+
raise ValueError(f"Invalid component: {part}")
|
|
356
|
+
|
|
357
|
+
try:
|
|
358
|
+
query_msg = dexcontrol_query_pb2.RebootComponent(
|
|
359
|
+
component=component_map[part]
|
|
360
|
+
)
|
|
361
|
+
self._zenoh_session.get(
|
|
362
|
+
resolve_key_name(self._configs.reboot_query_name),
|
|
363
|
+
payload=query_msg.SerializeToString(),
|
|
364
|
+
)
|
|
365
|
+
logger.info(f"Rebooting component: {part}")
|
|
366
|
+
except Exception as e:
|
|
367
|
+
raise RuntimeError(f"Failed to reboot component {part}: {e}") from e
|
|
368
|
+
|
|
369
|
+
def clear_error(
|
|
370
|
+
self,
|
|
371
|
+
part: Literal["left_arm", "right_arm", "chassis", "head"] | str,
|
|
372
|
+
) -> None:
|
|
373
|
+
"""Clear error state for a specific component.
|
|
374
|
+
|
|
375
|
+
Args:
|
|
376
|
+
part: Component to clear error state for.
|
|
377
|
+
|
|
378
|
+
Raises:
|
|
379
|
+
ValueError: If the specified component is invalid.
|
|
380
|
+
RuntimeError: If the error clearing operation fails.
|
|
381
|
+
"""
|
|
382
|
+
component_map = {
|
|
383
|
+
"left_arm": dexcontrol_query_pb2.ClearError.Component.LEFT_ARM,
|
|
384
|
+
"right_arm": dexcontrol_query_pb2.ClearError.Component.RIGHT_ARM,
|
|
385
|
+
"chassis": dexcontrol_query_pb2.ClearError.Component.CHASSIS,
|
|
386
|
+
"head": dexcontrol_query_pb2.ClearError.Component.HEAD,
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if part not in component_map:
|
|
390
|
+
raise ValueError(f"Invalid component: {part}")
|
|
391
|
+
|
|
392
|
+
try:
|
|
393
|
+
query_msg = dexcontrol_query_pb2.ClearError(component=component_map[part])
|
|
394
|
+
self._zenoh_session.get(
|
|
395
|
+
resolve_key_name(self._configs.clear_error_query_name),
|
|
396
|
+
handler=lambda reply: logger.info(f"Cleared error of {part}"),
|
|
397
|
+
payload=query_msg.SerializeToString(),
|
|
398
|
+
)
|
|
399
|
+
except Exception as e:
|
|
400
|
+
raise RuntimeError(
|
|
401
|
+
f"Failed to clear error for component {part}: {e}"
|
|
402
|
+
) from e
|
|
403
|
+
|
|
404
|
+
def _show_version_info(self, version_info: dict[str, Any]) -> None:
|
|
405
|
+
"""Display comprehensive version information in a formatted table.
|
|
406
|
+
|
|
407
|
+
Args:
|
|
408
|
+
version_info: Dictionary containing server and client version information.
|
|
409
|
+
"""
|
|
410
|
+
from rich.console import Console
|
|
411
|
+
from rich.table import Table
|
|
412
|
+
|
|
413
|
+
console = Console()
|
|
414
|
+
table = Table(title="🤖 Robot System Version Information")
|
|
415
|
+
|
|
416
|
+
table.add_column("Component", justify="left", style="cyan", no_wrap=True)
|
|
417
|
+
table.add_column("Hardware Ver.", justify="center", style="magenta")
|
|
418
|
+
table.add_column("Software Ver.", justify="center", style="green")
|
|
419
|
+
table.add_column("Compile Time", justify="center", style="yellow")
|
|
420
|
+
table.add_column("Main Hash", justify="center", style="blue")
|
|
421
|
+
table.add_column("Sub Hash", justify="center", style="red")
|
|
422
|
+
|
|
423
|
+
# Display server components
|
|
424
|
+
server_info = version_info.get("server", {})
|
|
425
|
+
for component, info in server_info.items():
|
|
426
|
+
if isinstance(info, dict):
|
|
427
|
+
table.add_row(
|
|
428
|
+
component,
|
|
429
|
+
str(info.get("hardware_version", "N/A")),
|
|
430
|
+
str(info.get("software_version", "N/A")),
|
|
431
|
+
str(info.get("compile_time", "N/A")),
|
|
432
|
+
str(info.get("main_hash", "N/A")[:8])
|
|
433
|
+
if info.get("main_hash")
|
|
434
|
+
else "N/A",
|
|
435
|
+
str(info.get("sub_hash", "N/A")[:8])
|
|
436
|
+
if info.get("sub_hash")
|
|
437
|
+
else "N/A",
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
console.print(table)
|
dexcontrol/core/torso.py
CHANGED
|
@@ -49,13 +49,18 @@ class Torso(RobotJointComponent):
|
|
|
49
49
|
super().__init__(
|
|
50
50
|
state_sub_topic=configs.state_sub_topic,
|
|
51
51
|
control_pub_topic=configs.control_pub_topic,
|
|
52
|
-
state_message_type=dexcontrol_msg_pb2.
|
|
52
|
+
state_message_type=dexcontrol_msg_pb2.MotorStateWithCurrent,
|
|
53
53
|
zenoh_session=zenoh_session,
|
|
54
54
|
joint_name=configs.joint_name,
|
|
55
|
+
joint_limit=configs.joint_limit
|
|
56
|
+
if hasattr(configs, "joint_limit")
|
|
57
|
+
else None,
|
|
58
|
+
joint_vel_limit=configs.joint_vel_limit
|
|
59
|
+
if hasattr(configs, "joint_vel_limit")
|
|
60
|
+
else None,
|
|
55
61
|
pose_pool=configs.pose_pool,
|
|
56
62
|
)
|
|
57
|
-
self.
|
|
58
|
-
self.max_vel = configs.max_vel
|
|
63
|
+
assert self._joint_vel_limit is not None, "joint_vel_limit is not set"
|
|
59
64
|
|
|
60
65
|
def set_joint_pos_vel(
|
|
61
66
|
self,
|
|
@@ -104,10 +109,19 @@ class Torso(RobotJointComponent):
|
|
|
104
109
|
joint_pos = self._convert_joint_cmd_to_array(joint_pos)
|
|
105
110
|
joint_vel = self._process_joint_velocities(joint_vel, joint_pos)
|
|
106
111
|
|
|
112
|
+
if self._joint_limit is not None:
|
|
113
|
+
joint_pos = np.clip(
|
|
114
|
+
joint_pos, self._joint_limit[:, 0], self._joint_limit[:, 1]
|
|
115
|
+
)
|
|
116
|
+
if self._joint_vel_limit is not None:
|
|
117
|
+
joint_vel = np.clip(
|
|
118
|
+
joint_vel, -self._joint_vel_limit, self._joint_vel_limit
|
|
119
|
+
)
|
|
120
|
+
|
|
107
121
|
# Create and send control message
|
|
108
|
-
control_msg = dexcontrol_msg_pb2.
|
|
109
|
-
control_msg.
|
|
110
|
-
control_msg.
|
|
122
|
+
control_msg = dexcontrol_msg_pb2.MotorPosVelCommand()
|
|
123
|
+
control_msg.pos.extend(joint_pos.tolist())
|
|
124
|
+
control_msg.vel.extend(joint_vel.tolist())
|
|
111
125
|
self._publish_control(control_msg)
|
|
112
126
|
|
|
113
127
|
# Wait if specified
|
|
@@ -207,12 +221,16 @@ class Torso(RobotJointComponent):
|
|
|
207
221
|
if motion_norm < 1e-6: # Avoid division by zero
|
|
208
222
|
return np.zeros(3, dtype=np.float32)
|
|
209
223
|
|
|
210
|
-
|
|
211
|
-
|
|
224
|
+
default_vel = (
|
|
225
|
+
0.6 if self._joint_vel_limit is None else np.min(self._joint_vel_limit)
|
|
226
|
+
)
|
|
227
|
+
return (joint_motion / motion_norm) * default_vel
|
|
212
228
|
|
|
213
229
|
if isinstance(joint_vel, (int, float)):
|
|
214
230
|
# Single value - apply to all joints
|
|
215
231
|
return np.full(3, joint_vel, dtype=np.float32)
|
|
216
232
|
|
|
217
|
-
# Convert to array and clip to
|
|
218
|
-
return self._convert_joint_cmd_to_array(
|
|
233
|
+
# Convert to array and clip to velocity limits
|
|
234
|
+
return self._convert_joint_cmd_to_array(
|
|
235
|
+
joint_vel, clip_value=self._joint_vel_limit
|
|
236
|
+
)
|
|
@@ -25,47 +25,37 @@ _sym_db = _symbol_database.Default()
|
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x14\x64\x65xcontrol_msg.proto\x12\ndexcontrol\"
|
|
28
|
+
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x14\x64\x65xcontrol_msg.proto\x12\ndexcontrol\"e\n\x14MotorStateWithTorque\x12\x0b\n\x03pos\x18\x01 \x03(\x02\x12\x0b\n\x03vel\x18\x02 \x03(\x02\x12\x0e\n\x06torque\x18\x03 \x03(\x02\x12\r\n\x05\x65rror\x18\x04 \x03(\r\x12\x14\n\x0ctimestamp_ns\x18\x05 \x01(\x04\"c\n\x15MotorStateWithCurrent\x12\x0b\n\x03pos\x18\x01 \x03(\x02\x12\x0b\n\x03vel\x18\x02 \x03(\x02\x12\x0b\n\x03\x63ur\x18\x03 \x03(\x02\x12\r\n\x05\x65rror\x18\x04 \x03(\r\x12\x14\n\x0ctimestamp_ns\x18\x05 \x01(\x04\"4\n\x0fMotorPosCommand\x12\x0b\n\x03pos\x18\x01 \x03(\x02\x12\x14\n\x0ctimestamp_ns\x18\x02 \x01(\x04\"4\n\x0fMotorVelCommand\x12\x0b\n\x03vel\x18\x01 \x03(\x02\x12\x14\n\x0ctimestamp_ns\x18\x02 \x01(\x04\"D\n\x12MotorPosVelCommand\x12\x0b\n\x03pos\x18\x01 \x03(\x02\x12\x0b\n\x03vel\x18\x02 \x03(\x02\x12\x14\n\x0ctimestamp_ns\x18\x03 \x01(\x04\"X\n\x19MotorPosVelCurrentCommand\x12\x0b\n\x03pos\x18\x01 \x03(\x02\x12\x0b\n\x03vel\x18\x02 \x03(\x02\x12\x0b\n\x03\x63ur\x18\x03 \x03(\x02\x12\x14\n\x0ctimestamp_ns\x18\x04 \x01(\x04\"C\n\x1d\x45ndEffectorPassThroughCommand\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\x0c\x12\x14\n\x0ctimestamp_ns\x18\x02 \x01(\x04\"\x8f\x01\n\x08\x42MSState\x12\x0f\n\x07voltage\x18\x01 \x01(\x02\x12\x0f\n\x07\x63urrent\x18\x02 \x01(\x02\x12\x13\n\x0btemperature\x18\x03 \x01(\x02\x12\x12\n\npercentage\x18\x04 \x01(\r\x12\x13\n\x0bis_charging\x18\x05 \x01(\x08\x12\r\n\x05\x65rror\x18\x06 \x01(\r\x12\x14\n\x0ctimestamp_ns\x18\x07 \x01(\x04\"^\n\x0bWrenchState\x12\x0e\n\x06wrench\x18\x01 \x03(\x02\x12\x13\n\x0b\x62lue_button\x18\x02 \x01(\x08\x12\x14\n\x0cgreen_button\x18\x03 \x01(\x08\x12\x14\n\x0ctimestamp_ns\x18\x04 \x01(\x04\"\xbc\x01\n\nEStopState\x12\x1e\n\x16software_estop_enabled\x18\x01 \x01(\x08\x12\x1b\n\x13left_button_pressed\x18\x02 \x01(\x08\x12\x1c\n\x14right_button_pressed\x18\x03 \x01(\x08\x12\x1c\n\x14waist_button_pressed\x18\x04 \x01(\x08\x12\x1f\n\x17wireless_button_pressed\x18\x05 \x01(\x08\x12\x14\n\x0ctimestamp_ns\x18\x06 \x01(\x04\"w\n\x0fUltrasonicState\x12\x12\n\nfront_left\x18\x01 \x01(\x02\x12\x13\n\x0b\x66ront_right\x18\x02 \x01(\x02\x12\x11\n\tback_left\x18\x03 \x01(\x02\x12\x12\n\nback_right\x18\x04 \x01(\x02\x12\x14\n\x0ctimestamp_ns\x18\x05 \x01(\x04\"\xbd\x01\n\x08IMUState\x12\r\n\x05\x61\x63\x63_x\x18\x01 \x01(\x02\x12\r\n\x05\x61\x63\x63_y\x18\x02 \x01(\x02\x12\r\n\x05\x61\x63\x63_z\x18\x03 \x01(\x02\x12\x0e\n\x06gyro_x\x18\x04 \x01(\x02\x12\x0e\n\x06gyro_y\x18\x05 \x01(\x02\x12\x0e\n\x06gyro_z\x18\x06 \x01(\x02\x12\x0e\n\x06quat_w\x18\x07 \x01(\x02\x12\x0e\n\x06quat_x\x18\x08 \x01(\x02\x12\x0e\n\x06quat_y\x18\t \x01(\x02\x12\x0e\n\x06quat_z\x18\n \x01(\x02\x12\x14\n\x0ctimestamp_ns\x18\x0b \x01(\x04\";\n\x14HandTouchSensorState\x12\r\n\x05\x66orce\x18\x01 \x03(\x02\x12\x14\n\x0ctimestamp_ns\x18\x02 \x01(\x04\x62\x06proto3')
|
|
29
29
|
|
|
30
30
|
_globals = globals()
|
|
31
31
|
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
|
32
32
|
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'dexcontrol_msg_pb2', _globals)
|
|
33
33
|
if not _descriptor._USE_C_DESCRIPTORS:
|
|
34
34
|
DESCRIPTOR._loaded_options = None
|
|
35
|
-
_globals['
|
|
36
|
-
_globals['
|
|
37
|
-
_globals['
|
|
38
|
-
_globals['
|
|
39
|
-
_globals['
|
|
40
|
-
_globals['
|
|
41
|
-
_globals['
|
|
42
|
-
_globals['
|
|
43
|
-
_globals['
|
|
44
|
-
_globals['
|
|
45
|
-
_globals['
|
|
46
|
-
_globals['
|
|
47
|
-
_globals['
|
|
48
|
-
_globals['
|
|
49
|
-
_globals['
|
|
50
|
-
_globals['
|
|
51
|
-
_globals['
|
|
52
|
-
_globals['
|
|
53
|
-
_globals['
|
|
54
|
-
_globals['
|
|
55
|
-
_globals['
|
|
56
|
-
_globals['
|
|
57
|
-
_globals['
|
|
58
|
-
_globals['
|
|
59
|
-
_globals['
|
|
60
|
-
_globals['
|
|
61
|
-
_globals['_HANDCOMMAND']._serialized_start=1190
|
|
62
|
-
_globals['_HANDCOMMAND']._serialized_end=1222
|
|
63
|
-
_globals['_HEADCOMMAND']._serialized_start=1224
|
|
64
|
-
_globals['_HEADCOMMAND']._serialized_end=1275
|
|
65
|
-
_globals['_TORSOCOMMAND']._serialized_start=1277
|
|
66
|
-
_globals['_TORSOCOMMAND']._serialized_end=1329
|
|
67
|
-
_globals['_SINGLEWHEELCOMMAND']._serialized_start=1331
|
|
68
|
-
_globals['_SINGLEWHEELCOMMAND']._serialized_end=1392
|
|
69
|
-
_globals['_CHASSISCOMMAND']._serialized_start=1394
|
|
70
|
-
_globals['_CHASSISCOMMAND']._serialized_end=1503
|
|
35
|
+
_globals['_MOTORSTATEWITHTORQUE']._serialized_start=36
|
|
36
|
+
_globals['_MOTORSTATEWITHTORQUE']._serialized_end=137
|
|
37
|
+
_globals['_MOTORSTATEWITHCURRENT']._serialized_start=139
|
|
38
|
+
_globals['_MOTORSTATEWITHCURRENT']._serialized_end=238
|
|
39
|
+
_globals['_MOTORPOSCOMMAND']._serialized_start=240
|
|
40
|
+
_globals['_MOTORPOSCOMMAND']._serialized_end=292
|
|
41
|
+
_globals['_MOTORVELCOMMAND']._serialized_start=294
|
|
42
|
+
_globals['_MOTORVELCOMMAND']._serialized_end=346
|
|
43
|
+
_globals['_MOTORPOSVELCOMMAND']._serialized_start=348
|
|
44
|
+
_globals['_MOTORPOSVELCOMMAND']._serialized_end=416
|
|
45
|
+
_globals['_MOTORPOSVELCURRENTCOMMAND']._serialized_start=418
|
|
46
|
+
_globals['_MOTORPOSVELCURRENTCOMMAND']._serialized_end=506
|
|
47
|
+
_globals['_ENDEFFECTORPASSTHROUGHCOMMAND']._serialized_start=508
|
|
48
|
+
_globals['_ENDEFFECTORPASSTHROUGHCOMMAND']._serialized_end=575
|
|
49
|
+
_globals['_BMSSTATE']._serialized_start=578
|
|
50
|
+
_globals['_BMSSTATE']._serialized_end=721
|
|
51
|
+
_globals['_WRENCHSTATE']._serialized_start=723
|
|
52
|
+
_globals['_WRENCHSTATE']._serialized_end=817
|
|
53
|
+
_globals['_ESTOPSTATE']._serialized_start=820
|
|
54
|
+
_globals['_ESTOPSTATE']._serialized_end=1008
|
|
55
|
+
_globals['_ULTRASONICSTATE']._serialized_start=1010
|
|
56
|
+
_globals['_ULTRASONICSTATE']._serialized_end=1129
|
|
57
|
+
_globals['_IMUSTATE']._serialized_start=1132
|
|
58
|
+
_globals['_IMUSTATE']._serialized_end=1321
|
|
59
|
+
_globals['_HANDTOUCHSENSORSTATE']._serialized_start=1323
|
|
60
|
+
_globals['_HANDTOUCHSENSORSTATE']._serialized_end=1382
|
|
71
61
|
# @@protoc_insertion_point(module_scope)
|