# Kimi 代码分析报告

> 时间: 2026-05-16
> 范围: ARM↔MCU 串口通信、Service 层架构、多 APK 调用

---

## 一、整体架构

```
┌──────────────────────────────────────────────────────────────────┐
│  APK A / APK B / Demo                                           │
│    McuClient.vehicle().setMute(true)                            │
└──────────────────────────┬───────────────────────────────────────┘
                           │ AIDL (跨进程)
┌──────────────────────────▼───────────────────────────────────────┐
│  Service (com.qsjnic.mcu.service)                               │
│                                                                    │
│  ┌───────────────────────────────────────────────────────┐       │
│  │  QsMcuSystemService — AIDL 实现层(559行, 60方法)      │       │
│  │  每个方法: 校验参数 → 拼装byte[] → McuLink.sendNormal │       │
│  └──────────────────────┬────────────────────────────────┘       │
│                         │ JNI                                     │
│  ┌──────────────────────▼────────────────────────────────┐       │
│  │  McuLink.java — JNI 桥接层(单例)                       │       │
│  │  sendBusinessFrame / sendAndWaitAck / runUpgrade       │       │
│  └──────────────────────┬────────────────────────────────┘       │
│                         │ System.loadLibrary("mcujni")            │
│  ┌──────────────────────▼────────────────────────────────┐       │
│  │  libmcujni.so (cpp/) — L1~L4 协议栈                    │       │
│  │  mcu_link_send_and_wait_ack / mcu_upgrade_run          │       │
│  │  读线程 + 事件队列 + auto-ACK                          │       │
│  └──────────────────────┬────────────────────────────────┘       │
│                         │ /dev/ttyS7                               │
│  ┌──────────────────────▼────────────────────────────────┐       │
│  │  MCU (车机)                                            │       │
│  │  半双工: 发一帧 → 等 ACK → 发下一帧                     │       │
│  └───────────────────────────────────────────────────────┘       │
└──────────────────────────────────────────────────────────────────┘
```

---

## 二、关键文件分析

### 2.1 C 协议层 (`test_Update/mcu_api.c`)

**现状：**
- 全局单例 `g_fd` / `g_linked` / `g_seq`
- `send_data_cmd()`: 发帧 → 等 ACK → 返回
- `mcu_upgrade_run()`: 读线程 + 事件队列，自包含，不用 `g_fd`
- 约 40 个对外 API 函数，每个调用 `send_data_cmd`

**问题：**
1. **全局变量线程不安全** — `g_fd` / `g_seq` 没有锁，多线程并发调用会乱序
2. **`send_data_cmd` 阻塞调用** — 等 ACK 期间卡住整个线程，Service 无法处理其他请求
3. **`send_data_cmd` 丢弃非 ACK 帧** — MCU 推送的 M2A 帧（如电压上报）在等 ACK 期间被丢弃
4. **`mcu_upgrade_run` 打开自己的 fd** — 不用 `g_fd`，升级时与其他命令冲突

### 2.2 JNI 桥接层 (`McuLink.java`)

**现状：**
- 单例模式 `McuLink.get()`
- 封装了 native 方法：`sendAndWaitAck` / `runUpgrade` / `doSetup`
- 有 `sendBusinessFrame` 和 `sendNormal` 两种发送方式
- `sendAndWaitAck` 是新原语（已重构）

**问题：**
1. ✅ `sendAndWaitAck` 已封装，Service 层可以直接用——但没人调
2. ⚠️ Service 层还在用旧的 `sendNormal` fire-and-forget + 轮询 `getLastAckSeq`
3. ✅ `runUpgrade` 已封装，可以直接替代 `UpgradeManager` 的 450 行 Java OTA

### 2.3 Service 层 (`QsMcuSystemService.java`)

**现状：**
- 559 行，60 个 `@Override` 实现 AIDL 方法
- 每个方法都是模板：校验 → 拼 byte[] → `McuLink.sendNormal` → 返回
- `UpgradeManager` 556 行，其中 ~450 行是死代码（Java OTA 状态机）

**问题：**
1. **没有消息队列** —— 多 APK 并发调用时，串口同时只能处理一个请求，目前无串行化
2. **byte[] 拼装内联在 Service 里** —— 不可测、易错（如 `setTimeInfo` 里 `year-2000`）
3. **60 个方法与 AIDL 1:1** —— 加新命令要改 4 处
4. **同步阻塞** —— 等 ACK 期间 Service 的 Binder 线程被卡住

---

## 三、核心问题：半双工串口与消息队列

### 3.1 为什么需要消息队列

```
时间轴 →─────────────────────────────────────────────────
APK A:  ──发 SET_FREQ──┐
                        ├──串口忙──MCU 处理中──回 ACK──
APK B:                  └──发 REQ_MUTE──────┐
                                           ├──冲突！MCU 收到乱序命令
APK C:                ──发 GET_VERSION──────┘
```

串口是**半双工**，同时只能有一个请求在飞。没有消息队列时：
- 多 APK 并发 → 帧交错 → MCU 协议栈错乱 → 死锁
- 即使单 APK，连续快速调用也需要等前一个 ACK 回来

### 3.2 当前代码的问题

**`send_data_cmd` 是阻塞的**，等 ACK 期间：
1. Java 线程卡住
2. MCU 推上来的 M2A 帧（电压、车速等）被丢弃
3. 其他 APK 的请求排队无门

**Service 层没有排队机制**：
```java
// 当前: 每个 AIDL 方法直接调 McuLink
@Override public int setMute(boolean on) {
    byte[] payload = new byte[]{0x01, (byte)(on?1:0)};
    return McuLink.get().sendNormal(0x01, 0x82, payload) ? OK : ERR;
}
// 问题: 两个 APK 同时调 setMute + setFreq，串口会乱
```

### 3.3 改进方案：消息队列

```
APK A: sendCommand →┐                    ┌─→ 执行 REQ_MUTE → 等 ACK → 回调 A
                    ├─→ 消息队列(FIFO) ──┼─→ 执行 SET_FREQ → 等 ACK → 回调 B
APK B: sendCommand →┘                    └─→ 执行 GET_VER  → 等 ACK → 回调 C
```

**关键设计：**
1. **Service 里一个 `LinkedBlockingQueue`** 存放待处理命令
2. **一个单线程 Worker** 从队列取命令 → 调 `McuLink.sendAndWaitAck` → 回调 APK
3. **AIDL 只暴露 `sendCommand(msgType, cmd, body)` + 回调通道**
4. **MCU 推送上行的 M2A 帧**——通过一个独立的读线程（cppe 已有）接收，通过 `DomainBus` 派发

---

## 四、架构评估（对照改进方案）

### 4.1 已实现的（文档建议的 P1~P3）

| 改进项 | 状态 | 对应代码 |
|--------|------|----------|
| cpp 重构完成(L1~L4) | ✅ 完成 | `mcu_link_send_and_wait_ack` / `mcu_upgrade_run` |
| McuLink 封装新原语 | ✅ 完成 | `sendAndWaitAck` / `runUpgrade` |
| Feature 模式 | ✅ 已有 | `VehicleFeature` / `RadioFeature` / `UpgradeFeature` |
| DomainBus | ✅ 已有 | 在 `feature/` 包下 |
| 单元测试 | ✅ 已有 | `VehicleFeatureTest` / `RadioFeatureTest` |

### 4.2 未实现的（需要继续做）

| 改进项 | 优先级 | 说明 |
|--------|--------|------|
| **Service 消息队列** | **P0** | 串口半双工，必须串行化请求 |
| **AIDL 精简到 ~10 方法** | P1 | 去掉 50 个 typed 方法，换成统一通道 |
| **byte[] 拼装抽到 domain 层** | P1 | 从 `QsMcuSystemService` 抽到纯 Java 模块 |
| **UpgradeManager 删 450 行死代码** | P2 | 改为直接调 `McuLink.runUpgrade` |
| **simple_app 引用规范化** | P3 | 不再跨目录引用 `test_Update/` |

---

## 五、具体代码问题

### 5.1 C 层 (`mcu_api.c`)

```c
static int g_fd = -1;           // 全局变量，无锁
static bool g_linked = false;
static uint8_t g_seq = 2;

static int send_data_cmd(...) {
    uint8_t seq = g_seq++;       // seq 自增，多线程并发会乱
    // ... 阻塞等 ACK，期间丢弃所有非 ACK 帧
}
```

**影响：**
- 多线程并发调用 → seq 乱序 → MCU 协议栈错乱
- 等 ACK 期间 M2A 帧被丢

**建议：**
- 短期内不改 C 层（刚重构完），消息队列在 Java Service 层做
- 长期可以把 `g_seq` 加锁，或把序列号管理交给上层

### 5.2 Java Service 层 (`QsMcuSystemService.java`)

```java
// 60 个方法，每个都是这个模板
@Override public int setBacklight(int d1, int d2) {
    if (d1<0 || d1>100 || d2<0 || d2>100) return ERR_INVALID_ARG;
    if (!McuLink.get().isLinkAlive()) return ERR_LINK_DOWN;
    byte[] payload = new byte[] { 0x01, (byte) d1, (byte) d2 };
    return McuLink.get().sendNormal(0x01, 0x84, payload) ? OK : ERR;
}
```

**问题：**
- byte[] 拼装内联，不可测
- `sendNormal` 是 fire-and-forget，不等 ACK 就返回
- 没有排队，并发调用串口乱

**建议：**
- 改成 `int seq = McuLink.get().sendAndWaitAck(0x01, 0x84, payload, 1500); return seq >= 0 ? OK : ERR;`
- 加消息队列统一入口

### 5.3 升级功能 (`UpgradeManager.java`)

556 行中有 ~450 行 Java OTA 状态机，已被 native `mcu_upgrade_run` 替代。**可以直接删掉换成一行的：**
```java
int rc = McuLink.get().runUpgrade(binPath, project);
```

### 5.4 AIDL 接口 (`IMcuService.aidl`)

50 个方法，每条新 MCU 命令都要加。**应该精简到 ~10 个，新增命令只需改 SDK Facade + domain 编码。**

---

## 六、多 APK 调用场景分析

### 6.1 场景

```
APK A (音乐App): 调 setVolume / setMute
APK B (导航App): 调 setReverse / getVehicleCfg  
APK C (设置App): 调 setBacklight / setTftOnOff
```

### 6.2 当前问题

- 无队列 → 并发请求串口乱序 → MCU 死锁
- 无优先级 → 高优请求（倒车）和低优请求（背光）同等排队
- 无超时机制 → 如果 MCU 不回复 ACK，请求永远挂着

### 6.3 改进后

```java
// Service 内部消息队列
public class CommandQueue {
    private final LinkedBlockingQueue<Command> queue = new LinkedBlockingQueue<>();
    private final ExecutorService worker = Executors.newSingleThreadExecutor();

    public void enqueue(int msgType, int cmd, byte[] body, 
                        int timeoutMs, ResultCallback cb) {
        queue.put(new Command(msgType, cmd, body, timeoutMs, cb));
        // worker 自动取队列执行
    }

    // Worker 线程
    private void processLoop() {
        while (true) {
            Command cmd = queue.take();  // 阻塞直到有请求
            int seq = McuLink.get().sendAndWaitAck(
                cmd.msgType, cmd.cmd, cmd.body, cmd.timeoutMs);
            cmd.callback.onResult(seq);
        }
    }
}
```

---

## 七、推荐立即动手项

### P0 — Service 消息队列（半天）

在 `QsMcuSystemService` 中加 `LinkedBlockingQueue` + 单线程 Worker，把所有 AIDL 方法改为入队而非直接调 `McuLink`。这是**必须的**，没有队列串口一定出问题。

### P1 — AIDL 统一通道（1 天）

加 `sendCommand(int msgType, int cmd, byte[] body, int timeoutMs)` 方法，旧方法标 `@Deprecated`，保留 6 个月兼容期。

### P2 — byte[] 编码抽到 domain（1 天）

新建 Java library 模块，把 60 个方法的 byte[] 拼装逻辑从 Service 移到 domain 层，写 JUnit 测试覆盖。

### P3 — 删 UpgradeManager 死代码（0.5 天）

删掉 ~450 行 Java OTA 状态机，改为 `McuLink.get().runUpgrade()`。

---

## 八、发现的 Bug

### Bug #1 🔴 Service 循环探测连续发帧不等待 ACK

**位置：** `QsMcuSystemService.java` 第 69-73 行

```java
McuLink.get().sendNormal(0x01, 0x81, null);   // REQ_INIT_INFO
McuLink.get().sendNormal(0x01, 0x86, new byte[] { 0x02 });  // REQ_TYPE_INFO
```

两帧之间无任何间隔，MCU 收到 seq=N 的 REQ_INIT_INFO 后还没处理完，seq=N+1 的 REQ_TYPE_INFO 就到了。MCU 协议栈期望 seq 递增，收到跳跃序列号会丢弃或出错。

**影响：** 每 10 秒一次，REQ_INIT_INFO 和 REQ_TYPE_INFO 交替失败，MCU 可能误判为协议异常。

### Bug #2 🔴 所有 typed AIDL 方法用 `sendNormal`，不等 ACK

**位置：** `McuBinder.java` 全部 30+ 个方法

```java
public int setMute(boolean on) {
    return McuLink.get().sendNormal(...) ? OK : ERR_LINK_DOWN;
}
```

`sendNormal` 是 fire-and-forget，返回 `true` 只表示帧写入了串口缓冲区，**不等于 MCU 收到了**。MCU 回的 ACK 没人处理。

**影响：**
- APK 调用 `setMute()` 返回 `OK`，但 MCU 可能根本没收到（串口忙、校验错）
- seq 失控：`sendNormal` 内部也会自增 `g_seq`，连续调用多次后 seq 和 MCU 期望的不一致
- 50 个 AIDL 方法无一例外全用这个模式

### Bug #3 🟠 C 层 `send_data_cmd` 丢弃 MCU 推送的 M2A 帧

**位置：** `test_Update/mcu_api.c` `send_data_cmd()`

```c
while (timeout) {
    n = rx_wait(rx, RX_BUF, 100);
    for (int j = 0; j <= n - 7;) {
        // 只检查 ACK，其他帧全部跳过
        if (rx[j+5] == FT_ACK && rx[j+4] == seq) return MCU_OK;
        j += fl2;
    }
}
```

等 ACK 期间，MCU 可能主动推送 M2A 帧（如电压 0x0D、车速 0x0A、开关状态 0x0C）。这些帧被读出来、解析了，但因为不是 ACK 就被**丢弃**，不会送到上层的 DomainBus。

**影响：** 如果在等待一个慢命令的 ACK，中间 MCU 上报的 M2A 数据全丢。车机上的电压/车速更新会卡住。

### Bug #4 🟠 `sendAndWaitAck` 已实现但无人使用

**位置：** `McuLink.java` 第 151-164 行 / `McuBinder.java` 第 280-286 行

`McuBinder` 里 AIDL v2 的 `sendCommand()` 正确使用了 `sendAndWaitAck`：

```java
int seq = McuLink.get().sendAndWaitAck(msgType, cmd, body, timeoutMs);
return seq >= 0 ? OK : ERR_TIMEOUT;
```

但 **50 个 typed 方法全部绕过它，继续用 `sendNormal`**。新 API 通了但没人走。

---

## 九、结论

**最大风险是串口没有消息队列。** 其他问题（AIDL 膨胀、byte[] 内联、死代码）影响开发效率但不会导致运行时崩溃，而并发调用串口会直接导致 MCU 死锁。

**立刻做 P0**，其他按优先级慢慢推进。
