dexcontrol 0.2.12__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 -1
- dexcontrol/config/core/chassis.py +9 -4
- dexcontrol/config/core/hand.py +1 -0
- dexcontrol/config/vega.py +4 -1
- dexcontrol/core/arm.py +32 -12
- dexcontrol/core/chassis.py +146 -115
- dexcontrol/core/component.py +42 -16
- dexcontrol/core/hand.py +74 -39
- dexcontrol/core/head.py +6 -5
- dexcontrol/core/misc.py +172 -22
- dexcontrol/core/robot_query_interface.py +440 -0
- dexcontrol/core/torso.py +4 -4
- dexcontrol/proto/dexcontrol_msg_pb2.py +27 -39
- dexcontrol/proto/dexcontrol_msg_pb2.pyi +75 -118
- dexcontrol/proto/dexcontrol_query_pb2.py +39 -39
- dexcontrol/proto/dexcontrol_query_pb2.pyi +17 -4
- dexcontrol/robot.py +259 -566
- dexcontrol/utils/os_utils.py +183 -1
- dexcontrol/utils/pb_utils.py +0 -22
- dexcontrol/utils/zenoh_utils.py +249 -2
- {dexcontrol-0.2.12.dist-info → dexcontrol-0.3.0.dist-info}/METADATA +12 -1
- {dexcontrol-0.2.12.dist-info → dexcontrol-0.3.0.dist-info}/RECORD +24 -23
- {dexcontrol-0.2.12.dist-info → dexcontrol-0.3.0.dist-info}/WHEEL +0 -0
- {dexcontrol-0.2.12.dist-info → dexcontrol-0.3.0.dist-info}/licenses/LICENSE +0 -0
dexcontrol/utils/os_utils.py
CHANGED
|
@@ -12,8 +12,11 @@
|
|
|
12
12
|
|
|
13
13
|
import os
|
|
14
14
|
import re
|
|
15
|
-
from typing import Final
|
|
15
|
+
from typing import Any, Final
|
|
16
16
|
|
|
17
|
+
from loguru import logger
|
|
18
|
+
|
|
19
|
+
import dexcontrol
|
|
17
20
|
from dexcontrol.utils.constants import ROBOT_NAME_ENV_VAR
|
|
18
21
|
|
|
19
22
|
|
|
@@ -56,3 +59,182 @@ def get_robot_model() -> str:
|
|
|
56
59
|
raise ValueError(f"Unknown robot model: {robot_model_abb}")
|
|
57
60
|
model = robot_model_abb_mapping[robot_model_abb] + "-" + robot_name.split("-")[-1]
|
|
58
61
|
return model
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def check_version_compatibility(
|
|
65
|
+
version_info: dict[str, Any], show_warnings: bool = True
|
|
66
|
+
) -> None:
|
|
67
|
+
"""Check version compatibility between client and server.
|
|
68
|
+
|
|
69
|
+
This function uses the new JSON-based version interface to:
|
|
70
|
+
1. Compare client library version with server's minimum required version
|
|
71
|
+
2. Check server component versions for compatibility
|
|
72
|
+
3. Provide clear guidance for version mismatches
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
version_info: Dictionary containing version information from get_version_info()
|
|
76
|
+
show_warnings: Whether to show warning messages (default: True)
|
|
77
|
+
"""
|
|
78
|
+
validate_client_version(version_info)
|
|
79
|
+
validate_server_version(version_info)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def validate_client_version(version_info: dict[str, Any]) -> None:
|
|
83
|
+
"""Validate client library version against server requirements.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
version_info: Dictionary containing server and client version information.
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
client_info = version_info.get("client", {})
|
|
90
|
+
min_required_version = client_info.get("minimal_version")
|
|
91
|
+
|
|
92
|
+
if not min_required_version:
|
|
93
|
+
logger.debug("No minimum version requirement from server")
|
|
94
|
+
return
|
|
95
|
+
|
|
96
|
+
# Get current library version
|
|
97
|
+
current_version = getattr(dexcontrol, "__version__", "unknown")
|
|
98
|
+
|
|
99
|
+
if current_version == "unknown":
|
|
100
|
+
logger.warning("Could not determine current library version")
|
|
101
|
+
return
|
|
102
|
+
|
|
103
|
+
# Compare versions
|
|
104
|
+
comparison = compare_versions(current_version, min_required_version)
|
|
105
|
+
|
|
106
|
+
if comparison < 0:
|
|
107
|
+
show_version_upgrade_warning(current_version, min_required_version)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def show_version_upgrade_warning(current: str, required: str) -> None:
|
|
111
|
+
"""Display version upgrade warning to user.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
current: Current library version
|
|
115
|
+
required: Required minimum version
|
|
116
|
+
"""
|
|
117
|
+
logger.error(
|
|
118
|
+
f"🚨 CLIENT VERSION TOO OLD! 🚨\n"
|
|
119
|
+
f"Current library version: {current}\n"
|
|
120
|
+
f"Minimum required version: {required}\n"
|
|
121
|
+
f"\n"
|
|
122
|
+
f"⚠️ Your dexcontrol library is outdated and may not work correctly!\n"
|
|
123
|
+
f"📦 Please update your library using: pip install --upgrade dexcontrol\n"
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def validate_server_version(version_info: dict[str, Any]) -> None:
|
|
128
|
+
"""Validate server software versions against minimum requirements.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
version_info: Dictionary containing server and client version information.
|
|
132
|
+
"""
|
|
133
|
+
server_info = version_info.get("server", {})
|
|
134
|
+
|
|
135
|
+
if not server_info:
|
|
136
|
+
logger.debug("No server version information available")
|
|
137
|
+
return
|
|
138
|
+
|
|
139
|
+
# Check each component's software version
|
|
140
|
+
components_below_min = []
|
|
141
|
+
for component_name, component_info in server_info.items():
|
|
142
|
+
if isinstance(component_info, dict):
|
|
143
|
+
software_version = component_info.get("software_version")
|
|
144
|
+
if software_version is not None:
|
|
145
|
+
try:
|
|
146
|
+
# Convert to int if it's a string
|
|
147
|
+
software_version_int = int(software_version)
|
|
148
|
+
if software_version_int < dexcontrol.MIN_SOC_SOFTWARE_VERSION:
|
|
149
|
+
components_below_min.append(
|
|
150
|
+
(component_name, software_version_int)
|
|
151
|
+
)
|
|
152
|
+
except (ValueError, TypeError) as e:
|
|
153
|
+
logger.debug(
|
|
154
|
+
f"Could not parse software version for {component_name}: {e}"
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
# If any components are below minimum version, show warning
|
|
158
|
+
if components_below_min:
|
|
159
|
+
show_server_version_warning(
|
|
160
|
+
components_below_min, dexcontrol.MIN_SOC_SOFTWARE_VERSION
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def show_server_version_warning(
|
|
165
|
+
components: list[tuple[str, int]], min_version: int
|
|
166
|
+
) -> None:
|
|
167
|
+
"""Display server version warning to user.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
components: List of (component_name, version) tuples for components below minimum.
|
|
171
|
+
min_version: Minimum required server software version.
|
|
172
|
+
"""
|
|
173
|
+
components_str = "\n".join(
|
|
174
|
+
f" - {name}: version {version}" for name, version in components
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
logger.error(
|
|
178
|
+
f"🚨 SERVER VERSION TOO OLD! 🚨\n"
|
|
179
|
+
f"The following server components are below minimum version {min_version}:\n"
|
|
180
|
+
f"{components_str}\n"
|
|
181
|
+
f"\n"
|
|
182
|
+
f"⚠️ Your robot's firmware may be outdated and some features may not work correctly!\n"
|
|
183
|
+
f"📦 Please contact your robot admin or check https://github.com/dexmate-ai/vega-firmware.\n"
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def compare_versions(version1: str, version2: str) -> int:
|
|
188
|
+
"""Compare two semantic version strings.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
version1: First version string (e.g., "1.2.3")
|
|
192
|
+
version2: Second version string (e.g., "1.1.0")
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
-1 if version1 < version2
|
|
196
|
+
0 if version1 == version2
|
|
197
|
+
1 if version1 > version2
|
|
198
|
+
"""
|
|
199
|
+
try:
|
|
200
|
+
# Clean version strings (remove 'v' prefix, handle pre-release suffixes)
|
|
201
|
+
def clean_version(v: str) -> list[int]:
|
|
202
|
+
v = v.strip().lower()
|
|
203
|
+
if v.startswith("v"):
|
|
204
|
+
v = v[1:]
|
|
205
|
+
# Split by dots and take only numeric parts
|
|
206
|
+
parts = v.split(".")
|
|
207
|
+
numeric_parts = []
|
|
208
|
+
for part in parts:
|
|
209
|
+
# Remove any non-numeric suffixes (like -alpha, -rc1, etc.)
|
|
210
|
+
numeric_part = ""
|
|
211
|
+
for char in part:
|
|
212
|
+
if char.isdigit():
|
|
213
|
+
numeric_part += char
|
|
214
|
+
else:
|
|
215
|
+
break
|
|
216
|
+
if numeric_part:
|
|
217
|
+
numeric_parts.append(int(numeric_part))
|
|
218
|
+
return numeric_parts
|
|
219
|
+
|
|
220
|
+
parts1 = clean_version(version1)
|
|
221
|
+
parts2 = clean_version(version2)
|
|
222
|
+
|
|
223
|
+
# Pad shorter version with zeros
|
|
224
|
+
max_len = max(len(parts1), len(parts2))
|
|
225
|
+
parts1.extend([0] * (max_len - len(parts1)))
|
|
226
|
+
parts2.extend([0] * (max_len - len(parts2)))
|
|
227
|
+
|
|
228
|
+
# Compare part by part
|
|
229
|
+
for p1, p2 in zip(parts1, parts2):
|
|
230
|
+
if p1 < p2:
|
|
231
|
+
return -1
|
|
232
|
+
elif p1 > p2:
|
|
233
|
+
return 1
|
|
234
|
+
|
|
235
|
+
return 0
|
|
236
|
+
|
|
237
|
+
except Exception as e:
|
|
238
|
+
logger.debug(f"Version comparison error: {e}")
|
|
239
|
+
# Fallback to string comparison
|
|
240
|
+
return -1 if version1 < version2 else (1 if version1 > version2 else 0)
|
dexcontrol/utils/pb_utils.py
CHANGED
|
@@ -20,28 +20,6 @@ TYPE_SOFTWARE_VERSION = dict[
|
|
|
20
20
|
]
|
|
21
21
|
|
|
22
22
|
|
|
23
|
-
def software_version_to_dict(
|
|
24
|
-
version_msg: dexcontrol_query_pb2.SoftwareVersion,
|
|
25
|
-
) -> dict[str, TYPE_SOFTWARE_VERSION]:
|
|
26
|
-
"""Convert a SoftwareVersion protobuf message to a dictionary.
|
|
27
|
-
|
|
28
|
-
Args:
|
|
29
|
-
version_msg: SoftwareVersion protobuf message.
|
|
30
|
-
|
|
31
|
-
Returns:
|
|
32
|
-
Dictionary containing version information with component names as keys.
|
|
33
|
-
"""
|
|
34
|
-
return {
|
|
35
|
-
key: {
|
|
36
|
-
"hardware_version": value.hardware_version,
|
|
37
|
-
"software_version": value.software_version,
|
|
38
|
-
"main_hash": value.main_hash,
|
|
39
|
-
"compile_time": value.compile_time,
|
|
40
|
-
}
|
|
41
|
-
for key, value in version_msg.firmware_version.items()
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
|
|
45
23
|
class ComponentStatus(Enum):
|
|
46
24
|
"""Enum representing the status of a component."""
|
|
47
25
|
|
dexcontrol/utils/zenoh_utils.py
CHANGED
|
@@ -10,20 +10,158 @@
|
|
|
10
10
|
|
|
11
11
|
"""Zenoh utilities for dexcontrol.
|
|
12
12
|
|
|
13
|
-
This module provides
|
|
14
|
-
communication framework
|
|
13
|
+
This module provides comprehensive utility functions for working with Zenoh
|
|
14
|
+
communication framework, including session management, configuration loading,
|
|
15
|
+
JSON queries, and statistics computation.
|
|
15
16
|
"""
|
|
16
17
|
|
|
18
|
+
import gc
|
|
17
19
|
import json
|
|
20
|
+
import threading
|
|
18
21
|
import time
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from typing import TYPE_CHECKING
|
|
19
24
|
|
|
20
25
|
import numpy as np
|
|
21
26
|
import zenoh
|
|
22
27
|
from loguru import logger
|
|
28
|
+
from omegaconf import DictConfig, OmegaConf
|
|
23
29
|
|
|
30
|
+
import dexcontrol
|
|
31
|
+
from dexcontrol.config.vega import get_vega_config
|
|
24
32
|
from dexcontrol.utils.os_utils import resolve_key_name
|
|
25
33
|
|
|
34
|
+
if TYPE_CHECKING:
|
|
35
|
+
from dexcontrol.config.vega import VegaConfig
|
|
26
36
|
|
|
37
|
+
|
|
38
|
+
# =============================================================================
|
|
39
|
+
# Session Management Functions
|
|
40
|
+
# =============================================================================
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def get_default_zenoh_config() -> str | None:
|
|
44
|
+
"""Gets the default zenoh configuration file path.
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Path to default config file if it exists, None otherwise.
|
|
48
|
+
"""
|
|
49
|
+
default_path = dexcontrol.COMM_CFG_PATH
|
|
50
|
+
if not default_path.exists():
|
|
51
|
+
logger.warning(f"Zenoh config file not found at {default_path}")
|
|
52
|
+
logger.warning("Please use dextop to set up the zenoh config file")
|
|
53
|
+
return None
|
|
54
|
+
return str(default_path)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def create_zenoh_session(zenoh_config_file: str | None = None) -> zenoh.Session:
|
|
58
|
+
"""Creates and initializes a Zenoh communication session.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
zenoh_config_file: Path to zenoh configuration file. If None,
|
|
62
|
+
uses the default configuration path.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
Initialized zenoh session.
|
|
66
|
+
|
|
67
|
+
Raises:
|
|
68
|
+
RuntimeError: If zenoh session initialization fails.
|
|
69
|
+
"""
|
|
70
|
+
try:
|
|
71
|
+
config_path = zenoh_config_file or get_default_zenoh_config()
|
|
72
|
+
if config_path is None:
|
|
73
|
+
logger.warning("Using default zenoh config settings")
|
|
74
|
+
return zenoh.open(zenoh.Config())
|
|
75
|
+
return zenoh.open(zenoh.Config.from_file(config_path))
|
|
76
|
+
except Exception as e:
|
|
77
|
+
raise RuntimeError(f"Failed to initialize zenoh session: {e}") from e
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def load_robot_config(
|
|
81
|
+
robot_config_path: str | None = None,
|
|
82
|
+
) -> "VegaConfig":
|
|
83
|
+
"""Load robot configuration from file or use default variant.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
robot_config_path: Path to robot configuration file. If None,
|
|
87
|
+
uses default configuration for detected robot model.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
Robot configuration as OmegaConf object.
|
|
91
|
+
|
|
92
|
+
Raises:
|
|
93
|
+
ValueError: If configuration cannot be loaded or parsed.
|
|
94
|
+
"""
|
|
95
|
+
try:
|
|
96
|
+
if robot_config_path is not None:
|
|
97
|
+
# Load custom configuration from file
|
|
98
|
+
config_path = Path(robot_config_path)
|
|
99
|
+
if not config_path.exists():
|
|
100
|
+
raise ValueError(f"Configuration file not found: {config_path}")
|
|
101
|
+
|
|
102
|
+
# Load YAML configuration and merge with default
|
|
103
|
+
base_config = DictConfig, get_vega_config()
|
|
104
|
+
custom_config = OmegaConf.load(config_path)
|
|
105
|
+
return OmegaConf.merge(base_config, custom_config)
|
|
106
|
+
else:
|
|
107
|
+
# Use default configuration for detected robot model
|
|
108
|
+
try:
|
|
109
|
+
return get_vega_config()
|
|
110
|
+
except ValueError as e:
|
|
111
|
+
# If robot model detection fails, use default vega-1 config
|
|
112
|
+
if "Robot name is not set" in str(e):
|
|
113
|
+
logger.warning(
|
|
114
|
+
"Robot model not detected, using default vega-1 configuration"
|
|
115
|
+
)
|
|
116
|
+
return get_vega_config("vega-1")
|
|
117
|
+
raise
|
|
118
|
+
|
|
119
|
+
except Exception as e:
|
|
120
|
+
raise ValueError(f"Failed to load robot configuration: {e}") from e
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def create_standalone_robot_interface(
|
|
124
|
+
zenoh_config_file: str | None = None,
|
|
125
|
+
robot_config_path: str | None = None,
|
|
126
|
+
) -> tuple[zenoh.Session, "VegaConfig"]:
|
|
127
|
+
"""Create standalone zenoh session and robot configuration.
|
|
128
|
+
|
|
129
|
+
This function provides a convenient way to create both a zenoh session
|
|
130
|
+
and robot configuration for use with RobotQueryInterface without
|
|
131
|
+
requiring the full Robot class initialization.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
zenoh_config_file: Path to zenoh configuration file. If None,
|
|
135
|
+
uses the default configuration path.
|
|
136
|
+
robot_config_path: Path to robot configuration file. If None,
|
|
137
|
+
uses default configuration for detected robot model.
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
Tuple of (zenoh_session, robot_config) ready for use with
|
|
141
|
+
RobotQueryInterface.
|
|
142
|
+
|
|
143
|
+
Raises:
|
|
144
|
+
RuntimeError: If zenoh session initialization fails.
|
|
145
|
+
ValueError: If robot configuration cannot be loaded.
|
|
146
|
+
|
|
147
|
+
Example:
|
|
148
|
+
>>> session, config = create_standalone_robot_interface()
|
|
149
|
+
>>> query_interface = RobotQueryInterface(session, config)
|
|
150
|
+
>>> version_info = query_interface.get_version_info()
|
|
151
|
+
>>> session.close()
|
|
152
|
+
"""
|
|
153
|
+
# Create zenoh session
|
|
154
|
+
session = create_zenoh_session(zenoh_config_file)
|
|
155
|
+
|
|
156
|
+
# Load robot configuration
|
|
157
|
+
config = load_robot_config(robot_config_path)
|
|
158
|
+
|
|
159
|
+
return session, config
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
# =============================================================================
|
|
163
|
+
# Query and Communication Functions
|
|
164
|
+
# =============================================================================
|
|
27
165
|
def query_zenoh_json(
|
|
28
166
|
zenoh_session: zenoh.Session,
|
|
29
167
|
topic: str,
|
|
@@ -84,6 +222,115 @@ def query_zenoh_json(
|
|
|
84
222
|
return None
|
|
85
223
|
|
|
86
224
|
|
|
225
|
+
# =============================================================================
|
|
226
|
+
# Cleanup and Exit Handling Functions
|
|
227
|
+
# =============================================================================
|
|
228
|
+
def close_zenoh_session_with_timeout(
|
|
229
|
+
session: zenoh.Session, timeout: float = 2.0
|
|
230
|
+
) -> tuple[bool, Exception | None]:
|
|
231
|
+
"""Close a Zenoh session with timeout handling.
|
|
232
|
+
|
|
233
|
+
This function attempts to close a Zenoh session gracefully with a timeout.
|
|
234
|
+
If the close operation takes too long, it returns with a timeout indication.
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
session: The Zenoh session to close.
|
|
238
|
+
timeout: Maximum time to wait for session close (default 2.0 seconds).
|
|
239
|
+
|
|
240
|
+
Returns:
|
|
241
|
+
Tuple of (success, exception):
|
|
242
|
+
- success: True if session closed successfully, False otherwise
|
|
243
|
+
- exception: Any exception that occurred during close, or None
|
|
244
|
+
"""
|
|
245
|
+
|
|
246
|
+
close_success = False
|
|
247
|
+
close_exception = None
|
|
248
|
+
|
|
249
|
+
def _close_session():
|
|
250
|
+
"""Inner function to close the session."""
|
|
251
|
+
nonlocal close_success, close_exception
|
|
252
|
+
try:
|
|
253
|
+
session.close()
|
|
254
|
+
close_success = True
|
|
255
|
+
except Exception as e: # pylint: disable=broad-except
|
|
256
|
+
close_exception = e
|
|
257
|
+
logger.debug(f"Zenoh session close attempt failed: {e}")
|
|
258
|
+
# Try to trigger garbage collection as fallback
|
|
259
|
+
try:
|
|
260
|
+
gc.collect()
|
|
261
|
+
except Exception: # pylint: disable=broad-except
|
|
262
|
+
pass
|
|
263
|
+
|
|
264
|
+
# Try to close zenoh session with timeout
|
|
265
|
+
close_thread = threading.Thread(target=_close_session, daemon=True)
|
|
266
|
+
close_thread.start()
|
|
267
|
+
|
|
268
|
+
# Use progressive timeout strategy
|
|
269
|
+
timeouts = [timeout / 2, timeout / 2] # Split timeout into two attempts
|
|
270
|
+
for i, wait_time in enumerate(timeouts):
|
|
271
|
+
close_thread.join(timeout=wait_time)
|
|
272
|
+
if not close_thread.is_alive():
|
|
273
|
+
break
|
|
274
|
+
|
|
275
|
+
if close_thread.is_alive():
|
|
276
|
+
return False, Exception("Close operation timed out")
|
|
277
|
+
elif close_success:
|
|
278
|
+
return True, None
|
|
279
|
+
else:
|
|
280
|
+
logger.debug(f"Zenoh session closed with error: {close_exception}")
|
|
281
|
+
return False, close_exception
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def wait_for_zenoh_cleanup(cleanup_delays: list[float] | None = None) -> list[str]:
|
|
285
|
+
"""Wait for Zenoh internal threads to clean up.
|
|
286
|
+
|
|
287
|
+
This function waits for Zenoh's internal pyo3 threads to clean up after
|
|
288
|
+
session closure, using progressive delays to balance responsiveness and
|
|
289
|
+
thoroughness.
|
|
290
|
+
|
|
291
|
+
Args:
|
|
292
|
+
cleanup_delays: List of delays in seconds to wait between checks.
|
|
293
|
+
Defaults to [0.1, 0.2, 0.3] if not provided.
|
|
294
|
+
|
|
295
|
+
Returns:
|
|
296
|
+
List of thread names that are still active after cleanup attempts.
|
|
297
|
+
"""
|
|
298
|
+
if cleanup_delays is None:
|
|
299
|
+
cleanup_delays = [0.1, 0.2, 0.3] # Progressive delays totaling 0.6s
|
|
300
|
+
|
|
301
|
+
for delay in cleanup_delays:
|
|
302
|
+
time.sleep(delay)
|
|
303
|
+
# Check if threads are still active
|
|
304
|
+
active_threads = get_active_zenoh_threads()
|
|
305
|
+
if not active_threads:
|
|
306
|
+
return []
|
|
307
|
+
|
|
308
|
+
# Return any remaining threads
|
|
309
|
+
lingering_threads = get_active_zenoh_threads()
|
|
310
|
+
if lingering_threads:
|
|
311
|
+
logger.debug(
|
|
312
|
+
f"Note: {len(lingering_threads)} Zenoh internal thread(s) still active. "
|
|
313
|
+
"These typically clean up after script exit."
|
|
314
|
+
)
|
|
315
|
+
return lingering_threads
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def get_active_zenoh_threads() -> list[str]:
|
|
319
|
+
"""Get list of active Zenoh (pyo3) threads.
|
|
320
|
+
|
|
321
|
+
Returns:
|
|
322
|
+
List of thread names that are pyo3-related and still active.
|
|
323
|
+
"""
|
|
324
|
+
return [
|
|
325
|
+
t.name
|
|
326
|
+
for t in threading.enumerate()
|
|
327
|
+
if "pyo3" in t.name and t.is_alive() and not t.daemon
|
|
328
|
+
]
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
# =============================================================================
|
|
332
|
+
# Statistics and Analysis Functions
|
|
333
|
+
# =============================================================================
|
|
87
334
|
def compute_ntp_stats(offsets: list[float], rtts: list[float]) -> dict[str, float]:
|
|
88
335
|
"""Compute NTP statistics, removing outliers based on RTT median and std.
|
|
89
336
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dexcontrol
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: A Python library of Sensing and Control for Dexmate's Robot
|
|
5
5
|
Project-URL: Repository, https://github.com/dexmate-ai/dexcontrol
|
|
6
6
|
Author-email: Dexmate <contact@dexmate.ai>
|
|
@@ -253,6 +253,17 @@ To run the examples in this repo, you can try:
|
|
|
253
253
|
pip install dexcontrol[example]
|
|
254
254
|
```
|
|
255
255
|
|
|
256
|
+
## ⚠️ Version Compatibility
|
|
257
|
+
|
|
258
|
+
**Important:** `dexcontrol >= 0.3.0` requires robot firmware with SoC version `286` or higher.
|
|
259
|
+
|
|
260
|
+
**Before upgrading, check your current firmware version:**
|
|
261
|
+
```shell
|
|
262
|
+
dextop firmware info
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
If your firmware is outdated, please update it before installing the new version to ensure full compatibility. Please contact the Dexmate team if you do not know how to do it.
|
|
266
|
+
|
|
256
267
|
## 📄 Licensing
|
|
257
268
|
|
|
258
269
|
This project is **dual-licensed**:
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
dexcontrol/__init__.py,sha256=
|
|
2
|
-
dexcontrol/robot.py,sha256=
|
|
1
|
+
dexcontrol/__init__.py,sha256=Cr2LZw5hPGiwC-Uz7VYgVdLN6AsEmCwTRu_eyKRXrec,1746
|
|
2
|
+
dexcontrol/robot.py,sha256=zvUVgc9hfYrxxD9qsUzEtdAVc59RdE74-yBwUjoy2xw,41385
|
|
3
3
|
dexcontrol/apps/dualsense_teleop_base.py,sha256=Dw1z-2HA5D7DPKutZxlOdXsN9vpk4gS6XzJsL5ZQLM0,12702
|
|
4
4
|
dexcontrol/config/__init__.py,sha256=UVLNpzGD14e8g68rUZFXTh0B7FRx6uS0Eg_MecjinYM,520
|
|
5
|
-
dexcontrol/config/vega.py,sha256=
|
|
5
|
+
dexcontrol/config/vega.py,sha256=Uh14vBOZoMAmFXQgjh-IK9x_W0j8YvqgUsoDNHx1ZsE,8443
|
|
6
6
|
dexcontrol/config/core/__init__.py,sha256=Ym2R1hr1iMKQuXcg16BpZfQtTb0hQ5Q7smUIMlwKfho,637
|
|
7
7
|
dexcontrol/config/core/arm.py,sha256=5hN1dQMe2H6oufaqgtZqx9vuB969DxM26leJqPsKEiA,1471
|
|
8
|
-
dexcontrol/config/core/chassis.py,sha256=
|
|
9
|
-
dexcontrol/config/core/hand.py,sha256=
|
|
8
|
+
dexcontrol/config/core/chassis.py,sha256=2FjyFujg2q7aw8J9BklNM7eeLxscY9BSTlBvz-tLwOM,1092
|
|
9
|
+
dexcontrol/config/core/hand.py,sha256=r6XVyGCuwv7MFmaMLn7l3iPZUH376NZSmtsfLnznAgw,1033
|
|
10
10
|
dexcontrol/config/core/head.py,sha256=SLwZE-lYEOk8XAmW-Ex7VkLF2w5HLItwsA3Dc7n5FtE,1061
|
|
11
11
|
dexcontrol/config/core/misc.py,sha256=zHkJ144b6kbmMFE63wy_fgfo_6V-4XmM19hr6BUtQ0Y,1567
|
|
12
12
|
dexcontrol/config/core/torso.py,sha256=DCTFgN1_Gn4THkKy23sIHOedACQtQ7cET3g4AmkVPco,1460
|
|
@@ -24,17 +24,18 @@ dexcontrol/config/sensors/lidar/rplidar.py,sha256=ybuT_f1ADWF3oGH1gi6D2F80TbJEm4
|
|
|
24
24
|
dexcontrol/config/sensors/ultrasonic/__init__.py,sha256=-q83RhIMZJGVFVPYaA4hOugoG6wZw8EL6wJg7-HTSxU,294
|
|
25
25
|
dexcontrol/config/sensors/ultrasonic/ultrasonic.py,sha256=7b4dm1QOhy5_5RFVpY-frXZyDzqok0K1u7ed9gf3PL0,552
|
|
26
26
|
dexcontrol/core/__init__.py,sha256=bYPMLxbbn5QeuPyA6OPGDS2JTYpnVvaZJT8PeILFjQY,252
|
|
27
|
-
dexcontrol/core/arm.py,sha256=
|
|
28
|
-
dexcontrol/core/chassis.py,sha256=
|
|
29
|
-
dexcontrol/core/component.py,sha256=
|
|
30
|
-
dexcontrol/core/hand.py,sha256=
|
|
31
|
-
dexcontrol/core/head.py,sha256=
|
|
32
|
-
dexcontrol/core/misc.py,sha256=
|
|
33
|
-
dexcontrol/core/
|
|
34
|
-
dexcontrol/
|
|
35
|
-
dexcontrol/proto/dexcontrol_msg_pb2.
|
|
36
|
-
dexcontrol/proto/
|
|
37
|
-
dexcontrol/proto/dexcontrol_query_pb2.
|
|
27
|
+
dexcontrol/core/arm.py,sha256=7kBm5rTcw0K_GmIo22tJ1m03KS5ObYYp_qYjWXLI-z4,16132
|
|
28
|
+
dexcontrol/core/chassis.py,sha256=9XSxu9dpLp027nlXVoU6C_My92f6RRsgKZIh1WwP2Y4,24693
|
|
29
|
+
dexcontrol/core/component.py,sha256=i1T1AOUtzCWOymNkkIUaVAAqVu4CWrsZmaRUoI1V2BI,35713
|
|
30
|
+
dexcontrol/core/hand.py,sha256=iu6aY5bZRi7reXaHpf2EyVaiAi5pUVxCojq-EOUwJiw,9644
|
|
31
|
+
dexcontrol/core/head.py,sha256=NfulFdgCfLBoNn40fb3-Gw7y0OmSAEF4xVWC_3qYHfk,10543
|
|
32
|
+
dexcontrol/core/misc.py,sha256=x7-Fxj_drCGP6deqWZG7MvuXWeRz_f59qSFg1lZJEaU,30406
|
|
33
|
+
dexcontrol/core/robot_query_interface.py,sha256=c395oggoKsfAYAYOQipKBwW5gBCwFXRxqNYdwHY4ZSs,17113
|
|
34
|
+
dexcontrol/core/torso.py,sha256=UlY1T8ENiHoiW9OWGRHfP6viOChg6kIuTNg9aNCioI4,8937
|
|
35
|
+
dexcontrol/proto/dexcontrol_msg_pb2.py,sha256=pIbSlweIVSU3hN13kZxPycySU_OYXPoHRuDh7NIBXho,5289
|
|
36
|
+
dexcontrol/proto/dexcontrol_msg_pb2.pyi,sha256=1KpTJnLq6KG2eijZ_vlFq1JsTPeCeLz1IdmoUhW6Tec,8722
|
|
37
|
+
dexcontrol/proto/dexcontrol_query_pb2.py,sha256=KXPCAaKb2n55MBxmhIaFvAoqbS9XsKD6RdztGNe0occ,6230
|
|
38
|
+
dexcontrol/proto/dexcontrol_query_pb2.pyi,sha256=CGwwceF5Ibt9U3R8x2e7GjVNv2GyuD5WM_nyN4YF2wQ,7822
|
|
38
39
|
dexcontrol/sensors/__init__.py,sha256=3DtVBwX5E3RPPjfCh2elnqk6uIWB_ukIqPQgBPZ7w7g,1008
|
|
39
40
|
dexcontrol/sensors/manager.py,sha256=Qsbs9zZ4TvJgyn8yuI85wOU6mqgBQEus_MYSXj7wA9w,7025
|
|
40
41
|
dexcontrol/sensors/ultrasonic.py,sha256=WAHvHh64iQ0HfqVf-Oo0Rg8R32Cdk5d8k8kDSwa3Xrc,3258
|
|
@@ -52,14 +53,14 @@ dexcontrol/utils/constants.py,sha256=6SG5HoSo7V-DlWH7cdNaMJtZs05dWrqYWIuKpmXfdI0
|
|
|
52
53
|
dexcontrol/utils/error_code.py,sha256=iy840qnWn9wv_nVqyEDP8-l2CuXlPH2xXXW0k-5cHKk,7321
|
|
53
54
|
dexcontrol/utils/io_utils.py,sha256=4TYV33ufECo8fuQivrZR9vtSdwWYUiPvpAUSneEzOOs,850
|
|
54
55
|
dexcontrol/utils/motion_utils.py,sha256=p4kXQm_YorISDC2crrpY0gwCVw_yQCPv-acxPUSfh8w,7172
|
|
55
|
-
dexcontrol/utils/os_utils.py,sha256=
|
|
56
|
-
dexcontrol/utils/pb_utils.py,sha256=
|
|
56
|
+
dexcontrol/utils/os_utils.py,sha256=63sMR6fbd1GgAn6GmmYbSvPfA5dNPRJ-8Yv9VwotRuI,8185
|
|
57
|
+
dexcontrol/utils/pb_utils.py,sha256=zN4pMS9GV9OTj1TmhcWTaDmfmgttyIDFJEuOE5tbCS0,2508
|
|
57
58
|
dexcontrol/utils/rate_limiter.py,sha256=wFNaJ1fh-GO6zItuksKd_DSxLA1esE71WAiNDLpGsU0,6176
|
|
58
59
|
dexcontrol/utils/rtc_utils.py,sha256=o2F9puC7CdAPnqiVq2vzomFZ7hMHljwtAbp9UiLhxJY,4426
|
|
59
60
|
dexcontrol/utils/timer.py,sha256=1sOYYEapbZ5aBqJwknClsxgjDx0FDRQuGEdcTGnYTCI,3948
|
|
60
61
|
dexcontrol/utils/trajectory_utils.py,sha256=TURFb0DeDey0416z4L7AXiWcKJYsgg_bB5AE_JPSpXY,1879
|
|
61
62
|
dexcontrol/utils/viz_utils.py,sha256=rKtZfu32-9D9CS4cSiil-oLub_MiKTJV6hURvJbKd0s,6295
|
|
62
|
-
dexcontrol/utils/zenoh_utils.py,sha256=
|
|
63
|
+
dexcontrol/utils/zenoh_utils.py,sha256=JiwEmCHEDk-EWtoUjIGw-VgYu1mapUSR1S-OcD2R7Ro,13166
|
|
63
64
|
dexcontrol/utils/subscribers/__init__.py,sha256=Sqa-PPElwdUKxdh9BbU5MSqnf_i7BFqANrzVUXYUNuQ,1380
|
|
64
65
|
dexcontrol/utils/subscribers/base.py,sha256=t4zcps_kjFG1wj6WSrZ4HJg0Bxcdnu-C8NkVVupmNVg,9769
|
|
65
66
|
dexcontrol/utils/subscribers/camera.py,sha256=0kaeoKjKCxRQ5iaKK3r_94xEaVxNDBAMd2ZHL6X1kWU,11201
|
|
@@ -69,7 +70,7 @@ dexcontrol/utils/subscribers/imu.py,sha256=Us4ZWzLfqujg16jvrNIoHcvUyxbRCv6mZlaAi
|
|
|
69
70
|
dexcontrol/utils/subscribers/lidar.py,sha256=OlmORIf-dBO4LV4u6pWccoxh-nYTAtGlIfhjj-sG1F4,5691
|
|
70
71
|
dexcontrol/utils/subscribers/protobuf.py,sha256=gtE2b9ZtR2UXftKA5nX7bvTLkj8AeXDYZMqe4B3t5BQ,3696
|
|
71
72
|
dexcontrol/utils/subscribers/rtc.py,sha256=mxD-IIeQwluvq0_D63lQJJEXrJsIc6yxX7uD0PDh08k,11350
|
|
72
|
-
dexcontrol-0.
|
|
73
|
-
dexcontrol-0.
|
|
74
|
-
dexcontrol-0.
|
|
75
|
-
dexcontrol-0.
|
|
73
|
+
dexcontrol-0.3.0.dist-info/METADATA,sha256=S7GeiNE2J1A3YutnF1x98mnAczFc_DqmLOo_3nnOHXo,37239
|
|
74
|
+
dexcontrol-0.3.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
75
|
+
dexcontrol-0.3.0.dist-info/licenses/LICENSE,sha256=0J2KCMNNnW5WZPK5x8xUiCxApBf7h83693ggSJYiue0,31745
|
|
76
|
+
dexcontrol-0.3.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|