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,1325 @@
1
+ import os
2
+ import roslibpy
3
+ import numpy as np
4
+ from typing import Tuple
5
+ from kuavo_humanoid_sdk.common.logger import SDKLogger
6
+ from kuavo_humanoid_sdk.common.websocket_kuavo_sdk import WebSocketKuavoSDK
7
+ from kuavo_humanoid_sdk.interfaces.data_types import (KuavoArmCtrlMode, KuavoIKParams, KuavoPose,
8
+ KuavoManipulationMpcControlFlow, KuavoManipulationMpcCtrlMode
9
+ ,KuavoManipulationMpcFrame)
10
+ from kuavo_humanoid_sdk.kuavo.core.ros.sat_utils import RotatingRectangle
11
+ from kuavo_humanoid_sdk.kuavo.core.ros.param import EndEffectorType
12
+
13
+
14
+ class ControlEndEffectorWebsocket:
15
+ def __init__(self, eef_type: str = EndEffectorType.QIANGNAO):
16
+ self._eef_type = eef_type
17
+ self._pubs = []
18
+ websocket = WebSocketKuavoSDK()
19
+ if self._eef_type == EndEffectorType.QIANGNAO:
20
+ self._pub_ctrl_robot_hand = roslibpy.Topic(websocket.client, '/control_robot_hand_position', 'kuavo_msgs/robotHandPosition')
21
+ # publisher, name, require
22
+ self._pubs.append((self._pub_ctrl_robot_hand, False))
23
+ elif self._eef_type == EndEffectorType.QIANGNAO_TOUCH:
24
+ self._pub_ctrl_robot_hand = roslibpy.Topic(websocket.client, '/control_robot_hand_position', 'kuavo_msgs/robotHandPosition')
25
+ self._pub_dexhand_command = roslibpy.Topic(websocket.client, '/dexhand/command', 'kuavo_msgs/dexhandCommand')
26
+ self._pub_dexhand_right_command = roslibpy.Topic(websocket.client, '/dexhand/right/command', 'kuavo_msgs/dexhandCommand')
27
+ self._pub_dexhand_left_command = roslibpy.Topic(websocket.client, '/dexhand/left/command', 'kuavo_msgs/dexhandCommand')
28
+ # publisher, name, require
29
+ self._pubs.append((self._pub_dexhand_command, False))
30
+ self._pubs.append((self._pub_dexhand_right_command, False))
31
+ self._pubs.append((self._pub_dexhand_left_command, False))
32
+
33
+ def connect(self, timeout:float=1.0)-> bool:
34
+ return True
35
+
36
+ def pub_control_robot_dexhand(self, left_position:list, right_position:list)->bool:
37
+ if not self._eef_type.startswith(EndEffectorType.QIANGNAO): # qiangnao, qiangnao_touch
38
+ SDKLogger.warning(f"{self._eef_type} not support control dexhand")
39
+ return False
40
+ try:
41
+ hand_pose_msg = {
42
+ "left_hand_position": left_position,
43
+ "right_hand_position": right_position
44
+ }
45
+ self._pub_ctrl_robot_hand.publish(roslibpy.Message(hand_pose_msg))
46
+ SDKLogger.debug(f"publish robot dexhand: {left_position}, {right_position}")
47
+ return True
48
+ except Exception as e:
49
+ SDKLogger.error(f"publish robot dexhand: {e}")
50
+ return False
51
+
52
+ def pub_dexhand_command(self, data:list, ctrl_mode, hand_side)->bool:
53
+ """
54
+ ctrl_mode: 0 --> POSITION, 1 --> VELOCITY
55
+ hand_side: 0 --> left, 1 --> right, 2-->dual
56
+ """
57
+ if not self._eef_type == EndEffectorType.QIANGNAO_TOUCH:
58
+ SDKLogger.warning(f"{self._eef_type} not support pub_left_dexhand_command")
59
+ return False
60
+ try:
61
+ if hand_side != 2 and len(data) != 6:
62
+ SDKLogger.warning("Data length should be 6")
63
+ return False
64
+ if hand_side == 2 and len(data) != 12:
65
+ SDKLogger.warning("Data length should be 12")
66
+ return False
67
+ if ctrl_mode not in [dexhandCommand.POSITION_CONTROL, dexhandCommand.VELOCITY_CONTROL]:
68
+ SDKLogger.error(f"Invalid mode for pub_left_dexhand_command: {ctrl_mode}")
69
+ return False
70
+
71
+ msg = {
72
+ "data": [int(d) for d in data], # Convert data to integers
73
+ "control_mode": ctrl_mode
74
+ }
75
+ if hand_side == 0:
76
+ self._pub_dexhand_left_command.publish(roslibpy.Message(msg))
77
+ elif hand_side == 1:
78
+ self._pub_dexhand_right_command.publish(roslibpy.Message(msg))
79
+ else:
80
+ self._pub_dexhand_command.publish(roslibpy.Message(msg))
81
+ return True
82
+ except Exception as e:
83
+ SDKLogger.error(f"Failed to publish left dexhand command: {e}")
84
+ return False
85
+
86
+ def srv_execute_gesture(self, gestures:list)->bool:
87
+ if not self._eef_type.startswith(EndEffectorType.QIANGNAO): # qiangnao, qiangnao_touch
88
+ SDKLogger.warning(f"{self._eef_type} not support control dexhand")
89
+ return False
90
+ try:
91
+ websocket = WebSocketKuavoSDK()
92
+ service = roslibpy.Service(websocket.client, 'gesture/execute', 'kuavo_msgs/gestureExecute')
93
+ request = {
94
+ "gestures": [
95
+ {
96
+ "gesture_name": gs["gesture_name"],
97
+ "hand_side": gs["hand_side"]
98
+ } for gs in gestures
99
+ ]
100
+ }
101
+ response = service.call(request)
102
+ if not response.get('success', False):
103
+ SDKLogger.error(f"Failed to execute gesture '{gestures}': {response.get('message', '')}")
104
+ return response.get('success', False)
105
+ except Exception as e:
106
+ SDKLogger.error(f"Service call failed: {e}")
107
+ return False
108
+
109
+ def srv_get_gesture_names(self)->list:
110
+ if not self._eef_type.startswith(EndEffectorType.QIANGNAO): # qiangnao, qiangnao_touch
111
+ SDKLogger.warning(f"{self._eef_type} not support control dexhand")
112
+ return []
113
+ try:
114
+ websocket = WebSocketKuavoSDK()
115
+ service = roslibpy.Service(websocket.client, 'gesture/list', 'kuavo_msgs/gestureList')
116
+ request = {}
117
+ response = service.call(request)
118
+ gestures = []
119
+ for gesture_info in response.get('gesture_infos', []):
120
+ gestures.append(gesture_info['gesture_name'])
121
+ for alias in gesture_info.get('alias', []):
122
+ gestures.append(alias)
123
+ return list(set(gestures))
124
+ except Exception as e:
125
+ SDKLogger.error(f"Service call failed: {e}")
126
+ return []
127
+
128
+ def srv_control_leju_claw(self, postions:list, velocities:list, torques:list) ->bool:
129
+ if self._eef_type != 'lejuclaw':
130
+ SDKLogger.warning(f"{self._eef_type} not support control lejuclaw.")
131
+ return False
132
+ try:
133
+ websocket = WebSocketKuavoSDK()
134
+ service = roslibpy.Service(websocket.client, 'control_robot_leju_claw', 'kuavo_msgs/controlLejuClaw')
135
+ request = {
136
+ "data": {
137
+ "position": postions,
138
+ "velocity": velocities,
139
+ "effort": torques
140
+ }
141
+ }
142
+ response = service.call(request)
143
+ if not response.get('result', False):
144
+ SDKLogger.error(f"Failed to control leju claw: {response.get('message', '')}")
145
+ return response.get('result', False)
146
+ except Exception as e:
147
+ SDKLogger.error(f"Service `control_robot_leju_claw` call failed: {e}")
148
+ return False
149
+
150
+ class ControlRobotArmWebsocket:
151
+ def __init__(self):
152
+ websocket = WebSocketKuavoSDK()
153
+ self._pub_ctrl_arm_traj_arm_collision = roslibpy.Topic(websocket.client,'/arm_collision/kuavo_arm_traj', 'sensor_msgs/JointState')
154
+ self._pub_ctrl_arm_target_poses_arm_collision = roslibpy.Topic(websocket.client, '/arm_collision/kuavo_arm_target_poses', 'kuavo_msgs/armTargetPoses')
155
+ self._pub_ctrl_hand_pose_cmd_arm_collision = roslibpy.Topic(websocket.client, '/arm_collision/mm/two_arm_hand_pose_cmd', 'kuavo_msgs/twoArmHandPoseCmd')
156
+ self._sub_arm_collision_info = roslibpy.Topic(websocket.client, '/arm_collision/info', 'kuavo_msgs/armCollisionCheckInfo')
157
+ self._sub_arm_collision_info.subscribe(self.callback_arm_collision_info)
158
+ self._is_collision = False
159
+ self.arm_collision_enable = False
160
+ #正常轨迹发布
161
+ self._pub_ctrl_arm_traj = roslibpy.Topic(websocket.client, '/kuavo_arm_traj', 'sensor_msgs/JointState')
162
+ self._pub_ctrl_arm_target_poses = roslibpy.Topic(websocket.client, '/kuavo_arm_target_poses', 'kuavo_msgs/armTargetPoses')
163
+ self._pub_ctrl_hand_pose_cmd = roslibpy.Topic(websocket.client, '/mm/two_arm_hand_pose_cmd', 'kuavo_msgs/twoArmHandPoseCmd')
164
+ self._pub_ctrl_arm_traj.advertise()
165
+ self._pub_ctrl_arm_target_poses.advertise()
166
+ self._pub_ctrl_hand_pose_cmd.advertise()
167
+ self._pub_ctrl_hand_pose_cmd_arm_collision.advertise()
168
+
169
+ def connect(self, timeout:float=1.0)-> bool:
170
+ return True
171
+
172
+ def pub_control_robot_arm_traj(self, joint_q: list)->bool:
173
+ try:
174
+ msg = {
175
+ "name": ["arm_joint_" + str(i) for i in range(0, 14)],
176
+ "position": [float(180.0 / np.pi * q) for q in joint_q] # convert to degree
177
+ }
178
+ if self.arm_collision_enable:
179
+ self._pub_ctrl_arm_traj_arm_collision.publish(roslibpy.Message(msg))
180
+ else:
181
+ self._pub_ctrl_arm_traj.publish(roslibpy.Message(msg))
182
+ return True
183
+ except Exception as e:
184
+ SDKLogger.error(f"publish robot arm traj: {e}")
185
+ return False
186
+
187
+ def is_arm_collision_mode(self)->bool:
188
+ return self.arm_collision_enable
189
+
190
+ def set_arm_collision_mode(self, enable: bool):
191
+ """
192
+ Set arm collision mode
193
+ """
194
+ self.arm_collision_enable = enable
195
+ websocket = WebSocketKuavoSDK()
196
+ srv_set_arm_collision_mode_srv = roslibpy.Service(websocket.client, '/arm_collision/set_arm_moving_enable', 'std_srvs/SetBool')
197
+ req = {
198
+ "data": enable
199
+ }
200
+ resp = srv_set_arm_collision_mode_srv.call(req)
201
+ if not resp.get('success', False):
202
+ SDKLogger.error(f"Failed to set arm collision mode: {resp.get('message', '')}")
203
+
204
+ def is_arm_collision(self)->bool:
205
+ return self._is_collision
206
+
207
+ def callback_arm_collision_info(self, msg):
208
+ self._is_collision = True
209
+ SDKLogger.info(f"Arm collision detected")
210
+
211
+ def release_arm_collision_mode(self):
212
+ self._is_collision = False
213
+
214
+ def wait_arm_collision_complete(self):
215
+ if self._is_collision:
216
+ websocket = WebSocketKuavoSDK()
217
+ srv_wait_arm_collision_complete_srv = roslibpy.Service(websocket.client, '/arm_collision/wait_complete', 'std_srvs/SetBool')
218
+ req = {
219
+ "data": True
220
+ }
221
+ resp = srv_wait_arm_collision_complete_srv.call(req)
222
+ if not resp.get('success', False):
223
+ SDKLogger.error(f"Failed to wait arm collision complete: {resp.get('message', '')}")
224
+
225
+ def pub_arm_target_poses(self, times:list, joint_q:list)->bool:
226
+ try:
227
+ msg_values = []
228
+ for i in range(len(joint_q)):
229
+ degs = [float(q) for q in joint_q[i]]
230
+ msg_values.extend(degs)
231
+ msg = {
232
+ "times": [float(q) for q in times],
233
+ "values": msg_values
234
+ }
235
+ if self.arm_collision_enable:
236
+ self._pub_ctrl_arm_target_poses_arm_collision.publish(roslibpy.Message(msg))
237
+ else:
238
+ self._pub_ctrl_arm_target_poses.publish(roslibpy.Message(msg))
239
+ return True
240
+ except Exception as e:
241
+ SDKLogger.error(f"publish arm target poses: {e}")
242
+
243
+ return False
244
+
245
+ def pub_end_effector_pose_cmd(self, left_pose: KuavoPose, right_pose: KuavoPose, frame: KuavoManipulationMpcFrame)->bool:
246
+ try:
247
+ msg = {
248
+ "hand_poses": {
249
+ "left_pose": {
250
+ "pos_xyz": left_pose.position,
251
+ "quat_xyzw": left_pose.orientation
252
+ },
253
+ "right_pose": {
254
+ "pos_xyz": right_pose.position,
255
+ "quat_xyzw": right_pose.orientation
256
+ }
257
+ },
258
+ "frame": frame.value
259
+ }
260
+ if frame.value not in [0, 1, 2, 3, 4]:
261
+ SDKLogger.error(f"Invalid frame: {frame}")
262
+ return False
263
+ if self.arm_collision_enable:
264
+ self._pub_ctrl_hand_pose_cmd_arm_collision.publish(roslibpy.Message(msg))
265
+ else:
266
+ self._pub_ctrl_hand_pose_cmd.publish(roslibpy.Message(msg))
267
+ return True
268
+ except Exception as e:
269
+ SDKLogger.error(f"publish arm target poses: {e}")
270
+ return False
271
+
272
+ def srv_change_manipulation_mpc_frame(self, frame: KuavoManipulationMpcFrame)->bool:
273
+ try:
274
+ websocket = WebSocketKuavoSDK()
275
+ service = roslibpy.Service(websocket.client, '/set_mm_ctrl_frame', 'kuavo_msgs/setMmCtrlFrame')
276
+ request = {
277
+ "frame": frame.value
278
+ }
279
+ response = service.call(request)
280
+ if not response.get('result', False):
281
+ SDKLogger.error(f"Failed to change manipulation mpc frame to {frame}: {response.get('message', '')}")
282
+ return response.get('result', False)
283
+ except Exception as e:
284
+ SDKLogger.error(f"Service call failed: {e}")
285
+ return False
286
+
287
+ def srv_change_manipulation_mpc_ctrl_mode(self, ctrl_mode: KuavoManipulationMpcCtrlMode)->bool:
288
+ try:
289
+ websocket = WebSocketKuavoSDK()
290
+ service = roslibpy.Service(websocket.client, '/mobile_manipulator_mpc_control', 'kuavo_msgs/changeTorsoCtrlMode')
291
+ request = {
292
+ "control_mode": ctrl_mode.value
293
+ }
294
+ response = service.call(request)
295
+ if not response.get('result', False):
296
+ SDKLogger.error(f"Failed to change manipulation mpc control mode to {ctrl_mode}: {response.get('message', '')}")
297
+ return response.get('result', False)
298
+ except Exception as e:
299
+ SDKLogger.error(f"Service call failed: {e}")
300
+ return False
301
+
302
+ def srv_change_manipulation_mpc_control_flow(self, ctrl_flow: KuavoManipulationMpcControlFlow)-> bool:
303
+ try:
304
+ websocket = WebSocketKuavoSDK()
305
+ service = roslibpy.Service(websocket.client, '/enable_mm_wbc_arm_trajectory_control', 'kuavo_msgs/changeArmCtrlMode')
306
+ request = {
307
+ "control_mode": ctrl_flow.value
308
+ }
309
+ response = service.call(request)
310
+ if not response.get('result', False):
311
+ SDKLogger.error(f"Failed to change manipulation mpc wbc arm trajectory control to {ctrl_flow}: {response.get('message', '')}")
312
+ return response.get('result', False)
313
+ except Exception as e:
314
+ SDKLogger.error(f"Service call failed: {e}")
315
+ return False
316
+
317
+ def srv_get_manipulation_mpc_ctrl_mode(self, )->KuavoManipulationMpcCtrlMode:
318
+ try:
319
+ websocket = WebSocketKuavoSDK()
320
+ service = roslibpy.Service(websocket.client, '/mobile_manipulator_get_mpc_control_mode', 'kuavo_msgs/changeTorsoCtrlMode')
321
+ request = {}
322
+ response = service.call(request)
323
+ if not response.get('result', False):
324
+ SDKLogger.error(f"Failed to get manipulation mpc control mode: {response.get('message', '')}")
325
+ return KuavoManipulationMpcCtrlMode.ERROR
326
+ return KuavoManipulationMpcCtrlMode(response.get('mode', 0))
327
+ except Exception as e:
328
+ SDKLogger.error(f"Service call failed: {e}")
329
+ return KuavoManipulationMpcCtrlMode.ERROR
330
+
331
+ def srv_get_manipulation_mpc_frame(self, )->KuavoManipulationMpcFrame:
332
+ try:
333
+ websocket = WebSocketKuavoSDK()
334
+ service = roslibpy.Service(websocket.client, '/get_mm_ctrl_frame', 'kuavo_msgs/setMmCtrlFrame')
335
+ request = {}
336
+ response = service.call(request)
337
+ if not response.get('result', False):
338
+ SDKLogger.error(f"Failed to get manipulation mpc frame: {response.get('message', '')}")
339
+ return KuavoManipulationMpcFrame.ERROR
340
+ return KuavoManipulationMpcFrame(response.get('currentFrame', 0))
341
+ except Exception as e:
342
+ SDKLogger.error(f"Service call failed: {e}")
343
+ return KuavoManipulationMpcFrame.ERROR
344
+
345
+ def srv_get_manipulation_mpc_control_flow(self, )->KuavoManipulationMpcControlFlow:
346
+ try:
347
+ websocket = WebSocketKuavoSDK()
348
+ service = roslibpy.Service(websocket.client, '/get_mm_wbc_arm_trajectory_control', 'kuavo_msgs/changeArmCtrlMode')
349
+ request = {}
350
+ response = service.call(request)
351
+ if not response.get('result', False):
352
+ SDKLogger.error(f"Failed to get manipulation mpc wbc arm trajectory control mode: {response.get('message', '')}")
353
+ return KuavoManipulationMpcControlFlow.Error
354
+ return KuavoManipulationMpcControlFlow(response.get('mode', 0))
355
+ except Exception as e:
356
+ SDKLogger.error(f"Service call failed: {e}")
357
+ return KuavoManipulationMpcControlFlow.Error
358
+
359
+ def srv_change_arm_ctrl_mode(self, mode: KuavoArmCtrlMode)->bool:
360
+ try:
361
+ websocket = WebSocketKuavoSDK()
362
+ service = roslibpy.Service(websocket.client, '/arm_traj_change_mode', 'kuavo_msgs/changeArmCtrlMode')
363
+ request = {
364
+ "control_mode": mode.value
365
+ }
366
+ response = service.call(request)
367
+ return response.get('result', False)
368
+ except Exception as e:
369
+ SDKLogger.error(f"Service call failed: {e}")
370
+ return False
371
+
372
+ def srv_get_arm_ctrl_mode(self)-> KuavoArmCtrlMode:
373
+ try:
374
+ websocket = WebSocketKuavoSDK()
375
+ service = roslibpy.Service(websocket.client, '/humanoid_get_arm_ctrl_mode', 'kuavo_msgs/changeArmCtrlMode')
376
+ request = {}
377
+ response = service.call(request)
378
+ return KuavoArmCtrlMode(response.get('control_mode', 0))
379
+ except Exception as e:
380
+ SDKLogger.error(f"Service call failed: {e}")
381
+ return None
382
+
383
+ """ Control Robot Waist """
384
+
385
+ class ControlRobotWaistWebsocket:
386
+ def __init__(self):
387
+ # 创建 WebSocket 客户端
388
+ websocket = WebSocketKuavoSDK()
389
+
390
+ # Publisher:发布到 /robot_waist_motion_data,消息类型 kuavo_msgs/robotWaistControl
391
+ self._pub_ctrl_robot_waist = roslibpy.Topic(
392
+ websocket.client,
393
+ '/robot_waist_motion_data',
394
+ 'kuavo_msgs/robotWaistControl'
395
+ )
396
+ self._pub_ctrl_robot_waist.advertise()
397
+
398
+ def connect(self, timeout: float = 1.0) -> bool:
399
+ return True
400
+
401
+ def pub_control_robot_waist(self, waistPos: list) -> bool:
402
+ """
403
+ 发布腰部位置控制命令 (WebSocket)
404
+ 参数:
405
+ waistPos: 长度为 1 的数组,例如 [30.0]
406
+ """
407
+ # 检查输入维度
408
+ if len(waistPos) != 1:
409
+ SDKLogger.error("Waist data must be 1-dimensional")
410
+ return False
411
+
412
+ try:
413
+ # 获取当前时间戳(秒和纳秒)
414
+ import time
415
+ now = time.time()
416
+ secs = int(now)
417
+ nsecs = int((now - secs) * 1e9)
418
+
419
+ # roslibpy 方式构建消息
420
+ msg = {
421
+ 'header': {
422
+ 'stamp': {
423
+ 'secs': secs,
424
+ 'nsecs': nsecs
425
+ }
426
+ },
427
+ 'data': {
428
+ 'data': [float(waistPos[0])]
429
+ }
430
+ }
431
+
432
+ # 发布
433
+ self._pub_ctrl_robot_waist.publish(roslibpy.Message(msg))
434
+ return True
435
+
436
+ except Exception as e:
437
+ SDKLogger.error(f"Publish waist pos failed: {e}")
438
+ return False
439
+
440
+
441
+ """ Control Robot Head """
442
+ class ControlRobotHeadWebsocket:
443
+ def __init__(self):
444
+ websocket = WebSocketKuavoSDK()
445
+ self._pub_ctrl_robot_head = roslibpy.Topic(websocket.client, '/robot_head_motion_data', 'kuavo_msgs/robotHeadMotionData')
446
+ self._pub_ctrl_robot_head.advertise()
447
+
448
+ def connect(self, timeout:float=1.0)->bool:
449
+ return True
450
+
451
+ def pub_control_robot_head(self, yaw:float, pitch:float)->bool:
452
+ try:
453
+ msg = {
454
+ "joint_data": [float(yaw), float(pitch)]
455
+ }
456
+ self._pub_ctrl_robot_head.publish(roslibpy.Message(msg))
457
+ return True
458
+ except Exception as e:
459
+ SDKLogger.error(f"[Error] publish robot head: {e}")
460
+ return False
461
+
462
+ def srv_enable_head_tracking(self, target_id: int)->bool:
463
+ """Enable the head tracking for a specific tag ID.
464
+
465
+ Args:
466
+ target_id: The ID of the tag to track
467
+
468
+ Returns:
469
+ bool: True if successful, False otherwise
470
+ """
471
+ try:
472
+ websocket = WebSocketKuavoSDK()
473
+ # First set the target tag ID
474
+ set_tag_service = roslibpy.Service(websocket.client, '/set_target_tag_id', 'kuavo_msgs/setTagId')
475
+ set_tag_request = {
476
+ "tag_id": target_id
477
+ }
478
+ set_tag_response = set_tag_service.call(set_tag_request)
479
+ if not set_tag_response.get('success', False):
480
+ SDKLogger.error(f"Failed to set target tag ID: {set_tag_response.get('message', '')}")
481
+ return False
482
+
483
+ SDKLogger.info(f"Successfully set target tag ID to {target_id}: {set_tag_response.get('message', '')}")
484
+
485
+ # Then start continuous tracking
486
+ track_service = roslibpy.Service(websocket.client, '/continuous_track', 'std_srvs/SetBool')
487
+ track_request = {
488
+ "data": True
489
+ }
490
+ track_response = track_service.call(track_request)
491
+ if not track_response.get('success', False):
492
+ SDKLogger.error(f"Failed to start continuous tracking: {track_response.get('message', '')}")
493
+ return False
494
+
495
+ SDKLogger.info(f"Successfully started continuous tracking: {track_response.get('message', '')}")
496
+ return True
497
+
498
+ except Exception as e:
499
+ SDKLogger.error(f"Failed to enable head tracking: {e}")
500
+ return False
501
+
502
+ def srv_disable_head_tracking(self)->bool:
503
+ """Disable the head tracking.
504
+
505
+ Returns:
506
+ bool: True if successful, False otherwise
507
+ """
508
+ try:
509
+ websocket = WebSocketKuavoSDK()
510
+ service = roslibpy.Service(websocket.client, '/continuous_track', 'std_srvs/SetBool')
511
+ request = {
512
+ "data": False
513
+ }
514
+ response = service.call(request)
515
+ if not response.get('success', False):
516
+ SDKLogger.error(f"Failed to stop continuous tracking: {response.get('message', '')}")
517
+ return False
518
+
519
+ SDKLogger.info(f"Successfully stopped continuous tracking: {response.get('message', '')}")
520
+ return True
521
+
522
+ except Exception as e:
523
+ SDKLogger.error(f"Failed to disable head tracking: {e}")
524
+ return False
525
+
526
+ """ Control Robot Motion """
527
+
528
+ # JoyButton constants
529
+ BUTTON_A = 0
530
+ BUTTON_B = 1
531
+ BUTTON_X = 2
532
+ BUTTON_Y = 3
533
+ BUTTON_LB = 4
534
+ BUTTON_RB = 5
535
+ BUTTON_BACK = 6
536
+ BUTTON_START = 7
537
+
538
+ # JoyAxis constants
539
+ AXIS_LEFT_STICK_Y = 0
540
+ AXIS_LEFT_STICK_X = 1
541
+ AXIS_LEFT_LT = 2 # 1 -> (-1)
542
+ AXIS_RIGHT_STICK_YAW = 3
543
+ AXIS_RIGHT_STICK_Z = 4
544
+ AXIS_RIGHT_RT = 5 # 1 -> (-1)
545
+ AXIS_LEFT_RIGHT_TRIGGER = 6
546
+ AXIS_FORWARD_BACK_TRIGGER = 7
547
+
548
+
549
+ class ControlRobotMotionWebsocket:
550
+ def __init__(self):
551
+ websocket = WebSocketKuavoSDK()
552
+ self._pub_cmd_vel = roslibpy.Topic(websocket.client, '/cmd_vel', 'geometry_msgs/Twist')
553
+ self._pub_cmd_pose = roslibpy.Topic(websocket.client, '/cmd_pose', 'geometry_msgs/Twist')
554
+ self._pub_cmd_pose_world = roslibpy.Topic(websocket.client, '/cmd_pose_world', 'geometry_msgs/Twist')
555
+ self._pub_joy = roslibpy.Topic(websocket.client, '/joy', 'sensor_msgs/Joy')
556
+ self._pub_switch_gait = roslibpy.Topic(websocket.client, '/humanoid_switch_gait_by_name', 'kuavo_msgs/switchGaitByName')
557
+ self._pub_step_ctrl = roslibpy.Topic(websocket.client, '/humanoid_mpc_foot_pose_target_trajectories', 'kuavo_msgs/footPoseTargetTrajectories')
558
+ self._pub_mpc_target_pose = roslibpy.Topic(websocket.client, '/humanoid_mpc_target_pose', 'kuavo_msgs/mpc_target_trajectories')
559
+ self._pub_cmd_vel.advertise()
560
+ self._pub_cmd_pose.advertise()
561
+ self._pub_cmd_pose_world.advertise()
562
+ self._pub_joy.advertise()
563
+ self._pub_switch_gait.advertise()
564
+ self._pub_step_ctrl.advertise()
565
+ self._pub_mpc_target_pose.advertise()
566
+
567
+ def connect(self, timeout:float=2.0)-> bool:
568
+ return True
569
+
570
+ def pub_cmd_vel(self, linear_x: float, linear_y: float, angular_z: float)->bool:
571
+ try:
572
+ msg = {
573
+ "linear": {"x": float(linear_x), "y": float(linear_y), "z": 0.0},
574
+ "angular": {"x": 0.0, "y": 0.0, "z": float(angular_z)}
575
+ }
576
+ self._pub_cmd_vel.publish(roslibpy.Message(msg))
577
+ return True
578
+ except Exception as e:
579
+ SDKLogger.error(f"[Error] publish cmd vel: {e}")
580
+ return False
581
+
582
+ def pub_cmd_pose(self, msg)->bool:
583
+ try:
584
+ self._pub_cmd_pose.publish(roslibpy.Message(msg))
585
+ return True
586
+ except Exception as e:
587
+ SDKLogger.error(f"[Error] publish cmd pose: {e}")
588
+ return False
589
+
590
+ def pub_cmd_pose_world(self, msg)->bool:
591
+ try:
592
+ self._pub_cmd_pose_world.publish(roslibpy.Message(msg))
593
+ return True
594
+ except Exception as e:
595
+ SDKLogger.error(f"[Error] publish cmd pose world: {e}")
596
+ return False
597
+
598
+ def _pub_joy_command(self, button_index: int, command_name: str) -> bool:
599
+ try:
600
+ msg = {
601
+ "axes": [0.0] * 8,
602
+ "buttons": [0] * 16
603
+ }
604
+ msg["buttons"][button_index] = 1
605
+ self._pub_joy.publish(roslibpy.Message(msg))
606
+ SDKLogger.debug(f"Published {command_name} command")
607
+ return True
608
+ except Exception as e:
609
+ SDKLogger.error(f"[Error] publish {command_name}: {e}")
610
+ return False
611
+
612
+ def _pub_switch_gait_by_name(self, gait_name: str) -> bool:
613
+ try:
614
+ msg = {
615
+ "gait_name": gait_name
616
+ }
617
+ self._pub_switch_gait.publish(roslibpy.Message(msg))
618
+ return True
619
+ except Exception as e:
620
+ SDKLogger.error(f"[Error] publish switch gait {gait_name}: {e}")
621
+ return False
622
+
623
+ def pub_walk_command(self) -> bool:
624
+ return self._pub_switch_gait_by_name("walk")
625
+
626
+ def pub_stance_command(self) -> bool:
627
+ try:
628
+ self.pub_cmd_vel(linear_x=0.0, linear_y=0.0, angular_z=0.0)
629
+ return self._pub_switch_gait_by_name("stance")
630
+ except Exception as e:
631
+ SDKLogger.error(f"[Error] publish stance: {e}")
632
+ return False
633
+
634
+ def pub_trot_command(self) -> bool:
635
+ return self._pub_switch_gait_by_name("walk")
636
+
637
+ def pub_mpc_target_pose(self, target_pose: list, initial_pose: list = None, time_horizon: float = 2.0)->bool:
638
+ """
639
+ 发布6DOF躯干姿态目标轨迹到MPC
640
+
641
+ 参数:
642
+ target_pose: 6DOF目标姿态 [x, y, z, yaw, pitch, roll]
643
+ initial_pose: 6DOF初始姿态 [x, y, z, yaw, pitch, roll],如果为None则从当前observation获取
644
+ time_horizon: 目标时间(相对于当前MPC时间),单位秒
645
+ 返回:
646
+ bool: 发布成功返回True,否则返回False
647
+
648
+ 注意:
649
+ - 如果initial_pose为None,必须能够从MPC observation中获取当前状态,否则返回False
650
+ - 必须能够获取MPC observation中的时间,否则返回False
651
+ - 时间使用MPC observation中的时间,而不是系统时间
652
+ """
653
+ try:
654
+ if len(target_pose) != 6:
655
+ SDKLogger.error(f"[Error] target_pose must have 6 elements, got {len(target_pose)}")
656
+ return False
657
+
658
+ # 获取MPC observation数据(用于获取当前时间和状态)
659
+ from kuavo_humanoid_sdk.kuavo.core.ros.state import KuavoRobotStateCoreWebsocket
660
+ state_core = KuavoRobotStateCoreWebsocket()
661
+ current_time = None
662
+ current_state = None
663
+
664
+ # 检查是否能够获取MPC observation数据
665
+ if not hasattr(state_core, '_mpc_observation_data') or state_core._mpc_observation_data is None:
666
+ SDKLogger.error("[Error] Cannot get MPC observation data. Please ensure the MPC controller is running and publishing observation data.")
667
+ return False
668
+
669
+ obs = state_core._mpc_observation_data
670
+
671
+ # 获取MPC时间(websocket版本中obs是字典)
672
+ if 'time' not in obs:
673
+ SDKLogger.error("[Error] MPC observation data does not have 'time' field.")
674
+ return False
675
+ current_time = obs['time']
676
+
677
+ # 如果需要从observation获取初始状态
678
+ if initial_pose is None:
679
+ # 检查observation中是否有state数据
680
+ if 'state' not in obs or 'value' not in obs['state']:
681
+ SDKLogger.error("[Error] MPC observation data does not have 'state.value' field.")
682
+ return False
683
+
684
+ # MPC状态向量索引说明:
685
+ # 0-5: 质心动量 (vcom_x, vcom_y, vcom_z, L_x/m, L_y/m, L_z/m)
686
+ # 6-11: 躯干姿态 (p_base_x, p_base_y, p_base_z, theta_base_z/yaw, theta_base_y/pitch, theta_base_x/roll)
687
+ if len(obs['state']['value']) < 12:
688
+ SDKLogger.error(f"[Error] MPC observation state value length ({len(obs['state']['value'])}) is less than 12. Cannot extract current pose.")
689
+ return False
690
+
691
+ # 从observation的state中提取索引6-11的元素作为当前姿态 [x, y, z, yaw, pitch, roll]
692
+ current_state = [
693
+ obs['state']['value'][6], # p_base_x
694
+ obs['state']['value'][7], # p_base_y
695
+ obs['state']['value'][8], # p_base_z
696
+ obs['state']['value'][9], # theta_base_z (yaw)
697
+ obs['state']['value'][10], # theta_base_y (pitch)
698
+ obs['state']['value'][11] # theta_base_x (roll)
699
+ ]
700
+ initial_pose = current_state
701
+ elif len(initial_pose) != 6:
702
+ SDKLogger.error(f"[Error] initial_pose must have 6 elements, got {len(initial_pose)}")
703
+ return False
704
+
705
+ # 验证时间是否有效
706
+ if current_time is None or current_time <= 0:
707
+ SDKLogger.error(f"[Error] Invalid MPC time: {current_time}. Cannot publish trajectory.")
708
+ return False
709
+
710
+ # 创建mpc_target_trajectories消息(websocket版本使用字典格式)
711
+ msg = {
712
+ "timeTrajectory": [current_time, current_time + time_horizon],
713
+ "stateTrajectory": [
714
+ {"value": [float(x) for x in initial_pose]},
715
+ {"value": [float(x) for x in target_pose]}
716
+ ],
717
+ "inputTrajectory": [
718
+ {"value": []},
719
+ {"value": []}
720
+ ]
721
+ }
722
+
723
+ self._pub_mpc_target_pose.publish(roslibpy.Message(msg))
724
+ return True
725
+ except Exception as e:
726
+ SDKLogger.error(f"[Error] publish mpc target pose: {e}")
727
+ return False
728
+
729
+ def pub_step_ctrl(self, msg)->bool:
730
+ try:
731
+ websocket_msg = {
732
+ "timeTrajectory": msg["timeTrajectory"],
733
+ "footIndexTrajectory": msg["footIndexTrajectory"],
734
+ "footPoseTrajectory": [
735
+ {
736
+ "footPose": list(fp["footPose"]),
737
+ "torsoPose": list(fp["torsoPose"])
738
+ } for fp in msg["footPoseTrajectory"]
739
+ ]
740
+ }
741
+ SDKLogger.debug(f"websocket_msg {websocket_msg}")
742
+ self._pub_step_ctrl.publish(roslibpy.Message(websocket_msg))
743
+ SDKLogger.debug(f"after publish")
744
+ return True
745
+ except Exception as e:
746
+ SDKLogger.error(f"[Error] publish step ctrl: {e}")
747
+ return False
748
+
749
+
750
+ class KuavoRobotArmIKFKWebsocket:
751
+ def __init__(self):
752
+ pass
753
+
754
+ def arm_ik(self,
755
+ left_pose: KuavoPose,
756
+ right_pose: KuavoPose,
757
+ left_elbow_pos_xyz: list = [0.0, 0.0, 0.0],
758
+ right_elbow_pos_xyz: list = [0.0, 0.0, 0.0],
759
+ arm_q0: list = None,
760
+ params: KuavoIKParams=None) -> list:
761
+ try:
762
+ websocket = WebSocketKuavoSDK()
763
+ service = roslibpy.Service(websocket.client, '/ik/two_arm_hand_pose_cmd_srv', 'kuavo_msgs/twoArmHandPoseCmdSrv')
764
+
765
+ request = {
766
+ "twoArmHandPoseCmdRequest": {
767
+ "hand_poses": {
768
+ "header": {
769
+ "seq": 0,
770
+ "stamp": {
771
+ "secs": 0,
772
+ "nsecs": 0
773
+ },
774
+ "frame_id": ""
775
+ },
776
+ "left_pose": {
777
+ "pos_xyz": left_pose.position,
778
+ "quat_xyzw": left_pose.orientation,
779
+ "elbow_pos_xyz": left_elbow_pos_xyz,
780
+ },
781
+ "right_pose": {
782
+ "pos_xyz": right_pose.position,
783
+ "quat_xyzw": right_pose.orientation,
784
+ "elbow_pos_xyz": right_elbow_pos_xyz,
785
+ }
786
+ },
787
+ "use_custom_ik_param": params is not None,
788
+ "joint_angles_as_q0": arm_q0 is not None,
789
+ "ik_param": {
790
+ "major_optimality_tol": params.major_optimality_tol if params else 0.0,
791
+ "major_feasibility_tol": params.major_feasibility_tol if params else 0.0,
792
+ "minor_feasibility_tol": params.minor_feasibility_tol if params else 0.0,
793
+ "major_iterations_limit": params.major_iterations_limit if params else 0,
794
+ "oritation_constraint_tol": params.oritation_constraint_tol if params else 0.0,
795
+ "pos_constraint_tol": params.pos_constraint_tol if params else 0.0,
796
+ "pos_cost_weight": params.pos_cost_weight if params else 0.0
797
+ }
798
+ }
799
+ }
800
+
801
+ response = service.call(request)
802
+ if response.get('success', False):
803
+ return response['hand_poses']['left_pose']['joint_angles'] + response['hand_poses']['right_pose']['joint_angles']
804
+ return None
805
+ except Exception as e:
806
+ SDKLogger.error(f"Service call failed: {e}")
807
+ return None
808
+
809
+ def arm_fk(self, q: list) -> Tuple[KuavoPose, KuavoPose]:
810
+ try:
811
+ websocket = WebSocketKuavoSDK()
812
+ service = roslibpy.Service(websocket.client, '/ik/fk_srv', 'kuavo_msgs/fkSrv')
813
+ request = {"q": q}
814
+ response = service.call(request)
815
+
816
+ if response.get('success', False):
817
+ left_pose = KuavoPose(
818
+ position=response['hand_poses']['left_pose']['pos_xyz'],
819
+ orientation=response['hand_poses']['left_pose']['quat_xyzw']
820
+ )
821
+ right_pose = KuavoPose(
822
+ position=response['hand_poses']['right_pose']['pos_xyz'],
823
+ orientation=response['hand_poses']['right_pose']['quat_xyzw']
824
+ )
825
+ return left_pose, right_pose
826
+ return None
827
+ except Exception as e:
828
+ SDKLogger.error(f"Service call failed: {e}")
829
+ return None
830
+
831
+ """
832
+ Kuavo Robot Control
833
+ """
834
+ class KuavoRobotControlWebsocket:
835
+ _instance = None
836
+
837
+ def __new__(cls, *args, **kwargs):
838
+ if not cls._instance:
839
+ cls._instance = super().__new__(cls)
840
+ return cls._instance
841
+
842
+ def __init__(self):
843
+ if not hasattr(self, '_initialized'):
844
+ try:
845
+ self._initialized = True
846
+ self.kuavo_eef_control = None
847
+ self.kuavo_head_control = ControlRobotHeadWebsocket()
848
+ self.kuavo_arm_control = ControlRobotArmWebsocket()
849
+ self.kuavo_motion_control = ControlRobotMotionWebsocket()
850
+ self.kuavo_arm_ik_fk = KuavoRobotArmIKFKWebsocket()
851
+ self.kuavo_waist_control = ControlRobotWaistWebsocket()
852
+ except Exception as e:
853
+ SDKLogger.error(f"Failed to initialize KuavoRobotControlWebsocket: {e}")
854
+ raise
855
+
856
+ def initialize(self, eef_type:str=None, debug:bool=False, timeout:float=1.0)-> Tuple[bool, str]:
857
+ try:
858
+ # init eef control
859
+ if eef_type is None:
860
+ self.kuavo_eef_control = None
861
+ else:
862
+ self.kuavo_eef_control = ControlEndEffectorWebsocket(eef_type=eef_type)
863
+
864
+ connect_success = True
865
+ err_msg = ''
866
+ if not self.kuavo_arm_control.connect(timeout):
867
+ connect_success = False
868
+ err_msg = "Failed to connect to arm control topics, \n"
869
+ if not self.kuavo_head_control.connect(timeout):
870
+ connect_success = False
871
+ err_msg += "Failed to connect to head control topics, \n"
872
+ if not self.kuavo_motion_control.connect(timeout):
873
+ err_msg += "Failed to connect to motion control topics, \n"
874
+ connect_success = False
875
+
876
+ if self.kuavo_eef_control is not None and not self.kuavo_eef_control.connect(timeout):
877
+ connect_success = False
878
+ err_msg += "Failed to connect to end effector control topics."
879
+
880
+ if connect_success:
881
+ err_msg = 'success'
882
+ return connect_success, err_msg
883
+ except Exception as e:
884
+ SDKLogger.error(f"Failed to initialize KuavoRobotControlWebsocket: {e}")
885
+ return False, str(e)
886
+
887
+ """ End Effector Control"""
888
+ def control_robot_dexhand(self, left_position:list, right_position:list)->bool:
889
+ """
890
+ Control robot dexhand
891
+ Args:
892
+ left_position: list of 6 floats between 0 and 100
893
+ right_position: list of 6 floats between 0 and 100
894
+ """
895
+ if self.kuavo_eef_control is None:
896
+ SDKLogger.error("End effector control is not initialized.")
897
+ return False
898
+
899
+ if len(left_position) != 6 or len(right_position) != 6:
900
+ raise ValueError("Position lists must have a length of 6.")
901
+
902
+ for position in left_position + right_position:
903
+ if position < 0.0 or position > 100.0:
904
+ raise ValueError("All position values must be in the range [0.0, 100.0].")
905
+
906
+ SDKLogger.debug(f"Control robot dexhand: {left_position}, {right_position}")
907
+ return self.kuavo_eef_control.pub_control_robot_dexhand(left_position, right_position)
908
+
909
+ def robot_dexhand_command(self, data, ctrl_mode, hand_side):
910
+ """
911
+ Publish dexhand command
912
+ Args:
913
+ - data: list of 6 floats between 0 and 100
914
+ - ctrl_mode: int between 0(position), 1(velocity)
915
+ - hand_side: int between 0(left), 1(right), 2(dual)
916
+ """
917
+ if self.kuavo_eef_control is None:
918
+ SDKLogger.error("End effector control is not initialized.")
919
+ return False
920
+ return self.kuavo_eef_control.pub_dexhand_command(data, ctrl_mode, hand_side)
921
+
922
+ def execute_gesture(self, gestures:list)->bool:
923
+ """
924
+ Execute gestures
925
+ Arguments:
926
+ - gestures: list of dicts with keys 'gesture_name' and 'hand_side'
927
+ e.g. [{'gesture_name': 'fist', 'hand_side': 0},]
928
+ """
929
+ if self.kuavo_eef_control is None:
930
+ SDKLogger.warn("End effectors control is not initialized.")
931
+ return False
932
+ return self.kuavo_eef_control.srv_execute_gesture(gestures)
933
+
934
+ def get_gesture_names(self)->list:
935
+ """
936
+ Get the names of all gestures.
937
+ """
938
+ if self.kuavo_eef_control is None:
939
+ SDKLogger.warn("End effectors control is not initialized.")
940
+ return []
941
+ return self.kuavo_eef_control.srv_get_gesture_names()
942
+
943
+ def control_leju_claw(self, postions:list, velocities:list=[90, 90], torques:list=[1.0, 1.0]) ->bool:
944
+ """
945
+ Control leju claw
946
+ Arguments:
947
+ - postions: list of positions for left and right claw
948
+ - velocities: list of velocities for left and right claw
949
+ - torques: list of torques for left and right claw
950
+ """
951
+ if self.kuavo_eef_control is None:
952
+ SDKLogger.warn("End effectors control is not initialized.")
953
+ return False
954
+ SDKLogger.debug(f"Control leju claw: {postions}, {velocities}, {torques}")
955
+ if len(postions) != 2 or len(velocities) != 2 or len(torques) != 2:
956
+ raise ValueError("Position, velocity, and torque lists must have a length of 2.")
957
+ return self.kuavo_eef_control.srv_control_leju_claw(postions, velocities, torques)
958
+
959
+ def control_robot_waist(self, yaw: float) -> bool:
960
+ """
961
+ Control robot waist
962
+ Arguments:
963
+ - yaw: waist yaw angle, radian
964
+ """
965
+ SDKLogger.debug(f"Control robot waist: {yaw}")
966
+ return self.kuavo_waist_control.pub_control_robot_waist(yaw)
967
+
968
+ def control_robot_head(self, yaw:float, pitch:float)->bool:
969
+ """
970
+ Control robot head
971
+ Arguments:
972
+ - yaw: yaw angle, radian
973
+ - pitch: pitch angle, radian
974
+ """
975
+ SDKLogger.debug(f"Control robot head: {yaw}, {pitch}")
976
+ return self.kuavo_head_control.pub_control_robot_head(yaw, pitch)
977
+
978
+ def control_robot_arm_joint_positions(self, joint_data:list)->bool:
979
+ """
980
+ Control robot arm joint positions
981
+ Arguments:
982
+ - joint_data: list of joint data (degrees)
983
+ """
984
+ return self.kuavo_arm_control.pub_control_robot_arm_traj(joint_data)
985
+
986
+ def is_arm_collision(self)->bool:
987
+ return self.kuavo_arm_control.is_arm_collision()
988
+
989
+ def is_arm_collision_mode(self)->bool:
990
+ """
991
+ Check if arm collision mode is enabled
992
+ Returns:
993
+ bool: True if collision mode is enabled, False otherwise
994
+ """
995
+ return self.kuavo_arm_control.is_arm_collision_mode()
996
+
997
+
998
+
999
+ def release_arm_collision_mode(self):
1000
+ return self.kuavo_arm_control.release_arm_collision_mode()
1001
+
1002
+ def wait_arm_collision_complete(self):
1003
+ return self.kuavo_arm_control.wait_arm_collision_complete()
1004
+
1005
+ def set_arm_collision_mode(self, enable: bool):
1006
+ """
1007
+ Set arm collision mode
1008
+ """
1009
+ return self.kuavo_arm_control.set_arm_collision_mode(enable)
1010
+
1011
+
1012
+ def control_robot_arm_joint_trajectory(self, times:list, joint_q:list)->bool:
1013
+ """
1014
+ Control robot arm joint trajectory
1015
+ Arguments:
1016
+ - times: list of times (seconds)
1017
+ - joint_q: list of joint data (degrees)
1018
+ """
1019
+ if len(times) != len(joint_q):
1020
+ raise ValueError("Times and joint_q must have the same length.")
1021
+ elif len(times) == 0:
1022
+ raise ValueError("Times and joint_q must not be empty.")
1023
+
1024
+ return self.kuavo_arm_control.pub_arm_target_poses(times=times, joint_q=joint_q)
1025
+
1026
+ def control_robot_end_effector_pose(self, left_pose: KuavoPose, right_pose: KuavoPose, frame: KuavoManipulationMpcFrame)->bool:
1027
+ """
1028
+ Control robot end effector pose
1029
+ Arguments:
1030
+ - left_pose: left end effector pose
1031
+ - right_pose: right end effector pose
1032
+ - frame: frame of the end effector pose, 0: keep current frame, 1: world frame, 2: local frame, 3: VR frame, 4: manipulation world frame
1033
+ """
1034
+ return self.kuavo_arm_control.pub_end_effector_pose_cmd(left_pose, right_pose, frame)
1035
+
1036
+ def change_manipulation_mpc_frame(self, frame: KuavoManipulationMpcFrame)->bool:
1037
+ """
1038
+ Change manipulation mpc frame
1039
+ Arguments:
1040
+ - frame: frame of the manipulation mpc
1041
+ """
1042
+ return self.kuavo_arm_control.srv_change_manipulation_mpc_frame(frame)
1043
+
1044
+ def change_manipulation_mpc_ctrl_mode(self, ctrl_mode: KuavoManipulationMpcCtrlMode)->bool:
1045
+ """
1046
+ Change manipulation mpc control mode
1047
+ Arguments:
1048
+ - control_mode: control mode of the manipulation mpc
1049
+ """
1050
+ return self.kuavo_arm_control.srv_change_manipulation_mpc_ctrl_mode(ctrl_mode)
1051
+
1052
+ def change_manipulation_mpc_control_flow(self, ctrl_flow: KuavoManipulationMpcControlFlow)->bool:
1053
+ """
1054
+ Change manipulation mpc wbc arm traj control mode, control signal will be sent to wbc directly
1055
+ Arguments:
1056
+ - control_mode: control mode of the manipulation mpc wbc arm traj
1057
+ """
1058
+ return self.kuavo_arm_control.srv_change_manipulation_mpc_control_flow(ctrl_flow)
1059
+
1060
+ def get_manipulation_mpc_ctrl_mode(self)->KuavoManipulationMpcCtrlMode:
1061
+ """
1062
+ Get manipulation mpc control mode
1063
+ """
1064
+ return self.kuavo_arm_control.srv_get_manipulation_mpc_ctrl_mode()
1065
+
1066
+ def get_manipulation_mpc_frame(self)-> KuavoManipulationMpcFrame:
1067
+ """
1068
+ Get manipulation mpc frame
1069
+ """
1070
+ return self.kuavo_arm_control.srv_get_manipulation_mpc_frame()
1071
+
1072
+ def get_manipulation_mpc_control_flow(self)->KuavoManipulationMpcControlFlow:
1073
+ """
1074
+ Get manipulation mpc wbc arm traj control mode
1075
+ """
1076
+ return self.kuavo_arm_control.srv_get_manipulation_mpc_control_flow()
1077
+
1078
+ def change_robot_arm_ctrl_mode(self, mode:KuavoArmCtrlMode)->bool:
1079
+ """
1080
+ Change robot arm control mode
1081
+ Arguments:
1082
+ - mode: arm control mode
1083
+ """
1084
+ SDKLogger.debug(f"[WebSocket] Change robot arm control mode: {mode}")
1085
+ return self.kuavo_arm_control.srv_change_arm_ctrl_mode(mode)
1086
+
1087
+ def get_robot_arm_ctrl_mode(self)->int:
1088
+ """
1089
+ Get robot arm control mode
1090
+ """
1091
+ return self.kuavo_arm_control.srv_get_arm_ctrl_mode()
1092
+
1093
+ def arm_ik(self, left_pose: KuavoPose, right_pose: KuavoPose,
1094
+ left_elbow_pos_xyz: list = [0.0, 0.0, 0.0],
1095
+ right_elbow_pos_xyz: list = [0.0, 0.0, 0.0],
1096
+ arm_q0: list = None, params: KuavoIKParams=None) -> list:
1097
+ return self.kuavo_arm_ik_fk.arm_ik(left_pose, right_pose, left_elbow_pos_xyz, right_elbow_pos_xyz, arm_q0, params)
1098
+
1099
+ def arm_fk(self, q: list) -> Tuple[KuavoPose, KuavoPose]:
1100
+ return self.kuavo_arm_ik_fk.arm_fk(q)
1101
+
1102
+ """ Motion """
1103
+ def robot_stance(self)->bool:
1104
+ return self.kuavo_motion_control.pub_stance_command()
1105
+
1106
+ def robot_trot(self)->bool:
1107
+ return self.kuavo_motion_control.pub_trot_command()
1108
+
1109
+ def robot_walk(self, linear_x:float, linear_y:float, angular_z:float)->bool:
1110
+ return self.kuavo_motion_control.pub_cmd_vel(linear_x, linear_y, angular_z)
1111
+
1112
+ def control_torso_height(self, height:float, pitch:float=0.0)->bool:
1113
+ """
1114
+ 控制躯干高度和俯仰角(使用MPC目标轨迹接口)
1115
+ 参数:
1116
+ height: 相对于当前高度的变化量(米),负值表示下蹲,正值表示上升
1117
+ pitch: 相对于当前俯仰角的变化量(弧度),默认0.0
1118
+ 返回:
1119
+ bool: 控制成功返回True,否则返回False
1120
+ """
1121
+ # 获取当前状态
1122
+ from kuavo_humanoid_sdk.kuavo.core.ros.state import KuavoRobotStateCoreWebsocket
1123
+ state_core = KuavoRobotStateCoreWebsocket()
1124
+ if not hasattr(state_core, '_mpc_observation_data') or state_core._mpc_observation_data is None:
1125
+ SDKLogger.error("[Error] Cannot get MPC observation data for control_torso_height")
1126
+ return False
1127
+
1128
+ obs = state_core._mpc_observation_data
1129
+ if 'state' not in obs or 'value' not in obs['state'] or len(obs['state']['value']) < 12:
1130
+ SDKLogger.error("[Error] Cannot get current state from observation for control_torso_height")
1131
+ return False
1132
+
1133
+ # 从observation获取当前姿态 [x, y, z, yaw, pitch, roll]
1134
+ current_pose = [
1135
+ obs['state']['value'][6], # p_base_x
1136
+ obs['state']['value'][7], # p_base_y
1137
+ obs['state']['value'][8], # p_base_z
1138
+ obs['state']['value'][9], # theta_base_z (yaw)
1139
+ obs['state']['value'][10], # theta_base_y (pitch)
1140
+ obs['state']['value'][11] # theta_base_x (roll)
1141
+ ]
1142
+
1143
+ # 计算目标姿态:当前姿态 + 变化量
1144
+ target_pose = [
1145
+ current_pose[0], # x: 保持不变
1146
+ current_pose[1], # y: 保持不变
1147
+ height, # z: 目标高度
1148
+ current_pose[3], # yaw: 保持不变
1149
+ pitch, # pitch: 目标俯仰角
1150
+ current_pose[5] # roll: 保持不变
1151
+ ]
1152
+
1153
+ # 使用当前姿态作为初始姿态,目标姿态 = 当前姿态 + 变化量
1154
+ return self.kuavo_motion_control.pub_mpc_target_pose(target_pose, initial_pose=current_pose, time_horizon=3.0)
1155
+
1156
+ def control_command_pose_world(self, target_pose_x:float, target_pose_y:float, target_pose_z:float, target_pose_yaw:float)->bool:
1157
+ """
1158
+ odom下的机器人cmd_pose_world
1159
+ """
1160
+ msg = {
1161
+ "linear": {"x": float(target_pose_x), "y": float(target_pose_y), "z": float(target_pose_z)},
1162
+ "angular": {"x": 0.0, "y": 0.0, "z": float(target_pose_yaw)}
1163
+ }
1164
+ return self.kuavo_motion_control.pub_cmd_pose_world(roslibpy.Message(msg))
1165
+
1166
+ def control_command_pose(self, target_pose_x:float, target_pose_y:float, target_pose_z:float, target_pose_yaw:float)->bool:
1167
+ """
1168
+ base_link下的机器人cmd_pose
1169
+ """
1170
+ msg = {
1171
+ "linear": {"x": float(target_pose_x), "y": float(target_pose_y), "z": float(target_pose_z)},
1172
+ "angular": {"x": 0.0, "y": 0.0, "z": float(target_pose_yaw)}
1173
+ }
1174
+ return self.kuavo_motion_control.pub_cmd_pose(roslibpy.Message(msg))
1175
+
1176
+ def step_control(self, body_poses:list, dt:float, is_left_first_default:bool=True, collision_check:bool=True)->bool:
1177
+ """
1178
+ Step control
1179
+ Arguments:
1180
+ - body_poses: list of body poses (x, y, z, yaw), meters and degrees
1181
+ - dt: time step (seconds)
1182
+ - is_left_first_default: whether to start with left foot
1183
+ - collision_check: whether to check for collisions
1184
+ """
1185
+ if len(body_poses) == 0:
1186
+ raise ValueError("Body poses must not be empty.")
1187
+ if dt <= 0.0:
1188
+ raise ValueError("Time step must be greater than 0.0.")
1189
+ for bp in body_poses:
1190
+ if len(bp) != 4:
1191
+ raise ValueError("Body pose must have 4 elements: [x, y, z, yaw]")
1192
+
1193
+ msg = get_multiple_steps_msg(body_poses, dt, is_left_first_default, collision_check)
1194
+ return self.kuavo_motion_control.pub_step_ctrl(msg)
1195
+
1196
+
1197
+ def euler_to_rotation_matrix(yaw, pitch, roll):
1198
+ # 计算各轴的旋转矩阵
1199
+ R_yaw = np.array([[np.cos(yaw), -np.sin(yaw), 0],
1200
+ [np.sin(yaw), np.cos(yaw), 0],
1201
+ [0, 0, 1]])
1202
+
1203
+ R_pitch = np.array([[np.cos(pitch), 0, np.sin(pitch)],
1204
+ [0, 1, 0],
1205
+ [-np.sin(pitch), 0, np.cos(pitch)]])
1206
+
1207
+ R_roll = np.array([[1, 0, 0],
1208
+ [0, np.cos(roll), -np.sin(roll)],
1209
+ [0, np.sin(roll), np.cos(roll)]])
1210
+
1211
+ # 按照 Yaw-Pitch-Roll 的顺序组合旋转矩阵
1212
+ R = np.dot(R_roll, np.dot(R_pitch, R_yaw))
1213
+ return R
1214
+
1215
+ def get_foot_pose_traj_msg(time_traj:list, foot_idx_traj:list, foot_traj:list, torso_traj:list):
1216
+ num = len(time_traj)
1217
+
1218
+ msg = {
1219
+ "timeTrajectory": time_traj,
1220
+ "footIndexTrajectory": foot_idx_traj,
1221
+ "footPoseTrajectory": []
1222
+ }
1223
+
1224
+ for i in range(num):
1225
+ foot_pose = {
1226
+ "footPose": foot_traj[i],
1227
+ "torsoPose": torso_traj[i]
1228
+ }
1229
+ msg["footPoseTrajectory"].append(foot_pose)
1230
+
1231
+
1232
+ return msg
1233
+
1234
+ def generate_steps(torso_pos, torso_yaw, foot_bias):
1235
+ l_foot_bias = np.array([0, foot_bias, -torso_pos[2]])
1236
+ r_foot_bias = np.array([0, -foot_bias, -torso_pos[2]])
1237
+ R_z = np.array([
1238
+ [np.cos(torso_yaw), -np.sin(torso_yaw), 0],
1239
+ [np.sin(torso_yaw), np.cos(torso_yaw), 0],
1240
+ [0, 0, 1]
1241
+ ])
1242
+ l_foot = torso_pos + R_z.dot(l_foot_bias)
1243
+ r_foot = torso_pos + R_z.dot(r_foot_bias)
1244
+ return l_foot, r_foot
1245
+
1246
+ def get_multiple_steps_msg(body_poses:list, dt:float, is_left_first:bool=True, collision_check:bool=True):
1247
+ num_steps = 2*len(body_poses)
1248
+ time_traj = []
1249
+ foot_idx_traj = []
1250
+ foot_traj = []
1251
+ torso_traj = []
1252
+ l_foot_rect_last = RotatingRectangle(center=(0, 0.1), width=0.24, height=0.1, angle=0)
1253
+ r_foot_rect_last = RotatingRectangle(center=(0,-0.1), width=0.24, height=0.1, angle=0)
1254
+ torso_pose_last = np.array([0, 0, 0, 0])
1255
+ for i in range(num_steps):
1256
+ time_traj.append(dt * (i+1))
1257
+ body_pose = body_poses[i//2]
1258
+ torso_pos = np.asarray(body_pose[:3])
1259
+ torso_yaw = np.radians(body_pose[3])
1260
+ l_foot, r_foot = generate_steps(torso_pos, torso_yaw, 0.1)
1261
+ l_foot = [*l_foot[:3], torso_yaw]
1262
+ r_foot = [*r_foot[:3], torso_yaw]
1263
+
1264
+ if(i%2 == 0):
1265
+ torso_pose = np.array([*body_pose[:3], torso_yaw])
1266
+ R_wl = euler_to_rotation_matrix(torso_pose_last[3], 0, 0)
1267
+ delta_pos = R_wl.T @ (torso_pose[:3] - torso_pose_last[:3])
1268
+ # print("delta_pos:", delta_pos)
1269
+ if(torso_yaw > 0.0 or delta_pos[1] > 0.0):
1270
+ is_left_first = True
1271
+ else:
1272
+ is_left_first = False
1273
+
1274
+ if(collision_check and i%2 == 0):
1275
+ l_foot_rect_next = RotatingRectangle(center=(l_foot[0],l_foot[1]), width=0.24, height=0.1, angle=torso_yaw)
1276
+ r_foot_rect_next = RotatingRectangle(center=(r_foot[0],r_foot[1]), width=0.24, height=0.1, angle=torso_yaw)
1277
+ l_collision = l_foot_rect_next.is_collision(r_foot_rect_last)
1278
+ r_collision = r_foot_rect_next.is_collision(l_foot_rect_last)
1279
+ if l_collision and r_collision:
1280
+ SDKLogger.error("[Control] Detect collision, Please adjust your body_poses input!!!")
1281
+ break
1282
+ elif l_collision:
1283
+ SDKLogger.warn("[Control] Left foot is in collision, switch to right foot")
1284
+ is_left_first = False
1285
+ elif r_collision:
1286
+ SDKLogger.warn("[Control] Right foot is in collision, switch to left foot")
1287
+ is_left_first = True
1288
+ l_foot_rect_last = l_foot_rect_next
1289
+ r_foot_rect_last = r_foot_rect_next
1290
+ if(i%2 == 0):
1291
+ torso_traj.append((torso_pose_last + torso_pose)/2.0)
1292
+ if is_left_first:
1293
+ foot_idx_traj.append(0)
1294
+ foot_traj.append(l_foot)
1295
+ else:
1296
+ foot_idx_traj.append(1)
1297
+ foot_traj.append(r_foot)
1298
+ else:
1299
+ torso_traj.append(torso_pose)
1300
+ if is_left_first:
1301
+ foot_idx_traj.append(1)
1302
+ foot_traj.append(r_foot)
1303
+ else:
1304
+ foot_idx_traj.append(0)
1305
+ foot_traj.append(l_foot)
1306
+ torso_pose_last = torso_traj[-1]
1307
+ # print("time_traj:", time_traj)
1308
+ # print("foot_idx_traj:", foot_idx_traj)
1309
+ # print("foot_traj:", foot_traj)
1310
+ # print("torso_traj:", torso_traj)
1311
+ return get_foot_pose_traj_msg(time_traj, foot_idx_traj, foot_traj, torso_traj)
1312
+ """ ------------------------------------------------------------------------------"""
1313
+
1314
+
1315
+ # if __name__ == "__main__":
1316
+ # control = KuavoRobotControl()
1317
+ # control.change_manipulation_mpc_frame(KuavoManipulationMpcFrame.KeepCurrentFrame)
1318
+ # control.change_manipulation_mpc_ctrl_mode(KuavoManipulationMpcCtrlMode.ArmOnly)
1319
+ # control.change_manipulation_mpc_control_flow(KuavoManipulationMpcControlFlow.DirectToWbc)
1320
+ # print(control.get_manipulation_mpc_ctrl_mode())
1321
+ # print(control.get_manipulation_mpc_frame())
1322
+ # print(control.get_manipulation_mpc_control_flow())
1323
+ # control.change_manipulation_mpc_frame(KuavoManipulationMpcFrame.WorldFrame)
1324
+ # control.change_robot_arm_ctrl_mode(KuavoArmCtrlMode.ExternalControl)
1325
+ # control.control_robot_end_effector_pose(KuavoPose(position=[0.3, 0.4, 0.9], orientation=[0.0, 0.0, 0.0, 1.0]), KuavoPose(position=[0.3, -0.5, 1.0], orientation=[0.0, 0.0, 0.0, 1.0]), KuavoManipulationMpcFrame.WorldFrame)