kuavo-humanoid-sdk 1.1.6b1139__20250612053748-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.

Files changed (132) hide show
  1. kuavo_humanoid_sdk/__init__.py +6 -0
  2. kuavo_humanoid_sdk/common/logger.py +42 -0
  3. kuavo_humanoid_sdk/interfaces/__init__.py +4 -0
  4. kuavo_humanoid_sdk/interfaces/data_types.py +276 -0
  5. kuavo_humanoid_sdk/interfaces/end_effector.py +62 -0
  6. kuavo_humanoid_sdk/interfaces/robot.py +22 -0
  7. kuavo_humanoid_sdk/interfaces/robot_info.py +56 -0
  8. kuavo_humanoid_sdk/kuavo/__init__.py +11 -0
  9. kuavo_humanoid_sdk/kuavo/core/audio.py +32 -0
  10. kuavo_humanoid_sdk/kuavo/core/core.py +619 -0
  11. kuavo_humanoid_sdk/kuavo/core/dex_hand_control.py +114 -0
  12. kuavo_humanoid_sdk/kuavo/core/leju_claw_control.py +67 -0
  13. kuavo_humanoid_sdk/kuavo/core/ros/audio.py +92 -0
  14. kuavo_humanoid_sdk/kuavo/core/ros/control.py +1309 -0
  15. kuavo_humanoid_sdk/kuavo/core/ros/observation.py +94 -0
  16. kuavo_humanoid_sdk/kuavo/core/ros/param.py +183 -0
  17. kuavo_humanoid_sdk/kuavo/core/ros/sat_utils.py +103 -0
  18. kuavo_humanoid_sdk/kuavo/core/ros/state.py +606 -0
  19. kuavo_humanoid_sdk/kuavo/core/ros/tools.py +219 -0
  20. kuavo_humanoid_sdk/kuavo/core/ros/vision.py +234 -0
  21. kuavo_humanoid_sdk/kuavo/core/ros_env.py +237 -0
  22. kuavo_humanoid_sdk/kuavo/dexterous_hand.py +201 -0
  23. kuavo_humanoid_sdk/kuavo/leju_claw.py +235 -0
  24. kuavo_humanoid_sdk/kuavo/robot.py +464 -0
  25. kuavo_humanoid_sdk/kuavo/robot_arm.py +210 -0
  26. kuavo_humanoid_sdk/kuavo/robot_audio.py +39 -0
  27. kuavo_humanoid_sdk/kuavo/robot_head.py +50 -0
  28. kuavo_humanoid_sdk/kuavo/robot_info.py +113 -0
  29. kuavo_humanoid_sdk/kuavo/robot_observation.py +64 -0
  30. kuavo_humanoid_sdk/kuavo/robot_state.py +299 -0
  31. kuavo_humanoid_sdk/kuavo/robot_tool.py +82 -0
  32. kuavo_humanoid_sdk/kuavo/robot_vision.py +83 -0
  33. kuavo_humanoid_sdk/kuavo_strategy/__init__.py +2 -0
  34. kuavo_humanoid_sdk/kuavo_strategy/grasp_box/grasp_box_strategy.py +1126 -0
  35. kuavo_humanoid_sdk/kuavo_strategy/kuavo_strategy.py +104 -0
  36. kuavo_humanoid_sdk/msg/__init__.py +4 -0
  37. kuavo_humanoid_sdk/msg/kuavo_msgs/__init__.py +7 -0
  38. kuavo_humanoid_sdk/msg/kuavo_msgs/msg/_AprilTagDetection.py +306 -0
  39. kuavo_humanoid_sdk/msg/kuavo_msgs/msg/_AprilTagDetectionArray.py +437 -0
  40. kuavo_humanoid_sdk/msg/kuavo_msgs/msg/_Metadata.py +199 -0
  41. kuavo_humanoid_sdk/msg/kuavo_msgs/msg/_RobotActionState.py +112 -0
  42. kuavo_humanoid_sdk/msg/kuavo_msgs/msg/_TFArray.py +323 -0
  43. kuavo_humanoid_sdk/msg/kuavo_msgs/msg/__init__.py +44 -0
  44. kuavo_humanoid_sdk/msg/kuavo_msgs/msg/_armHandPose.py +160 -0
  45. kuavo_humanoid_sdk/msg/kuavo_msgs/msg/_armPoseWithTimeStamp.py +168 -0
  46. kuavo_humanoid_sdk/msg/kuavo_msgs/msg/_armTargetPoses.py +151 -0
  47. kuavo_humanoid_sdk/msg/kuavo_msgs/msg/_bezierCurveCubicPoint.py +178 -0
  48. kuavo_humanoid_sdk/msg/kuavo_msgs/msg/_dexhandCommand.py +229 -0
  49. kuavo_humanoid_sdk/msg/kuavo_msgs/msg/_dexhandTouchState.py +256 -0
  50. kuavo_humanoid_sdk/msg/kuavo_msgs/msg/_endEffectorData.py +227 -0
  51. kuavo_humanoid_sdk/msg/kuavo_msgs/msg/_footPose.py +123 -0
  52. kuavo_humanoid_sdk/msg/kuavo_msgs/msg/_footPoseTargetTrajectories.py +301 -0
  53. kuavo_humanoid_sdk/msg/kuavo_msgs/msg/_footPoses.py +149 -0
  54. kuavo_humanoid_sdk/msg/kuavo_msgs/msg/_fullBodyTargetTrajectories.py +258 -0
  55. kuavo_humanoid_sdk/msg/kuavo_msgs/msg/_gaitTimeName.py +147 -0
  56. kuavo_humanoid_sdk/msg/kuavo_msgs/msg/_gestureInfo.py +218 -0
  57. kuavo_humanoid_sdk/msg/kuavo_msgs/msg/_gestureTask.py +149 -0
  58. kuavo_humanoid_sdk/msg/kuavo_msgs/msg/_handPose.py +136 -0
  59. kuavo_humanoid_sdk/msg/kuavo_msgs/msg/_headBodyPose.py +145 -0
  60. kuavo_humanoid_sdk/msg/kuavo_msgs/msg/_ikSolveError.py +171 -0
  61. kuavo_humanoid_sdk/msg/kuavo_msgs/msg/_ikSolveParam.py +140 -0
  62. kuavo_humanoid_sdk/msg/kuavo_msgs/msg/_imuData.py +165 -0
  63. kuavo_humanoid_sdk/msg/kuavo_msgs/msg/_jointBezierTrajectory.py +201 -0
  64. kuavo_humanoid_sdk/msg/kuavo_msgs/msg/_jointCmd.py +390 -0
  65. kuavo_humanoid_sdk/msg/kuavo_msgs/msg/_jointData.py +205 -0
  66. kuavo_humanoid_sdk/msg/kuavo_msgs/msg/_lejuClawCommand.py +320 -0
  67. kuavo_humanoid_sdk/msg/kuavo_msgs/msg/_lejuClawState.py +341 -0
  68. kuavo_humanoid_sdk/msg/kuavo_msgs/msg/_motorParam.py +122 -0
  69. kuavo_humanoid_sdk/msg/kuavo_msgs/msg/_planArmState.py +120 -0
  70. kuavo_humanoid_sdk/msg/kuavo_msgs/msg/_questJoySticks.py +191 -0
  71. kuavo_humanoid_sdk/msg/kuavo_msgs/msg/_qv.py +121 -0
  72. kuavo_humanoid_sdk/msg/kuavo_msgs/msg/_robotArmQVVD.py +177 -0
  73. kuavo_humanoid_sdk/msg/kuavo_msgs/msg/_robotHandPosition.py +225 -0
  74. kuavo_humanoid_sdk/msg/kuavo_msgs/msg/_robotHeadMotionData.py +128 -0
  75. kuavo_humanoid_sdk/msg/kuavo_msgs/msg/_robotState.py +222 -0
  76. kuavo_humanoid_sdk/msg/kuavo_msgs/msg/_sensorsData.py +495 -0
  77. kuavo_humanoid_sdk/msg/kuavo_msgs/msg/_switchGaitByName.py +200 -0
  78. kuavo_humanoid_sdk/msg/kuavo_msgs/msg/_touchSensorStatus.py +162 -0
  79. kuavo_humanoid_sdk/msg/kuavo_msgs/msg/_twoArmHandPose.py +272 -0
  80. kuavo_humanoid_sdk/msg/kuavo_msgs/msg/_twoArmHandPoseCmd.py +315 -0
  81. kuavo_humanoid_sdk/msg/kuavo_msgs/msg/_yoloDetection.py +251 -0
  82. kuavo_humanoid_sdk/msg/kuavo_msgs/msg/_yoloOutputData.py +168 -0
  83. kuavo_humanoid_sdk/msg/kuavo_msgs/srv/_ExecuteArmAction.py +281 -0
  84. kuavo_humanoid_sdk/msg/kuavo_msgs/srv/_RepublishTFs.py +373 -0
  85. kuavo_humanoid_sdk/msg/kuavo_msgs/srv/_SetJoyTopic.py +282 -0
  86. kuavo_humanoid_sdk/msg/kuavo_msgs/srv/_SpeechSynthesis.py +270 -0
  87. kuavo_humanoid_sdk/msg/kuavo_msgs/srv/__init__.py +28 -0
  88. kuavo_humanoid_sdk/msg/kuavo_msgs/srv/_changeArmCtrlMode.py +275 -0
  89. kuavo_humanoid_sdk/msg/kuavo_msgs/srv/_changeArmCtrlModeKuavo.py +236 -0
  90. kuavo_humanoid_sdk/msg/kuavo_msgs/srv/_changeMotorParam.py +299 -0
  91. kuavo_humanoid_sdk/msg/kuavo_msgs/srv/_changeTorsoCtrlMode.py +274 -0
  92. kuavo_humanoid_sdk/msg/kuavo_msgs/srv/_controlLejuClaw.py +408 -0
  93. kuavo_humanoid_sdk/msg/kuavo_msgs/srv/_enableHandTouchSensor.py +304 -0
  94. kuavo_humanoid_sdk/msg/kuavo_msgs/srv/_fkSrv.py +394 -0
  95. kuavo_humanoid_sdk/msg/kuavo_msgs/srv/_footPoseTargetTrajectoriesSrv.py +409 -0
  96. kuavo_humanoid_sdk/msg/kuavo_msgs/srv/_gestureExecute.py +339 -0
  97. kuavo_humanoid_sdk/msg/kuavo_msgs/srv/_gestureExecuteState.py +257 -0
  98. kuavo_humanoid_sdk/msg/kuavo_msgs/srv/_gestureList.py +418 -0
  99. kuavo_humanoid_sdk/msg/kuavo_msgs/srv/_getCurrentGaitName.py +253 -0
  100. kuavo_humanoid_sdk/msg/kuavo_msgs/srv/_getMotorParam.py +299 -0
  101. kuavo_humanoid_sdk/msg/kuavo_msgs/srv/_handForceLevel.py +330 -0
  102. kuavo_humanoid_sdk/msg/kuavo_msgs/srv/_jointMoveTo.py +302 -0
  103. kuavo_humanoid_sdk/msg/kuavo_msgs/srv/_planArmTrajectoryBezierCurve.py +421 -0
  104. kuavo_humanoid_sdk/msg/kuavo_msgs/srv/_planArmTrajectoryCubicSpline.py +490 -0
  105. kuavo_humanoid_sdk/msg/kuavo_msgs/srv/_playmusic.py +268 -0
  106. kuavo_humanoid_sdk/msg/kuavo_msgs/srv/_setHwIntialState.py +304 -0
  107. kuavo_humanoid_sdk/msg/kuavo_msgs/srv/_setMmCtrlFrame.py +273 -0
  108. kuavo_humanoid_sdk/msg/kuavo_msgs/srv/_setMotorEncoderRoundService.py +283 -0
  109. kuavo_humanoid_sdk/msg/kuavo_msgs/srv/_setTagId.py +275 -0
  110. kuavo_humanoid_sdk/msg/kuavo_msgs/srv/_singleStepControl.py +444 -0
  111. kuavo_humanoid_sdk/msg/kuavo_msgs/srv/_twoArmHandPoseCmdSrv.py +662 -0
  112. kuavo_humanoid_sdk/msg/motion_capture_ik/__init__.py +7 -0
  113. kuavo_humanoid_sdk/msg/ocs2_msgs/__init__.py +7 -0
  114. kuavo_humanoid_sdk/msg/ocs2_msgs/msg/__init__.py +12 -0
  115. kuavo_humanoid_sdk/msg/ocs2_msgs/msg/_constraint.py +142 -0
  116. kuavo_humanoid_sdk/msg/ocs2_msgs/msg/_controller_data.py +121 -0
  117. kuavo_humanoid_sdk/msg/ocs2_msgs/msg/_lagrangian_metrics.py +148 -0
  118. kuavo_humanoid_sdk/msg/ocs2_msgs/msg/_mode_schedule.py +150 -0
  119. kuavo_humanoid_sdk/msg/ocs2_msgs/msg/_mpc_flattened_controller.py +666 -0
  120. kuavo_humanoid_sdk/msg/ocs2_msgs/msg/_mpc_input.py +122 -0
  121. kuavo_humanoid_sdk/msg/ocs2_msgs/msg/_mpc_observation.py +209 -0
  122. kuavo_humanoid_sdk/msg/ocs2_msgs/msg/_mpc_performance_indices.py +140 -0
  123. kuavo_humanoid_sdk/msg/ocs2_msgs/msg/_mpc_solver_data.py +886 -0
  124. kuavo_humanoid_sdk/msg/ocs2_msgs/msg/_mpc_state.py +122 -0
  125. kuavo_humanoid_sdk/msg/ocs2_msgs/msg/_mpc_target_trajectories.py +239 -0
  126. kuavo_humanoid_sdk/msg/ocs2_msgs/msg/_multiplier.py +148 -0
  127. kuavo_humanoid_sdk/msg/ocs2_msgs/srv/__init__.py +1 -0
  128. kuavo_humanoid_sdk/msg/ocs2_msgs/srv/_reset.py +376 -0
  129. kuavo_humanoid_sdk-1.1.6b1139.dist-info/METADATA +291 -0
  130. kuavo_humanoid_sdk-1.1.6b1139.dist-info/RECORD +132 -0
  131. kuavo_humanoid_sdk-1.1.6b1139.dist-info/WHEEL +6 -0
  132. kuavo_humanoid_sdk-1.1.6b1139.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1126 @@
