# @gaozh1024/rn-veepoo-sdk

Expo 模块，用于 Veepoo 设备蓝牙连接和健康数据交互。

当前包默认导出已经切换为**新的统一接口**：

- TS 层统一返回结构：`UnifiedResult<T>`
- Android / iOS 字段差异统一收敛
- 仅提供**临时内存缓存**
- 所有读取能力都支持统一读取策略
- 所有测量能力都走统一 `startMeasurement / stopMeasurement`

> 包入口已经切到统一接口，业务侧应只面向新接口开发。

---

## 安装

```bash
npm install @gaozh1024/rn-veepoo-sdk
```

---

## 平台要求

| 平台 | 最低版本 | Expo Go | 说明 |
| --- | --- | --- | --- |
| iOS | 13.4+ | ❌ | 需要开发构建 |
| Android | 6.0+ | ❌ | 需要开发构建 |

---

## 配置

### iOS

在 `app.json` / `app.config.js` 中添加插件：

```json
{
  "expo": {
    "plugins": [
      [
        "@gaozh1024/rn-veepoo-sdk",
        {
          "bluetoothAlwaysPermission": "需要蓝牙权限来连接设备",
          "bluetoothPeripheralPermission": "需要蓝牙权限来扫描设备"
        }
      ]
    ]
  }
}
```

然后执行：

```bash
npx expo prebuild --clean
cd ios && pod install && cd ..
npx expo run:ios
```

### Android

Android 权限自动配置，无需额外修改。

---

## 快速开始

```ts
import VeepooSDK from '@gaozh1024/rn-veepoo-sdk';

await VeepooSDK.init();

const bluetooth = await VeepooSDK.getBluetoothStatus();
if (bluetooth.state !== 'poweredOn') {
  throw new Error('请先打开蓝牙');
}

const permission = await VeepooSDK.requestPermissions();
if (!permission.granted) {
  throw new Error('请先授予蓝牙权限');
}

VeepooSDK.on('scan.deviceFound', ({ device }) => {
  console.log('发现设备', device);
});

await VeepooSDK.startScan({ timeout: 10000 });

const connected = await VeepooSDK.connect('device-id', { password: '0000' });
console.log('连接结果', connected.data);

const battery = await VeepooSDK.getBatteryInfo({
  strategy: 'device-first',
});
console.log('电量', battery.data.level, battery.source);

await VeepooSDK.startMeasurement('heartRate');

VeepooSDK.on('measurement.updated', ({ type, state }) => {
  if (type === 'heartRate') {
    console.log('心率状态', state.state, state.result);
  }
});
```

---

## 统一接口设计

### 1. 统一返回结构

所有查询型接口统一返回：

```ts
type UnifiedResult<T> = {
  data: T;
  source: 'device' | 'memory-cache' | 'storage-cache';
  deviceId?: string;
  timestamp: number;
  cachedAt?: number;
  stale?: boolean;
};
```

当前实现里：

- `device`：直接来自设备
- `memory-cache`：来自内存临时缓存
- `storage-cache`：类型预留，**当前未启用持久化**

### 2. 统一读取策略

所有读取接口支持：

```ts
type ReadStrategy =
  | 'device-only'
  | 'cache-only'
  | 'cache-first'
  | 'device-first';
```

示例：

```ts
await VeepooSDK.getSleepData('2026-03-23', {
  strategy: 'cache-first',
  maxAgeMs: 5 * 60 * 1000,
});
```

### 3. 仅内存缓存

当前缓存是**临时内存缓存**：

- App 进程结束即失效
- 不落盘
- 不做持久化恢复
- 适合“先读缓存，再补设备数据”的链路

---

## 核心接口

### 初始化与连接

```ts
await VeepooSDK.init();
VeepooSDK.getSDKState();
await VeepooSDK.getBluetoothStatus();
await VeepooSDK.requestPermissions();

await VeepooSDK.startScan();
await VeepooSDK.stopScan();

await VeepooSDK.connect(deviceId, { password: '0000' });
await VeepooSDK.disconnect(deviceId);
await VeepooSDK.getConnectionState(deviceId);
await VeepooSDK.verifyPassword('0000', false);
```

### 设备信息

```ts
await VeepooSDK.getDeviceProfile();
await VeepooSDK.getBatteryInfo();
await VeepooSDK.getDeviceFunctions();
await VeepooSDK.getDeviceVersion();
await VeepooSDK.getSocialMessageSettings();
```

