Skip to content

Commit

Permalink
VIT-7195: Support Temperature and RespiratoryRate (#154)
Browse files Browse the repository at this point in the history
  • Loading branch information
andersio authored Aug 15, 2024
1 parent 372ae22 commit 4d91845
Show file tree
Hide file tree
Showing 8 changed files with 88 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -230,4 +230,5 @@ sealed class SampleType(val unit: String) {
object BloodPressureSystolic : SampleType("mmHg")
object BloodPressureDiastolic : SampleType("mmHg")
object Water : SampleType("ml")
object Temperature : SampleType("°C")
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ enum class IngestibleTimeseriesResource {
@Json(name = "glucose") BloodGlucose,
@Json(name = "water") Water,
@Json(name = "heartrate") HeartRate,
@Json(name = "heartrate_variability") HeartRateVariability;
@Json(name = "heartrate_variability") HeartRateVariability,
@Json(name = "respiratory_rate") RespiratoryRate,
@Json(name = "temperature") Temperature;

// Use the Json name also when converting to string.
// This is intended for Retrofit request parameter serialization.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package io.tryvital.vitalhealthconnect.model

import android.health.connect.datatypes.units.Power
import androidx.health.connect.client.records.*

import kotlin.reflect.KClass
Expand All @@ -25,6 +24,9 @@ sealed class VitalResource(val name: String) {
object DistanceWalkingRunning : VitalResource("distanceWalkingRunning")
object Vo2Max : VitalResource("vo2Max")

object RespiratoryRate : VitalResource("respiratoryRate")
object Temperature : VitalResource("temperature")

override fun toString(): String {
return name
}
Expand All @@ -40,6 +42,8 @@ sealed class VitalResource(val name: String) {
HeartRateVariability -> 23
Vo2Max -> 24
Water -> 25
RespiratoryRate -> 26
Temperature -> 27
Workout -> 31
Steps -> 51
DistanceWalkingRunning -> 52
Expand Down Expand Up @@ -70,31 +74,12 @@ sealed class VitalResource(val name: String) {
Water,
HeartRateVariability,
MenstrualCycle,
RespiratoryRate,
Temperature,
)
}

fun valueOf(value: String): VitalResource {
return when (value) {
"profile" -> Profile
"body" -> Body
"workout" -> Workout
"activity" -> Activity
"sleep" -> Sleep
"glucose" -> Glucose
"bloodPressure" -> BloodPressure
"heartRate" -> HeartRate
"steps" -> Steps
"activeEnergyBurned" -> ActiveEnergyBurned
"basalEnergyBurned" -> BasalEnergyBurned
"floorsClimbed" -> FloorsClimbed
"distanceWalkingRunning" -> DistanceWalkingRunning
"vo2Max" -> Vo2Max
"water" -> Water
"heartRateVariability" -> HeartRateVariability
"menstrualCycle" -> MenstrualCycle
else -> throw IllegalArgumentException("No object io.tryvital.vitalhealthconnect.model.HealthResource.$value")
}
}
fun valueOf(value: String) = values().first { it.name == value }
}
}

Expand Down Expand Up @@ -222,6 +207,9 @@ internal fun VitalResource.recordTypeDependencies(): RecordTypeRequirements = wh
VitalResource.Steps -> RecordTypeRequirements.single(StepsRecord::class)
VitalResource.Vo2Max -> RecordTypeRequirements.single(Vo2MaxRecord::class)

VitalResource.RespiratoryRate -> RecordTypeRequirements.single(RespiratoryRateRecord::class)
VitalResource.Temperature -> RecordTypeRequirements.single(BodyTemperatureRecord::class)

VitalResource.Body -> RecordTypeRequirements(
required = emptyList(),
optional = listOf(
Expand All @@ -237,7 +225,6 @@ internal fun VitalResource.recordTypeDependencies(): RecordTypeRequirements = wh
HeartRateRecord::class,
HeartRateVariabilityRmssdRecord::class,
RespiratoryRateRecord::class,
RestingHeartRateRecord::class,
OxygenSaturationRecord::class,
)
)
Expand Down Expand Up @@ -279,24 +266,12 @@ internal fun VitalResource.recordTypeDependencies(): RecordTypeRequirements = wh
internal fun VitalResource.recordTypeChangesToTriggerSync(): List<KClass<out Record>> = when (this) {
VitalResource.Water -> listOf(HydrationRecord::class)
VitalResource.Activity -> listOf()
VitalResource.ActiveEnergyBurned -> listOf(
ActiveCaloriesBurnedRecord::class,
)
VitalResource.BasalEnergyBurned -> listOf(
BasalMetabolicRateRecord::class,
)
VitalResource.DistanceWalkingRunning -> listOf(
DistanceRecord::class,
)
VitalResource.FloorsClimbed -> listOf(
FloorsClimbedRecord::class,
)
VitalResource.Steps -> listOf(
StepsRecord::class,
)
VitalResource.Vo2Max -> listOf(
Vo2MaxRecord::class,
)
VitalResource.ActiveEnergyBurned -> listOf(ActiveCaloriesBurnedRecord::class)
VitalResource.BasalEnergyBurned -> listOf(BasalMetabolicRateRecord::class)
VitalResource.DistanceWalkingRunning -> listOf(DistanceRecord::class)
VitalResource.FloorsClimbed -> listOf(FloorsClimbedRecord::class)
VitalResource.Steps -> listOf(StepsRecord::class)
VitalResource.Vo2Max -> listOf(Vo2MaxRecord::class)
VitalResource.BloodPressure -> listOf(BloodPressureRecord::class)
VitalResource.Body -> listOf(BodyFatRecord::class, WeightRecord::class)
VitalResource.Glucose -> listOf(BloodGlucoseRecord::class)
Expand All @@ -313,4 +288,6 @@ internal fun VitalResource.recordTypeChangesToTriggerSync(): List<KClass<out Rec
IntermenstrualBleedingRecord::class,
SexualActivityRecord::class,
)
VitalResource.RespiratoryRate -> listOf(RespiratoryRateRecord::class)
VitalResource.Temperature -> listOf(BodyTemperatureRecord::class)
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import androidx.health.connect.client.records.BasalMetabolicRateRecord
import androidx.health.connect.client.records.BloodGlucoseRecord
import androidx.health.connect.client.records.BloodPressureRecord
import androidx.health.connect.client.records.BodyFatRecord
import androidx.health.connect.client.records.BodyTemperatureRecord
import androidx.health.connect.client.records.DistanceRecord
import androidx.health.connect.client.records.ExerciseSessionRecord
import androidx.health.connect.client.records.ExerciseSessionRecord.Companion.EXERCISE_TYPE_INT_TO_STRING_MAP
Expand Down Expand Up @@ -129,6 +130,14 @@ interface RecordProcessor {
options: ProcessorOptions,
): TimeSeriesData.QuantitySamples

suspend fun processRespiratoryRateRecords(
respiratoryRates: List<RespiratoryRateRecord>,
): TimeSeriesData.QuantitySamples

suspend fun processBodyTemperatureRecords(
temperatures: List<BodyTemperatureRecord>,
): TimeSeriesData.QuantitySamples

suspend fun processMenstrualCyclesFromRecords(
endDate: LocalDate,
startDate: LocalDate?,
Expand Down Expand Up @@ -615,6 +624,32 @@ internal class HealthConnectRecordProcessor(
)
}

override suspend fun processBodyTemperatureRecords(temperatures: List<BodyTemperatureRecord>) = TimeSeriesData.QuantitySamples(
IngestibleTimeseriesResource.Temperature,
temperatures.map {
quantitySample(
value = it.temperature.inCelsius,
unit = SampleType.Temperature.unit,
startDate = it.time,
endDate = it.time,
metadata = it.metadata,
)
}
)

override suspend fun processRespiratoryRateRecords(respiratoryRates: List<RespiratoryRateRecord>) = TimeSeriesData.QuantitySamples(
IngestibleTimeseriesResource.RespiratoryRate,
respiratoryRates.map {
quantitySample(
value = it.rate,
unit = SampleType.RespiratoryRate.unit,
startDate = it.time,
endDate = it.time,
metadata = it.metadata,
)
}
)

override suspend fun processActivities(
lastSynced: Instant?,
timeZone: TimeZone,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import androidx.health.connect.client.records.BasalMetabolicRateRecord
import androidx.health.connect.client.records.BloodGlucoseRecord
import androidx.health.connect.client.records.BloodPressureRecord
import androidx.health.connect.client.records.BodyFatRecord
import androidx.health.connect.client.records.BodyTemperatureRecord
import androidx.health.connect.client.records.CervicalMucusRecord
import androidx.health.connect.client.records.DistanceRecord
import androidx.health.connect.client.records.ExerciseSessionRecord
Expand Down Expand Up @@ -57,11 +58,6 @@ interface RecordReader {
endTime: Instant
): List<HeartRateVariabilityRmssdRecord>

suspend fun readRespiratoryRate(
startTime: Instant,
endTime: Instant
): List<RespiratoryRateRecord>

suspend fun readHeights(
startTime: Instant,
endTime: Instant
Expand Down Expand Up @@ -132,6 +128,9 @@ interface RecordReader {
endTime: Instant
): List<HydrationRecord>

suspend fun readRespiratoryRates(start: Instant, end: Instant): List<RespiratoryRateRecord>
suspend fun readBodyTemperatures(start: Instant, end: Instant): List<BodyTemperatureRecord>

suspend fun menstruationPeriod(start: Instant, end: Instant): List<MenstruationPeriodRecord>
suspend fun menstruationFlow(start: Instant, end: Instant): List<MenstruationFlowRecord>
suspend fun cervicalMucus(start: Instant, end: Instant): List<CervicalMucusRecord>
Expand Down Expand Up @@ -165,10 +164,6 @@ internal class HealthConnectRecordReader(
startTime: Instant, endTime: Instant
): List<HeartRateVariabilityRmssdRecord> = readRecords(startTime, endTime)

override suspend fun readRespiratoryRate(
startTime: Instant, endTime: Instant
): List<RespiratoryRateRecord> = readRecords(startTime, endTime)

override suspend fun readHeights(
startTime: Instant, endTime: Instant
): List<HeightRecord> = readRecords(startTime, endTime)
Expand Down Expand Up @@ -241,7 +236,11 @@ internal class HealthConnectRecordReader(
override suspend fun intermenstrualBleeding(start: Instant, end: Instant): List<IntermenstrualBleedingRecord>
= readRecords(start, end)
override suspend fun ovulationTest(start: Instant, end: Instant): List<OvulationTestRecord>
= readRecords(start, end)
= readRecords(start, end)
override suspend fun readRespiratoryRates(start: Instant, end: Instant): List<RespiratoryRateRecord>
= readRecords(start, end)
override suspend fun readBodyTemperatures(start: Instant, end: Instant): List<BodyTemperatureRecord>
= readRecords(start, end)

private suspend inline fun <reified T : Record> readRecords(
startTime: Instant, endTime: Instant
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import androidx.health.connect.client.records.BasalMetabolicRateRecord
import androidx.health.connect.client.records.BloodGlucoseRecord
import androidx.health.connect.client.records.BloodPressureRecord
import androidx.health.connect.client.records.BodyFatRecord
import androidx.health.connect.client.records.BodyTemperatureRecord
import androidx.health.connect.client.records.DistanceRecord
import androidx.health.connect.client.records.ExerciseSessionRecord
import androidx.health.connect.client.records.FloorsClimbedRecord
Expand All @@ -14,6 +15,7 @@ import androidx.health.connect.client.records.HeartRateVariabilityRmssdRecord
import androidx.health.connect.client.records.HeightRecord
import androidx.health.connect.client.records.HydrationRecord
import androidx.health.connect.client.records.Record
import androidx.health.connect.client.records.RespiratoryRateRecord
import androidx.health.connect.client.records.SleepSessionRecord
import androidx.health.connect.client.records.StepsRecord
import androidx.health.connect.client.records.Vo2MaxRecord
Expand Down Expand Up @@ -100,6 +102,16 @@ internal suspend fun processChangesResponse(
processorOptions
).let(ProcessedResourceData::TimeSeries)

VitalResource.RespiratoryRate -> processor.processRespiratoryRateRecords(
records.get<RespiratoryRateRecord>()
.filter { it.time <= endAdjusted },
).let(ProcessedResourceData::TimeSeries)

VitalResource.Temperature -> processor.processBodyTemperatureRecords(
records.get<BodyTemperatureRecord>()
.filter { it.time <= endAdjusted },
).let(ProcessedResourceData::TimeSeries)

VitalResource.Workout -> processor.processWorkoutsFromRecords(
exerciseRecords = records.get<ExerciseSessionRecord>()
.filter { it.endTime <= endAdjusted }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,14 @@ internal suspend fun readResourceByTimeRange(
processorOptions
).let(ProcessedResourceData::TimeSeries)

VitalResource.Temperature -> processor.processBodyTemperatureRecords(
reader.readBodyTemperatures(startTime, endTime),
).let(ProcessedResourceData::TimeSeries)

VitalResource.RespiratoryRate -> processor.processRespiratoryRateRecords(
reader.readRespiratoryRates(startTime, endTime),
).let(ProcessedResourceData::TimeSeries)

VitalResource.Workout -> processor.processWorkoutsFromRecords(
exerciseRecords = reader.readExerciseSessions(startTime, endTime)
).let(ProcessedResourceData::Summary)
Expand Down
1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
<uses-permission android:name="android.permission.health.READ_TOTAL_CALORIES_BURNED" />
<uses-permission android:name="android.permission.health.READ_VO2_MAX" />
<uses-permission android:name="android.permission.health.READ_WEIGHT" />
<uses-permission android:name="android.permission.health.READ_BODY_TEMPERATURE" />

<uses-permission android:name="android.permission.health.READ_MENSTRUATION" />
<uses-permission android:name="android.permission.health.READ_CERVICAL_MUCUS" />
Expand Down

0 comments on commit 4d91845

Please sign in to comment.