kuavo-humanoid-sdk-ws 1.3.2__20260122221834-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.
Files changed (43) hide show
  1. kuavo_humanoid_sdk/__init__.py +6 -0
  2. kuavo_humanoid_sdk/common/launch_robot_tool.py +170 -0
  3. kuavo_humanoid_sdk/common/logger.py +45 -0
  4. kuavo_humanoid_sdk/common/websocket_kuavo_sdk.py +26 -0
  5. kuavo_humanoid_sdk/interfaces/__init__.py +4 -0
  6. kuavo_humanoid_sdk/interfaces/data_types.py +293 -0
  7. kuavo_humanoid_sdk/interfaces/end_effector.py +62 -0
  8. kuavo_humanoid_sdk/interfaces/robot.py +22 -0
  9. kuavo_humanoid_sdk/interfaces/robot_info.py +56 -0
  10. kuavo_humanoid_sdk/kuavo/__init__.py +12 -0
  11. kuavo_humanoid_sdk/kuavo/core/audio.py +32 -0
  12. kuavo_humanoid_sdk/kuavo/core/core.py +755 -0
  13. kuavo_humanoid_sdk/kuavo/core/dex_hand_control.py +114 -0
  14. kuavo_humanoid_sdk/kuavo/core/leju_claw_control.py +67 -0
  15. kuavo_humanoid_sdk/kuavo/core/ros/audio.py +86 -0
  16. kuavo_humanoid_sdk/kuavo/core/ros/control.py +1325 -0
  17. kuavo_humanoid_sdk/kuavo/core/ros/observation.py +125 -0
  18. kuavo_humanoid_sdk/kuavo/core/ros/param.py +335 -0
  19. kuavo_humanoid_sdk/kuavo/core/ros/sat_utils.py +103 -0
  20. kuavo_humanoid_sdk/kuavo/core/ros/state.py +426 -0
  21. kuavo_humanoid_sdk/kuavo/core/ros/tools.py +197 -0
  22. kuavo_humanoid_sdk/kuavo/core/ros/vision.py +280 -0
  23. kuavo_humanoid_sdk/kuavo/core/ros_env.py +236 -0
  24. kuavo_humanoid_sdk/kuavo/dexterous_hand.py +198 -0
  25. kuavo_humanoid_sdk/kuavo/leju_claw.py +232 -0
  26. kuavo_humanoid_sdk/kuavo/robot.py +550 -0
  27. kuavo_humanoid_sdk/kuavo/robot_arm.py +235 -0
  28. kuavo_humanoid_sdk/kuavo/robot_audio.py +39 -0
  29. kuavo_humanoid_sdk/kuavo/robot_head.py +39 -0
  30. kuavo_humanoid_sdk/kuavo/robot_info.py +235 -0
  31. kuavo_humanoid_sdk/kuavo/robot_observation.py +69 -0
  32. kuavo_humanoid_sdk/kuavo/robot_state.py +346 -0
  33. kuavo_humanoid_sdk/kuavo/robot_tool.py +58 -0
  34. kuavo_humanoid_sdk/kuavo/robot_vision.py +82 -0
  35. kuavo_humanoid_sdk/kuavo/robot_waist.py +53 -0
  36. kuavo_humanoid_sdk/kuavo_strategy/__init__.py +2 -0
  37. kuavo_humanoid_sdk/kuavo_strategy/grasp_box/grasp_box_strategy.py +418 -0
  38. kuavo_humanoid_sdk/kuavo_strategy/kuavo_strategy.py +63 -0
  39. kuavo_humanoid_sdk/msg/__init__.py +4 -0
  40. kuavo_humanoid_sdk_ws-1.3.2.dist-info/METADATA +276 -0
  41. kuavo_humanoid_sdk_ws-1.3.2.dist-info/RECORD +43 -0
  42. kuavo_humanoid_sdk_ws-1.3.2.dist-info/WHEEL +6 -0
  43. kuavo_humanoid_sdk_ws-1.3.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,755 @@
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 threading
23
+ import numpy as np
24
+ from typing import Tuple
25
+ from transitions import Machine, State
26
+
27
+ from kuavo_humanoid_sdk.interfaces.data_types import KuavoArmCtrlMode, KuavoIKParams, KuavoPose, KuavoManipulationMpcFrame, KuavoManipulationMpcCtrlMode, KuavoManipulationMpcControlFlow
28
+ from kuavo_humanoid_sdk.kuavo.core.ros.control import KuavoRobotControlWebsocket
29
+ from kuavo_humanoid_sdk.kuavo.core.ros.state import KuavoRobotStateCoreWebsocket
30
+ from kuavo_humanoid_sdk.kuavo.core.ros.param import make_robot_param
31
+ from kuavo_humanoid_sdk.common.logger import SDKLogger
32
+ # Define robot states
33
+ ROBOT_STATES = [
34
+ State(name='stance', on_enter=['_on_enter_stance']),
35
+ State(name='walk', on_enter=['_on_enter_walk']),
36
+ State(name='trot', on_enter=['_on_enter_trot']),
37
+ State(name='custom_gait', on_enter=['_on_enter_custom_gait']),
38
+ State(name='command_pose_world', on_enter=['_on_enter_command_pose_world']),
39
+ State(name='command_pose', on_enter=['_on_enter_command_pose']),
40
+ ]
41
+
42
+ # Define state transitions
43
+ ROBOT_TRANSITIONS = [
44
+ {'trigger': 'to_stance', 'source': ['walk', 'trot', 'custom_gait', 'command_pose_world', 'command_pose'], '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
+ {'trigger': 'to_command_pose_world', 'source': ['stance', 'command_pose_world'], 'dest': 'command_pose_world'},
49
+ {'trigger': 'to_command_pose', 'source': ['stance', 'command_pose'], 'dest': 'command_pose'},
50
+ ]
51
+
52
+ class KuavoRobotCore:
53
+ _instance = None
54
+
55
+ def __new__(cls):
56
+ if cls._instance is None:
57
+ cls._instance = super(KuavoRobotCore, cls).__new__(cls)
58
+ return cls._instance
59
+
60
+ def __init__(self):
61
+ if not hasattr(self, '_initialized'):
62
+ self.machine = Machine(
63
+ model=self,
64
+ states=ROBOT_STATES,
65
+ transitions=ROBOT_TRANSITIONS,
66
+ initial='stance',
67
+ send_event=True
68
+ )
69
+ # robot control
70
+ self._control = KuavoRobotControlWebsocket()
71
+ self._rb_state = KuavoRobotStateCoreWebsocket()
72
+
73
+ # manipulation mpc
74
+ self._manipulation_mpc_frame = KuavoManipulationMpcFrame.KeepCurrentFrame
75
+ self._manipulation_mpc_ctrl_mode = KuavoManipulationMpcCtrlMode.NoControl
76
+ self._manipulation_mpc_control_flow = KuavoManipulationMpcControlFlow.ThroughFullBodyMpc
77
+
78
+ self._arm_ctrl_mode = KuavoArmCtrlMode.AutoSwing
79
+
80
+ # register gait changed callback
81
+ self._rb_state.register_gait_changed_callback(self._humanoid_gait_changed)
82
+ # initialized
83
+ self._initialized = True
84
+
85
+ def initialize(self, debug: bool=False)->bool:
86
+ """
87
+ raise RuntimeError if initialize failed.
88
+ """
89
+ try:
90
+ # init state by gait_name
91
+ gait_name = self._rb_state.gait_name()
92
+ if gait_name is not None:
93
+ to_method = f'to_{gait_name}'
94
+ if hasattr(self, to_method):
95
+ SDKLogger.debug(f"[Core] initialize state: {gait_name}")
96
+ # Call the transition method if it exists
97
+ getattr(self, to_method)()
98
+ else:
99
+ SDKLogger.warn(f"[Core] gait_name is None, use default `stance`")
100
+ # init arm control mode
101
+ arm_ctrl_mode = self._rb_state.arm_control_mode
102
+ if arm_ctrl_mode is not None:
103
+ self._arm_ctrl_mode = arm_ctrl_mode
104
+ SDKLogger.debug(f"[Core] initialize arm control mode: {arm_ctrl_mode}")
105
+
106
+ # init manipulation mpc
107
+ manipulation_mpc_frame = self._rb_state.manipulation_mpc_frame
108
+ if manipulation_mpc_frame is not None:
109
+ self._manipulation_mpc_frame = manipulation_mpc_frame
110
+ SDKLogger.debug(f"[Core] initialize manipulation mpc frame: {manipulation_mpc_frame}")
111
+ manipulation_mpc_ctrl_mode = self._rb_state.manipulation_mpc_ctrl_mode
112
+ if manipulation_mpc_ctrl_mode is not None:
113
+ self._manipulation_mpc_ctrl_mode = manipulation_mpc_ctrl_mode
114
+ SDKLogger.debug(f"[Core] initialize manipulation mpc ctrl mode: {manipulation_mpc_ctrl_mode}")
115
+ manipulation_mpc_control_flow = self._rb_state.manipulation_mpc_control_flow
116
+ if manipulation_mpc_control_flow is not None:
117
+ self._manipulation_mpc_control_flow = manipulation_mpc_control_flow
118
+ SDKLogger.debug(f"[Core] initialize manipulation mpc control flow: {manipulation_mpc_control_flow}")
119
+
120
+ except Exception as e:
121
+ raise RuntimeError(f"[Core] initialize failed: \n"
122
+ f"{e}, please check the robot is launched, "
123
+ f"e.g. `roslaunch humanoid_controllers load_kuavo_real.launch`")
124
+ self._rb_info = make_robot_param()
125
+ self._robot_version_major = (int(self._rb_info['robot_version']) // 10) % 10000
126
+ success, err_msg = self._control.initialize(eef_type=self._rb_info["end_effector_type"], debug=debug)
127
+
128
+ if not success:
129
+ raise RuntimeError(f"[Core] initialize failed: \n{err_msg}, please check the robot is launched, "
130
+ f"e.g. `roslaunch humanoid_controllers load_kuavo_real.launch`")
131
+ return True
132
+
133
+ """ ----------------------- Machine State -----------------------"""
134
+ def _on_enter_stance(self, event):
135
+ previous_state = event.transition.source
136
+ if self.state == previous_state:
137
+ SDKLogger.debug(f"[Core] [StateMachine] State unchanged: already in stance state")
138
+ return
139
+
140
+ SDKLogger.debug(f"[Core] [StateMachine] Entering stance state, from {previous_state}")
141
+ if previous_state == 'walk':
142
+ self._control.robot_walk(0.0, 0.0, 0.0) # stop walk state
143
+ start_time = time.time()
144
+ # slow down walk
145
+ try:
146
+ while time.time() - start_time < 1.5:
147
+ self._control.robot_walk(0.0, 0.0, 0.0)
148
+ # linear_x, linear_y, angular_z
149
+ if (abs(self._rb_state.odom_data.linear[0]) < 0.05 and abs(self._rb_state.odom_data.linear[1]) < 0.08
150
+ and abs(self._rb_state.odom_data.angular[2]) < 0.05):
151
+ SDKLogger.debug(f"walk stop, time_cost:{time.time() - start_time}, odom_data:{self._rb_state.odom_data.linear}")
152
+ break
153
+ # SDKLogger.debug(f"kuavo robot linear: {self._rb_state.odom_data.linear}")
154
+ time.sleep(0.1)
155
+ except KeyboardInterrupt:
156
+ pass
157
+ self._control.robot_stance()
158
+ else:
159
+ self._control.robot_stance()
160
+ time.sleep(0.5)
161
+
162
+ def _on_enter_walk(self, event):
163
+ previous_state = event.transition.source
164
+ if self.state == previous_state:
165
+ SDKLogger.debug(f"[Core] [StateMachine] State unchanged: already in walk state")
166
+ return
167
+ SDKLogger.debug(f"[Core] [StateMachine] Entering walk state, from {previous_state}")
168
+
169
+ def _on_enter_trot(self, event):
170
+ previous_state = event.transition.source
171
+ if self.state == previous_state:
172
+ SDKLogger.debug(f"[Core] [StateMachine] State unchanged: already in trot state")
173
+ return
174
+ SDKLogger.debug(f"[Core] [StateMachine] Entering trot state, from {previous_state}")
175
+ self._control.robot_trot()
176
+
177
+ def _on_enter_custom_gait(self, event):
178
+ previous_state = event.transition.source
179
+ if self.state == previous_state:
180
+ SDKLogger.debug(f"[Core] [StateMachine] State unchanged: already in custom_gait state")
181
+ return
182
+ SDKLogger.debug(f"[Core] [StateMachine] Entering custom_gait state, from {previous_state}")
183
+
184
+ def _on_enter_command_pose_world(self, event):
185
+ previous_state = event.transition.source
186
+ if self.state == previous_state:
187
+ SDKLogger.debug(f"[Core] [StateMachine] State unchanged: already in command_pose_world state")
188
+ return
189
+ SDKLogger.debug(f"[Core] [StateMachine] Entering command_pose_world state, from {previous_state}")
190
+
191
+ def _on_enter_command_pose(self, event):
192
+ previous_state = event.transition.source
193
+ if self.state == previous_state:
194
+ SDKLogger.debug(f"[Core] [StateMachine] State unchanged: already in command_pose state")
195
+ return
196
+ SDKLogger.debug(f"[Core] [StateMachine] Entering command_pose state, from {previous_state}")
197
+
198
+ """ -------------------------------------------------------------"""
199
+
200
+ """ --------------------------- Control -------------------------"""
201
+ def walk(self, linear_x:float, linear_y:float, angular_z:float)-> bool:
202
+ if self.state != 'walk':
203
+ self.to_walk()
204
+
205
+ if self._robot_version_major == 1:
206
+ MAX_LINEAR_X = 0.3
207
+ MAX_LINEAR_Y = 0.2
208
+ MAX_ANGULAR_Z = 0.3
209
+ elif self._robot_version_major == 4 or self._robot_version_major == 5:
210
+ MAX_LINEAR_X = 0.4
211
+ MAX_LINEAR_Y = 0.2
212
+ MAX_ANGULAR_Z = 0.4
213
+ else:
214
+ SDKLogger.warn("[Core] walk failed: robot version is not supported, current version major: {self._robot_version_major}")
215
+ return False
216
+
217
+ limited_linear_x = min(MAX_LINEAR_X, abs(linear_x)) * (1 if linear_x >= 0 else -1)
218
+ limited_linear_y = min(MAX_LINEAR_Y, abs(linear_y)) * (1 if linear_y >= 0 else -1)
219
+ limited_angular_z = min(MAX_ANGULAR_Z, abs(angular_z)) * (1 if angular_z >= 0 else -1)
220
+ return self._control.robot_walk(limited_linear_x, limited_linear_y, limited_angular_z)
221
+
222
+ def squat(self, height:float, pitch:float)->bool:
223
+ if self.state != 'stance':
224
+ SDKLogger.warn(f"[Core] control torso height failed, robot is not in stance state({self.state})!")
225
+ return False
226
+
227
+ if self._robot_version_major == 1:
228
+ MIN_HEIGHT = -0.35
229
+ MAX_HEIGHT = 0.1
230
+ MIN_PITCH = 0
231
+ MAX_PITCH = 0
232
+ if pitch != 0:
233
+ SDKLogger.warn("[Core] roban2 pitch is not supported, will be set to 0")
234
+ pitch = 0
235
+ elif self._robot_version_major == 4 or self._robot_version_major == 5:
236
+ MIN_HEIGHT = -0.35
237
+ MAX_HEIGHT = 0.1
238
+ MIN_PITCH = 0
239
+ MAX_PITCH = 0.4
240
+ else:
241
+ SDKLogger.warn("[Core] control torso height failed: robot version is not supported, current version major: {self._robot_version_major}")
242
+ return False
243
+
244
+ # Limit height range
245
+ limited_height = min(MAX_HEIGHT, max(MIN_HEIGHT, height))
246
+ if height > MAX_HEIGHT or height < MIN_HEIGHT:
247
+ SDKLogger.warn(f"[Core] height {height} exceeds limit [{MIN_HEIGHT}, {MAX_HEIGHT}], will be limited")
248
+
249
+ # Limit pitch range
250
+ limited_pitch = min(MAX_PITCH, max(MIN_PITCH, pitch))
251
+ if abs(pitch) > MAX_PITCH:
252
+ SDKLogger.warn(f"[Core] pitch {pitch} exceeds limit [{MIN_PITCH}, {MAX_PITCH}], will be limited")
253
+
254
+ # 结合当前高度做过滤
255
+ target_height = self._rb_info['init_stand_height'] + limited_height
256
+ # 躯干上升运动变化不宜过大, 目标高度减去实时躯干高度大于阈值
257
+ HEIGHT_CHANGE_THRESHOLD = 0.25
258
+ if (self._rb_state.com_height < target_height) and (target_height - self._rb_state.com_height) >= HEIGHT_CHANGE_THRESHOLD:
259
+ limited_height = (self._rb_state.com_height + HEIGHT_CHANGE_THRESHOLD)
260
+ print(f"\033[33mWarning! Height change too large, limiting to safe range,reset height to {limited_height}\033[0m")
261
+ else:
262
+ limited_height = target_height
263
+
264
+ return self._control.control_torso_height(limited_height, limited_pitch)
265
+
266
+ def step_control(self, target_pose:list, dt:float=0.4, is_left_first_default:bool=True, collision_check:bool=True)->bool:
267
+ """
268
+ Control the robot's motion by step.
269
+ Raises:
270
+ ValueError: If target_pose length is not 4.
271
+ RuntimeError: If the robot is not in stance state when trying to control step motion.
272
+ """
273
+ if len(target_pose) != 4:
274
+ raise ValueError(f"[Core] target_pose length must be 4, but got {len(target_pose)}")
275
+
276
+ # Wait up to 1.0s for stance state
277
+ wait_time = 0
278
+ while self._rb_state.gait_name() != 'stance' and wait_time < 1.0:
279
+ time.sleep(0.1)
280
+ wait_time += 0.1
281
+
282
+ if self._rb_state.gait_name() != 'stance':
283
+ raise RuntimeError(f"[Core] control robot step failed, robot is not in stance state, {self._rb_state.gait_name()}!")
284
+
285
+ if self.state != 'stance':
286
+ raise RuntimeError(f"[Core] control robot step failed, robot is not in stance state({self.state})!")
287
+
288
+ com_height = self._rb_state.com_height
289
+ # print(f"[Core] Current COM height: {com_height:.2f}m")
290
+ # Check height limits based on current COM height
291
+ MIN_COM_HEIGHT = self._rb_info['init_stand_height'] - 0.15 # Minimum allowed COM height in meters
292
+ MAX_COM_HEIGHT = self._rb_info['init_stand_height'] + 0.02 # Maximum allowed COM height in meters
293
+
294
+ if com_height < MIN_COM_HEIGHT:
295
+ print(f"\033[31m[Core] Torso height too low, control failed: current COM height {com_height:.2f}m is below the minimum allowed height {MIN_COM_HEIGHT}m\033[0m")
296
+ return False
297
+
298
+ # Validate COM height constraints
299
+ if target_pose[2] < 0 and com_height < MIN_COM_HEIGHT:
300
+ print(f"\033[33mWarning! Cannot squat lower: COM height {com_height:.2f}m below minimum {MIN_COM_HEIGHT}m\033[0m")
301
+ return False
302
+
303
+
304
+ if target_pose[2] > 0 and com_height > MAX_COM_HEIGHT:
305
+ print(f"\033[33mWarning! Cannot stand higher: COM height {com_height:.2f}m above maximum {MAX_COM_HEIGHT}m\033[0m")
306
+ return False
307
+
308
+
309
+ # Ensure target height is within allowed range if height change requested
310
+ if target_pose[2] != 0:
311
+ target_height = com_height + target_pose[2]
312
+ if target_height < MIN_COM_HEIGHT:
313
+ SDKLogger.warn(f"[Core] Target height {target_height:.2f}m below minimum {MIN_COM_HEIGHT}m, limiting")
314
+ target_pose[2] = MIN_COM_HEIGHT - com_height
315
+ elif target_height > MAX_COM_HEIGHT:
316
+ SDKLogger.warn(f"[Core] Target height {target_height:.2f}m above maximum {MAX_COM_HEIGHT}m, limiting")
317
+ target_pose[2] = MAX_COM_HEIGHT - com_height
318
+
319
+ if com_height > (self._rb_info['init_stand_height']-0.03):
320
+ max_x_step = 0.17
321
+ max_y_step = 0.17
322
+ max_yaw_step = 60
323
+ else:
324
+ max_x_step = 0.15
325
+ max_y_step = 0.15
326
+ max_yaw_step = 45
327
+
328
+ body_poses = []
329
+
330
+ # 计算目标点到原点的距离和朝向
331
+ target_dist_x = abs(target_pose[0])
332
+ target_dist_y = abs(target_pose[1])
333
+ target_yaw = target_pose[3] * 180 / math.pi # Convert yaw from radians to degrees
334
+
335
+ # 计算需要的步数(考虑x位移、y位移和转角)
336
+ steps_for_x = int(np.ceil(target_dist_x / max_x_step))
337
+ steps_for_y = int(np.ceil(target_dist_y / max_y_step))
338
+ steps_for_yaw = int(np.ceil(abs(target_yaw) / max_yaw_step))
339
+ steps_needed = max(steps_for_x, steps_for_y, steps_for_yaw)
340
+ # print(f"[Core] Steps needed - X: {steps_for_x}, Y: {steps_for_y}, Yaw: {steps_for_yaw}, Total: {steps_needed}")
341
+
342
+ # 计算每一步的增量
343
+ dx = target_pose[0] / steps_needed
344
+ dy = target_pose[1] / steps_needed
345
+ dyaw = target_yaw / steps_needed
346
+
347
+ # 分解为多个小步,沿着直线路径前进并逐步调整朝向
348
+ for i in range(steps_needed):
349
+ x = dx * (i+1)
350
+ y = dy * (i+1)
351
+ z = target_pose[2]
352
+ yaw = dyaw * (i+1)
353
+ body_poses.append([x, y, 0.0, yaw])
354
+
355
+ # print("target_pose:", target_pose)
356
+ # print("body_poses:", body_poses)
357
+
358
+ if not self._control.step_control(body_poses, dt, is_left_first_default, collision_check):
359
+ return False
360
+
361
+ # # Wait for gait to switch to custom_gait
362
+ # start_time = time.time()
363
+ # while not self._rb_state.is_gait('custom_gait'):
364
+ # if time.time() - start_time > 1.0: # 1.0s timeout
365
+ # SDKLogger.warn("[Core] Timeout waiting for gait to switch to custom_gait")
366
+ # return False
367
+ # time.sleep(0.01)
368
+
369
+ return True
370
+
371
+ def control_command_pose(self, target_pose_x:float, target_pose_y:float, target_pose_z:float, target_pose_yaw:float)->bool:
372
+ """
373
+ Control robot pose in base_link frame
374
+
375
+ Arguments:
376
+ - target_pose_x: x position (meters)
377
+ - target_pose_y: y position (meters)
378
+ - target_pose_z: z position (meters)
379
+ - target_pose_yaw: yaw angle (radians)
380
+
381
+ Returns:
382
+ bool: True if command was sent successfully, False otherwise
383
+
384
+ Raises:
385
+ RuntimeError: If robot is not in stance state
386
+ """
387
+ # if self.state != 'stance':
388
+ # raise RuntimeError(f"[Core] control_command_pose failed: robot must be in stance state, current state: {self.state}")
389
+
390
+ # Add any parameter validation if needed
391
+ # e.g., limit ranges for safety
392
+ MAX_HEIGHT = 0.1
393
+ MIN_HEIGHT = -0.35
394
+ limited_height = min(MAX_HEIGHT, max(MIN_HEIGHT, target_pose_z))
395
+ if target_pose_z > MAX_HEIGHT or target_pose_z < MIN_HEIGHT:
396
+ SDKLogger.warn(f"[Core] target_pose_z {target_pose_z:.3f} exceeds limit [{MIN_HEIGHT}, {MAX_HEIGHT}], will be limited")
397
+
398
+ # 结合当前高度做过滤,限制上升时的高度变化
399
+ target_height = self._rb_info['init_stand_height'] + limited_height
400
+ # 躯干上升运动变化不宜过大, 目标高度减去实时躯干高度大于阈值
401
+ HEIGHT_CHANGE_THRESHOLD = 0.25
402
+ if (self._rb_state.com_height < target_height) and (target_height - self._rb_state.com_height) >= HEIGHT_CHANGE_THRESHOLD:
403
+ limited_height = (self._rb_state.com_height + HEIGHT_CHANGE_THRESHOLD) - self._rb_info['init_stand_height']
404
+ SDKLogger.warn(f"[Core] Warning! Height change too large, limiting to safe range, reset height to {limited_height:.3f}")
405
+
406
+ self.to_command_pose()
407
+ return self._control.control_command_pose(target_pose_x, target_pose_y, limited_height, target_pose_yaw)
408
+
409
+ def control_command_pose_world(self, target_pose_x:float, target_pose_y:float, target_pose_z:float, target_pose_yaw:float)->bool:
410
+ """
411
+ Control robot pose in odom (world) frame
412
+
413
+ Arguments:
414
+ - target_pose_x: x position (meters)
415
+ - target_pose_y: y position (meters)
416
+ - target_pose_z: z position (meters)
417
+ - target_pose_yaw: yaw angle (radians)
418
+
419
+ Returns:
420
+ bool: True if command was sent successfully, False otherwise
421
+
422
+ Raises:
423
+ RuntimeError: If robot is not in stance state
424
+ """
425
+ # if self.state != 'stance':
426
+ # raise RuntimeError(f"[Core] control_command_pose_world failed: robot must be in stance state, current state: {self.state}")
427
+
428
+ # Add any parameter validation if needed
429
+ # e.g., limit ranges for safety
430
+ MAX_HEIGHT = 0.1
431
+ MIN_HEIGHT = -0.35
432
+ # Limit height range
433
+ limited_height = min(MAX_HEIGHT, max(MIN_HEIGHT, target_pose_z))
434
+ if target_pose_z > MAX_HEIGHT or target_pose_z < MIN_HEIGHT:
435
+ SDKLogger.warn(f"[Core] target_pose_z {target_pose_z:.3f} exceeds limit [{MIN_HEIGHT}, {MAX_HEIGHT}], will be limited")
436
+
437
+ # 结合当前高度做过滤,限制上升时的高度变化
438
+ target_height = self._rb_info['init_stand_height'] + limited_height
439
+ # 躯干上升运动变化不宜过大, 目标高度减去实时躯干高度大于阈值
440
+ HEIGHT_CHANGE_THRESHOLD = 0.25
441
+ if (self._rb_state.com_height < target_height) and (target_height - self._rb_state.com_height) >= HEIGHT_CHANGE_THRESHOLD:
442
+ limited_height = (self._rb_state.com_height + HEIGHT_CHANGE_THRESHOLD) - self._rb_info['init_stand_height']
443
+ SDKLogger.warn(f"[Core] Warning! Height change too large, limiting to safe range, reset height to {limited_height:.3f}")
444
+
445
+ self.to_command_pose_world()
446
+ return self._control.control_command_pose_world(target_pose_x, target_pose_y, limited_height, target_pose_yaw)
447
+
448
+ def execute_gesture(self, gestures:list)->bool:
449
+ return self._control.execute_gesture(gestures)
450
+
451
+ def get_gesture_names(self)->list:
452
+ return self._control.get_gesture_names()
453
+
454
+ def control_robot_dexhand(self, left_position:list, right_position:list)->bool:
455
+ return self._control.control_robot_dexhand(left_position, right_position)
456
+
457
+ def robot_dexhand_command(self, data, ctrl_mode, hand_side):
458
+ return self._control.robot_dexhand_command(data, ctrl_mode, hand_side)
459
+
460
+
461
+ def control_leju_claw(self, postions:list, velocities:list=[90, 90], torques:list=[1.0, 1.0]) ->bool:
462
+ return self._control.control_leju_claw(postions, velocities, torques)
463
+
464
+ def control_robot_head(self, yaw:float, pitch:float)->bool:
465
+ # Convert radians to degrees
466
+ yaw_deg = yaw * 180 / math.pi
467
+ pitch_deg = pitch * 180 / math.pi
468
+ return self._control.control_robot_head(yaw_deg, pitch_deg)
469
+
470
+ def control_robot_waist(self, target_pos:list):
471
+ return self._control.control_robot_waist(target_pos)
472
+
473
+ def enable_head_tracking(self, target_id: int)->bool:
474
+ return self._control.enable_head_tracking(target_id)
475
+
476
+ def disable_head_tracking(self)->bool:
477
+ return self._control.disable_head_tracking()
478
+
479
+ def control_robot_arm_joint_positions(self, joint_data:list)->bool:
480
+ if self.state != 'stance':
481
+ raise RuntimeError(f"[Core] control_robot_arm_joint_positions failed: robot must be in stance state, current state: {self.state}")
482
+
483
+ if self._control.is_arm_collision_mode() and self._control.is_arm_collision():
484
+ SDKLogger.error(f"Arm collision detected, cannot publish arm trajectory")
485
+ return False
486
+ # change to external control mode
487
+ if self._arm_ctrl_mode != KuavoArmCtrlMode.ExternalControl:
488
+ SDKLogger.debug("[Core] control_robot_arm_joint_positions, current arm mode != ExternalControl, change it.")
489
+ if not self.change_robot_arm_ctrl_mode(KuavoArmCtrlMode.ExternalControl):
490
+ SDKLogger.warn("[Core] control_robot_arm_joint_positions failed, change robot arm ctrl mode failed!")
491
+ return False
492
+ return self._control.control_robot_arm_joint_positions(joint_data)
493
+
494
+ def control_robot_arm_joint_trajectory(self, times:list, joint_q:list)->bool:
495
+ if self.state != 'stance':
496
+ raise RuntimeError("[Core] control_robot_arm_joint_trajectory failed: robot must be in stance state")
497
+
498
+ if self._control.is_arm_collision_mode() and self._control.is_arm_collision():
499
+ SDKLogger.error(f"Arm collision detected, cannot publish arm trajectory")
500
+ return False
501
+
502
+ if self._arm_ctrl_mode != KuavoArmCtrlMode.ExternalControl:
503
+ SDKLogger.debug("[Core] control_robot_arm_joint_trajectory, current arm mode != ExternalControl, change it.")
504
+ if not self.change_robot_arm_ctrl_mode(KuavoArmCtrlMode.ExternalControl):
505
+ SDKLogger.warn("[Core] control_robot_arm_joint_trajectory failed, change robot arm ctrl mode failed!")
506
+ return False
507
+
508
+ return self._control.control_robot_arm_joint_trajectory(times, joint_q)
509
+
510
+ def control_robot_end_effector_pose(self, left_pose: KuavoPose, right_pose: KuavoPose, frame: KuavoManipulationMpcFrame)->bool:
511
+ if self._arm_ctrl_mode != KuavoArmCtrlMode.ExternalControl:
512
+ SDKLogger.debug("[Core] control_robot_end_effector_pose, current arm mode != ExternalControl, change it.")
513
+ if not self.change_robot_arm_ctrl_mode(KuavoArmCtrlMode.ExternalControl):
514
+ SDKLogger.warn("[Core] control_robot_end_effector_pose failed, change robot arm ctrl mode failed!")
515
+ return False
516
+
517
+ if self._manipulation_mpc_ctrl_mode == KuavoManipulationMpcCtrlMode.NoControl:
518
+ SDKLogger.debug("[Core] control_robot_end_effector_pose, manipulation mpc ctrl mode is NoControl, change it.")
519
+ if not self.change_manipulation_mpc_ctrl_mode(KuavoManipulationMpcCtrlMode.ArmOnly):
520
+ SDKLogger.warn("[Core] control_robot_end_effector_pose failed, change manipulation mpc ctrl mode failed!")
521
+ return False
522
+
523
+ return self._control.control_robot_end_effector_pose(left_pose, right_pose, frame)
524
+
525
+ def change_manipulation_mpc_frame(self, frame: KuavoManipulationMpcFrame)->bool:
526
+ # Check if service is available (if current state is ERROR, service is not available)
527
+ current_frame = self._rb_state.manipulation_mpc_frame
528
+ if current_frame == KuavoManipulationMpcFrame.ERROR:
529
+ SDKLogger.warn("[Core] Manipulation MPC frame service not available, updating local state only")
530
+ if not hasattr(self, '_manipulation_mpc_frame_lock'):
531
+ self._manipulation_mpc_frame_lock = threading.Lock()
532
+ with self._manipulation_mpc_frame_lock:
533
+ self._manipulation_mpc_frame = frame
534
+ return True
535
+
536
+ timeout = 1.0
537
+ count = 0
538
+ while self._rb_state.manipulation_mpc_frame != frame:
539
+ SDKLogger.debug(f"[Core] Change manipulation mpc frame from {self._rb_state.manipulation_mpc_frame} to {frame}, retry: {count}")
540
+ self._control.change_manipulation_mpc_frame(frame)
541
+ if self._rb_state.manipulation_mpc_frame == frame:
542
+ break
543
+ if timeout <= 0:
544
+ SDKLogger.warn("[Core] Change manipulation mpc frame timeout!")
545
+ return False
546
+ timeout -= 0.1
547
+ time.sleep(0.1)
548
+ count += 1
549
+ if not hasattr(self, '_manipulation_mpc_frame_lock'):
550
+ self._manipulation_mpc_frame_lock = threading.Lock()
551
+ with self._manipulation_mpc_frame_lock:
552
+ self._manipulation_mpc_frame = frame
553
+ return True
554
+
555
+ def change_manipulation_mpc_ctrl_mode(self, control_mode: KuavoManipulationMpcCtrlMode)->bool:
556
+ # Check if service is available (if current state is ERROR, service is not available)
557
+ current_mode = self._rb_state.manipulation_mpc_ctrl_mode
558
+ if current_mode == KuavoManipulationMpcCtrlMode.ERROR:
559
+ SDKLogger.warn("[Core] Manipulation MPC control mode service not available, updating local state only")
560
+ if not hasattr(self, '_manipulation_mpc_ctrl_mode_lock'):
561
+ self._manipulation_mpc_ctrl_mode_lock = threading.Lock()
562
+ with self._manipulation_mpc_ctrl_mode_lock:
563
+ self._manipulation_mpc_ctrl_mode = control_mode
564
+ return True
565
+
566
+ timeout = 1.0
567
+ count = 0
568
+ while self._rb_state.manipulation_mpc_ctrl_mode != control_mode:
569
+ SDKLogger.debug(f"[Core] Change manipulation mpc ctrl mode from {self._rb_state.manipulation_mpc_ctrl_mode} to {control_mode}, retry: {count}")
570
+ self._control.change_manipulation_mpc_ctrl_mode(control_mode)
571
+ if self._rb_state.manipulation_mpc_ctrl_mode == control_mode:
572
+ break
573
+ if timeout <= 0:
574
+ SDKLogger.warn("[Core] Change manipulation mpc ctrl mode timeout!")
575
+ return False
576
+ timeout -= 0.1
577
+ time.sleep(0.1)
578
+ count += 1
579
+ if not hasattr(self, '_manipulation_mpc_ctrl_mode_lock'):
580
+ self._manipulation_mpc_ctrl_mode_lock = threading.Lock()
581
+ with self._manipulation_mpc_ctrl_mode_lock:
582
+ self._manipulation_mpc_ctrl_mode = control_mode
583
+ return True
584
+
585
+ def change_manipulation_mpc_control_flow(self, control_flow: KuavoManipulationMpcControlFlow)->bool:
586
+ # Check if service is available (if current state is ERROR, service is not available)
587
+ current_flow = self._rb_state.manipulation_mpc_control_flow
588
+ if current_flow == KuavoManipulationMpcControlFlow.Error:
589
+ SDKLogger.warn("[Core] Manipulation MPC control flow service not available, updating local state only")
590
+ if not hasattr(self, '_manipulation_mpc_control_flow_lock'):
591
+ self._manipulation_mpc_control_flow_lock = threading.Lock()
592
+ with self._manipulation_mpc_control_flow_lock:
593
+ self._manipulation_mpc_control_flow = control_flow
594
+ return True
595
+
596
+ timeout = 1.0
597
+ count = 0
598
+ while self._rb_state.manipulation_mpc_control_flow != control_flow:
599
+ SDKLogger.debug(f"[Core] Change manipulation mpc control flow from {self._rb_state.manipulation_mpc_control_flow} to {control_flow}, retry: {count}")
600
+ self._control.change_manipulation_mpc_control_flow(control_flow)
601
+ if self._rb_state.manipulation_mpc_control_flow == control_flow:
602
+ break
603
+ if timeout <= 0:
604
+ SDKLogger.warn("[Core] Change manipulation mpc control flow timeout!")
605
+ return False
606
+ timeout -= 0.1
607
+ time.sleep(0.1)
608
+ count += 1
609
+ if not hasattr(self, '_manipulation_mpc_control_flow_lock'):
610
+ self._manipulation_mpc_control_flow_lock = threading.Lock()
611
+ with self._manipulation_mpc_control_flow_lock:
612
+ self._manipulation_mpc_control_flow = control_flow
613
+ return True
614
+
615
+ def change_robot_arm_ctrl_mode(self, mode:KuavoArmCtrlMode)->bool:
616
+
617
+ if self._control.is_arm_collision_mode() and self.is_arm_collision():
618
+ SDKLogger.warn("[Core] change_robot_arm_ctrl_mode failed, arm collision detected!")
619
+ return False
620
+
621
+ # Wait for state update to complete, similar to change_manipulation_mpc_ctrl_mode
622
+ timeout = 1.0
623
+ count = 0
624
+ if self._rb_state.arm_control_mode != mode:
625
+ while self._rb_state.arm_control_mode != mode:
626
+ SDKLogger.debug(f"[Core] Change robot arm control from {self._rb_state.arm_control_mode} to {mode}, retry: {count}")
627
+ self._control.change_robot_arm_ctrl_mode(mode)
628
+ if self._rb_state.arm_control_mode == mode:
629
+ break
630
+ if timeout <= 0:
631
+ SDKLogger.warn("[Core] Change robot arm control mode timeout!")
632
+ return False
633
+ timeout -= 0.1
634
+ time.sleep(0.1)
635
+ count += 1
636
+
637
+ if not hasattr(self, '_arm_ctrl_mode_lock'):
638
+ self._arm_ctrl_mode_lock = threading.Lock()
639
+ with self._arm_ctrl_mode_lock:
640
+ # 手臂控制模式切换成功,更新当前手臂控制模式
641
+ self._arm_ctrl_mode = mode # update arm ctrl mode
642
+
643
+ return True
644
+
645
+ def robot_arm_reset(self)->bool:
646
+ if self.state != 'stance':
647
+ SDKLogger.warn("[Core] robot arm reset failed, robot is not in stance state!")
648
+ return
649
+
650
+ # init_pos = [0.0]*14
651
+ # if not self.control_robot_arm_joint_trajectory([1.5], [init_pos]):
652
+ # SDKLogger.warn("[Core] robot arm reset failed, control robot arm traj failed!")
653
+ # return False
654
+
655
+ return self.change_robot_arm_ctrl_mode(KuavoArmCtrlMode.AutoSwing)
656
+
657
+ def robot_manipulation_mpc_reset(self)->bool:
658
+ SDKLogger.info("[Core] Starting manipulation MPC reset...")
659
+
660
+ if self._manipulation_mpc_ctrl_mode != KuavoManipulationMpcCtrlMode.NoControl:
661
+ SDKLogger.info("[Core] Resetting manipulation MPC control mode to NoControl...")
662
+ if not self.change_manipulation_mpc_ctrl_mode(KuavoManipulationMpcCtrlMode.NoControl):
663
+ SDKLogger.warn("[Core] robot manipulation mpc reset failed, change manipulation mpc ctrl mode failed!")
664
+ return False
665
+ SDKLogger.info("[Core] Manipulation MPC control mode reset to NoControl successfully")
666
+ else:
667
+ SDKLogger.info("[Core] Manipulation MPC control mode is already NoControl")
668
+
669
+ if self._manipulation_mpc_control_flow != KuavoManipulationMpcControlFlow.ThroughFullBodyMpc:
670
+ SDKLogger.info("[Core] Resetting manipulation MPC control flow to ThroughFullBodyMpc...")
671
+ if not self.change_manipulation_mpc_control_flow(KuavoManipulationMpcControlFlow.ThroughFullBodyMpc):
672
+ SDKLogger.warn("[Core] robot manipulation mpc reset failed, change manipulation mpc control flow failed!")
673
+ return False
674
+ SDKLogger.info("[Core] Manipulation MPC control flow reset to ThroughFullBodyMpc successfully")
675
+ else:
676
+ SDKLogger.info("[Core] Manipulation MPC control flow is already ThroughFullBodyMpc")
677
+
678
+ SDKLogger.info("[Core] Manipulation MPC reset completed successfully")
679
+ return True
680
+
681
+ """ ------------------------------------------------------------------------"""
682
+ """ Arm Forward kinematics && Arm Inverse kinematics """
683
+ def arm_ik(self,
684
+ l_eef_pose: KuavoPose,
685
+ r_eef_pose: KuavoPose,
686
+ l_elbow_pos_xyz: list = [0.0, 0.0, 0.0],
687
+ r_elbow_pos_xyz: list = [0.0, 0.0, 0.0],
688
+ arm_q0: list = None,
689
+ params: KuavoIKParams=None) -> list:
690
+ return self._control.arm_ik(l_eef_pose, r_eef_pose, l_elbow_pos_xyz, r_elbow_pos_xyz, arm_q0, params)
691
+
692
+
693
+ def arm_fk(self, q: list) -> Tuple[KuavoPose, KuavoPose]:
694
+ return self._control.arm_fk(q)
695
+
696
+ """ Callbacks """
697
+ def _humanoid_gait_changed(self, current_time: float, gait_name: str):
698
+ if self.state != gait_name:
699
+ # Check if to_$gait_name method exists
700
+ to_method = f'to_{gait_name}'
701
+ if hasattr(self, to_method):
702
+ SDKLogger.debug(f"[Core] Received gait change notification: {gait_name} at time {current_time}")
703
+ # Call the transition method if it exists
704
+ getattr(self, to_method)()
705
+
706
+ def is_arm_collision(self)->bool:
707
+ return self._control.is_arm_collision()
708
+
709
+ def is_arm_collision_mode(self)->bool:
710
+ """Check if arm collision mode is enabled.
711
+
712
+ Returns:
713
+ bool: True if collision mode is enabled, False otherwise.
714
+ """
715
+ return self._control.is_arm_collision_mode()
716
+
717
+ def release_arm_collision_mode(self):
718
+
719
+ self._control.release_arm_collision_mode()
720
+ # if not self.change_robot_arm_ctrl_mode(KuavoArmCtrlMode.ExternalControl):
721
+ # SDKLogger.warn("[Core] control_robot_arm_joint_positions failed, change robot arm ctrl mode failed!")
722
+ # return False
723
+
724
+
725
+ def wait_arm_collision_complete(self):
726
+ self._control.wait_arm_collision_complete()
727
+
728
+
729
+ def set_arm_collision_mode(self, enable: bool):
730
+ self._control.set_arm_collision_mode(enable)
731
+
732
+
733
+ if __name__ == "__main__":
734
+ DEBUG_MODE = 0
735
+ core = KuavoRobotCore()
736
+ core.initialize()
737
+
738
+ if DEBUG_MODE == 0:
739
+ time.sleep(1.0)
740
+ core.change_robot_arm_ctrl_mode(KuavoArmCtrlMode.ExternalControl)
741
+ core.change_manipulation_mpc_frame(KuavoManipulationMpcFrame.VRFrame)
742
+ core.change_manipulation_mpc_ctrl_mode(KuavoManipulationMpcCtrlMode.ArmOnly)
743
+ core.change_manipulation_mpc_control_flow(KuavoManipulationMpcControlFlow.DirectToWbc)
744
+ core.robot_manipulation_mpc_reset()
745
+ elif DEBUG_MODE == 1:
746
+ core.to_stance()
747
+ print("state now is to_stance:", core.state)
748
+ core.control_command_pose_world(0.0, 1.0, 0.0, 1.57)
749
+ print("state now is control_command_pose_world:", core.state)
750
+ elif DEBUG_MODE == 2:
751
+ core.to_trot()
752
+ print("state now is to_trot:", core.state)
753
+ time.sleep(3.0)
754
+ core.to_stance()
755
+ print("state now is to_stance:", core.state)