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>
13 KiB
warrenchen 雲端版 FW 偵測 + 升降版實作分析
對應 visionA-local 研究 plan §10 來源:
/tmp/web_academy_prototype/repo(shallow 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 A:KneronDFUT.exe | KL520 升版 / 降版、KL720 升版 | 呼叫 Windows 第三方 KneronDFUT.exe CLI(subprocess) |
❌ Windows only |
| Path B:libkplus.dll via ctypes | KL520 KDP1 → KDP2「載入到 RAM」升級(不寫 flash) | 直接 ctypes 載 libkplus.dll、呼叫 C API |
✅ 三平台都能用(換成 .so / .dylib) |
我們選 Path B——理由:
- Path A 的 DFUT.exe 是 Windows-only Qt5 binary(30+ 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 |
舊版 KDP1(legacy、需升級) |
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):
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 經常會回非零 code(USB re-enumerate 中),但不算錯誤
注意 1:這條路徑的「升級」實際上是「載到 RAM」、不是「寫 flash」。對 KL520 USB Boot 裝置而言、每次斷電都要再 load 一次。這對我們夠用——KL520 反正每次 connect 都會 load_firmware(visionA-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)整理:
KP_ERROR_INVALID_FIRMWARE_24:很多出貨的 dongle 還在舊 KDP firmware、即使connect_devices_without_check也會回此錯誤。解法:用kp_connect_devices_with_magic_pass(MAGIC=536173391)繞過 version check。- DFUT 對某些舊裝置 timeout:warrenchen 加了一條後備邏輯(main.py L1039-1052)——如果 DFUT timeout 但 rescan 看到裝置已切到 KDP 狀態、就視為成功。
- disconnect 失敗不一定是錯:FW load 後 USB 重 enumerate、原 device handle 失效、disconnect 回非零是預期行為。
- timeout 設定:升級 KL520 用 30 秒、升級 KL720 用 180 秒(後者要寫 flash、慢很多)。
- 驅動程式問題(Windows):升級前必須確認 WinUSB driver 已綁定、否則
kp_connect_devices直接失敗。warrenchen 用kp.core.install_driver_for_windows自動處理、我們有自己的kneron_winusb.inf+ driver 安裝邏輯(M1+ TODO)。
2. warrenchen 的 firmware 二進位內容
| 裝置 | 路徑 | 檔案 | 用途 |
|---|---|---|---|
| KL520(KDP2 主版) | firmware/KL520/ |
fw_scpu.bin / fw_ncpu.bin |
升級到 KDP2 / 連線時 load to RAM |
| KL520(KDP2 主版) | firmware/KL520/ |
fw_loader.bin |
USB Boot Loader(升降版必要) |
| KL520(KDP2 主版) | firmware/KL520/dfw/ |
minions.bin |
DFUT 用的中介 firmware(不確定我們是否需要) |
| KL520(KDP1 降版) | 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(命名規則不同)
大小估算(合計)
| 裝置 | 檔案 | 估計大小 |
|---|---|---|
| KL520(KDP2 主版) | fw_scpu + fw_ncpu | ~92KB(既有 TDD L3219 標 52+40KB) |
| KL520 | + fw_loader.bin | + ~10KB(估計) |
| KL520_kdp(KDP1) | fw_scpu + fw_ncpu | ~80KB(KDP1 較小) |
| 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(壓縮包) |
→ MVP(KL520 + KL720)+ 補 fw_loader.bin + KL520_kdp 合計 < 300KB、可忽略。 → 完整版加 KL630/KL730 約 +5-7MB。
注意:warrenchen 把 firmware/ 跟程式碼一起打包進 installer payload(STRATEGY.md L513-518)、跟我們做法一致。我們已經把 firmware 進 git(server/scripts/firmware/)、新增的 firmware(fw_loader.bin、KL520_kdp/)也應該 commit 進來、不走 vendor-sync。
3. warrenchen 暴露的 FW API(HTTP 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 雲端版的特殊需求:
- 跨進程 RPC:雲端版有「網頁 → 雲端 → 本地服務」的 RPC 鏈、需要自訂 URL scheme + 引導下載安裝代理。我們單進程 Wails、不需要這套。
- DFUT.exe 整包打包:因為要支援所有舊裝置 / Windows 維運場景。我們只走 KneronPLUS Python API、跨平台、不打包 DFUT。
- FastAPI / Uvicorn:他們選 Python FastAPI 寫本地服務、我們是 Go server。我們不重用 service 層、只 reverse-engineer FW 升級邏輯。
/tools/video-inference視覺驗證頁:他們用 HTML 內嵌 page 做 PoC 視覺化、我們的 Next.js 推論頁已完整、不需要。/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 B)100% 可移植到我們 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)盤點我們確切有什麼、缺什麼。