mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-08-25 00:30:37 +02:00
Do summary parsing on the fly if raw binary data is available
This commit is contained in:
parent
da53062d31
commit
715fb67859
@ -50,10 +50,12 @@ import java.util.Iterator;
|
|||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiActivitySummaryParser;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
|
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryItems;
|
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryItems;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
|
import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
|
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||||
@ -61,7 +63,6 @@ import nodomain.freeyourgadget.gadgetbridge.util.SwipeEvents;
|
|||||||
|
|
||||||
public class ActivitySummaryDetail extends AbstractGBActivity {
|
public class ActivitySummaryDetail extends AbstractGBActivity {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(ActivitySummaryDetail.class);
|
private static final Logger LOG = LoggerFactory.getLogger(ActivitySummaryDetail.class);
|
||||||
private GBDevice mGBDevice;
|
|
||||||
private JSONObject groupData = setGroups();
|
private JSONObject groupData = setGroups();
|
||||||
private boolean show_raw_data = false;
|
private boolean show_raw_data = false;
|
||||||
BaseActivitySummary currentItem = null;
|
BaseActivitySummary currentItem = null;
|
||||||
@ -73,10 +74,10 @@ public class ActivitySummaryDetail extends AbstractGBActivity {
|
|||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.activity_summary_details);
|
setContentView(R.layout.activity_summary_details);
|
||||||
Intent intent = getIntent();
|
Intent intent = getIntent();
|
||||||
mGBDevice = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
|
GBDevice gbDevice = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
|
||||||
final int filter = intent.getIntExtra("filter",0);
|
final int filter = intent.getIntExtra("filter", 0);
|
||||||
final int position = intent.getIntExtra("position",0);
|
final int position = intent.getIntExtra("position", 0);
|
||||||
final ActivitySummaryItems items = new ActivitySummaryItems(this, mGBDevice, filter);
|
final ActivitySummaryItems items = new ActivitySummaryItems(this, gbDevice, filter);
|
||||||
final RelativeLayout layout = findViewById(R.id.activity_summary_detail_relative_layout);
|
final RelativeLayout layout = findViewById(R.id.activity_summary_detail_relative_layout);
|
||||||
alternateColor = getAlternateColor(this);
|
alternateColor = getAlternateColor(this);
|
||||||
|
|
||||||
@ -131,7 +132,7 @@ public class ActivitySummaryDetail extends AbstractGBActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//allows long-press.switch of data being in raw form or recalculated
|
//allows long-press.switch of data being in raw form or recalculated
|
||||||
ImageView activity_icon = (ImageView) findViewById(R.id.item_image);
|
ImageView activity_icon = findViewById(R.id.item_image);
|
||||||
activity_icon.setOnLongClickListener(new View.OnLongClickListener() {
|
activity_icon.setOnLongClickListener(new View.OnLongClickListener() {
|
||||||
public boolean onLongClick(View v) {
|
public boolean onLongClick(View v) {
|
||||||
show_raw_data=!show_raw_data;
|
show_raw_data=!show_raw_data;
|
||||||
@ -149,7 +150,7 @@ public class ActivitySummaryDetail extends AbstractGBActivity {
|
|||||||
private void makeSummaryHeader(BaseActivitySummary item){
|
private void makeSummaryHeader(BaseActivitySummary item){
|
||||||
//make view of data from main part of item
|
//make view of data from main part of item
|
||||||
final String gpxTrack = item.getGpxTrack();
|
final String gpxTrack = item.getGpxTrack();
|
||||||
Button show_track_btn = (Button) findViewById(R.id.showTrack);
|
Button show_track_btn = findViewById(R.id.showTrack);
|
||||||
show_track_btn.setVisibility(View.GONE);
|
show_track_btn.setVisibility(View.GONE);
|
||||||
|
|
||||||
if (gpxTrack != null) {
|
if (gpxTrack != null) {
|
||||||
@ -165,34 +166,40 @@ public class ActivitySummaryDetail extends AbstractGBActivity {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
String activitykindname = ActivityKind.asString(item.getActivityKind(), getApplicationContext());
|
String activitykindname = ActivityKind.asString(item.getActivityKind(), getApplicationContext());
|
||||||
Date starttime = (Date) item.getStartTime();
|
Date starttime = item.getStartTime();
|
||||||
Date endtime = (Date) item.getEndTime();
|
Date endtime = item.getEndTime();
|
||||||
String starttimeS = DateTimeUtils.formatDateTime(starttime);
|
String starttimeS = DateTimeUtils.formatDateTime(starttime);
|
||||||
String endtimeS = DateTimeUtils.formatDateTime(endtime);
|
String endtimeS = DateTimeUtils.formatDateTime(endtime);
|
||||||
String durationhms = DateTimeUtils.formatDurationHoursMinutes((endtime.getTime() - starttime.getTime()), TimeUnit.MILLISECONDS);
|
String durationhms = DateTimeUtils.formatDurationHoursMinutes((endtime.getTime() - starttime.getTime()), TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
ImageView activity_icon = (ImageView) findViewById(R.id.item_image);
|
ImageView activity_icon = findViewById(R.id.item_image);
|
||||||
activity_icon.setImageResource(ActivityKind.getIconId(item.getActivityKind()));
|
activity_icon.setImageResource(ActivityKind.getIconId(item.getActivityKind()));
|
||||||
|
|
||||||
TextView activity_kind = (TextView) findViewById(R.id.activitykind);
|
TextView activity_kind = findViewById(R.id.activitykind);
|
||||||
activity_kind.setText(activitykindname);
|
activity_kind.setText(activitykindname);
|
||||||
TextView start_time = (TextView) findViewById(R.id.starttime);
|
TextView start_time = findViewById(R.id.starttime);
|
||||||
start_time.setText(starttimeS);
|
start_time.setText(starttimeS);
|
||||||
TextView end_time = (TextView) findViewById(R.id.endtime);
|
TextView end_time = findViewById(R.id.endtime);
|
||||||
end_time.setText(endtimeS);
|
end_time.setText(endtimeS);
|
||||||
TextView activity_duration = (TextView) findViewById(R.id.duration);
|
TextView activity_duration = findViewById(R.id.duration);
|
||||||
activity_duration.setText(durationhms);
|
activity_duration.setText(durationhms);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void makeSummaryContent (BaseActivitySummary item){
|
private void makeSummaryContent(BaseActivitySummary item) {
|
||||||
//make view of data from summaryData of item
|
//make view of data from summaryData of item
|
||||||
|
|
||||||
TableLayout fieldLayout = findViewById(R.id.summaryDetails);
|
TableLayout fieldLayout = findViewById(R.id.summaryDetails);
|
||||||
fieldLayout.removeAllViews(); //remove old widgets
|
fieldLayout.removeAllViews(); //remove old widgets
|
||||||
|
|
||||||
JSONObject summarySubdata = null;
|
JSONObject summarySubdata = null;
|
||||||
JSONObject data = null;
|
JSONObject data;
|
||||||
|
|
||||||
|
// Parse on the fly if we have binary data available
|
||||||
|
if (item.getRawSummaryData() != null) {
|
||||||
|
ActivitySummaryParser parser = new HuamiActivitySummaryParser(); // FIXME: if something else than huami supports that make sure to have the right parser
|
||||||
|
item = parser.parseBinaryData(item);
|
||||||
|
}
|
||||||
|
|
||||||
String sumData = item.getSummaryData();
|
String sumData = item.getSummaryData();
|
||||||
|
|
||||||
@ -207,8 +214,6 @@ public class ActivitySummaryDetail extends AbstractGBActivity {
|
|||||||
if (summarySubdata == null) return;
|
if (summarySubdata == null) return;
|
||||||
data = makeSummaryList(summarySubdata); //make new list, grouped by groups
|
data = makeSummaryList(summarySubdata); //make new list, grouped by groups
|
||||||
|
|
||||||
if (data == null) return;
|
|
||||||
|
|
||||||
Iterator<String> keys = data.keys();
|
Iterator<String> keys = data.keys();
|
||||||
DecimalFormat df = new DecimalFormat("#.##");
|
DecimalFormat df = new DecimalFormat("#.##");
|
||||||
|
|
||||||
|
@ -0,0 +1,276 @@
|
|||||||
|
/* Copyright (C) 2020 Andreas Shimokawa
|
||||||
|
|
||||||
|
This file is part of Gadgetbridge.
|
||||||
|
|
||||||
|
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published
|
||||||
|
by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Gadgetbridge is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||||
|
package nodomain.freeyourgadget.gadgetbridge.devices.huami;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiActivityDetailsParser;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSportsActivityType;
|
||||||
|
|
||||||
|
public class HuamiActivitySummaryParser implements ActivitySummaryParser {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(HuamiActivityDetailsParser.class);
|
||||||
|
private JSONObject summaryData = new JSONObject();
|
||||||
|
|
||||||
|
|
||||||
|
public BaseActivitySummary parseBinaryData(BaseActivitySummary summary) {
|
||||||
|
Date startTime = summary.getStartTime();
|
||||||
|
if (startTime == null) {
|
||||||
|
LOG.error("Due to a bug, we can only parse the summary when startTime is already set");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return parseBinaryData(summary, startTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BaseActivitySummary parseBinaryData(BaseActivitySummary summary, Date startTime) {
|
||||||
|
summaryData = new JSONObject();
|
||||||
|
ByteBuffer buffer = ByteBuffer.wrap(summary.getRawSummaryData()).order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
|
||||||
|
short version = buffer.getShort(); // version
|
||||||
|
LOG.debug("Got sport summary version " + version + " total bytes=" + buffer.capacity());
|
||||||
|
int activityKind = ActivityKind.TYPE_UNKNOWN;
|
||||||
|
try {
|
||||||
|
int rawKind = BLETypeConversions.toUnsigned(buffer.getShort());
|
||||||
|
HuamiSportsActivityType activityType = HuamiSportsActivityType.fromCode(rawKind);
|
||||||
|
activityKind = activityType.toActivityKind();
|
||||||
|
} catch (Exception ex) {
|
||||||
|
LOG.error("Error mapping activity kind: " + ex.getMessage(), ex);
|
||||||
|
}
|
||||||
|
summary.setActivityKind(activityKind);
|
||||||
|
|
||||||
|
// FIXME: should honor timezone we were in at that time etc
|
||||||
|
long timestamp_start = BLETypeConversions.toUnsigned(buffer.getInt()) * 1000;
|
||||||
|
long timestamp_end = BLETypeConversions.toUnsigned(buffer.getInt()) * 1000;
|
||||||
|
|
||||||
|
|
||||||
|
// FIXME: should be done like this but seems to return crap when in DST
|
||||||
|
//summary.setStartTime(new Date(timestamp_start));
|
||||||
|
//summary.setEndTime(new Date(timestamp_end));
|
||||||
|
|
||||||
|
// FIXME ... so do it like this
|
||||||
|
long duration = timestamp_end - timestamp_start;
|
||||||
|
summary.setEndTime(new Date(startTime.getTime() + duration));
|
||||||
|
|
||||||
|
int baseLongitude = buffer.getInt();
|
||||||
|
int baseLatitude = buffer.getInt();
|
||||||
|
int baseAltitude = buffer.getInt();
|
||||||
|
summary.setBaseLongitude(baseLongitude);
|
||||||
|
summary.setBaseLatitude(baseLatitude);
|
||||||
|
summary.setBaseAltitude(baseAltitude);
|
||||||
|
|
||||||
|
int steps;
|
||||||
|
int activeSeconds;
|
||||||
|
int maxLatitude;
|
||||||
|
int minLatitude;
|
||||||
|
int maxLongitude;
|
||||||
|
int minLongitude;
|
||||||
|
float caloriesBurnt;
|
||||||
|
float distanceMeters;
|
||||||
|
float ascentMeters = 0;
|
||||||
|
float descentMeters = 0;
|
||||||
|
float maxAltitude = 0;
|
||||||
|
float minAltitude = 0;
|
||||||
|
float maxSpeed = 0;
|
||||||
|
float minPace;
|
||||||
|
float maxPace;
|
||||||
|
float totalStride = 0;
|
||||||
|
float averageStride;
|
||||||
|
short averageHR;
|
||||||
|
short averageKMPaceSeconds;
|
||||||
|
int ascentSeconds = 0;
|
||||||
|
int descentSeconds = 0;
|
||||||
|
int flatSeconds = 0;
|
||||||
|
|
||||||
|
// Just assuming, Bip has 259 which seems like 256+x
|
||||||
|
// Bip S now has 518 so assuming 512+x, might be wrong
|
||||||
|
|
||||||
|
if (version >= 512) {
|
||||||
|
steps = buffer.getInt();
|
||||||
|
activeSeconds = buffer.getInt();
|
||||||
|
//unknown
|
||||||
|
buffer.getLong();
|
||||||
|
buffer.getLong();
|
||||||
|
caloriesBurnt = buffer.getFloat();
|
||||||
|
distanceMeters = buffer.getFloat();
|
||||||
|
//unknown
|
||||||
|
buffer.getLong();
|
||||||
|
buffer.getLong();
|
||||||
|
buffer.getLong();
|
||||||
|
buffer.getLong();
|
||||||
|
maxPace = buffer.getFloat();
|
||||||
|
minPace = buffer.getFloat();
|
||||||
|
//unknown
|
||||||
|
buffer.getLong();
|
||||||
|
buffer.getLong();
|
||||||
|
buffer.getLong();
|
||||||
|
buffer.getLong();
|
||||||
|
buffer.getInt();
|
||||||
|
averageHR = buffer.getShort();
|
||||||
|
averageKMPaceSeconds = buffer.getShort();
|
||||||
|
averageStride = buffer.getShort();
|
||||||
|
buffer.getShort(); // unknown
|
||||||
|
|
||||||
|
if (activityKind == ActivityKind.TYPE_CYCLING || activityKind == ActivityKind.TYPE_RUNNING) {
|
||||||
|
// this had nonsense data with treadmill on bip s, need to test it with running
|
||||||
|
// for cycling it seems to work... hmm...
|
||||||
|
// 28 bytes
|
||||||
|
buffer.getInt(); // unknown
|
||||||
|
buffer.getInt(); // unknown
|
||||||
|
ascentSeconds = buffer.getInt() / 1000; //ms?
|
||||||
|
buffer.getInt(); // unknown;
|
||||||
|
descentSeconds = buffer.getInt() / 1000; //ms?
|
||||||
|
buffer.getInt(); // unknown;
|
||||||
|
flatSeconds = buffer.getInt() / 1000; // ms?
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
distanceMeters = buffer.getFloat();
|
||||||
|
ascentMeters = buffer.getFloat();
|
||||||
|
descentMeters = buffer.getFloat();
|
||||||
|
minAltitude = buffer.getFloat();
|
||||||
|
maxAltitude = buffer.getFloat();
|
||||||
|
maxLatitude = buffer.getInt(); // format?
|
||||||
|
minLatitude = buffer.getInt(); // format?
|
||||||
|
maxLongitude = buffer.getInt(); // format?
|
||||||
|
minLongitude = buffer.getInt(); // format?
|
||||||
|
steps = buffer.getInt();
|
||||||
|
activeSeconds = buffer.getInt();
|
||||||
|
caloriesBurnt = buffer.getFloat();
|
||||||
|
maxSpeed = buffer.getFloat();
|
||||||
|
minPace = buffer.getFloat();
|
||||||
|
maxPace = buffer.getFloat();
|
||||||
|
totalStride = buffer.getFloat();
|
||||||
|
|
||||||
|
buffer.getInt(); // unknown
|
||||||
|
if (activityKind == ActivityKind.TYPE_SWIMMING) {
|
||||||
|
// 28 bytes
|
||||||
|
float averageStrokeDistance = buffer.getFloat();
|
||||||
|
float averageStrokesPerSecond = buffer.getFloat();
|
||||||
|
float averageLapPace = buffer.getFloat();
|
||||||
|
short strokes = buffer.getShort();
|
||||||
|
short swolfIndex = buffer.getShort();
|
||||||
|
byte swimStyle = buffer.get();
|
||||||
|
byte laps = buffer.get();
|
||||||
|
buffer.getInt(); // unknown
|
||||||
|
buffer.getInt(); // unknown
|
||||||
|
buffer.getShort(); // unknown
|
||||||
|
|
||||||
|
addSummaryData("averageStrokeDistance", averageStrokeDistance, "meters");
|
||||||
|
addSummaryData("averageStrokesPerSecond", averageStrokesPerSecond, "strokes_second");
|
||||||
|
addSummaryData("averageLapPace", averageLapPace, "second");
|
||||||
|
addSummaryData("strokes", strokes, "strokes");
|
||||||
|
addSummaryData("swolfIndex", swolfIndex, "swolf_index");
|
||||||
|
addSummaryData("swimStyle", swimStyle, "swim_style");
|
||||||
|
addSummaryData("laps", laps, "laps");
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// 28 bytes
|
||||||
|
buffer.getInt(); // unknown
|
||||||
|
buffer.getInt(); // unknown
|
||||||
|
ascentSeconds = buffer.getInt() / 1000; //ms?
|
||||||
|
buffer.getInt(); // unknown;
|
||||||
|
descentSeconds = buffer.getInt() / 1000; //ms?
|
||||||
|
buffer.getInt(); // unknown;
|
||||||
|
flatSeconds = buffer.getInt() / 1000; // ms?
|
||||||
|
|
||||||
|
addSummaryData("ascentSeconds", ascentSeconds, "seconds");
|
||||||
|
addSummaryData("descentSeconds", descentSeconds, "seconds");
|
||||||
|
addSummaryData("flatSeconds", flatSeconds, "seconds");
|
||||||
|
}
|
||||||
|
|
||||||
|
averageHR = buffer.getShort();
|
||||||
|
|
||||||
|
averageKMPaceSeconds = buffer.getShort();
|
||||||
|
averageStride = buffer.getShort();
|
||||||
|
}
|
||||||
|
|
||||||
|
// summary.setBaseCoordinate(new GPSCoordinate(baseLatitude, baseLongitude, baseAltitude));
|
||||||
|
// summary.setDistanceMeters(distanceMeters);
|
||||||
|
// summary.setAscentMeters(ascentMeters);
|
||||||
|
// summary.setDescentMeters(descentMeters);
|
||||||
|
// summary.setMinAltitude(maxAltitude);
|
||||||
|
// summary.setMaxAltitude(maxAltitude);
|
||||||
|
// summary.setMinLatitude(minLatitude);
|
||||||
|
// summary.setMaxLatitude(maxLatitude);
|
||||||
|
// summary.setMinLongitude(minLatitude);
|
||||||
|
// summary.setMaxLongitude(maxLatitude);
|
||||||
|
// summary.setSteps(steps);
|
||||||
|
// summary.setActiveTimeSeconds(secondsActive);
|
||||||
|
// summary.setCaloriesBurnt(caloriesBurnt);
|
||||||
|
// summary.setMaxSpeed(maxSpeed);
|
||||||
|
// summary.setMinPace(minPace);
|
||||||
|
// summary.setMaxPace(maxPace);
|
||||||
|
// summary.setTotalStride(totalStride);
|
||||||
|
// summary.setTimeAscent(BLETypeConversions.toUnsigned(ascentSeconds);
|
||||||
|
// summary.setTimeDescent(BLETypeConversions.toUnsigned(descentSeconds);
|
||||||
|
// summary.setTimeFlat(BLETypeConversions.toUnsigned(flatSeconds);
|
||||||
|
// summary.setAverageHR(BLETypeConversions.toUnsigned(averageHR);
|
||||||
|
// summary.setAveragePace(BLETypeConversions.toUnsigned(averagePace);
|
||||||
|
// summary.setAverageStride(BLETypeConversions.toUnsigned(averageStride);
|
||||||
|
|
||||||
|
addSummaryData("ascentSeconds", ascentSeconds, "seconds");
|
||||||
|
addSummaryData("descentSeconds", descentSeconds, "seconds");
|
||||||
|
addSummaryData("flatSeconds", flatSeconds, "seconds");
|
||||||
|
|
||||||
|
addSummaryData("distanceMeters", distanceMeters, "meters");
|
||||||
|
addSummaryData("ascentMeters", ascentMeters, "meters");
|
||||||
|
addSummaryData("descentMeters", descentMeters, "meters");
|
||||||
|
if (maxAltitude != -100000) {
|
||||||
|
addSummaryData("maxAltitude", maxAltitude, "meters");
|
||||||
|
}
|
||||||
|
if (minAltitude != 100000) {
|
||||||
|
addSummaryData("minAltitude", minAltitude, "meters");
|
||||||
|
}
|
||||||
|
addSummaryData("steps", steps, "steps_unit");
|
||||||
|
addSummaryData("activeSeconds", activeSeconds, "seconds");
|
||||||
|
addSummaryData("caloriesBurnt", caloriesBurnt, "calories_unit");
|
||||||
|
addSummaryData("maxSpeed", maxSpeed, "meters_second");
|
||||||
|
addSummaryData("minPace", minPace, "seconds_m");
|
||||||
|
addSummaryData("maxPace", maxPace, "seconds_m");
|
||||||
|
addSummaryData("totalStride", totalStride, "meters");
|
||||||
|
addSummaryData("averageHR", averageHR, "bpm");
|
||||||
|
addSummaryData("averageKMPaceSeconds", averageKMPaceSeconds, "seconds_km");
|
||||||
|
addSummaryData("averageStride", averageStride, "cm");
|
||||||
|
|
||||||
|
summary.setSummaryData(summaryData.toString());
|
||||||
|
return summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void addSummaryData(String key, float value, String unit) {
|
||||||
|
if (value > 0) {
|
||||||
|
try {
|
||||||
|
JSONObject innerData = new JSONObject();
|
||||||
|
innerData.put("value", value);
|
||||||
|
innerData.put("unit", unit);
|
||||||
|
summaryData.put(key, innerData);
|
||||||
|
} catch (JSONException ignore) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
/* Copyright (C) 2020 Andreas Shimokawa
|
||||||
|
|
||||||
|
This file is part of Gadgetbridge.
|
||||||
|
|
||||||
|
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published
|
||||||
|
by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Gadgetbridge is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||||
|
package nodomain.freeyourgadget.gadgetbridge.model;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
|
||||||
|
|
||||||
|
public interface ActivitySummaryParser {
|
||||||
|
BaseActivitySummary parseBinaryData(BaseActivitySummary summary);
|
||||||
|
}
|
@ -21,37 +21,25 @@ import android.bluetooth.BluetoothGatt;
|
|||||||
import android.bluetooth.BluetoothGattCharacteristic;
|
import android.bluetooth.BluetoothGattCharacteristic;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.ByteOrder;
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.GregorianCalendar;
|
import java.util.GregorianCalendar;
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.Logging;
|
import nodomain.freeyourgadget.gadgetbridge.Logging;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiActivitySummaryParser;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbip.AmazfitBipService;
|
import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbip.AmazfitBipService;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
|
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.User;
|
import nodomain.freeyourgadget.gadgetbridge.entities.User;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSportsActivityType;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -62,7 +50,6 @@ public class FetchSportsSummaryOperation extends AbstractFetchOperation {
|
|||||||
private static final Logger LOG = LoggerFactory.getLogger(FetchSportsSummaryOperation.class);
|
private static final Logger LOG = LoggerFactory.getLogger(FetchSportsSummaryOperation.class);
|
||||||
|
|
||||||
private ByteArrayOutputStream buffer = new ByteArrayOutputStream(140);
|
private ByteArrayOutputStream buffer = new ByteArrayOutputStream(140);
|
||||||
private JSONObject summaryData = new JSONObject();
|
|
||||||
public FetchSportsSummaryOperation(HuamiSupport support) {
|
public FetchSportsSummaryOperation(HuamiSupport support) {
|
||||||
super(support);
|
super(support);
|
||||||
setName("fetching sport summaries");
|
setName("fetching sport summaries");
|
||||||
@ -91,17 +78,24 @@ public class FetchSportsSummaryOperation extends AbstractFetchOperation {
|
|||||||
|
|
||||||
BaseActivitySummary summary = null;
|
BaseActivitySummary summary = null;
|
||||||
if (success) {
|
if (success) {
|
||||||
summary = parseSummary(buffer);
|
summary = new BaseActivitySummary();
|
||||||
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
summary.setStartTime(getLastStartTimestamp().getTime()); // due to a bug this has to be set
|
||||||
DaoSession session = dbHandler.getDaoSession();
|
summary.setRawSummaryData(buffer.toByteArray());
|
||||||
Device device = DBHelper.getDevice(getDevice(), session);
|
HuamiActivitySummaryParser parser = new HuamiActivitySummaryParser();
|
||||||
User user = DBHelper.getUser(session);
|
summary = parser.parseBinaryData(summary);
|
||||||
summary.setDevice(device);
|
if (summary != null) {
|
||||||
summary.setUser(user);
|
summary.setSummaryData(null); // remove json before saving to database,
|
||||||
summary.setRawSummaryData(buffer.toByteArray());
|
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
||||||
session.getBaseActivitySummaryDao().insertOrReplace(summary);
|
DaoSession session = dbHandler.getDaoSession();
|
||||||
} catch (Exception ex) {
|
Device device = DBHelper.getDevice(getDevice(), session);
|
||||||
GB.toast(getContext(), "Error saving activity summary", Toast.LENGTH_LONG, GB.ERROR, ex);
|
User user = DBHelper.getUser(session);
|
||||||
|
summary.setDevice(device);
|
||||||
|
summary.setUser(user);
|
||||||
|
summary.setRawSummaryData(buffer.toByteArray());
|
||||||
|
session.getBaseActivitySummaryDao().insertOrReplace(summary);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
GB.toast(getContext(), "Error saving activity summary", Toast.LENGTH_LONG, GB.ERROR, ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,6 +109,7 @@ public class FetchSportsSummaryOperation extends AbstractFetchOperation {
|
|||||||
GB.toast(getContext(), "Unable to fetch activity details: " + ex.getMessage(), Toast.LENGTH_LONG, GB.ERROR, ex);
|
GB.toast(getContext(), "Unable to fetch activity details: " + ex.getMessage(), Toast.LENGTH_LONG, GB.ERROR, ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -155,7 +150,6 @@ public class FetchSportsSummaryOperation extends AbstractFetchOperation {
|
|||||||
} else {
|
} else {
|
||||||
GB.toast("Error " + getName() + ", invalid package counter: " + value[0] + ", last was: " + lastPacketCounter, Toast.LENGTH_LONG, GB.ERROR);
|
GB.toast("Error " + getName() + ", invalid package counter: " + value[0] + ", last was: " + lastPacketCounter, Toast.LENGTH_LONG, GB.ERROR);
|
||||||
handleActivityFetchFinish(false);
|
handleActivityFetchFinish(false);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,246 +164,6 @@ public class FetchSportsSummaryOperation extends AbstractFetchOperation {
|
|||||||
buffer.write(value, 1, value.length - 1); // skip the counter
|
buffer.write(value, 1, value.length - 1); // skip the counter
|
||||||
}
|
}
|
||||||
|
|
||||||
private BaseActivitySummary parseSummary(ByteArrayOutputStream stream) {
|
|
||||||
BaseActivitySummary summary = new BaseActivitySummary();
|
|
||||||
|
|
||||||
boolean dumptofile = false;
|
|
||||||
if (dumptofile) {
|
|
||||||
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd-hhmmss", Locale.US);
|
|
||||||
String filename = "rawsummary_" + dateFormat.format(new Date()) + ".bin";
|
|
||||||
try {
|
|
||||||
File dir = FileUtils.getExternalFilesDir();
|
|
||||||
File outputFile = new File(dir, filename);
|
|
||||||
FileUtils.copyStreamToFile(new ByteArrayInputStream(stream.toByteArray()), outputFile);
|
|
||||||
} catch (IOException e) {
|
|
||||||
LOG.warn("could not create file");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ByteBuffer buffer = ByteBuffer.wrap(stream.toByteArray()).order(ByteOrder.LITTLE_ENDIAN);
|
|
||||||
|
|
||||||
short version = buffer.getShort(); // version
|
|
||||||
LOG.debug("Got sport summary version " + version + " total bytes=" + buffer.capacity());
|
|
||||||
int activityKind = ActivityKind.TYPE_UNKNOWN;
|
|
||||||
try {
|
|
||||||
int rawKind = BLETypeConversions.toUnsigned(buffer.getShort());
|
|
||||||
HuamiSportsActivityType activityType = HuamiSportsActivityType.fromCode(rawKind);
|
|
||||||
activityKind = activityType.toActivityKind();
|
|
||||||
} catch (Exception ex) {
|
|
||||||
LOG.error("Error mapping activity kind: " + ex.getMessage(), ex);
|
|
||||||
}
|
|
||||||
summary.setActivityKind(activityKind);
|
|
||||||
|
|
||||||
// FIXME: should honor timezone we were in at that time etc
|
|
||||||
long timestamp_start = BLETypeConversions.toUnsigned(buffer.getInt()) * 1000;
|
|
||||||
long timestamp_end = BLETypeConversions.toUnsigned(buffer.getInt()) * 1000;
|
|
||||||
|
|
||||||
|
|
||||||
// FIXME: should be done like this but seems to return crap when in DST
|
|
||||||
//summary.setStartTime(new Date(timestamp_start));
|
|
||||||
//summary.setEndTime(new Date(timestamp_end));
|
|
||||||
|
|
||||||
// FIXME ... so do it like this
|
|
||||||
long duration = timestamp_end - timestamp_start;
|
|
||||||
summary.setStartTime(new Date(getLastStartTimestamp().getTimeInMillis()));
|
|
||||||
summary.setEndTime(new Date(getLastStartTimestamp().getTimeInMillis() + duration));
|
|
||||||
|
|
||||||
int baseLongitude = buffer.getInt();
|
|
||||||
int baseLatitude = buffer.getInt();
|
|
||||||
int baseAltitude = buffer.getInt();
|
|
||||||
summary.setBaseLongitude(baseLongitude);
|
|
||||||
summary.setBaseLatitude(baseLatitude);
|
|
||||||
summary.setBaseAltitude(baseAltitude);
|
|
||||||
|
|
||||||
int steps;
|
|
||||||
int activeSeconds;
|
|
||||||
int maxLatitude;
|
|
||||||
int minLatitude;
|
|
||||||
int maxLongitude;
|
|
||||||
int minLongitude;
|
|
||||||
float caloriesBurnt;
|
|
||||||
float distanceMeters;
|
|
||||||
float ascentMeters = 0;
|
|
||||||
float descentMeters = 0;
|
|
||||||
float maxAltitude = 0;
|
|
||||||
float minAltitude = 0;
|
|
||||||
float maxSpeed = 0;
|
|
||||||
float minPace;
|
|
||||||
float maxPace;
|
|
||||||
float totalStride = 0;
|
|
||||||
float averageStride;
|
|
||||||
short averageHR;
|
|
||||||
short averageKMPaceSeconds;
|
|
||||||
int ascentSeconds = 0;
|
|
||||||
int descentSeconds = 0;
|
|
||||||
int flatSeconds = 0;
|
|
||||||
|
|
||||||
// Just assuming, Bip has 259 which seems like 256+x
|
|
||||||
// Bip S now has 518 so assuming 512+x, might be wrong
|
|
||||||
|
|
||||||
if (version >= 512) {
|
|
||||||
steps = buffer.getInt();
|
|
||||||
activeSeconds = buffer.getInt();
|
|
||||||
//unknown
|
|
||||||
buffer.getLong();
|
|
||||||
buffer.getLong();
|
|
||||||
caloriesBurnt = buffer.getFloat();
|
|
||||||
distanceMeters = buffer.getFloat();
|
|
||||||
//unknown
|
|
||||||
buffer.getLong();
|
|
||||||
buffer.getLong();
|
|
||||||
buffer.getLong();
|
|
||||||
buffer.getLong();
|
|
||||||
maxPace = buffer.getFloat();
|
|
||||||
minPace = buffer.getFloat();
|
|
||||||
//unknown
|
|
||||||
buffer.getLong();
|
|
||||||
buffer.getLong();
|
|
||||||
buffer.getLong();
|
|
||||||
buffer.getLong();
|
|
||||||
buffer.getInt();
|
|
||||||
averageHR = buffer.getShort();
|
|
||||||
averageKMPaceSeconds = buffer.getShort();
|
|
||||||
averageStride = buffer.getShort();
|
|
||||||
buffer.getShort(); // unknown
|
|
||||||
|
|
||||||
if (activityKind == ActivityKind.TYPE_CYCLING || activityKind == ActivityKind.TYPE_RUNNING) {
|
|
||||||
// this had nonsense data with treadmill on bip s, need to test it with running
|
|
||||||
// for cycling it seems to work... hmm...
|
|
||||||
// 28 bytes
|
|
||||||
buffer.getInt(); // unknown
|
|
||||||
buffer.getInt(); // unknown
|
|
||||||
ascentSeconds = buffer.getInt() / 1000; //ms?
|
|
||||||
buffer.getInt(); // unknown;
|
|
||||||
descentSeconds = buffer.getInt() / 1000; //ms?
|
|
||||||
buffer.getInt(); // unknown;
|
|
||||||
flatSeconds = buffer.getInt() / 1000; // ms?
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
distanceMeters = buffer.getFloat();
|
|
||||||
ascentMeters = buffer.getFloat();
|
|
||||||
descentMeters = buffer.getFloat();
|
|
||||||
minAltitude = buffer.getFloat();
|
|
||||||
maxAltitude = buffer.getFloat();
|
|
||||||
maxLatitude = buffer.getInt(); // format?
|
|
||||||
minLatitude = buffer.getInt(); // format?
|
|
||||||
maxLongitude = buffer.getInt(); // format?
|
|
||||||
minLongitude = buffer.getInt(); // format?
|
|
||||||
steps = buffer.getInt();
|
|
||||||
activeSeconds = buffer.getInt();
|
|
||||||
caloriesBurnt = buffer.getFloat();
|
|
||||||
maxSpeed = buffer.getFloat();
|
|
||||||
minPace = buffer.getFloat();
|
|
||||||
maxPace = buffer.getFloat();
|
|
||||||
totalStride = buffer.getFloat();
|
|
||||||
|
|
||||||
buffer.getInt(); // unknown
|
|
||||||
if (activityKind == ActivityKind.TYPE_SWIMMING) {
|
|
||||||
// 28 bytes
|
|
||||||
float averageStrokeDistance = buffer.getFloat();
|
|
||||||
float averageStrokesPerSecond = buffer.getFloat();
|
|
||||||
float averageLapPace = buffer.getFloat();
|
|
||||||
short strokes = buffer.getShort();
|
|
||||||
short swolfIndex = buffer.getShort();
|
|
||||||
byte swimStyle = buffer.get();
|
|
||||||
byte laps = buffer.get();
|
|
||||||
buffer.getInt(); // unknown
|
|
||||||
buffer.getInt(); // unknown
|
|
||||||
buffer.getShort(); // unknown
|
|
||||||
|
|
||||||
addSummaryData("averageStrokeDistance", averageStrokeDistance, "meters");
|
|
||||||
addSummaryData("averageStrokesPerSecond", averageStrokesPerSecond, "strokes_second");
|
|
||||||
addSummaryData("averageLapPace", averageLapPace, "second");
|
|
||||||
addSummaryData("strokes", strokes, "strokes");
|
|
||||||
addSummaryData("swolfIndex", swolfIndex, "swolf_index");
|
|
||||||
addSummaryData("swimStyle", swimStyle, "swim_style");
|
|
||||||
addSummaryData("laps", laps, "laps");
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// 28 bytes
|
|
||||||
buffer.getInt(); // unknown
|
|
||||||
buffer.getInt(); // unknown
|
|
||||||
ascentSeconds = buffer.getInt() / 1000; //ms?
|
|
||||||
buffer.getInt(); // unknown;
|
|
||||||
descentSeconds = buffer.getInt() / 1000; //ms?
|
|
||||||
buffer.getInt(); // unknown;
|
|
||||||
flatSeconds = buffer.getInt() / 1000; // ms?
|
|
||||||
|
|
||||||
addSummaryData("ascentSeconds", ascentSeconds, "seconds");
|
|
||||||
addSummaryData("descentSeconds", descentSeconds, "seconds");
|
|
||||||
addSummaryData("flatSeconds", flatSeconds, "seconds");
|
|
||||||
}
|
|
||||||
|
|
||||||
averageHR = buffer.getShort();
|
|
||||||
|
|
||||||
averageKMPaceSeconds = buffer.getShort();
|
|
||||||
averageStride = buffer.getShort();
|
|
||||||
}
|
|
||||||
|
|
||||||
// summary.setBaseCoordinate(new GPSCoordinate(baseLatitude, baseLongitude, baseAltitude));
|
|
||||||
// summary.setDistanceMeters(distanceMeters);
|
|
||||||
// summary.setAscentMeters(ascentMeters);
|
|
||||||
// summary.setDescentMeters(descentMeters);
|
|
||||||
// summary.setMinAltitude(maxAltitude);
|
|
||||||
// summary.setMaxAltitude(maxAltitude);
|
|
||||||
// summary.setMinLatitude(minLatitude);
|
|
||||||
// summary.setMaxLatitude(maxLatitude);
|
|
||||||
// summary.setMinLongitude(minLatitude);
|
|
||||||
// summary.setMaxLongitude(maxLatitude);
|
|
||||||
// summary.setSteps(steps);
|
|
||||||
// summary.setActiveTimeSeconds(secondsActive);
|
|
||||||
// summary.setCaloriesBurnt(caloriesBurnt);
|
|
||||||
// summary.setMaxSpeed(maxSpeed);
|
|
||||||
// summary.setMinPace(minPace);
|
|
||||||
// summary.setMaxPace(maxPace);
|
|
||||||
// summary.setTotalStride(totalStride);
|
|
||||||
// summary.setTimeAscent(BLETypeConversions.toUnsigned(ascentSeconds);
|
|
||||||
// summary.setTimeDescent(BLETypeConversions.toUnsigned(descentSeconds);
|
|
||||||
// summary.setTimeFlat(BLETypeConversions.toUnsigned(flatSeconds);
|
|
||||||
// summary.setAverageHR(BLETypeConversions.toUnsigned(averageHR);
|
|
||||||
// summary.setAveragePace(BLETypeConversions.toUnsigned(averagePace);
|
|
||||||
// summary.setAverageStride(BLETypeConversions.toUnsigned(averageStride);
|
|
||||||
|
|
||||||
addSummaryData("ascentSeconds", ascentSeconds, "seconds");
|
|
||||||
addSummaryData("descentSeconds", descentSeconds, "seconds");
|
|
||||||
addSummaryData("flatSeconds", flatSeconds, "seconds");
|
|
||||||
|
|
||||||
addSummaryData("distanceMeters", distanceMeters, "meters");
|
|
||||||
addSummaryData("ascentMeters", ascentMeters, "meters");
|
|
||||||
addSummaryData("descentMeters", descentMeters, "meters");
|
|
||||||
if (maxAltitude != -100000) {
|
|
||||||
addSummaryData("maxAltitude", maxAltitude, "meters");
|
|
||||||
}
|
|
||||||
if (minAltitude != 100000) {
|
|
||||||
addSummaryData("minAltitude", minAltitude, "meters");
|
|
||||||
}
|
|
||||||
addSummaryData("steps", steps, "steps_unit");
|
|
||||||
addSummaryData("activeSeconds", activeSeconds, "seconds");
|
|
||||||
addSummaryData("caloriesBurnt", caloriesBurnt, "calories_unit");
|
|
||||||
addSummaryData("maxSpeed", maxSpeed, "meters_second");
|
|
||||||
addSummaryData("minPace", minPace, "seconds_m");
|
|
||||||
addSummaryData("maxPace", maxPace, "seconds_m");
|
|
||||||
addSummaryData("totalStride", totalStride, "meters");
|
|
||||||
addSummaryData("averageHR", averageHR, "bpm");
|
|
||||||
addSummaryData("averageKMPaceSeconds", averageKMPaceSeconds, "seconds_km");
|
|
||||||
addSummaryData("averageStride", averageStride, "cm");
|
|
||||||
|
|
||||||
summary.setSummaryData(summaryData.toString());
|
|
||||||
return summary;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addSummaryData(String key, float value, String unit) {
|
|
||||||
if (value> 0) {
|
|
||||||
try {
|
|
||||||
JSONObject innerData= new JSONObject();
|
|
||||||
innerData.put("value", value);
|
|
||||||
innerData.put("unit", unit);
|
|
||||||
summaryData.put(key, innerData);
|
|
||||||
} catch (JSONException ignore) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String getLastSyncTimeKey() {
|
protected String getLastSyncTimeKey() {
|
||||||
|
Loading…
Reference in New Issue
Block a user