mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-08-24 16:21:16 +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 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("#.##");
|
||||
|
||||
|
@ -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.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,7 +78,13 @@ public class FetchSportsSummaryOperation extends AbstractFetchOperation {
|
||||
|
||||
BaseActivitySummary summary = null;
|
||||
if (success) {
|
||||
summary = parseSummary(buffer);
|
||||
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);
|
||||
@ -104,6 +97,7 @@ public class FetchSportsSummaryOperation extends AbstractFetchOperation {
|
||||
GB.toast(getContext(), "Error saving activity summary", Toast.LENGTH_LONG, GB.ERROR, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
super.handleActivityFetchFinish(success);
|
||||
|
||||
@ -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() {
|
||||
|
Loading…
Reference in New Issue
Block a user