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

26 KiB
Raw Blame History

Integration PlanFW 偵測 + 升級 MVP建議方案 A

對應研究 plan §30 範圍:階段 A自動升級 KDP1 → KDP2KL520 + KL720 不涵蓋階段 B手動降版、KL630/KL730— 等 A 驗證後再決定


1. Milestone 詳細拆解

整體依賴圖

M9-0 (decision)  使用者 review 此 plan → 同意 MVP 範圍
   ↓
M9-1 (bridge)    bridge.py 新增 handle_firmware_upgrade + 補 fw_loader.bin
   ↓
M9-2 (driver)    Go driver: UpgradeFirmware() method
   ↓
M9-3 (api)       API handler + WebSocket room + DeviceInfo 欄位擴充
   ↓
M9-4 (ui)        Frontend: FW badge + upgrade button + progress modal
   ↓
M9-5 (verify)    三平台實機驗證macOS / Windows / Linux

M9-0決策 + 文件先行0.5 人天)

負責Orchestrator 整合 PM + Architect + Design 三方意見、定稿 PRD / TDD / Design Spec 補丁。

任務

  1. 使用者 confirm MVP 範圍KL520 + KL720、僅自動升級、不降版
  2. PM 補 docs/autoflow/02-prd/PRD-v2.md 新章節「§N. Firmware 管理v2.2 補丁)」、明標 R5-Q9 翻案 + 範圍
  3. Architect 補 docs/autoflow/04-architecture/TDD-v2.md 新子節 v2/device-firmware.md、新 ADR adr/ADR-009-firmware-management.md
  4. Design 補 docs/autoflow/03-design/ Devices 頁面 FW 卡片 wireframe + 升級 modal flow
  5. 三方互審 → 使用者 confirm

驗收:所有共享文件三方互審通過、使用者 sign-off。


M9-1bridge.py 新增 firmware_upgrade handler1 人天)

負責Backend Agent

依賴M9-0 完成

