---
date: 2025-11-12T23:23:41+08:00
researcher: Claude (Sonnet 4.5)
git_commit: 7ec38681f205715ecf5bf1e73bdea6e46cebe6eb
branch: deploy
repository: FWAuto
topic: "Deploy Operations 與 Maketool Abstraction Layer 架構分析"
tags: [research, codebase, deploy, abstraction-layer, architecture]
status: complete
last_updated: 2025-11-13
last_updated_by: Claude (Sonnet 4.5)
last_updated_note: "Added follow-up research for deploy flow types and architecture decision"
---

# Research: Deploy Operations 與 Maketool Abstraction Layer 架構分析

**Date**: 2025-11-12T23:23:41+08:00
**Researcher**: Claude (Sonnet 4.5)
**Git Commit**: 7ec38681f205715ecf5bf1e73bdea6e46cebe6eb
**Branch**: deploy
**Repository**: FWAuto

## 📋 Research 文檔規範

**允許的章節** (Focus on Discovery):
- ✅ Research Question - 研究問題
- ✅ Summary - 高層次發現摘要
- ✅ Detailed Findings - 詳細發現（程式碼位置、結構、問題點）
- ✅ Code References - 程式碼參考（檔案路徑、行號）
- ✅ Architecture Insights - 架構洞察（發現的模式、設計決策）
- ✅ Technical Feasibility - 技術可行性分析（方向性建議）
- ✅ Historical Context - 歷史脈絡
- ✅ Related Research - 相關研究
- ✅ Open Questions - 待探索問題

## Research Question

**核心問題**：
> 目前 `deploy_operations` 模組直接執行 SSH/SCP 命令，但根據 CLAUDE.md 的 Maketool Abstraction Layer 設計原則，它應該負責資訊蒐集並調用 `deploy.py` script，讓 script 負責實際的工具執行。現有架構的差異在哪裡？應該如何重構？

**研究範圍**：
1. 現有 `deploy_operations.py` 的實作方式
2. `deploy.py` script 的設計和功能
3. `build_node` 如何使用 Makefile Abstraction Layer
4. 兩者的架構差異和問題點

## Summary

### 核心發現

**目前架構問題**：
- ✅ **build_node** 完全遵循 Maketool Abstraction Layer：Python 編排 → Makefile → Script → 工具鏈
- ❌ **deploy_node** 不遵循此模式：Python 直接執行 SSH/SCP，跳過 script 層
- ❌ **deploy.py** 設計為獨立工具，但從未被程式調用

**設計目標**（應有架構）：
```mermaid
flowchart TD
    DN[deploy_node]
    DO[deploy_operations]
    DP[deploy.py]
    B[Board]

    DN -->|"蒐集 State 和配置"| DO
    DO -->|"準備參數，調用一次"| DP
    DP -->|"1. 網路檢查<br/>2. 尋找 binary<br/>3. SCP copy<br/>4. SSH execute"| B

    style DO fill:#bbf,stroke:#333
    style DP fill:#bfb,stroke:#333
```

**關鍵洞察**：
- deploy_operations 應該只負責**資訊蒐集和參數構建**
- deploy.py 負責**完整的部署流程**（一次調用完成所有步驟）
- deploy_operations **不區分** copy 和 execute，統一委託給 deploy.py

## Detailed Findings

### 1. 目前 deploy_operations 的實作方式

**檔案位置**: `src/fwauto/deploy_operations.py`

#### 直接執行模式（問題所在）

**網路連線檢查** (第 68-126 行)：
```python
def check_network_connectivity(board_ip: str) -> None:
    # 使用 subprocess 直接執行 ping
    ping_cmd = ["ping", "-c", "1", "-W", "2", board_ip]
    result = subprocess.run(ping_cmd, capture_output=True, timeout=5)

    # 使用 socket 直接檢查 SSH port
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    result = sock.connect_ex((board_ip, 22))
```

**SCP 傳輸** (第 177-212 行)：
```python
def deploy_binary(binary_path: Path, config: DeployConfig) -> str:
    # 直接構建 scp 命令
    ssh_opts = config.ssh_options.split()
    scp_cmd = ["scp"] + ssh_opts + [str(binary_path), f"{config.board_user}@{config.board_ip}:{remote_path}"]

    # ❌ 直接執行 subprocess
    result = subprocess.run(scp_cmd, capture_output=True, timeout=30, text=True)
```

**SSH 執行** (第 220-276 行)：
```python
def execute_on_board(binary_name: str, config: DeployConfig) -> DeployResult:
    # 直接構建 ssh 命令
    ssh_opts = config.ssh_options.split()
    ssh_cmd = ["ssh"] + ssh_opts + [f"{config.board_user}@{config.board_ip}", "bash"]

    # ❌ 直接執行 subprocess
    result = subprocess.run(ssh_cmd, input=remote_script, capture_output=True, timeout=10, text=True)
```

**高層函數 deploy_firmware()** (第 365-423 行)：
```python
def deploy_firmware(build_dir: Path, config: DeployConfig) -> DeployResult:
    # 第 407 行：直接調用 Python 函數
    check_network_connectivity(config.board_ip)

    # 第 410 行：直接調用 Python 函數
    binary_path = find_binary(build_dir)

    # 第 413 行：直接調用 Python 函數
    binary_name = deploy_binary(binary_path, config)

    # 第 416 行：直接調用 Python 函數
    result = execute_on_board(binary_name, config)
```

