# 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 1. **warrenchen 沒有對 KL630/KL730 做 FW 升降版**——他們只 bundle 了 firmware tar 檔(為了未來雲端 `/firmware/load` endpoint),但 `legacy_plus121_runner.py` 與相關升級流程**完全只針對 KL520 + KL720 KDP1 → KDP2 場景**。對 KL630/KL730、warrenchen 只實作 driver install + scan。 2. **既有 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 3. **擴 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 升降版」 4. **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)` 之類的 call - `update_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 的策略: 1. 把 SDK 官方提供的 firmware 整套 vendored 進來(一次性 release 製作的副產物) 2. 為未來 `/firmware/load` 端點預留——讓雲端業務邏輯決定要不要 load 3. 但實際路徑、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: ```python _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: ```python 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 的 firmware - `load_firmware_from_file(KL630_device, kl520_scpu)` → 預期會炸(不確切錯誤碼、估計 `KP_FW_INFO_ERR` 或類似) ### 2.3 Go 端 detector 也漏判 `server/internal/driver/kneron/detector.go` L97-111: ```go 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 看到: ```python 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 取得這些資訊的方法 1. **KneronPLUS SDK 官方 PDF 文件**——使用者應該有 SDK release 附的 documentation(`KneronPLUS_SDK_Reference_Manual.pdf` 之類)、查 KL630/KL730 section 2. **wheel 內 Python 原始碼**——`unzip KneronPLUS-3.1.2-py3-none-any.whl` 後看 `kp/core.py` 的 docstring + `kp/__init__.py` 看 enum 定義 3. **SDK example code**——Kneron 通常會附 sample(用 KL630/KL730 跑 inference 的範例)、看他們怎麼初始化裝置 4. **在真實 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) **理由**: 1. KL520/KL720 已是同一個 driver、檔名雖叫 `kl720_driver.go` 但已是「Kneron 通用 driver」,再加 KL630/KL730 與這個方向一致 2. bridge.py 強制共用(Python subprocess spawn 成本高),driver 拆但 bridge 不拆會造成 driver-bridge 對應錯位 3. KL520 vs KL720 的差異(USB Boot vs flash-based、是否每次都要 load firmware、reset 策略)**比** KL520 vs KL630 的差異**可能還大**——KL630/KL730 是新世代、可能跟 KL720 行為更接近(都是 flash-based) 4. 拆 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: ```go // 偽碼、不出 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 行為抽出來: ```go // 偽碼 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: ```go // 偽碼 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 判斷擴展(具體偽碼) ```python # 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 擴展 ```python # _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_paths` fallback 機制) ### 8.3 版本選擇邏輯 - 預設用 `current/` - 使用者主動選版本 → driver 傳 `version` 參數給 bridge.py - bridge.py 用 `firmware///...` 解析路徑 → 詳細 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 沒效、要用別的 API - `kp.ProductId.KP_DEVICE_KL730` 在我們既有 wheel 版本不存在(要升級 wheel) **緩解**: 1. **B0 milestone 第一件事**:實機 + Python REPL 驗證所有 API、記錄到 `41-tar-firmware-handling.md` 後續更新 2. 必要時升級 KneronPLUS wheel 版本(既有可能是 2.x、warrenchen 用 3.1.2) 3. 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) **任務**: 1. 取得 KneronPLUS SDK 官方文件(PDF 或網頁),查 KL630/KL730 章節 2. unzip `KneronPLUS-3.1.2-py3-none-any.whl`、讀 `kp/core.py` / `kp/__init__.py` 3. 在實機(使用者有 KL630/KL730 dongle 的話)跑 Python REPL: ```python 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、看回傳 ``` 4. 評估是否要升級 KneronPLUS wheel(從目前版本 → 3.1.2)+ 回歸風險 5. 產出 `41-tar-firmware-handling.md` 「實測結果」段落填空 **驗收**:研究文件補齊、確認所有 §3.2 未確認項目都有答案 #### M9-7 — B0 認 chip(0.5 人天) **負責**:Backend Agent **任務**: 1. `kneron_bridge.py` chip 判斷加 KL630/KL730 case(§6.2 偽碼) 2. `detector.go:chipFromProductID()` 加 case(§4.3 偽碼) 3. `kl720_driver.go:NewKneronDriver()` 加 chip 判斷(§4.3 偽碼) 4. `handle_connect()` 對 KL630/KL730 暫時回 `{"error": "KL630/KL730 not yet fully supported, only scan info available"}`、不假裝能連 5. Frontend Devices 頁面、未支援 chip 顯示 disabled 狀態 + tooltip「即將支援」 **驗收**: - scan 看得到 KL630/KL730 dongle 名字、不會誤標成 KL520 - 按 connect 會看到友善訊息(非靜默失敗) #### M9-8 — B1.1 .tar firmware handling(1.5 人天) **負責**:Backend Agent **任務**: 1. 從 warrenchen 複製 `firmware/KL630/{kp_firmware.tar, kp_loader.tar}` `firmware/KL730/{kp_firmware.tar, kp_loader.tar}` 到 `server/scripts/firmware/` 2. 加 `firmware/KL630/VERSION` `firmware/KL730/VERSION` 3. `_resolve_firmware_paths()` 擴展支援 .tar(§6.3 偽碼) 4. 決定解壓策略(runtime / build time):根據 M9-6 SDK 驗證結果決定 - 如果 SDK 接受 .tar → 直接餵 - 如果不接受 → build time 解壓進 `extracted/`、安裝包 ship 解壓後 .bin 5. 如果走 build time 解壓:擴 `installer/` 的 build script、確保解壓步驟跑在 wails build 之前 6. 加 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 **任務**: 1. `handle_connect()` 對 KL630/KL730 的完整流程(連線 + 必要時載入 firmware) 2. driver `Flash()` / `RunInference()` 對 KL630/KL730 的 chip-specific 邏輯(從 M9-6 研究結果得出) 3. 建立 `chipBehavior` 抽象(§4.3 改動 3、可選 refactor) 4. Frontend Devices 頁面、KL630/KL730 解除 disabled、能正常 connect + 跑 inference 5. 三平台 smoke test(KL630/KL730 各跑一支 sample NEF) **驗收**: - KL630/KL730 dongle 能 connect + 跑 inference 成功 - 既有 KL520/KL720 沒有 regression #### M9-10 — B1.3 FW 升版擴 KL630/KL730(1 人天) **負責**:Backend Agent **任務**: 1. `handle_firmware_upgrade()` 加 KL630/KL730 路徑(A 階段的 handler 擴展) 2. 升版 stage 邏輯依照 M9-6 結論調整(如 KL630/KL730 沒有 "KDP" legacy state、就跳過 loader 那段) 3. Frontend FW badge 對 KL630/KL730 顯示對應狀態 4. 三平台實機驗證升版 **驗收**: - KL630/KL730 dongle(如果有比 bundled 還舊的 firmware)能升級成功 #### M9-11 — B2.1 多版本 firmware 並存(1.5 人天) **負責**:Backend Agent **任務**: 1. 重整 `firmware/` 目錄結構為 §8.2 設計(每 chip 多版本 + `current/` symlink/副本) 2. bridge.py `handle_firmware_list_versions(chip)` 新 handler、列出可選版本 3. bridge.py `handle_firmware_upgrade` / `handle_firmware_downgrade` 接受 `version` 參數 4. driver 與 service 層擴展傳 version 參數 5. API endpoint `GET /api/devices/:id/firmware/versions` 6. 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(設計部分) **任務**: 1. Design Agent 補 Settings > 韌體面板的 wireframe + 警告語 + 二次確認 UX(不是 architect 範圍) 2. Frontend 實作 Settings 韌體面板: - 顯示當前 FW 版本 - 「升級到最新」按鈕 - 「降版」按鈕 → 展開版本 dropdown + 警告語 + 二次確認 modal 3. 警告語內容(給 design 補強): - 「降版可能導致現有 model 無法運作」 - 「降版過程不可中斷、否則裝置可能損壞」 - 「請確認版本相容性」 4. 二次確認 modal: - 需要使用者輸入「DOWNGRADE」字串(防誤觸) - 顯示降版預估時間 + before/after 版本對照 5. 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 的下一步建議 1. **A 階段 MVP 先做(不變)**——M9-1 ~ M9-5、5 人天 2. **A 階段驗證後(或同時平行)啟動 M9-6**——SDK 驗證、純研究、Architect 自己跑 3. **M9-6 結論回填**到 `41-tar-firmware-handling.md` + 更新本檔 §5.2 / §7.1 / R-FW-8 4. **B 階段是否要全做、依 M9-6 結果決定**——如果 SDK 不支援、可能要先升級 wheel、回歸成本變高、可重新 scope 5. **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 |