任務

  1. 複製 firmware 檔:從 warrenchen repo 複製 local_service_win/firmware/KL520/fw_loader.binserver/scripts/firmware/KL520/fw_loader.bin、commit 進 git

    • 確認檔案大小、加進 server/scripts/firmware/KL520/VERSION 對照表(既有檔則加註)
  2. bridge.py 新增 handlerpseudo-code、實作時 architect 會給更詳細的 stub

    def handle_firmware_upgrade(params):
        """Upgrade KL520/KL720 to bundled KDP2 firmware.
    
        Params:
          port: int (USB port id, required)
          chip: str ("KL520" or "KL720", required)
    
        Returns:
          {"status":"upgraded", "before_firmware":"KDP", "after_firmware":"KDP2",
           "method":"kp_update_kdp_firmware_from_files", "duration_ms":28500}
          or {"error":"...", "stage":"connect|loader|load_firmware|verify"}
        """
        global _device_group, _firmware_loaded
    
        chip = params.get("chip", "")
        target_port = params.get("port", "")
    
        if not HAS_KP:
            return {"error": "kp module not available"}
    
        # Stage 1: scan + find target
        try:
            descs = kp.core.scan_devices()
            target_dev = None
            for i in range(descs.device_descriptor_number):
                dev = descs.device_descriptor_list[i]
                if str(dev.usb_port_id) == str(target_port):
                    target_dev = dev
                    break
            if target_dev is None:
                return {"error": f"device on port {target_port} not found", "stage": "scan"}
    
            detected_fw = str(target_dev.firmware)
            _log(f"FW upgrade: detected={detected_fw}, chip={chip}, port={target_port}")
        except Exception as e:
            return {"error": str(e), "stage": "scan"}
    
        # Stage 2: connect with magic pass
        try:
            _clear_device_group()
            _device_group = kp.core.connect_devices_with_magic_pass(
                usb_port_ids=[target_dev.usb_port_id],
                magic=536173391  # KDP_MAGIC_CONNECTION_PASS
            )
            kp.core.set_timeout(_device_group, 60000)
        except Exception as e:
            return {"error": str(e), "stage": "connect"}
    
        # Stage 3: 找 firmware paths
        scpu_path, ncpu_path = _resolve_firmware_paths(chip)
        loader_path = os.path.join(
            os.path.dirname(os.path.abspath(__file__)),
            "firmware", chip, "fw_loader.bin"
        )
        if not scpu_path or not ncpu_path:
            return {"error": f"firmware files not found for {chip}", "stage": "resolve"}
    
        # Stage 4: 升級
        try:
            start = time.time()
    
            if "KDP" in detected_fw.upper() and "KDP2" not in detected_fw.upper():
                # 舊 KDP1 → 先走 loader、再 load KDP2 to RAM
                if not os.path.exists(loader_path):
                    return {"error": f"loader not found: {loader_path}", "stage": "loader"}
    
                _log(f"Loading loader to switch to USB Boot: {loader_path}")
                kp.core.update_kdp_firmware_from_files(
                    _device_group, loader_path, None, auto_reboot=True
                )
                # auto_reboot → device re-enumerate、 disconnect 會回非零、 容忍
                time.sleep(2)
    
                # 重新 scan + connect
                _clear_device_group()
                descs = kp.core.scan_devices()
                # ... 找回 target_dev、 reconnect with magic
                # 細節在實作時補
    
                kp.core.set_timeout(_device_group, 60000)
                kp.core.load_firmware_from_file(_device_group, scpu_path, ncpu_path)
            else:
                # 已是 KDP2 或 Loader、直接 load
                kp.core.load_firmware_from_file(_device_group, scpu_path, ncpu_path)
    
            _firmware_loaded = True
            duration_ms = int((time.time() - start) * 1000)
        except Exception as e:
            return {"error": str(e), "stage": "upgrade"}
    
        # Stage 5: verify
        try:
            # disconnect、 等待 USB stable、 rescan、 確認 firmware 已變
            try:
                kp.core.disconnect_devices(_device_group)
            except Exception:
                pass  # auto_reboot 後 disconnect 失敗預期
            _device_group = None
            time.sleep(3)
    
            descs = kp.core.scan_devices()
            after_fw = "unknown"
            for i in range(descs.device_descriptor_number):
                dev = descs.device_descriptor_list[i]
                if str(dev.usb_port_id) == str(target_port):
                    after_fw = str(dev.firmware)
                    break
    
            return {
                "status": "upgraded",
                "before_firmware": detected_fw,
                "after_firmware": after_fw,
                "method": "kp_update_kdp_firmware_from_files",
                "duration_ms": duration_ms,
            }
        except Exception as e:
            return {"error": str(e), "stage": "verify"}
    
  3. main loop 加 dispatchL1166-1180

    elif action == "firmware_upgrade":
        result = handle_firmware_upgrade(cmd)
    

驗收

  • python3 server/scripts/kneron_bridge.py 手動測試(不接 device→ ready 訊號正常
  • 模擬 device-not-found 流程 → 回正確 error
  • 實機接 KL520 KDP2 dongle 跑 firmware_upgrade → 應該 short-circuitdetected_fw 不是 KDP1、走 load_firmware 路徑)
  • 實機接 KL520 KDP1如果使用者有舊 dongle→ 完整升級流程通過

M9-2Go driver UpgradeFirmware() method1 人天)

負責Backend Agent

依賴M9-1 完成

