mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2026-07-31 07:44:24 +02:00
CMF Watch Pro: Add ActivityTrackProvider
This commit is contained in:
+8
@@ -49,6 +49,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.devices.cmfwatchpro.samples.CmfActivitySampleProvider;
|
import nodomain.freeyourgadget.gadgetbridge.devices.cmfwatchpro.samples.CmfActivitySampleProvider;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.cmfwatchpro.samples.CmfSpo2SampleProvider;
|
import nodomain.freeyourgadget.gadgetbridge.devices.cmfwatchpro.samples.CmfSpo2SampleProvider;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.cmfwatchpro.samples.CmfStressSampleProvider;
|
import nodomain.freeyourgadget.gadgetbridge.devices.cmfwatchpro.samples.CmfStressSampleProvider;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.cmfwatchpro.workout.CmfActivityTrackProvider;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.cmfwatchpro.workout.CmfWorkoutSummaryParser;
|
import nodomain.freeyourgadget.gadgetbridge.devices.cmfwatchpro.workout.CmfWorkoutSummaryParser;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummaryDao;
|
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummaryDao;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.CmfActivitySampleDao;
|
import nodomain.freeyourgadget.gadgetbridge.entities.CmfActivitySampleDao;
|
||||||
@@ -62,6 +63,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
|
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.ActivityTrackProvider;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample;
|
import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.StressSample;
|
import nodomain.freeyourgadget.gadgetbridge.model.StressSample;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
|
import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
|
||||||
@@ -182,6 +184,12 @@ public class CmfWatchProCoordinator extends AbstractBLEDeviceCoordinator {
|
|||||||
return new CmfWorkoutSummaryParser(device, context);
|
return new CmfWorkoutSummaryParser(device, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public ActivityTrackProvider getActivityTrackProvider(@NonNull final GBDevice device, @NonNull final Context context) {
|
||||||
|
return new CmfActivityTrackProvider(device);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsFlashing(@NonNull final GBDevice device) {
|
public boolean supportsFlashing(@NonNull final GBDevice device) {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
+130
@@ -0,0 +1,130 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.devices.cmfwatchpro.workout;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.cmfwatchpro.samples.CmfHeartRateSampleProvider;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.cmfwatchpro.samples.CmfWorkoutGpsSampleProvider;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.entities.CmfHeartRateSample;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.entities.CmfWorkoutGpsSample;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.ActivityPoint;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.ActivityTrack;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.ActivityTrackProvider;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.GPSCoordinate;
|
||||||
|
|
||||||
|
@SuppressWarnings("ClassCanBeRecord")
|
||||||
|
public class CmfActivityTrackProvider implements ActivityTrackProvider {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(CmfActivityTrackProvider.class);
|
||||||
|
|
||||||
|
private final GBDevice gbDevice;
|
||||||
|
|
||||||
|
public CmfActivityTrackProvider(final GBDevice gbDevice) {
|
||||||
|
this.gbDevice = gbDevice;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public ActivityTrack getActivityTrack(@NonNull final BaseActivitySummary summary) {
|
||||||
|
final ActivityTrack track = new ActivityTrack();
|
||||||
|
track.setUser(summary.getUser());
|
||||||
|
track.setDevice(summary.getDevice());
|
||||||
|
track.setName(createActivityName(summary));
|
||||||
|
|
||||||
|
final List<CmfWorkoutGpsSample> gpsSamples;
|
||||||
|
final List<CmfHeartRateSample> hrSamples;
|
||||||
|
try (DBHandler handler = GBApplication.acquireDB()) {
|
||||||
|
final DaoSession session = handler.getDaoSession();
|
||||||
|
|
||||||
|
final CmfWorkoutGpsSampleProvider gpsSampleProvider = new CmfWorkoutGpsSampleProvider(gbDevice, session);
|
||||||
|
gpsSamples = gpsSampleProvider.getAllSamples(summary.getStartTime().getTime(), summary.getEndTime().getTime());
|
||||||
|
|
||||||
|
final CmfHeartRateSampleProvider hrSampleProvider = new CmfHeartRateSampleProvider(gbDevice, session);
|
||||||
|
hrSamples = new ArrayList<>(hrSampleProvider.getAllSamples(summary.getStartTime().getTime(), summary.getEndTime().getTime()));
|
||||||
|
} catch (final Exception e) {
|
||||||
|
LOG.error("Error while building activity track", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Collections.sort(hrSamples, Comparator.comparingLong(CmfHeartRateSample::getTimestamp));
|
||||||
|
|
||||||
|
for (final CmfWorkoutGpsSample gpsSample : gpsSamples) {
|
||||||
|
final ActivityPoint ap = new ActivityPoint(new Date(gpsSample.getTimestamp()));
|
||||||
|
final GPSCoordinate coordinate = new GPSCoordinate(
|
||||||
|
gpsSample.getLongitude() / 10000000d,
|
||||||
|
gpsSample.getLatitude() / 10000000d
|
||||||
|
);
|
||||||
|
ap.setLocation(coordinate);
|
||||||
|
|
||||||
|
final CmfHeartRateSample hrSample = findNearestSample(hrSamples, gpsSample.getTimestamp());
|
||||||
|
if (hrSample != null) {
|
||||||
|
ap.setHeartRate(hrSample.getHeartRate());
|
||||||
|
}
|
||||||
|
|
||||||
|
track.addTrackPoint(ap);
|
||||||
|
}
|
||||||
|
|
||||||
|
return track;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private CmfHeartRateSample findNearestSample(final List<CmfHeartRateSample> samples, final long timestamp) {
|
||||||
|
if (samples.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timestamp < samples.get(0).getTimestamp()) {
|
||||||
|
return samples.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timestamp > samples.get(samples.size() - 1).getTimestamp()) {
|
||||||
|
return samples.get(samples.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int start = 0;
|
||||||
|
int end = samples.size() - 1;
|
||||||
|
|
||||||
|
while (start <= end) {
|
||||||
|
final int mid = (start + end) / 2;
|
||||||
|
|
||||||
|
if (timestamp < samples.get(mid).getTimestamp()) {
|
||||||
|
end = mid - 1;
|
||||||
|
} else if (timestamp > samples.get(mid).getTimestamp()) {
|
||||||
|
start = mid + 1;
|
||||||
|
} else {
|
||||||
|
return samples.get(mid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME return null if too far?
|
||||||
|
|
||||||
|
if (samples.get(start).getTimestamp() - timestamp < timestamp - samples.get(end).getTimestamp()) {
|
||||||
|
return samples.get(start);
|
||||||
|
}
|
||||||
|
|
||||||
|
return samples.get(end);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static String createActivityName(final BaseActivitySummary summary) {
|
||||||
|
String name = summary.getName();
|
||||||
|
String nameText = "";
|
||||||
|
Long id = summary.getId();
|
||||||
|
if (name != null) {
|
||||||
|
nameText = name + " - ";
|
||||||
|
}
|
||||||
|
return nameText + id;
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
@@ -119,6 +119,7 @@ public class CmfWorkoutSummaryParser implements ActivitySummaryParser {
|
|||||||
summary.setEndTime(new Date(endTime * 1000L));
|
summary.setEndTime(new Date(endTime * 1000L));
|
||||||
|
|
||||||
final boolean gps = buf.get() == 1;
|
final boolean gps = buf.get() == 1;
|
||||||
|
summaryData.setHasGps(gps);
|
||||||
buf.get(); // ?
|
buf.get(); // ?
|
||||||
|
|
||||||
if (buf.position() < buf.limit()) {
|
if (buf.position() < buf.limit()) {
|
||||||
|
|||||||
+3
-96
@@ -29,8 +29,6 @@ import java.io.IOException;
|
|||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import de.greenrobot.dao.query.QueryBuilder;
|
import de.greenrobot.dao.query.QueryBuilder;
|
||||||
@@ -45,6 +43,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.cmfwatchpro.samples.CmfSleep
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.devices.cmfwatchpro.samples.CmfSpo2SampleProvider;
|
import nodomain.freeyourgadget.gadgetbridge.devices.cmfwatchpro.samples.CmfSpo2SampleProvider;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.cmfwatchpro.samples.CmfStressSampleProvider;
|
import nodomain.freeyourgadget.gadgetbridge.devices.cmfwatchpro.samples.CmfStressSampleProvider;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.cmfwatchpro.samples.CmfWorkoutGpsSampleProvider;
|
import nodomain.freeyourgadget.gadgetbridge.devices.cmfwatchpro.samples.CmfWorkoutGpsSampleProvider;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.cmfwatchpro.workout.CmfActivityTrackProvider;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.cmfwatchpro.workout.CmfWorkoutSummaryParser;
|
import nodomain.freeyourgadget.gadgetbridge.devices.cmfwatchpro.workout.CmfWorkoutSummaryParser;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
|
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummaryDao;
|
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummaryDao;
|
||||||
@@ -62,9 +61,7 @@ import nodomain.freeyourgadget.gadgetbridge.export.ActivityTrackExporter;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.export.GPXExporter;
|
import nodomain.freeyourgadget.gadgetbridge.export.GPXExporter;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityPoint;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityTrack;
|
import nodomain.freeyourgadget.gadgetbridge.model.ActivityTrack;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.GPSCoordinate;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
|
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||||
@@ -501,7 +498,8 @@ public class CmfActivitySync {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void processGps(final BaseActivitySummary summary) {
|
private void processGps(final BaseActivitySummary summary) {
|
||||||
final ActivityTrack activityTrack = buildActivityTrack(summary);
|
final CmfActivityTrackProvider activityTrackProvider = new CmfActivityTrackProvider(getDevice());
|
||||||
|
final ActivityTrack activityTrack = activityTrackProvider.getActivityTrack(summary);
|
||||||
if (activityTrack == null) {
|
if (activityTrack == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -567,97 +565,6 @@ public class CmfActivitySync {
|
|||||||
return gpxTargetFile;
|
return gpxTargetFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private ActivityTrack buildActivityTrack(final BaseActivitySummary summary) {
|
|
||||||
final ActivityTrack track = new ActivityTrack();
|
|
||||||
track.setUser(summary.getUser());
|
|
||||||
track.setDevice(summary.getDevice());
|
|
||||||
track.setName(createActivityName(summary));
|
|
||||||
|
|
||||||
final List<CmfWorkoutGpsSample> gpsSamples;
|
|
||||||
final List<CmfHeartRateSample> hrSamples;
|
|
||||||
try (DBHandler handler = GBApplication.acquireDB()) {
|
|
||||||
final DaoSession session = handler.getDaoSession();
|
|
||||||
|
|
||||||
final CmfWorkoutGpsSampleProvider gpsSampleProvider = new CmfWorkoutGpsSampleProvider(getDevice(), session);
|
|
||||||
gpsSamples = gpsSampleProvider.getAllSamples(summary.getStartTime().getTime(), summary.getEndTime().getTime());
|
|
||||||
|
|
||||||
final CmfHeartRateSampleProvider hrSampleProvider = new CmfHeartRateSampleProvider(getDevice(), session);
|
|
||||||
hrSamples = new ArrayList<>(hrSampleProvider.getAllSamples(summary.getStartTime().getTime(), summary.getEndTime().getTime()));
|
|
||||||
} catch (final Exception e) {
|
|
||||||
LOG.error("Error while building activity track", e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
Collections.sort(hrSamples, (a, b) -> Long.compare(a.getTimestamp(), b.getTimestamp()));
|
|
||||||
|
|
||||||
for (final CmfWorkoutGpsSample gpsSample : gpsSamples) {
|
|
||||||
final ActivityPoint ap = new ActivityPoint(new Date(gpsSample.getTimestamp()));
|
|
||||||
final GPSCoordinate coordinate = new GPSCoordinate(
|
|
||||||
gpsSample.getLongitude() / 10000000d,
|
|
||||||
gpsSample.getLatitude() / 10000000d
|
|
||||||
);
|
|
||||||
ap.setLocation(coordinate);
|
|
||||||
|
|
||||||
final CmfHeartRateSample hrSample = findNearestSample(hrSamples, gpsSample.getTimestamp());
|
|
||||||
if (hrSample != null) {
|
|
||||||
ap.setHeartRate(hrSample.getHeartRate());
|
|
||||||
}
|
|
||||||
|
|
||||||
track.addTrackPoint(ap);
|
|
||||||
}
|
|
||||||
|
|
||||||
return track;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private CmfHeartRateSample findNearestSample(final List<CmfHeartRateSample> samples, final long timestamp) {
|
|
||||||
if (samples.isEmpty()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (timestamp < samples.get(0).getTimestamp()) {
|
|
||||||
return samples.get(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (timestamp > samples.get(samples.size() - 1).getTimestamp()) {
|
|
||||||
return samples.get(samples.size() - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
int start = 0;
|
|
||||||
int end = samples.size() - 1;
|
|
||||||
|
|
||||||
while (start <= end) {
|
|
||||||
final int mid = (start + end) / 2;
|
|
||||||
|
|
||||||
if (timestamp < samples.get(mid).getTimestamp()) {
|
|
||||||
end = mid - 1;
|
|
||||||
} else if (timestamp > samples.get(mid).getTimestamp()) {
|
|
||||||
start = mid + 1;
|
|
||||||
} else {
|
|
||||||
return samples.get(mid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME return null if too far?
|
|
||||||
|
|
||||||
if (samples.get(start).getTimestamp() - timestamp < timestamp - samples.get(end).getTimestamp()) {
|
|
||||||
return samples.get(start);
|
|
||||||
}
|
|
||||||
|
|
||||||
return samples.get(end);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static String createActivityName(final BaseActivitySummary summary) {
|
|
||||||
String name = summary.getName();
|
|
||||||
String nameText = "";
|
|
||||||
Long id = summary.getId();
|
|
||||||
if (name != null) {
|
|
||||||
nameText = name + " - ";
|
|
||||||
}
|
|
||||||
return nameText + id;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Context getContext() {
|
private Context getContext() {
|
||||||
return mSupport.getContext();
|
return mSupport.getContext();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user