**問題分析**：
- ❌ **職責混淆**：deploy_operations 既蒐集資訊，又執行工具
- ❌ **違反抽象原則**：Python 代碼直接知道 SSH/SCP 的存在
- ❌ **難以擴展**：新增部署方式（如 JTAG）需修改 Python 代碼

#### 僅記錄 deploy.py 命令（未執行）

**第 387-403 行**：
```python
def deploy_firmware(build_dir: Path, config: DeployConfig) -> DeployResult:
    # 生成等效的手動執行命令
    deploy_script = build_dir.parent / ".fwauto" / "deploy" / "deploy.py"
    cmd_parts = [f"python3 {deploy_script}"]
    cmd_parts.append(f"--board-ip {config.board_ip}")
    # ...

    manual_cmd = " ".join(cmd_parts)
    logger.info("💡 Manual reproduction command:")
    logger.info(f"   {manual_cmd}")  # ❌ 僅記錄，不執行！

    # ❌ 接著直接執行 Python 函數，而非調用 deploy.py
    check_network_connectivity(config.board_ip)
```

**發現**：
- deploy_operations 知道 deploy.py 的存在
- 能構建正確的調用命令
- **但從未實際執行**，只是記錄到 log 中

### 2. deploy.py 的設計和功能

**檔案位置**: `src/fwauto/templates/sdk_am62x_linux/deploy/deploy.py`

#### 完整的獨立部署流程

**主函數** (第 385-418 行)：
```python
def main():
    config = get_config()  # 解析 CLI 參數和環境變數

    # Step 1: 檢查網路連線
    check_network_connectivity(config.board_ip)

    # Step 2: 尋找 binary
    binary_path = find_binary(config.build_dir)

    # Step 3: 部署 binary (SCP)
    binary_name = deploy_binary(binary_path, config.board_ip, config.board_user, ...)

    # Step 4: 執行 binary (SSH)
    execute_on_board(binary_name, config.board_ip, config.board_user, ...)

    return 0
```

**關鍵特性**：
- ✅ **零依賴**：僅使用 Python 內建庫
- ✅ **完整流程**：一次執行完成所有步驟
- ✅ **可獨立運行**：不需要 FWAuto 環境
- ✅ **參數完整**：支援 CLI、環境變數、預設值

**參數支援** (第 34-87 行)：
```python
parser.add_argument("--board-ip", default=os.getenv("BOARD_IP", "192.168.50.169"))
parser.add_argument("--board-user", default=os.getenv("BOARD_USER", "root"))
parser.add_argument("--deploy-path", default=os.getenv("DEPLOY_PATH", "/home/root"))
parser.add_argument("--ssh-options", default=os.getenv("SSH_OPTIONS", "..."))
parser.add_argument("--binary-args", default=os.getenv("BINARY_ARGS", ""))
parser.add_argument("--execution-mode", choices=["foreground", "background"], default=os.getenv("EXECUTION_MODE", "foreground"))
parser.add_argument("--build-dir", type=Path, default=Path(__file__).resolve().parents[2] / "build")
```

**發現**：
- deploy.py 已經實作了 deploy_operations 所有的功能
- 兩者功能**完全重複**（網路檢查、尋找 binary、SCP、SSH）
- deploy.py 設計為**完整獨立工具**，不是被調用的子模組

### 3. build_node 的 Makefile Abstraction 實作

**檔案位置**: `src/fwauto/nodes/build.py`

#### 完全委託模式（正確做法）

**build_node 流程** (第 21-132 行)：
```python
def build_node(state: FirmwareState) -> dict[str, Any]:
    # 1. 驗證和載入配置
    config = load_project_config(project_root_path)

    # 2. 取得 Makefile 路徑和 target
    makefile_path = project_root_path / config.build_makefile
    target = config.build_target

    # 3. ✅ 調用共用工具函數，委託給 Makefile
    returncode, full_output = execute_make_command(
        makefile_path=makefile_path,
        target=target,
        project_root=project_root_path,
        timeout=60,
    )

    # 4. 解析結果並更新 state
    if returncode == 0:
        return {"build_status": "success", ...}
```

**execute_make_command 實作** (`src/fwauto/utils.py:79-165`)：
```python
def execute_make_command(makefile_path, target, project_root, timeout=120):
    # ✅ 構建 make 命令
    cmd = ["make", target, f"PROJECT_ROOT={project_root}"]

    # ✅ 執行 subprocess (調用 Makefile)
    process = subprocess.Popen(cmd, ...)
    stdout_data, _ = process.communicate(timeout=timeout)

    return process.returncode, stdout_data
```

**Makefile 範例** (`src/fwauto/templates/sdk_am62x_linux/build/Makefile`)：
```makefile
build: clean all

$(BUILD_DIR)/%: $(SRC_DIR)/%.c
    @if [ -f "$(ARAGO_ENV)" ]; then \
        source "$(ARAGO_ENV)"; \
    fi
    $${CC:-cc} $$CPPFLAGS $$CFLAGS -o "$@" "$<" $$LDFLAGS $$LDLIBS
```

**架構圖**：
```mermaid
flowchart TD
    BN[build_node]
    EMC[execute_make_command]
    MF[Makefile]
    GCC[GCC/Keil/IAR]

    BN -->|"載入配置<br/>makefile_path, target"| EMC
    EMC -->|"make build<br/>PROJECT_ROOT=..."| MF
    MF -->|"執行編譯工具"| GCC
    GCC -.返回 exit code.-> MF
    MF -.返回 exit code.-> EMC
    EMC -.返回 returncode.-> BN

    style EMC fill:#bbf,stroke:#333
    style MF fill:#bfb,stroke:#333
```