任務

  1. driver interface 擴充 server/internal/driver/interface.go

    type DeviceDriver interface {
        // ... 既有 methods ...
        UpgradeFirmware(progressCh chan<- FirmwareProgress) error  // 新
    }
    
    type FirmwareProgress struct {
        Percent int    `json:"percent"`
        Stage   string `json:"stage"`    // "connecting" | "loading_loader" | "loading_firmware" | "verifying" | "done"
        Message string `json:"message,omitempty"`
        Error   string `json:"error,omitempty"`
    }
    
    type DeviceInfo struct {
        // ... 既有 ...
        FirmwareIsLegacy   bool   `json:"firmwareIsLegacy,omitempty"`
        FirmwareCanUpgrade bool   `json:"firmwareCanUpgrade,omitempty"`
        BundledFirmwareVer string `json:"bundledFirmwareVersion,omitempty"`
    }
    
  2. KneronDriver.UpgradeFirmware() 實作(位於 kl720_driver.go、雖然檔名歷史包袱、就先共用):

    func (d *KneronDriver) UpgradeFirmware(progressCh chan<- driver.FirmwareProgress) error {
        d.mu.Lock()
        d.info.Status = driver.StatusUpgrading  // 新增 status
        chip := d.chipType
        port := d.info.Port
        d.mu.Unlock()
    
        // Disconnect existing connection first
        d.Disconnect()
    
        // Start a fresh Python bridge
        if err := d.startPython(); err != nil {
            return fmt.Errorf("start bridge: %w", err)
        }
        defer d.stopPython()
    
        progressCh <- driver.FirmwareProgress{Percent: 5, Stage: "connecting"}
    
        resp, err := d.sendCommand(map[string]interface{}{
            "cmd":  "firmware_upgrade",
            "port": port,
            "chip": chip,
        })
        if err != nil {
            return fmt.Errorf("upgrade: %w", err)
        }
    
        // Push progressbridge 一次性回傳結果、 progress 模擬)
        progressCh <- driver.FirmwareProgress{Percent: 90, Stage: "verifying"}
    
        beforeFw, _ := resp["before_firmware"].(string)
        afterFw, _ := resp["after_firmware"].(string)
        d.mu.Lock()
        d.info.FirmwareVer = afterFw
        d.info.Status = driver.StatusDetected  // 升級後需要重新 connect
        d.needsReset = true                    // 下次 connect 走完整 reset
        d.mu.Unlock()
    
        progressCh <- driver.FirmwareProgress{
            Percent: 100,
            Stage:   "done",
            Message: fmt.Sprintf("upgraded %s -> %s", beforeFw, afterFw),
        }
        return nil
    }
    
  3. 新增 driver statusStatusUpgrading DeviceStatus = "upgrading"

  4. Info() 計算衍生欄位:在 Info() 內根據 FirmwareVer 字串設定 FirmwareIsLegacy

    func (d *KneronDriver) Info() driver.DeviceInfo {
        d.mu.Lock()
        defer d.mu.Unlock()
        info := d.info
        fw := strings.ToUpper(info.FirmwareVer)
        info.FirmwareIsLegacy = strings.Contains(fw, "KDP") && !strings.Contains(fw, "KDP2")
        info.FirmwareCanUpgrade = info.FirmwareIsLegacy && bundledFirmwareExists(d.chipType)
        info.BundledFirmwareVer = readBundledFwVersion(d.chipType)  // 從 firmware/<chip>/VERSION
        return info
    }
    
  5. 新建 server/internal/firmware/service.go 仿 flash/service.go

    package firmware
    
    type Service struct {
        deviceMgr *device.Manager
        tracker   *ProgressTracker
    }
    
    func (s *Service) StartUpgrade(deviceID string) (string, <-chan driver.FirmwareProgress, error) {
        session, _ := s.deviceMgr.GetDevice(deviceID)
        // ... 仿 flash/service.go 的 goroutine + progressCh pattern ...
        go func() {
            err := session.Driver.UpgradeFirmware(task.ProgressCh)
            if err != nil {
                task.ProgressCh <- driver.FirmwareProgress{Percent: -1, Stage: "error", Error: err.Error()}
            }
            close(task.ProgressCh)
        }()
        return taskID, task.ProgressCh, nil
    }
    

驗收

  • go build ./... PASS
  • go test ./server/internal/firmware/... 有單元測試mock driver
  • 與 M9-1 整合測試(手動接 device 跑完整 flow

M9-3API handler + WebSocket + DeviceInfo 擴充0.5 人天)

負責Backend Agent

依賴M9-2 完成

