# 欧姆龙 J735 蓝牙血压计 — 实施计划

---

## 一、当前状态

- 设备已购：欧姆龙 J735
- 前端占位：`DeviceManagementPage` 目前是空壳（显示"暂无绑定设备"），路由 `devices` 已注册，入口在 ProfilePage → 设备管理
- 项目风格：淡紫清新风（`AppTheme`），Material 3 + shadcn_ui + Riverpod
- 本地存储：SQLite（`LocalDatabase`，kv_store 表）
- 后端：已有 `POST /api/health-records` 支持 BloodPressure 类型，Source 枚举需加 `Device`

---

## 二、实施范围

### 2.1 新增依赖

```yaml
# pubspec.yaml
flutter_blue_plus: ^1.34.0    # BLE 核心
permission_handler: ^11.3.0    # 蓝牙权限
```

### 2.2 新增文件（6 个）

```
health_app/lib/
├── services/
│   └── omron_ble_service.dart    # BLE 连接 + SFLOAT 协议解析
├── models/
│   └── bp_reading.dart           # 血压数据模型
├── providers/
│   └── omron_device_provider.dart # 设备绑定状态 + 实时读数
├── pages/
│   └── device/
│       ├── device_scan_page.dart  # 扫描设备页
│       └── device_bind_page.dart  # 已绑定设备管理页（替换空壳）
```

### 2.3 修改文件（3 个）

| 文件 | 改动内容 |
|------|---------|
| `pages/remaining_pages.dart` | 替换 `DeviceManagementPage` 空壳为 `DeviceBindPage` |
| `core/app_router.dart` | `devices` 路由指向新页面 |
| `providers/data_providers.dart` | 新增 `omronBleServiceProvider`、`bpReadingsProvider` |
| 后端 `health_enums.cs` | `HealthRecordSource` 加 `Device` |

---

## 三、分步实施（3 天）

### Day 1 — BLE 服务层

**目标**：手机能搜到 J735、连接上、收到蓝牙数据并正确解析

1. 添加 `flutter_blue_plus` + `permission_handler` 依赖
2. 配置 Android 蓝牙权限（`AndroidManifest.xml`）+ iOS 权限（`Info.plist`）
3. 实现 `BpReading` 数据模型（systolic/diastolic/pulse/timestamp/status）
4. 实现 `OmronBleService`：
   - `scan()` — 过滤蓝牙名含 `OMRON`/`HEM`/`BLEsmart` 且有 `0x1810` 服务的设备
   - `connect()` — 连接 → discoverServices → 订阅 `0x2A35` Indicate
   - `_parseReading()` — SFLOAT 解码 + Flags 解析（kPa 转换、体动、心律等）
   - `_syncTime()` — 写入 `0x2A08` 同步时间
   - `disconnect()` / `dispose()`
5. 用 nRF Connect 抓包验证原始 HEX → SFLOAT 解码结果与屏幕一致

**产出**：`OmronBleService` 可独立运行，单元测试验证 SFLOAT 解码

### Day 2 — UI 页面

**目标**：实现扫描绑定页 + 已绑定管理页，风格对齐项目淡紫主题

#### 扫描绑定页 (`DeviceScanPage`)
- AppBar 标题"添加血压计"，返回按钮
- 顶部：扫描状态指示（转圈 + "正在扫描..." / "扫描完成，发现 N 台设备"）
- 空状态：未发现设备时显示使用说明卡片（装电池 → 长按蓝牙键 → 手机靠近 → 检查权限）
- 设备列表：每行显示 BLE 设备名 + 信号强度 + MAC + "连接"按钮
- 连接中：按钮变 loading
- 连接成功：SnackBar "已连接 OMRON xxx"，返回上一页并传递设备信息
- 底部：重新扫描按钮

#### 已绑定管理页 (`DeviceBindPage`，替换空壳)
- **未绑定状态**：大蓝牙图标 + "未绑定设备" + 说明文字 + "添加设备"按钮，点击跳扫描页
- **已绑定状态**：白底圆角卡片
  - 设备图标（紫色渐变圆）+ 设备名
  - MAC 地址（灰色小字）
  - 上次同步时间
  - "解绑设备"按钮（红色文字）

**风格要点**（保证不出现"全黑"）：
- 背景色：`AppTheme.bg`（淡紫白 `#FAF9FF`）
- 卡片：白色 `Colors.white`，圆角 `AppTheme.rLg`（20），`AppTheme.shadowCard` 阴影
- 按钮：紫色实心（`AppTheme.primary`）+ 圆角 `AppTheme.rPill`
- 空状态图标：`Colors.grey[300]`，文字 `AppTheme.textHint`
- 使用 shadcn_ui 图标（`LucideIcons`）
- 与 SettingsPage、ProfilePage 保持一致的 AppBar 风格

**产出**：两个 UI 页面，风格与项目统一，从 ProfilePage → 设备管理可进入

### Day 3 — 数据同步 + 联调

1. **Provider 层**：`omronDeviceProvider` 管理绑定状态（MAC/name/lastSync 存入 SQLite）
2. **自动同步**：收到 BLE 读数后自动 `POST /api/health-records`
   ```dart
   {
     'type': 'BloodPressure',
     'systolic': reading.systolic,
     'diastolic': reading.diastolic,
     'source': 'Device',
     'unit': 'mmHg',
     'recordedAt': reading.timestamp.toUtc().toIso8601String(),
   }
   ```
3. **去重逻辑**：同一时间戳（精确到秒）+ 同一值 → 跳过
4. **健康概览刷新**：同步后 `ref.invalidate(latestHealthProvider)`
5. **真机 + J735 联调**：
   - 扫描 → 连接 → 测量 → 数据上传 → 健康概览显示新数据
   - 断连 → 重新打开 App → 再次连接
   - iOS 和 Android 双平台测试

---

## 四、交互流程

```
用户操作                        App 行为
────────                      ────────
设置 → 蓝牙血压计             打开 DeviceBindPage（已绑定/未绑定）
  │
  ├─ 未绑定
  │   └─ 点"添加设备"          打开 DeviceScanPage
  │       └─ 扫到 J735         点击"连接"
  │           └─ 连接成功      返回 DeviceBindPage（已绑定状态）
  │
  └─ 已绑定
      └─ 血压计测量            血压计按开始键 → 测量完成
          │                    BLE Indicate 推送数据
          │                    App 收到 120/80
          │                    SnackBar: "已同步: 120/80 mmHg"
          │                    自动写入 HealthRecords
          │                    健康概览刷新
          └─ 查看              点"健康概览"可看到新数据
```

---

## 五、预期效果

- 从 ProfilePage → 设备管理，不再是全黑空壳，而是漂亮的设备管理页面
- 点击"添加设备"打开扫描页，15 秒内搜索到 J735
- 点击"连接" 2 秒内完成绑定
- J735 测量完毕后，App 自动收到数据（无需手动操作）
- 数据自动写入健康记录，健康概览实时刷新
- 同一数据不重复录入
- 重新打开 App 后已绑定设备状态保留
- iOS + Android 双平台均可使用

---

## 六、风险点

| 风险 | 应对 |
|------|------|
| J735 蓝牙名称与预期不符 | 不硬编码名称，用 `0x1810` 服务 UUID 过滤；首次连接后打印实际名称 |
| Android 12+ 蓝牙权限弹窗被拒 | `permission_handler` 检测权限状态，引导用户去设置开启 |
| 设备已被系统蓝牙占用 | 扫描页提示"如搜不到请先取消系统蓝牙配对" |
| SFLOAT 解码与屏幕值有偏差 | 用 nRF Connect 抓 HEX 对比，误差 > 1 则调整解码 |
| iOS BLE 后台断连 | 前台连接同步，完成后断开；不做后台持久连接 |
