Initial live heartrate measurement in the live activity tab #178

This commit is contained in:
cpfeiffer 2016-04-12 23:12:15 +02:00
parent 58d90c2a66
commit f15a97d994
17 changed files with 213 additions and 31 deletions

View File

@ -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;
}

View File

@ -39,6 +39,7 @@ import java.util.Set;
import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBFragment; import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBFragment;
import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils;
import nodomain.freeyourgadget.gadgetbridge.database.DBAccess; import nodomain.freeyourgadget.gadgetbridge.database.DBAccess;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
@ -116,6 +117,7 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
protected int CHART_TEXT_COLOR; protected int CHART_TEXT_COLOR;
protected int LEGEND_TEXT_COLOR; protected int LEGEND_TEXT_COLOR;
protected int HEARTRATE_COLOR; protected int HEARTRATE_COLOR;
protected int HEARTRATE_FILL_COLOR;
protected int AK_ACTIVITY_COLOR; protected int AK_ACTIVITY_COLOR;
protected int AK_DEEP_SLEEP_COLOR; protected int AK_DEEP_SLEEP_COLOR;
protected int AK_LIGHT_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); CHART_TEXT_COLOR = getResources().getColor(R.color.secondarytext);
LEGEND_TEXT_COLOR = getResources().getColor(R.color.primarytext); LEGEND_TEXT_COLOR = getResources().getColor(R.color.primarytext);
HEARTRATE_COLOR = getResources().getColor(R.color.chart_heartrate); 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_ACTIVITY_COLOR = getResources().getColor(R.color.chart_activity_light);
AK_DEEP_SLEEP_COLOR = getResources().getColor(R.color.chart_light_sleep_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); 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) { protected boolean isValidHeartRateValue(int value) {
return value > 0 && value < 255; return value > HeartRateUtils.MIN_HEART_RATE_VALUE && value < HeartRateUtils.MAX_HEART_RATE_VALUE;
} }
/** /**

View File

@ -20,6 +20,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
@ -83,8 +84,8 @@ public class ActivitySleepChartFragment extends AbstractChartFragment {
yAxisRight.setDrawLabels(true); yAxisRight.setDrawLabels(true);
yAxisRight.setDrawTopYLabelEntry(true); yAxisRight.setDrawTopYLabelEntry(true);
yAxisRight.setTextColor(CHART_TEXT_COLOR); yAxisRight.setTextColor(CHART_TEXT_COLOR);
yAxisRight.setAxisMaxValue(250); yAxisRight.setAxisMaxValue(HeartRateUtils.MAX_HEART_RATE_VALUE);
yAxisRight.setAxisMinValue(0); yAxisRight.setAxisMinValue(HeartRateUtils.MIN_HEART_RATE_VALUE);
// refresh immediately instead of use refreshIfVisible(), for perceived performance // refresh immediately instead of use refreshIfVisible(), for perceived performance
refresh(); refresh();

View File

@ -23,6 +23,7 @@ import com.github.mikephil.charting.components.YAxis;
import com.github.mikephil.charting.data.BarData; import com.github.mikephil.charting.data.BarData;
import com.github.mikephil.charting.data.BarDataSet; import com.github.mikephil.charting.data.BarDataSet;
import com.github.mikephil.charting.data.BarEntry; 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.Entry;
import com.github.mikephil.charting.data.LineData; import com.github.mikephil.charting.data.LineData;
import com.github.mikephil.charting.data.LineDataSet; 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.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService; import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
import nodomain.freeyourgadget.gadgetbridge.model.Measurement;
import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.GB;
public class LiveActivityFragment extends AbstractChartFragment { public class LiveActivityFragment extends AbstractChartFragment {
@ -63,6 +66,9 @@ public class LiveActivityFragment extends AbstractChartFragment {
private final Steps mSteps = new Steps(); private final Steps mSteps = new Steps();
private ScheduledExecutorService pulseScheduler; private ScheduledExecutorService pulseScheduler;
private int maxStepsResetCounter; private int maxStepsResetCounter;
private List<Measurement> heartRateValues;
private LineDataSet mHeartRateSet;
private int mHeartRate;
private class Steps { private class Steps {
private int initialSteps; private int initialSteps;
@ -145,16 +151,36 @@ public class LiveActivityFragment extends AbstractChartFragment {
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
String action = intent.getAction(); String action = intent.getAction();
switch (action) { switch (action) {
case DeviceService.ACTION_REALTIME_STEPS: case DeviceService.ACTION_REALTIME_STEPS: {
int steps = intent.getIntExtra(DeviceService.EXTRA_REALTIME_STEPS, 0); int steps = intent.getIntExtra(DeviceService.EXTRA_REALTIME_STEPS, 0);
long timestamp = intent.getLongExtra(DeviceService.EXTRA_TIMESTAMP, System.currentTimeMillis()); long timestamp = intent.getLongExtra(DeviceService.EXTRA_TIMESTAMP, System.currentTimeMillis());
refreshCurrentSteps(steps, timestamp); addEntries(steps, timestamp);
break; 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); mSteps.updateCurrentSteps(steps, timestamp);
if (++maxStepsResetCounter > RESET_COUNT) { if (++maxStepsResetCounter > RESET_COUNT) {
maxStepsResetCounter = 0; 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 // 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)); LOG.info("Steps: " + steps + ", total: " + mSteps.getTotalSteps() + ", current: " + mSteps.getStepsPerMinute(false));
// refreshCurrentSteps(); // addEntries();
} }
private void refreshCurrentSteps() { private void addEntries() {
mTotalStepsChart.setSingleEntryYValue(mSteps.getTotalSteps()); mTotalStepsChart.setSingleEntryYValue(mSteps.getTotalSteps());
YAxis stepsPerMinuteCurrentYAxis = mStepsPerMinuteCurrentChart.getAxisLeft(); YAxis stepsPerMinuteCurrentYAxis = mStepsPerMinuteCurrentChart.getAxisLeft();
int maxStepsPerMinute = mSteps.getMaxStepsPerMinute(); int maxStepsPerMinute = mSteps.getMaxStepsPerMinute();
@ -180,24 +206,36 @@ public class LiveActivityFragment extends AbstractChartFragment {
int stepsPerMinute = mSteps.getStepsPerMinute(true); int stepsPerMinute = mSteps.getStepsPerMinute(true);
mStepsPerMinuteCurrentChart.setSingleEntryYValue(stepsPerMinute); mStepsPerMinuteCurrentChart.setSingleEntryYValue(stepsPerMinute);
if (!addHistoryDataSet(false)) {
return;
}
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));
}
private boolean addHistoryDataSet(boolean force) {
if (mStepsPerMinuteHistoryChart.getData() == null) { if (mStepsPerMinuteHistoryChart.getData() == null) {
if (mSteps.getTotalSteps() == 0) { // ignore the first default value to keep the "no-data-description" visible
return; // ignore the first default value to keep the "no-data-description" visible if (force || mSteps.getTotalSteps() > 0) {
}
LineData data = new LineData(); LineData data = new LineData();
mStepsPerMinuteHistoryChart.setData(data);
data.addDataSet(mHistorySet); data.addDataSet(mHistorySet);
data.addDataSet(mHeartRateSet);
mStepsPerMinuteHistoryChart.setData(data);
return true;
} }
return false;
LineData historyData = (LineData) mStepsPerMinuteHistoryChart.getData(); }
historyData.addXValue(""); return true;
historyData.addEntry(new Entry(stepsPerMinute, mHistorySet.getEntryCount()), 0);
mTotalStepsData.notifyDataSetChanged();
mStepsPerMinuteData.notifyDataSetChanged();
mStepsPerMinuteHistoryChart.notifyDataSetChanged();
renderCharts();
} }
@Nullable @Nullable
@ -205,6 +243,8 @@ public class LiveActivityFragment extends AbstractChartFragment {
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
IntentFilter filterLocal = new IntentFilter(); IntentFilter filterLocal = new IntentFilter();
filterLocal.addAction(DeviceService.ACTION_REALTIME_STEPS); 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); 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. * Called in the UI thread.
*/ */
private void pulse() { 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() { private long getPulseIntervalMillis() {
@ -272,6 +327,7 @@ public class LiveActivityFragment extends AbstractChartFragment {
@Override @Override
protected void onMadeVisibleInActivity() { protected void onMadeVisibleInActivity() {
GBApplication.deviceService().onEnableRealtimeSteps(true); GBApplication.deviceService().onEnableRealtimeSteps(true);
GBApplication.deviceService().onEnableRealtimeHeartRateMeasurement(true);
super.onMadeVisibleInActivity(); super.onMadeVisibleInActivity();
if (getActivity() != null) { if (getActivity() != null) {
getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
@ -281,6 +337,7 @@ public class LiveActivityFragment extends AbstractChartFragment {
@Override @Override
protected void onMadeInvisibleInActivity() { protected void onMadeInvisibleInActivity() {
GBApplication.deviceService().onEnableRealtimeSteps(false); GBApplication.deviceService().onEnableRealtimeSteps(false);
GBApplication.deviceService().onEnableRealtimeHeartRateMeasurement(false);
if (getActivity() != null) { if (getActivity() != null) {
getActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); getActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
} }
@ -346,6 +403,7 @@ public class LiveActivityFragment extends AbstractChartFragment {
private void setupHistoryChart(BarLineChartBase chart) { private void setupHistoryChart(BarLineChartBase chart) {
configureBarLineChartDefaults(chart); configureBarLineChartDefaults(chart);
chart.setTouchEnabled(false); // no zooming or anything, because it's updated all the time
chart.setBackgroundColor(BACKGROUND_COLOR); chart.setBackgroundColor(BACKGROUND_COLOR);
chart.setDescriptionColor(DESCRIPTION_COLOR); chart.setDescriptionColor(DESCRIPTION_COLOR);
chart.setDescription(getString(R.string.live_activity_steps_per_minute_history)); chart.setDescription(getString(R.string.live_activity_steps_per_minute_history));
@ -367,22 +425,34 @@ public class LiveActivityFragment extends AbstractChartFragment {
y.setDrawGridLines(false); y.setDrawGridLines(false);
y.setDrawTopYLabelEntry(false); y.setDrawTopYLabelEntry(false);
y.setTextColor(CHART_TEXT_COLOR); y.setTextColor(CHART_TEXT_COLOR);
y.setEnabled(true); y.setEnabled(true);
y.setAxisMinValue(0);
YAxis yAxisRight = chart.getAxisRight(); YAxis yAxisRight = chart.getAxisRight();
yAxisRight.setDrawGridLines(false); yAxisRight.setDrawGridLines(false);
yAxisRight.setEnabled(false); yAxisRight.setEnabled(true);
yAxisRight.setDrawLabels(false); yAxisRight.setDrawLabels(true);
yAxisRight.setDrawTopYLabelEntry(false); yAxisRight.setDrawTopYLabelEntry(false);
yAxisRight.setTextColor(CHART_TEXT_COLOR); yAxisRight.setTextColor(CHART_TEXT_COLOR);
yAxisRight.setAxisMaxValue(HeartRateUtils.MAX_HEART_RATE_VALUE);
yAxisRight.setAxisMinValue(HeartRateUtils.MIN_HEART_RATE_VALUE);
mHistorySet = new LineDataSet(new ArrayList<Entry>(), getString(R.string.live_activity_steps_history)); mHistorySet = new LineDataSet(new ArrayList<Entry>(), getString(R.string.live_activity_steps_history));
mHistorySet.setAxisDependency(YAxis.AxisDependency.LEFT);
mHistorySet.setColor(akActivity.color); mHistorySet.setColor(akActivity.color);
mHistorySet.setDrawCircles(false); mHistorySet.setDrawCircles(false);
mHistorySet.setDrawCubic(true); mHistorySet.setDrawCubic(true);
mHistorySet.setDrawFilled(true); mHistorySet.setDrawFilled(true);
mHistorySet.setDrawValues(false); mHistorySet.setDrawValues(false);
mHeartRateSet = new LineDataSet(new ArrayList<Entry>(), 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 @Override

View File

@ -27,6 +27,7 @@ import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityAmount; import nodomain.freeyourgadget.gadgetbridge.model.ActivityAmount;
@ -174,8 +175,8 @@ public class SleepChartFragment extends AbstractChartFragment {
yAxisRight.setDrawLabels(true); yAxisRight.setDrawLabels(true);
yAxisRight.setDrawTopYLabelEntry(true); yAxisRight.setDrawTopYLabelEntry(true);
yAxisRight.setTextColor(CHART_TEXT_COLOR); yAxisRight.setTextColor(CHART_TEXT_COLOR);
yAxisRight.setAxisMaxValue(250); yAxisRight.setAxisMaxValue(HeartRateUtils.MAX_HEART_RATE_VALUE);
yAxisRight.setAxisMinValue(0); yAxisRight.setAxisMinValue(HeartRateUtils.MIN_HEART_RATE_VALUE);
} }
protected void setupLegend(Chart chart) { protected void setupLegend(Chart chart) {

View File

@ -44,6 +44,8 @@ public interface EventHandler {
void onHeartRateTest(); void onHeartRateTest();
void onEnableRealtimeHeartRateMeasurement(boolean enable);
void onFindDevice(boolean start); void onFindDevice(boolean start);
void onScreenshotReq(); void onScreenshotReq();

View File

@ -216,4 +216,11 @@ public class GBDeviceService implements DeviceService {
.putExtra(EXTRA_BOOLEAN_ENABLE, enable); .putExtra(EXTRA_BOOLEAN_ENABLE, enable);
invokeService(intent); invokeService(intent);
} }
@Override
public void onEnableRealtimeHeartRateMeasurement(boolean enable) {
Intent intent = createIntent().setAction(ACTION_ENABLE_REALTIME_HEARTRATE_MEASUREMENT)
.putExtra(EXTRA_BOOLEAN_ENABLE, enable);
invokeService(intent);
}
} }

View File

@ -33,7 +33,9 @@ public interface DeviceService extends EventHandler {
String ACTION_SET_ALARMS = PREFIX + ".action.set_alarms"; String ACTION_SET_ALARMS = PREFIX + ".action.set_alarms";
String ACTION_ENABLE_REALTIME_STEPS = PREFIX + ".action.enable_realtime_steps"; String ACTION_ENABLE_REALTIME_STEPS = PREFIX + ".action.enable_realtime_steps";
String ACTION_REALTIME_STEPS = PREFIX + ".action.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_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_DEVICE_ADDRESS = "device_address";
String EXTRA_NOTIFICATION_BODY = "notification_body"; String EXTRA_NOTIFICATION_BODY = "notification_body";
String EXTRA_NOTIFICATION_FLAGS = "notification_flags"; String EXTRA_NOTIFICATION_FLAGS = "notification_flags";
@ -62,6 +64,7 @@ public interface DeviceService extends EventHandler {
String EXTRA_BOOLEAN_ENABLE = "enable_realtime_steps"; String EXTRA_BOOLEAN_ENABLE = "enable_realtime_steps";
String EXTRA_REALTIME_STEPS = "realtime_steps"; String EXTRA_REALTIME_STEPS = "realtime_steps";
String EXTRA_TIMESTAMP = "timestamp"; String EXTRA_TIMESTAMP = "timestamp";
String EXTRA_HEART_RATE_VALUE = "hr_value";
void start(); void start();

View File

@ -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);
}
}

View File

@ -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_DELETEAPP;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_DISCONNECT; 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_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_ENABLE_REALTIME_STEPS;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_FETCH_ACTIVITY_DATA; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_FETCH_ACTIVITY_DATA;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_FIND_DEVICE; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_FIND_DEVICE;
@ -349,6 +350,11 @@ public class DeviceCommunicationService extends Service {
mDeviceSupport.onEnableHeartRateSleepSupport(enable); mDeviceSupport.onEnableHeartRateSleepSupport(enable);
break; break;
} }
case ACTION_ENABLE_REALTIME_HEARTRATE_MEASUREMENT: {
boolean enable = intent.getBooleanExtra(EXTRA_BOOLEAN_ENABLE, false);
mDeviceSupport.onEnableRealtimeHeartRateMeasurement(enable);
break;
}
} }
return START_STICKY; return START_STICKY;

View File

@ -250,4 +250,12 @@ public class ServiceDeviceSupport implements DeviceSupport {
} }
delegate.onEnableHeartRateSleepSupport(enable); delegate.onEnableHeartRateSleepSupport(enable);
} }
@Override
public void onEnableRealtimeHeartRateMeasurement(boolean enable) {
if (checkBusy("enable realtime heart rate measurement: " + enable)) {
return;
}
delegate.onEnableRealtimeHeartRateMeasurement(enable);
}
} }

View File

@ -603,7 +603,6 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
TransactionBuilder builder = performInitialized("HeartRateTest"); 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), stopHeartMeasurementContinuous);
builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT), stopHeartMeasurementManual); 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.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT), startHeartMeasurementManual);
builder.queue(getQueue()); builder.queue(getQueue());
} catch (IOException ex) { } 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() { public boolean supportsHeartRate() {
return getDeviceInfo() != null && getDeviceInfo().supportsHeartrate(); return getDeviceInfo() != null && getDeviceInfo().supportsHeartrate();
} }
@ -745,7 +763,7 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
} else if (MiBandService.UUID_CHARACTERISTIC_REALTIME_STEPS.equals(characteristicUUID)) { } else if (MiBandService.UUID_CHARACTERISTIC_REALTIME_STEPS.equals(characteristicUUID)) {
handleRealtimeSteps(characteristic.getValue()); handleRealtimeSteps(characteristic.getValue());
} else if (MiBandService.UUID_CHARACTERISTIC_HEART_RATE_MEASUREMENT.equals(characteristicUUID)) { } else if (MiBandService.UUID_CHARACTERISTIC_HEART_RATE_MEASUREMENT.equals(characteristicUUID)) {
logHeartrate(characteristic.getValue()); handleHeartrate(characteristic.getValue());
} else { } else {
LOG.info("Unhandled characteristic changed: " + characteristicUUID); LOG.info("Unhandled characteristic changed: " + characteristicUUID);
logMessageContent(characteristic.getValue()); 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) { private void handleRealtimeSteps(byte[] value) {
int steps = 0xff & value[0] | (0xff & value[1]) << 8; int steps = 0xff & value[0] | (0xff & value[1]) << 8;

View File

@ -181,4 +181,10 @@ public abstract class AbstractSerialDeviceSupport extends AbstractDeviceSupport
byte[] bytes = gbDeviceProtocol.encodeEnableHeartRateSleepSupport(enable); byte[] bytes = gbDeviceProtocol.encodeEnableHeartRateSleepSupport(enable);
sendToDevice(bytes); sendToDevice(bytes);
} }
@Override
public void onEnableRealtimeHeartRateMeasurement(boolean enable) {
byte[] bytes = gbDeviceProtocol.encodeEnableRealtimeHeartRateMeasurement(enable);
sendToDevice(bytes);
}
} }

