This commit is contained in:
warrenchen 2026-03-03 16:05:47 +09:00
commit 11e779bb40
2 changed files with 876 additions and 48 deletions

View File

@ -8,8 +8,8 @@
|------|------|
| 文件名稱 | 邊緣 AI 開發平台 PRD |
| 產品名稱 | (暫未定名,以下稱「本平台」) |
| 版本 | v2.3 |
| 日期 | 2026-02-24 |
| 版本 | v2.7 |
| 日期 | 2026-03-02 |
| 狀態 | 更新中 |
---
@ -1199,6 +1199,77 @@ 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使用 pyusbKL720 KDP legacy 裝置提示韌體更新 |
#### F17 — 圖形化安裝與解除安裝程式
| 項目 | 規格 |
|------|------|
| **概述** | 提供非技術人員可使用的桌面 GUI 安裝精靈雙擊即可完成所有安裝步驟server binary、Python 環境、系統依賴、硬體驅動),無需開終端機或輸入任何指令 |
| **目標平台** | macOS (.dmg → .app) + Windows (.exe installer) |
| **技術方案** | Go + Wails v2WebView-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 預訓練模型、libusbUSB 裝置通訊) |
| **可選元件** | ffmpeg攝影機/影片串流、yt-dlpYouTube 影片下載) |
| **自動依賴解析** | macOS: 自動安裝 Homebrew若未安裝`brew install libusb python3 ffmpeg`Windows: 自動下載 Python embedded + libusb DLL免管理員權限 |
| **即時進度顯示** | 每個安裝步驟獨立顯示進度條 + 狀態文字(下載中 → 解壓中 → 設定中 → 完成),失敗時顯示錯誤訊息 + 重試按鈕 |
| **硬體偵測** | 安裝完成後自動掃描 USB Kneron 裝置顯示偵測到的晶片型號KL520/KL720、韌體版本、連線狀態KL720 KDP legacy 裝置提示一鍵韌體更新 |
| **解除安裝** | 內建解除安裝功能:刪除 server binary + Python venv + 資料檔案 + symlink/PATHmacOS 提供拖曳到垃圾桶 + 深度清理選項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 MBbinary ~10MB + 模型 ~73MB + Python venv ~250MB + firmware ~2MB |
#### F18 — Kneron 硬體通訊整合KL520 + KL720
| 項目 | 規格 |
|------|------|
| **概述** | 雙晶片 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 dylibApple Silicon 透過 Rosetta 2 執行) |
| **通訊架構** | 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: ~25msTiny YOLO v3, 224×224、KL720: 更快USB 3.0 + 更強 NPU |
| **後處理引擎** | 多模型自動偵測Tiny YOLO v3雙尺度 7×7+14×14、YOLOv5s三尺度 P3/P4/P5、FCOS5 層特徵金字塔、SSD 人臉偵測、ResNet18 分類Top-5 softmax |
| **Letterbox 校正** | 自動讀取 `hw_pre_proc_info` 的 padding 參數,修正 bbox 座標以對應原始圖片比例 |
| **輸出格式** | 統一 `InferenceResult` JSONtaskType (detection/classification)、latencyMs、detections[{label, confidence, bbox}]、classifications[{label, confidence}] |
| **Python Bridge 指令** | `scan``connect`(含 `device_type` 參數)、`load_model``inference``reset`KP_RESET_REBOOT`disconnect` |
| **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
@ -1385,4 +1456,4 @@ Phase 3 — 進階功能(長期差異化)
---
*文件版本v2.3 | 日期2026-02-24 | 狀態:更新中*
*文件版本v2.7 | 日期2026-03-02 | 狀態:更新中*

View File