**關鍵原則**：
- ✅ build_node **不知道**使用 Keil 還是 GCC
- ✅ Python 層只負責**參數傳遞**和**結果解析**
- ✅ Makefile 封裝**工具鏈細節**

### 4. deploy_node 的實作方式

**檔案位置**: `src/fwauto/nodes/deploy.py`

#### 使用 deploy_operations（而非 Makefile）

**deploy_node 流程** (第 48-201 行)：
```python
def deploy_node(state: FirmwareState) -> dict[str, Any]:
    # 1. 驗證和載入配置
    config = load_project_config(project_root_path)

    # 2. 提取參數
    binary_args = command_args.get("binary_args", "")

    # 3. 構建 DeployConfig
    deploy_config = DeployConfig(
        board_ip=config.board_ip,
        board_user=config.board_user,
        deploy_path=config.deploy_path,
        ssh_options=config.ssh_options,
        binary_args=binary_args,
        execution_mode="background",
        remote_log_enabled=config.remote_log_enabled,
        remote_log_path=remote_log_path_local or "/dev/null",
    )

    # 4. ❌ 調用 Python 模組（而非 script/Makefile）
    result = deploy_firmware(build_dir=project_root_path / "build", config=deploy_config)

    # 5. 更新 state
    if result.success:
        return {"deploy_status": "success", ...}
```

**對比 build_node 的差異**：

| 層面 | build_node | deploy_node |
|------|-----------|-------------|
| **委託對象** | Makefile | Python 模組 (deploy_operations) |
| **執行方式** | `execute_make_command()` | `deploy_firmware()` |
| **工具封裝** | ✅ Makefile 封裝工具鏈 | ❌ Python 直接執行 SSH/SCP |
| **抽象層級** | ✅ 高（不知道工具鏈） | ❌ 低（直接知道 SSH/SCP） |
| **擴展性** | ✅ 新工具鏈僅需 Makefile | ❌ 新方式需修改 Python |

### 5. 架構對比圖

#### 目前的 Deploy 架構（問題）

```mermaid
flowchart TD
    subgraph "❌ 目前架構（混淆職責）"
        DN1[deploy_node]
        DO1[deploy_operations]

        DN1 -->|"DeployConfig"| DO1
        DO1 -->|"直接執行"| SSH1[SSH subprocess]
        DO1 -->|"直接執行"| SCP1[SCP subprocess]
    end

    subgraph "🔧 deploy.py（未被使用）"
        DP1[deploy.py]
        DP1 -.僅供手動執行.-> B1[Board]
    end

    SSH1 --> B1
    SCP1 --> B1

    style DO1 fill:#fbb,stroke:#333
    style DP1 fill:#ddd,stroke:#333,stroke-dasharray: 5 5
```

#### Build 架構（正確模式）

```mermaid
flowchart TD
    subgraph "✅ Build 架構（職責清晰）"
        BN2[build_node]
        EMC2[execute_make_command]
        MF2[Makefile]

        BN2 -->|"makefile_path, target"| EMC2
        EMC2 -->|"make build"| MF2
        MF2 -->|"執行工具"| TC2[Toolchain<br/>Keil/GCC/IAR]
    end

    style EMC2 fill:#bbf,stroke:#333
    style MF2 fill:#bfb,stroke:#333
```

#### 應有的 Deploy 架構（目標）

```mermaid
flowchart TD
    subgraph "✅ 目標架構（遵循 Abstraction）"
        DN3[deploy_node]
        DO3[deploy_operations]
        DP3[deploy.py]

        DN3 -->|"蒐集 State 和配置"| DO3
        DO3 -->|"構建參數<br/>調用一次"| DP3
        DP3 -->|"1. 網路檢查<br/>2. 尋找 binary<br/>3. SCP<br/>4. SSH"| B3[Board]
    end

    style DO3 fill:#bbf,stroke:#333
    style DP3 fill:#bfb,stroke:#333
```

## Code References

### 核心檔案

| 檔案路徑 | 行號 | 說明 |
|---------|------|------|
| `src/fwauto/deploy_operations.py` | 177-212 | `deploy_binary()` 直接執行 SCP |
| `src/fwauto/deploy_operations.py` | 220-276 | `execute_on_board()` 直接執行 SSH |
| `src/fwauto/deploy_operations.py` | 365-423 | `deploy_firmware()` 高層協調函數 |
| `src/fwauto/deploy_operations.py` | 387-403 | 僅記錄 deploy.py 命令，未執行 |
| `src/fwauto/nodes/deploy.py` | 48-201 | `deploy_node()` 調用 deploy_operations |
| `src/fwauto/nodes/deploy.py` | 150 | 調用 `deploy_firmware()` |
| `src/fwauto/nodes/build.py` | 77-84 | `build_node()` 調用 `execute_make_command()` |
| `src/fwauto/utils.py` | 79-165 | `execute_make_command()` 實作 |
| `src/fwauto/templates/sdk_am62x_linux/deploy/deploy.py` | 385-418 | deploy.py 主函數 |
| `src/fwauto/templates/sdk_am62x_linux/deploy/deploy.py` | 34-87 | 參數解析（CLI + ENV） |

### 相關配置

