jim800121chen 46514d77d7 docs(local-tool): M9 — Kneron Dongle FW 偵測 + 升降版(A+B、翻案 R5-Q9)
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>
2026-05-25 07:40:56 +08:00

704 lines
32 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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_idscan 看得到)
`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 全部掉這裡
```
→ KL6300x0630/ KL7300x0730會被當成 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-59chip 判斷加 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-111switch 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 legacypid=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 特定 chipYOLOv5_KL520_*.nef vs YOLOv5_KL630_*.nefdriver 只負責跑不在 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 既有 firmwaresymlink 或實檔)
│ │ ├── 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/
└── ...同結構...
```
**對 MVPA 階段)的影響**
- 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/<chip>/<version>/...` 解析路徑
詳細 API + UI 設計在手動降版研究檔`42-manual-downgrade-for-end-users.md`)。
---
## 9. 風險分析B 階段新增)
承前一份 R-FW-1 ~ R-FW-7本份補
### R-FW-8KneronPLUS 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.xwarrenchen 3.1.2
3. wheel 版本升級會影響 KL520/KL720 既有行為regression 風險)、必須三平台回歸驗
### R-FW-9.tar 解包對安裝包大小衝擊(低度風險)
**情境**如果採 build time extract安裝包同時包 .tar + 解壓後的 .bin大小翻倍
**緩解**
- build script 解壓後刪原始 .tar ship .bin)、安裝包多塞 .bin 但少塞 .tarnet 差約等於壓縮率
- 估算KL630 .tar 預估 ~2-3MB解壓後 .bin ~3-4MB淨增 +1MBacceptable
### R-FW-10KL630/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-5B 階段拆 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 認 chip0.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 handling1.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 testmock`_resolve_firmware_paths("KL630")` 回正確路徑
**驗收**
- `python3 kneron_bridge.py` 跑 `_resolve_firmware_paths("KL630")` 回正確路徑(不接 device 也能測)
- 安裝包大小變化在預估範圍(+3-5MB
#### M9-9 — B1.2 connect + inference2 人天)
**負責**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 testKL630/KL730 各跑一支 sample NEF
**驗收**
- KL630/KL730 dongle 能 connect + 跑 inference 成功
- 既有 KL520/KL720 沒有 regression
#### M9-10 — B1.3 FW 升版擴 KL630/KL7301 人天)
**負責**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 結束過 Reviewerprogram 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 |