@ -7,9 +7,9 @@
| 項目 | 內容 |
|------|------|
| 文件名稱 | 邊緣 AI 開發平台 TDD |
| 對應 PRD | PRD-Integrated.md v2.2 |
| 版本 | v1.2 |
| 日期 | 2026-02-24 |
| 對應 PRD | PRD-Integrated.md v2.7 |
| 版本 | v1.6 |
| 日期 | 2026-03-02 |
| 狀態 | 更新中 |
---
@ -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,64 +1200,162 @@ type BBox struct {
}
```
### 5.2 Kneron KL720 Driver 實作策略
### 5.2 Kneron Driver 實作策略已實作KL520 + KL720
Go Driver 透過 Python subprocess`kneron_bridge.py`)與 Kneron PLUS SDK 通訊,採用 JSON-RPC over stdin/stdout 模式。支援 KL520USB Boot和 KL720Flash-based兩種晶片。
```
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 {
info DeviceInfo
sdk *KneronSDK // Kneron PLUS SDK 封裝
connected bool
inferring bool
mu sync.Mutex
type KneronDriver struct {
info driver.DeviceInfo
chipType string // "KL520" or "KL720" — 從 info.Type 推導
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 (需確認實際值)
}
func (d *KL720Driver) Connect() error {
d.mu.Lock()
defer d.mu.Unlock()
// 1. 透過 Kneron PLUS SDK 初始化裝置
// 2. 取得裝置資訊 (kp_get_system_info)
// 3. 設定連線狀態
return d.sdk.Connect(d.info.Port)
}
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. 重啟裝置
}
func (d *KL720Driver) ReadInference() (*InferenceResult, error) {
// 透過 Kneron PLUS SDK 讀取推論結果
// 解析 Kneron 格式 → 統一 InferenceResult
// 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, 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 模式):**
```
connect_devices(port_id)
→ set_timeout(10000ms)
→ 偵測 firmware 狀態
→ if "Loader": load_firmware_from_file(scpu, ncpu)
→ sleep(5s) 等待 reboot
→ 重新 scan + connect
→ 回傳 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 DriverKL520 + 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)
},
})
@ -1585,6 +1682,615 @@ 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` + `scripts/firmware/KL520/` + `scripts/firmware/KL720/`
#### 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,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 (2.0) │
│ (Gin) │◄────────────│ (Python 3.9) │◄──────────│(Kneron PLUS) │◄──────────│ KL720 (3.0) │
└──────────┘ └──────────────────┘ └──────────────┘ └─────────────┘
│ │
│ chipType="KL520"/"KL720" │ _device_chip 全域變數
│ device_type 參數傳遞 │ 依晶片調整連線/韌體邏輯
```
**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. 依 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()
```
**後處理引擎細節:**
| 模型 | 偵測頭 / 輸出 | 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 相容性:**
```
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 <machine/endian.h>
CMakeLists.txt 修正:移除 -Werror改用 -Wno-unused-but-set-variable
```
**KL520 驗證結果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) |
推論延遲KL520 ~25msNPU 硬體推論)
**KL720 驗證結果:**
| 項目 | 結果 |
|------|------|
| 韌體更新 | KDP→KDP2 成功PID 0x0200→0x0720firmware: 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/JSvanilla 或 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 後端 APIWails 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
```
#### 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 channelsize=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. 開發環境與工具鏈
@ -1801,6 +2507,57 @@ clean:
rm -rf dist/ frontend/out/ server/frontend/out/
```
### 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` |
**安裝內容:**
1. Edge AI Server binary + data 檔案
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` symlinkmacOS或 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`
- Windows: `Remove-Item -Recurse -Force "$env:LOCALAPPDATA\EdgeAIPlatform"` + 移除 PATH
### 11.6 啟動依賴檢查
`internal/deps/checker.go` 在 Server 啟動時檢查外部工具:
| 工具 | 等級 | 用途 |
|------|------|------|
| ffmpeg | RequiredCamera 功能)| 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 +2839,4 @@ go.uber.org/zap // 結構化日誌
---
*文件版本v1.2 | 日期2026-02-24 | 狀態:更新中*
*文件版本v1.6 | 日期2026-03-02 | 狀態:更新中*