1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-11-04 01:09:47 +01:00

Some work in progress for heart rate graphs #178

Currently we get the heart rate when synchronizing activity data
(i.e. not live) and we write it to the activity database so that we
can show a nice graph. The value is currently always 0 though,
because we can't enable recording hr, yet.
This commit is contained in:
cpfeiffer 2016-02-26 23:45:17 +01:00
parent df741e9571
commit 0ef738067d
17 changed files with 130 additions and 42 deletions

View File

@ -12,9 +12,14 @@ import android.view.View;
import com.github.mikephil.charting.charts.BarLineChartBase;
import com.github.mikephil.charting.charts.Chart;
import com.github.mikephil.charting.charts.CombinedChart;
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.CombinedData;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.data.LineData;
import com.github.mikephil.charting.data.LineDataSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -23,6 +28,7 @@ import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashSet;
@ -72,12 +78,16 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
}
};
private boolean mChartDirty = true;
private boolean supportsHeartrateChart = false;
public boolean isChartDirty() {
return mChartDirty;
}
public abstract String getTitle();
public boolean supportsHeartrate() {
return supportsHeartrateChart;
}
protected static final class ActivityConfig {
public final int type;
@ -101,6 +111,7 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
protected int DESCRIPTION_COLOR;
protected int CHART_TEXT_COLOR;
protected int LEGEND_TEXT_COLOR;
protected int HEARTRATE_COLOR;
protected int AK_ACTIVITY_COLOR;
protected int AK_DEEP_SLEEP_COLOR;
protected int AK_LIGHT_SLEEP_COLOR;
@ -134,6 +145,7 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
DESCRIPTION_COLOR = getResources().getColor(R.color.primarytext);
CHART_TEXT_COLOR = getResources().getColor(R.color.secondarytext);
LEGEND_TEXT_COLOR = getResources().getColor(R.color.primarytext);
HEARTRATE_COLOR = getResources().getColor(R.color.chart_heartrate);
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);
@ -389,6 +401,8 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
int numEntries = samples.size();
List<String> xLabels = new ArrayList<>(numEntries);
List<BarEntry> activityEntries = new ArrayList<>(numEntries);
boolean hr = supportsHeartrate();
List<Entry> heartrateEntries = hr ? new ArrayList<Entry>(numEntries) : null;
List<Integer> colors = new ArrayList<>(numEntries); // this is kinda inefficient...
for (int i = 0; i < numEntries; i++) {
@ -431,6 +445,9 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
colors.add(akActivity.color);
}
activityEntries.add(createBarEntry(value, i));
if (hr) {
heartrateEntries.add(createLineEntry(sample.getCustomShortValue(), i));
}
String xLabel = "";
if (annotate) {
@ -463,13 +480,19 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
chart.getXAxis().setValues(xLabels);
BarDataSet activitySet = createActivitySet(activityEntries, colors, "Activity");
ArrayList<BarDataSet> dataSets = new ArrayList<>();
dataSets.add(activitySet);
// create a data object with the datasets
BarData data = new BarData(xLabels, dataSets);
data.setGroupSpace(0);
CombinedData combinedData = new CombinedData(xLabels);
List<BarDataSet> list = new ArrayList<>();
list.add(activitySet);
BarData barData = new BarData(xLabels, list);
barData.setGroupSpace(0);
combinedData.setData(barData);
if (hr) {
LineDataSet heartrateSet = createHeartrateSet(heartrateEntries, "Heart Rate");
LineData lineData = new LineData(xLabels, heartrateSet);
combinedData.setData(lineData);
}
chart.setDescription("");
// chart.setDescription(getString(R.string.sleep_activity_date_range, dateStringFrom, dateStringTo));
@ -477,7 +500,7 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
setupLegend(chart);
chart.setData(data);
chart.setData(combinedData);
}
}
@ -498,6 +521,10 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
return new BarEntry(value, index);
}
protected Entry createLineEntry(float value, int index) {
return new Entry(value, index);
}
protected BarDataSet createActivitySet(List<BarEntry> values, List<Integer> colors, String label) {
BarDataSet set1 = new BarDataSet(values, label);
set1.setColors(colors);
@ -515,6 +542,24 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
return set1;
}
protected LineDataSet createHeartrateSet(List<Entry> values, String label) {
LineDataSet set1 = new LineDataSet(values, label);
set1.setColor(HEARTRATE_COLOR);
// set1.setColors(colors);
// set1.setDrawCubic(true);
// set1.setCubicIntensity(0.2f);
// //set1.setDrawFilled(true);
// set1.setDrawCircles(false);
// set1.setLineWidth(2f);
// set1.setCircleSize(5f);
// set1.setFillColor(ColorTemplate.getHoloBlue());
set1.setDrawValues(false);
// set1.setHighLightColor(Color.rgb(128, 0, 255));
// set1.setColor(Color.rgb(89, 178, 44));
set1.setValueTextColor(CHART_TEXT_COLOR);
return set1;
}
protected BarDataSet createDeepSleepSet(List<BarEntry> values, String label) {
BarDataSet set1 = new BarDataSet(values, label);
// set1.setDrawCubic(true);

View File

@ -78,7 +78,7 @@ public class ActivitySleepChartFragment extends AbstractChartFragment {
YAxis yAxisRight = mChart.getAxisRight();
yAxisRight.setDrawGridLines(false);
yAxisRight.setEnabled(false);
yAxisRight.setEnabled(supportsHeartrate());
yAxisRight.setDrawLabels(false);
yAxisRight.setDrawTopYLabelEntry(false);
yAxisRight.setTextColor(CHART_TEXT_COLOR);

View File

@ -10,6 +10,7 @@ import android.view.ViewGroup;
import com.github.mikephil.charting.animation.Easing;
import com.github.mikephil.charting.charts.BarLineChartBase;
import com.github.mikephil.charting.charts.Chart;
import com.github.mikephil.charting.charts.CombinedChart;
import com.github.mikephil.charting.charts.PieChart;
import com.github.mikephil.charting.components.XAxis;
import com.github.mikephil.charting.components.YAxis;
@ -39,7 +40,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
public class SleepChartFragment extends AbstractChartFragment {
protected static final Logger LOG = LoggerFactory.getLogger(ActivitySleepChartFragment.class);
private BarLineChartBase mActivityChart;
private CombinedChart mActivityChart;
private PieChart mSleepAmountChart;
private int mSmartAlarmFrom = -1;
@ -99,7 +100,7 @@ public class SleepChartFragment extends AbstractChartFragment {
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_sleepchart, container, false);
mActivityChart = (BarLineChartBase) rootView.findViewById(R.id.sleepchart);
mActivityChart = (CombinedChart) rootView.findViewById(R.id.sleepchart);
mSleepAmountChart = (PieChart) rootView.findViewById(R.id.sleepchart_pie_light_deep);
setupActivityChart();

View File

@ -27,13 +27,14 @@ import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_PROV
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_STEPS;
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_TIMESTAMP;
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_TYPE;
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_CUSTOM_SHORT;
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.TABLE_GBACTIVITYSAMPLES;
public class ActivityDatabaseHandler extends SQLiteOpenHelper implements DBHandler {
public class ActivityDatabaseHandler extends SQLiteOpenHelper implements DBHandler {
private static final Logger LOG = LoggerFactory.getLogger(ActivityDatabaseHandler.class);
private static final int DATABASE_VERSION = 5;
private static final int DATABASE_VERSION = 6;
public ActivityDatabaseHandler(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
@ -101,6 +102,7 @@ public class ActivityDatabaseHandler extends SQLiteOpenHelper implements DBHandl
values.put(KEY_PROVIDER, sample.getProvider().getID());
values.put(KEY_INTENSITY, sample.getRawIntensity());
values.put(KEY_STEPS, sample.getSteps());
values.put(KEY_CUSTOM_SHORT, sample.getCustomShortValue());
values.put(KEY_TYPE, sample.getRawKind());
db.insert(TABLE_GBACTIVITYSAMPLES, null, values);
@ -117,7 +119,7 @@ public class ActivityDatabaseHandler extends SQLiteOpenHelper implements DBHandl
* @param kind the raw activity kind of the sample
*/
@Override
public void addGBActivitySample(int timestamp, byte provider, short intensity, short steps, byte kind) {
public void addGBActivitySample(int timestamp, byte provider, short intensity, short steps, byte kind, short customShortValue) {
if (intensity < 0) {
LOG.error("negative intensity received, ignoring");
intensity = 0;
@ -127,6 +129,11 @@ public class ActivityDatabaseHandler extends SQLiteOpenHelper implements DBHandl
steps = 0;
}
if (customShortValue < 0) {
LOG.error("negative short value received, ignoring");
customShortValue = 0;
}
try (SQLiteDatabase db = this.getWritableDatabase()) {
ContentValues values = new ContentValues();
values.put(KEY_TIMESTAMP, timestamp);
@ -134,6 +141,7 @@ public class ActivityDatabaseHandler extends SQLiteOpenHelper implements DBHandl
values.put(KEY_INTENSITY, intensity);
values.put(KEY_STEPS, steps);
values.put(KEY_TYPE, kind);
values.put(KEY_CUSTOM_SHORT, customShortValue);
db.insert(TABLE_GBACTIVITYSAMPLES, null, values);
}
@ -144,8 +152,8 @@ public class ActivityDatabaseHandler extends SQLiteOpenHelper implements DBHandl
try (SQLiteDatabase db = this.getWritableDatabase()) {
String sql = "INSERT INTO " + TABLE_GBACTIVITYSAMPLES + " (" + KEY_TIMESTAMP + "," +
KEY_PROVIDER + "," + KEY_INTENSITY + "," + KEY_STEPS + "," + KEY_TYPE + ")" +
" VALUES (?,?,?,?,?);";
KEY_PROVIDER + "," + KEY_INTENSITY + "," + KEY_STEPS + "," + KEY_TYPE + "," + KEY_CUSTOM_SHORT + ")" +
" VALUES (?,?,?,?,?,?);";
SQLiteStatement statement = db.compileStatement(sql);
db.beginTransaction();
@ -156,6 +164,7 @@ public class ActivityDatabaseHandler extends SQLiteOpenHelper implements DBHandl
statement.bindLong(3, activitySample.getRawIntensity());
statement.bindLong(4, activitySample.getSteps());
statement.bindLong(5, activitySample.getRawKind());
statement.bindLong(6, activitySample.getCustomShortValue());
statement.execute();
}
db.setTransactionSuccessful();
@ -216,7 +225,8 @@ public class ActivityDatabaseHandler extends SQLiteOpenHelper implements DBHandl
cursor.getInt(cursor.getColumnIndex(KEY_TIMESTAMP)),
cursor.getShort(cursor.getColumnIndex(KEY_INTENSITY)),
cursor.getShort(cursor.getColumnIndex(KEY_STEPS)),
(byte) cursor.getShort(cursor.getColumnIndex(KEY_TYPE)));
(byte) cursor.getShort(cursor.getColumnIndex(KEY_TYPE)),
cursor.getShort(cursor.getColumnIndex(KEY_CUSTOM_SHORT)));
samples.add(sample);
} while (cursor.moveToNext());
}

View File

@ -10,5 +10,6 @@ public class DBConstants {
public static final String KEY_PROVIDER = "provider";
public static final String KEY_INTENSITY = "intensity";
public static final String KEY_STEPS = "steps";
public static final String KEY_CUSTOM_SHORT = "customShort";
public static final String KEY_TYPE = "type";
}

View File

@ -24,7 +24,7 @@ public interface DBHandler {
List<ActivitySample> getSleepSamples(int tsFrom, int tsTo, SampleProvider provider);
void addGBActivitySample(int timestamp, byte provider, short intensity, short steps, byte kind);
void addGBActivitySample(int timestamp, byte provider, short intensity, short steps, byte kind, short heartrate);
void addGBActivitySamples(ActivitySample[] activitySamples);

View File

@ -5,27 +5,25 @@ import android.database.sqlite.SQLiteDatabase;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.database.DBUpdateScript;
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_CUSTOM_SHORT;
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_PROVIDER;
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_STEPS;
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_TIMESTAMP;
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.TABLE_GBACTIVITYSAMPLES;
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.TABLE_STEPS_PER_DAY;
/**
* Adds a table "STEPS_PER_DAY".
* Adds a column "customShort" to the table "GBActivitySamples"
*/
public class ActivityDBUpdate_6 implements DBUpdateScript {
@Override
public void upgradeSchema(SQLiteDatabase db) {
String CREATE_STEPS_PER_DAY_TABLE = "CREATE TABLE IF NOT EXISTS " + TABLE_STEPS_PER_DAY + " ("
+ KEY_TIMESTAMP + " INT,"
+ KEY_PROVIDER + " TINYINT,"
+ KEY_STEPS + " MEDIUMINT,"
+ " PRIMARY KEY (" + KEY_TIMESTAMP + "," + KEY_PROVIDER + ") ON CONFLICT REPLACE)" + DBHelper.getWithoutRowId();
db.execSQL(CREATE_STEPS_PER_DAY_TABLE);
String ADD_COLUMN_CUSTOM_SHORT = "ALTER TABLE " + TABLE_GBACTIVITYSAMPLES + " ADD COLUMN "
+ KEY_CUSTOM_SHORT + " INT;";
db.execSQL(ADD_COLUMN_CUSTOM_SHORT);
}
@Override
public void downgradeSchema(SQLiteDatabase db) {
DBHelper.dropTable(TABLE_STEPS_PER_DAY, db);
}
}

View File

@ -0,0 +1,31 @@
package nodomain.freeyourgadget.gadgetbridge.database.schema;
import android.database.sqlite.SQLiteDatabase;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.database.DBUpdateScript;
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_PROVIDER;
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_STEPS;
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_TIMESTAMP;
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.TABLE_STEPS_PER_DAY;
/**
* Adds a table "STEPS_PER_DAY".
*/
public class ActivityDBUpdate_X implements DBUpdateScript {
@Override
public void upgradeSchema(SQLiteDatabase db) {
String CREATE_STEPS_PER_DAY_TABLE = "CREATE TABLE IF NOT EXISTS " + TABLE_STEPS_PER_DAY + " ("
+ KEY_TIMESTAMP + " INT,"
+ KEY_PROVIDER + " TINYINT,"
+ KEY_STEPS + " MEDIUMINT,"
+ " PRIMARY KEY (" + KEY_TIMESTAMP + "," + KEY_PROVIDER + ") ON CONFLICT REPLACE)" + DBHelper.getWithoutRowId();
db.execSQL(CREATE_STEPS_PER_DAY_TABLE);
}
@Override
public void downgradeSchema(SQLiteDatabase db) {
DBHelper.dropTable(TABLE_STEPS_PER_DAY, db);
}
}

View File

@ -9,19 +9,19 @@ public class GBActivitySample implements ActivitySample {
private final SampleProvider provider;
private final short intensity;
private final short steps;
private final short heartrate;
private final byte type;
private final short customShortValue;
public GBActivitySample(SampleProvider provider, int timestamp, short intensity, short steps, byte type) {
this(provider, timestamp, intensity, steps, (short) 0, type);
this(provider, timestamp, intensity, steps, type, (short) 0);
}
public GBActivitySample(SampleProvider provider, int timestamp, short intensity, short steps, short heartrate, byte type) {
public GBActivitySample(SampleProvider provider, int timestamp, short intensity, short steps, byte type, short customShortValue) {
this.timestamp = timestamp;
this.provider = provider;
this.intensity = intensity;
this.steps = steps;
this.heartrate = heartrate;
this.customShortValue = customShortValue;
this.type = type;
validate();
}
@ -36,8 +36,8 @@ public class GBActivitySample implements ActivitySample {
if (timestamp < 0) {
throw new IllegalArgumentException("timestamp must be >= 0");
}
if (heartrate < 0) {
throw new IllegalArgumentException("heartrate must be >= 0");
if (customShortValue < 0) {
throw new IllegalArgumentException("customShortValue must be >= 0");
}
}
@ -77,8 +77,8 @@ public class GBActivitySample implements ActivitySample {
}
@Override
public short getHeartRate() {
return heartrate;
public short getCustomShortValue() {
return customShortValue;
}
@Override
@ -87,7 +87,7 @@ public class GBActivitySample implements ActivitySample {
"timestamp=" + DateTimeUtils.formatDateTime(DateTimeUtils.parseTimeStamp(timestamp)) +
", intensity=" + getIntensity() +
", steps=" + getSteps() +
", heartrate=" + getHeartRate() +
", customShortValue=" + getCustomShortValue() +
", type=" + getKind() +
'}';
}

View File

@ -42,5 +42,5 @@ public interface ActivitySample {
*/
short getSteps();
short getHeartRate();
short getCustomShortValue();
}

View File

@ -305,7 +305,7 @@ public class FetchActivityOperation extends AbstractMiBandOperation {
}
int bpm = getBytesPerMinuteOfActivityData();
LOG.debug("flushing activity data samples: " + activityStruct.activityDataHolderProgress / bpm);
byte category, intensity, steps, heartrate;
byte category, intensity, steps, heartrate = 0;
DBHandler dbHandler = null;
try {
@ -334,7 +334,8 @@ public class FetchActivityOperation extends AbstractMiBandOperation {
timestampInSeconds,
(short) (intensity & 0xff),
(short) (steps & 0xff),
category);
category,
(short) (heartrate & 0xff));
// next minute
minutes++;

View File

@ -55,7 +55,7 @@ public class AppMessageHandlerGBPebble extends AppMessageHandler {
byte type = (byte) ((sample & 0xe000) >>> 13);
byte intensity = (byte) ((sample & 0x1f80) >>> 7);
byte steps = (byte) (sample & 0x007f);
db.addGBActivitySample(timestamp + offset_seconds, SampleProvider.PROVIDER_PEBBLE_GADGETBRIDGE, (short) (intensity & 0xff), (short) (steps & 0xff), type);
db.addGBActivitySample(timestamp + offset_seconds, SampleProvider.PROVIDER_PEBBLE_GADGETBRIDGE, (short) (intensity & 0xff), (short) (steps & 0xff), type, (short)0);
offset_seconds += 60;
}
} catch (GBException e) {

View File

@ -93,7 +93,7 @@ public class AppMessageHandlerMorpheuz extends AppMessageHandler {
DBHandler db = null;
try {
db = GBApplication.acquireDB();
db.addGBActivitySample(recording_base_timestamp + index * 600, SampleProvider.PROVIDER_PEBBLE_MORPHEUZ, intensity, (byte) 0, type);
db.addGBActivitySample(recording_base_timestamp + index * 600, SampleProvider.PROVIDER_PEBBLE_MORPHEUZ, intensity, (byte) 0, type, (short)0);
} catch (GBException e) {
LOG.error("Error acquiring database", e);
} finally {

View File

@ -11,7 +11,7 @@
android:layout_weight="40">
</com.github.mikephil.charting.charts.PieChart>
<com.github.mikephil.charting.charts.BarChart
<com.github.mikephil.charting.charts.CombinedChart
android:id="@+id/sleepchart"
android:layout_width="fill_parent"
android:layout_height="fill_parent"

View File

@ -3,7 +3,7 @@
android:layout_height="match_parent"
tools:context="nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsActivity$PlaceholderFragment">
<com.github.mikephil.charting.charts.BarChart
<com.github.mikephil.charting.charts.CombinedChart
android:id="@+id/activitysleepchart"
android:layout_width="match_parent"
android:layout_height="match_parent"

View File

@ -11,7 +11,7 @@
android:layout_weight="20">
</com.github.mikephil.charting.charts.PieChart>
<com.github.mikephil.charting.charts.BarChart
<com.github.mikephil.charting.charts.CombinedChart
android:id="@+id/sleepchart"
android:layout_width="fill_parent"
android:layout_height="fill_parent"

View File

@ -11,6 +11,7 @@
<color name="secondarytext" type="color">#ff808080</color>
<color name="divider" type="color">#1f000000</color>
<color name="chart_heartrate" type="color">#b40000</color>
<color name="chart_deep_sleep_light" type="color">#0071b7</color>
<color name="chart_deep_sleep_dark" type="color">#4c5aff</color>