From f15a97d9947ea60548bb058c9683c6a91576d5cc Mon Sep 17 00:00:00 2001 From: cpfeiffer Date: Tue, 12 Apr 2016 23:12:15 +0200 Subject: [PATCH] Initial live heartrate measurement in the live activity tab #178 --- .../activities/HeartRateUtils.java | 6 + .../charts/AbstractChartFragment.java | 5 +- .../charts/ActivitySleepChartFragment.java | 5 +- .../charts/LiveActivityFragment.java | 118 ++++++++++++++---- .../activities/charts/SleepChartFragment.java | 5 +- .../gadgetbridge/devices/EventHandler.java | 2 + .../gadgetbridge/impl/GBDeviceService.java | 7 ++ .../gadgetbridge/model/DeviceService.java | 3 + .../gadgetbridge/model/Measurement.java | 33 +++++ .../service/DeviceCommunicationService.java | 6 + .../service/ServiceDeviceSupport.java | 8 ++ .../service/devices/miband/MiBandSupport.java | 31 ++++- .../serial/AbstractSerialDeviceSupport.java | 6 + .../service/serial/GBDeviceProtocol.java | 2 + app/src/main/res/values/colors.xml | 1 + app/src/main/res/values/strings.xml | 1 + .../service/TestDeviceSupport.java | 5 + 17 files changed, 213 insertions(+), 31 deletions(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/HeartRateUtils.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/Measurement.java diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/HeartRateUtils.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/HeartRateUtils.java new file mode 100644 index 00000000..9ac085d7 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/HeartRateUtils.java @@ -0,0 +1,6 @@ +package nodomain.freeyourgadget.gadgetbridge.activities; + +public class HeartRateUtils { + public static final int MAX_HEART_RATE_VALUE = 250; + public static final int MIN_HEART_RATE_VALUE = 0; +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractChartFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractChartFragment.java index c32752d3..75a3c13e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractChartFragment.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractChartFragment.java @@ -39,6 +39,7 @@ import java.util.Set; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBFragment; +import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils; import nodomain.freeyourgadget.gadgetbridge.database.DBAccess; import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator; @@ -116,6 +117,7 @@ public abstract class AbstractChartFragment extends AbstractGBFragment { protected int CHART_TEXT_COLOR; protected int LEGEND_TEXT_COLOR; protected int HEARTRATE_COLOR; + protected int HEARTRATE_FILL_COLOR; protected int AK_ACTIVITY_COLOR; protected int AK_DEEP_SLEEP_COLOR; protected int AK_LIGHT_SLEEP_COLOR; @@ -152,6 +154,7 @@ public abstract class AbstractChartFragment extends AbstractGBFragment { CHART_TEXT_COLOR = getResources().getColor(R.color.secondarytext); LEGEND_TEXT_COLOR = getResources().getColor(R.color.primarytext); HEARTRATE_COLOR = getResources().getColor(R.color.chart_heartrate); + HEARTRATE_FILL_COLOR = getResources().getColor(R.color.chart_heartrate_fill); AK_ACTIVITY_COLOR = getResources().getColor(R.color.chart_activity_light); AK_DEEP_SLEEP_COLOR = getResources().getColor(R.color.chart_light_sleep_light); AK_LIGHT_SLEEP_COLOR = getResources().getColor(R.color.chart_deep_sleep_light); @@ -518,7 +521,7 @@ public abstract class AbstractChartFragment extends AbstractGBFragment { } protected boolean isValidHeartRateValue(int value) { - return value > 0 && value < 255; + return value > HeartRateUtils.MIN_HEART_RATE_VALUE && value < HeartRateUtils.MAX_HEART_RATE_VALUE; } /** diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivitySleepChartFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivitySleepChartFragment.java index 7c58693e..7260a205 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivitySleepChartFragment.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivitySleepChartFragment.java @@ -20,6 +20,7 @@ import java.util.ArrayList; import java.util.List; import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils; import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; @@ -83,8 +84,8 @@ public class ActivitySleepChartFragment extends AbstractChartFragment { yAxisRight.setDrawLabels(true); yAxisRight.setDrawTopYLabelEntry(true); yAxisRight.setTextColor(CHART_TEXT_COLOR); - yAxisRight.setAxisMaxValue(250); - yAxisRight.setAxisMinValue(0); + yAxisRight.setAxisMaxValue(HeartRateUtils.MAX_HEART_RATE_VALUE); + yAxisRight.setAxisMinValue(HeartRateUtils.MIN_HEART_RATE_VALUE); // refresh immediately instead of use refreshIfVisible(), for perceived performance refresh(); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/LiveActivityFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/LiveActivityFragment.java index f69e2ae5..cd014362 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/LiveActivityFragment.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/LiveActivityFragment.java @@ -23,6 +23,7 @@ import com.github.mikephil.charting.components.YAxis; import com.github.mikephil.charting.data.BarData; import com.github.mikephil.charting.data.BarDataSet; import com.github.mikephil.charting.data.BarEntry; +import com.github.mikephil.charting.data.ChartData; import com.github.mikephil.charting.data.Entry; import com.github.mikephil.charting.data.LineData; import com.github.mikephil.charting.data.LineDataSet; @@ -39,10 +40,12 @@ import java.util.concurrent.TimeUnit; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils; import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; import nodomain.freeyourgadget.gadgetbridge.model.DeviceService; +import nodomain.freeyourgadget.gadgetbridge.model.Measurement; import nodomain.freeyourgadget.gadgetbridge.util.GB; public class LiveActivityFragment extends AbstractChartFragment { @@ -63,6 +66,9 @@ public class LiveActivityFragment extends AbstractChartFragment { private final Steps mSteps = new Steps(); private ScheduledExecutorService pulseScheduler; private int maxStepsResetCounter; + private List heartRateValues; + private LineDataSet mHeartRateSet; + private int mHeartRate; private class Steps { private int initialSteps; @@ -145,16 +151,36 @@ public class LiveActivityFragment extends AbstractChartFragment { public void onReceive(Context context, Intent intent) { String action = intent.getAction(); switch (action) { - case DeviceService.ACTION_REALTIME_STEPS: + case DeviceService.ACTION_REALTIME_STEPS: { int steps = intent.getIntExtra(DeviceService.EXTRA_REALTIME_STEPS, 0); long timestamp = intent.getLongExtra(DeviceService.EXTRA_TIMESTAMP, System.currentTimeMillis()); - refreshCurrentSteps(steps, timestamp); + addEntries(steps, timestamp); break; + } + case DeviceService.ACTION_HEARTRATE_MEASUREMENT: { + int heartRate = intent.getIntExtra(DeviceService.EXTRA_HEART_RATE_VALUE, 0); + long timestamp = intent.getLongExtra(DeviceService.EXTRA_TIMESTAMP, System.currentTimeMillis()); + if (isValidHeartRateValue(heartRate)) { + setCurrentHeartRate(heartRate, timestamp); + } + break; + } } } }; - private void refreshCurrentSteps(int steps, long timestamp) { + private void setCurrentHeartRate(int heartRate, long timestamp) { + addHistoryDataSet(true); + mHeartRate = heartRate; + } + + private int getCurrentHeartRate() { + int result = mHeartRate; + mHeartRate = -1; + return result; + } + + private void addEntries(int steps, long timestamp) { mSteps.updateCurrentSteps(steps, timestamp); if (++maxStepsResetCounter > RESET_COUNT) { maxStepsResetCounter = 0; @@ -163,10 +189,10 @@ public class LiveActivityFragment extends AbstractChartFragment { // Or: count down the steps until goal reached? And then flash GOAL REACHED -> Set stretch goal LOG.info("Steps: " + steps + ", total: " + mSteps.getTotalSteps() + ", current: " + mSteps.getStepsPerMinute(false)); -// refreshCurrentSteps(); +// addEntries(); } - private void refreshCurrentSteps() { + private void addEntries() { mTotalStepsChart.setSingleEntryYValue(mSteps.getTotalSteps()); YAxis stepsPerMinuteCurrentYAxis = mStepsPerMinuteCurrentChart.getAxisLeft(); int maxStepsPerMinute = mSteps.getMaxStepsPerMinute(); @@ -180,24 +206,36 @@ public class LiveActivityFragment extends AbstractChartFragment { int stepsPerMinute = mSteps.getStepsPerMinute(true); mStepsPerMinuteCurrentChart.setSingleEntryYValue(stepsPerMinute); - if (mStepsPerMinuteHistoryChart.getData() == null) { - if (mSteps.getTotalSteps() == 0) { - return; // ignore the first default value to keep the "no-data-description" visible - } - LineData data = new LineData(); - mStepsPerMinuteHistoryChart.setData(data); - data.addDataSet(mHistorySet); + if (!addHistoryDataSet(false)) { + return; } - LineData historyData = (LineData) mStepsPerMinuteHistoryChart.getData(); - historyData.addXValue(""); - historyData.addEntry(new Entry(stepsPerMinute, mHistorySet.getEntryCount()), 0); + ChartData data = mStepsPerMinuteHistoryChart.getData(); + data.addXValue(""); + if (stepsPerMinute < 0) { + stepsPerMinute = 0; + } + mHistorySet.addEntry(new Entry(stepsPerMinute, data.getXValCount() - 1)); + int hr = getCurrentHeartRate(); + if (hr < 0) { + hr = 0; + } + mHeartRateSet.addEntry(new Entry(hr, data.getXValCount() - 1)); + } - mTotalStepsData.notifyDataSetChanged(); - mStepsPerMinuteData.notifyDataSetChanged(); - mStepsPerMinuteHistoryChart.notifyDataSetChanged(); - - renderCharts(); + private boolean addHistoryDataSet(boolean force) { + if (mStepsPerMinuteHistoryChart.getData() == null) { + // ignore the first default value to keep the "no-data-description" visible + if (force || mSteps.getTotalSteps() > 0) { + LineData data = new LineData(); + data.addDataSet(mHistorySet); + data.addDataSet(mHeartRateSet); + mStepsPerMinuteHistoryChart.setData(data); + return true; + } + return false; + } + return true; } @Nullable @@ -205,6 +243,8 @@ public class LiveActivityFragment extends AbstractChartFragment { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { IntentFilter filterLocal = new IntentFilter(); filterLocal.addAction(DeviceService.ACTION_REALTIME_STEPS); + filterLocal.addAction(DeviceService.ACTION_HEARTRATE_MEASUREMENT); + heartRateValues = new ArrayList<>(); View rootView = inflater.inflate(R.layout.fragment_live_activity, container, false); @@ -262,7 +302,22 @@ public class LiveActivityFragment extends AbstractChartFragment { * Called in the UI thread. */ private void pulse() { - refreshCurrentSteps(); + addEntries(); + + LineData historyData = (LineData) mStepsPerMinuteHistoryChart.getData(); + if (historyData == null) { + return; + } + + historyData.notifyDataChanged(); + mTotalStepsData.notifyDataSetChanged(); + mStepsPerMinuteData.notifyDataSetChanged(); + mStepsPerMinuteHistoryChart.notifyDataSetChanged(); + + renderCharts(); + + // have to enable it again and again to keep it measureing + GBApplication.deviceService().onEnableRealtimeHeartRateMeasurement(true); } private long getPulseIntervalMillis() { @@ -272,6 +327,7 @@ public class LiveActivityFragment extends AbstractChartFragment { @Override protected void onMadeVisibleInActivity() { GBApplication.deviceService().onEnableRealtimeSteps(true); + GBApplication.deviceService().onEnableRealtimeHeartRateMeasurement(true); super.onMadeVisibleInActivity(); if (getActivity() != null) { getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); @@ -281,6 +337,7 @@ public class LiveActivityFragment extends AbstractChartFragment { @Override protected void onMadeInvisibleInActivity() { GBApplication.deviceService().onEnableRealtimeSteps(false); + GBApplication.deviceService().onEnableRealtimeHeartRateMeasurement(false); if (getActivity() != null) { getActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } @@ -346,6 +403,7 @@ public class LiveActivityFragment extends AbstractChartFragment { private void setupHistoryChart(BarLineChartBase chart) { configureBarLineChartDefaults(chart); + chart.setTouchEnabled(false); // no zooming or anything, because it's updated all the time chart.setBackgroundColor(BACKGROUND_COLOR); chart.setDescriptionColor(DESCRIPTION_COLOR); chart.setDescription(getString(R.string.live_activity_steps_per_minute_history)); @@ -367,22 +425,34 @@ public class LiveActivityFragment extends AbstractChartFragment { y.setDrawGridLines(false); y.setDrawTopYLabelEntry(false); y.setTextColor(CHART_TEXT_COLOR); - y.setEnabled(true); + y.setAxisMinValue(0); YAxis yAxisRight = chart.getAxisRight(); yAxisRight.setDrawGridLines(false); - yAxisRight.setEnabled(false); - yAxisRight.setDrawLabels(false); + yAxisRight.setEnabled(true); + yAxisRight.setDrawLabels(true); yAxisRight.setDrawTopYLabelEntry(false); yAxisRight.setTextColor(CHART_TEXT_COLOR); + yAxisRight.setAxisMaxValue(HeartRateUtils.MAX_HEART_RATE_VALUE); + yAxisRight.setAxisMinValue(HeartRateUtils.MIN_HEART_RATE_VALUE); mHistorySet = new LineDataSet(new ArrayList(), getString(R.string.live_activity_steps_history)); + mHistorySet.setAxisDependency(YAxis.AxisDependency.LEFT); mHistorySet.setColor(akActivity.color); mHistorySet.setDrawCircles(false); mHistorySet.setDrawCubic(true); mHistorySet.setDrawFilled(true); mHistorySet.setDrawValues(false); + + mHeartRateSet = new LineDataSet(new ArrayList(), getString(R.string.live_activity_heart_rate)); + mHeartRateSet.setAxisDependency(YAxis.AxisDependency.RIGHT); + mHeartRateSet.setColor(HEARTRATE_COLOR); + mHeartRateSet.setDrawCircles(false); + mHeartRateSet.setDrawCubic(true); + mHeartRateSet.setDrawFilled(false); +// mHeartRateSet.setFillColor(HEARTRATE_FILL_COLOR); + mHeartRateSet.setDrawValues(false); } @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/SleepChartFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/SleepChartFragment.java index 4f18495d..48ff18f8 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/SleepChartFragment.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/SleepChartFragment.java @@ -27,6 +27,7 @@ import java.util.List; import java.util.concurrent.TimeUnit; import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils; import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.model.ActivityAmount; @@ -174,8 +175,8 @@ public class SleepChartFragment extends AbstractChartFragment { yAxisRight.setDrawLabels(true); yAxisRight.setDrawTopYLabelEntry(true); yAxisRight.setTextColor(CHART_TEXT_COLOR); - yAxisRight.setAxisMaxValue(250); - yAxisRight.setAxisMinValue(0); + yAxisRight.setAxisMaxValue(HeartRateUtils.MAX_HEART_RATE_VALUE); + yAxisRight.setAxisMinValue(HeartRateUtils.MIN_HEART_RATE_VALUE); } protected void setupLegend(Chart chart) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/EventHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/EventHandler.java index 5ddad49e..a6dd21bf 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/EventHandler.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/EventHandler.java @@ -44,6 +44,8 @@ public interface EventHandler { void onHeartRateTest(); + void onEnableRealtimeHeartRateMeasurement(boolean enable); + void onFindDevice(boolean start); void onScreenshotReq(); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java index add9f676..61af3fcb 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java @@ -216,4 +216,11 @@ public class GBDeviceService implements DeviceService { .putExtra(EXTRA_BOOLEAN_ENABLE, enable); invokeService(intent); } + + @Override + public void onEnableRealtimeHeartRateMeasurement(boolean enable) { + Intent intent = createIntent().setAction(ACTION_ENABLE_REALTIME_HEARTRATE_MEASUREMENT) + .putExtra(EXTRA_BOOLEAN_ENABLE, enable); + invokeService(intent); + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java index 7c38088f..2cf979cd 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java @@ -33,7 +33,9 @@ public interface DeviceService extends EventHandler { String ACTION_SET_ALARMS = PREFIX + ".action.set_alarms"; String ACTION_ENABLE_REALTIME_STEPS = PREFIX + ".action.enable_realtime_steps"; String ACTION_REALTIME_STEPS = PREFIX + ".action.realtime_steps"; + String ACTION_ENABLE_REALTIME_HEARTRATE_MEASUREMENT = PREFIX + ".action.realtime_hr_measurement"; String ACTION_ENABLE_HEARTRATE_SLEEP_SUPPORT = PREFIX + ".action.enable_heartrate_sleep_support"; + String ACTION_HEARTRATE_MEASUREMENT = PREFIX + ".action.hr_measurement"; String EXTRA_DEVICE_ADDRESS = "device_address"; String EXTRA_NOTIFICATION_BODY = "notification_body"; String EXTRA_NOTIFICATION_FLAGS = "notification_flags"; @@ -62,6 +64,7 @@ public interface DeviceService extends EventHandler { String EXTRA_BOOLEAN_ENABLE = "enable_realtime_steps"; String EXTRA_REALTIME_STEPS = "realtime_steps"; String EXTRA_TIMESTAMP = "timestamp"; + String EXTRA_HEART_RATE_VALUE = "hr_value"; void start(); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/Measurement.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/Measurement.java new file mode 100644 index 00000000..f841da00 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/Measurement.java @@ -0,0 +1,33 @@ +package nodomain.freeyourgadget.gadgetbridge.model; + +public class Measurement { + private final int value; + private final long timestamp; + + public Measurement(int value, long timestamp) { + this.value = value; + this.timestamp = timestamp; + } + + public int getValue() { + return value; + } + + public long getTimestamp() { + return timestamp; + } + + @Override + public int hashCode() { + return (int) (71 ^ value ^ timestamp); + } + + @Override + public boolean equals(Object o) { + if (o instanceof Measurement) { + Measurement m = (Measurement) o; + return timestamp == m.timestamp && value == m.value; + } + return super.equals(o); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java index 6ade401d..85e3ceb7 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java @@ -46,6 +46,7 @@ import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_CO import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_DELETEAPP; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_DISCONNECT; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_ENABLE_HEARTRATE_SLEEP_SUPPORT; +import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_ENABLE_REALTIME_HEARTRATE_MEASUREMENT; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_ENABLE_REALTIME_STEPS; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_FETCH_ACTIVITY_DATA; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_FIND_DEVICE; @@ -349,6 +350,11 @@ public class DeviceCommunicationService extends Service { mDeviceSupport.onEnableHeartRateSleepSupport(enable); break; } + case ACTION_ENABLE_REALTIME_HEARTRATE_MEASUREMENT: { + boolean enable = intent.getBooleanExtra(EXTRA_BOOLEAN_ENABLE, false); + mDeviceSupport.onEnableRealtimeHeartRateMeasurement(enable); + break; + } } return START_STICKY; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java index e54e61bd..2a0de20e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java @@ -250,4 +250,12 @@ public class ServiceDeviceSupport implements DeviceSupport { } delegate.onEnableHeartRateSleepSupport(enable); } + + @Override + public void onEnableRealtimeHeartRateMeasurement(boolean enable) { + if (checkBusy("enable realtime heart rate measurement: " + enable)) { + return; + } + delegate.onEnableRealtimeHeartRateMeasurement(enable); + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java index 37b22591..9eec7cf5 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java @@ -603,7 +603,6 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport { TransactionBuilder builder = performInitialized("HeartRateTest"); builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT), stopHeartMeasurementContinuous); builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT), stopHeartMeasurementManual); - builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT), stopHeartMeasurementSleep); builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT), startHeartMeasurementManual); builder.queue(getQueue()); } catch (IOException ex) { @@ -614,6 +613,25 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport { } } + @Override + public void onEnableRealtimeHeartRateMeasurement(boolean enable) { + if (supportsHeartRate()) { + try { + TransactionBuilder builder = performInitialized("EnableRealtimeHeartRateMeasurement"); + if (enable) { + builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT), stopHeartMeasurementManual); + builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT), stopHeartMeasurementSleep); + builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT), startHeartMeasurementContinuous); + } else { + builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT), stopHeartMeasurementContinuous); + } + builder.queue(getQueue()); + } catch (IOException ex) { + LOG.error("Unable to enable realtime heart rate measurement in MI1S", ex); + } + } + } + public boolean supportsHeartRate() { return getDeviceInfo() != null && getDeviceInfo().supportsHeartrate(); } @@ -745,7 +763,7 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport { } else if (MiBandService.UUID_CHARACTERISTIC_REALTIME_STEPS.equals(characteristicUUID)) { handleRealtimeSteps(characteristic.getValue()); } else if (MiBandService.UUID_CHARACTERISTIC_HEART_RATE_MEASUREMENT.equals(characteristicUUID)) { - logHeartrate(characteristic.getValue()); + handleHeartrate(characteristic.getValue()); } else { LOG.info("Unhandled characteristic changed: " + characteristicUUID); logMessageContent(characteristic.getValue()); @@ -814,6 +832,15 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport { } } + private void handleHeartrate(byte[] value) { + if (value.length == 2 && value[0] == 6) { + int hrValue = (value[1] & 0xff); + Intent intent = new Intent(DeviceService.ACTION_HEARTRATE_MEASUREMENT) + .putExtra(DeviceService.EXTRA_HEART_RATE_VALUE, hrValue) + .putExtra(DeviceService.EXTRA_TIMESTAMP, System.currentTimeMillis()); + LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent); + } + } private void handleRealtimeSteps(byte[] value) { int steps = 0xff & value[0] | (0xff & value[1]) << 8; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/AbstractSerialDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/AbstractSerialDeviceSupport.java index aa78a393..5dfa8b2a 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/AbstractSerialDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/AbstractSerialDeviceSupport.java @@ -181,4 +181,10 @@ public abstract class AbstractSerialDeviceSupport extends AbstractDeviceSupport byte[] bytes = gbDeviceProtocol.encodeEnableHeartRateSleepSupport(enable); sendToDevice(bytes); } + + @Override + public void onEnableRealtimeHeartRateMeasurement(boolean enable) { + byte[] bytes = gbDeviceProtocol.encodeEnableRealtimeHeartRateMeasurement(enable); + sendToDevice(bytes); + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceProtocol.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceProtocol.java index 2aa1fb66..bdf1b101 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceProtocol.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceProtocol.java @@ -63,6 +63,8 @@ public abstract class GBDeviceProtocol { return null; } + public byte[] encodeEnableRealtimeHeartRateMeasurement(boolean enable) { return null; } + public GBDeviceEvent[] decodeResponse(byte[] responseData) { return null; } diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 82744a0c..5f53994c 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -12,6 +12,7 @@ #1f000000 #ffab40 + #fadab1 #0071b7 #4c5aff diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index beac2a5d..fa365a9b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -240,5 +240,6 @@ Firmware update in progress Firmware not sent Heart Rate + Heart Rate diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/TestDeviceSupport.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/TestDeviceSupport.java index 19fd1810..9d486bc0 100644 --- a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/TestDeviceSupport.java +++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/TestDeviceSupport.java @@ -129,4 +129,9 @@ public class TestDeviceSupport extends AbstractDeviceSupport { public void onEnableHeartRateSleepSupport(boolean enable) { } + + @Override + public void onEnableRealtimeHeartRateMeasurement(boolean enable) { + + } }