/* Copyright (C) 2023-2024 Frank Ertl 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.service.devices.withingssteelhr.activity; import java.util.List; import nodomain.freeyourgadget.gadgetbridge.devices.withingssteelhr.WithingsSteelHRSampleProvider; import nodomain.freeyourgadget.gadgetbridge.entities.WithingsSteelHRActivitySample; import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; /** * This class is needed for sleep tracking as the withings steel HR sends heartrate while sleeping in an extra activity. * This leads to breaking the sleep session in the sleep calculation of GB. */ public class SleepActivitySampleHelper { public static WithingsSteelHRActivitySample mergeIfNecessary(WithingsSteelHRSampleProvider provider, WithingsSteelHRActivitySample sample) { if (!shouldMerge(sample)) { return sample; } WithingsSteelHRActivitySample overlappingSample = getOverlappingSample(provider, (int)sample.getTimestamp()); if (overlappingSample != null) { sample = doMerge(overlappingSample, sample); } return sample; } private static WithingsSteelHRActivitySample getOverlappingSample(WithingsSteelHRSampleProvider provider, long timestamp) { List samples = provider.getActivitySamples((int)timestamp - 500, (int)timestamp); if (samples.isEmpty()) { return null; } for (int i = samples.size()-1; i >= 0; i--) { WithingsSteelHRActivitySample lastSample = samples.get(i); if (isNotHeartRateOnly(lastSample)) { return lastSample; } } return null; } private static boolean isNotHeartRateOnly(WithingsSteelHRActivitySample lastSample) { return lastSample.getRawKind() != ActivityKind.NOT_MEASURED.getCode(); // && lastSample.getTimestamp() <= timestamp && (lastSample.getTimestamp() + lastSample.getDuration()) >= timestamp); } private static boolean shouldMerge(WithingsSteelHRActivitySample sample) { return sample.getSteps() == 0 && sample.getDistance() == 0 && sample.getRawKind() == -1 && sample.getCalories() == 0 && sample.getHeartRate() > 1 && sample.getRawIntensity() == 0; } private static WithingsSteelHRActivitySample doMerge(WithingsSteelHRActivitySample origin, WithingsSteelHRActivitySample update) { WithingsSteelHRActivitySample mergeResult = new WithingsSteelHRActivitySample(); mergeResult.setTimestamp(update.getTimestamp()); mergeResult.setRawKind(origin.getRawKind()); mergeResult.setRawIntensity(origin.getRawIntensity()); mergeResult.setDuration(origin.getDuration() - (update.getTimestamp() - origin.getTimestamp())); mergeResult.setDevice(origin.getDevice()); mergeResult.setDeviceId(origin.getDeviceId()); mergeResult.setUser(origin.getUser()); mergeResult.setUserId(origin.getUserId()); mergeResult.setProvider(origin.getProvider()); mergeResult.setHeartRate(update.getHeartRate()); return mergeResult; } }