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:
parent
b806a42bcd
commit
3c3bc6cbec
@ -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 與 Linux(Ubuntu) |
|
||||
| **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),使用 pyusb;KL720 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 executable,Ubuntu 24.04+) |
|
||||
| **技術方案** | Go + Wails v2(WebView-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 預訓練模型、libusb(USB 裝置通訊)、WinUSB driver(Windows,pnputil 自動安裝) |
|
||||
| **媒體工具(自動安裝)** | ffmpeg(攝影機/影片串流)、yt-dlp(YouTube 影片下載);macOS 透過 Homebrew、Windows 透過 winget、Linux 透過 apt-get/pip3 |
|
||||
| **自動依賴解析** | macOS: `brew install libusb ffmpeg yt-dlp`;Windows: libusb-1.0.dll(payload 內建)+ WinUSB driver(pnputil `/add-driver`)+ Python 3(winget 自動安裝)+ ffmpeg/yt-dlp(winget);Linux: `apt-get install libusb-1.0-0-dev ffmpeg` |
|
||||
| **自動依賴解析** | macOS: `brew install libusb ffmpeg yt-dlp python@3.12`;Windows: libusb-1.0.dll(payload 內建)+ WinUSB driver(pnputil `/add-driver`)+ Python 3(`winget install --source winget`,跳過 msstore 避免憑證錯誤)+ ffmpeg/yt-dlp(winget);Linux: `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,涵蓋 KL520(VID_3231&PID_0100)、KL720 KDP(PID_0200)、KL720 KDP2(PID_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\`(不需管理員) |
|
||||
| **安裝完成動作** | 可選擇立即啟動 server(Windows: 系統匣 GUI、macOS: system tray)+ 自動開啟瀏覽器(`http://localhost:3721`)、設定開機自動啟動 |
|
||||
| **安裝目錄** | macOS/Linux: `~/.edge-ai-platform/`(不需 sudo)、Windows: `%LOCALAPPDATA%\EdgeAIPlatform\`(不需管理員) |
|
||||
| **安裝完成動作** | 可選擇立即啟動 server(Windows: 系統匣 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 MB(binary ~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.bin;USB 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 dylib(Apple 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 1(stdout),污染 JSON-RPC 協定;Python Bridge 啟動時以 `os.dup(1)` + `os.dup2(2, 1)` 在 OS 層級將 fd 1 重導至 stderr,JSON-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: ~25ms(Tiny YOLO v3, 224×224)、KL720: 更快(USB 3.0 + 更強 NPU) |
|
||||
| **後處理引擎** | 多模型自動偵測:Tiny YOLO v3(雙尺度 7×7+14×14)、YOLOv5s(三尺度 P3/P4/P5)、FCOS(5 層特徵金字塔)、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` JSON:taskType (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 gzip,MIT,spotlight overlay + popover),Zustand 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 攜帶 token;Relay 驗證 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)
|
||||
|
||||
@ -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 作為推論輸入,支援直接連結、YouTube(yt-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 → stderr(C 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)在使用 YOLOv5s(640×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 + pip;venv 失敗時自動安裝 `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 → python(PATH 搜尋)
|
||||
// 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 入口 | 獨立 binary,port 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 Bridge(Kneron 硬體通訊):**
|
||||
|
||||
| 套件 | 版本 | 用途 |
|
||||
|------|------|------|
|
||||
| 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 關鍵測試場景
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user