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

731 lines
29 KiB
Markdown
Raw Permalink 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.

# M9-6 SDK 驗證計畫書
> 對應 research index §50
> 範圍B 階段啟動前的 KneronPLUS SDK + KL630/KL730 SDK 行為實機驗證計畫
> 撰寫日期2026-05-24
> 限制:純 plan、不出 code、不執行驗證驗證由 testing/backend 工程師執行)
> 路徑使用相對路徑(相對於 `/Users/jimchen/visionA/local-tool/`
---
## 0. TL;DR
1. 本檔是「**將來要做的驗證計畫**」、不是驗證執行報告。執行階段由 testing + backend Agent 接手、結果回填到本檔 §8 與相關研究檔。
2. 必須驗證的關鍵 unknown 共 **10 項**,分 3 大類:
- **SDK API 行為**API 簽名 / .tar 支援 / chip enum 完整性)— 6 項
- **KL630/KL730 連線行為**USB scan / connect / firmware load— 3 項
- **KneronPLUS wheel 版本相容性**(既有 wheel 是否需升級 + 升級風險)— 1 項
3. 驗證有兩個強度等級:
- **強驗證**(有實機 KL630/KL730 dongle— 涵蓋 10 項全部、結果可信
- **弱驗證**(無實機、只查 SDK 文件 + wheel source— 涵蓋 6 項API 層)、剩 4 項(連線行為)只能靠文件推斷、留風險到 M9-9 開發階段
4. 預估時間:強驗證 2 人天、弱驗證 1 人天
5. **關鍵風險**:拿不到 KL630/KL730 硬體 → 強驗證降級為弱、B 階段所有 milestone 都帶 SDK unknown 風險
---
## 1. 為什麼需要 SDK 驗證
### 1.1 前兩輪研究的 SDK unknown 清單
`40-b-phase-kl630-kl730-extension.md` §3.2 + `41-tar-firmware-handling.md` §2 + §5 整理:
| 來源 | unknown | 影響範圍 |
|------|---------|---------|
| `40-b-phase` §3.2 row 1 | KL630 `connect_devices()` 是否與 KL520/KL720 共用簽名 | connect 流程設計 |
| `40-b-phase` §3.2 row 2 | `load_firmware_from_file()` 對 KL630/KL730 是否仍接受兩個 .bin解壓後 | .tar 處理策略選定Y or Z|
| `40-b-phase` §3.2 row 3 | 是否有 `load_firmware_from_tar()` 之類新 API | 策略 Z 是否可行 |
| `40-b-phase` §3.2 row 4 | `update_kdp_firmware_from_files()` 對 KL630/KL730 是否適用 | FW 升降版 API 選擇 |
| `40-b-phase` §3.2 row 5 | KL630/KL730 是否仍有 Loader / KDP / KDP2 三 state | FW 偵測邏輯 |
| `40-b-phase` §3.2 row 6 | KL630/KL730 是 flash-based 還是每次都要 load firmware | connect 速度設計 |
| `40-b-phase` §3.2 row 7 | `generic_image_inference_send/receive` 對 KL630/KL730 是否一致 | inference 流程是否分支 |
| `40-b-phase` §3.2 row 8 | KL630/KL730 firmware 字串可能值 | FW badge 顯示邏輯 |
| `41-tar` §1.3 | .tar 內容解壓後有哪些檔案、metadata | bridge.py 解析邏輯 |
| `41-tar` §2.3 | visionA-local 既有 KneronPLUS wheel 版本 vs 3.1.2 | wheel 升級決策 |
### 1.2 為什麼這些必驗、不能靠推測
每個 unknown 都有「假設錯了會炸」的後果:
- API 簽名假設錯 → bridge.py 寫出來直接 import error / type error / runtime crash
- .tar 處理策略選錯 → 安裝包大小 / build script 設計全錯、要從 M9-8 整段重做
- wheel 版本假設錯 → KL520/KL720 既有功能 regression、回頭重做 A 階段
→ M9-6 是 B 階段 critical path、所有後續 milestone 都依賴它。
---
## 2. 驗證目標清單10 項)
每項 = 一個必須回答的具體問題。每項標:是否依賴實機、強度等級、後續決策影響。
### 類別 ASDK API 行為(弱驗證即可、查 wheel source / SDK 文件)
#### A-1既有 KneronPLUS wheel 版本是什麼
- **問題**visionA-local repo 內目前用的 KneronPLUS wheel 是哪個版本?是否內含 `KP_DEVICE_KL630` / `KP_DEVICE_KL730` enum
- **依賴實機**:否(只需 Python 環境)
- **影響**:決定要不要升級 wheel升級會牽動 KL520/KL720 regression 風險)
#### A-2wheel 內 `kp.ProductId.KP_DEVICE_KL630` / `KL730` enum 是否存在
- **問題**:既有 wheel `kp.ProductId.KP_DEVICE_KL630` / `KL730` 是否可 import、值是多少
- **依賴實機**:否
- **影響**A-2 若失敗 → 必須升級 wheel連 driver 認 chip 都做不到)
#### A-3`kp.core.load_firmware_from_file()` 對 .tar 的行為
- **問題**:把 `.tar` 路徑當 `scpu_file` 參數餵進去、SDK 回什麼?接受、解析錯誤、還是 silent fail
- **依賴實機**:否(傳路徑、看 exception type
- **影響**:決定 .tar 處理策略Y 解壓 or Z 直接餵)
#### A-4是否有 `load_firmware_from_tar()` / `update_*_from_tar()` 新 API
- **問題**wheel 內是否提供 .tar 專用 firmware load API簽名為何
- **依賴實機**grep wheel source
- **影響**:策略 Z 是否可行(最乾淨方案)
#### A-5`update_kdp_firmware_from_files()` 對 KL630/KL730 是否適用
- **問題**:把 KL630 device + KL630 .tar / .bin 餵進去、SDK 回什麼?是否有 chip-aware 內部 dispatch、還是 KL520/KL720 專用?
- **依賴實機**:部分(無實機只能查 source code 的 chip 分支邏輯)
- **影響**FW 升降版 API 選擇
#### A-6KneronPLUS wheel 升級對 KL520/KL720 既有行為的 regression 範圍
- **問題**:從目前 wheel → 3.1.2(或 latest有哪些 breaking change
- **依賴實機**:強驗證(升級後跑既有 E2E test弱驗證看 release notes / changelog
- **影響**:是否值得升 wheel、升級後 A 階段 KL520/KL720 是否需要回歸測試
### 類別 BKL630 / KL730 連線行為(需實機強驗證)
#### B-1KL630 / KL730 在 USB scan 時的 product_id
- **問題**:實機 `kp.core.scan_devices()` / pyusb scan 時KL630 dongle 的 `product_id``0x0630`KL730 是 `0x0730` 嗎?是否有 legacy/new 雙版本(像 KL720 的 `0x0200` vs `0x0720`
- **依賴實機**:是(必須有 dongle
- **影響**`_KNOWN_PRODUCTS` map 是否需擴展、`chipFromProductID()` switch 是否要加額外 case
#### B-2KL630 / KL730 是否每次 connect 都要 load firmware
- **問題**:實機連續 connect 兩次、第二次是否需要重新 load firmwareKL520 要、KL720 不要)
- **依賴實機**:是
- **影響**connect 流程設計(速度 + 用量)、是否走 build time 解壓 vs runtime 解壓
#### B-3KL630 / KL730 firmware 字串可能值
- **問題**:實機 `scan_devices()` 回傳的 `device.firmware` 欄位字串是什麼?"KDP2" / "KDP3" / "Loader" / 其他是否能透過此值區分「需要升級」vs「已 ready」
- **依賴實機**:是
- **影響**FW badge UI 顯示邏輯、`detector.go` firmware state 判斷
### 類別 C.tar 檔案內容(無實機也能驗、純 tar -xf
#### C-1.tar 內容實測
- **問題**`tar -tvf kp_firmware.tar` / `tar -tvf kp_loader.tar` 內含哪些檔案?大小?是否有 manifest
- **依賴實機**:否(只需 warrenchen bundle 的 .tar 檔)
- **影響**bridge.py 解壓後檔案路徑解析邏輯、`_resolve_firmware_paths` 設計
---
## 3. 各項驗證方法(執行 step-by-step
> **注意**:這節是給未來執行 M9-6 的 testing/backend 工程師看的、step 描述要可重複。
### A-1既有 wheel 版本查詢
**前置**:在 visionA-local repo 根目錄、Python 環境已 setup。
**步驟**
```bash
# Step 1: 找 KneronPLUS wheel 位置
find . -name "KneronPLUS*.whl" 2>/dev/null
find . -name "kneronplus*.whl" 2>/dev/null
# 若上述都沒結果、檢查是否 venv 內安裝
. server/scripts/venv/bin/activate 2>/dev/null && pip show kneronplus 2>/dev/null
# 也檢查專案有沒有 vendored Python source
find . -path "*/kp/__init__.py" -not -path "*/node_modules/*" 2>/dev/null
```
**預期結果三種**
- **A**:找到 `KneronPLUS-X.Y.Z-py3-none-any.whl` → 記錄版號 X.Y.Z
- **B**:找到 installed package、`pip show` 回版本 → 記錄
- **C**:找不到 → wheel 是 lazy import 或 system-wide install、需查 `kneron_bridge.py` `_macos_preload_native_libs()` 內的 lib path 反推 wheel 位置
**回填位置**:本檔 §8.1、`41-tar-firmware-handling.md` §2.3
### A-2wheel 內 KL630/KL730 enum 存在驗證
**前置**A-1 已找到 wheel、Python 環境可 `import kp`
**步驟**
```bash
python3 -c "
import kp
print('KP_DEVICE_KL520:', getattr(kp.ProductId, 'KP_DEVICE_KL520', 'NOT FOUND'))
print('KP_DEVICE_KL720:', getattr(kp.ProductId, 'KP_DEVICE_KL720', 'NOT FOUND'))
print('KP_DEVICE_KL630:', getattr(kp.ProductId, 'KP_DEVICE_KL630', 'NOT FOUND'))
print('KP_DEVICE_KL730:', getattr(kp.ProductId, 'KP_DEVICE_KL730', 'NOT FOUND'))
print('KP_DEVICE_KL530:', getattr(kp.ProductId, 'KP_DEVICE_KL530', 'NOT FOUND'))
"
```
**預期結果三種**
- **A**:四個都 print 出整數值 → 既有 wheel OK、不必升級僅針對 enum 層)
- **B**KL520/KL720 有、KL630/KL730 沒有 → 必須升級 wheel
- **C**`import kp` 直接 import error → wheel 未安裝、A-1 結果有誤、回去查
**回填位置**:本檔 §8.1、影響 M9-7 決策
### A-3load_firmware_from_file 對 .tar 行為
**前置**A-1 + A-2 完成、warrenchen .tar 檔已取得(在 `/tmp/web_academy_prototype/local_service_win/firmware/KL630/kp_firmware.tar`)。
**步驟**
```bash
python3 << 'EOF'
import kp
import sys
# 試 connect KL630 dongle、若無實機跳過、改成 mock test
tar_path = "/tmp/web_academy_prototype/local_service_win/firmware/KL630/kp_firmware.tar"
# 情境 1用實機若有
try:
descs = kp.core.scan_devices()
kl630_port = None
for i in range(descs.device_descriptor_number):
dev = descs.device_descriptor_list[i]
# 假設 KL630 product_id = 0x0630、若 B-1 驗證後值不同要修
if hex(dev.product_id) == "0x630":
kl630_port = dev.usb_port_id
print(f"KL630 found at port {kl630_port}")
break
if kl630_port:
dg = kp.core.connect_devices([kl630_port])
# 試把 .tar 直接餵
try:
ret = kp.core.load_firmware_from_file(dg, tar_path, "")
print(f"load_firmware_from_file(.tar, '') = {ret}")
except Exception as e:
print(f"load_firmware_from_file(.tar, '') raised: {type(e).__name__}: {e}")
# 試把 .tar 當 scpu + ncpu 同檔
try:
ret = kp.core.load_firmware_from_file(dg, tar_path, tar_path)
print(f"load_firmware_from_file(.tar, .tar) = {ret}")
except Exception as e:
print(f"load_firmware_from_file(.tar, .tar) raised: {type(e).__name__}: {e}")
except Exception as e:
print(f"No device, skip runtime test: {e}")
# 情境 2純靜態查 source無實機也能跑
import inspect
print("\nload_firmware_from_file source:")
try:
print(inspect.getsource(kp.core.load_firmware_from_file))
except Exception as e:
print(f"Cannot get source: {e}")
EOF
```
**預期結果三種**
- **A**SDK 接受 .tar、回 success → 策略 Z 可行
- **B**SDK 報 `KP_FW_INFO_ERR` / 類似錯誤碼 → 策略 Y 必須走build time 解壓)
- **C**source 顯示函式內有 `.endswith(".tar")` 之類判斷 → 確認 SDK 內部分支邏輯
**回填位置**:本檔 §8.1、`41-tar-firmware-handling.md` §2.1 + §4.4
### A-4load_firmware_from_tar / update_*_from_tar 是否存在
**步驟**
```bash
python3 << 'EOF'
import kp
import kp.core
# 列出 kp.core 所有 firmware-related 函式
for name in dir(kp.core):
if any(k in name.lower() for k in ["firmware", "fw_", "load_fw", "update_kdp", "tar"]):
print(f"kp.core.{name}")
# 列出 kp 模組頂層 firmware-related
for name in dir(kp):
if any(k in name.lower() for k in ["firmware", "fw_", "tar"]):
print(f"kp.{name}")
EOF
```
**或解壓 wheel grep**(無 Python 環境也能跑):
```bash
cd /tmp && unzip -o KneronPLUS-X.Y.Z-py3-none-any.whl -d kneron_plus_inspect/
grep -rn "load_firmware_from_tar\|update_kdp.*tar\|extract_tar\|from_tar" kneron_plus_inspect/kp/
grep -rn "^def \|^ def " kneron_plus_inspect/kp/core.py | grep -iE "fw|firmware|tar"
```
**預期結果**
- **A**:找到 `load_firmware_from_tar()``update_kdp2_firmware_from_tar()` → 策略 Z 確定可行
- **B**:找不到、只有既有 `load_firmware_from_file()` → 策略 Ybuild time 解壓)
**回填位置**:本檔 §8.1、`41-tar-firmware-handling.md` §2.1 表格
### A-5update_kdp_firmware_from_files 對 KL630/KL730 chip dispatch
**步驟**
```bash
python3 << 'EOF'
import kp.core
import inspect
# 看 update_kdp_firmware_from_files source 是否有 chip 判斷
try:
src = inspect.getsource(kp.core.update_kdp_firmware_from_files)
print(src)
except Exception:
pass
# 列其他 update 系列函式
for name in dir(kp.core):
if "update" in name.lower() and "fw" in name.lower():
print(name)
EOF
```
**預期結果**
- **A**source 內有 `if chip == "KL630"` 之類分支 → 適用、可直接用
- **B**source 只處理 KL520/KL720 → 需找 `update_kdp2_firmware_*` 之類新 API
- **C**:找不到對應 API → 須 fall back 用 `load_firmware_from_file` + 手動 reboot 流程
**回填位置**:本檔 §8.1、`40-b-phase` §3.2 row 4
### A-6wheel 升級對 KL520/KL720 regression 範圍
**步驟**(弱驗證版本,無實機):
```bash
# Step 1: 取得 SDK changelog
# 通常在 Kneron developer portal 或 wheel METADATA
cd /tmp && unzip -o KneronPLUS-X.Y.Z-py3-none-any.whl -d wheel_old/
unzip -o /tmp/web_academy_prototype/local_service_win/KneronPLUS-3.1.2-py3-none-any.whl -d wheel_new/
diff -r wheel_old/kp/ wheel_new/kp/ | head -100
# 重點看 kp/core.py 和 kp/inference.py 的 API 簽名變化
```
**步驟**(強驗證版本,有實機):
1. 備份既有 wheel 安裝
2. pip install warrenchen wheel 3.1.2
3. 跑 visionA-local 既有 KL520 + KL720 完整 E2Escan → connect → load firmware → inference
4. 比對 inference 結果(用同一張圖、同一個 NEF、檢查 detection box 是否一致)
5. 紀錄 break point
**預期結果**
- **A**API 簽名完全相容、inference 結果一致 → 升級安全
- **B**API 簽名有 breaking change部分函式 rename / 參數順序變) → 升級需配合 bridge.py 更新
- **C**inference 結果不一致 → 升級風險高、需深查
**回填位置**:本檔 §8.1、新增風險紀錄到 R-FW-8
### B-1KL630/KL730 USB scan product_id
**前置**:實機 KL630 + KL730 dongle 接上電腦、driver 已裝。
**步驟**
```bash
python3 << 'EOF'
import kp
descs = kp.core.scan_devices()
print(f"Total devices: {descs.device_descriptor_number}")
for i in range(descs.device_descriptor_number):
dev = descs.device_descriptor_list[i]
print(f" device {i}:")
print(f" product_id = {hex(dev.product_id)}")
print(f" usb_port_id = {dev.usb_port_id}")
print(f" firmware = {dev.firmware}")
print(f" is_connectable = {dev.is_connectable}")
print(f" kn_number = {dev.kn_number}")
EOF
```
**同時用 pyusb 比對**(看 OS 層級看到什麼 product_id
```bash
python3 << 'EOF'
import usb.core
devs = usb.core.find(find_all=True, idVendor=0x3231) # Kneron VID
for dev in devs:
print(f"VID:PID = {hex(dev.idVendor)}:{hex(dev.idProduct)}")
EOF
```
**預期結果**
- KL630 → product_id = `0x0630`、KL730 → `0x0730`、跟前兩輪研究假設一致
- 若有 legacy/new 雙版本(像 KL720 的 0x0200 vs 0x0720→ 紀錄並更新 `_KNOWN_PRODUCTS` map
**回填位置**:本檔 §8.2、`40-b-phase` §2.1
### B-2KL630/KL730 每次 connect 都要 load firmware 嗎
**前置**B-1 完成、確認能識別 KL630/KL730。
**步驟**
```bash
python3 << 'EOF'
import kp
import time
descs = kp.core.scan_devices()
# 找 KL630或 KL730
port_id = None
for i in range(descs.device_descriptor_number):
dev = descs.device_descriptor_list[i]
if hex(dev.product_id) == "0x630": # KL630
port_id = dev.usb_port_id
firmware_before = dev.firmware
is_connectable_before = dev.is_connectable
break
if not port_id:
print("No KL630 found")
exit()
print(f"Before connect: firmware={firmware_before}, is_connectable={is_connectable_before}")
# 第一次 connect、不 load firmware
dg1 = kp.core.connect_devices([port_id])
print(f"First connect OK, dg={dg1}")
del dg1
time.sleep(2)
# 重新 scan、看 firmware 字串是否變化
descs2 = kp.core.scan_devices()
for i in range(descs2.device_descriptor_number):
dev = descs2.device_descriptor_list[i]
if dev.usb_port_id == port_id:
print(f"After first connect: firmware={dev.firmware}, is_connectable={dev.is_connectable}")
break
# 第二次 connect、看是否仍能 inference不重 load
dg2 = kp.core.connect_devices([port_id])
# 試跑 model load (用 KL630 sample NEF)
# kp.core.load_model_from_file(dg2, "...sample.nef")
# 觀察是否 OK
EOF
```
**預期結果**
- **A**:第二次 connect 後直接能 inference、不用重 load firmware → flash-based、像 KL720
- **B**:第二次 connect 後 inference 失敗、需重新 `load_firmware_from_file()` → 像 KL520
**回填位置**:本檔 §8.2、`40-b-phase` §5.2 / §5.3 R-FW-10
### B-3KL630/KL730 firmware 字串可能值
**步驟**:在 B-1 跑完已自然取得(`dev.firmware` 欄位)。額外重複測試不同 state
1. KL630 剛從盒子拿出來(未灌任何 firmware→ 紀錄 firmware 字串
2. KL630 跑過一次 inference 後(已 load firmware→ 紀錄
3. KL630 reboot 後 → 紀錄
**預期結果**
- 可能值:`""` / `"KDP"` / `"KDP2"` / `"KDP3"` / `"Loader"` / `"App"` / 其他
- 紀錄 chip × state 對照表
**回填位置**:本檔 §8.2、`40-b-phase` R-FW-10、`detector.go` firmware state map
### C-1.tar 檔案內容實測
**前置**warrenchen bundle 已 clone 到 `/tmp/web_academy_prototype/`
**步驟**
```bash
cd /tmp/web_academy_prototype/local_service_win/firmware
# KL630
echo "=== KL630 kp_firmware.tar ==="
ls -lh KL630/kp_firmware.tar KL630/kp_loader.tar
tar -tvf KL630/kp_firmware.tar
tar -tvf KL630/kp_loader.tar
# KL730
echo "=== KL730 kp_firmware.tar ==="
ls -lh KL730/kp_firmware.tar KL730/kp_loader.tar
tar -tvf KL730/kp_firmware.tar
tar -tvf KL730/kp_loader.tar
# 試解壓到 temp
mkdir -p /tmp/kl630_extracted
tar -xf KL630/kp_firmware.tar -C /tmp/kl630_extracted
ls -lhR /tmp/kl630_extracted/
file /tmp/kl630_extracted/*
# 看是否有 manifest / metadata
for f in /tmp/kl630_extracted/*.json /tmp/kl630_extracted/*.txt /tmp/kl630_extracted/*.yml /tmp/kl630_extracted/*.yaml; do
[ -f "$f" ] && echo "--- $f ---" && cat "$f"
done
```
**預期結果**
- **A**.tar 內含 `fw_scpu.bin` + `fw_ncpu.bin` + 可能 `manifest.json` → 策略 Y 解壓後拿 .bin 直餵
- **B**.tar 內含其他結構(如 nested .tar / 加密檔) → 處理複雜度上升
**回填位置**:本檔 §8.3、`41-tar-firmware-handling.md` §1.3
---
## 4. 驗證環境需求
### 4.1 硬體
| 硬體 | 強驗證 | 弱驗證 | 取得來源 |
|------|--------|--------|---------|
| KL520 dongle | 可選(用於 wheel 升級回歸測試)| 不需 | 既有開發環境 |
| KL720 dongle | 可選(同上)| 不需 | 既有開發環境 |
| KL630 dongle | **必須 ≥1 顆** | 不需 | 問 Innovedus team / Kneron 借機 |
| KL730 dongle | **必須 ≥1 顆** | 不需 | 問 Innovedus team / Kneron 借機 |
| 跑驗證的主機 | macOS / Linux 至少 1 台、Windows 1 台(驗 driver install| macOS 或 Linux 1 台即可 | 既有開發環境 |
### 4.2 軟體
- Python 3.10+(既有 venv 即可)
- KneronPLUS Python wheel既有 + warrenchen 3.1.2 兩版各一份)
- KL630 / KL730 sample NEF 模型(從 Kneron sample 拿、用於 B-2 inference 測試)
- 解壓工具Python `tarfile` 標準庫 + shell `tar`macOS / Linux 內建、Windows 10+ 內建)
### 4.3 SDK 文件
- KneronPLUS SDK Reference Manual PDF從 Kneron developer portal 下載)
- SDK changelog / release notes決定升級的 breaking change 範圍)
### 4.4 平台覆蓋策略
**M9-6 階段**只驗 1 平台(推薦 macOS 或 Linux、剩 2 平台留到 M9-13 整體三平台驗證跑。
理由M9-6 主要驗 SDK API 行為(跨平台一致)、不是驗安裝 / driver / OS 整合(那是 M9-13
---
## 5. 預估時間
### 5.1 強驗證(有實機)
| 階段 | 工時 | 累積 |
|------|------|------|
| 環境準備(取得硬體、裝 driver、setup Python | 0.5 天 | 0.5 |
| A 類驗證執行A-1 ~ A-6| 0.5 天 | 1.0 |
| B 類驗證執行B-1 ~ B-3| 0.5 天 | 1.5 |
| C 類驗證執行C-1| 0.1 天 | 1.6 |
| 結果回填到本檔 §8 + 其他研究檔 | 0.4 天 | 2.0 |
| **合計** | **2 人天** | |
### 5.2 弱驗證(無實機)
| 階段 | 工時 | 累積 |
|------|------|------|
| 環境準備(裝 wheel、解壓 wheel source| 0.2 天 | 0.2 |
| A 類驗證執行(弱版本,無實機部分)| 0.3 天 | 0.5 |
| B 類驗證跳過(只靠 SDK 文件推斷)| 0.2 天 | 0.7 |
| C 類驗證執行 | 0.1 天 | 0.8 |
| 結果回填 + 標註「需 M9-9 補強驗」| 0.2 天 | 1.0 |
| **合計** | **1 人天** | |
### 5.3 預期 B 階段風險增量(採弱驗證的話)
如果 M9-6 走弱驗證、B-1 / B-2 / B-3 留到 M9-9 開發階段才實機驗:
- M9-9 工時可能從 2 天 → 3 天(多 1 天驗 SDK 行為 + 修 bridge.py
- M9-13 三平台驗證可能多發現 1-2 個原本以為 work 的 case 實際不 work
**強烈建議走強驗證**、即使要等硬體到位。
---
## 6. 風險
### R-VAL-1拿不到 KL630 / KL730 硬體(高度風險)
**情境**Innovedus / Kneron 沒法提供 dongle、或要等好幾週。
**緩解選項**
- **選項 A**降級為弱驗證、B 階段啟動時 SDK unknown 帶到 M9-9
- **選項 B**:延後 B 階段啟動、等硬體到位(不影響 A 階段 MVP
- **選項 C**:先做 mock 驗證(建 mock SDK、用 monkey patch 假裝 KL630 device、等實機到再覆驗
**建議**:選項 B延後 B 階段)+ 順手做選項 C建 mock 為將來測試鋪路)
### R-VAL-2KneronPLUS wheel 不支援 KL630/KL730中度
**情境**A-2 驗證結果是 "B"(既有 wheel 沒有 KL630/KL730 enum
**緩解**
- 升級 wheel 到 warrenchen 3.1.2 或更新版
- A-6 評估 regression 範圍、必要時跑完整 KL520/KL720 回歸測試
- 預留 0.5-1 人天到 M9-7 做 wheel 升級 + regression
### R-VAL-3驗證結果跟前兩輪研究假設不同中度
**情境**:例如 B-1 發現 KL630 product_id 不是 `0x0630`、是 legacy + new 雙 ID。
**觸發重大調整 plan 的條件**
1. KL630 / KL730 product_id 跟假設不同 → 更新 `40-b-phase` §2.1 + bridge.py 改動範圍
2. KL630 / KL730 是 USB Boot 而非 flash-based → 整段 §5.2 推測重寫、`.tar` 處理流程設計重來
3. SDK 完全沒有 .tar 支援 API → 策略 Z 永久排除、回到策略 Y、build script 必加
4. wheel 升級對 KL520 regression > 30% → 不升級、找其他方案(可能要找 Kneron 要 dev 版)
每個觸發條件對應的 plan 重做工時估算:
- 觸發 1 → 0.2 天重寫 + M9-7 多 0.3 天
- 觸發 2 → 1 天重寫研究檔 + M9-8/M9-9 各多 0.5 天
- 觸發 3 → 不影響(策略選定即可)
- 觸發 4 → 1-2 天找替代方案
### R-VAL-4硬體環境不穩、間歇性失敗低度
**情境**USB 接觸不良、dongle 過熱、driver 偶發崩潰。
**緩解**
- 每個 B 類驗證重複 3 次、取一致結果
- 紀錄不一致 case、單獨追
---
## 7. 驗證完成的下游影響
### 7.1 既有研究檔需要更新
| 檔案 | 章節 | 更新內容 |
|------|------|---------|
| `40-b-phase-kl630-kl730-extension.md` | §3.2 表格 | 填入「實測值」column |
| `40-b-phase-kl630-kl730-extension.md` | §5.2 | KL630/KL730 推測段落改成「驗證結果」 |
| `40-b-phase-kl630-kl730-extension.md` | §7 | inference 流程驗證 |
| `40-b-phase-kl630-kl730-extension.md` | R-FW-8 / R-FW-10 | 風險紀錄狀態更新 |
| `41-tar-firmware-handling.md` | §1.3 | .tar 內容實測結果 |
| `41-tar-firmware-handling.md` | §2.1 / §2.2 / §2.3 | wheel 版本 + API 驗證結果 |
| `41-tar-firmware-handling.md` | §3.3 | .tar 大小實測 |
| `41-tar-firmware-handling.md` | §4.4 | 策略選定Y or Z |
| `41-tar-firmware-handling.md` | §5 | 驗證計畫狀態改「已完成」+ 結果 |
### 7.2 milestone 重排可能性
| milestone | 受 M9-6 結果影響的程度 |
|-----------|---------------------|
| M9-7B0 認 chip | 低B-1 結果可能調整 chip 判斷邏輯、+0.1 天 |
| M9-8.tar handling | 高:策略 Y vs Z 完全不同實作、工時可能 ±0.5 天 |
| M9-9connect + inference | 中B-2 / B-3 結果決定 connect 流程設計 |
| M9-10FW 升版擴 KL630/KL730 | 中A-5 結果決定用哪個 API |
| M9-11多版本 | 低 |
| M9-12降版 UI | 低 |
| M9-13三平台驗證 | 中:若驗證發現新問題、增加 case 數 |
### 7.3 TDD v2.2 firmware-management.md 影響
`30-integration-plan.md` 已標 firmware-management 章節範圍。M9-6 結果可能影響:
- §「SDK API 對照表」需填實測值
- §「KL630/KL730 連線流程」需確認 flash-based vs USB Boot
- §「.tar 處理策略」需選定 Y or Z
- §「KneronPLUS wheel 升級需求」需填升級決策
**TDD v2.2 firmware-management.md 應該等 M9-6 完成再 finalize**、或 M9-6 結果作為 v2.2 → v2.3 的 update trigger。
---
## 8. 驗證結果填空區(執行後填)
> 本節是執行 M9-6 的工程師填寫的、現階段空白。
### 8.1 A 類結果
| 項目 | 結果 | 證據 / 紀錄路徑 | 對下游決策影響 |
|------|------|---------------|--------------|
| A-1既有 wheel 版本 | _未驗證_ | | |
| A-2KL630/KL730 enum 存在 | _未驗證_ | | |
| A-3load_firmware_from_file 對 .tar 行為 | _未驗證_ | | |
| A-4load_firmware_from_tar 是否存在 | _未驗證_ | | |
| A-5update_kdp_firmware_from_files 對 KL630/KL730 適用性 | _未驗證_ | | |
| A-6wheel 升級 regression 範圍 | _未驗證_ | | |
### 8.2 B 類結果
| 項目 | 結果 | 證據 | 對下游決策影響 |
|------|------|------|--------------|
| B-1KL630/KL730 product_id | _未驗證_ | | |
| B-2KL630/KL730 是否每次 connect 重 load firmware | _未驗證_ | | |
| B-3KL630/KL730 firmware 字串可能值 | _未驗證_ | | |
### 8.3 C 類結果
| 項目 | 結果 | 證據 | 對下游決策影響 |
|------|------|------|--------------|
| C-1.tar 內容 | _未驗證_ | | |
### 8.4 整體結論(執行後填)
- 策略 Y vs Z 選定_待驗證後填_
- wheel 升級決策_待驗證後填_
- B 階段啟動 go/no-go_待驗證後填_
---
## 9. 給 Orchestrator 的執行建議
### 9.1 何時派工
**選項 AA 階段 MVP 同步起跑**(使用者目前決策)
- M9-6 與 M9-1 ~ M9-5 平行
- 派 testing/backend Agent 在 A 階段任何時點啟動 M9-6推薦 A 階段 50% 進度時)
- M9-6 完成後 B 階段才能啟動、不卡 A 階段
- 風險A 階段也在動 bridge.py、M9-6 可能跟 A 階段同檔修改衝突(協調好就好)
**選項 BA 階段 MVP 完成後啟動**(原計畫)
- M9-6 + A 階段 5 人天 + B 階段 10.5 人天 = 序列
- 簡單、無衝突
- 風險B 階段啟動延後 5 人天
**建議**:選 A、但安排 M9-6 在 A 階段 M9-3 或 M9-4 完成後啟動bridge.py 大改動已完成)、避開檔案衝突。
### 9.2 派工 Agent 選擇
**強驗證階段**
- 主要testing Agent驗證執行 + 結果紀錄是專業)
- 配合backend Agent讀 SDK source code + 寫驗證 script 偏 backend 領域)
- 推薦testing 主派、backend 顧問
**弱驗證階段**(若無實機):
- 主要backend Agent純查 source + 文件)
- testing 可不參與
### 9.3 派工前必須先做的事
1. **跟使用者確認硬體狀態**:問「你或 Innovedus team 手上有 KL630 / KL730 dongle 嗎?多久能拿到?」
2. **根據答案決定強 / 弱驗證**
- 有 → 強驗證、派 testing + backend
- 沒、3 週內能拿到 → 延後 M9-6、改先做選項 C mock順手鋪路
- 沒、無法取得 → 弱驗證、接受 M9-9 補強驗的風險
3. **取得 KneronPLUS SDK Reference Manual PDF**:問使用者 / 從 Kneron developer portal 下載、放到 `.autoflow/04-architecture/research-kl520-fw-management/refs/` 供工程師查
### 9.4 派工 prompt 模板(給 Orchestrator 用)
```
路徑資訊:
- 你的角色定義:(對應 agent CLAUDE.md 絕對路徑)
- 專案目錄:當前工作目錄
- 本任務 plan`.autoflow/04-architecture/research-kl520-fw-management/50-m9-6-sdk-validation.md`
- 結果回填到本檔 §8.1 / §8.2 / §8.3
任務:執行 M9-6 SDK 驗證(強驗證版本)
範圍:本檔 §2 列出的 10 個驗證項目、按 §3 的 step 執行
產出:
1. 本檔 §8 填空區填實測結果
2. 更新 §7.1 列的下游研究檔(標明「本次更新」)
3. 結果摘要丟給 Orchestrator、附 go/no-go 建議
不要做:
- 改 bridge.py / driver code驗證 only、不寫產品 code
- 改 TDD v2.2 firmware-management.md那是 architect 範圍、本任務只回填到研究檔)
```
---
## 10. 與其他研究檔的關係
| 連結 | 引用內容 |
|------|---------|
| `40-b-phase-kl630-kl730-extension.md` §3.2 | 本檔 §1.1 / §2 類別 A 大部分項目來源 |
| `40-b-phase-kl630-kl730-extension.md` §5.2 / R-FW-10 | 本檔 §2 類別 B連線行為驗證 |
| `40-b-phase-kl630-kl730-extension.md` M9-6 段落 | 本檔是 M9-6 的詳細展開 |
| `41-tar-firmware-handling.md` §1.3 | 本檔 §2 C-1 來源 |
| `41-tar-firmware-handling.md` §2 | 本檔 §2 類別 A row A-3 / A-4 來源 |
| `41-tar-firmware-handling.md` §5 | 本檔取代之前的 「待 M9-6 驗證」placeholder、§5 升級為 reference 本檔 |
| `30-integration-plan.md` 階段 B 評估 | M9-6 結果回填觸發 TDD v2.2 firmware-management.md 更新 |