From 1fb4ee8a8f86eaca68c2fce76bfa02468a2a3f94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joa=CC=83o=20Paulo=20Barraca?= Date: Mon, 2 Jan 2017 00:58:37 +0000 Subject: [PATCH] HPlus: Basic support for data synchronization --- .../gadgetbridge/daogen/GBDaoGenerator.java | 23 +- .../devices/hplus/HPlusConstants.java | 126 +++-- .../devices/hplus/HPlusCoordinator.java | 24 +- .../hplus/HPlusHealthSampleProvider.java | 152 ++++++ .../devices/hplus/HPlusSampleProvider.java | 82 ---- .../devices/hplus/HPlusDataRecord.java | 38 ++ .../devices/hplus/HPlusDataRecordDay.java | 40 ++ .../hplus/HPlusDataRecordRealtime.java | 69 +++ .../devices/hplus/HPlusDataRecordSleep.java | 79 +++ .../devices/hplus/HPlusDataRecordSteps.java | 59 +++ .../devices/hplus/HPlusHandlerThread.java | 453 ++++++++++++++++++ .../devices/hplus/HPlusSleepRecord.java | 86 ---- .../service/devices/hplus/HPlusSupport.java | 368 ++++---------- 13 files changed, 1093 insertions(+), 506 deletions(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/HPlusHealthSampleProvider.java delete mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/HPlusSampleProvider.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusDataRecord.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusDataRecordDay.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusDataRecordRealtime.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusDataRecordSleep.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusDataRecordSteps.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusHandlerThread.java delete mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusSleepRecord.java diff --git a/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java b/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java index e6cb8ddf..2905d45f 100644 --- a/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java +++ b/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java @@ -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"); @@ -226,15 +227,31 @@ public class GBDaoGenerator { Entity activitySample = addEntity(schema, "HPlusHealthActivitySample"); 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"); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/HPlusConstants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/HPlusConstants.java index d0f450fa..9f82d13d 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/HPlusConstants.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/HPlusConstants.java @@ -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,66 +13,86 @@ public final class HPlusConstants { public static final UUID UUID_SERVICE_HP = UUID.fromString("14701820-620a-3973-7c78-9cfff0876abd"); - public static final byte PREF_VALUE_COUNTRY_CN = 1; - public static final byte PREF_VALUE_COUNTRY_OTHER = 2; + public static final byte ARG_COUNTRY_CN = 1; + public static final byte ARG_COUNTRY_OTHER = 2; - public static final byte PREF_VALUE_CLOCK_24H = 0; - public static final byte PREF_VALUE_CLOCK_12H = 1; + public static final byte ARG_TIMEMODE_24H = 0; + public static final byte ARG_TIMEMODE_12H = 1; - public static final byte PREF_VALUE_UNIT_METRIC = 0; - public static final byte PREF_VALUE_UNIT_IMPERIAL = 1; + public static final byte ARG_UNIT_METRIC = 0; + public static final byte ARG_UNIT_IMPERIAL = 1; - public static final byte PREF_VALUE_GENDER_MALE = 0; - public static final byte PREF_VALUE_GENDER_FEMALE = 1; + public static final byte ARG_GENDER_MALE = 0; + public static final byte ARG_GENDER_FEMALE = 1; - public static final byte PREF_VALUE_HEARTRATE_MEASURE_ON = 11; - public static final byte PREF_VALUE_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 PREF_VALUE_HEARTRATE_ALLDAY_ON = 10; - public static final byte PREF_VALUE_HEARTRATE_ALLDAY_OFF = -1; + public static final byte ARG_HEARTRATE_ALLDAY_ON = 10; + public static final byte ARG_HEARTRATE_ALLDAY_OFF = -1; 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 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 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 COMMAND_SET_INCOMING_CALL = 0x41; - public static final byte COMMAND_SET_CONF_SAVE = 0x17; - public static final byte COMMAND_SET_CONF_END = 0x4f; - public static final byte COMMAND_SET_PREFS = 0x50; - public static final byte COMMAND_SET_SIT_INTERVAL = 0x51; + 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_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 CMD_SET_FINDME = 0x0a; + public static final byte ARG_FINDME_ON = 0x01; + public static final byte ARG_FINDME_OFF = 0x02; + + 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[] COMMAND_FACTORY_RESET = new byte[] {-74, 90}; //Actions to device - 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 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 CMD_ACTION_INCOMING_SOCIAL = 0x31; + //public static final byte COMMAND_ACTION_INCOMING_SMS = 0x40; + public static final byte CMD_ACTION_DISPLAY_TEXT = 0x43; + + 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, 0}; + public static final byte CMD_SHUTDOWN = 91; + public static final byte ARG_SHUTDOWN_EN = 90; + + public static final byte CMD_FACTORY_RESET = -74; + public static final byte ARG_FACTORY_RESET_EN = 90; + + + + 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; @@ -81,9 +100,14 @@ public final class HPlusConstants { 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 DATA_INCOMING_CALL_STATE = 0x18; + public static final byte DATA_VERSION = 0x18; + public static final byte DB_TYPE_DAY_SLOT_SUMMARY = 1; + public static final byte DB_TYPE_DAY_SUMMARY = 2; + public static final byte DB_TYPE_INSTANT_STATS = 3; + public static final byte DB_TYPE_SLEEP_STATS = 4; + public static final String PREF_HPLUS_SCREENTIME = "hplus_screentime"; public static final String PREF_HPLUS_ALLDAYHR = "hplus_alldayhr"; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/HPlusCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/HPlusCoordinator.java index b8848ebe..3de594b6 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/HPlusCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/HPlusCoordinator.java @@ -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 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 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 { @@ -181,9 +179,9 @@ public class HPlusCoordinator extends AbstractDeviceCoordinator { ActivityUser activityUser = new ActivityUser(); if (activityUser.getGender() == ActivityUser.GENDER_MALE) - return HPlusConstants.PREF_VALUE_GENDER_MALE; + return HPlusConstants.ARG_GENDER_MALE; - return HPlusConstants.PREF_VALUE_GENDER_FEMALE; + return HPlusConstants.ARG_GENDER_FEMALE; } public static int getGoal(String address) { @@ -197,7 +195,7 @@ 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 getSocial(String address) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/HPlusHealthSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/HPlusHealthSampleProvider.java new file mode 100644 index 00000000..eec2fee8 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/HPlusHealthSampleProvider.java @@ -0,0 +1,152 @@ +package nodomain.freeyourgadget.gadgetbridge.devices.hplus; + +/* +* @author João Paulo Barraca <jpbarraca@gmail.com> +*/ + +import android.support.annotation.NonNull; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Collections; +import java.util.Comparator; +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; + +public class HPlusHealthSampleProvider extends AbstractSampleProvider { + private static final Logger LOG = LoggerFactory.getLogger(HPlusHealthSampleProvider.class); + + + 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) { + + return rawType; + } + + public int toRawActivityKind(int activityKind) { + + return activityKind; + } + + @NonNull + @Override + protected Property getTimestampSampleProperty() { + return HPlusHealthActivitySampleDao.Properties.Timestamp; + } + + @Override + public HPlusHealthActivitySample createActivitySample() { + return new HPlusHealthActivitySample(); + } + + @Override + protected Property getRawKindSampleProperty() { + return HPlusHealthActivitySampleDao.Properties.RawKind; + } + + @Override + public float normalizeIntensity(int rawIntensity) { + return rawIntensity; //TODO: Calculate actual value + } + + @NonNull + @Override + protected Property getDeviceIdentifierSampleProperty() { + return HPlusHealthActivitySampleDao.Properties.DeviceId; + } + + @Override + public AbstractDao getSampleDao() { + return getSession().getHPlusHealthActivitySampleDao(); + } + + @Override + public List getAllActivitySamples(int timestamp_from, int timestamp_to) { + List samples = super.getGBActivitySamples(timestamp_from, timestamp_to, ActivityKind.TYPE_ALL); + + Device dbDevice = DBHelper.findDevice(getDevice(), getSession()); + if (dbDevice == null) { + return Collections.emptyList(); + } + + QueryBuilder qb = getSession().getHPlusHealthActivityOverlayDao().queryBuilder(); + + qb.where(HPlusHealthActivityOverlayDao.Properties.DeviceId.eq(dbDevice.getId()), HPlusHealthActivityOverlayDao.Properties.TimestampFrom.ge(timestamp_from - 24 * 60 * 60)) + .where(HPlusHealthActivityOverlayDao.Properties.TimestampTo.le(timestamp_to)); + + List overlayRecords = qb.build().list(); + + for (HPlusHealthActivityOverlay overlay : overlayRecords) { + insertVirtualItem(samples, overlay.getTimestampFrom(), overlay.getDeviceId(), overlay.getUserId()); + insertVirtualItem(samples, overlay.getTimestampTo() - 1, overlay.getDeviceId(), overlay.getUserId()); + + for (HPlusHealthActivitySample sample : samples) { + if (overlay.getTimestampFrom() <= sample.getTimestamp() && sample.getTimestamp() < overlay.getTimestampTo()) { + sample.setRawKind(overlay.getRawKind()); + } + } + } + + detachFromSession(); + + LOG.debug("Returning " + samples.size() + " samples processed by " + overlayRecords.size() + " overlays"); + + Collections.sort(samples, new Comparator() { + public int compare(HPlusHealthActivitySample one, HPlusHealthActivitySample other) { + return one.getTimestamp() - other.getTimestamp(); + } + }); + + return samples; + } + + private List insertVirtualItem(List samples, int timestamp, long deviceId, long userId){ + HPlusHealthActivitySample sample = new HPlusHealthActivitySample( + timestamp, // ts + deviceId, + userId, // User id + null, // Raw Data + ActivityKind.TYPE_UNKNOWN, + ActivitySample.NOT_MEASURED, // 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; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/HPlusSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/HPlusSampleProvider.java deleted file mode 100644 index a26f6be2..00000000 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/HPlusSampleProvider.java +++ /dev/null @@ -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 { - - - 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 getSampleDao() { - return getSession().getHPlusHealthActivitySampleDao(); - } -} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusDataRecord.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusDataRecord.java new file mode 100644 index 00000000..cc3afc5f --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusDataRecord.java @@ -0,0 +1,38 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.hplus; + +import java.util.List; + +import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; + +/** + * Created by jpbarraca on 30/12/2016. + */ + +public class HPlusDataRecord { + public final static int TYPE_SLEEP = 1; + public int activityKind = ActivityKind.TYPE_UNKNOWN; + + public int timestamp; + public byte[] rawData; + + public HPlusDataRecord(byte[] data){ + rawData = data; + } + + public byte[] getRawData() { + + return rawData; + } + + public class RecordInterval { + public int timestampFrom; + public int timestampTo; + public int activityKind; + + RecordInterval(int timestampFrom, int timestampTo, int activityKind) { + this.timestampFrom = timestampFrom; + this.timestampTo = timestampTo; + this.activityKind = activityKind; + } + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusDataRecordDay.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusDataRecordDay.java new file mode 100644 index 00000000..1c42a1d8 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusDataRecordDay.java @@ -0,0 +1,40 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.hplus; + +/* +* @author João Paulo Barraca <jpbarraca@gmail.com> +*/ + +import java.util.Calendar; +import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; + + +public class HPlusDataRecordDay extends HPlusDataRecord { + int slot; + int steps; + int secondsInactive; + int heartRate; + + public HPlusDataRecordDay(byte[] data) { + super(data); + + int a = (data[4] & 0xFF) * 256 + (data[5] & 0xFF); + if (a >= 144) { + throw new IllegalArgumentException("Invalid Slot Number"); + } + + slot = a; // 10 minute slots as an offset from 0:00 AM + heartRate = data[1] & 0xFF; //Average Heart Rate ? + + if(heartRate == 255) + heartRate = ActivityKind.TYPE_NOT_MEASURED; + + steps = (data[2] & 0xFF) * 256 + data[3] & 0xFF; // Steps in this period + + //?? data[6]; + secondsInactive = data[7] & 0xFF; // ? + + int now = (int) (Calendar.getInstance().getTimeInMillis() / (3600 * 24 * 1000L)); + timestamp = now * 3600 * 24 + (slot / 6 * 3600 + slot % 6 * 10); + } + +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusDataRecordRealtime.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusDataRecordRealtime.java new file mode 100644 index 00000000..5e363d55 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusDataRecordRealtime.java @@ -0,0 +1,69 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.hplus; + +/* +* @author João Paulo Barraca <jpbarraca@gmail.com> +*/ + + +import java.util.Calendar; + +import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; + + +public class HPlusDataRecordRealtime extends HPlusDataRecord { + int distance; + int calories; + int heartRate; + byte battery; + int activeTime; + + public HPlusDataRecordRealtime(byte[] data) { + super(data); + + if (data.length < 15) { + throw new IllegalArgumentException("Invalid data packet"); + } + + timestamp = (int) (Calendar.getInstance().getTimeInMillis() / 1000); + distance = 10 * ((data[4] & 0xFF) * 256 + (data[3] & 0xFF)); // meters + + 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); + + } + + 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 distance == other.distance && calories == other.calories && heartRate == other.heartRate && battery == other.battery; + } + +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusDataRecordSleep.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusDataRecordSleep.java new file mode 100644 index 00000000..839c0882 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusDataRecordSleep.java @@ -0,0 +1,79 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.hplus; + +/* +* @author João Paulo Barraca <jpbarraca@gmail.com> +*/ + + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.List; + +import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; + +public class HPlusDataRecordSleep extends HPlusDataRecord { + private static final Logger LOG = LoggerFactory.getLogger(HPlusDataRecordSleep.class); + + int type = TYPE_SLEEP; + int bedTimeStart; + int bedTimeEnd; + int deepSleepMinutes; + int lightSleepMinutes; + int enterSleepMinutes; + int spindleMinutes; + int remSleepMinutes; + int wakeupMinutes; + int wakeupCount; + + public HPlusDataRecordSleep(byte[] data) { + super(data); + + 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 = Calendar.getInstance(); + sleepStart.set(Calendar.YEAR, year); + sleepStart.set(Calendar.MONTH, month - 1); + sleepStart.set(Calendar.DAY_OF_MONTH, day); + sleepStart.set(Calendar.HOUR, 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 getIntervals() { + List 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; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusDataRecordSteps.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusDataRecordSteps.java new file mode 100644 index 00000000..cdc18307 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusDataRecordSteps.java @@ -0,0 +1,59 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.hplus; + +/* +* @author João Paulo Barraca <jpbarraca@gmail.com> +*/ + + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Calendar; + +import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; + + +public class HPlusDataRecordSteps extends HPlusDataRecord{ + private static final Logger LOG = LoggerFactory.getLogger(HPlusDataRecordSteps.class); + + int steps; + int distance; + + HPlusDataRecordSteps(byte[] data) { + super(data); + + int year = (data[10] & 0xFF) * 256 + (data[9] & 0xFF); + int month = data[11] & 0xFF; + int day = data[12] & 0xFF; + + 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); + + /* + 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]; + */ + + Calendar date = Calendar.getInstance(); + date.set(Calendar.YEAR, year); + date.set(Calendar.MONTH, month - 1); + date.set(Calendar.DAY_OF_MONTH, day); + date.set(Calendar.HOUR, 23); + date.set(Calendar.MINUTE, 59); + date.set(Calendar.SECOND, 59); + date.set(Calendar.MILLISECOND, 999); + + timestamp = (int) (date.getTimeInMillis() / 1000); + } + + public int getType(int ts){ + return ActivityKind.TYPE_UNKNOWN; + } +} \ No newline at end of file diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusHandlerThread.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusHandlerThread.java new file mode 100644 index 00000000..a197bd4c --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusHandlerThread.java @@ -0,0 +1,453 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.hplus; + +/* +* @author João Paulo Barraca <jpbarraca@gmail.com> +*/ + + +import android.content.Context; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.List; + +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.SampleProvider; +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.Device; +import nodomain.freeyourgadget.gadgetbridge.entities.HPlusHealthActivityOverlay; +import nodomain.freeyourgadget.gadgetbridge.entities.HPlusHealthActivityOverlayDao; +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.service.btle.TransactionBuilder; +import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceIoThread; + + +public class HPlusHandlerThread extends GBDeviceIoThread { + + private int SYNC_PERIOD = 60 * 10; + private int SYNC_RETRY_PERIOD = 6; + private int SLEEP_SYNC_PERIOD = 12 * 60 * 60; + private int SLEEP_RETRY_PERIOD = 30; + + private int HELLO_INTERVAL = 30; + + private static final Logger LOG = LoggerFactory.getLogger(HPlusHandlerThread.class); + + private boolean mQuit = false; + private HPlusSupport mHPlusSupport; + private int mLastSlotReceived = 0; + private int mLastSlotRequested = 0; + + private Calendar mLastSleepDayReceived = Calendar.getInstance(); + + private Calendar mHelloTime = Calendar.getInstance(); + + private Calendar mGetDaySlotsTime = Calendar.getInstance(); + private Calendar mGetSleepTime = Calendar.getInstance(); + + private Object waitObject = new Object(); + + private HPlusDataRecordRealtime prevRealTimeRecord = null; + + public HPlusHandlerThread(GBDevice gbDevice, Context context, HPlusSupport hplusSupport) { + super(gbDevice, context); + + mQuit = false; + + mHPlusSupport = hplusSupport; + + mLastSleepDayReceived.setTimeInMillis(0); + mGetSleepTime.setTimeInMillis(0); + mGetDaySlotsTime.setTimeInMillis(0); + } + + + @Override + public void run() { + mQuit = false; + + sync(); + + boolean starting = true; + long waitTime = 0; + while (!mQuit) { + //LOG.debug("Waiting " + (waitTime)); + if (waitTime > 0) { + synchronized (waitObject) { + try { + waitObject.wait(waitTime); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + if (mQuit) { + break; + } + + Calendar now = Calendar.getInstance(); + + if (now.compareTo(mHelloTime) > 0) { + sendHello(); + } + + if (now.compareTo(mGetDaySlotsTime) > 0) { + requestNextDaySlots(); + } + + if (now.compareTo(mGetSleepTime) > 0) { + requestNextSleepData(); + } + + now = Calendar.getInstance(); + waitTime = 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); + + TransactionBuilder builder = new TransactionBuilder("startSyncDayStats"); + + builder.write(mHPlusSupport.ctrlCharacteristic, new byte[]{HPlusConstants.CMD_GET_SLEEP}); + builder.write(mHPlusSupport.ctrlCharacteristic, new byte[]{HPlusConstants.CMD_GET_DAY_DATA}); + builder.write(mHPlusSupport.ctrlCharacteristic, new byte[]{HPlusConstants.CMD_GET_ACTIVE_DAY}); + builder.write(mHPlusSupport.ctrlCharacteristic, new byte[]{HPlusConstants.CMD_GET_DEVICE_ID}); + builder.write(mHPlusSupport.ctrlCharacteristic, new byte[]{HPlusConstants.CMD_GET_VERSION}); + builder.write(mHPlusSupport.ctrlCharacteristic, new byte[]{HPlusConstants.CMD_GET_CURR_DATA}); + + builder.write(mHPlusSupport.ctrlCharacteristic, new byte[]{HPlusConstants.CMD_SET_ALLDAY_HRM, HPlusConstants.ARG_HEARTRATE_ALLDAY_ON}); + + builder.queue(mHPlusSupport.getQueue()); + + synchronized (waitObject) { + waitObject.notify(); + } + } + + public void sendHello() { + mHelloTime = Calendar.getInstance(); + mHelloTime.add(Calendar.SECOND, HELLO_INTERVAL); + + TransactionBuilder builder = new TransactionBuilder("hello"); + builder.write(mHPlusSupport.ctrlCharacteristic, HPlusConstants.CMD_ACTION_HELLO); + + builder.queue(mHPlusSupport.getQueue()); + } + + + public void processIncomingDaySlotData(byte[] data) { + + HPlusDataRecordDay record; + try{ + record = new HPlusDataRecordDay(data); + } catch(IllegalArgumentException e){ + LOG.debug((e.getMessage())); + return; + } + + if ((record.slot == 0 && mLastSlotReceived == 0) || (record.slot == mLastSlotReceived + 1)) { + mLastSlotReceived = record.slot; + + try (DBHandler dbHandler = GBApplication.acquireDB()) { + HPlusHealthSampleProvider provider = new HPlusHealthSampleProvider(getDevice(), dbHandler.getDaoSession()); + + Long userId = DBHelper.getUser(dbHandler.getDaoSession()).getId(); + Long deviceId = DBHelper.getDevice(getDevice(), dbHandler.getDaoSession()).getId(); + HPlusHealthActivitySample sample = new HPlusHealthActivitySample( + record.timestamp, // ts + deviceId, userId, // User id + record.getRawData(), // Raw Data + ActivityKind.TYPE_UNKNOWN, + ActivitySample.NOT_MEASURED, // Intensity + record.steps, // Steps + record.heartRate, // HR + ActivitySample.NOT_MEASURED, // Distance + ActivitySample.NOT_MEASURED // Calories + ); + sample.setProvider(provider); + + provider.addGBActivitySample(sample); + + } catch (GBException ex) { + LOG.debug((ex.getMessage())); + } catch (Exception ex) { + LOG.debug(ex.getMessage()); + } + + if (record.slot >= mLastSlotRequested) { + synchronized (waitObject) { + mGetDaySlotsTime.setTimeInMillis(0); + waitObject.notify(); + } + } + } + } + + private void requestNextDaySlots() { + LOG.debug("Request Next Slot: Got: " + mLastSlotReceived + " Request: " + mLastSlotRequested); + + //Sync Day Stats + byte hour = (byte) ((mLastSlotReceived) / 6); + byte nextHour = (byte) (hour + 1); + + byte nextMinute = 0; + + if (nextHour == (byte) Calendar.getInstance().get(Calendar.HOUR_OF_DAY)) { + nextMinute = (byte) Calendar.getInstance().get(Calendar.MINUTE); + } + + byte minute = (byte) ((mLastSlotReceived % 6) * 10); + + mLastSlotRequested = (nextHour) * 6 + Math.round(nextMinute / 10); + + if (nextHour >= 24 && nextMinute > 0) { // 24 * 6 + LOG.debug("Reached End of the Day"); + mLastSlotRequested = 0; + mLastSlotReceived = 0; + mGetDaySlotsTime = Calendar.getInstance(); + mGetDaySlotsTime.add(Calendar.SECOND, SYNC_PERIOD); + + return; + } + + if (nextHour > Calendar.getInstance().get(Calendar.HOUR_OF_DAY)) { + LOG.debug("Day data is up to date"); + mGetDaySlotsTime = Calendar.getInstance(); + mGetDaySlotsTime.add(Calendar.SECOND, SYNC_PERIOD); + return; + } + + LOG.debug("Making new Request From " + hour + ":" + minute + " to " + nextHour + ":" + nextMinute); + + byte[] msg = new byte[]{39, hour, minute, nextHour, nextMinute}; //Request the entire day + TransactionBuilder builder = new TransactionBuilder("getNextDaySlot"); + builder.write(mHPlusSupport.ctrlCharacteristic, msg); + builder.queue(mHPlusSupport.getQueue()); + + mGetDaySlotsTime = Calendar.getInstance(); + mGetDaySlotsTime.add(Calendar.SECOND, SYNC_RETRY_PERIOD); + } + + public void processIncomingSleepData(byte[] data){ + LOG.debug("Processing Sleep Data"); + + HPlusDataRecordSleep record; + + try{ + record = new HPlusDataRecordSleep(data); + } catch(IllegalArgumentException e){ + LOG.debug((e.getMessage())); + return; + } + + 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()); + + //Insert the Overlays + List overlayList = new ArrayList<>(); + List 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 = new HPlusHealthActivitySample( + record.timestamp, // ts + deviceId, userId, // User id + record.getRawData(), // Raw Data + record.activityKind, + ActivitySample.NOT_MEASURED, // Intensity + ActivitySample.NOT_MEASURED, // Steps + ActivitySample.NOT_MEASURED, // HR + ActivitySample.NOT_MEASURED, // Distance + ActivitySample.NOT_MEASURED // Calories + ); + + sample.setProvider(provider); + + provider.addGBActivitySample(sample); + + + } catch (Exception ex) { + LOG.debug(ex.getMessage()); + } + + mGetSleepTime = Calendar.getInstance(); + mGetSleepTime.add(Calendar.SECOND, SLEEP_SYNC_PERIOD); + + } + + private void requestNextSleepData() { + LOG.debug("Request New Sleep Data"); + + mGetSleepTime = Calendar.getInstance(); + mGetSleepTime.add(Calendar.SECOND, SLEEP_RETRY_PERIOD); + + TransactionBuilder builder = new TransactionBuilder("requestSleepStats"); + builder.write(mHPlusSupport.ctrlCharacteristic, new byte[]{HPlusConstants.CMD_GET_SLEEP}); + builder.queue(mHPlusSupport.getQueue()); + } + + + public void processRealtimeStats(byte[] data) { + LOG.debug("Processing Real time Stats"); + + HPlusDataRecordRealtime record; + + try{ + record = new HPlusDataRecordRealtime(data); + } catch(IllegalArgumentException e){ + LOG.debug((e.getMessage())); + return; + } + + if(record.same(prevRealTimeRecord)) + return; + + prevRealTimeRecord = record; + + getDevice().setBatteryLevel(record.battery); + getDevice().sendDeviceUpdateIntent(getContext()); + + //Skip when measuring + if(record.heartRate == 255) { + getDevice().setFirmwareVersion2("---"); + getDevice().sendDeviceUpdateIntent(getContext()); + return; + } + + getDevice().setFirmwareVersion2(""+record.heartRate); + getDevice().sendDeviceUpdateIntent(getContext()); + + try (DBHandler dbHandler = GBApplication.acquireDB()) { + DaoSession session = dbHandler.getDaoSession(); + + HPlusHealthSampleProvider provider = new HPlusHealthSampleProvider(getDevice(), dbHandler.getDaoSession()); + HPlusHealthActivityOverlayDao overlayDao = session.getHPlusHealthActivityOverlayDao(); + + Long userId = DBHelper.getUser(dbHandler.getDaoSession()).getId(); + Long deviceId = DBHelper.getDevice(getDevice(), dbHandler.getDaoSession()).getId(); + + HPlusHealthActivitySample sample = new HPlusHealthActivitySample( + record.timestamp, // ts + deviceId, userId, // User id + record.getRawData(), // Raw Data + record.activityKind, + ActivitySample.NOT_MEASURED, // Intensity + ActivitySample.NOT_MEASURED, // Steps + record.heartRate, // HR + record.distance, // Distance + record.calories // Calories + ); + + sample.setProvider(provider); + provider.addGBActivitySample(sample); + + if(record.activeTime > 0){ + //TODO: Register ACTIVITY Time + + //Insert the Overlays + //List overlayList = new ArrayList<>(); + //overlayList.add(new HPlusHealthActivityOverlay(record.timestamp - record.activeTime * 60, record.timestamp, ActivityKind.TYPE_ACTIVITY, deviceId, userId, null)); + //overlayDao.insertOrReplaceInTx(overlayList); + } + + } catch (GBException ex) { + LOG.debug((ex.getMessage())); + } catch (Exception ex) { + LOG.debug(ex.getMessage()); + } + } + + + public void processStepStats(byte[] data) { + LOG.debug("Processing Step Stats"); + HPlusDataRecordSteps record; + + try{ + record = new HPlusDataRecordSteps(data); + } catch(IllegalArgumentException e){ + LOG.debug((e.getMessage())); + return; + } + + try (DBHandler dbHandler = GBApplication.acquireDB()) { + HPlusHealthSampleProvider provider = new HPlusHealthSampleProvider(getDevice(), dbHandler.getDaoSession()); + + Long userId = DBHelper.getUser(dbHandler.getDaoSession()).getId(); + Long deviceId = DBHelper.getDevice(getDevice(), dbHandler.getDaoSession()).getId(); + HPlusHealthActivitySample sample = new HPlusHealthActivitySample( + record.timestamp, // ts + deviceId, userId, // User id + record.getRawData(), // Raw Data + ActivityKind.TYPE_UNKNOWN, + ActivitySample.NOT_MEASURED, // Intensity + record.steps, // Steps + ActivitySample.NOT_MEASURED, // HR + record.distance, // Distance + ActivitySample.NOT_MEASURED // Calories + ); + sample.setProvider(provider); + provider.addGBActivitySample(sample); + } catch (GBException ex) { + LOG.debug((ex.getMessage())); + } catch (Exception ex) { + LOG.debug(ex.getMessage()); + } + } + + public boolean processVersion(byte[] data) { + LOG.debug("Process Version"); + + int major = data[2] & 0xFF; + int minor = data[1] & 0xFF; + + getDevice().setFirmwareVersion(major + "." + minor); + + getDevice().sendDeviceUpdateIntent(getContext()); + + return true; + } + + public static 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; + } +} \ No newline at end of file diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusSleepRecord.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusSleepRecord.java deleted file mode 100644 index bed5d581..00000000 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusSleepRecord.java +++ /dev/null @@ -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; - } - -} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusSupport.java index c93a4f65..65350305 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusSupport.java @@ -23,22 +23,10 @@ import java.util.ArrayList; import java.util.Calendar; 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; @@ -58,12 +46,13 @@ 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 int[] lastDataStats = null; private final GBDeviceEventVersionInfo versionCmd = new GBDeviceEventVersionInfo(); + private HPlusHandlerThread syncHelper; private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override @@ -85,55 +74,68 @@ 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); super.dispose(); + + if(syncHelper != null) + syncHelper.quit(); } @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 - syncPreferences(builder); //Sync preferences + sendUserInfo(builder); //Sync preferences setSIT(builder); //Sync SIT Interval setCurrentDate(builder); // Sync Current Date setCurrentTime(builder); // Sync Current Time - builder.notify(getCharacteristic(HPlusConstants.UUID_CHARACTERISTIC_MEASURE), true); + requestDeviceInfo(builder); + setInitialized(builder); + + + syncHelper.start(); + + builder.notify(getCharacteristic(HPlusConstants.UUID_CHARACTERISTIC_MEASURE), true); builder.setGattCallback(this); builder.notify(measureCharacteristic, true); - setInitialized(builder); + //LOG.debug("Initialization Done"); + return builder; } 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..."); + LOG.info("Attempting to sync preferences with: " + getDevice().getAddress()); byte gender = HPlusCoordinator.getUserGender(getDevice().getAddress()); byte age = HPlusCoordinator.getUserAge(getDevice().getAddress()); @@ -159,7 +161,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { byte timemode = HPlusCoordinator.getTimeMode((getDevice().getAddress())); transaction.write(ctrlCharacteristic, new byte[]{ - HPlusConstants.COMMAND_SET_PREFS, + HPlusConstants.CMD_SET_PREFS, gender, age, bodyHeight, @@ -174,20 +176,24 @@ 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) { + LOG.info("Attempting to set language..."); byte value = HPlusCoordinator.getCountry(getDevice().getAddress()); transaction.write(ctrlCharacteristic, new byte[]{ - HPlusConstants.COMMAND_SET_PREF_COUNTRY, + HPlusConstants.CMD_SET_LANGUAGE, value }); return this; @@ -199,7 +205,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { byte value = HPlusCoordinator.getTimeMode(getDevice().getAddress()); transaction.write(ctrlCharacteristic, new byte[]{ - HPlusConstants.COMMAND_SET_PREF_TIMEMODE, + HPlusConstants.CMD_SET_TIMEMODE, value }); return this; @@ -211,7 +217,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { byte value = HPlusCoordinator.getUnit(getDevice().getAddress()); transaction.write(ctrlCharacteristic, new byte[]{ - HPlusConstants.COMMAND_SET_PREF_UNIT, + HPlusConstants.CMD_SET_UNITS, value }); return this; @@ -226,7 +232,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { 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), @@ -242,7 +248,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { Calendar c = Calendar.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) @@ -258,7 +264,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { Calendar c = Calendar.getInstance(); transaction.write(ctrlCharacteristic, new byte[]{ - HPlusConstants.COMMAND_SET_PREF_WEEK, + HPlusConstants.CMD_SET_WEEK, (byte) c.get(Calendar.DAY_OF_WEEK) }); return this; @@ -274,7 +280,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { Calendar now = Calendar.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), @@ -302,7 +308,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { byte value = HPlusCoordinator.getUserWeight(getDevice().getAddress()); transaction.write(ctrlCharacteristic, new byte[]{ - HPlusConstants.COMMAND_SET_PREF_WEIGHT, + HPlusConstants.CMD_SET_WEIGHT, value }); @@ -314,7 +320,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { byte value = HPlusCoordinator.getUserHeight(getDevice().getAddress()); transaction.write(ctrlCharacteristic, new byte[]{ - HPlusConstants.COMMAND_SET_PREF_HEIGHT, + HPlusConstants.CMD_HEIGHT, value }); @@ -327,19 +333,19 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { 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..."); + private HPlusSupport setGender(TransactionBuilder transaction) { + LOG.info("Attempting to set Gender..."); byte value = HPlusCoordinator.getUserGender(getDevice().getAddress()); transaction.write(ctrlCharacteristic, new byte[]{ - HPlusConstants.COMMAND_SET_PREF_SEX, + HPlusConstants.CMD_SET_GENDER, value }); @@ -352,7 +358,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { 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) @@ -366,7 +372,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { byte value = HPlusCoordinator.getScreenTime(getDevice().getAddress()); transaction.write(ctrlCharacteristic, new byte[]{ - HPlusConstants.COMMAND_SET_PREF_SCREENTIME, + HPlusConstants.CMD_SET_SCREENTIME, value }); @@ -378,7 +384,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { byte value = HPlusCoordinator.getAllDayHR(getDevice().getAddress()); transaction.write(ctrlCharacteristic, new byte[]{ - HPlusConstants.COMMAND_SET_PREF_ALLDAYHR, + HPlusConstants.CMD_SET_ALLDAY_HRM, value }); @@ -407,8 +413,11 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { 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; } @@ -435,7 +444,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { public void onNotification(NotificationSpec notificationSpec) { LOG.debug("Got Notification"); //TODO: Show different notifications acccording to source as Band supports this - showText(notificationSpec.body); + showText(notificationSpec.title, notificationSpec.body); } @@ -515,7 +524,8 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { @Override public void onFetchActivityData() { - + if(syncHelper != null) + syncHelper.sync(); } @Override @@ -531,7 +541,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { 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_ALLDAY_HRM, 0x0A}); //Set Real Time... ? builder.queue(getQueue()); } @@ -545,11 +555,11 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { byte state; if (enable) - state = HPlusConstants.PREF_VALUE_HEARTRATE_ALLDAY_ON; + state = HPlusConstants.ARG_HEARTRATE_ALLDAY_ON; else - state = HPlusConstants.PREF_VALUE_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()); } @@ -561,12 +571,12 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { TransactionBuilder builder = performInitialized("findMe"); byte[] msg = new byte[2]; - msg[0] = HPlusConstants.COMMAND_SET_PREF_FINDME; + msg[0] = HPlusConstants.CMD_SET_FINDME; if (start) - msg[1] = 1; + msg[1] = HPlusConstants.ARG_FINDME_ON; else - msg[1] = 0; + msg[1] = HPlusConstants.ARG_FINDME_OFF; builder.write(ctrlCharacteristic, msg); builder.queue(getQueue()); @@ -586,7 +596,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { 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); @@ -630,17 +640,17 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { } - private void showIncomingCall(String name, String number){ + private void showIncomingCall(String name, String number) { LOG.debug("Show Incoming Call"); 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_ACTION_INCOMING_CALL, HPlusConstants.ARG_INCOMING_CALL}); //builder = performInitialized("incomingCallText"); builder.queue(getQueue()); @@ -660,11 +670,11 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { 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()); @@ -682,10 +692,10 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { 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 { @@ -694,17 +704,18 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { e.printStackTrace(); } - 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); } @@ -719,7 +730,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { 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 = ""; @@ -737,7 +748,7 @@ 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}); int remaining; @@ -773,7 +784,7 @@ 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); } @@ -799,17 +810,24 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { return true; switch (data[0]) { - case HPlusConstants.DATA_STATS: - return processDataStats(data); - case HPlusConstants.DATA_SLEEP: - return processSleepStats(data); - case HPlusConstants.DATA_STEPS: - return processStepStats(data); + case HPlusConstants.DATA_VERSION: + syncHelper.processVersion(data); + case HPlusConstants.DATA_STATS: { + syncHelper.processRealtimeStats(data); + return true; + } + case HPlusConstants.DATA_SLEEP: { + syncHelper.processIncomingSleepData(data); + return true; + } + case HPlusConstants.DATA_STEPS:{ + syncHelper.processStepStats(data); + return true; + } case HPlusConstants.DATA_DAY_SUMMARY: case HPlusConstants.DATA_DAY_SUMMARY_ALT: - return processDaySummary(data); - case HPlusConstants.DATA_INCOMING_CALL_STATE: - return processIncomingCallState(data); + syncHelper.processIncomingDaySlotData(data); + return true; default: LOG.info("Unhandled characteristic changed: " + characteristicUUID); @@ -817,197 +835,5 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { return false; } - private boolean processIncomingCallState(byte[] data){ - LOG.debug("Process Incoming Call State"); - //Disabled now - return true; - } - /* - Receives a message containing the status of the day. - */ - private boolean processDaySummary(byte[] data) { - LOG.debug("Process Day Summary"); - 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; - } }