feat(mp): BloodPressureAdapter + GlucoseMeterAdapter — BLE 0x1810/0x1808 标准协议适配器
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
import type { DeviceAdapter, NormalizedReading } from '../types';
|
||||
|
||||
// Bluetooth SIG Blood Pressure Service
|
||||
const BPS_SERVICE = '00001810-0000-1000-8000-00805f9b34fb';
|
||||
const BPM_CHARACTERISTIC = '00002A35-0000-1000-8000-00805f9b34fb';
|
||||
|
||||
interface BPMData {
|
||||
systolic: number;
|
||||
diastolic: number;
|
||||
map: number;
|
||||
unit: string;
|
||||
pulseRate?: number;
|
||||
}
|
||||
|
||||
// SFLOAT (IEEE 11073 16-bit float): mantissa bits 0-11, exponent bits 12-15
|
||||
function readSfloat(view: DataView, byteOffset: number): number {
|
||||
const raw = view.getUint16(byteOffset, true);
|
||||
const mantissa = raw & 0x0FFF;
|
||||
const exponent = (raw >> 12) & 0x0F;
|
||||
const signedMantissa = mantissa > 0x07FF ? mantissa - 0x1000 : mantissa;
|
||||
const signedExponent = exponent > 0x07 ? exponent - 0x10 : exponent;
|
||||
return signedMantissa * Math.pow(10, signedExponent);
|
||||
}
|
||||
|
||||
function parseBloodPressureMeasurement(data: ArrayBuffer): BPMData | null {
|
||||
const view = new DataView(data);
|
||||
if (view.byteLength < 7) return null;
|
||||
|
||||
let offset = 0;
|
||||
const flags = view.getUint8(offset);
|
||||
offset += 1;
|
||||
|
||||
const systolic = readSfloat(view, offset);
|
||||
offset += 2;
|
||||
const diastolic = readSfloat(view, offset);
|
||||
offset += 2;
|
||||
const map = readSfloat(view, offset);
|
||||
offset += 2;
|
||||
|
||||
let pulseRate: number | undefined;
|
||||
if (flags & 0x01) offset += 7; // timestamp
|
||||
if (flags & 0x02) {
|
||||
pulseRate = readSfloat(view, offset);
|
||||
}
|
||||
|
||||
return {
|
||||
systolic: Math.round(systolic * 10) / 10,
|
||||
diastolic: Math.round(diastolic * 10) / 10,
|
||||
map: Math.round(map * 10) / 10,
|
||||
unit: 'mmHg',
|
||||
pulseRate,
|
||||
};
|
||||
}
|
||||
|
||||
export const BloodPressureAdapter: DeviceAdapter = {
|
||||
name: 'Blood Pressure Monitor',
|
||||
supportedModels: [
|
||||
'AND UA-651BLE',
|
||||
'Omron HEM-7322',
|
||||
'Omron BLE',
|
||||
'BP Monitor',
|
||||
'A&D BLE',
|
||||
'iHealth BP',
|
||||
'Beurer BM',
|
||||
'Yuwell BLE',
|
||||
],
|
||||
serviceUUIDs: [BPS_SERVICE],
|
||||
notifyCharacteristics: [
|
||||
{ service: BPS_SERVICE, characteristic: BPM_CHARACTERISTIC },
|
||||
],
|
||||
readCharacteristics: [
|
||||
{ service: BPS_SERVICE, characteristic: BPM_CHARACTERISTIC },
|
||||
],
|
||||
|
||||
parseNotification(
|
||||
_serviceUUID: string,
|
||||
charUUID: string,
|
||||
data: ArrayBuffer,
|
||||
): NormalizedReading[] {
|
||||
if (charUUID.toUpperCase() !== BPM_CHARACTERISTIC.toUpperCase()) return [];
|
||||
|
||||
const parsed = parseBloodPressureMeasurement(data);
|
||||
if (!parsed) return [];
|
||||
|
||||
const measuredAt = new Date().toISOString();
|
||||
const readings: NormalizedReading[] = [
|
||||
{
|
||||
device_type: 'blood_pressure',
|
||||
metric: 'systolic',
|
||||
values: { value: parsed.systolic, unit: parsed.unit },
|
||||
measured_at: measuredAt,
|
||||
},
|
||||
{
|
||||
device_type: 'blood_pressure',
|
||||
metric: 'diastolic',
|
||||
values: { value: parsed.diastolic, unit: parsed.unit },
|
||||
measured_at: measuredAt,
|
||||
},
|
||||
{
|
||||
device_type: 'blood_pressure',
|
||||
metric: 'map',
|
||||
values: { value: parsed.map, unit: parsed.unit },
|
||||
measured_at: measuredAt,
|
||||
},
|
||||
];
|
||||
|
||||
if (parsed.pulseRate != null) {
|
||||
readings.push({
|
||||
device_type: 'heart_rate',
|
||||
metric: 'pulse_rate',
|
||||
values: { value: parsed.pulseRate, unit: 'bpm' },
|
||||
measured_at: measuredAt,
|
||||
});
|
||||
}
|
||||
|
||||
return readings;
|
||||
},
|
||||
|
||||
parseReadResponse(
|
||||
serviceUUID: string,
|
||||
charUUID: string,
|
||||
data: ArrayBuffer,
|
||||
): NormalizedReading[] {
|
||||
return this.parseNotification(serviceUUID, charUUID, data);
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user