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.
- dexcontrol/__init__.py +17 -8
- dexcontrol/apps/dualsense_teleop_base.py +1 -1
- dexcontrol/comm/__init__.py +51 -0
- dexcontrol/comm/rtc.py +401 -0
- dexcontrol/comm/subscribers.py +329 -0
- dexcontrol/config/core/chassis.py +9 -4
- dexcontrol/config/core/hand.py +1 -0
- dexcontrol/config/sensors/cameras/__init__.py +1 -2
- dexcontrol/config/sensors/cameras/zed_camera.py +2 -2
- dexcontrol/config/sensors/vega_sensors.py +12 -18
- dexcontrol/config/vega.py +4 -1
- dexcontrol/core/arm.py +66 -42
- dexcontrol/core/chassis.py +142 -120
- dexcontrol/core/component.py +107 -58
- dexcontrol/core/hand.py +119 -86
- dexcontrol/core/head.py +22 -33
- dexcontrol/core/misc.py +331 -158
- dexcontrol/core/robot_query_interface.py +467 -0
- dexcontrol/core/torso.py +5 -9
- dexcontrol/robot.py +245 -574
- dexcontrol/sensors/__init__.py +1 -2
- dexcontrol/sensors/camera/__init__.py +0 -2
- dexcontrol/sensors/camera/base_camera.py +150 -0
- dexcontrol/sensors/camera/rgb_camera.py +68 -64
- dexcontrol/sensors/camera/zed_camera.py +140 -164
- dexcontrol/sensors/imu/chassis_imu.py +81 -62
- dexcontrol/sensors/imu/zed_imu.py +54 -43
- dexcontrol/sensors/lidar/rplidar.py +16 -20
- dexcontrol/sensors/manager.py +4 -14
- dexcontrol/sensors/ultrasonic.py +15 -28
- dexcontrol/utils/__init__.py +0 -11
- dexcontrol/utils/comm_helper.py +110 -0
- dexcontrol/utils/constants.py +1 -1
- dexcontrol/utils/error_code.py +2 -4
- dexcontrol/utils/os_utils.py +172 -4
- dexcontrol/utils/pb_utils.py +6 -28
- {dexcontrol-0.2.12.dist-info → dexcontrol-0.3.4.dist-info}/METADATA +16 -3
- dexcontrol-0.3.4.dist-info/RECORD +62 -0
- {dexcontrol-0.2.12.dist-info → dexcontrol-0.3.4.dist-info}/WHEEL +1 -1
- dexcontrol/config/sensors/cameras/luxonis_camera.py +0 -51
- dexcontrol/proto/dexcontrol_msg_pb2.py +0 -73
- dexcontrol/proto/dexcontrol_msg_pb2.pyi +0 -220
- dexcontrol/proto/dexcontrol_query_pb2.py +0 -77
- dexcontrol/proto/dexcontrol_query_pb2.pyi +0 -162
- dexcontrol/sensors/camera/luxonis_camera.py +0 -169
- dexcontrol/utils/motion_utils.py +0 -199
- dexcontrol/utils/rate_limiter.py +0 -172
- dexcontrol/utils/rtc_utils.py +0 -144
- dexcontrol/utils/subscribers/__init__.py +0 -52
- dexcontrol/utils/subscribers/base.py +0 -281
- dexcontrol/utils/subscribers/camera.py +0 -332
- dexcontrol/utils/subscribers/decoders.py +0 -88
- dexcontrol/utils/subscribers/generic.py +0 -110
- dexcontrol/utils/subscribers/imu.py +0 -175
- dexcontrol/utils/subscribers/lidar.py +0 -172
- dexcontrol/utils/subscribers/protobuf.py +0 -111
- dexcontrol/utils/subscribers/rtc.py +0 -316
- dexcontrol/utils/zenoh_utils.py +0 -122
- dexcontrol-0.2.12.dist-info/RECORD +0 -75
- {dexcontrol-0.2.12.dist-info → dexcontrol-0.3.4.dist-info}/licenses/LICENSE +0 -0
dexcontrol/sensors/ultrasonic.py
CHANGED
|
@@ -8,21 +8,21 @@
|
|
|
8
8
|
# 2. Commercial License
|
|
9
9
|
# For commercial licensing terms, contact: contact@dexmate.ai
|
|
10
10
|
|
|
11
|
-
"""Ultrasonic sensor implementations using
|
|
11
|
+
"""Ultrasonic sensor implementations using DexComm subscribers.
|
|
12
12
|
|
|
13
|
-
This module provides ultrasonic sensor classes that use
|
|
14
|
-
|
|
13
|
+
This module provides ultrasonic sensor classes that use DexComm's
|
|
14
|
+
Raw API for distance measurements.
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
17
|
import numpy as np
|
|
18
|
-
import
|
|
18
|
+
from dexcomm.serialization import deserialize_protobuf
|
|
19
|
+
from dexcomm.serialization.protobuf import control_msg_pb2
|
|
19
20
|
|
|
20
|
-
from dexcontrol.
|
|
21
|
-
from dexcontrol.utils.subscribers import ProtobufZenohSubscriber
|
|
21
|
+
from dexcontrol.comm import create_subscriber
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
class UltrasonicSensor:
|
|
25
|
-
"""Ultrasonic sensor using
|
|
25
|
+
"""Ultrasonic sensor using DexComm subscriber.
|
|
26
26
|
|
|
27
27
|
This sensor provides distance measurements from ultrasonic sensors
|
|
28
28
|
"""
|
|
@@ -30,24 +30,18 @@ class UltrasonicSensor:
|
|
|
30
30
|
def __init__(
|
|
31
31
|
self,
|
|
32
32
|
configs,
|
|
33
|
-
zenoh_session: zenoh.Session,
|
|
34
33
|
) -> None:
|
|
35
34
|
"""Initialize the ultrasonic sensor.
|
|
36
35
|
|
|
37
36
|
Args:
|
|
38
37
|
configs: Configuration for the ultrasonic sensor.
|
|
39
|
-
zenoh_session: Active Zenoh session for communication.
|
|
40
38
|
"""
|
|
41
39
|
self._name = configs.name
|
|
42
40
|
|
|
43
|
-
# Create the
|
|
44
|
-
self._subscriber =
|
|
41
|
+
# Create the protobuf subscriber using our clean DexComm integration
|
|
42
|
+
self._subscriber = create_subscriber(
|
|
45
43
|
topic=configs.topic,
|
|
46
|
-
|
|
47
|
-
message_type=dexcontrol_msg_pb2.UltrasonicState,
|
|
48
|
-
name=f"{self._name}_subscriber",
|
|
49
|
-
enable_fps_tracking=configs.enable_fps_tracking,
|
|
50
|
-
fps_log_interval=configs.fps_log_interval,
|
|
44
|
+
deserializer=lambda data: deserialize_protobuf(data, control_msg_pb2.UltrasonicState),
|
|
51
45
|
)
|
|
52
46
|
|
|
53
47
|
|
|
@@ -61,7 +55,8 @@ class UltrasonicSensor:
|
|
|
61
55
|
Returns:
|
|
62
56
|
True if receiving data, False otherwise.
|
|
63
57
|
"""
|
|
64
|
-
|
|
58
|
+
data = self._subscriber.get_latest()
|
|
59
|
+
return data is not None
|
|
65
60
|
|
|
66
61
|
def wait_for_active(self, timeout: float = 5.0) -> bool:
|
|
67
62
|
"""Wait for the ultrasonic sensor to start receiving data.
|
|
@@ -72,7 +67,8 @@ class UltrasonicSensor:
|
|
|
72
67
|
Returns:
|
|
73
68
|
True if sensor becomes active, False if timeout is reached.
|
|
74
69
|
"""
|
|
75
|
-
|
|
70
|
+
msg = self._subscriber.wait_for_message(timeout)
|
|
71
|
+
return msg is not None
|
|
76
72
|
|
|
77
73
|
def get_obs(self) -> np.ndarray | None:
|
|
78
74
|
"""Get observation data for the ultrasonic sensor.
|
|
@@ -84,7 +80,7 @@ class UltrasonicSensor:
|
|
|
84
80
|
Numpy array of distances in meters with shape (4,) in the order:
|
|
85
81
|
[front_left, front_right, back_left, back_right].
|
|
86
82
|
"""
|
|
87
|
-
data = self._subscriber.
|
|
83
|
+
data = self._subscriber.get_latest()
|
|
88
84
|
if data is not None:
|
|
89
85
|
obs = [
|
|
90
86
|
data.front_left,
|
|
@@ -96,15 +92,6 @@ class UltrasonicSensor:
|
|
|
96
92
|
|
|
97
93
|
return None
|
|
98
94
|
|
|
99
|
-
@property
|
|
100
|
-
def fps(self) -> float:
|
|
101
|
-
"""Get the current FPS measurement.
|
|
102
|
-
|
|
103
|
-
Returns:
|
|
104
|
-
Current frames per second measurement.
|
|
105
|
-
"""
|
|
106
|
-
return self._subscriber.fps
|
|
107
|
-
|
|
108
95
|
@property
|
|
109
96
|
def name(self) -> str:
|
|
110
97
|
"""Get the sensor name.
|
dexcontrol/utils/__init__.py
CHANGED
|
@@ -7,14 +7,3 @@
|
|
|
7
7
|
#
|
|
8
8
|
# 2. Commercial License
|
|
9
9
|
# For commercial licensing terms, contact: contact@dexmate.ai
|
|
10
|
-
|
|
11
|
-
from .subscribers import (
|
|
12
|
-
BaseZenohSubscriber,
|
|
13
|
-
DecoderFunction,
|
|
14
|
-
GenericZenohSubscriber,
|
|
15
|
-
ProtobufZenohSubscriber,
|
|
16
|
-
json_decoder,
|
|
17
|
-
protobuf_decoder,
|
|
18
|
-
raw_bytes_decoder,
|
|
19
|
-
string_decoder,
|
|
20
|
-
)
|
|
@@ -0,0 +1,110 @@
|
|
|
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
|
+
"""Communication helper utilities for DexControl using DexComm.
|
|
12
|
+
|
|
13
|
+
This module provides simple helper functions for DexControl's communication
|
|
14
|
+
needs using the DexComm library's Raw API.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import time
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
from dexcomm import ZenohConfig, call_service
|
|
22
|
+
from dexcomm.serialization import deserialize_json
|
|
23
|
+
from loguru import logger
|
|
24
|
+
|
|
25
|
+
import dexcontrol
|
|
26
|
+
from dexcontrol.utils.os_utils import resolve_key_name
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def get_robot_config() -> ZenohConfig:
|
|
30
|
+
"""Get DexComm configuration for robot communication.
|
|
31
|
+
|
|
32
|
+
This checks for the robot's Zenoh configuration file and creates
|
|
33
|
+
appropriate DexComm config.
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
ZenohConfig instance configured for the robot.
|
|
37
|
+
"""
|
|
38
|
+
config_path = dexcontrol.COMM_CFG_PATH
|
|
39
|
+
|
|
40
|
+
if config_path and config_path != Path("/tmp/no_config") and config_path.exists():
|
|
41
|
+
logger.debug(f"Loading config from: {config_path}")
|
|
42
|
+
return ZenohConfig.from_file(config_path)
|
|
43
|
+
else:
|
|
44
|
+
logger.debug("No config file found, using default peer mode")
|
|
45
|
+
return ZenohConfig.default_peer()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def get_zenoh_config_path() -> Path | None:
|
|
49
|
+
"""Get robot config only if not using default SessionManager.
|
|
50
|
+
|
|
51
|
+
DexComm's SessionManager will automatically use the config from
|
|
52
|
+
environment variables if available, so we only need to provide
|
|
53
|
+
config explicitly if we have a specific robot config file.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
path to the config file
|
|
57
|
+
"""
|
|
58
|
+
config_path = dexcontrol.COMM_CFG_PATH
|
|
59
|
+
return config_path
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def query_json_service(
|
|
63
|
+
topic: str,
|
|
64
|
+
timeout: float = 2.0,
|
|
65
|
+
max_retries: int = 1,
|
|
66
|
+
retry_delay: float = 0.5,
|
|
67
|
+
) -> dict | None:
|
|
68
|
+
"""Query for JSON information using DexComm with retry logic.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
topic: Topic to query (will be resolved with robot namespace).
|
|
72
|
+
timeout: Maximum time to wait for a response in seconds.
|
|
73
|
+
max_retries: Maximum number of retry attempts.
|
|
74
|
+
retry_delay: Initial delay between retries (doubles each retry).
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
Dictionary containing the parsed JSON response if successful, None otherwise.
|
|
78
|
+
"""
|
|
79
|
+
resolved_topic = resolve_key_name(topic)
|
|
80
|
+
logger.debug(f"Querying topic: {resolved_topic}")
|
|
81
|
+
|
|
82
|
+
current_delay = retry_delay
|
|
83
|
+
for attempt in range(max_retries + 1):
|
|
84
|
+
try:
|
|
85
|
+
data = call_service(
|
|
86
|
+
resolved_topic,
|
|
87
|
+
timeout=timeout,
|
|
88
|
+
config=get_zenoh_config_path(),
|
|
89
|
+
request_serializer=None,
|
|
90
|
+
response_deserializer=deserialize_json,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
if data:
|
|
94
|
+
logger.debug(f"Successfully received JSON data from {resolved_topic}")
|
|
95
|
+
return data
|
|
96
|
+
|
|
97
|
+
except json.JSONDecodeError as e:
|
|
98
|
+
logger.warning(f"Failed to parse JSON response from {resolved_topic}: {e}")
|
|
99
|
+
except Exception as e:
|
|
100
|
+
logger.warning(
|
|
101
|
+
f"Query failed for {resolved_topic} (attempt {attempt + 1}/{max_retries + 1}): {e}"
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
if attempt < max_retries:
|
|
105
|
+
logger.debug(f"Retrying in {current_delay:.1f} seconds...")
|
|
106
|
+
time.sleep(current_delay)
|
|
107
|
+
current_delay *= 2 # Exponential backoff
|
|
108
|
+
|
|
109
|
+
logger.error(f"Failed to query {resolved_topic} after {max_retries + 1} attempts")
|
|
110
|
+
return None
|
dexcontrol/utils/constants.py
CHANGED
|
@@ -16,7 +16,7 @@ from typing import Final
|
|
|
16
16
|
ROBOT_NAME_ENV_VAR: Final[str] = "ROBOT_NAME"
|
|
17
17
|
|
|
18
18
|
# Environment variable for communication config path
|
|
19
|
-
COMM_CFG_PATH_ENV_VAR: Final[str] = "
|
|
19
|
+
COMM_CFG_PATH_ENV_VAR: Final[str] = "ZENOH_CONFIG"
|
|
20
20
|
|
|
21
21
|
# Environment variable to disable heartbeat monitoring
|
|
22
22
|
DISABLE_HEARTBEAT_ENV_VAR: Final[str] = "DEXCONTROL_DISABLE_HEARTBEAT"
|
dexcontrol/utils/error_code.py
CHANGED
|
@@ -10,8 +10,6 @@
|
|
|
10
10
|
|
|
11
11
|
"""Error code interpretation utilities for robot components."""
|
|
12
12
|
|
|
13
|
-
from typing import Dict
|
|
14
|
-
|
|
15
13
|
|
|
16
14
|
class ErrorCodeInterpreter:
|
|
17
15
|
"""Interprets error codes for different robot components."""
|
|
@@ -139,7 +137,7 @@ class ErrorCodeInterpreter:
|
|
|
139
137
|
|
|
140
138
|
@classmethod
|
|
141
139
|
def _interpret_bitmask_errors(
|
|
142
|
-
cls, error_code: int, error_dict:
|
|
140
|
+
cls, error_code: int, error_dict: dict[int, str]
|
|
143
141
|
) -> str:
|
|
144
142
|
"""
|
|
145
143
|
Interpret bitmask-style error codes.
|
|
@@ -220,7 +218,7 @@ def get_error_description(component: str, error_code: int) -> str:
|
|
|
220
218
|
return ErrorCodeInterpreter.interpret_error(component, error_code)
|
|
221
219
|
|
|
222
220
|
|
|
223
|
-
def get_multiple_errors(components_errors:
|
|
221
|
+
def get_multiple_errors(components_errors: dict[str, int]) -> dict[str, str]:
|
|
224
222
|
"""
|
|
225
223
|
Get error descriptions for multiple components.
|
|
226
224
|
|
dexcontrol/utils/os_utils.py
CHANGED
|
@@ -12,8 +12,12 @@
|
|
|
12
12
|
|
|
13
13
|
import os
|
|
14
14
|
import re
|
|
15
|
-
from
|
|
15
|
+
from importlib.metadata import version
|
|
16
|
+
from typing import Any, Final
|
|
16
17
|
|
|
18
|
+
from loguru import logger
|
|
19
|
+
|
|
20
|
+
import dexcontrol
|
|
17
21
|
from dexcontrol.utils.constants import ROBOT_NAME_ENV_VAR
|
|
18
22
|
|
|
19
23
|
|
|
@@ -48,11 +52,175 @@ def get_robot_model() -> str:
|
|
|
48
52
|
raise ValueError(
|
|
49
53
|
f"Robot name is not set, please set the environment variable {ROBOT_NAME_ENV_VAR}"
|
|
50
54
|
)
|
|
51
|
-
if not re.match(r"^dm/[a-zA-Z0-9]{12}-(?:\d
|
|
55
|
+
if not re.match(r"^dm/[a-zA-Z0-9]{12}-(?:\d+p?|rc\d+)$", robot_name):
|
|
52
56
|
raise ValueError(f"Robot name is not in the correct format: {robot_name}")
|
|
53
|
-
|
|
54
57
|
robot_model_abb = robot_name.split("/")[-1].split("-")[0][:2]
|
|
55
58
|
if robot_model_abb not in robot_model_abb_mapping:
|
|
56
59
|
raise ValueError(f"Unknown robot model: {robot_model_abb}")
|
|
57
|
-
model =
|
|
60
|
+
model = (
|
|
61
|
+
robot_model_abb_mapping[robot_model_abb] + "-" + robot_name.split("-")[-1][0]
|
|
62
|
+
)
|
|
58
63
|
return model
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def check_version_compatibility(version_info: dict[str, Any]) -> 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 using importlib.metadata
|
|
97
|
+
current_version = version("dexcontrol")
|
|
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
|
+
soc_info = server_info.get("soc", {})
|
|
141
|
+
software_version = soc_info.get("software_version", {})
|
|
142
|
+
if software_version is not None:
|
|
143
|
+
software_version_int = int(software_version)
|
|
144
|
+
if software_version_int < dexcontrol.MIN_SOC_SOFTWARE_VERSION:
|
|
145
|
+
show_server_version_warning(
|
|
146
|
+
[("soc", software_version_int)], dexcontrol.MIN_SOC_SOFTWARE_VERSION
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def show_server_version_warning(
|
|
151
|
+
components: list[tuple[str, int]], min_version: int
|
|
152
|
+
) -> None:
|
|
153
|
+
"""Display server version warning to user.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
components: List of (component_name, version) tuples for components below minimum.
|
|
157
|
+
min_version: Minimum required server software version.
|
|
158
|
+
"""
|
|
159
|
+
components_str = "\n".join(
|
|
160
|
+
f" - {name}: version {version}" for name, version in components
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
logger.error(
|
|
164
|
+
f"🚨 SERVER VERSION TOO OLD! 🚨\n"
|
|
165
|
+
f"The following server components are below minimum version {min_version}:\n"
|
|
166
|
+
f"{components_str}\n"
|
|
167
|
+
f"\n"
|
|
168
|
+
f"⚠️ Your robot's firmware may be outdated and some features may not work correctly!\n"
|
|
169
|
+
f"📦 Please contact your robot admin or check https://github.com/dexmate-ai/vega-firmware.\n"
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def compare_versions(version1: str, version2: str) -> int:
|
|
174
|
+
"""Compare two semantic version strings.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
version1: First version string (e.g., "1.2.3")
|
|
178
|
+
version2: Second version string (e.g., "1.1.0")
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
-1 if version1 < version2
|
|
182
|
+
0 if version1 == version2
|
|
183
|
+
1 if version1 > version2
|
|
184
|
+
"""
|
|
185
|
+
try:
|
|
186
|
+
# Clean version strings (remove 'v' prefix, handle pre-release suffixes)
|
|
187
|
+
def clean_version(v: str) -> list[int]:
|
|
188
|
+
v = v.strip().lower()
|
|
189
|
+
if v.startswith("v"):
|
|
190
|
+
v = v[1:]
|
|
191
|
+
# Split by dots and take only numeric parts
|
|
192
|
+
parts = v.split(".")
|
|
193
|
+
numeric_parts = []
|
|
194
|
+
for part in parts:
|
|
195
|
+
# Remove any non-numeric suffixes (like -alpha, -rc1, etc.)
|
|
196
|
+
numeric_part = ""
|
|
197
|
+
for char in part:
|
|
198
|
+
if char.isdigit():
|
|
199
|
+
numeric_part += char
|
|
200
|
+
else:
|
|
201
|
+
break
|
|
202
|
+
if numeric_part:
|
|
203
|
+
numeric_parts.append(int(numeric_part))
|
|
204
|
+
return numeric_parts
|
|
205
|
+
|
|
206
|
+
parts1 = clean_version(version1)
|
|
207
|
+
parts2 = clean_version(version2)
|
|
208
|
+
|
|
209
|
+
# Pad shorter version with zeros
|
|
210
|
+
max_len = max(len(parts1), len(parts2))
|
|
211
|
+
parts1.extend([0] * (max_len - len(parts1)))
|
|
212
|
+
parts2.extend([0] * (max_len - len(parts2)))
|
|
213
|
+
|
|
214
|
+
# Compare part by part
|
|
215
|
+
for p1, p2 in zip(parts1, parts2):
|
|
216
|
+
if p1 < p2:
|
|
217
|
+
return -1
|
|
218
|
+
elif p1 > p2:
|
|
219
|
+
return 1
|
|
220
|
+
|
|
221
|
+
return 0
|
|
222
|
+
|
|
223
|
+
except Exception as e:
|
|
224
|
+
logger.debug(f"Version comparison error: {e}")
|
|
225
|
+
# Fallback to string comparison
|
|
226
|
+
return -1 if version1 < version2 else (1 if version1 > version2 else 0)
|
dexcontrol/utils/pb_utils.py
CHANGED
|
@@ -13,35 +13,13 @@
|
|
|
13
13
|
from enum import Enum
|
|
14
14
|
from typing import Any, Literal
|
|
15
15
|
|
|
16
|
-
from
|
|
16
|
+
from dexcomm.serialization.protobuf import control_query_pb2
|
|
17
17
|
|
|
18
18
|
TYPE_SOFTWARE_VERSION = dict[
|
|
19
19
|
Literal["hardware_version", "software_version", "main_hash", "compile_time"], Any
|
|
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
|
|
|
@@ -50,7 +28,7 @@ class ComponentStatus(Enum):
|
|
|
50
28
|
ERROR = 2
|
|
51
29
|
|
|
52
30
|
|
|
53
|
-
def status_to_enum(status:
|
|
31
|
+
def status_to_enum(status: control_query_pb2.ComponentStatus) -> ComponentStatus:
|
|
54
32
|
"""Convert a ComponentStatus protobuf message to a ComponentStatus enum.
|
|
55
33
|
|
|
56
34
|
Args:
|
|
@@ -63,9 +41,9 @@ def status_to_enum(status: dexcontrol_query_pb2.ComponentStatus) -> ComponentSta
|
|
|
63
41
|
ValueError: If the status value is not recognized.
|
|
64
42
|
"""
|
|
65
43
|
status_map = {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
44
|
+
control_query_pb2.ComponentStatus.NORMAL: ComponentStatus.NORMAL,
|
|
45
|
+
control_query_pb2.ComponentStatus.NA: ComponentStatus.NA,
|
|
46
|
+
control_query_pb2.ComponentStatus.ERROR: ComponentStatus.ERROR,
|
|
69
47
|
}
|
|
70
48
|
|
|
71
49
|
if status not in status_map:
|
|
@@ -75,7 +53,7 @@ def status_to_enum(status: dexcontrol_query_pb2.ComponentStatus) -> ComponentSta
|
|
|
75
53
|
|
|
76
54
|
|
|
77
55
|
def status_to_dict(
|
|
78
|
-
status_msg:
|
|
56
|
+
status_msg: control_query_pb2.ComponentStates,
|
|
79
57
|
) -> dict[str, dict[str, Any]]:
|
|
80
58
|
"""Convert a ComponentStates protobuf message to a dictionary.
|
|
81
59
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dexcontrol
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.4
|
|
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>
|
|
@@ -202,13 +202,13 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
202
202
|
Classifier: Typing :: Typed
|
|
203
203
|
Requires-Python: <3.14,>=3.10
|
|
204
204
|
Requires-Dist: aiortc
|
|
205
|
+
Requires-Dist: dexcomm>=0.1.18
|
|
205
206
|
Requires-Dist: eclipse-zenoh>=1.2.0
|
|
206
207
|
Requires-Dist: hydra-core==1.3.2
|
|
207
208
|
Requires-Dist: jaxtyping>=0.2.38
|
|
208
209
|
Requires-Dist: loguru>=0.7.3
|
|
209
210
|
Requires-Dist: numpy>=1.26.4
|
|
210
211
|
Requires-Dist: opencv-python>=4.11.0
|
|
211
|
-
Requires-Dist: protobuf>=6.31.0
|
|
212
212
|
Requires-Dist: rich
|
|
213
213
|
Requires-Dist: uvloop>=0.17.0; sys_platform != 'win32'
|
|
214
214
|
Requires-Dist: websockets
|
|
@@ -231,7 +231,7 @@ Requires-Dist: dexmotion>=0.2.1; extra == 'example'
|
|
|
231
231
|
Requires-Dist: dualsense-controller>=0.3.1; extra == 'example'
|
|
232
232
|
Requires-Dist: matplotlib>=3.8.0; extra == 'example'
|
|
233
233
|
Requires-Dist: pytransform3d==3.13.0; extra == 'example'
|
|
234
|
-
Requires-Dist:
|
|
234
|
+
Requires-Dist: scipy>=1.16.1; extra == 'example'
|
|
235
235
|
Requires-Dist: tyro; extra == 'example'
|
|
236
236
|
Description-Content-Type: text/markdown
|
|
237
237
|
|
|
@@ -253,6 +253,19 @@ 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
|
+
|
|
267
|
+
**📋 See [CHANGELOG.md](./CHANGELOG.md) for detailed release notes and version history.**
|
|
268
|
+
|
|
256
269
|
## 📄 Licensing
|
|
257
270
|
|
|
258
271
|
This project is **dual-licensed**:
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
dexcontrol/__init__.py,sha256=SqdTD3v-qOX6oOGNUcLWEzc5EzY_morOB3TEs-7yv6Y,1955
|
|
2
|
+
dexcontrol/robot.py,sha256=mXezG10UMVBYynE-WD5_XBOjvnjVBhYZjarP_W09TPs,40471
|
|
3
|
+
dexcontrol/apps/dualsense_teleop_base.py,sha256=HJ5aB9zaXdEUQtkd8Vrd0dQqYZrKH1nEjcSGSlDpYD4,12686
|
|
4
|
+
dexcontrol/comm/__init__.py,sha256=YV8Sfv15vuUUqR2lg_H97RCHWUaOblxbI5NgigP4kzc,1272
|
|
5
|
+
dexcontrol/comm/rtc.py,sha256=I5CQz2RuSdE4MyKYraDoQ1E1MSbyYA-rp1kmjxRX9T0,13072
|
|
6
|
+
dexcontrol/comm/subscribers.py,sha256=3rD_YUC5DH3rH_JN8iGjgIkblXcl_pI51dVQ4iQOQQc,8900
|
|
7
|
+
dexcontrol/config/__init__.py,sha256=UVLNpzGD14e8g68rUZFXTh0B7FRx6uS0Eg_MecjinYM,520
|
|
8
|
+
dexcontrol/config/vega.py,sha256=Uh14vBOZoMAmFXQgjh-IK9x_W0j8YvqgUsoDNHx1ZsE,8443
|
|
9
|
+
dexcontrol/config/core/__init__.py,sha256=Ym2R1hr1iMKQuXcg16BpZfQtTb0hQ5Q7smUIMlwKfho,637
|
|
10
|
+
dexcontrol/config/core/arm.py,sha256=5hN1dQMe2H6oufaqgtZqx9vuB969DxM26leJqPsKEiA,1471
|
|
11
|
+
dexcontrol/config/core/chassis.py,sha256=2FjyFujg2q7aw8J9BklNM7eeLxscY9BSTlBvz-tLwOM,1092
|
|
12
|
+
dexcontrol/config/core/hand.py,sha256=r6XVyGCuwv7MFmaMLn7l3iPZUH376NZSmtsfLnznAgw,1033
|
|
13
|
+
dexcontrol/config/core/head.py,sha256=SLwZE-lYEOk8XAmW-Ex7VkLF2w5HLItwsA3Dc7n5FtE,1061
|
|
14
|
+
dexcontrol/config/core/misc.py,sha256=zHkJ144b6kbmMFE63wy_fgfo_6V-4XmM19hr6BUtQ0Y,1567
|
|
15
|
+
dexcontrol/config/core/torso.py,sha256=DCTFgN1_Gn4THkKy23sIHOedACQtQ7cET3g4AmkVPco,1460
|
|
16
|
+
dexcontrol/config/sensors/__init__.py,sha256=bYPMLxbbn5QeuPyA6OPGDS2JTYpnVvaZJT8PeILFjQY,252
|
|
17
|
+
dexcontrol/config/sensors/vega_sensors.py,sha256=m_Z5ywKxGBvP7pUr6dxLNnGEPI2jWt2hDtxln4uFnY8,2675
|
|
18
|
+
dexcontrol/config/sensors/cameras/__init__.py,sha256=GmwRW9ovZ_JcpD2QmzTO_in_LRBoRDorjMVGY6XgGI8,383
|
|
19
|
+
dexcontrol/config/sensors/cameras/rgb_camera.py,sha256=MN4SjyZlfbrQ3JKDDkT8HhC0Aiyc0bWfDLt4ik0Xcvs,1448
|
|
20
|
+
dexcontrol/config/sensors/cameras/zed_camera.py,sha256=UGAjXlOu5E0M3vjMSIOqdvNjK8mhx52OIDgs5RksqUc,1895
|
|
21
|
+
dexcontrol/config/sensors/imu/__init__.py,sha256=fW-DlevCvf_W8HV_fvLe9yIe-XL5op2mggoTKh-6fGQ,328
|
|
22
|
+
dexcontrol/config/sensors/imu/chassis_imu.py,sha256=3OlTTBH6k1QGM5c5bcg8NL3XUXzYA8gCLM8lpCq2KFM,559
|
|
23
|
+
dexcontrol/config/sensors/imu/zed_imu.py,sha256=y-dPI-XS6Kyq0WOf0wwuc2BgVnMN2hwCMxb0Vmwt4O4,550
|
|
24
|
+
dexcontrol/config/sensors/lidar/__init__.py,sha256=j8vFkF675Z7zKtCztJcyG7oSA_XqrD8OeQLEK0GACug,288
|
|
25
|
+
dexcontrol/config/sensors/lidar/rplidar.py,sha256=ybuT_f1ADWF3oGH1gi6D2F80TbJEm4vbm68Fe108OAA,541
|
|
26
|
+
dexcontrol/config/sensors/ultrasonic/__init__.py,sha256=-q83RhIMZJGVFVPYaA4hOugoG6wZw8EL6wJg7-HTSxU,294
|
|
27
|
+
dexcontrol/config/sensors/ultrasonic/ultrasonic.py,sha256=7b4dm1QOhy5_5RFVpY-frXZyDzqok0K1u7ed9gf3PL0,552
|
|
28
|
+
dexcontrol/core/__init__.py,sha256=bYPMLxbbn5QeuPyA6OPGDS2JTYpnVvaZJT8PeILFjQY,252
|
|
29
|
+
dexcontrol/core/arm.py,sha256=iwAFy6C7fxwp8t5ieJNMq1Iulq-OB2L0mzpQCEgl9W0,16211
|
|
30
|
+
dexcontrol/core/chassis.py,sha256=vllwQQB7DUere4Y77qrKhOa_GAckPXmNlW_HfPkz9YE,24188
|
|
31
|
+
dexcontrol/core/component.py,sha256=qXhGsNwa9EHO5DaGybylgFoGJmUUppNznH5JHmH08sQ,36296
|
|
32
|
+
dexcontrol/core/hand.py,sha256=P_UGAqT71j_9k_wP_YQZep_cU5bR0loIxdRb3IH42w8,9595
|
|
33
|
+
dexcontrol/core/head.py,sha256=axqxiqdec9TDpYCt9_QSvhEu2Dy3bf2n196FGkpt710,9925
|
|
34
|
+
dexcontrol/core/misc.py,sha256=AjOgLRSvQqxGC6VsJiewtkOchzawpqW3VZ7eTrNt8iI,30867
|
|
35
|
+
dexcontrol/core/robot_query_interface.py,sha256=IiIpPNQ9uMopuo0z4m0ErrQtbY8RtwBOkj4dLPXOLlM,17628
|
|
36
|
+
dexcontrol/core/torso.py,sha256=XSNu5O8DmbtBVVcJtGr6JFdYrs8vgNFkJkK1gAC43II,8767
|
|
37
|
+
dexcontrol/sensors/__init__.py,sha256=Dp06cuO_3xC6i4u5rHqfK5NqlIC5kaCue_bAtTC6JEE,960
|
|
38
|
+
dexcontrol/sensors/manager.py,sha256=uhP_matbXMzG_HQv9fmcGbw_rm9xloMoKUeNe3KBmTU,6590
|
|
39
|
+
dexcontrol/sensors/ultrasonic.py,sha256=uFiPl0js49PFcx9yLSUesFdm1U9IsR8KCtoDtEn1E6k,2890
|
|
40
|
+
dexcontrol/sensors/camera/__init__.py,sha256=Vwe98I4Lvdv3F2UslOzKkeUkt5Rl2jSqbKlU6gIBeF0,609
|
|
41
|
+
dexcontrol/sensors/camera/base_camera.py,sha256=flmpoLP8rC-NZg7TFHFLqXJNHRT45jwqsVPk-L1G6oc,4808
|
|
42
|
+
dexcontrol/sensors/camera/rgb_camera.py,sha256=Mcd51BXOBatfK5fcIjsNuJsClXhdnVR2ck-wgyJYKnQ,6363
|
|
43
|
+
dexcontrol/sensors/camera/zed_camera.py,sha256=BYLAlxuHSZKTVpHMP2eKwWfbHOxWXhT18VG3aXAN0sY,14164
|
|
44
|
+
dexcontrol/sensors/imu/__init__.py,sha256=bBC7_NSLJ5qLMvUYu2-9yXKO2bRpQLC0HyywBwnbM0A,768
|
|
45
|
+
dexcontrol/sensors/imu/chassis_imu.py,sha256=RzaY6VhhGEjzrFNxDcXL43SdIwoozW5PctKdUxfc_2k,5565
|
|
46
|
+
dexcontrol/sensors/imu/zed_imu.py,sha256=rGT0S2TIj0JGdlJMQKNldtCclOYunCmS0xdzvjAhq9A,5628
|
|
47
|
+
dexcontrol/sensors/lidar/__init__.py,sha256=frF16HmeQnfbvH0dVJ4pPjD4TySF13wCk-O9L3Memeg,317
|
|
48
|
+
dexcontrol/sensors/lidar/rplidar.py,sha256=MrmxP99-YsfoRlYUZTCcmvNbF-YCOLynhoe2tCMpDnE,4191
|
|
49
|
+
dexcontrol/utils/__init__.py,sha256=bYPMLxbbn5QeuPyA6OPGDS2JTYpnVvaZJT8PeILFjQY,252
|
|
50
|
+
dexcontrol/utils/comm_helper.py,sha256=PDB_Qyo39BdtR0d5-k0zPkAq6nwGV9eGMMXo3i7CFDw,3541
|
|
51
|
+
dexcontrol/utils/constants.py,sha256=uzR6AW5LNU3Otmf0G8M4yg-0iaHLhKtJ5e-OPuqxr9g,783
|
|
52
|
+
dexcontrol/utils/error_code.py,sha256=MQJHfM76YgRmE_7rAj0qSSHQf9P71oRUBa50m-3-Pwc,7296
|
|
53
|
+
dexcontrol/utils/io_utils.py,sha256=4TYV33ufECo8fuQivrZR9vtSdwWYUiPvpAUSneEzOOs,850
|
|
54
|
+
dexcontrol/utils/os_utils.py,sha256=LBye18I-I7mDYuTf_zDB5k-KQAx7Pt11ibgyH5F36mc,7586
|
|
55
|
+
dexcontrol/utils/pb_utils.py,sha256=fMJJQgfCPtGlij0KdLHnPjElW0_DCwPpuSM2VMqhrK4,2504
|
|
56
|
+
dexcontrol/utils/timer.py,sha256=1sOYYEapbZ5aBqJwknClsxgjDx0FDRQuGEdcTGnYTCI,3948
|
|
57
|
+
dexcontrol/utils/trajectory_utils.py,sha256=TURFb0DeDey0416z4L7AXiWcKJYsgg_bB5AE_JPSpXY,1879
|
|
58
|
+
dexcontrol/utils/viz_utils.py,sha256=rKtZfu32-9D9CS4cSiil-oLub_MiKTJV6hURvJbKd0s,6295
|
|
59
|
+
dexcontrol-0.3.4.dist-info/METADATA,sha256=THlzSihnxku-i8wXM0q8fFUeITImxjX8KNv2lvI6D6g,37338
|
|
60
|
+
dexcontrol-0.3.4.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
61
|
+
dexcontrol-0.3.4.dist-info/licenses/LICENSE,sha256=0J2KCMNNnW5WZPK5x8xUiCxApBf7h83693ggSJYiue0,31745
|
|
62
|
+
dexcontrol-0.3.4.dist-info/RECORD,,
|