Health Connect: Sync blood glucose

This commit is contained in:
José Rebelo
2026-01-06 21:19:51 +00:00
parent 667d5fdfc4
commit 13b2fb9dd1
4 changed files with 67 additions and 6 deletions
+1
View File
@@ -128,6 +128,7 @@
<uses-permission android:name="android.permission.health.WRITE_EXERCISE_ROUTE" />
<uses-permission android:name="android.permission.health.WRITE_RESPIRATORY_RATE" />
<uses-permission android:name="android.permission.health.WRITE_RESTING_HEART_RATE" />
<uses-permission android:name="android.permission.health.WRITE_BLOOD_GLUCOSE" />
<uses-sdk tools:overrideLibrary="androidx.health.connect.client"/>
<uses-feature
@@ -59,6 +59,7 @@ object HealthConnectPermissionManager {
TEMPERATURE,
RESPIRATORY_RATE,
RESTING_HEART_RATE,
BLOOD_GLUCOSE,
WORKOUTS
}
@@ -79,6 +80,7 @@ object HealthConnectPermissionManager {
HealthConnectDataType.SPO2 -> setOf(HealthPermission.getWritePermission(OxygenSaturationRecord::class))
HealthConnectDataType.RESPIRATORY_RATE -> setOf(HealthPermission.getWritePermission(RespiratoryRateRecord::class))
HealthConnectDataType.RESTING_HEART_RATE -> setOf(HealthPermission.getWritePermission(RestingHeartRateRecord::class))
HealthConnectDataType.BLOOD_GLUCOSE -> setOf(HealthPermission.getWritePermission(BloodGlucoseRecord::class))
HealthConnectDataType.TEMPERATURE -> setOf(
HealthPermission.getWritePermission(BodyTemperatureRecord::class),
HealthPermission.getWritePermission(SkinTemperatureRecord::class)
@@ -35,6 +35,7 @@ import nodomain.freeyourgadget.gadgetbridge.database.DBHandler
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractSampleProvider
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator
import nodomain.freeyourgadget.gadgetbridge.devices.GlucoseSampleProvider
import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummaryDao
import nodomain.freeyourgadget.gadgetbridge.entities.HealthConnectSyncState
@@ -530,12 +531,6 @@ class HealthConnectUtils {
}
val initialSyncPrefs = context.getSharedPreferences(GBPrefs.HEALTH_CONNECT_SETTINGS, Context.MODE_PRIVATE)
val initialSyncStartTs = initialSyncPrefs.getLong(GBPrefs.HEALTH_CONNECT_INITIAL_SYNC_START_TS, -1L)
if (initialSyncStartTs != -1L) {
CompanionLogger.info("$HC_SYNC_TAG Using initial sync start timestamp for {}({}): {} ({})", gbDevice.aliasOrName, dataType.name, initialSyncStartTs, Instant.ofEpochSecond(initialSyncStartTs))
return Instant.ofEpochSecond(initialSyncStartTs)
}
val firstTs = getFirstSampleTimestamp(deviceCoordinator, gbDevice, db, dataType)
if (firstTs != null) {
@@ -610,6 +605,10 @@ class HealthConnectUtils {
healthConnectClient, gbDevice, metadata, offset,
currentSliceStartTs, currentSliceEndTs, grantedPermissions
))
HealthConnectPermissionManager.HealthConnectDataType.BLOOD_GLUCOSE -> sliceStats.add(BloodGlucoseSyncer.sync(
healthConnectClient, gbDevice, metadata, offset,
currentSliceStartTs, currentSliceEndTs, grantedPermissions
))
HealthConnectPermissionManager.HealthConnectDataType.WORKOUTS -> {
// Sync explicitly recorded workouts from BaseActivitySummary
val coordinator = gbDevice.deviceCoordinator
@@ -707,6 +706,7 @@ class HealthConnectUtils {
HealthConnectPermissionManager.HealthConnectDataType.HRV -> coordinator.getHrvValueSampleProvider(device, db.daoSession)
HealthConnectPermissionManager.HealthConnectDataType.RESPIRATORY_RATE -> coordinator.getRespiratoryRateSampleProvider(device, db.daoSession)
HealthConnectPermissionManager.HealthConnectDataType.RESTING_HEART_RATE -> coordinator.getHeartRateRestingSampleProvider(device, db.daoSession)
HealthConnectPermissionManager.HealthConnectDataType.BLOOD_GLUCOSE -> GlucoseSampleProvider(device, db.daoSession)
HealthConnectPermissionManager.HealthConnectDataType.WEIGHT -> coordinator.getWeightSampleProvider(device, db.daoSession)
// For SpO2 and Temperature, there might be a specific provider or fallback to general sample provider
HealthConnectPermissionManager.HealthConnectDataType.SPO2 -> coordinator.getSpo2SampleProvider(device, db.daoSession) // Potentially add fallback if needed: ?: coordinator.getSampleProvider(device, db.daoSession)
@@ -0,0 +1,58 @@
/* Copyright (C) 2026 José Rebelo
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.util.healthconnect.syncers
import androidx.health.connect.client.records.BloodGlucoseRecord
import androidx.health.connect.client.records.metadata.Metadata
import androidx.health.connect.client.units.BloodGlucose
import nodomain.freeyourgadget.gadgetbridge.devices.GlucoseSampleProvider
import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession
import nodomain.freeyourgadget.gadgetbridge.entities.GlucoseSample
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.time.Instant
import java.time.ZoneOffset
import kotlin.reflect.KClass
internal object BloodGlucoseSyncer : AbstractTimeSampleSyncer<GlucoseSample, BloodGlucoseRecord>() {
override val logger: Logger = LoggerFactory.getLogger(BloodGlucoseSyncer::class.java)
override val recordClass: KClass<BloodGlucoseRecord> = BloodGlucoseRecord::class
override fun getSampleProvider(
gbDevice: GBDevice,
daoSession: DaoSession
): TimeSampleProvider<out GlucoseSample> {
@Suppress("UNCHECKED_CAST")
return GlucoseSampleProvider(gbDevice, daoSession) as TimeSampleProvider<out GlucoseSample>
}
override fun convertSample(
sample: GlucoseSample,
offset: ZoneOffset,
metadata: Metadata,
deviceName: String
): BloodGlucoseRecord {
return BloodGlucoseRecord(
time = Instant.ofEpochMilli(sample.timestamp),
zoneOffset = offset,
level = BloodGlucose.milligramsPerDeciliter(sample.valueMgDl),
metadata = metadata
)
}
}