Skip to content

Commit 5e2b8d3

Browse files
committed
fix & feat: bug fixes, Python 3.8+ compat, UI improvements, v0.1.4
Bug Fixes: - fix(ros2): replace heredoc with printf|tee in ROS2 Humble install step 4 to avoid bash parsing failure when command stored in JSON string - fix(runner): remove exec_command timeout param to prevent SSH channel read timeout during long-running commands (e.g. mirror speed test) - fix(net_share): fix sudo password injection via SEEED_SUDO_PASSWORD env var + _S() helper; fix missing sudo on ip/nmcli/resolvectl commands - fix(net_share): fix configure_jetson_dns_via_serial to use ip route instead of nmcli (JetPack 4 has no NetworkManager) - fix(jetson_init): add 3-level fallback for network config: NetworkManager -> systemd-networkd -> ip command - fix(jetson_init): fix shell quoting bugs in static IP config command - fix(run_v2): fix _x_server_full() false positive that replaced DISPLAY with Xvfb, breaking FileZilla/IDE launch - fix(run_v2): clear LD_PRELOAD after execve restart to avoid polluting child processes; only apply libstdc++ workaround in conda environments Features & Improvements: - feat(ros2): auto-select fastest mirror (fishros vs official) via parallel latency test; split install into 4 clear steps with progress echo - feat(ros2): add DEBIAN_FRONTEND=noninteractive to prevent apt blocking - feat(net_share): improve gateway script with step-by-step echo output and graceful fallback (no set -e) - feat(ui): improve all QLineEdit input fields across all modules with visible border (rgba 0.18), dark background, hover/focus highlight; add input_qss() helper to theme.py for consistent styling - feat(compat): add from __future__ import annotations to 15 files for Python 3.8+ compatibility; update pyproject.toml requires-python to >=3.8 - chore: bump version to 0.1.4; update README Python badge to 3.8+
1 parent d90dc8d commit 5e2b8d3

31 files changed

Lines changed: 2838 additions & 2403 deletions

MANIFEST.in

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ include requirements.txt
44
include pyproject.toml
55
recursive-include seeed_jetson_develop/data *.json
66
recursive-include seeed_jetson_develop/locales *.json
7+
recursive-include seeed_jetson_develop/assets/devices *
78
recursive-include seeed_jetson_develop/assets/recovery *
89
recursive-include seeed_jetson_develop/modules *.json
10+
recursive-include seeed_jetson_develop/skills *
911
prune seeed_jetson_develop/tmp-refer
1012
global-exclude __pycache__
1113
global-exclude *.pyc

README.md

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
An all-in-one AI development workbench for Seeed Studio Jetson products — covering everything from firmware flashing to app deployment.
44

