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

260 lines
13 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.

# warrenchen 雲端版 FW 偵測 + 升降版實作分析
> 對應 visionA-local 研究 plan §10
> 來源:`/tmp/web_academy_prototype/` reposhallow clone, c9d56a6 HEAD
> 主要分析檔:
> - `local_service_win/LocalAPI/main.py`FastAPI HTTP 服務、含 FW endpoints
> - `local_service_win/LocalAPI/legacy_plus121_runner.py`(用 ctypes 直接呼叫 libkplus.dll、最關鍵
> - `local_service_win/STRATEGY.md`API spec + recovery flow
> - `local_service_win/firmware/`(裝置 firmware 二進位)
> - `local_service_win/third_party/Kneron_DFUT/`Windows-only Qt GUI 工具)
---
## 1. warrenchen 用了哪些技術做 FW 偵測 + 升降版
### 1.1 兩條獨立路徑(並存)
| 路徑 | 用途 | 技術 | 跨平台? |
|------|------|------|---------|
| **Path AKneronDFUT.exe** | KL520 升版 / 降版、KL720 升版 | 呼叫 Windows 第三方 `KneronDFUT.exe` CLIsubprocess | ❌ Windows only |
| **Path Blibkplus.dll via ctypes** | KL520 KDP1 → KDP2「載入到 RAM」升級不寫 flash | 直接 ctypes 載 `libkplus.dll`、呼叫 C API | ✅ 三平台都能用(換成 .so / .dylib |
**我們選 Path B**——理由:
- Path A 的 DFUT.exe 是 Windows-only Qt5 binary30+ DLLs、bin/ 目錄 ~40MB、無 macOS / Linux 版本、無法跨平台
- Path B 用的 `libkplus` 三平台都有、我們既有 wheel 已含、額外成本 ~0KB
- Path B 的 API`kp_update_kdp_firmware_from_files`)也能寫 flash、不只 load to RAM
### 1.2 偵測 FW 版本
scan 時從 KneronPLUS SDK 拿到的 `device_descriptor.firmware` 字串、可能值:
| firmware 字串 | 意義 |
|--------------|------|
| `KDP` | 舊版 KDP1legacy、需升級 |
| `KDP2 Comp/U` | KDP2、在 USB Boot 模式(剛 load 進 RAM |
| `KDP2 Comp/F` | KDP2、在 Flash 模式(已永久燒入 flash |
| `Loader` | USB Boot Loader 模式(等待 load firmware |
| `Unknown` / 空字串 | 偵測失敗 |
**判斷邏輯**(從 `legacy_plus121_runner.py` L228
```python
if detected_firmware == "KDP":
# 走 KDP1 → KDP2 升級路徑loader → load_firmware
elif detected_firmware == "Loader":
# 在 USB Boot loader、直接 load_firmware
else:
# 已是 KDP2 或其他、不需要升級
```
**比對 product_id 也能輔助判斷**visionA-local bridge.py L657 已有此表):
| product_id | 意義 |
|-----------|------|
| `0x0100` | KL520 |
| `0x0200` | KL720 KDP legacy舊韌體 |
| `0x0530` | KL530 |
| `0x0630` | KL630 |
| `0x0720` | KL720 KDP2 |
| `0x0730` | KL730 |
→ KL720 `0x0200 vs 0x0720` 直接區分新舊版本(不用看 firmware 字串)。
### 1.3 KDP1 → KDP2 升級流程(最核心)
`legacy_plus121_runner.py` L185-292 整理:
```
Step 1: kp_connect_devices_with_magic_pass(port_id, MAGIC=536173391)
→ 用 magic number 繞過 SDK 的 firmware version check
→ 拿到 device_group handle
Step 2: kp_set_timeout(device_group, 5000ms)
→ 給後續 API 設超時
Step 3: 偵測當前 firmware
if firmware == "KDP":
# KDP1 → 必須先切到 USB Boot loader
Step 3a: kp_update_kdp_firmware_from_files(
device_group,
loader_path, # fw_loader.bin
NULL, # 不寫 NCPU
auto_reboot=True
)
→ 寫 loader 到 flash、自動重啟
Step 3b: kp_set_timeout(再設一次)
Step 3c: kp_load_firmware_from_file(
device_group,
scpu_path, # fw_scpu.bin
ncpu_path # fw_ncpu.bin
)
→ 載入 KDP2 firmware 到 RAM不寫 flash
else:
# 不是 KDP1、直接 load_firmware_from_file
Step 3': kp_load_firmware_from_file(scpu_path, ncpu_path)
Step 4: kp_disconnect_devices(device_group)
→ 注意:升級後 disconnect 經常會回非零 codeUSB re-enumerate 中),但不算錯誤
```
**注意 1**:這條路徑的「升級」實際上是「載到 RAM」、不是「寫 flash」。對 KL520 USB Boot 裝置而言、每次斷電都要再 load 一次。這對我們**夠用**——KL520 反正每次 connect 都會 load_firmwarevisionA-local bridge.py L876 已實作)、加 legacy KDP → KDP2 升級邏輯只是擴充當前的 connect 流程。
**注意 2**:真正寫 flash 的是 `kp_update_kdp_firmware_from_files`、會留在 device 重啟仍存在。這個 API 是 warrenchen「legacy-upgrade」endpoint 用 DFUT.exe 跑的路徑Path A對應的 C API、跨平台可用。
### 1.4 警告與限制warrenchen 已踩過的坑)
`STRATEGY.md` 「Legacy Firmware Story」段L521-545整理
1. **`KP_ERROR_INVALID_FIRMWARE_24`**:很多出貨的 dongle 還在舊 KDP firmware、即使 `connect_devices_without_check` 也會回此錯誤。**解法**:用 `kp_connect_devices_with_magic_pass`MAGIC=536173391繞過 version check。
2. **DFUT 對某些舊裝置 timeout**warrenchen 加了一條後備邏輯main.py L1039-1052——如果 DFUT timeout 但 rescan 看到裝置已切到 KDP 狀態、就視為成功。
3. **disconnect 失敗不一定是錯**FW load 後 USB 重 enumerate、原 device handle 失效、disconnect 回非零是預期行為。
4. **timeout 設定**:升級 KL520 用 30 秒、升級 KL720 用 180 秒(後者要寫 flash、慢很多
5. **驅動程式問題**Windows升級前必須確認 WinUSB driver 已綁定、否則 `kp_connect_devices` 直接失敗。warrenchen 用 `kp.core.install_driver_for_windows` 自動處理、我們有自己的 `kneron_winusb.inf` + driver 安裝邏輯M1+ TODO
---
## 2. warrenchen 的 firmware 二進位內容
| 裝置 | 路徑 | 檔案 | 用途 |
|------|------|------|------|
| KL520KDP2 主版) | `firmware/KL520/` | `fw_scpu.bin` / `fw_ncpu.bin` | 升級到 KDP2 / 連線時 load to RAM |
| KL520KDP2 主版) | `firmware/KL520/` | `fw_loader.bin` | USB Boot Loader升降版必要 |
| KL520KDP2 主版) | `firmware/KL520/dfw/` | `minions.bin` | DFUT 用的中介 firmware不確定我們是否需要 |
| KL520KDP1 降版) | `firmware/KL520_kdp/` | `fw_scpu.bin` / `fw_ncpu.bin` | 降版回 KDP1 用(測試用) |
| KL720 | `firmware/KL720/` | `fw_scpu.bin` / `fw_ncpu.bin` | 升級到 KDP2 / 連線時 load to RAM |
| KL630 | `firmware/KL630/` | `kp_firmware.tar` / `kp_loader.tar` | 整套打包格式(新 SDK 用) |
| KL730 | `firmware/KL730/` | `kp_firmware.tar` / `kp_loader.tar` | 整套打包格式 |
**比對 visionA-local 既有**`server/scripts/firmware/`
| 裝置 | 我們有 | warrenchen 多了什麼 |
|------|--------|-------------------|
| KL520 | ✅ fw_scpu.bin + fw_ncpu.bin | ❌ fw_loader.bin升降版必要、要拿、minions.bin可能要、KL520_kdp 整組(降版要) |
| KL720 | ✅ fw_scpu.bin + fw_ncpu.bin | 無新增 |
| KL630 / KL730 | ❌ | ✅ 全套(但我們延後做)|
**從 VERSION 檔看版本**
- KL520 / KL720 都是 firmware v2.2.0
- KL630 是 SDK-v2.5.7(命名規則不同)
### 大小估算(合計)
| 裝置 | 檔案 | 估計大小 |
|------|------|---------|
| KL520KDP2 主版)| fw_scpu + fw_ncpu | ~92KB既有 TDD L3219 標 52+40KB |
| KL520 | + fw_loader.bin | + ~10KB估計 |
| KL520_kdpKDP1 | fw_scpu + fw_ncpu | ~80KBKDP1 較小) |
| KL720 | fw_scpu + fw_ncpu | ~150KB估計、KDP2 較複雜) |
| KL630 | kp_firmware.tar + kp_loader.tar | ~2-3MB壓縮包 |
| KL730 | kp_firmware.tar + kp_loader.tar | ~3-4MB壓縮包 |
→ MVPKL520 + KL720+ 補 fw_loader.bin + KL520_kdp 合計 < 300KB可忽略
完整版加 KL630/KL730 +5-7MB
**注意**warrenchen `firmware/` 跟程式碼一起打包進 installer payloadSTRATEGY.md L513-518)、跟我們做法一致我們已經把 firmware git`server/scripts/firmware/`)、新增的 firmwarefw_loader.binKL520_kdp/也應該 commit 進來不走 vendor-sync
---
## 3. warrenchen 暴露的 FW APIHTTP REST
STRATEGY.md + main.py 整理。**我們不會照搬全部只取其中 3 endpoint MVP**
### 3.1 對我們有用的(要做)
| Endpoint | Method | Body | 我們的對應 |
|----------|--------|------|----------|
| `/devices` | GET | | 我們已有 `/api/devices`回傳已含 `firmwareVersion` 欄位 |
| `/firmware/legacy-plus121/load` | POST | `{port_id, loader_path, scpu_path, ncpu_path}` | **建議新增 `/api/devices/:id/firmware/upgrade`**自動推導 path不暴露給前端 |
### 3.2 不需要照搬的(雲端版才需要)
| Endpoint | 為什麼不要 |
|----------|----------|
| `/firmware/legacy-upgrade/kl520` (DFUT.exe) | DFUT Windows only跨平台不能用 |
| `/firmware/legacy-downgrade/kl520` (DFUT.exe) | 同上且降版主要是內部測試用MVP 不做 |
| `/firmware/legacy-upgrade/kl720` (DFUT.exe) | 同上 |
| `/firmware/load`暴露 scpu_path/ncpu_path| 沒必要讓前端決定 firmware 路徑後端內部處理 |
| `/devices/connect_force` | 我們 bridge.py `handle_connect()` 已有 `connect_devices_without_check` 自動 fallback不暴露給 API |
| `/driver/check` / `/driver/install` / `/driver/ensure` | 我們已有 `ensureDriverInstalled()`M1+ TODO)、不暴露 |
---
## 4. 我們**可以直接重用**的 warrenchen 邏輯
| 來源 | 邏輯 | 我們怎麼用 |
|------|------|----------|
| `legacy_plus121_runner.py:_load_libkplus` | ctypes 直接載 libkplus argtypes/restype 宣告 | **不重用 ctypes 寫法**——我們用 Python kp module`kp.core.update_kdp_firmware_from_files`即可 ctypes 乾淨 |
| `legacy_plus121_runner.py:_connect_with_magic` | MAGIC firmware check | **重用概念、用 Python API**——`kp.core.connect_devices_with_magic_pass(port_ids, magic=536173391)` |
| `legacy_plus121_runner.py:main()` 的升級流程 | KDP loader load_firmware 兩階段 | **直接重用流程**翻譯成 visionA-local bridge.py handle_firmware_upgrade |
| `legacy_plus121_runner.py:_reboot_and_reconnect` | reset sleep retry connect | **重用**visionA-local 已有類似的 restartBridge |
| `main.py:_kl520_kdp_observed_after_timeout` | timeout rescan 確認狀態 | **重用**升級不確定有沒有真的成功時rescan 來驗證 |
| `main.py:_run_dfut_command` timeout / retry / streaming output 處理 | 子進程通訊管理 | **不重用**我們不用 DFUT|
---
## 5. 我們**不該照搬**的(雲端版才需要的)
README + STRATEGY 可見 warrenchen 雲端版的特殊需求
1. **跨進程 RPC**雲端版有網頁 雲端 本地服務 RPC 需要自訂 URL scheme + 引導下載安裝代理我們**單進程 Wails**、不需要這套
2. **DFUT.exe 整包打包**因為要支援所有舊裝置 / Windows 維運場景我們**只走 KneronPLUS Python API**、跨平台不打包 DFUT
3. **FastAPI / Uvicorn**他們選 Python FastAPI 寫本地服務我們是 Go server我們不重用 service reverse-engineer FW 升級邏輯
4. **`/tools/video-inference` 視覺驗證頁**他們用 HTML 內嵌 page PoC 視覺化我們的 Next.js 推論頁已完整不需要
5. **`/firmware/load` 直接暴露 path 給前端**安全 / 易用考量我們不暴露後端自己解析
---
## 6. 對接 visionA-local 既有 bridge.py 的具體改動建議(純 plan
`server/scripts/kneron_bridge.py` 新增一個 handler
```
def handle_firmware_upgrade(params):
"""
升級 KL520 / KL720 dongle 到內建 KDP2 firmware跨晶片自動偵測
參數:
port: 裝置 USB port id必填
chip: "KL520" or "KL720"(必填)
回傳(成功):
{
"status": "upgraded",
"before_firmware": "KDP",
"after_firmware": "KDP2",
"method": "kp_update_kdp_firmware_from_files",
"duration_ms": 28500
}
回傳(失敗):
{"error": "...", "stage": "connect" | "loader" | "load_firmware" | "verify"}
"""
# 1. scan + 找到 target_dev by port_id
# 2. kp.core.connect_devices_with_magic_pass(port_ids, magic=536173391)
# 3. kp.core.set_timeout(60000)
# 4. 偵測 detected_firmware
# 5. if detected_firmware == "KDP":
# a. kp.core.update_kdp_firmware_from_files(dg, loader_path, None, auto_reboot=True)
# b. wait_for_reenumerate(8s)
# c. reconnect (with magic if needed)
# d. kp.core.load_firmware_from_file(scpu, ncpu) # 已是 visionA-local bridge 既有邏輯
# else:
# a. kp.core.load_firmware_from_file(scpu, ncpu) # 直接 load 即可
# 6. 重新 scan、驗證 firmware 已變
# 7. disconnect容忍非零回傳
# 8. 回傳結果
```
**注意**bridge.py main loop 已是 JSON-RPC 模式L1159-1182)、加新 cmd 只需在 switch 表加 `elif action == "firmware_upgrade": result = handle_firmware_upgrade(cmd)`改動量極小
---
## 7. 結論
warrenchen 的雲端版**核心 FW 升級邏輯Path B100% 可移植**到我們 local
- 不需要打包 DFUT.exe跨平台 win
- 不需要 ctypes KneronPLUS Python API 即可
- 既有 bridge.py + firmware bundle 70% 就緒只差升級 handler + 1 endpoint + 1 UI 卡片
- 風險低工時可估MVP 5 人天
下一份檔`20-our-current-state.md`盤點我們**確切**有什麼缺什麼