| 檔案路徑 | 行號 | 說明 |
|---------|------|------|
| `src/fwauto/config.py` | 109-128 | `deployment_config` 屬性 |
| `src/fwauto/config.py` | 131-149 | Deploy 參數的 getter |
| `CLAUDE.md` | 113-117 | Maketool Abstraction Layer 定義 |

## Architecture Insights

### 設計模式分析

#### Maketool Abstraction Layer 的核心理念

**CLAUDE.md 原文引用** (第 113-117 行)：
> Python code orchestrates workflows but delegates toolchain details to Makefiles:
> - `build_node` calls `make -f .fwauto/build/Makefile build`
> - `deploy_node` calls `make -f .fwauto/deploy/Makefile deploy`
> - This decouples Python from Keil/GCC/IAR/ARMCC specifics
> - Adding new toolchains requires only Makefile changes, no Python changes

**核心原則**：
1. **職責分離**：Python 編排工作流，Makefile/Script 執行工具
2. **工具無關**：Python 不知道底層用什麼工具
3. **易於擴展**：新增工具只需修改 Makefile/Script

#### 目前架構的問題

**違反抽象原則**：
- deploy_operations 直接使用 `subprocess.run(["ssh", ...])` 和 `subprocess.run(["scp", ...])`
- Python 代碼**明確知道** SSH/SCP 的存在
- 無法透過修改 script 切換部署方式（如改用 TFTP）

**功能重複**：
- deploy_operations 實作了完整的部署邏輯（400+ 行）
- deploy.py 也實作了相同的邏輯（400+ 行）
- 兩者**完全獨立**，沒有代碼重用

**擴展困難**：
- 新增 STM32 部署需修改 deploy_operations.py
- 無法僅透過新增 script 支援新平台

### 應有的架構設計

#### 三層職責劃分

```mermaid
flowchart TD
    subgraph L1["Layer 1: Workflow Orchestration"]
        DN[deploy_node]
    end

    subgraph L2["Layer 2: Configuration & Invocation"]
        DO[deploy_operations]
    end

    subgraph L3["Layer 3: Tool Execution"]
        DP[deploy.py / deploy.ps1]
    end

    subgraph L4["Layer 4: Target System"]
        B[Board / Device]
    end

    DN -->|"FirmwareState"| DO
    DO -->|"subprocess.run(['python3', 'deploy.py', ...])"| DP
    DP -->|"SSH/SCP/ST-LINK"| B

    style L2 fill:#e3f2fd,stroke:#333
    style L3 fill:#e8f5e9,stroke:#333
```

**Layer 1 (deploy_node)**：
- 驗證專案初始化
- 載入配置
- 處理遠端日誌
- 更新 FirmwareState

**Layer 2 (deploy_operations)**：
- 蒐集所有必要資訊（binary 路徑、IP、參數等）
- 構建 deploy.py 的完整命令列
- 執行 `subprocess.run(["python3", "deploy.py", ...])`
- 解析 exit code 和輸出

**Layer 3 (deploy.py)**：
- 接收參數（CLI + ENV）
- 執行完整部署流程
- 返回 exit code（0=成功，非0=失敗）

#### 參數傳遞流程

```mermaid
sequenceDiagram
    participant DN as deploy_node
    participant DO as deploy_operations
    participant DP as deploy.py
    participant B as Board

    DN->>DN: 載入 ProjectConfig
    DN->>DN: 提取 command_args
    DN->>DO: deploy_firmware(build_dir, DeployConfig)

    DO->>DO: 構建命令:<br/>python3 deploy.py<br/>--board-ip 192.168.50.169<br/>--binary-args "on"<br/>--execution-mode background
    DO->>DP: subprocess.run(cmd)

    DP->>DP: 解析參數
    DP->>B: check_network_connectivity()
    B-->>DP: OK
    DP->>DP: find_binary()
    DP->>B: SCP copy
    B-->>DP: 成功
    DP->>B: SSH execute
    B-->>DP: 成功

    DP-->>DO: exit code 0
    DO->>DO: 解析結果
    DO-->>DN: DeployResult(success=True)
    DN->>DN: 更新 FirmwareState
```

### 與 build 架構的一致性

| 層面 | build_node | deploy_node (應有) |
|------|-----------|-------------------|
| **Node 職責** | 驗證、配置、路由 | 驗證、配置、路由 |
| **Operations 職責** | 構建 make 命令 | 構建 deploy.py 命令 |
| **Script 職責** | 執行編譯工具 | 執行部署工具 |
| **抽象層級** | 高（不知道 Keil/GCC） | 高（不知道 SSH/SCP） |
| **擴展方式** | 新增 Makefile | 新增 deploy.py |

### 平台差異處理

#### AM62X (Linux)

```bash
# deploy_operations 調用：
python3 .fwauto/deploy/deploy.py \
  --board-ip 192.168.50.169 \
  --binary-args "on" \
  --execution-mode background

# deploy.py 內部：
- 使用 SSH/SCP 網路部署
- 支援 foreground/background 模式
- 遠端日誌支援
```

#### STM32 (Windows) - 未來設計方向

根據使用者指示：**STM32 未來可能改用 stm32 版本的 deploy.py**

```powershell
# deploy_operations 調用（未來設計）：
python3 .fwauto\deploy\deploy.py `
  --hex-file .\OBJ\Template.hex `
  --stlink-cli "C:\Program Files\STLink\ST-LINK_CLI.exe"

# deploy.py 內部（未來實作）：
- 檢查 ST-LINK 連線
- 使用 ST-LINK CLI 燒錄
- 驗證燒錄結果
```

