diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/ComputedHrvSummarySampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/ComputedHrvSummarySampleProvider.java
index 7f9043755b..01beb69811 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/ComputedHrvSummarySampleProvider.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/ComputedHrvSummarySampleProvider.java
@@ -26,6 +26,10 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import nodomain.freeyourgadget.gadgetbridge.activities.charts.SleepAnalysis;
+import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.HrvSummarySample;
import nodomain.freeyourgadget.gadgetbridge.model.HrvValueSample;
@@ -36,7 +40,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.HrvValueSample;
*
* The provider calculates:
* - Weekly average (7-day rolling average of all HRV values)
- * - Last night average and 5-min high (8 hours before midnight to midnight)
+ * - Last night average and 5-min high (based on actual sleep sessions detected from activity data)
* - Baseline values using Garmin's method: mean ± standard deviation of 28 overnight averages
*
* Summaries are computed on-demand and cached for performance.
@@ -47,6 +51,8 @@ public class ComputedHrvSummarySampleProvider implements TimeSampleProvider valueProvider;
+ private final GBDevice device;
+ private final DaoSession session;
// LRU cache for computed summaries
private final Map summaryCache = new LinkedHashMap<>(CACHE_SIZE, 0.75f, true) {
@@ -56,8 +62,12 @@ public class ComputedHrvSummarySampleProvider implements TimeSampleProvider valueProvider) {
+ public ComputedHrvSummarySampleProvider(final TimeSampleProvider extends HrvValueSample> valueProvider,
+ final GBDevice device,
+ final DaoSession session) {
this.valueProvider = valueProvider;
+ this.device = device;
+ this.session = session;
}
@NonNull
@@ -133,12 +143,50 @@ public class ComputedHrvSummarySampleProvider implements TimeSampleProvider sampleProvider =
+ device.getDeviceCoordinator().getSampleProvider(device, session);
+ final List extends AbstractActivitySample> activitySamples =
+ sampleProvider.getAllActivitySamples(startTs, endTs);
+
+ if (!activitySamples.isEmpty()) {
+ final SleepAnalysis sleepAnalysis = new SleepAnalysis();
+ final List sleepSessions =
+ sleepAnalysis.calculateSleepSessions(activitySamples);
+
+ // Use the last (most recent) sleep session - this should be the previous night
+ // SleepAnalysis returns sessions with their actual start/end times
+ if (!sleepSessions.isEmpty()) {
+ final SleepAnalysis.SleepSession lastSession = sleepSessions.get(sleepSessions.size() - 1);
+ final long sessionStart = lastSession.getSleepStart().getTime();
+ final long sessionEnd = lastSession.getSleepEnd().getTime();
+
+ lastNightAvg = calculateAverageHrv(sessionStart, sessionEnd);
+ lastNight5MinHigh = calculate5MinHighHrv(sessionStart, sessionEnd);
+ }
+ }
// Calculate baseline values (using past 28 overnight averages)
final BaselineValues baseline = calculateBaseline(dayEndTimestamp);
@@ -206,30 +254,52 @@ public class ComputedHrvSummarySampleProvider implements TimeSampleProvider sampleProvider =
+ device.getDeviceCoordinator().getSampleProvider(device, session);
+ final SleepAnalysis sleepAnalysis = new SleepAnalysis();
+
// Collect overnight averages for the past 28 days
for (int i = 0; i < DAYS_FOR_BASELINE; i++) {
- // Define "overnight" as 8 hours before midnight to midnight
- final Calendar dayStart = (Calendar) cal.clone();
- dayStart.set(Calendar.HOUR_OF_DAY, 0);
- dayStart.set(Calendar.MINUTE, 0);
- dayStart.set(Calendar.SECOND, 0);
- dayStart.set(Calendar.MILLISECOND, 0);
+ // Get activity samples for a 24-hour window (noon to noon) to find sleep sessions
+ final Calendar searchEnd = (Calendar) cal.clone();
+ searchEnd.set(Calendar.HOUR_OF_DAY, 12);
+ searchEnd.set(Calendar.MINUTE, 0);
+ searchEnd.set(Calendar.SECOND, 0);
+ searchEnd.set(Calendar.MILLISECOND, 0);
- final Calendar nightStart = (Calendar) dayStart.clone();
- nightStart.add(Calendar.HOUR_OF_DAY, -8);
+ final Calendar searchStart = (Calendar) searchEnd.clone();
+ searchStart.add(Calendar.DATE, -1);
- final int nightAvg = calculateAverageHrv(nightStart.getTimeInMillis(), dayStart.getTimeInMillis());
- if (nightAvg > 0) {
- overnightAverages.add(nightAvg);
+ final int startTs = (int) (searchStart.getTimeInMillis() / 1000);
+ final int endTs = (int) (searchEnd.getTimeInMillis() / 1000);
+
+ final List extends AbstractActivitySample> activitySamples =
+ sampleProvider.getAllActivitySamples(startTs, endTs);
+
+ if (!activitySamples.isEmpty()) {
+ final List sleepSessions =
+ sleepAnalysis.calculateSleepSessions(activitySamples);
+
+ // Use the last sleep session in the window (should be the previous night)
+ if (!sleepSessions.isEmpty()) {
+ final SleepAnalysis.SleepSession lastSession = sleepSessions.get(sleepSessions.size() - 1);
+ final long sessionStart = lastSession.getSleepStart().getTime();
+ final long sessionEnd = lastSession.getSleepEnd().getTime();
+
+ final int nightAvg = calculateAverageHrv(sessionStart, sessionEnd);
+ if (nightAvg > 0) {
+ overnightAverages.add(nightAvg);
+ }
+ }
}
// Move to previous day
cal.add(Calendar.DATE, -1);
}
- // Require at least 7 days of overnight data before calculating a baseline
+ // Require at least DAYS_FOR_WEEKLY_AVG of overnight data before calculating a baseline
// This prevents unreliable status calculations in the first few days
- if (overnightAverages.size() < 7) {
+ if (overnightAverages.size() < DAYS_FOR_WEEKLY_AVG) {
return new BaselineValues(0, 0, 0);
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiCoordinator.java
index d65cd01c4f..ab15ce8f98 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiCoordinator.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiCoordinator.java
@@ -212,7 +212,7 @@ public abstract class HuamiCoordinator extends AbstractBLEDeviceCoordinator {
@Override
public TimeSampleProvider extends HrvSummarySample> getHrvSummarySampleProvider(GBDevice device, DaoSession session) {
- return new ComputedHrvSummarySampleProvider(getHrvValueSampleProvider(device, session));
+ return new ComputedHrvSummarySampleProvider(getHrvValueSampleProvider(device, session), device, session);
}
@Override
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/ultrahuman/UltrahumanDeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/ultrahuman/UltrahumanDeviceCoordinator.java
index 29d46c4872..45278797b4 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/ultrahuman/UltrahumanDeviceCoordinator.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/ultrahuman/UltrahumanDeviceCoordinator.java
@@ -154,7 +154,7 @@ public class UltrahumanDeviceCoordinator extends AbstractBLEDeviceCoordinator {
@Override
public TimeSampleProvider extends HrvSummarySample> getHrvSummarySampleProvider(GBDevice device, DaoSession session) {
- return new ComputedHrvSummarySampleProvider(getHrvValueSampleProvider(device, session));
+ return new ComputedHrvSummarySampleProvider(getHrvValueSampleProvider(device, session), device, session);
}
@Override
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/yawell/ring/AbstractYawellRingCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/yawell/ring/AbstractYawellRingCoordinator.java
index 9a11451ff2..b951344ef7 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/yawell/ring/AbstractYawellRingCoordinator.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/yawell/ring/AbstractYawellRingCoordinator.java
@@ -179,7 +179,7 @@ public abstract class AbstractYawellRingCoordinator extends AbstractBLEDeviceCoo
@Override
public TimeSampleProvider extends HrvSummarySample> getHrvSummarySampleProvider(GBDevice device, DaoSession session) {
- return new ComputedHrvSummarySampleProvider(getHrvValueSampleProvider(device, session));
+ return new ComputedHrvSummarySampleProvider(getHrvValueSampleProvider(device, session), device, session);
}
@Override
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/yawell/ring/samples/ColmiHrvSummarySampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/yawell/ring/samples/ColmiHrvSummarySampleProvider.java
deleted file mode 100644
index 89d6ff5219..0000000000
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/yawell/ring/samples/ColmiHrvSummarySampleProvider.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/* Copyright (C) 2024 Arjan Schrijver
-
- 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 . */
-package nodomain.freeyourgadget.gadgetbridge.devices.yawell.ring.samples;
-
-import androidx.annotation.NonNull;
-
-import de.greenrobot.dao.AbstractDao;
-import de.greenrobot.dao.Property;
-import nodomain.freeyourgadget.gadgetbridge.devices.AbstractTimeSampleProvider;
-import nodomain.freeyourgadget.gadgetbridge.entities.ColmiHrvSummarySample;
-import nodomain.freeyourgadget.gadgetbridge.entities.ColmiHrvSummarySampleDao;
-import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
-import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
-
-public class ColmiHrvSummarySampleProvider extends AbstractTimeSampleProvider {
- public ColmiHrvSummarySampleProvider(final GBDevice device, final DaoSession session) {
- super(device, session);
- }
-
- @NonNull
- @Override
- public AbstractDao getSampleDao() {
- return getSession().getColmiHrvSummarySampleDao();
- }
-
- @NonNull
- @Override
- protected Property getTimestampSampleProperty() {
- return ColmiHrvSummarySampleDao.Properties.Timestamp;
- }
-
- @NonNull
- @Override
- protected Property getDeviceIdentifierSampleProperty() {
- return ColmiHrvSummarySampleDao.Properties.DeviceId;
- }
-
- @Override
- public ColmiHrvSummarySample createSample() {
- return new ColmiHrvSummarySample();
- }
-}