View File

@ -63,6 +63,8 @@ public abstract class GBDeviceProtocol {
return null; return null;
} }
public byte[] encodeEnableRealtimeHeartRateMeasurement(boolean enable) { return null; }
public GBDeviceEvent[] decodeResponse(byte[] responseData) { public GBDeviceEvent[] decodeResponse(byte[] responseData) {
return null; return null;
} }

View File

@ -12,6 +12,7 @@
<color name="divider" type="color">#1f000000</color> <color name="divider" type="color">#1f000000</color>
<color name="chart_heartrate" type="color">#ffab40</color> <color name="chart_heartrate" type="color">#ffab40</color>
<color name="chart_heartrate_fill" type="color">#fadab1</color>
<color name="chart_deep_sleep_light" type="color">#0071b7</color> <color name="chart_deep_sleep_light" type="color">#0071b7</color>
<color name="chart_deep_sleep_dark" type="color">#4c5aff</color> <color name="chart_deep_sleep_dark" type="color">#4c5aff</color>

View File

@ -240,5 +240,6 @@
<string name="updatefirmwareoperation_update_in_progress">Firmware update in progress</string> <string name="updatefirmwareoperation_update_in_progress">Firmware update in progress</string>
<string name="updatefirmwareoperation_firmware_not_sent">Firmware not sent</string> <string name="updatefirmwareoperation_firmware_not_sent">Firmware not sent</string>
<string name="charts_legend_heartrate">Heart Rate</string> <string name="charts_legend_heartrate">Heart Rate</string>
<string name="live_activity_heart_rate">Heart Rate</string>
</resources> </resources>

View File

@ -129,4 +129,9 @@ public class TestDeviceSupport extends AbstractDeviceSupport {
public void onEnableHeartRateSleepSupport(boolean enable) { public void onEnableHeartRateSleepSupport(boolean enable) {
} }
@Override
public void onEnableRealtimeHeartRateMeasurement(boolean enable) {
}
} }