kimi# MCU Full Interface Test

基于 `mcu_structured_test.c` 扩展的全接口测试架构，覆盖协议中所有 **Public / Key / Radio / Update** 域的 A2M 命令，并在测试过程中自动收集 M2A 回发统计。支持文件日志、域级筛选、危险命令保护。

---

## 目录文件

| 文件 | 说明 |
|------|------|
| `mcu_full_test.c` | 主源码，~3400 行，6 层架构 |
| `Makefile` | 本地 / Android ARM64 交叉编译 |
| `mcu_full_test` | 本地编译产物（macOS/Linux） |

---

## 架构设计（6 层）

| 层级 | 职责 |
|------|------|
| **Layer 1** | 协议原语：帧编解码、流解析器、校验和、序列号、CRC16 |
| **Layer 2** | 实时传输：串口读写、读线程、事件队列、Setup/ACK 等待辅助函数 |
| **Layer 3a** | 旧场景目录：`smoke` / `full` / `radio-seek` / `upgrade`（向后兼容） |
| **Layer 3b** | **接口元数据目录**：所有 Public / Key / Radio / Update A2M 接口注册 |
| **Layer 3c** | **域测试运行器 + M2A 统计收集**：按域执行、收集 MCU 回发、输出统计 |
| **Layer 4** | 离线协议自测：checksum、parser、state machine、Update 目录构帧等 14 项自检 |
| **Layer 5** | CLI 入口：参数解析、模式分发、结果汇总 |

---

## 覆盖的接口

当前共注册 **63 条 A2M 测试用例**。Key 域另有 13 条 M2A 期望组合，需要外部按键/Key source 触发后验证。

### Public 域（31 条）

`REQ_INIT_INFO`、`REQ_MUTE`、`TIME_INFO`、`TFT_DUTY_CYCLE`、`REQ_SLEEP`、`REQ_TYPE_INFO(MCU_VERSION/LINE_DETECT)`、`SET_TFT_ON_OFF`、`SET_AMP_PWR`、`SET_KEY_BACKLIGHT`、`SET_SUB_MUTE`、`KEY_PWR_STATUS`、`SET_REVERSE`、`SET_SPEED_RATIO`、`REQ_TRIP_CLEAR`、`SET_SWITCH_STATUS`、`BT_ERROR_CODE`、`GPS_INFO`、`SET_TOT_MILES`、`SET_WORK_MODE`、`SET_FUEL_CAL`、`SET_VEHICLE_CFG`、`REQ_VEHICLE_CFG`、`ARM_SW_VERSION`、`ARM_HW_VERSION`、`AUTO_CLEAR_METER_TYPE`、`RESPONSE_FACTORY_CMD`、`RESPONSE_TEST_CMD`

> **危险命令**（默认跳过，需加 `--include-dangerous`）：
> - `REQ_ARM_RESET`
> - `FACTORY_RESTORE`
> - `REQ_ARM_FORCE_UPGRADE`
> - `SET_WORK_MODE`
> - `SET_FUEL_CAL`
> - `SET_VEHICLE_CFG`
> - `SET_TOT_MILES`

### Key 域（A2M 13 条 + M2A 13 条期望）

来自 `docs/MCU_ARM_Reference/Key.xlsx`：`Byte6=0x02`，M2A `Byte7=0x01`，A2M `Byte7=0x81`，参数为 `P0=KeyValue`、`P1=KeyStatus`。

自动发送的 A2M 覆盖 MC3 表中 `KeyValue=0x00..0x0B` 的全部键值，并额外覆盖 `MUTE/STUCK`：

`KEY_A2M_KEY(NONE/RELEASE)`、`MODE/SHORT`、`MUTE/SHORT`、`WORKMODE/SHORT`、`CALL/SHORT`、`VOICE_ASSISTANT/SHORT`、`HOME/SHORT`、`OK/SHORT`、`CHANNEL_UP/SHORT`、`CHANNEL_DOWN/SHORT`、`VOLUME_UP/ENC_LEFT`、`VOLUME_DOWN/ENC_RIGHT`、`MUTE/STUCK`

### Radio 域（11 条）

`SEEK_MODE(Stop/Forward/Backward/AMS)`、`SWITCH_ON_OFF(ON)`、`SET_BAND(FM)`、`SET_FREQ(87.5MHz)`、`FREQ_RANGE`、`ANT_PWR(ON)`、`REG_MUTE(Open/Close)`

### Update 域（7 条）

`UPDATE_REQ`、`UPDATE_BEGIN`、`UPDATE_DATA`、`UPDATE_END`、`BOOT_UPDATE_BEGIN`、`BOOT_UPDATE_DATA`、`BOOT_UPDATE_CHECKSUM`

