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,388 @@
1
+ #!/usr/bin/env python3
2
+ # coding: utf-8
3
+ """
4
+ This layer is responsible for robot state transitions.
5
+ The robot has three states:
6
+ - stance: Standing still state
7
+ - walk: Walking state
8
+ - trot: Trotting state
9
+
10
+ State transitions are managed by a state machine that ensures valid transitions between states.
11
+ The state machine enforces the following transitions:
12
+ - stance <-> walk
13
+ - stance <-> trot
14
+ - walk <-> trot
15
+
16
+ Each state has an entry callback that handles initialization when entering that state.
17
+ """
18
+
19
+
20
+ import time
21
+ import math
22
+ import rospy
23
+ import threading
24
+ import numpy as np
25
+ from typing import Tuple
26
+ from transitions import Machine, State
27
+
28
+ from kuavo_humanoid_sdk.interfaces.data_types import KuavoArmCtrlMode, KuavoIKParams, KuavoPose
29
+ from kuavo_humanoid_sdk.kuavo.core.ros.control import KuavoRobotControl
30
+ from kuavo_humanoid_sdk.kuavo.core.ros.state import KuavoRobotStateCore
31
+ from kuavo_humanoid_sdk.kuavo.core.ros.param import make_robot_param, kuavo_ros_param
32
+ from kuavo_humanoid_sdk.common.logger import SDKLogger
33
+
34
+ # Define robot states
35
+ ROBOT_STATES = [
36
+ State(name='stance', on_enter=['_on_enter_stance']),
37
+ State(name='walk', on_enter=['_on_enter_walk']),
38
+ State(name='trot', on_enter=['_on_enter_trot']),
39
+ State(name='custom_gait', on_enter=['_on_enter_custom_gait'])
40
+ ]
41
+
42
+ # Define state transitions
43
+ ROBOT_TRANSITIONS = [
44
+ {'trigger': 'to_stance', 'source': ['walk', 'trot', 'custom_gait'], 'dest': 'stance'},
45
+ {'trigger': 'to_walk', 'source': ['stance', 'trot', 'custom_gait'], 'dest': 'walk'},
46
+ {'trigger': 'to_trot', 'source': ['stance', 'walk', 'custom_gait'], 'dest': 'trot'},
47
+ {'trigger': 'to_custom_gait', 'source': ['stance', 'custom_gait'], 'dest': 'custom_gait'},
48
+ ]
49
+
50
+ class KuavoRobotCore:
51
+ _instance = None
52
+
53
+ def __new__(cls):
54
+ if cls._instance is None:
55
+ cls._instance = super(KuavoRobotCore, cls).__new__(cls)
56
+ return cls._instance
57
+
58
+ def __init__(self):
59
+ if not hasattr(self, '_initialized'):
60
+ self.machine = Machine(
61
+ model=self,
62
+ states=ROBOT_STATES,
63
+ transitions=ROBOT_TRANSITIONS,
64
+ initial='stance',
65
+ send_event=True
66
+ )
67
+ # robot control
68
+ self._control = KuavoRobotControl()
69
+ self._rb_state = KuavoRobotStateCore()
70
+ self._arm_ctrl_mode = KuavoArmCtrlMode.AutoSwing
71
+ # register gait changed callback
72
+ self._rb_state.register_gait_changed_callback(self._humanoid_gait_changed)
73
+ # initialized
74
+ self._initialized = True
75
+
76
+ def initialize(self, debug: bool=False)->bool:
77
+ """
78
+ raise RuntimeError if initialize failed.
79
+ """
80
+ try:
81
+ info = make_robot_param()
82
+ # init state by gait_name
83
+ gait_name = self._rb_state.gait_name()
84
+ if gait_name is not None:
85
+ to_method = f'to_{gait_name}'
86
+ if hasattr(self, to_method):
87
+ SDKLogger.debug(f"[Core] initialize state: {gait_name}")
88
+ # Call the transition method if it exists
89
+ getattr(self, to_method)()
90
+ else:
91
+ SDKLogger.warn(f"[Core] gait_name is None, use default `stance`")
92
+ # init arm control mode
93
+ arm_ctrl_mode = self._rb_state.arm_control_mode
94
+ if arm_ctrl_mode is not None:
95
+ self._arm_ctrl_mode = arm_ctrl_mode
96
+ SDKLogger.debug(f"[Core] initialize arm control mode: {arm_ctrl_mode}")
97
+ except Exception as e:
98
+ raise RuntimeError(f"[Core] initialize failed: \n"
99
+ f"{e}, please check the robot is launched, "
100
+ f"e.g. `roslaunch humanoid_controllers load_kuavo_real.launch`")
101
+ rb_info = make_robot_param()
102
+ success, err_msg = self._control.initialize(eef_type=rb_info["end_effector_type"], debug=debug)
103
+ if not success:
104
+ raise RuntimeError(f"[Core] initialize failed: \n{err_msg}, please check the robot is launched, "
105
+ f"e.g. `roslaunch humanoid_controllers load_kuavo_real.launch`")
106
+ return True
107
+
108
+ """ ----------------------- Machine State -----------------------"""
109
+ def _on_enter_stance(self, event):
110
+ previous_state = event.transition.source
111
+ if self.state == previous_state:
112
+ SDKLogger.debug(f"[Core] [StateMachine] State unchanged: already in stance state")
113
+ return
114
+
115
+ SDKLogger.debug(f"[Core] [StateMachine] Entering stance state, from {previous_state}")
116
+ if previous_state == 'walk':
117
+ self._control.robot_walk(0.0, 0.0, 0.0) # stop walk state
118
+ start_time = time.time()
119
+ # slow down walk
120
+ try:
121
+ while time.time() - start_time < 1.5:
122
+ self._control.robot_walk(0.0, 0.0, 0.0)
123
+ # linear_x, linear_y, angular_z
124
+ if (abs(self._rb_state.odom_data.linear[0]) < 0.05 and abs(self._rb_state.odom_data.linear[1]) < 0.08
125
+ and abs(self._rb_state.odom_data.angular[2]) < 0.05):
126
+ SDKLogger.debug(f"walk stop, time_cost:{time.time() - start_time}, odom_data:{self._rb_state.odom_data.linear}")
127
+ break
128
+ # SDKLogger.debug(f"kuavo robot linear: {self._rb_state.odom_data.linear}")
129
+ time.sleep(0.1)
130
+ except KeyboardInterrupt:
131
+ pass
132
+ self._control.robot_stance()
133
+ else:
134
+ self._control.robot_stance()
135
+ time.sleep(0.5)
136
+
137
+ def _on_enter_walk(self, event):
138
+ previous_state = event.transition.source
139
+ if self.state == previous_state:
140
+ SDKLogger.debug(f"[Core] [StateMachine] State unchanged: already in walk state")
141
+ return
142
+ SDKLogger.debug(f"[Core] [StateMachine] Entering walk state, from {previous_state}")
143
+
144
+ def _on_enter_trot(self, event):
145
+ previous_state = event.transition.source
146
+ if self.state == previous_state:
147
+ SDKLogger.debug(f"[Core] [StateMachine] State unchanged: already in trot state")
148
+ return
149
+ SDKLogger.debug(f"[Core] [StateMachine] Entering trot state, from {previous_state}")
150
+ self._control.robot_trot()
151
+
152
+ def _on_enter_custom_gait(self, event):
153
+ previous_state = event.transition.source
154
+ if self.state == previous_state:
155
+ SDKLogger.debug(f"[Core] [StateMachine] State unchanged: already in custom_gait state")
156
+ return
157
+ SDKLogger.debug(f"[Core] [StateMachine] Entering custom_gait state, from {previous_state}")
158
+ """ -------------------------------------------------------------"""
159
+
160
+ """ --------------------------- Control -------------------------"""
161
+ def walk(self, linear_x:float, linear_y:float, angular_z:float)-> bool:
162
+ if self.state != 'walk':
163
+ self.to_walk()
164
+
165
+ # +-0.4, +-0.2, +-0.4 => linear_x, linear_y, angular_z
166
+ limited_linear_x = min(0.4, abs(linear_x)) * (1 if linear_x >= 0 else -1)
167
+ limited_linear_y = min(0.2, abs(linear_y)) * (1 if linear_y >= 0 else -1)
168
+ limited_angular_z = min(0.4, abs(angular_z)) * (1 if angular_z >= 0 else -1)
169
+ return self._control.robot_walk(limited_linear_x, limited_linear_y, limited_angular_z)
170
+
171
+ def squat(self, height:float, pitch:float)->bool:
172
+ if self.state != 'stance':
173
+ SDKLogger.warn(f"[Core] control torso height failed, robot is not in stance state({self.state})!")
174
+ return False
175
+
176
+ # Limit height range to [-0.3, 0.0]
177
+ limited_height = min(0.0, max(-0.3, height))
178
+ if height > 0.0 or height < -0.3:
179
+ SDKLogger.warn(f"[Core] height {height} exceeds limit [-0.3, 0.0], will be limited")
180
+
181
+ # Limit pitch range to [-0.4, 0.4]
182
+ limited_pitch = min(0.4, max(-0.4, pitch))
183
+ if abs(pitch) > 0.4:
184
+ SDKLogger.warn(f"[Core] pitch {pitch} exceeds limit [-0.4, 0.4], will be limited")
185
+
186
+ return self._control.control_torso_height(limited_height, limited_pitch)
187
+
188
+ def step_control(self, target_pose:list, dt:float=0.4, is_left_first_default:bool=True, collision_check:bool=True)->bool:
189
+ """
190
+ Control the robot's motion by step.
191
+ Raises:
192
+ ValueError: If target_pose length is not 4.
193
+ RuntimeError: If the robot is not in stance state when trying to control step motion.
194
+ """
195
+ if len(target_pose) != 4:
196
+ raise ValueError(f"[Core] target_pose length must be 4, but got {len(target_pose)}")
197
+
198
+ # Wait up to 1.0s for stance state
199
+ wait_time = 0
200
+ while self._rb_state.gait_name() != 'stance' and wait_time < 1.0:
201
+ time.sleep(0.1)
202
+ wait_time += 0.1
203
+
204
+ if self._rb_state.gait_name() != 'stance':
205
+ raise RuntimeError(f"[Core] control robot step failed, robot is not in stance state, {self._rb_state.gait_name()}!")
206
+
207
+ if self.state != 'stance':
208
+ raise RuntimeError(f"[Core] control robot step failed, robot is not in stance state({self.state})!")
209
+
210
+ com_height = self._rb_state.com_height
211
+ # print(f"[Core] Current COM height: {com_height:.2f}m")
212
+ # Check height limits based on current COM height
213
+ MIN_COM_HEIGHT = 0.66 # Minimum allowed COM height in meters
214
+ MAX_COM_HEIGHT = 0.86 # Maximum allowed COM height in meters
215
+
216
+ # Validate COM height constraints
217
+ if target_pose[2] < 0 and com_height < MIN_COM_HEIGHT:
218
+ SDKLogger.warn(f"[Core] Cannot squat lower: COM height {com_height:.2f}m below minimum {MIN_COM_HEIGHT}m")
219
+ return None
220
+
221
+ if target_pose[2] > 0 and com_height > MAX_COM_HEIGHT:
222
+ SDKLogger.warn(f"[Core] Cannot stand higher: COM height {com_height:.2f}m above maximum {MAX_COM_HEIGHT}m")
223
+ return None
224
+
225
+ # Ensure target height is within allowed range if height change requested
226
+ if target_pose[2] != 0:
227
+ target_height = com_height + target_pose[2]
228
+ if target_height < MIN_COM_HEIGHT:
229
+ SDKLogger.warn(f"[Core] Target height {target_height:.2f}m below minimum {MIN_COM_HEIGHT}m, limiting")
230
+ target_pose[2] = MIN_COM_HEIGHT - com_height
231
+ elif target_height > MAX_COM_HEIGHT:
232
+ SDKLogger.warn(f"[Core] Target height {target_height:.2f}m above maximum {MAX_COM_HEIGHT}m, limiting")
233
+ target_pose[2] = MAX_COM_HEIGHT - com_height
234
+
235
+ # TODO(kuavo): 根据实物测试来调整....
236
+ if com_height > 0.82:
237
+ max_x_step = 0.20
238
+ max_y_step = 0.20
239
+ max_yaw_step = 90
240
+ else:
241
+ max_x_step = 0.10
242
+ max_y_step = 0.10
243
+ max_yaw_step = 30
244
+
245
+ body_poses = []
246
+
247
+ # 计算目标点到原点的距离和朝向
248
+ target_dist_x = abs(target_pose[0])
249
+ target_dist_y = abs(target_pose[1])
250
+ target_yaw = target_pose[3] * 180 / math.pi # Convert yaw from radians to degrees
251
+
252
+ # 计算需要的步数(考虑x位移、y位移和转角)
253
+ steps_for_x = int(np.ceil(target_dist_x / max_x_step))
254
+ steps_for_y = int(np.ceil(target_dist_y / max_y_step))
255
+ steps_for_yaw = int(np.ceil(abs(target_yaw) / max_yaw_step))
256
+ steps_needed = max(steps_for_x, steps_for_y, steps_for_yaw)
257
+ # print(f"[Core] Steps needed - X: {steps_for_x}, Y: {steps_for_y}, Yaw: {steps_for_yaw}, Total: {steps_needed}")
258
+
259
+ # 计算每一步的增量
260
+ dx = target_pose[0] / steps_needed
261
+ dy = target_pose[1] / steps_needed
262
+ dyaw = target_yaw / steps_needed
263
+
264
+ # 分解为多个小步,沿着直线路径前进并逐步调整朝向
265
+ for i in range(steps_needed):
266
+ x = dx * (i+1)
267
+ y = dy * (i+1)
268
+ z = target_pose[2]
269
+ yaw = dyaw * (i+1)
270
+ body_poses.append([x, y, 0.0, yaw])
271
+
272
+ # print("target_pose:", target_pose)
273
+ # print("body_poses:", body_poses)
274
+
275
+ if not self._control.step_control(body_poses, dt, is_left_first_default, collision_check):
276
+ return False
277
+
278
+ # Wait for gait to switch to custom_gait
279
+ start_time = time.time()
280
+ while not self._rb_state.is_gait('custom_gait'):
281
+ if time.time() - start_time > 1.0: # 1.0s timeout
282
+ SDKLogger.warn("[Core] Timeout waiting for gait to switch to custom_gait")
283
+ return False
284
+ time.sleep(0.01)
285
+
286
+ return True
287
+
288
+ def execute_gesture(self, gestures:list)->bool:
289
+ return self._control.execute_gesture(gestures)
290
+
291
+ def get_gesture_names(self)->list:
292
+ return self._control.get_gesture_names()
293
+
294
+ def control_robot_dexhand(self, left_position:list, right_position:list)->bool:
295
+ return self._control.control_robot_dexhand(left_position, right_position)
296
+
297
+ def control_leju_claw(self, postions:list, velocities:list=[90, 90], torques:list=[1.0, 1.0]) ->bool:
298
+ return self._control.control_leju_claw(postions, velocities, torques)
299
+
300
+ def control_robot_head(self, yaw:float, pitch:float)->bool:
301
+ # Convert radians to degrees
302
+ yaw_deg = yaw * 180 / math.pi
303
+ pitch_deg = pitch * 180 / math.pi
304
+ return self._control.control_robot_head(yaw_deg, pitch_deg)
305
+
306
+ def control_robot_arm_traj(self, joint_data:list)->bool:
307
+ if self.state != 'stance':
308
+ raise RuntimeError(f"[Core] control_robot_arm_traj failed: robot must be in stance state, current state: {self.state}")
309
+
310
+ # change to external control mode
311
+ if self._arm_ctrl_mode != KuavoArmCtrlMode.ExternalControl:
312
+ SDKLogger.debug("[Core] control_robot_arm_traj, current arm mode != ExternalControl, change it.")
313
+ if not self.change_robot_arm_ctrl_mode(KuavoArmCtrlMode.ExternalControl):
314
+ SDKLogger.warn("[Core] control_robot_arm_traj failed, change robot arm ctrl mode failed!")
315
+ return False
316
+ return self._control.control_robot_arm_traj(joint_data)
317
+
318
+ def control_robot_arm_target_poses(self, times:list, joint_q:list)->bool:
319
+ if self.state != 'stance':
320
+ raise RuntimeError("[Core] control_robot_arm_target_poses failed: robot must be in stance state")
321
+
322
+ if self._arm_ctrl_mode != KuavoArmCtrlMode.ExternalControl:
323
+ SDKLogger.debug("[Core] control_robot_arm_target_poses, current arm mode != ExternalControl, change it.")
324
+ if not self.change_robot_arm_ctrl_mode(KuavoArmCtrlMode.ExternalControl):
325
+ SDKLogger.warn("[Core] control_robot_arm_target_poses failed, change robot arm ctrl mode failed!")
326
+ return False
327
+
328
+ return self._control.control_robot_arm_target_poses(times, joint_q)
329
+
330
+ def change_robot_arm_ctrl_mode(self, mode:KuavoArmCtrlMode)->bool:
331
+ timeout = 1.0
332
+ count = 0
333
+ while self._rb_state.arm_control_mode != mode:
334
+ SDKLogger.debug(f"[Core] Change robot arm control from {self._rb_state.arm_control_mode} to {mode}, retry: {count}")
335
+ self._control.change_robot_arm_ctrl_mode(mode)
336
+ if self._rb_state.arm_control_mode == mode:
337
+ break
338
+ if timeout <= 0:
339
+ SDKLogger.warn("[Core] Change robot arm control mode timeout!")
340
+ return False
341
+ timeout -= 0.1
342
+ time.sleep(0.1)
343
+ count += 1
344
+
345
+ if not hasattr(self, '_arm_ctrl_mode_lock'):
346
+ self._arm_ctrl_mode_lock = threading.Lock()
347
+ with self._arm_ctrl_mode_lock:
348
+ # 手臂控制模式切换成功,更新当前手臂控制模式
349
+ self._arm_ctrl_mode = mode # update arm ctrl mode
350
+
351
+ return True
352
+
353
+ def robot_arm_reset(self)->bool:
354
+ if self.state != 'stance':
355
+ SDKLogger.warn("[Core] robot arm reset failed, robot is not in stance state!")
356
+ return
357
+
358
+ # init_pos = [0.0]*14
359
+ # if not self.control_robot_arm_target_poses([1.5], [init_pos]):
360
+ # SDKLogger.warn("[Core] robot arm reset failed, control robot arm traj failed!")
361
+ # return False
362
+
363
+ return self.change_robot_arm_ctrl_mode(KuavoArmCtrlMode.AutoSwing)
364
+
365
+ """ ------------------------------------------------------------------------"""
366
+ """ Arm Forward kinematics && Arm Inverse kinematics """
367
+ def arm_ik(self,
368
+ l_eef_pose: KuavoPose,
369
+ r_eef_pose: KuavoPose,
370
+ l_elbow_pos_xyz: list = [0.0, 0.0, 0.0],
371
+ r_elbow_pos_xyz: list = [0.0, 0.0, 0.0],
372
+ arm_q0: list = None,
373
+ params: KuavoIKParams=None) -> list:
374
+ return self._control.arm_ik(l_eef_pose, r_eef_pose, l_elbow_pos_xyz, r_elbow_pos_xyz, arm_q0, params)
375
+
376
+
377
+ def arm_fk(self, q: list) -> Tuple[KuavoPose, KuavoPose]:
378
+ return self._control.arm_fk(q)
379
+
380
+ """ Callbacks """
381
+ def _humanoid_gait_changed(self, current_time: float, gait_name: str):
382
+ if self.state != gait_name:
383
+ # Check if to_$gait_name method exists
384
+ to_method = f'to_{gait_name}'
385
+ if hasattr(self, to_method):
386
+ SDKLogger.debug(f"[Core] Received gait change notification: {gait_name} at time {current_time}")
387
+ # Call the transition method if it exists
388
+ getattr(self, to_method)()
@@ -0,0 +1,85 @@
1
+ import copy
2
+ import threading
3
+ from queue import Queue
4
+ from kuavo_humanoid_sdk.interfaces.data_types import EndEffectorSide
5
+ from kuavo_humanoid_sdk.kuavo.core.core import KuavoRobotCore
6
+ from kuavo_humanoid_sdk.common.logger import SDKLogger
7
+ class DexHandControl:
8
+ _instance = None
9
+
10
+ def __new__(cls, *args, **kwargs):
11
+ if not cls._instance:
12
+ cls._instance = super().__new__(cls)
13
+ return cls._instance
14
+
15
+ def __init__(self):
16
+ if not hasattr(self, '_initialized'):
17
+ self.queue = Queue() # Initialize a queue to hold commands
18
+ self._kuavo_core = KuavoRobotCore()
19
+ self.thread = threading.Thread(target=self._process_queue) # Create a thread to process the queue
20
+ self.thread.daemon = True # Set the thread as a daemon so it will exit when the main program exits
21
+ self.thread.start() # Start the thread
22
+ # Initialize last command position, torque, and velocity
23
+ self.last_cmd_position = {EndEffectorSide.LEFT: [0] * 6, EndEffectorSide.RIGHT: [0] * 6}
24
+ self._initialized = True
25
+
26
+ def control(self, target_positions: list, side: EndEffectorSide):
27
+ self.queue.put(('position', EndEffectorSide(side.value), target_positions))
28
+
29
+ def make_gestures(self, gestures:list)->bool:
30
+ """
31
+ Make a gesture for the dexhand.
32
+ Args:
33
+ gestures: list of gestures to make.
34
+ [{'gesture_name': 'name', 'side': EndEffectorSide.LEFT},...]
35
+ """
36
+ exec_gs = []
37
+ for gs in gestures:
38
+ side = gs['hand_side']
39
+ gesture_name = gs['gesture_name']
40
+ if side == EndEffectorSide.LEFT:
41
+ exec_gs.append({'gesture_name': gesture_name, 'hand_side': 0})
42
+ elif side == EndEffectorSide.RIGHT:
43
+ exec_gs.append({'gesture_name': gesture_name, 'hand_side': 1})
44
+ elif side == EndEffectorSide.BOTH:
45
+ exec_gs.append({'gesture_name': gesture_name, 'hand_side': 2})
46
+
47
+ if len(exec_gs) == 0:
48
+ SDKLogger.error('No gestures to make')
49
+ return False
50
+
51
+ # Make gestures
52
+ self._kuavo_core.execute_gesture(exec_gs)
53
+
54
+ def get_gesture_names(self)->list:
55
+ """
56
+ Get the names of all gestures.
57
+ """
58
+ gs = self._kuavo_core.get_gesture_names()
59
+ if not gs:
60
+ return None
61
+ return gs
62
+
63
+ def _process_queue(self):
64
+ while True:
65
+ try:
66
+ command, side, data = self.queue.get() # This will block until an item is available in the queue
67
+ SDKLogger.debug(f'[DexHandControl] Received command: {command}, for side: {side}, with data: {data}')
68
+ if command == 'position':
69
+ pos = self.last_cmd_position[EndEffectorSide.LEFT] + self.last_cmd_position[EndEffectorSide.RIGHT]
70
+ if side == EndEffectorSide.BOTH:
71
+ pos = copy.deepcopy(data)
72
+ elif side == EndEffectorSide.LEFT:
73
+ pos[:6] = data
74
+ elif side == EndEffectorSide.RIGHT:
75
+ pos[6:] = data
76
+ else:
77
+ return
78
+ self._kuavo_core.control_robot_dexhand(left_position=pos[:6], right_position=pos[6:])
79
+ self.last_cmd_position[EndEffectorSide.LEFT] = pos[:6]
80
+ self.last_cmd_position[EndEffectorSide.RIGHT] = pos[6:]
81
+
82
+ # task done.
83
+ self.queue.task_done()
84
+ except KeyboardInterrupt:
85
+ break
@@ -0,0 +1,67 @@
1
+ import threading
2
+ from queue import Queue
3
+ from kuavo_humanoid_sdk.kuavo.core.core import KuavoRobotCore
4
+ from kuavo_humanoid_sdk.interfaces.data_types import EndEffectorSide
5
+ from kuavo_humanoid_sdk.common.logger import SDKLogger
6
+ class LejuClawControl:
7
+ _instance = None
8
+
9
+ def __new__(cls, *args, **kwargs):
10
+ if not cls._instance:
11
+ cls._instance = super().__new__(cls)
12
+ return cls._instance
13
+
14
+ def __init__(self):
15
+ if not hasattr(self, '_initialized'):
16
+ self._initialized = True
17
+ self.queue = Queue() # Initialize a queue to hold commands
18
+ self._kuavo_core = KuavoRobotCore()
19
+ self.thread = threading.Thread(target=self._process_queue) # Create a thread to process the queue
20
+ self.thread.daemon = True # Set the thread as a daemon so it will exit when the main program exits
21
+ self.thread.start() # Start the thread
22
+ self.last_cmd_position = {EndEffectorSide.LEFT: 0, EndEffectorSide.RIGHT: 0}
23
+ self.last_cmd_torque = {EndEffectorSide.LEFT: 1.0, EndEffectorSide.RIGHT: 1.0}
24
+ self.last_cmd_velocity = {EndEffectorSide.LEFT: 90, EndEffectorSide.RIGHT: 90}
25
+ def control(self, position:list, velocity:list, torque:list, side:EndEffectorSide):
26
+ self.queue.put((EndEffectorSide(side.value), position, velocity, torque))
27
+
28
+ def release(self, side:EndEffectorSide):
29
+ if side == EndEffectorSide.BOTH:
30
+ self.queue.put((EndEffectorSide.BOTH, [0.0, 0.0], [90, 90], [1.0, 1.0]))
31
+ else:
32
+ self.queue.put((EndEffectorSide(side.value), [0], [90], [1.0]))
33
+
34
+ def _process_queue(self):
35
+ while True:
36
+ try:
37
+ side, q, v, tau = self.queue.get()
38
+ SDKLogger.debug(f"[LejuClawControl] Received command: {side} to {q} with {v} and {tau}")
39
+ postions = [self.last_cmd_position[EndEffectorSide.LEFT], self.last_cmd_position[EndEffectorSide.RIGHT]]
40
+ velocities = [self.last_cmd_velocity[EndEffectorSide.LEFT], self.last_cmd_velocity[EndEffectorSide.RIGHT]]
41
+ torques = [self.last_cmd_torque[EndEffectorSide.LEFT], self.last_cmd_torque[EndEffectorSide.RIGHT]]
42
+ if side == EndEffectorSide.LEFT:
43
+ postions[0] = q[0]
44
+ velocities[0] = v[0]
45
+ torques[0] = tau[0]
46
+ elif side == EndEffectorSide.RIGHT:
47
+ postions[1] = q[0]
48
+ velocities[1] = v[0]
49
+ torques[1] = tau[0]
50
+ else: # both
51
+ postions = q
52
+ velocities = v
53
+ torques = tau
54
+
55
+ # call ros service.
56
+ self._kuavo_core.control_leju_claw(postions, velocities, torques)
57
+ # update last cmd
58
+ self.last_cmd_position[EndEffectorSide.LEFT] = postions[0]
59
+ self.last_cmd_position[EndEffectorSide.RIGHT] = postions[1]
60
+ self.last_cmd_velocity[EndEffectorSide.LEFT] = velocities[0]
61
+ self.last_cmd_velocity[EndEffectorSide.RIGHT] = velocities[1]
62
+ self.last_cmd_torque[EndEffectorSide.LEFT] = torques[0]
63
+ self.last_cmd_torque[EndEffectorSide.RIGHT] = torques[1]
64
+ # mark task as done
65
+ self.queue.task_done()
66
+ except KeyboardInterrupt:
67
+ break