**統一介面**：
- 不同平台的 deploy.py 接受不同參數
- 但都遵循相同的調用模式：`python3 deploy.py [options]`
- deploy_operations 根據 `sdk_type` 調用對應平台的 deploy.py

## Technical Feasibility

### 重構可行性分析

#### 方案 1：最小化修改（推薦）

**修改範圍**：
- ✅ `deploy_operations.py` 的 `deploy_firmware()` 函數（約 60 行）
- ✅ 保持 `deploy.py` 不變（已完整實作）
- ✅ 保持 `deploy_node.py` 不變（只調用 deploy_firmware）

**實作方向**：
```python
# deploy_operations.py
def deploy_firmware(build_dir: Path, config: DeployConfig) -> DeployResult:
    deploy_script = build_dir.parent / ".fwauto" / "deploy" / "deploy.py"

    # 構建命令（目前已有，第 387-403 行）
    cmd = [
        "python3", str(deploy_script),
        "--board-ip", config.board_ip,
        "--board-user", config.board_user,
        "--deploy-path", config.deploy_path,
        "--ssh-options", config.ssh_options,
        "--binary-args", config.binary_args,
        "--execution-mode", config.execution_mode,
    ]

    # ✅ 執行（新增）
    result = subprocess.run(cmd, capture_output=True, timeout=120, text=True)

    # 解析結果
    if result.returncode == 0:
        return DeployResult(success=True, message=result.stderr)
    else:
        return DeployResult(success=False, message=result.stderr)
```

**優點**：
- ✅ 修改最小（僅一個函數）
- ✅ 遵循 Maketool Abstraction 原則
- ✅ deploy.py 可繼續獨立使用
- ✅ 與 build 架構一致

#### 方案 2：完全移除重複代碼

**修改範圍**：
- ❌ 移除 deploy_operations 中的所有實作函數
- ❌ 僅保留 `deploy_firmware()` 作為調用入口
- ❌ 風險較高（需確保 deploy.py 涵蓋所有功能）

**不推薦原因**：
- deploy_operations 中的函數可能被其他地方使用
- 需要全面測試確保無遺漏
- 重構範圍較大

### 遠端日誌支援

**目前問題**：
- deploy.py 原生不支援遠端日誌功能
- deploy_operations 在調用前需處理遠端日誌配置

**解決方向**：
1. **透過環境變數傳遞**（簡單）：
   ```python
   env = os.environ.copy()
   if config.remote_log_enabled:
       env["REMOTE_LOG_PATH"] = config.remote_log_path

   subprocess.run(cmd, env=env)
   ```

2. **新增 CLI 參數**（需修改 deploy.py）：
   ```python
   parser.add_argument("--remote-log-path", default=os.getenv("REMOTE_LOG_PATH", ""))
   ```

3. **保持現狀**（最簡單）：
   - deploy_operations 負責遠端日誌處理
   - deploy.py 僅負責基本部署
   - 兩者職責互補

### STM32 整合考量

**目前狀況**：
- STM32 有 `deploy/Makefile` + `deploy.ps1`
- 尚未整合進 deploy_operations

**未來方向**（根據使用者指示）：
> STM32 的原來使用 make 的實作方式僅供參考，之後可能會改成 stm32 版本的 deploy.py

**統一策略**：
1. 為 STM32 建立 `deploy.py`（Python 版本，調用 ST-LINK CLI）
2. deploy_operations 根據 `sdk_type` 選擇對應的 deploy.py
3. 保持所有平台使用相同的調用模式

**參數差異處理**：
```python
def deploy_firmware(build_dir: Path, config: DeployConfig) -> DeployResult:
    deploy_script = build_dir.parent / ".fwauto" / "deploy" / "deploy.py"

    if config.sdk_type == "am62x":
        cmd = ["python3", str(deploy_script),
               "--board-ip", config.board_ip,
               "--binary-args", config.binary_args]
    elif config.sdk_type == "stm32":
        cmd = ["python3", str(deploy_script),
               "--hex-file", str(config.hex_file),
               "--stlink-cli", config.stlink_cli]

    result = subprocess.run(cmd, ...)
```

## Historical Context (from .ai/prpi)

### Flash to Deploy 重構 (2025-11-12)

**相關文件**：
- `.ai/prpi/research/2025-11-12-flash-to-deploy-rename.md`
- `.ai/prpi/plans/2025-11-12-flash-to-deploy-refactoring.md`
- `.ai/prpi/implementations/2025-11-12-flash-to-deploy-refactoring.md`

**重構背景**：
- 原本使用 `flash_node` 和 `flash_operations`
- 與 `deployment_config` 術語不一致
- 2025-11-12 完成重新命名為 `deploy_node` 和 `deploy_operations`

**重構範圍**：
- State 欄位：`flash_status` → `deploy_status`
- 模組命名：`flash_operations.py` → `deploy_operations.py`
- CLI 命令：`fwauto flash` → `fwauto deploy`
- Template 目錄：`.fwauto/flash/` → `.fwauto/deploy/`

**重構成果**：
- ✅ 104/110 測試通過
- ✅ 語義一致性
- ✅ Git commit: 2cd98c9f70b1a489809ec19b5878628650b85296

**與本研究的關聯**：
- 重構僅處理**命名**問題
- **未處理架構**問題（deploy_operations 直接執行 SSH/SCP）
- 本研究發現的問題在重構前後都存在

### AM62X Deployment Config (2025-11-06)

