From 09a954c371866d4ca7f7c1a94cf40fe6633acd1f Mon Sep 17 00:00:00 2001 From: Daniel Shokouhi Date: Thu, 21 Nov 2024 12:48:59 -0800 Subject: [PATCH 1/3] Add 5 more health connect sensors --- app/src/full/AndroidManifest.xml | 5 + .../sensors/HealthConnectSensorManager.kt | 174 ++++++++++++++++++ common/src/main/res/values/strings.xml | 10 + 3 files changed, 189 insertions(+) diff --git a/app/src/full/AndroidManifest.xml b/app/src/full/AndroidManifest.xml index 2bb6029a719..bfa1a297e54 100644 --- a/app/src/full/AndroidManifest.xml +++ b/app/src/full/AndroidManifest.xml @@ -7,20 +7,25 @@ + + + + + diff --git a/app/src/full/java/io/homeassistant/companion/android/sensors/HealthConnectSensorManager.kt b/app/src/full/java/io/homeassistant/companion/android/sensors/HealthConnectSensorManager.kt index f351be03da2..539e0c269d9 100644 --- a/app/src/full/java/io/homeassistant/companion/android/sensors/HealthConnectSensorManager.kt +++ b/app/src/full/java/io/homeassistant/companion/android/sensors/HealthConnectSensorManager.kt @@ -10,23 +10,28 @@ import androidx.health.connect.client.aggregate.AggregateMetric import androidx.health.connect.client.aggregate.AggregationResult import androidx.health.connect.client.permission.HealthPermission import androidx.health.connect.client.records.ActiveCaloriesBurnedRecord +import androidx.health.connect.client.records.BasalBodyTemperatureRecord 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.BodyTemperatureMeasurementLocation import androidx.health.connect.client.records.BodyTemperatureRecord +import androidx.health.connect.client.records.BodyWaterMassRecord import androidx.health.connect.client.records.BoneMassRecord import androidx.health.connect.client.records.DistanceRecord import androidx.health.connect.client.records.ElevationGainedRecord import androidx.health.connect.client.records.FloorsClimbedRecord import androidx.health.connect.client.records.HeartRateRecord +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.LeanBodyMassRecord import androidx.health.connect.client.records.MealType import androidx.health.connect.client.records.OxygenSaturationRecord import androidx.health.connect.client.records.Record import androidx.health.connect.client.records.RespiratoryRateRecord +import androidx.health.connect.client.records.RestingHeartRateRecord import androidx.health.connect.client.records.SleepSessionRecord import androidx.health.connect.client.records.StepsRecord import androidx.health.connect.client.records.TotalCaloriesBurnedRecord @@ -70,6 +75,17 @@ class HealthConnectSensorManager : SensorManager { entityCategory = SensorManager.ENTITY_CATEGORY_DIAGNOSTIC ) + val basalBodyTemperature = SensorManager.BasicSensor( + id = "health_connect_basal_body_temperature", + type = "sensor", + commonR.string.basic_sensor_name_basal_body_temperature, + commonR.string.sensor_description_basal_body_temperature, + "mdi:thermometer", + deviceClass = "temperature", + unitOfMeasurement = "°C", + entityCategory = SensorManager.ENTITY_CATEGORY_DIAGNOSTIC + ) + val basalMetabolicRate = SensorManager.BasicSensor( id = "health_connect_basal_metabolic_rate", type = "sensor", @@ -101,6 +117,17 @@ class HealthConnectSensorManager : SensorManager { entityCategory = SensorManager.ENTITY_CATEGORY_DIAGNOSTIC ) + val bodyWaterMass = SensorManager.BasicSensor( + id = "health_connect_body_water_mass", + type = "sensor", + commonR.string.basic_sensor_name_body_water_mass, + commonR.string.sensor_description_body_water_mass, + "mdi:water", + deviceClass = "weight", + unitOfMeasurement = "g", + entityCategory = SensorManager.ENTITY_CATEGORY_DIAGNOSTIC + ) + val bodyTemperature = SensorManager.BasicSensor( id = "health_connect_body_temperature", type = "sensor", @@ -179,6 +206,17 @@ class HealthConnectSensorManager : SensorManager { entityCategory = SensorManager.ENTITY_CATEGORY_DIAGNOSTIC ) + val heartRateVariability = SensorManager.BasicSensor( + id = "health_connect_heart_rate_variability", + type = "sensor", + commonR.string.basic_sensor_name_heart_rate_variability, + commonR.string.sensor_description_heart_rate_variability, + "mdi:heart-pulse", + deviceClass = "duration", + unitOfMeasurement = "ms", + entityCategory = SensorManager.ENTITY_CATEGORY_DIAGNOSTIC + ) + val height = SensorManager.BasicSensor( id = "health_connect_height", type = "sensor", @@ -190,6 +228,18 @@ class HealthConnectSensorManager : SensorManager { entityCategory = SensorManager.ENTITY_CATEGORY_DIAGNOSTIC ) + val hydration = SensorManager.BasicSensor( + id = "health_connect_hydration", + type = "sensor", + commonR.string.basic_sensor_name_hydration, + commonR.string.sensor_description_hydration, + "mdi:cup-water", + unitOfMeasurement = "mL", + deviceClass = "volume", + stateClass = SensorManager.STATE_CLASS_TOTAL_INCREASING, + entityCategory = SensorManager.ENTITY_CATEGORY_DIAGNOSTIC + ) + val leanBodyMass = SensorManager.BasicSensor( id = "health_connect_lean_body_mass", type = "sensor", @@ -221,6 +271,16 @@ class HealthConnectSensorManager : SensorManager { entityCategory = SensorManager.ENTITY_CATEGORY_DIAGNOSTIC ) + val restingHeartRate = SensorManager.BasicSensor( + id = "health_connect_resting_heart_rate", + type = "sensor", + commonR.string.basic_sensor_name_resting_heart_rate, + commonR.string.sensor_description_resting_heart_rate, + "mdi:heart-pulse", + unitOfMeasurement = "bpm", + entityCategory = SensorManager.ENTITY_CATEGORY_DIAGNOSTIC + ) + val sleepDuration = SensorManager.BasicSensor( id = "health_connect_sleep_duration", type = "sensor", @@ -295,9 +355,11 @@ class HealthConnectSensorManager : SensorManager { return try { when { (sensorId == activeCaloriesBurned.id) -> arrayOf(HealthPermission.getReadPermission(ActiveCaloriesBurnedRecord::class)) + (sensorId == basalBodyTemperature.id) -> arrayOf(HealthPermission.getReadPermission(BasalBodyTemperatureRecord::class)) (sensorId == basalMetabolicRate.id) -> arrayOf(HealthPermission.getReadPermission(BasalMetabolicRateRecord::class)) (sensorId == bloodGlucose.id) -> arrayOf(HealthPermission.getReadPermission(BloodGlucoseRecord::class)) (sensorId == bodyFat.id) -> arrayOf(HealthPermission.getReadPermission(BodyFatRecord::class)) + (sensorId == bodyWaterMass.id) -> arrayOf(HealthPermission.getReadPermission(BodyWaterMassRecord::class)) (sensorId == bodyTemperature.id) -> arrayOf(HealthPermission.getReadPermission(BodyTemperatureRecord::class)) (sensorId == boneMass.id) -> arrayOf(HealthPermission.getReadPermission(BoneMassRecord::class)) (sensorId == diastolicBloodPressure.id) -> arrayOf(HealthPermission.getReadPermission(BloodPressureRecord::class)) @@ -305,10 +367,13 @@ class HealthConnectSensorManager : SensorManager { (sensorId == elevationGained.id) -> arrayOf(HealthPermission.getReadPermission(ElevationGainedRecord::class)) (sensorId == floorsClimbed.id) -> arrayOf(HealthPermission.getReadPermission(FloorsClimbedRecord::class)) (sensorId == heartRate.id) -> arrayOf(HealthPermission.getReadPermission(HeartRateRecord::class)) + (sensorId == heartRateVariability.id) -> arrayOf(HealthPermission.getReadPermission(HeartRateVariabilityRmssdRecord::class)) (sensorId == height.id) -> arrayOf(HealthPermission.getReadPermission(HeightRecord::class)) + (sensorId == hydration.id) -> arrayOf(HealthPermission.getReadPermission(HydrationRecord::class)) (sensorId == leanBodyMass.id) -> arrayOf(HealthPermission.getReadPermission(LeanBodyMassRecord::class)) (sensorId == oxygenSaturation.id) -> arrayOf(HealthPermission.getReadPermission(OxygenSaturationRecord::class)) (sensorId == respiratoryRate.id) -> arrayOf(HealthPermission.getReadPermission(RespiratoryRateRecord::class)) + (sensorId == restingHeartRate.id) -> arrayOf(HealthPermission.getReadPermission(RestingHeartRateRecord::class)) (sensorId == sleepDuration.id) -> arrayOf(HealthPermission.getReadPermission(SleepSessionRecord::class)) (sensorId == steps.id) -> arrayOf(HealthPermission.getReadPermission(StepsRecord::class)) (sensorId == systolicBloodPressure.id) -> arrayOf(HealthPermission.getReadPermission(BloodPressureRecord::class)) @@ -327,6 +392,9 @@ class HealthConnectSensorManager : SensorManager { if (isEnabled(context, activeCaloriesBurned)) { updateActiveCaloriesBurnedSensor(context) } + if (isEnabled(context, basalBodyTemperature)) { + updateBasalBodyTemperatureSensor(context) + } if (isEnabled(context, basalMetabolicRate)) { updateBasalMetabolicRateSensor(context) } @@ -336,6 +404,9 @@ class HealthConnectSensorManager : SensorManager { if (isEnabled(context, bodyFat)) { updateBodyFatSensor(context) } + if (isEnabled(context, bodyWaterMass)) { + updateBodyWaterMassSensor(context) + } if (isEnabled(context, bodyTemperature)) { updateBodyTemperatureSensor(context) } @@ -357,9 +428,15 @@ class HealthConnectSensorManager : SensorManager { if (isEnabled(context, heartRate)) { updateHeartRateSensor(context) } + if (isEnabled(context, heartRateVariability)) { + updateHeartRateVariabilitySensor(context) + } if (isEnabled(context, height)) { updateHeightSensor(context) } + if (isEnabled(context, hydration)) { + updateHydrationSensor(context) + } if (isEnabled(context, leanBodyMass)) { updateLeanBodyMassSensor(context) } @@ -369,6 +446,9 @@ class HealthConnectSensorManager : SensorManager { if (isEnabled(context, respiratoryRate)) { updateRespiratoryRateSensor(context) } + if (isEnabled(context, restingHeartRate)) { + updateRestingHeartRateSensor(context) + } if (isEnabled(context, sleepDuration)) { updateSleepDurationSensor(context) } @@ -408,6 +488,25 @@ class HealthConnectSensorManager : SensorManager { ) } + private suspend fun updateBasalBodyTemperatureSensor(context: Context) { + val healthConnectClient = getOrCreateHealthConnectClient(context) ?: return + val basalBodyTemperatureRequest = buildReadRecordsRequest(BasalBodyTemperatureRecord::class) as ReadRecordsRequest + val response = healthConnectClient.readRecordsOrNull(basalBodyTemperatureRequest) + if (response == null || response.records.isEmpty()) { + return + } + onSensorUpdated( + context, + basalBodyTemperature, + response.records.last().temperature.inCelsius, + basalBodyTemperature.statelessIcon, + attributes = mapOf( + "date" to response.records.last().time, + "source" to response.records.last().metadata.dataOrigin.packageName + ) + ) + } + private suspend fun updateBasalMetabolicRateSensor(context: Context) { val healthConnectClient = getOrCreateHealthConnectClient(context) ?: return val basalMetabolicRateRequest = buildReadRecordsRequest(BasalMetabolicRateRecord::class) as ReadRecordsRequest @@ -489,6 +588,25 @@ class HealthConnectSensorManager : SensorManager { ) } + private suspend fun updateBodyWaterMassSensor(context: Context) { + val healthConnectClient = getOrCreateHealthConnectClient(context) ?: return + val bodyWaterMassRequest = buildReadRecordsRequest(BodyWaterMassRecord::class) as ReadRecordsRequest + val response = healthConnectClient.readRecordsOrNull(bodyWaterMassRequest) + if (response == null || response.records.isEmpty()) { + return + } + onSensorUpdated( + context, + bodyWaterMass, + response.records.last().mass.inGrams, + bodyWaterMass.statelessIcon, + attributes = mapOf( + "date" to response.records.last().time, + "source" to response.records.last().metadata.dataOrigin.packageName + ) + ) + } + private suspend fun updateBodyTemperatureSensor(context: Context) { val healthConnectClient = getOrCreateHealthConnectClient(context) ?: return val bodyTemperatureRequest = buildReadRecordsRequest(BodyTemperatureRecord::class) as ReadRecordsRequest @@ -586,6 +704,25 @@ class HealthConnectSensorManager : SensorManager { ) } + private suspend fun updateHeartRateVariabilitySensor(context: Context) { + val healthConnectClient = getOrCreateHealthConnectClient(context) ?: return + val heartRateVariabilityRequest = buildReadRecordsRequest(HeartRateVariabilityRmssdRecord::class) as ReadRecordsRequest + val response = healthConnectClient.readRecordsOrNull(heartRateVariabilityRequest) + if (response == null || response.records.isEmpty()) { + return + } + onSensorUpdated( + context, + heartRateVariability, + response.records.last().heartRateVariabilityMillis, + heartRateVariability.statelessIcon, + attributes = mapOf( + "date" to response.records.last().time, + "source" to response.records.last().metadata.dataOrigin.packageName + ) + ) + } + private suspend fun updateHeightSensor(context: Context) { val healthConnectClient = getOrCreateHealthConnectClient(context) ?: return val heightRequest = buildReadRecordsRequest(HeightRecord::class) as ReadRecordsRequest @@ -605,6 +742,19 @@ class HealthConnectSensorManager : SensorManager { ) } + private suspend fun updateHydrationSensor(context: Context) { + val healthConnectClient = getOrCreateHealthConnectClient(context) ?: return + val hydrationRequest = healthConnectClient.aggregateOrNull(buildAggregationRequest(HydrationRecord.VOLUME_TOTAL)) ?: return + val hydrationTotal = hydrationRequest[HydrationRecord.VOLUME_TOTAL]?.inMilliliters ?: 0 + onSensorUpdated( + context, + hydration, + hydrationTotal, + hydration.statelessIcon, + attributes = buildAggregationAttributes(hydrationRequest) + ) + } + private suspend fun updateLeanBodyMassSensor(context: Context) { val healthConnectClient = getOrCreateHealthConnectClient(context) ?: return val leanBodyMassRequest = buildReadRecordsRequest(LeanBodyMassRecord::class) as ReadRecordsRequest @@ -662,6 +812,25 @@ class HealthConnectSensorManager : SensorManager { ) } + private suspend fun updateRestingHeartRateSensor(context: Context) { + val healthConnectClient = getOrCreateHealthConnectClient(context) ?: return + val restingHeartRateRequest = buildReadRecordsRequest(RestingHeartRateRecord::class) as ReadRecordsRequest + val response = healthConnectClient.readRecordsOrNull(restingHeartRateRequest) + if (response == null || response.records.isEmpty()) { + return + } + onSensorUpdated( + context, + restingHeartRate, + response.records.last().beatsPerMinute, + restingHeartRate.statelessIcon, + attributes = mapOf( + "date" to response.records.last().time, + "source" to response.records.last().metadata.dataOrigin.packageName + ) + ) + } + private suspend fun updateSleepDurationSensor(context: Context) { val healthConnectClient = getOrCreateHealthConnectClient(context) ?: return val sleepRequest = buildReadRecordsRequest(SleepSessionRecord::class) as ReadRecordsRequest @@ -758,9 +927,11 @@ class HealthConnectSensorManager : SensorManager { return if (hasSensor(context)) { listOf( activeCaloriesBurned, + basalBodyTemperature, basalMetabolicRate, bloodGlucose, bodyFat, + bodyWaterMass, bodyTemperature, boneMass, diastolicBloodPressure, @@ -768,10 +939,13 @@ class HealthConnectSensorManager : SensorManager { elevationGained, floorsClimbed, heartRate, + heartRateVariability, height, + hydration, leanBodyMass, oxygenSaturation, respiratoryRate, + restingHeartRate, sleepDuration, steps, systolicBloodPressure, diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index e3add3b885b..6866b95167a 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -1335,4 +1335,14 @@ Last recorded respiratory rate in breaths per minute from Health Connect VO2 max Last recorded VO2 max score from Health Connect + Basal body temperature + Last recorded basal body temperature from Health Connect + Body water mass + Last recorded body water mass from Health Connect + Heart rate variiability + Last recorded heart rate variability from Health Connect + Daily hydration + Total hydration in milliliters since midnight from Health Connect + Resting heart rate + Last recorded resting heart rate from Health Connect From f1a09a93d1812e86842b21aa03f2170de60d6f55 Mon Sep 17 00:00:00 2001 From: Daniel Shokouhi Date: Thu, 21 Nov 2024 14:13:37 -0800 Subject: [PATCH 2/3] Fix unit for hydration --- .../companion/android/sensors/HealthConnectSensorManager.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/full/java/io/homeassistant/companion/android/sensors/HealthConnectSensorManager.kt b/app/src/full/java/io/homeassistant/companion/android/sensors/HealthConnectSensorManager.kt index 539e0c269d9..a4294ef64a5 100644 --- a/app/src/full/java/io/homeassistant/companion/android/sensors/HealthConnectSensorManager.kt +++ b/app/src/full/java/io/homeassistant/companion/android/sensors/HealthConnectSensorManager.kt @@ -234,7 +234,7 @@ class HealthConnectSensorManager : SensorManager { commonR.string.basic_sensor_name_hydration, commonR.string.sensor_description_hydration, "mdi:cup-water", - unitOfMeasurement = "mL", + unitOfMeasurement = "ml", deviceClass = "volume", stateClass = SensorManager.STATE_CLASS_TOTAL_INCREASING, entityCategory = SensorManager.ENTITY_CATEGORY_DIAGNOSTIC From 6de88cc2829ed8f5d9b000d1dd68d012240cdfd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joris=20Pelgr=C3=B6m?= Date: Sat, 23 Nov 2024 16:26:33 +0100 Subject: [PATCH 3/3] Update common/src/main/res/values/strings.xml Co-authored-by: alfie <45316273+alfieV@users.noreply.github.com> --- common/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index 6866b95167a..0aac9f4a489 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -1339,7 +1339,7 @@ Last recorded basal body temperature from Health Connect Body water mass Last recorded body water mass from Health Connect - Heart rate variiability + Heart rate variability Last recorded heart rate variability from Health Connect Daily hydration Total hydration in milliliters since midnight from Health Connect