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 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 = 233
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
- control_pub_topic: str = "control/chassis"
18
- state_sub_topic: str = "state/chassis"
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
- joint_name: list[str] = field(
25
- default_factory=lambda: ["L_wheel_j1", "L_wheel_j2", "R_wheel_j1", "R_wheel_j2"]
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
@@ -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/version"
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.ArmState,
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
- if mode not in mode_map:
114
- raise ValueError(
115
- f"Invalid mode: {mode}. Must be one of {list(mode_map.keys())}"
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
- query_msg = dexcontrol_query_pb2.SetArmMode(mode=mode_map[mode])
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.ArmCommand()
128
- control_msg.joint_pos.extend(joint_pos.tolist())
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.ArmCommand(
291
- command_type=dexcontrol_msg_pb2.ArmCommand.CommandType.VELOCITY_FEEDFORWARD,
292
- joint_pos=list(target_pos),
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
 
@@ -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 Chassis(RobotJointComponent):
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
- super().__init__(
56
- state_sub_topic=configs.state_sub_topic,
57
- control_pub_topic=configs.control_pub_topic,
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
- super().shutdown()
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
- state = self._get_state()
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
- state = self._get_state()
121
- return np.array([state.left.wheel_vel, state.right.wheel_vel])
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: left wheel position
360
- 2: right steering position
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, left_wheel_pos, right_steering_pos, right_wheel_pos]
452
+ [left_steering_pos, right_steering_pos, left_wheel_pos, right_wheel_pos]
366
453
  """
367
- state = self._get_state()
368
- joint_pos = np.array(
369
- [
370
- state.left.steering_pos,
371
- state.left.wheel_pos,
372
- state.right.steering_pos,
373
- state.right.wheel_pos,
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: left wheel position
387
- 2: right steering position
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
- values = self.get_joint_pos(joint_id)
394
- return self._convert_to_dict(values, joint_id)
395
-
396
- def get_joint_state(self, joint_id: list[int] | int | None = None) -> np.ndarray:
397
- """Get current wheel states (left and right wheel).
398
-
399
- Args:
400
- joint_id: Optional ID(s) of specific joints to query.
401
- 0: left wheel
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
- while time.time() - start_time < wait_time:
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
- control_msg = dexcontrol_msg_pb2.ChassisCommand()
548
- state = self._get_state()
549
-
550
- # Set steering positions and wheel velocities
551
- control_msg.left.steering_pos = _get_value(
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
- self._publish_control(control_msg)
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
- def _get_value(
613
- data: float | np.ndarray | dict[str, float],
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()
@@ -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, "joint_pos"):
314
+ if not hasattr(state, "pos"):
315
315
  raise ValueError("Joint positions are not available for this component.")
316
- joint_pos = np.array(state.joint_pos, dtype=np.float32)
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, "joint_vel"):
352
+ if not hasattr(state, "vel"):
353
353
  raise ValueError("Joint velocities are not available for this component.")
354
- joint_vel = np.array(state.joint_vel, dtype=np.float32)
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, "joint_cur"):
389
+ if not hasattr(state, "cur"):
390
390
  raise ValueError("Joint currents are not available for this component.")
391
- joint_cur = np.array(state.joint_cur, dtype=np.float32)
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, "joint_err"):
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.joint_err, dtype=np.uint32)
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, "joint_pos") or not hasattr(state, "joint_vel"):
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.joint_pos, dtype=np.float32)
467
- joint_vel = np.array(state.joint_vel, dtype=np.float32)
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, "joint_cur"):
489
+ if hasattr(state, "cur"):
470
490
  # If currents are available, include them
471
- joint_cur = np.array(state.joint_cur, dtype=np.float32)
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
- # Otherwise just include positions and velocities
475
- joint_state = np.stack([joint_pos, joint_vel], axis=1)
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
- self, joint_info: np.ndarray, joint_id: list[int] | int | None = None
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