### 数据同步与读取

```ts
await VeepooSDK.syncHealthData({
  include: ['sleep', 'sport', 'origin5m', 'origin30m', 'daySummary', 'spo2Origin'],
  saveToCache: true,
});

VeepooSDK.getLastSyncState();
await VeepooSDK.cancelSyncHealthData();

await VeepooSDK.getSleepData(date);
await VeepooSDK.getSportData(date);
await VeepooSDK.getOrigin5mData(date);
await VeepooSDK.getOrigin30mData(date);
await VeepooSDK.getDaySummaryData(date);
await VeepooSDK.getSpo2OriginData(date);
await VeepooSDK.getDailyHealthSnapshot(date);

await VeepooSDK.getHeartRateList(date);
await VeepooSDK.getHeartRateItem({ date, time: '10:30' });
await VeepooSDK.getLatestHeartRate(date);

await VeepooSDK.getBloodPressureList(date);
await VeepooSDK.getBloodPressureItem({ date, time: '10:30' });
await VeepooSDK.getLatestBloodPressure(date);

await VeepooSDK.getBloodOxygenList(date);
await VeepooSDK.getBloodOxygenItem({ date, time: '10:30' });
await VeepooSDK.getLatestBloodOxygen(date);

await VeepooSDK.getTemperatureList(date);
await VeepooSDK.getTemperatureItem({ date, time: '10:30' });
await VeepooSDK.getLatestTemperature(date);

await VeepooSDK.getStressList(date);
await VeepooSDK.getStressItem({ date, time: '10:30' });
await VeepooSDK.getLatestStress(date);

await VeepooSDK.getBloodGlucoseList(date);
await VeepooSDK.getBloodGlucoseItem({ date, time: '10:30' });
await VeepooSDK.getLatestBloodGlucose(date);

await VeepooSDK.getSportList(date);
await VeepooSDK.getSportItem({ date, time: '10:30' });
await VeepooSDK.getLatestSport(date);
```

### 指标化统一接口

针对心率、血压、血氧、体温、压力、血糖、运动，统一层现在都提供三类方法：

- `getXxxList(date, options)`：获取某一天的指标列表
- `getXxxItem({ date, time | timestamp }, options)`：按时间点获取单条指标
- `getLatestXxx(date?, options)`：获取最新一条指标

这些接口优先复用统一层已经归一化后的：

- `origin5m`
- `origin30m`
- `daySummary`
- `spo2Origin`
- 最近一次测量缓存

因此业务侧不需要再分别处理 Android / iOS 的差异字段。

### 测量

```ts
await VeepooSDK.startMeasurement('heartRate');
await VeepooSDK.startMeasurement('bloodPressure');
await VeepooSDK.startMeasurement('bloodOxygen');
await VeepooSDK.startMeasurement('temperature');
await VeepooSDK.startMeasurement('stress');
await VeepooSDK.startMeasurement('bloodGlucose');

await VeepooSDK.stopMeasurement('heartRate');

VeepooSDK.getMeasurementState('stress');
await VeepooSDK.getLastMeasurementResult('bloodGlucose');
```

### 设置类能力

```ts
await VeepooSDK.syncPersonalInfo(info);
await VeepooSDK.getAutoMeasureSettings();
await VeepooSDK.updateAutoMeasureSetting(setting);
await VeepooSDK.setLanguage('english');
```

### 缓存与诊断

```ts
VeepooSDK.getCacheStatus();
VeepooSDK.getCachedValue({ key: 'sleep:device-id:2026-03-23' });
await VeepooSDK.clearCache();
VeepooSDK.invalidateCache('sleep:device-id:2026-03-23');
await VeepooSDK.warmupCache({ include: ['sleep', 'sport'] });
await VeepooSDK.getDiagnostics();
```

### 完整统一接口清单

> 下表对应当前对外暴露的统一接口；业务接入时可以只面向这一层开发。

