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/__init__.py
CHANGED
|
@@ -30,9 +30,10 @@ from dexcontrol.robot import Robot
|
|
|
30
30
|
from dexcontrol.utils.constants import COMM_CFG_PATH_ENV_VAR
|
|
31
31
|
|
|
32
32
|
# Package-level constants
|
|
33
|
+
__version__: str = "0.3.0" # Current library version
|
|
33
34
|
LIB_PATH: Final[Path] = Path(__file__).resolve().parent
|
|
34
35
|
CFG_PATH: Final[Path] = LIB_PATH / "config"
|
|
35
|
-
MIN_SOC_SOFTWARE_VERSION: int =
|
|
36
|
+
MIN_SOC_SOFTWARE_VERSION: int = 286
|
|
36
37
|
|
|
37
38
|
logger.configure(handlers=[{"sink": RichHandler(markup=True), "format": "{message}"}])
|
|
38
39
|
|
|
@@ -14,14 +14,19 @@ from dataclasses import dataclass, field
|
|
|
14
14
|
@dataclass
|
|
15
15
|
class ChassisConfig:
|
|
16
16
|
_target_: str = "dexcontrol.core.chassis.Chassis"
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
steer_control_pub_topic: str = "control/chassis/steer"
|
|
18
|
+
steer_state_sub_topic: str = "state/chassis/steer"
|
|
19
|
+
drive_control_pub_topic: str = "control/chassis/drive"
|
|
20
|
+
drive_state_sub_topic: str = "state/chassis/drive"
|
|
19
21
|
dof: int = 2
|
|
20
22
|
center_to_wheel_axis_dist: float = (
|
|
21
23
|
0.219 # the distance between base center and wheel axis in m
|
|
22
24
|
)
|
|
23
25
|
wheels_dist: float = 0.45 # the distance between two wheels in m (0.41 for vega-rc2, 0.45 for vega-1)
|
|
24
|
-
|
|
25
|
-
default_factory=lambda: ["L_wheel_j1", "
|
|
26
|
+
steer_joint_name: list[str] = field(
|
|
27
|
+
default_factory=lambda: ["L_wheel_j1", "R_wheel_j1"]
|
|
28
|
+
)
|
|
29
|
+
drive_joint_name: list[str] = field(
|
|
30
|
+
default_factory=lambda: ["L_wheel_j2", "R_wheel_j2"]
|
|
26
31
|
)
|
|
27
32
|
max_vel: float = 0.8
|
dexcontrol/config/core/hand.py
CHANGED
|
@@ -16,6 +16,7 @@ class HandConfig:
|
|
|
16
16
|
_target_: str = "dexcontrol.core.hand.HandF5D6"
|
|
17
17
|
state_sub_topic: str = "state/hand/right"
|
|
18
18
|
control_pub_topic: str = "control/hand/right"
|
|
19
|
+
touch_sensor_sub_topic: str = "state/hand/right/touch" # Only for V2 hand
|
|
19
20
|
dof: int = 6
|
|
20
21
|
joint_name: list[str] = field(
|
|
21
22
|
default_factory=lambda: [
|
dexcontrol/config/vega.py
CHANGED
|
@@ -77,6 +77,7 @@ class VegaConfig:
|
|
|
77
77
|
default_factory=lambda: HandConfig(
|
|
78
78
|
state_sub_topic="state/hand/left",
|
|
79
79
|
control_pub_topic="control/hand/left",
|
|
80
|
+
touch_sensor_sub_topic="state/hand/left/touch",
|
|
80
81
|
joint_name=[
|
|
81
82
|
"L_th_j1",
|
|
82
83
|
"L_ff_j1",
|
|
@@ -91,6 +92,7 @@ class VegaConfig:
|
|
|
91
92
|
default_factory=lambda: HandConfig(
|
|
92
93
|
state_sub_topic="state/hand/right",
|
|
93
94
|
control_pub_topic="control/hand/right",
|
|
95
|
+
touch_sensor_sub_topic="state/hand/right/touch",
|
|
94
96
|
joint_name=[
|
|
95
97
|
"R_th_j1",
|
|
96
98
|
"R_ff_j1",
|
|
@@ -111,8 +113,9 @@ class VegaConfig:
|
|
|
111
113
|
heartbeat: HeartbeatConfig = field(default_factory=HeartbeatConfig)
|
|
112
114
|
|
|
113
115
|
# Queries
|
|
114
|
-
version_info_name: str = "info/
|
|
116
|
+
version_info_name: str = "info/versions" # Updated to use JSON interface
|
|
115
117
|
status_info_name: str = "info/status"
|
|
118
|
+
hand_info_query_name: str = "info/hand_type"
|
|
116
119
|
reboot_query_name: str = "system/reboot"
|
|
117
120
|
clear_error_query_name: str = "system/clear_error"
|
|
118
121
|
led_query_name: str = "system/led"
|
dexcontrol/core/arm.py
CHANGED
|
@@ -55,7 +55,7 @@ class Arm(RobotJointComponent):
|
|
|
55
55
|
super().__init__(
|
|
56
56
|
state_sub_topic=configs.state_sub_topic,
|
|
57
57
|
control_pub_topic=configs.control_pub_topic,
|
|
58
|
-
state_message_type=dexcontrol_msg_pb2.
|
|
58
|
+
state_message_type=dexcontrol_msg_pb2.MotorStateWithCurrent,
|
|
59
59
|
zenoh_session=zenoh_session,
|
|
60
60
|
joint_name=configs.joint_name,
|
|
61
61
|
joint_limit=configs.joint_limit
|
|
@@ -97,6 +97,9 @@ class Arm(RobotJointComponent):
|
|
|
97
97
|
def set_mode(self, mode: Literal["position", "disable"]) -> None:
|
|
98
98
|
"""Sets the operating mode of the arm.
|
|
99
99
|
|
|
100
|
+
.. deprecated::
|
|
101
|
+
Use set_modes() instead for setting arm modes.
|
|
102
|
+
|
|
100
103
|
Args:
|
|
101
104
|
mode: Operating mode for the arm. Must be either "position" or "disable".
|
|
102
105
|
"position": Enable position control
|
|
@@ -105,17 +108,35 @@ class Arm(RobotJointComponent):
|
|
|
105
108
|
Raises:
|
|
106
109
|
ValueError: If an invalid mode is specified.
|
|
107
110
|
"""
|
|
111
|
+
logger.warning("arm.set_mode() is deprecated, use set_modes() instead")
|
|
112
|
+
self.set_modes([mode] * 7)
|
|
113
|
+
|
|
114
|
+
def set_modes(self, modes: list[Literal["position", "disable", "release"]]) -> None:
|
|
115
|
+
"""Sets the operating modes of the arm.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
modes: List of operating modes for the arm. Each mode must be either "position", "disable", or "current".
|
|
119
|
+
|
|
120
|
+
Raises:
|
|
121
|
+
ValueError: If any mode in the list is invalid.
|
|
122
|
+
"""
|
|
108
123
|
mode_map = {
|
|
109
124
|
"position": dexcontrol_query_pb2.SetArmMode.Mode.POSITION,
|
|
110
125
|
"disable": dexcontrol_query_pb2.SetArmMode.Mode.DISABLE,
|
|
126
|
+
"release": dexcontrol_query_pb2.SetArmMode.Mode.CURRENT,
|
|
111
127
|
}
|
|
112
128
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
129
|
+
for mode in modes:
|
|
130
|
+
if mode not in mode_map:
|
|
131
|
+
raise ValueError(
|
|
132
|
+
f"Invalid mode: {mode}. Must be one of {list(mode_map.keys())}"
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
if len(modes) != 7:
|
|
136
|
+
raise ValueError("Arm modes length must match arm DoF (7).")
|
|
117
137
|
|
|
118
|
-
|
|
138
|
+
converted_modes = [mode_map[mode] for mode in modes]
|
|
139
|
+
query_msg = dexcontrol_query_pb2.SetArmMode(modes=converted_modes)
|
|
119
140
|
self.mode_querier.get(payload=query_msg.SerializeToString())
|
|
120
141
|
|
|
121
142
|
def _send_position_command(self, joint_pos: np.ndarray) -> None:
|
|
@@ -124,8 +145,8 @@ class Arm(RobotJointComponent):
|
|
|
124
145
|
Args:
|
|
125
146
|
joint_pos: Joint positions as numpy array.
|
|
126
147
|
"""
|
|
127
|
-
control_msg = dexcontrol_msg_pb2.
|
|
128
|
-
control_msg.
|
|
148
|
+
control_msg = dexcontrol_msg_pb2.MotorPosVelCurrentCommand()
|
|
149
|
+
control_msg.pos.extend(joint_pos.tolist())
|
|
129
150
|
self._publish_control(control_msg)
|
|
130
151
|
|
|
131
152
|
def set_joint_pos(
|
|
@@ -287,10 +308,9 @@ class Arm(RobotJointComponent):
|
|
|
287
308
|
joint_vel, clip_value=self._joint_vel_limit
|
|
288
309
|
)
|
|
289
310
|
|
|
290
|
-
control_msg = dexcontrol_msg_pb2.
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
joint_vel=list(target_vel),
|
|
311
|
+
control_msg = dexcontrol_msg_pb2.MotorPosVelCurrentCommand(
|
|
312
|
+
pos=list(target_pos),
|
|
313
|
+
vel=list(target_vel),
|
|
294
314
|
)
|
|
295
315
|
self._publish_control(control_msg)
|
|
296
316
|
|
dexcontrol/core/chassis.py
CHANGED
|
@@ -18,6 +18,7 @@ import time
|
|
|
18
18
|
|
|
19
19
|
import numpy as np
|
|
20
20
|
import zenoh
|
|
21
|
+
from jaxtyping import Float
|
|
21
22
|
|
|
22
23
|
from dexcontrol.config.core import ChassisConfig
|
|
23
24
|
from dexcontrol.core.component import RobotJointComponent
|
|
@@ -25,7 +26,88 @@ from dexcontrol.proto import dexcontrol_msg_pb2
|
|
|
25
26
|
from dexcontrol.utils.rate_limiter import RateLimiter
|
|
26
27
|
|
|
27
28
|
|
|
28
|
-
class
|
|
29
|
+
class ChassisSteer(RobotJointComponent):
|
|
30
|
+
"""Robot hand control class.
|
|
31
|
+
|
|
32
|
+
This class provides methods to control a robotic hand by publishing commands and
|
|
33
|
+
receiving state information through Zenoh communication.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
configs: ChassisConfig,
|
|
39
|
+
zenoh_session: zenoh.Session,
|
|
40
|
+
) -> None:
|
|
41
|
+
"""Initialize the hand controller.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
configs: Hand configuration parameters containing communication topics
|
|
45
|
+
and predefined hand positions.
|
|
46
|
+
zenoh_session: Active Zenoh communication session for message passing.
|
|
47
|
+
"""
|
|
48
|
+
super().__init__(
|
|
49
|
+
state_sub_topic=configs.steer_state_sub_topic,
|
|
50
|
+
control_pub_topic=configs.steer_control_pub_topic,
|
|
51
|
+
state_message_type=dexcontrol_msg_pb2.MotorStateWithCurrent,
|
|
52
|
+
zenoh_session=zenoh_session,
|
|
53
|
+
joint_name=configs.steer_joint_name,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
def _send_position_command(
|
|
57
|
+
self, joint_pos: Float[np.ndarray, " 2"] | list[float]
|
|
58
|
+
) -> None:
|
|
59
|
+
"""Send joint position control commands to the hand.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
joint_pos: Joint positions as list or numpy array.
|
|
63
|
+
"""
|
|
64
|
+
control_msg = dexcontrol_msg_pb2.MotorPosVelCommand()
|
|
65
|
+
joint_pos_array = self._convert_joint_cmd_to_array(joint_pos)
|
|
66
|
+
control_msg.pos.extend(joint_pos_array.tolist())
|
|
67
|
+
self._publish_control(control_msg)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class ChassisDrive(RobotJointComponent):
|
|
71
|
+
"""Robot base control class.
|
|
72
|
+
|
|
73
|
+
This class provides methods to control a robot's wheeled base by publishing commands
|
|
74
|
+
and receiving state information through Zenoh communication.
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
def __init__(
|
|
78
|
+
self,
|
|
79
|
+
configs: ChassisConfig,
|
|
80
|
+
zenoh_session: zenoh.Session,
|
|
81
|
+
) -> None:
|
|
82
|
+
"""Initialize the base controller.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
configs: Base configuration parameters containing communication topics.
|
|
86
|
+
zenoh_session: Active Zenoh communication session for message passing.
|
|
87
|
+
"""
|
|
88
|
+
super().__init__(
|
|
89
|
+
state_sub_topic=configs.drive_state_sub_topic,
|
|
90
|
+
control_pub_topic=configs.drive_control_pub_topic,
|
|
91
|
+
state_message_type=dexcontrol_msg_pb2.MotorStateWithCurrent,
|
|
92
|
+
zenoh_session=zenoh_session,
|
|
93
|
+
joint_name=configs.drive_joint_name,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
def _send_position_command(
|
|
97
|
+
self, joint_pos: Float[np.ndarray, " 2"] | list[float]
|
|
98
|
+
) -> None:
|
|
99
|
+
raise NotImplementedError("ChassisDrive does not support position control.")
|
|
100
|
+
|
|
101
|
+
def _send_velocity_command(
|
|
102
|
+
self, joint_vel: Float[np.ndarray, " 2"] | list[float]
|
|
103
|
+
) -> None:
|
|
104
|
+
control_msg = dexcontrol_msg_pb2.MotorVelCommand()
|
|
105
|
+
joint_vel_array = self._convert_joint_cmd_to_array(joint_vel)
|
|
106
|
+
control_msg.vel.extend(joint_vel_array.tolist())
|
|
107
|
+
self._publish_control(control_msg)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class Chassis:
|
|
29
111
|
"""Robot base control class.
|
|
30
112
|
|
|
31
113
|
This class provides methods to control a robot's wheeled base by publishing commands
|
|
@@ -52,13 +134,9 @@ class Chassis(RobotJointComponent):
|
|
|
52
134
|
configs: Base configuration parameters containing communication topics.
|
|
53
135
|
zenoh_session: Active Zenoh communication session for message passing.
|
|
54
136
|
"""
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
state_message_type=dexcontrol_msg_pb2.ChassisState,
|
|
59
|
-
zenoh_session=zenoh_session,
|
|
60
|
-
joint_name=configs.joint_name,
|
|
61
|
-
)
|
|
137
|
+
self.chassis_steer = ChassisSteer(configs, zenoh_session)
|
|
138
|
+
self.chassis_drive = ChassisDrive(configs, zenoh_session)
|
|
139
|
+
|
|
62
140
|
self.max_vel = configs.max_vel
|
|
63
141
|
self._center_to_wheel_axis_dist = configs.center_to_wheel_axis_dist
|
|
64
142
|
self._wheels_dist = configs.wheels_dist
|
|
@@ -96,7 +174,8 @@ class Chassis(RobotJointComponent):
|
|
|
96
174
|
def shutdown(self) -> None:
|
|
97
175
|
"""Shutdown the base by stopping all motion."""
|
|
98
176
|
self.stop()
|
|
99
|
-
|
|
177
|
+
self.chassis_steer.shutdown()
|
|
178
|
+
self.chassis_drive.shutdown()
|
|
100
179
|
|
|
101
180
|
@property
|
|
102
181
|
def steering_angle(self) -> np.ndarray:
|
|
@@ -106,8 +185,7 @@ class Chassis(RobotJointComponent):
|
|
|
106
185
|
Numpy array of shape (2,) containing current steering angles in radians.
|
|
107
186
|
Index 0 is left wheel, index 1 is right wheel.
|
|
108
187
|
"""
|
|
109
|
-
|
|
110
|
-
return np.array([state.left.steering_pos, state.right.steering_pos])
|
|
188
|
+
return self.chassis_steer.get_joint_pos()
|
|
111
189
|
|
|
112
190
|
@property
|
|
113
191
|
def wheel_velocity(self) -> np.ndarray:
|
|
@@ -117,8 +195,17 @@ class Chassis(RobotJointComponent):
|
|
|
117
195
|
Numpy array of shape (2,) containing current wheel velocities in m/s.
|
|
118
196
|
Index 0 is left wheel, index 1 is right wheel.
|
|
119
197
|
"""
|
|
120
|
-
|
|
121
|
-
|
|
198
|
+
return self.chassis_drive.get_joint_vel()
|
|
199
|
+
|
|
200
|
+
@property
|
|
201
|
+
def wheel_encoder_pos(self) -> np.ndarray:
|
|
202
|
+
"""Get current wheel encoder positions.
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
Numpy array of shape (2,) containing current wheel encoder positions in radians.
|
|
206
|
+
Index 0 is left wheel, index 1 is right wheel.
|
|
207
|
+
"""
|
|
208
|
+
return self.chassis_drive.get_joint_pos()
|
|
122
209
|
|
|
123
210
|
def set_velocity(
|
|
124
211
|
self,
|
|
@@ -356,24 +443,23 @@ class Chassis(RobotJointComponent):
|
|
|
356
443
|
Args:
|
|
357
444
|
joint_id: Optional ID(s) of specific joints to query.
|
|
358
445
|
0: left steering position
|
|
359
|
-
1:
|
|
360
|
-
2:
|
|
446
|
+
1: right steering position
|
|
447
|
+
2: left wheel position
|
|
361
448
|
3: right wheel position
|
|
362
449
|
|
|
363
450
|
Returns:
|
|
364
451
|
Array of joint positions in the order:
|
|
365
|
-
[left_steering_pos,
|
|
452
|
+
[left_steering_pos, right_steering_pos, left_wheel_pos, right_wheel_pos]
|
|
366
453
|
"""
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
return self._extract_joint_info(joint_pos, joint_id=joint_id)
|
|
454
|
+
steer_pos = self.chassis_steer.get_joint_pos()
|
|
455
|
+
drive_pos = self.chassis_drive.get_joint_pos()
|
|
456
|
+
joint_pos = np.concatenate([steer_pos, drive_pos])
|
|
457
|
+
return RobotJointComponent._extract_joint_info(joint_pos, joint_id=joint_id)
|
|
458
|
+
|
|
459
|
+
@property
|
|
460
|
+
def joint_name(self) -> list[str]:
|
|
461
|
+
"""Get the joint names of the chassis."""
|
|
462
|
+
return self.chassis_steer.joint_name + self.chassis_drive.joint_name
|
|
377
463
|
|
|
378
464
|
def get_joint_pos_dict(
|
|
379
465
|
self, joint_id: list[int] | int | None = None
|
|
@@ -383,58 +469,22 @@ class Chassis(RobotJointComponent):
|
|
|
383
469
|
Args:
|
|
384
470
|
joint_id: Optional ID(s) of specific joints to query.
|
|
385
471
|
0: left steering position
|
|
386
|
-
1:
|
|
387
|
-
2:
|
|
472
|
+
1: right steering position
|
|
473
|
+
2: left wheel position
|
|
388
474
|
3: right wheel position
|
|
389
475
|
|
|
390
476
|
Returns:
|
|
391
477
|
Dictionary mapping joint names to position values.
|
|
392
478
|
"""
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
1: right wheel
|
|
403
|
-
|
|
404
|
-
Returns:
|
|
405
|
-
Numpy array containing:
|
|
406
|
-
[:, 0]: Current steering positions in radians
|
|
407
|
-
[:, 1]: Current wheel positions in radians
|
|
408
|
-
[:, 2]: Current wheel velocities in m/s
|
|
409
|
-
"""
|
|
410
|
-
state = self._get_state()
|
|
411
|
-
wheel_states = np.array(
|
|
412
|
-
[
|
|
413
|
-
[state.left.steering_pos, state.left.wheel_pos, state.left.wheel_vel],
|
|
414
|
-
[
|
|
415
|
-
state.right.steering_pos,
|
|
416
|
-
state.right.wheel_pos,
|
|
417
|
-
state.right.wheel_vel,
|
|
418
|
-
],
|
|
419
|
-
]
|
|
420
|
-
)
|
|
421
|
-
return self._extract_joint_info(wheel_states, joint_id=joint_id)
|
|
422
|
-
|
|
423
|
-
def get_joint_state_dict(
|
|
424
|
-
self, joint_id: list[int] | int | None = None
|
|
425
|
-
) -> dict[str, np.ndarray]:
|
|
426
|
-
"""Get current wheel states as a dictionary.
|
|
427
|
-
|
|
428
|
-
Args:
|
|
429
|
-
joint_id: Optional ID(s) of specific joints to query.
|
|
430
|
-
0: left wheel
|
|
431
|
-
1: right wheel
|
|
432
|
-
|
|
433
|
-
Returns:
|
|
434
|
-
Dictionary mapping joint names to arrays of [steering_pos, wheel_pos, wheel_vel]
|
|
435
|
-
"""
|
|
436
|
-
values = self.get_joint_state(joint_id)
|
|
437
|
-
return self._convert_to_dict(values, joint_id)
|
|
479
|
+
steer_pos = self.chassis_steer.get_joint_pos_dict()
|
|
480
|
+
drive_pos = self.chassis_drive.get_joint_pos_dict()
|
|
481
|
+
if joint_id is None:
|
|
482
|
+
return {**steer_pos, **drive_pos}
|
|
483
|
+
else:
|
|
484
|
+
joint_names = self.joint_name
|
|
485
|
+
if isinstance(joint_id, int):
|
|
486
|
+
joint_id = [joint_id]
|
|
487
|
+
return {joint_names[i]: steer_pos[joint_names[i]] for i in joint_id}
|
|
438
488
|
|
|
439
489
|
def set_motion_state(
|
|
440
490
|
self,
|
|
@@ -527,10 +577,19 @@ class Chassis(RobotJointComponent):
|
|
|
527
577
|
# Set default control frequency if not provided
|
|
528
578
|
control_hz = wait_kwargs.get("control_hz", 50)
|
|
529
579
|
rate_limiter = RateLimiter(control_hz)
|
|
530
|
-
start_time = time.time()
|
|
531
580
|
|
|
532
|
-
|
|
581
|
+
now_sec = time.monotonic()
|
|
582
|
+
duration_sec = max(wait_time - 1.0, 0.0)
|
|
583
|
+
end_time_sec = now_sec + duration_sec
|
|
584
|
+
|
|
585
|
+
while True:
|
|
586
|
+
# Ensure command is sent at least once
|
|
533
587
|
self._send_single_command(steering_angle, wheel_velocity)
|
|
588
|
+
|
|
589
|
+
# Exit if we've reached the allotted duration
|
|
590
|
+
if time.monotonic() >= end_time_sec:
|
|
591
|
+
break
|
|
592
|
+
|
|
534
593
|
rate_limiter.sleep()
|
|
535
594
|
|
|
536
595
|
def _send_single_command(
|
|
@@ -544,24 +603,16 @@ class Chassis(RobotJointComponent):
|
|
|
544
603
|
steering_angle: Target steering angles.
|
|
545
604
|
wheel_velocity: Target wheel velocities.
|
|
546
605
|
"""
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
steering_angle, 0, "L_wheel_j1", state.left.steering_pos
|
|
553
|
-
)
|
|
554
|
-
control_msg.right.steering_pos = _get_value(
|
|
555
|
-
steering_angle, 1, "R_wheel_j1", state.right.steering_pos
|
|
556
|
-
)
|
|
557
|
-
control_msg.left.wheel_vel = _get_value(
|
|
558
|
-
wheel_velocity, 0, "L_wheel_j2", state.left.wheel_vel
|
|
559
|
-
)
|
|
560
|
-
control_msg.right.wheel_vel = _get_value(
|
|
561
|
-
wheel_velocity, 1, "R_wheel_j2", state.right.wheel_vel
|
|
562
|
-
)
|
|
606
|
+
if isinstance(steering_angle, float):
|
|
607
|
+
steering_angle = np.array([steering_angle, steering_angle])
|
|
608
|
+
self.chassis_steer._send_position_command(steering_angle)
|
|
609
|
+
elif isinstance(steering_angle, np.ndarray):
|
|
610
|
+
self.chassis_steer._send_position_command(steering_angle)
|
|
563
611
|
|
|
564
|
-
|
|
612
|
+
if isinstance(wheel_velocity, float):
|
|
613
|
+
wheel_velocity = np.array([wheel_velocity, wheel_velocity])
|
|
614
|
+
elif isinstance(wheel_velocity, np.ndarray):
|
|
615
|
+
self.chassis_drive._send_velocity_command(wheel_velocity)
|
|
565
616
|
|
|
566
617
|
def _compute_wheel_control(
|
|
567
618
|
self, wheel_velocity: np.ndarray, current_angle: float
|
|
@@ -608,26 +659,6 @@ class Chassis(RobotJointComponent):
|
|
|
608
659
|
|
|
609
660
|
return steering_angle, wheel_speed
|
|
610
661
|
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
idx: int,
|
|
615
|
-
key: str,
|
|
616
|
-
fallback: float,
|
|
617
|
-
) -> float:
|
|
618
|
-
"""Helper function to extract values with fallback to current state.
|
|
619
|
-
|
|
620
|
-
Args:
|
|
621
|
-
data: Input scalar, array or dictionary containing values.
|
|
622
|
-
idx: Index to access if data is array.
|
|
623
|
-
key: Key to access if data is dictionary.
|
|
624
|
-
fallback: Default value if key/index not found.
|
|
625
|
-
|
|
626
|
-
Returns:
|
|
627
|
-
Extracted float value.
|
|
628
|
-
"""
|
|
629
|
-
if isinstance(data, dict):
|
|
630
|
-
return float(data.get(key, fallback))
|
|
631
|
-
if isinstance(data, (int, float)):
|
|
632
|
-
return float(data)
|
|
633
|
-
return float(data[idx])
|
|
662
|
+
def is_active(self) -> bool:
|
|
663
|
+
"""Check if the chassis is active."""
|
|
664
|
+
return self.chassis_steer.is_active() and self.chassis_drive.is_active()
|
dexcontrol/core/component.py
CHANGED
|
@@ -311,9 +311,9 @@ class RobotJointComponent(RobotComponent):
|
|
|
311
311
|
ValueError: If joint positions are not available for this component.
|
|
312
312
|
"""
|
|
313
313
|
state = self._get_state()
|
|
314
|
-
if not hasattr(state, "
|
|
314
|
+
if not hasattr(state, "pos"):
|
|
315
315
|
raise ValueError("Joint positions are not available for this component.")
|
|
316
|
-
joint_pos = np.array(state.
|
|
316
|
+
joint_pos = np.array(state.pos, dtype=np.float32)
|
|
317
317
|
return self._extract_joint_info(joint_pos, joint_id=joint_id)
|
|
318
318
|
|
|
319
319
|
def get_joint_pos_dict(
|
|
@@ -349,9 +349,9 @@ class RobotJointComponent(RobotComponent):
|
|
|
349
349
|
ValueError: If joint velocities are not available for this component.
|
|
350
350
|
"""
|
|
351
351
|
state = self._get_state()
|
|
352
|
-
if not hasattr(state, "
|
|
352
|
+
if not hasattr(state, "vel"):
|
|
353
353
|
raise ValueError("Joint velocities are not available for this component.")
|
|
354
|
-
joint_vel = np.array(state.
|
|
354
|
+
joint_vel = np.array(state.vel, dtype=np.float32)
|
|
355
355
|
return self._extract_joint_info(joint_vel, joint_id=joint_id)
|
|
356
356
|
|
|
357
357
|
def get_joint_vel_dict(
|
|
@@ -386,11 +386,31 @@ class RobotJointComponent(RobotComponent):
|
|
|
386
386
|
ValueError: If joint currents are not available for this component.
|
|
387
387
|
"""
|
|
388
388
|
state = self._get_state()
|
|
389
|
-
if not hasattr(state, "
|
|
389
|
+
if not hasattr(state, "cur"):
|
|
390
390
|
raise ValueError("Joint currents are not available for this component.")
|
|
391
|
-
joint_cur = np.array(state.
|
|
391
|
+
joint_cur = np.array(state.cur, dtype=np.float32)
|
|
392
392
|
return self._extract_joint_info(joint_cur, joint_id=joint_id)
|
|
393
393
|
|
|
394
|
+
def get_joint_torque(
|
|
395
|
+
self, joint_id: list[int] | int | None = None
|
|
396
|
+
) -> Float[np.ndarray, " N"]:
|
|
397
|
+
"""Gets the torque of all joints in the component.
|
|
398
|
+
|
|
399
|
+
Args:
|
|
400
|
+
joint_id: Optional ID(s) of specific joints to query.
|
|
401
|
+
|
|
402
|
+
Returns:
|
|
403
|
+
Array of joint torques in component-specific units (Nm).
|
|
404
|
+
|
|
405
|
+
Raises:
|
|
406
|
+
ValueError: If joint torques are not available for this component.
|
|
407
|
+
"""
|
|
408
|
+
state = self._get_state()
|
|
409
|
+
if not hasattr(state, "torque"):
|
|
410
|
+
raise ValueError("Joint torques are not available for this component.")
|
|
411
|
+
joint_torque = np.array(state.torque, dtype=np.float32)
|
|
412
|
+
return self._extract_joint_info(joint_torque, joint_id=joint_id)
|
|
413
|
+
|
|
394
414
|
def get_joint_current_dict(
|
|
395
415
|
self, joint_id: list[int] | int | None = None
|
|
396
416
|
) -> dict[str, float]:
|
|
@@ -421,9 +441,9 @@ class RobotJointComponent(RobotComponent):
|
|
|
421
441
|
ValueError: If joint error codes are not available for this component.
|
|
422
442
|
"""
|
|
423
443
|
state = self._get_state()
|
|
424
|
-
if not hasattr(state, "
|
|
444
|
+
if not hasattr(state, "error"):
|
|
425
445
|
raise ValueError("Joint error codes are not available for this component.")
|
|
426
|
-
joint_err = np.array(state.
|
|
446
|
+
joint_err = np.array(state.error, dtype=np.uint32)
|
|
427
447
|
return self._extract_joint_info(joint_err, joint_id=joint_id)
|
|
428
448
|
|
|
429
449
|
def get_joint_err_dict(
|
|
@@ -457,22 +477,27 @@ class RobotJointComponent(RobotComponent):
|
|
|
457
477
|
ValueError: If joint positions or velocities are not available.
|
|
458
478
|
"""
|
|
459
479
|
state = self._get_state()
|
|
460
|
-
if not hasattr(state, "
|
|
480
|
+
if not hasattr(state, "pos") or not hasattr(state, "vel"):
|
|
461
481
|
raise ValueError(
|
|
462
482
|
"Joint positions or velocities are not available for this component."
|
|
463
483
|
)
|
|
464
484
|
|
|
465
485
|
# Create initial state array with positions and velocities
|
|
466
|
-
joint_pos = np.array(state.
|
|
467
|
-
joint_vel = np.array(state.
|
|
486
|
+
joint_pos = np.array(state.pos, dtype=np.float32)
|
|
487
|
+
joint_vel = np.array(state.vel, dtype=np.float32)
|
|
468
488
|
|
|
469
|
-
if hasattr(state, "
|
|
489
|
+
if hasattr(state, "cur"):
|
|
470
490
|
# If currents are available, include them
|
|
471
|
-
joint_cur = np.array(state.
|
|
491
|
+
joint_cur = np.array(state.cur, dtype=np.float32)
|
|
472
492
|
joint_state = np.stack([joint_pos, joint_vel, joint_cur], axis=1)
|
|
493
|
+
elif hasattr(state, "torque"):
|
|
494
|
+
# If torques are available, include them
|
|
495
|
+
joint_torque = np.array(state.torque, dtype=np.float32)
|
|
496
|
+
joint_state = np.stack([joint_pos, joint_vel, joint_torque], axis=1)
|
|
473
497
|
else:
|
|
474
|
-
|
|
475
|
-
|
|
498
|
+
raise ValueError(
|
|
499
|
+
f"Either current or torque should be available for this {self.__class__.__name__}."
|
|
500
|
+
)
|
|
476
501
|
|
|
477
502
|
return self._extract_joint_info(joint_state, joint_id=joint_id)
|
|
478
503
|
|
|
@@ -543,8 +568,9 @@ class RobotJointComponent(RobotComponent):
|
|
|
543
568
|
joint_cmd = self._convert_joint_cmd_to_array(joint_cmd)
|
|
544
569
|
return self.get_joint_pos() + joint_cmd
|
|
545
570
|
|
|
571
|
+
@staticmethod
|
|
546
572
|
def _extract_joint_info(
|
|
547
|
-
|
|
573
|
+
joint_info: np.ndarray, joint_id: list[int] | int | None = None
|
|
548
574
|
) -> np.ndarray:
|
|
549
575
|
"""Extract the joint information of the component as a numpy array.
|
|
550
576
|
|