kuavo-humanoid-sdk-ws 1.3.2a429__20260205154449-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.
- kuavo_humanoid_sdk/__init__.py +6 -0
- kuavo_humanoid_sdk/common/launch_robot_tool.py +170 -0
- kuavo_humanoid_sdk/common/logger.py +45 -0
- kuavo_humanoid_sdk/common/websocket_kuavo_sdk.py +26 -0
- kuavo_humanoid_sdk/interfaces/__init__.py +4 -0
- kuavo_humanoid_sdk/interfaces/data_types.py +293 -0
- kuavo_humanoid_sdk/interfaces/end_effector.py +62 -0
- kuavo_humanoid_sdk/interfaces/robot.py +22 -0
- kuavo_humanoid_sdk/interfaces/robot_info.py +56 -0
- kuavo_humanoid_sdk/kuavo/__init__.py +12 -0
- kuavo_humanoid_sdk/kuavo/core/audio.py +32 -0
- kuavo_humanoid_sdk/kuavo/core/core.py +755 -0
- kuavo_humanoid_sdk/kuavo/core/dex_hand_control.py +114 -0
- kuavo_humanoid_sdk/kuavo/core/leju_claw_control.py +67 -0
- kuavo_humanoid_sdk/kuavo/core/ros/audio.py +86 -0
- kuavo_humanoid_sdk/kuavo/core/ros/control.py +1325 -0
- kuavo_humanoid_sdk/kuavo/core/ros/observation.py +125 -0
- kuavo_humanoid_sdk/kuavo/core/ros/param.py +335 -0
- kuavo_humanoid_sdk/kuavo/core/ros/sat_utils.py +103 -0
- kuavo_humanoid_sdk/kuavo/core/ros/state.py +426 -0
- kuavo_humanoid_sdk/kuavo/core/ros/tools.py +197 -0
- kuavo_humanoid_sdk/kuavo/core/ros/vision.py +280 -0
- kuavo_humanoid_sdk/kuavo/core/ros_env.py +236 -0
- kuavo_humanoid_sdk/kuavo/dexterous_hand.py +198 -0
- kuavo_humanoid_sdk/kuavo/leju_claw.py +232 -0
- kuavo_humanoid_sdk/kuavo/robot.py +550 -0
- kuavo_humanoid_sdk/kuavo/robot_arm.py +235 -0
- kuavo_humanoid_sdk/kuavo/robot_audio.py +39 -0
- kuavo_humanoid_sdk/kuavo/robot_head.py +39 -0
- kuavo_humanoid_sdk/kuavo/robot_info.py +235 -0
- kuavo_humanoid_sdk/kuavo/robot_observation.py +69 -0
- kuavo_humanoid_sdk/kuavo/robot_state.py +346 -0
- kuavo_humanoid_sdk/kuavo/robot_tool.py +58 -0
- kuavo_humanoid_sdk/kuavo/robot_vision.py +82 -0
- kuavo_humanoid_sdk/kuavo/robot_waist.py +53 -0
- kuavo_humanoid_sdk/kuavo_strategy/__init__.py +2 -0
- kuavo_humanoid_sdk/kuavo_strategy/grasp_box/grasp_box_strategy.py +418 -0
- kuavo_humanoid_sdk/kuavo_strategy/kuavo_strategy.py +63 -0
- kuavo_humanoid_sdk/msg/__init__.py +4 -0
- kuavo_humanoid_sdk_ws-1.3.2a429.dist-info/METADATA +279 -0
- kuavo_humanoid_sdk_ws-1.3.2a429.dist-info/RECORD +43 -0
- kuavo_humanoid_sdk_ws-1.3.2a429.dist-info/WHEEL +6 -0
- kuavo_humanoid_sdk_ws-1.3.2a429.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
import roslibpy
|
|
2
|
+
import time
|
|
3
|
+
import copy
|
|
4
|
+
from collections import deque
|
|
5
|
+
from kuavo_humanoid_sdk.common.logger import SDKLogger
|
|
6
|
+
from kuavo_humanoid_sdk.common.websocket_kuavo_sdk import WebSocketKuavoSDK
|
|
7
|
+
from kuavo_humanoid_sdk.kuavo.core.ros.param import make_robot_param, EndEffectorType
|
|
8
|
+
from kuavo_humanoid_sdk.interfaces.data_types import (KuavoImuData, KuavoJointData, KuavoOdometry, KuavoManipulationMpcFrame,
|
|
9
|
+
KuavoArmCtrlMode, EndEffectorState, KuavoDexHandTouchState,
|
|
10
|
+
KuavoManipulationMpcCtrlMode, KuavoManipulationMpcControlFlow)
|
|
11
|
+
class GaitManager:
|
|
12
|
+
def __init__(self, max_size: int = 20):
|
|
13
|
+
self._max_size = max_size
|
|
14
|
+
self._gait_time_names = deque(maxlen=max_size)
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def is_empty(self) -> bool:
|
|
18
|
+
"""Check if there are any gait time names stored.
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
bool: True if no gait time names are stored, False otherwise
|
|
22
|
+
"""
|
|
23
|
+
return len(self._gait_time_names) == 0
|
|
24
|
+
def add(self, start_time: float, name: str) -> None:
|
|
25
|
+
self._gait_time_names.append((start_time, name))
|
|
26
|
+
|
|
27
|
+
def get_gait(self, current_time: float) -> str:
|
|
28
|
+
if not self._gait_time_names:
|
|
29
|
+
return "No_Gait"
|
|
30
|
+
|
|
31
|
+
for start_time, name in reversed(self._gait_time_names):
|
|
32
|
+
if current_time >= start_time:
|
|
33
|
+
return name
|
|
34
|
+
|
|
35
|
+
return self._gait_time_names[0].name
|
|
36
|
+
|
|
37
|
+
def get_gait_name(self, current_time: float) -> str:
|
|
38
|
+
return self.get_gait(current_time)[1]
|
|
39
|
+
|
|
40
|
+
def get_last_gait_name(self) -> str:
|
|
41
|
+
if not self._gait_time_names:
|
|
42
|
+
return "No_Gait"
|
|
43
|
+
return self._gait_time_names[-1][1]
|
|
44
|
+
|
|
45
|
+
class KuavoRobotStateCoreWebsocket:
|
|
46
|
+
_instance = None
|
|
47
|
+
|
|
48
|
+
def __new__(cls, *args, **kwargs):
|
|
49
|
+
if not cls._instance:
|
|
50
|
+
cls._instance = super().__new__(cls)
|
|
51
|
+
return cls._instance
|
|
52
|
+
|
|
53
|
+
def __init__(self):
|
|
54
|
+
if not hasattr(self, '_initialized'):
|
|
55
|
+
try:
|
|
56
|
+
self.websocket = WebSocketKuavoSDK()
|
|
57
|
+
if not self.websocket.client.is_connected:
|
|
58
|
+
SDKLogger.error("Failed to connect to WebSocket server")
|
|
59
|
+
raise ConnectionError("Failed to connect to WebSocket server")
|
|
60
|
+
|
|
61
|
+
# Initialize subscribers
|
|
62
|
+
self._sub_sensors_data = roslibpy.Topic(self.websocket.client, '/sensors_data_raw', 'kuavo_msgs/sensorsData')
|
|
63
|
+
self._sub_odom = roslibpy.Topic(self.websocket.client, '/odom', 'nav_msgs/Odometry')
|
|
64
|
+
self._sub_terrain_height = roslibpy.Topic(self.websocket.client, '/humanoid/mpc/terrainHeight', 'std_msgs/Float64')
|
|
65
|
+
self._sub_gait_time_name = roslibpy.Topic(self.websocket.client, '/humanoid_mpc_gait_time_name', 'kuavo_msgs/gaitTimeName')
|
|
66
|
+
self._sub_mpc_observation = roslibpy.Topic(self.websocket.client, '/humanoid_mpc_observation', 'ocs2_msgs/mpc_observation')
|
|
67
|
+
|
|
68
|
+
# service calls are time-consuming after subscription, place them before subscription
|
|
69
|
+
kuavo_info = make_robot_param()
|
|
70
|
+
|
|
71
|
+
# Subscribe to topics
|
|
72
|
+
self._sub_sensors_data.subscribe(self._sensors_data_raw_callback)
|
|
73
|
+
self._sub_odom.subscribe(self._odom_callback)
|
|
74
|
+
self._sub_terrain_height.subscribe(self._terrain_height_callback)
|
|
75
|
+
self._sub_gait_time_name.subscribe(self._humanoid_mpc_gait_changed_callback)
|
|
76
|
+
self._sub_mpc_observation.subscribe(self._humanoid_mpc_observation_callback)
|
|
77
|
+
|
|
78
|
+
if kuavo_info is None:
|
|
79
|
+
SDKLogger.error("Failed to get robot parameters")
|
|
80
|
+
raise RuntimeError("Failed to get robot parameters")
|
|
81
|
+
|
|
82
|
+
self._ee_type = kuavo_info['end_effector_type']
|
|
83
|
+
if self._ee_type == EndEffectorType.LEJUCLAW:
|
|
84
|
+
self._sub_lejuclaw_state = roslibpy.Topic(self.websocket.client, '/leju_claw_state', 'kuavo_msgs/lejuClawState')
|
|
85
|
+
self._sub_lejuclaw_state.subscribe(self._lejuclaw_state_callback)
|
|
86
|
+
elif self._ee_type == EndEffectorType.QIANGNAO:
|
|
87
|
+
self._sub_dexhand_state = roslibpy.Topic(self.websocket.client, '/dexhand/state', 'sensor_msgs/JointState')
|
|
88
|
+
self._sub_dexhand_state.subscribe(self._dexterous_hand_state_callback)
|
|
89
|
+
elif self._ee_type == EndEffectorType.QIANGNAO_TOUCH:
|
|
90
|
+
self._sub_dexhand_state = roslibpy.Topic(self.websocket.client, '/dexhand/state', 'sensor_msgs/JointState')
|
|
91
|
+
self._sub_dexhand_touch_state = roslibpy.Topic(self.websocket.client, '/dexhand/touch_state', 'kuavo_msgs/dexhandTouchState')
|
|
92
|
+
self._sub_dexhand_state.subscribe(self._dexterous_hand_state_callback)
|
|
93
|
+
self._sub_dexhand_touch_state.subscribe(self._dexhand_touch_state_callback)
|
|
94
|
+
# Initialize touch state for both hands
|
|
95
|
+
self._dexhand_touch_state = (
|
|
96
|
+
KuavoDexHandTouchState(
|
|
97
|
+
data=(KuavoDexHandTouchState.KuavoTouchState(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
|
|
98
|
+
KuavoDexHandTouchState.KuavoTouchState(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
|
|
99
|
+
KuavoDexHandTouchState.KuavoTouchState(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
|
|
100
|
+
KuavoDexHandTouchState.KuavoTouchState(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
|
|
101
|
+
KuavoDexHandTouchState.KuavoTouchState(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0))
|
|
102
|
+
),
|
|
103
|
+
KuavoDexHandTouchState(
|
|
104
|
+
data=(KuavoDexHandTouchState.KuavoTouchState(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
|
|
105
|
+
KuavoDexHandTouchState.KuavoTouchState(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
|
|
106
|
+
KuavoDexHandTouchState.KuavoTouchState(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
|
|
107
|
+
KuavoDexHandTouchState.KuavoTouchState(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
|
|
108
|
+
KuavoDexHandTouchState.KuavoTouchState(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0))
|
|
109
|
+
)
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
""" data """
|
|
113
|
+
self._terrain_height = 0.0
|
|
114
|
+
self._imu_data = KuavoImuData(
|
|
115
|
+
gyro = (0.0, 0.0, 0.0),
|
|
116
|
+
acc = (0.0, 0.0, 0.0),
|
|
117
|
+
free_acc = (0.0, 0.0, 0.0),
|
|
118
|
+
quat = (0.0, 0.0, 0.0, 0.0)
|
|
119
|
+
)
|
|
120
|
+
self._joint_data = KuavoJointData(
|
|
121
|
+
position = [0.0] * 28,
|
|
122
|
+
velocity = [0.0] * 28,
|
|
123
|
+
torque = [0.0] * 28,
|
|
124
|
+
acceleration = [0.0] * 28
|
|
125
|
+
)
|
|
126
|
+
self._odom_data = KuavoOdometry(
|
|
127
|
+
position = (0.0, 0.0, 0.0),
|
|
128
|
+
orientation = (0.0, 0.0, 0.0, 0.0),
|
|
129
|
+
linear = (0.0, 0.0, 0.0),
|
|
130
|
+
angular = (0.0, 0.0, 0.0)
|
|
131
|
+
)
|
|
132
|
+
self._eef_state = (EndEffectorState(
|
|
133
|
+
position = [0.0] * 12 if kuavo_info['end_effector_type'] is not None and kuavo_info['end_effector_type'].startswith('qiangnao') else [0.0] * 2,
|
|
134
|
+
velocity = [0.0] * 12 if kuavo_info['end_effector_type'] is not None and kuavo_info['end_effector_type'].startswith('qiangnao') else [0.0] * 2,
|
|
135
|
+
effort = [0.0] * 12 if kuavo_info['end_effector_type'] is not None and kuavo_info['end_effector_type'].startswith('qiangnao') else [0.0] * 2,
|
|
136
|
+
state=EndEffectorState.GraspingState.UNKNOWN
|
|
137
|
+
), EndEffectorState(
|
|
138
|
+
position = [0.0] * 12 if kuavo_info['end_effector_type'] is not None and kuavo_info['end_effector_type'].startswith('qiangnao') else [0.0] * 2,
|
|
139
|
+
velocity = [0.0] * 12 if kuavo_info['end_effector_type'] is not None and kuavo_info['end_effector_type'].startswith('qiangnao') else [0.0] * 2,
|
|
140
|
+
effort = [0.0] * 12 if kuavo_info['end_effector_type'] is not None and kuavo_info['end_effector_type'].startswith('qiangnao') else [0.0] * 2,
|
|
141
|
+
state=EndEffectorState.GraspingState.UNKNOWN
|
|
142
|
+
))
|
|
143
|
+
|
|
144
|
+
# gait manager
|
|
145
|
+
self._gait_manager = GaitManager()
|
|
146
|
+
self._prev_gait_name = self.gait_name()
|
|
147
|
+
|
|
148
|
+
# Wait for first MPC observation data
|
|
149
|
+
self._mpc_observation_data = None
|
|
150
|
+
start_time = time.time()
|
|
151
|
+
while self._mpc_observation_data is None:
|
|
152
|
+
if time.time() - start_time > 1.0: # 1.0s timeout
|
|
153
|
+
start_time = time.time()
|
|
154
|
+
SDKLogger.warn("Timeout waiting for MPC observation data")
|
|
155
|
+
# break
|
|
156
|
+
SDKLogger.debug("Waiting for first MPC observation data...")
|
|
157
|
+
time.sleep(0.1)
|
|
158
|
+
|
|
159
|
+
if self._mpc_observation_data is not None:
|
|
160
|
+
if self._gait_manager.is_empty:
|
|
161
|
+
self._prev_gait_name = self.gait_name()
|
|
162
|
+
SDKLogger.debug(f"[State] Adding initial gait state: {self._prev_gait_name} at time {self._mpc_observation_data['time']}")
|
|
163
|
+
self._gait_manager.add(self._mpc_observation_data['time'], self._prev_gait_name)
|
|
164
|
+
|
|
165
|
+
# 获取当前手臂控制模式
|
|
166
|
+
self._arm_ctrl_mode = self._srv_get_arm_ctrl_mode()
|
|
167
|
+
|
|
168
|
+
# 获取manipulation mpc 相关参数
|
|
169
|
+
self._manipulation_mpc_frame = self._srv_get_manipulation_mpc_frame()
|
|
170
|
+
self._manipulation_mpc_ctrl_mode = self._srv_get_manipulation_mpc_ctrl_mode()
|
|
171
|
+
self._manipulation_mpc_control_flow = self._srv_get_manipulation_mpc_control_flow()
|
|
172
|
+
|
|
173
|
+
self._initialized = True
|
|
174
|
+
except Exception as e:
|
|
175
|
+
SDKLogger.error(f"Failed to initialize KuavoRobotStateCoreWebsocket: {e}")
|
|
176
|
+
raise
|
|
177
|
+
|
|
178
|
+
@property
|
|
179
|
+
def com_height(self)->float:
|
|
180
|
+
return self._odom_data.position[2] - self._terrain_height
|
|
181
|
+
|
|
182
|
+
@property
|
|
183
|
+
def imu_data(self):
|
|
184
|
+
return self._imu_data
|
|
185
|
+
|
|
186
|
+
@property
|
|
187
|
+
def joint_data(self):
|
|
188
|
+
return self._joint_data
|
|
189
|
+
|
|
190
|
+
@property
|
|
191
|
+
def odom_data(self):
|
|
192
|
+
return self._odom_data
|
|
193
|
+
|
|
194
|
+
@property
|
|
195
|
+
def arm_control_mode(self):
|
|
196
|
+
mode = self._srv_get_arm_ctrl_mode()
|
|
197
|
+
if mode is not None:
|
|
198
|
+
self._arm_ctrl_mode = mode
|
|
199
|
+
return self._arm_ctrl_mode
|
|
200
|
+
|
|
201
|
+
@property
|
|
202
|
+
def manipulation_mpc_ctrl_mode(self):
|
|
203
|
+
mode = self._srv_get_manipulation_mpc_ctrl_mode()
|
|
204
|
+
if mode is not None:
|
|
205
|
+
self._manipulation_mpc_ctrl_mode = mode
|
|
206
|
+
return self._manipulation_mpc_ctrl_mode
|
|
207
|
+
|
|
208
|
+
@property
|
|
209
|
+
def manipulation_mpc_frame(self):
|
|
210
|
+
frame = self._srv_get_manipulation_mpc_frame()
|
|
211
|
+
if frame is not None:
|
|
212
|
+
self._manipulation_mpc_frame = frame
|
|
213
|
+
return self._manipulation_mpc_frame
|
|
214
|
+
|
|
215
|
+
@property
|
|
216
|
+
def manipulation_mpc_control_flow(self):
|
|
217
|
+
flow = self._srv_get_manipulation_mpc_control_flow()
|
|
218
|
+
if flow is not None:
|
|
219
|
+
self._manipulation_mpc_control_flow = flow
|
|
220
|
+
return self._manipulation_mpc_control_flow
|
|
221
|
+
|
|
222
|
+
@property
|
|
223
|
+
def eef_state(self):
|
|
224
|
+
return self._eef_state
|
|
225
|
+
|
|
226
|
+
@property
|
|
227
|
+
def dexhand_touch_state(self):
|
|
228
|
+
if self._ee_type != EndEffectorType.QIANGNAO_TOUCH:
|
|
229
|
+
raise Exception("This robot does not support touch state")
|
|
230
|
+
return self._dexhand_touch_state
|
|
231
|
+
|
|
232
|
+
@property
|
|
233
|
+
def current_observation_time(self) -> float:
|
|
234
|
+
if self._mpc_observation_data is None:
|
|
235
|
+
return None
|
|
236
|
+
return self._mpc_observation_data['time']
|
|
237
|
+
|
|
238
|
+
@property
|
|
239
|
+
def current_gait_mode(self) -> int:
|
|
240
|
+
if self._mpc_observation_data is None:
|
|
241
|
+
return None
|
|
242
|
+
return self._mpc_observation_data['mode']
|
|
243
|
+
|
|
244
|
+
def gait_name(self)->str:
|
|
245
|
+
return self._srv_get_current_gait_name()
|
|
246
|
+
|
|
247
|
+
def is_gait(self, gait_name: str) -> bool:
|
|
248
|
+
return self._gait_manager.get_gait(self._mpc_observation_data['time']) == gait_name
|
|
249
|
+
|
|
250
|
+
def register_gait_changed_callback(self, callback):
|
|
251
|
+
if not hasattr(self, '_gait_changed_callbacks'):
|
|
252
|
+
self._gait_changed_callbacks = []
|
|
253
|
+
|
|
254
|
+
from inspect import signature
|
|
255
|
+
sig = signature(callback)
|
|
256
|
+
if len(sig.parameters) != 2:
|
|
257
|
+
raise ValueError("Callback must accept 2 parameters: current_time (float), gait_name (str)")
|
|
258
|
+
if callback not in self._gait_changed_callbacks:
|
|
259
|
+
self._gait_changed_callbacks.append(callback)
|
|
260
|
+
|
|
261
|
+
def _terrain_height_callback(self, msg)->None:
|
|
262
|
+
self._terrain_height = msg['data']
|
|
263
|
+
|
|
264
|
+
def _sensors_data_raw_callback(self, msg)->None:
|
|
265
|
+
# update imu data
|
|
266
|
+
self._imu_data = KuavoImuData(
|
|
267
|
+
gyro = (msg['imu_data']['gyro']['x'], msg['imu_data']['gyro']['y'], msg['imu_data']['gyro']['z']),
|
|
268
|
+
acc = (msg['imu_data']['acc']['x'], msg['imu_data']['acc']['y'], msg['imu_data']['acc']['z']),
|
|
269
|
+
free_acc = (msg['imu_data']['free_acc']['x'], msg['imu_data']['free_acc']['y'], msg['imu_data']['free_acc']['z']),
|
|
270
|
+
quat = (msg['imu_data']['quat']['x'], msg['imu_data']['quat']['y'], msg['imu_data']['quat']['z'], msg['imu_data']['quat']['w'])
|
|
271
|
+
)
|
|
272
|
+
# update joint data
|
|
273
|
+
self._joint_data = KuavoJointData(
|
|
274
|
+
position = copy.deepcopy(msg['joint_data']['joint_q']),
|
|
275
|
+
velocity = copy.deepcopy(msg['joint_data']['joint_v']),
|
|
276
|
+
torque = copy.deepcopy(msg['joint_data']['joint_vd']),
|
|
277
|
+
acceleration = copy.deepcopy(msg['joint_data'].get('joint_current', msg['joint_data']['joint_torque']))
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
def _odom_callback(self, msg)->None:
|
|
281
|
+
# update odom data
|
|
282
|
+
self._odom_data = KuavoOdometry(
|
|
283
|
+
position = (msg['pose']['pose']['position']['x'], msg['pose']['pose']['position']['y'], msg['pose']['pose']['position']['z']),
|
|
284
|
+
orientation = (msg['pose']['pose']['orientation']['x'], msg['pose']['pose']['orientation']['y'], msg['pose']['pose']['orientation']['z'], msg['pose']['pose']['orientation']['w']),
|
|
285
|
+
linear = (msg['twist']['twist']['linear']['x'], msg['twist']['twist']['linear']['y'], msg['twist']['twist']['linear']['z']),
|
|
286
|
+
angular = (msg['twist']['twist']['angular']['x'], msg['twist']['twist']['angular']['y'], msg['twist']['twist']['angular']['z'])
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
def _lejuclaw_state_callback(self, msg)->None:
|
|
290
|
+
self._eef_state = (EndEffectorState(
|
|
291
|
+
position = [msg['data']['position'][0]],
|
|
292
|
+
velocity = [msg['data']['velocity'][0]],
|
|
293
|
+
effort = [msg['data']['effort'][0]],
|
|
294
|
+
state=EndEffectorState.GraspingState(msg['state'][0])
|
|
295
|
+
), EndEffectorState(
|
|
296
|
+
position = [msg['data']['position'][1]],
|
|
297
|
+
velocity = [msg['data']['velocity'][1]],
|
|
298
|
+
effort = [msg['data']['effort'][1]],
|
|
299
|
+
state=EndEffectorState.GraspingState(msg['state'][1])
|
|
300
|
+
))
|
|
301
|
+
|
|
302
|
+
def _dexterous_hand_state_callback(self, msg)->None:
|
|
303
|
+
self._eef_state = (EndEffectorState(
|
|
304
|
+
position = list(msg['position'][:len(msg['position'])//2]),
|
|
305
|
+
velocity = list(msg['velocity'][:len(msg['velocity'])//2]),
|
|
306
|
+
effort = list(msg['effort'][:len(msg['effort'])//2]),
|
|
307
|
+
state=EndEffectorState.GraspingState.UNKNOWN
|
|
308
|
+
), EndEffectorState(
|
|
309
|
+
position = list(msg['position'][len(msg['position'])//2:]),
|
|
310
|
+
velocity = list(msg['velocity'][len(msg['velocity'])//2:]),
|
|
311
|
+
effort = list(msg['effort'][len(msg['effort'])//2:]),
|
|
312
|
+
state=EndEffectorState.GraspingState.UNKNOWN
|
|
313
|
+
))
|
|
314
|
+
|
|
315
|
+
def _dexhand_touch_state_callback(self, msg)->None:
|
|
316
|
+
self._dexhand_touch_state = (
|
|
317
|
+
KuavoDexHandTouchState(
|
|
318
|
+
data=tuple(KuavoDexHandTouchState.KuavoTouchState(
|
|
319
|
+
sensor['normal_force1'], sensor['normal_force2'], sensor['normal_force3'],
|
|
320
|
+
sensor['tangential_force1'], sensor['tangential_force2'], sensor['tangential_force3'],
|
|
321
|
+
sensor['tangential_direction1'], sensor['tangential_direction2'], sensor['tangential_direction3'],
|
|
322
|
+
sensor['self_proximity1'], sensor['self_proximity2'], sensor['mutual_proximity'],
|
|
323
|
+
sensor['status']
|
|
324
|
+
) for sensor in msg['left_hand'])
|
|
325
|
+
),
|
|
326
|
+
KuavoDexHandTouchState(
|
|
327
|
+
data=tuple(KuavoDexHandTouchState.KuavoTouchState(
|
|
328
|
+
sensor['normal_force1'], sensor['normal_force2'], sensor['normal_force3'],
|
|
329
|
+
sensor['tangential_force1'], sensor['tangential_force2'], sensor['tangential_force3'],
|
|
330
|
+
sensor['tangential_direction1'], sensor['tangential_direction2'], sensor['tangential_direction3'],
|
|
331
|
+
sensor['self_proximity1'], sensor['self_proximity2'], sensor['mutual_proximity'],
|
|
332
|
+
sensor['status']
|
|
333
|
+
) for sensor in msg['right_hand'])
|
|
334
|
+
)
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
def _humanoid_mpc_gait_changed_callback(self, msg):
|
|
338
|
+
SDKLogger.debug(f"[State] Received gait change message: {msg['gait_name']} at time {msg['start_time']}")
|
|
339
|
+
self._gait_manager.add(msg['start_time'], msg['gait_name'])
|
|
340
|
+
|
|
341
|
+
def _humanoid_mpc_observation_callback(self, msg) -> None:
|
|
342
|
+
try:
|
|
343
|
+
# SDKLogger.debug(f"[State] Received MPC observation message: {msg}")
|
|
344
|
+
self._mpc_observation_data = msg
|
|
345
|
+
if hasattr(self, '_initialized'):
|
|
346
|
+
curr_time = self._mpc_observation_data['time']
|
|
347
|
+
current_gait = self._gait_manager.get_gait(curr_time)
|
|
348
|
+
if self._prev_gait_name != current_gait:
|
|
349
|
+
SDKLogger.debug(f"[State] Gait changed to: {current_gait} at time {curr_time}")
|
|
350
|
+
self._prev_gait_name = current_gait
|
|
351
|
+
if hasattr(self, '_gait_changed_callbacks') and self._gait_changed_callbacks is not None:
|
|
352
|
+
for callback in self._gait_changed_callbacks:
|
|
353
|
+
callback(curr_time, current_gait)
|
|
354
|
+
except Exception as e:
|
|
355
|
+
SDKLogger.error(f"Error processing MPC observation: {e}")
|
|
356
|
+
|
|
357
|
+
def _srv_get_arm_ctrl_mode(self):
|
|
358
|
+
try:
|
|
359
|
+
service = roslibpy.Service(self.websocket.client, '/humanoid_get_arm_ctrl_mode', 'kuavo_msgs/changeArmCtrlMode')
|
|
360
|
+
request = {}
|
|
361
|
+
response = service.call(request)
|
|
362
|
+
return KuavoArmCtrlMode(response.get('mode', 0))
|
|
363
|
+
except Exception as e:
|
|
364
|
+
SDKLogger.error(f"Service call failed: {e}")
|
|
365
|
+
return None
|
|
366
|
+
|
|
367
|
+
def _srv_get_current_gait_name(self)->str:
|
|
368
|
+
try:
|
|
369
|
+
service = roslibpy.Service(self.websocket.client, '/humanoid_get_current_gait_name', 'kuavo_msgs/getCurrentGaitName')
|
|
370
|
+
request = {}
|
|
371
|
+
response = service.call(request)
|
|
372
|
+
if response.get('success', False):
|
|
373
|
+
return response['gait_name']
|
|
374
|
+
return None
|
|
375
|
+
except Exception as e:
|
|
376
|
+
SDKLogger.error(f"Service call failed: {e}")
|
|
377
|
+
return None
|
|
378
|
+
|
|
379
|
+
def _srv_get_manipulation_mpc_ctrl_mode(self, ):
|
|
380
|
+
try:
|
|
381
|
+
service_name = '/mobile_manipulator_get_mpc_control_mode'
|
|
382
|
+
service = roslibpy.Service(self.websocket.client, service_name, 'kuavo_msgs/changeTorsoCtrlMode')
|
|
383
|
+
request = {}
|
|
384
|
+
response = service.call(request)
|
|
385
|
+
if not response.get('result', False):
|
|
386
|
+
SDKLogger.error(f"Failed to get manipulation mpc control mode: {response.get('message', '')}")
|
|
387
|
+
return KuavoManipulationMpcCtrlMode.ERROR
|
|
388
|
+
return KuavoManipulationMpcCtrlMode(response.get('mode', 0))
|
|
389
|
+
except Exception as e:
|
|
390
|
+
SDKLogger.error(f"Failed to get manipulation mpc control mode: {e}")
|
|
391
|
+
return KuavoManipulationMpcCtrlMode.ERROR
|
|
392
|
+
|
|
393
|
+
def _srv_get_manipulation_mpc_frame(self, ):
|
|
394
|
+
try:
|
|
395
|
+
service_name = '/get_mm_ctrl_frame'
|
|
396
|
+
service = roslibpy.Service(self.websocket.client, service_name, 'kuavo_msgs/setMmCtrlFrame')
|
|
397
|
+
request = {}
|
|
398
|
+
response = service.call(request)
|
|
399
|
+
if not response.get('result', False):
|
|
400
|
+
SDKLogger.error(f"Failed to get manipulation mpc frame: {response.get('message', '')}")
|
|
401
|
+
return KuavoManipulationMpcFrame.ERROR
|
|
402
|
+
return KuavoManipulationMpcFrame(response.get('currentFrame', 0))
|
|
403
|
+
except Exception as e:
|
|
404
|
+
SDKLogger.error(f"Failed to get manipulation mpc frame: {e}")
|
|
405
|
+
return KuavoManipulationMpcFrame.ERROR
|
|
406
|
+
|
|
407
|
+
def _srv_get_manipulation_mpc_control_flow(self, ):
|
|
408
|
+
try:
|
|
409
|
+
service_name = '/get_mm_wbc_arm_trajectory_control'
|
|
410
|
+
service = roslibpy.Service(self.websocket.client, service_name, 'kuavo_msgs/changeArmCtrlMode')
|
|
411
|
+
request = {}
|
|
412
|
+
response = service.call(request)
|
|
413
|
+
if not response.get('result', False):
|
|
414
|
+
SDKLogger.error(f"Failed to get manipulation mpc wbc arm trajectory control mode: {response.get('message', '')}")
|
|
415
|
+
return KuavoManipulationMpcControlFlow.Error
|
|
416
|
+
return KuavoManipulationMpcControlFlow(response.get('mode', 0))
|
|
417
|
+
except Exception as e:
|
|
418
|
+
SDKLogger.error(f"Failed to get manipulation mpc wbc arm trajectory control mode: {e}")
|
|
419
|
+
return KuavoManipulationMpcControlFlow.Error
|
|
420
|
+
|
|
421
|
+
# if __name__ == "__main__":
|
|
422
|
+
# state = KuavoRobotStateCore()
|
|
423
|
+
# print(state.manipulation_mpc_frame)
|
|
424
|
+
# print(state.manipulation_mpc_control_flow)
|
|
425
|
+
# print(state.manipulation_mpc_ctrl_mode)
|
|
426
|
+
# print(state.arm_control_mode)
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
#! /usr/bin/env python3
|
|
2
|
+
# coding: utf-8
|
|
3
|
+
|
|
4
|
+
import copy
|
|
5
|
+
import time
|
|
6
|
+
import numpy as np
|
|
7
|
+
from typing import Tuple, Union
|
|
8
|
+
from kuavo_humanoid_sdk.common.logger import SDKLogger
|
|
9
|
+
from transforms3d import euler, quaternions
|
|
10
|
+
import roslibpy
|
|
11
|
+
from kuavo_humanoid_sdk.common.websocket_kuavo_sdk import WebSocketKuavoSDK
|
|
12
|
+
from kuavo_humanoid_sdk.interfaces.data_types import (PoseQuaternion, HomogeneousMatrix)
|
|
13
|
+
from kuavo_humanoid_sdk.kuavo.core.ros.param import make_robot_param, EndEffectorType
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class KuavoRobotToolsCoreWebsocket:
|
|
17
|
+
"""Provides core ROS tools for Kuavo humanoid robot transformations.
|
|
18
|
+
|
|
19
|
+
Attributes:
|
|
20
|
+
tf_service (roslibpy.Service): Service proxy for tf2_web_republisher
|
|
21
|
+
_transform_cache (dict): Cache for storing recent transforms
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(self):
|
|
25
|
+
"""Initializes TF2 web republisher service proxy."""
|
|
26
|
+
if not hasattr(self, '_initialized'):
|
|
27
|
+
try:
|
|
28
|
+
# Initialize WebSocket connection
|
|
29
|
+
self.websocket = WebSocketKuavoSDK()
|
|
30
|
+
|
|
31
|
+
# Initialize TF2 web republisher service
|
|
32
|
+
self.tf_service = roslibpy.Service(
|
|
33
|
+
self.websocket.client,
|
|
34
|
+
'/republish_tfs',
|
|
35
|
+
'kuavo_msgs/RepublishTFs'
|
|
36
|
+
)
|
|
37
|
+
self._transform_cache = {}
|
|
38
|
+
self._initialized = True
|
|
39
|
+
except Exception as e:
|
|
40
|
+
SDKLogger.error(f"Failed to initialize kuavo_tf2_web_republisher: {str(e)}")
|
|
41
|
+
SDKLogger.error(f"kuavo_tf2_web_republisher 节点未运行")
|
|
42
|
+
SDKLogger.error("请运行 `cd <kuavo_ros_application> && source devel/setup.bash && rosrun kuavo_tf2_web_republisher kuavo_tf2_web_republisher` 启动 kuavo_tf2_web_republisher 节点")
|
|
43
|
+
raise
|
|
44
|
+
|
|
45
|
+
def _get_tf_tree_transform(self, target_frame: str, source_frame: str,
|
|
46
|
+
time_=0.0, timeout=5.0,
|
|
47
|
+
return_type: str = "pose_quaternion") -> Union[PoseQuaternion, HomogeneousMatrix, None]:
|
|
48
|
+
"""Gets transform between coordinate frames using tf2_web_republisher.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
target_frame (str): Target coordinate frame name
|
|
52
|
+
source_frame (str): Source coordinate frame name
|
|
53
|
+
time (float, optional): Time of transform. Defaults to 0.0.
|
|
54
|
+
timeout (float, optional): Wait timeout in seconds. Defaults to 5.0.
|
|
55
|
+
return_type (str, optional): Return data format. Options:
|
|
56
|
+
"pose_quaternion", "homogeneous". Defaults to "pose_quaternion".
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
Union[PoseQuaternion, HomogeneousMatrix, None]: Requested transform data
|
|
60
|
+
or None if failed
|
|
61
|
+
"""
|
|
62
|
+
try:
|
|
63
|
+
|
|
64
|
+
# 调用服务
|
|
65
|
+
request = {
|
|
66
|
+
'source_frames': [source_frame],
|
|
67
|
+
'target_frame': target_frame,
|
|
68
|
+
'angular_thres': 0.01,
|
|
69
|
+
'trans_thres': 0.01,
|
|
70
|
+
'rate': 10.0,
|
|
71
|
+
'timeout': {'secs': int(timeout), 'nsecs': int((timeout % 1) * 1e9)}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
response = self.tf_service.call(request)
|
|
75
|
+
if response['status'] == -1:
|
|
76
|
+
SDKLogger.error(f"{source_frame} or {target_frame} not exist")
|
|
77
|
+
return None
|
|
78
|
+
|
|
79
|
+
# 检查话题是否发布
|
|
80
|
+
topic_list = self.websocket.client.get_topics()
|
|
81
|
+
if response['topic_name'] not in topic_list:
|
|
82
|
+
SDKLogger.error(f"Topic {response['topic_name']} not published")
|
|
83
|
+
return None
|
|
84
|
+
|
|
85
|
+
# 创建订阅者
|
|
86
|
+
transform_received = False
|
|
87
|
+
transform_data = None
|
|
88
|
+
|
|
89
|
+
def transform_callback(msg):
|
|
90
|
+
nonlocal transform_received, transform_data
|
|
91
|
+
transform_received = True
|
|
92
|
+
transform_data = msg
|
|
93
|
+
|
|
94
|
+
sub = roslibpy.Topic(self.websocket.client, response['topic_name'], 'kuavo_msgs/TFArray')
|
|
95
|
+
sub.subscribe(transform_callback)
|
|
96
|
+
|
|
97
|
+
# 等待接收数据
|
|
98
|
+
start_time = time.time()
|
|
99
|
+
while not transform_received and (time.time() - start_time) < timeout:
|
|
100
|
+
time.sleep(0.1)
|
|
101
|
+
|
|
102
|
+
# 取消订阅
|
|
103
|
+
sub.unsubscribe()
|
|
104
|
+
|
|
105
|
+
if not transform_received:
|
|
106
|
+
SDKLogger.error("No transform data received")
|
|
107
|
+
return None
|
|
108
|
+
|
|
109
|
+
# 从TFArray中获取对应的变换
|
|
110
|
+
for tf_msg in transform_data['transforms']:
|
|
111
|
+
if tf_msg['header']['frame_id'] == target_frame and tf_msg['child_frame_id'] == source_frame:
|
|
112
|
+
return self._parse_transform(tf_msg['transform'], return_type)
|
|
113
|
+
|
|
114
|
+
SDKLogger.error(f"No matching transform found in TFArray")
|
|
115
|
+
return None
|
|
116
|
+
|
|
117
|
+
except Exception as e:
|
|
118
|
+
SDKLogger.error(f"Transform error: {str(e)}")
|
|
119
|
+
return None
|
|
120
|
+
|
|
121
|
+
def _parse_transform(self, transform, return_type: str) -> Union[PoseQuaternion, HomogeneousMatrix, None]:
|
|
122
|
+
"""Parses transform data to specified format.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
transform (dict): Input transform data
|
|
126
|
+
return_type (str): Output format type. Valid values:
|
|
127
|
+
"pose_quaternion", "homogeneous"
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
Union[PoseQuaternion, HomogeneousMatrix, None]: Parsed transform data
|
|
131
|
+
in requested format, or None if invalid input
|
|
132
|
+
"""
|
|
133
|
+
if return_type == "pose_quaternion":
|
|
134
|
+
return PoseQuaternion(
|
|
135
|
+
position=(transform['translation']['x'],
|
|
136
|
+
transform['translation']['y'],
|
|
137
|
+
transform['translation']['z']),
|
|
138
|
+
orientation=(transform['rotation']['x'],
|
|
139
|
+
transform['rotation']['y'],
|
|
140
|
+
transform['rotation']['z'],
|
|
141
|
+
transform['rotation']['w'])
|
|
142
|
+
)
|
|
143
|
+
elif return_type == "homogeneous":
|
|
144
|
+
return HomogeneousMatrix(
|
|
145
|
+
matrix=self._transform_to_homogeneous(transform)
|
|
146
|
+
)
|
|
147
|
+
else:
|
|
148
|
+
SDKLogger.warn(f"Invalid return_type: {return_type}, using default(pose_quaternion)")
|
|
149
|
+
return self._parse_transform(transform, "pose_quaternion")
|
|
150
|
+
|
|
151
|
+
def _transform_to_homogeneous(self, transform) -> np.ndarray:
|
|
152
|
+
"""Converts transform dict to homogeneous matrix.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
transform (dict): Input transform message
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
np.ndarray: 4x4 homogeneous transformation matrix (numpy.float32)
|
|
159
|
+
"""
|
|
160
|
+
# 四元数转旋转矩阵
|
|
161
|
+
rotation = [
|
|
162
|
+
transform['rotation']['x'],
|
|
163
|
+
transform['rotation']['y'],
|
|
164
|
+
transform['rotation']['z'],
|
|
165
|
+
transform['rotation']['w']
|
|
166
|
+
]
|
|
167
|
+
|
|
168
|
+
# Convert quaternion to rotation matrix using transforms3d
|
|
169
|
+
rot_matrix = np.eye(4)
|
|
170
|
+
rot_matrix[:3,:3] = quaternions.quat2mat([rotation[3], rotation[0], rotation[1], rotation[2]])
|
|
171
|
+
|
|
172
|
+
# 设置平移分量
|
|
173
|
+
translation = [
|
|
174
|
+
transform['translation']['x'],
|
|
175
|
+
transform['translation']['y'],
|
|
176
|
+
transform['translation']['z']
|
|
177
|
+
]
|
|
178
|
+
rot_matrix[:3, 3] = translation
|
|
179
|
+
|
|
180
|
+
return rot_matrix.astype(np.float32) # 确保矩阵数据类型一致
|
|
181
|
+
|
|
182
|
+
# if __name__ == "__main__":
|
|
183
|
+
# robot_tools = KuavoRobotToolsCore()
|
|
184
|
+
# time.sleep(0.1)
|
|
185
|
+
# # 获取位姿信息
|
|
186
|
+
# pose = robot_tools._get_tf_tree_transform("odom", "base_link", return_type="pose_quaternion")
|
|
187
|
+
# print(f"Position: {pose.position}")
|
|
188
|
+
# print(f"Orientation: {pose.orientation}")
|
|
189
|
+
|
|
190
|
+
# # 获取齐次矩阵
|
|
191
|
+
# homogeneous = robot_tools._get_tf_tree_transform("odom", "base_link", return_type="homogeneous")
|
|
192
|
+
# print(f"Transformation matrix:\n{homogeneous.matrix}")
|
|
193
|
+
|
|
194
|
+
# # 矩阵运算示例
|
|
195
|
+
# transform_matrix = homogeneous.matrix
|
|
196
|
+
# inverse_matrix = np.linalg.inv(transform_matrix) # 求逆变换
|
|
197
|
+
# print(f"Inverse matrix:\n{inverse_matrix}")
|