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
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
|
@@ -89,11 +89,11 @@ class RobotComponent:
|
|
|
89
89
|
Parsed protobuf state message.
|
|
90
90
|
|
|
91
91
|
Raises:
|
|
92
|
-
|
|
92
|
+
None: If no state data is available.
|
|
93
93
|
"""
|
|
94
94
|
state = self._subscriber.get_latest_data()
|
|
95
95
|
if state is None:
|
|
96
|
-
|
|
96
|
+
logger.error(f"No state data available for {self.__class__.__name__}")
|
|
97
97
|
return state
|
|
98
98
|
|
|
99
99
|
def wait_for_active(self, timeout: float = 5.0) -> bool:
|
|
@@ -133,6 +133,15 @@ class RobotComponent:
|
|
|
133
133
|
if hasattr(self, "_subscriber") and self._subscriber:
|
|
134
134
|
self._subscriber.shutdown()
|
|
135
135
|
|
|
136
|
+
def get_timestamp_ns(self) -> int:
|
|
137
|
+
"""Get the current timestamp (in nanoseconds) of the most recent state update.
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
The current timestamp in nanoseconds when driver updated the state.
|
|
141
|
+
We convert the time to client clock by adding the server time offset.
|
|
142
|
+
"""
|
|
143
|
+
return self._get_state().timestamp_ns
|
|
144
|
+
|
|
136
145
|
|
|
137
146
|
class RobotJointComponent(RobotComponent):
|
|
138
147
|
"""Base class for robot components with both state and control interfaces.
|
|
@@ -171,6 +180,8 @@ class RobotJointComponent(RobotComponent):
|
|
|
171
180
|
state_message_type: type[M],
|
|
172
181
|
zenoh_session: zenoh.Session,
|
|
173
182
|
joint_name: list[str] | None = None,
|
|
183
|
+
joint_limit: list[list[float]] | None = None,
|
|
184
|
+
joint_vel_limit: list[float] | None = None,
|
|
174
185
|
pose_pool: Mapping[str, list[float] | np.ndarray] | None = None,
|
|
175
186
|
) -> None:
|
|
176
187
|
"""Initializes RobotJointComponent.
|
|
@@ -190,6 +201,13 @@ class RobotJointComponent(RobotComponent):
|
|
|
190
201
|
resolved_topic
|
|
191
202
|
)
|
|
192
203
|
self._joint_name: list[str] | None = joint_name
|
|
204
|
+
self._joint_limit: np.ndarray | None = (
|
|
205
|
+
np.array(joint_limit) if joint_limit else None
|
|
206
|
+
)
|
|
207
|
+
self._joint_vel_limit: np.ndarray | None = (
|
|
208
|
+
np.array(joint_vel_limit) if joint_vel_limit else None
|
|
209
|
+
)
|
|
210
|
+
|
|
193
211
|
self._pose_pool: dict[str, np.ndarray] | None = (
|
|
194
212
|
self._convert_pose_pool_to_arrays(pose_pool)
|
|
195
213
|
)
|
|
@@ -231,6 +249,18 @@ class RobotJointComponent(RobotComponent):
|
|
|
231
249
|
raise ValueError("Joint names not available for this component")
|
|
232
250
|
return self._joint_name.copy()
|
|
233
251
|
|
|
252
|
+
@property
|
|
253
|
+
def joint_limit(self) -> np.ndarray | None:
|
|
254
|
+
"""Gets the joint limits of the component."""
|
|
255
|
+
return self._joint_limit.copy() if self._joint_limit is not None else None
|
|
256
|
+
|
|
257
|
+
@property
|
|
258
|
+
def joint_vel_limit(self) -> np.ndarray | None:
|
|
259
|
+
"""Gets the joint velocity limits of the component."""
|
|
260
|
+
return (
|
|
261
|
+
self._joint_vel_limit.copy() if self._joint_vel_limit is not None else None
|
|
262
|
+
)
|
|
263
|
+
|
|
234
264
|
def get_predefined_pose(self, pose_name: str) -> np.ndarray:
|
|
235
265
|
"""Gets a predefined pose from the pose pool.
|
|
236
266
|
|
|
@@ -281,9 +311,9 @@ class RobotJointComponent(RobotComponent):
|
|
|
281
311
|
ValueError: If joint positions are not available for this component.
|
|
282
312
|
"""
|
|
283
313
|
state = self._get_state()
|
|
284
|
-
if not hasattr(state, "
|
|
314
|
+
if not hasattr(state, "pos"):
|
|
285
315
|
raise ValueError("Joint positions are not available for this component.")
|
|
286
|
-
joint_pos = np.array(state.
|
|
316
|
+
joint_pos = np.array(state.pos, dtype=np.float32)
|
|
287
317
|
return self._extract_joint_info(joint_pos, joint_id=joint_id)
|
|
288
318
|
|
|
289
319
|
def get_joint_pos_dict(
|
|
@@ -319,9 +349,9 @@ class RobotJointComponent(RobotComponent):
|
|
|
319
349
|
ValueError: If joint velocities are not available for this component.
|
|
320
350
|
"""
|
|
321
351
|
state = self._get_state()
|
|
322
|
-
if not hasattr(state, "
|
|
352
|
+
if not hasattr(state, "vel"):
|
|
323
353
|
raise ValueError("Joint velocities are not available for this component.")
|
|
324
|
-
joint_vel = np.array(state.
|
|
354
|
+
joint_vel = np.array(state.vel, dtype=np.float32)
|
|
325
355
|
return self._extract_joint_info(joint_vel, joint_id=joint_id)
|
|
326
356
|
|
|
327
357
|
def get_joint_vel_dict(
|
|
@@ -356,11 +386,31 @@ class RobotJointComponent(RobotComponent):
|
|
|
356
386
|
ValueError: If joint currents are not available for this component.
|
|
357
387
|
"""
|
|
358
388
|
state = self._get_state()
|
|
359
|
-
if not hasattr(state, "
|
|
389
|
+
if not hasattr(state, "cur"):
|
|
360
390
|
raise ValueError("Joint currents are not available for this component.")
|
|
361
|
-
joint_cur = np.array(state.
|
|
391
|
+
joint_cur = np.array(state.cur, dtype=np.float32)
|
|
362
392
|
return self._extract_joint_info(joint_cur, joint_id=joint_id)
|
|
363
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
|
+
|
|
364
414
|
def get_joint_current_dict(
|
|
365
415
|
self, joint_id: list[int] | int | None = None
|
|
366
416
|
) -> dict[str, float]:
|
|
@@ -391,9 +441,9 @@ class RobotJointComponent(RobotComponent):
|
|
|
391
441
|
ValueError: If joint error codes are not available for this component.
|
|
392
442
|
"""
|
|
393
443
|
state = self._get_state()
|
|
394
|
-
if not hasattr(state, "
|
|
444
|
+
if not hasattr(state, "error"):
|
|
395
445
|
raise ValueError("Joint error codes are not available for this component.")
|
|
396
|
-
joint_err = np.array(state.
|
|
446
|
+
joint_err = np.array(state.error, dtype=np.uint32)
|
|
397
447
|
return self._extract_joint_info(joint_err, joint_id=joint_id)
|
|
398
448
|
|
|
399
449
|
def get_joint_err_dict(
|
|
@@ -427,22 +477,27 @@ class RobotJointComponent(RobotComponent):
|
|
|
427
477
|
ValueError: If joint positions or velocities are not available.
|
|
428
478
|
"""
|
|
429
479
|
state = self._get_state()
|
|
430
|
-
if not hasattr(state, "
|
|
480
|
+
if not hasattr(state, "pos") or not hasattr(state, "vel"):
|
|
431
481
|
raise ValueError(
|
|
432
482
|
"Joint positions or velocities are not available for this component."
|
|
433
483
|
)
|
|
434
484
|
|
|
435
485
|
# Create initial state array with positions and velocities
|
|
436
|
-
joint_pos = np.array(state.
|
|
437
|
-
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)
|
|
438
488
|
|
|
439
|
-
if hasattr(state, "
|
|
489
|
+
if hasattr(state, "cur"):
|
|
440
490
|
# If currents are available, include them
|
|
441
|
-
joint_cur = np.array(state.
|
|
491
|
+
joint_cur = np.array(state.cur, dtype=np.float32)
|
|
442
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)
|
|
443
497
|
else:
|
|
444
|
-
|
|
445
|
-
|
|
498
|
+
raise ValueError(
|
|
499
|
+
f"Either current or torque should be available for this {self.__class__.__name__}."
|
|
500
|
+
)
|
|
446
501
|
|
|
447
502
|
return self._extract_joint_info(joint_state, joint_id=joint_id)
|
|
448
503
|
|
|
@@ -466,7 +521,7 @@ class RobotJointComponent(RobotComponent):
|
|
|
466
521
|
def _convert_joint_cmd_to_array(
|
|
467
522
|
self,
|
|
468
523
|
joint_cmd: Float[np.ndarray, " N"] | list[float] | dict[str, float],
|
|
469
|
-
clip_value: float | None = None,
|
|
524
|
+
clip_value: float | np.ndarray | None = None,
|
|
470
525
|
) -> np.ndarray:
|
|
471
526
|
"""Convert joint command to numpy array format.
|
|
472
527
|
|
|
@@ -475,7 +530,9 @@ class RobotJointComponent(RobotComponent):
|
|
|
475
530
|
- List of joint values [j1, j2, ..., jN]
|
|
476
531
|
- Numpy array with shape (N,)
|
|
477
532
|
- Dictionary mapping joint names to values
|
|
478
|
-
clip_value: Optional value to clip the output array
|
|
533
|
+
clip_value: Optional value to clip the output array. Can be:
|
|
534
|
+
- float: symmetric clipping between [-clip_value, clip_value]
|
|
535
|
+
- numpy array: element-wise clipping between [-clip_value, clip_value]
|
|
479
536
|
|
|
480
537
|
Returns:
|
|
481
538
|
Joint command as numpy array.
|
|
@@ -511,8 +568,9 @@ class RobotJointComponent(RobotComponent):
|
|
|
511
568
|
joint_cmd = self._convert_joint_cmd_to_array(joint_cmd)
|
|
512
569
|
return self.get_joint_pos() + joint_cmd
|
|
513
570
|
|
|
571
|
+
@staticmethod
|
|
514
572
|
def _extract_joint_info(
|
|
515
|
-
|
|
573
|
+
joint_info: np.ndarray, joint_id: list[int] | int | None = None
|
|
516
574
|
) -> np.ndarray:
|
|
517
575
|
"""Extract the joint information of the component as a numpy array.
|
|
518
576
|
|
|
@@ -660,6 +718,11 @@ class RobotJointComponent(RobotComponent):
|
|
|
660
718
|
if isinstance(joint_pos, (list, dict)):
|
|
661
719
|
joint_pos = self._convert_joint_cmd_to_array(joint_pos)
|
|
662
720
|
|
|
721
|
+
if self._joint_limit is not None:
|
|
722
|
+
joint_pos = np.clip(
|
|
723
|
+
joint_pos, self._joint_limit[:, 0], self._joint_limit[:, 1]
|
|
724
|
+
)
|
|
725
|
+
|
|
663
726
|
self._send_position_command(joint_pos)
|
|
664
727
|
|
|
665
728
|
if wait_time > 0.0:
|