Merge pull request #491 from jpbarraca/hplus-handle-data
HPlus: Improves device support
This commit is contained in:
commit
d646b6773e
|
@ -60,6 +60,7 @@ public class GBDaoGenerator {
|
|||
addPebbleHealthActivityKindOverlay(schema, user, device);
|
||||
addPebbleMisfitActivitySample(schema, user, device);
|
||||
addPebbleMorpheuzActivitySample(schema, user, device);
|
||||
addHPlusHealthActivityKindOverlay(schema, user, device);
|
||||
addHPlusHealthActivitySample(schema, user, device);
|
||||
|
||||
new DaoGenerator().generateAll(schema, "app/src/main/java");
|
||||
|
@ -224,17 +225,34 @@ public class GBDaoGenerator {
|
|||
|
||||
private static Entity addHPlusHealthActivitySample(Schema schema, Entity user, Entity device) {
|
||||
Entity activitySample = addEntity(schema, "HPlusHealthActivitySample");
|
||||
activitySample.implementsSerializable();
|
||||
addCommonActivitySampleProperties("AbstractActivitySample", activitySample, user, device);
|
||||
activitySample.addByteArrayProperty("rawHPlusHealthData");
|
||||
activitySample.addIntProperty("rawHPlusCalories").notNull();
|
||||
activitySample.addIntProperty("rawHPlusDistance").notNull();
|
||||
activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull().primaryKey();
|
||||
activitySample.addIntProperty(SAMPLE_RAW_INTENSITY).notNull().codeBeforeGetterAndSetter(OVERRIDE);
|
||||
activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE);
|
||||
activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull().codeBeforeGetterAndSetter(OVERRIDE);
|
||||
addHeartRateProperties(activitySample);
|
||||
activitySample.addIntProperty("distance");
|
||||
activitySample.addIntProperty("calories");
|
||||
|
||||
return activitySample;
|
||||
}
|
||||
|
||||
private static Entity addHPlusHealthActivityKindOverlay(Schema schema, Entity user, Entity device) {
|
||||
Entity activityOverlay = addEntity(schema, "HPlusHealthActivityOverlay");
|
||||
|
||||
activityOverlay.addIntProperty(TIMESTAMP_FROM).notNull().primaryKey();
|
||||
activityOverlay.addIntProperty(TIMESTAMP_TO).notNull().primaryKey();
|
||||
activityOverlay.addIntProperty(SAMPLE_RAW_KIND).notNull().primaryKey();
|
||||
Property deviceId = activityOverlay.addLongProperty("deviceId").primaryKey().notNull().getProperty();
|
||||
activityOverlay.addToOne(device, deviceId);
|
||||
|
||||
Property userId = activityOverlay.addLongProperty("userId").notNull().getProperty();
|
||||
activityOverlay.addToOne(user, userId);
|
||||
activityOverlay.addByteArrayProperty("rawHPlusHealthData");
|
||||
return activityOverlay;
|
||||
}
|
||||
|
||||
private static void addCommonActivitySampleProperties(String superClass, Entity activitySample, Entity user, Entity device) {
|
||||
activitySample.setSuperclass(superClass);
|
||||
activitySample.addImport(MAIN_PACKAGE + ".devices.SampleProvider");
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
package nodomain.freeyourgadget.gadgetbridge.devices.hplus;
|
||||
|
||||
/*
|
||||
* @author João Paulo Barraca <jpbarraca@gmail.com>
|
||||
*/
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Message constants reverse-engineered by João Paulo Barraca, jpbarraca@gmail.com.
|
||||
*
|
||||
* @author João Paulo Barraca <jpbarraca@gmail.com>
|
||||
*/
|
||||
public final class HPlusConstants {
|
||||
|
||||
public static final UUID UUID_CHARACTERISTIC_CONTROL = UUID.fromString("14702856-620a-3973-7c78-9cfff0876abd");
|
||||
|
@ -14,79 +13,95 @@ public final class HPlusConstants {
|
|||
public static final UUID UUID_SERVICE_HP = UUID.fromString("14701820-620a-3973-7c78-9cfff0876abd");
|
||||
|
||||
|
||||
public static final byte COUNTRY_CN = 1;
|
||||
public static final byte COUNTRY_OTHER = 2;
|
||||
public static final byte ARG_COUNTRY_CN = 1;
|
||||
public static final byte ARG_COUNTRY_OTHER = 2;
|
||||
|
||||
public static final byte CLOCK_24H = 0;
|
||||
public static final byte CLOCK_12H = 1;
|
||||
public static final byte ARG_TIMEMODE_24H = 0;
|
||||
public static final byte ARG_TIMEMODE_12H = 1;
|
||||
|
||||
public static final byte UNIT_METRIC = 0;
|
||||
public static final byte UNIT_IMPERIAL = 1;
|
||||
public static final byte ARG_UNIT_METRIC = 0;
|
||||
public static final byte ARG_UNIT_IMPERIAL = 1;
|
||||
|
||||
public static final byte SEX_MALE = 0;
|
||||
public static final byte SEX_FEMALE = 1;
|
||||
public static final byte ARG_GENDER_MALE = 0;
|
||||
public static final byte ARG_GENDER_FEMALE = 1;
|
||||
|
||||
public static final byte HEARTRATE_MEASURE_ON = 11;
|
||||
public static final byte HEARTRATE_MEASURE_OFF = 22;
|
||||
public static final byte ARG_HEARTRATE_MEASURE_ON = 11;
|
||||
public static final byte ARG_HEARTRATE_MEASURE_OFF = 22;
|
||||
|
||||
public static final byte HEARTRATE_ALLDAY_ON = 10;
|
||||
public static final byte HEARTRATE_ALLDAY_OFF = -1;
|
||||
public static final byte ARG_HEARTRATE_ALLDAY_ON = 0x0A;
|
||||
public static final byte ARG_HEARTRATE_ALLDAY_OFF = (byte) 0xff;
|
||||
|
||||
public static final byte[] COMMAND_SET_INIT1 = new byte[]{0x50,0x00,0x25,(byte) 0xb1,0x4a,0x00,0x00,0x27,0x10,0x05,0x02,0x00,(byte) 0xff,0x0a,(byte) 0xff,0x00,(byte) 0xff,(byte) 0xff,0x00,0x01};
|
||||
public static final byte[] COMMAND_SET_INIT2 = new byte[]{0x51,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,(byte) 0xe0,0x0c,0x12,0x16,0x0a,0x10,0x00,0x00,0x00,0x00};
|
||||
public static final byte INCOMING_CALL_STATE_DISABLED_THRESHOLD = 0x7B;
|
||||
public static final byte INCOMING_CALL_STATE_ENABLED = (byte) 0xAA;
|
||||
|
||||
public static final byte[] COMMAND_SET_PREF_START = new byte[]{0x4f, 0x5a};
|
||||
public static final byte[] COMMAND_SET_PREF_START1 = new byte[]{0x4d};
|
||||
public static final byte[] CMD_SET_PREF_START = new byte[]{0x4f, 0x5a};
|
||||
public static final byte[] CMD_SET_PREF_START1 = new byte[]{0x4d};
|
||||
public static final byte CMD_SET_ALARM = 0x4c;
|
||||
public static final byte CMD_SET_LANGUAGE = 0x22;
|
||||
public static final byte CMD_SET_TIMEMODE = 0x47;
|
||||
public static final byte CMD_SET_UNITS = 0x48;
|
||||
public static final byte CMD_SET_GENDER = 0x2d;
|
||||
public static final byte CMD_SET_DATE = 0x08;
|
||||
public static final byte CMD_SET_TIME = 0x09;
|
||||
public static final byte CMD_SET_WEEK = 0x2a;
|
||||
public static final byte CMD_SET_PREF_SIT = 0x1e;
|
||||
public static final byte CMD_SET_WEIGHT = 0x05;
|
||||
public static final byte CMD_HEIGHT = 0x04;
|
||||
public static final byte CMD_SET_AGE = 0x2c;
|
||||
public static final byte CMD_SET_GOAL = 0x26;
|
||||
public static final byte CMD_SET_SCREENTIME = 0x0b;
|
||||
public static final byte CMD_SET_BLOOD = 0x4e; //??
|
||||
|
||||
public static final byte COMMAND_SET_PREF_COUNTRY = 0x22;
|
||||
public static final byte COMMAND_SET_PREF_TIMEMODE = 0x47;
|
||||
public static final byte COMMAND_SET_PREF_UNIT = 0x48;
|
||||
public static final byte COMMAND_SET_PREF_SEX = 0x2d;
|
||||
public static final byte CMD_SET_FINDME = 0x0a;
|
||||
public static final byte ARG_FINDME_ON = 0x01;
|
||||
public static final byte ARG_FINDME_OFF = 0x02;
|
||||
|
||||
public static final byte COMMAND_SET_PREF_DATE = 0x08;
|
||||
public static final byte COMMAND_SET_PREF_TIME = 0x09;
|
||||
public static final byte COMMAND_SET_PREF_WEEK = 0x2a;
|
||||
public static final byte COMMAND_SET_PREF_SIT = 0x1e;
|
||||
public static final byte COMMAND_SET_PREF_WEIGHT = 0x05;
|
||||
public static final byte COMMAND_SET_PREF_HEIGHT = 0x04;
|
||||
public static final byte COMMAND_SET_PREF_AGE = 0x2c;
|
||||
public static final byte COMMAND_SET_PREF_GOAL = 0x26;
|
||||
public static final byte COMMAND_SET_PREF_SCREENTIME = 0x0b;
|
||||
public static final byte COMMAND_SET_PREF_BLOOD = 0x4e; //??
|
||||
public static final byte COMMAND_SET_PREF_FINDME = 0x0a;
|
||||
public static final byte COMMAND_SET_PREF_SAVE = 0x17;
|
||||
public static final byte COMMAND_SET_PREF_END = 0x4f;
|
||||
public static final byte COMMAND_SET_DISPLAY_ALERT = 0x23;
|
||||
public static final byte COMMAND_SET_PREF_ALLDAYHR = 53;
|
||||
public static final byte CMD_GET_VERSION = 0x17;
|
||||
public static final byte CMD_SET_END = 0x4f;
|
||||
public static final byte CMD_SET_INCOMING_CALL_NUMBER = 0x23;
|
||||
public static final byte CMD_SET_ALLDAY_HRM = 0x35;
|
||||
public static final byte CMD_ACTION_INCOMING_CALL = 0x41;
|
||||
public static final byte CMD_SET_CONF_END = 0x4f;
|
||||
public static final byte CMD_SET_PREFS = 0x50;
|
||||
public static final byte CMD_SET_SIT_INTERVAL = 0x51;
|
||||
public static final byte CMD_SET_HEARTRATE_STATE = 0x32;
|
||||
|
||||
public static final byte COMMAND_SET_INCOMING_CALL = 0x41;
|
||||
public static final byte[] COMMAND_FACTORY_RESET = new byte[] {-74, 90};
|
||||
//Actions to device
|
||||
public static final byte CMD_GET_ACTIVE_DAY = 0x27;
|
||||
public static final byte CMD_GET_DAY_DATA = 0x15;
|
||||
public static final byte CMD_GET_SLEEP = 0x19;
|
||||
public static final byte CMD_GET_CURR_DATA = 0x16;
|
||||
public static final byte CMD_GET_DEVICE_ID = 0x24;
|
||||
|
||||
public static final byte COMMAND_SET_CONF_SAVE = 0x17;
|
||||
public static final byte COMMAND_SET_CONF_END = 0x4f;
|
||||
public static final byte CMD_ACTION_INCOMING_SOCIAL = 0x31;
|
||||
//public static final byte COMMAND_ACTION_INCOMING_SMS = 0x40; //Unknown
|
||||
public static final byte CMD_ACTION_DISPLAY_TEXT = 0x43;
|
||||
|
||||
public static final byte COMMAND_SET_PREFS = 0x50;
|
||||
public static final byte COMMAND_SET_SIT_INTERVAL = 0x51;
|
||||
public static final byte CMD_ACTION_DISPLAY_TEXT_NAME = 0x3F;
|
||||
public static final byte CMD_ACTION_DISPLAY_TEXT_NAME_CN = 0x3E; //Text in GB2312?
|
||||
public static final byte[] CMD_ACTION_HELLO = new byte[]{0x01, 0x00};
|
||||
public static final byte CMD_SHUTDOWN = 0x5B;
|
||||
public static final byte ARG_SHUTDOWN_EN = 0x5A;
|
||||
|
||||
public static final byte CMD_FACTORY_RESET = -74;
|
||||
public static final byte ARG_FACTORY_RESET_EN = 0x5A;
|
||||
|
||||
public static final byte CMD_SET_INCOMING_MESSAGE = 0x07;
|
||||
public static final byte CMD_SET_INCOMING_CALL = 0x06;
|
||||
public static final byte ARG_INCOMING_CALL = (byte) -86;
|
||||
public static final byte ARG_INCOMING_MESSAGE = (byte) -86;
|
||||
|
||||
//Incoming Messages
|
||||
public static final byte DATA_STATS = 0x33;
|
||||
public static final byte DATA_STEPS = 0x36;
|
||||
|
||||
public static final byte DATA_DAY_SUMMARY = 0x38;
|
||||
public static final byte DATA_DAY_SUMMARY_ALT = 0x39;
|
||||
public static final byte DATA_SLEEP = 0x1A;
|
||||
|
||||
public static final byte COMMAND_ACTION_INCOMING_SOCIAL = 0x31;
|
||||
public static final byte COMMAND_ACTION_INCOMING_SMS = 0x40;
|
||||
public static final byte COMMAND_ACTION_DISPLAY_TEXT = 0x43;
|
||||
public static final byte[] COMMAND_ACTION_INCOMING_CALL = new byte[] {6, -86};
|
||||
public static final byte COMMAND_ACTION_DISPLAY_TEXT_CENTER = 0x23;
|
||||
public static final byte COMMAND_ACTION_DISPLAY_TEXT_NAME = 0x3F;
|
||||
public static final byte COMMAND_ACTION_DISPLAY_TEXT_NAME_CN = 0x3E; //Text in GB2312?
|
||||
|
||||
|
||||
public static final byte DATA_VERSION = 0x18;
|
||||
|
||||
public static final String PREF_HPLUS_SCREENTIME = "hplus_screentime";
|
||||
public static final String PREF_HPLUS_ALLDAYHR = "hplus_alldayhr";
|
||||
public static final String PREF_HPLUS_HR = "hplus_hr_enable";
|
||||
public static final String PREF_HPLUS_UNIT = "hplus_unit";
|
||||
public static final String PREF_HPLUS_TIMEMODE = "hplus_timemode";
|
||||
public static final String PREF_HPLUS_WRIST = "hplus_wrist";
|
||||
|
@ -95,5 +110,4 @@ public final class HPlusConstants {
|
|||
public static final String PREF_HPLUS_SIT_START_TIME = "hplus_sit_start_time";
|
||||
public static final String PREF_HPLUS_SIT_END_TIME = "hplus_sit_end_time";
|
||||
public static final String PREF_HPLUS_COUNTRY = "hplus_country";
|
||||
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import android.os.Build;
|
|||
import android.os.ParcelUuid;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import de.greenrobot.dao.query.QueryBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBException;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
|
@ -20,9 +21,9 @@ import nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsActivity;
|
|||
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.UserInfo;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HPlusHealthActivitySampleDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
|
@ -44,20 +45,15 @@ public class HPlusCoordinator extends AbstractDeviceCoordinator {
|
|||
@Override
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public Collection<? extends ScanFilter> createBLEScanFilters() {
|
||||
ParcelUuid mi2Service = new ParcelUuid(HPlusConstants.UUID_SERVICE_HP);
|
||||
ScanFilter filter = new ScanFilter.Builder().setServiceUuid(mi2Service).build();
|
||||
ParcelUuid hpService = new ParcelUuid(HPlusConstants.UUID_SERVICE_HP);
|
||||
ScanFilter filter = new ScanFilter.Builder().setServiceUuid(hpService).build();
|
||||
return Collections.singletonList(filter);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public DeviceType getSupportedType(GBDeviceCandidate candidate) {
|
||||
if (candidate.supportsService(HPlusConstants.UUID_SERVICE_HP)) {
|
||||
return DeviceType.HPLUS;
|
||||
}
|
||||
|
||||
String name = candidate.getDevice().getName();
|
||||
LOG.debug("Looking for: " + name);
|
||||
if (name != null && name.startsWith("HPLUS")) {
|
||||
return DeviceType.HPLUS;
|
||||
}
|
||||
|
@ -97,7 +93,7 @@ public class HPlusCoordinator extends AbstractDeviceCoordinator {
|
|||
|
||||
@Override
|
||||
public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
|
||||
return new HPlusSampleProvider(device, session);
|
||||
return new HPlusHealthSampleProvider(device, session);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -137,7 +133,9 @@ public class HPlusCoordinator extends AbstractDeviceCoordinator {
|
|||
|
||||
@Override
|
||||
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
|
||||
// nothing to delete, yet
|
||||
Long deviceId = device.getId();
|
||||
QueryBuilder<?> qb = session.getHPlusHealthActivitySampleDao().queryBuilder();
|
||||
qb.where(HPlusHealthActivitySampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
|
||||
}
|
||||
|
||||
public static int getFitnessGoal(String address) throws IllegalArgumentException {
|
||||
|
@ -177,10 +175,13 @@ public class HPlusCoordinator extends AbstractDeviceCoordinator {
|
|||
return (byte) (activityUser.getAge() & 0xFF);
|
||||
}
|
||||
|
||||
public static byte getUserSex(String address) {
|
||||
public static byte getUserGender(String address) {
|
||||
ActivityUser activityUser = new ActivityUser();
|
||||
|
||||
return (byte) (activityUser.getGender() & 0xFF);
|
||||
if (activityUser.getGender() == ActivityUser.GENDER_MALE)
|
||||
return HPlusConstants.ARG_GENDER_MALE;
|
||||
|
||||
return HPlusConstants.ARG_GENDER_FEMALE;
|
||||
}
|
||||
|
||||
public static int getGoal(String address) {
|
||||
|
@ -194,7 +195,11 @@ public class HPlusCoordinator extends AbstractDeviceCoordinator {
|
|||
}
|
||||
|
||||
public static byte getAllDayHR(String address) {
|
||||
return (byte) (prefs.getInt(HPlusConstants.PREF_HPLUS_ALLDAYHR + "_" + address, 10) & 0xFF);
|
||||
return (byte) (prefs.getInt(HPlusConstants.PREF_HPLUS_ALLDAYHR + "_" + address, HPlusConstants.ARG_HEARTRATE_ALLDAY_ON) & 0xFF);
|
||||
}
|
||||
|
||||
public static byte getHRState(String address) {
|
||||
return (byte) (prefs.getInt(HPlusConstants.PREF_HPLUS_HR + "_" + address, HPlusConstants.ARG_HEARTRATE_MEASURE_ON) & 0xFF);
|
||||
}
|
||||
|
||||
public static byte getSocial(String address) {
|
||||
|
|
|
@ -0,0 +1,207 @@
|
|||
package nodomain.freeyourgadget.gadgetbridge.devices.hplus;
|
||||
|
||||
/*
|
||||
* @author João Paulo Barraca <jpbarraca@gmail.com>
|
||||
*/
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.GregorianCalendar;
|
||||
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.AbstractSampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HPlusHealthActivityOverlay;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HPlusHealthActivityOverlayDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HPlusHealthActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HPlusHealthActivitySampleDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.hplus.HPlusDataRecord;
|
||||
|
||||
public class HPlusHealthSampleProvider extends AbstractSampleProvider<HPlusHealthActivitySample> {
|
||||
|
||||
private GBDevice mDevice;
|
||||
private DaoSession mSession;
|
||||
|
||||
public HPlusHealthSampleProvider(GBDevice device, DaoSession session) {
|
||||
super(device, session);
|
||||
|
||||
mSession = session;
|
||||
mDevice = device;
|
||||
}
|
||||
|
||||
public int getID() {
|
||||
|
||||
return SampleProvider.PROVIDER_HPLUS;
|
||||
}
|
||||
|
||||
public int normalizeType(int rawType) {
|
||||
switch (rawType){
|
||||
case HPlusDataRecord.TYPE_DAY_SLOT:
|
||||
case HPlusDataRecord.TYPE_DAY_SUMMARY:
|
||||
case HPlusDataRecord.TYPE_REALTIME:
|
||||
case HPlusDataRecord.TYPE_SLEEP:
|
||||
case HPlusDataRecord.TYPE_UNKNOWN:
|
||||
return ActivityKind.TYPE_UNKNOWN;
|
||||
default:
|
||||
return rawType;
|
||||
}
|
||||
}
|
||||
|
||||
public int toRawActivityKind(int activityKind) {
|
||||
switch (activityKind){
|
||||
case ActivityKind.TYPE_DEEP_SLEEP:
|
||||
return ActivityKind.TYPE_DEEP_SLEEP;
|
||||
case ActivityKind.TYPE_LIGHT_SLEEP:
|
||||
return ActivityKind.TYPE_LIGHT_SLEEP;
|
||||
default:
|
||||
return HPlusDataRecord.TYPE_DAY_SLOT;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Property getTimestampSampleProperty() {
|
||||
return HPlusHealthActivitySampleDao.Properties.Timestamp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HPlusHealthActivitySample createActivitySample() {
|
||||
return new HPlusHealthActivitySample();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Property getRawKindSampleProperty() {
|
||||
return null; // HPlusHealthActivitySampleDao.Properties.RawKind;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float normalizeIntensity(int rawIntensity) {
|
||||
return rawIntensity / (float) 100.0;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Property getDeviceIdentifierSampleProperty() {
|
||||
return HPlusHealthActivitySampleDao.Properties.DeviceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractDao<HPlusHealthActivitySample, ?> getSampleDao() {
|
||||
return getSession().getHPlusHealthActivitySampleDao();
|
||||
}
|
||||
|
||||
|
||||
public List<HPlusHealthActivitySample> getActivityamples(int timestamp_from, int timestamp_to) {
|
||||
return getAllActivitySamples(timestamp_from, timestamp_to);
|
||||
}
|
||||
|
||||
public List<HPlusHealthActivitySample> getSleepSamples(int timestamp_from, int timestamp_to) {
|
||||
return getAllActivitySamples(timestamp_from, timestamp_to);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public List<HPlusHealthActivitySample> getAllActivitySamples(int timestamp_from, int timestamp_to) {
|
||||
List<HPlusHealthActivitySample> samples = super.getGBActivitySamples(timestamp_from, timestamp_to, ActivityKind.TYPE_ALL);
|
||||
|
||||
Device dbDevice = DBHelper.findDevice(getDevice(), getSession());
|
||||
if (dbDevice == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
QueryBuilder<HPlusHealthActivityOverlay> qb = getSession().getHPlusHealthActivityOverlayDao().queryBuilder();
|
||||
|
||||
qb.where(HPlusHealthActivityOverlayDao.Properties.DeviceId.eq(dbDevice.getId()),
|
||||
HPlusHealthActivityOverlayDao.Properties.TimestampFrom.ge(timestamp_from - 3600 * 24),
|
||||
HPlusHealthActivityOverlayDao.Properties.TimestampTo.le(timestamp_to),
|
||||
HPlusHealthActivityOverlayDao.Properties.TimestampTo.ge(timestamp_from));
|
||||
|
||||
List<HPlusHealthActivityOverlay> overlayRecords = qb.build().list();
|
||||
|
||||
//Todays sample steps will come from the Day Slots messages
|
||||
//Historical steps will be provided by Day Summaries messages
|
||||
//This will allow both week and current day results to be consistent
|
||||
Calendar today = GregorianCalendar.getInstance();
|
||||
today.set(Calendar.HOUR_OF_DAY, 0);
|
||||
today.set(Calendar.MINUTE, 0);
|
||||
today.set(Calendar.SECOND, 0);
|
||||
today.set(Calendar.MILLISECOND, 0);
|
||||
|
||||
int stepsToday = 0;
|
||||
for(HPlusHealthActivitySample sample: samples){
|
||||
if(sample.getTimestamp() >= today.getTimeInMillis() / 1000){
|
||||
//Only consider these for the current day as a single message is enough for steps
|
||||
//HR and Overlays will still benefit from the full set of samples
|
||||
if(sample.getRawKind() == HPlusDataRecord.TYPE_REALTIME) {
|
||||
int aux = sample.getSteps();
|
||||
sample.setSteps(sample.getSteps() - stepsToday);
|
||||
stepsToday = aux;
|
||||
}else
|
||||
sample.setSteps(ActivitySample.NOT_MEASURED);
|
||||
}else{
|
||||
if (sample.getRawKind() != HPlusDataRecord.TYPE_DAY_SUMMARY) {
|
||||
sample.setSteps(ActivityKind.TYPE_NOT_MEASURED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (HPlusHealthActivityOverlay overlay : overlayRecords) {
|
||||
|
||||
//Create fake events to improve activity counters if there are no events around the overlay
|
||||
//timestamp boundaries
|
||||
insertVirtualItem(samples, Math.max(overlay.getTimestampFrom(), timestamp_from), overlay.getDeviceId(), overlay.getUserId());
|
||||
insertVirtualItem(samples, Math.min(overlay.getTimestampTo() - 1, timestamp_to - 1), overlay.getDeviceId(), overlay.getUserId());
|
||||
for (HPlusHealthActivitySample sample : samples) {
|
||||
|
||||
if (sample.getTimestamp() >= overlay.getTimestampFrom() && sample.getTimestamp() < overlay.getTimestampTo()) {
|
||||
sample.setRawKind(overlay.getRawKind());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
detachFromSession();
|
||||
|
||||
Collections.sort(samples, new Comparator<HPlusHealthActivitySample>() {
|
||||
public int compare(HPlusHealthActivitySample one, HPlusHealthActivitySample other) {
|
||||
return one.getTimestamp() - other.getTimestamp();
|
||||
}
|
||||
});
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
private List<HPlusHealthActivitySample> insertVirtualItem(List<HPlusHealthActivitySample> samples, int timestamp, long deviceId, long userId) {
|
||||
HPlusHealthActivitySample sample = new HPlusHealthActivitySample(
|
||||
timestamp, // ts
|
||||
deviceId,
|
||||
userId, // User id
|
||||
null, // Raw Data
|
||||
ActivityKind.TYPE_UNKNOWN,
|
||||
0, // Intensity
|
||||
ActivitySample.NOT_MEASURED, // Steps
|
||||
ActivitySample.NOT_MEASURED, // HR
|
||||
ActivitySample.NOT_MEASURED, // Distance
|
||||
ActivitySample.NOT_MEASURED // Calories
|
||||
);
|
||||
|
||||
sample.setProvider(this);
|
||||
samples.add(sample);
|
||||
|
||||
return samples;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,82 +0,0 @@
|
|||
package nodomain.freeyourgadget.gadgetbridge.devices.hplus;
|
||||
|
||||
|
||||
/*
|
||||
* @author João Paulo Barraca <jpbarraca@gmail.com>
|
||||
*/
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import de.greenrobot.dao.AbstractDao;
|
||||
import de.greenrobot.dao.query.QueryBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractSampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HPlusHealthActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HPlusHealthActivitySampleDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
|
||||
public class HPlusSampleProvider extends AbstractSampleProvider<HPlusHealthActivitySample> {
|
||||
|
||||
|
||||
private GBDevice mDevice;
|
||||
private DaoSession mSession;
|
||||
|
||||
public HPlusSampleProvider(GBDevice device, DaoSession session) {
|
||||
super(device, session);
|
||||
|
||||
mSession = session;
|
||||
mDevice = device;;
|
||||
}
|
||||
|
||||
public int getID() {
|
||||
return SampleProvider.PROVIDER_HPLUS;
|
||||
}
|
||||
|
||||
public int normalizeType(int rawType) {
|
||||
return rawType;
|
||||
}
|
||||
|
||||
public int toRawActivityKind(int activityKind) {
|
||||
return activityKind;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected de.greenrobot.dao.Property getTimestampSampleProperty() {
|
||||
return HPlusHealthActivitySampleDao.Properties.Timestamp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HPlusHealthActivitySample createActivitySample() {
|
||||
return new HPlusHealthActivitySample();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected de.greenrobot.dao.Property getRawKindSampleProperty() {
|
||||
return HPlusHealthActivitySampleDao.Properties.RawKind;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float normalizeIntensity(int rawIntensity) {
|
||||
return rawIntensity; //TODO: Calculate actual value
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected de.greenrobot.dao.Property getDeviceIdentifierSampleProperty() {
|
||||
return HPlusHealthActivitySampleDao.Properties.DeviceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractDao<HPlusHealthActivitySample, ?> getSampleDao() {
|
||||
return getSession().getHPlusHealthActivitySampleDao();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
package nodomain.freeyourgadget.gadgetbridge.service.devices.hplus;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
|
||||
/*
|
||||
* @author João Paulo Barraca <jpbarraca@gmail.com>
|
||||
*/
|
||||
|
||||
|
||||
public class HPlusDataRecord {
|
||||
public final static int TYPE_UNKNOWN = 0;
|
||||
public final static int TYPE_SLEEP = 100;
|
||||
public final static int TYPE_DAY_SUMMARY = 101;
|
||||
public final static int TYPE_DAY_SLOT = 102;
|
||||
public final static int TYPE_REALTIME = 103;
|
||||
|
||||
public int type = TYPE_UNKNOWN;
|
||||
public int activityKind = ActivityKind.TYPE_UNKNOWN;
|
||||
|
||||
/**
|
||||
* Time of this record in seconds
|
||||
*/
|
||||
public int timestamp;
|
||||
|
||||
/**
|
||||
* Raw data as sent from the device
|
||||
*/
|
||||
public byte[] rawData;
|
||||
|
||||
protected HPlusDataRecord(){
|
||||
|
||||
}
|
||||
|
||||
protected HPlusDataRecord(byte[] data, int type){
|
||||
this.rawData = data;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public byte[] getRawData() {
|
||||
|
||||
return rawData;
|
||||
}
|
||||
|
||||
public class RecordInterval {
|
||||
/**
|
||||
* Start time of this interval in seconds
|
||||
*/
|
||||
public int timestampFrom;
|
||||
|
||||
/**
|
||||
* End time of this interval in seconds
|
||||
*/
|
||||
public int timestampTo;
|
||||
|
||||
/**
|
||||
* Type of activity {@link ActivityKind}
|
||||
*/
|
||||
public int activityKind;
|
||||
|
||||
RecordInterval(int timestampFrom, int timestampTo, int activityKind) {
|
||||
this.timestampFrom = timestampFrom;
|
||||
this.timestampTo = timestampTo;
|
||||
this.activityKind = activityKind;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
package nodomain.freeyourgadget.gadgetbridge.service.devices.hplus;
|
||||
|
||||
/*
|
||||
* @author João Paulo Barraca <jpbarraca@gmail.com>
|
||||
*/
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.Locale;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
|
||||
|
||||
public class HPlusDataRecordDaySlot extends HPlusDataRecord {
|
||||
|
||||
/**
|
||||
* The device reports data aggregated in slots.
|
||||
* There are 144 slots in a given day, summarizing 10 minutes of data
|
||||
* Integer with the slot number from 0 to 143
|
||||
*/
|
||||
public int slot;
|
||||
|
||||
/**
|
||||
* Number of steps
|
||||
*/
|
||||
public int steps;
|
||||
|
||||
/**
|
||||
* Number of seconds without activity (TBC)
|
||||
*/
|
||||
public int secondsInactive;
|
||||
|
||||
/**
|
||||
* Average Heart Rate in Beats Per Minute
|
||||
*/
|
||||
public int heartRate;
|
||||
|
||||
public HPlusDataRecordDaySlot(byte[] data) {
|
||||
super(data, TYPE_DAY_SLOT);
|
||||
|
||||
int a = (data[4] & 0xFF) * 256 + (data[5] & 0xFF);
|
||||
if (a >= 144) {
|
||||
throw new IllegalArgumentException("Invalid Slot Number");
|
||||
}
|
||||
|
||||
slot = a;
|
||||
heartRate = data[1] & 0xFF;
|
||||
|
||||
if(heartRate == 255)
|
||||
heartRate = ActivityKind.TYPE_NOT_MEASURED;
|
||||
|
||||
steps = (data[2] & 0xFF) * 256 + data[3] & 0xFF;
|
||||
|
||||
//?? data[6]; atemp?? always 0
|
||||
secondsInactive = data[7] & 0xFF; // ?
|
||||
|
||||
Calendar slotTime = GregorianCalendar.getInstance();
|
||||
|
||||
slotTime.set(Calendar.MINUTE, (slot % 6) * 10);
|
||||
slotTime.set(Calendar.HOUR_OF_DAY, slot / 6);
|
||||
slotTime.set(Calendar.SECOND, 0);
|
||||
|
||||
timestamp = (int) (slotTime.getTimeInMillis() / 1000L);
|
||||
}
|
||||
|
||||
public String toString(){
|
||||
Calendar slotTime = GregorianCalendar.getInstance();
|
||||
slotTime.setTimeInMillis(timestamp * 1000L);
|
||||
return String.format(Locale.US, "Slot: %d, Time: %s, Steps: %d, InactiveSeconds: %d, HeartRate: %d", slot, slotTime.getTime(), steps, secondsInactive, heartRate);
|
||||
}
|
||||
|
||||
public void add(HPlusDataRecordDaySlot other){
|
||||
if(other == null)
|
||||
return;
|
||||
|
||||
steps += other.steps;
|
||||
secondsInactive += other.secondsInactive;
|
||||
if(heartRate == -1)
|
||||
heartRate = other.heartRate;
|
||||
else if(other.heartRate != -1) {
|
||||
heartRate = (heartRate + other.heartRate) / 2;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
package nodomain.freeyourgadget.gadgetbridge.service.devices.hplus;
|
||||
|
||||
/*
|
||||
* @author João Paulo Barraca <jpbarraca@gmail.com>
|
||||
*/
|
||||
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.Locale;
|
||||
|
||||
|
||||
class HPlusDataRecordDaySummary extends HPlusDataRecord{
|
||||
|
||||
/**
|
||||
* Year of the record reported by the device
|
||||
* Sometimes the device will report a low number (e.g, 116) which will be "corrected"
|
||||
* by adding 1900
|
||||
*/
|
||||
public int year;
|
||||
|
||||
/**
|
||||
* Month of the record reported by the device from 1 to 12
|
||||
*/
|
||||
public int month;
|
||||
|
||||
/**
|
||||
* Day of the record reported by the device
|
||||
*/
|
||||
public int day;
|
||||
|
||||
/**
|
||||
* Number of steps accumulated in the day reported
|
||||
*/
|
||||
public int steps;
|
||||
|
||||
/**
|
||||
* Distance in meters accumulated in the day reported
|
||||
*/
|
||||
public int distance;
|
||||
|
||||
/**
|
||||
* Amount of time active in the day (Units are To Be Determined)
|
||||
*/
|
||||
public int activeTime;
|
||||
|
||||
/**
|
||||
* Max Heart Rate recorded in Beats Per Minute
|
||||
*/
|
||||
public int maxHeartRate;
|
||||
|
||||
/**
|
||||
* Min Heart Rate recorded in Beats Per Minute
|
||||
*/
|
||||
public int minHeartRate;
|
||||
|
||||
/**
|
||||
* Amount of estimated calories consumed during the day in KCalories
|
||||
*/
|
||||
public int calories;
|
||||
|
||||
HPlusDataRecordDaySummary(byte[] data) {
|
||||
super(data, TYPE_DAY_SUMMARY);
|
||||
|
||||
year = (data[10] & 0xFF) * 256 + (data[9] & 0xFF);
|
||||
month = data[11] & 0xFF;
|
||||
day = data[12] & 0xFF;
|
||||
|
||||
//Recover from bug in firmware where year is corrupted
|
||||
//data[10] will be set to 0, effectively offsetting values by minus 1900 years
|
||||
if(year < 1900)
|
||||
year += 1900;
|
||||
|
||||
if (year < 2000 || month > 12 || day > 31) {
|
||||
throw new IllegalArgumentException("Invalid record date "+year+"-"+month+"-"+day);
|
||||
}
|
||||
steps = (data[2] & 0xFF) * 256 + (data[1] & 0xFF);
|
||||
distance = ((data[4] & 0xFF) * 256 + (data[3] & 0xFF)) * 10;
|
||||
activeTime = (data[14] & 0xFF) * 256 + (data[13] & 0xFF);
|
||||
calories = (data[6] & 0xFF) * 256 + (data[5] & 0xFF);
|
||||
calories += (data[8] & 0xFF) * 256 + (data[7] & 0xFF);
|
||||
|
||||
maxHeartRate = data[15] & 0xFF;
|
||||
minHeartRate = data[16] & 0xFF;
|
||||
|
||||
Calendar date = GregorianCalendar.getInstance();
|
||||
date.set(Calendar.YEAR, year);
|
||||
date.set(Calendar.MONTH, month - 1);
|
||||
date.set(Calendar.DAY_OF_MONTH, day);
|
||||
date.set(Calendar.HOUR_OF_DAY, 23);
|
||||
date.set(Calendar.MINUTE, 59);
|
||||
date.set(Calendar.SECOND, 59);
|
||||
date.set(Calendar.MILLISECOND, 999);
|
||||
|
||||
timestamp = (int) (date.getTimeInMillis() / 1000);
|
||||
}
|
||||
|
||||
public String toString(){
|
||||
return String.format(Locale.US, "%s-%s-%s steps:%d distance:%d minHR:%d maxHR:%d calories:%d activeTime:%d", year, month, day, steps, distance,minHeartRate, maxHeartRate, calories, activeTime);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
package nodomain.freeyourgadget.gadgetbridge.service.devices.hplus;
|
||||
|
||||
/*
|
||||
* @author João Paulo Barraca <jpbarraca@gmail.com>
|
||||
*/
|
||||
|
||||
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.Locale;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
|
||||
|
||||
class HPlusDataRecordRealtime extends HPlusDataRecord {
|
||||
|
||||
/**
|
||||
* Distance accumulated during the day in meters
|
||||
*/
|
||||
public int distance;
|
||||
|
||||
/**
|
||||
* Calories consumed during the day in KCalories
|
||||
*/
|
||||
public int calories;
|
||||
|
||||
/**
|
||||
* Instantaneous Heart Rate measured in Beats Per Minute
|
||||
*/
|
||||
public int heartRate;
|
||||
|
||||
/**
|
||||
* Battery level from 0 to 100
|
||||
*/
|
||||
public byte battery;
|
||||
|
||||
/**
|
||||
* Number of steps today
|
||||
*/
|
||||
public int steps;
|
||||
|
||||
/**
|
||||
* Time active (To be determined how it works)
|
||||
*/
|
||||
public int activeTime;
|
||||
|
||||
/**
|
||||
* Computing intensity
|
||||
* To be calculated appropriately
|
||||
*/
|
||||
public int intensity;
|
||||
|
||||
public HPlusDataRecordRealtime(byte[] data) {
|
||||
super(data, TYPE_REALTIME);
|
||||
|
||||
if (data.length < 15) {
|
||||
throw new IllegalArgumentException("Invalid data packet");
|
||||
}
|
||||
|
||||
timestamp = (int) (GregorianCalendar.getInstance().getTimeInMillis() / 1000);
|
||||
distance = 10 * ((data[4] & 0xFF) * 256 + (data[3] & 0xFF)); // meters
|
||||
steps = (data[2] & 0xFF) * 256 + (data[1] & 0xFF);
|
||||
int x = (data[6] & 0xFF) * 256 + data[5] & 0xFF;
|
||||
int y = (data[8] & 0xFF) * 256 + data[7] & 0xFF;
|
||||
|
||||
battery = data[9];
|
||||
|
||||
calories = x + y; // KCal
|
||||
|
||||
heartRate = data[11] & 0xFF; // BPM
|
||||
activeTime = (data[14] & 0xFF * 256) + (data[13] & 0xFF);
|
||||
if(heartRate == 255) {
|
||||
intensity = 0;
|
||||
activityKind = ActivityKind.TYPE_NOT_MEASURED;
|
||||
}
|
||||
else {
|
||||
intensity = (int) (100 * Math.max(0, Math.min((heartRate - 60) / 120.0, 1))); // TODO: Calculate a proper value
|
||||
activityKind = ActivityKind.TYPE_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
public void computeActivity(HPlusDataRecordRealtime prev){
|
||||
if(prev == null)
|
||||
return;
|
||||
|
||||
int deltaDistance = distance - prev.distance;
|
||||
|
||||
if(deltaDistance <= 0)
|
||||
return;
|
||||
|
||||
int deltaTime = timestamp - prev.timestamp;
|
||||
|
||||
if(deltaTime <= 0)
|
||||
return;
|
||||
|
||||
double speed = deltaDistance / deltaTime;
|
||||
|
||||
if(speed >= 1.6) // ~6 KM/h
|
||||
activityKind = ActivityKind.TYPE_ACTIVITY;
|
||||
}
|
||||
|
||||
public boolean same(HPlusDataRecordRealtime other){
|
||||
if(other == null)
|
||||
return false;
|
||||
|
||||
return steps == other.steps && distance == other.distance && calories == other.calories && heartRate == other.heartRate && battery == other.battery;
|
||||
}
|
||||
|
||||
public String toString(){
|
||||
return String.format(Locale.US, "Distance: %d Steps: %d Calories: %d HeartRate: %d Battery: %d ActiveTime: %d Intensity: %d", distance, steps, calories, heartRate, battery, activeTime, intensity);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
package nodomain.freeyourgadget.gadgetbridge.service.devices.hplus;
|
||||
|
||||
/*
|
||||
* @author João Paulo Barraca <jpbarraca@gmail.com>
|
||||
*/
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
|
||||
public class HPlusDataRecordSleep extends HPlusDataRecord {
|
||||
|
||||
/**
|
||||
* Time which the device determined to be the bed time in seconds
|
||||
*/
|
||||
public int bedTimeStart;
|
||||
|
||||
/**
|
||||
* Time which the device determined to be the end of this sleep period in seconds
|
||||
*/
|
||||
public int bedTimeEnd;
|
||||
|
||||
/**
|
||||
* Number of minutes in Deep Sleep
|
||||
*/
|
||||
public int deepSleepMinutes;
|
||||
|
||||
/**
|
||||
* Number of minutes in Light Sleep
|
||||
* This is considered as Light Sleep
|
||||
|
||||
*/
|
||||
public int lightSleepMinutes;
|
||||
|
||||
/**
|
||||
* Number of minutes to start sleeping (??)
|
||||
* This is considered as Light Sleep
|
||||
*/
|
||||
public int enterSleepMinutes;
|
||||
|
||||
/**
|
||||
* Number of minutes with Sleep Spindles (??)
|
||||
* This is considered as Light Sleep
|
||||
*/
|
||||
public int spindleMinutes;
|
||||
|
||||
/**
|
||||
* Number of minutes in REM sleep
|
||||
* This is considered as Light Sleep
|
||||
|
||||
*/
|
||||
public int remSleepMinutes;
|
||||
|
||||
/**
|
||||
* Number of wake up minutes during the sleep period
|
||||
* This is not considered as a sleep activity
|
||||
*/
|
||||
public int wakeupMinutes;
|
||||
|
||||
/**
|
||||
* Number of times the user woke up
|
||||
*/
|
||||
public int wakeupCount;
|
||||
|
||||
public HPlusDataRecordSleep(byte[] data) {
|
||||
super(data, TYPE_SLEEP);
|
||||
|
||||
int year = (data[2] & 0xFF) * 256 + (data[1] & 0xFF);
|
||||
int month = data[3] & 0xFF;
|
||||
int day = data[4] & 0xFF;
|
||||
|
||||
if (year < 2000) //Attempt to recover from bug from device.
|
||||
year += 1900;
|
||||
|
||||
if (year < 2000 || month > 12 || day <= 0 || day > 31) {
|
||||
throw new IllegalArgumentException("Invalid record date: " + year + "-" + month + "-" + day);
|
||||
}
|
||||
|
||||
enterSleepMinutes = ((data[6] & 0xFF) * 256 + (data[5] & 0xFF));
|
||||
spindleMinutes = ((data[8] & 0xFF) * 256 + (data[7] & 0xFF));
|
||||
deepSleepMinutes = ((data[10] & 0xFF) * 256 + (data[9] & 0xFF));
|
||||
remSleepMinutes = ((data[12] & 0xFF) * 256 + (data[11] & 0xFF));
|
||||
wakeupMinutes = ((data[14] & 0xFF) * 256 + (data[13] & 0xFF));
|
||||
wakeupCount = ((data[16] & 0xFF) * 256 + (data[15] & 0xFF));
|
||||
|
||||
int hour = data[17] & 0xFF;
|
||||
int minute = data[18] & 0xFF;
|
||||
|
||||
Calendar sleepStart = GregorianCalendar.getInstance();
|
||||
sleepStart.clear();
|
||||
sleepStart.set(Calendar.YEAR, year);
|
||||
sleepStart.set(Calendar.MONTH, month - 1);
|
||||
sleepStart.set(Calendar.DAY_OF_MONTH, day);
|
||||
sleepStart.set(Calendar.HOUR_OF_DAY, hour);
|
||||
sleepStart.set(Calendar.MINUTE, minute);
|
||||
sleepStart.set(Calendar.SECOND, 0);
|
||||
sleepStart.set(Calendar.MILLISECOND, 0);
|
||||
|
||||
bedTimeStart = (int) (sleepStart.getTimeInMillis() / 1000);
|
||||
bedTimeEnd = (enterSleepMinutes + spindleMinutes + deepSleepMinutes + remSleepMinutes + wakeupMinutes) * 60 + bedTimeStart;
|
||||
lightSleepMinutes = enterSleepMinutes + spindleMinutes + remSleepMinutes;
|
||||
|
||||
timestamp = bedTimeStart;
|
||||
}
|
||||
|
||||
public List<RecordInterval> getIntervals() {
|
||||
List<RecordInterval> intervals = new ArrayList<>();
|
||||
|
||||
int ts = bedTimeStart + lightSleepMinutes * 60;
|
||||
intervals.add(new RecordInterval(bedTimeStart, ts, ActivityKind.TYPE_LIGHT_SLEEP));
|
||||
intervals.add(new RecordInterval(ts, bedTimeEnd, ActivityKind.TYPE_DEEP_SLEEP));
|
||||
return intervals;
|
||||
}
|
||||
|
||||
public String toString(){
|
||||
Calendar s = GregorianCalendar.getInstance();
|
||||
s.setTimeInMillis(bedTimeStart * 1000L);
|
||||
|
||||
Calendar end = GregorianCalendar.getInstance();
|
||||
end.setTimeInMillis(bedTimeEnd * 1000L);
|
||||
|
||||
return String.format(Locale.US, "Sleep start: %s end: %s enter: %d spindles: %d rem: %d deep: %d wake: %d-%d", s.getTime(), end.getTime(), enterSleepMinutes, spindleMinutes, remSleepMinutes, deepSleepMinutes, wakeupMinutes, wakeupCount);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,569 @@
|
|||
package nodomain.freeyourgadget.gadgetbridge.service.devices.hplus;
|
||||
|
||||
/*
|
||||
* @author João Paulo Barraca <jpbarraca@gmail.com>
|
||||
*/
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.util.Log;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.List;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBException;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.hplus.HPlusConstants;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.hplus.HPlusHealthSampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HPlusHealthActivityOverlay;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HPlusHealthActivityOverlayDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HPlusHealthActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceIoThread;
|
||||
|
||||
|
||||
class HPlusHandlerThread extends GBDeviceIoThread {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(HPlusHandlerThread.class);
|
||||
|
||||
private int CURRENT_DAY_SYNC_PERIOD = 24 * 60 * 60 * 365; //Never
|
||||
private int CURRENT_DAY_SYNC_RETRY_PERIOD = 10;
|
||||
|
||||
private int SLEEP_SYNC_PERIOD = 12 * 60 * 60;
|
||||
private int SLEEP_SYNC_RETRY_PERIOD = 30;
|
||||
|
||||
private int DAY_SUMMARY_SYNC_PERIOD = 24 * 60 * 60;
|
||||
private int DAY_SUMMARY_SYNC_RETRY_PERIOD = 30;
|
||||
|
||||
private int HELLO_INTERVAL = 60;
|
||||
|
||||
private boolean mQuit = false;
|
||||
private HPlusSupport mHPlusSupport;
|
||||
|
||||
private int mLastSlotReceived = -1;
|
||||
private int mLastSlotRequested = 0;
|
||||
|
||||
private Calendar mLastSleepDayReceived = GregorianCalendar.getInstance();
|
||||
private Calendar mHelloTime = GregorianCalendar.getInstance();
|
||||
private Calendar mGetDaySlotsTime = GregorianCalendar.getInstance();
|
||||
private Calendar mGetSleepTime = GregorianCalendar.getInstance();
|
||||
private Calendar mGetDaySummaryTime = GregorianCalendar.getInstance();
|
||||
|
||||
private boolean mSlotsInitialSync = true;
|
||||
|
||||
private HPlusDataRecordRealtime prevRealTimeRecord = null;
|
||||
|
||||
private final Object waitObject = new Object();
|
||||
|
||||
List<HPlusDataRecordDaySlot> mDaySlotSamples = new ArrayList<>();
|
||||
|
||||
public HPlusHandlerThread(GBDevice gbDevice, Context context, HPlusSupport hplusSupport) {
|
||||
super(gbDevice, context);
|
||||
|
||||
mQuit = false;
|
||||
|
||||
mHPlusSupport = hplusSupport;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
mQuit = false;
|
||||
|
||||
sync();
|
||||
|
||||
long waitTime = 0;
|
||||
while (!mQuit) {
|
||||
|
||||
if (waitTime > 0) {
|
||||
synchronized (waitObject) {
|
||||
try {
|
||||
waitObject.wait(waitTime);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mQuit) {
|
||||
break;
|
||||
}
|
||||
|
||||
if(!mHPlusSupport.getDevice().isConnected()){
|
||||
quit();
|
||||
break;
|
||||
}
|
||||
|
||||
Calendar now = GregorianCalendar.getInstance();
|
||||
|
||||
if (now.compareTo(mHelloTime) > 0) {
|
||||
sendHello();
|
||||
}
|
||||
|
||||
if (now.compareTo(mGetDaySlotsTime) > 0) {
|
||||
requestNextDaySlots();
|
||||
}
|
||||
|
||||
if (now.compareTo(mGetSleepTime) > 0) {
|
||||
requestNextSleepData();
|
||||
}
|
||||
|
||||
if(now.compareTo(mGetDaySummaryTime) > 0) {
|
||||
requestDaySummaryData();
|
||||
}
|
||||
|
||||
now = GregorianCalendar.getInstance();
|
||||
waitTime = Math.min(mGetDaySummaryTime.getTimeInMillis(), Math.min(Math.min(mGetDaySlotsTime.getTimeInMillis(), mGetSleepTime.getTimeInMillis()), mHelloTime.getTimeInMillis())) - now.getTimeInMillis();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void quit() {
|
||||
mQuit = true;
|
||||
synchronized (waitObject) {
|
||||
waitObject.notify();
|
||||
}
|
||||
}
|
||||
|
||||
public void sync() {
|
||||
mGetSleepTime.setTimeInMillis(0);
|
||||
mGetDaySlotsTime.setTimeInMillis(0);
|
||||
mGetDaySummaryTime.setTimeInMillis(0);
|
||||
mLastSleepDayReceived.setTimeInMillis(0);
|
||||
|
||||
mSlotsInitialSync = true;
|
||||
mLastSlotReceived = -1;
|
||||
mLastSlotRequested = 0;
|
||||
|
||||
TransactionBuilder builder = new TransactionBuilder("startSyncDayStats");
|
||||
|
||||
builder.write(mHPlusSupport.ctrlCharacteristic, new byte[]{HPlusConstants.CMD_GET_DEVICE_ID});
|
||||
builder.wait(400);
|
||||
builder.write(mHPlusSupport.ctrlCharacteristic, new byte[]{HPlusConstants.CMD_GET_VERSION});
|
||||
builder.wait(400);
|
||||
|
||||
builder.write(mHPlusSupport.ctrlCharacteristic, new byte[]{HPlusConstants.CMD_GET_SLEEP});
|
||||
builder.wait(400);
|
||||
builder.write(mHPlusSupport.ctrlCharacteristic, new byte[]{HPlusConstants.CMD_GET_DAY_DATA});
|
||||
builder.wait(400);
|
||||
builder.write(mHPlusSupport.ctrlCharacteristic, new byte[]{HPlusConstants.CMD_GET_ACTIVE_DAY});
|
||||
builder.wait(400);
|
||||
builder.write(mHPlusSupport.ctrlCharacteristic, new byte[]{HPlusConstants.CMD_GET_CURR_DATA});
|
||||
|
||||
builder.queue(mHPlusSupport.getQueue());
|
||||
scheduleHello();
|
||||
|
||||
synchronized (waitObject) {
|
||||
waitObject.notify();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an Hello/Null Packet to keep connection
|
||||
*/
|
||||
private void sendHello() {
|
||||
TransactionBuilder builder = new TransactionBuilder("hello");
|
||||
|
||||
builder.write(mHPlusSupport.ctrlCharacteristic, HPlusConstants.CMD_ACTION_HELLO);
|
||||
builder.queue(mHPlusSupport.getQueue());
|
||||
|
||||
scheduleHello();
|
||||
|
||||
synchronized (waitObject) {
|
||||
waitObject.notify();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule an Hello Packet in the future
|
||||
*/
|
||||
public void scheduleHello(){
|
||||
mHelloTime = GregorianCalendar.getInstance();
|
||||
mHelloTime.add(Calendar.SECOND, HELLO_INTERVAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a message containing information regarding a day slot
|
||||
* A slot summarizes 10 minutes of data
|
||||
*
|
||||
* @param data the message from the device
|
||||
* @return boolean indicating success or fail
|
||||
*/
|
||||
public boolean processIncomingDaySlotData(byte[] data) {
|
||||
|
||||
HPlusDataRecordDaySlot record;
|
||||
|
||||
try{
|
||||
record = new HPlusDataRecordDaySlot(data);
|
||||
} catch(IllegalArgumentException e){
|
||||
LOG.debug((e.getMessage()));
|
||||
return false;
|
||||
}
|
||||
|
||||
//Ignore real time messages as they are still not understood
|
||||
if(!mSlotsInitialSync){
|
||||
mGetDaySlotsTime.set(Calendar.SECOND, CURRENT_DAY_SYNC_PERIOD);
|
||||
return true;
|
||||
}
|
||||
|
||||
Calendar now = GregorianCalendar.getInstance();
|
||||
int nowSlot = now.get(Calendar.HOUR_OF_DAY) * 6 + (now.get(Calendar.MINUTE) / 10);
|
||||
|
||||
//If the slot is in the future, actually it is from the previous day
|
||||
//Subtract a day of seconds
|
||||
if(record.slot >= nowSlot){
|
||||
record.timestamp -= 3600 * 24;
|
||||
}
|
||||
|
||||
//Ignore out of order messages
|
||||
if(record.slot == mLastSlotReceived + 1) {
|
||||
mLastSlotReceived = record.slot;
|
||||
}
|
||||
|
||||
if(record.slot < 143){
|
||||
mDaySlotSamples.add(record);
|
||||
}else {
|
||||
|
||||
//Sort the samples
|
||||
Collections.sort(mDaySlotSamples, new Comparator<HPlusDataRecordDaySlot>() {
|
||||
public int compare(HPlusDataRecordDaySlot one, HPlusDataRecordDaySlot other) {
|
||||
return one.timestamp - other.timestamp;
|
||||
}
|
||||
});
|
||||
|
||||
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
||||
HPlusHealthSampleProvider provider = new HPlusHealthSampleProvider(getDevice(), dbHandler.getDaoSession());
|
||||
List<HPlusHealthActivitySample> samples = new ArrayList<>();
|
||||
|
||||
for(HPlusDataRecordDaySlot storedRecord : mDaySlotSamples) {
|
||||
HPlusHealthActivitySample sample = createSample(dbHandler, storedRecord.timestamp);
|
||||
|
||||
sample.setRawHPlusHealthData(record.getRawData());
|
||||
sample.setSteps(record.steps);
|
||||
sample.setHeartRate(record.heartRate);
|
||||
sample.setRawKind(record.type);
|
||||
|
||||
sample.setProvider(provider);
|
||||
samples.add(sample);
|
||||
}
|
||||
|
||||
provider.getSampleDao().insertOrReplaceInTx(samples);
|
||||
mDaySlotSamples.clear();
|
||||
|
||||
} catch (GBException ex) {
|
||||
LOG.debug((ex.getMessage()));
|
||||
} catch (Exception ex) {
|
||||
LOG.debug(ex.getMessage());
|
||||
}
|
||||
}
|
||||
//Still fetching ring buffer. Request the next slots
|
||||
if (record.slot == mLastSlotRequested) {
|
||||
mGetDaySlotsTime.clear();
|
||||
synchronized (waitObject) {
|
||||
waitObject.notify();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Process sleep data from the device
|
||||
* Devices send a single sleep message for each sleep period
|
||||
* This message contains the duration of the sub-intervals (rem, deep, etc...)
|
||||
*
|
||||
* @param data the message from the device
|
||||
* @return boolean indicating success or fail
|
||||
*/
|
||||
public boolean processIncomingSleepData(byte[] data){
|
||||
HPlusDataRecordSleep record;
|
||||
|
||||
try{
|
||||
record = new HPlusDataRecordSleep(data);
|
||||
} catch(IllegalArgumentException e){
|
||||
LOG.debug((e.getMessage()));
|
||||
return false;
|
||||
}
|
||||
|
||||
mLastSleepDayReceived.setTimeInMillis(record.bedTimeStart * 1000L);
|
||||
|
||||
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
||||
DaoSession session = dbHandler.getDaoSession();
|
||||
Long userId = DBHelper.getUser(session).getId();
|
||||
Long deviceId = DBHelper.getDevice(getDevice(), session).getId();
|
||||
|
||||
HPlusHealthActivityOverlayDao overlayDao = session.getHPlusHealthActivityOverlayDao();
|
||||
HPlusHealthSampleProvider provider = new HPlusHealthSampleProvider(getDevice(), dbHandler.getDaoSession());
|
||||
|
||||
//Get the individual Sleep overlays and insert them
|
||||
List<HPlusHealthActivityOverlay> overlayList = new ArrayList<>();
|
||||
List<HPlusDataRecord.RecordInterval> intervals = record.getIntervals();
|
||||
|
||||
for(HPlusDataRecord.RecordInterval interval : intervals){
|
||||
overlayList.add(new HPlusHealthActivityOverlay(interval.timestampFrom, interval.timestampTo, interval.activityKind, deviceId, userId, null));
|
||||
}
|
||||
|
||||
overlayDao.insertOrReplaceInTx(overlayList);
|
||||
|
||||
//Store the data
|
||||
HPlusHealthActivitySample sample = createSample(dbHandler, record.timestamp);
|
||||
sample.setRawHPlusHealthData(record.getRawData());
|
||||
sample.setRawKind(record.activityKind);
|
||||
|
||||
sample.setProvider(provider);
|
||||
|
||||
provider.addGBActivitySample(sample);
|
||||
} catch (Exception ex) {
|
||||
LOG.debug(ex.getMessage());
|
||||
}
|
||||
|
||||
mGetSleepTime = GregorianCalendar.getInstance();
|
||||
mGetSleepTime.add(GregorianCalendar.SECOND, SLEEP_SYNC_PERIOD);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a message containing real time information
|
||||
*
|
||||
* @param data the message from the device
|
||||
* @return boolean indicating success or fail
|
||||
*/
|
||||
public boolean processRealtimeStats(byte[] data) {
|
||||
HPlusDataRecordRealtime record;
|
||||
|
||||
try{
|
||||
record = new HPlusDataRecordRealtime(data);
|
||||
} catch(IllegalArgumentException e){
|
||||
LOG.debug((e.getMessage()));
|
||||
return false;
|
||||
}
|
||||
//Skip duplicated messages as the device seems to send the same record multiple times
|
||||
//This can be used to detect the user is moving (not sleeping)
|
||||
if(prevRealTimeRecord != null && record.same(prevRealTimeRecord))
|
||||
return true;
|
||||
|
||||
prevRealTimeRecord = record;
|
||||
|
||||
getDevice().setBatteryLevel(record.battery);
|
||||
|
||||
//Skip when measuring heart rate
|
||||
//Calories and Distance are updated and these values will be lost.
|
||||
//Because a message with a valid Heart Rate will be provided, this loss very limited
|
||||
if(record.heartRate == ActivityKind.TYPE_NOT_MEASURED) {
|
||||
getDevice().setFirmwareVersion2("---");
|
||||
getDevice().sendDeviceUpdateIntent(getContext());
|
||||
}else {
|
||||
getDevice().setFirmwareVersion2("" + record.heartRate);
|
||||
getDevice().sendDeviceUpdateIntent(getContext());
|
||||
}
|
||||
|
||||
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
||||
HPlusHealthSampleProvider provider = new HPlusHealthSampleProvider(getDevice(), dbHandler.getDaoSession());
|
||||
|
||||
HPlusHealthActivitySample sample = createSample(dbHandler, record.timestamp);
|
||||
sample.setRawKind(record.type);
|
||||
sample.setRawIntensity(record.intensity);
|
||||
sample.setHeartRate(record.heartRate);
|
||||
sample.setDistance(record.distance);
|
||||
sample.setCalories(record.calories);
|
||||
sample.setSteps(record.steps);
|
||||
|
||||
sample.setRawHPlusHealthData(record.getRawData());
|
||||
sample.setProvider(provider);
|
||||
|
||||
sample.setSteps(sample.getSteps() - prevRealTimeRecord.steps);
|
||||
|
||||
Intent intent = new Intent(DeviceService.ACTION_REALTIME_SAMPLES)
|
||||
.putExtra(DeviceService.EXTRA_REALTIME_SAMPLE, sample)
|
||||
.putExtra(DeviceService.EXTRA_TIMESTAMP, System.currentTimeMillis());
|
||||
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
|
||||
|
||||
provider.addGBActivitySample(sample);
|
||||
|
||||
//TODO: Handle Active Time. With Overlay?
|
||||
} catch (GBException ex) {
|
||||
LOG.debug((ex.getMessage()));
|
||||
} catch (Exception ex) {
|
||||
LOG.debug(ex.getMessage());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a day summary message
|
||||
* This message includes aggregates regarding an entire day
|
||||
*
|
||||
* @param data the message from the device
|
||||
* @return boolean indicating success or fail
|
||||
*/
|
||||
public boolean processDaySummary(byte[] data) {
|
||||
HPlusDataRecordDaySummary record;
|
||||
|
||||
try{
|
||||
record = new HPlusDataRecordDaySummary(data);
|
||||
} catch(IllegalArgumentException e){
|
||||
LOG.debug((e.getMessage()));
|
||||
return false;
|
||||
}
|
||||
|
||||
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
||||
HPlusHealthSampleProvider provider = new HPlusHealthSampleProvider(getDevice(), dbHandler.getDaoSession());
|
||||
|
||||
HPlusHealthActivitySample sample = createSample(dbHandler, record.timestamp);
|
||||
|
||||
sample.setRawKind(record.type);
|
||||
sample.setSteps(record.steps);
|
||||
sample.setDistance(record.distance);
|
||||
sample.setCalories(record.calories);
|
||||
sample.setDistance(record.distance);
|
||||
sample.setHeartRate((record.maxHeartRate - record.minHeartRate) / 2); //TODO: Find an alternative approach for Day Summary Heart Rate
|
||||
sample.setRawHPlusHealthData(record.getRawData());
|
||||
|
||||
sample.setProvider(provider);
|
||||
provider.addGBActivitySample(sample);
|
||||
} catch (GBException ex) {
|
||||
LOG.debug((ex.getMessage()));
|
||||
} catch (Exception ex) {
|
||||
LOG.debug(ex.getMessage());
|
||||
}
|
||||
|
||||
mGetDaySummaryTime = GregorianCalendar.getInstance();
|
||||
mGetDaySummaryTime.add(Calendar.SECOND, DAY_SUMMARY_SYNC_PERIOD);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a message containing information regarding firmware version
|
||||
*
|
||||
* @param data the message from the device
|
||||
* @return boolean indicating success or fail
|
||||
*/
|
||||
public boolean processVersion(byte[] data) {
|
||||
int major = data[2] & 0xFF;
|
||||
int minor = data[1] & 0xFF;
|
||||
|
||||
getDevice().setFirmwareVersion(major + "." + minor);
|
||||
|
||||
getDevice().sendDeviceUpdateIntent(getContext());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Issue a message requesting the next batch of sleep data
|
||||
*/
|
||||
private void requestNextSleepData() {
|
||||
TransactionBuilder builder = new TransactionBuilder("requestSleepStats");
|
||||
builder.write(mHPlusSupport.ctrlCharacteristic, new byte[]{HPlusConstants.CMD_GET_SLEEP});
|
||||
builder.queue(mHPlusSupport.getQueue());
|
||||
|
||||
|
||||
mGetSleepTime = GregorianCalendar.getInstance();
|
||||
mGetSleepTime.add(GregorianCalendar.SECOND, SLEEP_SYNC_RETRY_PERIOD);
|
||||
}
|
||||
|
||||
/**
|
||||
* Issue a message requesting the next set of slots
|
||||
* The process will sync 1h at a time until the device is in sync
|
||||
* Then it will request samples until the end of the day in order to minimize data loss
|
||||
* Messages will be provided every 10 minutes after they are available
|
||||
*/
|
||||
private void requestNextDaySlots() {
|
||||
|
||||
Calendar now = GregorianCalendar.getInstance();
|
||||
int currentSlot = now.get(Calendar.HOUR_OF_DAY) * 6 + now.get(Calendar.MINUTE) / 10;
|
||||
|
||||
//Finished dumping the entire ring buffer
|
||||
//Sync to current time
|
||||
mGetDaySlotsTime = now;
|
||||
|
||||
if(mSlotsInitialSync) {
|
||||
if(mLastSlotReceived == 143) {
|
||||
mSlotsInitialSync = false;
|
||||
mGetDaySlotsTime.set(Calendar.SECOND, CURRENT_DAY_SYNC_PERIOD); //Sync complete. Delay timer forever
|
||||
mLastSlotReceived = -1;
|
||||
mLastSlotRequested = mLastSlotReceived + 1;
|
||||
return;
|
||||
}else {
|
||||
mGetDaySlotsTime.add(Calendar.SECOND, CURRENT_DAY_SYNC_RETRY_PERIOD);
|
||||
}
|
||||
}else{
|
||||
//Sync complete. Delay timer forever
|
||||
mGetDaySlotsTime.set(Calendar.SECOND, CURRENT_DAY_SYNC_PERIOD);
|
||||
return;
|
||||
}
|
||||
|
||||
if(mLastSlotReceived == 143)
|
||||
mLastSlotReceived = -1;
|
||||
|
||||
byte hour = (byte) ((mLastSlotReceived + 1)/ 6);
|
||||
byte minute = (byte) (((mLastSlotReceived + 1) % 6) * 10);
|
||||
|
||||
byte nextHour = hour;
|
||||
byte nextMinute = 59;
|
||||
|
||||
mLastSlotRequested = nextHour * 6 + (nextMinute / 10);
|
||||
|
||||
byte[] msg = new byte[]{HPlusConstants.CMD_GET_ACTIVE_DAY, hour, minute, nextHour, nextMinute};
|
||||
|
||||
TransactionBuilder builder = new TransactionBuilder("getNextDaySlot");
|
||||
builder.write(mHPlusSupport.ctrlCharacteristic, msg);
|
||||
builder.queue(mHPlusSupport.getQueue());
|
||||
}
|
||||
/**
|
||||
* Request a batch of data with the summary of the previous days
|
||||
*/
|
||||
public void requestDaySummaryData(){
|
||||
TransactionBuilder builder = new TransactionBuilder("startSyncDaySummary");
|
||||
builder.write(mHPlusSupport.ctrlCharacteristic, new byte[]{HPlusConstants.CMD_GET_DAY_DATA});
|
||||
builder.queue(mHPlusSupport.getQueue());
|
||||
|
||||
mGetDaySummaryTime = GregorianCalendar.getInstance();
|
||||
mGetDaySummaryTime.add(Calendar.SECOND, DAY_SUMMARY_SYNC_RETRY_PERIOD);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to create a sample
|
||||
* @param dbHandler The database handler
|
||||
* @param timestamp The sample timestamp
|
||||
* @return The sample just created
|
||||
*/
|
||||
private HPlusHealthActivitySample createSample(DBHandler dbHandler, int timestamp){
|
||||
Long userId = DBHelper.getUser(dbHandler.getDaoSession()).getId();
|
||||
Long deviceId = DBHelper.getDevice(getDevice(), dbHandler.getDaoSession()).getId();
|
||||
HPlusHealthActivitySample sample = new HPlusHealthActivitySample(
|
||||
timestamp, // ts
|
||||
deviceId, userId, // User id
|
||||
null, // Raw Data
|
||||
ActivityKind.TYPE_UNKNOWN,
|
||||
0, // Intensity
|
||||
ActivitySample.NOT_MEASURED, // Steps
|
||||
ActivitySample.NOT_MEASURED, // HR
|
||||
ActivitySample.NOT_MEASURED, // Distance
|
||||
ActivitySample.NOT_MEASURED // Calories
|
||||
);
|
||||
|
||||
return sample;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,86 +0,0 @@
|
|||
package nodomain.freeyourgadget.gadgetbridge.service.devices.hplus;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Calendar;
|
||||
|
||||
|
||||
|
||||
public class HPlusSleepRecord {
|
||||
private long bedTimeStart;
|
||||
private long bedTimeEnd;
|
||||
private int deepSleepSeconds;
|
||||
private int spindleSeconds;
|
||||
private int remSleepSeconds;
|
||||
private int wakeupTime;
|
||||
private int wakeupCount;
|
||||
private int enterSleepSeconds;
|
||||
private byte[] rawData;
|
||||
|
||||
HPlusSleepRecord(byte[] data) {
|
||||
rawData = data;
|
||||
int year = data[2] * 256 + data[1];
|
||||
int month = data[3];
|
||||
int day = data[4];
|
||||
|
||||
enterSleepSeconds = data[6] * 256 + data[5];
|
||||
spindleSeconds = data[8] * 256 + data[7];
|
||||
deepSleepSeconds = data[10] * 256 + data[9];
|
||||
remSleepSeconds = data[12] * 256 + data[11];
|
||||
wakeupTime = data[14] * 256 + data[13];
|
||||
wakeupCount = data[16] * 256 + data[15];
|
||||
int hour = data[17];
|
||||
int minute = data[18];
|
||||
|
||||
Calendar c = Calendar.getInstance();
|
||||
c.set(Calendar.YEAR, year);
|
||||
c.set(Calendar.MONTH, month);
|
||||
c.set(Calendar.DAY_OF_MONTH, day);
|
||||
c.set(Calendar.HOUR, hour);
|
||||
c.set(Calendar.MINUTE, minute);
|
||||
c.set(Calendar.SECOND, 0);
|
||||
c.set(Calendar.MILLISECOND, 0);
|
||||
|
||||
bedTimeStart = (c.getTimeInMillis() / 1000L);
|
||||
bedTimeEnd = bedTimeStart + enterSleepSeconds + spindleSeconds + deepSleepSeconds + remSleepSeconds + wakeupTime;
|
||||
}
|
||||
|
||||
byte[] getRawData() {
|
||||
|
||||
return rawData;
|
||||
}
|
||||
|
||||
public long getBedTimeStart() {
|
||||
return bedTimeStart;
|
||||
}
|
||||
|
||||
public long getBedTimeEnd() {
|
||||
return bedTimeEnd;
|
||||
}
|
||||
|
||||
public int getDeepSleepSeconds() {
|
||||
return deepSleepSeconds;
|
||||
}
|
||||
|
||||
public int getSpindleSeconds() {
|
||||
return spindleSeconds;
|
||||
}
|
||||
|
||||
public int getRemSleepSeconds() {
|
||||
return remSleepSeconds;
|
||||
}
|
||||
|
||||
public int getWakeupTime() {
|
||||
return wakeupTime;
|
||||
}
|
||||
|
||||
public int getWakeupCount() {
|
||||
return wakeupCount;
|
||||
}
|
||||
|
||||
public int getEnterSleepSeconds() {
|
||||
return enterSleepSeconds;
|
||||
}
|
||||
|
||||
}
|
|
@ -4,7 +4,6 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.hplus;
|
|||
* @author João Paulo Barraca <jpbarraca@gmail.com>
|
||||
*/
|
||||
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothGatt;
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.content.BroadcastReceiver;
|
||||
|
@ -21,24 +20,12 @@ import org.slf4j.LoggerFactory;
|
|||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.UUID;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBException;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.hplus.HPlusConstants;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.hplus.HPlusCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.hplus.HPlusSampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HPlusHealthActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.User;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
|
||||
|
@ -48,10 +35,11 @@ import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
|
|||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.AbstractBleProfile;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfo;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfoProfile;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
|
@ -59,12 +47,10 @@ import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
|||
public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(HPlusSupport.class);
|
||||
|
||||
private BluetoothGattCharacteristic ctrlCharacteristic = null;
|
||||
private BluetoothGattCharacteristic measureCharacteristic = null;
|
||||
public BluetoothGattCharacteristic ctrlCharacteristic = null;
|
||||
public BluetoothGattCharacteristic measureCharacteristic = null;
|
||||
|
||||
private byte[] lastDataStats = null;
|
||||
|
||||
private final GBDeviceEventVersionInfo versionCmd = new GBDeviceEventVersionInfo();
|
||||
private HPlusHandlerThread syncHelper;
|
||||
|
||||
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
|
@ -86,65 +72,65 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
|||
IntentFilter intentFilter = new IntentFilter();
|
||||
|
||||
broadcastManager.registerReceiver(mReceiver, intentFilter);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
LOG.debug("Dispose");
|
||||
LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(getContext());
|
||||
broadcastManager.unregisterReceiver(mReceiver);
|
||||
|
||||
close();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
|
||||
LOG.debug("Initializing");
|
||||
|
||||
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
|
||||
|
||||
measureCharacteristic = getCharacteristic(HPlusConstants.UUID_CHARACTERISTIC_MEASURE);
|
||||
ctrlCharacteristic = getCharacteristic(HPlusConstants.UUID_CHARACTERISTIC_CONTROL);
|
||||
|
||||
getDevice().setFirmwareVersion("N/A");
|
||||
getDevice().setFirmwareVersion2("N/A");
|
||||
|
||||
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
|
||||
|
||||
getDevice().setFirmwareVersion("0");
|
||||
getDevice().setFirmwareVersion2("0");
|
||||
syncHelper = new HPlusHandlerThread(getDevice(), getContext(), this);
|
||||
|
||||
//Initialize device
|
||||
setInitValues(builder);
|
||||
setCurrentDate(builder);
|
||||
setCurrentTime(builder);
|
||||
syncPreferences(builder);
|
||||
sendUserInfo(builder); //Sync preferences
|
||||
setSIT(builder); //Sync SIT Interval
|
||||
setCurrentDate(builder); // Sync Current Date
|
||||
setCurrentTime(builder); // Sync Current Time
|
||||
|
||||
requestDeviceInfo(builder);
|
||||
|
||||
setInitialized(builder);
|
||||
|
||||
syncHelper.start();
|
||||
|
||||
builder.notify(getCharacteristic(HPlusConstants.UUID_CHARACTERISTIC_MEASURE), true);
|
||||
|
||||
builder.setGattCallback(this);
|
||||
builder.notify(measureCharacteristic, true);
|
||||
|
||||
setInitialized(builder);
|
||||
return builder;
|
||||
}
|
||||
|
||||
private HPlusSupport setInitValues(TransactionBuilder builder) {
|
||||
LOG.debug("Set Init Values");
|
||||
|
||||
builder.write(ctrlCharacteristic, HPlusConstants.COMMAND_SET_INIT1);
|
||||
builder.write(ctrlCharacteristic, HPlusConstants.COMMAND_SET_INIT2);
|
||||
return this;
|
||||
}
|
||||
|
||||
private HPlusSupport sendUserInfo(TransactionBuilder builder) {
|
||||
builder.write(ctrlCharacteristic, HPlusConstants.COMMAND_SET_PREF_START);
|
||||
builder.write(ctrlCharacteristic, HPlusConstants.COMMAND_SET_PREF_START1);
|
||||
builder.write(ctrlCharacteristic, HPlusConstants.CMD_SET_PREF_START);
|
||||
builder.write(ctrlCharacteristic, HPlusConstants.CMD_SET_PREF_START1);
|
||||
|
||||
syncPreferences(builder);
|
||||
|
||||
builder.write(ctrlCharacteristic, new byte[]{HPlusConstants.COMMAND_SET_CONF_SAVE});
|
||||
builder.write(ctrlCharacteristic, new byte[]{HPlusConstants.COMMAND_SET_CONF_END});
|
||||
builder.write(ctrlCharacteristic, new byte[]{HPlusConstants.CMD_SET_CONF_END});
|
||||
return this;
|
||||
}
|
||||
|
||||
private HPlusSupport syncPreferences(TransactionBuilder transaction) {
|
||||
LOG.info("Attempting to sync preferences...");
|
||||
|
||||
byte sex = HPlusCoordinator.getUserSex(getDevice().getAddress());
|
||||
byte gender = HPlusCoordinator.getUserGender(getDevice().getAddress());
|
||||
byte age = HPlusCoordinator.getUserAge(getDevice().getAddress());
|
||||
byte bodyHeight = HPlusCoordinator.getUserHeight(getDevice().getAddress());
|
||||
byte bodyWeight = HPlusCoordinator.getUserWeight(getDevice().getAddress());
|
||||
|
@ -168,8 +154,8 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
|||
byte timemode = HPlusCoordinator.getTimeMode((getDevice().getAddress()));
|
||||
|
||||
transaction.write(ctrlCharacteristic, new byte[]{
|
||||
HPlusConstants.COMMAND_SET_PREF_COUNTRY,
|
||||
sex,
|
||||
HPlusConstants.CMD_SET_PREFS,
|
||||
gender,
|
||||
age,
|
||||
bodyHeight,
|
||||
bodyWeight,
|
||||
|
@ -183,20 +169,22 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
|||
social,
|
||||
allDayHeart,
|
||||
wrist,
|
||||
0,
|
||||
alertTimeHour,
|
||||
alertTimeMinute,
|
||||
unit,
|
||||
timemode
|
||||
});
|
||||
|
||||
setAllDayHeart(transaction);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private HPlusSupport setCountry(TransactionBuilder transaction) {
|
||||
LOG.info("Attempting to set country...");
|
||||
|
||||
private HPlusSupport setLanguage(TransactionBuilder transaction) {
|
||||
byte value = HPlusCoordinator.getCountry(getDevice().getAddress());
|
||||
transaction.write(ctrlCharacteristic, new byte[]{
|
||||
HPlusConstants.COMMAND_SET_PREF_COUNTRY,
|
||||
HPlusConstants.CMD_SET_LANGUAGE,
|
||||
value
|
||||
});
|
||||
return this;
|
||||
|
@ -204,38 +192,31 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
|||
|
||||
|
||||
private HPlusSupport setTimeMode(TransactionBuilder transaction) {
|
||||
LOG.info("Attempting to set Time Mode...");
|
||||
|
||||
byte value = HPlusCoordinator.getTimeMode(getDevice().getAddress());
|
||||
transaction.write(ctrlCharacteristic, new byte[]{
|
||||
HPlusConstants.COMMAND_SET_PREF_TIMEMODE,
|
||||
HPlusConstants.CMD_SET_TIMEMODE,
|
||||
value
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
private HPlusSupport setUnit(TransactionBuilder transaction) {
|
||||
LOG.info("Attempting to set Units...");
|
||||
|
||||
|
||||
byte value = HPlusCoordinator.getUnit(getDevice().getAddress());
|
||||
transaction.write(ctrlCharacteristic, new byte[]{
|
||||
HPlusConstants.COMMAND_SET_PREF_UNIT,
|
||||
HPlusConstants.CMD_SET_UNITS,
|
||||
value
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
private HPlusSupport setCurrentDate(TransactionBuilder transaction) {
|
||||
LOG.info("Attempting to set Current Date...");
|
||||
|
||||
Calendar c = Calendar.getInstance();
|
||||
Calendar c = GregorianCalendar.getInstance();
|
||||
int year = c.get(Calendar.YEAR) - 1900;
|
||||
int month = c.get(Calendar.MONTH);
|
||||
int day = c.get(Calendar.DAY_OF_MONTH);
|
||||
|
||||
transaction.write(ctrlCharacteristic, new byte[]{
|
||||
HPlusConstants.COMMAND_SET_PREF_DATE,
|
||||
HPlusConstants.CMD_SET_DATE,
|
||||
(byte) ((year / 256) & 0xff),
|
||||
(byte) (year % 256),
|
||||
(byte) (month + 1),
|
||||
|
@ -246,12 +227,10 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
|||
}
|
||||
|
||||
private HPlusSupport setCurrentTime(TransactionBuilder transaction) {
|
||||
LOG.info("Attempting to set Current Time...");
|
||||
|
||||
Calendar c = Calendar.getInstance();
|
||||
Calendar c = GregorianCalendar.getInstance();
|
||||
|
||||
transaction.write(ctrlCharacteristic, new byte[]{
|
||||
HPlusConstants.COMMAND_SET_PREF_TIME,
|
||||
HPlusConstants.CMD_SET_TIME,
|
||||
(byte) c.get(Calendar.HOUR_OF_DAY),
|
||||
(byte) c.get(Calendar.MINUTE),
|
||||
(byte) c.get(Calendar.SECOND)
|
||||
|
@ -262,12 +241,10 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
|||
|
||||
|
||||
private HPlusSupport setDayOfWeek(TransactionBuilder transaction) {
|
||||
LOG.info("Attempting to set Day Of Week...");
|
||||
|
||||
Calendar c = Calendar.getInstance();
|
||||
Calendar c = GregorianCalendar.getInstance();
|
||||
|
||||
transaction.write(ctrlCharacteristic, new byte[]{
|
||||
HPlusConstants.COMMAND_SET_PREF_WEEK,
|
||||
HPlusConstants.CMD_SET_WEEK,
|
||||
(byte) c.get(Calendar.DAY_OF_WEEK)
|
||||
});
|
||||
return this;
|
||||
|
@ -275,15 +252,13 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
|||
|
||||
|
||||
private HPlusSupport setSIT(TransactionBuilder transaction) {
|
||||
LOG.info("Attempting to set SIT...");
|
||||
|
||||
int startTime = HPlusCoordinator.getSITStartTime(getDevice().getAddress());
|
||||
int endTime = HPlusCoordinator.getSITEndTime(getDevice().getAddress());
|
||||
|
||||
Calendar now = Calendar.getInstance();
|
||||
Calendar now = GregorianCalendar.getInstance();
|
||||
|
||||
transaction.write(ctrlCharacteristic, new byte[]{
|
||||
HPlusConstants.COMMAND_SET_SIT_INTERVAL,
|
||||
HPlusConstants.CMD_SET_SIT_INTERVAL,
|
||||
(byte) ((startTime / 256) & 0xff),
|
||||
(byte) (startTime % 256),
|
||||
(byte) ((endTime / 256) & 0xff),
|
||||
|
@ -294,7 +269,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
|||
(byte) (now.get(Calendar.YEAR) % 256),
|
||||
(byte) (now.get(Calendar.MONTH) + 1),
|
||||
(byte) (now.get(Calendar.DAY_OF_MONTH)),
|
||||
(byte) (now.get(Calendar.HOUR)),
|
||||
(byte) (now.get(Calendar.HOUR_OF_DAY)),
|
||||
(byte) (now.get(Calendar.MINUTE)),
|
||||
(byte) (now.get(Calendar.SECOND)),
|
||||
0,
|
||||
|
@ -307,11 +282,9 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
|||
}
|
||||
|
||||
private HPlusSupport setWeight(TransactionBuilder transaction) {
|
||||
LOG.info("Attempting to set Weight...");
|
||||
|
||||
byte value = HPlusCoordinator.getUserWeight(getDevice().getAddress());
|
||||
transaction.write(ctrlCharacteristic, new byte[]{
|
||||
HPlusConstants.COMMAND_SET_PREF_WEIGHT,
|
||||
HPlusConstants.CMD_SET_WEIGHT,
|
||||
value
|
||||
|
||||
});
|
||||
|
@ -319,11 +292,9 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
|||
}
|
||||
|
||||
private HPlusSupport setHeight(TransactionBuilder transaction) {
|
||||
LOG.info("Attempting to set Height...");
|
||||
|
||||
byte value = HPlusCoordinator.getUserHeight(getDevice().getAddress());
|
||||
transaction.write(ctrlCharacteristic, new byte[]{
|
||||
HPlusConstants.COMMAND_SET_PREF_HEIGHT,
|
||||
HPlusConstants.CMD_HEIGHT,
|
||||
value
|
||||
|
||||
});
|
||||
|
@ -332,23 +303,19 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
|||
|
||||
|
||||
private HPlusSupport setAge(TransactionBuilder transaction) {
|
||||
LOG.info("Attempting to set Age...");
|
||||
|
||||
byte value = HPlusCoordinator.getUserAge(getDevice().getAddress());
|
||||
transaction.write(ctrlCharacteristic, new byte[]{
|
||||
HPlusConstants.COMMAND_SET_PREF_AGE,
|
||||
HPlusConstants.CMD_SET_AGE,
|
||||
value
|
||||
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
private HPlusSupport setSex(TransactionBuilder transaction) {
|
||||
LOG.info("Attempting to set Sex...");
|
||||
|
||||
byte value = HPlusCoordinator.getUserSex(getDevice().getAddress());
|
||||
private HPlusSupport setGender(TransactionBuilder transaction) {
|
||||
byte value = HPlusCoordinator.getUserGender(getDevice().getAddress());
|
||||
transaction.write(ctrlCharacteristic, new byte[]{
|
||||
HPlusConstants.COMMAND_SET_PREF_SEX,
|
||||
HPlusConstants.CMD_SET_GENDER,
|
||||
value
|
||||
|
||||
});
|
||||
|
@ -357,11 +324,9 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
|||
|
||||
|
||||
private HPlusSupport setGoal(TransactionBuilder transaction) {
|
||||
LOG.info("Attempting to set Sex...");
|
||||
|
||||
int value = HPlusCoordinator.getGoal(getDevice().getAddress());
|
||||
transaction.write(ctrlCharacteristic, new byte[]{
|
||||
HPlusConstants.COMMAND_SET_PREF_GOAL,
|
||||
HPlusConstants.CMD_SET_GOAL,
|
||||
(byte) ((value / 256) & 0xff),
|
||||
(byte) (value % 256)
|
||||
|
||||
|
@ -371,11 +336,9 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
|||
|
||||
|
||||
private HPlusSupport setScreenTime(TransactionBuilder transaction) {
|
||||
LOG.info("Attempting to set Screentime...");
|
||||
|
||||
byte value = HPlusCoordinator.getScreenTime(getDevice().getAddress());
|
||||
transaction.write(ctrlCharacteristic, new byte[]{
|
||||
HPlusConstants.COMMAND_SET_PREF_SCREENTIME,
|
||||
HPlusConstants.CMD_SET_SCREENTIME,
|
||||
value
|
||||
|
||||
});
|
||||
|
@ -383,41 +346,60 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
|||
}
|
||||
|
||||
private HPlusSupport setAllDayHeart(TransactionBuilder transaction) {
|
||||
LOG.info("Attempting to set All Day HR...");
|
||||
|
||||
byte value = HPlusCoordinator.getAllDayHR(getDevice().getAddress());
|
||||
byte value = HPlusCoordinator.getHRState(getDevice().getAddress());
|
||||
|
||||
transaction.write(ctrlCharacteristic, new byte[]{
|
||||
HPlusConstants.COMMAND_SET_PREF_ALLDAYHR,
|
||||
HPlusConstants.CMD_SET_HEARTRATE_STATE,
|
||||
value
|
||||
});
|
||||
|
||||
|
||||
value = HPlusCoordinator.getAllDayHR(getDevice().getAddress());
|
||||
|
||||
transaction.write(ctrlCharacteristic, new byte[]{
|
||||
HPlusConstants.CMD_SET_ALLDAY_HRM,
|
||||
value
|
||||
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
private HPlusSupport setAlarm(TransactionBuilder transaction) {
|
||||
LOG.info("Attempting to set Alarm...");
|
||||
//TODO: Find how to set alarms
|
||||
private HPlusSupport setAlarm(TransactionBuilder transaction, Calendar t) {
|
||||
|
||||
transaction.write(ctrlCharacteristic, new byte[]{HPlusConstants.CMD_SET_ALARM,
|
||||
(byte) (t.get(Calendar.YEAR) / 256),
|
||||
(byte) (t.get(Calendar.YEAR) % 256),
|
||||
(byte) (t.get(Calendar.MONTH) + 1),
|
||||
(byte) t.get(Calendar.HOUR_OF_DAY),
|
||||
(byte) t.get(Calendar.MINUTE),
|
||||
(byte) t.get(Calendar.SECOND)});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private HPlusSupport setBlood(TransactionBuilder transaction) {
|
||||
LOG.info("Attempting to set Blood...");
|
||||
//TODO: Find what blood means for the band
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
private HPlusSupport setFindMe(TransactionBuilder transaction) {
|
||||
LOG.info("Attempting to set Findme...");
|
||||
private HPlusSupport setFindMe(TransactionBuilder transaction, boolean state) {
|
||||
//TODO: Find how this works
|
||||
|
||||
byte[] msg = new byte[2];
|
||||
msg[0] = HPlusConstants.CMD_SET_FINDME;
|
||||
|
||||
if (state)
|
||||
msg[1] = HPlusConstants.ARG_FINDME_ON;
|
||||
else
|
||||
msg[1] = HPlusConstants.ARG_FINDME_OFF;
|
||||
|
||||
transaction.write(ctrlCharacteristic, msg);
|
||||
return this;
|
||||
}
|
||||
|
||||
private HPlusSupport requestDeviceInfo(TransactionBuilder builder) {
|
||||
LOG.debug("Requesting Device Info!");
|
||||
BluetoothGattCharacteristic deviceName = getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_GAP_DEVICE_NAME);
|
||||
builder.read(deviceName);
|
||||
// HPlus devices seem to report some information in an alternative manner
|
||||
builder.write(ctrlCharacteristic, new byte[]{HPlusConstants.CMD_GET_DEVICE_ID});
|
||||
builder.write(ctrlCharacteristic, new byte[]{HPlusConstants.CMD_GET_VERSION});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -433,18 +415,19 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
|||
|
||||
@Override
|
||||
public void pair() {
|
||||
|
||||
LOG.debug("Pair");
|
||||
}
|
||||
|
||||
private void handleDeviceInfo(nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfo info) {
|
||||
private void handleDeviceInfo(DeviceInfo info) {
|
||||
LOG.warn("Device info: " + info);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNotification(NotificationSpec notificationSpec) {
|
||||
LOG.debug("Got Notification");
|
||||
//TODO: Show different notifications acccording to source as Band supports this
|
||||
showText(notificationSpec.body);
|
||||
//TODO: Show different notifications according to source as Band supports this
|
||||
//LOG.debug("OnNotification: Title: "+notificationSpec.title+" Body: "+notificationSpec.body+" Source: "+notificationSpec.sourceName+" Sender: "+notificationSpec.sender+" Subject: "+notificationSpec.subject);
|
||||
showText(notificationSpec.title, notificationSpec.body);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -452,17 +435,36 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
|||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onSetTime() {
|
||||
TransactionBuilder builder = new TransactionBuilder("time");
|
||||
|
||||
setCurrentDate(builder);
|
||||
setCurrentTime(builder);
|
||||
|
||||
builder.queue(getQueue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetAlarms(ArrayList<? extends Alarm> alarms) {
|
||||
if (alarms.size() == 0)
|
||||
return;
|
||||
|
||||
for (Alarm alarm : alarms) {
|
||||
|
||||
if (!alarm.isEnabled())
|
||||
continue;
|
||||
|
||||
if (alarm.isSmartWakeup()) //Not available
|
||||
continue;
|
||||
|
||||
Calendar t = alarm.getAlarmCal();
|
||||
TransactionBuilder builder = new TransactionBuilder("alarm");
|
||||
setAlarm(builder, t);
|
||||
builder.queue(getQueue());
|
||||
|
||||
return; //Only first alarm
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -529,78 +531,69 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
|||
|
||||
@Override
|
||||
public void onFetchActivityData() {
|
||||
|
||||
if (syncHelper != null)
|
||||
syncHelper.sync();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReboot() {
|
||||
getQueue().clear();
|
||||
|
||||
TransactionBuilder builder = new TransactionBuilder("Shutdown");
|
||||
builder.write(ctrlCharacteristic, new byte[]{HPlusConstants.CMD_SHUTDOWN, HPlusConstants.ARG_SHUTDOWN_EN});
|
||||
builder.queue(getQueue());
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHeartRateTest() {
|
||||
LOG.debug("On HeartRateTest");
|
||||
|
||||
getQueue().clear();
|
||||
|
||||
TransactionBuilder builder = new TransactionBuilder("HeartRateTest");
|
||||
|
||||
builder.write(ctrlCharacteristic, new byte[]{HPlusConstants.COMMAND_SET_PREF_ALLDAYHR, 0x10}); //Set Real Time... ?
|
||||
builder.write(ctrlCharacteristic, new byte[]{HPlusConstants.CMD_SET_HEARTRATE_STATE, HPlusConstants.ARG_HEARTRATE_MEASURE_ON}); //Set Real Time... ?
|
||||
builder.queue(getQueue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnableRealtimeHeartRateMeasurement(boolean enable) {
|
||||
LOG.debug("Set Real Time HR Measurement: " + enable);
|
||||
|
||||
getQueue().clear();
|
||||
|
||||
TransactionBuilder builder = new TransactionBuilder("realTimeHeartMeasurement");
|
||||
byte state;
|
||||
|
||||
if (enable)
|
||||
state = HPlusConstants.HEARTRATE_ALLDAY_ON;
|
||||
state = HPlusConstants.ARG_HEARTRATE_ALLDAY_ON;
|
||||
else
|
||||
state = HPlusConstants.HEARTRATE_ALLDAY_OFF;
|
||||
state = HPlusConstants.ARG_HEARTRATE_ALLDAY_OFF;
|
||||
|
||||
builder.write(ctrlCharacteristic, new byte[]{HPlusConstants.COMMAND_SET_PREF_ALLDAYHR, state});
|
||||
builder.write(ctrlCharacteristic, new byte[]{HPlusConstants.CMD_SET_ALLDAY_HRM, state});
|
||||
builder.queue(getQueue());
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFindDevice(boolean start) {
|
||||
LOG.debug("Find Me");
|
||||
|
||||
try {
|
||||
TransactionBuilder builder = performInitialized("findMe");
|
||||
|
||||
byte[] msg = new byte[2];
|
||||
msg[0] = HPlusConstants.COMMAND_SET_PREF_FINDME;
|
||||
|
||||
if (start)
|
||||
msg[1] = 1;
|
||||
else
|
||||
msg[1] = 0;
|
||||
|
||||
builder.write(ctrlCharacteristic, msg);
|
||||
setFindMe(builder, start);
|
||||
builder.queue(getQueue());
|
||||
} catch (IOException e) {
|
||||
GB.toast(getContext(), "Error toogling Find Me: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
|
||||
GB.toast(getContext(), "Error toggling Find Me: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetConstantVibration(int intensity) {
|
||||
LOG.debug("Vibration Trigger");
|
||||
|
||||
getQueue().clear();
|
||||
|
||||
try {
|
||||
TransactionBuilder builder = performInitialized("vibration");
|
||||
|
||||
byte[] msg = new byte[15];
|
||||
msg[0] = HPlusConstants.COMMAND_SET_DISPLAY_ALERT;
|
||||
msg[0] = HPlusConstants.CMD_SET_INCOMING_CALL_NUMBER;
|
||||
|
||||
for (int i = 0; i < msg.length - 1; i++)
|
||||
msg[i + 1] = (byte) "GadgetBridge".charAt(i);
|
||||
|
@ -619,6 +612,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
|||
|
||||
@Override
|
||||
public void onEnableHeartRateSleepSupport(boolean enable) {
|
||||
onEnableRealtimeHeartRateMeasurement(enable);
|
||||
|
||||
}
|
||||
|
||||
|
@ -649,96 +643,72 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
|||
}
|
||||
|
||||
|
||||
private void showIncomingCall(String name, String number){
|
||||
LOG.debug("Show Incoming Call");
|
||||
|
||||
private void showIncomingCall(String name, String number) {
|
||||
try {
|
||||
TransactionBuilder builder = performInitialized("incomingCallIcon");
|
||||
|
||||
//Enable call notifications
|
||||
builder.write(ctrlCharacteristic, new byte[] {HPlusConstants.COMMAND_SET_INCOMING_CALL, 1 });
|
||||
builder.write(ctrlCharacteristic, new byte[]{HPlusConstants.CMD_ACTION_INCOMING_CALL, 1});
|
||||
|
||||
//Show Call Icon
|
||||
builder.write(ctrlCharacteristic, HPlusConstants.COMMAND_ACTION_INCOMING_CALL);
|
||||
builder.write(ctrlCharacteristic, new byte[]{HPlusConstants.CMD_SET_INCOMING_CALL, HPlusConstants.ARG_INCOMING_CALL});
|
||||
|
||||
//builder = performInitialized("incomingCallText");
|
||||
builder.queue(getQueue());
|
||||
|
||||
//TODO: Use WaitAction
|
||||
try {
|
||||
Thread.sleep(200);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
byte[] msg = new byte[13];
|
||||
|
||||
builder = performInitialized("incomingCallNumber");
|
||||
builder.wait(200);
|
||||
|
||||
//Show call number
|
||||
for (int i = 0; i < msg.length; i++)
|
||||
msg[i] = ' ';
|
||||
|
||||
for(int i = 0; i < number.length() && i < (msg.length - 1); i++)
|
||||
for (int i = 0; i < number.length() && i < (msg.length - 1); i++)
|
||||
msg[i + 1] = (byte) number.charAt(i);
|
||||
|
||||
|
||||
msg[0] = HPlusConstants.COMMAND_ACTION_DISPLAY_TEXT_CENTER;
|
||||
msg[0] = HPlusConstants.CMD_SET_INCOMING_CALL_NUMBER;
|
||||
|
||||
builder.write(ctrlCharacteristic, msg);
|
||||
builder.queue(getQueue());
|
||||
|
||||
try {
|
||||
Thread.sleep(200);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
builder = performInitialized("incomingCallText");
|
||||
builder.wait(200);
|
||||
|
||||
//Show call name
|
||||
//Must call twice, otherwise nothing happens
|
||||
for (int i = 0; i < msg.length; i++)
|
||||
msg[i] = ' ';
|
||||
|
||||
for(int i = 0; i < name.length() && i < (msg.length - 1); i++)
|
||||
for (int i = 0; i < name.length() && i < (msg.length - 1); i++)
|
||||
msg[i + 1] = (byte) name.charAt(i);
|
||||
|
||||
msg[0] = HPlusConstants.COMMAND_ACTION_DISPLAY_TEXT_NAME;
|
||||
msg[0] = HPlusConstants.CMD_ACTION_DISPLAY_TEXT_NAME;
|
||||
builder.write(ctrlCharacteristic, msg);
|
||||
|
||||
try {
|
||||
Thread.sleep(200);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
builder.wait(200);
|
||||
|
||||
msg[0] = HPlusConstants.COMMAND_ACTION_DISPLAY_TEXT_NAME_CN;
|
||||
msg[0] = HPlusConstants.CMD_ACTION_DISPLAY_TEXT_NAME_CN;
|
||||
builder.write(ctrlCharacteristic, msg);
|
||||
|
||||
builder.queue(getQueue());
|
||||
}catch(IOException e){
|
||||
} catch (IOException e) {
|
||||
GB.toast(getContext(), "Error showing incoming call: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void showText(String message) {
|
||||
showText(null, message);
|
||||
}
|
||||
|
||||
private void showText(String title, String body) {
|
||||
LOG.debug("Show Notification");
|
||||
|
||||
LOG.debug("Show Notification: "+title+" --> "+body);
|
||||
try {
|
||||
TransactionBuilder builder = performInitialized("notification");
|
||||
|
||||
|
||||
byte[] msg = new byte[20];
|
||||
for (int i = 0; i < msg.length; i++)
|
||||
msg[i] = ' ';
|
||||
|
||||
msg[0] = HPlusConstants.COMMAND_ACTION_DISPLAY_TEXT;
|
||||
msg[0] = HPlusConstants.CMD_ACTION_DISPLAY_TEXT;
|
||||
|
||||
String message = "";
|
||||
|
||||
|
@ -756,7 +726,9 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
|||
|
||||
int length = message.length() / 17;
|
||||
|
||||
builder.write(ctrlCharacteristic, new byte[]{HPlusConstants.COMMAND_ACTION_INCOMING_SOCIAL, (byte) 255});
|
||||
builder.write(ctrlCharacteristic, new byte[]{HPlusConstants.CMD_ACTION_INCOMING_SOCIAL, (byte) 255});
|
||||
|
||||
builder.write(ctrlCharacteristic, new byte[]{HPlusConstants.CMD_SET_INCOMING_MESSAGE, HPlusConstants.ARG_INCOMING_MESSAGE});
|
||||
|
||||
int remaining;
|
||||
|
||||
|
@ -792,17 +764,17 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
|||
|
||||
builder.write(ctrlCharacteristic, msg);
|
||||
builder.queue(getQueue());
|
||||
}catch(IOException e){
|
||||
} catch (IOException e) {
|
||||
GB.toast(getContext(), "Error showing device Notification: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isExpectedDevice(BluetoothDevice device) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void close() {
|
||||
private void close() {
|
||||
if (syncHelper != null) {
|
||||
syncHelper.quit();
|
||||
syncHelper = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -818,203 +790,26 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
|||
return true;
|
||||
|
||||
switch (data[0]) {
|
||||
case HPlusConstants.DATA_VERSION:
|
||||
return syncHelper.processVersion(data);
|
||||
|
||||
case HPlusConstants.DATA_STATS:
|
||||
return processDataStats(data);
|
||||
return syncHelper.processRealtimeStats(data);
|
||||
|
||||
case HPlusConstants.DATA_SLEEP:
|
||||
return processSleepStats(data);
|
||||
return syncHelper.processIncomingSleepData(data);
|
||||
|
||||
case HPlusConstants.DATA_STEPS:
|
||||
return processStepStats(data);
|
||||
return syncHelper.processDaySummary(data);
|
||||
|
||||
case HPlusConstants.DATA_DAY_SUMMARY:
|
||||
case HPlusConstants.DATA_DAY_SUMMARY_ALT:
|
||||
return syncHelper.processIncomingDaySlotData(data);
|
||||
|
||||
default:
|
||||
LOG.info("Unhandled characteristic changed: " + characteristicUUID);
|
||||
|
||||
LOG.debug("Unhandled characteristic changed: " + characteristicUUID);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
Receives a message containing the status of the day.
|
||||
*/
|
||||
private boolean processDayStats(byte[] data) {
|
||||
int a = data[4] * 256 + data[5];
|
||||
if (a < 144) {
|
||||
int slot = a * 2; // 10 minute slots as an offset from 0:00 AM
|
||||
int avgHR = data[1]; //Average Heart Rate
|
||||
int steps = data[2] * 256 + data[3]; // Steps in this period
|
||||
|
||||
//?? data[6];
|
||||
int timeInactive = data[7];
|
||||
|
||||
LOG.debug("Day Stats: Slot: " + slot + " HR: " + avgHR + " Steps: " + steps + " TimeInactive: " + timeInactive);
|
||||
|
||||
try (DBHandler handler = GBApplication.acquireDB()) {
|
||||
DaoSession session = handler.getDaoSession();
|
||||
|
||||
Device device = DBHelper.getDevice(getDevice(), session);
|
||||
User user = DBHelper.getUser(session);
|
||||
int ts = (int) (System.currentTimeMillis() / 1000);
|
||||
HPlusSampleProvider provider = new HPlusSampleProvider(gbDevice, session);
|
||||
|
||||
|
||||
//TODO: Store Sample. How?
|
||||
|
||||
//provider.addGBActivitySample(record);
|
||||
|
||||
|
||||
} catch (GBException e) {
|
||||
e.printStackTrace();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
} else
|
||||
LOG.error("Invalid day stats");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean processStepStats(byte[] data) {
|
||||
LOG.debug("Process Step Stats");
|
||||
|
||||
if (data.length < 19) {
|
||||
LOG.error("Invalid Steps Message Length " + data.length);
|
||||
return false;
|
||||
}
|
||||
/*
|
||||
This is a dump of the entire day.
|
||||
*/
|
||||
int year = data[9] + data[10] * 256;
|
||||
short month = data[11];
|
||||
short day = data[12];
|
||||
int steps = data[2] * 256 + data[1];
|
||||
|
||||
float distance = ((float) (data[3] + data[4] * 256) / 100.0f);
|
||||
|
||||
/*
|
||||
unknown fields
|
||||
short s12 = (short)(data[5] + data[6] * 256);
|
||||
short s13 = (short)(data[7] + data[8] * 256);
|
||||
short s16 = (short)(data[13]) + data[14] * 256);
|
||||
short s17 = data[15];
|
||||
short s18 = data[16];
|
||||
*/
|
||||
|
||||
|
||||
LOG.debug("Step Stats: Year: " + year + " Month: " + month + " Day:");
|
||||
try (DBHandler handler = GBApplication.acquireDB()) {
|
||||
DaoSession session = handler.getDaoSession();
|
||||
|
||||
Device device = DBHelper.getDevice(getDevice(), session);
|
||||
User user = DBHelper.getUser(session);
|
||||
int ts = (int) (System.currentTimeMillis() / 1000);
|
||||
HPlusSampleProvider provider = new HPlusSampleProvider(gbDevice, session);
|
||||
|
||||
|
||||
//TODO: Store Sample. How?
|
||||
|
||||
//provider.addGBActivitySample(record);
|
||||
|
||||
|
||||
} catch (GBException e) {
|
||||
e.printStackTrace();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private boolean processSleepStats(byte[] data) {
|
||||
LOG.debug("Process Sleep Stats");
|
||||
|
||||
if (data.length < 19) {
|
||||
LOG.error("Invalid Sleep Message Length " + data.length);
|
||||
return false;
|
||||
}
|
||||
HPlusSleepRecord record = new HPlusSleepRecord(data);
|
||||
|
||||
try (DBHandler handler = GBApplication.acquireDB()) {
|
||||
DaoSession session = handler.getDaoSession();
|
||||
|
||||
Device device = DBHelper.getDevice(getDevice(), session);
|
||||
User user = DBHelper.getUser(session);
|
||||
int ts = (int) (System.currentTimeMillis() / 1000);
|
||||
HPlusSampleProvider provider = new HPlusSampleProvider(gbDevice, session);
|
||||
|
||||
//TODO: Store Sample. How?
|
||||
|
||||
//provider.addGBActivitySample(record);
|
||||
|
||||
|
||||
} catch (GBException e) {
|
||||
e.printStackTrace();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private boolean processDataStats(byte[] data) {
|
||||
//TODO: Store Calories and Distance. How?
|
||||
|
||||
LOG.debug("Process Data Stats");
|
||||
|
||||
if (data.length < 15) {
|
||||
LOG.error("Invalid Stats Message Length " + data.length);
|
||||
return false;
|
||||
}
|
||||
|
||||
//Ignore duplicate packets
|
||||
if(data.equals(lastDataStats))
|
||||
return true;
|
||||
|
||||
lastDataStats = data.clone();
|
||||
|
||||
double distance = ((int) data[4] * 256 + data[3]) / 100.0;
|
||||
|
||||
int x = (int) data[6] * 256 + data[5];
|
||||
int y = (int) data[8] * 256 + data[7];
|
||||
int calories = x + y;
|
||||
|
||||
int bpm = (data[11] == -1) ? HPlusHealthActivitySample.NOT_MEASURED : data[11];
|
||||
|
||||
try (DBHandler handler = GBApplication.acquireDB()) {
|
||||
DaoSession session = handler.getDaoSession();
|
||||
|
||||
Device device = DBHelper.getDevice(getDevice(), session);
|
||||
User user = DBHelper.getUser(session);
|
||||
int ts = (int) (System.currentTimeMillis() / 1000);
|
||||
HPlusSampleProvider provider = new HPlusSampleProvider(gbDevice, session);
|
||||
|
||||
|
||||
if (bpm != HPlusHealthActivitySample.NOT_MEASURED) {
|
||||
HPlusHealthActivitySample sample = createActivitySample(device, user, ts, provider);
|
||||
sample.setHeartRate(bpm);
|
||||
sample.setSteps(0);
|
||||
sample.setRawIntensity(ActivitySample.NOT_MEASURED);
|
||||
sample.setRawKind(ActivityKind.TYPE_ACTIVITY); // to make it visible in the charts TODO: add a MANUAL kind for that?
|
||||
provider.addGBActivitySample(sample);
|
||||
}
|
||||
} catch (GBException e) {
|
||||
e.printStackTrace();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public HPlusHealthActivitySample createActivitySample(Device device, User user, int timestampInSeconds, SampleProvider provider) {
|
||||
HPlusHealthActivitySample sample = new HPlusHealthActivitySample();
|
||||
sample.setDevice(device);
|
||||
sample.setUser(user);
|
||||
sample.setTimestamp(timestampInSeconds);
|
||||
sample.setProvider(provider);
|
||||
|
||||
return sample;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue