[O] Refactor notification system
This commit is contained in:
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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<UInt>(UNLIMITED)
|
||||
val heartRateChannel = Channel<HeartRateMeasurement>(UNLIMITED)
|
||||
val bloodPressureChannel = Channel<BloodPressureMeasurement>(UNLIMITED)
|
||||
val glucoseChannel = Channel<GlucoseMeasurement>(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 <T> BluetoothPeripheral.setupNotification(channel: Channel<T>, dec: IDecoder<T>)
|
||||
{
|
||||
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 <T> BluetoothPeripheral.read(decoder: IDecoder<T>) =
|
||||
getCharacteristic(decoder.sid, decoder.cid)?.let { decoder.decode(readCharacteristic(it)) }
|
||||
}
|
||||
|
||||
@JvmField
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Int>()
|
||||
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()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.hydev.wearsync.bles.decoders
|
||||
|
||||
import com.welie.blessed.asUInt8
|
||||
import java.util.*
|
||||
|
||||
class BatteryDecoder : IDecoder<UInt>
|
||||
{
|
||||
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()!!
|
||||
}
|
||||
}
|
||||
@@ -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<BloodPressureMeasurement>
|
||||
{
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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<GlucoseMeasurement>
|
||||
{
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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<HeartRateMeasurement>
|
||||
{
|
||||
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<Int>()
|
||||
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()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.hydev.wearsync.bles.decoders
|
||||
|
||||
import java.util.*
|
||||
|
||||
interface IDecoder<T>
|
||||
{
|
||||
val sid: UUID
|
||||
val cid: UUID
|
||||
|
||||
fun decode(value: ByteArray): T
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.hydev.wearsync.bles.decoders
|
||||
|
||||
import com.welie.blessed.asString
|
||||
import java.util.*
|
||||
|
||||
class ManufacturerDecoder : IDecoder<String>
|
||||
{
|
||||
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()
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.hydev.wearsync.bles.decoders
|
||||
|
||||
import com.welie.blessed.asString
|
||||
import java.util.*
|
||||
|
||||
class ModelDecoder : IDecoder<String>
|
||||
{
|
||||
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()
|
||||
}
|
||||
}
|
||||
+61
@@ -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<PulseOximeterContinuousMeasurement> {
|
||||
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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<PulseOximeterSpotMeasurement>
|
||||
{
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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<TemperatureMeasurement> {
|
||||
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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<WeightMeasurement> {
|
||||
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user