From fed56387828ebfcfc44011842762a8b13289ccba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joa=CC=83o=20Paulo=20Barraca?= Date: Wed, 28 Dec 2016 13:50:56 +0000 Subject: [PATCH 01/15] HPlus: Refactor Sex into Gender and convert value appropriatelly --- .../gadgetbridge/devices/hplus/HPlusConstants.java | 4 ++-- .../gadgetbridge/devices/hplus/HPlusCoordinator.java | 7 +++++-- .../gadgetbridge/service/devices/hplus/HPlusSupport.java | 8 ++++---- 3 files changed, 11 insertions(+), 8 deletions(-) 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 f69bb453..8cfe6a90 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 @@ -23,8 +23,8 @@ public final class HPlusConstants { public static final byte UNIT_METRIC = 0; public static final byte UNIT_IMPERIAL = 1; - public static final byte SEX_MALE = 0; - public static final byte SEX_FEMALE = 1; + public static final byte PREF_VALUE_GENDER_MALE = 0; + public static final byte PREF_VALUE_GENDER_FEMALE = 1; public static final byte HEARTRATE_MEASURE_ON = 11; public static final byte HEARTRATE_MEASURE_OFF = 22; 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 d1cf8e25..b8848ebe 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 @@ -177,10 +177,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.PREF_VALUE_GENDER_MALE; + + return HPlusConstants.PREF_VALUE_GENDER_FEMALE; } public static int getGoal(String address) { 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 c3704ffe..a944fa49 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 @@ -143,7 +143,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { 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()); @@ -167,8 +167,8 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { byte timemode = HPlusCoordinator.getTimeMode((getDevice().getAddress())); transaction.write(ctrlCharacteristic, new byte[]{ - HPlusConstants.COMMAND_SET_PREF_COUNTRY, - sex, + HPlusConstants.COMMAND_SET_PREFS, + gender, age, bodyHeight, bodyWeight, @@ -345,7 +345,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { private HPlusSupport setSex(TransactionBuilder transaction) { LOG.info("Attempting to set Sex..."); - byte value = HPlusCoordinator.getUserSex(getDevice().getAddress()); + byte value = HPlusCoordinator.getUserGender(getDevice().getAddress()); transaction.write(ctrlCharacteristic, new byte[]{ HPlusConstants.COMMAND_SET_PREF_SEX, value From a135f51d315d897413f609d661760ae3f137a5c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joa=CC=83o=20Paulo=20Barraca?= Date: Wed, 28 Dec 2016 13:52:51 +0000 Subject: [PATCH 02/15] HPlus: Improve initial configuration process and refactor constants --- .../devices/hplus/HPlusConstants.java | 46 +++++++++---------- .../service/devices/hplus/HPlusSupport.java | 40 ++++++++-------- 2 files changed, 45 insertions(+), 41 deletions(-) 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 8cfe6a90..d0f450fa 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 @@ -14,35 +14,33 @@ 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 PREF_VALUE_COUNTRY_CN = 1; + public static final byte PREF_VALUE_COUNTRY_OTHER = 2; - public static final byte CLOCK_24H = 0; - public static final byte CLOCK_12H = 1; + public static final byte PREF_VALUE_CLOCK_24H = 0; + public static final byte PREF_VALUE_CLOCK_12H = 1; - public static final byte UNIT_METRIC = 0; - public static final byte UNIT_IMPERIAL = 1; + public static final byte PREF_VALUE_UNIT_METRIC = 0; + public static final byte PREF_VALUE_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 HEARTRATE_MEASURE_ON = 11; - public static final byte HEARTRATE_MEASURE_OFF = 22; + public static final byte PREF_VALUE_HEARTRATE_MEASURE_ON = 11; + public static final byte PREF_VALUE_HEARTRATE_MEASURE_OFF = 22; - public static final byte HEARTRATE_ALLDAY_ON = 10; - public static final byte HEARTRATE_ALLDAY_OFF = -1; + public static final byte PREF_VALUE_HEARTRATE_ALLDAY_ON = 10; + public static final byte PREF_VALUE_HEARTRATE_ALLDAY_OFF = -1; - 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 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; @@ -58,23 +56,16 @@ public final class HPlusConstants { 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_FACTORY_RESET = new byte[] {-74, 90}; - 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[] COMMAND_FACTORY_RESET = new byte[] {-74, 90}; - public static final byte DATA_STATS = 0x33; - public static final byte DATA_STEPS = 0x36; - - public static final byte DATA_SLEEP = 0x1A; - + //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; @@ -84,6 +75,15 @@ public final class HPlusConstants { public static final byte COMMAND_ACTION_DISPLAY_TEXT_NAME_CN = 0x3E; //Text in GB2312? + //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 DATA_INCOMING_CALL_STATE = 0x18; + + 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/service/devices/hplus/HPlusSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusSupport.java index a944fa49..c93a4f65 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 @@ -107,10 +107,10 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { getDevice().setFirmwareVersion2("0"); //Initialize device - setInitValues(builder); - setCurrentDate(builder); - setCurrentTime(builder); - syncPreferences(builder); + syncPreferences(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); @@ -121,14 +121,6 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { 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); @@ -553,9 +545,9 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { byte state; if (enable) - state = HPlusConstants.HEARTRATE_ALLDAY_ON; + state = HPlusConstants.PREF_VALUE_HEARTRATE_ALLDAY_ON; else - state = HPlusConstants.HEARTRATE_ALLDAY_OFF; + state = HPlusConstants.PREF_VALUE_HEARTRATE_ALLDAY_OFF; builder.write(ctrlCharacteristic, new byte[]{HPlusConstants.COMMAND_SET_PREF_ALLDAYHR, state}); builder.queue(getQueue()); @@ -813,7 +805,11 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { return processSleepStats(data); case HPlusConstants.DATA_STEPS: return processStepStats(data); - + case HPlusConstants.DATA_DAY_SUMMARY: + case HPlusConstants.DATA_DAY_SUMMARY_ALT: + return processDaySummary(data); + case HPlusConstants.DATA_INCOMING_CALL_STATE: + return processIncomingCallState(data); default: LOG.info("Unhandled characteristic changed: " + characteristicUUID); @@ -821,18 +817,25 @@ 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 processDayStats(byte[] data) { + 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 avgHR = data[1]; //Average Heart Rate ? int steps = data[2] * 256 + data[3]; // Steps in this period //?? data[6]; - int timeInactive = data[7]; + int timeInactive = data[7]; // ? LOG.debug("Day Stats: Slot: " + slot + " HR: " + avgHR + " Steps: " + steps + " TimeInactive: " + timeInactive); @@ -997,6 +1000,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { return true; } + public HPlusHealthActivitySample createActivitySample(Device device, User user, int timestampInSeconds, SampleProvider provider) { HPlusHealthActivitySample sample = new HPlusHealthActivitySample(); sample.setDevice(device); 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 03/15] 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; - } } From 547736f8f7ca7060f4f9c99d6ddfca359be43d0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joa=CC=83o=20Paulo=20Barraca?= Date: Mon, 2 Jan 2017 10:13:34 +0000 Subject: [PATCH 04/15] HPlus: removed test values --- .../gadgetbridge/devices/hplus/HPlusHealthSampleProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index eec2fee8..61ce6a2e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/HPlusHealthSampleProvider.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/HPlusHealthSampleProvider.java @@ -101,7 +101,7 @@ public class HPlusHealthSampleProvider extends AbstractSampleProvider qb = getSession().getHPlusHealthActivityOverlayDao().queryBuilder(); - qb.where(HPlusHealthActivityOverlayDao.Properties.DeviceId.eq(dbDevice.getId()), HPlusHealthActivityOverlayDao.Properties.TimestampFrom.ge(timestamp_from - 24 * 60 * 60)) + qb.where(HPlusHealthActivityOverlayDao.Properties.DeviceId.eq(dbDevice.getId()), HPlusHealthActivityOverlayDao.Properties.TimestampFrom.ge(timestamp_from)) .where(HPlusHealthActivityOverlayDao.Properties.TimestampTo.le(timestamp_to)); List overlayRecords = qb.build().list(); From 9d67394720e1df1951b9909a86e13f9f2a9cf8c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joa=CC=83o=20Paulo=20Barraca?= Date: Wed, 4 Jan 2017 01:46:24 +0000 Subject: [PATCH 05/15] HPlus: Code cleanup --- .../devices/hplus/HPlusConstants.java | 1 + .../hplus/HPlusHealthSampleProvider.java | 21 +- .../devices/hplus/HPlusDataRecord.java | 9 +- .../devices/hplus/HPlusDataRecordDay.java | 8 +- .../hplus/HPlusDataRecordRealtime.java | 23 ++- .../devices/hplus/HPlusDataRecordSleep.java | 32 ++- .../devices/hplus/HPlusDataRecordSteps.java | 19 +- .../devices/hplus/HPlusHandlerThread.java | 121 +++++------ .../service/devices/hplus/HPlusSupport.java | 194 +++++++----------- 9 files changed, 188 insertions(+), 240 deletions(-) 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 9f82d13d..70546e87 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 @@ -36,6 +36,7 @@ public final class HPlusConstants { 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; 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 index 61ce6a2e..3fe16e41 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/HPlusHealthSampleProvider.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/HPlusHealthSampleProvider.java @@ -6,9 +6,6 @@ package nodomain.freeyourgadget.gadgetbridge.devices.hplus; 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; @@ -30,8 +27,6 @@ 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; @@ -71,12 +66,12 @@ public class HPlusHealthSampleProvider extends AbstractSampleProvider getAllActivitySamples(int timestamp_from, int timestamp_to) { List samples = super.getGBActivitySamples(timestamp_from, timestamp_to, ActivityKind.TYPE_ALL); @@ -107,11 +103,11 @@ public class HPlusHealthSampleProvider extends AbstractSampleProvider 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()); + 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 (overlay.getTimestampFrom() <= sample.getTimestamp() && sample.getTimestamp() < overlay.getTimestampTo()) { + if (sample.getTimestamp() >= overlay.getTimestampFrom() && sample.getTimestamp() < overlay.getTimestampTo()) { sample.setRawKind(overlay.getRawKind()); } } @@ -119,8 +115,6 @@ public class HPlusHealthSampleProvider extends AbstractSampleProvider() { public int compare(HPlusHealthActivitySample one, HPlusHealthActivitySample other) { return one.getTimestamp() - other.getTimestamp(); @@ -137,7 +131,7 @@ public class HPlusHealthSampleProvider extends AbstractSampleProvider getIntervals() { - List intervals = new ArrayList(); + List intervals = new ArrayList<>(); int ts = bedTimeStart + lightSleepMinutes * 60; intervals.add(new RecordInterval(bedTimeStart, ts, ActivityKind.TYPE_LIGHT_SLEEP)); 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 index cdc18307..33901ddd 100644 --- 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 @@ -5,19 +5,12 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.hplus; */ -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; +class HPlusDataRecordSteps extends HPlusDataRecord{ + public int steps; + public int distance; HPlusDataRecordSteps(byte[] data) { super(data); @@ -45,15 +38,11 @@ public class HPlusDataRecordSteps extends HPlusDataRecord{ 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.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 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 index a197bd4c..fe8ebffc 100644 --- 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 @@ -18,15 +18,12 @@ 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; @@ -34,16 +31,15 @@ import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceIoThread; -public class HPlusHandlerThread extends GBDeviceIoThread { +class HPlusHandlerThread extends GBDeviceIoThread { + private static final Logger LOG = LoggerFactory.getLogger(HPlusHandlerThread.class); 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 int HELLO_INTERVAL = 60; private boolean mQuit = false; private HPlusSupport mHPlusSupport; @@ -57,7 +53,7 @@ public class HPlusHandlerThread extends GBDeviceIoThread { private Calendar mGetDaySlotsTime = Calendar.getInstance(); private Calendar mGetSleepTime = Calendar.getInstance(); - private Object waitObject = new Object(); + private final Object waitObject = new Object(); private HPlusDataRecordRealtime prevRealTimeRecord = null; @@ -80,7 +76,6 @@ public class HPlusHandlerThread extends GBDeviceIoThread { sync(); - boolean starting = true; long waitTime = 0; while (!mQuit) { //LOG.debug("Waiting " + (waitTime)); @@ -147,25 +142,27 @@ public class HPlusHandlerThread extends GBDeviceIoThread { } } - public void sendHello() { - mHelloTime = Calendar.getInstance(); - mHelloTime.add(Calendar.SECOND, HELLO_INTERVAL); - + private void sendHello() { TransactionBuilder builder = new TransactionBuilder("hello"); builder.write(mHPlusSupport.ctrlCharacteristic, HPlusConstants.CMD_ACTION_HELLO); builder.queue(mHPlusSupport.getQueue()); + scheduleHello(); } + public void scheduleHello(){ + mHelloTime = Calendar.getInstance(); + mHelloTime.add(Calendar.SECOND, HELLO_INTERVAL); + } - public void processIncomingDaySlotData(byte[] data) { + public boolean processIncomingDaySlotData(byte[] data) { HPlusDataRecordDay record; try{ record = new HPlusDataRecordDay(data); } catch(IllegalArgumentException e){ LOG.debug((e.getMessage())); - return; + return true; } if ((record.slot == 0 && mLastSlotReceived == 0) || (record.slot == mLastSlotReceived + 1)) { @@ -181,7 +178,7 @@ public class HPlusHandlerThread extends GBDeviceIoThread { deviceId, userId, // User id record.getRawData(), // Raw Data ActivityKind.TYPE_UNKNOWN, - ActivitySample.NOT_MEASURED, // Intensity + 0, // Intensity record.steps, // Steps record.heartRate, // HR ActivitySample.NOT_MEASURED, // Distance @@ -204,11 +201,10 @@ public class HPlusHandlerThread extends GBDeviceIoThread { } } } + return true; } 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); @@ -240,7 +236,7 @@ public class HPlusHandlerThread extends GBDeviceIoThread { return; } - LOG.debug("Making new Request From " + hour + ":" + minute + " to " + nextHour + ":" + nextMinute); + //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"); @@ -251,16 +247,14 @@ public class HPlusHandlerThread extends GBDeviceIoThread { mGetDaySlotsTime.add(Calendar.SECOND, SYNC_RETRY_PERIOD); } - public void processIncomingSleepData(byte[] data){ - LOG.debug("Processing Sleep Data"); - + public boolean processIncomingSleepData(byte[] data){ HPlusDataRecordSleep record; try{ record = new HPlusDataRecordSleep(data); } catch(IllegalArgumentException e){ LOG.debug((e.getMessage())); - return; + return true; } mLastSleepDayReceived.setTimeInMillis(record.bedTimeStart * 1000L); @@ -288,7 +282,7 @@ public class HPlusHandlerThread extends GBDeviceIoThread { deviceId, userId, // User id record.getRawData(), // Raw Data record.activityKind, - ActivitySample.NOT_MEASURED, // Intensity + 0, // Intensity ActivitySample.NOT_MEASURED, // Steps ActivitySample.NOT_MEASURED, // HR ActivitySample.NOT_MEASURED, // Distance @@ -307,11 +301,10 @@ public class HPlusHandlerThread extends GBDeviceIoThread { mGetSleepTime = Calendar.getInstance(); mGetSleepTime.add(Calendar.SECOND, SLEEP_SYNC_PERIOD); + return true; } private void requestNextSleepData() { - LOG.debug("Request New Sleep Data"); - mGetSleepTime = Calendar.getInstance(); mGetSleepTime.add(Calendar.SECOND, SLEEP_RETRY_PERIOD); @@ -321,20 +314,18 @@ public class HPlusHandlerThread extends GBDeviceIoThread { } - public void processRealtimeStats(byte[] data) { - LOG.debug("Processing Real time Stats"); - + public boolean processRealtimeStats(byte[] data) { HPlusDataRecordRealtime record; try{ record = new HPlusDataRecordRealtime(data); } catch(IllegalArgumentException e){ LOG.debug((e.getMessage())); - return; + return true; } if(record.same(prevRealTimeRecord)) - return; + return true; prevRealTimeRecord = record; @@ -345,18 +336,14 @@ public class HPlusHandlerThread extends GBDeviceIoThread { if(record.heartRate == 255) { getDevice().setFirmwareVersion2("---"); getDevice().sendDeviceUpdateIntent(getContext()); - return; + return true; } - getDevice().setFirmwareVersion2(""+record.heartRate); + 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(); @@ -365,7 +352,7 @@ public class HPlusHandlerThread extends GBDeviceIoThread { deviceId, userId, // User id record.getRawData(), // Raw Data record.activityKind, - ActivitySample.NOT_MEASURED, // Intensity + record.intensity, // Intensity ActivitySample.NOT_MEASURED, // Steps record.heartRate, // HR record.distance, // Distance @@ -375,32 +362,25 @@ public class HPlusHandlerThread extends GBDeviceIoThread { 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); - } + //TODO: Handle Active Time. With Overlay? } catch (GBException ex) { LOG.debug((ex.getMessage())); } catch (Exception ex) { LOG.debug(ex.getMessage()); } + return true; } - public void processStepStats(byte[] data) { - LOG.debug("Processing Step Stats"); + public boolean processStepStats(byte[] data) { HPlusDataRecordSteps record; try{ record = new HPlusDataRecordSteps(data); } catch(IllegalArgumentException e){ LOG.debug((e.getMessage())); - return; + return true; } try (DBHandler dbHandler = GBApplication.acquireDB()) { @@ -408,17 +388,40 @@ public class HPlusHandlerThread extends GBDeviceIoThread { Long userId = DBHelper.getUser(dbHandler.getDaoSession()).getId(); Long deviceId = DBHelper.getDevice(getDevice(), dbHandler.getDaoSession()).getId(); + + //Hugly (?) fix. + //This message returns the day summary, but the DB already has some detailed entries with steps and distance. + //However DB data is probably incomplete as some update messages could be missing + //Proposed fix: Calculate the total steps and distance and store a new sample with the remaining data + //Existing data will reflect user activity with the issue of a potencially large number of steps at midnight. + //Steps counters by day will be OK with this + + List samples = provider.getActivitySamples(record.timestamp - 3600 * 24 + 1, record.timestamp); + + int missingDistance = record.distance; + int missingSteps = record.steps; + + for(HPlusHealthActivitySample sample : samples){ + if(sample.getSteps() > 0) { + missingSteps -= sample.getSteps(); + } + if(sample.getDistance() > 0){ + missingDistance -= sample.getDistance(); + } + } + 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 + 0, // Intensity + Math.max( missingSteps, 0), // Steps ActivitySample.NOT_MEASURED, // HR - record.distance, // Distance + Math.max( missingDistance, 0), // Distance ActivitySample.NOT_MEASURED // Calories ); + sample.setProvider(provider); provider.addGBActivitySample(sample); } catch (GBException ex) { @@ -426,11 +429,11 @@ public class HPlusHandlerThread extends GBDeviceIoThread { } catch (Exception ex) { LOG.debug(ex.getMessage()); } + + return true; } public boolean processVersion(byte[] data) { - LOG.debug("Process Version"); - int major = data[2] & 0xFF; int minor = data[1] & 0xFF; @@ -440,14 +443,4 @@ public class HPlusHandlerThread extends GBDeviceIoThread { 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/HPlusSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusSupport.java index 65350305..1deda41d 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 @@ -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; @@ -23,7 +22,6 @@ import java.util.ArrayList; import java.util.Calendar; import java.util.UUID; -import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo; import nodomain.freeyourgadget.gadgetbridge.devices.hplus.HPlusConstants; import nodomain.freeyourgadget.gadgetbridge.devices.hplus.HPlusCoordinator; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; @@ -35,10 +33,10 @@ import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; 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.deviceinfo.DeviceInfo; import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfoProfile; import nodomain.freeyourgadget.gadgetbridge.util.GB; @@ -49,9 +47,6 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { public BluetoothGattCharacteristic ctrlCharacteristic = null; public BluetoothGattCharacteristic measureCharacteristic = null; - private int[] lastDataStats = null; - - private final GBDeviceEventVersionInfo versionCmd = new GBDeviceEventVersionInfo(); private HPlusHandlerThread syncHelper; private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @@ -82,10 +77,10 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { LOG.debug("Dispose"); LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(getContext()); broadcastManager.unregisterReceiver(mReceiver); - super.dispose(); - if(syncHelper != null) - syncHelper.quit(); + close(); + + super.dispose(); } @Override @@ -112,15 +107,12 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { setInitialized(builder); - syncHelper.start(); builder.notify(getCharacteristic(HPlusConstants.UUID_CHARACTERISTIC_MEASURE), true); builder.setGattCallback(this); builder.notify(measureCharacteristic, true); - //LOG.debug("Initialization Done"); - return builder; } @@ -135,8 +127,6 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { } private HPlusSupport syncPreferences(TransactionBuilder transaction) { - LOG.info("Attempting to sync preferences with: " + getDevice().getAddress()); - byte gender = HPlusCoordinator.getUserGender(getDevice().getAddress()); byte age = HPlusCoordinator.getUserAge(getDevice().getAddress()); byte bodyHeight = HPlusCoordinator.getUserHeight(getDevice().getAddress()); @@ -189,8 +179,6 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { } private HPlusSupport setLanguage(TransactionBuilder transaction) { - LOG.info("Attempting to set language..."); - byte value = HPlusCoordinator.getCountry(getDevice().getAddress()); transaction.write(ctrlCharacteristic, new byte[]{ HPlusConstants.CMD_SET_LANGUAGE, @@ -201,8 +189,6 @@ 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.CMD_SET_TIMEMODE, @@ -212,9 +198,6 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { } private HPlusSupport setUnit(TransactionBuilder transaction) { - LOG.info("Attempting to set Units..."); - - byte value = HPlusCoordinator.getUnit(getDevice().getAddress()); transaction.write(ctrlCharacteristic, new byte[]{ HPlusConstants.CMD_SET_UNITS, @@ -224,8 +207,6 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { } private HPlusSupport setCurrentDate(TransactionBuilder transaction) { - LOG.info("Attempting to set Current Date..."); - Calendar c = Calendar.getInstance(); int year = c.get(Calendar.YEAR) - 1900; int month = c.get(Calendar.MONTH); @@ -243,8 +224,6 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { } private HPlusSupport setCurrentTime(TransactionBuilder transaction) { - LOG.info("Attempting to set Current Time..."); - Calendar c = Calendar.getInstance(); transaction.write(ctrlCharacteristic, new byte[]{ @@ -259,8 +238,6 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { private HPlusSupport setDayOfWeek(TransactionBuilder transaction) { - LOG.info("Attempting to set Day Of Week..."); - Calendar c = Calendar.getInstance(); transaction.write(ctrlCharacteristic, new byte[]{ @@ -272,8 +249,6 @@ 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()); @@ -291,7 +266,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, @@ -304,8 +279,6 @@ 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.CMD_SET_WEIGHT, @@ -316,8 +289,6 @@ 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.CMD_HEIGHT, @@ -329,8 +300,6 @@ 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.CMD_SET_AGE, @@ -341,8 +310,6 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { } private HPlusSupport setGender(TransactionBuilder transaction) { - LOG.info("Attempting to set Gender..."); - byte value = HPlusCoordinator.getUserGender(getDevice().getAddress()); transaction.write(ctrlCharacteristic, new byte[]{ HPlusConstants.CMD_SET_GENDER, @@ -354,8 +321,6 @@ 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.CMD_SET_GOAL, @@ -368,8 +333,6 @@ 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.CMD_SET_SCREENTIME, @@ -392,28 +355,35 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { } - 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!"); - // 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}); @@ -433,31 +403,50 @@ 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 + //TODO: Show different notifications according to source as Band supports this showText(notificationSpec.title, notificationSpec.body); } - @Override public void onSetTime() { TransactionBuilder builder = new TransactionBuilder("time"); setCurrentDate(builder); setCurrentTime(builder); + + builder.queue(getQueue()); } @Override public void onSetAlarms(ArrayList 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 + } } @@ -524,19 +513,22 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { @Override public void onFetchActivityData() { - if(syncHelper != null) + 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"); @@ -547,8 +539,6 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { @Override public void onEnableRealtimeHeartRateMeasurement(boolean enable) { - LOG.debug("Set Real Time HR Measurement: " + enable); - getQueue().clear(); TransactionBuilder builder = new TransactionBuilder("realTimeHeartMeasurement"); @@ -561,35 +551,24 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { 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.CMD_SET_FINDME; - - if (start) - msg[1] = HPlusConstants.ARG_FINDME_ON; - else - msg[1] = HPlusConstants.ARG_FINDME_OFF; - - 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 { @@ -615,6 +594,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { @Override public void onEnableHeartRateSleepSupport(boolean enable) { + onEnableRealtimeHeartRateMeasurement(enable); } @@ -641,8 +621,6 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { private void showIncomingCall(String name, String number) { - LOG.debug("Show Incoming Call"); - try { TransactionBuilder builder = performInitialized("incomingCallIcon"); @@ -652,7 +630,6 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { //Show Call Icon builder.write(ctrlCharacteristic, new byte[]{HPlusConstants.CMD_ACTION_INCOMING_CALL, HPlusConstants.ARG_INCOMING_CALL}); - //builder = performInitialized("incomingCallText"); builder.queue(getQueue()); //TODO: Use WaitAction @@ -714,18 +691,10 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { } } - private void showText(String message) { - - showText(null, message); - } - private void showText(String title, String body) { - LOG.debug("Show Notification"); - try { TransactionBuilder builder = performInitialized("notification"); - byte[] msg = new byte[20]; for (int i = 0; i < msg.length; i++) msg[i] = ' '; @@ -790,11 +759,11 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { } } - public boolean isExpectedDevice(BluetoothDevice device) { - return true; - } - - public void close() { + private void close() { + if (syncHelper != null) { + syncHelper.quit(); + syncHelper = null; + } } @Override @@ -811,29 +780,24 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { switch (data[0]) { 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; - } + return syncHelper.processVersion(data); + + case HPlusConstants.DATA_STATS: + return syncHelper.processRealtimeStats(data); + + case HPlusConstants.DATA_SLEEP: + return syncHelper.processIncomingSleepData(data); + + case HPlusConstants.DATA_STEPS: + return syncHelper.processStepStats(data); + case HPlusConstants.DATA_DAY_SUMMARY: case HPlusConstants.DATA_DAY_SUMMARY_ALT: - syncHelper.processIncomingDaySlotData(data); - return true; + return syncHelper.processIncomingDaySlotData(data); + default: - LOG.info("Unhandled characteristic changed: " + characteristicUUID); - + LOG.debug("Unhandled characteristic changed: " + characteristicUUID); + return true; } - return false; } - - } From 91b346b23de79089d73f7c6e950c7a3c73ff56b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joa=CC=83o=20Paulo=20Barraca?= Date: Wed, 4 Jan 2017 23:24:17 +0000 Subject: [PATCH 06/15] HPlus: Enabled decoding of additional fields in day summary message --- .../devices/hplus/HPlusDataRecordSteps.java | 37 +++++++++++++------ 1 file changed, 26 insertions(+), 11 deletions(-) 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 index 33901ddd..a1bd3e4e 100644 --- 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 @@ -6,33 +6,44 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.hplus; import java.util.Calendar; +import java.util.Locale; class HPlusDataRecordSteps extends HPlusDataRecord{ + public int year; + public int month; + public int day; + public int steps; public int distance; + public int activeTime; + public int maxHeartRate; + public int minHeartRate; + public int calories; + HPlusDataRecordSteps(byte[] data) { super(data); - int year = (data[10] & 0xFF) * 256 + (data[9] & 0xFF); - int month = data[11] & 0xFF; - int day = data[12] & 0xFF; + 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 + 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); + activeTime = (data[14] & 0xFF) * 256 + (data[13] & 0xFF); + calories = (data[6] & 0xFF) * 256 + (data[5] & 0xFF); + calories += (data[8] & 0xFF) * 256 + (data[7] & 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]; - */ + maxHeartRate = data[15] & 0xFF; + minHeartRate = data[16] & 0xFF; Calendar date = Calendar.getInstance(); date.set(Calendar.YEAR, year); @@ -45,4 +56,8 @@ class HPlusDataRecordSteps extends HPlusDataRecord{ 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); + } } \ No newline at end of file From 510427e30be1f1ca436132b038dc35ae508effcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joa=CC=83o=20Paulo=20Barraca?= Date: Wed, 4 Jan 2017 23:41:35 +0000 Subject: [PATCH 07/15] HPlus: Refactoring. Calendar -> GregorianCalendar --- .../devices/hplus/HPlusDataRecordDay.java | 5 ++- .../hplus/HPlusDataRecordRealtime.java | 4 +- .../devices/hplus/HPlusDataRecordSleep.java | 6 +-- .../devices/hplus/HPlusDataRecordSteps.java | 3 +- .../devices/hplus/HPlusHandlerThread.java | 41 +++++++++---------- .../service/devices/hplus/HPlusSupport.java | 9 ++-- 6 files changed, 34 insertions(+), 34 deletions(-) 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 index b0481101..c6a8603f 100644 --- 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 @@ -4,7 +4,8 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.hplus; * @author João Paulo Barraca <jpbarraca@gmail.com> */ -import java.util.Calendar; +import java.util.GregorianCalendar; + import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; @@ -33,7 +34,7 @@ public class HPlusDataRecordDay extends HPlusDataRecord { //?? data[6]; secondsInactive = data[7] & 0xFF; // ? - int now = (int) (Calendar.getInstance().getTimeInMillis() / (3600 * 24 * 1000L)); + int now = (int) (GregorianCalendar.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 index 4c9eb9ee..8a3750ec 100644 --- 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 @@ -5,7 +5,7 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.hplus; */ -import java.util.Calendar; +import java.util.GregorianCalendar; import java.util.Locale; import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; @@ -26,7 +26,7 @@ class HPlusDataRecordRealtime extends HPlusDataRecord { throw new IllegalArgumentException("Invalid data packet"); } - timestamp = (int) (Calendar.getInstance().getTimeInMillis() / 1000); + timestamp = (int) (GregorianCalendar.getInstance().getTimeInMillis() / 1000); distance = 10 * ((data[4] & 0xFF) * 256 + (data[3] & 0xFF)); // meters int x = (data[6] & 0xFF) * 256 + data[5] & 0xFF; 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 index 9a898f6f..b7dbdd42 100644 --- 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 @@ -7,6 +7,7 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.hplus; import java.util.ArrayList; import java.util.Calendar; +import java.util.GregorianCalendar; import java.util.List; import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; @@ -47,7 +48,7 @@ public class HPlusDataRecordSleep extends HPlusDataRecord { int hour = data[17] & 0xFF; int minute = data[18] & 0xFF; - Calendar sleepStart = Calendar.getInstance(); + Calendar sleepStart = GregorianCalendar.getInstance(); sleepStart.set(Calendar.YEAR, year); sleepStart.set(Calendar.MONTH, month - 1); sleepStart.set(Calendar.DAY_OF_MONTH, day); @@ -61,9 +62,6 @@ public class HPlusDataRecordSleep extends HPlusDataRecord { lightSleepMinutes = enterSleepMinutes + spindleMinutes + remSleepMinutes; timestamp = bedTimeStart; - - Calendar sleepEnd = Calendar.getInstance(); - sleepEnd.setTimeInMillis(bedTimeEnd * 1000L); } public List getIntervals() { 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 index a1bd3e4e..997a09ed 100644 --- 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 @@ -6,6 +6,7 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.hplus; import java.util.Calendar; +import java.util.GregorianCalendar; import java.util.Locale; @@ -45,7 +46,7 @@ class HPlusDataRecordSteps extends HPlusDataRecord{ maxHeartRate = data[15] & 0xFF; minHeartRate = data[16] & 0xFF; - Calendar date = Calendar.getInstance(); + Calendar date = GregorianCalendar.getInstance(); date.set(Calendar.YEAR, year); date.set(Calendar.MONTH, month - 1); date.set(Calendar.DAY_OF_MONTH, day); 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 index fe8ebffc..fcb7f6a4 100644 --- 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 @@ -12,6 +12,7 @@ import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.Calendar; +import java.util.GregorianCalendar; import java.util.List; import nodomain.freeyourgadget.gadgetbridge.GBApplication; @@ -46,17 +47,15 @@ class HPlusHandlerThread extends GBDeviceIoThread { 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 final Object waitObject = new Object(); + private Calendar mLastSleepDayReceived = GregorianCalendar.getInstance(); + private Calendar mHelloTime = GregorianCalendar.getInstance(); + private Calendar mGetDaySlotsTime = GregorianCalendar.getInstance(); + private Calendar mGetSleepTime = GregorianCalendar.getInstance(); private HPlusDataRecordRealtime prevRealTimeRecord = null; + private final Object waitObject = new Object(); + public HPlusHandlerThread(GBDevice gbDevice, Context context, HPlusSupport hplusSupport) { super(gbDevice, context); @@ -92,7 +91,7 @@ class HPlusHandlerThread extends GBDeviceIoThread { break; } - Calendar now = Calendar.getInstance(); + Calendar now = GregorianCalendar.getInstance(); if (now.compareTo(mHelloTime) > 0) { sendHello(); @@ -106,7 +105,7 @@ class HPlusHandlerThread extends GBDeviceIoThread { requestNextSleepData(); } - now = Calendar.getInstance(); + now = GregorianCalendar.getInstance(); waitTime = Math.min(Math.min(mGetDaySlotsTime.getTimeInMillis(), mGetSleepTime.getTimeInMillis()), mHelloTime.getTimeInMillis()) - now.getTimeInMillis(); } @@ -151,7 +150,7 @@ class HPlusHandlerThread extends GBDeviceIoThread { } public void scheduleHello(){ - mHelloTime = Calendar.getInstance(); + mHelloTime = GregorianCalendar.getInstance(); mHelloTime.add(Calendar.SECOND, HELLO_INTERVAL); } @@ -211,8 +210,8 @@ class HPlusHandlerThread extends GBDeviceIoThread { byte nextMinute = 0; - if (nextHour == (byte) Calendar.getInstance().get(Calendar.HOUR_OF_DAY)) { - nextMinute = (byte) Calendar.getInstance().get(Calendar.MINUTE); + if (nextHour == (byte) GregorianCalendar.getInstance().get(Calendar.HOUR_OF_DAY)) { + nextMinute = (byte) GregorianCalendar.getInstance().get(Calendar.MINUTE); } byte minute = (byte) ((mLastSlotReceived % 6) * 10); @@ -223,15 +222,15 @@ class HPlusHandlerThread extends GBDeviceIoThread { LOG.debug("Reached End of the Day"); mLastSlotRequested = 0; mLastSlotReceived = 0; - mGetDaySlotsTime = Calendar.getInstance(); + mGetDaySlotsTime = GregorianCalendar.getInstance(); mGetDaySlotsTime.add(Calendar.SECOND, SYNC_PERIOD); return; } - if (nextHour > Calendar.getInstance().get(Calendar.HOUR_OF_DAY)) { + if (nextHour > GregorianCalendar.getInstance().get(GregorianCalendar.HOUR_OF_DAY)) { LOG.debug("Day data is up to date"); - mGetDaySlotsTime = Calendar.getInstance(); + mGetDaySlotsTime = GregorianCalendar.getInstance(); mGetDaySlotsTime.add(Calendar.SECOND, SYNC_PERIOD); return; } @@ -243,7 +242,7 @@ class HPlusHandlerThread extends GBDeviceIoThread { builder.write(mHPlusSupport.ctrlCharacteristic, msg); builder.queue(mHPlusSupport.getQueue()); - mGetDaySlotsTime = Calendar.getInstance(); + mGetDaySlotsTime = GregorianCalendar.getInstance(); mGetDaySlotsTime.add(Calendar.SECOND, SYNC_RETRY_PERIOD); } @@ -298,15 +297,15 @@ class HPlusHandlerThread extends GBDeviceIoThread { LOG.debug(ex.getMessage()); } - mGetSleepTime = Calendar.getInstance(); - mGetSleepTime.add(Calendar.SECOND, SLEEP_SYNC_PERIOD); + mGetSleepTime = GregorianCalendar.getInstance(); + mGetSleepTime.add(GregorianCalendar.SECOND, SLEEP_SYNC_PERIOD); return true; } private void requestNextSleepData() { - mGetSleepTime = Calendar.getInstance(); - mGetSleepTime.add(Calendar.SECOND, SLEEP_RETRY_PERIOD); + mGetSleepTime = GregorianCalendar.getInstance(); + mGetSleepTime.add(GregorianCalendar.SECOND, SLEEP_RETRY_PERIOD); TransactionBuilder builder = new TransactionBuilder("requestSleepStats"); builder.write(mHPlusSupport.ctrlCharacteristic, new byte[]{HPlusConstants.CMD_GET_SLEEP}); 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 1deda41d..54fd18c5 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 @@ -20,6 +20,7 @@ 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.devices.hplus.HPlusConstants; @@ -207,7 +208,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { } private HPlusSupport setCurrentDate(TransactionBuilder transaction) { - Calendar c = Calendar.getInstance(); + Calendar c = GregorianGregorianCalendar.getInstance(); int year = c.get(Calendar.YEAR) - 1900; int month = c.get(Calendar.MONTH); int day = c.get(Calendar.DAY_OF_MONTH); @@ -224,7 +225,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { } private HPlusSupport setCurrentTime(TransactionBuilder transaction) { - Calendar c = Calendar.getInstance(); + Calendar c = GregorianCalendar.getInstance(); transaction.write(ctrlCharacteristic, new byte[]{ HPlusConstants.CMD_SET_TIME, @@ -238,7 +239,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { private HPlusSupport setDayOfWeek(TransactionBuilder transaction) { - Calendar c = Calendar.getInstance(); + Calendar c = GregorianCalendar.getInstance(); transaction.write(ctrlCharacteristic, new byte[]{ HPlusConstants.CMD_SET_WEEK, @@ -252,7 +253,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { 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.CMD_SET_SIT_INTERVAL, From ae0718c398529fda1c70f14211da6e81c406563d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joa=CC=83o=20Paulo=20Barraca?= Date: Thu, 5 Jan 2017 00:03:54 +0000 Subject: [PATCH 08/15] HPlus: Refactoring and added comments to the message decoders members --- .../devices/hplus/HPlusDataRecord.java | 18 ++++++++ ...rdDay.java => HPlusDataRecordDaySlot.java} | 28 ++++++++++--- .../hplus/HPlusDataRecordRealtime.java | 25 +++++++++++ .../devices/hplus/HPlusDataRecordSleep.java | 42 +++++++++++++++++++ .../devices/hplus/HPlusDataRecordSteps.java | 37 ++++++++++++++++ .../devices/hplus/HPlusHandlerThread.java | 4 +- 6 files changed, 147 insertions(+), 7 deletions(-) rename app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/{HPlusDataRecordDay.java => HPlusDataRecordDaySlot.java} (59%) 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 index 7e854d68..d2c96471 100644 --- 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 @@ -11,7 +11,14 @@ public class HPlusDataRecord { public final static int TYPE_SLEEP = 1; 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; public HPlusDataRecord(byte[] data){ @@ -24,8 +31,19 @@ public class HPlusDataRecord { } 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) { 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/HPlusDataRecordDaySlot.java similarity index 59% rename from app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusDataRecordDay.java rename to app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusDataRecordDaySlot.java index c6a8603f..3e3b9ad1 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusDataRecordDay.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusDataRecordDaySlot.java @@ -9,13 +9,31 @@ import java.util.GregorianCalendar; import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; -public class HPlusDataRecordDay extends HPlusDataRecord { +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 HPlusDataRecordDay(byte[] data) { + public HPlusDataRecordDaySlot(byte[] data) { super(data); int a = (data[4] & 0xFF) * 256 + (data[5] & 0xFF); @@ -23,13 +41,13 @@ public class HPlusDataRecordDay extends HPlusDataRecord { 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 ? + slot = a; + heartRate = data[1] & 0xFF; if(heartRate == 255) heartRate = ActivityKind.TYPE_NOT_MEASURED; - steps = (data[2] & 0xFF) * 256 + data[3] & 0xFF; // Steps in this period + steps = (data[2] & 0xFF) * 256 + data[3] & 0xFF; //?? data[6]; secondsInactive = data[7] & 0xFF; // ? 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 index 8a3750ec..d827976d 100644 --- 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 @@ -12,11 +12,36 @@ 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; + + /** + * Time active (To be determined how it works) + */ public int activeTime; + + /** + * Computing intensity + * To be calculated appropriately + */ public int intensity; public HPlusDataRecordRealtime(byte[] data) { 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 index b7dbdd42..97ea0294 100644 --- 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 @@ -14,14 +14,56 @@ 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) { 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 index 997a09ed..0d8e6c34 100644 --- 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 @@ -11,16 +11,52 @@ import java.util.Locale; class HPlusDataRecordSteps 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; HPlusDataRecordSteps(byte[] data) { @@ -31,6 +67,7 @@ class HPlusDataRecordSteps extends HPlusDataRecord{ 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; 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 index fcb7f6a4..5f960392 100644 --- 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 @@ -156,9 +156,9 @@ class HPlusHandlerThread extends GBDeviceIoThread { public boolean processIncomingDaySlotData(byte[] data) { - HPlusDataRecordDay record; + HPlusDataRecordDaySlot record; try{ - record = new HPlusDataRecordDay(data); + record = new HPlusDataRecordDaySlot(data); } catch(IllegalArgumentException e){ LOG.debug((e.getMessage())); return true; From 051d1e739094bddde285f0c306bc9f230be39005 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joa=CC=83o=20Paulo=20Barraca?= Date: Thu, 5 Jan 2017 00:17:28 +0000 Subject: [PATCH 09/15] HPlus: fix typo --- .../gadgetbridge/service/devices/hplus/HPlusSupport.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 54fd18c5..da76d7c9 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 @@ -208,7 +208,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { } private HPlusSupport setCurrentDate(TransactionBuilder transaction) { - Calendar c = GregorianGregorianCalendar.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); From 93ae57bd605aa5b738a59469d0651dabfae882a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joa=CC=83o=20Paulo=20Barraca?= Date: Thu, 5 Jan 2017 10:33:37 +0000 Subject: [PATCH 10/15] Refactoring class HPlusDataRecordDaySummary --- ...ps.java => HPlusDataRecordDaySummary.java} | 4 +- .../devices/hplus/HPlusHandlerThread.java | 40 +++++++++++++------ 2 files changed, 29 insertions(+), 15 deletions(-) rename app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/{HPlusDataRecordSteps.java => HPlusDataRecordDaySummary.java} (96%) 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/HPlusDataRecordDaySummary.java similarity index 96% rename from app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusDataRecordSteps.java rename to app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusDataRecordDaySummary.java index 0d8e6c34..0bb6609c 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusDataRecordSteps.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusDataRecordDaySummary.java @@ -10,7 +10,7 @@ import java.util.GregorianCalendar; import java.util.Locale; -class HPlusDataRecordSteps extends HPlusDataRecord{ +class HPlusDataRecordDaySummary extends HPlusDataRecord{ /** * Year of the record reported by the device @@ -59,7 +59,7 @@ class HPlusDataRecordSteps extends HPlusDataRecord{ */ public int calories; - HPlusDataRecordSteps(byte[] data) { + HPlusDataRecordDaySummary(byte[] data) { super(data); year = (data[10] & 0xFF) * 256 + (data[9] & 0xFF); 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 index 5f960392..caf9435f 100644 --- 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 @@ -35,10 +35,12 @@ import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceIoThread; class HPlusHandlerThread extends GBDeviceIoThread { private static final Logger LOG = LoggerFactory.getLogger(HPlusHandlerThread.class); - private int SYNC_PERIOD = 60 * 10; - private int SYNC_RETRY_PERIOD = 6; + private int CURRENT_DAY_SYNC_PERIOD = 60 * 10; + private int CURRENT_DAY_SYNC_RETRY_PERIOD = 6; private int SLEEP_SYNC_PERIOD = 12 * 60 * 60; - private int SLEEP_RETRY_PERIOD = 30; + private int SLEEP_SYNC_RETRY_PERIOD = 30; + + private int DAY_SUMMARY_SYNC_PERIOD = 24 * 60 * 60; private int HELLO_INTERVAL = 60; @@ -51,6 +53,7 @@ class HPlusHandlerThread extends GBDeviceIoThread { private Calendar mHelloTime = GregorianCalendar.getInstance(); private Calendar mGetDaySlotsTime = GregorianCalendar.getInstance(); private Calendar mGetSleepTime = GregorianCalendar.getInstance(); + private Calendar mGetDaySummaryTime = GregorianCalendar.getInstance(); private HPlusDataRecordRealtime prevRealTimeRecord = null; @@ -62,10 +65,6 @@ class HPlusHandlerThread extends GBDeviceIoThread { mQuit = false; mHPlusSupport = hplusSupport; - - mLastSleepDayReceived.setTimeInMillis(0); - mGetSleepTime.setTimeInMillis(0); - mGetDaySlotsTime.setTimeInMillis(0); } @@ -105,6 +104,10 @@ class HPlusHandlerThread extends GBDeviceIoThread { requestNextSleepData(); } + if(now.compareTo(mGetDaySummaryTime) > 0) { + requestDaySummaryData(); + } + now = GregorianCalendar.getInstance(); waitTime = Math.min(Math.min(mGetDaySlotsTime.getTimeInMillis(), mGetSleepTime.getTimeInMillis()), mHelloTime.getTimeInMillis()) - now.getTimeInMillis(); } @@ -122,6 +125,7 @@ class HPlusHandlerThread extends GBDeviceIoThread { public void sync() { mGetSleepTime.setTimeInMillis(0); mGetDaySlotsTime.setTimeInMillis(0); + mGetDaySummaryTime.setTimeInMillis(0); TransactionBuilder builder = new TransactionBuilder("startSyncDayStats"); @@ -154,6 +158,14 @@ class HPlusHandlerThread extends GBDeviceIoThread { mHelloTime.add(Calendar.SECOND, HELLO_INTERVAL); } + public void requestDaySummaryData(){ + TransactionBuilder builder = new TransactionBuilder("startSyncDaySummary"); + builder.write(mHPlusSupport.ctrlCharacteristic, new byte[]{HPlusConstants.CMD_GET_DAY_DATA}); + builder.queue(mHPlusSupport.getQueue()); + + mGetDaySummaryTime.add(Calendar.SECOND, DAY_SUMMARY_SYNC_PERIOD); + } + public boolean processIncomingDaySlotData(byte[] data) { HPlusDataRecordDaySlot record; @@ -223,7 +235,7 @@ class HPlusHandlerThread extends GBDeviceIoThread { mLastSlotRequested = 0; mLastSlotReceived = 0; mGetDaySlotsTime = GregorianCalendar.getInstance(); - mGetDaySlotsTime.add(Calendar.SECOND, SYNC_PERIOD); + mGetDaySlotsTime.add(Calendar.SECOND, CURRENT_DAY_SYNC_PERIOD); return; } @@ -231,7 +243,7 @@ class HPlusHandlerThread extends GBDeviceIoThread { if (nextHour > GregorianCalendar.getInstance().get(GregorianCalendar.HOUR_OF_DAY)) { LOG.debug("Day data is up to date"); mGetDaySlotsTime = GregorianCalendar.getInstance(); - mGetDaySlotsTime.add(Calendar.SECOND, SYNC_PERIOD); + mGetDaySlotsTime.add(Calendar.SECOND, CURRENT_DAY_SYNC_PERIOD); return; } @@ -243,7 +255,7 @@ class HPlusHandlerThread extends GBDeviceIoThread { builder.queue(mHPlusSupport.getQueue()); mGetDaySlotsTime = GregorianCalendar.getInstance(); - mGetDaySlotsTime.add(Calendar.SECOND, SYNC_RETRY_PERIOD); + mGetDaySlotsTime.add(Calendar.SECOND, CURRENT_DAY_SYNC_RETRY_PERIOD); } public boolean processIncomingSleepData(byte[] data){ @@ -305,7 +317,7 @@ class HPlusHandlerThread extends GBDeviceIoThread { private void requestNextSleepData() { mGetSleepTime = GregorianCalendar.getInstance(); - mGetSleepTime.add(GregorianCalendar.SECOND, SLEEP_RETRY_PERIOD); + mGetSleepTime.add(GregorianCalendar.SECOND, SLEEP_SYNC_RETRY_PERIOD); TransactionBuilder builder = new TransactionBuilder("requestSleepStats"); builder.write(mHPlusSupport.ctrlCharacteristic, new byte[]{HPlusConstants.CMD_GET_SLEEP}); @@ -373,14 +385,16 @@ class HPlusHandlerThread extends GBDeviceIoThread { public boolean processStepStats(byte[] data) { - HPlusDataRecordSteps record; + LOG.debug("Process Step Stats"); + HPlusDataRecordDaySummary record; try{ - record = new HPlusDataRecordSteps(data); + record = new HPlusDataRecordDaySummary(data); } catch(IllegalArgumentException e){ LOG.debug((e.getMessage())); return true; } + LOG.debug("Received: " + record); try (DBHandler dbHandler = GBApplication.acquireDB()) { HPlusHealthSampleProvider provider = new HPlusHealthSampleProvider(getDevice(), dbHandler.getDaoSession()); From d491921b1c89b899e0d588c7ca56a4147fd917fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joa=CC=83o=20Paulo=20Barraca?= Date: Thu, 5 Jan 2017 10:36:22 +0000 Subject: [PATCH 11/15] HPlus: Rename HPlusHandlerThread method --- .../service/devices/hplus/HPlusHandlerThread.java | 4 ++-- .../gadgetbridge/service/devices/hplus/HPlusSupport.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) 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 index caf9435f..e564b4b1 100644 --- 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 @@ -384,8 +384,8 @@ class HPlusHandlerThread extends GBDeviceIoThread { } - public boolean processStepStats(byte[] data) { - LOG.debug("Process Step Stats"); + public boolean processDaySummary(byte[] data) { + LOG.debug("Process Day Summary"); HPlusDataRecordDaySummary record; try{ 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 da76d7c9..17e9e666 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 @@ -790,7 +790,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { return syncHelper.processIncomingSleepData(data); case HPlusConstants.DATA_STEPS: - return syncHelper.processStepStats(data); + return syncHelper.processDaySummary(data); case HPlusConstants.DATA_DAY_SUMMARY: case HPlusConstants.DATA_DAY_SUMMARY_ALT: From ade7161c4d21fbabe82298e380c6b88ab323d7a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joa=CC=83o=20Paulo=20Barraca?= Date: Thu, 5 Jan 2017 14:03:49 +0000 Subject: [PATCH 12/15] HPlus: Buffer Day Slot data before commit to DB --- .../devices/hplus/HPlusDataRecordDaySlot.java | 4 + .../devices/hplus/HPlusHandlerThread.java | 129 ++++++++++-------- 2 files changed, 75 insertions(+), 58 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusDataRecordDaySlot.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusDataRecordDaySlot.java index 3e3b9ad1..5f9e310d 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusDataRecordDaySlot.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusDataRecordDaySlot.java @@ -5,6 +5,7 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.hplus; */ import java.util.GregorianCalendar; +import java.util.Locale; import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; @@ -56,4 +57,7 @@ public class HPlusDataRecordDaySlot extends HPlusDataRecord { timestamp = now * 3600 * 24 + (slot / 6 * 3600 + slot % 6 * 10); } + public String toString(){ + return String.format(Locale.US, "Slot: %d, Steps: %d, InactiveSeconds: %d, HeartRate: %d", slot, steps, secondsInactive, heartRate); + } } 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 index e564b4b1..c40abfae 100644 --- 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 @@ -36,7 +36,7 @@ class HPlusHandlerThread extends GBDeviceIoThread { private static final Logger LOG = LoggerFactory.getLogger(HPlusHandlerThread.class); private int CURRENT_DAY_SYNC_PERIOD = 60 * 10; - private int CURRENT_DAY_SYNC_RETRY_PERIOD = 6; + private int CURRENT_DAY_SYNC_RETRY_PERIOD = 10; private int SLEEP_SYNC_PERIOD = 12 * 60 * 60; private int SLEEP_SYNC_RETRY_PERIOD = 30; @@ -48,6 +48,7 @@ class HPlusHandlerThread extends GBDeviceIoThread { private HPlusSupport mHPlusSupport; private int mLastSlotReceived = 0; private int mLastSlotRequested = 0; + private int mSlotsToRequest = 6; private Calendar mLastSleepDayReceived = GregorianCalendar.getInstance(); private Calendar mHelloTime = GregorianCalendar.getInstance(); @@ -58,6 +59,7 @@ class HPlusHandlerThread extends GBDeviceIoThread { private HPlusDataRecordRealtime prevRealTimeRecord = null; private final Object waitObject = new Object(); + List mDaySlotSamples = new ArrayList<>(); public HPlusHandlerThread(GBDevice gbDevice, Context context, HPlusSupport hplusSupport) { super(gbDevice, context); @@ -163,6 +165,7 @@ class HPlusHandlerThread extends GBDeviceIoThread { 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_PERIOD); } @@ -175,87 +178,97 @@ class HPlusHandlerThread extends GBDeviceIoThread { LOG.debug((e.getMessage())); return true; } + mLastSlotReceived = record.slot; - 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()); - 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, + 0, // Intensity + record.steps, // Steps + record.heartRate, // HR + ActivitySample.NOT_MEASURED, // Distance + ActivitySample.NOT_MEASURED // Calories + ); + sample.setProvider(provider); + mDaySlotSamples.add(sample); - 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, - 0, // Intensity - record.steps, // Steps - record.heartRate, // HR - ActivitySample.NOT_MEASURED, // Distance - ActivitySample.NOT_MEASURED // Calories - ); - sample.setProvider(provider); + Calendar now = GregorianCalendar.getInstance(); - provider.addGBActivitySample(sample); + //Dump buffered samples to DB + if ((record.timestamp + (60*100) >= (now.getTimeInMillis() / 1000L) )) { - } catch (GBException ex) { - LOG.debug((ex.getMessage())); - } catch (Exception ex) { - LOG.debug(ex.getMessage()); + provider.getSampleDao().insertOrReplaceInTx(mDaySlotSamples); + + mGetDaySlotsTime.setTimeInMillis(0); + mDaySlotSamples.clear(); + mSlotsToRequest = 144 - mLastSlotReceived; } + } 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(); - } + //Request next slot + if(mLastSlotReceived == mLastSlotRequested){ + synchronized (waitObject) { + mGetDaySlotsTime.setTimeInMillis(0); + waitObject.notify(); } } + + return true; } private void requestNextDaySlots() { - //Sync Day Stats - byte hour = (byte) ((mLastSlotReceived) / 6); - byte nextHour = (byte) (hour + 1); - byte nextMinute = 0; + Calendar now = GregorianCalendar.getInstance(); - if (nextHour == (byte) GregorianCalendar.getInstance().get(Calendar.HOUR_OF_DAY)) { - nextMinute = (byte) GregorianCalendar.getInstance().get(Calendar.MINUTE); + if (mLastSlotReceived >= 144 + 6) { // 24 * 6 + 6 + LOG.debug("Reached End of the Day"); + mLastSlotReceived = 0; + mSlotsToRequest = 6; // 1h + mGetDaySlotsTime = now; + mGetDaySlotsTime.add(Calendar.SECOND, CURRENT_DAY_SYNC_PERIOD); + mLastSlotRequested = 0; + return; } + //Sync Day Stats + mLastSlotRequested = Math.min(mLastSlotReceived + mSlotsToRequest, 144); + + LOG.debug("Requesting slot " + mLastSlotRequested); + + byte nextHour = (byte) (mLastSlotRequested / 6); + byte nextMinute = (byte) ((mLastSlotRequested % 6) * 10); + + if (nextHour == (byte) now.get(Calendar.HOUR_OF_DAY)) { + nextMinute = (byte) now.get(Calendar.MINUTE); + } + + byte hour = (byte) (mLastSlotReceived / 6); byte minute = (byte) ((mLastSlotReceived % 6) * 10); - mLastSlotRequested = (nextHour) * 6 + Math.round(nextMinute / 10); + byte[] msg = new byte[]{39, hour, minute, nextHour, nextMinute}; - if (nextHour >= 24 && nextMinute > 0) { // 24 * 6 - LOG.debug("Reached End of the Day"); - mLastSlotRequested = 0; - mLastSlotReceived = 0; - mGetDaySlotsTime = GregorianCalendar.getInstance(); - mGetDaySlotsTime.add(Calendar.SECOND, CURRENT_DAY_SYNC_PERIOD); - - return; - } - - if (nextHour > GregorianCalendar.getInstance().get(GregorianCalendar.HOUR_OF_DAY)) { - LOG.debug("Day data is up to date"); - mGetDaySlotsTime = GregorianCalendar.getInstance(); - mGetDaySlotsTime.add(Calendar.SECOND, CURRENT_DAY_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 = GregorianCalendar.getInstance(); - mGetDaySlotsTime.add(Calendar.SECOND, CURRENT_DAY_SYNC_RETRY_PERIOD); + mGetDaySlotsTime = now; + if(mSlotsToRequest < 144) { + mGetDaySlotsTime.add(Calendar.SECOND, CURRENT_DAY_SYNC_RETRY_PERIOD); + }else{ + mGetDaySlotsTime.add(Calendar.SECOND, CURRENT_DAY_SYNC_PERIOD); + } } public boolean processIncomingSleepData(byte[] data){ From 970c6960ea41f3937e79d39039e22ce34ad5d7ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joa=CC=83o=20Paulo=20Barraca?= Date: Thu, 5 Jan 2017 14:24:39 +0000 Subject: [PATCH 13/15] HPlus: delay day slot fetch --- .../service/devices/hplus/HPlusHandlerThread.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 index c40abfae..93715fce 100644 --- 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 @@ -264,11 +264,13 @@ class HPlusHandlerThread extends GBDeviceIoThread { builder.queue(mHPlusSupport.getQueue()); mGetDaySlotsTime = now; - if(mSlotsToRequest < 144) { + if(mSlotsToRequest == 6) { mGetDaySlotsTime.add(Calendar.SECOND, CURRENT_DAY_SYNC_RETRY_PERIOD); }else{ mGetDaySlotsTime.add(Calendar.SECOND, CURRENT_DAY_SYNC_PERIOD); } + LOG.debug("Requesting next slot " + mLastSlotRequested+ " at " + mGetDaySlotsTime.getTime()); + } public boolean processIncomingSleepData(byte[] data){ From 4cf872664cb3bc3954c7f88d3459b3aff3a128bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joa=CC=83o=20Paulo=20Barraca?= Date: Tue, 10 Jan 2017 13:08:45 +0000 Subject: [PATCH 14/15] HPlus: Improved support for storing and displaying data. --- .../devices/hplus/HPlusConstants.java | 29 +- .../devices/hplus/HPlusCoordinator.java | 4 + .../hplus/HPlusHealthSampleProvider.java | 76 ++- .../devices/hplus/HPlusDataRecord.java | 17 +- .../devices/hplus/HPlusDataRecordDaySlot.java | 31 +- .../hplus/HPlusDataRecordDaySummary.java | 4 +- .../hplus/HPlusDataRecordRealtime.java | 21 +- .../devices/hplus/HPlusDataRecordSleep.java | 14 +- .../devices/hplus/HPlusHandlerThread.java | 484 +++++++++++------- .../service/devices/hplus/HPlusSupport.java | 44 +- 10 files changed, 466 insertions(+), 258 deletions(-) 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 70546e87..5feaf6d7 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 @@ -28,8 +28,8 @@ public final class HPlusConstants { public static final byte ARG_HEARTRATE_MEASURE_ON = 11; public static final byte ARG_HEARTRATE_MEASURE_OFF = 22; - public static final byte ARG_HEARTRATE_ALLDAY_ON = 10; - public static final byte ARG_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 INCOMING_CALL_STATE_DISABLED_THRESHOLD = 0x7B; public static final byte INCOMING_CALL_STATE_ENABLED = (byte) 0xAA; @@ -64,9 +64,7 @@ public final class HPlusConstants { 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}; - + public static final byte CMD_SET_HEARTRATE_STATE = 0x32; //Actions to device public static final byte CMD_GET_ACTIVE_DAY = 0x27; @@ -76,19 +74,17 @@ public final class HPlusConstants { 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 COMMAND_ACTION_INCOMING_SMS = 0x40; //Unknown 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_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 = 90; - - + 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; @@ -103,15 +99,9 @@ public final class HPlusConstants { public static final byte DATA_SLEEP = 0x1A; 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"; + 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"; @@ -120,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"; - } 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 3de594b6..8a596d4c 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 @@ -198,6 +198,10 @@ public class HPlusCoordinator extends AbstractDeviceCoordinator { 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) { //TODO: Figure what this is. Returning the default value 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 index 3fe16e41..9a7fa8c1 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/HPlusHealthSampleProvider.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/HPlusHealthSampleProvider.java @@ -5,9 +5,12 @@ package nodomain.freeyourgadget.gadgetbridge.devices.hplus; */ 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; @@ -25,6 +28,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.HPlusHealthActivitySampleDa 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 { @@ -44,13 +48,28 @@ public class HPlusHealthSampleProvider extends AbstractSampleProvider getActivityamples(int timestamp_from, int timestamp_to) { + return getAllActivitySamples(timestamp_from, timestamp_to); + } + + public List getSleepSamples(int timestamp_from, int timestamp_to) { + return getAllActivitySamples(timestamp_from, timestamp_to); + } + @NonNull @Override public List getAllActivitySamples(int timestamp_from, int timestamp_to) { @@ -97,16 +125,48 @@ public class HPlusHealthSampleProvider extends AbstractSampleProvider qb = getSession().getHPlusHealthActivityOverlayDao().queryBuilder(); - qb.where(HPlusHealthActivityOverlayDao.Properties.DeviceId.eq(dbDevice.getId()), HPlusHealthActivityOverlayDao.Properties.TimestampFrom.ge(timestamp_from)) - .where(HPlusHealthActivityOverlayDao.Properties.TimestampTo.le(timestamp_to)); + 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 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()); } @@ -124,7 +184,7 @@ public class HPlusHealthSampleProvider extends AbstractSampleProvider insertVirtualItem(List samples, int timestamp, long deviceId, long userId){ + private List insertVirtualItem(List samples, int timestamp, long deviceId, long userId) { HPlusHealthActivitySample sample = new HPlusHealthActivitySample( timestamp, // ts deviceId, @@ -143,5 +203,5 @@ public class HPlusHealthSampleProvider extends AbstractSampleProvider= 144) { @@ -50,14 +51,34 @@ public class HPlusDataRecordDaySlot extends HPlusDataRecord { steps = (data[2] & 0xFF) * 256 + data[3] & 0xFF; - //?? data[6]; + //?? data[6]; atemp?? always 0 secondsInactive = data[7] & 0xFF; // ? - int now = (int) (GregorianCalendar.getInstance().getTimeInMillis() / (3600 * 24 * 1000L)); - timestamp = now * 3600 * 24 + (slot / 6 * 3600 + slot % 6 * 10); + 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(){ - return String.format(Locale.US, "Slot: %d, Steps: %d, InactiveSeconds: %d, HeartRate: %d", slot, steps, secondsInactive, heartRate); + 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; + } } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusDataRecordDaySummary.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusDataRecordDaySummary.java index 0bb6609c..e28960f9 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusDataRecordDaySummary.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusDataRecordDaySummary.java @@ -60,7 +60,7 @@ class HPlusDataRecordDaySummary extends HPlusDataRecord{ public int calories; HPlusDataRecordDaySummary(byte[] data) { - super(data); + super(data, TYPE_DAY_SUMMARY); year = (data[10] & 0xFF) * 256 + (data[9] & 0xFF); month = data[11] & 0xFF; @@ -75,7 +75,7 @@ class HPlusDataRecordDaySummary extends HPlusDataRecord{ 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); + 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); 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 index d827976d..8b39f84f 100644 --- 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 @@ -33,6 +33,11 @@ class HPlusDataRecordRealtime extends HPlusDataRecord { */ public byte battery; + /** + * Number of steps today + */ + public int steps; + /** * Time active (To be determined how it works) */ @@ -45,7 +50,7 @@ class HPlusDataRecordRealtime extends HPlusDataRecord { public int intensity; public HPlusDataRecordRealtime(byte[] data) { - super(data); + super(data, TYPE_REALTIME); if (data.length < 15) { throw new IllegalArgumentException("Invalid data packet"); @@ -53,7 +58,7 @@ class HPlusDataRecordRealtime extends HPlusDataRecord { 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; @@ -63,10 +68,14 @@ class HPlusDataRecordRealtime extends HPlusDataRecord { heartRate = data[11] & 0xFF; // BPM activeTime = (data[14] & 0xFF * 256) + (data[13] & 0xFF); - if(heartRate == 255) + if(heartRate == 255) { intensity = 0; - else + 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){ @@ -93,11 +102,11 @@ class HPlusDataRecordRealtime extends HPlusDataRecord { if(other == null) return false; - return distance == other.distance && calories == other.calories && heartRate == other.heartRate && battery == other.battery; + 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 Calories: %d HeartRate: %d Battery: %d ActiveTime: %d Intensity: %d", distance, calories, heartRate, battery, activeTime, intensity); + 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); } } 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 index 97ea0294..f74db8fb 100644 --- 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 @@ -9,6 +9,7 @@ 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; @@ -67,7 +68,7 @@ public class HPlusDataRecordSleep extends HPlusDataRecord { public int wakeupCount; public HPlusDataRecordSleep(byte[] data) { - super(data); + super(data, TYPE_SLEEP); int year = (data[2] & 0xFF) * 256 + (data[1] & 0xFF); int month = data[3] & 0xFF; @@ -91,6 +92,7 @@ public class HPlusDataRecordSleep extends HPlusDataRecord { 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); @@ -114,4 +116,14 @@ public class HPlusDataRecordSleep extends HPlusDataRecord { 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); + } } 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 index 93715fce..d8e5de5a 100644 --- 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 @@ -6,14 +6,21 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.hplus; 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; @@ -28,6 +35,7 @@ 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; @@ -35,20 +43,22 @@ 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 = 60 * 10; + 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 = 0; + + private int mLastSlotReceived = -1; private int mLastSlotRequested = 0; - private int mSlotsToRequest = 6; private Calendar mLastSleepDayReceived = GregorianCalendar.getInstance(); private Calendar mHelloTime = GregorianCalendar.getInstance(); @@ -56,10 +66,13 @@ class HPlusHandlerThread extends GBDeviceIoThread { 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 mDaySlotSamples = new ArrayList<>(); + + List mDaySlotSamples = new ArrayList<>(); public HPlusHandlerThread(GBDevice gbDevice, Context context, HPlusSupport hplusSupport) { super(gbDevice, context); @@ -78,7 +91,7 @@ class HPlusHandlerThread extends GBDeviceIoThread { long waitTime = 0; while (!mQuit) { - //LOG.debug("Waiting " + (waitTime)); + if (waitTime > 0) { synchronized (waitObject) { try { @@ -88,10 +101,16 @@ class HPlusHandlerThread extends GBDeviceIoThread { } } } + if (mQuit) { break; } + if(!mHPlusSupport.getDevice().isConnected()){ + quit(); + break; + } + Calendar now = GregorianCalendar.getInstance(); if (now.compareTo(mHelloTime) > 0) { @@ -111,7 +130,7 @@ class HPlusHandlerThread extends GBDeviceIoThread { } now = GregorianCalendar.getInstance(); - waitTime = Math.min(Math.min(mGetDaySlotsTime.getTimeInMillis(), mGetSleepTime.getTimeInMillis()), mHelloTime.getTimeInMillis()) - now.getTimeInMillis(); + waitTime = Math.min(mGetDaySummaryTime.getTimeInMillis(), Math.min(Math.min(mGetDaySlotsTime.getTimeInMillis(), mGetSleepTime.getTimeInMillis()), mHelloTime.getTimeInMillis())) - now.getTimeInMillis(); } } @@ -128,151 +147,153 @@ class HPlusHandlerThread extends GBDeviceIoThread { 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_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.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.write(mHPlusSupport.ctrlCharacteristic, new byte[]{HPlusConstants.CMD_SET_ALLDAY_HRM, HPlusConstants.ARG_HEARTRATE_ALLDAY_ON}); - 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.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); } - 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_PERIOD); - } - + /** + * 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; } - mLastSlotReceived = record.slot; - try (DBHandler dbHandler = GBApplication.acquireDB()) { - HPlusHealthSampleProvider provider = new HPlusHealthSampleProvider(getDevice(), dbHandler.getDaoSession()); + Calendar now = GregorianCalendar.getInstance(); + int nowSlot = now.get(Calendar.HOUR_OF_DAY) * 6 + (now.get(Calendar.MINUTE) / 10); - 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, - 0, // Intensity - record.steps, // Steps - record.heartRate, // HR - ActivitySample.NOT_MEASURED, // Distance - ActivitySample.NOT_MEASURED // Calories - ); - sample.setProvider(provider); - mDaySlotSamples.add(sample); - - Calendar now = GregorianCalendar.getInstance(); - - //Dump buffered samples to DB - if ((record.timestamp + (60*100) >= (now.getTimeInMillis() / 1000L) )) { - - provider.getSampleDao().insertOrReplaceInTx(mDaySlotSamples); - - mGetDaySlotsTime.setTimeInMillis(0); - mDaySlotSamples.clear(); - mSlotsToRequest = 144 - mLastSlotReceived; - } - } catch (GBException ex) { - LOG.debug((ex.getMessage())); - } catch (Exception ex) { - LOG.debug(ex.getMessage()); + //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; } - //Request next slot - if(mLastSlotReceived == mLastSlotRequested){ + //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() { + 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 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) { - mGetDaySlotsTime.setTimeInMillis(0); waitObject.notify(); } } - return true; } - private void requestNextDaySlots() { - - Calendar now = GregorianCalendar.getInstance(); - - if (mLastSlotReceived >= 144 + 6) { // 24 * 6 + 6 - LOG.debug("Reached End of the Day"); - mLastSlotReceived = 0; - mSlotsToRequest = 6; // 1h - mGetDaySlotsTime = now; - mGetDaySlotsTime.add(Calendar.SECOND, CURRENT_DAY_SYNC_PERIOD); - mLastSlotRequested = 0; - return; - } - - //Sync Day Stats - mLastSlotRequested = Math.min(mLastSlotReceived + mSlotsToRequest, 144); - - LOG.debug("Requesting slot " + mLastSlotRequested); - - byte nextHour = (byte) (mLastSlotRequested / 6); - byte nextMinute = (byte) ((mLastSlotRequested % 6) * 10); - - if (nextHour == (byte) now.get(Calendar.HOUR_OF_DAY)) { - nextMinute = (byte) now.get(Calendar.MINUTE); - } - - byte hour = (byte) (mLastSlotReceived / 6); - byte minute = (byte) ((mLastSlotReceived % 6) * 10); - - byte[] msg = new byte[]{39, hour, minute, nextHour, nextMinute}; - - TransactionBuilder builder = new TransactionBuilder("getNextDaySlot"); - builder.write(mHPlusSupport.ctrlCharacteristic, msg); - builder.queue(mHPlusSupport.getQueue()); - - mGetDaySlotsTime = now; - if(mSlotsToRequest == 6) { - mGetDaySlotsTime.add(Calendar.SECOND, CURRENT_DAY_SYNC_RETRY_PERIOD); - }else{ - mGetDaySlotsTime.add(Calendar.SECOND, CURRENT_DAY_SYNC_PERIOD); - } - LOG.debug("Requesting next slot " + mLastSlotRequested+ " at " + mGetDaySlotsTime.getTime()); - - } + /** + * 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; @@ -280,7 +301,7 @@ class HPlusHandlerThread extends GBDeviceIoThread { record = new HPlusDataRecordSleep(data); } catch(IllegalArgumentException e){ LOG.debug((e.getMessage())); - return true; + return false; } mLastSleepDayReceived.setTimeInMillis(record.bedTimeStart * 1000L); @@ -293,9 +314,10 @@ class HPlusHandlerThread extends GBDeviceIoThread { HPlusHealthActivityOverlayDao overlayDao = session.getHPlusHealthActivityOverlayDao(); HPlusHealthSampleProvider provider = new HPlusHealthSampleProvider(getDevice(), dbHandler.getDaoSession()); - //Insert the Overlays + //Get the individual Sleep overlays and insert them 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)); } @@ -303,23 +325,13 @@ class HPlusHandlerThread extends GBDeviceIoThread { overlayDao.insertOrReplaceInTx(overlayList); //Store the data - HPlusHealthActivitySample sample = new HPlusHealthActivitySample( - record.timestamp, // ts - deviceId, userId, // User id - record.getRawData(), // Raw Data - record.activityKind, - 0, // Intensity - ActivitySample.NOT_MEASURED, // Steps - ActivitySample.NOT_MEASURED, // HR - ActivitySample.NOT_MEASURED, // Distance - ActivitySample.NOT_MEASURED // Calories - ); + 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()); } @@ -330,16 +342,12 @@ class HPlusHandlerThread extends GBDeviceIoThread { return true; } - private void requestNextSleepData() { - mGetSleepTime = GregorianCalendar.getInstance(); - mGetSleepTime.add(GregorianCalendar.SECOND, SLEEP_SYNC_RETRY_PERIOD); - - TransactionBuilder builder = new TransactionBuilder("requestSleepStats"); - builder.write(mHPlusSupport.ctrlCharacteristic, new byte[]{HPlusConstants.CMD_GET_SLEEP}); - builder.queue(mHPlusSupport.getQueue()); - } - - + /** + * 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; @@ -347,49 +355,59 @@ class HPlusHandlerThread extends GBDeviceIoThread { record = new HPlusDataRecordRealtime(data); } catch(IllegalArgumentException e){ LOG.debug((e.getMessage())); - return true; + return false; } - - if(record.same(prevRealTimeRecord)) + //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); - getDevice().sendDeviceUpdateIntent(getContext()); - //Skip when measuring - if(record.heartRate == 255) { + //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()); - return true; + }else { + getDevice().setFirmwareVersion2("" + record.heartRate); + getDevice().sendDeviceUpdateIntent(getContext()); } - getDevice().setFirmwareVersion2("" + record.heartRate); - getDevice().sendDeviceUpdateIntent(getContext()); - 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 - record.activityKind, - record.intensity, // Intensity - ActivitySample.NOT_MEASURED, // Steps - record.heartRate, // HR - record.distance, // Distance - record.calories // Calories - ); + 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); + + if (sample.getSteps() != ActivitySample.NOT_MEASURED && sample.getSteps() - prevRealTimeRecord.steps > 0) { + Intent intent = new Intent(DeviceService.ACTION_REALTIME_STEPS) + .putExtra(DeviceService.EXTRA_REALTIME_STEPS, sample.getSteps() - prevRealTimeRecord.steps) + .putExtra(DeviceService.EXTRA_TIMESTAMP, System.currentTimeMillis()); + LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent); + } + + if (sample.getHeartRate() != ActivitySample.NOT_MEASURED) { + Intent intent = new Intent(DeviceService.ACTION_HEARTRATE_MEASUREMENT) + .putExtra(DeviceService.EXTRA_HEART_RATE_VALUE, sample.getHeartRate()) + .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) { @@ -398,57 +416,35 @@ class HPlusHandlerThread extends GBDeviceIoThread { 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) { - LOG.debug("Process Day Summary"); HPlusDataRecordDaySummary record; try{ record = new HPlusDataRecordDaySummary(data); } catch(IllegalArgumentException e){ LOG.debug((e.getMessage())); - return true; + return false; } - LOG.debug("Received: " + record); 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 = createSample(dbHandler, record.timestamp); - //Hugly (?) fix. - //This message returns the day summary, but the DB already has some detailed entries with steps and distance. - //However DB data is probably incomplete as some update messages could be missing - //Proposed fix: Calculate the total steps and distance and store a new sample with the remaining data - //Existing data will reflect user activity with the issue of a potencially large number of steps at midnight. - //Steps counters by day will be OK with this - - List samples = provider.getActivitySamples(record.timestamp - 3600 * 24 + 1, record.timestamp); - - int missingDistance = record.distance; - int missingSteps = record.steps; - - for(HPlusHealthActivitySample sample : samples){ - if(sample.getSteps() > 0) { - missingSteps -= sample.getSteps(); - } - if(sample.getDistance() > 0){ - missingDistance -= sample.getDistance(); - } - } - - HPlusHealthActivitySample sample = new HPlusHealthActivitySample( - record.timestamp, // ts - deviceId, userId, // User id - record.getRawData(), // Raw Data - ActivityKind.TYPE_UNKNOWN, - 0, // Intensity - Math.max( missingSteps, 0), // Steps - ActivitySample.NOT_MEASURED, // HR - Math.max( missingDistance, 0), // Distance - ActivitySample.NOT_MEASURED // Calories - ); + 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); @@ -458,9 +454,17 @@ class HPlusHandlerThread extends GBDeviceIoThread { 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; @@ -471,4 +475,102 @@ class HPlusHandlerThread extends GBDeviceIoThread { return true; } -} \ No newline at end of file + + /** + * 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; + } + +} 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 17e9e666..dd229b81 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 @@ -37,6 +37,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSuppo 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; @@ -344,14 +345,23 @@ 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.CMD_SET_HEARTRATE_STATE, + value + }); + + + value = HPlusCoordinator.getAllDayHR(getDevice().getAddress()); + transaction.write(ctrlCharacteristic, new byte[]{ HPlusConstants.CMD_SET_ALLDAY_HRM, value }); + return this; } @@ -415,6 +425,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { @Override public void onNotification(NotificationSpec notificationSpec) { //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); } @@ -534,7 +545,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { TransactionBuilder builder = new TransactionBuilder("HeartRateTest"); - builder.write(ctrlCharacteristic, new byte[]{HPlusConstants.CMD_SET_ALLDAY_HRM, 0x0A}); //Set Real Time... ? + builder.write(ctrlCharacteristic, new byte[]{HPlusConstants.CMD_SET_HEARTRATE_STATE, HPlusConstants.ARG_HEARTRATE_MEASURE_ON}); //Set Real Time... ? builder.queue(getQueue()); } @@ -629,20 +640,14 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { builder.write(ctrlCharacteristic, new byte[]{HPlusConstants.CMD_ACTION_INCOMING_CALL, 1}); //Show Call Icon - builder.write(ctrlCharacteristic, new byte[]{HPlusConstants.CMD_ACTION_INCOMING_CALL, HPlusConstants.ARG_INCOMING_CALL}); + builder.write(ctrlCharacteristic, new byte[]{HPlusConstants.CMD_SET_INCOMING_CALL, HPlusConstants.ARG_INCOMING_CALL}); 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++) @@ -657,13 +662,8 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { 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 @@ -676,11 +676,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { 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.CMD_ACTION_DISPLAY_TEXT_NAME_CN; builder.write(ctrlCharacteristic, msg); @@ -693,6 +689,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { } private void showText(String title, String body) { + LOG.debug("Show Notification: "+title+" --> "+body); try { TransactionBuilder builder = performInitialized("notification"); @@ -720,6 +717,8 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { 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; if (message.length() % 17 > 0) @@ -801,4 +800,5 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { return true; } } + } From b92b1c08bfd29fdb00e5c7ebf62fa2dce9e10ea6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joa=CC=83o=20Paulo=20Barraca?= Date: Tue, 10 Jan 2017 13:44:32 +0000 Subject: [PATCH 15/15] HPlus: Fix deprecation warning --- .../gadgetbridge/daogen/GBDaoGenerator.java | 1 + .../devices/hplus/HPlusHandlerThread.java | 17 +++++------------ 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java b/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java index 2905d45f..d70679a1 100644 --- a/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java +++ b/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java @@ -225,6 +225,7 @@ 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(SAMPLE_RAW_KIND).notNull().primaryKey(); 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 index d8e5de5a..7b05775d 100644 --- 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 @@ -391,19 +391,12 @@ class HPlusHandlerThread extends GBDeviceIoThread { sample.setRawHPlusHealthData(record.getRawData()); sample.setProvider(provider); - if (sample.getSteps() != ActivitySample.NOT_MEASURED && sample.getSteps() - prevRealTimeRecord.steps > 0) { - Intent intent = new Intent(DeviceService.ACTION_REALTIME_STEPS) - .putExtra(DeviceService.EXTRA_REALTIME_STEPS, sample.getSteps() - prevRealTimeRecord.steps) - .putExtra(DeviceService.EXTRA_TIMESTAMP, System.currentTimeMillis()); - LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent); - } + sample.setSteps(sample.getSteps() - prevRealTimeRecord.steps); - if (sample.getHeartRate() != ActivitySample.NOT_MEASURED) { - Intent intent = new Intent(DeviceService.ACTION_HEARTRATE_MEASUREMENT) - .putExtra(DeviceService.EXTRA_HEART_RATE_VALUE, sample.getHeartRate()) - .putExtra(DeviceService.EXTRA_TIMESTAMP, System.currentTimeMillis()); - LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent); - } + 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);