kuavo-humanoid-sdk 0.1.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 kuavo-humanoid-sdk might be problematic. Click here for more details.

@@ -0,0 +1,196 @@
1
+ #!/usr/bin/env python3
2
+ # coding: utf-8
3
+
4
+ import os
5
+ import rospy
6
+ import subprocess
7
+ import atexit
8
+ from kuavo_humanoid_sdk.common.logger import SDKLogger
9
+
10
+ class KuavoROSEnv:
11
+ _instance = None
12
+ _processes = [] # Store all subprocess instances
13
+
14
+ def __new__(cls):
15
+ if cls._instance is None:
16
+ cls._instance = super(KuavoROSEnv, cls).__new__(cls)
17
+ cls._instance._initialized = False
18
+ cls._instance._init_success = False # Add flag to track successful Init() call
19
+ # Register cleanup handler
20
+ atexit.register(cls._cleanup_processes)
21
+ return cls._instance
22
+
23
+ def __init__(self):
24
+ if not self._initialized:
25
+ self._initialized = True
26
+
27
+ @classmethod
28
+ def _cleanup_processes(cls):
29
+ """Cleanup all registered processes on exit"""
30
+ for process in cls._processes:
31
+ if process.poll() is None: # Process is still running
32
+ process.terminate()
33
+ try:
34
+ process.wait(timeout=3) # Wait for process to terminate
35
+ except subprocess.TimeoutExpired:
36
+ process.kill() # Force kill if not terminated
37
+ cls._processes.clear()
38
+
39
+ def _get_kuavo_ws_root(self) -> str:
40
+ if not rospy.has_param('/modelPath'):
41
+ raise Exception("modelPath parameter not found")
42
+ model_path = rospy.get_param('/modelPath')
43
+
44
+ if not os.path.exists(model_path):
45
+ raise Exception(f"modelPath f{model_path} not found")
46
+ # ws
47
+ return model_path.replace('/src/kuavo_assets/models', '')
48
+
49
+ def Init(self) -> bool:
50
+ """
51
+ Initialize the ROS environment.
52
+ Raises:
53
+ Exception: If the modelPath parameter is not found or the modelPath parameter is not a valid path.
54
+ """
55
+ # Return directly if already initialized successfully
56
+ if self._init_success:
57
+ return True
58
+
59
+ # Check if ROS master is running
60
+ try:
61
+ rospy.get_master().getPid()
62
+ except Exception as e:
63
+ raise Exception(f"ROS master is not running. Please start roscore first or check ROS_MASTER_URI, {e}")
64
+
65
+ kuavo_ws_root = self._get_kuavo_ws_root()
66
+
67
+ # Add kuavo_msgs package to Python path
68
+ dist_path = os.path.join(kuavo_ws_root, 'devel/lib/python3/dist-packages')
69
+ if not os.path.exists(dist_path):
70
+ dist_path = os.path.join(kuavo_ws_root, 'install/lib/python3/dist-packages')
71
+ if not os.path.exists(dist_path):
72
+ raise Exception(f"{dist_path} package not found in Python path")
73
+
74
+ if dist_path not in os.sys.path:
75
+ os.sys.path.append(dist_path)
76
+
77
+ # Initialize the ROS node
78
+ if not rospy.get_node_uri():
79
+ rospy.init_node(f'kuavo_sdk_node', anonymous=True, disable_signals=True)
80
+
81
+ self.launch_ik_node()
82
+ self.launch_gait_switch_node()
83
+
84
+ self._init_success = True # Set flag after successful initialization
85
+ return True
86
+ def _launch_ros_node(self, node_name, launch_cmd, log_name):
87
+ """Launch a ROS node with the given command and log the output.
88
+
89
+ Args:
90
+ node_name (str): Name of the node to launch
91
+ launch_cmd (str): Full launch command including source and roslaunch
92
+ log_name (str): Name for the log file
93
+
94
+ Raises:
95
+ Exception: If node launch fails
96
+ """
97
+ # Launch in background and check if successful
98
+ try:
99
+ os.makedirs('/var/log/kuavo_humanoid_sdk', exist_ok=True)
100
+ log_path = f'/var/log/kuavo_humanoid_sdk/{log_name}.log'
101
+ except PermissionError:
102
+ os.makedirs('log/kuavo_humanoid_sdk', exist_ok=True)
103
+ log_path = f'log/kuavo_humanoid_sdk/{log_name}.log'
104
+
105
+ with open(log_path, 'w') as log_file:
106
+ process = subprocess.Popen(launch_cmd, shell=True, executable='/bin/bash', stdout=log_file, stderr=log_file)
107
+ self._processes.append(process) # Add process to tracking list
108
+
109
+ if process.returncode is not None and process.returncode != 0:
110
+ raise Exception(f"Failed to launch {node_name}, return code: {process.returncode}")
111
+
112
+ SDKLogger.info(f"{node_name} launched successfully")
113
+
114
+ def _get_setup_file(self, ws_root=None):
115
+ """Get the appropriate ROS setup file path based on shell type.
116
+
117
+ Args:
118
+ ws_root (str, optional): ROS workspace root path. If None, uses ROS_WORKSPACE.
119
+
120
+ Returns:
121
+ str: Path to the setup file
122
+
123
+ Raises:
124
+ Exception: If setup file not found
125
+ """
126
+ is_zsh = 'zsh' in os.environ.get('SHELL', '')
127
+
128
+ if ws_root is None:
129
+ ws_root = os.environ['ROS_WORKSPACE']
130
+
131
+ setup_files = {
132
+ 'zsh': os.path.join(ws_root, 'devel/setup.zsh'),
133
+ 'bash': os.path.join(ws_root, 'devel/setup.bash')
134
+ }
135
+
136
+ setup_file = setup_files['zsh'] if is_zsh else setup_files['bash']
137
+ if not os.path.exists(setup_file):
138
+ setup_file = setup_file.replace('devel', 'install')
139
+ if not os.path.exists(setup_file):
140
+ raise Exception(f"Setup file not found in either devel or install: {setup_file}")
141
+
142
+ return setup_file
143
+
144
+ def launch_ik_node(self):
145
+ # nodes: /arms_ik_node
146
+ # services: /ik/two_arm_hand_pose_cmd_srv, /ik/fk_srv
147
+ try:
148
+ rospy.get_master().getPid()
149
+ except Exception as e:
150
+ raise Exception(f"ROS master is not running. Please start roscore first or check ROS_MASTER_URI, {e}")
151
+
152
+ # Check if IK node and services exist
153
+ try:
154
+ # Check if arms_ik_node is running
155
+ nodes = subprocess.check_output(['rosnode', 'list']).decode('utf-8').split('\n')
156
+ if '/arms_ik_node' not in nodes:
157
+ # Launch IK node if not running
158
+ kuavo_ws_root = self._get_kuavo_ws_root()
159
+ setup_file = self._get_setup_file(kuavo_ws_root)
160
+ source_cmd = f"source {setup_file}"
161
+
162
+ # Get robot version from rosparam
163
+ robot_version = rospy.get_param('/robot_version', None)
164
+ if robot_version is None:
165
+ raise Exception("Failed to get robot_version from rosparam")
166
+
167
+ # Launch IK node
168
+ launch_cmd = f"roslaunch motion_capture_ik ik_node.launch robot_version:={robot_version}"
169
+ full_cmd = f"{source_cmd} && {launch_cmd}"
170
+
171
+ self._launch_ros_node('IK node', full_cmd, 'launch_ik')
172
+
173
+ return True
174
+
175
+ except Exception as e:
176
+ raise Exception(f"Failed to verify IK node and services: {e}")
177
+
178
+
179
+ def launch_gait_switch_node(self)-> bool:
180
+ """Verify that the gait switch node is running, launch if not."""
181
+ try:
182
+ # Check if node exists
183
+ nodes = subprocess.check_output(['rosnode', 'list']).decode('utf-8').split('\n')
184
+ if '/humanoid_gait_switch_by_name' not in nodes:
185
+ kuavo_ws_root = self._get_kuavo_ws_root()
186
+ setup_file = self._get_setup_file(kuavo_ws_root)
187
+ source_cmd = f"source {setup_file}"
188
+
189
+ # Launch gait switch node
190
+ launch_cmd = "roslaunch humanoid_interface_ros humanoid_switch_gait.launch"
191
+ full_cmd = f"{source_cmd} && {launch_cmd}"
192
+
193
+ self._launch_ros_node('Gait switch node', full_cmd, 'launch_gait_switch')
194
+
195
+ except Exception as e:
196
+ raise Exception(f"Failed to launch gait_switch node: {e}")
@@ -0,0 +1,183 @@
1
+ #!/usr/bin/env python3
2
+ # coding: utf-8
3
+ from typing import Tuple
4
+ from kuavo_humanoid_sdk.interfaces.end_effector import EndEffector
5
+ from kuavo_humanoid_sdk.interfaces.data_types import EndEffectorSide, EndEffectorState
6
+ from kuavo_humanoid_sdk.kuavo.core.dex_hand_control import DexHandControl
7
+ from kuavo_humanoid_sdk.kuavo.core.ros.state import KuavoRobotStateCore
8
+
9
+ class DexterousHand(EndEffector):
10
+ def __init__(self):
11
+ joint_names = ['l_thumb', 'l_thumb_aux', 'l_index', 'l_middle', 'l_ring', 'l_pinky',
12
+ 'r_thumb', 'r_thumb_aux', 'r_index', 'r_middle', 'r_ring', 'r_pinky',]
13
+ super().__init__(joint_names=joint_names)
14
+ self.dex_hand_control = DexHandControl()
15
+ self._rb_state = KuavoRobotStateCore()
16
+
17
+ def control(self, target_positions:list, target_velocities:list=None, target_torques:list=None)->bool:
18
+ """Set the position of the hand.
19
+
20
+ Args:
21
+ target_positions (list): List of target positions for all joints, length must be 12 (6 joints for each hand),
22
+ range => [0.0 ~ 100.0]
23
+ target_velocities (list, optional): Not supported. Defaults to None.
24
+ target_torques (list, optional): Not supported. Defaults to None.
25
+
26
+ Returns:
27
+ bool: True if control successful, False otherwise.
28
+
29
+ Note:
30
+ target_velocities and target_torques are not supported.
31
+ """
32
+ if len(target_positions) != len(self.joint_names):
33
+ raise ValueError(f"Target positions must have the same length as joint names {len(target_positions)} != {len(self.joint_names)}")
34
+
35
+ q = [max(0, min(100, pos if isinstance(pos, int) else int(pos))) for pos in target_positions]
36
+
37
+ # control the hand
38
+ return self.dex_hand_control.control(target_positions=q, side=EndEffectorSide.BOTH)
39
+
40
+ def control_right(self, target_positions:list, target_velocities:list=None, target_torques:list=None)->bool:
41
+ """Control the right dexterous hand.
42
+
43
+ Args:
44
+ target_positions (list): Target positions for right hand joints [0 ~ 100], length must be 6
45
+ target_velocities (list, optional): Not supported. Defaults to None.
46
+ target_torques (list, optional): Not supported. Defaults to None.
47
+
48
+ Returns:
49
+ bool: True if control successful, False otherwise.
50
+
51
+ Raises:
52
+ ValueError: If target positions length doesn't match joint count or values outside [0,100] range
53
+
54
+ Note:
55
+ target_velocities and target_torques are not supported.
56
+ """
57
+ if len(target_positions) != (len(self.joint_names)/2):
58
+ raise ValueError(f"Target positions must have the same length as joint names {len(target_positions)} != {len(self.joint_names)/2}.")
59
+
60
+ q = [max(0, min(100, pos if isinstance(pos, int) else int(pos))) for pos in target_positions]
61
+
62
+ return self.dex_hand_control.control(target_positions=q, side=EndEffectorSide.RIGHT)
63
+
64
+ def control_left(self, target_positions:list, target_velocities:list=None, target_torques:list=None)->bool:
65
+ """Control the left dexterous hand.
66
+
67
+ Args:
68
+ target_positions (list): Target positions for left hand joints [0 ~ 100], length must be 6
69
+ target_velocities (list, optional): Not supported. Defaults to None.
70
+ target_torques (list, optional): Not supported. Defaults to None.
71
+
72
+ Returns:
73
+ bool: True if control successful, False otherwise.
74
+
75
+ Raises:
76
+ ValueError: If target positions length doesn't match joint count or values outside [0,100] range
77
+
78
+ Note:
79
+ target_velocities and target_torques are not supported.
80
+ """
81
+ if len(target_positions) != (len(self.joint_names)/2):
82
+ raise ValueError(f"Target positions must have the same length as joint names {len(target_positions)} != {len(self.joint_names)/2}.")
83
+
84
+ q = [max(0, min(100, pos if isinstance(pos, int) else int(pos))) for pos in target_positions]
85
+
86
+ return self.dex_hand_control.control(target_positions=q, side=EndEffectorSide.LEFT)
87
+
88
+ def open(self, side: EndEffectorSide=EndEffectorSide.BOTH)->bool:
89
+ """Open the dexterous hand(s) by setting all joint positions to 0.
90
+
91
+ Args:
92
+ side (EndEffectorSide, optional): Which hand(s) to open. Defaults to EndEffectorSide.BOTH.
93
+ Can be LEFT, RIGHT, or BOTH.
94
+
95
+ Returns:
96
+ bool: True if open command sent successfully, False otherwise.
97
+ """
98
+ zero_pos = [0]*len(self.joint_names)
99
+ if side == EndEffectorSide.LEFT:
100
+ return self.dex_hand_control.control(target_positions=zero_pos[:len(zero_pos)//2], side=EndEffectorSide.LEFT)
101
+ elif side == EndEffectorSide.RIGHT:
102
+ return self.dex_hand_control.control(target_positions=zero_pos[len(zero_pos)//2:], side=EndEffectorSide.RIGHT)
103
+ else:
104
+ return self.dex_hand_control.control(target_positions=zero_pos, side=EndEffectorSide.BOTH)
105
+
106
+ def make_gesture(self, l_gesture_name: str, r_gesture_name: str)->bool:
107
+ """Make predefined gestures for both hands.
108
+
109
+ Args:
110
+ l_gesture_name (str): Name of gesture for left hand. None to skip left hand.
111
+ r_gesture_name (str): Name of gesture for right hand. None to skip right hand.
112
+
113
+ Returns:
114
+ bool: True if gesture command sent successfully, False otherwise.
115
+
116
+ Note:
117
+ gestures e.g.: 'fist', 'ok', 'thumbs_up', '666'...
118
+ """
119
+ gesture = []
120
+ if l_gesture_name is not None:
121
+ gesture.append({'gesture_name':l_gesture_name, 'hand_side':EndEffectorSide.LEFT})
122
+ if r_gesture_name is not None:
123
+ gesture.append({'gesture_name':r_gesture_name, 'hand_side':EndEffectorSide.RIGHT})
124
+ self.dex_hand_control.make_gestures(gesture)
125
+
126
+ def get_gesture_names(self)->list:
127
+ """Get the names of all gestures.
128
+
129
+ Returns:
130
+ list: List of gesture names.
131
+ e.g.: ['fist', 'ok', 'thumbs_up', '666', 'number_1', 'number_2', 'number_3', ... ],
132
+ None if no gestures.
133
+ """
134
+ return self.dex_hand_control.get_gesture_names()
135
+
136
+ def get_state(self)->Tuple[EndEffectorState, EndEffectorState]:
137
+ # TODO(kuavo): Not implemented yet
138
+ """Get the state of the dexterous hand.
139
+
140
+ Returns:
141
+ Tuple[EndEffectorState, EndEffectorState]: The state of the dexterous hand.
142
+ """
143
+ return self._rb_state.eef_state
144
+
145
+ def get_position(self)->Tuple[float, float]:
146
+ # TODO(kuavo): Not implemented yet
147
+ """Get the position of the dexterous hand.
148
+
149
+ Returns:
150
+ Tuple[float, float]: The position of the dexterous hand.
151
+ """
152
+ return self._rb_state.eef_state.position
153
+
154
+ def get_velocity(self)->Tuple[float, float]:
155
+ # TODO(kuavo): Not implemented yet
156
+ """Get the velocity of the dexterous hand.
157
+
158
+ Returns:
159
+ Tuple[float, float]: The velocity of the dexterous hand.
160
+ """
161
+ return self._rb_state.eef_state.velocity
162
+
163
+ def get_effort(self)->Tuple[float, float]:
164
+ # TODO(kuavo): Not implemented yet
165
+ """Get the effort of the dexterous hand.
166
+
167
+ Returns:
168
+ Tuple[float, float]: The effort of the dexterous hand.
169
+
170
+ Note:
171
+ 0 ~ 100 for each finger. Fraction of max motor current, absolute number.
172
+ The max motor current is 600mA, in a word, 100.
173
+ """
174
+ return self._rb_state.eef_state.effort
175
+
176
+ def get_grasping_state(self)->Tuple[EndEffectorState.GraspingState, EndEffectorState.GraspingState]:
177
+ # TODO(kuavo): Not implemented yet
178
+ """Get the grasping state of the dexterous hand.
179
+
180
+ Returns:
181
+ Tuple[EndEffectorState.GraspingState, EndEffectorState.GraspingState]: The grasping state of the dexterous hand.
182
+ """
183
+ return self._rb_state.eef_state.state
@@ -0,0 +1,225 @@
1
+ #!/usr/bin/env python3
2
+ # coding: utf-8
3
+ import time
4
+ from typing import Tuple
5
+ from kuavo_humanoid_sdk.interfaces.end_effector import EndEffector
6
+ from kuavo_humanoid_sdk.interfaces.data_types import EndEffectorSide, EndEffectorState
7
+ from kuavo_humanoid_sdk.kuavo.core.leju_claw_control import LejuClawControl
8
+ from kuavo_humanoid_sdk.kuavo.core.ros.state import KuavoRobotStateCore
9
+
10
+ class LejuClaw(EndEffector):
11
+ def __init__(self):
12
+ super().__init__(joint_names=['left_claw', 'right_claw'])
13
+ self.leju_claw_control = LejuClawControl()
14
+ self._rb_state = KuavoRobotStateCore()
15
+
16
+ def control(self, target_positions: list, target_velocities:list=None, target_torques: list=None)->bool:
17
+ """Control the claws to grip.
18
+
19
+ Args:
20
+ target_positions (list): The target positions of the claws.
21
+ target_velocities (list, optional): The target velocities of the claws. If None, default value [90, 90] will be used.
22
+ target_torques (list, optional): The target torques of the claws. If None, default value [1.0, 1.0] will be used.
23
+
24
+ Note:
25
+ The target_positions, target_velocities, target_torques must be a list of length 2.
26
+ After calling this function, you can call wait_for_finish() to wait until the claws reach the target position.
27
+
28
+ Warning:
29
+ If the claws are still in motion from a previous command, this request may be dropped.
30
+
31
+ Returns:
32
+ bool: True if the claws are successfully gripped, False otherwise
33
+ """
34
+ if target_positions is None:
35
+ raise ValueError("Target positions must be provided.")
36
+
37
+ target_positions = [max(0.0, min(100.0, pos)) for pos in target_positions]
38
+ if target_velocities is None:
39
+ target_velocities = [90, 90]
40
+ else:
41
+ target_velocities = [max(0.0, min(100.0, vel)) for vel in target_velocities]
42
+
43
+ if target_torques is None:
44
+ target_torques = [1.0, 1.0]
45
+ else:
46
+ target_torques = [max(0.0, min(10.0, torque)) for torque in target_torques]
47
+
48
+ return self.leju_claw_control.control(target_positions, target_velocities, target_torques, EndEffectorSide.BOTH)
49
+
50
+ def control_left(self, target_positions:list, target_velocities:list=None, target_torques:list=None)->bool:
51
+ """Control the left claw to grip.
52
+
53
+ Args:
54
+ target_positions (list): The target position of the left claw.
55
+ target_velocities (list, optional): The target velocity of the left claw. If None, default value 90 will be used.
56
+ target_torques (list, optional): The target torque of the left claw. If None, default value 1.0 will be used.
57
+
58
+ Note:
59
+ The target_positions, target_velocities, target_torques must be a list of length 1
60
+ After calling this function, you can call wait_for_finish() to wait until the claws reach the target position.
61
+
62
+ Warning:
63
+ If the claws are still in motion from a previous command, this request may be dropped.
64
+
65
+ Returns:
66
+ bool: True if the claw is successfully gripped, False otherwise.
67
+ """
68
+ if target_positions is None:
69
+ raise ValueError("Target positions must be provided.")
70
+
71
+ for data in [target_positions, target_velocities, target_torques]:
72
+ if data is not None and len(data) != 1:
73
+ raise ValueError("Target positions must be a list of length 1.")
74
+
75
+ q = max(0.0, min(100.0, target_positions[0]))
76
+
77
+ if target_velocities is not None:
78
+ v = max(0.0, min(100.0, target_velocities[0]))
79
+ else:
80
+ v = 90
81
+
82
+ if target_torques is not None:
83
+ tau = max(0.0, min(10.0, target_torques[0]))
84
+ else:
85
+ tau = 1.0
86
+
87
+ return self.leju_claw_control.control([q], [v], [tau], EndEffectorSide.LEFT)
88
+
89
+ def control_right(self, target_positions:list, target_velocities:list=None, target_torques:list=None)->bool:
90
+ """Control the right claw to grip.
91
+
92
+ Args:
93
+ target_positions (list): The target position of the right claw.
94
+ target_velocities (list, optional): The target velocity of the right claw. If None, default value 90 will be used.
95
+ target_torques (list, optional): The target torque of the right claw. If None, default value 1.0 will be used.
96
+
97
+ Returns:
98
+ bool: True if the claw is successfully gripped, False otherwise.
99
+
100
+ Note:
101
+ The target_positions, target_velocities, target_torques must be a list of length 1
102
+ After calling this function, you can call wait_for_finish() to wait until the claws reach the target position.
103
+
104
+ Warning:
105
+ If the claws are still in motion from a previous command, this request may be dropped.
106
+ """
107
+ if target_positions is None:
108
+ raise ValueError("Target positions must be provided.")
109
+
110
+ for data in [target_positions, target_velocities, target_torques]:
111
+ if data is not None and len(data) != 1:
112
+ raise ValueError("Target positions must be a list of length 1.")
113
+
114
+ q = max(0.0, min(100.0, target_positions[0]))
115
+
116
+ if target_velocities is not None:
117
+ v = max(0.0, min(100.0, target_velocities[0]))
118
+ else:
119
+ v = 90
120
+
121
+ if target_torques is not None:
122
+ tau = max(0.0, min(10.0, target_torques[0]))
123
+ else:
124
+ tau = 1.0
125
+
126
+ return self.leju_claw_control.control([q], [v], [tau], EndEffectorSide.RIGHT)
127
+
128
+ def open(self, side:EndEffectorSide=EndEffectorSide.BOTH)->bool:
129
+ """Control the claws to release/open.
130
+
131
+ Note:
132
+ Control the claws to open.
133
+ After calling this function, you can call wait_for_finish() to wait until the claws reach the target position.
134
+
135
+ Args:
136
+ side (EndEffectorSide, optional): The side to control. Defaults to EndEffectorSide.BOTH.
137
+
138
+ Returns:
139
+ bool: True if the claw is successfully released, False otherwise.
140
+ """
141
+ return self.leju_claw_control.release(side)
142
+
143
+ def close(self, side:EndEffectorSide=EndEffectorSide.BOTH)->bool:
144
+ """Control the claws to close/grip.
145
+
146
+ Note:
147
+ Control the claws to close.
148
+ After calling this function, you can call wait_for_finish() to wait until the claws reach the target position.
149
+
150
+ Args:
151
+ side (EndEffectorSide, optional): The side to control. Defaults to EndEffectorSide.BOTH.
152
+
153
+ Returns:
154
+ bool: True if the claw is successfully gripped, False otherwise.
155
+ """
156
+ return self.leju_claw_control.control(position=[100, 100], velocity=[90, 90], torque=[1.0, 1.0], side=side)
157
+
158
+ def get_grasping_state(self)->Tuple[EndEffectorState.GraspingState, EndEffectorState.GraspingState]:
159
+ """Get the grasping state of the claws.
160
+
161
+ Returns:
162
+ Tuple[EndEffectorState.GraspingState, EndEffectorState.GraspingState]: The grasping state of the claws.
163
+ """
164
+ return self._rb_state.eef_state.state
165
+
166
+ def get_position(self)->Tuple[float, float]:
167
+ """Get the position of the claws.
168
+
169
+ Returns:
170
+ Tuple[float, float]: The position of the claws, range [0.0, 100.0].
171
+ """
172
+ return self._rb_state.eef_state.position
173
+
174
+ def get_velocity(self)->Tuple[float, float]:
175
+ """Get the velocity of the claws.
176
+
177
+ Returns:
178
+ Tuple[float, float]: The velocity of the claws.
179
+ """
180
+ return self._rb_state.eef_state.velocity
181
+
182
+ def get_effort(self)->Tuple[float, float]:
183
+ """Get the effort of the claws.
184
+
185
+ Returns:
186
+ Tuple[float, float]: The effort of the claws.
187
+ """
188
+ return self._rb_state.eef_state.effort
189
+
190
+ def get_state(self)->Tuple[EndEffectorState, EndEffectorState]:
191
+ """Get the state of the claws.
192
+
193
+ Returns:
194
+ Tuple[EndEffectorState, EndEffectorState]: The state of the claws.
195
+ - position: The position of the claws, range [0.0, 100.0].
196
+ - velocity: The velocity of the claws.
197
+ - effort: The effort of the claws.
198
+ - state: The grasping state of the claws.
199
+ """
200
+ return self._rb_state.eef_state
201
+
202
+ def wait_for_finish(self, side: EndEffectorSide=EndEffectorSide.BOTH, timeout:float=2.5):
203
+ """Wait for the claw motion to finish.
204
+
205
+ Args:
206
+ side (EndEffectorSide, optional): The side of the claw to wait for. Defaults to EndEffectorSide.BOTH.
207
+ timeout (float, optional): The timeout duration in seconds. Defaults to 2.5.
208
+
209
+ Returns:
210
+ bool: True if motion completed before timeout, False otherwise.
211
+ """
212
+ start_time = time.time()
213
+ while time.time() - start_time < timeout:
214
+ if side == EndEffectorSide.BOTH:
215
+ if self._rb_state.eef_state[0].state >= EndEffectorState.GraspingState.REACHED and \
216
+ self._rb_state.eef_state[1].state >= EndEffectorState.GraspingState.REACHED:
217
+ return True
218
+ elif side == EndEffectorSide.LEFT:
219
+ if self._rb_state.eef_state[0].state >= EndEffectorState.GraspingState.REACHED:
220
+ return True
221
+ elif side == EndEffectorSide.RIGHT:
222
+ if self._rb_state.eef_state[1].state >= EndEffectorState.GraspingState.REACHED:
223
+ return True
224
+ time.sleep(0.1)
225
+ return False