Initial live heartrate measurement in the live activity tab #178
This commit is contained in:
parent
58d90c2a66
commit
f15a97d994
|
@ -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;
|
||||||
|
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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 (mStepsPerMinuteHistoryChart.getData() == null) {
|
if (!addHistoryDataSet(false)) {
|
||||||
if (mSteps.getTotalSteps() == 0) {
|
return;
|
||||||
return; // ignore the first default value to keep the "no-data-description" visible
|
|
||||||
}
|
|
||||||
LineData data = new LineData();
|
|
||||||
mStepsPerMinuteHistoryChart.setData(data);
|
|
||||||
data.addDataSet(mHistorySet);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
LineData historyData = (LineData) mStepsPerMinuteHistoryChart.getData();
|
ChartData data = mStepsPerMinuteHistoryChart.getData();
|
||||||
historyData.addXValue("");
|
data.addXValue("");
|
||||||
historyData.addEntry(new Entry(stepsPerMinute, mHistorySet.getEntryCount()), 0);
|
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();
|
private boolean addHistoryDataSet(boolean force) {
|
||||||
mStepsPerMinuteData.notifyDataSetChanged();
|
if (mStepsPerMinuteHistoryChart.getData() == null) {
|
||||||
mStepsPerMinuteHistoryChart.notifyDataSetChanged();
|
// ignore the first default value to keep the "no-data-description" visible
|
||||||
|
if (force || mSteps.getTotalSteps() > 0) {
|
||||||
renderCharts();
|
LineData data = new LineData();
|
||||||
|
data.addDataSet(mHistorySet);
|
||||||
|
data.addDataSet(mHeartRateSet);
|
||||||
|
mStepsPerMinuteHistoryChart.setData(data);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@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
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue