# QSJNIC SystemService 架构与接口设计

> 配合补充条款"架构变更说明"使用,提供从串口到应用层的完整技术契约。
> 承接 `MCU & Linux 通信链路设计.doc`、`MCU-ARM_v2.xlsx`、`Public_v2.xlsx`、`Radio_v2.xlsx`、`MCU升级-Update.xlsx`,以及 `test_Serial/` 已验证的协议实现。

---

## 1. 范围与目标

| 维度 | 设计目标 |
|---|---|
| **进程隔离** | 多 App 同时调用,彼此数据/回调互不污染 |
| **串口独占** | 全系统仅 `system_server`(或 SystemService 进程)持有 `/dev/ttyS7`,业务 App 无 root/无串口权限 |
| **延迟** | 端到端 ≤ 50ms(App→Binder→Service→串口物理 TX 完成,不含 MCU 应答) |
| **可重启** | Service 异常重启后,SDK 自动重连且把状态恢复广播给 App |
| **可演进** | AIDL 接口设版本号,后续加帧不破坏旧 SDK |
| **协议保真** | 完整覆盖 Public/Radio/Update 三大业务域已知 cmd,字段对齐规范 xlsx |

---

## 2. 整体架构

### 2.1 进程拓扑

```
┌──────────────────────────────────────────────────────────────────────────┐
│ 应用进程 (× N)                                                            │
│  com.xxx.radio       com.xxx.vehicle      com.xxx.upgrade                │
│  └─ McuSdk.aar       └─ McuSdk.aar        └─ McuSdk.aar                  │
│       │                   │                    │                          │
│       │ bindService("mcu_service") + Binder transact                      │
└───────┼───────────────────┼────────────────────┼──────────────────────────┘
        │                   │                    │
        └─────── Binder IPC ────────────┐
                                        │
┌───────────────────────────────────────┼──────────────────────────────────┐
│ system_server 或 com.qsjnic.mcuservice (Privileged 进程)                   │
│                                       ▼                                    │
│   McuSystemService                                                         │
│    ├─ Binder thread pool (默认 16,处理 transact)                           │
│    ├─ Tx queue + Tx thread        (序列化 ARM→MCU 帧)                      │
│    ├─ Rx thread                   (读串口、解帧、路由)                      │
│    ├─ Heartbeat thread            (2s 周期,沿用 test_Serial)               │
│    └─ Upgrade state machine       (DATA loop 时进入排他模式)               │
│                                       │                                    │
│                                JNI    │   libmcujni.so (C++)               │
│                                       │   ├─ protocol_pack/parse_frame     │
│                                       │   ├─ serial_open/read/write        │
│                                       │   └─ jni_bridge.cpp                │
└───────────────────────────────────────┼────────────────────────────────────┘
                                        │
                                /dev/ttyS7  460800 8N1
                                        │
                                        ▼
                                       MCU
```

### 2.2 工程结构与模块划分

工程采用单仓库多模块(Mono-repo, Multi-Module Gradle)。详细工程结构与交付边界见
`doc/工程结构与交付清单.md`,此处仅列模块责任:

```
qsjnic/                        ← 主仓库,内部不交付
├── doc/                       ← 设计与集成文档
├── test_Serial/               ← C raw 测试工具(保留,不动)
├── contract/                  ← Gradle 模块:AIDL + Parcelable + 错误码
├── service/                   ← Gradle 模块:QsMcuSystemService(Java + JNI + native)
│   └── src/main/cpp/          ← libmcujni.so,沿用 test_Serial 的 protocol.c/serial_io.c
├── sdk/                       ← Gradle 模块:McuClient + 域 Facade,产出 qsmw-sdk.aar
└── demos/                     ← 客户演示工程,各自独立 AS 工程,内嵌 qsmw-sdk.aar
    ├── ota-demo/
    ├── radio-demo/
    └── vehicle-demo/
```