| 分类 | 接口 | 返回/说明 |
| --- | --- | --- |
| 生命周期 | `init()` | 初始化 SDK |
| 生命周期 | `destroy()` | 销毁 SDK |
| 状态 | `getSDKState()` | `SDKState` |
| 蓝牙/权限 | `getBluetoothStatus()` | `Promise<BluetoothStatus>` |
| 蓝牙/权限 | `requestPermissions()` | `Promise<PermissionsResult>` |
| 扫描 | `startScan(options?)` | 开始扫描 |
| 扫描 | `stopScan()` | 停止扫描 |
| 连接 | `connect(deviceId, options?)` | `Promise<UnifiedResult<ConnectionSession>>` |
| 连接 | `disconnect(deviceId?)` | 断开连接 |
| 连接 | `getConnectionState(deviceId?)` | `Promise<ConnectionSnapshot>` |
| 连接 | `verifyPassword(password?, is24Hour?)` | `Promise<UnifiedResult<PasswordData>>` |
| 设备信息 | `getDeviceProfile(options?)` | `UnifiedResult<DeviceProfile>` |
| 设备信息 | `getBatteryInfo(options?)` | `UnifiedResult<BatteryInfo>` |
| 设备信息 | `getDeviceFunctions(options?)` | `UnifiedResult<DeviceFunctions>` |
| 设备信息 | `getDeviceVersion(options?)` | `UnifiedResult<DeviceVersion>` |
| 设备信息 | `getSocialMessageSettings(options?)` | `UnifiedResult<SocialMsgData>` |
| 同步 | `syncHealthData(options?)` | `Promise<SyncTaskResult>` |
| 同步 | `cancelSyncHealthData(taskId?)` | 取消同步 |
| 同步 | `getLastSyncState()` | `SyncState` |
| 日数据 | `getSleepData(date?, options?)` | `UnifiedResult<SleepData>` |
| 日数据 | `getSportData(date?, options?)` | `UnifiedResult<SportStepData>` |
| 日数据 | `getOrigin5mData(date?, options?)` | `UnifiedResult<OriginData[]>` |
| 日数据 | `getOrigin30mData(date?, options?)` | `UnifiedResult<HalfHourData[]>` |
| 日数据 | `getDaySummaryData(date?, options?)` | `UnifiedResult<DaySummaryData>` |
| 日数据 | `getSpo2OriginData(date?, options?)` | `UnifiedResult<Spo2OriginData[]>` |
| 日数据 | `getDailyHealthSnapshot(date?, options?)` | `UnifiedResult<DailyHealthSnapshot>` |
| 指标化接口 | `getHeartRateList / getHeartRateItem / getLatestHeartRate` | 心率列表 / 单项 / 最新 |
| 指标化接口 | `getBloodPressureList / getBloodPressureItem / getLatestBloodPressure` | 血压列表 / 单项 / 最新 |
| 指标化接口 | `getBloodOxygenList / getBloodOxygenItem / getLatestBloodOxygen` | 血氧列表 / 单项 / 最新 |
| 指标化接口 | `getTemperatureList / getTemperatureItem / getLatestTemperature` | 体温列表 / 单项 / 最新 |
| 指标化接口 | `getStressList / getStressItem / getLatestStress` | 压力列表 / 单项 / 最新 |
| 指标化接口 | `getBloodGlucoseList / getBloodGlucoseItem / getLatestBloodGlucose` | 血糖列表 / 单项 / 最新 |
| 指标化接口 | `getSportList / getSportItem / getLatestSport` | 运动列表 / 单项 / 最新 |
| 测量 | `startMeasurement(type)` | 启动统一测量 |
| 测量 | `stopMeasurement(type)` | 停止统一测量 |
| 测量 | `getMeasurementState(type)` | 本地测量状态快照 |
| 测量 | `getLastMeasurementResult(type, options?)` | 最近一次测量结果 |
| 设置 | `syncPersonalInfo(info)` | `Promise<boolean>` |
| 设置 | `getAutoMeasureSettings(options?)` | `UnifiedResult<AutoMeasureSetting[]>` |
| 设置 | `updateAutoMeasureSetting(setting)` | `UnifiedResult<AutoMeasureSetting[]>` |
| 设置 | `setLanguage(language)` | `Promise<boolean>` |
| 缓存 | `getCacheStatus(deviceId?)` | `CacheStatus` |
| 缓存 | `getCachedValue({ key })` | `CacheValue<T> \| null` |
| 缓存 | `clearCache(scope?)` | 清空内存缓存 |
| 缓存 | `invalidateCache(key)` | 失效指定缓存 |
| 缓存 | `warmupCache(options)` | `Promise<SyncTaskResult>` |
| 诊断 | `getDiagnostics(options?)` | `Promise<UnifiedDiagnostics>` |
| 事件 | `on(event, listener)` | 注册监听 |
| 事件 | `off(event, listener)` | 移除监听 |
| 事件 | `once(event, listener)` | 一次性监听 |
| 事件 | `removeAllListeners(event?)` | 清空监听 |

