diff --git a/edge-ai-platform/docs/PRD-Integrated.md b/edge-ai-platform/docs/PRD-Integrated.md index 7bc247f..bcc1ea5 100644 --- a/edge-ai-platform/docs/PRD-Integrated.md +++ b/edge-ai-platform/docs/PRD-Integrated.md @@ -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) diff --git a/edge-ai-platform/docs/TDD.md b/edge-ai-platform/docs/TDD.md index d6e727a..745b4e4 100644 --- a/edge-ai-platform/docs/TDD.md +++ b/edge-ai-platform/docs/TDD.md @@ -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 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 { + 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 \| bash` | -| `scripts/install.ps1` | Windows | `%LOCALAPPDATA%\EdgeAIPlatform` | `irm \| iex` | -| `scripts/setup-kneron.sh` | macOS | 同上(venv 子目錄) | `bash scripts/setup-kneron.sh` | +| `scripts/install.sh` | macOS / Linux | CLI 安裝腳本 | `curl -fsSL \| bash` | +| `scripts/install.ps1` | Windows | CLI 安裝腳本(含 App Alias 偵測) | `irm \| 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 關鍵測試場景