| 模块 | Gradle 名 | 产物 | 责任 |
|---|---|---|---|
| Native 协议引擎 | `:service` 内置 cpp | `libmcujni.so` | 串口 fd 管理、协议封解包、CS、分帧、字节流读写;直接复用 `test_Serial/protocol.c` 与 `serial_io.c`(CMake include,不复制) |
| **接口契约** | `:contract` | 内部 AAR(被 service 与 sdk 依赖) | AIDL 接口、Parcelable 数据类、`McuStatus` 错误码,**单一真相源** |
| **系统服务** | `:service` | `qsmw-service.apk` | 启动注册、JNI 持有、Tx 序列化、Rx 路由、回调 fan-out、升级状态机 |
| **客户端 SDK** | `:sdk` | `qsmw-sdk.aar` | bindService、断线重连、回调线程封装、面向应用的强类型 API;打包时把 `:contract` 的 AIDL/Parcelable 一并带入 |
| **演示 App** | 独立工程,不在主 multi-module | demo APK + 源码 | 演示如何集成 `qsmw-sdk.aar`,各自一份(OTA/Radio/Vehicle) |

### 2.3 线程模型

**Service 端**

- Binder thread (16):接收 transact,**只做入队**,不做 I/O
- Tx thread (1):从优先级队列消费、调 JNI 写串口、记录 pending ACK 状态
- Rx thread (1):阻塞 read → 解帧 → 投递到分发线程
- Dispatch thread (1):按 (msg_type, cmd) 路由到注册的客户端回调,经 Binder oneway 调用
- Heartbeat thread (1):2s 一次,链路保活

**SDK 端**

- 调用线程:发起 transact(同步阻塞或 Future)
- Binder thread:接收回调,投递到客户端注册的 Looper(默认主线程)

---

## 3. SystemService 启动与注册

### 3.1 注册方式(二选一,推荐方案 A)

**方案 A:作为 framework SystemService(需要甲方修改 frameworks/base)**

`SystemServer.java` 中:
```java
ServiceManager.addService(McuServiceManager.SERVICE_NAME, new McuSystemService(context));
```

**方案 B:作为 privileged APK 自启**

`AndroidManifest.xml`:
```xml
<application android:persistent="true" android:sharedUserId="android.uid.system">
  <service android:name=".McuSystemService"
           android:exported="true"
           android:permission="cn.qsjnic.permission.MCU_ACCESS">
    <intent-filter>
      <action android:name="cn.qsjnic.IMcuService"/>
    </intent-filter>
  </service>
</application>
```

`receiver` 监听 `BOOT_COMPLETED` → `bindService` 永久持有自身。

### 3.2 服务名约定

```java
public final class McuServiceManager {
    public static final String SERVICE_NAME = "mcu_service";
    public static final int    AIDL_VERSION = 1;
}
```

---

## 4. AIDL 接口设计

文件统一放 `McuSdk.aar` 的 `src/main/aidl/cn/qsjnic/mcu/`。

### 4.1 主接口 `IMcuService.aidl`

