1import 'package:blood_pressure_app/logging.dart';
  2
  3import 'ble_date_time.dart';
  4import 'ble_measurement_status.dart';
  5import 'decoding_util.dart';
  6
  7/// https://developer.nordicsemi.com/nRF51_SDK/nRF51_SDK_v4.x.x/doc/html/structble__bps__meas__s.html
  8/// https://github.com/NordicSemiconductor/Kotlin-BLE-Library/blob/6b565e59de21dfa53ef80ff8351ac4a4550e8d58/profile/src/main/java/no/nordicsemi/android/kotlin/ble/profile/bps/BloodPressureMeasurementParser.kt
  9class BleMeasurementData with TypeLogger {
 10  BleMeasurementData({
 11    required this.systolic,
 12    required this.diastolic,
 13    required this.meanArterialPressure,
 14    required this.isMMHG,
 15    required this.pulse,
 16    required this.userID,
 17    required this.status,
 18    required this.timestamp,
 19  });
 20
 21  static BleMeasurementData? decode(List<int> data, int offset) {
 22    // https://github.com/NordicSemiconductor/Kotlin-BLE-Library/blob/6b565e59de21dfa53ef80ff8351ac4a4550e8d58/profile/src/main/java/no/nordicsemi/android/kotlin/ble/profile/bps/BloodPressureMeasurementParser.kt
 23
 24    // Reading specific bits: `(byte & (1 << bitIdx))`
 25
 26    if (data.length < 7) {
 27      log.finest('BleMeasurementData decodeMeasurement: Not enough data, $data has less than 7 bytes.');
 28      return null;
 29    }
 30
 31    int offset = 0;
 32
 33    final int flagsByte = data[offset];
 34    offset += 1;
 35
 36    final bool isMMHG = !isBitIntByteSet(flagsByte, 0); // 0 => mmHg 1 =>kPA
 37    final bool timestampPresent = isBitIntByteSet(flagsByte, 1);
 38    final bool pulseRatePresent = isBitIntByteSet(flagsByte, 2);
 39    final bool userIdPresent = isBitIntByteSet(flagsByte, 3);
 40    final bool measurementStatusPresent = isBitIntByteSet(flagsByte, 4);
 41
 42    if (data.length < (7
 43      + (timestampPresent ? 7 : 0)
 44      + (pulseRatePresent ? 2 : 0)
 45      + (userIdPresent ? 1 : 0)
 46      + (measurementStatusPresent ? 2 : 0)
 47    )) {
 48      log.finest("BleMeasurementData decodeMeasurement: Flags don't match, $data has less bytes than expected.");
 49      return null;
 50    }
 51
 52    final double? systolic = readSFloat(data, offset);
 53    offset += 2;
 54    final double? diastolic = readSFloat(data, offset);
 55    offset += 2;
 56    final double? meanArterialPressure = readSFloat(data, offset);
 57    offset += 2;
 58
 59    if (systolic == null || diastolic == null || meanArterialPressure == null) {
 60      log.finest('BleMeasurementData decodeMeasurement: Unable to decode required values sys, dia, and meanArterialPressure, $data.');
 61      return null;
 62    }
 63
 64    DateTime? timestamp;
 65    if (timestampPresent) {
 66      timestamp = BleDateTimeParser.parseBle(data, offset);
 67      offset += 7;
 68    }
 69
 70    double? pulse;
 71    if (pulseRatePresent) {
 72      pulse = readSFloat(data, offset);
 73      offset += 2;
 74    }
 75
 76    int? userId;
 77    if (userIdPresent) {
 78      userId = data[offset];
 79      offset += 1;
 80    }
 81
 82    BleMeasurementStatus? status;
 83    if (measurementStatusPresent) {
 84      status = BleMeasurementStatus.decode(data[offset]);
 85    }
 86
 87    return BleMeasurementData(
 88      systolic: systolic,
 89      diastolic: diastolic,
 90      meanArterialPressure: meanArterialPressure,
 91      isMMHG: isMMHG,
 92      pulse: pulse,
 93      userID: userId,
 94      status: status,
 95      timestamp: timestamp,
 96    );
 97  }
 98
 99  final double systolic;
100  final double diastolic;
101  final double meanArterialPressure;
102  final bool isMMHG; // mmhg or kpa
103  final double? pulse;
104  final int? userID;
105  final BleMeasurementStatus? status;
106  final DateTime? timestamp;
107
108  @override
109  String toString() => 'BleMeasurementData{systolic: $systolic, diastolic: $diastolic, meanArterialPressure: $meanArterialPressure, isMMHG: $isMMHG, pulse: $pulse, userID: $userID, status: $status, timestamp: $timestamp}';
110}