**相關文件**：
- `.ai/prpi/research/2025-11-06-am62x-deployment-config.md`

**研究發現**：
- AM62X 開發板連線配置硬編碼問題
- 建議使用 config.toml 配置管理
- 已實施：`deployment_config` 支援

**與本研究的關聯**：
- 確立了配置管理的模式
- deploy_operations 可透過 config 取得所有參數
- 為委託給 deploy.py 奠定基礎

## Related Research

- `.ai/prpi/research/2025-11-12-flash-to-deploy-rename.md` - Flash to Deploy 重新命名可行性分析
- `.ai/prpi/research/2025-11-06-am62x-deployment-config.md` - AM62X 部署配置研究

## Open Questions - 已討論並確認

### 1. 遠端日誌功能的歸屬 ✅ 已確認

**決策**：**方向 B - 新增 deploy.py 的 `--remote-log-path` 參數**

**實作方式**：
- deploy_operations 使用 Jinja2 渲染日誌路徑
- 透過 `--remote-log-path` 參數傳遞給 deploy.py
- deploy.py 接收參數並處理遠端日誌寫入

**理由**：明確、清晰，保持介面一致性

### 2. STM32 的 deploy.py 設計 ✅ 已確認

**決策**：**方向 C - 完全獨立實作**

**實作方式**：
- STM32 有自己的 deploy script（deploy_stm32.py 或 deploy.py）
- AM62X 有自己的 deploy script
- deploy_operations 根據 `sdk_type` 選擇調用哪個 script

**理由**：符合 KISS 原則，避免過度抽象

### 3. 錯誤訊息的傳遞 ✅ 已確認

**決策**：**簡化方案 - 移除豐富的錯誤診斷**

**重要設計原則**（使用者補充）：
> deploy_operations 原本就應該儘量平台獨立，只負責蒐集 FWAuto 提供的信息，而不應該跟開發板有太多的關聯

**實作方式**：
- deploy_operations：平台無關，僅蒐集資訊、構建命令、調用 script、解析基本輸出
- deploy.py：包含所有平台相關邏輯（網路檢查、SSH 診斷、錯誤訊息、故障排除建議）

**理由**：職責清晰，deploy_operations 真正做到平台獨立

### 4. 效能影響 ✅ 已確認

**決策**：**不是問題**

**結論**：
- 部署本身耗時（SCP 傳輸、SSH 連線），啟動 Python 解釋器的開銷可忽略
- 追求架構清晰性優先於微小的效能差異

---

## 確認的設計原則

### deploy_operations 的職責

✅ **應該做的**：
- 從 FirmwareState 和 ProjectConfig 蒐集資訊
- 構建 deploy script 的命令列參數
- 調用 deploy script（subprocess）
- 解析 exit code 和基本輸出
- 更新 FirmwareState

❌ **不應該做的**：
- 平台相關的網路檢查、SSH 診斷
- 豐富的錯誤診斷和故障排除建議
- 直接執行 SSH/SCP/ST-LINK 等工具
- 知道底層使用什麼部署工具

### deploy.py 的職責

✅ **應該做的**：
- 完整的部署流程（檢查 → 傳輸 → 執行）
- 平台相關的所有邏輯
- 豐富的使用者訊息和錯誤診斷
- 可獨立執行（CLI + ENV 參數）

---

**研究完成時間**: 2025-11-12T23:23:41+08:00
**決策確認時間**: 2025-11-12T23:40:00+08:00
**後續行動**: 執行 `/prpi:plan` 建立詳細的重構計劃

## Follow-up Research [2025-11-13]

### 關鍵發現：deploy_operations 的平台無關設計問題

#### 問題背景

原始研究假設 `deploy_operations` 可以使用統一的 `DeployConfig` 結構來處理所有平台。但在討論中發現一個**關鍵缺陷**：

**目前的 DeployConfig 是 SSH-specific 的**：
```python
DeployConfig(
    board_ip=...,          # ❌ STM32 不需要
    board_user=...,        # ❌ STM32 不需要
    deploy_path=...,       # ❌ STM32 不需要
    ssh_options=...,       # ❌ STM32 不需要
    binary_args=...,       # ⚠️ 可能需要
    execution_mode=...,    # ❌ STM32 不需要
)
```

**STM32 需要的是完全不同的參數**：
- `hex_file`（binary 路徑）
- `stlink_cli`（ST-LINK CLI 路徑）
- `flash_address`（可能）
- `verify`（燒錄後驗證）

這違反了 **deploy_operations 應該平台無關** 的設計原則。

---

### 韌體開發中的 Deploy 流程類型分析

#### 1. Direct Flashing（直接燒錄型）

**適用平台**：MCU 開發（STM32、AVR、ESP32、Nordic nRF52 等）

**特點**：
- ✅ 實體連線（USB/JTAG/SWD）
- ✅ 本機執行
- ✅ 同步操作
- ✅ 檢測簡單（燒錄器連接狀態）
- ❌ 不需要網路

**流程**：
```mermaid
flowchart LR
    A["檢查燒錄器"] --> B["讀取 binary"]
    B --> C["寫入 Flash"]
    C --> D["驗證"]
    D --> E["重置 MCU"]
```

#### 2. Network Deployment（網路部署型）

**適用平台**：Linux 嵌入式系統（AM62X、Raspberry Pi、RISC-V SBC、Jetson Nano 等）

