1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-10-06 13:20:30 +02:00

Zepp OS: Add VO2 Max support

This commit is contained in:
José Rebelo 2024-09-18 21:08:50 +01:00
parent 8ee03d3c08
commit 7b76d21254
3 changed files with 70 additions and 24 deletions

View File

@ -14,7 +14,7 @@
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.devices.garmin;
package nodomain.freeyourgadget.gadgetbridge.devices;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@ -26,6 +26,7 @@ import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
@ -33,8 +34,8 @@ import java.util.Objects;
import java.util.stream.Collectors;
import de.greenrobot.dao.query.QueryBuilder;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.devices.Vo2MaxSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummaryDao;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
@ -42,15 +43,20 @@ import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
import nodomain.freeyourgadget.gadgetbridge.model.Vo2MaxSample;
public class GarminVo2MaxSampleProvider implements Vo2MaxSampleProvider<Vo2MaxSample> {
private static final Logger LOG = LoggerFactory.getLogger(GarminVo2MaxSampleProvider.class);
/**
* An implementation of {@link Vo2MaxSampleProvider} that extracts VO2 Max values from
* workouts, for devices that provide them as part of the workout data.
*/
public class WorkoutVo2MaxSampleProvider implements Vo2MaxSampleProvider<Vo2MaxSample> {
private static final Logger LOG = LoggerFactory.getLogger(WorkoutVo2MaxSampleProvider.class);
private final GBDevice device;
private final DaoSession session;
public GarminVo2MaxSampleProvider(final GBDevice device, final DaoSession session) {
public WorkoutVo2MaxSampleProvider(final GBDevice device, final DaoSession session) {
this.device = device;
this.session = session;
}
@ -65,6 +71,8 @@ public class GarminVo2MaxSampleProvider implements Vo2MaxSampleProvider<Vo2MaxSa
return Collections.emptyList();
}
final DeviceCoordinator coordinator = device.getDeviceCoordinator();
final QueryBuilder<BaseActivitySummary> qb = summaryDao.queryBuilder();
qb.where(BaseActivitySummaryDao.Properties.DeviceId.eq(dbDevice.getId()))
.where(BaseActivitySummaryDao.Properties.StartTime.gt(new Date(timestampFrom)))
@ -73,6 +81,7 @@ public class GarminVo2MaxSampleProvider implements Vo2MaxSampleProvider<Vo2MaxSa
final List<BaseActivitySummary> samples = qb.build().list();
summaryDao.detachAll();
fillSummaryData(coordinator, samples);
return samples.stream()
.map(GarminVo2maxSample::fromActivitySummary)
@ -105,9 +114,10 @@ public class GarminVo2MaxSampleProvider implements Vo2MaxSampleProvider<Vo2MaxSa
return null;
}
final DeviceCoordinator coordinator = device.getDeviceCoordinator();
final QueryBuilder<BaseActivitySummary> qb = summaryDao.queryBuilder();
addWhereFilter(qb, type);
addWhereFilter(coordinator, qb, type);
if (until != 0) {
qb.where(BaseActivitySummaryDao.Properties.StartTime.le(new Date(until)));
@ -119,35 +129,52 @@ public class GarminVo2MaxSampleProvider implements Vo2MaxSampleProvider<Vo2MaxSa
final List<BaseActivitySummary> samples = qb.build().list();
summaryDao.detachAll();
fillSummaryData(coordinator, samples);
return !samples.isEmpty() ? GarminVo2maxSample.fromActivitySummary(samples.get(0)) : null;
}
private static void addWhereFilter(final QueryBuilder<BaseActivitySummary> qb, final Vo2MaxSample.Type type) {
private static void addWhereFilter(final DeviceCoordinator coordinator,
final QueryBuilder<BaseActivitySummary> qb,
final Vo2MaxSample.Type type) {
final List<Integer> codes = new ArrayList<>();
if (type == Vo2MaxSample.Type.ANY || type == Vo2MaxSample.Type.RUNNING) {
codes.addAll(Arrays.asList(
ActivityKind.INDOOR_RUNNING.getCode(),
ActivityKind.OUTDOOR_RUNNING.getCode(),
ActivityKind.CROSS_COUNTRY_RUNNING.getCode(),
ActivityKind.RUNNING.getCode()
));
if (coordinator.supportsVO2MaxRunning()) {
if (type == Vo2MaxSample.Type.ANY || type == Vo2MaxSample.Type.RUNNING) {
codes.addAll(Arrays.asList(
ActivityKind.INDOOR_RUNNING.getCode(),
ActivityKind.OUTDOOR_RUNNING.getCode(),
ActivityKind.CROSS_COUNTRY_RUNNING.getCode(),
ActivityKind.RUNNING.getCode()
));
}
}
if (type == Vo2MaxSample.Type.ANY || type == Vo2MaxSample.Type.CYCLING) {
codes.addAll(Arrays.asList(
ActivityKind.CYCLING.getCode(),
ActivityKind.INDOOR_CYCLING.getCode(),
ActivityKind.HANDCYCLING.getCode(),
ActivityKind.HANDCYCLING_INDOOR.getCode(),
ActivityKind.MOTORCYCLING.getCode(),
ActivityKind.OUTDOOR_CYCLING.getCode()
));
if (coordinator.supportsVO2MaxCycling()) {
if (type == Vo2MaxSample.Type.ANY || type == Vo2MaxSample.Type.CYCLING) {
codes.addAll(Arrays.asList(
ActivityKind.CYCLING.getCode(),
ActivityKind.INDOOR_CYCLING.getCode(),
ActivityKind.HANDCYCLING.getCode(),
ActivityKind.HANDCYCLING_INDOOR.getCode(),
ActivityKind.MOTORCYCLING.getCode(),
ActivityKind.OUTDOOR_CYCLING.getCode()
));
}
}
qb.where(BaseActivitySummaryDao.Properties.ActivityKind.in(codes));
}
private void fillSummaryData(final DeviceCoordinator coordinator,
final Collection<BaseActivitySummary> summaries) {
ActivitySummaryParser activitySummaryParser = coordinator.getActivitySummaryParser(device, GBApplication.getContext());
for (final BaseActivitySummary summary : summaries) {
if (summary.getSummaryData() == null) {
activitySummaryParser.parseBinaryData(summary, true);
}
}
}
@Nullable
@Override
public Vo2MaxSample getLatestSample() {

View File

@ -20,6 +20,7 @@ import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpec
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.WorkoutVo2MaxSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider;
@ -138,7 +139,7 @@ public abstract class GarminCoordinator extends AbstractBLEDeviceCoordinator {
@Override
public Vo2MaxSampleProvider<? extends Vo2MaxSample> getVo2MaxSampleProvider(final GBDevice device, final DaoSession session) {
return new GarminVo2MaxSampleProvider(device, session);
return new WorkoutVo2MaxSampleProvider(device, session);
}
@Override

View File

@ -43,6 +43,8 @@ import nodomain.freeyourgadget.gadgetbridge.capabilities.password.PasswordCapabi
import nodomain.freeyourgadget.gadgetbridge.GBException;
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.Vo2MaxSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.WorkoutVo2MaxSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiExtendedSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample;
@ -58,6 +60,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.HuamiSpo2SampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.HuamiStressSampleDao;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
import nodomain.freeyourgadget.gadgetbridge.model.Vo2MaxSample;
import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsFwInstallHandler;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
@ -177,6 +180,16 @@ public abstract class ZeppOsCoordinator extends HuamiCoordinator {
return true;
}
@Override
public boolean supportsVO2Max() {
return true;
}
@Override
public boolean supportsVO2MaxRunning() {
return true;
}
@Override
public boolean supportsHeartRateStats() {
return true;
@ -301,6 +314,11 @@ public abstract class ZeppOsCoordinator extends HuamiCoordinator {
return new HuamiExtendedSampleProvider(device, session);
}
@Override
public Vo2MaxSampleProvider<? extends Vo2MaxSample> getVo2MaxSampleProvider(final GBDevice device, final DaoSession session) {
return new WorkoutVo2MaxSampleProvider(device, session);
}
@Override
public ActivitySummaryParser getActivitySummaryParser(final GBDevice device, final Context context) {
return new ZeppOsActivitySummaryParser();