kuavo-humanoid-sdk-ws 1.3.2b397__20260202203030-py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. kuavo_humanoid_sdk/__init__.py +6 -0
  2. kuavo_humanoid_sdk/common/launch_robot_tool.py +170 -0
  3. kuavo_humanoid_sdk/common/logger.py +45 -0
  4. kuavo_humanoid_sdk/common/websocket_kuavo_sdk.py +26 -0
  5. kuavo_humanoid_sdk/interfaces/__init__.py +4 -0
  6. kuavo_humanoid_sdk/interfaces/data_types.py +293 -0
  7. kuavo_humanoid_sdk/interfaces/end_effector.py +62 -0
  8. kuavo_humanoid_sdk/interfaces/robot.py +22 -0
  9. kuavo_humanoid_sdk/interfaces/robot_info.py +56 -0
  10. kuavo_humanoid_sdk/kuavo/__init__.py +12 -0
  11. kuavo_humanoid_sdk/kuavo/core/audio.py +32 -0
  12. kuavo_humanoid_sdk/kuavo/core/core.py +755 -0
  13. kuavo_humanoid_sdk/kuavo/core/dex_hand_control.py +114 -0
  14. kuavo_humanoid_sdk/kuavo/core/leju_claw_control.py +67 -0
  15. kuavo_humanoid_sdk/kuavo/core/ros/audio.py +86 -0
  16. kuavo_humanoid_sdk/kuavo/core/ros/control.py +1325 -0
  17. kuavo_humanoid_sdk/kuavo/core/ros/observation.py +125 -0
  18. kuavo_humanoid_sdk/kuavo/core/ros/param.py +335 -0
  19. kuavo_humanoid_sdk/kuavo/core/ros/sat_utils.py +103 -0
  20. kuavo_humanoid_sdk/kuavo/core/ros/state.py +426 -0
  21. kuavo_humanoid_sdk/kuavo/core/ros/tools.py +197 -0
  22. kuavo_humanoid_sdk/kuavo/core/ros/vision.py +280 -0
  23. kuavo_humanoid_sdk/kuavo/core/ros_env.py +236 -0
  24. kuavo_humanoid_sdk/kuavo/dexterous_hand.py +198 -0
  25. kuavo_humanoid_sdk/kuavo/leju_claw.py +232 -0
  26. kuavo_humanoid_sdk/kuavo/robot.py +550 -0
  27. kuavo_humanoid_sdk/kuavo/robot_arm.py +235 -0
  28. kuavo_humanoid_sdk/kuavo/robot_audio.py +39 -0
  29. kuavo_humanoid_sdk/kuavo/robot_head.py +39 -0
  30. kuavo_humanoid_sdk/kuavo/robot_info.py +235 -0
  31. kuavo_humanoid_sdk/kuavo/robot_observation.py +69 -0
  32. kuavo_humanoid_sdk/kuavo/robot_state.py +346 -0
  33. kuavo_humanoid_sdk/kuavo/robot_tool.py +58 -0
  34. kuavo_humanoid_sdk/kuavo/robot_vision.py +82 -0
  35. kuavo_humanoid_sdk/kuavo/robot_waist.py +53 -0
  36. kuavo_humanoid_sdk/kuavo_strategy/__init__.py +2 -0
  37. kuavo_humanoid_sdk/kuavo_strategy/grasp_box/grasp_box_strategy.py +418 -0
  38. kuavo_humanoid_sdk/kuavo_strategy/kuavo_strategy.py +63 -0
  39. kuavo_humanoid_sdk/msg/__init__.py +4 -0
  40. kuavo_humanoid_sdk_ws-1.3.2b397.dist-info/METADATA +279 -0
  41. kuavo_humanoid_sdk_ws-1.3.2b397.dist-info/RECORD +43 -0
  42. kuavo_humanoid_sdk_ws-1.3.2b397.dist-info/WHEEL +6 -0
  43. kuavo_humanoid_sdk_ws-1.3.2b397.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}")