```aidl
package cn.qsjnic.mcu;

import cn.qsjnic.mcu.IMcuVehicleListener;
import cn.qsjnic.mcu.IMcuRadioListener;
import cn.qsjnic.mcu.IMcuUpgradeListener;
import cn.qsjnic.mcu.VehicleSnapshot;
import cn.qsjnic.mcu.RadioState;
import cn.qsjnic.mcu.McuVersion;
import cn.qsjnic.mcu.UpgradeRequest;

interface IMcuService {

    // === 元信息 ===
    int getAidlVersion();                          // 返回 1
    boolean isLinkAlive();                         // MCU 当前是否可达

    // === Public 域(车辆信号 / 系统) ===
    void requestInitInfo();                        // 0x81 触发 MCU 上报系统状态
    McuVersion getMcuVersion();                    // 同步,首次未拿到时返回缓存或 null
    VehicleSnapshot getLatestVehicleSnapshot();    // 缓存(LINE_DETECT/电压/时间)
    int  setBacklight(int duty1Percent, int duty2Percent);   // 0x84
    int  setTftOnOff(int bitmap);                  // 0x88
    int  setAmpPower(boolean on);                  // 0x8A
    int  setMute(boolean on);                      // 0x82
    int  setTime(in long epochMillis);             // 0x83
    int  requestSleep(int reason);                 // 0x85
    void registerVehicleListener(IMcuVehicleListener cb);
    void unregisterVehicleListener(IMcuVehicleListener cb);

    // === Radio 域 ===
    int  setBand(int band);                        // 0=FM 1=AM,cmd 0x83
    int  setFrequency(int freq, int band);         // 0x84
    int  setFreqRange(int area, int amMin, int amMax, int fmMin, int fmMax,
                      int amStep, int fmStep);     // 0x85
    int  setSwitch(int subType, int value);        // 0x82(stereo/local/...)
    int  setSeek(int mode, int ptyType);           // 0x81
    int  setAntPower(boolean on);                  // 0x86
    int  setRegMute(boolean closeMute);            // 0x87
    RadioState getLatestRadioState();              // 缓存
    void registerRadioListener(IMcuRadioListener cb);
    void unregisterRadioListener(IMcuRadioListener cb);

    // === Update 域(强独占) ===
    int  startUpgrade(in UpgradeRequest req, IMcuUpgradeListener cb);
    int  cancelUpgrade();
    boolean isUpgradeInProgress();

    // === 调试(仅 debug 构建可用)===
    int  sendRawFrame(in byte[] frame);
}
```

**约定**:

- 所有 `int` 返回值:`0` 成功;负值见 `McuStatus`(见第 7 章)
- 命令方法是**链路层 ACK 确认后即返回**的同步语义(SDK 内部包装为可选异步)
- 业务结果(MCU 主动上报或查询响应)走 Listener 异步分发
- Listener 注册可重复,Service 内部用 `RemoteCallbackList` 管理 + `linkToDeath` 自动清理死亡客户端

### 4.2 三个 Listener AIDL

`IMcuVehicleListener.aidl`:
```aidl
oneway interface IMcuVehicleListener {
    void onLineDetectChanged(in LineDetectInfo info);    // 0x01 LINE_DETECT
    void onVoltage(int milliVolt);                       // 0x0D VOLTAGE
    void onSysStatus(int firstPwrOn);                    // 0x05 SYS_STATUS
    void onBacklightChanged(int duty);                   // 0x08 TFT_DUTY
    void onTimeChanged(long epochMillis, boolean isSet); // 0x03 TIME_INFO
    void onMcuVersion(in McuVersion version);            // 0x02 / 0x13
    void onKeyEvent(int keyCode, int action);            // 0x06 KEY_PWR
    void onBpAbnormal(int code);                         // 0x07 BP_ABNORMAL
}
```

`IMcuRadioListener.aidl`:
```aidl
oneway interface IMcuRadioListener {
    void onFreqChanged(in RadioState state);     // 0x01 FREQ
    void onPsPty(in RdsPsPty info);              // 0x02
    void onRadioText(String rt);                 // 0x03
    void onWorkStatus(int status);               // 0x05
}
```

`IMcuUpgradeListener.aidl`:
```aidl
oneway interface IMcuUpgradeListener {
    void onPhase(int phase);            // 0=REQ发出 1=BEGIN发出 2=DATA循环中 3=END发出 4=完成 -1=失败
    void onProgress(int sentPackets, int totalPackets, int percent);
    void onError(int code, String msg);
    void onCompleted(boolean success);
}
```

### 4.3 Parcelable 数据类

`VehicleSnapshot.aidl` / `.java`:

```java
public final class VehicleSnapshot implements Parcelable {
    public LineDetectInfo lineDetect;   // 0x01: ACC/IG/IIL/Back/Aux/Brake/LTurn/RTurn
    public int    voltageMv;            // 0x0D
    public long   timeEpochMillis;      // 0x03
    public boolean timeIsSet;
    public int    firstPwrOn;           // 0x05
    public int    tftDutyPercent;       // 0x08
    public McuVersion mcuVersion;
}

public final class LineDetectInfo implements Parcelable {
    public boolean ig, acc, illumination, reverse, aux, brake, leftTurn, rightTurn;
    // 由 0x01 LINE_DETECT 的 1B bitmap 解析,bit0~bit7
}
```

`RadioState.aidl` / `.java`:

```java
public final class RadioState implements Parcelable {
    public int  band;          // 0=FM 1=AM,来自 FREQ payload byte[2] & 0x0F
    public int  freqValue;     // 8750 = 87.5MHz, AM 单位 KHz
    public int  signalStrength;
    public int  workStatus;    // 来自 WORK_STATUS
}
```

`McuVersion.aidl` / `.java`:
```java
public final class McuVersion implements Parcelable {
    public String hwVersionStr;     // 0x02 → 18B "E112C069XZA0120015"
    public String swVersionStr;     // 0x13 type=0
    public String bootVersionStr;   // 0x13 type=1
}
```

`UpgradeRequest.aidl` / `.java`:
```java
public final class UpgradeRequest implements Parcelable {
    public String binFilePath;     // 必填,绝对路径
    public int    projIdOffset;    // 5 字节 ProjID 在 bin 中的偏移,默认 9280
    public byte   reqType;         // 默认 4 = 请求 MCU 升级,见 xlsx P0
    public int    timeoutMs;       // 单帧 ACK 超时,默认 3000
    public int    maxRetries;      // 单帧重试次数,默认 2
}
```

---

## 5. McuSdk.aar 客户端 API

### 5.1 入口与生命周期

```java
public final class McuClient {
    public static McuClient getInstance();          // 单例,首次自动 bind

    public void init(Context appContext);           // 必须先调用,内部 bindService
    public void release();

    public boolean isReady();                       // Service 已 bound 且 link alive
    public void addReadyListener(ReadyListener cb); // 上电/重连时回调
}
```

bindService 失败/Service 死亡时,SDK 内置指数退避重连(1s/2s/4s/8s/最多 30s),重连成功后:

1. 重新 `registerXxxListener` 所有原有回调
2. 通过 `ReadyListener.onReady()` 通知 App 业务层
3. 重连前的 in-flight 请求统一以 `McuStatus.RECONNECTING` 失败,App 自行决定重发

### 5.2 域 Facade

```java
McuClient.vehicle().setBacklight(50, 50);
McuClient.vehicle().requestInitInfo();
McuClient.vehicle().registerListener(new VehicleListener() {
    @Override public void onLineDetectChanged(LineDetectInfo i) { ... }
    @Override public void onVoltage(int mv) { ... }
});

McuClient.radio().setFrequency(8750, Band.FM);
McuClient.radio().seekForward();

McuClient.upgrade().start(req, new UpgradeListener() {
    @Override public void onProgress(int sent, int total, int pct) { ... }
    @Override public void onCompleted(boolean ok) { ... }
});
```

`Listener` 和 `IXxxListener.Stub` 的关系:SDK 内部把 Java Listener 包成 Stub 持有,失败/release 时反注册 Service 端,避免内存泄漏。

---

## 6. 升级流程的特殊设计

### 6.1 排他模式

调用 `startUpgrade()` 后,Service 进入 `UPGRADING` 状态:

- 拒绝其他 App 的 Public/Radio 写命令(返回 `McuStatus.UPGRADE_BUSY`)
- 仅允许:心跳、`isUpgradeInProgress`、`cancelUpgrade`、`isLinkAlive`
- MCU 主动上报的 Public Notify(电压等)继续转发,不影响其他 App 的 read-only 监听

### 6.2 状态机(对应 `test_update_real()`)

```
IDLE
 │ startUpgrade()
 ▼
SEND_REQ ──→ WAIT_REQ_ACK ──→ SEND_BEGIN ──→ WAIT_BEGIN_ACK
                                                  │
                                                  ▼
                                          WAIT_LEN_AND_IND
                                                  │
                                          DATA_LOOP ◀────┐
                                                  │      │ next IND_AQUIRE
                                                  ▼      │
                                          SEND_DATA(idx) ┘
                                                  │
                                                  │ MCU LAST_FRAME_OK(0x05)
                                                  ▼
                                          SEND_END(CRC)
                                                  │
                                                  ▼
                                          WAIT_FINAL_ACK
                                                  │
                          ┌───────────────────────┴────────────────────────┐
                          ▼ SUCCESS(3)                                     ▼ FAIL(4) 或 timeout
                       COMPLETED                                          ABORTED
                          │                                                 │
                          └────────────────→ IDLE ◀───────────────────────┘
```

### 6.3 失败恢复

- 单帧 DATA 超时 ≤ retries → 直接重发同 idx
- 全部 retry 用完 → `onError(TIMEOUT_DATA)` + 退出 UPGRADING(进入 ABORTED 后回 IDLE)
- App 取消 → 发 `cancelUpgrade()`,Service 不主动通知 MCU,直接回 IDLE 并断开链路重连(规避 MCU 卡死)

> **当前已知问题**(见 `doc/ota_evidence/`):MCU `E112C069XZA0120015` 在收到首个 1035B DATA 后死锁。在硬件修复前,SystemService 上线**先关掉真升级 API**(返回 `NOT_IMPLEMENTED`),只保留协议握手测试入口。

---

## 7. 错误码与权限

### 7.1 `McuStatus`

```java
public final class McuStatus {
    public static final int OK                  =  0;
    public static final int ERR_LINK_DOWN       = -1;  // 链路未建立或已断
    public static final int ERR_TIMEOUT         = -2;  // 等 ACK / 业务响应超时
    public static final int ERR_PROTOCOL        = -3;  // CS / 长度 / 格式错
    public static final int ERR_SERVICE         = -4;  // Service 未 ready
    public static final int ERR_PERMISSION      = -5;  // 调用方无 MCU_ACCESS
    public static final int ERR_UPGRADE_BUSY    = -6;  // 升级中,拒绝其他写命令
    public static final int ERR_INVALID_ARG     = -7;
    public static final int ERR_RECONNECTING    = -8;  // SDK 重连中,稍后重试
    public static final int ERR_NOT_IMPLEMENTED = -9;
}
```

### 7.2 权限

声明:

```xml
<permission android:name="cn.qsjnic.permission.MCU_ACCESS"
            android:protectionLevel="signature|privileged" />
```

应用清单:
```xml
<uses-permission android:name="cn.qsjnic.permission.MCU_ACCESS"/>
```

Service 端在每次 transact 入口校验:

```java
@Override public int setBacklight(int d1, int d2) {
    enforceCallingPermission(MCU_ACCESS);
    ...
}
```

`signature|privileged` 意味着只有**与系统同签名**或**预置在 priv-app**的 App 能调用。第三方 App 无法绕过。

---

## 8. 端到端延迟预算

| 段 | 预算 | 说明 |
|---|---|---|
| App API → Binder transact 入队 | < 2 ms | 同进程 stub |
| Binder IPC | < 5 ms | 跨进程 |
| Service 入 Tx 队列 → Tx 线程消费 | < 3 ms | 锁竞争 |
| JNI 调用 + protocol_pack_frame | < 2 ms | 纯 CPU |
| serial_write + tcdrain(7B 帧) | < 5 ms | 7B @ 460800 ≈ 0.15ms,tcdrain 是大头 |
| serial_write + tcdrain(1035B 帧) | < 35 ms | 22ms 物理 + 13ms tcdrain |
| **总计(普通命令)** | **< 20 ms** | 远小于 50 ms 上限 |
| **总计(升级 DATA)** | **< 50 ms** | 单帧符合 |

延迟监测埋点:

- Tx 队列入队/出队 timestamp
- 串口 write() 完成 timestamp
- 客户端 SDK 调用前/Binder 返回 timestamp

`adb shell dumpsys mcu_service` 输出最近 N 次的延迟统计,作为验收报告材料。

---

## 9. 验收测试映射

| 补充条款验收点 | 落地方法 |
|---|---|
| 多进程隔离 | 写测试 App × 3,各自注册不同 Listener,断言不串台 |
| 服务异常重启自愈 | `kill -9` Service 进程,SDK 应自动重连,Listener 重新生效 |
| 端到端延迟 ≤ 50ms | dumpsys 输出统计;CTS-style 测试用例 |
| 权限控制 | 用一个无 MCU_ACCESS 的测试 App 尝试调用,断言返回 ERR_PERMISSION |
| 协议保真 | 沿用 `test_Serial/qsjnic_test`,Service 替换原 raw 程序,跑 Public+Radio 全 PASS |