### 指标接口返回约定

- `getXxxList()`：统一返回 `UnifiedResult<PointType[]>`
- `getXxxItem()`：统一返回 `UnifiedResult<PointType> | null`
- `getLatestXxx()`：统一返回 `UnifiedResult<PointType> | null`

其中 `PointType` 统一包含：

```ts
type MetricPointBase = {
  date: string;
  time: string;
  timestamp: number;
  sourceType: 'origin5m' | 'origin30m' | 'daySummary' | 'spo2Origin' | 'measurement';
};
```

---

## 统一事件

新接口统一事件名如下：

```ts
type UnifiedEventMap = {
  'scan.deviceFound': { device: VeepooDevice; timestamp: number };
  'connection.changed': { deviceId: string; status: ConnectionStatus; authenticated?: boolean; ready?: boolean };
  'device.ready': { deviceId: string; isOadModel?: boolean };
  'sync.progress': { taskId: string; progress: ReadOriginProgress };
  'sync.completed': { taskId: string; success: boolean; timestamp: number };
  'measurement.updated': { type: MeasurementType; state: MeasurementState };
  'cache.updated': { key: string; domain: CacheDomain; deviceId?: string; date?: string; timestamp: number };
  error: VeepooError;
};
```

监听示例：

```ts
VeepooSDK.on('measurement.updated', ({ type, state }) => {
  console.log(type, state.state);
});

VeepooSDK.on('sync.progress', ({ progress }) => {
  console.log(progress.progress);
});
```

---

## 推荐功能链路

### 1. 扫描并连接

```ts
await VeepooSDK.init();
await VeepooSDK.requestPermissions();
await VeepooSDK.startScan();
await VeepooSDK.connect(deviceId, { password: '0000' });
```

### 2. 同步并缓存

```ts
await VeepooSDK.syncHealthData({
  include: ['sleep', 'sport', 'origin30m'],
  saveToCache: true,
});
```

### 3. 页面优先读缓存

```ts
const snapshot = await VeepooSDK.getDailyHealthSnapshot('2026-03-23', {
  strategy: 'cache-first',
});
```

### 4. 测量统一走单入口

```ts
await VeepooSDK.startMeasurement('stress');
await VeepooSDK.stopMeasurement('stress');
```

---

## 旧接口迁移映射

| 旧接口 | 新接口 |
| --- | --- |
| `checkBluetoothStatus()` | `getBluetoothStatus()` |
| `getConnectionStatus()` | `getConnectionState()` |
| `readBattery()` | `getBatteryInfo()` |
| `readDeviceFunctions()` | `getDeviceFunctions()` |
| `readDeviceVersion()` | `getDeviceVersion()` |
| `readSocialMsgData()` | `getSocialMessageSettings()` |
| `startReadOriginData()` / `readDeviceAllData()` | `syncHealthData()` |
| `readSleepData()` | `getSleepData()` |
| `readSportStepData()` | `getSportData()` |
| `readOriginData()` | `getOrigin5mData()` |
| `readDaySummaryData()` | `getDaySummaryData()` |
| `readAutoMeasureSetting()` | `getAutoMeasureSettings()` |
| `modifyAutoMeasureSetting()` | `updateAutoMeasureSetting()` |
| `startHeartRateTest()` 等 | `startMeasurement(type)` |
| `stopHeartRateTest()` 等 | `stopMeasurement(type)` |
| 旧原生事件名 | 统一事件名 `scan.* / connection.* / sync.* / measurement.*` |

---

## 导出

```ts
import VeepooSDK from '@gaozh1024/rn-veepoo-sdk';
import {
  VeepooUnifiedSDK,
  type UnifiedResult,
  type MeasurementType,
  type SDKState,
  type UnifiedEventMap,
} from '@gaozh1024/rn-veepoo-sdk';
```

- `default` = 新统一接口
- `VeepooSDK` = 新统一接口
- `VeepooUnifiedSDK` = 新统一接口

---

## 开发验证

```bash
npm run typecheck
npm run build
```

---

## 许可证

MIT
