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

552 lines
24 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.

# 手動降版面向一般使用者:流程設計與 Design 需求清單
> 對應 research index §42
> 範圍B2 階段——把 FW 降版功能暴露給一般使用者(不只 dev mode / 內部測試)、所需 driver / bridge / API endpoint 細節 + 給 Design Agent 的 UX 補強需求
> 撰寫日期2026-05-24
> 限制:架構面、不出 code、UX 細節留給 Design Agent
> 路徑使用相對路徑(相對於 `/Users/jimchen/visionA/local-tool/`
---
## 0. TL;DR
1. **使用者決策2026-05-24翻案前一份研究 §3 的「降版僅內部測試」假設**——降版面向一般使用者
2. 一般使用者面向後、責任分工:
- **Architect本檔**:標記技術需求、定義 safety guards、API 簽名、儲存結構、driver 與 bridge.py 介面
- **Design Agent另派**:警告語、二次確認流程、版本 dropdown 視覺、降版進行中互動、失敗復原 UX
- **Frontend Agent**:實作 UI、串 API、i18n
- **Backend Agent**:實作 driver method、bridge.py handler、API endpoint
3. 「面向一般使用者」≠ 「無腦讓使用者隨便降」——必須有**多層 safety net**
- UI 警告 + 二次確認design 領域、本檔只標需求)
- Driver 層 guard拒絕跨晶片誤匹配、拒絕「降版」到比 current 還新的版本)
- bridge.py 層 guardchecksum 驗證 firmware 完整性、version 字串嚴格 match
4. 暴露範圍Settings → 「韌體管理」面板(不在 Devices 頁主流程、避免誤觸)
---
## 1. 使用情境分析
### 1.1 一般使用者為什麼會降版
| 情境 | 頻率 | 描述 | 處理建議 |
|------|------|------|---------|
| 「我的舊 model 在新 FW 上跑不出結果」 | 中 | 內建升版到 v2.2 後、舊 NEF modelv2.1 編的)相容性問題 | 提供降版回 v2.1 選項 |
| 「跟某個 third-party tool 不相容」 | 低 | 例如某些 model debugging tool 只認 KDP1 | 提供降版回 KDP1 選項KL520 |
| 「我升錯了、想回到原本狀態」 | 低 | 使用者誤觸升級、想 revert | 提供「降版回上次的版本」選項 |
| 「測試環境需要特定版本」 | 中 | 開發者場景(但你說也要 face 一般使用者)| 提供降版到任意 bundled 版本 |
| 「就是想試試看」 | 中 | 好奇心驅動 | 用警告語勸阻、但不阻止 |
→ 「跟一般使用者解釋降版」必須包裝成「**FW 版本切換**」、不要用「**降版**」這個帶負面含義詞——但內部技術文件仍叫 downgrade程式碼/log/Architect 文件用語)。
### 1.2 UI 觸發位置(建議給 Design Agent 評估)
| 位置 | 優點 | 缺點 | 建議 |
|------|------|------|------|
| Devices 頁主卡片 | 醒目、與 FW badge 同位置 | 太容易誤觸、新手使用者不需要 | **不建議** |
| Devices 頁 → Device Detail Modal | 進階入口、需要點開 | 仍偏 device-centric | **次佳** |
| Settings → 韌體管理 | 進階入口、與其他 settings 同位置 | 較深入路徑 | **推薦** |
| Settings → 進階advanced | 最深入口 | 但使用者面向後不應藏太深 | 折衷 |
**架構建議****Settings → 韌體管理** 是主入口、Devices 頁 device card 顯示 FW badge 但不放降版按鈕。
---
## 2. Driver / Bridge / API 詳細設計
### 2.1 driver interface 擴展
承前一份 30-integration-plan §1 M9-2 已定義的 `UpgradeFirmware()`、本檔加:
```go
// 偽碼、不出 code給 architect 補 TDD 用)
type DeviceDriver interface {
// ... 既有 methods 與 UpgradeFirmware() ...
// B2 階段新增
DowngradeFirmware(version string, progressCh chan<- FirmwareProgress) error
ListFirmwareVersions() ([]FirmwareVersion, error)
GetCurrentFirmwareVersion() (FirmwareVersion, error)
}
type FirmwareVersion struct {
Version string `json:"version"` // "v2.2.0" / "v2.1.0" / "kdp1"
DisplayName string `json:"displayName"` // "v2.2.0 (current)" / "v2.1.0 (older)"
IsCurrent bool `json:"isCurrent"`
IsBundled bool `json:"isBundled"` // 是否在安裝包內、總是 true不做線上更新
ReleaseDate string `json:"releaseDate,omitempty"` // ISO 8601、optional
Notes string `json:"notes,omitempty"` // 給使用者看的說明
}
type FirmwareProgress struct {
// ... 既有欄位 ...
Direction string `json:"direction"` // "upgrade" / "downgrade"
}
```
### 2.2 bridge.py handler 設計
#### 2.2.1 `handle_firmware_list_versions`
```python
# 偽碼
def handle_firmware_list_versions(params):
"""List bundled firmware versions for a chip.
Params:
chip: str ("KL520" | "KL720" | "KL630" | "KL730", required)
Returns:
{"versions": [{"version":"v2.2.0", "isCurrent":true, "isBundled":true, ...}, ...]}
or {"error": "..."}
"""
chip = params.get("chip", "").upper()
if chip not in ("KL520", "KL720", "KL630", "KL730"):
return {"error": f"unknown chip: {chip}"}
base = os.path.dirname(os.path.abspath(__file__))
fw_dir = os.path.join(base, "firmware", chip)
if not os.path.isdir(fw_dir):
return {"error": f"firmware dir not found for {chip}"}
# 讀 current/VERSION
current_version = _read_version_file(os.path.join(fw_dir, "current", "VERSION"))
versions = []
for entry in os.listdir(fw_dir):
entry_path = os.path.join(fw_dir, entry)
if not os.path.isdir(entry_path):
continue
if entry == "current":
continue # current 是 alias、不重複列
version_file = os.path.join(entry_path, "VERSION")
if not os.path.exists(version_file):
continue
v = _read_version_file(version_file)
versions.append({
"version": v,
"displayName": _format_display_name(v, current_version),
"isCurrent": v == current_version,
"isBundled": True,
"directory": entry, # 給 bridge.py 內部用、不外露給 API
})
return {"versions": versions, "current": current_version}
```
#### 2.2.2 `handle_firmware_downgrade`
```python
# 偽碼
def handle_firmware_downgrade(params):
"""Downgrade firmware to a specific bundled version.
Params:
port: int (USB port id, required)
chip: str ("KL520" | "KL720" | "KL630" | "KL730", required)
version: str (target version, e.g. "v2.1.0" / "kdp1", required)
Returns:
{"status":"downgraded", "before_version":"v2.2.0", "after_version":"v2.1.0",
"duration_ms":31000, "stage":"done"}
or {"error":"...", "stage":"validate|connect|download|verify"}
"""
chip = params.get("chip", "")
target_version = params.get("version", "")
target_port = params.get("port", "")
# Stage 1: validate
# - chip 必須是支援的
# - version 必須在 bundled list 內
# - target_version 不能等於 current那是 no-op
# - target_version 不能比 current 還新(那是 upgrade、走另一支
if not _validate_downgrade_request(chip, target_version):
return {"error": "invalid downgrade request", "stage": "validate"}
# Stage 2: 解析 firmware paths
fw_paths = _resolve_firmware_paths_versioned(chip, target_version)
if fw_paths is None:
return {"error": f"firmware not found: {chip}/{target_version}", "stage": "validate"}
# Stage 3: connect
# 跟 firmware_upgrade 一樣的 connect_with_magic 流程
# ...
# Stage 4: 跑 SDK API
# 視 chip:
# - KL520 KDP2 → KDP1: kp.core.update_kdp_firmware_from_files(loader, ..., auto_reboot=True) + load_firmware
# - KL520 v2.2 → v2.1: 同上但用不同 .bin
# - KL720: 同上KL720 是 flash-based、要寫 flash
# - KL630/KL730: 用 update_*_from_tar APIM9-6 驗證後填)
# ...
# Stage 5: verify
# disconnect → sleep 3s → rescan → 看 firmware 字串 / kn_number 是否符合預期
# ...
return {
"status": "downgraded",
"before_version": before_version,
"after_version": after_version,
"duration_ms": duration,
"stage": "done",
}
```
### 2.3 API endpoint 設計
| Endpoint | Method | Request Body | Response |
|----------|--------|-------------|----------|
| `GET /api/devices/:id/firmware/versions` | GET | — | `{success:true, data:{versions:[...], current:"v2.2.0"}}` |
| `POST /api/devices/:id/firmware/downgrade` | POST | `{version:"v2.1.0", confirmToken:"DOWNGRADE"}` | `202 {success:true, data:{taskId:"..."}}` |
| WebSocket room `firmware:<deviceId>` | — | — | progress events同 upgrade 流) |
**`confirmToken` 設計**
- API 層要求 body 含 `confirmToken: "DOWNGRADE"`(字面字串)
- 沒帶 / 帶錯 → API 直接 400
- **目的**:防止 CSRF、防止前端 bug 誤觸發、強制 UI 二次確認流程
- Frontend 必須先讓使用者輸入字面字串 / 點兩次按鈕、才把 token 加進 request
### 2.4 driver 層 safety guards
`KneronDriver.DowngradeFirmware(version)` 內必做:
1. **不能跨晶片**:呼叫前驗 `version``ListFirmwareVersions()` 結果內、不接受任意字串
2. **不能升版偽裝**:比較 `version``GetCurrentFirmwareVersion()` 結果、若目標版本 >= current → 拒絕(要走升版 API
3. **不能 no-op**:若 `version == current` → 拒絕(節省時間 + 避免 device 不必要 reset
4. **status guard**device 必須是 `StatusDetected``StatusConnected`、不能在 `StatusInferencing` / `StatusFlashing` / `StatusUpgrading` 期間降版(會卡 mutex
```go
// 偽碼
func (d *KneronDriver) DowngradeFirmware(version string, progressCh chan<- driver.FirmwareProgress) error {
d.mu.Lock()
if d.info.Status == driver.StatusInferencing || d.info.Status == driver.StatusFlashing {
d.mu.Unlock()
return fmt.Errorf("device busy: %s", d.info.Status)
}
chip := d.chipType
current := d.info.FirmwareVer
d.mu.Unlock()
// Validate version exists
versions, err := d.ListFirmwareVersions()
if err != nil {
return err
}
var targetVer *driver.FirmwareVersion
for _, v := range versions {
if v.Version == version {
targetVer = &v
break
}
}
if targetVer == nil {
return fmt.Errorf("version %s not found in bundled firmware for %s", version, chip)
}
// Validate it's actually a downgrade順序比較邏輯 chip-specific
if !isOlderVersion(version, current, chip) {
return fmt.Errorf("target version %s is not older than current %s", version, current)
}
// 進實際降版流程
// ... 跟 UpgradeFirmware 類似、call bridge.py firmware_downgrade ...
}
```
---
## 3. 多版本 firmware 儲存結構B2 細化)
### 3.1 完整目錄結構
```
server/scripts/firmware/
├── KL520/
│ ├── current/ ← 預設 firmwareA 階段位置 = MVP 既有)
│ │ ├── fw_scpu.bin ← KDP2 v2.2.0
│ │ ├── fw_ncpu.bin
│ │ ├── fw_loader.bin ← KDP1→KDP2 升級用A 階段補進來)
│ │ └── VERSION ← "v2.2.0"
│ ├── v2.2.0/ ← 跟 current 同內容、用於版本切換 reference
│ │ ├── fw_scpu.bin
│ │ ├── fw_ncpu.bin
│ │ ├── fw_loader.bin
│ │ └── VERSION
│ ├── v2.1.0/ ← 舊版本(如果決定 bundle
│ │ ├── fw_scpu.bin
│ │ ├── fw_ncpu.bin
│ │ ├── fw_loader.bin
│ │ └── VERSION
│ └── kdp1/ ← KDP1 降版
│ ├── fw_scpu.bin
│ ├── fw_ncpu.bin
│ └── VERSION ← "KDP1"(特殊標記、不是 semver
├── KL720/
│ ├── current/ ← v2.2.0
│ ├── v2.2.0/
│ └── v2.1.0/ ← 視是否 bundle
├── KL630/
│ ├── current/ ← SDK-v2.5.7
│ │ ├── kp_firmware.tar
│ │ ├── kp_loader.tar
│ │ ├── extracted/ ← build time 解壓(如選策略 Y
│ │ │ ├── fw_scpu.bin
│ │ │ └── fw_ncpu.bin
│ │ └── VERSION ← "SDK-v2.5.7"
│ └── SDK-v2.4.0/ ← 視是否 bundle 舊 SDK
│ └── ...
└── KL730/
├── current/ ← SDK-v1.3.0
│ ├── kp_firmware.tar
│ ├── kp_loader.tar
│ ├── extracted/
│ └── VERSION
└── ...
```
### 3.2 `current/` 是什麼
**選項 Asymbolic link**`current/` 是 symlink 指向 `v2.2.0/`
- 優點:節省空間(不重複 binary
- 缺點Windows symbolic link 需要 admin 權限、`tar`/zip 壓縮對 symlink 處理各 OS 不同
- **不推薦**(跨平台 symlink 太脆弱)
**選項 B實體副本**`current/``v2.2.0/` 的 file copy
- 優點:跨平台、簡單
- 缺點:每個 chip 多佔 1 份KL520 ~100KB、KL720 ~250KB、KL630/KL730 ~3-4MB、合計 ~7-8MB 額外
- **推薦**
**選項 C取消 `current/`、用 metadata 記錄當前版本**
- 結構簡化:`firmware/<chip>/{v2.2.0,v2.1.0,kdp1}/` + `firmware/<chip>/CURRENT_VERSION`(單行檔 = "v2.2.0"
- 優點節省空間、structure 清晰
- 缺點bridge.py 多一次 file read 才知道用哪個版本trivial 成本)
- **可選**(架構乾淨度 vs 簡單度的 trade-off
**建議選 C**——架構最乾淨、空間最省、跨平台無 symlink 風險。
### 3.3 採選項 C 的具體結構
```
server/scripts/firmware/
├── KL520/
│ ├── CURRENT_VERSION ← 單行檔:"v2.2.0"
│ ├── v2.2.0/
│ │ ├── fw_scpu.bin
│ │ ├── fw_ncpu.bin
│ │ ├── fw_loader.bin
│ │ └── VERSION ← "v2.2.0"
│ ├── v2.1.0/
│ │ └── ...
│ └── kdp1/
│ └── ...
├── KL720/
│ └── ...同結構...
├── KL630/
│ └── ...同結構(.tar / extracted...
└── KL730/
└── ...
```
`_resolve_firmware_paths_versioned(chip, version=None)`
- `version=None` → 讀 `CURRENT_VERSION` 拿當前版本 → 找 `<chip>/<version>/`
- `version="v2.1.0"` → 直接找 `<chip>/v2.1.0/`
**A 階段MVP相容性**A 階段 `_resolve_firmware_paths(chip)``<chip>/fw_scpu.bin`、B2 階段重整目錄時、要把 A 階段檔案搬進 `<chip>/v2.2.0/` 並加 `CURRENT_VERSION`。**A 階段的 caller 必須升級成 `_resolve_firmware_paths_versioned`**B2 migration step
---
## 4. Bundle 多版本對安裝包大小的精確估算
### 4.1 預估 bundle 策略
| Chip | current | 額外 bundled | 合計(每 chip|
|------|---------|------------|--------------|
| KL520 | v2.2.0 (~100KB) | v2.1.0 (~100KB) + kdp1 (~90KB) | ~290KB |
| KL720 | v2.2.0 (~250KB) | v2.1.0 (~250KB)(如果 bundle | ~500KB |
| KL630 | SDK-v2.5.7 (~3MB) | SDK-v2.4.0 (~3MB)(如果 bundle+ extracted (~3MB) | ~9MB |
| KL730 | SDK-v1.3.0 (~4MB) | + extracted (~4MB) | ~8MB |
**合計 B2 階段所有 bundled firmware**~18MB多版本全部 bundle
### 4.2 與既有 dmg 163MB 比
| 階段 | 累計 | 衝擊 |
|------|------|------|
| 既有 | 163MB | baseline |
| A 階段MVP| 163MB + 10KB | <0.01% |
| B 階段單版本 KL630/KL730 + 降版用 KL520_kdp| 163MB + 6-8MB 170MB | +4-5% |
| B2 階段 chip bundle 1 舊版 | 163MB + 14-18MB 178-181MB | +9-11% |
**使用者「+5MB 接受」對 B 階段成立、但 B2 多版本超出**
### 4.3 取捨建議
| 策略 | 大小 | 取捨 |
|------|------|------|
| **保守** chip bundle current + 1 個降版選項KL520 v2.1.0其他不 bundle 舊版 | +7MB | 滿足使用者「+5MB 接受邊緣降版選擇變少 |
| **完整** chip current + 2 個舊版 | +18MB | 超出 +5MB 估算但選擇豐富 |
| **極簡** bundle current + KDP1 降版KL520 only | +5MB | 嚴格 +5MB其他 chip 沒降版選擇 |
**建議選保守策略**——KL520 提供kdp1」「v2.1.0兩個降版KL720/KL630/KL730 提供 current + 1 個舊版如有可用)。
**重要前提** SDK release 不一定提供舊版 firmwareKL630/KL730 舊版可能取不到M9-6 待驗證)。
---
## 5. Design Agent 需求清單(給 Orchestrator 派 Design 用)
> **注意**:以下是 architect 標記的「**功能性需求**」、Design Agent 負責設計具體 wireframe / 視覺 / 文案。Architect 不畫設計稿、不寫文案。
### 5.1 Settings → 韌體管理面板(新頁面)
**功能需求**
- F1. 列出所有偵測到的 dongle每張 dongle 一張卡片
- F2. 卡片顯示dongle 名稱"KL520 #1")、kn_number當前 FW 版本bundled current 版本讓使用者看出是否最新
- F3. 卡片內 actions
- 升級到最新按鈕如果 current bundled 不同
- 切換 FW 版本按鈕永遠顯示暴露多版本選擇
- F4. 切換 FW 版本展開後版本 dropdown + 詳細說明每個版本一段話+ 警告語 + 降版按鈕
- F5. 二次確認 modal §5.3
- F6. 降版進行中 progress UIprogress bar + 階段提示 + 不可中斷警告
- F7. 降版完成後 toast 通知 + 自動 rescan + 卡片更新到新版本
### 5.2 Devices 頁面 FW badge補強既有 A 階段)
**功能需求**
- 既有// badge 顯示 FW 健康度
- 新增badge 旁加「⚙icon點擊 deep-link Settings 韌體管理對應卡片
- 不在 Devices 頁放降版按鈕避免誤觸
### 5.3 二次確認 modal最關鍵的 design 細節)
**功能需求****design 要全部達成**
- D1. 警告語必須包含
- 降版可能導致現有 model 無法運作
- 降版過程不可中斷否則裝置可能損壞
- 降版完成後可能需要重新插拔裝置
- 請確認版本相容性降版至 KDP1 的舊版會限制可用功能
- D2. **使用者必須輸入字面字串「DOWNGRADE」確認**防誤觸
- 不接受其他大小寫
- 輸入欄為空 / 不對時確認按鈕 disabled
- D3. 顯示 before/after 版本比對表當前 vs 目標
- D4. 顯示預估時間KL520 ~30sKL720 ~180sKL630/KL730 估計 ~60s
- D5. modal 不可被點外部關閉必須點取消明確關閉
- D6. 確認降版按鈕視覺要 destructive紅色 / 警告色
- D7. 確認後 modal 不關閉直接切到進行中狀態避免使用者誤以為什麼都沒發生
### 5.4 「進行中」UI
**功能需求**
- E1. progress bar雖然底層是 stage-based給使用者看百分比更直觀
- E2. 階段文字連線中 / 載入引導程式 / 寫入韌體 / 驗證 / 完成
- E3. **不顯示「取消」按鈕**降版不可中斷有按鈕會誘惑使用者點
- E4. 顯示請勿拔除裝置persistent banner不可關閉
- E5. 如果失敗顯示 friendly error 訊息 + 重新插拔裝置後重試指引
### 5.5 失敗復原 UX
**功能需求**
- R1. 區分失敗類型
- 升級時 device disconnect」→ 訊息:「裝置已斷開請重新插入後重試
- Firmware 損毀 / 寫入失敗」→ 訊息:「韌體寫入失敗請聯絡客服」(罕見可能 brick
- Timeout」→ 訊息:「升級時間過長可能成功也可能未完成請拔插裝置再 scan
- R2. 失敗後不自動關閉 modal給使用者讀完訊息再關
- R3. 提供重試按鈕如果是可重試的 error
- R4. 提供複製錯誤訊息按鈕給技術支援用
### 5.6 i18n keys給 frontend 估算)
預估 新增中英雙語 keys
- Settings 韌體管理頁標題說明~3
- 卡片內各欄位按鈕~8
- 二次確認 modal~12
- 進行中 UI~8
- 失敗訊息~10
- toast / banner~5
- **合計 ~46 個新 i18n keys**
---
## 6. 一般使用者誤觸降版 brick 風險評估
### 6.1 風險矩陣
| 誤操作 | 機率 | 嚴重性 | 已緩解 | 殘餘風險 |
|--------|------|-------|--------|---------|
| 點錯按鈕沒看清楚就降版| | | 二次確認 + 輸入DOWNGRADE字串 | |
| 降版到不相容版本KL520 KDP2 KDP1且使用者依賴 KDP2-only 功能| | | 警告語明示 KDP1 限制 | 使用者沒讀完警告|
| 降版中拔 USB | | 可能 brick| persistent banner + 不可關 modal | |
| 降版中關 app | | | server graceful shutdown 機制既有| |
| 升級當降版誤把新版降到舊版反向| | | driver guard不允許目標版本 >= current | 已消除 |
| 跨晶片誤匹配 | 極低 | 高 | driver guardversion 必須在 `ListFirmwareVersions(chip)` 內 | 已消除 |
### 6.2 殘餘風險最大的:使用者不讀警告就降版
**對策**
- 二次確認字串「DOWNGRADE」不是 「OK」/「Yes」、強制使用者打字
- 視覺破壞性 button color紅色
- 「進行中」期間 banner persist不可關
但**無法完全消除**——一般使用者場景下、總有人會無視警告。
### 6.3 Plan B給技術支援用的救磚 SOP
如果使用者真的把 dongle 弄 brick、需要
- DFUT.exeWindows-only Qt 工具、可從 Kneron 拿)
- 燒回 KDP2 standard firmware
**不打包 DFUT.exe 進 visionA-local**(因為跨平台限制 + 30MB 額外大小 + 安全性考量)、但**提供 SOP 文件**(內部 wiki / docs/troubleshooting/brick-recovery.md讓技術支援能幫忙。
---
## 7. 法律 / 合規 考量(待釐清)
承前一份 R-FW-5「打包 Kneron 官方 firmware 是否合法」。
**B2 階段新增變數**
- 多版本 bundle 含舊版 firmwarev2.1.0、KDP1→ 法律問題加重?
- 暴露給一般使用者降版 → Kneron 是否允許KDP1 是被棄用版本、Kneron 不一定希望使用者降回去)
**建議**
- B 階段啟動前、與 Kneron 取得 firmware re-distribution **明確書面授權**
- 授權內容必須包含current + 舊版v2.1.0 等)+ KDP1
- 如果 Kneron 不允許降版到 KDP1、調整 bundle 範圍
---
## 8. 給 Orchestrator 的決策點清單
1. **使用者已決策手動降版面向一般使用者** → 本檔 §5 design 需求成立
2. **使用者已決策 FW 內嵌進安裝包、+5MB 接受** → 本檔 §4.3「保守」策略對齊
3. **使用者已決策不做線上更新通道** → 不實作 OTA、所有 firmware bundle
4. **待使用者裁決**
- 多版本目錄結構選 A/B/C建議 C、§3.2
- bundle 哪些舊版建議「保守」策略、§4.3
- KneronPLUS wheel 升級 vs 不升級M9-6 驗證後決定)
- 與 Kneron 取得 firmware redistribution 授權的時程?
---
## 9. 與其他研究檔的關係
| 連結 | 引用 |
|------|------|
| `30-integration-plan.md` §6 階段 B 評估提示 | 本檔詳化 B2 階段細節 |
| `40-b-phase-kl630-kl730-extension.md` §8 多版本 firmware 並存 | 本檔 §3 詳化儲存結構 |
| `40-b-phase-kl630-kl730-extension.md` R-FW-11/R-FW-12 | 本檔 §5/§6 提供緩解措施 |
| `41-tar-firmware-handling.md` §3 大小估算 | 本檔 §4 整合計算 |
---
## 10. 工時影響補充
B 階段拆三層之後、本檔內容對應 M9-11 + M9-12 兩個 milestone
| Milestone | 本檔 § | 工時 |
|-----------|--------|------|
| M9-11 多版本後端 | §2.1 ~ §2.4 + §3 + §4 | 1.5 人天 |
| M9-12 降版 UI | §5 + §6 + §7 | Frontend 1 人天 + Design 1 人天 = 2 人天 |
| **合計** | | **3.5 人天** |
(已含在 §40 B 階段 M9-6 ~ M9-13 工時表內、本檔不重複加總)