feat(miniprogram): BLE 增强层 — DataBuffer + GenericBleAdapter + DataSyncScheduler
Some checks failed
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / frontend-build (push) Has been cancelled
CI / security-audit (push) Has been cancelled

- DataBuffer: 离线持久化缓冲(分桶存储 + 去重 + 容量管理)
- GenericBleAdapter: 基于 Bluetooth SIG 标准 Health Profile 的通用适配器
  (Heart Rate 0x180D / Health Thermometer 0x1809 / Blood Pressure 0x1810)
- DataSyncScheduler: 定时自动同步调度(基于时间间隔判断是否需要同步)
- BLEManager: 集成 DataBuffer 替换简单 Storage 缓存
- device-sync 页面: 注册 CustomBandAdapter + 自动同步 + 状态显示
- 新增 vitest 单元测试配置,30 个测试全部通过
This commit is contained in:
iven
2026-05-04 02:42:58 +08:00
parent 70aacf47a0
commit 62c02e0f15
15 changed files with 1272 additions and 119 deletions

View File

@@ -8,9 +8,7 @@ import type {
SyncResult,
BLEManagerConfig,
} from './types';
const CACHE_KEY = 'ble_pending_readings';
const CACHE_MAX = 2000;
import { DataBuffer } from './DataBuffer';
const DEFAULT_CONFIG: BLEManagerConfig = {
scanTimeout: 10000,
@@ -22,6 +20,7 @@ export class BLEManager {
private adapters: DeviceAdapter[] = [];
private connection: BLEConnection | null = null;
private readings: NormalizedReading[] = [];
private dataBuffer: DataBuffer;
private config: BLEManagerConfig;
private scanTimer: ReturnType<typeof setTimeout> | null = null;
private onConnectionChange?: (state: BLEConnectionState) => void;
@@ -29,6 +28,8 @@ export class BLEManager {
constructor(config?: Partial<BLEManagerConfig>) {
this.config = { ...DEFAULT_CONFIG, ...config };
this.dataBuffer = new DataBuffer();
this.dataBuffer.restore();
}
/** 注册设备适配器 */
@@ -182,7 +183,7 @@ export class BLEManager {
);
if (newReadings.length > 0) {
this.readings = [...this.readings, ...newReadings];
this.persistPendingReadings();
this.dataBuffer.push(newReadings);
this.onReadings?.(newReadings);
}
});
@@ -213,8 +214,6 @@ export class BLEManager {
if (!svc) continue;
try {
// Taro readBLECharacteristicValue 触发 onBLECharacteristicValueChange 回调
// 读取结果会通过 BLEManager 已注册的 onBLECharacteristicValueChange 监听器返回
await Taro.readBLECharacteristicValue({
deviceId,
serviceId: svc.uuid,
@@ -241,7 +240,7 @@ export class BLEManager {
this.updateState('syncing');
const batch = this.readings.slice(-this.config.maxReadingsPerSync);
const batch = this.dataBuffer.flush();
if (batch.length === 0) {
this.updateState('connected');
return { success: true, readingsCount: 0, uploadedCount: 0 };
@@ -251,8 +250,7 @@ export class BLEManager {
for (let attempt = 1; attempt <= this.config.retryCount; attempt++) {
try {
const uploaded = await uploadFn(batch);
this.readings = this.readings.slice(batch.length);
this.clearPendingReadings();
this.readings = [];
this.updateState('connected');
return {
success: true,
@@ -261,6 +259,8 @@ export class BLEManager {
};
} catch (e: any) {
lastError = e.message || '上传失败';
// flush 已取出,失败时需要放回
this.dataBuffer.push(batch);
}
}
@@ -309,46 +309,19 @@ export class BLEManager {
this.readings = [];
}
// ── 离线缓存 ──
/** 将当前未上传读数同步写入 Storage */
private persistPendingReadings(): void {
try {
const batch = this.readings.slice(-CACHE_MAX);
Taro.setStorageSync(CACHE_KEY, JSON.stringify(batch));
} catch {
// Storage 写入失败不影响主流程
}
}
/** 上传成功后清除缓存 */
private clearPendingReadings(): void {
try {
Taro.removeStorageSync(CACHE_KEY);
} catch {
// 忽略
}
}
/** 启动时检查缓存,有未上传数据则自动重传。
* 返回重传的记录数 */
/** 启动时检查缓存,有未上传数据则自动重传 */
async flushPendingReadings(
uploadFn: (readings: NormalizedReading[]) => Promise<number>,
): Promise<number> {
try {
const raw = Taro.getStorageSync(CACHE_KEY) as string;
if (!raw) return 0;
const cached: NormalizedReading[] = JSON.parse(raw);
if (!Array.isArray(cached) || cached.length === 0) {
Taro.removeStorageSync(CACHE_KEY);
return 0;
}
const batch = this.dataBuffer.flush();
if (batch.length === 0) return 0;
const batch = cached.slice(0, this.config.maxReadingsPerSync);
try {
const uploaded = await uploadFn(batch);
Taro.removeStorageSync(CACHE_KEY);
return uploaded;
} catch {
// 失败时放回
this.dataBuffer.push(batch);
return 0;
}
}