---

## 10. 与 `test_Serial/` 的复用关系

`test_Serial/` 工程**保持不动**,作为已验证的 C raw 测试工具继续使用。
`service/src/main/cpp/CMakeLists.txt` 通过相对路径 include `test_Serial/` 下的源,**不做物理复制**:

```cmake
# service/src/main/cpp/CMakeLists.txt 片段
add_library(mcujni SHARED
    jni_bridge.cpp
    link_state_machine.c
    frame_dispatcher.c
    ../../../../test_Serial/protocol.c
    ../../../../test_Serial/serial_io.c)
target_include_directories(mcujni PRIVATE
    ../../../../test_Serial)
```

| `test_Serial/` 文件 | 在 service 模块中的角色 |
|---|---|
| `protocol.c/.h` | 直接 include 编进 `libmcujni.so`,封解包共用 |
| `serial_io.c/.h` | 同上 |
| `main_test.c` 的链路状态逻辑 | 拆出 `service/src/main/cpp/link_state_machine.c`,封成 `McuLink` struct,允许多实例 |
| `main_test.c` 的 RX 路由 | 拆出 `service/src/main/cpp/frame_dispatcher.c`,通过 JNI 回调投递给 Java |
| `main_test.c` 作为程序 | 保留为 `test_Serial/qsjnic_test`,Service 上线后仍可作为底层 raw 工具诊断协议问题 |

复用时把全局变量(`current_seq`, `is_linked` 等)封进 `McuLink` struct,既支持多实例(为单元测试),也避免与 `qsjnic_test` 进程同时跑时干扰(Service 持有 fd 时 raw 工具自然跑不起来,符合预期)。

---

## 11. 待定 / 后续

1. **升级 CRC 算法**:`UPDATE_END` 的 4 字节 CRC,现 spec 未明,等 MCU 侧确认(目前用 0)
2. **MCU 0x82 死锁修复**:阻塞真升级 API 上线,等硬件回复
3. **多 MCU 支持**:当前假设单串口,如果以后挂多个 MCU,Service 需带路由(`/dev/ttyS7`/`/dev/ttyS8`...)
4. **Binder oneway 队列容量**:Notify 帧高频(电压每 100ms),需评估是否要在 Service 端做合流/降频后再分发
5. **升级断点续传**:目前每次从 idx=0 开始,后续可与 MCU 协商持久化 idx

---

## 12. 交付清单(对应补充条款)

详细交付边界(谁拿到什么、源码是否随附)见 `doc/工程结构与交付清单.md`,此处仅列对应物:

| 补充条款交付项 | 本工程对应 | 产物 | 状态 |
|---|---|---|---|
| McuSystemService APK / 系统内置包 | `:service` 模块 | `release/qsmw-service.apk` | 待开发 |
| 更新版 McuSdk.aar(基于 Binder) | `:sdk` 模块 | `release/qsmw-sdk.aar`(内含 `:contract` 的 AIDL/Parcelable) | 待开发 |
| AIDL 接口契约 | `:contract` 模块 | 内部 AAR,被 service/sdk 依赖,客户通过 sdk.aar 间接获得 | 待开发 |
| 《SystemService 集成说明》 | `doc/SystemService集成说明.md` | 文档 | 待写 |
| OTA / Radio / Vehicle 演示 | `demos/ota-demo`、`demos/radio-demo`、`demos/vehicle-demo` | 各自一个独立 AS 工程,源码 + APK | 待开发 |
| Raw 协议测试工具(用于硬件联调) | `test_Serial/qsjnic_test` | C 可执行文件 | **已完成** |
| 测试证据(OTA DATA 帧死锁) | `doc/ota_evidence/` | log + README | **已完成** |
| 验收用例报告模板 | `doc/验收测试报告模板.md` | 文档 | 待写 |
