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>
24 KiB
手動降版面向一般使用者:流程設計與 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
- 使用者決策(2026-05-24)翻案前一份研究 §3 的「降版僅內部測試」假設——降版面向一般使用者
- 一般使用者面向後、責任分工:
- 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
- 「面向一般使用者」≠ 「無腦讓使用者隨便降」——必須有多層 safety net:
- UI 警告 + 二次確認(design 領域、本檔只標需求)
- Driver 層 guard(拒絕跨晶片誤匹配、拒絕「降版」到比 current 還新的版本)
- bridge.py 層 guard(checksum 驗證 firmware 完整性、version 字串嚴格 match)
- 暴露範圍:Settings → 「韌體管理」面板(不在 Devices 頁主流程、避免誤觸)
1. 使用情境分析
1.1 一般使用者為什麼會降版
| 情境 | 頻率 | 描述 | 處理建議 |
|---|---|---|---|
| 「我的舊 model 在新 FW 上跑不出結果」 | 中 | 內建升版到 v2.2 後、舊 NEF model(v2.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()、本檔加:
// 偽碼、不出 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
# 偽碼
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
# 偽碼
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 API(M9-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) 內必做:
- 不能跨晶片:呼叫前驗
version在ListFirmwareVersions()結果內、不接受任意字串 - 不能升版偽裝:比較
version與GetCurrentFirmwareVersion()結果、若目標版本 >= current → 拒絕(要走升版 API) - 不能 no-op:若
version == current→ 拒絕(節省時間 + 避免 device 不必要 reset) - status guard:device 必須是
StatusDetected或StatusConnected、不能在StatusInferencing/StatusFlashing/StatusUpgrading期間降版(會卡 mutex)
// 偽碼
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/ ← 預設 firmware(A 階段位置 = 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/ 是什麼
選項 A:symbolic 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 不一定提供舊版 firmware、KL630/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 UI(progress 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 ~30s、KL720 ~180s、KL630/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 guard:version 必須在 ListFirmwareVersions(chip) 內 |
已消除 |
6.2 殘餘風險最大的:使用者不讀警告就降版
對策:
- 二次確認字串「DOWNGRADE」(不是 「OK」/「Yes」、強制使用者打字)
- 視覺破壞性 button color(紅色)
- 「進行中」期間 banner persist(不可關)
但無法完全消除——一般使用者場景下、總有人會無視警告。
6.3 Plan B:給技術支援用的救磚 SOP
如果使用者真的把 dongle 弄 brick、需要:
- DFUT.exe(Windows-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 含舊版 firmware(v2.1.0、KDP1)→ 法律問題加重?
- 暴露給一般使用者降版 → Kneron 是否允許?(KDP1 是被棄用版本、Kneron 不一定希望使用者降回去)
建議:
- B 階段啟動前、與 Kneron 取得 firmware re-distribution 明確書面授權
- 授權內容必須包含:current + 舊版(v2.1.0 等)+ KDP1
- 如果 Kneron 不允許降版到 KDP1、調整 bundle 範圍
8. 給 Orchestrator 的決策點清單
- 使用者已決策手動降版面向一般使用者 → 本檔 §5 design 需求成立
- 使用者已決策 FW 內嵌進安裝包、+5MB 接受 → 本檔 §4.3「保守」策略對齊
- 使用者已決策不做線上更新通道 → 不實作 OTA、所有 firmware bundle
- 待使用者裁決:
- 多版本目錄結構選 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 工時表內、本檔不重複加總)