Refactor database / sample access (#206)

We now have separate tables for each provider's samples but a common interface.
master
Andreas Shimokawa 2016-07-27 23:34:13 +02:00
parent bce7a6c406
commit 8ea29e6e1d
12 changed files with 181 additions and 147 deletions

View File

@ -34,7 +34,7 @@ public class GBDaoGenerator {
private static final String VALID_BY_DATE = MODEL_PACKAGE + ".ValidByDate";
public static void main(String[] args) throws Exception {
Schema schema = new Schema(8, MAIN_PACKAGE + ".entities");
Schema schema = new Schema(9, MAIN_PACKAGE + ".entities");
addActivityDescription(schema);
@ -139,9 +139,7 @@ public class GBDaoGenerator {
}
private static void addHeartRateProperties(Entity activitySample) {
activitySample.addImport(MODEL_PACKAGE + ".HeartRateSample");
activitySample.implementsInterface("HeartRateSample");
activitySample.addIntProperty("heartRate");
activitySample.addIntProperty("heartRate").notNull();
}
private static Entity addPebbleActivitySample(Schema schema, Entity user, Entity device) {
@ -163,9 +161,7 @@ public class GBDaoGenerator {
private static void addCommonActivitySampleProperties(String superClass, Entity activitySample, Entity user, Entity device) {
activitySample.setSuperclass(superClass);
activitySample.addImport(MODEL_PACKAGE + ".ActivitySample");
activitySample.addImport(MAIN_PACKAGE + ".devices.SampleProvider");
activitySample.implementsInterface("ActivitySample");
activitySample.setJavaDoc(
"This class represents a sample specific to the device. Values like activity kind or\n" +
"intensity, are device specific. Normalized values can be retrieved through the\n" +

View File

@ -49,7 +49,6 @@ import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.model.HeartRateSample;
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
@ -465,13 +464,13 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
colors.add(akActivity.color);
}
activityEntries.add(createBarEntry(value, i));
if (hr && isValidHeartRateValue(((HeartRateSample)sample).getHeartRate())) {
if (hr && isValidHeartRateValue(sample.getHeartRate())) {
if (lastHrSampleIndex > -1 && i - lastHrSampleIndex > HeartRateUtils.MAX_HR_MEASUREMENTS_GAP_MINUTES) {
heartrateEntries.add(createLineEntry(0, lastHrSampleIndex + 1));
heartrateEntries.add(createLineEntry(0, i - 1));
}
heartrateEntries.add(createLineEntry(((HeartRateSample)sample).getHeartRate(), i));
heartrateEntries.add(createLineEntry(sample.getHeartRate(), i));
lastHrSampleIndex = i;
}

View File

@ -30,7 +30,6 @@ import nodomain.freeyourgadget.gadgetbridge.entities.UserAttributes;
import nodomain.freeyourgadget.gadgetbridge.entities.UserDao;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
import nodomain.freeyourgadget.gadgetbridge.model.HeartRateSample;
import nodomain.freeyourgadget.gadgetbridge.model.ValidByDate;
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
@ -383,9 +382,7 @@ public class DBHelper {
newSample.setSteps(cursor.getInt(colSteps));
int hrValue = cursor.getInt(colCustomShort);
if (newSample instanceof HeartRateSample) {
((HeartRateSample)newSample).setHeartRate(hrValue);
}
newSample.setHeartRate(hrValue);
newSamples.add(newSample);
}
sampleProvider.getSampleDao().insertOrReplaceInTx(newSamples, true);

View File

@ -1,30 +1,10 @@
package nodomain.freeyourgadget.gadgetbridge.database;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.support.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.database.schema.SchemaMigration;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoMaster;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.User;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.HeartRateSample;
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_CUSTOM_SHORT;
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_INTENSITY;
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_STEPS;
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_TIMESTAMP;
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_TYPE;
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.TABLE_GBACTIVITYSAMPLES;
public class DBOpenHelper extends DaoMaster.OpenHelper {
private final String updaterClassNamePrefix;
@ -38,11 +18,13 @@ public class DBOpenHelper extends DaoMaster.OpenHelper {
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
DaoMaster.createAllTables(db, true);
new SchemaMigration(updaterClassNamePrefix).onUpgrade(db, oldVersion, newVersion);
}
@Override
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
DaoMaster.createAllTables(db, true);
new SchemaMigration(updaterClassNamePrefix).onDowngrade(db, oldVersion, newVersion);
}

View File

@ -1,18 +1,30 @@
package nodomain.freeyourgadget.gadgetbridge.devices.pebble;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import de.greenrobot.dao.AbstractDao;
import de.greenrobot.dao.Property;
import de.greenrobot.dao.query.QueryBuilder;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.entities.PebbleMisfitSample;
import nodomain.freeyourgadget.gadgetbridge.entities.PebbleMisfitSampleDao;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
public class MisfitSampleProvider implements SampleProvider {
public class MisfitSampleProvider implements SampleProvider<PebbleMisfitSample> {
private final DaoSession mSession;
private final GBDevice mDevice;
protected final float movementDivisor = 300f;
public MisfitSampleProvider(GBDevice device, DaoSession session) {
mSession = session;
mDevice = device;
}
@Override
@ -22,7 +34,7 @@ public class MisfitSampleProvider implements SampleProvider {
@Override
public int toRawActivityKind(int activityKind) {
return (byte) activityKind;
return activityKind;
}
@Override
@ -31,47 +43,7 @@ public class MisfitSampleProvider implements SampleProvider {
}
@Override
public List getAllActivitySamples(int timestamp_from, int timestamp_to) {
return null;
}
@Override
public List getActivitySamples(int timestamp_from, int timestamp_to) {
return null;
}
@Override
public List getSleepSamples(int timestamp_from, int timestamp_to) {
return null;
}
@Override
public void changeStoredSamplesType(int timestampFrom, int timestampTo, int kind) {
}
@Override
public void changeStoredSamplesType(int timestampFrom, int timestampTo, int fromKind, int toKind) {
}
@Override
public int fetchLatestTimestamp() {
return 0;
}
@Override
public void addGBActivitySample(AbstractActivitySample activitySample) {
}
@Override
public void addGBActivitySamples(AbstractActivitySample[] activitySamples) {
}
@Override
public AbstractActivitySample createActivitySample() {
public PebbleMisfitSample createActivitySample() {
return null;
}
@ -79,4 +51,83 @@ public class MisfitSampleProvider implements SampleProvider {
public int getID() {
return SampleProvider.PROVIDER_PEBBLE_MISFIT;
}
@Override
public List<PebbleMisfitSample> getAllActivitySamples(int timestamp_from, int timestamp_to) {
return getGBActivitySamples(timestamp_from, timestamp_to, ActivityKind.TYPE_ALL);
}
@Override
public List<PebbleMisfitSample> getActivitySamples(int timestamp_from, int timestamp_to) {
return getGBActivitySamples(timestamp_from, timestamp_to, ActivityKind.TYPE_ACTIVITY);
}
@Override
public List<PebbleMisfitSample> getSleepSamples(int timestamp_from, int timestamp_to) {
return getGBActivitySamples(timestamp_from, timestamp_to, ActivityKind.TYPE_SLEEP);
}
@Override
public int fetchLatestTimestamp() {
QueryBuilder<PebbleMisfitSample> qb = getSampleDao().queryBuilder();
qb.orderDesc(getTimestampSampleProperty());
qb.limit(1);
List<PebbleMisfitSample> list = qb.build().list();
if (list.size() >= 1) {
return list.get(0).getTimestamp();
}
return -1;
}
@Override
public void addGBActivitySample(PebbleMisfitSample activitySample) {
getSampleDao().insertOrReplace(activitySample);
}
@Override
public void addGBActivitySamples(PebbleMisfitSample[] activitySamples) {
getSampleDao().insertOrReplaceInTx(activitySamples);
}
public void changeStoredSamplesType(int timestampFrom, int timestampTo, int kind) {
}
public void changeStoredSamplesType(int timestampFrom, int timestampTo, int fromKind, int toKind) {
}
protected List<PebbleMisfitSample> getGBActivitySamples(int timestamp_from, int timestamp_to, int activityType) {
QueryBuilder<PebbleMisfitSample> qb = getSampleDao().queryBuilder();
Property timestampProperty = getTimestampSampleProperty();
Device dbDevice = DBHelper.findDevice(mDevice, mSession);
if (dbDevice == null) {
// no device, no samples
return Collections.emptyList();
}
Property deviceProperty = getDeviceIdentifierSampleProperty();
qb.where(deviceProperty.eq(dbDevice.getId()), timestampProperty.ge(timestamp_from))
.where(timestampProperty.le(timestamp_to));
List<PebbleMisfitSample> samples = qb.build().list();
List<PebbleMisfitSample> filteredSamples = new ArrayList<>();
for (PebbleMisfitSample sample : samples) {
if ((sample.getRawKind() & activityType) != 0) {
sample.setProvider(this);
filteredSamples.add(sample);
}
}
return filteredSamples;
}
public AbstractDao<PebbleMisfitSample, ?> getSampleDao() {
return mSession.getPebbleMisfitSampleDao();
}
protected Property getTimestampSampleProperty() {
return PebbleMisfitSampleDao.Properties.Timestamp;
}
protected Property getDeviceIdentifierSampleProperty() {
return PebbleMisfitSampleDao.Properties.DeviceId;
}
}

View File

@ -21,33 +21,61 @@ public abstract class AbstractActivitySample implements ActivitySample {
return getProvider().normalizeType(getRawKind());
}
@Override
public int getRawKind() {
return NOT_MEASURED;
}
@Override
public float getIntensity() {
return getProvider().normalizeIntensity(getRawIntensity());
}
public abstract void setRawKind(int kind);
public void setRawKind(int kind) {
}
public abstract void setRawIntensity(int intensity);
public void setRawIntensity(int intensity) {
}
public abstract void setSteps(int steps);
public void setSteps(int steps) {
}
public abstract void setTimestamp(int timestamp);
public abstract void setUserId(Long userId);
public abstract Long getUserId();
@Override
public void setHeartRate(int heartRate) {
}
@Override
public int getHeartRate() {
return NOT_MEASURED;
}
public abstract void setDeviceId(Long deviceId);
public abstract Long getDeviceId();
public abstract Long getUserId();
@Override
public int getRawIntensity() {
return NOT_MEASURED;
}
@Override
public int getSteps() {
return NOT_MEASURED;
}
@Override
public String toString() {
return getClass().getSimpleName() + "{" +
"timestamp=" + DateTimeUtils.formatDateTime(DateTimeUtils.parseTimeStamp(getTimestamp())) +
", intensity=" + getIntensity() +
", steps=" + getSteps() +
", heartrate=" + getHeartRate() +
", type=" + getKind() +
", userId=" + getUserId() +
", deviceId=" + getDeviceId() +

View File

@ -13,7 +13,7 @@ public class GBActivitySample implements ActivitySample {
private final int intensity;
private final int steps;
private final int type;
private final int customValue;
private int customValue;
public GBActivitySample(SampleProvider provider, int timestamp, int intensity, int steps, int type) {
this(provider, timestamp, intensity, steps, type, 0);
@ -69,6 +69,16 @@ public class GBActivitySample implements ActivitySample {
return steps;
}
@Override
public int getHeartRate() {
return customValue;
}
@Override
public void setHeartRate(int value) {
customValue = value;
}
@Override
public int getRawKind() {
return type;

View File

@ -5,6 +5,7 @@ import java.util.Arrays;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
public class ActivityKind {
public static final int TYPE_NOT_MEASURED = -1;
public static final int TYPE_UNKNOWN = 0;
public static final int TYPE_ACTIVITY = 1;
public static final int TYPE_LIGHT_SLEEP = 2;

View File

@ -1,6 +1,18 @@
package nodomain.freeyourgadget.gadgetbridge.model;
public interface ActivitySample extends Sample {
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
public interface ActivitySample extends TimeStamped {
int NOT_MEASURED = -1;
/**
* Returns the provider of the data.
*
* @return who created the sample data
*/
SampleProvider getProvider();
/**
* Returns the raw activity kind value as recorded by the SampleProvider
*/
@ -27,4 +39,21 @@ public interface ActivitySample extends Sample {
* Returns the number of steps performed during the period of this sample
*/
int getSteps();
/**
* Returns the heart rate measured at the corresponding timestamp.
* The value is returned in heart beats per minute, in the range from
* 0-255, where 255 is an illegal value (e.g. due to a bad measurement)
*
* @return the heart rate value in beats per minute, or -1 if none
*/
int getHeartRate();
/**
* Sets the heart rate value of this sample. Typically only used in
* generic db migration.
*
* @param value the value in bpm
*/
void setHeartRate(int value);
}

View File

@ -1,18 +0,0 @@
package nodomain.freeyourgadget.gadgetbridge.model;
public interface HeartRateSample extends Sample {
/**
* Returns the heart rate measured at the corresponding timestamp.
* The value is returned in heart beats per minute, in the range from
* 0-255, where 255 is an illegal value (e.g. due to a bad measurement)
* @return the heart rate value in beats per minute, or null if none
*/
Integer getHeartRate();
/**
* Sets the heart rate value of this sample. Typically only used in
* generic db migration.
* @param value the value in bpm
*/
void setHeartRate(Integer value);
}

View File

@ -1,12 +0,0 @@
package nodomain.freeyourgadget.gadgetbridge.model;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
public interface Sample extends TimeStamped {
/**
* Returns the provider of the data.
*
* @return who created the sample data
*/
SampleProvider getProvider();
}

View File

@ -18,10 +18,8 @@ import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes;
import nodomain.freeyourgadget.gadgetbridge.devices.pebble.MisfitSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.PebbleActivitySample;
import nodomain.freeyourgadget.gadgetbridge.entities.PebbleMisfitSample;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public class AppMessageHandlerMisfit extends AppMessageHandler {
@ -78,7 +76,6 @@ public class AppMessageHandlerMisfit extends AppMessageHandler {
LOG.info("got data from " + startDate + " to " + endDate);
int totalSteps = 0;
PebbleActivitySample[] activitySamples = new PebbleActivitySample[samples];
PebbleMisfitSample[] misfitSamples = new PebbleMisfitSample[samples];
try (DBHandler db = GBApplication.acquireDB()) {
MisfitSampleProvider sampleProvider = new MisfitSampleProvider(device, db.getDaoSession());
@ -86,42 +83,16 @@ public class AppMessageHandlerMisfit extends AppMessageHandler {
Long deviceId = DBHelper.getDevice(getDevice(), db.getDaoSession()).getId();
for (int i = 0; i < samples; i++) {
short sample = buf.getShort();
int steps = 0;
int intensity = 0;
int activityKind = ActivityKind.TYPE_UNKNOWN;
if (((sample & 0x83ff) == 0x0001) && ((sample & 0xff00) <= 0x4800)) {
// sleep seems to be from 0x2401 to 0x4801 (0b0IIIII0000000001) where I = intensity ?
intensity = (sample & 0x7c00) >>> 10;
// 9-18 decimal after shift
if (intensity <= 13) {
activityKind = ActivityKind.TYPE_DEEP_SLEEP;
} else {
// FIXME: this leads to too much false positives, ignore for now
//activityKind = ActivityKind.TYPE_LIGHT_SLEEP;
//intensity *= 2; // better visual distinction
}
} else {
if ((sample & 0x0001) == 0) { // 16-??? steps encoded in bits 1-7
steps = (sample & 0x00fe);
} else { // 0-14 steps encoded in bits 1-3, most of the time fc71 bits are set in that case
steps = (sample & 0x000e);
}
intensity = steps;
activityKind = ActivityKind.TYPE_ACTIVITY;
}
misfitSamples[i] = new PebbleMisfitSample(null, sample & 0xffff, timestamp + i * 60, userId, deviceId);
misfitSamples[i].setProvider(sampleProvider);
int steps = misfitSamples[i].getSteps();
totalSteps += steps;
LOG.info("got steps for sample " + i + " : " + steps + "(" + Integer.toHexString(sample & 0xffff) + ")");
//activitySamples[i] = new PebbleActivitySample(null, timestamp + i * 60, intensity, steps, activityKind, userId, deviceId);
//activitySamples[i].setProvider(sampleProvider);
misfitSamples[i] = new PebbleMisfitSample(null, sample & 0xffff, timestamp + i * 60, userId, deviceId);
misfitSamples[i].setProvider(sampleProvider);
}
LOG.info("total steps for above period: " + totalSteps);
sampleProvider.addGBActivitySamples(activitySamples);
sampleProvider.addGBActivitySamples(misfitSamples);
} catch (Exception e) {
LOG.error("Error acquiring database", e);
return null;