mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2025-01-10 17:11:56 +01:00
parent
bbadb2b1ef
commit
ce7b0db5b1
@ -137,6 +137,12 @@ public abstract class AbstractSampleProvider<T extends AbstractActivitySample> i
|
|||||||
return sample;
|
return sample;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the activity samples between two timestamps. Exactly one every minute.
|
||||||
|
* @param timestamp_from Start timestamp
|
||||||
|
* @param timestamp_to End timestamp
|
||||||
|
* @return Exactly one sample for every minute
|
||||||
|
*/
|
||||||
protected List<T> getGBActivitySamples(int timestamp_from, int timestamp_to) {
|
protected List<T> getGBActivitySamples(int timestamp_from, int timestamp_to) {
|
||||||
QueryBuilder<T> qb = getSampleDao().queryBuilder();
|
QueryBuilder<T> qb = getSampleDao().queryBuilder();
|
||||||
Property timestampProperty = getTimestampSampleProperty();
|
Property timestampProperty = getTimestampSampleProperty();
|
||||||
|
@ -16,21 +16,16 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||||
package nodomain.freeyourgadget.gadgetbridge.devices.huawei;
|
package nodomain.freeyourgadget.gadgetbridge.devices.huawei;
|
||||||
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import de.greenrobot.dao.AbstractDao;
|
import de.greenrobot.dao.AbstractDao;
|
||||||
import de.greenrobot.dao.Property;
|
import de.greenrobot.dao.Property;
|
||||||
import de.greenrobot.dao.query.QueryBuilder;
|
import de.greenrobot.dao.query.QueryBuilder;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractSampleProvider;
|
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractSampleProvider;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||||
@ -44,11 +39,12 @@ import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutSummarySampleD
|
|||||||
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.ActivitySample;
|
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FitnessData;
|
|
||||||
|
|
||||||
public class HuaweiSampleProvider extends AbstractSampleProvider<HuaweiActivitySample> {
|
public class HuaweiSampleProvider extends AbstractSampleProvider<HuaweiActivitySample> {
|
||||||
/*
|
/*
|
||||||
* We save all data by saving a marker at the begin and end.
|
* We save all data by saving a marker at the begin and end. We do not actively use these for
|
||||||
|
* showing the data at the moment, but the samples are still saved as such, to keep the table
|
||||||
|
* entries consistent.
|
||||||
* Meaning of fields that are not self-explanatory:
|
* Meaning of fields that are not self-explanatory:
|
||||||
* - `otherTimestamp`
|
* - `otherTimestamp`
|
||||||
* The timestamp of the other marker, if it's larger this is the begin, otherwise the end
|
* The timestamp of the other marker, if it's larger this is the begin, otherwise the end
|
||||||
@ -289,296 +285,189 @@ public class HuaweiSampleProvider extends AbstractSampleProvider<HuaweiActivityS
|
|||||||
return samples;
|
return samples;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class SampleLoopState {
|
|
||||||
public long deviceId = 0;
|
|
||||||
public long userId = 0;
|
|
||||||
|
|
||||||
public int sleepModifier = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Note that this does a lot more than the normal implementation, as it takes care of everything
|
* This takes the following three steps:
|
||||||
* that is necessary for proper displaying of data.
|
* - Generate a sample every minute
|
||||||
*
|
* - Add the activity sample data to the generated samples
|
||||||
* This essentially boils down to four things:
|
* - Add the workout data to the generated samples
|
||||||
* - It adds in the workout heart rate data without activity data in between
|
|
||||||
* - It adds a sample with intensity zero before start markers (start of block)
|
|
||||||
* - It adds a sample with intensity zero after end markers (end of block)
|
|
||||||
* - It modifies some blocks so the sleep data gets handled correctly
|
|
||||||
* The second and fourth are necessary for proper stats calculation, the third is mostly for
|
|
||||||
* nicer graphs.
|
|
||||||
*
|
|
||||||
* Note that the data in the database isn't changed, as the samples are detached.
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected List<HuaweiActivitySample> getGBActivitySamples(int timestamp_from, int timestamp_to) {
|
protected List<HuaweiActivitySample> getGBActivitySamples(int timestamp_from, int timestamp_to) {
|
||||||
// Note that the result of this function has to be sorted by timestamp!
|
|
||||||
List<HuaweiActivitySample> rawSamples = getRawOrderedActivitySamples(timestamp_from, timestamp_to);
|
|
||||||
List<HuaweiWorkoutDataSample> workoutSamples = getRawOrderedWorkoutSamplesWithHeartRate(timestamp_from, timestamp_to);
|
|
||||||
|
|
||||||
List<int[]> workoutSpans = getWorkoutSpans(rawSamples, workoutSamples, 5);
|
|
||||||
List<HuaweiActivitySample> processedSamples = new ArrayList<>();
|
List<HuaweiActivitySample> processedSamples = new ArrayList<>();
|
||||||
|
|
||||||
Iterator<HuaweiActivitySample> itRawSamples = rawSamples.iterator();
|
for (int timestamp = timestamp_from; timestamp <= timestamp_to; timestamp += 60) {
|
||||||
Iterator<HuaweiWorkoutDataSample> itWorkoutSamples = workoutSamples.iterator();
|
processedSamples.add(createDummySample(timestamp));
|
||||||
|
|
||||||
HuaweiActivitySample nextRawSample = null;
|
|
||||||
if (itRawSamples.hasNext())
|
|
||||||
nextRawSample = itRawSamples.next();
|
|
||||||
HuaweiWorkoutDataSample nextWorkoutSample = null;
|
|
||||||
if (itWorkoutSamples.hasNext())
|
|
||||||
nextWorkoutSample = itWorkoutSamples.next();
|
|
||||||
|
|
||||||
SampleLoopState state = new SampleLoopState();
|
|
||||||
if (nextRawSample != null) {
|
|
||||||
state.deviceId = nextRawSample.getDeviceId();
|
|
||||||
state.userId = nextRawSample.getUserId();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
while (nextRawSample != null || nextWorkoutSample != null) {
|
overlayActivitySamples(processedSamples, timestamp_from, timestamp_to);
|
||||||
if (nextRawSample == null || (nextWorkoutSample != null && nextWorkoutSample.getTimestamp() < nextRawSample.getTimestamp())) {
|
overlayWorkoutSamples(processedSamples, timestamp_from, timestamp_to);
|
||||||
processWorkoutSample(processedSamples, state, nextWorkoutSample);
|
|
||||||
nextWorkoutSample = itWorkoutSamples.hasNext() ? itWorkoutSamples.next() : null;
|
|
||||||
} else {
|
|
||||||
boolean sampleInWorkout = isInWorkout(workoutSpans, nextRawSample.getTimestamp());
|
|
||||||
if (sampleInWorkout) {
|
|
||||||
nextRawSample.setHeartRate(ActivitySample.NOT_MEASURED);
|
|
||||||
nextRawSample.setRawIntensity(0);
|
|
||||||
}
|
|
||||||
processRawSample(processedSamples, state, nextRawSample);
|
|
||||||
nextRawSample = itRawSamples.hasNext() ? itRawSamples.next() : null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
processedSamples = interpolate(processedSamples);
|
|
||||||
|
|
||||||
return processedSamples;
|
return processedSamples;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
private HuaweiActivitySample createDummySample(int timestamp) {
|
||||||
* Calculates the timespans: [start, end] of workouts
|
HuaweiActivitySample activitySample = new HuaweiActivitySample(
|
||||||
* Normal activities should not be processed when in middle of workout
|
timestamp,
|
||||||
**/
|
-1,
|
||||||
private List<int[]> getWorkoutSpans(List<HuaweiActivitySample> activity, List<HuaweiWorkoutDataSample> workout, int threshold) {
|
-1,
|
||||||
List<int[]> validActivitySpans = new ArrayList<>();
|
|
||||||
|
|
||||||
Iterator<HuaweiActivitySample> activityIterator = activity.iterator();
|
|
||||||
Iterator<HuaweiWorkoutDataSample> workoutIterator = workout.iterator();
|
|
||||||
|
|
||||||
HuaweiActivitySample currentActivity = activityIterator.hasNext() ? activityIterator.next() : null;
|
|
||||||
HuaweiWorkoutDataSample currentWorkout = workoutIterator.hasNext() ? workoutIterator.next() : null;
|
|
||||||
|
|
||||||
int consecutiveActivityCount = 0;
|
|
||||||
Integer spanStart = null;
|
|
||||||
|
|
||||||
int workoutEnd = 0;
|
|
||||||
while (currentActivity != null || currentWorkout != null) {
|
|
||||||
if (currentWorkout == null || (currentActivity != null && currentActivity.getTimestamp() < currentWorkout.getTimestamp())) {
|
|
||||||
// handle activity
|
|
||||||
if (spanStart != null) {
|
|
||||||
// We're in workout, check for activity interruption
|
|
||||||
consecutiveActivityCount++;
|
|
||||||
if (consecutiveActivityCount > threshold) {
|
|
||||||
// Enough activity samples to interrupt the workout
|
|
||||||
validActivitySpans.add(new int[]{spanStart, workoutEnd});
|
|
||||||
spanStart = null;
|
|
||||||
consecutiveActivityCount = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
currentActivity = activityIterator.hasNext() ? activityIterator.next() : null;
|
|
||||||
} else {
|
|
||||||
// handle workout
|
|
||||||
if (spanStart == null) {
|
|
||||||
spanStart = currentWorkout.getTimestamp();
|
|
||||||
}
|
|
||||||
workoutEnd = currentWorkout.getTimestamp();
|
|
||||||
consecutiveActivityCount = 0;
|
|
||||||
currentWorkout = workoutIterator.hasNext() ? workoutIterator.next() : null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there's an open valid span at the end, close it
|
|
||||||
if (spanStart != null) {
|
|
||||||
validActivitySpans.add(new int[]{spanStart, workoutEnd});
|
|
||||||
}
|
|
||||||
|
|
||||||
return validActivitySpans;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isInWorkout(List<int[]> validSpans, int timestamp) {
|
|
||||||
for (int[] span : validSpans) {
|
|
||||||
if (timestamp > span[0] && timestamp < span[1]) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<HuaweiActivitySample> interpolate(List<HuaweiActivitySample> processedSamples) {
|
|
||||||
List<HuaweiActivitySample> retv = new ArrayList<>();
|
|
||||||
|
|
||||||
if (processedSamples.isEmpty())
|
|
||||||
return retv;
|
|
||||||
|
|
||||||
HuaweiActivitySample lastSample = processedSamples.get(0);
|
|
||||||
retv.add(lastSample);
|
|
||||||
for (int i = 1; i < processedSamples.size() - 1; i++) {
|
|
||||||
HuaweiActivitySample sample = processedSamples.get(i);
|
|
||||||
|
|
||||||
int timediff = sample.getTimestamp() - lastSample.getTimestamp();
|
|
||||||
if (timediff > 60) {
|
|
||||||
if (lastSample.getRawKind() != -1 && sample.getRawKind() != lastSample.getRawKind()) {
|
|
||||||
HuaweiActivitySample postSample = new HuaweiActivitySample(
|
|
||||||
lastSample.getTimestamp() + 1,
|
|
||||||
lastSample.getDeviceId(),
|
|
||||||
lastSample.getUserId(),
|
|
||||||
0,
|
|
||||||
(byte) 0x00,
|
|
||||||
ActivitySample.NOT_MEASURED,
|
|
||||||
0,
|
|
||||||
ActivitySample.NOT_MEASURED,
|
|
||||||
ActivitySample.NOT_MEASURED,
|
|
||||||
ActivitySample.NOT_MEASURED,
|
|
||||||
ActivitySample.NOT_MEASURED,
|
|
||||||
ActivitySample.NOT_MEASURED
|
|
||||||
);
|
|
||||||
postSample.setProvider(this);
|
|
||||||
retv.add(postSample);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sample.getRawKind() != -1 && sample.getRawKind() != lastSample.getRawKind()) {
|
|
||||||
HuaweiActivitySample preSample = new HuaweiActivitySample(
|
|
||||||
sample.getTimestamp() - 1,
|
|
||||||
sample.getDeviceId(),
|
|
||||||
sample.getUserId(),
|
|
||||||
0,
|
|
||||||
(byte) 0x00,
|
|
||||||
ActivitySample.NOT_MEASURED,
|
|
||||||
0,
|
|
||||||
ActivitySample.NOT_MEASURED,
|
|
||||||
ActivitySample.NOT_MEASURED,
|
|
||||||
ActivitySample.NOT_MEASURED,
|
|
||||||
ActivitySample.NOT_MEASURED,
|
|
||||||
ActivitySample.NOT_MEASURED
|
|
||||||
);
|
|
||||||
preSample.setProvider(this);
|
|
||||||
retv.add(preSample);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
retv.add(sample);
|
|
||||||
lastSample = sample;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lastSample.getRawKind() != -1) {
|
|
||||||
HuaweiActivitySample postSample = new HuaweiActivitySample(
|
|
||||||
lastSample.getTimestamp() + 1,
|
|
||||||
lastSample.getDeviceId(),
|
|
||||||
lastSample.getUserId(),
|
|
||||||
0,
|
|
||||||
(byte) 0x00,
|
|
||||||
ActivitySample.NOT_MEASURED,
|
|
||||||
0,
|
|
||||||
ActivitySample.NOT_MEASURED,
|
|
||||||
ActivitySample.NOT_MEASURED,
|
|
||||||
ActivitySample.NOT_MEASURED,
|
|
||||||
ActivitySample.NOT_MEASURED,
|
|
||||||
ActivitySample.NOT_MEASURED
|
|
||||||
);
|
|
||||||
postSample.setProvider(this);
|
|
||||||
retv.add(postSample);
|
|
||||||
}
|
|
||||||
|
|
||||||
return retv;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void processRawSample(List<HuaweiActivitySample> processedSamples, SampleLoopState state, HuaweiActivitySample sample) {
|
|
||||||
// Filter on Source 0x0d, Type 0x01, until we know what it is and how we should handle them.
|
|
||||||
// Just showing them currently has some issues.
|
|
||||||
if (sample.getSource() == FitnessData.MessageData.sleepId && sample.getRawKind() == RawTypes.UNKNOWN)
|
|
||||||
return;
|
|
||||||
|
|
||||||
HuaweiActivitySample lastSample = null;
|
|
||||||
|
|
||||||
boolean isStartMarker = sample.getTimestamp() < sample.getOtherTimestamp();
|
|
||||||
|
|
||||||
// Handle preferences for wakeup status ignore - can fix some quirks on some devices
|
|
||||||
if (sample.getRawKind() == 0x08) {
|
|
||||||
SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress());
|
|
||||||
if (isStartMarker && prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_IGNORE_WAKEUP_STATUS_START, false))
|
|
||||||
return;
|
|
||||||
if (!isStartMarker && prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_IGNORE_WAKEUP_STATUS_END, false))
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Backdate the end marker by one - otherwise the interpolation fails
|
|
||||||
if (sample.getTimestamp() > sample.getOtherTimestamp())
|
|
||||||
sample.setTimestamp(sample.getTimestamp() - 1);
|
|
||||||
|
|
||||||
if (!processedSamples.isEmpty())
|
|
||||||
lastSample = processedSamples.get(processedSamples.size() - 1);
|
|
||||||
if (lastSample != null && lastSample.getTimestamp() == sample.getTimestamp()) {
|
|
||||||
// Merge the samples - only if there isn't any data yet, except the kind
|
|
||||||
|
|
||||||
if (lastSample.getRawKind() == -1)
|
|
||||||
lastSample.setRawKind(sample.getRawKind());
|
|
||||||
// Do overwrite the kind if the new sample is a starting sample
|
|
||||||
if (isStartMarker && sample.getRawKind() != -1) {
|
|
||||||
lastSample.setRawKind(sample.getRawKind());
|
|
||||||
lastSample.setOtherTimestamp(sample.getOtherTimestamp()); // Necessary for interpolation
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lastSample.getRawIntensity() == -1)
|
|
||||||
lastSample.setRawIntensity(sample.getRawIntensity());
|
|
||||||
if (lastSample.getSteps() == -1)
|
|
||||||
lastSample.setSteps(sample.getSteps());
|
|
||||||
if (lastSample.getCalories() == -1)
|
|
||||||
lastSample.setCalories(sample.getCalories());
|
|
||||||
if (lastSample.getDistance() == -1)
|
|
||||||
lastSample.setDistance(sample.getDistance());
|
|
||||||
if (lastSample.getSpo() == -1)
|
|
||||||
lastSample.setSpo(sample.getSpo());
|
|
||||||
if (lastSample.getHeartRate() == -1)
|
|
||||||
lastSample.setHeartRate(sample.getHeartRate());
|
|
||||||
if (lastSample.getSource() != sample.getSource())
|
|
||||||
lastSample.setSource((byte) 0x00);
|
|
||||||
} else {
|
|
||||||
if (state.sleepModifier != 0)
|
|
||||||
sample.setRawKind(state.sleepModifier);
|
|
||||||
processedSamples.add(sample);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
(sample.getSource() == FitnessData.MessageData.sleepId || sample.getSource() == 0x0a) // Sleep sources
|
|
||||||
&& (sample.getRawKind() == RawTypes.LIGHT_SLEEP || sample.getRawKind() == RawTypes.DEEP_SLEEP) // Sleep types
|
|
||||||
) {
|
|
||||||
if (isStartMarker)
|
|
||||||
state.sleepModifier = sample.getRawKind();
|
|
||||||
else
|
|
||||||
state.sleepModifier = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void processWorkoutSample(List<HuaweiActivitySample> processedSamples, SampleLoopState state, HuaweiWorkoutDataSample workoutSample) {
|
|
||||||
processRawSample(processedSamples, state, convertWorkoutSampleToActivitySample(workoutSample, state));
|
|
||||||
}
|
|
||||||
|
|
||||||
private HuaweiActivitySample convertWorkoutSampleToActivitySample(HuaweiWorkoutDataSample workoutSample, SampleLoopState state) {
|
|
||||||
int hr = workoutSample.getHeartRate() & 0xFF;
|
|
||||||
HuaweiActivitySample newSample = new HuaweiActivitySample(
|
|
||||||
workoutSample.getTimestamp(),
|
|
||||||
state.deviceId,
|
|
||||||
state.userId,
|
|
||||||
0,
|
0,
|
||||||
(byte) 0x00,
|
(byte) 0x00,
|
||||||
ActivitySample.NOT_MEASURED,
|
ActivitySample.NOT_MEASURED,
|
||||||
|
0,
|
||||||
ActivitySample.NOT_MEASURED,
|
ActivitySample.NOT_MEASURED,
|
||||||
ActivitySample.NOT_MEASURED,
|
ActivitySample.NOT_MEASURED,
|
||||||
ActivitySample.NOT_MEASURED,
|
ActivitySample.NOT_MEASURED,
|
||||||
ActivitySample.NOT_MEASURED,
|
ActivitySample.NOT_MEASURED,
|
||||||
ActivitySample.NOT_MEASURED,
|
ActivitySample.NOT_MEASURED);
|
||||||
hr
|
activitySample.setProvider(this);
|
||||||
);
|
return activitySample;
|
||||||
newSample.setProvider(this);
|
}
|
||||||
return newSample;
|
|
||||||
|
/*
|
||||||
|
* For every activity sample, it adds the data into the following processed sample.
|
||||||
|
* If there are multiple activity samples, the steps, calories, and distance is added together.
|
||||||
|
* For the SpO and HR only the last value is used.
|
||||||
|
*/
|
||||||
|
private void overlayActivitySamples(List<HuaweiActivitySample> processedSamples, int timestamp_from, int timestamp_to) {
|
||||||
|
List<HuaweiActivitySample> activitySamples = getRawOrderedActivitySamples(timestamp_from, timestamp_to);
|
||||||
|
|
||||||
|
int currentIndex = 0;
|
||||||
|
|
||||||
|
boolean hasData = false;
|
||||||
|
|
||||||
|
int stepCount = ActivitySample.NOT_MEASURED;
|
||||||
|
int calorieCount = ActivitySample.NOT_MEASURED;
|
||||||
|
int distanceCount = ActivitySample.NOT_MEASURED;
|
||||||
|
|
||||||
|
int lastSpo = ActivitySample.NOT_MEASURED;
|
||||||
|
int lastHr = ActivitySample.NOT_MEASURED;
|
||||||
|
|
||||||
|
int stateModifier = ActivitySample.NOT_MEASURED;
|
||||||
|
|
||||||
|
for (HuaweiActivitySample activitySample : activitySamples) {
|
||||||
|
// Skip the processed samples that are before this activity sample
|
||||||
|
while (activitySample.getTimestamp() > processedSamples.get(currentIndex).getTimestamp()) {
|
||||||
|
// Add data to current index sample
|
||||||
|
if (hasData || stateModifier != ActivitySample.NOT_MEASURED)
|
||||||
|
processedSamples.get(currentIndex).setRawIntensity(1);
|
||||||
|
processedSamples.get(currentIndex).setSteps(stepCount);
|
||||||
|
processedSamples.get(currentIndex).setCalories(calorieCount);
|
||||||
|
processedSamples.get(currentIndex).setDistance(distanceCount);
|
||||||
|
processedSamples.get(currentIndex).setSpo(lastSpo);
|
||||||
|
processedSamples.get(currentIndex).setHeartRate(lastHr);
|
||||||
|
processedSamples.get(currentIndex).setRawKind(stateModifier);
|
||||||
|
|
||||||
|
// Reset counters
|
||||||
|
hasData = false;
|
||||||
|
stepCount = ActivitySample.NOT_MEASURED;
|
||||||
|
calorieCount = ActivitySample.NOT_MEASURED;
|
||||||
|
distanceCount = ActivitySample.NOT_MEASURED;
|
||||||
|
lastSpo = ActivitySample.NOT_MEASURED;
|
||||||
|
lastHr = ActivitySample.NOT_MEASURED;
|
||||||
|
|
||||||
|
currentIndex += 1;
|
||||||
|
if (currentIndex > processedSamples.size())
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update data
|
||||||
|
if (activitySample.getSteps() != ActivitySample.NOT_MEASURED) {
|
||||||
|
if (stepCount == ActivitySample.NOT_MEASURED)
|
||||||
|
stepCount = 0;
|
||||||
|
stepCount += activitySample.getSteps();
|
||||||
|
hasData = true;
|
||||||
|
}
|
||||||
|
if (activitySample.getCalories() != ActivitySample.NOT_MEASURED) {
|
||||||
|
if (calorieCount == ActivitySample.NOT_MEASURED)
|
||||||
|
calorieCount = 0;
|
||||||
|
calorieCount += activitySample.getCalories();
|
||||||
|
hasData = true;
|
||||||
|
}
|
||||||
|
if (activitySample.getDistance() != ActivitySample.NOT_MEASURED) {
|
||||||
|
if (distanceCount == ActivitySample.NOT_MEASURED)
|
||||||
|
distanceCount = 0;
|
||||||
|
distanceCount += activitySample.getDistance();
|
||||||
|
hasData = true;
|
||||||
|
}
|
||||||
|
if (activitySample.getSpo() != ActivitySample.NOT_MEASURED) {
|
||||||
|
lastSpo = activitySample.getSpo();
|
||||||
|
hasData = true;
|
||||||
|
}
|
||||||
|
if (activitySample.getHeartRate() != ActivitySample.NOT_MEASURED) {
|
||||||
|
lastHr = activitySample.getHeartRate();
|
||||||
|
hasData = true;
|
||||||
|
}
|
||||||
|
if (activitySample.getRawKind() != ActivitySample.NOT_MEASURED) {
|
||||||
|
if (activitySample.getTimestamp() < activitySample.getOtherTimestamp()) {
|
||||||
|
// Starting of modifier
|
||||||
|
stateModifier = activitySample.getRawKind();
|
||||||
|
} else {
|
||||||
|
// End of modifier, remove it if it was for the same state
|
||||||
|
if (activitySample.getRawKind() == stateModifier)
|
||||||
|
stateModifier = ActivitySample.NOT_MEASURED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is still data, it has to be part of the next index of processed samples
|
||||||
|
currentIndex += 1;
|
||||||
|
if (currentIndex >= processedSamples.size())
|
||||||
|
return;
|
||||||
|
if (hasData || stateModifier != ActivitySample.NOT_MEASURED)
|
||||||
|
processedSamples.get(currentIndex).setRawIntensity(10);
|
||||||
|
processedSamples.get(currentIndex).setSteps(stepCount);
|
||||||
|
processedSamples.get(currentIndex).setCalories(calorieCount);
|
||||||
|
processedSamples.get(currentIndex).setDistance(distanceCount);
|
||||||
|
processedSamples.get(currentIndex).setSpo(lastSpo);
|
||||||
|
processedSamples.get(currentIndex).setHeartRate(lastHr);
|
||||||
|
processedSamples.get(currentIndex).setRawKind(stateModifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* For every workout sample, it adds the data into the following processed sample.
|
||||||
|
* It also detects if it is still in the same workout, and resets the HR and intensity for the
|
||||||
|
* samples in between, see #4126 for the reasoning.
|
||||||
|
* NOTE: Huawei devices tend to generate a lot more data - mine up to every 5 seconds. Most of
|
||||||
|
* this is lost in the conversion to data by the minute. It only shows the most recent value.
|
||||||
|
*/
|
||||||
|
private void overlayWorkoutSamples(List<HuaweiActivitySample> processedSamples, int timestamp_from, int timestamp_to) {
|
||||||
|
int currentIndex = 0;
|
||||||
|
|
||||||
|
int lastHr = ActivitySample.NOT_MEASURED;
|
||||||
|
|
||||||
|
List<HuaweiWorkoutDataSample> workoutSamples = getRawOrderedWorkoutSamplesWithHeartRate(timestamp_from, timestamp_to);
|
||||||
|
|
||||||
|
for (int i = 0; i < workoutSamples.size(); i++) {
|
||||||
|
// Look ahead to see if this is still the same workout
|
||||||
|
boolean inWorkout = i != 0 && workoutSamples.get(i).getWorkoutId() == workoutSamples.get(i - 1).getWorkoutId();
|
||||||
|
|
||||||
|
// Skip the processed sample that are before this workout sample
|
||||||
|
while (workoutSamples.get(i).getTimestamp() > processedSamples.get(currentIndex).getTimestamp()) {
|
||||||
|
if (inWorkout) {
|
||||||
|
processedSamples.get(currentIndex).setHeartRate(lastHr);
|
||||||
|
processedSamples.get(currentIndex).setRawIntensity(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset
|
||||||
|
lastHr = ActivitySample.NOT_MEASURED;
|
||||||
|
|
||||||
|
currentIndex += 1;
|
||||||
|
if (currentIndex > processedSamples.size())
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (workoutSamples.get(i).getHeartRate() != ActivitySample.NOT_MEASURED)
|
||||||
|
lastHr = workoutSamples.get(i).getHeartRate() & 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is still data, it has to be part of the next index of processed samples
|
||||||
|
// Data being present implies it's still in a workout
|
||||||
|
currentIndex += 1;
|
||||||
|
if (currentIndex >= processedSamples.size())
|
||||||
|
return;
|
||||||
|
if (lastHr != ActivitySample.NOT_MEASURED) {
|
||||||
|
processedSamples.get(currentIndex).setHeartRate(lastHr);
|
||||||
|
processedSamples.get(currentIndex).setRawIntensity(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user