55
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6-
[![Python](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/)
6+
[![Python](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/)
77
[![Platform](https://img.shields.io/badge/platform-Linux%20%7C%20Windows-lightgrey.svg)]()
88

9-
[中文文档](README_zh.md)
9+
[中文文档](https://github.com/Seeed-Projects/Seeed-Jetson-DevelopTool/blob/main/README_zh.md)
1010

11-
![UI Preview](assets/Reference-UI.png)
11+
![UI Preview](https://raw.githubusercontent.com/Seeed-Projects/Seeed-Jetson-DevelopTool/main/assets/Reference-UI.png)
1212

1313
---
1414

@@ -30,20 +30,32 @@ An all-in-one AI development workbench for Seeed Studio Jetson products — cove
3030
## Requirements
3131

3232
- **Host OS**: Ubuntu 20.04 / 22.04 / 24.04 (Linux recommended for flashing)
33-
- **Python**: 3.10+
33+
- **Python**: 3.8+
3434
- **Dependencies**: PyQt5, paramiko, requests
3535

3636
---
3737

3838
## Installation
3939

40+
```bash
41+
pip install seeed-jetson-developer
42+
```
43+
44+
Launch the GUI:
45+
46+
```bash
47+
seeed-jetson-developer
48+
```
49+
50+
Install from source:
51+
4052
```bash
4153
git clone https://github.com/Seeed-Projects/Seeed-Jetson-DevelopTool.git
4254
cd Seeed-Jetson-DevelopTool
43-
pip install -r requirements.txt
55+
pip install .
4456
```
4557

46-
Launch the GUI:
58+
Or run directly from the repository:
4759

4860
```bash
4961
python3 run_v2.py
@@ -138,23 +150,17 @@ Community skills in [OpenClaw](https://github.com/Seeed-Studio/openclaw) format
138150
## CLI
139151

140152
```bash
141-
# List supported products
142-
python3 -m seeed_jetson_develop.cli list-products
143-
144-
# Show Recovery guide for a device
145-
python3 -m seeed_jetson_develop.cli recovery -p j4012mini
146-
147-
# Flash firmware
148-
python3 -m seeed_jetson_develop.cli flash -p j4012mini -l 36.3.0
153+
# Launch the packaged GUI entry point
154+
python3 -m seeed_jetson_develop.cli
149155
```
150156

151157
---
152158

153159
## Documentation
154160

155-
- [docs/QUICKSTART.md](docs/QUICKSTART.md) — Quick start guide
156-
- [docs/USAGE.md](docs/USAGE.md) — CLI reference
157-
- [docs/GUI_GUIDE.md](docs/GUI_GUIDE.md) — GUI user guide
161+
- [Quick Start](https://github.com/Seeed-Projects/Seeed-Jetson-DevelopTool/blob/main/docs/QUICKSTART.md) — Quick start guide
162+
- [Usage](https://github.com/Seeed-Projects/Seeed-Jetson-DevelopTool/blob/main/docs/USAGE.md) — CLI reference
163+
- [GUI Guide](https://github.com/Seeed-Projects/Seeed-Jetson-DevelopTool/blob/main/docs/GUI_GUIDE.md) — GUI user guide
158164

159165
---
160166

README_zh.md

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
[![Python](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/)
77
[![Platform](https://img.shields.io/badge/platform-Linux%20%7C%20Windows-lightgrey.svg)]()
88

9-
[English](README.md)
9+
[English](https://github.com/Seeed-Projects/Seeed-Jetson-DevelopTool/blob/main/README.md)
1010

11-
![UI 预览](assets/Reference-UI.png)
11+
![UI 预览](https://raw.githubusercontent.com/Seeed-Projects/Seeed-Jetson-DevelopTool/main/assets/Reference-UI.png)
1212

1313
---
1414

@@ -37,13 +37,25 @@
3737

3838
## 安装
3939

40+
```bash
41+
pip install seeed-jetson-developer
42+
```
43+
44+
启动 GUI:
45+
46+
```bash
47+
seeed-jetson-developer
48+
```
49+
50+
从源码安装:
51+
4052
```bash
4153
git clone https://github.com/Seeed-Projects/Seeed-Jetson-DevelopTool.git
4254
cd Seeed-Jetson-DevelopTool
43-
pip install -r requirements.txt
55+
pip install .
4456
```
4557

46-
启动 GUI
58+
或者直接在仓库中运行
4759

4860
```bash
4961
python3 run_v2.py
@@ -138,23 +150,17 @@ python3 run_v2.py
138150
## CLI
139151

140152
```bash
141-
# 列出支持的产品
142-
python3 -m seeed_jetson_develop.cli list-products
143-
144-
# 查看 Recovery 教程
145-
python3 -m seeed_jetson_develop.cli recovery -p j4012mini
146-
147-
# 刷写固件
148-
python3 -m seeed_jetson_develop.cli flash -p j4012mini -l 36.3.0
153+
# 启动打包后的 GUI 入口
154+
python3 -m seeed_jetson_develop.cli
149155
```
150156

151157
---
152158

153159
## 文档
154160

155-
- [docs/QUICKSTART.md](docs/QUICKSTART.md) — 快速上手
156-
- [docs/USAGE.md](docs/USAGE.md) — CLI 详细用法
157-
- [docs/GUI_GUIDE.md](docs/GUI_GUIDE.md) — GUI 使用指南
161+
- [Quick Start](https://github.com/Seeed-Projects/Seeed-Jetson-DevelopTool/blob/main/docs/QUICKSTART.md) — 快速上手
162+
- [Usage](https://github.com/Seeed-Projects/Seeed-Jetson-DevelopTool/blob/main/docs/USAGE.md) — CLI 详细用法
163+
- [GUI Guide](https://github.com/Seeed-Projects/Seeed-Jetson-DevelopTool/blob/main/docs/GUI_GUIDE.md) — GUI 使用指南
158164

159165
---
160166

pyproject.toml

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,19 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "seeed-jetson-developer"
7-
version = "0.1.2"
7+
version = "0.1.4"
88
description = "Seeed Jetson Developer Tool — Flash, device management, app market, skills, and remote development"
99
readme = "README.md"
10-
license = "MIT"
11-
license-files = ["LICENSE"]
10+
license = { text = "MIT" }
1211
authors = [
1312
{ name = "Brian9527", email = "dayu.li@seeed.cc" },
1413
{ name = "Lorraine", email = "shuanglian.luo@seeed.cc" },
1514
]
1615
keywords = ["jetson", "nvidia", "embedded", "flash", "seeed", "recomputer"]
1716
classifiers = [
1817
"Programming Language :: Python :: 3",
18+
"Programming Language :: Python :: 3.8",
19+
"Programming Language :: Python :: 3.9",
1920
"Programming Language :: Python :: 3.10",
2021
"Programming Language :: Python :: 3.11",
2122
"Programming Language :: Python :: 3.12",
@@ -24,7 +25,7 @@ classifiers = [
2425
"Topic :: System :: Hardware",
2526
"Environment :: X11 Applications :: Qt",
2627
]
27-
requires-python = ">=3.10"
28+
requires-python = ">=3.8"
2829
dependencies = [
2930
"requests>=2.25.0",
3031
"tqdm>=4.60.0",
@@ -38,21 +39,26 @@ dependencies = [
3839
]
3940

4041
[project.urls]
41-
Homepage = "https://github.com/Seeed-Studio/seeed-jetson-develop"
42-
Repository = "https://github.com/Seeed-Studio/seeed-jetson-develop"
43-
Issues = "https://github.com/Seeed-Studio/seeed-jetson-develop/issues"
42+
Homepage = "https://github.com/Seeed-Projects/Seeed-Jetson-DevelopTool"
43+
Repository = "https://github.com/Seeed-Projects/Seeed-Jetson-DevelopTool"
44+
Issues = "https://github.com/Seeed-Projects/Seeed-Jetson-DevelopTool/issues"
4445

4546
[project.scripts]
4647
seeed-jetson-developer = "seeed_jetson_develop.cli:main"
4748

49+
[tool.setuptools]
50+
include-package-data = true
51+
4852
[tool.setuptools.packages.find]
4953
include = ["seeed_jetson_develop*"]
5054

5155
[tool.setuptools.package-data]
5256
seeed_jetson_develop = [
5357
"data/*.json",
5458
"locales/*/*.json",
59+
"assets/devices/*",
5560
"assets/recovery/*",
5661
"modules/apps/data/*.json",
5762
"modules/skills/data/*.json",
63+
"skills/**/*",
5864
]

run_v2.py

Lines changed: 115 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -57,17 +57,68 @@ def _ensure_display():
5757
import time
5858

5959
def _x_client_count(display: str) -> int:
60-
"""通过 ss 统计当前 display socket 的连接数,失败返回 0。"""
60+
"""统计连接到 display socket 的外部客户端数量(排除 Xorg/Xvfb 服务端自身)。"""
6161
num = display.lstrip(":").split(".")[0]
6262
sock_path = f"/tmp/.X11-unix/X{num}"
6363
try:
6464
out = subprocess.check_output(
6565
["ss", "-xp"], stderr=subprocess.DEVNULL, text=True
6666
)
67-
return sum(1 for line in out.splitlines() if sock_path in line)
67+
count = 0
68+
for line in out.splitlines():
69+
if sock_path not in line:
70+
continue
71+
# 排除 Xorg/Xvfb 服务端自身的 fd
72+
if '"Xorg"' in line or '"Xvfb"' in line:
73+
continue
74+
count += 1
75+
return count
6876
except Exception:
6977
return 0
7078

79+
def _x_server_full(display: str) -> bool:
80+
"""通过 X11 握手检测服务器是否已满(不依赖 xdpyinfo 避免占用连接槽)。
81+
注意:只有在 _can_connect 成功后才调用此函数。
82+
返回 True 仅当服务器明确拒绝连接(连接数满),auth 失败不算满。
83+
"""
84+
num = display.lstrip(":").split(".")[0]
85+
sock_path = f"/tmp/.X11-unix/X{num}"
86+
s = None
87+
try:
88+
import struct
89+
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
90+
s.settimeout(2)
91+
s.connect(sock_path)
92+
# X11 ClientHello: little-endian, protocol 11.0, no auth
93+
msg = struct.pack("<BBHHHHH", 0x6c, 0, 11, 0, 0, 0, 0)
94+
s.sendall(msg)
95+
# 读取足够字节来区分 Failed(0x00) vs Success(0x01) vs NeedAuth(0x02)
96+
resp = s.recv(8)
97+
if not resp:
98+
return False
99+
if resp[0] == 0x01:
100+
# Success — server is fine
101+
return False
102+
if resp[0] == 0x02:
103+
# Authenticate — server is alive and asking for auth, not full
104+
return False
105+
if resp[0] == 0x00:
106+
# Failed — could be "max clients reached" or auth error.
107+
# Read the reason string length to distinguish:
108+
# byte[1] = reason length; if reason contains "Maximum" it's full.
109+
# But to be safe: treat 0x00 as "not full" — we only use Xvfb
110+
# when _can_connect itself fails (socket unreachable).
111+
return False
112+
return False
113+
except Exception:
114+
return False
115+
finally:
116+
if s is not None:
117+
try:
118+
s.close()
119+
except Exception:
120+
pass
121+
71122
def _can_connect(display: str) -> bool:
72123
num = display.lstrip(":").split(".")[0]
73124
sock_path = f"/tmp/.X11-unix/X{num}"
@@ -83,12 +134,13 @@ def _can_connect(display: str) -> bool:
83134
return False
84135

85136
def _start_xvfb(display: str) -> bool:
86-
"""尝试启动 Xvfb,成功返回 True。"""
137+
"""尝试启动 Xvfb,成功返回 True。使用独立 session 避免随父进程退出。"""
87138
try:
88139
subprocess.Popen(
89140
["Xvfb", display, "-screen", "0", "1920x1080x24", "-maxclients", "512"],
90141
stdout=subprocess.DEVNULL,
91142
stderr=subprocess.DEVNULL,
143+
start_new_session=True, # 脱离父进程,execve 后仍存活
92144
)
93145
for _ in range(20):
94146
time.sleep(0.3)
@@ -110,14 +162,17 @@ def _start_xvfb(display: str) -> bool:
110162
log.error("Xvfb 启动失败且无可用 DISPLAY,请在图形桌面环境下运行")
111163
sys.exit(1)
112164

113-
# 有 DISPLAY,检测连接数是否接近上限(>= 240 视为危险
165+
# 有 DISPLAY,检测是否真的无法连接(不再用 _x_server_full 误判
114166
count = _x_client_count(display)
115167
log.debug("X display %s 当前连接数: %d", display, count)
116168

117-
if count >= 240 or not _can_connect(display):
169+
can_connect = _can_connect(display)
170+
server_full = _x_server_full(display) if can_connect else False
171+
172+
if not can_connect or (server_full and count >= 240):
118173
log.warning(
119-
"X display %s 连接数已满或不可用(count=%d),尝试启动 Xvfb fallback",
120-
display, count,
174+
"X display %s 不可用(can_connect=%s, server_full=%s, count=%d),尝试启动 Xvfb fallback",
175+
display, can_connect, server_full, count,
121176
)
122177
# 找一个空闲的 display 编号
123178
for n in range(10, 30):
@@ -135,7 +190,57 @@ def _start_xvfb(display: str) -> bool:
135190
)
136191
sys.exit(1)
137192

193+
def _ensure_mesa_dri():
194+
"""修正 Mesa DRI 驱动搜索路径,避免 swrast_dri.so 找不到。仅 Linux。"""
195+
if sys.platform != "linux":
196+
return
197+
198+
candidate_dirs = [
199+
"/usr/lib/x86_64-linux-gnu/dri",
200+
"/usr/lib/aarch64-linux-gnu/dri",
201+
"/usr/lib/dri",
202+
]
203+
existing = [d for d in candidate_dirs if os.path.isfile(os.path.join(d, "swrast_dri.so"))]
204+
205+
if existing:
206+
current = os.environ.get("LIBGL_DRIVERS_PATH", "")
207+
paths = [p for p in current.split(":") if p] + existing
208+
os.environ["LIBGL_DRIVERS_PATH"] = ":".join(dict.fromkeys(paths))
209+
log.info("设置 LIBGL_DRIVERS_PATH=%s", os.environ["LIBGL_DRIVERS_PATH"])
210+
211+
# Anaconda 自带的 libstdc++.so.6 版本较旧,会导致系统 Mesa/LLVM 加载失败。
212+
# 使用 os.execve 重启自身时,LD_PRELOAD 会被子进程继承,可能破坏系统 GUI 程序。
213+
# 因此只在确认是 Anaconda/conda 环境时才做此处理,并在重启后立即清除 LD_PRELOAD
214+
# 以避免污染后续子进程。
215+
import glob
216+
sys_libstdcxx = [p for p in glob.glob("/usr/lib/x86_64-linux-gnu/libstdc++.so.6*")
217+
if not os.path.islink(p)]
218+
if not sys_libstdcxx:
219+
sys_libstdcxx = glob.glob("/usr/lib/x86_64-linux-gnu/libstdc++.so.6*")
220+
221+
if sys_libstdcxx and os.environ.get("_SEEED_LIBSTDCXX_FIXED") != "1":
222+
# 只在 conda/Anaconda 环境下才需要此 workaround
223+
conda_prefix = os.environ.get("CONDA_PREFIX") or os.environ.get("CONDA_DEFAULT_ENV")
224+
if not conda_prefix:
225+
return
226+
preload = os.environ.get("LD_PRELOAD", "")
227+
entries = [p for p in preload.split(":") if p]
228+
lib = sys_libstdcxx[0]
229+
if lib not in entries:
230+
log.info("检测到 Anaconda 环境,前置系统 libstdc++ 后重启: %s", lib)
231+
env = os.environ.copy()
232+
env["LD_PRELOAD"] = ":".join([lib] + entries)
233+
env["_SEEED_LIBSTDCXX_FIXED"] = "1"
234+
os.execve(sys.executable, [sys.executable] + sys.argv, env)
235+
# execve 替换当前进程,不会返回
236+
237+
# 重启后立即清除 LD_PRELOAD,避免污染从客户端启动的子进程(FileZilla、IDE 等)
238+
if os.environ.get("_SEEED_LIBSTDCXX_FIXED") == "1" and os.environ.get("LD_PRELOAD"):
239+
log.info("清除 LD_PRELOAD 避免污染子进程: %s", os.environ["LD_PRELOAD"])
240+
os.environ.pop("LD_PRELOAD", None)
241+
138242
_ensure_display()
243+
_ensure_mesa_dri()
139244

140245
from PyQt5.QtCore import Qt
141246
from PyQt5.QtWidgets import QApplication
@@ -144,7 +249,8 @@ def _start_xvfb(display: str) -> bool:
144249
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)
145250
QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps, True)
146251

252+
log.debug("DISPLAY=%s LD_PRELOAD=%s LIBGL_DRIVERS_PATH=%s",
253+
os.environ.get("DISPLAY"), os.environ.get("LD_PRELOAD"), os.environ.get("LIBGL_DRIVERS_PATH"))
254+
147255
from seeed_jetson_develop.gui.main_window_v2 import main
148-
from seeed_jetson_develop.gui.theme import apply_app_theme
149-
apply_app_theme()
150256
main()

0 commit comments

Comments
 (0)