docs: update PRD v3.4 + TDD v2.3 for Linux installer support

PRD changes:
- F16: Add Linux platform support, 5-step install, App Alias detection
- F17: Add Linux as target platform, Linux build requirements,
  headless server mode, python3-venv auto-fix, --source winget

TDD changes:
- 11.2: Replace outdated .dmg/.msi/.deb with actual CLI/GUI install methods
- 11.3: Update GoReleaser config to match actual .goreleaser.yaml
- 11.4: Replace outdated Makefile with current targets including
  installer-linux and build-installer scripts
- 11.5: Add build-installer-linux.sh and kneron_detect.py to script table
- Installation steps table: Add Linux column with apt-get/systemd details
- Python auto-install: Document autoInstallPython3() per-platform pattern,
  findPython3() search order, ensurepip fallback on Linux
- Scripts dir: Add KL720 firmware, kneron_detect.py, update_kl720_firmware.py
- Uninstall: Add Linux systemd cleanup

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
jim800121chen 2026-03-25 01:10:32 +08:00
parent b806a42bcd
commit 3c3bc6cbec
2 changed files with 531 additions and 98 deletions

View File

@ -8,8 +8,8 @@
|------|------|
| 文件名稱 | 邊緣 AI 開發平台 PRD |
| 產品名稱 | (暫未定名,以下稱「本平台」) |
| 版本 | v3.1 |
| 日期 | 2026-03-09 |
| 版本 | v3.4 |
| 日期 | 2026-03-25 |
| 狀態 | 更新中 |
---
@ -1087,7 +1087,7 @@ Kneron Dongle Arduino 開發板 非 Kneron 晶片
### B4.5 已實作的額外功能
> 以下功能為 MVP 開發過程中額外實作,超出原始 PRD 範圍。共 11 項功能。
> 以下功能為 MVP 開發過程中額外實作,超出原始 PRD 範圍。共 13 項功能。
#### F5 — 多來源推論輸入
@ -1203,14 +1203,15 @@ Kneron Dongle Arduino 開發板 非 Kneron 晶片
| 項目 | 規格 |
|------|------|
| **概述** | 提供一行指令安裝體驗,涵蓋 binary 下載、環境設定、硬體偵測,支援 macOS 與 Windows |
| **概述** | 提供一行指令安裝體驗,涵蓋 binary 下載、環境設定、硬體偵測,支援 macOS、Windows 與 LinuxUbuntu |
| **macOS/Linux 安裝** | `curl -fsSL https://gitea.innovedus.com/.../install.sh \| bash`,自動偵測 OS + 架構darwin/linux + amd64/arm64 |
| **Windows 安裝** | `irm https://gitea.innovedus.com/.../install.ps1 \| iex`,自動下載 zip、解壓、加入 PATH |
| **安裝步驟** | 4 步驟自動化:(1) 下載 binary + 資料檔 (2) 安裝 USB 驅動libusb(3) 建立 Python venv + pyusb (4) 檢查環境 + 偵測硬體 |
| **安裝目錄** | macOS: `~/.edge-ai-platform/`、Windows: `%LOCALAPPDATA%\EdgeAIPlatform` |
| **解除安裝** | macOS: `rm -rf ~/.edge-ai-platform && sudo rm -f /usr/local/bin/edge-ai-server`、Windows: 刪除目錄 + 移除 PATH |
| **Windows 安裝** | `irm https://gitea.innovedus.com/.../install.ps1 \| iex`,自動下載 zip、解壓、加入 PATH自動偵測 Windows Store App Alias 假 Python 並提示修復 |
| **安裝步驟** | 5 步驟自動化:(1) 下載 binary + 資料檔 (2) 安裝 USB 驅動libusb(3) 建立 Python venv + pyusb (4) 檢查環境 + 偵測硬體 (5) 設定自動啟動服務 |
| **安裝目錄** | macOS/Linux: `~/.edge-ai-platform/`、Windows: `%LOCALAPPDATA%\EdgeAIPlatform` |
| **解除安裝** | macOS/Linux: `rm -rf ~/.edge-ai-platform && sudo rm -f /usr/local/bin/edge-ai-server`、Windows: 刪除目錄 + 移除 PATH |
| **自動啟動** | macOS: launchd plist`~/Library/LaunchAgents/`、Windows: Registry Run key`HKCU\...\Run`)或 Scheduled Task、Linux: systemd user service |
| **啟動依賴檢查** | Server 啟動時自動檢查 ffmpeg、yt-dlp、python3缺少時顯示對應平台的安裝指引 |
| **GoReleaser 打包** | 跨平台 archive 自動產出darwin amd64/arm64 tar.gz、windows amd64 zip含 binary + data + scripts |
| **GoReleaser 打包** | 跨平台 archive 自動產出darwin amd64/arm64 tar.gz、linux amd64 tar.gz、windows amd64 zip含 binary + data + scripts |
| **Kneron 硬體偵測** | 安裝完成時自動偵測 USB Kneron 裝置KL520/KL720/KL730使用 pyusbKL720 KDP legacy 裝置提示韌體更新 |
#### F17 — 圖形化安裝與解除安裝程式
@ -1218,24 +1219,26 @@ Kneron Dongle Arduino 開發板 非 Kneron 晶片
| 項目 | 規格 |
|------|------|
| **概述** | 提供非技術人員可使用的桌面 GUI 安裝精靈雙擊即可完成所有安裝步驟server binary、Python 環境、系統依賴、硬體驅動、媒體工具),無需開終端機或輸入任何指令 |
| **目標平台** | macOS (.app) + Windows (.exe installer) |
| **目標平台** | macOS (.app) + Windows (.exe installer) + Linux (ELF executableUbuntu 24.04+) |
| **技術方案** | Go + Wails v2WebView-based GUI前端以 HTML/CSS/JS 實作安裝畫面,後端 Go 執行實際安裝邏輯。共用 Go 工具鏈,無需額外 runtime |
| **安裝精靈流程** | 6 步驟:(1) 歡迎頁 + 授權協議 → (2) 安裝路徑選擇 → (3) 元件選擇(必要/可選)→ (4) 自動安裝 + 即時進度11 子步驟)→ (5) 硬體偵測結果 → (6) 完成 + 啟動選項 |
| **自動安裝步驟** | 11 步驟依序執行:(1) 建立安裝目錄 → (2) 解壓 server binary → (3) 解壓模型與韌體 → (4) 解壓腳本 → (5) 系統設定symlink/PATH→ (6) USB 驅動安裝libusb DLL + WinUSB driver→ (7) Python 環境設定venv + requirements + KneronPLUS SDK→ (8) 媒體工具安裝ffmpeg + yt-dlp→ (9) 寫入設定檔 → (10) 驗證安裝 → (11) 設定自動啟動 |
| **必要元件(自動安裝)** | edge-ai-server binary含嵌入式前端、Python 3 venv + numpy + opencv-python-headless + pyusb + KneronPLUS SDK.whl、Kneron 韌體檔案KL520 + KL720、NEF 預訓練模型、libusbUSB 裝置通訊、WinUSB driverWindowspnputil 自動安裝) |
| **媒體工具(自動安裝)** | ffmpeg攝影機/影片串流、yt-dlpYouTube 影片下載macOS 透過 Homebrew、Windows 透過 winget、Linux 透過 apt-get/pip3 |
| **自動依賴解析** | macOS: `brew install libusb ffmpeg yt-dlp`Windows: libusb-1.0.dllpayload 內建)+ WinUSB driverpnputil `/add-driver`+ Python 3winget 自動安裝)+ ffmpeg/yt-dlpwingetLinux: `apt-get install libusb-1.0-0-dev ffmpeg` |
| **自動依賴解析** | macOS: `brew install libusb ffmpeg yt-dlp python@3.12`Windows: libusb-1.0.dllpayload 內建)+ WinUSB driverpnputil `/add-driver`+ Python 3`winget install --source winget`,跳過 msstore 避免憑證錯誤)+ ffmpeg/yt-dlpwingetLinux: `pkexec apt-get install python3 python3-venv python3-pip libusb-1.0-0-dev ffmpeg` + 自動偵測 Python 版本安裝對應 `python3.X-venv` 套件 |
| **WinUSB 驅動安裝** | 自動解壓 kneron_winusb.inf + co-installer DLLs 並透過 `pnputil /add-driver /install` 安裝 WinUSB driver涵蓋 KL520VID_3231&PID_0100、KL720 KDPPID_0200、KL720 KDP2PID_0720。無需手動安裝 Zadig |
| **即時進度顯示** | 每個安裝步驟獨立顯示進度條 + 狀態文字,失敗的非關鍵步驟顯示 warning 並繼續;關鍵步驟失敗則中止安裝並顯示錯誤訊息 |
| **硬體偵測** | 安裝完成後自動掃描 USB Kneron 裝置顯示偵測到的晶片型號KL520/KL720、韌體版本、連線狀態KL720 KDP legacy 裝置提示一鍵韌體更新 |
| **Windows Server 控制** | Windows 安裝後以 `--gui` 模式啟動,透過原生 Windows syscall 在系統匣顯示圖示(綠色=運行中、紅色=已停止),右鍵選單提供 Start/Stop Server、Open Dashboard、Quit 功能,無需 CGO |
| **macOS Server 控制** | macOS 安裝後以 `--tray` 模式啟動,透過 fyne.io/systray 顯示系統匣圖示與選單 |
| **自動啟動** | macOS: launchd plist`~/Library/LaunchAgents/`Windows: Registry Run key`HKCU\...\Run`免管理員權限Linux: systemd user service |
| **Linux Server 控制** | Linux 安裝後以 headless 模式啟動(無 `--tray`/`--gui` 旗標binary 以 `CGO_ENABLED=0 -tags notray` 編譯,不依賴 GUI 套件 |
| **自動啟動** | macOS: launchd plist`~/Library/LaunchAgents/`Windows: Registry Run key`HKCU\...\Run`免管理員權限Linux: systemd user service`~/.config/systemd/user/` |
| **解除安裝** | 內建解除安裝功能:刪除 server binary + Python venv + 資料檔案 + drivers + symlink/PATH + 自動啟動設定 |
| **安裝目錄** | macOS: `~/.edge-ai-platform/`(不需 sudo、Windows: `%LOCALAPPDATA%\EdgeAIPlatform\`(不需管理員) |
| **安裝完成動作** | 可選擇立即啟動 serverWindows: 系統匣 GUI、macOS: system tray+ 自動開啟瀏覽器(`http://localhost:3721`)、設定開機自動啟動 |
| **安裝目錄** | macOS/Linux: `~/.edge-ai-platform/`(不需 sudo、Windows: `%LOCALAPPDATA%\EdgeAIPlatform\`(不需管理員) |
| **安裝完成動作** | 可選擇立即啟動 serverWindows: 系統匣 GUI、macOS: system tray、Linux: headless+ 自動開啟瀏覽器(`http://localhost:3721`)、設定開機自動啟動 |
| **更新支援** | 偵測現有安裝版本,僅更新 binary + 新模型保留使用者資料custom-models、設定檔與 Python venv |
| **打包產出** | macOS: `EdgeAI-Installer-macOS.zip`(含 .app、Windows: `EdgeAI-Installer-Windows.zip`(含 .exe |
| **打包產出** | macOS: `EdgeAI-Installer-macOS.zip`(含 .app、Windows: `EdgeAI-Installer-Windows.zip`(含 .exe、Linux: `EdgeAI-Installer`ELF executable |
| **Linux Build 需求** | Ubuntu 24.04+`libgtk-3-dev` + `libwebkit2gtk-4.1-dev`Wails WebView 依賴build script 自動偵測 webkit2gtk 4.0/4.1 並設定對應 build tag |
| **安裝體積** | 完整安裝約 300-400 MBbinary ~10MB + 模型 ~73MB + Python venv ~250MB + KneronPLUS SDK ~4MB + firmware ~2MB + drivers ~3MB |
#### F18 — Kneron 硬體通訊整合KL520 + KL720
@ -1248,12 +1251,15 @@ Kneron Dongle Arduino 開發板 非 Kneron 晶片
| **KL520 USB Boot** | 無板載 Flash 韌體,每次連線自動上傳 fw_scpu.bin + fw_ncpu.binUSB Boot 模式 `is_connectable=False` 為正常狀態,使用 `connect_devices_without_check()` 連線並附帶重試機制(最多 3 次,含 re-scan每 USB session 僅能載入一個模型Error 40 限制),換模型需 reset + bridge restart |
| **KL720 Flash-based** | KDP2 韌體預燒在 SPI Flash連線免上傳韌體無 Error 40 限制,可自由切換模型 |
| **KL720 KDP→KDP2 更新** | KL720 出廠可能為舊版 KDP 韌體PID=0x0200透過 DFUT magic bypass (`0x1FF55B4F`) 連線後以 `kp_update_kdp_firmware_from_files()` 永久更新為 KDP2更新後 PID 變為 0x0720 |
| **SDK** | Kneron PLUS SDK v3.1.2,從 C 原始碼編譯為 macOS dylibApple Silicon 透過 Rosetta 2 執行) |
| **SDK(跨平台)** | macOS: KneronPLUS v2.0.0 wheel`libkplus.dylib` + `libusb-1.0.0.dylib`,安裝後自動 ad-hoc codesign、Windows: KneronPLUS v3.1.2 wheel`.dll`);安裝程式依平台自動選用對應 wheel |
| **通訊架構** | Go Server → JSON-RPC (stdin/stdout) → Python Bridge (`kneron_bridge.py`) → Kneron PLUS SDK (kp) → USB → KL520/KL720 |
| **stdout 保護** | Kneron C SDK 會將 ANSI 彩色警告直接寫入 fd 1stdout污染 JSON-RPC 協定Python Bridge 啟動時以 `os.dup(1)` + `os.dup2(2, 1)` 在 OS 層級將 fd 1 重導至 stderrJSON-RPC 寫入 duped fd確保 C SDK 輸出不會破壞協定 |
| **pyusb 掃描降級** | 當 kp 模組不可用時,`handle_scan()` 自動降級至 pyusb`usb.core.find()`),透過 VID/PID 識別 Kneron 裝置並回報晶片型號 |
| **支援模型** | Kneron NEF 格式10+ 預訓練模型。KL520: Tiny YOLO v3、YOLOv5s、FCOS、ResNet18、SSD 人臉偵測。KL720: YOLOv5s、FCOS、ResNet18 |
| **跨晶片模型路徑** | `resolveModelPath()` 自動將 `data/nef/kl520/kl520_20001_...` 替換為 `data/nef/kl720/kl720_20001_...`(依連線晶片),跨平台模型自動選用正確的 NEF 檔案 |
| **推論延遲** | KL520: ~25msTiny YOLO v3, 224×224、KL720: 更快USB 3.0 + 更強 NPU |
| **後處理引擎** | 多模型自動偵測Tiny YOLO v3雙尺度 7×7+14×14、YOLOv5s三尺度 P3/P4/P5、FCOS5 層特徵金字塔、SSD 人臉偵測、ResNet18 分類Top-5 softmax |
| **NPU 影像自動縮放** | KL520 NPU 要求輸入影像尺寸 ≥ 模型輸入尺寸且寬高皆為偶數Python Bridge 推論前自動檢測並縮放(等比放大至 ≥ model_input_size再以 `(n+1) & ~1` 確保偶數),解決 YouTube/低解析度影片的 `KP_ERROR_IMAGE_RESOLUTION_TOO_SMALL_22``KP_ERROR_IMAGE_ODD_WIDTH_23` 錯誤 |
| **Letterbox 校正** | 自動讀取 `hw_pre_proc_info` 的 padding 參數,修正 bbox 座標以對應原始圖片比例 |
| **輸出格式** | 統一 `InferenceResult` JSONtaskType (detection/classification)、latencyMs、detections[{label, confidence, bbox}]、classifications[{label, confidence}] |
| **Python Bridge 指令** | `scan``connect`(含 `device_type` 參數)、`load_model``inference``reset`KP_RESET_REBOOT`disconnect` |
@ -1275,6 +1281,34 @@ Kneron Dongle Arduino 開發板 非 Kneron 晶片
| **WebSocket** | 叢集推論結果使用 `inference:cluster:{clusterId}` room 廣播 |
| **限制** | MVP 階段:每叢集最多 8 裝置;叢集設定不持久化;不支援跨機器叢集 |
#### F20 — 互動式引導導覽Guided Tour
| 項目 | 規格 |
|------|------|
| **概述** | 使用 driver.js 實作互動式頁面導覽,透過 spotlight overlay 高亮實際 UI 元素,引導新使用者走完完整操作流程 |
| **導覽步驟** | 6 步Scan Devices → Connect Device → Manage Device → Flash Model → Open Workspace → Run Inference |
| **跨頁導航** | 步驟 1-3 在 `/devices`,步驟 4-5 在 `/devices/[id]`,步驟 6 在 `/workspace/[deviceId]`;透過 `router.push()` + `MutationObserver` 等待元素出現後再高亮 |
| **觸發方式** | 首次造訪OnboardingDialog「Get Started」完成後自動啟動延遲 300ms手動Header 右上角「?」按鈕隨時重新開始 |
| **技術選型** | driver.js~5kb gzipMITspotlight overlay + popoverZustand store 管理 tour 狀態 |
| **元素標記** | 使用 `data-tour-id` 屬性標記目標元素(如 `scan-devices-btn``connect-device-btn`),避免耦合 CSS class |
| **多語系** | 所有 tour 步驟標題與描述支援 i18n英文 + 繁體中文) |
| **深色模式** | CSS 變數覆寫讓 popover 自動適配深色/淺色主題 |
| **容錯** | 若目標元素不存在(裝置未連線等),顯示置中 popover 說明先決條件;使用者導航離開 → 自動結束 tour |
#### F21 — Relay/Tunnel 遠端存取
| 項目 | 規格 |
|------|------|
| **概述** | 透過公開部署的 Relay Server讓遠端瀏覽器無需 VPN 即可存取 NAT/防火牆後的本地 Edge AI Server支援完整的 HTTP API、WebSocket 即時推論、MJPEG 影像串流 |
| **架構** | Browser → Relay Server (公網) → yamux multiplexed WebSocket → Tunnel Client (本地) → Local Server (localhost:3721) |
| **Relay Server** | 獨立 Go binary (`relay-server`),預設 port 3800多租戶架構以 token 區分不同本地 server提供 `/tunnel/connect`WebSocket 入口)、`/relay/status`(連線狀態查詢)端點 |
| **多工協定** | HashiCorp yamux 在單一 WebSocket 連線上多工多個邏輯串流;支援 HTTP 請求/回應、WebSocket 升級、MJPEG 串流分段沖刷 |
| **認證機制** | Token-based可透過 `--relay-token` 指定或由 hardware ID 自動生成;瀏覽器透過 URL 參數 `?token=xxx``X-Relay-Token` header 攜帶 tokenRelay 驗證 token 對應已連線 tunnel 後才轉發 |
| **自動重連** | Tunnel Client 連線中斷後自動指數退避重連(最長 30 秒間隔),無需人工介入 |
| **前端整合** | `RelayTokenSync` 元件自動從 URL 讀取 token 並存入 localStorage所有 API 請求自動附加 `X-Relay-Token` header |
| **本地啟用** | `edge-ai-server --relay-url ws://relay-host:3800/tunnel/connect --relay-token abc123` |
| **限制** | MVP 階段:單一 relay server 無 HA不提供端對端加密仰賴 HTTPS |
---
## B5. 功能路線圖Post-MVP

View File

@ -7,9 +7,9 @@
| 項目 | 內容 |
|------|------|
| 文件名稱 | 邊緣 AI 開發平台 TDD |
| 對應 PRD | PRD-Integrated.md v2.7 |
| 版本 | v2.0 |
| 日期 | 2026-03-09 |
| 對應 PRD | PRD-Integrated.md v3.4 |
| 版本 | v2.3 |
| 日期 | 2026-03-25 |
| 狀態 | 更新中 |
---
@ -933,6 +933,20 @@ POST /api/media/url
請求: { url: string, deviceId: string }
回應: { success: boolean, resolvedUrl?: string }
說明: 提供影片 URL 作為推論輸入支援直接連結、YouTubeyt-dlp 解析、RTSP
POST /api/media/seek
請求: { timeSeconds: number }
回應: { success: boolean, data: { seekTo: number, frameOffset: number } }
說明: 影片播放中跳轉到指定時間點,停止目前 pipeline 後重建 VideoSource 並從新位置開始推論
POST /api/media/upload/batch-images
請求: multipart/form-data (files[] + deviceId)
回應: { success: boolean, count: number }
說明: 上傳多張圖片(最多 50 張)進行批次推論,依序處理每張圖片
GET /api/media/batch-images/:index
回應: image/jpeg
說明: 取得批次上傳中指定索引的圖片原始 JPEG
```
#### 自訂模型上傳 API
@ -976,12 +990,28 @@ GET /api/camera/stream
```
GET /api/system/info
回應: { version: string, platform: string, uptime: number }
說明: Server 系統資訊
回應: { version, buildTime, platform, goVersion, uptimeSeconds }
說明: Server 系統資訊(含 Go 版本、建置時間)
GET /api/system/health
回應: { status: "ok" }
說明: 健康檢查
GET /api/system/metrics
回應: { goroutines, memHeapAllocMB, memSysMB, memHeapObjects, gcCycles, nextGcMB }
說明: 伺服器執行時期效能指標記憶體、GC、goroutine 數量)
GET /api/system/deps
回應: { deps: [{ name, installed, version?, path? }] }
說明: 外部相依套件檢查ffmpeg、yt-dlp、python3
POST /api/system/restart
回應: { success: boolean }
說明: 透過 syscall.Exec 優雅重啟 server 程序
GET /api/system/update-check
回應: { currentVersion, latestVersion, updateAvailable, releaseURL, releaseNotes, publishedAt }
說明: 查詢 Gitea Release API 檢查是否有新版本
```
### 4.2 WebSocket 端點
@ -1002,6 +1032,10 @@ ws://localhost:3721/ws/devices/:id/flash-progress
ws://localhost:3721/ws/devices/:id/log
訊息格式: { timestamp: string, level: string, message: string }
說明: 裝置通訊日誌
ws://localhost:3721/ws/server-logs
訊息格式: { timestamp: string, level: string, message: string }
說明: Server 全域日誌串流(連線時先發送緩衝的近期日誌,之後即時推送新日誌)
```
### 4.3 資料流圖
@ -1697,8 +1731,10 @@ workspace 載入 → fetchCameras() → GET /api/camera/list
curl | bash → detect_platform() → resolve_version()
→ Step 1/4: install_binary() — 下載 tar.gz + 解壓到 ~/.edge-ai-platform/ + symlink
→ Step 2/4: setup_libusb() — brew install libusb (macOS) / apt install (Linux)
→ Step 3/4: setup_python_venv() — python3 -m venv + pip install pyusb
→ Step 4/4: check_optional_deps() + detect_kneron_devices()
→ Step 3/5: setup_python_venv() — python3 -m venv + pip install pyusb + 平台感知 KneronPLUS wheel
→ Step 4/5: check_optional_deps() + detect_kneron_devices()
→ Step 5/5: setup_auto_restart() — launchd plist (macOS) / systemd user service (Linux)
含 EnvironmentVariables PATH (/usr/local/bin:/opt/homebrew/bin:...) 確保 ffmpeg/yt-dlp 可用
```
**安裝流程Windows `install.ps1`**
@ -1808,6 +1844,21 @@ result = kp.inference.generic_image_inference_receive(device_group)
SDK 的 `hw_pre_proc_info` 提供 `pad_left`, `pad_top`, `resized_w`, `resized_h` 等參數,後處理自動從模型空間轉換到原始圖片空間,消除 padding 偏移。
**跨平台 SDK 安裝:**
```
macOS: KneronPLUS v2.0.0 wheel (含 libkplus.dylib + libusb-1.0.0.dylib)
↓ pip install → codesign --force --sign - (ad-hoc 簽署)
↓ installer/app.go 或 install.sh 自動處理
Windows: KneronPLUS v3.1.2 wheel (含 .dll)
↓ pip install → 直接可用
安裝程式依 runtime.GOOS / uname -s 選取對應 wheel:
macOS → scripts/macos/KneronPLUS*.whl (v2.0.0,存放於子目錄保留原始檔名)
Windows → scripts/KneronPLUS*.whl (v3.1.2)
```
**macOS Apple Silicon 相容性:**
```
@ -1819,6 +1870,60 @@ pendian.h 修正:#if defined(__APPLE__) #include <machine/endian.h>
CMakeLists.txt 修正:移除 -Werror改用 -Wno-unused-but-set-variable
```
**stdout fd 重導向(防 C SDK ANSI 污染):**
```python
# kneron_bridge.py main() 啟動時
_real_stdout_fd = os.dup(1) # 複製 fd 1 (stdout)
os.dup2(2, 1) # fd 1 → stderrC SDK 的 ANSI 警告寫到 stderr
_real_stdout = os.fdopen(_real_stdout_fd, "w")
sys.stdout = sys.stderr # Python print 也導向 stderr
def _respond(obj):
_real_stdout.write(json.dumps(obj) + "\n") # JSON-RPC 回應寫入原始 stdout fd
_real_stdout.flush()
```
Kneron C SDK 在 KL520 USB Boot 時會直接透過 `printf()` 輸出 ANSI 彩色警告(`\x1b[0;33m[warning]...`)到 fd 1污染 JSON-RPC 協定。Python 層級的 `sys.stdout` 重導向無法攔截 C 層級的寫入,因此必須在 OS 層級用 `os.dup2()` 將 fd 1 指向 stderr。
**pyusb 掃描降級scan fallback**
```python
def handle_scan(params):
if HAS_KP:
try:
devs = kp.core.scan_devices()
# ... 使用 kp SDK 掃描
except:
pass
if not result and HAS_PYUSB:
result = _scan_with_pyusb() # usb.core.find(find_all=True, idVendor=0x3231)
return result
```
**NPU 影像自動縮放(`handle_inference` 前處理):**
KL520 NPU 對輸入影像有兩項硬體限制:
1. 寬高皆須 ≥ 模型輸入尺寸(否則回傳 `KP_ERROR_IMAGE_RESOLUTION_TOO_SMALL_22`
2. 寬高皆須為偶數(否則回傳 `KP_ERROR_IMAGE_ODD_WIDTH_23`
YouTube 等低解析度影片(如 640×360在使用 YOLOv5s640×640時會觸發上述錯誤。Bridge 在推論前自動檢測並縮放:
```python
h, w = img.shape[:2]
min_dim = _model_input_size # e.g. 640 for YOLOv5s
if w < min_dim or h < min_dim or w % 2 != 0 or h % 2 != 0:
if w < min_dim or h < min_dim:
scale = max(min_dim / w, min_dim / h)
new_w, new_h = int(w * scale), int(h * scale)
else:
new_w, new_h = w, h
new_w = (new_w + 1) & ~1 # 確保偶數
new_h = (new_h + 1) & ~1
img = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_LINEAR)
# 範例: 640×360 → 1138×640 (偶數)
```
**KL520 驗證結果bike_cars_street_224x224.bmp**
| 物件 | 信心值 | BBox (x, y, w, h) |
@ -2016,19 +2121,19 @@ type StepProgress struct {
安裝流程共 11 步驟,依序執行:
| # | 步驟 | 進度% | Critical | macOS 實作 | Windows 實作 |
|---|------|-------|----------|-----------|-------------|
| 1 | 建立安裝目錄 | 5 | Yes | `os.MkdirAll` | 同左 |
| 2 | 解壓 server binary | 10 | Yes | 從 `embed.FS` payload 解壓 | 同左 |
| 3 | 解壓模型與韌體 | 30 | Yes | 遞迴解壓 `payload/data/` | 同左 |
| 4 | 解壓腳本 | 48 | Yes | 遞迴解壓 `payload/scripts/` | 同左 |
| 5 | 系統設定 | 55 | No | `os.Symlink``/usr/local/bin/` + 移除 quarantine | 加入 User PATH (PowerShell) |
| 6 | USB 驅動安裝 | 62 | No | `brew install libusb` | 解壓 `libusb-1.0.dll` + WinUSB driver (`pnputil /add-driver kneron_winusb.inf /install`) |
| 7 | Python 環境 | 70 | No | `python3 -m venv` + `pip install -r requirements.txt` + KneronPLUS SDK .whl | 同左Windows 自動以 `winget install Python.Python.3.12` 安裝 Python |
| 8 | 媒體工具 | 78 | No | `brew install ffmpeg yt-dlp` | `winget install Gyan.FFmpeg` + `winget install yt-dlp.yt-dlp` |
| 9 | 寫入設定檔 | 85 | Yes | `config.json``~/.edge-ai-platform/` | `config.json``%LOCALAPPDATA%\EdgeAIPlatform\` |
| 10 | 驗證安裝 | 90 | No | 檢查 binary 存在且大小 > 0 | 同左 |
| 11 | 自動啟動 | 95 | No | launchd plist + `launchctl load` | Registry Run key (`HKCU\...\Run`) + 立即啟動 `--gui` |
| # | 步驟 | 進度% | Critical | macOS 實作 | Windows 實作 | Linux 實作 |
|---|------|-------|----------|-----------|-------------|------------|
| 1 | 建立安裝目錄 | 5 | Yes | `os.MkdirAll` | 同左 | 同左 |
| 2 | 解壓 server binary | 10 | Yes | 從 `embed.FS` payload 解壓 | 同左 | 同左 |
| 3 | 解壓模型與韌體 | 30 | Yes | 遞迴解壓 `payload/data/` | 同左 | 同左 |
| 4 | 解壓腳本 | 48 | Yes | 遞迴解壓 `payload/scripts/` | 同左 | 同左 |
| 5 | 系統設定 | 55 | No | `os.Symlink``/usr/local/bin/` + 移除 quarantine | 加入 User PATH (PowerShell) | `pkexec ln -sf``/usr/local/bin/` |
| 6 | USB 驅動安裝 | 62 | No | `brew install libusb` | 解壓 `libusb-1.0.dll` + WinUSB driver (`pnputil /add-driver`) | `pkexec apt-get install libusb-1.0-0-dev` |
| 7 | Python 環境 | 70 | No | `brew install python@3.12` + venv + pip | `winget install --source winget Python.Python.3.12` + venv + pip | `pkexec apt-get install python3 python3-venv python3-pip` + venv + pipvenv 失敗時自動安裝 `python3.X-venv` |
| 8 | 媒體工具 | 78 | No | `brew install ffmpeg yt-dlp` | `winget install --source winget Gyan.FFmpeg` + `yt-dlp.yt-dlp` | `pkexec apt-get install ffmpeg` + `pip3 install yt-dlp` |
| 9 | 寫入設定檔 | 85 | Yes | `config.json``~/.edge-ai-platform/` | `config.json``%LOCALAPPDATA%\EdgeAIPlatform\` | `config.json``~/.edge-ai-platform/` |
| 10 | 驗證安裝 | 90 | No | 檢查 binary 存在且大小 > 0 | 同左 | 同左 |
| 11 | 自動啟動 | 95 | No | launchd plist + `launchctl load``--tray` | Registry Run key (`HKCU\...\Run`) + 立即啟動 `--gui` | systemd user service`systemctl --user enable`headless 無旗標) |
**WinUSB Driver 自動安裝Windows**
@ -2053,16 +2158,38 @@ Windows notray build 無法使用 `fyne.io/systray`(需 CGO改用純 Go
- `FreeConsole()` 脫離父 console`CREATE_NO_WINDOW` 隱藏子 process 視窗
- Build tag: `//go:build notray && windows`
**Python 環境自動安裝:**
**Python 環境自動安裝`autoInstallPython3()`,各平台獨立實作)**
```go
// Windows: winget 自動安裝 Python 3.12
func installPython3Windows() {
exec.Command("winget", "install", "Python.Python.3.12", "--silent")
// 自動搜尋 %LOCALAPPDATA%\Programs\Python\Python3XX\ 並加入 PATH
// 三平台共用進入點 (app.go setupPythonVenv)
pythonPath, err := findPython3()
if err != nil {
inst.autoInstallPython3() // 各平台獨立實作
pythonPath, _ = findPython3()
}
// venv 建立後安裝 KneronPLUS SDK
// Windows (platform_windows.go):
// winget install Python.Python.3.12 --source winget跳過 msstore 避免憑證錯誤)
// 自動搜尋 %LOCALAPPDATA%\Programs\Python\Python3{10..13}\ 並加入 PATH
// macOS (platform_darwin.go):
// brew install python@3.12
// Linux (platform_linux.go):
// pkexec apt-get install -y python3 python3-venv python3-pip
// venv 建立失敗時ensurepip 不可用):
// 偵測 Python 版本 → pkexec apt-get install python3.X-venv → 重試 venv
// findPython3() 搜尋順序:
// 1. python3 → pythonPATH 搜尋)
// 2. 跳過 Windows Store App Alias路徑含 WindowsApps
// 3. Windows fallback掃描 %LOCALAPPDATA%\Programs\Python\Python3{10..13}\
```
```go
// venv 建立後安裝 KneronPLUS SDK跨平台
// macOS: scripts/macos/KneronPLUS*.whl (v2.0.0,含 .dylib)
// Windows: scripts/KneronPLUS*.whl (v3.1.2,含 .dll)
matches, _ := filepath.Glob(filepath.Join(scriptsDir, "KneronPLUS*.whl"))
exec.Command(pipPath, "install", matches[0])
```
@ -2311,6 +2438,173 @@ POST /api/clusters/:id/flash {modelId: "yolov5s"}
實際吞吐量受 USB 頻寬和 host CPU 限制,建議使用多個 USB controller 或 powered hub。
#### 8.5.16 互動式引導導覽F20 — Guided Tour
| 前端元件 | 狀態管理 | 說明 |
|---------|---------|------|
| `components/guided-tour.tsx` | `stores/tour-store.ts` | 核心引擎:監聽 store + pathname → 跨頁導航 → 等待元素 → 高亮 |
| `components/layout/help-button.tsx` | — | Header「?」按鈕,觸發 `startTour()` |
| `lib/tour-steps.ts` | — | 6 步驟定義id, page, selector, titleKey, descKey, side |
| `app/globals.css`driver.js 主題覆寫) | — | `.driver-popover` 用 CSS 變數適配深色模式 |
**技術選型driver.js**
輕量 spotlight overlay 函式庫(~5kb gzip, MIT核心功能
- `driver.highlight({ element, popover })` 高亮任意 DOM 元素
- CSS overlay + popover 定位,不需 portal
- `onDestroyed` callback 用於步驟切換
**導覽流程6 步跨 3 頁):**
```
/devices 頁面:
Step 1: Scan Devices → 高亮 [data-tour-id="scan-devices-btn"]
Step 2: Connect Device → 高亮 [data-tour-id="connect-device-btn"]
Step 3: Manage Device → 高亮 [data-tour-id="manage-device-btn"]
↓ router.push('/devices/{id}')
/devices/[id] 頁面:
Step 4: Flash Model → 高亮 [data-tour-id="flash-model-btn"]
Step 5: Open Workspace → 高亮 [data-tour-id="open-workspace-btn"]
↓ router.push('/workspace/{deviceId}')
/workspace/[deviceId] 頁面:
Step 6: Run Inference → 高亮 [data-tour-id="start-inference-btn"]
```
**跨頁導航處理:**
```typescript
// GuidedTour 元件監聽 pathname 和 step 變化
useEffect(() => {
if (!isActive) return;
const step = tourSteps[currentStepIndex];
if (!pathname.startsWith(step.page)) {
router.push(resolvedPage); // 導航到目標頁
return;
}
waitForElement(step.elementSelector).then(el => {
driverObj.highlight({ element: el, popover: { ... } });
});
}, [pathname, currentStepIndex, isActive]);
// MutationObserver 等待跨頁導航後元素出現
function waitForElement(selector: string, timeout = 5000): Promise<Element> {
return new Promise((resolve, reject) => {
const el = document.querySelector(selector);
if (el) return resolve(el);
const observer = new MutationObserver(() => {
const el = document.querySelector(selector);
if (el) { observer.disconnect(); resolve(el); }
});
observer.observe(document.body, { childList: true, subtree: true });
setTimeout(() => { observer.disconnect(); reject(); }, timeout);
});
}
```
**Zustand Store`stores/tour-store.ts`**
```typescript
interface TourStore {
isActive: boolean;
currentStepIndex: number;
startTour: () => void;
endTour: () => void;
nextStep: () => void;
prevStep: () => void;
markTourCompleted: () => void; // localStorage 記錄
isTourCompleted: () => boolean;
}
```
#### 8.5.17 Relay/Tunnel 遠端存取F21
**架構總覽:**
```
┌─────────────┐ HTTP + X-Relay-Token
│ Browser │ ──────────────────────────►┌─────────────────────┐
│ (Remote) │ │ Relay Server │ [:3800]
└─────────────┘ │ /tunnel/connect │
│ /relay/status │
└────────┬────────────┘
│ WebSocket + yamux
│ (多工邏輯串流)
┌────────▼────────────┐
│ Tunnel Client │
│ (本地邊緣裝置) │
└────────┬────────────┘
│ HTTP (localhost)
┌────────▼────────────┐
│ Main Server │ [:3721]
│ /api/*, /ws/*, etc │
└─────────────────────┘
```
**關鍵檔案:**
| 檔案 | 角色 | 說明 |
|------|------|------|
| `server/cmd/relay-server/main.go` | Relay Server 入口 | 獨立 binaryport 3800 |
| `server/internal/relay/server.go` | Relay 核心 | 管理 token→yamux session 映射、HTTP/WS 轉發 |
| `server/internal/tunnel/client.go` | Tunnel Client | WebSocket → yamux client → HTTP 代理到本地 |
| `server/pkg/wsconn/wsconn.go` | WebSocket→net.Conn 轉接 | 讓 yamux 能運行在 WebSocket 上 |
| `server/main.go` | Server 整合 | `--relay-url` / `--relay-token` flags |
| `frontend/src/components/relay-token-sync.tsx` | 前端 Token 同步 | URL 參數 → localStorage → `X-Relay-Token` header |
| `frontend/src/lib/api.ts` | API 請求攔截 | 自動附加 `X-Relay-Token` header |
**多工協定HashiCorp yamux**
```
Relay Server 端:
POST /tunnel/connect?token=xxx
→ WebSocket Upgrade
→ wsconn.New(conn) → net.Conn
→ yamux.Server(netConn, DefaultConfig)
→ sessions[token] = session
Browser 請求轉發:
Browser → GET /api/devices (X-Relay-Token: xxx)
→ Relay 查找 sessions[xxx]
→ session.Open() → yamux stream
→ r.Write(stream) (寫入 HTTP 請求)
→ 回應讀取 + Flush (支援 MJPEG 串流)
Tunnel Client 端:
yamux.Client(netConn) → session
→ session.Accept() loop
→ http.ReadRequest(stream)
→ http.DefaultTransport.RoundTrip(localhost:3721)
→ resp.Write(stream)
```
**WebSocket 升級處理:**
Relay 偵測到 `Upgrade: websocket` 時,走特殊路徑:
1. 將 upgrade 請求寫入 yamux stream
2. 從 stream 讀取 101 Switching Protocols
3. `w.(http.Hijacker).Hijack()` 取得 browser 原始連線
4. 雙向 `io.Copy` 連接 hijacked conn ↔ yamux stream
**認證流程:**
```
1. 本地 Server 啟動: --relay-url ws://relay:3800/tunnel/connect --relay-token abc123
若 --relay-token 未設定 → hwid.Generate() 自動從硬體 ID 生成
2. Tunnel Client → Relay: WebSocket /tunnel/connect?token=abc123
Relay 儲存: sessions["abc123"] = yamux.Session
3. 使用者開啟: http://relay:3800/?token=abc123
RelayTokenSync 元件: URL → localStorage("edge-ai-relay-token")
後續 API 請求自動附加: X-Relay-Token: abc123
4. Relay 驗證 token 有對應 session 後才轉發請求
```
**自動重連機制:**
Tunnel Client 連線中斷後自動重連使用指數退避策略1s → 2s → 4s → ... → 30s 上限),無需人工介入。
---
## 9. 開發環境與工具鏈
@ -2320,19 +2614,49 @@ POST /api/clusters/:id/flash {modelId: "yolov5s"}
| 工具 | 版本 | 用途 |
|------|------|------|
| Node.js | 20 LTS | JavaScript runtime |
| pnpm | 9.x | 套件管理(比 npm 快、磁碟效率高) |
| pnpm | 10.x | 套件管理(比 npm 快、磁碟效率高) |
| TypeScript | 5.x | 型別安全 |
| ESLint | 9.x | 程式碼品質 |
| Prettier | 3.x | 程式碼格式化 |
**核心框架與程式庫:**
| 框架/程式庫 | 版本 | 用途 |
|-------------|------|------|
| Next.js | 16.1.6 | React 全端框架App Router、`output: export` 產生靜態檔) |
| React | 19.2.3 | UI 框架 |
| Tailwind CSS | 4.x | Utility-first CSS |
| shadcn/ui | 3.8.x | UI 元件庫(基於 Radix UI |
| Zustand | 5.x | 狀態管理 |
| Recharts | 3.7.x | 圖表(叢集效能面板) |
| Lucide React | 0.575.x | 圖示庫 |
| next-themes | 0.4.x | 深色/淺色主題切換 |
| driver.js | 1.4.x | 互動式引導導覽 |
### 9.2 後端開發環境
| 工具 | 版本 | 用途 |
|------|------|------|
| Go | 1.22+ | 後端語言 |
| Go | 1.26.0 | 後端語言 |
| golangci-lint | latest | Go linter |
| Air | latest | Go 熱重載(開發用) |
| GoReleaser | latest | 跨平台打包 |
| Wails | v2.x | GUI 安裝程式框架 |
**核心 Go 模組:**
| 模組 | 版本 | 用途 |
|------|------|------|
| gin-gonic/gin | 1.11.0 | HTTP 路由框架 |
| gorilla/websocket | 1.5.3 | WebSocket 通訊 |
| hashicorp/yamux | 0.1.2 | Relay/Tunnel 多工串流 |
**Python BridgeKneron 硬體通訊):**
| 套件 | 版本 | 用途 |
|------|------|------|
| KneronPLUS SDK | macOS v2.0.0 / Windows v3.1.2 | Kneron KL520/KL720 硬體控制 |
| pyusb | latest | USB 裝置掃描kp 不可用時的降級方案) |
| OpenCV (cv2) | latest | NPU 推論前影像縮放 |
### 9.3 開發流程
@ -2461,79 +2785,117 @@ edge-ai-platform/
### 11.2 目標平台
| 平台 | 架構 | 輸出檔案 | 安裝方式 |
|------|------|---------|---------|
| macOS (Apple Silicon) | darwin/arm64 | `edge-ai-platform-darwin-arm64` | `.dmg` 或直接執行 |
| macOS (Intel) | darwin/amd64 | `edge-ai-platform-darwin-amd64` | `.dmg` 或直接執行 |
| Windows | windows/amd64 | `edge-ai-platform.exe` | `.msi` 安裝包 |
| Linux | linux/amd64 | `edge-ai-platform-linux-amd64` | `.deb` / `.rpm` / 直接執行 |
| 平台 | 架構 | CLI 安裝GoReleaser | GUI 安裝Wails | Server 啟動旗標 |
|------|------|---------|---------|---------|
| macOS (Apple Silicon) | darwin/arm64 | `install.sh` → `~/.edge-ai-platform/` | `EdgeAI-Installer.app` | `--tray` |
| macOS (Intel) | darwin/amd64 | 同上 | 同上 | `--tray` |
| Windows | windows/amd64 | `install.ps1` → `%LOCALAPPDATA%\EdgeAIPlatform` | `EdgeAI-Installer.exe` | `--gui` |
| Linux (Ubuntu) | linux/amd64 | `install.sh``~/.edge-ai-platform/` | `EdgeAI-Installer` (ELF) | 無旗標headless |
### 11.3 GoReleaser 設定
```yaml
# .goreleaser.yml
# .goreleaser.yaml
version: 2
before:
hooks:
- make build-frontend build-embed
builds:
- id: edge-ai-platform
- id: edge-ai-server
dir: server
binary: edge-ai-platform
goos:
- darwin
- linux
- windows
goarch:
- amd64
- arm64
main: ./main.go
binary: edge-ai-server
env:
- CGO_ENABLED=0
tags:
- notray
goos: [darwin, linux, windows]
goarch: [amd64, arm64]
ignore:
- goos: windows
goarch: arm64
- goos: linux
goarch: arm64
ldflags:
- -s -w -X main.version={{.Version}}
- -s -w
- -X main.Version={{.Version}}
- -X main.BuildTime={{.Date}}
archives:
- format: tar.gz
format_overrides:
- format_overrides:
- goos: windows
format: zip
files:
- src: server/data/models.json
dst: data/
- src: server/scripts/kneron_bridge.py
dst: scripts/
- src: server/scripts/requirements.txt
dst: scripts/
- src: server/scripts/update_kl720_firmware.py
dst: scripts/
- src: scripts/kneron_detect.py
dst: scripts/
- src: server/scripts/firmware/KL520/*
dst: firmware/KL520/
- src: server/scripts/firmware/KL720/*
dst: firmware/KL720/
release:
gitea:
owner: warrenchen
name: web_academy_prototype
```
### 11.4 Makefile
### 11.4 Makefile(主要 targets
```makefile
.PHONY: dev dev-frontend dev-backend build clean
# 開發模式
dev: dev-frontend dev-backend
dev-frontend:
cd frontend && pnpm dev
dev-backend:
cd server && air
# 開發
dev # 前端 + 後端(真實硬體)
dev-mock # 前端 + 後端mock 裝置)
# 建置
build: build-frontend build-backend
build # 前端靜態匯出 + embed + server binary → dist/edge-ai-server
build-server-tray # CGO_ENABLED=1含 system tray 支援
build-frontend:
cd frontend && pnpm build && pnpm export
cp -r frontend/out server/frontend/out
# Release
release-snapshot # GoReleaser 本地打包(不發佈)
release # GoReleaser 打包 + 發佈到 Gitea
build-backend: build-frontend
cd server && go build -o ../dist/edge-ai-platform
# GUI Installer
installer-payload # 準備 installer/payload/binary + data + scripts + firmware
installer # macOS Wails build + codesign
installer-linux # Linux Wails build自動偵測 webkit2gtk 4.0/4.1
installer-dev # Wails dev mode即時預覽
installer-clean # 清除 installer 產物
# 跨平台打包
release:
goreleaser release --snapshot --clean
# 測試
test # Go tests + Vitest
test-coverage # 含覆蓋率報告
```
clean:
rm -rf dist/ frontend/out/ server/frontend/out/
**Windows GUI Installer Build**
```powershell
powershell -ExecutionPolicy Bypass -File scripts\build-installer.ps1
```
**Linux GUI Installer Build**
```bash
bash scripts/build-installer-linux.sh
# 前置需求libgtk-3-dev + libwebkit2gtk-4.1-dev (Ubuntu 24.04+)
```
### 11.5 安裝腳本
| 腳本 | 平台 | 安裝目錄 | 執行方式 |
| 腳本 | 平台 | 用途 | 執行方式 |
|------|------|---------|---------|
| `scripts/install.sh` | macOS / Linux | `~/.edge-ai-platform/` | `curl -fsSL <url> \| bash` |
| `scripts/install.ps1` | Windows | `%LOCALAPPDATA%\EdgeAIPlatform` | `irm <url> \| iex` |
| `scripts/setup-kneron.sh` | macOS | 同上venv 子目錄) | `bash scripts/setup-kneron.sh` |
| `scripts/install.sh` | macOS / Linux | CLI 安裝腳本 | `curl -fsSL <url> \| bash` |
| `scripts/install.ps1` | Windows | CLI 安裝腳本(含 App Alias 偵測) | `irm <url> \| iex` |
| `scripts/build-installer.ps1` | Windows | Build GUI installer (.exe) | `powershell -File scripts\build-installer.ps1` |
| `scripts/build-installer-linux.sh` | Linux (Ubuntu) | Build GUI installer (ELF) | `bash scripts/build-installer-linux.sh` |
| `scripts/kneron_detect.py` | 全平台 | 獨立硬體偵測工具 | `python3 scripts/kneron_detect.py` |
**安裝內容:**
1. Edge AI Server binary + data 檔案
@ -2546,18 +2908,24 @@ clean:
**scripts 目錄結構:**
```
scripts/
├── kneron_bridge.py # Go↔Kneron JSON-RPC bridge
├── requirements.txt # numpy, opencv-python-headless, pyusb
├── kneron_bridge.py # Go↔Kneron JSON-RPC bridge
├── kneron_detect.py # 獨立硬體偵測工具
├── update_kl720_firmware.py # KL720 KDP→KDP2 韌體更新
├── requirements.txt # numpy, opencv-python-headless, pyusb
├── firmware/
│ └── KL520/
│ ├── fw_scpu.bin # SCPU 韌體 (52KB)
│ └── fw_ncpu.bin # NCPU 韌體 (40KB)
└── venv/ # Python venv安裝時建立
└── lib/.../site-packages/kp/ # Kneron PLUS SDK module
│ ├── KL520/
│ │ ├── fw_scpu.bin # SCPU 韌體 (52KB)
│ │ └── fw_ncpu.bin # NCPU 韌體 (40KB)
│ └── KL720/
│ ├── fw_scpu.bin # KL720 SCPU 韌體
│ └── fw_ncpu.bin # KL720 NCPU 韌體
└── venv/ # Python venv安裝時建立
└── lib/.../site-packages/kp/ # Kneron PLUS SDK module
```
**解除安裝:**
- macOS: `rm -rf ~/.edge-ai-platform && sudo rm -f /usr/local/bin/edge-ai-server`
- macOS/Linux: `rm -rf ~/.edge-ai-platform && sudo rm -f /usr/local/bin/edge-ai-server`
- Linux額外`systemctl --user disable --now edge-ai-server.service`
- Windows: `Remove-Item -Recurse -Force "$env:LOCALAPPDATA\EdgeAIPlatform"` + 移除 PATH
### 11.6 啟動依賴檢查
@ -2644,6 +3012,37 @@ scripts/
| 單元測試 | Go testing | Driver 介面、解析器、業務邏輯 |
| 整合測試 | Go testing + httptest | API 端點、WebSocket |
| Mock 測試 | Mock Driver | 無硬體環境的完整流程測試 |
| E2E 整合測試 | Go testing + httptest + Mock Driver | 完整 API 流程驗證 |
### 14.2.1 E2E 整合測試(`api_e2e_test.go`
已實作的端對端測試使用 `httptest.NewServer` 建立完整 Gin Router含所有 handler + mock devices驗證 API 端點行為:
| 測試函式 | 驗證內容 |
|---------|---------|
| `TestHealthCheck` | `GET /api/system/health` 回傳 `{"status":"ok"}` |
| `TestDeviceWorkflow_MockMode` | 完整裝置生命週期list (2 devices) → get → get 404 → connect → verify status → start inference → stop inference → disconnect |
| `TestDeviceScan_MockMode` | `POST /api/devices/scan` 在 mock 模式下回傳正確裝置數量 |
| `TestFlashDevice_NotConnected` | 未連線裝置嘗試燒錄 → 失敗 |
| `TestFlashDevice_MissingModelID` | 燒錄缺少 modelId → 失敗 |
| `TestModelList` | `GET /api/models` 成功回傳模型列表 |
| `TestAuthToken` | `GET /auth/token` 回傳正確的認證 token |
| `TestConnectNonExistentDevice` | 連線不存在的裝置 → 失敗 |
| `TestMultiDeviceIsolation` | 3 台裝置中只連線 1 台,驗證其他裝置狀態不受影響 |
```go
// 測試伺服器建立(完整 DI wiring
func setupTestServer(t *testing.T, mockCount int) *httptest.Server {
registry := device.NewRegistry()
deviceMgr := device.NewManager(registry, true, mockCount, "")
modelRepo := model.NewRepository("")
// ... 完整 wiring 所有 handler
router := api.NewRouter(modelRepo, modelStore, deviceMgr, cameraMgr, ...)
return httptest.NewServer(router)
}
```
執行方式:`cd server && go test -v ./internal/api/...`
### 14.3 關鍵測試場景