任務

  1. 新增 endpoint server/internal/api/handlers/device_handler.go

    func (h *DeviceHandler) UpgradeFirmware(c *gin.Context) {
        id := c.Param("id")
        taskID, progressCh, err := h.firmwareSvc.StartUpgrade(id)
        if err != nil {
            c.JSON(400, gin.H{
                "success": false,
                "error":   gin.H{"code": "FW_UPGRADE_FAILED", "message": err.Error()},
            })
            return
        }
    
        go func() {
            room := "firmware:" + id
            for progress := range progressCh {
                h.wsHub.BroadcastToRoom(room, progress)
            }
            h.firmwareSvc.CleanupTask(taskID)
        }()
    
        c.JSON(202, gin.H{"success": true, "data": gin.H{"taskId": taskID}})
    }
    
  2. route 註冊router.go

    devices.POST("/:id/firmware/upgrade", h.UpgradeFirmware)
    
  3. WebSocket room subscribe 已有 pattern:客戶端 subscribe firmware:<deviceId>、跟既有 flash:<deviceId> 一致機制

  4. DeviceInfo 衍生欄位透過 Info() 自動回傳M9-2 已做)

驗收

  • curl -X POST localhost:3721/api/devices/<id>/firmware/upgrade 拿到 202 + taskID
  • WebSocket 連到 firmware:<id> room 看到 progress event 流
  • GET /api/devices 回傳已含新欄位

M9-4Frontend FW badge + 升級 UI1.5 人天)

負責Frontend Agent

依賴M9-3 完成

任務

  1. DeviceCard 元件 新增 FW badge位於 frontend/src/components/devices/device-card.tsx

    • device.firmwareVersion + firmwareIsLegacy 算出 badge 顏色
      • 紅:firmwareIsLegacy = trueKDP1 needs upgrade
      • 黃:含 KDP2 但版本字串不符合內建 bundledFirmwareVersion未來功能、MVP 不細做)
      • 綠:含 KDP2、且符合 bundled 版本
  2. 升級按鈕

    • firmwareCanUpgrade = true 時顯示
    • 點擊 → 開升級 modal
  3. 升級 modal

    • 顯示警告:「升級期間請勿拔除裝置 / 預估 30-60 秒」
    • 確認按鈕 → 呼叫 POST /api/devices/:id/firmware/upgrade
    • subscribe WebSocket firmware:<id> room
    • progress bar + 階段提示(連線中…/載入 loader.../載入 firmware.../驗證.../完成)
    • 完成 → toast 通知「升級成功」+ 自動 rescan devices
    • 失敗 → 顯示 error + 提示 re-plug device
  4. store 變更 frontend/src/lib/store/devices-store.ts(推測位置):

    • firmwareUpgradeProgress statekey: deviceId、value: FirmwareProgress
    • subscribeFirmwareProgress(deviceId) action
    • 升級完成後自動 rescan()
  5. i18n 新增 keysfrontend/src/lib/i18n/{zh-TW,en}.ts

    • devices.firmware.upgrade.button — 「升級韌體」/ "Upgrade Firmware"
    • devices.firmware.upgrade.modal.title — 「韌體升級」/ "Firmware Upgrade"
    • devices.firmware.upgrade.modal.warning — 「升級期間請勿拔除裝置...」/ ...
    • devices.firmware.upgrade.stage.connecting — 「連線中...」/ "Connecting..."
    • devices.firmware.upgrade.stage.loadingLoader — 「載入引導程式...」/ "Loading bootloader..."
    • devices.firmware.upgrade.stage.loadingFirmware — 「載入韌體...」/ "Loading firmware..."
    • devices.firmware.upgrade.stage.verifying — 「驗證中...」/ "Verifying..."
    • devices.firmware.upgrade.stage.done — 「完成」/ "Done"
    • devices.firmware.upgrade.error.generic — 「升級失敗、請重新插拔裝置後再試」/ ...
    • devices.firmware.badge.legacy — 「需要升級」/ "Update Required"
    • devices.firmware.badge.outdated — 「版本較舊」/ "Outdated"
    • devices.firmware.badge.uptodate — 「最新」/ "Up to Date"

驗收

  • pnpm --dir frontend build PASS
  • 手動測試mock devicebadge 顯示正確、modal 流程順暢
  • 真機測試M9-5 整合)

