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>
32 KiB
B 階段研究:KL630 / KL730 driver 擴展支援
對應 research index §40 範圍:B 階段(在 A 階段 MVP 完成後)擴 driver 支援 KL630 + KL730,並把它們納入 FW 偵測 + 升降版流程 撰寫日期:2026-05-24 限制:純 plan、不出 code、不改既有檔案 路徑使用相對路徑(相對於
/Users/jimchen/visionA/local-tool/)
0. TL;DR
- warrenchen 沒有對 KL630/KL730 做 FW 升降版——他們只 bundle 了 firmware tar 檔(為了未來雲端
/firmware/loadendpoint),但legacy_plus121_runner.py與相關升級流程完全只針對 KL520 + KL720 KDP1 → KDP2 場景。對 KL630/KL730、warrenchen 只實作 driver install + scan。 - 既有 visionA-local 對 KL630/KL730 偵測得到、但連不上:
kneron_bridge.py:_KNOWN_PRODUCTS已含0x0630: "KL630"/0x0730: "KL730"(L657-664)→ scan 階段會看到名字- 但
handle_connect()的 chip 判斷 fall-through 到_device_chip = "KL520"(L740-741)→ 用 KL520 firmware 路徑載入會直接失敗 chipFromProductID()(detector.go L97-111)也是 default 回 KL520 → driver 會被建成 KL520 type
- 擴 driver 不大、但 .tar firmware 處理是 unknown 區:
- 認 product_id 並 route 到
_device_chip = "KL630"/"KL730"是 5 行改動 - 但 KneronPLUS Python API
load_firmware_from_file()接受單個檔案路徑(scpu, ncpu)、.tar是新格式(KDP2 v2.x SDK 出現的打包形態)、SDK API 對.tar的接受方式必須查官方文件驗證 - 建議:B 階段先做「scan + 顯示 KL630/KL730」(不能 inference)、再做「FW load」、最後才做「FW 升降版」
- 認 product_id 並 route 到
- B 階段拆三層、不要一次做完:
- B0(最簡):driver 認 KL630/KL730、scan 看得到、不能連——使用者至少知道我們認得這晶片
- B1:bridge.py 能 load .tar firmware、能 connect、能 inference——FW 偵測完整
- B2:完整 FW 升降版(手動降版面向一般使用者)——多版本管理 + UX
1. warrenchen 對 KL630 / KL730 怎麼處理(資料來源確認)
1.1 程式碼層面:只有 driver install + scan
從 /tmp/web_academy_prototype/local_service_win/LocalAPI/main.py grep KL630|KL730 只有 4 個位置:
| 位置 | 用途 |
|---|---|
L188 DriverInstallRequest.target 文件 |
API 參數 hint:ALL | KL520 | KL720 | KL630 | KL730 | KL830 |
L433-436 _target_product_ids() |
把 "KL630" / "KL730" 字串轉成 kp.ProductId.KP_DEVICE_KL630 / KP_DEVICE_KL730 enum |
L443-445 同上 target == "ALL" 分支 |
ALL 把 KL630/KL730 也納入 driver install 對象 |
STRATEGY.md L196-203 /driver/ensure 註解 |
文件描述支援的 target 列表 |
完全沒有:
load_firmware_from_file(KL630_files)之類的 callupdate_kdp_firmware_from_files(KL630_files)之類的 call/firmware/load對 KL630/KL730 的專用處理- 任何
.tar解壓 / 處理邏輯
結論:warrenchen 對 KL630/KL730 的支援僅止於 Windows WinUSB driver 安裝。連線 / firmware load / inference 都沒實作過。
1.2 為什麼 warrenchen bundle 了 KL630/KL730 firmware(但沒用)
從 firmware/KL630/VERSION 是 SDK-v2.5.7、firmware/KL730/VERSION 是 SDK-v1.3.0、跟 KL520/KL720 的 2.2.0 顯然命名規則不一樣(KL630/KL730 用 SDK 版本、不是 firmware 版本)。
推測 warrenchen 的策略:
- 把 SDK 官方提供的 firmware 整套 vendored 進來(一次性 release 製作的副產物)
- 為未來
/firmware/load端點預留——讓雲端業務邏輯決定要不要 load - 但實際路徑、API 對應、
.tar解包處理 PoC 階段沒做
對我們的啟示:我們無法從 warrenchen 偷到 KL630/KL730 的「連線 / FW 升降版」流程實作、必須自己研究 KneronPLUS Python SDK 對這兩個晶片的 API。
1.3 STRATEGY.md 對 KL630/KL730 的描述
僅有 1 處:/driver/ensure 註解列出支援 target。沒有「Legacy Firmware Story」式的 KL630/KL730 章節——對 KL520/KL720 寫了一大段(STRATEGY.md L521-545)、對 KL630/KL730 完全沒寫。
2. 既有 visionA-local 對 KL630 / KL730 的支援狀態
2.1 已認得 product_id(scan 看得到)
server/scripts/kneron_bridge.py L657-664:
_KNOWN_PRODUCTS = {
0x0100: "KL520",
0x0200: "KL720",
0x0720: "KL720",
0x0530: "KL530",
0x0630: "KL630",
0x0730: "KL730",
}
→ scan 階段(_scan_with_pyusb / handle_scan)會在 devices[].product_id 與 chip 欄位顯示正確值。
2.2 chip 判斷邏輯漏判(connect 會誤路由到 KL520)
kneron_bridge.py L732-741:
pid = target_dev.product_id
if "kl720" in device_type.lower():
_device_chip = "KL720"
elif "kl520" in device_type.lower():
_device_chip = "KL520"
elif pid in (0x0200, 0x0720):
_device_chip = "KL720"
else:
_device_chip = "KL520" # ← KL630/KL730/KL530 全部掉這裡
→ KL630(0x0630)/ KL730(0x0730)會被當成 KL520 處理。
_resolve_firmware_paths("KL520")會去找firmware/KL520/fw_scpu.bin→ 拿到的是 KL520 的 firmwareload_firmware_from_file(KL630_device, kl520_scpu)→ 預期會炸(不確切錯誤碼、估計KP_FW_INFO_ERR或類似)
2.3 Go 端 detector 也漏判
server/internal/driver/kneron/detector.go L97-111:
func chipFromProductID(productID string) (chip string, deviceType string) {
pid := strings.ToLower(strings.TrimSpace(productID))
switch pid {
case "0x0100":
return "KL520", "kneron_kl520"
case "0x0200", "0x0720":
return "KL720", "kneron_kl720"
default:
return "KL520", "kneron_kl520" // ← KL630/KL730/KL530 全部掉這裡
}
}
→ Go driver 也會把 KL630/KL730 當成 KL520。
2.4 沒 bundle KL630/KL730 firmware
server/scripts/firmware/ 目前只有 KL520/ + KL720/、沒有 KL630/ KL730/ KL530/。
3. KneronPLUS Python SDK 對 KL630 / KL730 的 API 落差(需 SDK 官方文件驗證)
3.1 已確認的事
從 warrenchen main.py L433-436 看到:
if target == "KL630":
return [kp.ProductId.KP_DEVICE_KL630]
if target == "KL730":
return [kp.ProductId.KP_DEVICE_KL730]
→ KneronPLUS Python wheel 內 kp.ProductId enum 有 KP_DEVICE_KL630 / KP_DEVICE_KL730。
從 local_service_win/KneronPLUS-3.1.2-py3-none-any.whl 推測:
- KneronPLUS SDK 3.x 系列就有 KL630/KL730 支援
- 我們既有的 KneronPLUS wheel 版本需要查(待確認)——如果是 2.x、可能要升級
3.2 未確認、必須查 KneronPLUS 官方文件的事
| 問題 | 影響 |
|---|---|
kp.core.connect_devices([KL630_port_id]) 是否能跟 KL520/KL720 共用 API、不需要特殊參數? |
影響 connect 流程設計 |
load_firmware_from_file(dg, scpu, ncpu) 對 KL630/KL730 是否仍是兩個 .bin 檔(解壓 .tar 後)? |
影響 .tar 處理策略(runtime extract 還是 build time extract) |
是否有新的 load_firmware_from_tar() 之類的 API? |
如果有、我們直接餵 .tar 不用解壓 |
update_kdp_firmware_from_files() 對 KL630/KL730 是否適用?還是改用 update_kdp2_firmware_from_files() / update_kdp2_firmware_from_tar()? |
影響 FW 升降版實作 |
| KL630/KL730 是否仍有「Loader / KDP / KDP2」三種 firmware state、還是只有兩種? | 影響 FW 偵測邏輯 |
| KL630/KL730 是否仍像 KL520 一樣每次都要 load firmware 到 RAM、還是像 KL720 一樣 flash-based? | 影響 connect 流程設計(重大) |
inference.generic_image_inference_send/receive 對 KL630/KL730 是否一致? |
影響 inference 流程是否要分支 |
kp.core.scan_devices() 對 KL630/KL730 的 firmware 字串可能值("KDP2"? "KDP3"? 其他?) |
影響 FW badge 顯示邏輯 |
3.3 取得這些資訊的方法
- KneronPLUS SDK 官方 PDF 文件——使用者應該有 SDK release 附的 documentation(
KneronPLUS_SDK_Reference_Manual.pdf之類)、查 KL630/KL730 section - wheel 內 Python 原始碼——
unzip KneronPLUS-3.1.2-py3-none-any.whl後看kp/core.py的 docstring +kp/__init__.py看 enum 定義 - SDK example code——Kneron 通常會附 sample(用 KL630/KL730 跑 inference 的範例)、看他們怎麼初始化裝置
- 在真實 KL630/KL730 device 上 trial-and-error——使用者手上若有 KL630/KL730 dongle、可以裝 KneronPLUS、Python REPL 跑
kp.core.scan_devices()直接看回傳
B 階段第一個 milestone 必須先把這些查清楚,否則所有後續設計都是猜的。
4. 既有 driver 怎麼擴:「新加 driver」vs「同一個 driver 加 case」
4.1 兩個選項對比
| 維度 | A:同一個 driver 加 case | B:拆出 KL630Driver / KL730Driver |
|---|---|---|
| 程式碼複用 | 高(已支援 KL520/KL720 共用、邏輯相近處不重寫) | 低(每個 chip 自己一份 driver、可能 80% 重複) |
| 流程差異容納度 | 中(用 switch / if-else 處理差異、容易長成 spaghetti) | 高(每個 driver 清楚自己 chip 的特性) |
| 維護負擔 | 中(單檔變大、但所有 chip-specific 邏輯集中) | 高(4 個檔案要同步更新通用邏輯) |
| 測試隔離 | 中(mock 整個 driver、無法只測 KL630 部分) | 高(每個 driver 獨立測) |
| 既有 bridge.py 是否共用 | 是(已是這個結構) | 是(bridge.py 必定要共用,重 spawn Python 太貴) |
| 改動成本 | 低(擴 kl720_driver.go 既有結構) |
高(拆 driver 還要改 detector / manager / DI) |
4.2 建議:選 A(同一個 driver 擴 case)
理由:
- KL520/KL720 已是同一個 driver、檔名雖叫
kl720_driver.go但已是「Kneron 通用 driver」,再加 KL630/KL730 與這個方向一致 - bridge.py 強制共用(Python subprocess spawn 成本高),driver 拆但 bridge 不拆會造成 driver-bridge 對應錯位
- KL520 vs KL720 的差異(USB Boot vs flash-based、是否每次都要 load firmware、reset 策略)比 KL520 vs KL630 的差異可能還大——KL630/KL730 是新世代、可能跟 KL720 行為更接近(都是 flash-based)
- 拆 driver 不會省工作量、反而 5 個 driver 共用邏輯時更難改
接受的取捨:
kl720_driver.go檔名包袱繼續存在(已是現狀、本 B 階段不改名)- 內部 switch 變多(但每個 chip 的差異邏輯應 < 50 行、可控)
4.3 具體擴的位置
server/internal/driver/kneron/kl720_driver.go:
改動 1:NewKneronDriver()(L48-59)chip 判斷加 KL630/KL730:
// 偽碼、不出 code
chip := "KL520"
typ := strings.ToLower(info.Type)
switch {
case strings.Contains(typ, "kl720"):
chip = "KL720"
case strings.Contains(typ, "kl730"):
chip = "KL730"
case strings.Contains(typ, "kl630"):
chip = "KL630"
case strings.Contains(typ, "kl530"):
chip = "KL530" // 視 SDK 支援度決定要不要加(warrenchen 沒提)
}
改動 2:Connect() / Flash() / RunInference() 內所有 if chip == "KL720" 的分支點要加 KL630/KL730 行為(待 SDK 文件驗證後填)
改動 3:新增 helper 把 chip 行為抽出來:
// 偽碼
type chipBehavior struct {
RequireFirmwareLoadEverySession bool // KL520=true, KL720/KL630/KL730=待驗
SupportedFlashUpdate bool // FW 升降版是否支援
TimeoutMS int // KL720=60000, KL520=10000, KL630/KL730 待驗
InferenceConfig map[string]interface{} // 各 chip inference 細部設定
}
func behaviorFor(chip string) chipBehavior { ... }
→ 把現有散在各 method 裡的 if chip == "KL720" 集中到一個地方、之後加新 chip 只動這個表(Refactor、不算 B 階段的 mandatory work、但建議順手做)
server/internal/driver/kneron/detector.go:
改動 1:chipFromProductID()(L97-111)switch 加 case:
// 偽碼
case "0x0530":
return "KL530", "kneron_kl530" // 視 SDK 支援
case "0x0630":
return "KL630", "kneron_kl630"
case "0x0730":
return "KL730", "kneron_kl730"
改動 2:DetectDevices() chipCount map 自動 work(不用改)
5. 連線流程跟 KL520 / KL720 的差異(待 SDK 文件驗證)
5.1 KL520 vs KL720 既有差異對照(visionA-local 已實作)
| 維度 | KL520 | KL720 |
|---|---|---|
| Hardware 模式 | USB Boot(無 flash) | Flash-based |
| connect 階段 firmware 載入 | 必載(每次都要載 fw_scpu + fw_ncpu 到 RAM) | 不載(firmware 已預燒在 flash) |
| 例外:KDP1 legacy(pid=0x0200) | n/a | 必走 connect_without_check + 載 KDP2 firmware 到 RAM |
| Timeout | 10s | 60s(大 NEF 傳輸) |
| reset 策略 | restartBridge 8s 重啟 bridge | kp.core.reset_device(REBOOT) |
| Model load 後狀態 | 只能 load 一個 model、要換 model 必須 restart bridge | 可自由 reload |
5.2 KL630 / KL730 推測(待驗證)
從 warrenchen _target_product_ids 把 KL720_LEGACY 和 KL720 並列、但 KL630/KL730 只有單一 product_id、推測:
- KL630/KL730 沒有 legacy/new 雙版本(不像 KL720 有 KDP1 → KDP2 升級需求)
- 都是 flash-based(跟 KL720 而非 KL520 一族)
- connect 階段不需要 load firmware(除非在 Loader mode)
但這只是推測。SDK 文件 / 實機驗證才能確認。
5.3 風險:如果 KL630/KL730 行為跟 KL520 更像(每次都要 load firmware)
那 .tar firmware 的 runtime load 流程就變得頻繁(每次 connect 都跑一次解壓 + load)、可能影響 connect 速度。
緩解:
- build time 預先解壓
.tar到firmware/KL630/extracted/{fw_scpu.bin, fw_ncpu.bin, ...}、connect 時直接餵解壓後檔案 - 細節在「.tar firmware 處理」研究檔(41-tar-firmware-handling.md)展開
6. bridge.py 擴展工作
6.1 改動清單
| 改動 | 程式碼位置 | 大小 |
|---|---|---|
| chip 判斷加 KL630/KL730 case | handle_connect() L732-741 |
+10 行 |
_resolve_firmware_paths(chip) 支援 .tar 格式 |
_resolve_firmware_paths() L157-172 |
+30 行(解壓邏輯)/ 或 +5 行(直接餵 .tar 給 SDK) |
handle_connect() 對 KL630/KL730 的 firmware 流程分支 |
新增 if 分支或重構成 dispatcher | +30-50 行(待 SDK 驗證) |
handle_firmware_upgrade() 對 KL630/KL730 的支援 |
(A 階段已加、B 階段擴) | +20 行 |
handle_firmware_downgrade()(新) |
新 handler | +60-100 行 |
handle_firmware_list_versions()(新) |
新 handler、列出 bundled 多版本 | +30 行 |
6.2 chip 判斷擴展(具體偽碼)
# kneron_bridge.py handle_connect() 偽碼
pid = target_dev.product_id
device_type_lower = device_type.lower()
if "kl720" in device_type_lower or pid in (0x0200, 0x0720):
_device_chip = "KL720"
elif "kl730" in device_type_lower or pid == 0x0730:
_device_chip = "KL730"
elif "kl630" in device_type_lower or pid == 0x0630:
_device_chip = "KL630"
elif "kl530" in device_type_lower or pid == 0x0530:
_device_chip = "KL530" # 視 SDK 支援度
elif "kl520" in device_type_lower or pid == 0x0100:
_device_chip = "KL520"
else:
_device_chip = "KL520" # fallback、保留既有行為
6.3 firmware path resolution 擴展
# _resolve_firmware_paths(chip) 偽碼
def _resolve_firmware_paths(chip="KL520"):
base = os.path.dirname(os.path.abspath(__file__))
fw_dir = os.path.join(base, "firmware", chip)
# KL520 / KL720 走原本 .bin 路徑
if chip in ("KL520", "KL720"):
scpu = os.path.join(fw_dir, "fw_scpu.bin")
ncpu = os.path.join(fw_dir, "fw_ncpu.bin")
if os.path.exists(scpu) and os.path.exists(ncpu):
return scpu, ncpu
return None, None
# KL630 / KL730 走 .tar 路徑
if chip in ("KL630", "KL730"):
# 策略一:直接餵 .tar 給 SDK(如果 SDK 接受)
fw_tar = os.path.join(fw_dir, "kp_firmware.tar")
if os.path.exists(fw_tar):
return fw_tar, None # second arg None or different semantics、待驗
# 策略二:build time 已解壓、走解壓後路徑
extracted_scpu = os.path.join(fw_dir, "extracted", "fw_scpu.bin")
extracted_ncpu = os.path.join(fw_dir, "extracted", "fw_ncpu.bin")
if os.path.exists(extracted_scpu) and os.path.exists(extracted_ncpu):
return extracted_scpu, extracted_ncpu
return None, None
return None, None
注意:以上是兩種候選策略、實際選哪個取決於 SDK 文件——詳見 41-tar-firmware-handling.md。
7. inference 流程的差異(待 SDK 文件驗證)
7.1 既有 visionA-local inference 流程
從 kneron_bridge.py 看(grep generic_image_inference_send),既有用 kp.inference.generic_image_inference_send/receive API。這 API 是 KneronPLUS v2.x+ 的通用 inference API、理論上支援所有晶片。
7.2 推測:KL630/KL730 inference 流程可能不變
理由:
generic_image_inference_*設計成 chip-agnostic- 模型編譯時已 target 特定 chip(YOLOv5_KL520_.nef vs YOLOv5_KL630_.nef),driver 只負責跑、不在 driver 端區分 chip
7.3 但有可能需要調整的點
- Model NEF target_chip 對應:我們既有
_detect_model_type()根據 NEF 內容判斷 model——_model_nef.target_chip屬性對 KL630/KL730 NEF 會回什麼字串、需要驗證 - Input image format / preprocessing:KL630/KL730 NPU 規格不同、input shape / channel order 可能要從 NEF 拿、
generic_image_inference_send的 input 配置可能需要調整
→ B1 階段必驗:跑一支 KL630 NEF(從 SDK 範例拿)、看 inference 是否能直接 work。
8. 多版本 firmware 並存(B2 階段才做)
8.1 為什麼需要
「手動降版面向一般使用者」場景下、使用者可能需要選 firmware 版本:
- 「我目前裝的是 KL630 SDK-v2.5.7、但我的 model 是用 SDK-v2.4 編的、想降版回 v2.4」
- 「KL520 KDP2 v2.2.0 跟某個 third-party tool 不相容、想降版回 v2.1.0」
但這場景對「一般使用者」而言很罕見——更常見的是「我拿到舊 KDP1 dongle 想升級」(A 階段已涵蓋)。
8.2 儲存結構(建議)
server/scripts/firmware/
├── KL520/
│ ├── current/ ← MVP 既有 firmware(symlink 或實檔)
│ │ ├── fw_scpu.bin
│ │ ├── fw_ncpu.bin
│ │ ├── fw_loader.bin
│ │ └── VERSION ← "2.2.0"
│ ├── v2.2.0/ ← 多版本目錄
│ │ ├── fw_scpu.bin
│ │ ├── fw_ncpu.bin
│ │ ├── fw_loader.bin
│ │ └── VERSION
│ ├── v2.1.0/ ← 舊版(如果 bundle)
│ │ ├── fw_scpu.bin
│ │ ├── fw_ncpu.bin
│ │ └── VERSION
│ └── kdp1/ ← KDP1 降版用(B 階段)
│ ├── fw_scpu.bin
│ ├── fw_ncpu.bin
│ └── VERSION ← "1.x.x" 或 "KDP1"
├── KL720/
│ └── ...同結構...
├── KL630/
│ └── ...同結構,但 firmware 檔是 .tar...
└── KL730/
└── ...同結構...
對 MVP(A 階段)的影響:
- A 階段只用
KL520/current/KL720/current/、現有結構不需動 - B 階段加多版本時、把現有檔搬進
current/+ 加v2.2.0/副本(保留 backward compat、_resolve_firmware_pathsfallback 機制)
8.3 版本選擇邏輯
- 預設用
current/ - 使用者主動選版本 → driver 傳
version參數給 bridge.py - bridge.py 用
firmware/<chip>/<version>/...解析路徑
→ 詳細 API + UI 設計在「手動降版」研究檔(42-manual-downgrade-for-end-users.md)。
9. 風險分析(B 階段新增)
承前一份 R-FW-1 ~ R-FW-7、本份補:
R-FW-8:KneronPLUS SDK 對 KL630/KL730 API 不可預測(高度風險)
情境:我們現在對 KL630/KL730 的 SDK 行為都是推測、實際開發時可能:
load_firmware_from_file(.tar)不接受、要先解壓update_kdp_firmware_from_files()對 KL630 沒效、要用別的 APIkp.ProductId.KP_DEVICE_KL730在我們既有 wheel 版本不存在(要升級 wheel)
緩解:
- B0 milestone 第一件事:實機 + Python REPL 驗證所有 API、記錄到
41-tar-firmware-handling.md後續更新 - 必要時升級 KneronPLUS wheel 版本(既有可能是 2.x、warrenchen 用 3.1.2)
- wheel 版本升級會影響 KL520/KL720 既有行為(regression 風險)、必須三平台回歸驗
R-FW-9:.tar 解包對安裝包大小衝擊(低度風險)
情境:如果採 build time extract、安裝包同時包 .tar + 解壓後的 .bin、大小翻倍。
緩解:
- build script 解壓後刪原始 .tar(只 ship .bin)、安裝包多塞 .bin 但少塞 .tar、net 差約等於壓縮率
- 估算:KL630 .tar 預估 ~2-3MB、解壓後 .bin 約 ~3-4MB、淨增 +1MB(acceptable)
R-FW-10:KL630/KL730 沒有 Loader mode 概念(中度風險)
情境:如果 KL630/KL730 是純 flash-based、永遠沒有 USB Boot Loader、那 kp_loader.tar 用不到、FW 升級流程也沒有「先 load loader 再 load firmware」這一段。
影響:bridge.py handler 設計要分支、不能把 KL520/KL720 的「Loader 路徑」硬套到 KL630/KL730。
緩解:B0 milestone 驗證時、用 kp.core.scan_devices() 看 KL630 firmware 字串可能值(是否會出現 "Loader" / "KDP" / "KDP2" / "KDP3" / 其他),決定 chip-specific 分支邏輯。
R-FW-11:一般使用者誤觸降版 brick 風險(高度風險,B2)
情境:手動降版面向一般使用者後、誤操作可能把 dongle 變磚。
緩解(你 architect 標記、實作由 design + frontend):
- UI 警告 + 二次確認(design 領域)
- driver 端做安全 guard:
- 拒絕降版到比 current 還新的版本(這是升版、不是降版、走升版 API)
- 拒絕跨晶片誤匹配(KL520 firmware 拿來降 KL630)
- 降版前強制備份當前 firmware 版本資訊到
.autoflow/firmware-history.json(可選)
R-FW-12:多版本管理 UX 複雜度(中度風險,B2)
情境:dropdown 多版本選擇對一般使用者過於 cluttered、容易誤選舊版。
緩解(標記給 design):
- 預設只顯示「升級到最新」+「降版」兩個按鈕
- 「降版」展開後才顯示版本 dropdown
- 不熟悉版號的使用者預設選最新降版目標("v2.1.0 latest before 2.2.0")
10. B 階段 milestone 拆法
承 A 階段 M9-1 ~ M9-5、B 階段拆 M9-6 ~ M9-12:
整體依賴圖
M9-5 (A 階段三平台驗證) ✅
↓
M9-6 (research validation): SDK 文件 + 實機驗證 KL630/KL730 API 行為
↓
M9-7 (B0: 認 chip): driver + bridge.py 認 KL630/KL730、scan 顯示完整、connect 暫不支援
↓
M9-8 (B1.1: tar handling): bridge.py 處理 .tar firmware(解壓策略選定 + 實作)
↓
M9-9 (B1.2: connect): bridge.py + driver 完整支援 KL630/KL730 connect + inference
↓
M9-10 (B1.3: FW 升版): firmware_upgrade handler 擴 KL630/KL730 支援
↓ ─────────────┐
M9-11 (B2.1: 多版本) M9-12 (B2.2: 降版 UI + UX)
↓ ↓
M9-13 (三平台實機驗證所有 B 階段功能)
各 milestone 細節
M9-6 — SDK 驗證 + KneronPLUS wheel 升級評估(1 人天)
負責:Architect Agent(純研究、不寫產品 code)
任務:
- 取得 KneronPLUS SDK 官方文件(PDF 或網頁),查 KL630/KL730 章節
- unzip
KneronPLUS-3.1.2-py3-none-any.whl、讀kp/core.py/kp/__init__.py - 在實機(使用者有 KL630/KL730 dongle 的話)跑 Python REPL:
import kp descs = kp.core.scan_devices() for i in range(descs.device_descriptor_number): dev = descs.device_descriptor_list[i] print(dev.product_id, dev.firmware, dev.is_connectable, dev.usb_port_id) # 試 connect、load .tar firmware、看回傳 - 評估是否要升級 KneronPLUS wheel(從目前版本 → 3.1.2)+ 回歸風險
- 產出
41-tar-firmware-handling.md「實測結果」段落填空
驗收:研究文件補齊、確認所有 §3.2 未確認項目都有答案
M9-7 — B0 認 chip(0.5 人天)
負責:Backend Agent
任務:
kneron_bridge.pychip 判斷加 KL630/KL730 case(§6.2 偽碼)detector.go:chipFromProductID()加 case(§4.3 偽碼)kl720_driver.go:NewKneronDriver()加 chip 判斷(§4.3 偽碼)handle_connect()對 KL630/KL730 暫時回{"error": "KL630/KL730 not yet fully supported, only scan info available"}、不假裝能連- Frontend Devices 頁面、未支援 chip 顯示 disabled 狀態 + tooltip「即將支援」
驗收:
- scan 看得到 KL630/KL730 dongle 名字、不會誤標成 KL520
- 按 connect 會看到友善訊息(非靜默失敗)
M9-8 — B1.1 .tar firmware handling(1.5 人天)
負責:Backend Agent
任務:
- 從 warrenchen 複製
firmware/KL630/{kp_firmware.tar, kp_loader.tar}firmware/KL730/{kp_firmware.tar, kp_loader.tar}到server/scripts/firmware/ - 加
firmware/KL630/VERSIONfirmware/KL730/VERSION _resolve_firmware_paths()擴展支援 .tar(§6.3 偽碼)- 決定解壓策略(runtime / build time):根據 M9-6 SDK 驗證結果決定
- 如果 SDK 接受 .tar → 直接餵
- 如果不接受 → build time 解壓進
extracted/、安裝包 ship 解壓後 .bin
- 如果走 build time 解壓:擴
installer/的 build script、確保解壓步驟跑在 wails build 之前 - 加 unit test(mock):
_resolve_firmware_paths("KL630")回正確路徑
驗收:
python3 kneron_bridge.py跑_resolve_firmware_paths("KL630")回正確路徑(不接 device 也能測)- 安裝包大小變化在預估範圍(+3-5MB)
M9-9 — B1.2 connect + inference(2 人天)
負責:Backend Agent
任務:
handle_connect()對 KL630/KL730 的完整流程(連線 + 必要時載入 firmware)- driver
Flash()/RunInference()對 KL630/KL730 的 chip-specific 邏輯(從 M9-6 研究結果得出) - 建立
chipBehavior抽象(§4.3 改動 3、可選 refactor) - Frontend Devices 頁面、KL630/KL730 解除 disabled、能正常 connect + 跑 inference
- 三平台 smoke test(KL630/KL730 各跑一支 sample NEF)
驗收:
- KL630/KL730 dongle 能 connect + 跑 inference 成功
- 既有 KL520/KL720 沒有 regression
M9-10 — B1.3 FW 升版擴 KL630/KL730(1 人天)
負責:Backend Agent
任務:
handle_firmware_upgrade()加 KL630/KL730 路徑(A 階段的 handler 擴展)- 升版 stage 邏輯依照 M9-6 結論調整(如 KL630/KL730 沒有 "KDP" legacy state、就跳過 loader 那段)
- Frontend FW badge 對 KL630/KL730 顯示對應狀態
- 三平台實機驗證升版
驗收:
- KL630/KL730 dongle(如果有比 bundled 還舊的 firmware)能升級成功
M9-11 — B2.1 多版本 firmware 並存(1.5 人天)
負責:Backend Agent
任務:
- 重整
firmware/目錄結構為 §8.2 設計(每 chip 多版本 +current/symlink/副本) - bridge.py
handle_firmware_list_versions(chip)新 handler、列出可選版本 - bridge.py
handle_firmware_upgrade/handle_firmware_downgrade接受version參數 - driver 與 service 層擴展傳 version 參數
- API endpoint
GET /api/devices/:id/firmware/versions - Bundle 多版本(每 chip 至少 1 個非 current 的版本可選、實際版本由使用者決定 ship 哪些)
驗收:
- API
GET /firmware/versions?chip=KL520回傳["v2.2.0", "v2.1.0", "kdp1"] - 升版 / 降版 API 接受 version 參數能正確 route
M9-12 — B2.2 降版 UI(面向一般使用者)(2 人天)
負責:Frontend Agent + Design Agent(設計部分)
任務:
- Design Agent 補 Settings > 韌體面板的 wireframe + 警告語 + 二次確認 UX(不是 architect 範圍)
- Frontend 實作 Settings 韌體面板:
- 顯示當前 FW 版本
- 「升級到最新」按鈕
- 「降版」按鈕 → 展開版本 dropdown + 警告語 + 二次確認 modal
- 警告語內容(給 design 補強):
- 「降版可能導致現有 model 無法運作」
- 「降版過程不可中斷、否則裝置可能損壞」
- 「請確認版本相容性」
- 二次確認 modal:
- 需要使用者輸入「DOWNGRADE」字串(防誤觸)
- 顯示降版預估時間 + before/after 版本對照
- i18n 新增 keys(中英雙語)
驗收:
- 一般使用者打開 Settings 能看到韌體管理面板
- 誤觸降版按鈕後、二次確認 modal 出現、不容易誤過
- 降版完成後 UI 正確顯示新版本
M9-13 — B 階段完整三平台實機驗證(1 人天)
負責:Testing Agent
任務:
- 三平台 × {KL520, KL720, KL630, KL730} × {scan, connect, inference, firmware upgrade, firmware downgrade} = 60 個 smoke test cells
- 重點異常路徑:降版中拔 device、降版 timeout、跨晶片 firmware mismatch、版本回滾
驗收:
- 所有 cells PASS(或標註 N/A 且解釋原因)
- 無 regression
工時合計
| Milestone | 工時 | 累積 |
|---|---|---|
| M9-6 | 1 | 1 |
| M9-7 | 0.5 | 1.5 |
| M9-8 | 1.5 | 3 |
| M9-9 | 2 | 5 |
| M9-10 | 1 | 6 |
| M9-11 | 1.5 | 7.5 |
| M9-12 | 2 | 9.5 |
| M9-13 | 1 | 10.5 |
B 階段合計 ~10.5 人天、加 A 階段 5 人天 = 完整版總計 ~15.5 人天(比原預估 11-12 略多、主因是「面向一般使用者」這個範圍升級加上 SDK 驗證工時)
Reviewer 切點
- M9-6 純研究、Architect 自身產出、不過 Reviewer
- M9-7 ~ M9-12 每個 milestone 結束過 Reviewer(program code review + 設計合規)
- M9-13 testing report 過 Reviewer
平行性
- M9-6 必須先做(其他所有 milestone 都依賴研究結論)
- M9-7 ~ M9-10 序列(每個依賴前一個)
- M9-11 ~ M9-12 可平行(多版本後端 + 降版 UI 設計可同時做)
- M9-13 在所有 milestone 之後
11. 給 Orchestrator 的下一步建議
- A 階段 MVP 先做(不變)——M9-1 ~ M9-5、5 人天
- A 階段驗證後(或同時平行)啟動 M9-6——SDK 驗證、純研究、Architect 自己跑
- M9-6 結論回填到
41-tar-firmware-handling.md+ 更新本檔 §5.2 / §7.1 / R-FW-8 - B 階段是否要全做、依 M9-6 結果決定——如果 SDK 不支援、可能要先升級 wheel、回歸成本變高、可重新 scope
- B2 降版 UX 由 Design Agent 主導——本研究只標需求、不做 UI 設計
12. 與 A 階段研究的相依關係
| 項目 | A 階段(既定) | B 階段(本研究) |
|---|---|---|
| 範圍 | KL520 + KL720、自動升級 KDP1→KDP2 | + KL630 + KL730、+ 手動降版面向一般使用者、+ 多版本 |
| bridge.py handler | handle_firmware_upgrade |
+ handle_firmware_downgrade + handle_firmware_list_versions + chip 判斷擴展 + .tar 處理 |
| Go driver method | UpgradeFirmware() |
+ DowngradeFirmware(version) + ListFirmwareVersions() |
| API endpoint | POST /firmware/upgrade |
+ POST /firmware/downgrade + GET /firmware/versions |
| Frontend UI | Devices 頁 FW badge + 升級 modal | + Settings 韌體面板(含降版 + 多版本選擇) |
| 安裝包大小衝擊 | +0KB | +5MB(詳細估算見 42-manual-downgrade-for-end-users.md) |
| 工時 | 5 人天 | 10.5 人天 |
| Reviewer 切點 | M9-1 ~ M9-5 各一輪 | M9-7 ~ M9-12 各一輪 + M9-13 testing |
| 風險新增 | R-FW-1 ~ R-FW-7 | + R-FW-8 ~ R-FW-12 |