1
+ import time
2
+ import math
3
+ from kuavo_humanoid_sdk.kuavo_strategy.kuavo_strategy import KuavoRobotStrategyBase
4
+ from kuavo_humanoid_sdk.interfaces.data_types import KuavoPose
5
+ from kuavo_humanoid_sdk.interfaces.data_types import KuavoManipulationMpcFrame, KuavoManipulationMpcCtrlMode, KuavoManipulationMpcControlFlow
6
+ from kuavo_humanoid_sdk.interfaces.data_types import EndEffectorSide
7
+ from kuavo_humanoid_sdk.interfaces.data_types import AprilTagData, HomogeneousMatrix, PoseQuaternion
8
+ from kuavo_humanoid_sdk import KuavoRobot, KuavoRobotState, KuavoRobotTools, KuavoRobotVision
9
+ from dataclasses import dataclass
10
+ from typing import Tuple
11
+ import numpy as np
12
+ from scipy.spatial.transform import Rotation as R
13
+
14
+
15
+ @dataclass
16
+ class BoxInfo:
17
+ """箱子信息数据类
18
+
19
+ 描述箱子的位置、尺寸和质量信息,用于箱子抓取策略
20
+
21
+ Attributes:
22
+ pose (KuavoPose): 箱子的位姿信息
23
+ size (Tuple[float, float, float]): 箱子的尺寸 (长, 宽, 高) 单位: 米
24
+ mass (float): 箱子的质量 单位: 千克
25
+ """
26
+ pose: KuavoPose
27
+ size: Tuple[float, float, float] = (0.3, 0.2, 0.15) # 默认箱子尺寸
28
+ mass: float = 1.0 # 默认箱子质量(kg)
29
+
30
+ class KuavoGraspBox(KuavoRobotStrategyBase):
31
+ """箱子抓取策略类,继承自基础策略类"""
32
+
33
+ def __init__(self, robot:KuavoRobot, robot_state:KuavoRobotState, robot_tools:KuavoRobotTools, robot_vision:KuavoRobotVision):
34
+ """初始化箱子抓取策略类
35
+
36
+ Args:
37
+ robot: KuavoRobot实例
38
+ robot_state: KuavoRobotState实例
39
+ robot_tools: KuavoRobotTools实例
40
+ robot_vision: KuavoRobotVision实例
41
+ """
42
+ super().__init__(robot, robot_state, robot_tools, robot_vision)
43
+
44
+ # 箱子抓取相关的配置参数
45
+ self.search_timeout = 20.0 # 搜索超时时间(秒)
46
+ self.approach_timeout = 30.0 # 接近超时时间(秒)
47
+ self.grasp_height_offset = 0.2 # 抓取高度偏移量(米)
48
+ self.grasp_horizontal_offset = -0.2 # 手指与箱子表面的偏移量,取反为远离箱子 | 取正为靠近箱子
49
+ # 存放头部寻找AprilTag的目标,初始化为异常ID 9999
50
+ self.head_find_target_current_info_pose = AprilTagData(
51
+ id=[9999], # 异常ID
52
+ size=[0.0], # 默认尺寸为0
53
+ pose=[PoseQuaternion(
54
+ position=(0.0, 0.0, 0.0), # 默认零位置
55
+ orientation=(0.0, 0.0, 0.0, 1.0) # 默认朝向(无旋转)
56
+ )]
57
+ )
58
+ # 新增安全参数
59
+ self.orientation_safety_threshold = math.radians(20) # 20度安全阈值
60
+ # 新增位置安全参数
61
+ self.workspace_radius = 0.92 # 工作空间半径0.92米
62
+
63
+ def head_find_target(self, target_info:AprilTagData, max_search_time=None, search_pattern="rotate_head", **kwargs):
64
+ """使用头部旋转寻找AprilTag目标
65
+
66
+ Args:
67
+ target_info: AprilTag的信息
68
+ max_search_time: 最大搜索时间(秒),如果为None则使用默认值
69
+ search_pattern: 搜索模式,"rotate_head"或"rotate_body"
70
+
71
+ Returns:
72
+ bool: 是否成功找到目标
73
+
74
+ logic:
75
+ 1. 判断目标位置是否在机器人FOV(70度视场角)内
76
+ 2. 如果不在FOV内且search_pattern为"rotate_body",先旋转机器人身体朝向目标位置
77
+ 3. 无论如何都使用头部搜索模式尝试找到目标
78
+ 4. 找到apriltag_data_from_odom之后,马上开始头部追踪
79
+ """
80
+ # 初始目标赋值
81
+ self.head_find_target_current_info_pose = target_info
82
+
83
+ # 设置搜索超时时间
84
+ if max_search_time is None:
85
+ max_search_time = self.search_timeout
86
+
87
+ # 获取需要追踪的目标ID
88
+ target_id = target_info.id[0]
89
+
90
+ if target_id > 9999:
91
+ print(f"target_id: {target_id} 大于 9999, 无效的AprilTag家族ID")
92
+ return False
93
+
94
+ # 判断目标位置是否在FOV内
95
+ if len(target_info.pose) > 0:
96
+ target_position = target_info.pose[0].position
97
+ robot_position = self.state.robot_position()
98
+ robot_orientation = self.state.robot_orientation()
99
+
100
+ # 计算目标相对于机器人的位置向量
101
+ dx = target_position[0] - robot_position[0]
102
+ dy = target_position[1] - robot_position[1]
103
+
104
+ # 计算目标相对于机器人的角度
105
+ target_angle = math.atan2(dy, dx)
106
+
107
+ # 获取机器人当前朝向的yaw角
108
+ robot_yaw = self._extract_yaw_from_quaternion(robot_orientation)
109
+
110
+ # 计算目标与机器人朝向的角度差
111
+ angle_diff = target_angle - robot_yaw
112
+ # 标准化角度到[-pi, pi]
113
+ while angle_diff > math.pi:
114
+ angle_diff -= 2 * math.pi
115
+ while angle_diff < -math.pi:
116
+ angle_diff += 2 * math.pi
117
+
118
+ # 检查是否在FOV内(70度 = 约1.22弧度)
119
+ FOV_HALF_ANGLE = math.radians(35) # 70度/2 = 35度
120
+ is_in_fov = abs(angle_diff) <= FOV_HALF_ANGLE
121
+
122
+ print(f"目标位置: ({target_position[0]:.2f}, {target_position[1]:.2f})")
123
+ print(f"机器人位置: ({robot_position[0]:.2f}, {robot_position[1]:.2f})")
124
+ print(f"目标角度: {math.degrees(target_angle):.2f}度")
125
+ print(f"机器人朝向: {math.degrees(robot_yaw):.2f}度")
126
+ print(f"角度差: {math.degrees(angle_diff):.2f}度")
127
+ print(f"是否在FOV内: {is_in_fov}")
128
+
129
+ # 如果目标不在FOV内且模式允许旋转身体,先旋转机器人身体
130
+ if not is_in_fov and search_pattern == "rotate_body":
131
+ print("目标超出FOV,调整机器人朝向...")
132
+ # 调整机器人朝向
133
+ print(f"开始调整 - 机器人位置: {robot_position}")
134
+ print(f"开始调整 - 目标角度: {math.degrees(target_angle):.2f}度")
135
+ print(f"开始调整 - 目标角度: {target_angle}")
136
+ self.robot.control_command_pose_world(
137
+ robot_position[0], # 保持机器人当前x位置
138
+ robot_position[1], # 保持机器人当前y位置
139
+ 0.0, # 保持当前z高度
140
+ target_angle # 朝向目标位置 转换为弧度
141
+ )
142
+
143
+ # 等待机器人旋转到位,使用闭环控制替代固定时间等待
144
+ self._wait_for_orientation(target_angle, max_wait_time=10.0, angle_threshold=0.1)
145
+
146
+ # 开始搜索计时
147
+ start_time = time.time()
148
+ found_target = False
149
+
150
+ # 执行头部搜索模式,无论search_pattern是什么
151
+ # 定义头部搜索参数(角度制)
152
+ pitch_angles_deg = [12, -12] # 两档pitch角度:抬头和低头,角度制
153
+ yaw_angles_deg = [-30, -15, 0, 15, 30] # 左右扫描的yaw角度,角度制
154
+
155
+ # 在超时前循环搜索
156
+ while time.time() - start_time < max_search_time and not found_target:
157
+ # 遍历两档pitch角度
158
+ for pitch_deg in pitch_angles_deg:
159
+ # 遍历yaw角度进行左右扫描
160
+ for yaw_deg in yaw_angles_deg:
161
+ # 将角度转换为弧度
162
+ yaw_rad = yaw_deg * 0.0174533 # 度转弧度,math.pi/180
163
+ pitch_rad = pitch_deg * 0.0174533 # 度转弧度
164
+
165
+ # 控制头部旋转(使用弧度)
166
+ self.robot.control_head(yaw=yaw_rad, pitch=pitch_rad)
167
+ # 等待头部移动到位
168
+ time.sleep(0.5)
169
+
170
+ # 检查是否找到目标
171
+ target_data = self.vision.get_data_by_id_from_odom(target_id)
172
+ print(f"target_data: {target_data}")
173
+
174
+ if (target_data is not None and isinstance(target_data, dict) and
175
+ 'poses' in target_data and len(target_data['poses']) > 0):
176
+ print(f"Target AprilTag {target_id} found!")
177
+ found_target = True
178
+ # 开始头部追踪
179
+ print("---- 开始头部追踪 ---- ")
180
+ self.robot.enable_head_tracking(target_id) # self.robot.disable_head_tracking()
181
+ break
182
+
183
+ if found_target:
184
+ break
185
+
186
+ return found_target
187
+
188
+ def _is_orientation_aligned(self, orientation1, orientation2, threshold=0.3):
189
+ """检查两个朝向是否大致一致
190
+
191
+ Args:
192
+ orientation1: 第一个朝向的四元数
193
+ orientation2: 第二个朝向的四元数
194
+ threshold: 判断为一致的阈值
195
+
196
+ Returns:
197
+ bool: 朝向是否一致
198
+ """
199
+ # 这里简化实现,实际应用需要进行四元数计算
200
+ # 提取两个朝向的yaw角并比较差异
201
+ yaw1 = self._extract_yaw_from_quaternion(orientation1)
202
+ yaw2 = self._extract_yaw_from_quaternion(orientation2)
203
+
204
+ # 计算角度差异
205
+ diff = abs(yaw1 - yaw2)
206
+ while diff > math.pi:
207
+ diff -= 2 * math.pi
208
+
209
+ return abs(diff) < threshold
210
+
211
+ def _extract_yaw_from_quaternion(self, quaternion):
212
+ """从四元数中提取yaw角
213
+
214
+ Args:
215
+ quaternion: 四元数 (x, y, z, w)
216
+
217
+ Returns:
218
+ float: yaw角(弧度)
219
+ """
220
+ if not quaternion or len(quaternion) != 4:
221
+ print("无法获取有效的四元数")
222
+ return 0.0
223
+
224
+ # 计算yaw角 (围绕z轴的旋转)
225
+ # 四元数到欧拉角的简化计算,仅提取yaw
226
+ x, y, z, w = quaternion
227
+ yaw = math.atan2(2.0 * (w * z + x * y), 1.0 - 2.0 * (y * y + z * z))
228
+ return yaw
229
+
230
+ def _track_target_with_head(self, target_data):
231
+ """使用头部追踪目标
232
+
233
+ Args:
234
+ target_data: 目标数据,包含位置信息
235
+ """
236
+ # 从目标数据中提取相对位置
237
+ position = target_data["position"]
238
+
239
+ # 计算目标相对于机器人的方向
240
+ dx = position[0]
241
+ dy = position[1]
242
+ dz = position[2]
243
+
244
+ # 计算yaw和pitch角度来指向目标
245
+ # 简单的反正切计算(结果为弧度)
246
+ yaw_rad = math.atan2(dy, dx)
247
+ distance = math.sqrt(dx*dx + dy*dy)
248
+ pitch_rad = math.atan2(dz, distance)
249
+
250
+ # 限制角度范围(弧度)
251
+ yaw_rad = min(math.radians(80), max(math.radians(-80), yaw_rad)) # 限制在±80度
252
+ pitch_rad = min(math.radians(25), max(math.radians(-25), pitch_rad)) # 限制在±25度
253
+
254
+ # 控制头部指向目标(输入为弧度)
255
+ self.robot.control_head(yaw=yaw_rad, pitch=pitch_rad)
256
+
257
+ def walk_approach_target(self, target_info:AprilTagData, target_distance=0.5, approach_speed=0.15, **kwargs):
258
+ """走路接近AprilTag目标
259
+
260
+ Args:
261
+ target_info: AprilTag的信息
262
+ target_distance: 与目标的期望距离(米)
263
+ approach_speed: 接近速度(米/秒)
264
+
265
+ Returns:
266
+ bool: 是否成功接近目标
267
+ """
268
+ approach_success = False
269
+ start_time = time.time()
270
+ tag_id = target_info.id[0]
271
+ target_data = self.vision.get_data_by_id_from_odom(tag_id)
272
+ if target_data is None:
273
+ print(f"未找到目标ID: {tag_id} 的目标数据!")
274
+ return False
275
+ target_pose = target_data["poses"][0]
276
+ print(f"target_pose in _approach_target: {target_pose}")
277
+ while not approach_success:
278
+ approach_success = self._approach_target(target_pose, target_distance, approach_speed, **kwargs)
279
+ time.sleep(1)
280
+ time_cost = time.time() - start_time
281
+ print(f"walking approach target..., time_cost: {time_cost:.2f}秒.")
282
+ return approach_success
283
+
284
+ def _approach_target(self, target_pose, target_distance=0.5, approach_speed=0.15, **kwargs):
285
+ """根据目标信息和目标距离计算目标位姿
286
+
287
+ Args:
288
+ target_pose: 目标位姿信息
289
+ target_distance: 与目标的期望距离(米)
290
+ approach_speed: 接近速度(米/秒)
291
+
292
+ Returns:
293
+ bool: 是否成功接近目标
294
+ """
295
+ p_wa = np.array([target_pose.position.x, target_pose.position.y, target_pose.position.z])
296
+ quat_wa = np.array([target_pose.orientation.x, target_pose.orientation.y, target_pose.orientation.z, target_pose.orientation.w]) # x,y,z,w
297
+ R_wa = R.from_quat(quat_wa).as_matrix()
298
+ def get_target_pose_by_distance(p_wa, R_wa, target_distance=0.5):
299
+ """根据目标信息和目标距离计算目标位姿"""
300
+ p_at = np.array([0, 0, target_distance], np.float32)
301
+ p_at_w = R_wa @ p_at
302
+ p_wt = p_wa + p_at_w
303
+ yaw = math.atan2(p_at_w[1], p_at_w[0])
304
+ yaw += math.pi
305
+ while yaw > math.pi:
306
+ yaw -= 2 * math.pi
307
+ while yaw < -math.pi:
308
+ yaw += 2 * math.pi
309
+ return p_wt, yaw
310
+
311
+ p_wt, angle = get_target_pose_by_distance(p_wa, R_wa, target_distance)
312
+ self.robot.control_command_pose_world(p_wt[0], p_wt[1], 0, angle)
313
+
314
+ yaw_reached = self._yaw_check(angle)
315
+ pos_reached = self._pos_check(p_wt)
316
+ stance_check = (self.state == 'stance')
317
+ print(f"yaw_reached: {yaw_reached}, pos_reached: {pos_reached}, stance_check: {stance_check}")
318
+ return yaw_reached and pos_reached # and stance_check
319
+
320
+ def _check_target_reachable(self, target_info:BoxInfo) -> bool:
321
+ """检查目标位置是否在机器人手臂可达区域内
322
+
323
+ Args:
324
+ target_info: 目标信息,包含位置、尺寸等
325
+
326
+ Returns:
327
+ bool: 目标是否可达
328
+
329
+ Note:
330
+ 此函数为预留接口,待实现以下功能:
331
+ 1. 获取机器人当前位姿
332
+ 2. 获取机器人手臂工作空间范围
333
+ 3. 检查目标位置是否在工作空间内
334
+ """
335
+ # TODO: 实现可达性检查逻辑
336
+ # 1. 获取机器人当前位姿
337
+ # robot_pose = self.state.robot_pose()
338
+
339
+ # 2. 获取机器人手臂工作空间范围
340
+ # workspace_range = self.robot.get_arm_workspace()
341
+
342
+ # 3. 检查目标位置是否在工作空间内
343
+ # target_position = target_info.pose.position
344
+ # is_in_workspace = check_position_in_workspace(target_position, workspace_range)
345
+
346
+ # 临时返回True,等待接口实现后修改
347
+ return True
348
+
349
+ # 添加四元数乘法函数
350
+ @staticmethod
351
+ def _quaternion_multiply(q1, q2):
352
+ """
353
+ 四元数乘法,用于组合旋转
354
+ q1, q2: 两个四元数 [x, y, z, w]
355
+ """
356
+ x1, y1, z1, w1 = q1
357
+ x2, y2, z2, w2 = q2
358
+
359
+ w = w1 * w2 - x1 * x2 - y1 * y2 - z1 * z2
360
+ x = w1 * x2 + x1 * w2 + y1 * z2 - z1 * y2
361
+ y = w1 * y2 + y1 * w2 + z1 * x2 - x1 * z2
362
+ z = w1 * z2 + z1 * w2 + x1 * y2 - y1 * x2
363
+
364
+ return [x, y, z, w]
365
+
366
+ def _quaternion_rotate(self, q, v):
367
+ """
368
+ 使用四元数旋转向量
369
+ q: 四元数 [x, y, z, w]
370
+ v: 三维向量 [x, y, z]
371
+ """
372
+ q = np.array(q)
373
+ v = np.array(v)
374
+ q_conj = np.array([-q[0], -q[1], -q[2], q[3]])
375
+ v_quat = np.array([v[0], v[1], v[2], 0.0])
376
+ rotated = KuavoGraspBox._quaternion_multiply(KuavoGraspBox._quaternion_multiply(q, v_quat), q_conj)
377
+ return rotated[:3]
378
+
379
+ # 坐标转换函数
380
+ def _transform_to_odom(self, pose, transform):
381
+ """将姿态从base_link转换到odom坐标系"""
382
+ # 位置转换(显式转换为numpy数组)
383
+ pos_base = np.array(pose.position)
384
+ transform_pos = np.array(transform.position)
385
+
386
+ # 使用显式类型转换确保运算正确
387
+ rotated_pos = self._quaternion_rotate(
388
+ np.array(transform.orientation), # 确保四元数是numpy数组
389
+ pos_base
390
+ )
391
+ pos_odom = transform_pos + rotated_pos
392
+
393
+ # 姿态转换(显式转换为numpy数组)
394
+ transform_quat = np.array(transform.orientation)
395
+ pose_quat = np.array(pose.orientation)
396
+ rot_odom = KuavoGraspBox._quaternion_multiply(transform_quat, pose_quat)
397
+
398
+ # 转换为Python原生类型
399
+ return KuavoPose(
400
+ position=tuple(pos_odom.tolist()),
401
+ orientation=rot_odom # rot_odom 已经是列表,不需要转换
402
+ )
403
+
404
+ def _transform_to_base_link(self, pose):
405
+ """将姿态从odom坐标系转换到base_link坐标系
406
+
407
+ Args:
408
+ pose: KuavoPose类型,表示odom坐标系下的位姿
409
+
410
+ Returns:
411
+ KuavoPose: base_link坐标系下的位姿
412
+ """
413
+ try:
414
+ # 获取odom到base_link的变换
415
+ odom_to_base = self.tools.get_tf_transform("base_link", "odom")
416
+
417
+ # 位置转换
418
+ pos_odom = np.array(pose.position)
419
+ odom_pos = np.array(odom_to_base.position)
420
+
421
+ # 使用四元数旋转
422
+ rotated_pos = self._quaternion_rotate(
423
+ np.array(odom_to_base.orientation),
424
+ pos_odom
425
+ )
426
+ pos_base = rotated_pos + odom_pos
427
+
428
+ # 姿态转换
429
+ odom_quat = np.array(odom_to_base.orientation)
430
+ pose_quat = np.array(pose.orientation)
431
+ # 注意:这里需要先旋转odom_quat的共轭,再与pose_quat相乘
432
+ odom_quat_conj = np.array([-odom_quat[0], -odom_quat[1], -odom_quat[2], odom_quat[3]])
433
+ rot_base = KuavoGraspBox._quaternion_multiply(odom_quat_conj, pose_quat)
434
+
435
+ # 返回转换后的位姿
436
+ return KuavoPose(
437
+ position=tuple(pos_base.tolist()),
438
+ orientation=rot_base
439
+ )
440
+ except Exception as e:
441
+ print(f"坐标转换出错: {str(e)}")
442
+ return None
443
+
444
+ @staticmethod
445
+ def _interpolate_poses(start_pose, end_pose, num_points=20):
446
+ """
447
+ 在两个笛卡尔空间姿态之间进行三次样条插值
448
+
449
+ Args:
450
+ start_pose: 起始KuavoPose
451
+ end_pose: 终点KuavoPose
452
+ num_points: 插值点数量
453
+
454
+ Returns:
455
+ 插值后的KuavoPose列表
456
+ """
457
+ # 提取位置
458
+ start_pos = np.array(start_pose.position)
459
+ end_pos = np.array(end_pose.position)
460
+
461
+ # 提取四元数
462
+ start_quat = np.array(start_pose.orientation)
463
+ end_quat = np.array(end_pose.orientation)
464
+
465
+ # 确保四元数方向一致(避免绕远路)
466
+ if np.dot(start_quat, end_quat) < 0:
467
+ end_quat = -end_quat
468
+
469
+ # 生成参数t
470
+ t = np.linspace(0, 1, num_points)
471
+
472
+ # 位置插值 - 使用三次样条
473
+ # 为了进行三次样条插值,我们需要在t, x, y, z上分别拟合样条
474
+
475
+ # 四元数插值 - 球面线性插值 (SLERP)
476
+ interp_poses = []
477
+ for i in range(num_points):
478
+ # 位置插值
479
+ pos = start_pos * (1 - t[i]) + end_pos * t[i]
480
+ pos = (pos[0], pos[1], pos[2])
481
+
482
+ # 四元数球面插值
483
+ # 计算四元数之间的夹角
484
+ cos_half_theta = np.dot(start_quat, end_quat)
485
+ cos_half_theta = np.clip(cos_half_theta, -1.0, 1.0) # 确保在有效范围内
486
+
487
+ if abs(cos_half_theta) >= 1.0:
488
+ # 如果四元数几乎相同,直接使用起始四元数
489
+ quat = start_quat
490
+ else:
491
+ half_theta = np.arccos(cos_half_theta)
492
+ sin_half_theta = np.sqrt(1.0 - cos_half_theta * cos_half_theta)
493
+
494
+ # 如果夹角足够大,使用SLERP插值
495
+ if abs(sin_half_theta) < 0.001:
496
+ # 夹角太小,使用线性插值
497
+ quat = start_quat * (1 - t[i]) + end_quat * t[i]
498
+ quat = quat / np.linalg.norm(quat) # 归一化
499
+ else:
500
+ # SLERP公式
501
+ ratio_a = np.sin((1 - t[i]) * half_theta) / sin_half_theta
502
+ ratio_b = np.sin(t[i] * half_theta) / sin_half_theta
503
+ quat = start_quat * ratio_a + end_quat * ratio_b
504
+ quat = quat / np.linalg.norm(quat) # 归一化
505
+
506
+ # 创建新的KuavoPose
507
+ interp_poses.append(KuavoPose(
508
+ position=pos,
509
+ orientation=quat.tolist()
510
+ ))
511
+
512
+ return interp_poses
513
+
514
+ def _execute_trajectory(self, left_poses, right_poses, total_time=2.0):
515
+ """
516
+ 执行左右手轨迹
517
+
518
+ Args:
519
+ grasp_strategy: 抓取策略对象
520
+ left_poses: 左手KuavoPose列表
521
+ right_poses: 右手KuavoPose列表
522
+ total_time: 总执行时间(秒)
523
+ """
524
+
525
+ num_points = min(len(left_poses), len(right_poses))
526
+ time_per_point = total_time / (num_points - 1) if num_points > 1 else total_time
527
+
528
+ for i in range(num_points):
529
+ self.robot.control_robot_end_effector_pose(
530
+ left_pose=left_poses[i],
531
+ right_pose=right_poses[i],
532
+ frame=KuavoManipulationMpcFrame.WorldFrame,
533
+ )
534
+ if i < num_points - 1: # 最后一个点不需要延时
535
+ time.sleep(time_per_point)
536
+
537
+ def _get_target_pose(self, target_info:BoxInfo, traj_type="grasp", **kwargs):
538
+ """获取起始位置和目标位置
539
+
540
+ Args:
541
+ target_info: 目标信息,包含位置、尺寸等
542
+ traj_type: 轨迹类型:
543
+ - "grasp": 抓取轨迹
544
+ - "lift": 抬起轨迹
545
+ - "place": 放置轨迹
546
+
547
+ Returns:
548
+ tuple: (left_pose_init, right_pose_init, left_pose_target, right_pose_target)
549
+ """
550
+ # 计算抓取姿态
551
+ box_position = list(target_info.pose.position)
552
+ box_orientation = list(target_info.pose.orientation)
553
+ print(f"原始世界坐标系下的位置: {box_position}")
554
+ print(f"原始世界坐标系下的姿态: {box_orientation}")
555
+
556
+ box_size = target_info.size # (length, width, height)
557
+
558
+ if box_position is None:
559
+ return None, None, None, None
560
+ else:
561
+ # 将四元数转换为yaw角
562
+ qx, qy, qz, qw = box_orientation
563
+ box_yaw = np.arctan2(2*(qw*qz + qx*qy), qw**2 + qx**2 - qy**2 - qz**2)
564
+
565
+ # 计算箱子侧面的位置(基于box_yaw旋转)
566
+ half_width = box_size[1] / 2.0
567
+ grasp_height = box_position[2] # 通常在箱子高度的中间位置抓取
568
+
569
+ right_hand_position = ( # left_hand_position
570
+ box_position[0] + half_width * np.sin(box_yaw),
571
+ box_position[1] - half_width * np.cos(box_yaw),
572
+ grasp_height
573
+ )
574
+ left_hand_position = ( # right_hand_position
575
+ box_position[0] - half_width * np.sin(box_yaw),
576
+ box_position[1] + half_width * np.cos(box_yaw),
577
+ grasp_height
578
+ )
579
+ # 基础抓取姿态(只考虑roll和pitch)
580
+ base_left_orientation = [0.06163, -0.70442, -0.06163, 0.70442] # roll 10度 Pitch -90度
581
+ base_right_orientation = [-0.06163, -0.70442, 0.06163, 0.70442] # roll -10度 Pitch -90度
582
+
583
+ # 创建yaw旋转的四元数
584
+ yaw_quat = [0, 0, np.sin(box_yaw/2), np.cos(box_yaw/2)]
585
+
586
+ # 合并四元数:结合基础姿态和yaw旋转
587
+ left_grasp_orientation = KuavoGraspBox._quaternion_multiply(yaw_quat, base_left_orientation)
588
+ right_grasp_orientation = KuavoGraspBox._quaternion_multiply(yaw_quat, base_right_orientation)
589
+
590
+ # 计算基础姿态
591
+ # 1. 贴合箱子侧面左右手的末端位姿
592
+ left_hand_pose = KuavoPose(
593
+ position=left_hand_position,
594
+ orientation=left_grasp_orientation
595
+ )
596
+ right_hand_pose = KuavoPose(
597
+ position=right_hand_position,
598
+ orientation=right_grasp_orientation
599
+ )
600
+
601
+ # 2. 预抓取姿态
602
+ left_pre_grasp = KuavoPose(
603
+ position=(
604
+ left_hand_pose.position[0] + self.grasp_horizontal_offset * np.sin(box_yaw),
605
+ left_hand_pose.position[1] - self.grasp_horizontal_offset * np.cos(box_yaw),
606
+ left_hand_pose.position[2]
607
+ ),
608
+ orientation=left_hand_pose.orientation
609
+ )
610
+
611
+ right_pre_grasp = KuavoPose(
612
+ position=(
613
+ right_hand_pose.position[0] - self.grasp_horizontal_offset * np.sin(box_yaw),
614
+ right_hand_pose.position[1] + self.grasp_horizontal_offset * np.cos(box_yaw),
615
+ right_hand_pose.position[2]
616
+ ),
617
+ orientation=right_hand_pose.orientation
618
+ )
619
+
620
+ # 3. 抓取姿态(不只是贴合箱子,抓紧)
621
+ left_grasp = KuavoPose(
622
+ position=(
623
+ left_hand_pose.position[0] + 0.05 * np.sin(box_yaw),
624
+ left_hand_pose.position[1] - 0.05 * np.cos(box_yaw),
625
+ left_hand_pose.position[2]
626
+ ),
627
+ orientation=left_hand_pose.orientation
628
+ )
629
+
630
+ right_grasp = KuavoPose(
631
+ position=(
632
+ right_hand_pose.position[0] - 0.05 * np.sin(box_yaw),
633
+ right_hand_pose.position[1] + 0.05 * np.cos(box_yaw),
634
+ right_hand_pose.position[2]
635
+ ),
636
+ orientation=right_hand_pose.orientation
637
+ )
638
+
639
+ # 4. 抬起姿态(抓取后向上)
640
+ left_lift = KuavoPose(
641
+ position=(
642
+ left_grasp.position[0],
643
+ left_grasp.position[1],
644
+ left_grasp.position[2] + self.grasp_height_offset
645
+ ),
646
+ orientation=left_grasp.orientation
647
+ )
648
+
649
+ right_lift = KuavoPose(
650
+ position=(
651
+ right_grasp.position[0],
652
+ right_grasp.position[1],
653
+ right_grasp.position[2] + self.grasp_height_offset
654
+ ),
655
+ orientation=right_grasp.orientation
656
+ )
657
+
658
+ # 5. 收臂姿态
659
+ # 定义base_link坐标系下的目标姿态
660
+ l_arm_base_target_pose = KuavoPose(
661
+ position=(0.499, 0.121, 0.370),
662
+ orientation=[-0.107, -0.758, 0.063, 0.641]
663
+ )
664
+ r_arm_base_target_pose = KuavoPose(
665
+ position=(0.499, -0.121, 0.370),
666
+ orientation=[-0.026, -0.765, 0.049, 0.642]
667
+ )
668
+
669
+ # 获取base_link到odom的坐标变换
670
+ base_to_odom = self.tools.get_tf_transform("odom", "base_link")
671
+
672
+ # 添加调试信息
673
+ print(f"base_to_odom position type: {type(base_to_odom.position)}")
674
+ print(f"base_to_odom orientation type: {type(base_to_odom.orientation)}")
675
+
676
+ # 确保返回的是可迭代对象
677
+ if not isinstance(base_to_odom.position, (list, tuple, np.ndarray)):
678
+ raise ValueError("TF变换位置信息格式错误")
679
+ if not isinstance(base_to_odom.orientation, (list, tuple, np.ndarray)):
680
+ raise ValueError("TF变换姿态信息格式错误")
681
+
682
+ # 转换目标姿态到odom坐标系
683
+ left_pull = self._transform_to_odom(l_arm_base_target_pose, base_to_odom)
684
+ right_pull = self._transform_to_odom(r_arm_base_target_pose, base_to_odom)
685
+
686
+ # 6. 放置姿态(放下箱子)
687
+ l_arm_put_away_base_pose = KuavoPose(
688
+ position=(0.499, 0.351, 0.160),
689
+ orientation=[-0.107, -0.758, 0.063, 0.641]
690
+ )
691
+ r_arm_put_away_base_pose = KuavoPose(
692
+ position=(0.499, -0.351, 0.160),
693
+ orientation=[-0.026, -0.765, 0.049, 0.642]
694
+ )
695
+ base_to_odom = self.tools.get_tf_transform("odom", "base_link")
696
+ # 添加调试信息
697
+ print(f"base_to_odom position type: {type(base_to_odom.position)}")
698
+ print(f"base_to_odom orientation type: {type(base_to_odom.orientation)}")
699
+ # 转换目标姿态到odom坐标系
700
+ left_place = self._transform_to_odom(l_arm_put_away_base_pose, base_to_odom)
701
+ right_place = self._transform_to_odom(r_arm_put_away_base_pose, base_to_odom)
702
+
703
+ # 7. 松开手臂
704
+ left_release = KuavoPose(
705
+ position=(
706
+ left_place.position[0],
707
+ left_place.position[1] - self.grasp_horizontal_offset,
708
+ left_place.position[2]
709
+ ),
710
+ orientation=left_place.orientation
711
+ )
712
+
713
+ right_release = KuavoPose(
714
+ position=(
715
+ right_place.position[0],
716
+ right_place.position[1] + self.grasp_horizontal_offset,
717
+ right_place.position[2]
718
+ ),
719
+ orientation=right_place.orientation
720
+ )
721
+
722
+ # 根据轨迹类型返回对应的姿态
723
+ if traj_type == "grasp":
724
+ return left_pre_grasp, right_pre_grasp, left_grasp, right_grasp
725
+ elif traj_type == "lift":
726
+ return left_grasp, right_grasp, left_lift, right_lift
727
+ elif traj_type == "pull":
728
+ return left_lift, right_lift, left_pull, right_pull
729
+ elif traj_type == "place":
730
+ return left_pull, right_pull, left_place, right_place
731
+ elif traj_type == "release":
732
+ return left_place, right_place, left_release, right_release
733
+ else:
734
+ print(f"未知的轨迹类型: {traj_type}")
735
+ return None, None, None, None
736
+
737
+ def _check_orientation_safety(self, target_orientation, threshold=None):
738
+ """检查目标朝向与机器人当前朝向的安全性"""
739
+ if threshold is None:
740
+ threshold = self.orientation_safety_threshold
741
+
742
+ # 获取当前机器人朝向
743
+ current_orientation = self.state.robot_orientation()
744
+ current_yaw = self._extract_yaw_from_quaternion(current_orientation)
745
+
746
+ # 提取目标朝向的yaw角
747
+ target_yaw = self._extract_yaw_from_quaternion(target_orientation)
748
+
749
+ # 计算角度差
750
+ angle_diff = abs(target_yaw - current_yaw)
751
+ angle_diff = min(2*math.pi - angle_diff, angle_diff) # 取最小角度差
752
+
753
+ print(f"[安全检查] 当前朝向: {math.degrees(current_yaw):.1f}°, 目标朝向: {math.degrees(target_yaw):.1f}°, 差异: {math.degrees(angle_diff):.1f}°")
754
+
755
+ if angle_diff > threshold:
756
+ print(f"❌ 方向偏差超过安全阈值({math.degrees(threshold):.1f}°),终止操作!")
757
+ return False
758
+ return True
759
+
760
+ def _check_position_safety(self, target_info: BoxInfo) -> bool:
761
+ """检查目标位置是否在工作空间内"""
762
+ try:
763
+ # 将目标位置转换到base_link坐标系
764
+ target_pose_base = self._transform_to_base_link(target_info.pose)
765
+ # 获取左右臂关节位置(base_link坐标系)
766
+ l_joint_pos = self.tools.get_link_position("zarm_l1_link")
767
+ r_joint_pos = self.tools.get_link_position("zarm_r1_link")
768
+ # 计算目标到左右臂关节的水平距离
769
+ target_pos = np.array(target_pose_base.position)
770
+ l_distance = np.linalg.norm(target_pos[:2] - l_joint_pos[:2])
771
+ r_distance = np.linalg.norm(target_pos[:2] - r_joint_pos[:2])
772
+
773
+ print(f"[位置安全检查] 左臂距离: {l_distance:.2f}m, 右臂距离: {r_distance:.2f}m, 安全阈值: {self.workspace_radius:.2f}m")
774
+
775
+ # 检查是否在安全范围内
776
+ if l_distance > self.workspace_radius or r_distance > self.workspace_radius:
777
+ print(f"❌ 目标位置超出工作空间范围({self.workspace_radius}m)")
778
+ return False
779
+ return True
780
+ except Exception as e:
781
+ print(f"位置安全检查出错: {str(e)}")
782
+ return False
783
+
784
+ def _check_height_safety(self, target_info: BoxInfo) -> bool:
785
+ """检查目标位置的高度是否在机器人工作范围内
786
+
787
+ Args:
788
+ target_info: 目标信息,包含位置、尺寸等
789
+
790
+ Returns:
791
+ bool: 高度是否在安全范围内
792
+ """
793
+ target_height = target_info.pose.position[2]
794
+ min_height = 0.5 # 最小工作高度
795
+ max_height = 1.8 # 最大工作高度
796
+
797
+ print(f"[高度安全检查] 目标高度: {target_height:.2f}m, 工作范围: {min_height:.2f}m - {max_height:.2f}m")
798
+
799
+ if target_height < min_height or target_height > max_height:
800
+ print(f"❌ 目标高度 {target_height:.2f}m 超出工作范围({min_height:.2f}m - {max_height:.2f}m),终止操作!")
801
+ return False
802
+ return True
803
+
804
+ def arm_move_to_target(self, target_info:BoxInfo, arm_mode="manipulation_mpc", **kwargs):
805
+ """添加安全保护检查"""
806
+ # 统一的安全检查
807
+ if not self._check_orientation_safety(target_info.pose.orientation):
808
+ return False
809
+ if not self._check_position_safety(target_info):
810
+ return False
811
+ if not self._check_height_safety(target_info):
812
+ return False
813
+
814
+ # 原有代码保持不变
815
+ if arm_mode == "manipulation_mpc":
816
+ self.robot.set_manipulation_mpc_mode(KuavoManipulationMpcCtrlMode.BaseArm)
817
+ else:
818
+ self.robot.set_fixed_arm_mode()
819
+
820
+ # 获取预抓取轨迹
821
+ left_pose_init, right_pose_init, left_pose_target, right_pose_target = self._get_target_pose(target_info, traj_type="grasp")
822
+ if left_pose_init is None:
823
+ return False
824
+
825
+ # 控制手臂移动到预抓取位置
826
+ if not self.robot.control_robot_end_effector_pose(
827
+ left_pose_init,
828
+ right_pose_init,
829
+ KuavoManipulationMpcFrame.WorldFrame
830
+ ):
831
+ return False
832
+
833
+ print("执行预抓取姿态到抓取姿态的轨迹...")
834
+ left_traj_grasp = KuavoGraspBox._interpolate_poses(left_pose_init, left_pose_target)
835
+ right_traj_grasp = KuavoGraspBox._interpolate_poses(right_pose_init, right_pose_target)
836
+ self._execute_trajectory(left_traj_grasp, right_traj_grasp)
837
+
838
+ time.sleep(2)
839
+ self.robot.set_manipulation_mpc_mode(KuavoManipulationMpcCtrlMode.NoControl)
840
+ self.robot.set_manipulation_mpc_control_flow(KuavoManipulationMpcControlFlow.ThroughFullBodyMpc)
841
+
842
+ return True
843
+
844
+ def _check_box_lifting_status(self, target_info:BoxInfo) -> bool:
845
+ """检查箱子是否成功抬起
846
+
847
+ Args:
848
+ target_info: 目标信息,包含位置、尺寸等
849
+
850
+ Returns:
851
+ bool: 是否成功抬起箱子
852
+
853
+ Note:
854
+ 此函数为预留接口,待实现以下功能:
855
+ 1. 获取手部力反馈数据
856
+ 2. 根据箱子重量和力反馈判断是否成功抬起
857
+ 3. 检查力反馈是否稳定
858
+ """
859
+ # TODO: 实现力反馈检测逻辑
860
+ # 1. 获取手部力反馈数据
861
+ # left_force = self.state.get_end_effector_force(EndEffectorSide.Left)
862
+ # right_force = self.state.get_end_effector_force(EndEffectorSide.Right)
863
+
864
+ # 2. 根据箱子重量和力反馈判断
865
+ # expected_force = target_info.mass * 9.8
866
+ # actual_force = calculate_total_force(left_force, right_force)
867
+
868
+ # 3. 检查力反馈稳定性
869
+ # force_stable = check_force_stability()
870
+
871
+ # 临时返回True,等待接口实现后修改
872
+ return True
873
+
874
+ def arm_transport_target_up(self, target_info:BoxInfo, arm_mode="manipulation_mpc"):
875
+ """添加安全检查"""
876
+ # 统一的安全检查
877
+ if not self._check_orientation_safety(target_info.pose.orientation):
878
+ return False
879
+ if not self._check_position_safety(target_info): # 添加位置检查
880
+ return False
881
+ if not self._check_height_safety(target_info):
882
+ return False
883
+
884
+ # 原有代码保持不变
885
+ if arm_mode == "manipulation_mpc":
886
+ self.robot.set_manipulation_mpc_mode(KuavoManipulationMpcCtrlMode.BaseArm)
887
+ else:
888
+ self.robot.set_fixed_arm_mode()
889
+
890
+ # 获取抬起轨迹
891
+ left_pose_init, right_pose_init, left_pose_target, right_pose_target = self._get_target_pose(target_info, traj_type="lift")
892
+ if left_pose_init is None:
893
+ return False
894
+
895
+ # 执行抬起轨迹
896
+ left_traj_lift = KuavoGraspBox._interpolate_poses(left_pose_init, left_pose_target)
897
+ right_traj_lift = KuavoGraspBox._interpolate_poses(right_pose_init, right_pose_target)
898
+ self._execute_trajectory(left_traj_lift, right_traj_lift)
899
+
900
+ time.sleep(1)
901
+
902
+ left_pose_init, right_pose_init, left_pose_target, right_pose_target = self._get_target_pose(target_info, traj_type="pull")
903
+ if left_pose_init is None:
904
+ return False
905
+
906
+ # 执行收臂轨迹
907
+ left_traj_pull = KuavoGraspBox._interpolate_poses(left_pose_init, left_pose_target) # left_pose_init left_pose_target
908
+ right_traj_pull = KuavoGraspBox._interpolate_poses(right_pose_init, right_pose_target) # right_pose_init right_pose_target
909
+ self._execute_trajectory(left_traj_pull, right_traj_pull)
910
+
911
+ if not self._check_box_lifting_status(target_info):
912
+ return False
913
+
914
+ time.sleep(2)
915
+ self.robot.set_manipulation_mpc_mode(KuavoManipulationMpcCtrlMode.NoControl)
916
+ self.robot.set_manipulation_mpc_control_flow(KuavoManipulationMpcControlFlow.ThroughFullBodyMpc)
917
+
918
+ return True
919
+
920
+ def _arrive_pose(self, target_position: list, target_yaw: float, timeout: float = 10.0) -> bool:
921
+ """控制机器人移动到指定位姿并等待到达
922
+
923
+ Args:
924
+ target_position: 目标位置 [x, y, z]
925
+ target_yaw: 目标朝向角度(弧度)
926
+ timeout: 等待超时时间(秒)
927
+
928
+ Returns:
929
+ bool: 是否成功到达目标位姿
930
+ """
931
+ # 控制机器人移动到目标位姿
932
+ self.robot.control_command_pose_world(
933
+ target_position[0], # x
934
+ target_position[1], # y
935
+ 0, # z (保持当前高度)
936
+ target_yaw # 目标朝向
937
+ )
938
+
939
+ # 等待机器人到达目标位姿
940
+ start_time = time.time()
941
+ rate_hz = 10 # 检查频率
942
+ wait_interval = 1.0 / rate_hz
943
+
944
+ while time.time() - start_time < timeout:
945
+ # 检查位置和朝向是否到位
946
+ pos_reached = self._pos_check(target_position)
947
+ yaw_reached = self._yaw_check(target_yaw)
948
+
949
+ if pos_reached and yaw_reached:
950
+ print("机器人已到达目标位姿!")
951
+ return True
952
+
953
+ # 短暂等待再次检查
954
+ time.sleep(wait_interval)
955
+
956
+ # 超时
957
+ print(f"等待机器人到达目标位姿超时!")
958
+ return False
959
+
960
+ def walk_to_pose(self, target_info: BoxInfo, target_distance=0.5, approach_speed=0.15, **kwargs):
961
+ """让机器人走到指定的位姿
962
+
963
+ Args:
964
+ target_pose: 目标位姿
965
+ target_distance: 与目标的期望距离(米)
966
+ approach_speed: 接近速度(米/秒)
967
+
968
+ Returns:
969
+ bool: 是否成功到达目标位姿
970
+ """
971
+ # 获取目标位置和朝向
972
+ target_position = target_info.pose.position
973
+ target_orientation = target_info.pose.orientation
974
+ print(f"target_position: {target_position}, target_orientation: {target_orientation}")
975
+
976
+ # 从四元数中提取yaw角
977
+ target_yaw = self._extract_yaw_from_quaternion(target_orientation)
978
+ print(f"target_yaw: {target_yaw}")
979
+
980
+ # 计算偏移后的位置
981
+ # 根据目标朝向计算偏移方向
982
+ offset_x = -target_distance * math.cos(target_yaw) # 负号是因为要远离目标
983
+ offset_y = -target_distance * math.sin(target_yaw)
984
+
985
+ # 计算新的目标位置
986
+ new_target_position = [
987
+ target_position[0] + offset_x,
988
+ target_position[1] + offset_y,
989
+ target_position[2]
990
+ ]
991
+
992
+ print(f"开始移动到目标位姿:")
993
+ print(f"原始目标位置: ({target_position[0]:.2f}, {target_position[1]:.2f}, {target_position[2]:.2f})")
994
+ print(f"偏移后位置: ({new_target_position[0]:.2f}, {new_target_position[1]:.2f}, {new_target_position[2]:.2f})")
995
+ print(f"目标朝向: {math.degrees(target_yaw):.2f}度")
996
+
997
+ if not self._arrive_pose(
998
+ new_target_position,
999
+ target_yaw
1000
+ ):
1001
+ return False
1002
+
1003
+ return True
1004
+
1005
+ def arm_transport_target_down(self, target_info:BoxInfo, arm_mode="manipulation_mpc"):
1006
+ """添加安全检查"""
1007
+ # 统一的安全检查
1008
+ if not self._check_orientation_safety(target_info.pose.orientation):
1009
+ return False
1010
+ if not self._check_position_safety(target_info): # 添加位置检查
1011
+ return False
1012
+ if not self._check_height_safety(target_info):
1013
+ return False
1014
+
1015
+ # 原有代码保持不变
1016
+ if arm_mode == "manipulation_mpc":
1017
+ self.robot.set_manipulation_mpc_mode(KuavoManipulationMpcCtrlMode.BaseArm)
1018
+ else:
1019
+ self.robot.set_fixed_arm_mode()
1020
+
1021
+ # 获取放置轨迹
1022
+ left_pose_init, right_pose_init, left_pose_target, right_pose_target = self._get_target_pose(target_info, traj_type="place")
1023
+ if left_pose_init is None:
1024
+ return False
1025
+
1026
+ # 执行放置轨迹
1027
+ left_traj_place = KuavoGraspBox._interpolate_poses(left_pose_init, left_pose_target)
1028
+ right_traj_place = KuavoGraspBox._interpolate_poses(right_pose_init, right_pose_target)
1029
+ self._execute_trajectory(left_traj_place, right_traj_place)
1030
+
1031
+ time.sleep(2)
1032
+
1033
+ # 放开箱子
1034
+ left_pose_init, right_pose_init, left_pose_target, right_pose_target = self._get_target_pose(target_info, traj_type="release")
1035
+ if left_pose_init is None:
1036
+ return False
1037
+
1038
+ time.sleep(2)
1039
+ self.robot.set_manipulation_mpc_mode(KuavoManipulationMpcCtrlMode.NoControl)
1040
+ self.robot.set_manipulation_mpc_control_flow(KuavoManipulationMpcControlFlow.ThroughFullBodyMpc)
1041
+ time.sleep(1)
1042
+ self.robot.manipulation_mpc_reset()
1043
+ self.robot.arm_reset()
1044
+ time.sleep(1)
1045
+ return True
1046
+
1047
+ def _wait_for_orientation(self, target_angle, max_wait_time=10.0, angle_threshold=0.1):
1048
+ """等待机器人旋转到指定朝向
1049
+
1050
+ Args:
1051
+ target_angle: 目标朝向角度(弧度)
1052
+ max_wait_time: 最大等待时间(秒)
1053
+ angle_threshold: 角度阈值(弧度),小于此阈值认为已到位
1054
+
1055
+ Returns:
1056
+ bool: 是否成功到达目标朝向
1057
+ """
1058
+ start_time = time.time()
1059
+ rate_hz = 10 # 检查频率
1060
+ wait_interval = 1.0 / rate_hz
1061
+
1062
+ print(f"等待机器人旋转到位,目标角度: {math.degrees(target_angle):.2f}度")
1063
+
1064
+ while time.time() - start_time < max_wait_time:
1065
+ if self._yaw_check(target_angle, angle_threshold):
1066
+ return True
1067
+
1068
+ # 短暂等待再次检查
1069
+ time.sleep(wait_interval)
1070
+
1071
+ # 超时
1072
+ print(f"等待机器人旋转到位超时! 已经等待了 {max_wait_time:.2f}秒")
1073
+ return False
1074
+
1075
+ def _yaw_check(self, yaw_angle_target, angle_threshold=0.1):
1076
+ """检查机器人当前朝向与目标朝向的差异
1077
+
1078
+ Args:
1079
+ yaw_angle_target: 目标朝向角度(弧度)
1080
+ angle_threshold: 角度阈值(弧度),小于此阈值认为已到位
1081
+
1082
+ Returns:
1083
+ bool: 是否成功到达目标朝向
1084
+ """
1085
+ # 获取当前机器人朝向
1086
+ current_orientation = self.state.robot_orientation()
1087
+ current_yaw = self._extract_yaw_from_quaternion(current_orientation)
1088
+
1089
+ # 计算角度差
1090
+ angle_diff = yaw_angle_target - current_yaw
1091
+ # 标准化到[-pi, pi]
1092
+ while angle_diff > math.pi:
1093
+ angle_diff -= 2 * math.pi
1094
+ while angle_diff < -math.pi:
1095
+ angle_diff += 2 * math.pi
1096
+
1097
+ # 输出当前状态
1098
+ print(f"当前朝向: {math.degrees(current_yaw):.2f}度, 目标朝向: {math.degrees(yaw_angle_target):.2f}度, 差值: {math.degrees(abs(angle_diff)):.2f}度")
1099
+
1100
+ # 检查是否已到位
1101
+ if abs(angle_diff) < angle_threshold:
1102
+ print(f"机器人已旋转到位!")
1103
+ return True
1104
+ else:
1105
+ return False
1106
+
1107
+ def _pos_check(self, pos_target, pos_threshold=0.2):
1108
+ """检查机器人当前位置(x, y)与目标位置的差异
1109
+
1110
+ Args:
1111
+ pos_target: 目标位置
1112
+ pos_threshold: 位置阈值(米),小于此阈值认为已到位
1113
+ """
1114
+ current_pos = self.state.robot_position()
1115
+ if not current_pos or len(current_pos) < 2:
1116
+ print("无法获取有效的机器人当前位置")
1117
+ return False
1118
+
1119
+ # print(f"current_pos: {current_pos}, pos_target: {pos_target}")
1120
+ pos_diff = np.linalg.norm(np.array(pos_target[:2]) - np.array(current_pos[:2]))
1121
+ print(f"当前位置(x,y): ({current_pos[0]:.2f}, {current_pos[1]:.2f}), 目标位置(x,y): ({pos_target[0]:.2f}, {pos_target[1]:.2f}), 距离: {pos_diff:.2f}米")
1122
+ if pos_diff < pos_threshold:
1123
+ print(f"机器人已到达目标位置!")
1124
+ return True
1125
+ else:
1126
+ return False