**特點**：
- ⚠️ 需要網路連線
- ⚠️ 需要複雜環境檢測（ping、SSH port、防火牆）
- ⚠️ 可能有前置作業（killall、chmod）
- ⚠️ 非同步操作（背景執行）
- ⚠️ 需要遠端日誌收集

**流程**：
```mermaid
flowchart LR
    A["檢查網路"] --> B["檢查 SSH"]
    B --> C["傳輸 binary"]
    C --> D["停止舊程式"]
    D --> E["執行新程式"]
    E --> F["收集日誌"]
```

**關鍵洞察**：
- SSH-based 的檢測邏輯可以**跨平台共用**（AM62X、Raspberry Pi、RISC-V）
- 這些檢測應該在 **FWAuto 層處理**，因為大部分 Linux-based 平台都需要

#### 3. Bootloader Update（Bootloader 更新型）

**適用平台**：支援 IAP/DFU 的系統（STM32 DFU、ESP32 OTA 等）

**特點**：
- 需要進入 Bootloader 模式
- 有握手和驗證機制
- 可透過 USB 或網路

#### 4. OTA Update（Over-The-Air 更新型）

**適用平台**：IoT 裝置（ESP32、Nordic nRF9160、Zephyr RTOS）

**特點**：
- 遠端更新
- 裝置主動拉取
- 需要伺服器基礎設施

---

### 架構決策：在 deploy_operations 內部區隔

#### 選擇方案

**推薦方案 B：deploy_operations 內部區隔**

**理由**：

1. **deploy_node 保持簡潔**
```python
def deploy_node(state: FirmwareState) -> dict[str, Any]:
    config = load_project_config(project_root_path)

    # ✅ 統一介面，不區分部署類型
    result = deploy_firmware(
        build_dir=project_root_path / "build",
        config=config,
        binary_args=command_args.get("binary_args", "")
    )

    return {"deploy_status": "success" if result.success else "failed"}
```

2. **deploy_operations 根據部署類型分流**
```python
def deploy_firmware(build_dir: Path, config: ProjectConfig, binary_args: str) -> DeployResult:
    deploy_type = _get_deploy_type(config.sdk_type)

    if deploy_type == "direct_flashing":
        return _deploy_direct_flashing(build_dir, config, binary_args)
    elif deploy_type == "network_deployment":
        return _deploy_network(build_dir, config, binary_args)
    # ...

def _deploy_network(build_dir: Path, config: ProjectConfig, binary_args: str) -> DeployResult:
    """Linux-based：需要環境檢測"""

    # ⚠️ SSH-based 共用的檢測邏輯
    check_network_connectivity(config.board_ip)
    check_ssh_availability(config.board_ip, config.ssh_options)

    # ✅ 調用 deploy.py
    deploy_script = build_dir.parent / ".fwauto" / "deploy" / "deploy.py"
    cmd = ["python3", str(deploy_script), ...]
    result = subprocess.run(cmd, ...)

    return _parse_result(result)

def _deploy_direct_flashing(build_dir: Path, config: ProjectConfig, binary_args: str) -> DeployResult:
    """MCU-based：簡單調用"""

    # ✅ 簡單檢測（可選）
    # check_programmer_connected(config.programmer_type)

    # ✅ 直接調用 deploy script
    deploy_script = build_dir.parent / ".fwauto" / "deploy" / "deploy.py"
    cmd = ["python3", str(deploy_script), ...]
    result = subprocess.run(cmd, ...)

    return _parse_result(result)
```

3. **與 build_node 架構一致**
   - build_node 也是統一介面，不區分 Keil/GCC/IAR
   - deploy_node 應該統一介面，不區分 MCU/Linux/Bootloader

4. **檢測邏輯可共用**
   - SSH-based 檢測（網路、SSH port）可被多個 Linux 平台共用
   - Direct Flashing 檢測相對簡單（甚至可由 deploy.py 處理）

5. **易於擴展**
   - 新增部署類型只需在 deploy_operations 新增分支
   - deploy_node 完全不需修改
   - 符合開放封閉原則

#### Deploy 類型分流表

| SDK Type | Deploy Type | 環境檢測 | Script 類型 | deploy_operations 函數 |
|----------|------------|---------|-----------|---------------------|
| am62x | network_deployment | 網路、SSH | deploy.py (SSH) | `_deploy_network()` |
| raspberry-pi | network_deployment | 網路、SSH | deploy.py (SSH) | `_deploy_network()` ✅ 共用 |
| risc-v | network_deployment | 網路、SSH | deploy.py (SSH) | `_deploy_network()` ✅ 共用 |
| stm32 | direct_flashing | 燒錄器（可選） | deploy.py (ST-LINK) | `_deploy_direct_flashing()` |
| esp32 | direct_flashing | USB serial port | deploy.py (esptool) | `_deploy_direct_flashing()` ✅ 共用 |
| nrf52 | direct_flashing | J-Link | deploy.py (nrfjprog) | `_deploy_direct_flashing()` ✅ 共用 |

**關鍵洞察**：
- **Network Deployment**：所有 SSH-based 平台共用 `_deploy_network()` 函數
- **Direct Flashing**：所有 MCU 平台共用 `_deploy_direct_flashing()` 函數
- **工具差異由 deploy.py 處理**：deploy_operations 不知道使用 ST-LINK、J-Link 還是 esptool

#### ProjectConfig 設計建議

