debug(local-tool): driver install verbose 輸出 + 安裝後自我驗證

- Python script 對每個 PID install 後 capture traceback,不再 swallow
- 裝完立刻 scan_devices() 驗證 SDK 能不能開 handle,印 is_connectable
- 安裝結果完整寫到 server.stderr.log(而不是只回錯誤摘要)
- 判斷 DONE 標記 + 至少一個 OK 才算成功

這讓我們能清楚看到 libwdi 到底是「沒權限 / DLL 載入失敗 / 綁定成功但
裝置 is_connectable 還是 False(代表 driver 層級 OK 但裝置需要重插)」
哪一種情境,而不是只看到籠統的「error 28」。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
jim800121chen 2026-04-12 07:38:43 +08:00
parent 4902cb5531
commit d1ff55005a

View File

@ -28,30 +28,60 @@ func installKneronWinUSBDriverHandler(pythonBin string) error {
// venv 根目錄 // venv 根目錄
venvRoot := filepath.Dir(filepath.Dir(pythonBin)) venvRoot := filepath.Dir(filepath.Dir(pythonBin))
// Python script:
// 1. 對三種 PID 呼叫 kp.core.install_driver_for_windows
// 2. 安裝完立刻 scan_devices() 驗證,看 SDK 能不能開 handle
// 3. 不 swallow exception — 把 traceback 完整寫到 result 檔
pyScript := fmt.Sprintf(` pyScript := fmt.Sprintf(`
import sys, os import sys, os, traceback
result_path = r'%s' result_path = r'%s'
lines = []
def out(msg):
lines.append(str(msg))
try:
sys.stdout.write(str(msg) + '\n'); sys.stdout.flush()
except Exception:
pass
try: try:
kp_lib = os.path.join(r'%s', 'Lib', 'site-packages', 'kp', 'lib') kp_lib = os.path.join(r'%s', 'Lib', 'site-packages', 'kp', 'lib')
if os.path.isdir(kp_lib): if os.path.isdir(kp_lib):
os.environ['PATH'] = kp_lib + ';' + os.environ.get('PATH', '') os.environ['PATH'] = kp_lib + ';' + os.environ.get('PATH', '')
os.add_dll_directory(kp_lib) try:
os.add_dll_directory(kp_lib)
except Exception as dle:
out(f"WARN: add_dll_directory failed: {dle}")
import kp import kp
results = [] out(f"kp module imported: {kp.__file__}")
for pid in [kp.ProductId.KP_DEVICE_KL520, kp.ProductId.KP_DEVICE_KL720, kp.ProductId.KP_DEVICE_KL720_LEGACY]: for pid in [kp.ProductId.KP_DEVICE_KL520, kp.ProductId.KP_DEVICE_KL720, kp.ProductId.KP_DEVICE_KL720_LEGACY]:
try: try:
out(f"Installing driver for {pid.name} (value={pid.value})...")
kp.core.install_driver_for_windows(pid) kp.core.install_driver_for_windows(pid)
results.append(f"OK: {pid.name}") out(f" OK: {pid.name}")
except Exception as e: except Exception as e:
results.append(f"SKIP: {pid.name}: {e}") out(f" SKIP: {pid.name}: {type(e).__name__}: {e}")
with open(result_path, 'w') as f: # 驗證裝完立刻 scan + is_connectable
f.write('\n'.join(results) + '\nDONE\n') out("Verifying with scan_devices()...")
try:
descs = kp.core.scan_devices()
out(f" found {descs.device_descriptor_number} Kneron device(s)")
for i in range(descs.device_descriptor_number):
d = descs.device_descriptor_list[i]
out(f" [{i}] pid=0x{d.product_id:04X} usb_port={d.usb_port_id} connectable={d.is_connectable} fw={d.firmware}")
except Exception as se:
out(f" scan_devices failed: {type(se).__name__}: {se}")
out("DONE")
except ImportError as e: except ImportError as e:
with open(result_path, 'w') as f: out(f"ERROR: kp module not available: {e}")
f.write(f'ERROR: kp module not available: {e}\n') out(traceback.format_exc())
except Exception as e: except Exception as e:
with open(result_path, 'w') as f: out(f"ERROR: {type(e).__name__}: {e}")
f.write(f'ERROR: {e}\n') out(traceback.format_exc())
finally:
try:
with open(result_path, 'w', encoding='utf-8') as f:
f.write('\n'.join(lines))
except Exception:
pass
`, resultPath, venvRoot) `, resultPath, venvRoot)
scriptPath := filepath.Join(os.TempDir(), "visiona-local-install-usb-driver.py") scriptPath := filepath.Join(os.TempDir(), "visiona-local-install-usb-driver.py")
@ -77,9 +107,32 @@ except Exception as e:
} }
result := strings.TrimSpace(string(resultData)) result := strings.TrimSpace(string(resultData))
// 把完整結果印到 server stderr 方便使用者 / 我們 debug
fmt.Fprintln(os.Stderr, "=== Kneron driver install result ===")
fmt.Fprintln(os.Stderr, result)
fmt.Fprintln(os.Stderr, "=== end ===")
if strings.Contains(result, "ERROR:") { if strings.Contains(result, "ERROR:") {
return fmt.Errorf("driver 安裝失敗:%s", result) return fmt.Errorf("driver 安裝失敗(完整訊息見 server.stderr.log%s", firstLines(result, 6))
}
// 檢查 DONE 標記 + 至少一個 OK
if !strings.Contains(result, "DONE") {
return fmt.Errorf("driver 安裝未完成(沒有 DONE 標記,見 server.stderr.log%s", firstLines(result, 6))
}
if !strings.Contains(result, "OK:") {
return fmt.Errorf("driver 安裝全部失敗,沒有任何 PID 成功(見 server.stderr.log%s", firstLines(result, 10))
} }
return nil return nil
} }
// firstLines 取結果的前 n 行,避免 error message 過長
func firstLines(s string, n int) string {
lines := strings.Split(s, "\n")
if len(lines) > n {
lines = lines[:n]
lines = append(lines, "...")
}
return strings.Join(lines, " | ")
}