diff --git a/app/src/main/java/org/hydev/wearsync/bles/BloodPressureMeasurement.kt b/app/src/main/java/org/hydev/wearsync/bles/BloodPressureMeasurement.kt deleted file mode 100644 index 5bb5a29..0000000 --- a/app/src/main/java/org/hydev/wearsync/bles/BloodPressureMeasurement.kt +++ /dev/null @@ -1,52 +0,0 @@ -package org.hydev.wearsync.bles - -import com.welie.blessed.BluetoothBytesParser -import com.welie.blessed.BluetoothBytesParser.Companion.FORMAT_SFLOAT -import com.welie.blessed.BluetoothBytesParser.Companion.FORMAT_UINT16 -import com.welie.blessed.BluetoothBytesParser.Companion.FORMAT_UINT8 -import java.nio.ByteOrder -import java.util.* - -data class BloodPressureMeasurement( - val systolic: Float, - val diastolic: Float, - val meanArterialPressure: Float, - val unit: ObservationUnit, - val timestamp: Date?, - val pulseRate: Float?, - val userID: Int?, - val measurementStatus: BloodPressureMeasurementStatus?, - val createdAt: Date = Calendar.getInstance().time -) { - companion object { - fun fromBytes(value: ByteArray): BloodPressureMeasurement - { - val parser = BluetoothBytesParser(value, ByteOrder.LITTLE_ENDIAN) - val flags = parser.getIntValue(FORMAT_UINT8) - val unit = if (flags and 0x01 > 0) ObservationUnit.MMHG else ObservationUnit.KPA - val timestampPresent = flags and 0x02 > 0 - val pulseRatePresent = flags and 0x04 > 0 - val userIdPresent = flags and 0x08 > 0 - val measurementStatusPresent = flags and 0x10 > 0 - - val systolic = parser.getFloatValue(FORMAT_SFLOAT) - val diastolic = parser.getFloatValue(FORMAT_SFLOAT) - val meanArterialPressure = parser.getFloatValue(FORMAT_SFLOAT) - val timestamp = if (timestampPresent) parser.dateTime else null - val pulseRate = if (pulseRatePresent) parser.getFloatValue(FORMAT_SFLOAT) else null - val userID = if (userIdPresent) parser.getIntValue(FORMAT_UINT8) else null - val status = if (measurementStatusPresent) BloodPressureMeasurementStatus(parser.getIntValue(FORMAT_UINT16)) else null - - return BloodPressureMeasurement( - systolic = systolic, - diastolic = diastolic, - meanArterialPressure = meanArterialPressure, - unit = unit, - timestamp = timestamp, - pulseRate = pulseRate, - userID = userID, - measurementStatus = status - ) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/org/hydev/wearsync/bles/BloodPressureMeasurementStatus.kt b/app/src/main/java/org/hydev/wearsync/bles/BloodPressureMeasurementStatus.kt deleted file mode 100644 index a685b00..0000000 --- a/app/src/main/java/org/hydev/wearsync/bles/BloodPressureMeasurementStatus.kt +++ /dev/null @@ -1,32 +0,0 @@ -package org.hydev.wearsync.bles - -class BloodPressureMeasurementStatus internal constructor(measurementStatus: Int) { - /** - * Body Movement Detected - */ - val isBodyMovementDetected: Boolean - /** - * Cuff is too loose - */ - val isCuffTooLoose: Boolean - /** - * Irregular pulse detected - */ - val isIrregularPulseDetected: Boolean - /** - * Pulse is not in normal range - */ - val isPulseNotInRange: Boolean - /** - * Improper measurement position - */ - val isImproperMeasurementPosition: Boolean - - init { - isBodyMovementDetected = measurementStatus and 0x0001 > 0 - isCuffTooLoose = measurementStatus and 0x0002 > 0 - isIrregularPulseDetected = measurementStatus and 0x0004 > 0 - isPulseNotInRange = measurementStatus and 0x0008 > 0 - isImproperMeasurementPosition = measurementStatus and 0x0020 > 0 - } -} \ No newline at end of file diff --git a/app/src/main/java/org/hydev/wearsync/bles/BluetoothHandler.kt b/app/src/main/java/org/hydev/wearsync/bles/BluetoothHandler.kt index 58d3bdd..4b2c313 100644 --- a/app/src/main/java/org/hydev/wearsync/bles/BluetoothHandler.kt +++ b/app/src/main/java/org/hydev/wearsync/bles/BluetoothHandler.kt @@ -5,6 +5,7 @@ import com.welie.blessed.* import kotlinx.coroutines.* import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel.Factory.UNLIMITED +import org.hydev.wearsync.bles.decoders.* import timber.log.Timber import timber.log.Timber.DebugTree import java.nio.ByteOrder @@ -13,6 +14,7 @@ import java.util.* internal class BluetoothHandler private constructor(context: Context) { private var currentTimeCounter = 0 + val batteryChannel = Channel(UNLIMITED) val heartRateChannel = Channel(UNLIMITED) val bloodPressureChannel = Channel(UNLIMITED) val glucoseChannel = Channel(UNLIMITED) @@ -24,24 +26,12 @@ internal class BluetoothHandler private constructor(context: Context) { private fun handlePeripheral(peripheral: BluetoothPeripheral) { scope.launch { try { - val mtu = peripheral.requestMtu(185) - Timber.i("MTU is $mtu") - + Timber.i("MTU is ${peripheral.requestMtu(185)}") peripheral.requestConnectionPriority(ConnectionPriority.HIGH) - - val rssi = peripheral.readRemoteRssi() - Timber.i("RSSI is $rssi") - - peripheral.getCharacteristic(DIS_SERVICE_UUID, MANUFACTURER_NAME_CHARACTERISTIC_UUID)?.let { - val manufacturerName = peripheral.readCharacteristic(it).asString() - Timber.i("Received: $manufacturerName") - } - - val model = peripheral.readCharacteristic(DIS_SERVICE_UUID, MODEL_NUMBER_CHARACTERISTIC_UUID).asString() - Timber.i("Received: $model") - - val batteryLevel = peripheral.readCharacteristic(BTS_SERVICE_UUID, BATTERY_LEVEL_CHARACTERISTIC_UUID).asUInt8() - Timber.i("Battery level: $batteryLevel") + Timber.i("RSSI is ${peripheral.readRemoteRssi()}") + Timber.i("Received: ${peripheral.read(ManufacturerDecoder())}") + Timber.i("Received: ${peripheral.read(ModelDecoder())}") + Timber.i("Battery level: ${peripheral.read(BatteryDecoder())}") // Write Current Time if possible peripheral.getCharacteristic(CTS_SERVICE_UUID, CURRENT_TIME_CHARACTERISTIC_UUID)?.let { @@ -56,12 +46,14 @@ internal class BluetoothHandler private constructor(context: Context) { } } - setupHRSnotifications(peripheral) - setupPLXnotifications(peripheral) - setupHTSnotifications(peripheral) + peripheral.setupNotification(batteryChannel, BatteryDecoder()) + peripheral.setupNotification(heartRateChannel, HeartRateDecoder()) + peripheral.setupNotification(temperatureChannel, TemperatureDecoder()) + peripheral.setupNotification(weightChannel, WeightDecoder()) + peripheral.setupNotification(bloodPressureChannel, BloodPressureDecoder()) + peripheral.setupNotification(pulseOxSpotChannel, PLXSpotDecoder()) + peripheral.setupNotification(pulseOxContinuousChannel, PLXContinuousDecoder()) setupGLXnotifications(peripheral) - setupBLPnotifications(peripheral) - setupWSSnotifications(peripheral) setupCTSnotifications(peripheral) peripheral.getCharacteristic(CONTOUR_SERVICE_UUID, CONTOUR_CLOCK)?.let { @@ -119,34 +111,8 @@ internal class BluetoothHandler private constructor(context: Context) { } } - private suspend fun setupHRSnotifications(peripheral: BluetoothPeripheral) { - peripheral.getCharacteristic(HRS_SERVICE_UUID, HRS_MEASUREMENT_CHARACTERISTIC_UUID)?.let { - peripheral.observe(it) { value -> - val measurement = HeartRateMeasurement.fromBytes(value) - heartRateChannel.trySend(measurement) - Timber.d("%s", measurement) - } - } - } - - private suspend fun setupWSSnotifications(peripheral: BluetoothPeripheral) { - peripheral.getCharacteristic(WSS_SERVICE_UUID, WSS_MEASUREMENT_CHAR_UUID)?.let { - peripheral.observe(it) { value -> - val measurement = WeightMeasurement.fromBytes(value) - weightChannel.trySend(measurement) - Timber.d("%s", measurement) - } - } - } - private suspend fun setupGLXnotifications(peripheral: BluetoothPeripheral) { - peripheral.getCharacteristic(GLUCOSE_SERVICE_UUID, GLUCOSE_MEASUREMENT_CHARACTERISTIC_UUID)?.let { - peripheral.observe(it) { value -> - val measurement = GlucoseMeasurement.fromBytes(value) - glucoseChannel.trySend(measurement) - Timber.d("%s", measurement) - } - } + peripheral.setupNotification(glucoseChannel, GlucoseDecoder()) peripheral.getCharacteristic(GLUCOSE_SERVICE_UUID, GLUCOSE_RECORD_ACCESS_POINT_CHARACTERISTIC_UUID)?.let { val result = peripheral.observe(it) { value -> @@ -166,42 +132,13 @@ internal class BluetoothHandler private constructor(context: Context) { peripheral.writeCharacteristic(GLUCOSE_SERVICE_UUID, GLUCOSE_RECORD_ACCESS_POINT_CHARACTERISTIC_UUID, command, WriteType.WITH_RESPONSE) } - private suspend fun setupBLPnotifications(peripheral: BluetoothPeripheral) { - peripheral.getCharacteristic(BLP_SERVICE_UUID, BLP_MEASUREMENT_CHARACTERISTIC_UUID)?.let { - peripheral.observe(it) { value -> - val measurement = BloodPressureMeasurement.fromBytes(value) - bloodPressureChannel.trySend(measurement) - Timber.d("%s", measurement) - } - } - } - - private suspend fun setupHTSnotifications(peripheral: BluetoothPeripheral) { - peripheral.getCharacteristic(HTS_SERVICE_UUID, HTS_MEASUREMENT_CHARACTERISTIC_UUID)?.let { - peripheral.observe(it) { value -> - val measurement = TemperatureMeasurement.fromBytes(value) - temperatureChannel.trySend(measurement) - Timber.d("%s", measurement) - } - } - } - - private suspend fun setupPLXnotifications(peripheral: BluetoothPeripheral) { - peripheral.getCharacteristic(PLX_SERVICE_UUID, PLX_CONTINUOUS_MEASUREMENT_CHAR_UUID)?.let { - peripheral.observe(it) { value -> - val measurement = PulseOximeterContinuousMeasurement.fromBytes(value) - if (measurement.spO2 <= 100 && measurement.pulseRate <= 220) { - pulseOxContinuousChannel.trySend(measurement) - } - Timber.d("%s", measurement) - } - } - - peripheral.getCharacteristic(PLX_SERVICE_UUID, PLX_SPOT_MEASUREMENT_CHAR_UUID)?.let { - peripheral.observe(it) { value -> - val measurement = PulseOximeterSpotMeasurement.fromBytes(value) - pulseOxSpotChannel.trySend(measurement) - Timber.d("%s", measurement) + suspend fun BluetoothPeripheral.setupNotification(channel: Channel, dec: IDecoder) + { + getCharacteristic(dec.sid, dec.cid)?.let { + observe(it) { value -> + val measurement = dec.decode(value) + channel.trySend(measurement) + Timber.d(measurement.toString()) } } } @@ -236,44 +173,18 @@ internal class BluetoothHandler private constructor(context: Context) { companion object { // UUIDs for the Blood Pressure service (BLP) - val BLP_SERVICE_UUID: UUID = UUID.fromString("00001810-0000-1000-8000-00805f9b34fb") - val BLP_MEASUREMENT_CHARACTERISTIC_UUID: UUID = UUID.fromString("00002A35-0000-1000-8000-00805f9b34fb") - - // UUIDs for the Health Thermometer service (HTS) - val HTS_SERVICE_UUID = UUID.fromString("00001809-0000-1000-8000-00805f9b34fb") - val HTS_MEASUREMENT_CHARACTERISTIC_UUID = UUID.fromString("00002A1C-0000-1000-8000-00805f9b34fb") - - // UUIDs for the Heart Rate service (HRS) - val HRS_SERVICE_UUID: UUID = UUID.fromString("0000180D-0000-1000-8000-00805f9b34fb") - val HRS_MEASUREMENT_CHARACTERISTIC_UUID: UUID = UUID.fromString("00002A37-0000-1000-8000-00805f9b34fb") - - // UUIDs for the Device Information service (DIS) - val DIS_SERVICE_UUID: UUID = UUID.fromString("0000180A-0000-1000-8000-00805f9b34fb") - val MANUFACTURER_NAME_CHARACTERISTIC_UUID: UUID = UUID.fromString("00002A29-0000-1000-8000-00805f9b34fb") - val MODEL_NUMBER_CHARACTERISTIC_UUID: UUID = UUID.fromString("00002A24-0000-1000-8000-00805f9b34fb") + val BLP_SERVICE_UUID = UUID.fromString("00001810-0000-1000-8000-00805f9b34fb") + val BLP_MEASUREMENT_CHARACTERISTIC_UUID = UUID.fromString("00002A35-0000-1000-8000-00805f9b34fb") // UUIDs for the Current Time service (CTS) - val CTS_SERVICE_UUID: UUID = UUID.fromString("00001805-0000-1000-8000-00805f9b34fb") - val CURRENT_TIME_CHARACTERISTIC_UUID: UUID = UUID.fromString("00002A2B-0000-1000-8000-00805f9b34fb") + val CTS_SERVICE_UUID = UUID.fromString("00001805-0000-1000-8000-00805f9b34fb") + val CURRENT_TIME_CHARACTERISTIC_UUID = UUID.fromString("00002A2B-0000-1000-8000-00805f9b34fb") - // UUIDs for the Battery Service (BAS) - val BTS_SERVICE_UUID: UUID = UUID.fromString("0000180F-0000-1000-8000-00805f9b34fb") - val BATTERY_LEVEL_CHARACTERISTIC_UUID: UUID = UUID.fromString("00002A19-0000-1000-8000-00805f9b34fb") - - // UUIDs for the Pulse Oximeter Service (PLX) - val PLX_SERVICE_UUID: UUID = UUID.fromString("00001822-0000-1000-8000-00805f9b34fb") - val PLX_SPOT_MEASUREMENT_CHAR_UUID: UUID = UUID.fromString("00002a5e-0000-1000-8000-00805f9b34fb") - val PLX_CONTINUOUS_MEASUREMENT_CHAR_UUID: UUID = UUID.fromString("00002a5f-0000-1000-8000-00805f9b34fb") - - // UUIDs for the Weight Scale Service (WSS) - val WSS_SERVICE_UUID: UUID = UUID.fromString("0000181D-0000-1000-8000-00805f9b34fb") - val WSS_MEASUREMENT_CHAR_UUID: UUID = UUID.fromString("00002A9D-0000-1000-8000-00805f9b34fb") - val GLUCOSE_SERVICE_UUID: UUID = UUID.fromString("00001808-0000-1000-8000-00805f9b34fb") - val GLUCOSE_MEASUREMENT_CHARACTERISTIC_UUID: UUID = UUID.fromString("00002A18-0000-1000-8000-00805f9b34fb") - val GLUCOSE_RECORD_ACCESS_POINT_CHARACTERISTIC_UUID: UUID = UUID.fromString("00002A52-0000-1000-8000-00805f9b34fb") + val GLUCOSE_SERVICE_UUID = UUID.fromString("00001808-0000-1000-8000-00805f9b34fb") + val GLUCOSE_RECORD_ACCESS_POINT_CHARACTERISTIC_UUID = UUID.fromString("00002A52-0000-1000-8000-00805f9b34fb") // Contour Glucose Service - val CONTOUR_SERVICE_UUID: UUID = UUID.fromString("00000000-0002-11E2-9E96-0800200C9A66") + val CONTOUR_SERVICE_UUID = UUID.fromString("00000000-0002-11E2-9E96-0800200C9A66") val CONTOUR_CLOCK = UUID.fromString("00001026-0002-11E2-9E96-0800200C9A66") private var instance: BluetoothHandler? = null @@ -283,6 +194,9 @@ internal class BluetoothHandler private constructor(context: Context) { } return instance!! } + + suspend fun BluetoothPeripheral.read(decoder: IDecoder) = + getCharacteristic(decoder.sid, decoder.cid)?.let { decoder.decode(readCharacteristic(it)) } } @JvmField diff --git a/app/src/main/java/org/hydev/wearsync/bles/GlucoseMeasurement.kt b/app/src/main/java/org/hydev/wearsync/bles/GlucoseMeasurement.kt deleted file mode 100644 index 7453022..0000000 --- a/app/src/main/java/org/hydev/wearsync/bles/GlucoseMeasurement.kt +++ /dev/null @@ -1,49 +0,0 @@ -package org.hydev.wearsync.bles - -import com.welie.blessed.BluetoothBytesParser -import com.welie.blessed.BluetoothBytesParser.Companion.FORMAT_SFLOAT -import com.welie.blessed.BluetoothBytesParser.Companion.FORMAT_SINT16 -import com.welie.blessed.BluetoothBytesParser.Companion.FORMAT_UINT16 -import com.welie.blessed.BluetoothBytesParser.Companion.FORMAT_UINT8 -import org.hydev.wearsync.bles.ObservationUnit.MiligramPerDeciliter -import org.hydev.wearsync.bles.ObservationUnit.MmolPerLiter -import java.util.* - -data class GlucoseMeasurement( - val value: Float?, - val unit: ObservationUnit, - val timestamp: Date?, - val sequenceNumber: Int, - val contextWillFollow: Boolean, - val createdAt: Date = Calendar.getInstance().time -) { - companion object { - fun fromBytes(value: ByteArray): GlucoseMeasurement - { - val parser = BluetoothBytesParser(value) - val flags: Int = parser.getIntValue(FORMAT_UINT8) - val timeOffsetPresent = flags and 0x01 > 0 - val typeAndLocationPresent = flags and 0x02 > 0 - val unit = if (flags and 0x04 > 0) MmolPerLiter else MiligramPerDeciliter - val contextWillFollow = flags and 0x10 > 0 - - val sequenceNumber = parser.getIntValue(FORMAT_UINT16) - var timestamp = parser.dateTime - if (timeOffsetPresent) { - val timeOffset: Int = parser.getIntValue(FORMAT_SINT16) - timestamp = Date(timestamp.time + timeOffset * 60000) - } - - val multiplier = if (unit === MiligramPerDeciliter) 100000 else 1000 - val glucoseValue = if (typeAndLocationPresent) parser.getFloatValue(FORMAT_SFLOAT) * multiplier else null - - return GlucoseMeasurement( - unit = unit, - timestamp = timestamp, - sequenceNumber = sequenceNumber, - value = glucoseValue, - contextWillFollow = contextWillFollow - ) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/org/hydev/wearsync/bles/HeartRateMeasurement.kt b/app/src/main/java/org/hydev/wearsync/bles/HeartRateMeasurement.kt deleted file mode 100644 index 969672b..0000000 --- a/app/src/main/java/org/hydev/wearsync/bles/HeartRateMeasurement.kt +++ /dev/null @@ -1,51 +0,0 @@ -package org.hydev.wearsync.bles - -import com.welie.blessed.BluetoothBytesParser -import com.welie.blessed.BluetoothBytesParser.Companion.FORMAT_UINT16 -import com.welie.blessed.BluetoothBytesParser.Companion.FORMAT_UINT8 -import java.nio.ByteOrder -import java.util.* - -data class HeartRateMeasurement( - val pulse: Int, - val energyExpended: Int?, - val rrIntervals: IntArray, - val sensorContactStatus: SensorContactFeature, - val createdAt: Date = Calendar.getInstance().time -) { - companion object { - fun fromBytes(value: ByteArray): HeartRateMeasurement - { - val parser = BluetoothBytesParser(value, ByteOrder.LITTLE_ENDIAN) - val flags = parser.getIntValue(FORMAT_UINT8) - val pulse = if (flags and 0x01 == 0) parser.getIntValue(FORMAT_UINT8) else parser.getIntValue(FORMAT_UINT16) - val sensorContactStatusFlag = flags and 0x06 shr 1 - val energyExpenditurePresent = flags and 0x08 > 0 - val rrIntervalPresent = flags and 0x10 > 0 - - val sensorContactStatus = when (sensorContactStatusFlag) { - 0, 1 -> SensorContactFeature.NotSupported - 2 -> SensorContactFeature.SupportedNoContact - 3 -> SensorContactFeature.SupportedAndContact - else -> SensorContactFeature.NotSupported - } - - val energyExpended = if (energyExpenditurePresent) parser.getIntValue(FORMAT_UINT16) else null - - val rrArray = ArrayList() - if (rrIntervalPresent) { - while (parser.offset < value.size) { - val rrInterval = parser.getIntValue(FORMAT_UINT16) - rrArray.add((rrInterval.toDouble() / 1024.0 * 1000.0).toInt()) - } - } - - return HeartRateMeasurement( - pulse = pulse, - energyExpended = energyExpended, - sensorContactStatus = sensorContactStatus, - rrIntervals = rrArray.toIntArray() - ) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/org/hydev/wearsync/bles/PulseOximeterContinuousMeasurement.kt b/app/src/main/java/org/hydev/wearsync/bles/PulseOximeterContinuousMeasurement.kt deleted file mode 100644 index c684722..0000000 --- a/app/src/main/java/org/hydev/wearsync/bles/PulseOximeterContinuousMeasurement.kt +++ /dev/null @@ -1,56 +0,0 @@ -package org.hydev.wearsync.bles - -import com.welie.blessed.BluetoothBytesParser -import com.welie.blessed.BluetoothBytesParser.Companion.FORMAT_SFLOAT -import com.welie.blessed.BluetoothBytesParser.Companion.FORMAT_UINT16 -import com.welie.blessed.BluetoothBytesParser.Companion.FORMAT_UINT8 -import java.util.* - -data class PulseOximeterContinuousMeasurement( - val spO2: Float, - val pulseRate: Float, - val spO2Fast: Float?, - val pulseRateFast: Float?, - val spO2Slow: Float?, - val pulseRateSlow: Float?, - val pulseAmplitudeIndex: Float?, - val measurementStatus: Int?, - val sensorStatus: Int?, - val createdAt: Date = Calendar.getInstance().time -) { - companion object { - fun fromBytes(value: ByteArray): PulseOximeterContinuousMeasurement - { - val parser = BluetoothBytesParser(value) - val flags = parser.getIntValue(FORMAT_UINT8) - val spo2FastPresent = flags and 0x01 > 0 - val spo2SlowPresent = flags and 0x02 > 0 - val measurementStatusPresent = flags and 0x04 > 0 - val sensorStatusPresent = flags and 0x08 > 0 - val pulseAmplitudeIndexPresent = flags and 0x10 > 0 - - val spO2 = parser.getFloatValue(FORMAT_SFLOAT) - val pulseRate = parser.getFloatValue(FORMAT_SFLOAT) - val spO2Fast = if (spo2FastPresent) parser.getFloatValue(FORMAT_SFLOAT) else null - val pulseRateFast = if (spo2FastPresent) parser.getFloatValue(FORMAT_SFLOAT) else null - val spO2Slow = if (spo2SlowPresent) parser.getFloatValue(FORMAT_SFLOAT) else null - val pulseRateSlow = if (spo2SlowPresent) parser.getFloatValue(FORMAT_SFLOAT) else null - val measurementStatus = if (measurementStatusPresent) parser.getIntValue(FORMAT_UINT16) else null - val sensorStatus = if (sensorStatusPresent) parser.getIntValue(FORMAT_UINT16) else null - if (sensorStatusPresent) parser.getIntValue(FORMAT_UINT8) // Reserved byte - val pulseAmplitudeIndex = if (pulseAmplitudeIndexPresent) parser.getFloatValue(FORMAT_SFLOAT) else null - - return PulseOximeterContinuousMeasurement( - spO2 = spO2, - pulseRate = pulseRate, - spO2Fast = spO2Fast, - pulseRateFast = pulseRateFast, - spO2Slow = spO2Slow, - pulseRateSlow = pulseRateSlow, - measurementStatus = measurementStatus, - sensorStatus = sensorStatus, - pulseAmplitudeIndex = pulseAmplitudeIndex - ) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/org/hydev/wearsync/bles/PulseOximeterSpotMeasurement.kt b/app/src/main/java/org/hydev/wearsync/bles/PulseOximeterSpotMeasurement.kt deleted file mode 100644 index 127d441..0000000 --- a/app/src/main/java/org/hydev/wearsync/bles/PulseOximeterSpotMeasurement.kt +++ /dev/null @@ -1,49 +0,0 @@ -package org.hydev.wearsync.bles - -import com.welie.blessed.BluetoothBytesParser -import com.welie.blessed.BluetoothBytesParser.Companion.FORMAT_SFLOAT -import com.welie.blessed.BluetoothBytesParser.Companion.FORMAT_UINT16 -import com.welie.blessed.BluetoothBytesParser.Companion.FORMAT_UINT8 -import java.util.* - -data class PulseOximeterSpotMeasurement( - val spO2: Float, - val pulseRate: Float, - val pulseAmplitudeIndex: Float?, - val timestamp: Date?, - val isDeviceClockSet: Boolean, - val measurementStatus: Int?, - val sensorStatus: Int?, - val createdAt: Date = Calendar.getInstance().time -) { - companion object { - fun fromBytes(value: ByteArray): PulseOximeterSpotMeasurement - { - val parser = BluetoothBytesParser(value) - val flags = parser.getIntValue(FORMAT_UINT8) - val timestampPresent = flags and 0x01 > 0 - val measurementStatusPresent = flags and 0x02 > 0 - val sensorStatusPresent = flags and 0x04 > 0 - val pulseAmplitudeIndexPresent = flags and 0x08 > 0 - val isDeviceClockSet = flags and 0x10 == 0 - - val spO2 = parser.getFloatValue(FORMAT_SFLOAT) - val pulseRate = parser.getFloatValue(FORMAT_SFLOAT) - val timestamp = if (timestampPresent) parser.dateTime else null - val measurementStatus = if (measurementStatusPresent) parser.getIntValue(FORMAT_UINT16) else null - val sensorStatus = if (sensorStatusPresent) parser.getIntValue(FORMAT_UINT16) else null - if (sensorStatusPresent) parser.getIntValue(FORMAT_UINT8) // Reserved byte - val pulseAmplitudeIndex = if (pulseAmplitudeIndexPresent) parser.getFloatValue(FORMAT_SFLOAT) else null - - return PulseOximeterSpotMeasurement( - spO2 = spO2, - pulseRate = pulseRate, - measurementStatus = measurementStatus, - sensorStatus = sensorStatus, - pulseAmplitudeIndex = pulseAmplitudeIndex, - timestamp = timestamp, - isDeviceClockSet = isDeviceClockSet - ) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/org/hydev/wearsync/bles/TemperatureMeasurement.kt b/app/src/main/java/org/hydev/wearsync/bles/TemperatureMeasurement.kt deleted file mode 100644 index 76c7c85..0000000 --- a/app/src/main/java/org/hydev/wearsync/bles/TemperatureMeasurement.kt +++ /dev/null @@ -1,37 +0,0 @@ -package org.hydev.wearsync.bles - -import com.welie.blessed.BluetoothBytesParser -import com.welie.blessed.BluetoothBytesParser.Companion.FORMAT_FLOAT -import com.welie.blessed.BluetoothBytesParser.Companion.FORMAT_UINT8 -import java.nio.ByteOrder -import java.util.* - -data class TemperatureMeasurement( - val temperatureValue: Float, - val unit: ObservationUnit, - val timestamp: Date?, - val type: TemperatureType, - val createdAt: Date = Calendar.getInstance().time -) { - companion object { - fun fromBytes(value: ByteArray): TemperatureMeasurement - { - val parser = BluetoothBytesParser(value, ByteOrder.LITTLE_ENDIAN) - val flags = parser.getIntValue(FORMAT_UINT8) - val unit = if (flags and 0x01 > 0) ObservationUnit.Fahrenheit else ObservationUnit.Celsius - val timestampPresent = flags and 0x02 > 0 - val typePresent = flags and 0x04 > 0 - - val temperatureValue = parser.getFloatValue(FORMAT_FLOAT) - val timestamp = if (timestampPresent) parser.dateTime else null - val type = if (typePresent) TemperatureType.fromValue(parser.getIntValue(FORMAT_UINT8)) else TemperatureType.Unknown - - return TemperatureMeasurement( - unit = unit, - temperatureValue = temperatureValue, - timestamp = timestamp, - type = type - ) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/org/hydev/wearsync/bles/TemperatureType.kt b/app/src/main/java/org/hydev/wearsync/bles/TemperatureType.kt deleted file mode 100644 index 7db5307..0000000 --- a/app/src/main/java/org/hydev/wearsync/bles/TemperatureType.kt +++ /dev/null @@ -1,15 +0,0 @@ -package org.hydev.wearsync.bles - -enum class TemperatureType(val value: Int) { - Unknown(0), Armpit(1), Body(2), Ear(3), Finger(4), GastroIntestinalTract(5), Mouth(6), Rectum(7), Toe(8), Tympanum(9); - - companion object { - fun fromValue(value: Int): TemperatureType - { - for (type in values()) { - if (type.value == value) return type - } - return Unknown - } - } -} \ No newline at end of file diff --git a/app/src/main/java/org/hydev/wearsync/bles/WeightMeasurement.kt b/app/src/main/java/org/hydev/wearsync/bles/WeightMeasurement.kt deleted file mode 100644 index 4a9c787..0000000 --- a/app/src/main/java/org/hydev/wearsync/bles/WeightMeasurement.kt +++ /dev/null @@ -1,48 +0,0 @@ -package org.hydev.wearsync.bles - -import com.welie.blessed.BluetoothBytesParser -import com.welie.blessed.BluetoothBytesParser.Companion.FORMAT_UINT16 -import com.welie.blessed.BluetoothBytesParser.Companion.FORMAT_UINT8 -import org.hydev.wearsync.bles.ObservationUnit.Kilograms -import org.hydev.wearsync.bles.ObservationUnit.Pounds -import java.util.* -import kotlin.math.round - -data class WeightMeasurement( - val weight: Float, - val unit: ObservationUnit, - val timestamp: Date?, - val userID: Int?, - val bmi: Float?, - val heightInMetersOrInches: Float?, - val createdAt: Date = Calendar.getInstance().time -) { - companion object { - fun fromBytes(value: ByteArray): WeightMeasurement - { - val parser = BluetoothBytesParser(value) - val flags = parser.getIntValue(FORMAT_UINT8) - val unit = if (flags and 0x01 > 0) Pounds else Kilograms - val timestampPresent = flags and 0x02 > 0 - val userIDPresent = flags and 0x04 > 0 - val bmiAndHeightPresent = flags and 0x08 > 0 - - val weightMultiplier = if (unit == Kilograms) 0.005f else 0.01f - val weight = parser.getIntValue(FORMAT_UINT16) * weightMultiplier - val timestamp = if (timestampPresent) parser.dateTime else null - val userID = if (userIDPresent) parser.getIntValue(FORMAT_UINT8) else null - val bmi = if (bmiAndHeightPresent) parser.getIntValue(FORMAT_UINT16) * 0.1f else null - val heightMultiplier = if (unit == Kilograms) 0.001f else 0.1f - val height = if (bmiAndHeightPresent) parser.getIntValue(FORMAT_UINT16) * heightMultiplier else null - - return WeightMeasurement( - weight = round(weight * 100) / 100, - unit = unit, - timestamp = timestamp, - userID = userID, - bmi = bmi, - heightInMetersOrInches = height - ) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/org/hydev/wearsync/bles/decoders/BatteryDecoder.kt b/app/src/main/java/org/hydev/wearsync/bles/decoders/BatteryDecoder.kt new file mode 100644 index 0000000..ba93845 --- /dev/null +++ b/app/src/main/java/org/hydev/wearsync/bles/decoders/BatteryDecoder.kt @@ -0,0 +1,15 @@ +package org.hydev.wearsync.bles.decoders + +import com.welie.blessed.asUInt8 +import java.util.* + +class BatteryDecoder : IDecoder +{ + override val sid = UUID.fromString("0000180F-0000-1000-8000-00805f9b34fb") + override val cid = UUID.fromString("00002A19-0000-1000-8000-00805f9b34fb") + + override fun decode(value: ByteArray): UInt + { + return value.asUInt8()!! + } +} \ No newline at end of file diff --git a/app/src/main/java/org/hydev/wearsync/bles/decoders/BloodPressureMeasurement.kt b/app/src/main/java/org/hydev/wearsync/bles/decoders/BloodPressureMeasurement.kt new file mode 100644 index 0000000..477f609 --- /dev/null +++ b/app/src/main/java/org/hydev/wearsync/bles/decoders/BloodPressureMeasurement.kt @@ -0,0 +1,87 @@ +package org.hydev.wearsync.bles.decoders + +import com.welie.blessed.BluetoothBytesParser +import org.hydev.wearsync.bles.ObservationUnit +import java.nio.ByteOrder +import java.util.* + +class BloodPressureStatus internal constructor(measurementStatus: Int) { + /** + * Body Movement Detected + */ + val isBodyMovementDetected: Boolean + /** + * Cuff is too loose + */ + val isCuffTooLoose: Boolean + /** + * Irregular pulse detected + */ + val isIrregularPulseDetected: Boolean + /** + * Pulse is not in normal range + */ + val isPulseNotInRange: Boolean + /** + * Improper measurement position + */ + val isImproperMeasurementPosition: Boolean + + init { + isBodyMovementDetected = measurementStatus and 0x0001 > 0 + isCuffTooLoose = measurementStatus and 0x0002 > 0 + isIrregularPulseDetected = measurementStatus and 0x0004 > 0 + isPulseNotInRange = measurementStatus and 0x0008 > 0 + isImproperMeasurementPosition = measurementStatus and 0x0020 > 0 + } +} + +data class BloodPressureMeasurement( + val systolic: Float, + val diastolic: Float, + val meanArterialPressure: Float, + val unit: ObservationUnit, + val timestamp: Date?, + val pulseRate: Float?, + val userID: Int?, + val measurementStatus: BloodPressureStatus?, + val createdAt: Date = Calendar.getInstance().time +) + +class BloodPressureDecoder : IDecoder +{ + override val sid = UUID.fromString("00001810-0000-1000-8000-00805f9b34fb") + override val cid = UUID.fromString("00002A35-0000-1000-8000-00805f9b34fb") + + override fun decode(value: ByteArray): BloodPressureMeasurement + { + val parser = BluetoothBytesParser(value, ByteOrder.LITTLE_ENDIAN) + val flags = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT8) + val unit = if (flags and 0x01 > 0) ObservationUnit.MMHG else ObservationUnit.KPA + val timestampPresent = flags and 0x02 > 0 + val pulseRatePresent = flags and 0x04 > 0 + val userIdPresent = flags and 0x08 > 0 + val measurementStatusPresent = flags and 0x10 > 0 + + val systolic = parser.getFloatValue(BluetoothBytesParser.FORMAT_SFLOAT) + val diastolic = parser.getFloatValue(BluetoothBytesParser.FORMAT_SFLOAT) + val meanArterialPressure = parser.getFloatValue(BluetoothBytesParser.FORMAT_SFLOAT) + val timestamp = if (timestampPresent) parser.dateTime else null + val pulseRate = if (pulseRatePresent) parser.getFloatValue(BluetoothBytesParser.FORMAT_SFLOAT) else null + val userID = if (userIdPresent) parser.getIntValue(BluetoothBytesParser.FORMAT_UINT8) else null + val status = if (measurementStatusPresent) BloodPressureStatus(parser.getIntValue( + BluetoothBytesParser.FORMAT_UINT16 + )) else null + + return BloodPressureMeasurement( + systolic = systolic, + diastolic = diastolic, + meanArterialPressure = meanArterialPressure, + unit = unit, + timestamp = timestamp, + pulseRate = pulseRate, + userID = userID, + measurementStatus = status + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/hydev/wearsync/bles/decoders/GlucoseMeasurement.kt b/app/src/main/java/org/hydev/wearsync/bles/decoders/GlucoseMeasurement.kt new file mode 100644 index 0000000..0ebdb27 --- /dev/null +++ b/app/src/main/java/org/hydev/wearsync/bles/decoders/GlucoseMeasurement.kt @@ -0,0 +1,55 @@ +package org.hydev.wearsync.bles.decoders + +import com.welie.blessed.BluetoothBytesParser +import com.welie.blessed.BluetoothBytesParser.Companion.FORMAT_SFLOAT +import com.welie.blessed.BluetoothBytesParser.Companion.FORMAT_SINT16 +import com.welie.blessed.BluetoothBytesParser.Companion.FORMAT_UINT16 +import com.welie.blessed.BluetoothBytesParser.Companion.FORMAT_UINT8 +import org.hydev.wearsync.bles.BluetoothHandler.Companion.GLUCOSE_SERVICE_UUID +import org.hydev.wearsync.bles.ObservationUnit +import org.hydev.wearsync.bles.ObservationUnit.MiligramPerDeciliter +import org.hydev.wearsync.bles.ObservationUnit.MmolPerLiter +import java.util.* + +data class GlucoseMeasurement( + val value: Float?, + val unit: ObservationUnit, + val timestamp: Date?, + val sequenceNumber: Int, + val contextWillFollow: Boolean, + val createdAt: Date = Calendar.getInstance().time +) + +class GlucoseDecoder : IDecoder +{ + override val sid = GLUCOSE_SERVICE_UUID + override val cid = UUID.fromString("00002A18-0000-1000-8000-00805f9b34fb") + + override fun decode(value: ByteArray): GlucoseMeasurement + { + val parser = BluetoothBytesParser(value) + val flags: Int = parser.getIntValue(FORMAT_UINT8) + val timeOffsetPresent = flags and 0x01 > 0 + val typeAndLocationPresent = flags and 0x02 > 0 + val unit = if (flags and 0x04 > 0) MmolPerLiter else MiligramPerDeciliter + val contextWillFollow = flags and 0x10 > 0 + + val sequenceNumber = parser.getIntValue(FORMAT_UINT16) + var timestamp = parser.dateTime + if (timeOffsetPresent) { + val timeOffset: Int = parser.getIntValue(FORMAT_SINT16) + timestamp = Date(timestamp.time + timeOffset * 60000) + } + + val multiplier = if (unit === MiligramPerDeciliter) 100000 else 1000 + val glucoseValue = if (typeAndLocationPresent) parser.getFloatValue(FORMAT_SFLOAT) * multiplier else null + + return GlucoseMeasurement( + unit = unit, + timestamp = timestamp, + sequenceNumber = sequenceNumber, + value = glucoseValue, + contextWillFollow = contextWillFollow + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/hydev/wearsync/bles/decoders/HeartRateMeasurement.kt b/app/src/main/java/org/hydev/wearsync/bles/decoders/HeartRateMeasurement.kt new file mode 100644 index 0000000..7857336 --- /dev/null +++ b/app/src/main/java/org/hydev/wearsync/bles/decoders/HeartRateMeasurement.kt @@ -0,0 +1,56 @@ +package org.hydev.wearsync.bles.decoders + +import com.welie.blessed.BluetoothBytesParser +import com.welie.blessed.BluetoothBytesParser.Companion.FORMAT_UINT16 +import com.welie.blessed.BluetoothBytesParser.Companion.FORMAT_UINT8 +import org.hydev.wearsync.bles.SensorContactFeature +import java.nio.ByteOrder +import java.util.* + +data class HeartRateMeasurement( + val pulse: Int, + val energyExpended: Int?, + val rrIntervals: IntArray, + val sensorContactStatus: SensorContactFeature, + val createdAt: Date = Calendar.getInstance().time +) + +class HeartRateDecoder : IDecoder +{ + override val sid = UUID.fromString("0000180D-0000-1000-8000-00805f9b34fb") + override val cid = UUID.fromString("00002A37-0000-1000-8000-00805f9b34fb") + + override fun decode(value: ByteArray): HeartRateMeasurement + { + val parser = BluetoothBytesParser(value, ByteOrder.LITTLE_ENDIAN) + val flags = parser.getIntValue(FORMAT_UINT8) + val pulse = if (flags and 0x01 == 0) parser.getIntValue(FORMAT_UINT8) else parser.getIntValue(FORMAT_UINT16) + val sensorContactStatusFlag = flags and 0x06 shr 1 + val energyExpenditurePresent = flags and 0x08 > 0 + val rrIntervalPresent = flags and 0x10 > 0 + + val sensorContactStatus = when (sensorContactStatusFlag) { + 0, 1 -> SensorContactFeature.NotSupported + 2 -> SensorContactFeature.SupportedNoContact + 3 -> SensorContactFeature.SupportedAndContact + else -> SensorContactFeature.NotSupported + } + + val energyExpended = if (energyExpenditurePresent) parser.getIntValue(FORMAT_UINT16) else null + + val rrArray = ArrayList() + if (rrIntervalPresent) { + while (parser.offset < value.size) { + val rrInterval = parser.getIntValue(FORMAT_UINT16) + rrArray.add((rrInterval.toDouble() / 1024.0 * 1000.0).toInt()) + } + } + + return HeartRateMeasurement( + pulse = pulse, + energyExpended = energyExpended, + sensorContactStatus = sensorContactStatus, + rrIntervals = rrArray.toIntArray() + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/hydev/wearsync/bles/decoders/IDecoder.kt b/app/src/main/java/org/hydev/wearsync/bles/decoders/IDecoder.kt new file mode 100644 index 0000000..e30b9d4 --- /dev/null +++ b/app/src/main/java/org/hydev/wearsync/bles/decoders/IDecoder.kt @@ -0,0 +1,11 @@ +package org.hydev.wearsync.bles.decoders + +import java.util.* + +interface IDecoder +{ + val sid: UUID + val cid: UUID + + fun decode(value: ByteArray): T +} \ No newline at end of file diff --git a/app/src/main/java/org/hydev/wearsync/bles/decoders/ManufacturerDecoder.kt b/app/src/main/java/org/hydev/wearsync/bles/decoders/ManufacturerDecoder.kt new file mode 100644 index 0000000..89858a6 --- /dev/null +++ b/app/src/main/java/org/hydev/wearsync/bles/decoders/ManufacturerDecoder.kt @@ -0,0 +1,12 @@ +package org.hydev.wearsync.bles.decoders + +import com.welie.blessed.asString +import java.util.* + +class ManufacturerDecoder : IDecoder +{ + override val sid = UUID.fromString("0000180A-0000-1000-8000-00805f9b34fb") + override val cid = UUID.fromString("00002A29-0000-1000-8000-00805f9b34fb") + + override fun decode(value: ByteArray) = value.asString() +} \ No newline at end of file diff --git a/app/src/main/java/org/hydev/wearsync/bles/decoders/ModelDecoder.kt b/app/src/main/java/org/hydev/wearsync/bles/decoders/ModelDecoder.kt new file mode 100644 index 0000000..3390128 --- /dev/null +++ b/app/src/main/java/org/hydev/wearsync/bles/decoders/ModelDecoder.kt @@ -0,0 +1,15 @@ +package org.hydev.wearsync.bles.decoders + +import com.welie.blessed.asString +import java.util.* + +class ModelDecoder : IDecoder +{ + override val sid = UUID.fromString("0000180A-0000-1000-8000-00805f9b34fb") + override val cid = UUID.fromString("00002A24-0000-1000-8000-00805f9b34fb") + + override fun decode(value: ByteArray): String + { + return value.asString() + } +} \ No newline at end of file diff --git a/app/src/main/java/org/hydev/wearsync/bles/decoders/PulseOximeterContinuousMeasurement.kt b/app/src/main/java/org/hydev/wearsync/bles/decoders/PulseOximeterContinuousMeasurement.kt new file mode 100644 index 0000000..67d459e --- /dev/null +++ b/app/src/main/java/org/hydev/wearsync/bles/decoders/PulseOximeterContinuousMeasurement.kt @@ -0,0 +1,61 @@ +package org.hydev.wearsync.bles.decoders + +import com.welie.blessed.BluetoothBytesParser +import java.util.* + +data class PulseOximeterContinuousMeasurement( + val spO2: Float, + val pulseRate: Float, + val spO2Fast: Float?, + val pulseRateFast: Float?, + val spO2Slow: Float?, + val pulseRateSlow: Float?, + val pulseAmplitudeIndex: Float?, + val measurementStatus: Int?, + val sensorStatus: Int?, + val createdAt: Date = Calendar.getInstance().time +) + +class PLXContinuousDecoder : IDecoder { + + override val sid = UUID.fromString("00001822-0000-1000-8000-00805f9b34fb") + override val cid = UUID.fromString("00002a5f-0000-1000-8000-00805f9b34fb") + + override fun decode(value: ByteArray): PulseOximeterContinuousMeasurement + { + val parser = BluetoothBytesParser(value) + val flags = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT8) + val spo2FastPresent = flags and 0x01 > 0 + val spo2SlowPresent = flags and 0x02 > 0 + val measurementStatusPresent = flags and 0x04 > 0 + val sensorStatusPresent = flags and 0x08 > 0 + val pulseAmplitudeIndexPresent = flags and 0x10 > 0 + + val spO2 = parser.getFloatValue(BluetoothBytesParser.FORMAT_SFLOAT) + val pulseRate = parser.getFloatValue(BluetoothBytesParser.FORMAT_SFLOAT) + val spO2Fast = if (spo2FastPresent) parser.getFloatValue(BluetoothBytesParser.FORMAT_SFLOAT) else null + val pulseRateFast = if (spo2FastPresent) parser.getFloatValue(BluetoothBytesParser.FORMAT_SFLOAT) else null + val spO2Slow = if (spo2SlowPresent) parser.getFloatValue(BluetoothBytesParser.FORMAT_SFLOAT) else null + val pulseRateSlow = if (spo2SlowPresent) parser.getFloatValue(BluetoothBytesParser.FORMAT_SFLOAT) else null + val measurementStatus = if (measurementStatusPresent) parser.getIntValue( + BluetoothBytesParser.FORMAT_UINT16 + ) else null + val sensorStatus = if (sensorStatusPresent) parser.getIntValue(BluetoothBytesParser.FORMAT_UINT16) else null + if (sensorStatusPresent) parser.getIntValue(BluetoothBytesParser.FORMAT_UINT8) // Reserved byte + val pulseAmplitudeIndex = if (pulseAmplitudeIndexPresent) parser.getFloatValue( + BluetoothBytesParser.FORMAT_SFLOAT + ) else null + + return PulseOximeterContinuousMeasurement( + spO2 = spO2, + pulseRate = pulseRate, + spO2Fast = spO2Fast, + pulseRateFast = pulseRateFast, + spO2Slow = spO2Slow, + pulseRateSlow = pulseRateSlow, + measurementStatus = measurementStatus, + sensorStatus = sensorStatus, + pulseAmplitudeIndex = pulseAmplitudeIndex + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/hydev/wearsync/bles/decoders/PulseOximeterSpotMeasurement.kt b/app/src/main/java/org/hydev/wearsync/bles/decoders/PulseOximeterSpotMeasurement.kt new file mode 100644 index 0000000..456d072 --- /dev/null +++ b/app/src/main/java/org/hydev/wearsync/bles/decoders/PulseOximeterSpotMeasurement.kt @@ -0,0 +1,57 @@ +package org.hydev.wearsync.bles.decoders + +import com.welie.blessed.BluetoothBytesParser +import java.util.* + +data class PulseOximeterSpotMeasurement( + val spO2: Float, + val pulseRate: Float, + val pulseAmplitudeIndex: Float?, + val timestamp: Date?, + val isDeviceClockSet: Boolean, + val measurementStatus: Int?, + val sensorStatus: Int?, + val createdAt: Date = Calendar.getInstance().time +) + +/** + * Pulse Oximeter Service (PLX) + */ +class PLXSpotDecoder : IDecoder +{ + override val sid = UUID.fromString("00001822-0000-1000-8000-00805f9b34fb") + override val cid = UUID.fromString("00002a5e-0000-1000-8000-00805f9b34fb") + + override fun decode(value: ByteArray): PulseOximeterSpotMeasurement + { + val parser = BluetoothBytesParser(value) + val flags = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT8) + val timestampPresent = flags and 0x01 > 0 + val measurementStatusPresent = flags and 0x02 > 0 + val sensorStatusPresent = flags and 0x04 > 0 + val pulseAmplitudeIndexPresent = flags and 0x08 > 0 + val isDeviceClockSet = flags and 0x10 == 0 + + val spO2 = parser.getFloatValue(BluetoothBytesParser.FORMAT_SFLOAT) + val pulseRate = parser.getFloatValue(BluetoothBytesParser.FORMAT_SFLOAT) + val timestamp = if (timestampPresent) parser.dateTime else null + val measurementStatus = if (measurementStatusPresent) parser.getIntValue( + BluetoothBytesParser.FORMAT_UINT16 + ) else null + val sensorStatus = if (sensorStatusPresent) parser.getIntValue(BluetoothBytesParser.FORMAT_UINT16) else null + if (sensorStatusPresent) parser.getIntValue(BluetoothBytesParser.FORMAT_UINT8) // Reserved byte + val pulseAmplitudeIndex = if (pulseAmplitudeIndexPresent) parser.getFloatValue( + BluetoothBytesParser.FORMAT_SFLOAT + ) else null + + return PulseOximeterSpotMeasurement( + spO2 = spO2, + pulseRate = pulseRate, + measurementStatus = measurementStatus, + sensorStatus = sensorStatus, + pulseAmplitudeIndex = pulseAmplitudeIndex, + timestamp = timestamp, + isDeviceClockSet = isDeviceClockSet + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/hydev/wearsync/bles/decoders/TemperatureMeasurement.kt b/app/src/main/java/org/hydev/wearsync/bles/decoders/TemperatureMeasurement.kt new file mode 100644 index 0000000..fb8e0ae --- /dev/null +++ b/app/src/main/java/org/hydev/wearsync/bles/decoders/TemperatureMeasurement.kt @@ -0,0 +1,59 @@ +package org.hydev.wearsync.bles.decoders + +import com.welie.blessed.BluetoothBytesParser +import org.hydev.wearsync.bles.ObservationUnit +import java.nio.ByteOrder +import java.util.* + +enum class TemperatureType(val value: Int) { + Unknown(0), Armpit(1), Body(2), Ear(3), Finger(4), GastroIntestinalTract(5), Mouth(6), Rectum(7), Toe(8), Tympanum(9); + + companion object { + fun fromValue(value: Int): TemperatureType + { + for (type in values()) { + if (type.value == value) return type + } + return Unknown + } + } +} + +data class TemperatureMeasurement( + val temperatureValue: Float, + val unit: ObservationUnit, + val timestamp: Date?, + val type: TemperatureType, + val createdAt: Date = Calendar.getInstance().time +) + +/** + * Health Thermometer service + */ +class TemperatureDecoder : IDecoder { + + override val sid = UUID.fromString("00001809-0000-1000-8000-00805f9b34fb") + override val cid = UUID.fromString("00002A1C-0000-1000-8000-00805f9b34fb") + + override fun decode(value: ByteArray): TemperatureMeasurement + { + val parser = BluetoothBytesParser(value, ByteOrder.LITTLE_ENDIAN) + val flags = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT8) + val unit = if (flags and 0x01 > 0) ObservationUnit.Fahrenheit else ObservationUnit.Celsius + val timestampPresent = flags and 0x02 > 0 + val typePresent = flags and 0x04 > 0 + + val temperatureValue = parser.getFloatValue(BluetoothBytesParser.FORMAT_FLOAT) + val timestamp = if (timestampPresent) parser.dateTime else null + val type = if (typePresent) TemperatureType.fromValue(parser.getIntValue( + BluetoothBytesParser.FORMAT_UINT8 + )) else TemperatureType.Unknown + + return TemperatureMeasurement( + unit = unit, + temperatureValue = temperatureValue, + timestamp = timestamp, + type = type + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/hydev/wearsync/bles/decoders/WeightMeasurement.kt b/app/src/main/java/org/hydev/wearsync/bles/decoders/WeightMeasurement.kt new file mode 100644 index 0000000..53d664b --- /dev/null +++ b/app/src/main/java/org/hydev/wearsync/bles/decoders/WeightMeasurement.kt @@ -0,0 +1,52 @@ +package org.hydev.wearsync.bles.decoders + +import com.welie.blessed.BluetoothBytesParser +import org.hydev.wearsync.bles.ObservationUnit +import java.util.* +import kotlin.math.round + +data class WeightMeasurement( + val weight: Float, + val unit: ObservationUnit, + val timestamp: Date?, + val userID: Int?, + val bmi: Float?, + val heightInMetersOrInches: Float?, + val createdAt: Date = Calendar.getInstance().time +) + +/** + * Weight Scale Service + */ +class WeightDecoder : IDecoder { + + override val sid = UUID.fromString("0000181D-0000-1000-8000-00805f9b34fb") + override val cid = UUID.fromString("00002A9D-0000-1000-8000-00805f9b34fb") + + override fun decode(value: ByteArray): WeightMeasurement + { + val parser = BluetoothBytesParser(value) + val flags = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT8) + val unit = if (flags and 0x01 > 0) ObservationUnit.Pounds else ObservationUnit.Kilograms + val timestampPresent = flags and 0x02 > 0 + val userIDPresent = flags and 0x04 > 0 + val bmiAndHeightPresent = flags and 0x08 > 0 + + val weightMultiplier = if (unit == ObservationUnit.Kilograms) 0.005f else 0.01f + val weight = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT16) * weightMultiplier + val timestamp = if (timestampPresent) parser.dateTime else null + val userID = if (userIDPresent) parser.getIntValue(BluetoothBytesParser.FORMAT_UINT8) else null + val bmi = if (bmiAndHeightPresent) parser.getIntValue(BluetoothBytesParser.FORMAT_UINT16) * 0.1f else null + val heightMultiplier = if (unit == ObservationUnit.Kilograms) 0.001f else 0.1f + val height = if (bmiAndHeightPresent) parser.getIntValue(BluetoothBytesParser.FORMAT_UINT16) * heightMultiplier else null + + return WeightMeasurement( + weight = round(weight * 100) / 100, + unit = unit, + timestamp = timestamp, + userID = userID, + bmi = bmi, + heightInMetersOrInches = height + ) + } +} \ No newline at end of file