Update 单帧测试默认作为危险命令跳过。APP 升级通过 `--live-upgrade` 跑完整状态机（`UPDATE_REQ` → `UPDATE_BEGIN` → `UPDATE_DATA` → `UPDATE_END`），BOOT 升级 0x84-0x86 已纳入构帧目录和自测，实刷前需 MCU 厂商确认前置条件。

---

## 编译

```bash
# 本地编译（验证语法/调试）
make

# Android ARM64 交叉编译
make CC=aarch64-linux-android21-clang
```

---

## 使用

### 1. 基础用法

```bash
# 推送到设备
adb push mcu_full_test /data/local/tmp/
adb shell chmod +x /data/local/tmp/mcu_full_test

# 默认：离线自测 + live-full（旧场景）
adb shell /data/local/tmp/mcu_full_test

# 仅离线自测
adb shell /data/local/tmp/mcu_full_test --selftest
```

### 2. 全接口测试（核心功能）

```bash
# 测试所有域，输出到日志文件
adb shell /data/local/tmp/mcu_full_test \
    --live-interface-full \
    --domain all \
    --log-file /data/local/tmp/mcu_full_$(date +%Y%m%d_%H%M%S).log

# 只测 Public 域
adb shell /data/local/tmp/mcu_full_test \
    --live-interface-full \
    --domain public

# 只测 Radio 域
adb shell /data/local/tmp/mcu_full_test \
    --live-interface-full \
    --domain radio

# 只测 Key 域
adb shell /data/local/tmp/mcu_full_test \
    --live-interface-full \
    --domain key

# 观察 MCU->ARM Key 上报；默认未触发物理按键时只 WARN，不判失败
adb shell /data/local/tmp/mcu_full_test \
    --live-key-observe \
    --observe-ms 30000

# Key 全量测试：先跑 A2M，再观察 M2A；缺少 M2A 组合时判失败
adb shell /data/local/tmp/mcu_full_test \
    --live-key-full \
    --observe-ms 30000 \
    --require-key-m2a

# 包含危险命令（会执行 RESET / FACTORY_RESTORE / FORCE_UPGRADE / 标定配置类命令）
adb shell /data/local/tmp/mcu_full_test \
    --live-interface-full \
    --domain public \
    --include-dangerous
```

### 3. 旧模式（兼容 `mcu_structured_test`）

```bash
# Smoke 精简测试
adb shell /data/local/tmp/mcu_full_test --live-smoke

# Full 完整旧场景
adb shell /data/local/tmp/mcu_full_test --live-full

# 收音机 Seek 测试
adb shell /data/local/tmp/mcu_full_test --live-radio-seek

# OTA 升级测试
adb shell /data/local/tmp/mcu_full_test --live-upgrade \
    --upgrade-bin /data/local/tmp/mcu_app.bin \
    --upgrade-project 10040
```

### 4. 日志与调试

```bash
# 开启文件日志（终端和文件同时输出）
adb shell /data/local/tmp/mcu_full_test \
    --live-interface-full \
    --log-file /data/local/tmp/mcu_test.log

# 隐藏 TX/RX HEX 打印（减少日志噪音）
adb shell /data/local/tmp/mcu_full_test \
    --live-interface-full \
    --quiet-hex

# 指定串口和波特率
adb shell /data/local/tmp/mcu_full_test \
    --port /dev/ttyS7 \
    --baud 460800 \
    --live-interface-full
```

---

## 输出示例

### 终端 / 日志文件输出

```
[2026-05-16 16:18:37.383] === MCU Full Interface Test ===
[2026-05-16 16:18:37.383] Mode: live-interface-full (domain=all, dangerous=no)
[2026-05-16 16:18:37.383] === Protocol Self-tests ===
[2026-05-16 16:18:37.383] [PASS] proto.checksum.setup
...
[2026-05-16 16:18:37.383] Self-tests: total=14 passed=14 failed=0

[2026-05-16 16:18:37.383] Serial: /dev/ttyS7 @ 460800 baud

=== Interface Domain: PUBLIC (31 tests) ===

[Test] REQ_INIT_INFO (public.req_init_info)
[INFO] Triggers SYSTEM_INIT_OK and M2A status burst
[PASS] public.req_init_info ack_seq=0x02
[OBS] public.req_init_info watching for 500ms
[OBS] public.req_init_info received 12 follow-up frame(s)

[Test] REQ_MUTE(OFF) (public.req_mute.off)
[PASS] public.req_mute.off ack_seq=0x03
...

=== M2A Frame Statistics ===
[Public] total=12
  cmd=0x05 SYS_STATUS             count=4
  cmd=0x01 LINE_DETECT            count=4
  cmd=0x06 KEY_PWR_STATUS         count=4

=== Summary ===
Self-tests : total=14 passed=14 failed=0
Live-tests : total=31 passed=31 failed=0
```

