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] 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; } } + }