```toml
# .fwauto/config.toml
[general]
sdk_type = "am62x"  # 或 "stm32", "esp32", "nrf52"
# deploy_type 可自動從 sdk_type 推導

[deployment_config]
# Linux-based (AM62X, Raspberry Pi, RISC-V)
board_ip = "192.168.50.169"
board_user = "root"
ssh_options = "-o StrictHostKeyChecking=no"

# 或 MCU-based (STM32)
# programmer_type = "stlink"
# stlink_cli = "C:\\Program Files\\STLink\\ST-LINK_CLI.exe"
```

---

### 核心設計原則確認

#### deploy_operations 職責（修正版）

✅ **應該做的**：
- 從 FirmwareState 和 ProjectConfig 蒐集資訊
- **根據 deploy_type 執行對應的環境檢測**：
  - Network Deployment：網路、SSH 檢測（可跨平台共用）
  - Direct Flashing：燒錄器檢測（簡單或省略）
- 構建 deploy script 的命令列參數
- 調用對應平台的 deploy.py
- 解析結果並更新 FirmwareState

❌ **不應該做的**：
- 假設所有平台使用相同的配置結構（如統一的 DeployConfig）
- 直接執行部署工具（SSH/SCP/ST-LINK）
- 包含平台特定的部署邏輯（應該在 deploy.py 中）

#### deploy.py 職責（不變）

✅ **應該做的**：
- 完整的部署流程（傳輸 → 執行）
- 平台相關的所有邏輯
- 豐富的使用者訊息和錯誤診斷
- 可獨立執行（CLI + ENV 參數）

---

### 對原始研究的修正

#### 原始假設（錯誤）
- deploy_operations 應該「完全平台無關」
- 所有檢測邏輯都應該移到 deploy.py

#### 修正後的理解（正確）
- deploy_operations 應該「工具無關」（不知道 SSH/SCP/ST-LINK）
- **但可以根據部署類型執行對應的環境檢測**
- 環境檢測是 FWAuto 層的價值（尤其是可跨平台共用的 SSH 檢測）
- deploy.py 專注於「部署執行」，不需要重複實作環境檢測

#### 關鍵區別

| 層面 | 原始假設 | 修正理解 |
|------|---------|---------|
| **環境檢測** | 全部移到 deploy.py | deploy_operations 根據類型執行檢測 |
| **檢測邏輯共用** | 未考慮 | SSH 檢測可跨 Linux 平台共用 |
| **平台無關** | 完全無關 | 工具無關，但知道部署類型 |
| **deploy.py 職責** | 檢測 + 部署 | 專注部署執行 |

---

**Follow-up 研究完成時間**: 2025-11-13T00:15:00+08:00
**關鍵洞察**: deploy_operations 應該是「工具無關」而非「完全平台無關」，環境檢測是 FWAuto 層的價值所在

---

## Follow-up Discussion [2025-11-13 補充]

### 核心定位確認：FWAuto 是開發階段工具

**明確範圍**：
- ✅ **開發階段**：快速迭代、除錯、測試
- ❌ **Production 環境**：不支援生產環境的 firmware 更新

**不支援的類型**（Production 環境）：
- ❌ Bootloader Update (IAP/DFU)
- ❌ OTA Update (IoT 遠端更新)
- ❌ Mass Programming (生產線燒錄)
- ❌ Secure Boot / Signed Firmware
- ❌ A/B Partition / Rollback 機制
- ❌ SD Card OS 燒錄（但支援燒錄後的 SSH 部署）

### Direct Flashing 流程共用確認

**關鍵發現**：所有 MCU 平台（STM32、ESP32、nRF52）都可共用 `_deploy_direct_flashing()` 函數

**設計原則**：
```python
# deploy_operations.py
def _deploy_direct_flashing(build_dir: Path, config: ProjectConfig, binary_args: str) -> DeployResult:
    """所有 MCU 共用此函數"""

    # ✅ 不需要知道是 ST-LINK、J-Link 還是 esptool
    # ✅ 工具差異由各平台的 deploy.py 處理

    deploy_script = build_dir.parent / ".fwauto" / "deploy" / "deploy.py"
    cmd = ["python3", str(deploy_script), "--build-dir", str(build_dir)]
    result = subprocess.run(cmd, ...)

    return _parse_result(result)
```

**平台對應**：

| 平台 | 工具 | deploy_operations 函數 | 共用？ |
|------|------|---------------------|-------|
| STM32 | ST-LINK CLI | `_deploy_direct_flashing()` | 基準 |
| ESP32 | esptool.py | `_deploy_direct_flashing()` | ✅ 共用 |
| nRF52 | nrfjprog | `_deploy_direct_flashing()` | ✅ 共用 |
| AVR | avrdude | `_deploy_direct_flashing()` | ✅ 共用 |

**工具差異處理**：
- deploy_operations **不知道**使用什麼燒錄工具
- 每個平台的 `deploy.py` 負責調用對應的工具
- 新增平台無需修改 deploy_operations

**架構優勢**：
1. ✅ **高度可擴展**：新增 MCU 平台只需新增 template 中的 deploy.py
2. ✅ **deploy_operations 保持簡潔**：只有兩個函數（`_deploy_network` 和 `_deploy_direct_flashing`）
3. ✅ **符合 Maketool Abstraction Layer**：Python 不知道底層工具

---

**補充討論完成時間**: 2025-11-13T11:20:00+08:00
**關鍵確認**:
1. FWAuto 專注開發階段，不支援 production 環境
2. 所有 MCU 平台共用 `_deploy_direct_flashing()` 函數
3. SD Card OS 燒錄不支援，但支援燒錄後的應用程式部署
