Do summary parsing on the fly if raw binary data is available

This commit is contained in:
Andreas Shimokawa 2020-08-18 10:39:44 +02:00
parent da53062d31
commit 715fb67859
4 changed files with 342 additions and 284 deletions

View File

@ -50,10 +50,12 @@ import java.util.Iterator;
import java.util.concurrent.TimeUnit;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiActivitySummaryParser;
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryItems;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
@ -61,7 +63,6 @@ import nodomain.freeyourgadget.gadgetbridge.util.SwipeEvents;
public class ActivitySummaryDetail extends AbstractGBActivity {
private static final Logger LOG = LoggerFactory.getLogger(ActivitySummaryDetail.class);
private GBDevice mGBDevice;
private JSONObject groupData = setGroups();
private boolean show_raw_data = false;
BaseActivitySummary currentItem = null;
@ -73,10 +74,10 @@ public class ActivitySummaryDetail extends AbstractGBActivity {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_summary_details);
Intent intent = getIntent();
mGBDevice = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
final int filter = intent.getIntExtra("filter",0);
final int position = intent.getIntExtra("position",0);
final ActivitySummaryItems items = new ActivitySummaryItems(this, mGBDevice, filter);
GBDevice gbDevice = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
final int filter = intent.getIntExtra("filter", 0);
final int position = intent.getIntExtra("position", 0);
final ActivitySummaryItems items = new ActivitySummaryItems(this, gbDevice, filter);
final RelativeLayout layout = findViewById(R.id.activity_summary_detail_relative_layout);
alternateColor = getAlternateColor(this);
@ -131,7 +132,7 @@ public class ActivitySummaryDetail extends AbstractGBActivity {
}
//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() {
public boolean onLongClick(View v) {
show_raw_data=!show_raw_data;
@ -149,7 +150,7 @@ public class ActivitySummaryDetail extends AbstractGBActivity {
private void makeSummaryHeader(BaseActivitySummary item){
//make view of data from main part of item
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);
if (gpxTrack != null) {
@ -165,34 +166,40 @@ public class ActivitySummaryDetail extends AbstractGBActivity {
});
}
String activitykindname = ActivityKind.asString(item.getActivityKind(), getApplicationContext());
Date starttime = (Date) item.getStartTime();
Date endtime = (Date) item.getEndTime();
Date starttime = item.getStartTime();
Date endtime = item.getEndTime();
String starttimeS = DateTimeUtils.formatDateTime(starttime);
String endtimeS = DateTimeUtils.formatDateTime(endtime);
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()));
TextView activity_kind = (TextView) findViewById(R.id.activitykind);
TextView activity_kind = findViewById(R.id.activitykind);
activity_kind.setText(activitykindname);
TextView start_time = (TextView) findViewById(R.id.starttime);
TextView start_time = findViewById(R.id.starttime);
start_time.setText(starttimeS);
TextView end_time = (TextView) findViewById(R.id.endtime);
TextView end_time = findViewById(R.id.endtime);
end_time.setText(endtimeS);
TextView activity_duration = (TextView) findViewById(R.id.duration);
TextView activity_duration = findViewById(R.id.duration);
activity_duration.setText(durationhms);
}
private void makeSummaryContent (BaseActivitySummary item){
private void makeSummaryContent(BaseActivitySummary item) {
//make view of data from summaryData of item
TableLayout fieldLayout = findViewById(R.id.summaryDetails);
fieldLayout.removeAllViews(); //remove old widgets
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();
@ -207,8 +214,6 @@ public class ActivitySummaryDetail extends AbstractGBActivity {
if (summarySubdata == null) return;
data = makeSummaryList(summarySubdata); //make new list, grouped by groups
if (data == null) return;
Iterator<String> keys = data.keys();
DecimalFormat df = new DecimalFormat("#.##");

View File

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

View File

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

View File

@ -21,37 +21,25 @@ import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.widget.Toast;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
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.Locale;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.Logging;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
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.entities.BaseActivitySummary;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
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.devices.huami.HuamiSportsActivityType;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
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 ByteArrayOutputStream buffer = new ByteArrayOutputStream(140);
private JSONObject summaryData = new JSONObject();
public FetchSportsSummaryOperation(HuamiSupport support) {
super(support);
setName("fetching sport summaries");
@ -91,17 +78,24 @@ public class FetchSportsSummaryOperation extends AbstractFetchOperation {
BaseActivitySummary summary = null;
if (success) {
summary = parseSummary(buffer);
try (DBHandler dbHandler = GBApplication.acquireDB()) {
DaoSession session = dbHandler.getDaoSession();
Device device = DBHelper.getDevice(getDevice(), session);
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);
summary = new BaseActivitySummary();
summary.setStartTime(getLastStartTimestamp().getTime()); // due to a bug this has to be set
summary.setRawSummaryData(buffer.toByteArray());
HuamiActivitySummaryParser parser = new HuamiActivitySummaryParser();
summary = parser.parseBinaryData(summary);
if (summary != null) {
summary.setSummaryData(null); // remove json before saving to database,
try (DBHandler dbHandler = GBApplication.acquireDB()) {
DaoSession session = dbHandler.getDaoSession();
Device device = DBHelper.getDevice(getDevice(), session);
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);
}
}
}
@Override
@ -155,7 +150,6 @@ public class FetchSportsSummaryOperation extends AbstractFetchOperation {
} else {
GB.toast("Error " + getName() + ", invalid package counter: " + value[0] + ", last was: " + lastPacketCounter, Toast.LENGTH_LONG, GB.ERROR);
handleActivityFetchFinish(false);
return;
}
}
@ -170,246 +164,6 @@ public class FetchSportsSummaryOperation extends AbstractFetchOperation {
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
protected String getLastSyncTimeKey() {