### 输出字段说明

| 字段 | 含义 |
|------|------|
| `[PASS] / [FAIL] / [SKIP]` | 单条接口测试结果 |
| `[OBS]` | 观察窗口内收到的 M2A 帧统计 |
| `[INFO]` | 该接口的额外说明（如触发条件、占位符提示） |
| `ack_seq=0xXX` | MCU 回执 ACK 的序列号 |
| `M2A Frame Statistics` | 按 Domain + Cmd 汇总的 MCU 主动上报次数 |

---

## CLI 参数速查

| 参数 | 说明 |
|------|------|
| `--selftest` | 仅运行离线协议自测 |
| `--live-link` | 仅做三步握手建链测试 |
| `--live-smoke` | 运行精简 Smoke 场景 |
| `--live-full` | 运行旧版 Full 场景 |
| `--live-radio-seek` | 运行收音机 Seek 测试 |
| `--live-upgrade` | 运行 OTA 升级完整流程 |
| `--live-interface-full` | **全接口测试（新核心模式）** |
| `--live-key-observe` | 观察 `KEY_M2A_KEY` 上报 |
| `--live-key-full` | Key A2M 全量 + Key M2A 覆盖验证 |
| `--domain public/key/radio/update/all` | 选择测试域（默认 `all`） |
| `--observe-ms 30000` | 设置 Key M2A 观察窗口 |
| `--require-key-m2a` | Key M2A 期望组合未全覆盖时判失败 |
| `--include-dangerous` | 执行 RESET / FACTORY_RESTORE / FORCE_UPGRADE / 标定配置 / Update 刷写类命令 |
| `--log-file PATH` | 同时写入日志文件 |
| `--quiet-hex` | 关闭 TX/RX HEX 打印 |
| `--port /dev/ttyS7` | 指定串口 |
| `--baud 460800` | 指定波特率 |
| `--upgrade-bin PATH` | 指定升级 bin 文件路径 |
| `--upgrade-project 10040` | 指定升级项目号 |

---

## 协议帧格式（内部实现）

```
[0xFF][0xAA][LenH][LenL][Seq][FrameType][Payload...][Checksum]
  1B    1B     1B    1B    1B      1B         N B        1B
```

**FrameType**

| 值 | 名称 | 用途 |
|----|------|------|
| `0x01` | SETUP | 建链请求 |
| `0x02` | ACK | 确认帧 |
| `0x03` | SETUPACK | 建链响应 |
| `0x04` | CLOSE | 断链 |
| `0x05` | HB | 心跳 |
| `0x06` | DATA | 主要业务数据 |
| `0x07` | TRANSFER | 大数据传输 |
| `0x08` | BOOT | Boot 相关 |

**DATA 帧 Payload 结构**

```
[ModuleType(1B)][Cmd(1B)][Data...]
```

- Public: `ModuleType = 0x01`
- Radio: `ModuleType = 0x05`
- Update: `ModuleType = 0x07`

---

## 注意事项

1. **严格建链**：所有 Live 模式都以三步握手开始，SetupAck 未收到则整域失败。
2. **串口独占**：确保无其他进程（如系统服务）同时读取 `/dev/ttyS7`，否则会抢字节导致解析失败。
3. **占位 Payload**：`GPS_INFO`、`SET_SWITCH_STATUS`、`SET_VEHICLE_CFG`、`SET_FUEL_CAL` 等接口目前使用全 0 占位值，请根据实车配置在源码中调整。
4. **TRANSFER 帧**：MCU 主动推送的电压/广播类数据（FrameType = `0x07`）无需 ACK，读线程会自动识别。
5. **日志同步**：`--log-file` 会同时写终端和文件，每一行都有 `[YYYY-MM-DD hh:mm:ss.mmm]` 时间戳。

---

## 扩展指南

### 添加新的 A2M 接口测试

在 `mcu_full_test.c` 中找到对应域的数组（如 `kPublicA2MTests[]`），追加条目：

```c
{"public.xxx.cmd", "CMD_NAME", DOMAIN_PUBLIC, 0xXX, DIR_A2M,
 {MT_PUBLIC, 0xXX, ...}, N, NULL, false, "说明", 观察窗口毫秒},
```

如需动态生成 payload（如时间、版本字符串），实现一个 `PayloadBuilderFn` 并在条目中指定 `payload_builder`。

### 添加新的 M2A 命令名称解析

在 `public_m2a_cmd_name()` / `radio_m2a_cmd_name()` 函数中补充 `switch-case` 分支，统计输出时会自动显示可读名称。
