main
1import 'dart:typed_data';
2
3import 'package:blood_pressure_app/features/bluetooth/logic/characteristics/ble_date_time.dart';
4import 'package:blood_pressure_app/features/bluetooth/logic/characteristics/ble_measurement_status.dart';
5import 'package:blood_pressure_app/features/bluetooth/logic/characteristics/decoding_util.dart';
6import 'package:blood_pressure_app/logging.dart';
7import 'package:health_data_store/health_data_store.dart';
8
9/// Result of a single bp measurement as by ble spec.
10///
11/// https://developer.nordicsemi.com/nRF51_SDK/nRF51_SDK_v4.x.x/doc/html/structble__bps__meas__s.html
12/// https://github.com/NordicSemiconductor/Kotlin-BLE-Library/blob/6b565e59de21dfa53ef80ff8351ac4a4550e8d58/profile/src/main/java/no/nordicsemi/android/kotlin/ble/profile/bps/BloodPressureMeasurementParser.kt
13class BleMeasurementData {
14 /// Initialize result of a single bp measurement.
15 BleMeasurementData({
16 required this.systolic,
17 required this.diastolic,
18 required this.meanArterialPressure,
19 required this.isMMHG,
20 this.pulse,
21 this.userID,
22 this.status,
23 this.timestamp,
24 });
25
26 /// Return BleMeasurementData as a BloodPressureRecord
27 BloodPressureRecord asBloodPressureRecord() =>
28 BloodPressureRecord(
29 time: timestamp ?? DateTime.now(),
30 sys: isMMHG
31 ? Pressure.mmHg(systolic.toInt())
32 : Pressure.kPa(systolic),
33 dia: isMMHG
34 ? Pressure.mmHg(diastolic.toInt())
35 : Pressure.kPa(diastolic),
36 pul: pulse?.toInt(),
37 );
38
39 /// Decode bytes read from the characteristic into a [BleMeasurementData]
40 static BleMeasurementData? decode(Uint8List data, int offset) {
41 // https://github.com/NordicSemiconductor/Kotlin-BLE-Library/blob/6b565e59de21dfa53ef80ff8351ac4a4550e8d58/profile/src/main/java/no/nordicsemi/android/kotlin/ble/profile/bps/BloodPressureMeasurementParser.kt
42
43 // Reading specific bits: `(byte & (1 << bitIdx))`
44
45 if (data.length < 7) {
46 log.warning('BleMeasurementData decodeMeasurement: Not enough data, $data has less than 7 bytes.');
47 return null;
48 }
49
50 int offset = 0;
51
52 final int flagsByte = data[offset];
53 offset += 1;
54
55 final bool isMMHG = !isBitIntByteSet(flagsByte, 0); // 0 => mmHg 1 =>kPA
56 final bool timestampPresent = isBitIntByteSet(flagsByte, 1);
57 final bool pulseRatePresent = isBitIntByteSet(flagsByte, 2);
58 final bool userIdPresent = isBitIntByteSet(flagsByte, 3);
59 final bool measurementStatusPresent = isBitIntByteSet(flagsByte, 4);
60
61 if (data.length < (7
62 + (timestampPresent ? 7 : 0)
63 + (pulseRatePresent ? 2 : 0)
64 + (userIdPresent ? 1 : 0)
65 + (measurementStatusPresent ? 2 : 0)
66 )) {
67 log.warning("BleMeasurementData decodeMeasurement: Flags don't match, $data has less bytes than expected.");
68 return null;
69 }
70
71 final double? systolic = readSFloat(data, offset);
72 offset += 2;
73 final double? diastolic = readSFloat(data, offset);
74 offset += 2;
75 final double? meanArterialPressure = readSFloat(data, offset);
76 offset += 2;
77
78 if (systolic == null || diastolic == null || meanArterialPressure == null) {
79 log.warning('BleMeasurementData decodeMeasurement: Unable to decode required values sys, dia, and meanArterialPressure, $data.');
80 return null;
81 }
82
83 DateTime? timestamp;
84 if (timestampPresent) {
85 timestamp = BleDateTimeParser.parseBle(data, offset);
86 offset += 7;
87 }
88
89 double? pulse;
90 if (pulseRatePresent) {
91 pulse = readSFloat(data, offset);
92 offset += 2;
93 }
94
95 int? userId;
96 if (userIdPresent) {
97 userId = data[offset];
98 offset += 1;
99 }
100
101 BleMeasurementStatus? status;
102 if (measurementStatusPresent) {
103 status = BleMeasurementStatus.decode(data[offset]);
104 }
105
106 return BleMeasurementData(
107 systolic: systolic,
108 diastolic: diastolic,
109 meanArterialPressure: meanArterialPressure,
110 isMMHG: isMMHG,
111 pulse: pulse,
112 userID: userId,
113 status: status,
114 timestamp: timestamp,
115 );
116 }
117
118 /// Systolic pressure
119 final double systolic;
120 /// Diatolic pressure
121 final double diastolic;
122 /// Mean arterial pressure
123 final double meanArterialPressure;
124 /// True if pressure values are in mmHg, False if in kPa
125 final bool isMMHG; // mmhg or kpa
126 /// Pulse rate (of heart)
127 final double? pulse;
128 /// User id
129 final int? userID;
130 /// [BleMeasurementStatus] status
131 final BleMeasurementStatus? status;
132 /// Timestamp of measurement
133 final DateTime? timestamp;
134
135 @override
136 String toString() => 'BleMeasurementData{systolic: $systolic, diastolic: $diastolic, meanArterialPressure: $meanArterialPressure, isMMHG: $isMMHG, pulse: $pulse, userID: $userID, status: $status, timestamp: $timestamp}';
137}