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