1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2025-01-13 11:17:33 +01:00

Initial support for activity data sync with Mi 1S #205

Looks like the activity type is somehow wrong though, or I'm sleeping
all day ;-)
This commit is contained in:
cpfeiffer 2016-02-25 23:52:34 +01:00
parent defa97b882
commit 095ef56c14

View File

@ -27,7 +27,6 @@ import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandService; import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandService;
import nodomain.freeyourgadget.gadgetbridge.impl.GBActivitySample; import nodomain.freeyourgadget.gadgetbridge.impl.GBActivitySample;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEOperation;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceBusyAction; import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceBusyAction;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.MiBandSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.MiBandSupport;
@ -52,11 +51,13 @@ public class FetchActivityOperation extends AbstractMiBandOperation {
private final int activityMetadataLength = 11; private final int activityMetadataLength = 11;
//temporary buffer, size is a multiple of 60 because we want to store complete minutes (1 minute = 3 bytes) //temporary buffer, size is a multiple of 60 because we want to store complete minutes (1 minute = 3 or 4 bytes)
private static final int activityDataHolderSize = 3 * 60 * 4; // 4h private final int activityDataHolderSize;
private final boolean hasExtendedActivityData;
private static class ActivityStruct { private static class ActivityStruct {
private final byte[] activityDataHolder = new byte[activityDataHolderSize]; private final byte[] activityDataHolder;
private final int activityDataHolderSize;
//index of the buffer above //index of the buffer above
private int activityDataHolderProgress = 0; private int activityDataHolderProgress = 0;
//number of bytes we will get in a single data transfer, used as counter //number of bytes we will get in a single data transfer, used as counter
@ -68,6 +69,11 @@ public class FetchActivityOperation extends AbstractMiBandOperation {
//same as above, but remains untouched for the ack message //same as above, but remains untouched for the ack message
private GregorianCalendar activityDataTimestampToAck = null; private GregorianCalendar activityDataTimestampToAck = null;
ActivityStruct(int activityDataHolderSize) {
this.activityDataHolderSize = activityDataHolderSize;
activityDataHolder = new byte[activityDataHolderSize];
}
public boolean hasRoomFor(byte[] value) { public boolean hasRoomFor(byte[] value) {
return activityDataRemainingBytes >= value.length; return activityDataRemainingBytes >= value.length;
} }
@ -127,10 +133,13 @@ public class FetchActivityOperation extends AbstractMiBandOperation {
} }
} }
private ActivityStruct activityStruct = new ActivityStruct(); private ActivityStruct activityStruct;
public FetchActivityOperation(MiBandSupport support) { public FetchActivityOperation(MiBandSupport support) {
super(support); super(support);
hasExtendedActivityData = support.getDeviceInfo().isMilli1S();
activityDataHolderSize = getBytesPerMinuteOfActivityData() * 60 * 4; // 4h
activityStruct = new ActivityStruct(activityDataHolderSize);
} }
@Override @Override
@ -208,30 +217,34 @@ public class FetchActivityOperation extends AbstractMiBandOperation {
// counter of all data held by the band // counter of all data held by the band
int totalDataToRead = (value[7] & 0xff) | ((value[8] & 0xff) << 8); int totalDataToRead = (value[7] & 0xff) | ((value[8] & 0xff) << 8);
totalDataToRead *= (dataType == MiBandService.MODE_REGULAR_DATA_LEN_MINUTE) ? 3 : 1; totalDataToRead *= (dataType == MiBandService.MODE_REGULAR_DATA_LEN_MINUTE) ? getBytesPerMinuteOfActivityData() : 1;
// counter of this data block // counter of this data block
int dataUntilNextHeader = (value[9] & 0xff) | ((value[10] & 0xff) << 8); int dataUntilNextHeader = (value[9] & 0xff) | ((value[10] & 0xff) << 8);
dataUntilNextHeader *= (dataType == MiBandService.MODE_REGULAR_DATA_LEN_MINUTE) ? 3 : 1; dataUntilNextHeader *= (dataType == MiBandService.MODE_REGULAR_DATA_LEN_MINUTE) ? getBytesPerMinuteOfActivityData() : 1;
// there is a total of totalDataToRead that will come in chunks (3 bytes per minute if dataType == 1 (MiBandService.MODE_REGULAR_DATA_LEN_MINUTE)), // there is a total of totalDataToRead that will come in chunks (3 or 4 bytes per minute if dataType == 1 (MiBandService.MODE_REGULAR_DATA_LEN_MINUTE)),
// these chunks are usually 20 bytes long and grouped in blocks // these chunks are usually 20 bytes long and grouped in blocks
// after dataUntilNextHeader bytes we will get a new packet of 11 bytes that should be parsed // after dataUntilNextHeader bytes we will get a new packet of 11 bytes that should be parsed
// as we just did // as we just did
if (activityStruct.isFirstChunk() && dataUntilNextHeader != 0) { if (activityStruct.isFirstChunk() && dataUntilNextHeader != 0) {
GB.toast(getContext().getString(R.string.user_feedback_miband_activity_data_transfer, GB.toast(getContext().getString(R.string.user_feedback_miband_activity_data_transfer,
DateTimeUtils.formatDurationHoursMinutes((totalDataToRead / 3), TimeUnit.MINUTES), DateTimeUtils.formatDurationHoursMinutes((totalDataToRead / getBytesPerMinuteOfActivityData()), TimeUnit.MINUTES),
DateFormat.getDateTimeInstance().format(timestamp.getTime())), Toast.LENGTH_LONG, GB.INFO); DateFormat.getDateTimeInstance().format(timestamp.getTime())), Toast.LENGTH_LONG, GB.INFO);
} }
LOG.info("total data to read: " + totalDataToRead + " len: " + (totalDataToRead / 3) + " minute(s)"); LOG.info("total data to read: " + totalDataToRead + " len: " + (totalDataToRead / getBytesPerMinuteOfActivityData()) + " minute(s)");
LOG.info("data to read until next header: " + dataUntilNextHeader + " len: " + (dataUntilNextHeader / 3) + " minute(s)"); LOG.info("data to read until next header: " + dataUntilNextHeader + " len: " + (dataUntilNextHeader / getBytesPerMinuteOfActivityData()) + " minute(s)");
LOG.info("TIMESTAMP: " + DateFormat.getDateTimeInstance().format(timestamp.getTime()) + " magic byte: " + dataUntilNextHeader); LOG.info("TIMESTAMP: " + DateFormat.getDateTimeInstance().format(timestamp.getTime()) + " magic byte: " + dataUntilNextHeader);
activityStruct.startNewBlock(timestamp, dataUntilNextHeader); activityStruct.startNewBlock(timestamp, dataUntilNextHeader);
} }
private int getBytesPerMinuteOfActivityData() {
return hasExtendedActivityData ? 4 : 3;
}
/** /**
* Method to store temporarily the activity data values got from the Mi Band. * Method to store temporarily the activity data values got from the Mi Band.
* <p/> * <p/>
@ -290,7 +303,8 @@ public class FetchActivityOperation extends AbstractMiBandOperation {
LOG.debug("nothing to flush, struct is already null"); LOG.debug("nothing to flush, struct is already null");
return; return;
} }
LOG.debug("flushing activity data samples: " + activityStruct.activityDataHolderProgress / 3); int bpm = getBytesPerMinuteOfActivityData();
LOG.debug("flushing activity data samples: " + activityStruct.activityDataHolderProgress / bpm);
byte category, intensity, steps; byte category, intensity, steps;
DBHandler dbHandler = null; DBHandler dbHandler = null;
@ -299,18 +313,19 @@ public class FetchActivityOperation extends AbstractMiBandOperation {
int minutes = 0; int minutes = 0;
try (SQLiteDatabase db = dbHandler.getWritableDatabase()) { // explicitly keep the db open while looping over the samples try (SQLiteDatabase db = dbHandler.getWritableDatabase()) { // explicitly keep the db open while looping over the samples
int timestampInSeconds = (int) (activityStruct.activityDataTimestampProgress.getTimeInMillis() / 1000); int timestampInSeconds = (int) (activityStruct.activityDataTimestampProgress.getTimeInMillis() / 1000);
if ((activityStruct.activityDataHolderProgress % 3) != 0) { if ((activityStruct.activityDataHolderProgress % bpm) != 0) {
throw new IllegalStateException("Unexpected data, progress should be mutiple of 3: " + activityStruct.activityDataHolderProgress); throw new IllegalStateException("Unexpected data, progress should be mutiple of " + bpm +": " + activityStruct.activityDataHolderProgress);
} }
int numSamples = activityStruct.activityDataHolderProgress/3; int numSamples = activityStruct.activityDataHolderProgress / bpm;
ActivitySample[] samples = new ActivitySample[numSamples]; ActivitySample[] samples = new ActivitySample[numSamples];
SampleProvider sampleProvider = new MiBandSampleProvider(); SampleProvider sampleProvider = new MiBandSampleProvider();
int s = 0; int s = 0;
for (int i = 0; i < activityStruct.activityDataHolderProgress; i += 3) { for (int i = 0; i < activityStruct.activityDataHolderProgress; i += bpm) {
category = activityStruct.activityDataHolder[i]; category = activityStruct.activityDataHolder[i];
intensity = activityStruct.activityDataHolder[i + 1]; intensity = activityStruct.activityDataHolder[i + 1];
steps = activityStruct.activityDataHolder[i + 2]; steps = activityStruct.activityDataHolder[i + 2];
byte unknown = activityStruct.activityDataHolder[i + 3];
samples[minutes] = new GBActivitySample( samples[minutes] = new GBActivitySample(
sampleProvider, sampleProvider,