M9-5三平台實機驗證1 人天)

負責Testing Agent

依賴M9-4 完成

任務

  1. macOSIntel + Rosetta

    • 接 KL520KDP1如果使用者有舊 dongle完整升級流程
    • 接 KL520KDP2目前狀態「無升級需要」UI 路徑
    • 接 KL720類似測試如果有 legacy KL720
  2. Windows

    • 同上、特別注意 WinUSB driver 綁定狀態
    • 升級期間 USB re-enumerate 不會 hang HTTP 連線
  3. LinuxUbuntu 22.04/24.04

    • 同上、特別注意 udev rules
  4. 異常路徑

    • 升級期間拔除 device → error 訊息合理、UI 可復原
    • 升級期間關 app → server graceful shutdown + 升級中斷不留壞狀態
    • 升級超時 → timeout 訊息合理、device 仍可用
  5. 回歸測試

    • 既有功能model load / inference不受影響
    • 升級完成後立刻能正常 inference

驗收

  • 三平台 smoke test 報告(每個平台至少一張 dongle 跑完整 happy path + 一個異常路徑)
  • 無回歸 bug

2. 涉及檔案清單

2.1 新建檔案

路徑 用途 預估行數
server/scripts/firmware/KL520/fw_loader.bin KL520 USB Boot Loader binary從 warrenchen 複製) ~10KB
server/scripts/firmware/KL520/VERSION bundled FW 版本紀錄(如果還沒有) ~5 行
server/scripts/firmware/KL720/VERSION 同上 ~5 行
server/internal/firmware/service.go FW 升級 service ~150
server/internal/firmware/progress.go FW progress tracker ~50
frontend/src/components/devices/firmware-badge.tsx FW badge 元件 ~80
frontend/src/components/devices/firmware-upgrade-modal.tsx 升級 modal ~200
docs/autoflow/04-architecture/v2/device-firmware.md TDD 新子節 ~300
docs/autoflow/04-architecture/adr/ADR-009-firmware-management.md ADR 紀錄 Q9 翻案 + 設計決策 ~200

2.2 修改檔案

