# NFC 中间件开发需求文档

## 1. 项目概述

在 QSJNIC 车载中间件（v1.0.0-beta7）基础上，新增 NFC 读卡模块。BSP 提供 `/dev/nfc_dev` 设备节点，中间件负责封装协议、管理读卡生命周期、向应用层分发 NFC 事件。

**技术方案：** 应用层 Service + SDK 双通道回调（不改动 Linux 驱动 / Framework / VHAL）。

---

## 2. 术语定义

| 术语 | 说明 |
|------|------|
| BSP | Board Support Package，已提供 `si522a.c` 驱动及 `/dev/nfc_dev` |
| UID | NFC 卡片唯一标识符 |
| ATQA | 应答类型 A，卡片响应类型标识 |
| SAK | 选择确认，卡片容量及协议标识 |
| e-money | 电子钱包卡，ISO14443 Type A（MIFARE Classic / MIFARE Ultralight） |

---

## 3. 需求范围

### 3.1 在本次范围内的

- Service 层 JNI 封装 `/dev/nfc_dev` 读写
- Service 层 Java 读卡状态机管理（轮询/检测/回调）
- Contract 层 AIDL 接口及数据结构定义
- SDK 层封装（AIDL 注册 + 广播接收辅助）
- 双通道事件分发（AIDL 精确回调 + Intent 广播，AIDL 消费后阻断广播）
- NFC Demo 应用（第 4 个 demo）
- 单元测试及集成测试



## 4. 功能需求

### 4.1 数据协议（BSP 已定义）

**读数据协议：**

| 字节偏移 | 长度 | 含义 |
|---------|------|------|
| 0 | 1 | 数据类型：0=UID，1=ATQA，2=SAK |
| 1 | 1 | 状态：1=正常，0=失败 |
| 2-3 | 2 | 数据长度，高字节在前 |
| 4 ~ (4+len-1) | len | 数据内容 |

**写数据协议：**

| 字节偏移 | 长度 | 含义 |
|---------|------|------|
| 0 | 1 | 数据类型 |
| 1-2 | 2 | 数据长度，高字节在前 |
| 3 ~ (3+len-1) | len | 数据内容 |

> 注：本次仅实现读卡，不实现写卡。如需写卡后续可扩展。

### 4.2 Service 层（系统服务）

#### 4.2.1 JNI 层（C）

- 打开 `/dev/nfc_dev`（阻塞模式 read）
- 按读协议解析 UID / ATQA / SAK
- 异常处理：设备不存在 / 权限不足 / read 超时 / 数据校验失败

#### 4.2.2 Java Feature 层

- `NfcFeature` 类，接入现有 Feature 框架
- 读卡状态机：空闲 → 检测中 → 已读卡 → 卡片离开 → 空闲
- 去重防抖：同一张卡持续贴住天线，只触发一次 `onTagFound`，直到卡片离开
- 卡片离开检测：连续 **3 次**轮询未读到卡，触发 `onTagLost`
- 轮询间隔：**100ms**（与驱动 `task_nfc_fn` 保持一致）

### 4.3 双通道事件分发

Service 读到有效 UID 后，**先走 AIDL 回调，再走广播**。

**通道一：AIDL 精确回调**

```java
interface INfcListener {
    void onTagFound(in NfcCardInfo card);
    void onTagLost();
}
```

- 通过 `RemoteCallbackList` 管理已 bindService 的 App
- 支持前台 App 调用 `consume()` 标记事件已处理
- `consume()` 后阻断广播通道，本次 NFC 事件不再通过 Intent 发送
- 适合：前台门禁 App、车辆设置 App 等需要精确控制、避免后台重复响应的场景

**通道二：Intent 广播**

```
Action: com.qsjnic.mcu.action.NFC_TAG_FOUND
Extra:   uid (String), tagType (int), atqa (byte[]), sak (byte)
```

- 带自定义权限保护，不限制包名白名单
- 仅当没有任何 AIDL 消费者调用 `consume()` 时才发送
- 适合：没有集成 SDK 的第三方 App、后台轻量接入

### 4.4 历史记录缓存

- Service 层维护最近 **50 条**读卡记录环形缓存
- 新绑定的 App 可通过 AIDL 接口查询历史记录
- 缓存内容：UID、ATQA、SAK、时间戳、tagType

### 4.5 Contract 层（AIDL 接口定义）

- `INfcListener.aidl` — 回调接口
- `NfcCardInfo.aidl` / `NfcCardInfo.java` — Parcelable 数据结构
- `IMcuService.aidl` — 扩展 NFC 相关方法（注册/注销监听器、查询历史记录等）
- Intent Action 常量定义

**NfcCardInfo 字段：**

| 字段 | 类型 | 说明 |
|------|------|------|
| uid | byte[] / String | 卡片 UID |
| atqa | byte[] | ATQA 数据 |
| sak | byte | SAK 数据 |
| timestamp | long | 读卡时间戳 |
| tagType | int | 0=MIFARE Classic, 1=MIFARE Ultralight |

### 4.6 SDK 层（应用接入）

- `McuNfcClient` — AIDL 通道注册/注销监听器、查询历史记录
- `NfcBroadcastHelper` — 广播通道封装，一行代码注册 Receiver
- `NfcCardInfo` 解析工具类
- 连接状态管理（Service 断开重连）

### 4.7 Demo 应用（qsmw-nfc-demo）

在现有 radio/vehicle/ota 3 个 demo 基础上，新增第 4 个：

- 前台界面：展示最近读到的 UID / ATQA / SAK，贴卡实时刷新
- 历史记录：展示最近 50 条读卡记录
- 后台唤醒：通过广播 Receiver 被 NFC 事件唤醒，弹 Toast 或通知
- 代码示例：展示 AIDL 回调（consume 阻断广播）和广播两种接入方式

---

## 5. 非功能需求

### 5.1 性能

- 读卡检测延迟 < 500ms（从贴卡到 App 收到回调）
- Service 层轮询不能明显占用 CPU

### 5.2 安全性

- `/dev/nfc_dev` 访问权限检查（SELinux Permissive 下 root 可运行）
- Intent 广播带自定义权限，防止未授权应用监听 NFC 事件
- AIDL 接口仅对 SystemService / 已签名的 SDK 开放

### 5.3 兼容性

- Android API 34（Android 14）
- aarch64 架构
- 内核 5.15.192

---

## 6. 测试需求

### 6.1 单元测试

- `NfcFeatureTest`：状态机转换、去重逻辑（3次防抖）、卡片离开检测、consume 阻断广播
- 协议解析测试：UID / ATQA / SAK 各类型数据边界

### 6.2 集成测试

- 双通道并发测试：AIDL 回调 + 广播同时注册，验证 consume 阻断逻辑
- 历史记录测试：查询缓存、新绑定 App 能否获取历史
- 异常测试：设备节点不存在 / 权限不足 / 驱动返回错误状态

### 6.3 现场联调

- 实体 e-money 卡
- iPhone NFC 模拟卡
- 不同贴卡位置、角度、距离

---

## 7. 交付物

| 序号 | 交付物 | 说明 |
|------|--------|------|
| 1 | qsmw-contract.aar | 增量更新，含 NFC AIDL + 数据结构 + Intent 常量 |
| 2 | qsmw-service.apk | 增量更新，含 JNI 层 + NfcFeature + 双通道分发（consume 阻断广播）+ 历史缓存 |
| 3 | qsmw-sdk.aar | 增量更新，含 McuNfcClient + NfcBroadcastHelper + 历史查询 |
| 4 | qsmw-nfc-demo.apk | 新增，第 4 个 demo |
| 5 | 测试包 | NfcFeatureTest + 集成测试用例 |
| 6 | 文档 | API 文档 + 协议说明 + 双通道集成指南 |

---

## 8. 甲方前提条件

1. 提供已修复 `si522a.c`（RxSelReg 设置）的刷机镜像 `boot.img`
2. 提供 NFC 天线位置图纸
3. 提供实体 e-money 卡 + 支持 NFC 模拟的手机（iPhone / Android）
4. 提供稳定的 ADB 调试环境（IP 固定、root、SELinux 可关闭）
