ezgo 0.0.1__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/__init__.py +81 -0
- ezgo/camera.py +71 -0
- ezgo/go2.py +633 -0
- ezgo/ui.py +77 -0
- ezgo-0.0.1.dist-info/LICENSE +21 -0
- ezgo-0.0.1.dist-info/METADATA +295 -0
- ezgo-0.0.1.dist-info/RECORD +9 -0
- ezgo-0.0.1.dist-info/WHEEL +5 -0
- ezgo-0.0.1.dist-info/top_level.txt +1 -0
ezgo/__init__.py
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
ezgo - 宇树Go2机器狗Python控制库
|
|
6
|
+
|
|
7
|
+
这是一个用于控制宇树Go2机器狗的Python库,提供了简单易用的API接口。
|
|
8
|
+
支持运动控制、视频流获取、UI界面等功能。
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
__version__ = "0.0.1"
|
|
12
|
+
__author__ = "ezgo"
|
|
13
|
+
__email__ = ""
|
|
14
|
+
__license__ = "MIT"
|
|
15
|
+
|
|
16
|
+
# 导入主要类
|
|
17
|
+
try:
|
|
18
|
+
from .go2 import Go2
|
|
19
|
+
from .camera import Camera
|
|
20
|
+
from .ui import APP
|
|
21
|
+
except ImportError as e:
|
|
22
|
+
print(f"警告: 无法导入部分模块: {e}")
|
|
23
|
+
print("请确保已安装所有依赖包")
|
|
24
|
+
Go2 = None
|
|
25
|
+
Camera = None
|
|
26
|
+
APP = None
|
|
27
|
+
|
|
28
|
+
# 定义公开的API
|
|
29
|
+
__all__ = [
|
|
30
|
+
"Go2",
|
|
31
|
+
"Camera",
|
|
32
|
+
"APP",
|
|
33
|
+
"__version__"
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
# 依赖检查和提示
|
|
37
|
+
def _check_dependencies():
|
|
38
|
+
"""检查关键依赖是否安装,并给出安装提示"""
|
|
39
|
+
missing_deps = []
|
|
40
|
+
|
|
41
|
+
try:
|
|
42
|
+
import cv2
|
|
43
|
+
except ImportError:
|
|
44
|
+
missing_deps.append("opencv-python")
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
import numpy
|
|
48
|
+
except ImportError:
|
|
49
|
+
missing_deps.append("numpy")
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
from PIL import Image
|
|
53
|
+
except ImportError:
|
|
54
|
+
missing_deps.append("Pillow")
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
import netifaces
|
|
58
|
+
except ImportError:
|
|
59
|
+
missing_deps.append("netifaces")
|
|
60
|
+
|
|
61
|
+
try:
|
|
62
|
+
import unitree_sdk2py
|
|
63
|
+
except ImportError:
|
|
64
|
+
missing_deps.append("unitree-sdk2py")
|
|
65
|
+
|
|
66
|
+
if missing_deps:
|
|
67
|
+
print("=" * 60)
|
|
68
|
+
print("警告: ezgo 缺少以下依赖包:")
|
|
69
|
+
for dep in missing_deps:
|
|
70
|
+
print(f" - {dep}")
|
|
71
|
+
print()
|
|
72
|
+
print("请安装缺少的依赖包:")
|
|
73
|
+
print(" pip install opencv-python numpy Pillow netifaces")
|
|
74
|
+
print(" # unitree-sdk2py 需要从官方源安装")
|
|
75
|
+
print("=" * 60)
|
|
76
|
+
return False
|
|
77
|
+
|
|
78
|
+
return True
|
|
79
|
+
|
|
80
|
+
# 在导入时进行依赖检查
|
|
81
|
+
_check_dependencies()
|
ezgo/camera.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import cv2
|
|
2
|
+
from PIL import Image, ImageTk
|
|
3
|
+
import platform
|
|
4
|
+
|
|
5
|
+
class Camera:
|
|
6
|
+
def __init__(self, index=0, width=640, height=480, fps=30,):
|
|
7
|
+
self.index = index
|
|
8
|
+
self.width = width
|
|
9
|
+
self.height = height
|
|
10
|
+
self.fps = fps
|
|
11
|
+
|
|
12
|
+
self.cap = None
|
|
13
|
+
# self.open_camera()
|
|
14
|
+
|
|
15
|
+
def open_camera(self):
|
|
16
|
+
|
|
17
|
+
if platform.system() == "Linux":
|
|
18
|
+
self.cap = cv2.VideoCapture(self.index, cv2.CAP_V4L2) # 启用 V4L2 硬件加速
|
|
19
|
+
fourcc = cv2.VideoWriter_fourcc(*'MJPG') # 强制硬件压缩的 MJPG 格式(利用 FFMPEG 加速)
|
|
20
|
+
self.cap.set(cv2.CAP_PROP_FOURCC, fourcc)
|
|
21
|
+
else:
|
|
22
|
+
self.cap = cv2.VideoCapture(self.index)
|
|
23
|
+
|
|
24
|
+
self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, self.width)
|
|
25
|
+
self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, self.height)
|
|
26
|
+
self.cap.set(cv2.CAP_PROP_FPS, self.fps)
|
|
27
|
+
self.cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) # 清空缓存
|
|
28
|
+
self.cap.set(cv2.CAP_PROP_AUTOFOCUS, 0)
|
|
29
|
+
|
|
30
|
+
if not self.cap.isOpened():
|
|
31
|
+
raise Exception("无法打开摄像头")
|
|
32
|
+
actual_width = int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH))
|
|
33
|
+
actual_height = int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
|
34
|
+
print(f"打开摄像头 {self.index},分辨率 {actual_width}x{actual_height},帧率 {self.fps}")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def read_cv2_image(self):
|
|
38
|
+
ret, frame = self.cap.read()
|
|
39
|
+
if not ret:
|
|
40
|
+
raise Exception("无法读取摄像头帧")
|
|
41
|
+
return frame
|
|
42
|
+
|
|
43
|
+
def read_pil_image(self):
|
|
44
|
+
"""将BGR帧转换为PIL"""
|
|
45
|
+
frame = self.read_cv2_image()
|
|
46
|
+
return self.cv2_to_pil(frame)
|
|
47
|
+
|
|
48
|
+
def cv2_to_tk(self, frame):
|
|
49
|
+
"""将BGR帧转换为Tkinter PhotoImage"""
|
|
50
|
+
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
|
51
|
+
frame_pil = Image.fromarray(frame_rgb)
|
|
52
|
+
frame_tk = ImageTk.PhotoImage(image=frame_pil)
|
|
53
|
+
return frame_tk
|
|
54
|
+
|
|
55
|
+
def resize(self, frame, size=(224, 224)):
|
|
56
|
+
"""将图像调整为指定尺寸"""
|
|
57
|
+
return cv2.resize(frame, size, interpolation=cv2.INTER_LINEAR)
|
|
58
|
+
|
|
59
|
+
def crop_to_square(self, frame):
|
|
60
|
+
"""将图像裁剪为正方形"""
|
|
61
|
+
h, w = frame.shape[:2]
|
|
62
|
+
min_dim = min(h, w)
|
|
63
|
+
y = (h - min_dim) // 2
|
|
64
|
+
x = (w - min_dim) // 2
|
|
65
|
+
return frame[y:y+min_dim, x:x+min_dim]
|
|
66
|
+
|
|
67
|
+
def __del__(self):
|
|
68
|
+
"""释放摄像头资源"""
|
|
69
|
+
if self.cap is not None:
|
|
70
|
+
self.cap.release()
|
|
71
|
+
|
ezgo/go2.py
ADDED
|
@@ -0,0 +1,633 @@
|
|
|
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
|
+
if self.interface is None:
|
|
67
|
+
self.get_interface()
|
|
68
|
+
|
|
69
|
+
def get_interface(self):
|
|
70
|
+
"""获取当前接口"""
|
|
71
|
+
|
|
72
|
+
interfaces = netifaces.interfaces()
|
|
73
|
+
for iface in interfaces:
|
|
74
|
+
if iface.startswith("en"):
|
|
75
|
+
self.interface = iface
|
|
76
|
+
break
|
|
77
|
+
|
|
78
|
+
def check_go2_connection(self):
|
|
79
|
+
"""检查机器狗IP连通性"""
|
|
80
|
+
try:
|
|
81
|
+
# 使用sudo ping检查机器狗IP (192.168.123.161是Go2的默认IP)
|
|
82
|
+
result = subprocess.run(['sudo', 'ping', '-c', '1', '-W', '2', '192.168.123.161'],
|
|
83
|
+
capture_output=True, text=True, timeout=5)
|
|
84
|
+
return result.returncode == 0
|
|
85
|
+
except subprocess.TimeoutExpired:
|
|
86
|
+
print("ping超时")
|
|
87
|
+
return False
|
|
88
|
+
except Exception as e:
|
|
89
|
+
print(f"检查连接时出错: {e}")
|
|
90
|
+
return False
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def init(self):
|
|
96
|
+
"""初始化与Go2的连接"""
|
|
97
|
+
|
|
98
|
+
# 检查机器狗IP连通性
|
|
99
|
+
if not self.check_go2_connection():
|
|
100
|
+
print("无法连接到机器狗,请检查网络连接")
|
|
101
|
+
return False
|
|
102
|
+
|
|
103
|
+
ChannelFactoryInitialize(0, self.interface)
|
|
104
|
+
|
|
105
|
+
# 启动状态订阅
|
|
106
|
+
self.sub_state(self.callback)
|
|
107
|
+
|
|
108
|
+
# 初始化运动控制客户端
|
|
109
|
+
self.sport_client = SportClient()
|
|
110
|
+
self.sport_client.SetTimeout(self.timeout)
|
|
111
|
+
self.sport_client.Init()
|
|
112
|
+
# 初始化视频流客户端
|
|
113
|
+
# self.video_client = VideoClient()
|
|
114
|
+
# self.video_client.SetTimeout(self.timeout)
|
|
115
|
+
# self.video_client.Init()
|
|
116
|
+
self.cap = self.open_video()
|
|
117
|
+
|
|
118
|
+
return True
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def open_video(self, width: int = 480, height: int = 320):
|
|
122
|
+
"""打开视频流"""
|
|
123
|
+
gstreamer_str = (
|
|
124
|
+
f"udpsrc address=230.1.1.1 port=1720 multicast-iface={self.interface} "
|
|
125
|
+
"! application/x-rtp, media=video, encoding-name=H264 "
|
|
126
|
+
"! rtph264depay ! h264parse "
|
|
127
|
+
"! avdec_h264 " # 解码H.264
|
|
128
|
+
"! videoscale " # 添加缩放元素,用于调整分辨率
|
|
129
|
+
f"! video/x-raw,width={width},height={height} " # 目标分辨率
|
|
130
|
+
"! videoconvert ! video/x-raw, format=BGR " # 转换为OpenCV支持的BGR格式
|
|
131
|
+
"! appsink drop=1"
|
|
132
|
+
)
|
|
133
|
+
cap = cv2.VideoCapture(gstreamer_str, cv2.CAP_GSTREAMER)
|
|
134
|
+
if not cap.isOpened():
|
|
135
|
+
print("视频流打开失败")
|
|
136
|
+
return
|
|
137
|
+
return cap
|
|
138
|
+
|
|
139
|
+
def read_image(self):
|
|
140
|
+
"""从视频流获取一帧图像"""
|
|
141
|
+
ret, frame = self.cap.read()
|
|
142
|
+
if not ret:
|
|
143
|
+
print("读取图像失败")
|
|
144
|
+
return None
|
|
145
|
+
return frame
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def sub_state(self, callback, queue_size: int = 5):
|
|
149
|
+
subscriber = ChannelSubscriber("rt/sportmodestate", SportModeState_)
|
|
150
|
+
subscriber.Init(callback, queue_size)
|
|
151
|
+
|
|
152
|
+
def callback(self, msg):
|
|
153
|
+
self.error_code = msg.error_code
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
# def read_image(self):
|
|
157
|
+
# """从视频流获取一帧图像"""
|
|
158
|
+
# code, data = self.video_client.GetImageSample()
|
|
159
|
+
# if code != 0 or data is None:
|
|
160
|
+
# print("获取图像样本失败,错误码:", code)
|
|
161
|
+
# return None
|
|
162
|
+
# image_data = np.frombuffer(bytes(data), dtype=np.uint8)
|
|
163
|
+
# image = cv2.imdecode(image_data, cv2.IMREAD_COLOR)
|
|
164
|
+
# return image
|
|
165
|
+
|
|
166
|
+
def Damp(self):
|
|
167
|
+
"""进入阻尼状态。"""
|
|
168
|
+
self._call(self.sport_client.Damp)
|
|
169
|
+
|
|
170
|
+
def BalanceStand(self):
|
|
171
|
+
"""解除锁定。"""
|
|
172
|
+
self._call(self.sport_client.BalanceStand)
|
|
173
|
+
|
|
174
|
+
def StopMove(self):
|
|
175
|
+
"""停下当前动作,将绝大多数指令恢复成默认值。"""
|
|
176
|
+
print("停止移动")
|
|
177
|
+
# 停止持续移动线程
|
|
178
|
+
self._moving = False
|
|
179
|
+
if hasattr(self, '_move_thread') and self._move_thread.is_alive():
|
|
180
|
+
self._move_thread.join(timeout=1.0)
|
|
181
|
+
|
|
182
|
+
# 调用SDK的停止方法
|
|
183
|
+
self._call(self.sport_client.StopMove)
|
|
184
|
+
|
|
185
|
+
def _call(self, func, *args, **kwargs):
|
|
186
|
+
"""在线程中调用运动控制函数"""
|
|
187
|
+
def fun_thread():
|
|
188
|
+
ret = func(*args, **kwargs)
|
|
189
|
+
print(f"{func.__name__} 执行结果:", ret)
|
|
190
|
+
|
|
191
|
+
t = threading.Thread(target=fun_thread)
|
|
192
|
+
t.start()
|
|
193
|
+
t.join() # 等待线程完成
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def StandUp(self):
|
|
198
|
+
"""
|
|
199
|
+
关节锁定,站高。
|
|
200
|
+
执行后状态: 1002:站立锁定
|
|
201
|
+
"""
|
|
202
|
+
# 执行前判断状态
|
|
203
|
+
if self.error_code in [1002]: # 1002 :站立锁定
|
|
204
|
+
print("当前状态:", self.error_code, error_code[self.error_code], "无需执行")
|
|
205
|
+
return
|
|
206
|
+
if self.error_code not in [100, 1001, 1007, 1013]: # 100 :灵动, 1001 :阻尼, 1013 :平衡站立
|
|
207
|
+
print("繁忙中,当前状态:", self.error_code, error_code[self.error_code])
|
|
208
|
+
return
|
|
209
|
+
self._call(self.sport_client.StandUp)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def StandDown(self):
|
|
214
|
+
"""
|
|
215
|
+
关节锁定,站低。
|
|
216
|
+
执行后状态: 1001:阻尼
|
|
217
|
+
"""
|
|
218
|
+
# 执行前判断状态
|
|
219
|
+
if self.error_code in [1001, 1004, 2006]: # 1001:阻尼 1004 :蹲下 2006 :蹲下
|
|
220
|
+
print("当前状态:", self.error_code, error_code[self.error_code], "无需执行")
|
|
221
|
+
return
|
|
222
|
+
if self.error_code not in [100, 1002, 1013]: # 100 :灵动, 1001 :阻尼, 1013 :平衡站立
|
|
223
|
+
print("繁忙中,当前状态:", self.error_code, error_code[self.error_code])
|
|
224
|
+
return
|
|
225
|
+
|
|
226
|
+
# 执行指令
|
|
227
|
+
self._call(self.sport_client.StandDown)
|
|
228
|
+
|
|
229
|
+
def RecoveryStand(self):
|
|
230
|
+
""" 恢复站立。"""
|
|
231
|
+
self._call(self.sport_client.RecoveryStand)
|
|
232
|
+
|
|
233
|
+
def Euler(self, roll, pitch, yaw):
|
|
234
|
+
"""站立和行走时的姿态。"""
|
|
235
|
+
pass
|
|
236
|
+
|
|
237
|
+
def Move(self, vx, vy, vyaw):
|
|
238
|
+
"""移动。"""
|
|
239
|
+
# 检查状态是否允许移动
|
|
240
|
+
if self.error_code not in [100, 1002, 1013]: # 100:灵动, 1002:站立锁定, 1013:平衡站立
|
|
241
|
+
print(f"当前状态不允许移动: {self.error_code} - {error_code.get(self.error_code, '未知状态')}")
|
|
242
|
+
return False
|
|
243
|
+
|
|
244
|
+
# 限制速度范围
|
|
245
|
+
vx = max(-1.0, min(1.0, vx)) # 前后速度限制在-1到1之间
|
|
246
|
+
vy = max(-1.0, min(1.0, vy)) # 左右速度限制在-1到1之间
|
|
247
|
+
vyaw = max(-2.0, min(2.0, vyaw)) # 转动速度限制在-2到2之间
|
|
248
|
+
|
|
249
|
+
def move_thread():
|
|
250
|
+
ret = self.sport_client.Move(vx, vy, vyaw)
|
|
251
|
+
success = ret == 0
|
|
252
|
+
print(f"Move 执行结果: {ret}, 成功: {success}")
|
|
253
|
+
return success
|
|
254
|
+
|
|
255
|
+
t = threading.Thread(target=move_thread)
|
|
256
|
+
t.start()
|
|
257
|
+
t.join()
|
|
258
|
+
return True
|
|
259
|
+
|
|
260
|
+
def MoveForDuration(self, vx, vy, vyaw, duration):
|
|
261
|
+
"""
|
|
262
|
+
持续移动指定时间
|
|
263
|
+
|
|
264
|
+
Args:
|
|
265
|
+
vx (float): 前后速度 (-1.0 到 1.0)
|
|
266
|
+
vy (float): 左右速度 (-1.0 到 1.0)
|
|
267
|
+
vyaw (float): 转动速度 (-2.0 到 2.0)
|
|
268
|
+
duration (float): 移动时间(秒)
|
|
269
|
+
|
|
270
|
+
Returns:
|
|
271
|
+
bool: 是否成功执行
|
|
272
|
+
"""
|
|
273
|
+
print(f"开始移动: vx={vx}, vy={vy}, vyaw={vyaw}, 持续时间={duration}秒")
|
|
274
|
+
start_time = time.time()
|
|
275
|
+
|
|
276
|
+
try:
|
|
277
|
+
while time.time() - start_time < duration:
|
|
278
|
+
if not self.Move(vx, vy, vyaw):
|
|
279
|
+
return False
|
|
280
|
+
time.sleep(0.1) # 每100ms调用一次
|
|
281
|
+
|
|
282
|
+
print("移动完成")
|
|
283
|
+
return True
|
|
284
|
+
|
|
285
|
+
except KeyboardInterrupt:
|
|
286
|
+
print("移动被用户中断")
|
|
287
|
+
return False
|
|
288
|
+
|
|
289
|
+
def Forward(self, speed=0.3, duration=2.0):
|
|
290
|
+
"""向前移动"""
|
|
291
|
+
return self.MoveForDuration(speed, 0, 0, duration)
|
|
292
|
+
|
|
293
|
+
def Backward(self, speed=0.3, duration=2.0):
|
|
294
|
+
"""向后移动"""
|
|
295
|
+
return self.MoveForDuration(-speed, 0, 0, duration)
|
|
296
|
+
|
|
297
|
+
def Left(self, speed=0.3, duration=2.0):
|
|
298
|
+
"""向左移动"""
|
|
299
|
+
return self.MoveForDuration(0, speed, 0, duration)
|
|
300
|
+
|
|
301
|
+
def Right(self, speed=0.3, duration=2.0):
|
|
302
|
+
"""向右移动"""
|
|
303
|
+
return self.MoveForDuration(0, -speed, 0, duration)
|
|
304
|
+
|
|
305
|
+
def TurnLeft(self, speed=0.5, duration=2.0):
|
|
306
|
+
"""左转"""
|
|
307
|
+
return self.MoveForDuration(0, 0, speed, duration)
|
|
308
|
+
|
|
309
|
+
def TurnRight(self, speed=0.5, duration=2.0):
|
|
310
|
+
"""右转"""
|
|
311
|
+
return self.MoveForDuration(0, 0, -speed, duration)
|
|
312
|
+
|
|
313
|
+
def StartMove(self, vx, vy, vyaw):
|
|
314
|
+
"""
|
|
315
|
+
开始持续移动,需要调用StopMove来停止
|
|
316
|
+
|
|
317
|
+
Args:
|
|
318
|
+
vx (float): 前后速度 (-1.0 到 1.0)
|
|
319
|
+
vy (float): 左右速度 (-1.0 到 1.0)
|
|
320
|
+
vyaw (float): 转动速度 (-2.0 到 2.0)
|
|
321
|
+
|
|
322
|
+
Returns:
|
|
323
|
+
bool: 是否成功开始移动
|
|
324
|
+
"""
|
|
325
|
+
print(f"开始持续移动: vx={vx}, vy={vy}, vyaw={vyaw}")
|
|
326
|
+
self._moving = True
|
|
327
|
+
self._move_params = (vx, vy, vyaw)
|
|
328
|
+
|
|
329
|
+
def continuous_move():
|
|
330
|
+
while self._moving:
|
|
331
|
+
if not self.Move(vx, vy, vyaw):
|
|
332
|
+
break
|
|
333
|
+
time.sleep(0.1)
|
|
334
|
+
|
|
335
|
+
self._move_thread = threading.Thread(target=continuous_move)
|
|
336
|
+
self._move_thread.daemon = True # 设置为守护线程
|
|
337
|
+
self._move_thread.start()
|
|
338
|
+
return True
|
|
339
|
+
|
|
340
|
+
def StartForward(self, speed=0.3):
|
|
341
|
+
"""开始向前移动"""
|
|
342
|
+
return self.StartMove(speed, 0, 0)
|
|
343
|
+
|
|
344
|
+
def StartBackward(self, speed=0.3):
|
|
345
|
+
"""开始向后移动"""
|
|
346
|
+
return self.StartMove(-speed, 0, 0)
|
|
347
|
+
|
|
348
|
+
def StartLeft(self, speed=0.3):
|
|
349
|
+
"""开始向左移动"""
|
|
350
|
+
return self.StartMove(0, speed, 0)
|
|
351
|
+
|
|
352
|
+
def StartRight(self, speed=0.3):
|
|
353
|
+
"""开始向右移动"""
|
|
354
|
+
return self.StartMove(0, -speed, 0)
|
|
355
|
+
|
|
356
|
+
def StartTurnLeft(self, speed=0.5):
|
|
357
|
+
"""开始左转"""
|
|
358
|
+
return self.StartMove(0, 0, speed)
|
|
359
|
+
|
|
360
|
+
def StartTurnRight(self, speed=0.5):
|
|
361
|
+
"""开始右转"""
|
|
362
|
+
return self.StartMove(0, 0, -speed)
|
|
363
|
+
|
|
364
|
+
def Sit(self):
|
|
365
|
+
"""
|
|
366
|
+
坐下。
|
|
367
|
+
执行后状态: 1007: 坐下
|
|
368
|
+
"""
|
|
369
|
+
# 执行前判断状态
|
|
370
|
+
if self.error_code in [1007]: # 1007 : 坐下
|
|
371
|
+
print("当前状态:", self.error_code, error_code[self.error_code], "无需执行")
|
|
372
|
+
return
|
|
373
|
+
if self.error_code not in [100, 1002, 1013]: # 100 :灵动, 1001 :阻尼, 1013 :平衡站立
|
|
374
|
+
print("繁忙中,当前状态:", self.error_code, error_code[self.error_code])
|
|
375
|
+
return
|
|
376
|
+
|
|
377
|
+
# 执行指令
|
|
378
|
+
self._call(self.sport_client.Sit)
|
|
379
|
+
|
|
380
|
+
def RiseSit(self):
|
|
381
|
+
"""
|
|
382
|
+
站起(相对于坐下)。
|
|
383
|
+
执行后状态:
|
|
384
|
+
"""
|
|
385
|
+
# 执行前判断状态
|
|
386
|
+
if self.error_code in [1007]: # 1002 :站立锁定
|
|
387
|
+
print("当前状态:", self.error_code, error_code[self.error_code], "无需执行")
|
|
388
|
+
return
|
|
389
|
+
if self.error_code not in [100, 1007, 1013]: # 100 :灵动, 1007 : 坐下, 1013 :平衡站立
|
|
390
|
+
print("繁忙中,当前状态:", self.error_code, error_code[self.error_code])
|
|
391
|
+
return
|
|
392
|
+
|
|
393
|
+
# 执行指令
|
|
394
|
+
self._call(self.sport_client.RiseSit)
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def SpeedLevel(self, level: int):
|
|
399
|
+
"""设置速度档位。"""
|
|
400
|
+
pass
|
|
401
|
+
|
|
402
|
+
def Hello(self):
|
|
403
|
+
"""
|
|
404
|
+
打招呼
|
|
405
|
+
执行后状态: 1013:平衡站立
|
|
406
|
+
"""
|
|
407
|
+
# 执行前判断状态
|
|
408
|
+
if self.error_code in [1006]: # 1006 :打招呼/伸懒腰/舞蹈/拜年/比心/开心
|
|
409
|
+
print("当前状态:", self.error_code, error_code[self.error_code], "无需执行")
|
|
410
|
+
return
|
|
411
|
+
if self.error_code not in [100, 1002, 1013]: # 空闲状态
|
|
412
|
+
print("繁忙中,当前状态:", self.error_code, error_code[self.error_code])
|
|
413
|
+
return
|
|
414
|
+
|
|
415
|
+
# 执行指令
|
|
416
|
+
self._call(self.sport_client.Hello)
|
|
417
|
+
|
|
418
|
+
def Stretch(self):
|
|
419
|
+
"""
|
|
420
|
+
伸懒腰。
|
|
421
|
+
执行后状态: 1013:平衡站立
|
|
422
|
+
"""
|
|
423
|
+
# 执行前判断状态
|
|
424
|
+
if self.error_code in [1006]: # 1006 :打招呼/伸懒腰/舞蹈/拜年/比心/开心
|
|
425
|
+
print("当前状态:", self.error_code, error_code[self.error_code], "无需执行")
|
|
426
|
+
return
|
|
427
|
+
if self.error_code not in [100, 1002, 1013]: # 站立且空闲状态
|
|
428
|
+
print("繁忙中,当前状态:", self.error_code, error_code[self.error_code])
|
|
429
|
+
return
|
|
430
|
+
|
|
431
|
+
# 执行指令
|
|
432
|
+
self._call(self.sport_client.Stretch)
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
def Content(self):
|
|
437
|
+
"""
|
|
438
|
+
开心。
|
|
439
|
+
执行后状态: 1013:平衡站立
|
|
440
|
+
"""
|
|
441
|
+
# 执行前判断状态
|
|
442
|
+
if self.error_code in [1006]: # 1006 :打招呼/伸懒腰/舞蹈/拜年/比心/开心
|
|
443
|
+
print("当前状态:", self.error_code, error_code[self.error_code], "无需执行")
|
|
444
|
+
return
|
|
445
|
+
if self.error_code not in [100, 1002, 1013]: # 空闲状态
|
|
446
|
+
print("繁忙中,当前状态:", self.error_code, error_code[self.error_code])
|
|
447
|
+
return
|
|
448
|
+
|
|
449
|
+
# 执行指令
|
|
450
|
+
self._call(self.sport_client.Content)
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
def Heart(self):
|
|
454
|
+
"""
|
|
455
|
+
比心。
|
|
456
|
+
执行后状态: 1013:平衡站立
|
|
457
|
+
"""
|
|
458
|
+
# 执行前判断状态
|
|
459
|
+
if self.error_code in [1006]: # 1006 :打招呼/伸懒腰/舞蹈/拜年/比心/开心
|
|
460
|
+
print("当前状态:", self.error_code, error_code[self.error_code], "无需执行")
|
|
461
|
+
return
|
|
462
|
+
if self.error_code not in [100, 1002, 1013]: # 空闲状态
|
|
463
|
+
print("繁忙中,当前状态:", self.error_code, error_code[self.error_code])
|
|
464
|
+
return
|
|
465
|
+
|
|
466
|
+
# 执行指令
|
|
467
|
+
self._call(self.sport_client.Heart)
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
def Pose(self, flag):
|
|
471
|
+
"""摆姿势。"""
|
|
472
|
+
pass
|
|
473
|
+
|
|
474
|
+
def Scrape(self):
|
|
475
|
+
"""
|
|
476
|
+
拜年作揖。
|
|
477
|
+
执行后状态: 1013:平衡站立
|
|
478
|
+
"""
|
|
479
|
+
# 执行前判断状态
|
|
480
|
+
if self.error_code in [1006]: # 1006 :打招呼/伸懒腰/舞蹈/拜年/比心/开心
|
|
481
|
+
print("当前状态:", self.error_code, error_code[self.error_code], "无需执行")
|
|
482
|
+
return
|
|
483
|
+
if self.error_code not in [100, 1002, 1013]: # 空闲状态
|
|
484
|
+
print("繁忙中,当前状态:", self.error_code, error_code[self.error_code])
|
|
485
|
+
return
|
|
486
|
+
|
|
487
|
+
# 执行指令
|
|
488
|
+
self._call(self.sport_client.Scrape)
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
def FrontJump(self):
|
|
493
|
+
"""
|
|
494
|
+
前跳。
|
|
495
|
+
执行后状态: 1013: 平衡站立
|
|
496
|
+
"""
|
|
497
|
+
# 执行前判断状态
|
|
498
|
+
if self.error_code in [1008]: # 1008 : 前跳
|
|
499
|
+
print("当前状态:", self.error_code, error_code[self.error_code], "无需执行")
|
|
500
|
+
return
|
|
501
|
+
if self.error_code not in [100, 1002, 1013]: # 空闲状态
|
|
502
|
+
print("繁忙中,当前状态:", self.error_code, error_code[self.error_code])
|
|
503
|
+
return
|
|
504
|
+
|
|
505
|
+
# 执行指令
|
|
506
|
+
self._call(self.sport_client.FrontJump)
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
def FrontPounce(self):
|
|
511
|
+
"""
|
|
512
|
+
向前扑人。
|
|
513
|
+
执行后状态: 1013: 平衡站立
|
|
514
|
+
"""
|
|
515
|
+
# 执行前判断状态
|
|
516
|
+
if self.error_code in [1009]: # 1009 : 扑人
|
|
517
|
+
print("当前状态:", self.error_code, error_code[self.error_code], "无需执行")
|
|
518
|
+
return
|
|
519
|
+
if self.error_code not in [100, 1002, 1013]: # 空闲状态
|
|
520
|
+
print("繁忙中,当前状态:", self.error_code, error_code[self.error_code])
|
|
521
|
+
return
|
|
522
|
+
|
|
523
|
+
# 执行指令
|
|
524
|
+
self._call(self.sport_client.FrontPounce)
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
def Dance1(self):
|
|
528
|
+
"""
|
|
529
|
+
舞蹈段落1。
|
|
530
|
+
执行后状态: 1013: 平衡站立
|
|
531
|
+
"""
|
|
532
|
+
# 执行前判断状态
|
|
533
|
+
if self.error_code in [1006]: # 1006 :打招呼/伸懒腰/舞蹈/拜年/比心/开心
|
|
534
|
+
print("当前状态:", self.error_code, error_code[self.error_code], "无需执行")
|
|
535
|
+
return
|
|
536
|
+
if self.error_code not in [100, 1002, 1013]: # 空闲状态
|
|
537
|
+
print("繁忙中,当前状态:", self.error_code, error_code[self.error_code])
|
|
538
|
+
return
|
|
539
|
+
|
|
540
|
+
# 执行指令
|
|
541
|
+
self._call(self.sport_client.Dance1)
|
|
542
|
+
|
|
543
|
+
def Dance2(self):
|
|
544
|
+
"""
|
|
545
|
+
舞蹈段落2。
|
|
546
|
+
执行后状态: 1013: 平衡站立
|
|
547
|
+
"""
|
|
548
|
+
# 执行前判断状态
|
|
549
|
+
if self.error_code in [1006]: # 1006 :打招呼/伸懒腰/舞蹈/拜年/比心/开心
|
|
550
|
+
print("当前状态:", self.error_code, error_code[self.error_code], "无需执行")
|
|
551
|
+
return
|
|
552
|
+
if self.error_code not in [100, 1002, 1013]: # 空闲状态
|
|
553
|
+
print("繁忙中,当前状态:", self.error_code, error_code[self.error_code])
|
|
554
|
+
return
|
|
555
|
+
|
|
556
|
+
# 执行指令
|
|
557
|
+
self._call(self.sport_client.Dance2)
|
|
558
|
+
|
|
559
|
+
def HandStand(self, flag: int):
|
|
560
|
+
"""倒立行走。"""
|
|
561
|
+
pass
|
|
562
|
+
|
|
563
|
+
def LeftFlip(self):
|
|
564
|
+
"""左空翻。"""
|
|
565
|
+
pass
|
|
566
|
+
|
|
567
|
+
def BackFlip(self):
|
|
568
|
+
"""后空翻。"""
|
|
569
|
+
pass
|
|
570
|
+
|
|
571
|
+
def FreeWalk(self, flag: int):
|
|
572
|
+
""" 灵动模式(默认步态)。"""
|
|
573
|
+
pass
|
|
574
|
+
|
|
575
|
+
def FreeBound(self, flag: int):
|
|
576
|
+
""" 并腿跑模式。"""
|
|
577
|
+
pass
|
|
578
|
+
|
|
579
|
+
def FreeJump(self, flag: int):
|
|
580
|
+
""" 跳跃模式。"""
|
|
581
|
+
pass
|
|
582
|
+
|
|
583
|
+
def FreeAvoid(self, flag: int):
|
|
584
|
+
""" 闪避模式。"""
|
|
585
|
+
pass
|
|
586
|
+
|
|
587
|
+
def WalkUpright(self, flag: int):
|
|
588
|
+
""" 后腿直立模式。"""
|
|
589
|
+
pass
|
|
590
|
+
|
|
591
|
+
def CrossStep(self, flag: int):
|
|
592
|
+
""" 交叉步模式。"""
|
|
593
|
+
pass
|
|
594
|
+
|
|
595
|
+
def AutoRecoverSet(self, flag: int):
|
|
596
|
+
""" 设置自动翻身是否生效。"""
|
|
597
|
+
pass
|
|
598
|
+
|
|
599
|
+
def AutoRecoverGet(self):
|
|
600
|
+
""" 查询自动翻身是否生效。"""
|
|
601
|
+
pass
|
|
602
|
+
|
|
603
|
+
def ClassicWalk(self, flag: int):
|
|
604
|
+
""" 经典步态。"""
|
|
605
|
+
pass
|
|
606
|
+
|
|
607
|
+
def TrotRun(self):
|
|
608
|
+
""" 进入常规跑步模式 """
|
|
609
|
+
pass
|
|
610
|
+
|
|
611
|
+
def StaticWalk(self):
|
|
612
|
+
""" 进入常规行走模式"""
|
|
613
|
+
pass
|
|
614
|
+
|
|
615
|
+
def EconomicGait(self):
|
|
616
|
+
""" 进入常规续航模式 """
|
|
617
|
+
pass
|
|
618
|
+
|
|
619
|
+
def SwitchAvoidMode(self):
|
|
620
|
+
""" 闪避模式下,关闭摇杆未推时前方障碍物的闪避以及后方的障碍物躲避"""
|
|
621
|
+
pass
|
|
622
|
+
|
|
623
|
+
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
# -------------------- 测试 --------------------
|
|
627
|
+
if __name__ == "__main__":
|
|
628
|
+
interface = "enx00e0986113a6" # 替换为你的Go2网卡接口名称
|
|
629
|
+
|
|
630
|
+
go2 = Go2(interface=interface)
|
|
631
|
+
|
|
632
|
+
go2.stand_down()
|
|
633
|
+
go2.stand_up()
|
ezgo/ui.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import tkinter as tk
|
|
2
|
+
import platform
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
if platform.system() == "Linux":
|
|
7
|
+
os.environ["DISPLAY"] = ":0"
|
|
8
|
+
|
|
9
|
+
class APP(tk.Tk):
|
|
10
|
+
def __init__(self, title="APP", width=480, height=800):
|
|
11
|
+
super().__init__()
|
|
12
|
+
self.title_text = title
|
|
13
|
+
self.title(self.title_text)
|
|
14
|
+
self.geometry(f"{width}x{height}")
|
|
15
|
+
|
|
16
|
+
if platform.system() == "Linux":
|
|
17
|
+
self.resizable(False, False) # 固定窗口大小
|
|
18
|
+
self.attributes('-fullscreen', True) # 全屏
|
|
19
|
+
self.config(cursor="none") # 隐藏鼠标
|
|
20
|
+
self.update_idletasks() # 强制立即应用全屏,避免闪烁/延迟
|
|
21
|
+
|
|
22
|
+
# 显示图像和文字的全局变量
|
|
23
|
+
self.image = None # 显示的图像
|
|
24
|
+
self.text = "" # 显示的文字
|
|
25
|
+
|
|
26
|
+
self.is_running = True # 程序运行状态
|
|
27
|
+
|
|
28
|
+
# 初始化组件
|
|
29
|
+
self.create_widgets()
|
|
30
|
+
# 绑定窗口关闭事件
|
|
31
|
+
self.protocol("WM_DELETE_WINDOW", self.on_close)
|
|
32
|
+
|
|
33
|
+
def create_widgets(self):
|
|
34
|
+
# 标题
|
|
35
|
+
title_label = tk.Label(self, text=self.title_text, font=("SimHei", 18))
|
|
36
|
+
title_label.pack(pady=5)
|
|
37
|
+
|
|
38
|
+
# 图像显示区域
|
|
39
|
+
self.frame1 = tk.LabelFrame(self, text="图像", width=480, height=480, font=("SimHei", 10))
|
|
40
|
+
self.frame1.pack_propagate(False)
|
|
41
|
+
self.frame1.pack(fill=tk.X, pady=5)
|
|
42
|
+
self.image_label = tk.Label(self.frame1)
|
|
43
|
+
self.image_label.pack(pady=5, fill=tk.BOTH, expand=True, anchor=tk.CENTER)
|
|
44
|
+
|
|
45
|
+
# 文字显示区域
|
|
46
|
+
self.frame2 = tk.LabelFrame(self, text="结果", width=480, height=200, font=("SimHei", 10))
|
|
47
|
+
self.frame2.pack_propagate(False)
|
|
48
|
+
self.frame2.pack(fill=tk.X, pady=5)
|
|
49
|
+
self.text_label = tk.Label(self.frame2,
|
|
50
|
+
text="文本显示",
|
|
51
|
+
font=("SimHei", 14),
|
|
52
|
+
wraplength=450,
|
|
53
|
+
justify=tk.CENTER,)
|
|
54
|
+
self.text_label.pack(pady=5, fill=tk.BOTH, expand=True, anchor=tk.CENTER)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# 退出按钮
|
|
58
|
+
self.exit_btn = tk.Button(self, text="退出", command=self.on_close, font=("SimHei", 16))
|
|
59
|
+
self.exit_btn.pack(pady=10)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def set_image(self, image):
|
|
63
|
+
"""设置显示的图像"""
|
|
64
|
+
self.image = image
|
|
65
|
+
self.image_label.config(image=self.image)
|
|
66
|
+
|
|
67
|
+
def set_text(self, text):
|
|
68
|
+
"""设置显示的文字"""
|
|
69
|
+
self.text = text
|
|
70
|
+
self.text_label.config(text=self.text)
|
|
71
|
+
|
|
72
|
+
def on_close(self):
|
|
73
|
+
"""窗口关闭/退出按钮回调"""
|
|
74
|
+
self.is_running = False
|
|
75
|
+
self.quit() # 退出主循环
|
|
76
|
+
self.destroy() # 销毁窗口
|
|
77
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 ezgo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: ezgo
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: 宇树Go2机器狗Python控制库
|
|
5
|
+
Author-email: ezgo <noreply@example.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/your-username/ezgo
|
|
8
|
+
Project-URL: Repository, https://github.com/your-username/ezgo
|
|
9
|
+
Project-URL: Bug Reports, https://github.com/your-username/ezgo/issues
|
|
10
|
+
Keywords: robotics,unitree,go2,robot,control
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Requires-Python: >=3.7
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Provides-Extra: basic
|
|
26
|
+
Requires-Dist: opencv-python>=4.5.0; extra == "basic"
|
|
27
|
+
Requires-Dist: numpy>=1.19.0; extra == "basic"
|
|
28
|
+
Requires-Dist: Pillow>=8.0.0; extra == "basic"
|
|
29
|
+
Provides-Extra: full
|
|
30
|
+
Requires-Dist: opencv-python>=4.5.0; extra == "full"
|
|
31
|
+
Requires-Dist: numpy>=1.19.0; extra == "full"
|
|
32
|
+
Requires-Dist: Pillow>=8.0.0; extra == "full"
|
|
33
|
+
Requires-Dist: netifaces>=0.10.0; extra == "full"
|
|
34
|
+
Requires-Dist: unitree-sdk2py; extra == "full"
|
|
35
|
+
|
|
36
|
+
# ezgo - 宇树Go2机器狗Python控制库
|
|
37
|
+
|
|
38
|
+
[](https://badge.fury.io/py/ezgo)
|
|
39
|
+
[](https://pypi.org/project/ezgo/)
|
|
40
|
+
[](https://opensource.org/licenses/MIT)
|
|
41
|
+
|
|
42
|
+
ezgo 是一个用于控制宇树Go2机器狗的Python库,提供了简单易用的API接口,支持运动控制、视频流获取、UI界面等功能。
|
|
43
|
+
|
|
44
|
+
## 功能特性
|
|
45
|
+
|
|
46
|
+
- 🤖 **运动控制**: 支持前进、后退、转向、舞蹈等多种动作
|
|
47
|
+
- 📹 **视频流**: 实时获取机器狗摄像头画面
|
|
48
|
+
- 🎮 **状态管理**: 智能状态检测和动作切换
|
|
49
|
+
- 🖥️ **UI界面**: 提供简单的图形界面用于显示和控制
|
|
50
|
+
- 🔧 **易于集成**: 简洁的API设计,便于快速开发
|
|
51
|
+
|
|
52
|
+
## 安装
|
|
53
|
+
|
|
54
|
+
### 使用pip安装
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pip install ezgo
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### 从源码安装
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
git clone https://github.com/your-username/ezgo.git
|
|
64
|
+
cd ezgo
|
|
65
|
+
pip install -e .
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## 系统要求
|
|
69
|
+
|
|
70
|
+
在安装 ezgo 之前,请确保您的系统已安装以下依赖:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
# Ubuntu/Debian
|
|
74
|
+
sudo apt update
|
|
75
|
+
sudo apt install python3-opencv python3-numpy python3-pil
|
|
76
|
+
|
|
77
|
+
# CentOS/RHEL
|
|
78
|
+
sudo yum install opencv-python numpy Pillow
|
|
79
|
+
|
|
80
|
+
# macOS
|
|
81
|
+
brew install opencv
|
|
82
|
+
|
|
83
|
+
# Windows
|
|
84
|
+
# 请使用 pip 安装或下载预编译的二进制包
|
|
85
|
+
pip install opencv-python numpy Pillow
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**注意**: ezgo 不会自动安装这些依赖,因为它们可能需要系统级安装或特定配置。
|
|
89
|
+
|
|
90
|
+
## 快速开始
|
|
91
|
+
|
|
92
|
+
### 基本使用
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
from ezgo import Go2
|
|
96
|
+
|
|
97
|
+
# 初始化Go2控制器
|
|
98
|
+
go2 = Go2(interface="enx00e0986113a6") # 替换为你的网卡接口
|
|
99
|
+
|
|
100
|
+
# 初始化连接
|
|
101
|
+
if go2.init():
|
|
102
|
+
print("连接成功!")
|
|
103
|
+
|
|
104
|
+
# 基本动作
|
|
105
|
+
go2.StandUp() # 站立
|
|
106
|
+
go2.Forward(0.3, 2) # 向前移动2秒
|
|
107
|
+
go2.TurnLeft(0.5, 1) # 左转1秒
|
|
108
|
+
go2.Hello() # 打招呼
|
|
109
|
+
go2.Damp() # 进入阻尼状态
|
|
110
|
+
else:
|
|
111
|
+
print("连接失败!")
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### 视频流使用
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
import cv2
|
|
118
|
+
from ezgo import Go2
|
|
119
|
+
|
|
120
|
+
# 初始化
|
|
121
|
+
go2 = Go2()
|
|
122
|
+
if go2.init():
|
|
123
|
+
print("开始获取视频流...")
|
|
124
|
+
|
|
125
|
+
while True:
|
|
126
|
+
frame = go2.read_image()
|
|
127
|
+
if frame is not None:
|
|
128
|
+
cv2.imshow('Go2 Camera', frame)
|
|
129
|
+
|
|
130
|
+
if cv2.waitKey(1) & 0xFF == ord('q'):
|
|
131
|
+
break
|
|
132
|
+
|
|
133
|
+
cv2.destroyAllWindows()
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### UI界面使用
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
from ezgo.ui import APP
|
|
140
|
+
from ezgo.camera import Camera
|
|
141
|
+
import time
|
|
142
|
+
|
|
143
|
+
# 创建UI应用
|
|
144
|
+
app = APP(title="Go2控制面板", width=480, height=800)
|
|
145
|
+
|
|
146
|
+
# 创建摄像头
|
|
147
|
+
camera = Camera(index=0)
|
|
148
|
+
camera.open_camera()
|
|
149
|
+
|
|
150
|
+
# 更新界面
|
|
151
|
+
while app.is_running:
|
|
152
|
+
try:
|
|
153
|
+
# 获取摄像头图像
|
|
154
|
+
frame = camera.read_cv2_image()
|
|
155
|
+
image_tk = camera.cv2_to_tk(frame)
|
|
156
|
+
app.set_image(image_tk)
|
|
157
|
+
app.set_text("系统运行正常")
|
|
158
|
+
|
|
159
|
+
app.update()
|
|
160
|
+
time.sleep(0.03) # 约30FPS
|
|
161
|
+
except Exception as e:
|
|
162
|
+
print(f"更新界面出错: {e}")
|
|
163
|
+
break
|
|
164
|
+
|
|
165
|
+
app.mainloop()
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## API 文档
|
|
169
|
+
|
|
170
|
+
### Go2 类
|
|
171
|
+
|
|
172
|
+
主要的机器狗控制类,提供以下方法:
|
|
173
|
+
|
|
174
|
+
#### 运动控制
|
|
175
|
+
- `StandUp()` - 站立
|
|
176
|
+
- `StandDown()` - 蹲下
|
|
177
|
+
- `Sit()` - 坐下
|
|
178
|
+
- `Damp()` - 进入阻尼状态
|
|
179
|
+
- `BalanceStand()` - 平衡站立
|
|
180
|
+
- `StopMove()` - 停止移动
|
|
181
|
+
|
|
182
|
+
#### 移动控制
|
|
183
|
+
- `Move(vx, vy, vyaw)` - 移动控制
|
|
184
|
+
- `Forward(speed, duration)` - 向前移动
|
|
185
|
+
- `Backward(speed, duration)` - 向后移动
|
|
186
|
+
- `Left(speed, duration)` - 向左移动
|
|
187
|
+
- `Right(speed, duration)` - 向右移动
|
|
188
|
+
- `TurnLeft(speed, duration)` - 左转
|
|
189
|
+
- `TurnRight(speed, duration)` - 右转
|
|
190
|
+
|
|
191
|
+
#### 动作控制
|
|
192
|
+
- `Hello()` - 打招呼
|
|
193
|
+
- `Stretch()` - 伸懒腰
|
|
194
|
+
- `Content()` - 开心
|
|
195
|
+
- `Heart()` - 比心
|
|
196
|
+
- `Scrape()` - 拜年作揖
|
|
197
|
+
- `Dance1()` - 舞蹈1
|
|
198
|
+
- `Dance2()` - 舞蹈2
|
|
199
|
+
- `FrontJump()` - 前跳
|
|
200
|
+
- `FrontPounce()` - 向前扑人
|
|
201
|
+
|
|
202
|
+
#### 视频控制
|
|
203
|
+
- `read_image()` - 获取一帧图像
|
|
204
|
+
- `open_video(width, height)` - 打开视频流
|
|
205
|
+
|
|
206
|
+
### Camera 类
|
|
207
|
+
|
|
208
|
+
摄像头控制类,提供以下方法:
|
|
209
|
+
- `open_camera()` - 打开摄像头
|
|
210
|
+
- `read_cv2_image()` - 读取OpenCV格式图像
|
|
211
|
+
- `read_pil_image()` - 读取PIL格式图像
|
|
212
|
+
- `cv2_to_tk(frame)` - 转换为Tkinter格式
|
|
213
|
+
- `resize(frame, size)` - 调整图像尺寸
|
|
214
|
+
- `crop_to_square(frame)` - 裁剪为正方形
|
|
215
|
+
|
|
216
|
+
### APP 类
|
|
217
|
+
|
|
218
|
+
UI界面类,基于Tkinter:
|
|
219
|
+
- `set_image(image)` - 设置显示图像
|
|
220
|
+
- `set_text(text)` - 设置显示文字
|
|
221
|
+
|
|
222
|
+
## 项目结构
|
|
223
|
+
|
|
224
|
+
```
|
|
225
|
+
ezgo/
|
|
226
|
+
├── src/
|
|
227
|
+
│ ├── __init__.py # 包初始化文件
|
|
228
|
+
│ ├── go2.py # Go2机器狗控制类
|
|
229
|
+
│ ├── camera.py # 摄像头控制类
|
|
230
|
+
│ └── ui.py # UI界面类
|
|
231
|
+
├── pyproject.toml # 现代Python项目配置
|
|
232
|
+
├── README.md # 项目文档
|
|
233
|
+
├── LICENSE # 开源协议
|
|
234
|
+
├── quick_update.py # 快速更新脚本
|
|
235
|
+
└── .pypirc # PyPI配置文件
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## 开发指南
|
|
239
|
+
|
|
240
|
+
### 本地开发
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
# 克隆仓库
|
|
244
|
+
git clone https://github.com/your-username/ezgo.git
|
|
245
|
+
cd ezgo
|
|
246
|
+
|
|
247
|
+
# 安装开发依赖
|
|
248
|
+
pip install -e .
|
|
249
|
+
|
|
250
|
+
# 运行测试
|
|
251
|
+
python -m pytest tests/
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### 发布新版本
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
# 更新补丁版本
|
|
258
|
+
python quick_update.py patch
|
|
259
|
+
|
|
260
|
+
# 更新次版本
|
|
261
|
+
python quick_update.py minor
|
|
262
|
+
|
|
263
|
+
# 更新主版本
|
|
264
|
+
python quick_update.py major
|
|
265
|
+
|
|
266
|
+
# 发布到PyPI
|
|
267
|
+
python quick_update.py publish
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
## 常见问题
|
|
271
|
+
|
|
272
|
+
### Q: 连接机器狗失败怎么办?
|
|
273
|
+
A: 请检查网络连接,确保机器狗IP (192.168.123.161) 可以ping通,并确认网卡接口名称正确。
|
|
274
|
+
|
|
275
|
+
### Q: 视频流无法打开?
|
|
276
|
+
A: 请检查GStreamer是否正确安装,以及网卡多播配置是否正确。
|
|
277
|
+
|
|
278
|
+
### Q: 某些动作无法执行?
|
|
279
|
+
A: 请检查机器狗当前状态,某些动作需要在特定状态下才能执行。
|
|
280
|
+
|
|
281
|
+
## 贡献
|
|
282
|
+
|
|
283
|
+
欢迎提交 Issue 和 Pull Request!
|
|
284
|
+
|
|
285
|
+
## 许可证
|
|
286
|
+
|
|
287
|
+
本项目采用 MIT 许可证 - 详见 [LICENSE](LICENSE) 文件。
|
|
288
|
+
|
|
289
|
+
## 更新日志
|
|
290
|
+
|
|
291
|
+
### v0.0.1 (2024-01-01)
|
|
292
|
+
- 初始版本发布
|
|
293
|
+
- 基本运动控制功能
|
|
294
|
+
- 视频流获取功能
|
|
295
|
+
- UI界面功能
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
ezgo/__init__.py,sha256=qDPgcBaUNokuBFBLW7DwmZ53sdZh-VTReHOab1a6yZw,1918
|
|
2
|
+
ezgo/camera.py,sha256=Vsr44vFtaop4LE0DlWyOHyvxF-jHnVO8iMPUwp5mbsE,2538
|
|
3
|
+
ezgo/go2.py,sha256=6-wjSGKfKfR1f9qfmhU9x8gwfeapei2Kx1CqUd9xAhE,20818
|
|
4
|
+
ezgo/ui.py,sha256=jUik6maaoemI2vsBM92OLZfJfskg54W7hwMls_gxppg,2777
|
|
5
|
+
ezgo-0.0.1.dist-info/LICENSE,sha256=Zk4eZBT3KaBhqM3LB_xN7QquwnoWsEHAoA6eLdE6W5M,1060
|
|
6
|
+
ezgo-0.0.1.dist-info/METADATA,sha256=ZzisKNb6JDsXTFT0H8zcWSCfYALHHQMnndwLrz7efbI,7765
|
|
7
|
+
ezgo-0.0.1.dist-info/WHEEL,sha256=iAkIy5fosb7FzIOwONchHf19Qu7_1wCWyFNR5gu9nU0,91
|
|
8
|
+
ezgo-0.0.1.dist-info/top_level.txt,sha256=BdCFEVD5V_4FxUtvH0BVlZKgxYnp7EKNsYs5OyFxj-g,5
|
|
9
|
+
ezgo-0.0.1.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ezgo
|