1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-12-01 14:32:54 +01:00

Huami: Migrate activity fetching to repeated fetch operation

This commit is contained in:
José Rebelo 2023-05-16 18:59:07 +01:00
parent 6ce7e92752
commit 7e15462593

View File

@ -17,13 +17,11 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. */ along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations; package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations;
import android.text.format.DateUtils;
import android.widget.Toast; import android.widget.Toast;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Calendar; import java.util.Calendar;
@ -43,91 +41,54 @@ import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.entities.HuamiExtendedActivitySample; import nodomain.freeyourgadget.gadgetbridge.entities.HuamiExtendedActivitySample;
import nodomain.freeyourgadget.gadgetbridge.entities.MiBandActivitySample; import nodomain.freeyourgadget.gadgetbridge.entities.MiBandActivitySample;
import nodomain.freeyourgadget.gadgetbridge.entities.User; import nodomain.freeyourgadget.gadgetbridge.entities.User;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
import nodomain.freeyourgadget.gadgetbridge.util.CheckSums;
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils; import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper; import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
/** /**
* An operation that fetches activity data. For every fetch, a new operation must * An operation that fetches activity data. For every fetch, a new operation must
* be created, i.e. an operation may not be reused for multiple fetches. * be created, i.e. an operation may not be reused for multiple fetches.
*/ */
public class FetchActivityOperation extends AbstractFetchOperation { public class FetchActivityOperation extends AbstractRepeatingFetchOperation {
private static final Logger LOG = LoggerFactory.getLogger(FetchActivityOperation.class); private static final Logger LOG = LoggerFactory.getLogger(FetchActivityOperation.class);
private final int sampleSize; private final int sampleSize;
private List<MiBandActivitySample> samples = new ArrayList<>(60 * 24); // 1day per default
public FetchActivityOperation(HuamiSupport support) { public FetchActivityOperation(final HuamiSupport support) {
super(support); super(support, HuamiService.COMMAND_ACTIVITY_DATA_TYPE_ACTIVTY, "activity data");
setName("fetching activity data"); this.sampleSize = support.getActivitySampleSize();
sampleSize = getSupport().getActivitySampleSize();
} }
@Override @Override
protected void startFetching() throws IOException { protected boolean handleActivityData(final GregorianCalendar timestamp, final byte[] bytes) {
samples.clear(); if (bytes.length % sampleSize != 0) {
super.startFetching(); GB.toast(getContext(), "Unexpected " + getName() + " array size: " + bytes.length, Toast.LENGTH_LONG, GB.ERROR);
} return false;
}
@Override final List<MiBandActivitySample> samples = new ArrayList<>(60 * 24); // 1day per default
protected void startFetching(TransactionBuilder builder) {
GregorianCalendar sinceWhen = getLastSuccessfulSyncTime();
startFetching(builder, HuamiService.COMMAND_ACTIVITY_DATA_TYPE_ACTIVTY, sinceWhen);
}
@Override for (int i = 0; i < bytes.length; i += sampleSize) {
protected boolean handleActivityFetchFinish(boolean success) { final MiBandActivitySample sample;
LOG.info("{} has finished round {}", getName(), fetchCount);
GregorianCalendar lastSyncTimestamp = saveSamples();
if (lastSyncTimestamp != null && needsAnotherFetch(lastSyncTimestamp)) { switch (sampleSize) {
try { case 4:
startFetching(); sample = createSample(bytes, i);
return true; break;
} catch (IOException ex) { case 8:
LOG.error("Error starting another round of {}", getName(), ex); sample = createExtendedSample(bytes, i);
return false; break;
default:
throw new IllegalStateException("Unsupported sample size " + sampleSize);
} }
samples.add(sample);
} }
final boolean superSuccess = super.handleActivityFetchFinish(success);
GB.signalActivityDataFinish();
return superSuccess;
}
@Override
protected boolean validChecksum(int crc32) {
// TODO actually check it
LOG.warn("Checksum not implemented for activity data, assuming it's valid");
return true;
}
private boolean needsAnotherFetch(GregorianCalendar lastSyncTimestamp) {
if (fetchCount > 5) {
LOG.warn("Already have 5 fetch rounds, not doing another one.");
return false;
}
if (DateUtils.isToday(lastSyncTimestamp.getTimeInMillis())) {
LOG.info("Hopefully no further fetch needed, last synced timestamp is from today.");
return false;
}
if (lastSyncTimestamp.getTimeInMillis() > System.currentTimeMillis()) {
LOG.warn("Not doing another fetch since last synced timestamp is in the future: {}", DateTimeUtils.formatDateTime(lastSyncTimestamp.getTime()));
return false;
}
LOG.info("Doing another fetch since last sync timestamp is still too old: {}", DateTimeUtils.formatDateTime(lastSyncTimestamp.getTime()));
return true;
}
private GregorianCalendar saveSamples() {
if (samples.isEmpty()) { if (samples.isEmpty()) {
LOG.info("No samples to save"); LOG.info("No samples to save");
return null; return true;
} }
LOG.info("Saving {} samples", samples.size()); LOG.info("Saving {} samples", samples.size());
@ -141,7 +102,6 @@ public class FetchActivityOperation extends AbstractFetchOperation {
Device device = DBHelper.getDevice(getDevice(), session); Device device = DBHelper.getDevice(getDevice(), session);
User user = DBHelper.getUser(session); User user = DBHelper.getUser(session);
GregorianCalendar timestamp = (GregorianCalendar) startTimestamp.clone();
for (MiBandActivitySample sample : samples) { for (MiBandActivitySample sample : samples) {
sample.setDevice(device); sample.setDevice(device);
sample.setUser(user); sample.setUser(user);
@ -154,77 +114,27 @@ public class FetchActivityOperation extends AbstractFetchOperation {
} }
sampleProvider.addGBActivitySamples(samples.toArray(new MiBandActivitySample[0])); sampleProvider.addGBActivitySamples(samples.toArray(new MiBandActivitySample[0]));
saveLastSyncTimestamp(timestamp); timestamp.add(Calendar.MINUTE, -1);
LOG.info("Huami activity data: last sample timestamp: {}", DateTimeUtils.formatDateTime(timestamp.getTime())); LOG.info("Huami activity data: last sample timestamp: {}", DateTimeUtils.formatDateTime(timestamp.getTime()));
return timestamp; return true;
} catch (Exception ex) { } catch (Exception ex) {
GB.toast(getContext(), "Error saving activity samples", Toast.LENGTH_LONG, GB.ERROR); GB.toast(getContext(), "Error saving activity samples", Toast.LENGTH_LONG, GB.ERROR);
LOG.error("Error saving activity samples", ex); LOG.error("Error saving activity samples", ex);
return null; return false;
} finally {
samples.clear();
} }
} }
/** @Override
* Method to handle the incoming activity data. protected void postActivityFetchFinish(final boolean success) {
* There are two kind of messages we currently know: GB.signalActivityDataFinish();
* - the first one is 11 bytes long and contains metadata (how many bytes to expect, when the data starts, etc.)
* - the second one is 20 bytes long and contains the actual activity data
* <p/>
* The first message type is parsed by this method, for every other length of the value param, bufferActivityData is called.
*
* @param value
*/
protected void handleActivityNotif(byte[] value) {
if (!isOperationRunning()) {
LOG.error("ignoring activity data notification because operation is not running. Data length: " + value.length);
getSupport().logMessageContent(value);
return;
}
if ((value.length % sampleSize) == 1) {
if ((byte) (lastPacketCounter + 1) == value[0]) {
lastPacketCounter++;
bufferActivityData(value);
} else {
GB.toast("Error " + getName() + ", invalid package counter: " + value[0], Toast.LENGTH_LONG, GB.ERROR);
handleActivityFetchFinish(false);
return;
}
} else {
GB.toast("Error " + getName() + ", unexpected package length: " + value.length, Toast.LENGTH_LONG, GB.ERROR);
handleActivityFetchFinish(false);
}
} }
/** @Override
* Creates samples from the given 17-length array protected boolean validChecksum(final int crc32) {
* @param value // TODO actually check it
*/ LOG.warn("Checksum not implemented for activity data, assuming it's valid");
protected void bufferActivityData(byte[] value) { return true;
int len = value.length;
if (len % sampleSize != 1) {
throw new AssertionError("Unexpected activity array size: " + len);
}
for (int i = 1; i < len; i += sampleSize) {
final MiBandActivitySample sample;
switch (sampleSize) {
case 4:
sample = createSample(value, i);
break;
case 8:
sample = createExtendedSample(value, i);
break;
default:
throw new IllegalStateException("Unsupported sample size " + sampleSize);
}
samples.add(sample);
}
} }
private MiBandActivitySample createSample(byte[] value, int i) { private MiBandActivitySample createSample(byte[] value, int i) {