L 級新功能、PRD/Design/TDD/ADR 三方協作 + 互審 + M9-6 SDK 雙驗證、總計 ~9000 行文件。
範圍:
- A 階段(MVP、5 人天):KL520 + KL720 自動升級 KDP1 → KDP2
- B 階段(10.5 人天):手動降版面向一般使用者 + KL630 / KL730 擴展
- 合計 15.5 人天、安裝包 +7MB(保守 bundle 策略)
關鍵決策:
- 翻案 R5-Q9(progress.md 第二輪使用者決策「韌體燒錄 flash → B 砍掉」)
- 跨平台用 KneronPLUS Python C API、不用 DFUT.exe
- 多版本目錄結構選 C metadata(firmware/<chip>/{version}/ + CURRENT_VERSION)
- Kneron firmware redistribution 授權與 R5-B4 預置模型同性質、發佈前評估
文件產出:
- PRD v2.2(PRD-v2.md 495 行 + features/feature-firmware-management.md 599 行)
- Design v2.2(firmware-management.md 948 行 + control-panel.md §6a graceful shutdown)
- TDD v2.2(v2/firmware-management.md 823 行 + ADR-001 218 行)
- 8 份 research(含 M9-6 弱驗證 + 強驗證、~3200 行)
- 3 份三方互審報告(PM/Design/Architect cross-review)
M9-6 強驗證重大發現(影響 B 階段):
- KL730 product_id 實際是 0x732(不是 0x0730)
- KL630/KL730 firmware 是 embedded Linux rootfs(不是 .bin、不同代設計)
- KneronPLUS Python 沒 update_kdp_firmware_from_files 公開 API、warrenchen 走 ctypes
- 不影響 A 階段、B 階段 M9-8 需 spike
下一步:派 backend M9-1 起跑(bridge.py handle_firmware_upgrade)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
13 KiB
visionA-local 既有架構盤點(FW 偵測 + 升降版相關)
對應研究 plan §20 目的:列出我們已經有什麼、缺什麼、改動成本估計
1. 既有 driver 怎麼連 dongle
1.1 Driver 概覽
檔案:server/internal/driver/kneron/kl720_driver.go(檔名歷史包袱、實際同時管 KL520 + KL720)。
架構:
Go driver (KneronDriver)
↓ exec.Command(python3, kneron_bridge.py)
↓ JSON-RPC over stdin/stdout
Python bridge (kneron_bridge.py)
↓ kp module (KneronPLUS Python wheel)
↓ ctypes → libkplus.{dll,so,dylib}
USB device (Kneron Dongle)
已實作 driver methods(driver.DeviceDriver interface):
Connect()/Disconnect()/IsConnected()Flash(modelPath, progressCh)— 語意:load model 到 device RAM(不是燒 firmware)RunInference(imageData)/ReadInference()/StartInference()/StopInference()GetModelInfo()Info()— 回傳DeviceInfo含FirmwareVer
1.2 connect 流程(既有)
KneronDriver.Connect() (L250-323) 已做:
- 啟動 Python bridge subprocess
- send
connectcmd 到 bridge - bridge
handle_connect()內:kp.core.scan_devices()找 target by port- 判斷 chip type(從 device_type 或 product_id)
- 如果是 KL720 KDP legacy(pid=0x0200):自動走
connect_devices_without_check+ 載 KDP2 firmware 到 RAM(L749-804) - 如果是 USB Boot Loader mode:自動載 firmware 到 RAM(L872-914)
- 回傳
firmware字串 +fresh_firmware_loadedflag
- driver 根據
fresh_firmware_loaded決定是否做restartBridge()reset(L310-321)
1.3 已實作的 FW 偵測能力
bridge.py 已能偵測:
- USB vendor / product id(從
kp.core.scan_devices()、含 KL520 / KL630 / KL720_legacy / KL720_KDP2 / KL730、L657-664 已列表) - firmware 字串(從
device_descriptor.firmware) - kn_number、is_connectable、usb_port_id
Go driver 已記錄:
DeviceInfo.FirmwareVer(interface.goL26)DeviceInfo.ProductID/VendorID
1.4 已實作的 FW load 能力
這部分是關鍵——我們其實已經會 load_firmware_to_RAM:
kneron_bridge.py 內:
_resolve_firmware_paths(chip)— 從server/scripts/firmware/<chip>/fw_{scpu,ncpu}.bin解析路徑handle_connect()L749-764:KL720 legacy 路徑、kp.core.load_firmware_from_file(dg, scpu, ncpu)handle_connect()L876-914:USB Boot Loader 路徑、同 API
載完之後也會做 reconnect + verify(L887-912)。
缺什麼:
- 沒呼叫
kp.core.update_kdp_firmware_from_files(這個才寫 flash、持久升級) - 沒呼叫
kp.core.connect_devices_with_magic_pass(升級舊 KDP1 dongle 必要) - 沒「使用者主動升級」的入口、目前都是 connect 自動觸發
2. 既有 flash 模組(仍存在、語意 = model load)
2.1 程式碼狀態
R5-Q9 砍 flash 後、程式碼還在:
| 檔案 | 大小 | 用途 |
|---|---|---|
server/internal/flash/service.go |
158 行 | StartFlash(deviceID, modelID) — load model |
server/internal/flash/progress.go |
未讀(推測 progress tracker) | progress 追蹤 |
server/internal/driver/kneron/kl720_driver.go 的 Flash() method |
L425-604 | 實際呼叫 load_model cmd |
Flash() 的實際行為(看 L425-604):
- 不是燒 firmware
- 是 load .nef model 到 device(KL520 RAM / KL720 也是 load model)
- 含 KL520 USB Boot mode 的 retry + restartBridge fallback
R5-Q9 砍掉的是 UI 入口、不是技術能力:
device_handler.go:FlashDevice()(L128-160) 仍存在、route 也仍註冊(progress.md M1-2 跳過 cluster/tunnel/flash/update 是指 ut/update 這套 cluster 機制,不是這支 flash)- 前端 UI 是否還顯示「Flash」按鈕沒查證、但即使有也是「load model」、跟 firmware 升降版無關
2.2 對本任務的啟示
→ flash 模組保留原語意(load model)、不要把 firmware 升降版邏輯塞進去。
→ 新建 server/internal/firmware/ 模組(plan 30 會展開)、跟 flash 並列、職責清楚。
3. Bridge.py 是否有支援升降版能力(KneronPLUS Python SDK 文件對照)
3.1 KneronPLUS Python API 可用清單
從 kneron_bridge.py 內已 import 的 kp module(從 imports 推測 + warrenchen 程式碼驗證):
| API | 用途 | bridge.py 已用? |
|---|---|---|
kp.core.scan_devices() |
scan USB | ✅ 已用 |
kp.core.connect_devices(port_ids) |
標準 connect | ✅ 已用 |
kp.core.connect_devices_without_check(port_ids) |
繞 firmware check connect | ✅ 已用 |
kp.core.connect_devices_with_magic_pass(port_ids, magic) |
用 magic 繞檢查 | ❌ 缺、升級舊 KDP1 必需 |
kp.core.set_timeout(dg, ms) |
設超時 | ✅ 已用 |
kp.core.reset_device(dg, mode) |
重啟 device | ✅ 已用 |
kp.core.load_firmware_from_file(dg, scpu, ncpu) |
load to RAM | ✅ 已用 |
kp.core.update_kdp_firmware_from_files(dg, scpu, ncpu, auto_reboot) |
寫 flash 持久升級 | ❌ 缺、本任務核心 |
kp.core.load_model_from_file(dg, path) |
load model | ✅ 已用 |
kp.core.disconnect_devices(dg) |
disconnect | ✅ 已用 |
kp.core.install_driver_for_windows(target) |
裝 Windows driver | ❓ 我們有自己的 kneron_winusb.inf 機制、可能不需要 |
→ 核心缺 2 個 API call、其他都有。
3.2 既有 firmware bundle
| 路徑 | 內容 | 對 MVP 是否足夠 |
|---|---|---|
server/scripts/firmware/KL520/fw_scpu.bin |
KL520 SCPU firmware(KDP2、~52KB) | ✅ |
server/scripts/firmware/KL520/fw_ncpu.bin |
KL520 NCPU firmware(KDP2、~40KB) | ✅ |
server/scripts/firmware/KL720/fw_scpu.bin |
KL720 SCPU firmware(KDP2) | ✅ |
server/scripts/firmware/KL720/fw_ncpu.bin |
KL720 NCPU firmware(KDP2) | ✅ |
server/scripts/firmware/KL520/fw_loader.bin |
KL520 USB Boot Loader binary | ❌ 缺、升級舊 KDP1 必需 |
server/scripts/firmware/KL520/dfw/minions.bin |
跟 DFUT 配套(可能不需要) | ❌ 可能要研究 |
→ MVP 階段要從 warrenchen repo 補一個 KL520/fw_loader.bin(~10KB)。
3.3 為什麼缺的 API call 不太可能踩坑
kp.core.update_kdp_firmware_from_files 是 KneronPLUS SDK 標準 API、warrenchen 的 ctypes 版本(legacy_plus121_runner.py)也是用同一個 C symbol(lib.kp_update_kdp_firmware_from_files)、不是冷僻 API。
但要注意:
- 必須先
connect_devices_with_magic_pass——否則舊 KDP1 device 連connect_devices_without_check都會被拒(KP_ERROR_INVALID_FIRMWARE_24) - auto_reboot=True 後 disconnect 會回非零——預期行為、不能視為 error
- timeout 要拉長(warrenchen KL720 用 180s)
4. 現有 /api/devices endpoint 結構
4.1 既有 routes(從 device_handler.go)
GET /api/devices ListDevices 回 [DeviceInfo]
GET /api/devices/scan ScanDevices rescan + 回 [DeviceInfo]
GET /api/devices/:id GetDevice 回單一 DeviceInfo
POST /api/devices/:id/connect ConnectDevice
POST /api/devices/:id/disconnect DisconnectDevice
POST /api/devices/:id/flash FlashDevice (load model、保留)
POST /api/devices/:id/inference StartInference
DELETE /api/devices/:id/inference StopInference
WebSocket rooms(推測、需 grep 確認):
flash:<deviceId>— flash progressinference:<deviceId>— inference results
4.2 DeviceInfo 已含的欄位
type DeviceInfo struct {
ID string
Name string
Type string // "kl520" / "kl720"
Port string
VendorID uint16
ProductID uint16
Status DeviceStatus
FirmwareVer string // 已有!
FlashedModel string
}
4.3 為了 FW 管理需要新增的欄位(建議)
type DeviceInfo struct {
// ... 既有欄位 ...
// === FW 管理新增 ===
FirmwareIsLegacy bool `json:"firmwareIsLegacy,omitempty"` // true=需升級到 KDP2
FirmwareCanUpgrade bool `json:"firmwareCanUpgrade,omitempty"` // true=有 bundled firmware 可升
BundledFirmwareVer string `json:"bundledFirmwareVersion,omitempty"` // "v2.2.0"(從 VERSION 檔)
}
→ 這些都是 bridge.py 回傳完就能計算的衍生欄位、不額外查 USB。
4.4 為了 FW 管理需要新增的 endpoints
| Endpoint | Method | 用途 |
|---|---|---|
POST /api/devices/:id/firmware/upgrade |
POST | 觸發升級到內建 KDP2 |
WebSocket room firmware:<deviceId> |
— | 升級 progress 廣播 |
MVP 不做:
POST /firmware/downgrade(手動降版、階段 B)GET /firmware/versions(列出可選版本、階段 B)
5. 既有架構幾條重要限制(FW 升級必須避開)
5.1 KL520 reset bug 教訓(2026-04-21)
bridge.py L867-927 已有 fresh_firmware_loaded flag、避免雙重 load 浪費 60s。
升級流程要小心:
- 升級成功後 device re-enumerate、driver 端的
_device_grouphandle 失效 - 必須清掉
_device_group、回到 Go 端、讓使用者重新 scan / connect - 不要在升級 handler 內試圖「升級完繼續用同一個 connection」
5.2 Connect timeout = 120s(Windows worst-case)
device_handler.go L90 已設 120s。
升級不該共用這個 timeout:
- KL520 升級 ~30s、KL720 升級 ~180s
- 升級 endpoint 用獨立的 context.WithTimeout(300s) 或乾脆走 background goroutine + WebSocket 推進度
- 推薦做法:HTTP API 立刻回 202 Accepted + taskID、實際進度走 WebSocket(跟 flash 一樣的 pattern)
5.3 Python bridge 是 per-driver-instance
每個 KneronDriver 啟動自己的 Python bridge subprocess。升級時的 bridge 必須是當前 connected 的那一個。
升級流程:
- 升級前 bridge 已在 connected 狀態
- 升級期間 bridge 內部會 reset device、disconnect、reconnect
- 升級後 bridge 仍存活、但
_device_group已換新
5.4 三平台差異
| OS | 特殊處理 |
|---|---|
| macOS | 已用 _preload_kneron_dylibs_macos() 預載 libusb / libkplus(避開 hardened runtime 砍 DYLD) |
| Windows | 需要 WinUSB driver 綁定 + libusb-1.0.dll 在 PATH |
| Linux | wheel 自帶 libusb.so.1.0.0 + LD_LIBRARY_PATH |
→ 升級流程本身不需要額外的平台特化邏輯(用既有的 kp import 流程)。
5.5 既有 restartBridge() 不能拿來做 FW 升級
restartBridge() (L369-415) 是用來在 KL520 換 model 時清掉 USB Boot session(kill bridge → sleep 8s → 重啟 bridge)、跟 firmware update 完全不同。
→ FW 升級走獨立的 handler、不重用 restartBridge。但升級完成後、driver 應該 mark needsReset=true、下次 connect 走完整 reset flow(既有邏輯)保證 clean state。
6. 既有 UI 概況(推測、未實際讀前端 code)
從 progress.md + TDD 推測 visionA-local Web UI 結構:
/devices頁面(Next.js)顯示掃描到的 dongle 清單- 每張 device card 已顯示
firmwareVersion欄位(既有 DeviceInfo 有此欄位) - 但沒有「升級」按鈕、沒有 FW 健康度視覺化(綠/黃/紅)
MVP UI 新增(plan 30 會展開):
- FW badge(綠:KDP2 最新、黃:KDP2 但版本低、紅:KDP1 needs upgrade)
- 「升級韌體」按鈕(紅 badge 時 primary、其他 secondary)
- 升級 modal(progress bar + 階段提示 + 取消按鈕)
7. 缺項摘要
| 類別 | 缺什麼 | 取得方式 |
|---|---|---|
| Firmware binary | KL520/fw_loader.bin |
從 warrenchen local_service_win/firmware/KL520/fw_loader.bin 複製進 server/scripts/firmware/KL520/ |
| Firmware binary(B 階段) | KL520_kdp/fw_{scpu,ncpu}.bin(降版用) |
從 warrenchen 複製 |
| Firmware binary(B 階段) | KL630/ KL730/ |
從 warrenchen 複製、但要先驗 driver 支援 |
| Python API call | kp.core.connect_devices_with_magic_pass |
已內建於 KneronPLUS wheel、加 import 即可 |
| Python API call | kp.core.update_kdp_firmware_from_files |
已內建於 KneronPLUS wheel |
| bridge.py handler | handle_firmware_upgrade() |
新寫 ~80-100 行(plan 30 有 stub) |
| bridge.py handler(B 階段) | handle_firmware_downgrade() |
新寫 |
| Go driver method | UpgradeFirmware() error |
新寫 ~40-60 行(plan 30) |
| Go service | server/internal/firmware/service.go |
新建模組(仿 flash/service.go 結構) |
| Go API handler | POST /api/devices/:id/firmware/upgrade |
新增 ~50 行 |
| WebSocket room | firmware:<deviceId> |
沿用既有 WS hub broadcast pattern |
| DeviceInfo 欄位 | FirmwareIsLegacy / FirmwareCanUpgrade / BundledFirmwareVer |
加在 interface.go |
| Frontend 元件 | FW badge + 升級按鈕 + progress modal | 1.5 人天 |
| Frontend store update | Devices store 加 fw progress 訂閱 | 已有 flash progress 訂閱 pattern 可參考 |
| Frontend i18n | 升級相關文案(中英雙語) | ~20 個新 keys |
下一份檔(30-integration-plan.md)展開完整 milestone + 受影響檔案 + 風險清單。