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}