ezgo 0.0.3__py3-none-any.whl → 0.0.5__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.
ezgo/go2.py ADDED
@@ -0,0 +1,913 @@
1
+ import time
2
+ import threading
3
+ import numpy as np
4
+ import cv2
5
+ import netifaces
6
+ import subprocess
7
+ from unitree_sdk2py.core.channel import ChannelSubscriber, ChannelFactoryInitialize
8
+ from unitree_sdk2py.go2.sport.sport_client import SportClient
9
+ from unitree_sdk2py.go2.video.video_client import VideoClient
10
+ from unitree_sdk2py.idl.unitree_go.msg.dds_ import SportModeState_
11
+
12
+
13
+ error_code = {
14
+ 100: "灵动",
15
+ 1001: "阻尼",
16
+ 1002: "站立锁定",
17
+ 1004: "蹲下",
18
+ 2006: "蹲下",
19
+ 1006: "打招呼/伸懒腰/舞蹈/拜年/比心/开心",
20
+ 1007: "坐下",
21
+ 1008: "前跳",
22
+ 1009: "扑人",
23
+ 1013: "平衡站立",
24
+ 1015: "常规行走",
25
+ 1016: "常规跑步",
26
+ 1017: "常规续航",
27
+ 1091: "摆姿势",
28
+ 2004: "翻身", # ???
29
+ 2007: "闪避",
30
+ 2008: "并腿跑",
31
+ 2009: "跳跃跑",
32
+ 2010: "经典",
33
+ 2011: "倒立",
34
+ 2012: "前空翻",
35
+ 2013: "后空翻",
36
+ 2014: "左空翻",
37
+ 2016: "交叉步",
38
+ 2017: "直立",
39
+ 2019: "牵引",
40
+ }
41
+
42
+
43
+ class Go2:
44
+ """
45
+ 宇树Go2 运动控制封装类
46
+ 适用于:Unitree SDK2 Python接口
47
+ """
48
+ def __init__(self, interface=None, timeout=20.0):
49
+ """
50
+ 初始化控制器
51
+ :param interface: 网卡接口名称
52
+ :param timeout: 超时时间(秒)
53
+ """
54
+ self.interface = interface
55
+ self.timeout = timeout
56
+ self.sport_client = None
57
+ self.video_client = None
58
+ self.cap = None
59
+ self.error_code = 0 # 状态码
60
+
61
+ # 移动控制相关变量
62
+ self._moving = False
63
+ self._move_params = (0, 0, 0)
64
+ self._move_thread = None
65
+
66
+ # 摄像头对象
67
+ self.camera = None
68
+
69
+ # 声光控制对象
70
+ self._vui = None
71
+
72
+ if self.interface is None:
73
+ self.get_interface()
74
+
75
+ # 清理标志
76
+ self._initialized = False
77
+
78
+ def get_interface(self):
79
+ """获取当前接口"""
80
+
81
+ interfaces = netifaces.interfaces()
82
+ for iface in interfaces:
83
+ if iface.startswith("en"):
84
+ self.interface = iface
85
+ break
86
+
87
+ def check_go2_connection(self):
88
+ """检查机器狗IP连通性"""
89
+ try:
90
+ # 先尝试不使用sudo ping
91
+ result = subprocess.run(['ping', '-c', '1', '-W', '2', '192.168.123.161'],
92
+ capture_output=True, text=True, timeout=5)
93
+ if result.returncode == 0:
94
+ return True
95
+
96
+ # 如果不使用sudo失败,尝试使用sudo
97
+ result = subprocess.run(['sudo', 'ping', '-c', '1', '-W', '2', '192.168.123.161'],
98
+ capture_output=True, text=True, timeout=5)
99
+ return result.returncode == 0
100
+ except subprocess.TimeoutExpired:
101
+ print("ping超时")
102
+ return False
103
+ except Exception as e:
104
+ print(f"检查连接时出错: {e}")
105
+ return False
106
+
107
+
108
+
109
+
110
+ def init(self):
111
+ """初始化与Go2的连接"""
112
+
113
+ # 检查机器狗IP连通性(仅作为警告,不阻止初始化)
114
+ if not self.check_go2_connection():
115
+ print("警告:无法ping通机器狗IP,但继续尝试初始化SDK")
116
+ print("请确保机器狗已开机且网络连接正常")
117
+
118
+ try:
119
+ ChannelFactoryInitialize(0, self.interface)
120
+
121
+ # 启动状态订阅
122
+ self.sub_state(self.callback)
123
+
124
+ # 初始化运动控制客户端
125
+ self.sport_client = SportClient()
126
+ self.sport_client.SetTimeout(self.timeout)
127
+ self.sport_client.Init()
128
+
129
+ # 注意:视频流不再自动初始化,按需开启
130
+ # self.cap = self.open_video() # 移除自动初始化
131
+
132
+ self._initialized = True
133
+ print("Go2 SDK初始化成功")
134
+ return True
135
+ except Exception as e:
136
+ print(f"SDK初始化失败: {e}")
137
+ return False
138
+
139
+
140
+ def open_video(self, width: int = 480, height: int = 320):
141
+ """打开视频流"""
142
+ gstreamer_str = (
143
+ f"udpsrc address=230.1.1.1 port=1720 multicast-iface={self.interface} "
144
+ "! application/x-rtp, media=video, encoding-name=H264 "
145
+ "! rtph264depay ! h264parse "
146
+ "! avdec_h264 " # 解码H.264
147
+ "! videoscale " # 添加缩放元素,用于调整分辨率
148
+ f"! video/x-raw,width={width},height={height} " # 目标分辨率
149
+ "! videoconvert ! video/x-raw, format=BGR " # 转换为OpenCV支持的BGR格式
150
+ "! appsink drop=1"
151
+ )
152
+ cap = cv2.VideoCapture(gstreamer_str, cv2.CAP_GSTREAMER)
153
+ if not cap.isOpened():
154
+ print("视频流打开失败")
155
+ print(f"使用的网络接口: {self.interface}")
156
+ print("GStreamer字符串:", gstreamer_str)
157
+ return None
158
+ print("视频流打开成功")
159
+ return cap
160
+
161
+ def read_image(self):
162
+ """从视频流获取一帧图像"""
163
+ if self.cap is None:
164
+ print("视频流未打开,请先调用 open_video()")
165
+ return None
166
+
167
+ ret, frame = self.cap.read()
168
+ if not ret:
169
+ print("读取图像失败")
170
+ return None
171
+ return frame
172
+
173
+
174
+ def sub_state(self, callback, queue_size: int = 5):
175
+ subscriber = ChannelSubscriber("rt/sportmodestate", SportModeState_)
176
+ subscriber.Init(callback, queue_size)
177
+
178
+ def callback(self, msg):
179
+ self.error_code = msg.error_code
180
+
181
+
182
+ # def read_image(self):
183
+ # """从视频流获取一帧图像"""
184
+ # code, data = self.video_client.GetImageSample()
185
+ # if code != 0 or data is None:
186
+ # print("获取图像样本失败,错误码:", code)
187
+ # return None
188
+ # image_data = np.frombuffer(bytes(data), dtype=np.uint8)
189
+ # image = cv2.imdecode(image_data, cv2.IMREAD_COLOR)
190
+ # return image
191
+
192
+ def Damp(self):
193
+ """进入阻尼状态。"""
194
+ self._call(self.sport_client.Damp)
195
+
196
+ def BalanceStand(self):
197
+ """解除锁定。"""
198
+ self._call(self.sport_client.BalanceStand)
199
+
200
+ def StopMove(self):
201
+ """
202
+ 停止机器狗的所有移动动作,并重置相关状态。
203
+
204
+ 该方法会:
205
+ 1. 停止任何正在执行的移动线程
206
+ 2. 调用底层SDK的停止方法
207
+ 3. 重置移动状态标志
208
+
209
+ 注意:
210
+ - 该方法会阻塞等待移动线程结束,最多等待1秒
211
+ - 调用后所有运动指令将被重置为默认状态
212
+ """
213
+ print("停止移动")
214
+ # 停止持续移动线程
215
+ self._moving = False
216
+ if (hasattr(self, '_move_thread') and
217
+ self._move_thread is not None and
218
+ self._move_thread.is_alive()):
219
+ self._move_thread.join(timeout=1.0)
220
+ self._move_thread = None
221
+
222
+ # 调用SDK的停止方法
223
+ self._call(self.sport_client.StopMove)
224
+
225
+ def _call(self, func, *args, **kwargs):
226
+ """在线程中调用运动控制函数"""
227
+ def fun_thread():
228
+ ret = func(*args, **kwargs)
229
+ print(f"{func.__name__} 执行结果:", ret)
230
+
231
+ t = threading.Thread(target=fun_thread)
232
+ t.start()
233
+ t.join() # 等待线程完成
234
+
235
+
236
+
237
+ def StandUp(self):
238
+ """
239
+ 关节锁定,站高。
240
+ 执行后状态: 1002:站立锁定
241
+ """
242
+ # 执行前判断状态
243
+ if self.error_code in [1002]: # 1002 :站立锁定
244
+ print("当前状态:", self.error_code, error_code[self.error_code], "无需执行")
245
+ return
246
+ if self.error_code not in [100, 1001, 1007, 1013]: # 100 :灵动, 1001 :阻尼, 1013 :平衡站立
247
+ print("繁忙中,当前状态:", self.error_code, error_code[self.error_code])
248
+ return
249
+ self._call(self.sport_client.StandUp)
250
+
251
+
252
+
253
+ def StandDown(self):
254
+ """
255
+ 关节锁定,站低。
256
+ 执行后状态: 1001:阻尼
257
+ """
258
+ # 执行前判断状态
259
+ if self.error_code in [1001, 1004, 2006]: # 1001:阻尼 1004 :蹲下 2006 :蹲下
260
+ print("当前状态:", self.error_code, error_code[self.error_code], "无需执行")
261
+ return
262
+ if self.error_code not in [100, 1002, 1013]: # 100 :灵动, 1001 :阻尼, 1013 :平衡站立
263
+ print("繁忙中,当前状态:", self.error_code, error_code[self.error_code])
264
+ return
265
+
266
+ # 执行指令
267
+ self._call(self.sport_client.StandDown)
268
+
269
+ def RecoveryStand(self):
270
+ """ 恢复站立。"""
271
+ self._call(self.sport_client.RecoveryStand)
272
+
273
+ def Euler(self, roll, pitch, yaw):
274
+ """站立和行走时的姿态。"""
275
+ pass
276
+
277
+ def Move(self, vx, vy, vyaw):
278
+ """移动。"""
279
+ # 检查状态是否允许移动
280
+ if self.error_code not in [100, 1002, 1013]: # 100:灵动, 1002:站立锁定, 1013:平衡站立
281
+ print(f"当前状态不允许移动: {self.error_code} - {error_code.get(self.error_code, '未知状态')}")
282
+ return False
283
+
284
+ # 限制速度范围
285
+ vx = max(-1.0, min(1.0, vx)) # 前后速度限制在-1到1之间
286
+ vy = max(-1.0, min(1.0, vy)) # 左右速度限制在-1到1之间
287
+ vyaw = max(-2.0, min(2.0, vyaw)) # 转动速度限制在-2到2之间
288
+
289
+ def move_thread():
290
+ ret = self.sport_client.Move(vx, vy, vyaw)
291
+ success = ret == 0
292
+ print(f"Move 执行结果: {ret}, 成功: {success}")
293
+ return success
294
+
295
+ t = threading.Thread(target=move_thread)
296
+ t.start()
297
+ t.join()
298
+ return True
299
+
300
+ def MoveForDuration(self, vx, vy, vyaw, duration):
301
+ """
302
+ 持续移动指定时间
303
+
304
+ Args:
305
+ vx (float): 前后速度 (-1.0 到 1.0)
306
+ vy (float): 左右速度 (-1.0 到 1.0)
307
+ vyaw (float): 转动速度 (-2.0 到 2.0)
308
+ duration (float): 移动时间(秒)
309
+
310
+ Returns:
311
+ bool: 是否成功执行
312
+ """
313
+ print(f"开始移动: vx={vx}, vy={vy}, vyaw={vyaw}, 持续时间={duration}秒")
314
+ start_time = time.time()
315
+
316
+ try:
317
+ while time.time() - start_time < duration:
318
+ if not self.Move(vx, vy, vyaw):
319
+ return False
320
+ time.sleep(0.1) # 每100ms调用一次
321
+
322
+ print("移动完成")
323
+ return True
324
+
325
+ except KeyboardInterrupt:
326
+ print("移动被用户中断")
327
+ return False
328
+
329
+ def Forward(self, speed=0.3, duration=2.0):
330
+ """向前移动"""
331
+ return self.MoveForDuration(speed, 0, 0, duration)
332
+
333
+ def Backward(self, speed=0.3, duration=2.0):
334
+ """向后移动"""
335
+ return self.MoveForDuration(-speed, 0, 0, duration)
336
+
337
+ def Left(self, speed=0.3, duration=2.0):
338
+ """向左移动"""
339
+ return self.MoveForDuration(0, speed, 0, duration)
340
+
341
+ def Right(self, speed=0.3, duration=2.0):
342
+ """向右移动"""
343
+ return self.MoveForDuration(0, -speed, 0, duration)
344
+
345
+ def TurnLeft(self, speed=0.5, duration=2.0):
346
+ """左转"""
347
+ return self.MoveForDuration(0, 0, speed, duration)
348
+
349
+ def TurnRight(self, speed=0.5, duration=2.0):
350
+ """右转"""
351
+ return self.MoveForDuration(0, 0, -speed, duration)
352
+
353
+ def StartMove(self, vx, vy, vyaw):
354
+ """
355
+ 开始持续移动,需要调用StopMove来停止
356
+
357
+ Args:
358
+ vx (float): 前后速度 (-1.0 到 1.0)
359
+ vy (float): 左右速度 (-1.0 到 1.0)
360
+ vyaw (float): 转动速度 (-2.0 到 2.0)
361
+
362
+ Returns:
363
+ bool: 是否成功开始移动
364
+ """
365
+ print(f"开始持续移动: vx={vx}, vy={vy}, vyaw={vyaw}")
366
+
367
+ # 先检查是否可以移动
368
+ if not self.Move(vx, vy, vyaw):
369
+ print("无法开始持续移动:当前状态不允许移动")
370
+ return False
371
+
372
+ self._moving = True
373
+ self._move_params = (vx, vy, vyaw)
374
+
375
+ def continuous_move():
376
+ while self._moving:
377
+ if not self.Move(vx, vy, vyaw):
378
+ print("持续移动失败,停止移动")
379
+ break
380
+ time.sleep(0.1)
381
+
382
+ self._move_thread = threading.Thread(target=continuous_move)
383
+ self._move_thread.daemon = True # 设置为守护线程
384
+ self._move_thread.start()
385
+ return True
386
+
387
+ def StartForward(self, speed=0.3):
388
+ """开始向前移动"""
389
+ return self.StartMove(speed, 0, 0)
390
+
391
+ def StartBackward(self, speed=0.3):
392
+ """开始向后移动"""
393
+ return self.StartMove(-speed, 0, 0)
394
+
395
+ def StartLeft(self, speed=0.3):
396
+ """开始向左移动"""
397
+ return self.StartMove(0, speed, 0)
398
+
399
+ def StartRight(self, speed=0.3):
400
+ """开始向右移动"""
401
+ return self.StartMove(0, -speed, 0)
402
+
403
+ def StartTurnLeft(self, speed=0.5):
404
+ """开始左转"""
405
+ return self.StartMove(0, 0, speed)
406
+
407
+ def StartTurnRight(self, speed=0.5):
408
+ """开始右转"""
409
+ return self.StartMove(0, 0, -speed)
410
+
411
+ def Sit(self):
412
+ """
413
+ 坐下。
414
+ 执行后状态: 1007: 坐下
415
+ """
416
+ # 执行前判断状态
417
+ if self.error_code in [1007]: # 1007 : 坐下
418
+ print("当前状态:", self.error_code, error_code[self.error_code], "无需执行")
419
+ return
420
+ if self.error_code not in [100, 1002, 1013]: # 100 :灵动, 1001 :阻尼, 1013 :平衡站立
421
+ print("繁忙中,当前状态:", self.error_code, error_code[self.error_code])
422
+ return
423
+
424
+ # 执行指令
425
+ self._call(self.sport_client.Sit)
426
+
427
+ def RiseSit(self):
428
+ """
429
+ 站起(相对于坐下)。
430
+ 执行后状态:
431
+ """
432
+ # 执行前判断状态
433
+ if self.error_code in [1007]: # 1002 :站立锁定
434
+ print("当前状态:", self.error_code, error_code[self.error_code], "无需执行")
435
+ return
436
+ if self.error_code not in [100, 1007, 1013]: # 100 :灵动, 1007 : 坐下, 1013 :平衡站立
437
+ print("繁忙中,当前状态:", self.error_code, error_code[self.error_code])
438
+ return
439
+
440
+ # 执行指令
441
+ self._call(self.sport_client.RiseSit)
442
+
443
+
444
+
445
+ def SpeedLevel(self, level: int):
446
+ """设置速度档位。"""
447
+ pass
448
+
449
+ def Hello(self):
450
+ """
451
+ 打招呼
452
+ 执行后状态: 1013:平衡站立
453
+ """
454
+ # 执行前判断状态
455
+ if self.error_code in [1006]: # 1006 :打招呼/伸懒腰/舞蹈/拜年/比心/开心
456
+ print("当前状态:", self.error_code, error_code[self.error_code], "无需执行")
457
+ return
458
+ if self.error_code not in [100, 1002, 1013]: # 空闲状态
459
+ print("繁忙中,当前状态:", self.error_code, error_code[self.error_code])
460
+ return
461
+
462
+ # 执行指令
463
+ self._call(self.sport_client.Hello)
464
+
465
+ def Stretch(self):
466
+ """
467
+ 伸懒腰。
468
+ 执行后状态: 1013:平衡站立
469
+ """
470
+ # 执行前判断状态
471
+ if self.error_code in [1006]: # 1006 :打招呼/伸懒腰/舞蹈/拜年/比心/开心
472
+ print("当前状态:", self.error_code, error_code[self.error_code], "无需执行")
473
+ return
474
+ if self.error_code not in [100, 1002, 1013]: # 站立且空闲状态
475
+ print("繁忙中,当前状态:", self.error_code, error_code[self.error_code])
476
+ return
477
+
478
+ # 执行指令
479
+ self._call(self.sport_client.Stretch)
480
+
481
+
482
+
483
+ def Content(self):
484
+ """
485
+ 开心。
486
+ 执行后状态: 1013:平衡站立
487
+ """
488
+ # 执行前判断状态
489
+ if self.error_code in [1006]: # 1006 :打招呼/伸懒腰/舞蹈/拜年/比心/开心
490
+ print("当前状态:", self.error_code, error_code[self.error_code], "无需执行")
491
+ return
492
+ if self.error_code not in [100, 1002, 1013]: # 空闲状态
493
+ print("繁忙中,当前状态:", self.error_code, error_code[self.error_code])
494
+ return
495
+
496
+ # 执行指令
497
+ self._call(self.sport_client.Content)
498
+
499
+
500
+ def Heart(self):
501
+ """
502
+ 比心。
503
+ 执行后状态: 1013:平衡站立
504
+ """
505
+ # 执行前判断状态
506
+ if self.error_code in [1006]: # 1006 :打招呼/伸懒腰/舞蹈/拜年/比心/开心
507
+ print("当前状态:", self.error_code, error_code[self.error_code], "无需执行")
508
+ return
509
+ if self.error_code not in [100, 1002, 1013]: # 空闲状态
510
+ print("繁忙中,当前状态:", self.error_code, error_code[self.error_code])
511
+ return
512
+
513
+ # 执行指令
514
+ self._call(self.sport_client.Heart)
515
+
516
+
517
+ def Pose(self, flag):
518
+ """摆姿势。"""
519
+ pass
520
+
521
+ def Scrape(self):
522
+ """
523
+ 拜年作揖。
524
+ 执行后状态: 1013:平衡站立
525
+ """
526
+ # 执行前判断状态
527
+ if self.error_code in [1006]: # 1006 :打招呼/伸懒腰/舞蹈/拜年/比心/开心
528
+ print("当前状态:", self.error_code, error_code[self.error_code], "无需执行")
529
+ return
530
+ if self.error_code not in [100, 1002, 1013]: # 空闲状态
531
+ print("繁忙中,当前状态:", self.error_code, error_code[self.error_code])
532
+ return
533
+
534
+ # 执行指令
535
+ self._call(self.sport_client.Scrape)
536
+
537
+
538
+
539
+ def FrontJump(self):
540
+ """
541
+ 前跳。
542
+ 执行后状态: 1013: 平衡站立
543
+ """
544
+ # 执行前判断状态
545
+ if self.error_code in [1008]: # 1008 : 前跳
546
+ print("当前状态:", self.error_code, error_code[self.error_code], "无需执行")
547
+ return
548
+ if self.error_code not in [100, 1002, 1013]: # 空闲状态
549
+ print("繁忙中,当前状态:", self.error_code, error_code[self.error_code])
550
+ return
551
+
552
+ # 执行指令
553
+ self._call(self.sport_client.FrontJump)
554
+
555
+
556
+
557
+ def FrontPounce(self):
558
+ """
559
+ 向前扑人。
560
+ 执行后状态: 1013: 平衡站立
561
+ """
562
+ # 执行前判断状态
563
+ if self.error_code in [1009]: # 1009 : 扑人
564
+ print("当前状态:", self.error_code, error_code[self.error_code], "无需执行")
565
+ return
566
+ if self.error_code not in [100, 1002, 1013]: # 空闲状态
567
+ print("繁忙中,当前状态:", self.error_code, error_code[self.error_code])
568
+ return
569
+
570
+ # 执行指令
571
+ self._call(self.sport_client.FrontPounce)
572
+
573
+
574
+ def Dance1(self):
575
+ """
576
+ 舞蹈段落1。
577
+ 执行后状态: 1013: 平衡站立
578
+ """
579
+ # 执行前判断状态
580
+ if self.error_code in [1006]: # 1006 :打招呼/伸懒腰/舞蹈/拜年/比心/开心
581
+ print("当前状态:", self.error_code, error_code[self.error_code], "无需执行")
582
+ return
583
+ if self.error_code not in [100, 1002, 1013]: # 空闲状态
584
+ print("繁忙中,当前状态:", self.error_code, error_code[self.error_code])
585
+ return
586
+
587
+ # 执行指令
588
+ self._call(self.sport_client.Dance1)
589
+
590
+ def Dance2(self):
591
+ """
592
+ 舞蹈段落2。
593
+ 执行后状态: 1013: 平衡站立
594
+ """
595
+ # 执行前判断状态
596
+ if self.error_code in [1006]: # 1006 :打招呼/伸懒腰/舞蹈/拜年/比心/开心
597
+ print("当前状态:", self.error_code, error_code[self.error_code], "无需执行")
598
+ return
599
+ if self.error_code not in [100, 1002, 1013]: # 空闲状态
600
+ print("繁忙中,当前状态:", self.error_code, error_code[self.error_code])
601
+ return
602
+
603
+ # 执行指令
604
+ self._call(self.sport_client.Dance2)
605
+
606
+ def HandStand(self, flag: int):
607
+ """倒立行走。"""
608
+ # 执行指令
609
+ self._call(lambda: self.sport_client.HandStand(bool(flag)))
610
+ if flag:
611
+ time.sleep(4)
612
+ self._call(lambda: self.sport_client.HandStand(False))
613
+
614
+ def LeftFlip(self):
615
+ """左空翻。"""
616
+ # 执行指令
617
+ result = self._call(self.sport_client.LeftFlip)
618
+ return result == 0 if result is not None else False
619
+
620
+ def BackFlip(self):
621
+ """后空翻。"""
622
+ # 执行指令
623
+ result = self._call(self.sport_client.BackFlip)
624
+ return result == 0 if result is not None else False
625
+
626
+ def FreeWalk(self, flag: int = None):
627
+ """ 灵动模式(默认步态)。"""
628
+ # 执行指令
629
+ if flag is None:
630
+ # 切换模式
631
+ result = self._call(lambda: self.sport_client.FreeWalk())
632
+ else:
633
+ # 根据flag值执行相应操作
634
+ if flag:
635
+ # 开启灵动模式
636
+ result = self._call(lambda: self.sport_client.FreeWalk())
637
+ else:
638
+ # 关闭灵动模式 - 切换到其他模式
639
+ result = self._call(lambda: self.sport_client.StandUp())
640
+ return result == 0 if result is not None else False
641
+
642
+ def FreeBound(self, flag: int):
643
+ """ 并腿跑模式。"""
644
+ # 执行指令
645
+ result = self._call(lambda: self.sport_client.FreeBound(bool(flag)))
646
+ success = result == 0 if result is not None else False
647
+
648
+ if flag and success:
649
+ # 如果成功开启,等待2秒后自动关闭(这是为了演示效果)
650
+ time.sleep(2)
651
+ self._call(lambda: self.sport_client.FreeBound(False))
652
+
653
+ return success
654
+
655
+ def FreeJump(self, flag: int):
656
+ """ 跳跃模式。"""
657
+ # 执行指令
658
+ result = self._call(lambda: self.sport_client.FreeJump(bool(flag)))
659
+ success = result == 0 if result is not None else False
660
+
661
+ if flag and success:
662
+ # 如果成功开启,等待4秒后自动关闭(这是为了演示效果)
663
+ time.sleep(4)
664
+ self._call(lambda: self.sport_client.FreeJump(False))
665
+
666
+ return success
667
+
668
+ def FreeAvoid(self, flag: int):
669
+ """ 闪避模式。"""
670
+ # 执行指令
671
+ result = self._call(lambda: self.sport_client.FreeAvoid(bool(flag)))
672
+ success = result == 0 if result is not None else False
673
+
674
+ if flag and success:
675
+ # 如果成功开启,等待2秒后自动关闭(这是为了演示效果)
676
+ time.sleep(2)
677
+ self._call(lambda: self.sport_client.FreeAvoid(False))
678
+
679
+ return success
680
+
681
+ def WalkUpright(self, flag: int):
682
+ """ 后腿直立模式。"""
683
+ # 执行指令
684
+ result = self._call(lambda: self.sport_client.WalkUpright(bool(flag)))
685
+ success = result == 0 if result is not None else False
686
+
687
+ if flag and success:
688
+ # 如果成功开启,等待4秒后自动关闭(这是为了演示效果)
689
+ time.sleep(4)
690
+ self._call(lambda: self.sport_client.WalkUpright(False))
691
+
692
+ return success
693
+
694
+ def CrossStep(self, flag: int):
695
+ """ 交叉步模式。"""
696
+ # 执行指令
697
+ result = self._call(lambda: self.sport_client.CrossStep(bool(flag)))
698
+ success = result == 0 if result is not None else False
699
+
700
+ if flag and success:
701
+ # 如果成功开启,等待4秒后自动关闭(这是为了演示效果)
702
+ time.sleep(4)
703
+ self._call(lambda: self.sport_client.CrossStep(False))
704
+
705
+ return success
706
+
707
+ def AutoRecoverSet(self, flag: int):
708
+ """ 设置自动翻身是否生效。"""
709
+ try:
710
+ # 执行指令
711
+ result = self._call(lambda: self.sport_client.AutoRecoverSet(bool(flag)))
712
+ return result == 0 if result is not None else False
713
+ except AttributeError:
714
+ print("警告: AutoRecoverSet 方法在当前SDK版本中不可用")
715
+ return False
716
+
717
+ def AutoRecoverGet(self):
718
+ """ 查询自动翻身是否生效。"""
719
+ try:
720
+ # 执行指令
721
+ result = self._call(self.sport_client.AutoRecoverGet)
722
+ return result if result is not None else False
723
+ except AttributeError:
724
+ print("警告: AutoRecoverGet 方法在当前SDK版本中不可用")
725
+ return False
726
+
727
+ def ClassicWalk(self, flag: int):
728
+ """ 经典步态。"""
729
+ try:
730
+ # 执行指令
731
+ result = self._call(lambda: self.sport_client.ClassicWalk(bool(flag)))
732
+ return result == 0 if result is not None else False
733
+ except AttributeError:
734
+ print("警告: ClassicWalk 方法在当前SDK版本中不可用")
735
+ return False
736
+
737
+ def TrotRun(self):
738
+ """ 进入常规跑步模式 """
739
+ try:
740
+ # 执行指令
741
+ result = self._call(self.sport_client.TrotRun)
742
+ return result == 0 if result is not None else False
743
+ except AttributeError:
744
+ print("警告: TrotRun 方法在当前SDK版本中不可用")
745
+ return False
746
+
747
+ def StaticWalk(self):
748
+ """ 进入常规行走模式"""
749
+ try:
750
+ # 执行指令
751
+ result = self._call(self.sport_client.StaticWalk)
752
+ return result == 0 if result is not None else False
753
+ except AttributeError:
754
+ print("警告: StaticWalk 方法在当前SDK版本中不可用")
755
+ return False
756
+
757
+ def EconomicGait(self):
758
+ """ 进入常规续航模式 """
759
+ try:
760
+ # 执行指令
761
+ result = self._call(self.sport_client.EconomicGait)
762
+ return result == 0 if result is not None else False
763
+ except AttributeError:
764
+ print("警告: EconomicGait 方法在当前SDK版本中不可用")
765
+ return False
766
+
767
+ def SwitchAvoidMode(self):
768
+ """ 闪避模式下,关闭摇杆未推时前方障碍物的闪避以及后方的障碍物躲避"""
769
+ try:
770
+ # 执行指令
771
+ result = self._call(self.sport_client.SwitchAvoidMode)
772
+ return result == 0 if result is not None else False
773
+ except AttributeError:
774
+ print("警告: SwitchAvoidMode 方法在当前SDK版本中不可用")
775
+ return False
776
+
777
+ def get_camera(self):
778
+ """
779
+ 获取摄像头对象(按需初始化)
780
+
781
+ Returns:
782
+ Go2Camera: 摄像头控制对象
783
+ """
784
+ if self.camera is None:
785
+ # 直接导入go2_camera模块
786
+ from .go2_camera import Go2Camera
787
+ self.camera = Go2Camera(self.interface, self.timeout)
788
+ # 注意:这里不自动初始化,让用户按需调用
789
+ return self.camera
790
+
791
+ def get_vui(self):
792
+ """
793
+ 获取声光控制对象(按需初始化)
794
+
795
+ Returns:
796
+ Go2VUI: 声光控制对象
797
+ """
798
+ if not hasattr(self, '_vui') or self._vui is None:
799
+ # 直接导入go2_vui模块
800
+ from .go2_vui import Go2VUI
801
+ self._vui = Go2VUI(self.interface)
802
+ # 注意:这里不自动初始化,让用户按需调用
803
+ return self._vui
804
+
805
+ def capture_image(self, save_path=None):
806
+ """
807
+ 便利方法:获取一张图片(按需初始化摄像头)
808
+
809
+ Args:
810
+ save_path (str): 保存路径
811
+
812
+ Returns:
813
+ numpy.ndarray: 图像数据
814
+ """
815
+ camera = self.get_camera()
816
+ if not camera.init(self.interface):
817
+ print("摄像头初始化失败")
818
+ return None
819
+ return camera.capture_image(save_path)
820
+
821
+ def start_video_stream(self, width=480, height=320):
822
+ """
823
+ 便利方法:开始视频流(按需初始化摄像头)
824
+
825
+ Args:
826
+ width (int): 视频宽度
827
+ height (int): 视频高度
828
+
829
+ Returns:
830
+ bool: 是否成功
831
+ """
832
+ camera = self.get_camera()
833
+ if not camera.init(self.interface):
834
+ print("摄像头初始化失败")
835
+ return False
836
+ return camera.start_stream(width, height)
837
+
838
+ def get_video_frame(self):
839
+ """
840
+ 便利方法:获取最新视频帧
841
+
842
+ Returns:
843
+ numpy.ndarray: 图像数据
844
+ """
845
+ if self.camera is None:
846
+ print("视频流未启动,请先调用 start_video_stream()")
847
+ return None
848
+ return self.camera.get_latest_frame()
849
+
850
+ def stop_video_stream(self):
851
+ """便利方法:停止视频流"""
852
+ if self.camera:
853
+ self.camera.stop_stream()
854
+
855
+ def cleanup(self):
856
+ """清理资源,停止所有连接和线程"""
857
+ if not self._initialized:
858
+ return
859
+
860
+ print("正在清理Go2资源...")
861
+
862
+ # 停止移动线程
863
+ self._moving = False
864
+ if (hasattr(self, '_move_thread') and
865
+ self._move_thread is not None and
866
+ self._move_thread.is_alive()):
867
+ self._move_thread.join(timeout=1.0)
868
+ self._move_thread = None
869
+
870
+ # 清理摄像头资源
871
+ if self.camera is not None:
872
+ try:
873
+ self.camera.cleanup()
874
+ self.camera = None
875
+ except:
876
+ pass
877
+
878
+ # 释放视频流
879
+ if self.cap is not None:
880
+ try:
881
+ self.cap.release()
882
+ self.cap = None
883
+ except:
884
+ pass
885
+
886
+ # 清理DDS连接
887
+ try:
888
+ from unitree_sdk2py.core.channel import ChannelFactoryRelease
889
+ ChannelFactoryRelease()
890
+ except:
891
+ pass
892
+
893
+ self._initialized = False
894
+ print("Go2资源清理完成")
895
+
896
+ def __del__(self):
897
+ """析构函数,自动清理资源"""
898
+ try:
899
+ self.cleanup()
900
+ except:
901
+ pass
902
+
903
+
904
+
905
+
906
+ # -------------------- 测试 --------------------
907
+ if __name__ == "__main__":
908
+ interface = "enx00e0986113a6" # 替换为你的Go2网卡接口名称
909
+
910
+ go2 = Go2(interface=interface)
911
+
912
+ go2.stand_down()
913
+ go2.stand_up()