路徑 改什麼 影響範圍
server/scripts/kneron_bridge.py +handle_firmware_upgrade() + main loop dispatch + import time +~100 行
server/internal/driver/interface.go +UpgradeFirmware method、+FirmwareProgress struct、+StatusUpgrading、+DeviceInfo 3 個新欄位 +~30 行
server/internal/driver/kneron/kl720_driver.go +UpgradeFirmware() method、+Info() 計算衍生欄位、+bundledFirmwareExists() / readBundledFwVersion() helper +~80 行
server/internal/api/handlers/device_handler.go +UpgradeFirmware handler + DI firmwareSvc +~30 行
server/internal/api/router.go +route 註冊 +1 行
server/cmd/main.go 或 server init DI 注入 firmware.Service +~5 行
frontend/src/components/devices/device-card.tsx 嵌入 FirmwareBadge + 升級按鈕 +~30 行
frontend/src/lib/store/devices-store.ts +firmwareUpgradeProgress state + subscribe action +~50 行
frontend/src/lib/i18n/zh-TW.ts +11 個新 keys +~15 行
frontend/src/lib/i18n/en.ts 同上 +~15 行
docs/autoflow/02-prd/PRD-v2.md +§N 韌體管理章節 +~80 行
docs/autoflow/04-architecture/TDD-v2.md +§2.10 子檔索引、+§3 風險清單條目 +~20 行
installer/{macos,windows,linux}/* 確認 firmware bundle 進 installer payload既有應已涵蓋 0 行(驗證)

3. 與 TDD v2.1 / Design v2.1 / PRD v2.1 受影響章節清單

PRD v2.1 受影響章節(建議補丁)

章節 改動
§2 變更摘要0.0 加一列「v2.1 → v2.2:新增 Firmware 管理」
§N 韌體管理(新章節) 主要內容、含 Q9 翻案聲明、MVP 範圍、商業背景
§11 懸念列表 + N-R5「FW 升級失敗後使用者復原流程」
變更紀錄 加一列

TDD v2.1 受影響章節(建議補丁)

章節 改動
§0.0 v2.0 → v2.1 差異速覽 加一列「Firmware 管理新增 M9 milestone series」
§2 子檔案地圖 + 一列 v2/device-firmware.md
§3 風險清單 + R-v2-8「FW 升級中拔除裝置」+ R-v2-9「升級後 USB re-enumerate race」
§0.1 v1 → v2 差異速覽 加一列「FW 管理路徑R5-Q9 翻案」
新增 v2/device-firmware.md 完整新文件
v2/server-lifecycle.md 不動FW 升級獨立於 server lifecycle
v2/milestone-plan.md + M9 系列M9-0 ~ M9-5

Design v2.1 受影響章節(建議補丁)

檔案 改動
docs/autoflow/03-design/v2/* 既有 不動
新增 docs/autoflow/03-design/v2/device-firmware-ui.md FW badge 設計 + 升級 modal wireframe + i18n table
中英雙語文案表 + 11 個新 keys

新 ADR

docs/autoflow/04-architecture/adr/ADR-009-firmware-management.md

  • 狀態Proposed → 待使用者 sign-off
  • 背景:使用者要求加 FW 升級、Q9 砍 flash 決策翻案
  • 決策:用 KneronPLUS Python APIkp.core.update_kdp_firmware_from_files)、不引入 DFUT.exe
  • 替代方案:(a) DFUT.exe 拒因 Windows-only(b) ctypes 拒因 KneronPLUS Python API 已足夠;(c) 不做拒因真實痛點
  • 後果:+1 個新模組 + ~5 人天工時 + 0KB 安裝包衝擊

4. 風險清單

R-FW-1升級中拔除 device中度風險

情境:使用者在 firmware load 階段拔掉 USB。

影響

  • KL520不寫 flash、最壞情況「沒升級成功」、re-plug 後仍是舊狀態、不 brick
  • KL720可能正在寫 flash、有 brick 風險

緩解

  1. UI modal 警告「升級期間請勿拔除裝置」
  2. KL720 升級路徑加更明確的「不可中斷」提示
  3. 失敗時自動 rescan + 提示 re-plug
  4. 文件記錄:如果使用者真的拔了 KL720、可以走 warrenchen 的 DFUT.exe 救磚(不打包到 visionA-local、僅內部 SOP

R-FW-2升級後 USB re-enumerate race低度風險

情境:升級成功 → device disconnect 回非零 → re-enumerate 中 → 立刻 reconnect 可能拿到舊 handle。

緩解

  • bridge.py handler 內 time.sleep(3) 等 USB 穩定
  • 不在 handler 內 reconnect、回傳成功後讓 Go 端執行 rescan + 重新 GetDevice

R-FW-3KL520 升級不需要、但被誤觸發(低度風險)

情境:使用者 dongle 已是 KDP2、但 UI 顯示「升級」按鈕badge 計算錯誤)。

緩解

  • bridge.py handler 內 detect_firmware 後若已是 KDP2、走 short-circuit 路徑(只 load_firmware to RAM、不寫 flash、< 5 秒完成)
  • driver FirmwareIsLegacy 判定要嚴格(必須是 KDP 但不含 KDP2

R-FW-4跨晶片混淆中度風險

情境bridge.py 升級時用錯 firmware 檔(拿 KL520 firmware 燒到 KL720

緩解

  • handler 強制要求 chip 參數、_resolve_firmware_paths(chip) 不接受空字串
  • driver 自己 d.chipType 已從 USB pid 判定、傳給 bridge 不靠前端
  • 加 unit test 確認 KL520 升級時不會用到 KL720 路徑

R-FW-5簽章 / 法律風險(待釐清)

情境:把 Kneron 官方 firmwarefw_scpu.bin / fw_ncpu.bin / fw_loader.bin打包進我們的 installer、是否合法

緩解

  • 我們已 bundle KDP2 firmware 4 個月、Q9 砍的是「使用者主動燒」、不是「打包 firmware」
  • B4 已有「Kneron 預置模型 re-distribution 授權」未解決問題progress.md L792、firmware 同性質
  • 建議:發佈前跟 Kneron 確認、與 model bundle 一起處理

R-FW-6HTTP timeout低度風險

情境:升級 KL720 需 ~180s、HTTP keep-alive / proxy timeout 可能更短。

緩解

  • API 設計HTTP 立刻回 202、實際進度走 WebSocket已採此 pattern
  • WebSocket 心跳設定 < 60s

R-FW-7Windows admin 權限(中度風險)

情境kp.core.install_driver_for_windows 需 admin、若 WinUSB driver 未綁定、升級會在第一步失敗。

緩解

  • 既有 visionA-local 已有 driver 安裝邏輯M1+ TODO
  • 升級 handler 偵測到 driver 未綁時、明確錯誤訊息引導使用者
  • 不在 visionA-local 內自動裝 driver、提示使用者重跑 installer

5. 與既有架構衝突點(細項)

5.1 既有 Flash() method 不重用

Flash() 是 load model、不混進 firmware 升級。確保 plan 內flash/service.go

5.2 kl720_driver.go 檔名包袱

UpgradeFirmware() 方法放在這個檔內、但改檔名(範圍外)。新建 firmware/ package 與 flash/ 並列、不疊在 kneron/ 內。

5.3 既有 restartBridge() 不擴充

restartBridge() 是 KL520 換 model 時用、跟 firmware 升級不同流程。擴充它、新流程獨立。

5.4 既有 needsReset flag 要善用

升級完成後、driver 應設 needsReset=true、下次 connect 走完整 reset flow既有邏輯。確保升級後第一次 inference 不踩 Error 15 SEND_DATA_TOO_LARGE。

5.5 既有 WS rooms 命名規範

既有 flash:<id> / inference:<id> → 新增 firmware:<id>、命名一致。

5.6 既有 DeviceInfo.Status 列舉

新增 StatusUpgrading、跟既有 StatusConnecting / StatusFlashing / StatusInferencing 並列。前端要新增對應 status badge。

5.7 既有 watchServer Error state 機制

FW 升級失敗升級為 server Error statedevice-level 失敗)、僅 device 進 StatusError。確保 watchServer goroutine 不會把 FW 升級 timeout 誤判為 server 死掉。


6. 階段 A 完成後的階段 B 評估提示(不在 MVP 範圍)

階段 A 驗證後、使用者可以再決定是否做階段 B。階段 B 候選:

  1. 手動降版 KDP2 → KDP1(給開發者測試用)

    • 需要從 warrenchen 複製 firmware/KL520_kdp/~80KB
    • bridge.py 加 handle_firmware_downgrade handler
    • 暴露在 Settings > 進階面板(不在 Devices 頁主要 UI
    • 預估 1.5 人天
  2. 加 KL630 / KL730 支援

    • 需先擴 driver / bridge.py 認 product_id 0x0630 / 0x0730、能 load firmware
    • 需驗 KneronPLUS SDK 對應版本是否支援這兩個晶片
    • 預估 3-4 人天driver 擴展 + FW 升級擴展 + 三平台驗證)
  3. 多版本 firmware 並存

    • server/scripts/firmware/<chip>/<version>/fw_*.bin
    • 前端 dropdown 讓使用者選版本
    • 預估 1.5 人天

合計階段 B 約 6-7 人天、加總 MVP 5 人天、完整版約 11-12 人天。


7. 給 Orchestrator 的下一步建議

  1. 本份研究 plan 給使用者 review
  2. 使用者同意 MVP方案 A
    • 啟動 PM Agent 補 PRD v2.2 韌體管理章節
    • Architect Agent 自己補 TDD v2.1 §2.10 + 寫 ADR-009
    • 啟動 Design Agent 補 Devices 頁面 FW UI 規格
  3. 三方互審 + 使用者 sign-off
  4. 開發進入 M9-1 ~ M9-5 流程(依本檔 §1
  5. 階段 B 暫不啟動、等階段 A 驗證後再評估