From 8c75a6508a21cf547c7efab0e13c6a9c83d1375a Mon Sep 17 00:00:00 2001 From: jim800121chen Date: Sat, 28 Feb 2026 00:07:35 +0800 Subject: [PATCH 1/4] docs: add F16 installation/distribution and F17 GUI installer specs PRD v2.4: Added F16 (cross-platform install scripts, GoReleaser packaging, startup dependency check, Kneron hardware detection) and F17 (planned GUI installer wizard for non-technical users). TDD v1.3: Added 8.5.12 (F16 technical mapping with install flow diagrams), 11.5 (installation scripts documentation), 11.6 (startup dependency checker). Co-Authored-By: Claude Opus 4.6 --- docs/PRD-Integrated.md | 27 +++++++++++- docs/TDD.md | 97 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 122 insertions(+), 2 deletions(-) diff --git a/docs/PRD-Integrated.md b/docs/PRD-Integrated.md index 45d2ccc..de291ec 100644 --- a/docs/PRD-Integrated.md +++ b/docs/PRD-Integrated.md @@ -1199,6 +1199,31 @@ Kneron Dongle Arduino 開發板 非 Kneron 晶片 | **無攝影機行為** | Camera tab 自動 disabled、預設切換至 Image tab、顯示「未偵測到攝影機」提示訊息 | | **有攝影機行為** | 維持原有攝影機推論功能不變 | +#### F16 — 跨平台安裝與分發 + +| 項目 | 規格 | +|------|------| +| **概述** | 提供一行指令安裝體驗,涵蓋 binary 下載、環境設定、硬體偵測,支援 macOS 與 Windows | +| **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 | +| **啟動依賴檢查** | Server 啟動時自動檢查 ffmpeg、yt-dlp、python3,缺少時顯示對應平台的安裝指引 | +| **GoReleaser 打包** | 跨平台 archive 自動產出(darwin amd64/arm64 tar.gz、windows amd64 zip),含 binary + data + scripts | +| **Kneron 硬體偵測** | 安裝完成時自動偵測 USB Kneron 裝置(KL520/KL720/KL730),使用 pyusb | + +#### F17 — 圖形化安裝與解除安裝程式(規劃中) + +| 項目 | 規格 | +|------|------| +| **概述** | 提供非技術人員可使用的桌面 GUI 安裝精靈,雙擊即可安裝或解除安裝 | +| **目標平台** | macOS (.dmg) + Windows (.exe installer) | +| **技術方案** | macOS: DMG + .app bundle 包裝、Windows: NSIS 安裝精靈 | +| **安裝精靈畫面** | 歡迎頁 → 安裝路徑選擇 → 可選元件(Python venv / Kneron driver)→ 安裝進度 → 完成 | +| **解除安裝** | macOS: .app 內含解除安裝選項、Windows: 控制台「新增或移除程式」標準流程 | +| **狀態** | 規劃中,待 CLI 安裝穩定後實作 | + --- ## B5. 功能路線圖(Post-MVP) @@ -1385,4 +1410,4 @@ Phase 3 — 進階功能(長期差異化) --- -*文件版本:v2.3 | 日期:2026-02-24 | 狀態:更新中* +*文件版本:v2.4 | 日期:2026-02-28 | 狀態:更新中* diff --git a/docs/TDD.md b/docs/TDD.md index 273caab..ea7c7c6 100644 --- a/docs/TDD.md +++ b/docs/TDD.md @@ -1585,6 +1585,65 @@ workspace 載入 → fetchCameras() → GET /api/camera/list **Hydration 安全:** 使用 `useHydrated()` hook 確保 `disabled` 屬性在 SSR 時為 `false`(與 `Tabs value="camera"` 一致),hydration 完成後才根據 cameras 列表設定 disabled 狀態。 +#### 8.5.12 跨平台安裝與分發(F16) + +| 安裝腳本 | 後端模組 | 配置檔 | +|---------|---------|--------| +| `scripts/install.sh`(macOS/Linux) | `internal/deps/checker.go` | `.goreleaser.yaml` | +| `scripts/install.ps1`(Windows) | `server/main.go`(baseDir) | `Makefile`(release targets) | +| `scripts/setup-kneron.sh`(獨立硬體設定) | — | — | +| `scripts/kneron_detect.py`(USB 偵測) | — | — | + +**安裝流程(macOS `install.sh`):** + +``` +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() +``` + +**安裝流程(Windows `install.ps1`):** + +``` +irm | iex → Invoke-RestMethod (resolve version) +→ Step 1/4: 下載 zip + Expand-Archive + 加入 PATH (User scope) +→ Step 2/4: 檢查 libusb-1.0.dll,提示 Zadig 安裝 +→ Step 3/4: python -m venv + pip install pyusb(同時嘗試 python3 / python) +→ Step 4/4: 檢查 ffmpeg/yt-dlp + kneron_detect.py +``` + +**Binary 路徑解析(`main.go` baseDir):** + +```go +func baseDir(devMode bool) string { + if devMode { return "." } // go run 用 CWD + exe, _ := os.Executable() + return filepath.Dir(exe) // production 用 binary 目錄 +} +// 3 處路徑:data/models.json, data/custom-models, scripts/kneron_bridge.py +``` + +**啟動依賴檢查(`internal/deps/checker.go`):** + +``` +Server 啟動 → deps.PrintStartupReport(logger) +→ 檢查 ffmpeg (exec.LookPath) → [OK] / [MISSING] +→ 檢查 yt-dlp → [OK] / [OPTIONAL] +→ 檢查 python3 → [OK] / [OPTIONAL] +``` + +**GoReleaser 打包產出:** + +| 平台 | 格式 | 檔名範例 | +|------|------|---------| +| macOS Intel | tar.gz | `edge-ai-platform_v0.1.0_darwin_amd64.tar.gz` | +| macOS Apple Silicon | tar.gz | `edge-ai-platform_v0.1.0_darwin_arm64.tar.gz` | +| Windows x64 | zip | `edge-ai-platform_v0.1.0_windows_amd64.zip` | + +每個 archive 含:`edge-ai-server` binary + `data/models.json` + `scripts/kneron_bridge.py` + `scripts/requirements.txt` + --- ## 9. 開發環境與工具鏈 @@ -1801,6 +1860,42 @@ clean: rm -rf dist/ frontend/out/ server/frontend/out/ ``` +### 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` | + +**安裝內容:** +1. Edge AI Server binary + data 檔案 +2. Python venv(`$INSTALL_DIR/venv`)+ pyusb +3. libusb 系統驅動(macOS: Homebrew / Linux: apt / Windows: Zadig 提示) +4. `/usr/local/bin/edge-ai-server` symlink(macOS)或 PATH 設定(Windows) + +**解除安裝:** +- macOS: `rm -rf ~/.edge-ai-platform && sudo rm -f /usr/local/bin/edge-ai-server` +- Windows: `Remove-Item -Recurse -Force "$env:LOCALAPPDATA\EdgeAIPlatform"` + 移除 PATH + +### 11.6 啟動依賴檢查 + +`internal/deps/checker.go` 在 Server 啟動時檢查外部工具: + +| 工具 | 等級 | 用途 | +|------|------|------| +| ffmpeg | Required(Camera 功能)| Camera 擷取、影片處理 | +| yt-dlp | Optional | YouTube URL 解析 | +| python3 | Optional | Kneron 硬體驅動(pyusb) | + +啟動輸出範例: +``` +[INFO] External dependency check: +[INFO] [OK] ffmpeg: ffmpeg version 7.1.1 +[INFO] [OPTIONAL] yt-dlp: not found — macOS: brew install yt-dlp | Windows: winget install yt-dlp +[INFO] [OPTIONAL] python3: Python 3.12.3 +``` + --- ## 12. 安全性考量 @@ -2082,4 +2177,4 @@ go.uber.org/zap // 結構化日誌 --- -*文件版本:v1.2 | 日期:2026-02-24 | 狀態:更新中* +*文件版本:v1.3 | 日期:2026-02-28 | 狀態:更新中* From 3f02435414a175f8e8245a6048026ea07862ebf9 Mon Sep 17 00:00:00 2001 From: jim800121chen Date: Sat, 28 Feb 2026 04:52:22 +0800 Subject: [PATCH 2/4] docs: add F18 KL520 hardware integration specs to PRD + TDD MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PRD v2.5: - Add F18: Kneron KL520 hardware communication integration - USB Boot mode with auto firmware loading - JSON-RPC bridge architecture (Go → Python → SDK → USB) - Tiny YOLO v3 inference verified (~25ms latency) - COCO 80-class detection with NMS post-processing TDD v1.4: - Update 5.2: Replace placeholder driver with actual implementation - KL720Driver with JSON-RPC over stdin/stdout - Python bridge command protocol table - KL520 USB Boot connection flow - Add 8.5.13: KL520 technical mapping with architecture diagram, inference pipeline code, YOLO post-processing specs, Apple Silicon compatibility notes, and verified detection results - Update 11.5: Add firmware files, kp module, and scripts directory structure - Update directory structure to match actual driver files Co-Authored-By: Claude Opus 4.6 --- docs/PRD-Integrated.md | 21 ++++- docs/TDD.md | 189 ++++++++++++++++++++++++++++++++--------- 2 files changed, 169 insertions(+), 41 deletions(-) diff --git a/docs/PRD-Integrated.md b/docs/PRD-Integrated.md index de291ec..5672c03 100644 --- a/docs/PRD-Integrated.md +++ b/docs/PRD-Integrated.md @@ -8,8 +8,8 @@ |------|------| | 文件名稱 | 邊緣 AI 開發平台 PRD | | 產品名稱 | (暫未定名,以下稱「本平台」) | -| 版本 | v2.3 | -| 日期 | 2026-02-24 | +| 版本 | v2.5 | +| 日期 | 2026-02-28 | | 狀態 | 更新中 | --- @@ -1224,6 +1224,23 @@ Kneron Dongle Arduino 開發板 非 Kneron 晶片 | **解除安裝** | macOS: .app 內含解除安裝選項、Windows: 控制台「新增或移除程式」標準流程 | | **狀態** | 規劃中,待 CLI 安裝穩定後實作 | +#### F18 — Kneron KL520 硬體通訊整合 + +| 項目 | 規格 | +|------|------| +| **概述** | KL520 USB Dongle 的完整通訊管線已驗證,涵蓋 USB 偵測、韌體載入、模型載入、即時推論與結果後處理 | +| **支援裝置** | Kneron KL520 (VID 0x3231, PID 0x0100),USB 2.0 High-Speed | +| **USB Boot 模式** | KL520 無板載 Flash 韌體,每次連線時 host 自動上傳 fw_scpu.bin + fw_ncpu.bin | +| **SDK** | Kneron PLUS SDK v3.1.2,從 C 原始碼編譯為 macOS dylib(Apple Silicon 透過 Rosetta 2 執行) | +| **通訊架構** | Go Server → JSON-RPC (stdin/stdout) → Python Bridge (`kneron_bridge.py`) → Kneron PLUS SDK (kp) → USB → KL520 | +| **支援模型** | Kneron NEF 格式,已驗證 Tiny YOLO v3 (model_id=19, 輸入 224×224, COCO 80 類) | +| **推論延遲** | ~25ms(KL520 NPU 硬體推論,不含圖片前處理與網路傳輸) | +| **後處理** | YOLO v3 雙尺度偵測 (7×7 + 14×14)、Sigmoid 解碼、信心門檻過濾 (≥0.25)、NMS (IoU 0.45) | +| **輸出格式** | 統一 `InferenceResult` JSON:taskType、latencyMs、detections[{label, confidence, bbox{x,y,width,height}}] | +| **Python Bridge 指令** | `scan`(裝置掃描)、`connect`(連線+自動韌體載入)、`load_model`(NEF 模型載入)、`inference`(base64 圖片推論)、`disconnect` | +| **圖片前處理** | OpenCV 解碼 → BGR565 轉換 → KP_IMAGE_FORMAT_RGB565,支援 JPEG/PNG/BMP | +| **驗證結果** | 街道場景圖片偵測到 8 個物件(person ×2、car ×5、bicycle ×1),結果正確 | + --- ## B5. 功能路線圖(Post-MVP) diff --git a/docs/TDD.md b/docs/TDD.md index ea7c7c6..a2773e9 100644 --- a/docs/TDD.md +++ b/docs/TDD.md @@ -7,9 +7,9 @@ | 項目 | 內容 | |------|------| | 文件名稱 | 邊緣 AI 開發平台 TDD | -| 對應 PRD | PRD-Integrated.md v2.2 | -| 版本 | v1.2 | -| 日期 | 2026-02-24 | +| 對應 PRD | PRD-Integrated.md v2.5 | +| 版本 | v1.4 | +| 日期 | 2026-02-28 | | 狀態 | 更新中 | --- @@ -496,9 +496,8 @@ server/ │ ├── driver/ # 裝置驅動層 │ │ ├── interface.go # DeviceDriver 介面定義 │ │ ├── kneron/ -│ │ │ ├── kl720_driver.go # KL720 Dongle 驅動 (MVP) -│ │ │ ├── kl730_driver.go # KL730 / KNEO Pi 驅動 (Phase 1) -│ │ │ └── kneron_sdk.go # Kneron PLUS SDK 封裝 +│ │ │ ├── kl720_driver.go # Kneron Driver (KL520/KL720, JSON-RPC → Python bridge) +│ │ │ └── detector.go # USB 裝置偵測 (透過 Python bridge scan) │ │ └── mock/ │ │ └── mock_driver.go # 模擬驅動 (開發/測試用) │ │ @@ -1201,48 +1200,65 @@ type BBox struct { } ``` -### 5.2 Kneron KL720 Driver 實作策略 +### 5.2 Kneron Driver 實作策略(已實作) + +Go Driver 透過 Python subprocess(`kneron_bridge.py`)與 Kneron PLUS SDK 通訊,採用 JSON-RPC over stdin/stdout 模式。 + +``` +Go Server ──stdin/stdout──► kneron_bridge.py ──ctypes──► libkplus.dylib ──USB──► KL520 + JSON-RPC kp module BULK EP +``` ```go // internal/driver/kneron/kl720_driver.go type KL720Driver struct { - info DeviceInfo - sdk *KneronSDK // Kneron PLUS SDK 封裝 - connected bool - inferring bool - mu sync.Mutex + info driver.DeviceInfo + connected bool + inferring bool + modelLoaded string + mu sync.Mutex + scriptPath string // kneron_bridge.py 路徑 + pythonCmd *exec.Cmd // Python 子進程 + stdin io.WriteCloser // JSON 指令輸入 + stdout *bufio.Scanner // JSON 回應輸出 + pythonReady bool } -// USB 裝置識別 -var KL720USBFilter = USBFilter{ - VendorID: 0x3231, // Kneron VID (需確認實際值) - ProductID: 0x0200, // KL720 PID (需確認實際值) -} +// USB 裝置識別(已確認實際值) +const KneronVendorID uint16 = 0x3231 +// KL520 PID = 0x0100, KL720 PID = 0x0200 -func (d *KL720Driver) Connect() error { - d.mu.Lock() - defer d.mu.Unlock() +// Python venv 自動偵測:scripts/venv/bin/python3 → 系統 python3 +func (d *KL720Driver) resolvePython() string { ... } - // 1. 透過 Kneron PLUS SDK 初始化裝置 - // 2. 取得裝置資訊 (kp_get_system_info) - // 3. 設定連線狀態 - return d.sdk.Connect(d.info.Port) -} +// 啟動 Python bridge,設定 DYLD_LIBRARY_PATH,等待 {"status":"ready"} +func (d *KL720Driver) startPython() error { ... } -func (d *KL720Driver) Flash(modelPath string, progressCh chan<- FlashProgress) error { - // 1. 驗證 NEF 檔案 - // 2. 進入 bootloader / DFU 模式 - // 3. 透過 kp_load_model_from_file() 上傳 NEF - // 4. 回報進度 - // 5. 驗證 CRC - // 6. 重啟裝置 -} +// JSON-RPC 通訊:Marshal → stdin → stdout → Unmarshal +func (d *KL720Driver) sendCommand(cmd map[string]interface{}) (map[string]interface{}, error) { ... } +``` -func (d *KL720Driver) ReadInference() (*InferenceResult, error) { - // 透過 Kneron PLUS SDK 讀取推論結果 - // 解析 Kneron 格式 → 統一 InferenceResult -} +**Python Bridge 指令協定:** + +| 指令 | 參數 | 回應 | 說明 | +|------|------|------|------| +| `scan` | — | `{devices: [{port, firmware, kn_number, product_id}]}` | USB 裝置掃描 | +| `connect` | `port`, `index` | `{status, firmware, kn_number}` | 連線 + 自動韌體載入 | +| `load_model` | `path` | `{status, model_id, target_chip}` | NEF 模型載入 | +| `inference` | `image_base64` | `{taskType, latencyMs, detections[...]}` | 圖片推論 | +| `disconnect` | — | `{status}` | 斷線 | + +**KL520 連線流程(USB Boot 模式):** + +``` +connect_devices(port_id) + → set_timeout(10000ms) + → 偵測 firmware 狀態 + → if "Loader": load_firmware_from_file(scpu, ncpu) + → sleep(5s) 等待 reboot + → 重新 scan + connect + → 回傳 firmware 版本 ``` ### 5.3 Driver 註冊流程 @@ -1642,7 +1658,87 @@ Server 啟動 → deps.PrintStartupReport(logger) | macOS Apple Silicon | tar.gz | `edge-ai-platform_v0.1.0_darwin_arm64.tar.gz` | | Windows x64 | zip | `edge-ai-platform_v0.1.0_windows_amd64.zip` | -每個 archive 含:`edge-ai-server` binary + `data/models.json` + `scripts/kneron_bridge.py` + `scripts/requirements.txt` +每個 archive 含:`edge-ai-server` binary + `data/models.json` + `scripts/kneron_bridge.py` + `scripts/requirements.txt` + `scripts/firmware/KL520/` + +#### 8.5.13 Kneron KL520 硬體通訊整合(F18) + +| 前端元件 | 後端模組 | 腳本/資源 | +|---------|---------|----------| +| 既有 inference panel | `internal/driver/kneron/kl720_driver.go` | `scripts/kneron_bridge.py` | +| 既有 device panel | `internal/driver/kneron/detector.go` | `scripts/firmware/KL520/fw_scpu.bin` | +| — | `internal/driver/interface.go` | `scripts/firmware/KL520/fw_ncpu.bin` | + +**通訊架構:** + +``` +┌──────────┐ JSON-RPC ┌──────────────────┐ ctypes ┌──────────────┐ USB BULK ┌───────┐ +│ Go Server│──stdin/out──►│ kneron_bridge.py │──────────►│libkplus.dylib│──────────►│ KL520 │ +│ (Gin) │◄────────────│ (Python 3.9) │◄──────────│(Kneron PLUS) │◄──────────│ (NPU) │ +└──────────┘ └──────────────────┘ └──────────────┘ └───────┘ +``` + +**kneron_bridge.py 推論流程:** + +```python +# 1. 接收 base64 圖片 +img_bytes = base64.b64decode(image_base64) + +# 2. OpenCV 解碼 + 色彩空間轉換 +img = cv2.imdecode(np.frombuffer(img_bytes, np.uint8), cv2.IMREAD_COLOR) +img_bgr565 = cv2.cvtColor(img, cv2.COLOR_BGR2BGR565) + +# 3. 建立推論描述符 +inf_config = kp.GenericImageInferenceDescriptor( + model_id=model_id, + input_node_image_list=[kp.GenericInputNodeImage( + image=img_bgr565, + image_format=kp.ImageFormat.KP_IMAGE_FORMAT_RGB565, + )] +) + +# 4. 送出推論 + 接收結果 +kp.inference.generic_image_inference_send(device_group, inf_config) +result = kp.inference.generic_image_inference_receive(device_group) + +# 5. YOLO v3 後處理:decode → sigmoid → NMS → detections[] +``` + +**YOLO v3 後處理細節:** + +| 項目 | 規格 | +|------|------| +| 偵測頭 | 雙尺度:7×7(大物件)+ 14×14(小物件) | +| Anchor boxes | 7×7: (81,82), (135,169), (344,319);14×14: (10,14), (23,27), (37,58) | +| 輸出通道 | 255 = 3 anchors × (5 + 80 classes) | +| 信心門檻 | ≥ 0.25 | +| NMS IoU 門檻 | 0.45 | +| 類別 | COCO 80 類(person, bicycle, car, ... toothbrush) | + +**macOS Apple Silicon 相容性:** + +``` +Kneron PLUS SDK (C source) ──編譯──► libkplus.dylib (x86_64) + ↓ +Python 3.9 (x86_64 via Rosetta 2) ──ctypes.cdll.LoadLibrary──► libkplus.dylib + ↓ +pendian.h 修正:#if defined(__APPLE__) #include +CMakeLists.txt 修正:移除 -Werror,改用 -Wno-unused-but-set-variable +``` + +**驗證結果(bike_cars_street_224x224.bmp):** + +| 物件 | 信心值 | BBox (x, y, w, h) | +|------|--------|-------------------| +| car | 0.998 | (0.66, 0.38, 0.20, 0.37) | +| car | 0.971 | (0.53, 0.34, 0.43, 0.43) | +| person | 0.965 | (0.26, 0.34, 0.10, 0.44) | +| person | 0.754 | (0.18, 0.34, 0.26, 0.43) | +| car | 0.499 | (0.42, 0.41, 0.12, 0.07) | +| bicycle | 0.465 | (0.26, 0.50, 0.12, 0.37) | +| car | 0.368 | (0.16, 0.37, 0.05, 0.05) | +| car | 0.294 | (0.45, 0.43, 0.06, 0.09) | + +推論延遲:~25ms(NPU 硬體推論) --- @@ -1870,9 +1966,24 @@ clean: **安裝內容:** 1. Edge AI Server binary + data 檔案 -2. Python venv(`$INSTALL_DIR/venv`)+ pyusb +2. Python venv(`$INSTALL_DIR/venv`)+ pyusb + numpy + opencv-python-headless 3. libusb 系統驅動(macOS: Homebrew / Linux: apt / Windows: Zadig 提示) 4. `/usr/local/bin/edge-ai-server` symlink(macOS)或 PATH 設定(Windows) +5. KL520 韌體檔案:`scripts/firmware/KL520/fw_scpu.bin`, `fw_ncpu.bin` +6. Kneron PLUS SDK:`kp` Python module + `libkplus.dylib`(macOS)/ `libkplus.so`(Linux) + +**scripts 目錄結構:** +``` +scripts/ +├── kneron_bridge.py # Go↔Kneron JSON-RPC bridge +├── 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 +``` **解除安裝:** - macOS: `rm -rf ~/.edge-ai-platform && sudo rm -f /usr/local/bin/edge-ai-server` From f322557af366ad6e968dca0dcd68bd35cb3571ff Mon Sep 17 00:00:00 2001 From: jim800121chen Date: Sun, 1 Mar 2026 14:09:21 +0800 Subject: [PATCH 3/4] docs: add KL720 hardware support and GUI installer design MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PRD v2.6: - F17: complete GUI installer spec (Wails v2, 6-step wizard, auto deps) - F18: expand from KL520-only to KL520+KL720 dual-chip support - KDP→KDP2 firmware update, cross-chip model path resolution TDD v1.5: - Section 5.2: rewrite Kneron driver with chip-aware architecture - Section 8.5.13: dual-chip communication diagram + KL720 verification - Section 8.5.14: new GUI installer technical design (Wails, Go API, install/uninstall flow, platform-specific handling, ASCII mockups) Co-Authored-By: Claude Opus 4.6 --- docs/PRD-Integrated.md | 58 +++-- docs/TDD.md | 466 ++++++++++++++++++++++++++++++++++++----- 2 files changed, 454 insertions(+), 70 deletions(-) diff --git a/docs/PRD-Integrated.md b/docs/PRD-Integrated.md index 5672c03..fb4aebe 100644 --- a/docs/PRD-Integrated.md +++ b/docs/PRD-Integrated.md @@ -8,8 +8,8 @@ |------|------| | 文件名稱 | 邊緣 AI 開發平台 PRD | | 產品名稱 | (暫未定名,以下稱「本平台」) | -| 版本 | v2.5 | -| 日期 | 2026-02-28 | +| 版本 | v2.6 | +| 日期 | 2026-03-01 | | 狀態 | 更新中 | --- @@ -1211,35 +1211,49 @@ Kneron Dongle Arduino 開發板 非 Kneron 晶片 | **解除安裝** | macOS: `rm -rf ~/.edge-ai-platform && sudo rm -f /usr/local/bin/edge-ai-server`、Windows: 刪除目錄 + 移除 PATH | | **啟動依賴檢查** | Server 啟動時自動檢查 ffmpeg、yt-dlp、python3,缺少時顯示對應平台的安裝指引 | | **GoReleaser 打包** | 跨平台 archive 自動產出(darwin amd64/arm64 tar.gz、windows amd64 zip),含 binary + data + scripts | -| **Kneron 硬體偵測** | 安裝完成時自動偵測 USB Kneron 裝置(KL520/KL720/KL730),使用 pyusb | +| **Kneron 硬體偵測** | 安裝完成時自動偵測 USB Kneron 裝置(KL520/KL720/KL730),使用 pyusb;KL720 KDP legacy 裝置提示韌體更新 | -#### F17 — 圖形化安裝與解除安裝程式(規劃中) +#### F17 — 圖形化安裝與解除安裝程式 | 項目 | 規格 | |------|------| -| **概述** | 提供非技術人員可使用的桌面 GUI 安裝精靈,雙擊即可安裝或解除安裝 | -| **目標平台** | macOS (.dmg) + Windows (.exe installer) | -| **技術方案** | macOS: DMG + .app bundle 包裝、Windows: NSIS 安裝精靈 | -| **安裝精靈畫面** | 歡迎頁 → 安裝路徑選擇 → 可選元件(Python venv / Kneron driver)→ 安裝進度 → 完成 | -| **解除安裝** | macOS: .app 內含解除安裝選項、Windows: 控制台「新增或移除程式」標準流程 | -| **狀態** | 規劃中,待 CLI 安裝穩定後實作 | +| **概述** | 提供非技術人員可使用的桌面 GUI 安裝精靈,雙擊即可完成所有安裝步驟(server binary、Python 環境、系統依賴、硬體驅動),無需開終端機或輸入任何指令 | +| **目標平台** | macOS (.dmg → .app) + Windows (.exe installer) | +| **技術方案** | Go + Wails v2(WebView-based GUI),前端以 HTML/CSS/JS 實作安裝畫面,後端 Go 執行實際安裝邏輯。共用 Go 工具鏈,無需額外 runtime | +| **安裝精靈流程** | 6 步驟:(1) 歡迎頁 + 授權協議 → (2) 安裝路徑選擇 → (3) 元件選擇(必要/可選)→ (4) 自動安裝 + 即時進度 → (5) 硬體偵測結果 → (6) 完成 + 啟動選項 | +| **必要元件(自動安裝)** | edge-ai-server binary(含嵌入式前端)、Python 3 venv + numpy + opencv-python-headless + pyusb、Kneron 韌體檔案(KL520 + KL720)、NEF 預訓練模型、libusb(USB 裝置通訊) | +| **可選元件** | ffmpeg(攝影機/影片串流)、yt-dlp(YouTube 影片下載) | +| **自動依賴解析** | macOS: 自動安裝 Homebrew(若未安裝)→ `brew install libusb python3 ffmpeg`;Windows: 自動下載 Python embedded + libusb DLL,免管理員權限 | +| **即時進度顯示** | 每個安裝步驟獨立顯示進度條 + 狀態文字(下載中 → 解壓中 → 設定中 → 完成),失敗時顯示錯誤訊息 + 重試按鈕 | +| **硬體偵測** | 安裝完成後自動掃描 USB Kneron 裝置,顯示偵測到的晶片型號(KL520/KL720)、韌體版本、連線狀態;KL720 KDP legacy 裝置提示一鍵韌體更新 | +| **解除安裝** | 內建解除安裝功能:刪除 server binary + Python venv + 資料檔案 + symlink/PATH,macOS 提供拖曳到垃圾桶 + 深度清理選項,Windows 整合「新增或移除程式」 | +| **安裝目錄** | macOS: `~/.edge-ai-platform/`(不需 sudo)、Windows: `%LOCALAPPDATA%\EdgeAIPlatform\`(不需管理員) | +| **安裝完成動作** | 可選擇立即啟動 server + 自動開啟瀏覽器(`http://localhost:3721`)、建立桌面捷徑、設定開機自動啟動 | +| **更新支援** | 偵測現有安裝版本,僅更新 binary + 新模型,保留使用者資料(custom-models、設定檔)與 Python venv | +| **打包產出** | macOS: `EdgeAI-Installer.dmg`(含 .app + 背景圖 + Applications 捷徑)、Windows: `EdgeAI-Setup.exe`(NSIS + Wails 包裝) | +| **安裝體積** | 完整安裝約 300-400 MB(binary ~10MB + 模型 ~73MB + Python venv ~250MB + firmware ~2MB) | -#### F18 — Kneron KL520 硬體通訊整合 +#### F18 — Kneron 硬體通訊整合(KL520 + KL720) | 項目 | 規格 | |------|------| -| **概述** | KL520 USB Dongle 的完整通訊管線已驗證,涵蓋 USB 偵測、韌體載入、模型載入、即時推論與結果後處理 | -| **支援裝置** | Kneron KL520 (VID 0x3231, PID 0x0100),USB 2.0 High-Speed | -| **USB Boot 模式** | KL520 無板載 Flash 韌體,每次連線時 host 自動上傳 fw_scpu.bin + fw_ncpu.bin | +| **概述** | 雙晶片 Kneron USB Dongle 完整通訊管線已驗證,涵蓋 USB 偵測、晶片感知連線、韌體載入/更新、模型載入、即時推論與多模型後處理 | +| **支援裝置** | Kneron KL520 (VID 0x3231, PID 0x0100, USB 2.0) + KL720 (PID 0x0720 KDP2 / PID 0x0200 KDP legacy, USB 3.0) | +| **晶片感知架構** | Driver 從 `device_type` 或 `product_id` 自動推導晶片類型(KL520/KL720),所有行為依晶片動態調整 | +| **KL520 USB Boot** | 無板載 Flash 韌體,每次連線自動上傳 fw_scpu.bin + fw_ncpu.bin;每 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 執行) | -| **通訊架構** | Go Server → JSON-RPC (stdin/stdout) → Python Bridge (`kneron_bridge.py`) → Kneron PLUS SDK (kp) → USB → KL520 | -| **支援模型** | Kneron NEF 格式,已驗證 Tiny YOLO v3 (model_id=19, 輸入 224×224, COCO 80 類) | -| **推論延遲** | ~25ms(KL520 NPU 硬體推論,不含圖片前處理與網路傳輸) | -| **後處理** | YOLO v3 雙尺度偵測 (7×7 + 14×14)、Sigmoid 解碼、信心門檻過濾 (≥0.25)、NMS (IoU 0.45) | -| **輸出格式** | 統一 `InferenceResult` JSON:taskType、latencyMs、detections[{label, confidence, bbox{x,y,width,height}}] | -| **Python Bridge 指令** | `scan`(裝置掃描)、`connect`(連線+自動韌體載入)、`load_model`(NEF 模型載入)、`inference`(base64 圖片推論)、`disconnect` | -| **圖片前處理** | OpenCV 解碼 → BGR565 轉換 → KP_IMAGE_FORMAT_RGB565,支援 JPEG/PNG/BMP | -| **驗證結果** | 街道場景圖片偵測到 8 個物件(person ×2、car ×5、bicycle ×1),結果正確 | +| **通訊架構** | Go Server → JSON-RPC (stdin/stdout) → Python Bridge (`kneron_bridge.py`) → Kneron PLUS SDK (kp) → USB → KL520/KL720 | +| **支援模型** | 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) | +| **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` | +| **Error 40 處理** | KL520: `restartBridge()` → 重建整個 Python 子進程 → 重新連線 + 載入模型。KL720: 先直接 retry → 若失敗才 fallback 到 bridge restart | +| **驗證結果** | KL520: 街道場景 8 物件偵測正確;KL720: KDP→KDP2 韌體更新成功(0x0200→0x0720)、connect/flash/inference 流程驗證 | --- diff --git a/docs/TDD.md b/docs/TDD.md index a2773e9..f4bf935 100644 --- a/docs/TDD.md +++ b/docs/TDD.md @@ -7,9 +7,9 @@ | 項目 | 內容 | |------|------| | 文件名稱 | 邊緣 AI 開發平台 TDD | -| 對應 PRD | PRD-Integrated.md v2.5 | -| 版本 | v1.4 | -| 日期 | 2026-02-28 | +| 對應 PRD | PRD-Integrated.md v2.6 | +| 版本 | v1.5 | +| 日期 | 2026-03-01 | | 狀態 | 更新中 | --- @@ -1200,20 +1200,21 @@ type BBox struct { } ``` -### 5.2 Kneron Driver 實作策略(已實作) +### 5.2 Kneron Driver 實作策略(已實作,KL520 + KL720) -Go Driver 透過 Python subprocess(`kneron_bridge.py`)與 Kneron PLUS SDK 通訊,採用 JSON-RPC over stdin/stdout 模式。 +Go Driver 透過 Python subprocess(`kneron_bridge.py`)與 Kneron PLUS SDK 通訊,採用 JSON-RPC over stdin/stdout 模式。支援 KL520(USB Boot)和 KL720(Flash-based)兩種晶片。 ``` -Go Server ──stdin/stdout──► kneron_bridge.py ──ctypes──► libkplus.dylib ──USB──► KL520 +Go Server ──stdin/stdout──► kneron_bridge.py ──ctypes──► libkplus.dylib ──USB──► KL520/KL720 JSON-RPC kp module BULK EP ``` ```go // internal/driver/kneron/kl720_driver.go -type KL720Driver struct { +type KneronDriver struct { info driver.DeviceInfo + chipType string // "KL520" or "KL720" — 從 info.Type 推導 connected bool inferring bool modelLoaded string @@ -1225,29 +1226,34 @@ type KL720Driver struct { pythonReady bool } -// USB 裝置識別(已確認實際值) -const KneronVendorID uint16 = 0x3231 -// KL520 PID = 0x0100, KL720 PID = 0x0200 - -// Python venv 自動偵測:scripts/venv/bin/python3 → 系統 python3 -func (d *KL720Driver) resolvePython() string { ... } - -// 啟動 Python bridge,設定 DYLD_LIBRARY_PATH,等待 {"status":"ready"} -func (d *KL720Driver) startPython() error { ... } - -// JSON-RPC 通訊:Marshal → stdin → stdout → Unmarshal -func (d *KL720Driver) sendCommand(cmd map[string]interface{}) (map[string]interface{}, error) { ... } +// chipType 推導邏輯 +func NewKneronDriver(info driver.DeviceInfo) *KneronDriver { + chip := "KL520" + if strings.Contains(strings.ToLower(info.Type), "kl720") { + chip = "KL720" + } + return &KneronDriver{info: info, chipType: chip, ...} +} ``` +**USB 裝置識別:** + +| 晶片 | Vendor ID | Product ID | USB | 韌體模式 | +|------|-----------|-----------|-----|---------| +| KL520 | 0x3231 | 0x0100 | 2.0 High-Speed | USB Boot(每次上傳) | +| KL720 (KDP2) | 0x3231 | 0x0720 | 3.0 Super-Speed | Flash-based(預燒) | +| KL720 (KDP legacy) | 0x3231 | 0x0200 | 3.0 Super-Speed | 舊版韌體,需更新 | + **Python Bridge 指令協定:** | 指令 | 參數 | 回應 | 說明 | |------|------|------|------| -| `scan` | — | `{devices: [{port, firmware, kn_number, product_id}]}` | USB 裝置掃描 | -| `connect` | `port`, `index` | `{status, firmware, kn_number}` | 連線 + 自動韌體載入 | -| `load_model` | `path` | `{status, model_id, target_chip}` | NEF 模型載入 | -| `inference` | `image_base64` | `{taskType, latencyMs, detections[...]}` | 圖片推論 | -| `disconnect` | — | `{status}` | 斷線 | +| `scan` | — | `{devices: [{port, firmware, kn_number, product_id, connectable}]}` | USB 裝置掃描 | +| `connect` | `port`, `device_type` | `{status, firmware, kn_number, chip, kdp_legacy?}` | 晶片感知連線 + 自動韌體處理 | +| `load_model` | `path` | `{status, model_id, model_type, input_size, target_chip}` | NEF 模型載入 + 自動類型偵測 | +| `inference` | `image_base64` | `{taskType, latencyMs, detections[], classifications[]}` | 圖片推論(偵測或分類) | +| `reset` | — | `{status}` | 裝置重置(KP_RESET_REBOOT) | +| `disconnect` | — | `{status}` | 斷線 + 狀態清理 | **KL520 連線流程(USB Boot 模式):** @@ -1261,20 +1267,95 @@ connect_devices(port_id) → 回傳 firmware 版本 ``` +**KL720 連線流程(Flash-based):** + +``` +KL720 KDP2 (pid=0x0720): + → connect_devices(port_id) — 正常連線 + → firmware 已在 flash,跳過上傳 + → 直接就緒 + +KL720 KDP legacy (pid=0x0200): + → connect_devices_without_check(port_id) — 繞過韌體版本檢查 + → load_firmware_from_file(KL720/fw_scpu.bin, fw_ncpu.bin) — 載入 KDP2 到 RAM + → sleep(5s) → reconnect + → 提示使用者執行 update_kl720_firmware.py 永久更新 +``` + +**KL720 KDP→KDP2 韌體更新流程(`update_kl720_firmware.py`):** + +``` +1. scan_devices() → 找到 pid=0x0200 的 KL720 +2. kp_connect_devices(port, &KDP_MAGIC_CONNECTION_PASS) — DFUT magic bypass +3. kp_update_kdp_firmware_from_files(dg, scpu, NULL) — flash SCPU +4. kp_reset_device() → sleep(3s) → reconnect +5. kp_update_kdp_firmware_from_files(dg, NULL, ncpu) — flash NCPU +6. kp_reset_device() → verify pid=0x0720 +``` + +**晶片感知 Error 40 處理(`Flash()` 方法):** + +``` +KL520 — Error 40 (KP_ERROR_USB_BOOT_LOAD_SECOND_MODEL_40): + → restartBridge() — 完全重啟 Python 子進程 + → 重新 connect + load_firmware + → retry load_model + +KL720 — Error 40(理論上不會發生): + → 先直接 retry load_model(不需 restart) + → 若仍失敗才 fallback 到 restartBridge() +``` + +**跨晶片模型路徑解析(`flash/service.go`):** + +```go +// resolveModelPath 自動替換晶片特定 NEF 路徑 +// 例:data/nef/kl520/kl520_20001_... → data/nef/kl720/kl720_20001_... +func resolveModelPath(filePath string, deviceType string) string { + targetChip := chipFromDeviceType(deviceType) // "kl520" or "kl720" + if alreadyTargetChip(filePath, targetChip) { return filePath } + candidate := swapChipPrefix(filePath, sourceChip, targetChip) + if fileExists(candidate) { return candidate } + return filePath // fallback 原路徑 +} +``` + +**多模型後處理引擎(`kneron_bridge.py`):** + +| 模型類型 | Model ID | 輸入尺寸 | 後處理 | 適用晶片 | +|---------|----------|---------|--------|---------| +| Tiny YOLO v3 | 0/19 | 224×224 | 雙尺度 YOLO decode + NMS | KL520 | +| YOLOv5s | 20005 | 640×640 | 三尺度 YOLO decode + NMS | KL520, KL720 | +| FCOS | 20004 | 512×512 | 5 層 FPN decode + NMS | KL520, KL720 | +| SSD (人臉) | — | 320×320 | SSD bbox decode + NMS | KL520 | +| ResNet18 | 20001 | 224×224 | Softmax + Top-5 分類 | KL520, KL720 | + ### 5.3 Driver 註冊流程 ```go // main.go or cmd/root.go func setupDrivers(registry *device.DriverRegistry) { - // 註冊 Kneron KL720 Driver + // 註冊 Kneron Driver(KL520 + KL720) + // detector.go 的 chipFromProductID() 自動判斷晶片型號 + // KL520: PID=0x0100, KL720: PID=0x0200(KDP)/0x0720(KDP2) + registry.Register(device.DriverFactory{ + Name: "kneron_kl520", + Match: func(info USBDeviceInfo) bool { + return info.VendorID == 0x3231 && info.ProductID == 0x0100 + }, + Create: func(info USBDeviceInfo) (DeviceDriver, error) { + return kneron.NewKneronDriver(info) + }, + }) registry.Register(device.DriverFactory{ Name: "kneron_kl720", Match: func(info USBDeviceInfo) bool { - return info.VendorID == 0x3231 && info.ProductID == 0x0200 + return info.VendorID == 0x3231 && + (info.ProductID == 0x0200 || info.ProductID == 0x0720) }, Create: func(info USBDeviceInfo) (DeviceDriver, error) { - return kneron.NewKL720Driver(info) + return kneron.NewKneronDriver(info) }, }) @@ -1658,23 +1739,28 @@ Server 啟動 → deps.PrintStartupReport(logger) | macOS Apple Silicon | tar.gz | `edge-ai-platform_v0.1.0_darwin_arm64.tar.gz` | | Windows x64 | zip | `edge-ai-platform_v0.1.0_windows_amd64.zip` | -每個 archive 含:`edge-ai-server` binary + `data/models.json` + `scripts/kneron_bridge.py` + `scripts/requirements.txt` + `scripts/firmware/KL520/` +每個 archive 含:`edge-ai-server` binary + `data/models.json` + `scripts/kneron_bridge.py` + `scripts/requirements.txt` + `scripts/firmware/KL520/` + `scripts/firmware/KL720/` -#### 8.5.13 Kneron KL520 硬體通訊整合(F18) +#### 8.5.13 Kneron 硬體通訊整合(F18 — KL520 + KL720) | 前端元件 | 後端模組 | 腳本/資源 | |---------|---------|----------| | 既有 inference panel | `internal/driver/kneron/kl720_driver.go` | `scripts/kneron_bridge.py` | -| 既有 device panel | `internal/driver/kneron/detector.go` | `scripts/firmware/KL520/fw_scpu.bin` | -| — | `internal/driver/interface.go` | `scripts/firmware/KL520/fw_ncpu.bin` | +| 既有 device panel | `internal/driver/kneron/detector.go` | `scripts/firmware/KL520/fw_{scpu,ncpu}.bin` | +| — | `internal/flash/service.go` (resolveModelPath) | `scripts/firmware/KL720/fw_{scpu,ncpu}.bin` | +| — | `internal/driver/interface.go` | `scripts/update_kl720_firmware.py` | +| — | — | `data/nef/kl720/kl720_2000{1,4,5}_*.nef` | -**通訊架構:** +**通訊架構(晶片感知):** ``` -┌──────────┐ JSON-RPC ┌──────────────────┐ ctypes ┌──────────────┐ USB BULK ┌───────┐ -│ Go Server│──stdin/out──►│ kneron_bridge.py │──────────►│libkplus.dylib│──────────►│ KL520 │ -│ (Gin) │◄────────────│ (Python 3.9) │◄──────────│(Kneron PLUS) │◄──────────│ (NPU) │ -└──────────┘ └──────────────────┘ └──────────────┘ └───────┘ +┌──────────┐ JSON-RPC ┌──────────────────┐ ctypes ┌──────────────┐ USB BULK ┌─────────────┐ +│ Go Server│──stdin/out──►│ kneron_bridge.py │──────────►│libkplus.dylib│──────────►│ KL520 (2.0) │ +│ (Gin) │◄────────────│ (Python 3.9) │◄──────────│(Kneron PLUS) │◄──────────│ KL720 (3.0) │ +└──────────┘ └──────────────────┘ └──────────────┘ └─────────────┘ + │ │ + │ chipType="KL520"/"KL720" │ _device_chip 全域變數 + │ device_type 參數傳遞 │ 依晶片調整連線/韌體邏輯 ``` **kneron_bridge.py 推論流程:** @@ -1700,19 +1786,27 @@ inf_config = kp.GenericImageInferenceDescriptor( kp.inference.generic_image_inference_send(device_group, inf_config) result = kp.inference.generic_image_inference_receive(device_group) -# 5. YOLO v3 後處理:decode → sigmoid → NMS → detections[] +# 5. 依 model_type 自動選擇後處理 +# tiny_yolov3 → _parse_yolo_output(ANCHORS_TINY_YOLOV3) +# yolov5s → _parse_yolo_output(ANCHORS_YOLOV5S) +# fcos → _parse_fcos_output() +# ssd → _parse_ssd_output() +# resnet18 → _parse_classification_output() ``` -**YOLO v3 後處理細節:** +**後處理引擎細節:** -| 項目 | 規格 | -|------|------| -| 偵測頭 | 雙尺度:7×7(大物件)+ 14×14(小物件) | -| Anchor boxes | 7×7: (81,82), (135,169), (344,319);14×14: (10,14), (23,27), (37,58) | -| 輸出通道 | 255 = 3 anchors × (5 + 80 classes) | -| 信心門檻 | ≥ 0.25 | -| NMS IoU 門檻 | 0.45 | -| 類別 | COCO 80 類(person, bicycle, car, ... toothbrush) | +| 模型 | 偵測頭 / 輸出 | Anchor / 架構 | 信心門檻 | NMS IoU | +|------|-------------|--------------|---------|---------| +| Tiny YOLO v3 | 雙尺度 7×7 + 14×14 | (81,82)(135,169)(344,319) / (10,14)(23,27)(37,58) | ≥ 0.25 | 0.45 | +| YOLOv5s | 三尺度 P5/P4/P3 | (116,90)(156,198)(373,326) / (30,61)(62,45)(59,119) / (10,13)(16,30)(33,23) | ≥ 0.25 | 0.45 | +| FCOS | 5 層 FPN (stride 8~128) | Anchor-free (centerness × cls) | ≥ 0.25 | 0.45 | +| SSD 人臉 | 2 類(bg + face) | SSD prior boxes | ≥ 0.25 | 0.45 | +| ResNet18 | 1000 類 softmax | Top-5 排序 | — | — | + +**Letterbox 校正(`_correct_bbox_for_letterbox`):** + +SDK 的 `hw_pre_proc_info` 提供 `pad_left`, `pad_top`, `resized_w`, `resized_h` 等參數,後處理自動從模型空間轉換到原始圖片空間,消除 padding 偏移。 **macOS Apple Silicon 相容性:** @@ -1725,7 +1819,7 @@ pendian.h 修正:#if defined(__APPLE__) #include CMakeLists.txt 修正:移除 -Werror,改用 -Wno-unused-but-set-variable ``` -**驗證結果(bike_cars_street_224x224.bmp):** +**KL520 驗證結果(bike_cars_street_224x224.bmp):** | 物件 | 信心值 | BBox (x, y, w, h) | |------|--------|-------------------| @@ -1738,7 +1832,283 @@ CMakeLists.txt 修正:移除 -Werror,改用 -Wno-unused-but-set-variable | car | 0.368 | (0.16, 0.37, 0.05, 0.05) | | car | 0.294 | (0.45, 0.43, 0.06, 0.09) | -推論延遲:~25ms(NPU 硬體推論) +推論延遲:KL520 ~25ms(NPU 硬體推論) + +**KL720 驗證結果:** + +| 項目 | 結果 | +|------|------| +| 韌體更新 | KDP→KDP2 成功(PID 0x0200→0x0720,firmware: KDP2 Comp/F) | +| 連線 | `connect_devices()` 正常連線 | +| 模型載入 | NEF 模型載入成功(跨晶片路徑解析正確) | +| USB 速度 | USB 3.0 Super-Speed | + +#### 8.5.14 圖形化安裝與解除安裝程式(F17) + +**技術選型:Wails v2** + +| 項目 | 規格 | +|------|------| +| 框架 | [Wails v2](https://wails.io/) — Go + WebView GUI 框架 | +| 選型理由 | 共用 Go 工具鏈(不需 Node.js runtime)、單一 binary 產出、WebView 體積小(~5MB vs Electron ~150MB)、macOS/Windows 原生 WebView 支援 | +| 前端 | 輕量 HTML/CSS/JS(vanilla 或 Preact),不使用 React 以減少安裝程式體積 | +| 後端 | Go — 執行實際安裝邏輯(檔案操作、下載、venv 建立、硬體偵測) | +| 產出 | macOS: `.app` → `.dmg`、Windows: `.exe`(Wails 內建 NSIS 包裝) | +| 架構分離 | Installer 是獨立 Go module(`installer/`),與 server 分開編譯,但共用 GoReleaser 發布 | + +**安裝程式目錄結構:** + +``` +installer/ +├── main.go # Wails 應用程式入口 +├── app.go # Go 後端邏輯(安裝/解除安裝/偵測) +├── wails.json # Wails 專案設定 +├── frontend/ +│ ├── index.html # 安裝精靈 UI +│ ├── style.css # 樣式(含深色模式) +│ └── main.js # 前端邏輯(步驟導航、進度更新) +└── build/ + ├── darwin/ + │ └── Info.plist # macOS app metadata + └── windows/ + └── installer.nsi # NSIS 安裝腳本 +``` + +**Go 後端 API(Wails binding → 前端可呼叫):** + +```go +// installer/app.go + +type Installer struct { + ctx context.Context + installDir string + platform string // "darwin" / "windows" + arch string // "amd64" / "arm64" + progress chan StepProgress +} + +// 安裝流程 API +func (a *Installer) GetSystemInfo() SystemInfo // 偵測 OS、已安裝版本、磁碟空間 +func (a *Installer) SetInstallDir(dir string) error // 設定安裝路徑 +func (a *Installer) GetComponents() []Component // 取得可安裝元件清單 +func (a *Installer) Install(components []string) error // 執行安裝(串流進度) +func (a *Installer) DetectHardware() []HardwareInfo // 掃描 Kneron USB 裝置 +func (a *Installer) UpdateKL720Firmware() error // KDP→KDP2 韌體更新 +func (a *Installer) LaunchServer() error // 啟動 edge-ai-server +func (a *Installer) OpenBrowser() error // 開啟瀏覽器 + +// 解除安裝 API +func (a *Installer) GetInstalledInfo() InstalledInfo // 讀取已安裝版本 + 大小 +func (a *Installer) Uninstall(keepData bool) error // 解除安裝(可選保留資料) + +// 進度回報(透過 Wails Events 推送到前端) +type StepProgress struct { + Step string `json:"step"` // "download", "extract", "python", "libusb", ... + Percent int `json:"percent"` // 0-100 + Message string `json:"message"` // 人類可讀狀態 + Error string `json:"error"` // 錯誤訊息(空=正常) +} +``` + +**安裝精靈 UI 流程(6 步驟):** + +``` +┌─────────────────────────────────────────────────────────┐ +│ Step 1: 歡迎頁 │ +│ │ +│ ┌─────────────────────────────────┐ │ +│ │ 🔧 Edge AI Platform Installer │ │ +│ │ │ │ +│ │ 版本: v0.2.0 │ │ +│ │ 安裝大小: ~350 MB │ │ +│ │ │ │ +│ │ ☐ 我同意授權條款 │ │ +│ │ │ │ +│ │ [下一步 →] │ │ +│ └─────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────┐ +│ Step 2: 安裝路徑 │ +│ │ +│ 安裝位置: │ +│ ┌──────────────────────────────┐ [瀏覽...] │ +│ │ ~/.edge-ai-platform │ │ +│ └──────────────────────────────┘ │ +│ 可用空間: 58.3 GB │ +│ 需要空間: ~350 MB │ +│ │ +│ [← 上一步] [下一步 →] │ +└─────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────┐ +│ Step 3: 元件選擇 │ +│ │ +│ 必要元件: │ +│ ☑ Edge AI Server (10 MB) ── 核心伺服器 │ +│ ☑ Python 環境 (250 MB) ── Kneron SDK 執行 │ +│ ☑ 預訓練模型 (73 MB) ── KL520/KL720 NEF │ +│ ☑ 韌體檔案 (2 MB) ── KL520/KL720 FW │ +│ ☑ USB 驅動 (libusb) (1 MB) ── 裝置通訊 │ +│ │ +│ 可選元件: │ +│ ☐ ffmpeg (80 MB) ── 攝影機/影片串流 │ +│ ☐ yt-dlp (15 MB) ── YouTube 影片 │ +│ │ +│ [← 上一步] [安裝 →] │ +└─────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────┐ +│ Step 4: 安裝進度 │ +│ │ +│ ✅ 下載 Edge AI Server 完成 │ +│ ✅ 解壓縮檔案 完成 │ +│ ✅ 安裝 libusb 完成 │ +│ 🔄 建立 Python 環境 62% │ +│ ┌──────────────████████░░░░░░──────┐ │ +│ │ 安裝 numpy... │ │ +│ └──────────────────────────────────┘ │ +│ ○ 安裝模型檔案 等待中 │ +│ ○ 偵測硬體 等待中 │ +│ │ +│ [取消] │ +└─────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────┐ +│ Step 5: 硬體偵測 │ +│ │ +│ 偵測到的 Kneron 裝置: │ +│ │ +│ ┌──────────────────────────────────┐ │ +│ │ ✅ KL720 (port 136) │ │ +│ │ Firmware: KDP2 Comp/F │ │ +│ │ PID: 0x0720 │ │ +│ ├──────────────────────────────────┤ │ +│ │ ✅ KL520 (port 33) │ │ +│ │ Firmware: Loader │ │ +│ │ PID: 0x0100 │ │ +│ └──────────────────────────────────┘ │ +│ │ +│ ⚠️ 未偵測到裝置?請確認 USB 連接 │ +│ │ +│ [← 上一步] [下一步 →] │ +└─────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────┐ +│ Step 6: 完成 │ +│ │ +│ ✅ 安裝成功! │ +│ │ +│ 安裝位置: ~/.edge-ai-platform │ +│ 版本: v0.2.0 │ +│ 安裝大小: 342 MB │ +│ │ +│ ☑ 立即啟動 Edge AI Server │ +│ ☑ 開啟瀏覽器 (http://localhost:3721) │ +│ ☐ 建立桌面捷徑 │ +│ ☐ 開機自動啟動 │ +│ │ +│ [完成] │ +└─────────────────────────────────────────────────────────┘ +``` + +**安裝步驟實作細節:** + +| 步驟 | macOS 實作 | Windows 實作 | +|------|-----------|-------------| +| 下載 binary | `net/http` GET → Gitea release tar.gz | `net/http` GET → Gitea release zip | +| 解壓 | `archive/tar` + `compress/gzip` | `archive/zip` | +| symlink | `os.Symlink()` → `/usr/local/bin/edge-ai-server` (需確認權限) | 加入 User PATH (`os.Setenv` + registry) | +| libusb | `exec.Command("brew", "install", "libusb")` 或內嵌 dylib | 內嵌 `libusb-1.0.dll` 到安裝目錄 | +| Python venv | `exec.Command("python3", "-m", "venv", ...)` | `exec.Command("python", "-m", "venv", ...)` 或內嵌 Python embedded | +| pip install | `venv/bin/pip install numpy opencv-python-headless pyusb` | `venv\Scripts\pip install ...` | +| ffmpeg | `brew install ffmpeg` (可選) | `winget install Gyan.FFmpeg` 或內嵌 (可選) | +| 硬體偵測 | `exec.Command(python, "kneron_detect.py")` | 同左 | +| 桌面捷徑 | `~/Desktop/EdgeAI.command` | `shell:desktop\EdgeAI.lnk` (COM API) | +| 開機啟動 | `~/Library/LaunchAgents/com.innovedus.edge-ai.plist` | `HKCU\...\Run` registry | + +**macOS 無 Homebrew 情境處理:** + +``` +if !commandExists("brew") { + // 方案 A: 提示安裝 Homebrew(推薦) + // 方案 B: 直接下載 libusb dylib 放入安裝目錄 + // 設定 DYLD_LIBRARY_PATH 環境變數 +} +``` + +**Windows 無 Python 情境處理:** + +``` +if !commandExists("python") && !commandExists("python3") { + // 方案 A: 自動下載 Python embeddable package (無需安裝) + // 解壓到 installDir/python/,直接使用 + // 方案 B: 提示使用者安裝 Python (winget install Python.Python.3.12) +} +``` + +**解除安裝流程:** + +```go +func (a *Installer) Uninstall(keepData bool) error { + // 1. 停止正在運行的 edge-ai-server 進程 + killProcess("edge-ai-server") + + // 2. 移除 symlink / PATH + // macOS: os.Remove("/usr/local/bin/edge-ai-server") + // Windows: 從 User PATH 移除安裝目錄 + + // 3. 移除安裝目錄 + if keepData { + // 保留 data/custom-models/(使用者自訂模型) + removeAllExcept(installDir, "data/custom-models") + } else { + os.RemoveAll(installDir) + } + + // 4. 移除桌面捷徑 / 開機啟動 + removeDesktopShortcut() + removeAutoStart() + + // 5. macOS: 不需要額外清理(brew 套件保留) + // Windows: 不移除 Python(可能被其他程式使用) +} +``` + +**更新流程(覆蓋安裝):** + +```go +func (a *Installer) Install(components []string) error { + existing := a.GetInstalledInfo() + if existing.Version != "" { + // 升級模式:保留 venv + custom-models + 設定 + // 只更新 binary + 預訓練模型 + firmware + scripts + backupUserData() + installNewVersion() + restoreUserData() + } else { + // 全新安裝 + fullInstall() + } +} +``` + +**打包與發布:** + +```yaml +# .goreleaser.yaml — Installer 額外產出 +builds: + - id: installer + dir: installer + binary: EdgeAI-Installer + goos: [darwin, windows] + goarch: [amd64, arm64] + hooks: + pre: wails build -platform {{ .Os }}/{{ .Arch }} + +# macOS: create-dmg 產出 .dmg +# Windows: NSIS 包裝成 Setup.exe +``` --- From 0103a483b822cd06967952732a27cb01c949ec91 Mon Sep 17 00:00:00 2001 From: jim800121chen Date: Mon, 2 Mar 2026 10:33:55 +0800 Subject: [PATCH 4/4] docs: add F19 cluster inference specs to PRD + TDD - PRD v2.7: F19 multi-device cluster inference feature spec - TDD v1.6: 8.5.15 cluster architecture, dispatcher, pipeline, API design Co-Authored-By: Claude Opus 4.6 --- docs/PRD-Integrated.md | 21 ++++- docs/TDD.md | 189 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 203 insertions(+), 7 deletions(-) diff --git a/docs/PRD-Integrated.md b/docs/PRD-Integrated.md index fb4aebe..dd884c3 100644 --- a/docs/PRD-Integrated.md +++ b/docs/PRD-Integrated.md @@ -8,8 +8,8 @@ |------|------| | 文件名稱 | 邊緣 AI 開發平台 PRD | | 產品名稱 | (暫未定名,以下稱「本平台」) | -| 版本 | v2.6 | -| 日期 | 2026-03-01 | +| 版本 | v2.7 | +| 日期 | 2026-03-02 | | 狀態 | 更新中 | --- @@ -1255,6 +1255,21 @@ Kneron Dongle Arduino 開發板 非 Kneron 晶片 | **Error 40 處理** | KL520: `restartBridge()` → 重建整個 Python 子進程 → 重新連線 + 載入模型。KL720: 先直接 retry → 若失敗才 fallback 到 bridge restart | | **驗證結果** | KL520: 街道場景 8 物件偵測正確;KL720: KDP→KDP2 韌體更新成功(0x0200→0x0720)、connect/flash/inference 流程驗證 | +#### F19 — 多裝置叢集推論(Cluster Inference) + +| 項目 | 規格 | +|------|------| +| **概述** | 允許使用者將多個 Kneron 邊緣裝置(KL520、KL720 可混用)組成推論叢集,透過加權分派機制將影像幀分配至各裝置並行推論,合併後以單一串流輸出結果,實現線性吞吐量擴展 | +| **叢集管理** | 建立/刪除叢集、新增/移除成員裝置;叢集設定純記憶體管理(MVP 階段),重啟後需重新建立 | +| **異質支援** | 同一叢集可混用 KL520 + KL720,系統依裝置晶片類型自動選用正確的 NEF 檔案(透過既有 `resolveModelPath` 機制) | +| **加權分派** | 依裝置算力動態分配幀:KL720 預設權重 3、KL520 預設權重 1(可由使用者調整);採用加權輪詢(Weighted Round-Robin)演算法 | +| **統一結果** | 各裝置推論結果合併至單一 channel,標記來源裝置 ID 與幀序號(frameIndex),前端可依幀序排序或直接串流顯示 | +| **叢集燒錄** | 一鍵為叢集所有裝置燒錄同一邏輯模型(系統自動依晶片選取對應 NEF),各裝置燒錄進度獨立追蹤 | +| **叢集工作區** | 單一工作區 UI 顯示叢集推論串流,側邊面板顯示各裝置個別 FPS/延遲與叢集整體 FPS | +| **容錯機制** | 裝置推論失敗自動標記為 degraded,叢集持續以剩餘裝置運作;裝置恢復後可重新加入 | +| **WebSocket** | 叢集推論結果使用 `inference:cluster:{clusterId}` room 廣播 | +| **限制** | MVP 階段:每叢集最多 8 裝置;叢集設定不持久化;不支援跨機器叢集 | + --- ## B5. 功能路線圖(Post-MVP) @@ -1441,4 +1456,4 @@ Phase 3 — 進階功能(長期差異化) --- -*文件版本:v2.4 | 日期:2026-02-28 | 狀態:更新中* +*文件版本:v2.7 | 日期:2026-03-02 | 狀態:更新中* diff --git a/docs/TDD.md b/docs/TDD.md index f4bf935..67397ca 100644 --- a/docs/TDD.md +++ b/docs/TDD.md @@ -7,9 +7,9 @@ | 項目 | 內容 | |------|------| | 文件名稱 | 邊緣 AI 開發平台 TDD | -| 對應 PRD | PRD-Integrated.md v2.6 | -| 版本 | v1.5 | -| 日期 | 2026-03-01 | +| 對應 PRD | PRD-Integrated.md v2.7 | +| 版本 | v1.6 | +| 日期 | 2026-03-02 | | 狀態 | 更新中 | --- @@ -2110,6 +2110,187 @@ builds: # Windows: NSIS 包裝成 Setup.exe ``` +#### 8.5.15 多裝置叢集推論(F19 — Cluster Inference) + +| 前端元件 | 後端模組 | 說明 | +|---------|---------|------| +| `components/cluster/cluster-list.tsx` | `internal/cluster/manager.go` | 叢集清單與 CRUD | +| `components/cluster/cluster-card.tsx` | `internal/cluster/dispatcher.go` | 加權分派引擎 | +| `components/cluster/cluster-performance.tsx` | `internal/cluster/pipeline.go` | 叢集推論管線 | +| `app/workspace/cluster/[clusterId]/` | `internal/cluster/types.go` | 叢集資料結構 | +| `stores/cluster-store.ts` | `internal/api/handlers/cluster_handler.go` | 叢集 REST API | +| `hooks/use-cluster-inference-stream.ts` | `internal/api/ws/cluster_inference_ws.go` | 叢集推論 WS | +| `types/cluster.ts` | `internal/api/ws/cluster_flash_ws.go` | 叢集燒錄 WS | + +**叢集推論架構:** + +``` +┌──────────────────────────────────────────────────────────────────────┐ +│ Cluster Manager │ +│ clusters map[string]*Cluster │ +│ ┌────────────────────────────────────────────────────────────────┐ │ +│ │ Cluster {ID, Name, Devices[], ModelID, Status} │ │ +│ └────────────────────────────────────────────────────────────────┘ │ +└──────────┬───────────────────────────────────────────────────────────┘ + │ + ▼ +┌──────────────────────────────────────────────────────────────────────┐ +│ Cluster Inference Pipeline │ +│ │ +│ FrameSource ──► Dispatcher ──┬──► Device A (KL720, w=3) ──┐ │ +│ (camera/ (weighted ├──► Device B (KL520, w=1) ──┤ │ +│ image/ round-robin) └──► Device C (KL720, w=3) ──┤ │ +│ video) │ │ +│ ▼ │ +│ Result Collector │ +│ (merge + tag │ +│ frameIndex + │ +│ deviceId) │ +│ │ │ +│ ▼ │ +│ resultCh ──► WS │ +│ "inference: │ +│ cluster:{id}" │ +└──────────────────────────────────────────────────────────────────────┘ +``` + +**核心資料結構(`internal/cluster/types.go`):** + +```go +type ClusterStatus string // "idle" / "inferencing" / "degraded" + +type DeviceMember struct { + DeviceID string `json:"deviceId"` + Weight int `json:"weight"` // 分派權重: KL720=3, KL520=1 + Status string `json:"status"` // "active", "degraded", "removed" +} + +type Cluster struct { + ID string `json:"id"` + Name string `json:"name"` + Devices []DeviceMember `json:"devices"` + ModelID string `json:"modelId,omitempty"` + Status ClusterStatus `json:"status"` +} + +type ClusterResult struct { + *driver.InferenceResult + ClusterID string `json:"clusterId"` + FrameIndex int64 `json:"frameIndex"` +} +``` + +**加權分派演算法(`internal/cluster/dispatcher.go`):** + +```go +// Weighted Round-Robin 分派器 +// 範例: devices A(w=3), B(w=1), C(w=3) +// 分派序列: A, A, A, B, C, C, C, A, A, A, B, ... +type Dispatcher struct { + members []DeviceMember + drivers []driver.DeviceDriver + current int // 目前裝置索引 + remaining int // 目前裝置剩餘幀數 + frameIndex int64 // 全域幀計數器 + mu sync.Mutex +} + +func (d *Dispatcher) Next() (driver.DeviceDriver, int64, error) // 跳過 degraded 裝置 +func (d *Dispatcher) MarkDegraded(deviceID string) // 標記故障 +func (d *Dispatcher) MarkActive(deviceID string) // 恢復正常 +func (d *Dispatcher) ActiveCount() int // 可用裝置數 +``` + +**並行推論 Worker 模式(`internal/cluster/pipeline.go`):** + +``` +Main goroutine: + for { + frame = source.ReadFrame() + frameCh <- frame (MJPEG 預覽) + device, frameIdx = dispatcher.Next() + workerCh[device] <- {frame, frameIdx} + } + +Per-device worker goroutine (每裝置一個): + for job := range workerCh { + result = device.RunInference(job.frame) + result.FrameIndex = job.frameIdx + result.DeviceID = device.Info().ID + resultCh <- result // 合併到統一輸出 + } +``` + +每裝置獨立 goroutine + buffered channel(size=2),慢裝置不會阻塞快裝置。Dispatcher 按權重分配更多幀給算力強的裝置。 + +**容錯機制:** + +```go +// Worker 內部容錯 +result, err := device.RunInference(frame) +if err != nil { + consecutiveErrors++ + if consecutiveErrors >= 3 { + dispatcher.MarkDegraded(deviceID) + cluster.Status = ClusterDegraded + // 通知前端,worker 暫停並定期重試 + } + continue +} +consecutiveErrors = 0 +resultCh <- result +``` + +**REST API:** + +| Method | Path | 功能 | +|--------|------|------| +| `GET` | `/api/clusters` | 列出所有叢集 | +| `POST` | `/api/clusters` | 建立叢集 `{name, deviceIds}` | +| `GET` | `/api/clusters/:id` | 取得叢集詳情 | +| `DELETE` | `/api/clusters/:id` | 刪除叢集 | +| `POST` | `/api/clusters/:id/devices` | 新增裝置 `{deviceId, weight?}` | +| `DELETE` | `/api/clusters/:id/devices/:deviceId` | 移除裝置 | +| `PUT` | `/api/clusters/:id/devices/:deviceId/weight` | 更新權重 `{weight}` | +| `POST` | `/api/clusters/:id/flash` | 叢集燒錄 `{modelId}` | +| `POST` | `/api/clusters/:id/inference/start` | 啟動叢集推論 | +| `POST` | `/api/clusters/:id/inference/stop` | 停止叢集推論 | + +**WebSocket 端點:** + +| Path | Room | 說明 | +|------|------|------| +| `/ws/clusters/:id/inference` | `inference:cluster:{id}` | 叢集推論結果串流 | +| `/ws/clusters/:id/flash-progress` | `flash:cluster:{id}` | 叢集燒錄進度 | + +**叢集燒錄流程(異質 NEF 自動解析):** + +``` +POST /api/clusters/:id/flash {modelId: "yolov5s"} + │ + ├── 遍歷 cluster.Devices + │ ├── device A (KL720) → resolveModelPath → data/nef/kl720/kl720_20005_yolov5s.nef + │ ├── device B (KL520) → resolveModelPath → data/nef/kl520/kl520_20005_yolov5s.nef + │ └── device C (KL720) → resolveModelPath → data/nef/kl720/kl720_20005_yolov5s.nef + │ + ├── 並行燒錄所有裝置 (各開 goroutine) + │ + └── WS 推送各裝置進度: + {deviceId: "A", percent: 45, stage: "transferring"} + {deviceId: "B", percent: 20, stage: "preparing"} +``` + +**效能估算:** + +| 配置 | 單裝置 FPS | 叢集 FPS(理論) | +|------|-----------|----------------| +| 1× KL520 | ~40 | ~40 | +| 1× KL720 | ~80 | ~80 | +| 2× KL520 + 2× KL720 | — | ~240 (40×2 + 80×2) | +| 4× KL720 | — | ~320 | + +實際吞吐量受 USB 頻寬和 host CPU 限制,建議使用多個 USB controller 或 powered hub。 + --- ## 9. 開發環境與工具鏈 @@ -2658,4 +2839,4 @@ go.uber.org/zap // 結構化日誌 --- -*文件版本:v1.3 | 日期:2026-02-28 | 狀態:更新中* +*文件版本:v1.6 | 日期:2026-03-02 | 狀態:更新中*