mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-12-05 00:12:56 +01:00
Da Fit: Add activity fetching and logging
This commit is contained in:
parent
80c51999a2
commit
72743d893b
@ -49,6 +49,9 @@ public class GBDaoGenerator {
|
||||
private static final String SAMPLE_TEMPERATURE = "temperature";
|
||||
private static final String SAMPLE_TEMPERATURE_TYPE = "temperatureType";
|
||||
private static final String SAMPLE_WEIGHT_KG = "weightKg";
|
||||
private static final String SAMPLE_BLOOD_PRESSURE_SYSTOLIC = "bloodPressureSystolic";
|
||||
private static final String SAMPLE_BLOOD_PRESSURE_DIASTOLIC = "bloodPressureDiastolic";
|
||||
private static final String SAMPLE_BLOOD_OXIDATION = "bloodOxidation";
|
||||
private static final String TIMESTAMP_FROM = "timestampFrom";
|
||||
private static final String TIMESTAMP_TO = "timestampTo";
|
||||
|
||||
@ -586,6 +589,15 @@ public class GBDaoGenerator {
|
||||
activitySample.addIntProperty(SAMPLE_HEART_RATE).notNull().codeBeforeGetterAndSetter(OVERRIDE);
|
||||
}
|
||||
|
||||
private static void addBloodPressureProperies(Entity activitySample) {
|
||||
activitySample.addIntProperty(SAMPLE_BLOOD_PRESSURE_SYSTOLIC).notNull().codeBeforeGetterAndSetter(OVERRIDE);
|
||||
activitySample.addIntProperty(SAMPLE_BLOOD_PRESSURE_DIASTOLIC).notNull().codeBeforeGetterAndSetter(OVERRIDE);
|
||||
}
|
||||
|
||||
private static void addBloodOxidationProperies(Entity activitySample) {
|
||||
activitySample.addIntProperty(SAMPLE_BLOOD_OXIDATION).notNull().codeBeforeGetterAndSetter(OVERRIDE);
|
||||
}
|
||||
|
||||
private static Entity addPebbleHealthActivitySample(Schema schema, Entity user, Entity device) {
|
||||
Entity activitySample = addEntity(schema, "PebbleHealthActivitySample");
|
||||
addCommonActivitySampleProperties("AbstractPebbleHealthActivitySample", activitySample, user, device);
|
||||
@ -1024,6 +1036,22 @@ public class GBDaoGenerator {
|
||||
return activitySample;
|
||||
}
|
||||
|
||||
private static Entity addDaFitActivitySample(Schema schema, Entity user, Entity device) {
|
||||
Entity activitySample = addEntity(schema, "DaFitActivitySample");
|
||||
activitySample.implementsSerializable();
|
||||
addCommonActivitySampleProperties("AbstractActivitySample", activitySample, user, device);
|
||||
activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE).codeBeforeGetter("@Override\n public int getRawIntensity() {\n return getSteps();\n }\n\n");
|
||||
activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull().codeBeforeGetterAndSetter(OVERRIDE);
|
||||
activitySample.addIntProperty("dataSource").notNull();
|
||||
activitySample.addIntProperty("caloriesBurnt").notNull();
|
||||
activitySample.addIntProperty("distanceMeters").notNull();
|
||||
addHeartRateProperties(activitySample);
|
||||
addBloodPressureProperies(activitySample);
|
||||
addBloodOxidationProperies(activitySample);
|
||||
activitySample.addIntProperty("batteryLevel").notNull();
|
||||
return activitySample;
|
||||
}
|
||||
|
||||
private static void addCommonActivitySampleProperties(String superClass, Entity activitySample, Entity user, Entity device) {
|
||||
activitySample.setSuperclass(superClass);
|
||||
activitySample.addImport(MAIN_PACKAGE + ".devices.SampleProvider");
|
||||
|
@ -137,7 +137,8 @@ public class DaFitConstants {
|
||||
|
||||
public static final byte CMD_SYNC_SLEEP = 50; // {} -> {type, start_h, start_m}, repeating, type is SOBER(0),LIGHT(1),RESTFUL(2)
|
||||
public static final byte CMD_SYNC_PAST_SLEEP_AND_STEP = 51; // {b (see below)} -> {x<=2, distance:uint24, steps:uint24, calories:uint24} or {x>2, (sleep data like above)} - two functions same CMD
|
||||
|
||||
|
||||
// NOTE: these names are as specified in the original app. They do NOT match what my watch actually does. See note in FetchDataOperation.
|
||||
public static final byte ARG_SYNC_YESTERDAY_STEPS = 1;
|
||||
public static final byte ARG_SYNC_DAY_BEFORE_YESTERDAY_STEPS = 2;
|
||||
public static final byte ARG_SYNC_YESTERDAY_SLEEP = 3;
|
||||
|
@ -90,17 +90,17 @@ public class DaFitDeviceCoordinator extends AbstractDeviceCoordinator {
|
||||
|
||||
@Override
|
||||
public boolean supportsActivityDataFetching() {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsActivityTracking() {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
|
||||
return null;
|
||||
return new DaFitSampleProvider(device, session);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -145,7 +145,7 @@ public class DaFitDeviceCoordinator extends AbstractDeviceCoordinator {
|
||||
|
||||
@Override
|
||||
public boolean supportsRealtimeData() {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -0,0 +1,187 @@
|
||||
/* Copyright (C) 2019 krzys_h
|
||||
|
||||
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.dafit;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
|
||||
import de.greenrobot.dao.AbstractDao;
|
||||
import de.greenrobot.dao.Property;
|
||||
import de.greenrobot.dao.internal.SqlUtils;
|
||||
import de.greenrobot.dao.query.WhereCondition;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractSampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaFitActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaFitActivitySampleDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
|
||||
public class DaFitSampleProvider extends AbstractSampleProvider<DaFitActivitySample> {
|
||||
public static final int SOURCE_NOT_MEASURED = -1;
|
||||
public static final int SOURCE_STEPS_REALTIME = 1; // steps gathered at realtime from the steps characteristic
|
||||
public static final int SOURCE_STEPS_SUMMARY = 2; // steps gathered from the daily summary
|
||||
public static final int SOURCE_STEPS_IDLE = 3; // idle sample inserted because the user was not moving (to differentiate from missing data because watch not connected)
|
||||
public static final int SOURCE_SLEEP_SUMMARY = 4; // data collected from the sleep function
|
||||
public static final int SOURCE_SINGLE_MEASURE = 5; // heart rate / blood data gathered from the "single measurement" function
|
||||
public static final int SOURCE_TRAINING_HEARTRATE = 6; // heart rate data collected from the training function
|
||||
public static final int SOURCE_BATTERY = 7; // battery report
|
||||
|
||||
public static final int ACTIVITY_NOT_MEASURED = -1;
|
||||
public static final int ACTIVITY_TRAINING_WALK = DaFitConstants.TRAINING_TYPE_WALK;
|
||||
public static final int ACTIVITY_TRAINING_RUN = DaFitConstants.TRAINING_TYPE_RUN;
|
||||
public static final int ACTIVITY_TRAINING_BIKING = DaFitConstants.TRAINING_TYPE_BIKING;
|
||||
public static final int ACTIVITY_TRAINING_ROPE = DaFitConstants.TRAINING_TYPE_ROPE;
|
||||
public static final int ACTIVITY_TRAINING_BADMINTON = DaFitConstants.TRAINING_TYPE_BADMINTON;
|
||||
public static final int ACTIVITY_TRAINING_BASKETBALL = DaFitConstants.TRAINING_TYPE_BASKETBALL;
|
||||
public static final int ACTIVITY_TRAINING_FOOTBALL = DaFitConstants.TRAINING_TYPE_FOOTBALL;
|
||||
public static final int ACTIVITY_TRAINING_SWIM = DaFitConstants.TRAINING_TYPE_SWIM;
|
||||
public static final int ACTIVITY_TRAINING_MOUNTAINEERING = DaFitConstants.TRAINING_TYPE_MOUNTAINEERING;
|
||||
public static final int ACTIVITY_TRAINING_TENNIS = DaFitConstants.TRAINING_TYPE_TENNIS;
|
||||
public static final int ACTIVITY_TRAINING_RUGBY = DaFitConstants.TRAINING_TYPE_RUGBY;
|
||||
public static final int ACTIVITY_TRAINING_GOLF = DaFitConstants.TRAINING_TYPE_GOLF;
|
||||
public static final int ACTIVITY_SLEEP_LIGHT = 16;
|
||||
public static final int ACTIVITY_SLEEP_RESTFUL = 17;
|
||||
public static final int ACTIVITY_SLEEP_START = 18;
|
||||
public static final int ACTIVITY_SLEEP_END = 19;
|
||||
|
||||
public DaFitSampleProvider(GBDevice device, DaoSession session) {
|
||||
super(device, session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractDao<DaFitActivitySample, ?> getSampleDao() {
|
||||
return getSession().getDaFitActivitySampleDao();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Property getTimestampSampleProperty() {
|
||||
return DaFitActivitySampleDao.Properties.Timestamp;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected Property getRawKindSampleProperty() {
|
||||
return DaFitActivitySampleDao.Properties.RawKind;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Property getDeviceIdentifierSampleProperty() {
|
||||
return DaFitActivitySampleDao.Properties.DeviceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DaFitActivitySample createActivitySample() {
|
||||
return new DaFitActivitySample();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int normalizeType(int rawType) {
|
||||
if (rawType == ACTIVITY_NOT_MEASURED)
|
||||
return ActivityKind.TYPE_NOT_MEASURED;
|
||||
else if (rawType == ACTIVITY_SLEEP_LIGHT)
|
||||
return ActivityKind.TYPE_LIGHT_SLEEP;
|
||||
else if (rawType == ACTIVITY_SLEEP_RESTFUL)
|
||||
return ActivityKind.TYPE_DEEP_SLEEP;
|
||||
else if (rawType == ACTIVITY_SLEEP_START || rawType == ACTIVITY_SLEEP_END)
|
||||
return ActivityKind.TYPE_NOT_MEASURED;
|
||||
else
|
||||
return ActivityKind.TYPE_ACTIVITY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int toRawActivityKind(int activityKind) {
|
||||
if (activityKind == ActivityKind.TYPE_NOT_MEASURED)
|
||||
return ACTIVITY_NOT_MEASURED;
|
||||
else if (activityKind == ActivityKind.TYPE_LIGHT_SLEEP)
|
||||
return ACTIVITY_SLEEP_LIGHT;
|
||||
else if (activityKind == ActivityKind.TYPE_DEEP_SLEEP)
|
||||
return ACTIVITY_SLEEP_RESTFUL;
|
||||
else if (activityKind == ActivityKind.TYPE_ACTIVITY)
|
||||
return ACTIVITY_NOT_MEASURED; // TODO: ?
|
||||
else
|
||||
throw new IllegalArgumentException("Invalid Gadgetbridge activity kind: " + activityKind);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float normalizeIntensity(int rawIntensity) {
|
||||
if (rawIntensity == ActivitySample.NOT_MEASURED)
|
||||
return Float.NEGATIVE_INFINITY;
|
||||
else
|
||||
return rawIntensity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the activity kind from NOT_MEASURED to new_raw_activity_kind on the given range
|
||||
* @param timestamp_from the start timestamp
|
||||
* @param timestamp_to the end timestamp
|
||||
* @param new_raw_activity_kind the activity kind to set
|
||||
*/
|
||||
public void updateActivityInRange(int timestamp_from, int timestamp_to, int new_raw_activity_kind)
|
||||
{
|
||||
// greenDAO does not provide a bulk update functionality, and manual update fails because
|
||||
// of no primary key
|
||||
|
||||
Property timestampProperty = getTimestampSampleProperty();
|
||||
Device dbDevice = DBHelper.findDevice(getDevice(), getSession());
|
||||
if (dbDevice == null)
|
||||
throw new IllegalStateException();
|
||||
Property deviceProperty = getDeviceIdentifierSampleProperty();
|
||||
|
||||
/*QueryBuilder<DaFitActivitySample> qb = getSampleDao().queryBuilder();
|
||||
qb.where(deviceProperty.eq(dbDevice.getId()))
|
||||
.where(timestampProperty.ge(timestamp_from), timestampProperty.le(timestamp_to))
|
||||
.where(getRawKindSampleProperty().eq(ACTIVITY_NOT_MEASURED));
|
||||
List<DaFitActivitySample> samples = qb.build().list();
|
||||
for (DaFitActivitySample sample : samples) {
|
||||
sample.setProvider(this);
|
||||
sample.setRawKind(new_raw_activity_kind);
|
||||
sample.update();
|
||||
}*/
|
||||
|
||||
String tablename = getSampleDao().getTablename();
|
||||
String baseSql = SqlUtils.createSqlUpdate(tablename, new String[] { getRawKindSampleProperty().columnName }, new String[] { });
|
||||
StringBuilder builder = new StringBuilder(baseSql);
|
||||
|
||||
List<Object> values = new ArrayList<>();
|
||||
values.add(new_raw_activity_kind);
|
||||
List<WhereCondition> whereConditions = new ArrayList<>();
|
||||
whereConditions.add(deviceProperty.eq(dbDevice.getId()));
|
||||
whereConditions.add(timestampProperty.ge(timestamp_from));
|
||||
whereConditions.add(timestampProperty.le(timestamp_to));
|
||||
whereConditions.add(getRawKindSampleProperty().eq(ACTIVITY_NOT_MEASURED));
|
||||
|
||||
ListIterator<WhereCondition> iter = whereConditions.listIterator();
|
||||
while (iter.hasNext()) {
|
||||
if (iter.hasPrevious()) {
|
||||
builder.append(" AND ");
|
||||
}
|
||||
WhereCondition condition = iter.next();
|
||||
condition.appendTo(builder, tablename);
|
||||
condition.appendValuesTo(values);
|
||||
}
|
||||
getSampleDao().getDatabase().execSQL(builder.toString(), values.toArray());
|
||||
}
|
||||
}
|
@ -20,8 +20,12 @@ import android.bluetooth.BluetoothGatt;
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -29,24 +33,35 @@ import org.slf4j.LoggerFactory;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
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.deviceevents.GBDeviceEventBatteryInfo;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallControl;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.dafit.DaFitConstants;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.dafit.DaFitSampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaFitActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.User;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
|
||||
@ -58,6 +73,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.battery.Batter
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfo;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfoProfile;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.heartrate.HeartRateProfile;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.NotificationUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
|
||||
|
||||
@ -66,6 +82,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
|
||||
public class DaFitDeviceSupport extends AbstractBTLEDeviceSupport {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DaFitDeviceSupport.class);
|
||||
private static final long IDLE_STEPS_INTERVAL = 5 * 60 * 1000;
|
||||
|
||||
private final DeviceInfoProfile<DaFitDeviceSupport> deviceInfoProfile;
|
||||
private final BatteryInfoProfile<DaFitDeviceSupport> batteryInfoProfile;
|
||||
@ -85,11 +102,16 @@ public class DaFitDeviceSupport extends AbstractBTLEDeviceSupport {
|
||||
}
|
||||
};
|
||||
|
||||
private Handler idleUpdateHandler = new Handler();
|
||||
|
||||
public static final int MTU = 20; // TODO: there seems to be some way to change this value...?
|
||||
private DaFitPacketIn packetIn = new DaFitPacketIn();
|
||||
|
||||
private boolean realTimeHeartRate;
|
||||
|
||||
public DaFitDeviceSupport() {
|
||||
super(LOG);
|
||||
batteryCmd.level = ActivitySample.NOT_MEASURED;
|
||||
|
||||
addSupportedService(GattService.UUID_SERVICE_GENERIC_ACCESS);
|
||||
addSupportedService(GattService.UUID_SERVICE_GENERIC_ATTRIBUTE);
|
||||
@ -124,6 +146,12 @@ public class DaFitDeviceSupport extends AbstractBTLEDeviceSupport {
|
||||
return builder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
super.dispose();
|
||||
idleUpdateHandler.removeCallbacks(updateIdleStepsRunnable);
|
||||
}
|
||||
|
||||
private BluetoothGattCharacteristic getTargetCharacteristicForPacketType(byte packetType)
|
||||
{
|
||||
if (packetType == 1)
|
||||
@ -152,6 +180,7 @@ public class DaFitDeviceSupport extends AbstractBTLEDeviceSupport {
|
||||
{
|
||||
byte[] payload = characteristic.getValue();
|
||||
Log.i("AAAAAAAAAAAAAAAA", "Update step count: " + Logging.formatBytes(characteristic.getValue()));
|
||||
handleStepsHistory(0, payload, true);
|
||||
return true;
|
||||
}
|
||||
if (charUuid.equals(DaFitConstants.UUID_CHARACTERISTIC_DATA_IN))
|
||||
@ -181,6 +210,28 @@ public class DaFitDeviceSupport extends AbstractBTLEDeviceSupport {
|
||||
int heartRate = payload[0];
|
||||
Log.i("XXXXXXXX", "Measure heart rate finished: " + heartRate + " BPM");
|
||||
|
||||
DaFitActivitySample sample = new DaFitActivitySample();
|
||||
sample.setTimestamp((int) (System.currentTimeMillis() / 1000));
|
||||
|
||||
sample.setRawKind(DaFitSampleProvider.ACTIVITY_NOT_MEASURED);
|
||||
sample.setDataSource(DaFitSampleProvider.SOURCE_SINGLE_MEASURE);
|
||||
|
||||
sample.setBatteryLevel(ActivitySample.NOT_MEASURED);
|
||||
sample.setSteps(ActivitySample.NOT_MEASURED);
|
||||
sample.setDistanceMeters(ActivitySample.NOT_MEASURED);
|
||||
sample.setCaloriesBurnt(ActivitySample.NOT_MEASURED);
|
||||
|
||||
sample.setHeartRate(heartRate);
|
||||
sample.setBloodPressureSystolic(ActivitySample.NOT_MEASURED);
|
||||
sample.setBloodPressureDiastolic(ActivitySample.NOT_MEASURED);
|
||||
sample.setBloodOxidation(ActivitySample.NOT_MEASURED);
|
||||
|
||||
addGBActivitySample(sample);
|
||||
broadcastSample(sample);
|
||||
|
||||
if (realTimeHeartRate)
|
||||
onHeartRateTest();
|
||||
|
||||
return true;
|
||||
}
|
||||
if (packetType == DaFitConstants.CMD_TRIGGER_MEASURE_BLOOD_OXYGEN)
|
||||
@ -188,6 +239,25 @@ public class DaFitDeviceSupport extends AbstractBTLEDeviceSupport {
|
||||
int percent = payload[0];
|
||||
Log.i("XXXXXXXX", "Measure blood oxygen finished: " + percent + "%");
|
||||
|
||||
DaFitActivitySample sample = new DaFitActivitySample();
|
||||
sample.setTimestamp((int) (System.currentTimeMillis() / 1000));
|
||||
|
||||
sample.setRawKind(DaFitSampleProvider.ACTIVITY_NOT_MEASURED);
|
||||
sample.setDataSource(DaFitSampleProvider.SOURCE_SINGLE_MEASURE);
|
||||
|
||||
sample.setBatteryLevel(ActivitySample.NOT_MEASURED);
|
||||
sample.setSteps(ActivitySample.NOT_MEASURED);
|
||||
sample.setDistanceMeters(ActivitySample.NOT_MEASURED);
|
||||
sample.setCaloriesBurnt(ActivitySample.NOT_MEASURED);
|
||||
|
||||
sample.setHeartRate(ActivitySample.NOT_MEASURED);
|
||||
sample.setBloodPressureSystolic(ActivitySample.NOT_MEASURED);
|
||||
sample.setBloodPressureDiastolic(ActivitySample.NOT_MEASURED);
|
||||
sample.setBloodOxidation(percent);
|
||||
|
||||
addGBActivitySample(sample);
|
||||
broadcastSample(sample);
|
||||
|
||||
return true;
|
||||
}
|
||||
if (packetType == DaFitConstants.CMD_TRIGGER_MEASURE_BLOOD_PRESSURE)
|
||||
@ -197,6 +267,26 @@ public class DaFitDeviceSupport extends AbstractBTLEDeviceSupport {
|
||||
int data2 = payload[2];
|
||||
Log.i("XXXXXXXX", "Measure blood pressure finished: " + data1 + "/" + data2 + " (" + dataUnknown + ")");
|
||||
|
||||
|
||||
DaFitActivitySample sample = new DaFitActivitySample();
|
||||
sample.setTimestamp((int) (System.currentTimeMillis() / 1000));
|
||||
|
||||
sample.setRawKind(DaFitSampleProvider.ACTIVITY_NOT_MEASURED);
|
||||
sample.setDataSource(DaFitSampleProvider.SOURCE_SINGLE_MEASURE);
|
||||
|
||||
sample.setBatteryLevel(ActivitySample.NOT_MEASURED);
|
||||
sample.setSteps(ActivitySample.NOT_MEASURED);
|
||||
sample.setDistanceMeters(ActivitySample.NOT_MEASURED);
|
||||
sample.setCaloriesBurnt(ActivitySample.NOT_MEASURED);
|
||||
|
||||
sample.setHeartRate(ActivitySample.NOT_MEASURED);
|
||||
sample.setBloodPressureSystolic(data1);
|
||||
sample.setBloodPressureDiastolic(data2);
|
||||
sample.setBloodOxidation(ActivitySample.NOT_MEASURED);
|
||||
|
||||
addGBActivitySample(sample);
|
||||
broadcastSample(sample);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -243,6 +333,37 @@ public class DaFitDeviceSupport extends AbstractBTLEDeviceSupport {
|
||||
return false;
|
||||
}
|
||||
|
||||
private void addGBActivitySample(DaFitActivitySample sample) {
|
||||
addGBActivitySamples(new DaFitActivitySample[] { sample });
|
||||
}
|
||||
|
||||
private void addGBActivitySamples(DaFitActivitySample[] samples) {
|
||||
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
||||
User user = DBHelper.getUser(dbHandler.getDaoSession());
|
||||
Device device = DBHelper.getDevice(getDevice(), dbHandler.getDaoSession());
|
||||
|
||||
DaFitSampleProvider provider = new DaFitSampleProvider(getDevice(), dbHandler.getDaoSession());
|
||||
|
||||
for (DaFitActivitySample sample : samples) {
|
||||
sample.setDevice(device);
|
||||
sample.setUser(user);
|
||||
sample.setProvider(provider);
|
||||
provider.addGBActivitySample(sample);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
GB.toast(getContext(), "Error saving samples: " + ex.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
|
||||
GB.updateTransferNotification(null, "Data transfer failed", false, 0, getContext());
|
||||
}
|
||||
}
|
||||
|
||||
private void broadcastSample(DaFitActivitySample sample) {
|
||||
Intent intent = new Intent(DeviceService.ACTION_REALTIME_SAMPLES)
|
||||
.putExtra(DeviceService.EXTRA_REALTIME_SAMPLE, sample)
|
||||
.putExtra(DeviceService.EXTRA_TIMESTAMP, sample.getTimestamp());
|
||||
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
|
||||
}
|
||||
|
||||
private void handleDeviceInfo(DeviceInfo info) {
|
||||
LOG.warn("Device info: " + info);
|
||||
versionCmd.hwVersion = info.getHardwareRevision();
|
||||
@ -254,6 +375,25 @@ public class DaFitDeviceSupport extends AbstractBTLEDeviceSupport {
|
||||
LOG.warn("Battery info: " + info);
|
||||
batteryCmd.level = (short) info.getPercentCharged();
|
||||
handleGBDeviceEvent(batteryCmd);
|
||||
|
||||
DaFitActivitySample sample = new DaFitActivitySample();
|
||||
sample.setTimestamp((int) (System.currentTimeMillis() / 1000));
|
||||
|
||||
sample.setRawKind(DaFitSampleProvider.ACTIVITY_NOT_MEASURED);
|
||||
sample.setDataSource(DaFitSampleProvider.SOURCE_BATTERY);
|
||||
|
||||
sample.setBatteryLevel(batteryCmd.level);
|
||||
sample.setSteps(ActivitySample.NOT_MEASURED);
|
||||
sample.setDistanceMeters(ActivitySample.NOT_MEASURED);
|
||||
sample.setCaloriesBurnt(ActivitySample.NOT_MEASURED);
|
||||
|
||||
sample.setHeartRate(ActivitySample.NOT_MEASURED);
|
||||
sample.setBloodPressureSystolic(ActivitySample.NOT_MEASURED);
|
||||
sample.setBloodPressureDiastolic(ActivitySample.NOT_MEASURED);
|
||||
sample.setBloodOxidation(ActivitySample.NOT_MEASURED);
|
||||
|
||||
addGBActivitySample(sample);
|
||||
broadcastSample(sample);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -373,7 +513,327 @@ public class DaFitDeviceSupport extends AbstractBTLEDeviceSupport {
|
||||
|
||||
@Override
|
||||
public void onFetchRecordedData(int dataTypes) {
|
||||
// TODO
|
||||
if ((dataTypes & RecordedDataTypes.TYPE_ACTIVITY) != 0)
|
||||
{
|
||||
try {
|
||||
new FetchDataOperation(this).perform();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int BytesToInt24(byte[] bArr) {
|
||||
if (bArr.length != 3)
|
||||
throw new IllegalArgumentException();
|
||||
return ((bArr[2] << 24) >>> 8) | ((bArr[1] << 8) & 0xFF00) | (bArr[0] & 0xFF);
|
||||
}
|
||||
|
||||
private Runnable updateIdleStepsRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
updateIdleSteps();
|
||||
} finally {
|
||||
idleUpdateHandler.postDelayed(updateIdleStepsRunnable, IDLE_STEPS_INTERVAL);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private void updateIdleSteps()
|
||||
{
|
||||
// The steps value hasn't changed for a while, so the user is not moving
|
||||
// Store this information in the database to improve the averaging over long periods of time
|
||||
|
||||
if (!getDevice().isConnected())
|
||||
{
|
||||
LOG.warn("updateIdleSteps but device not connected?!");
|
||||
return;
|
||||
}
|
||||
|
||||
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
||||
User user = DBHelper.getUser(dbHandler.getDaoSession());
|
||||
Device device = DBHelper.getDevice(getDevice(), dbHandler.getDaoSession());
|
||||
|
||||
DaFitSampleProvider provider = new DaFitSampleProvider(getDevice(), dbHandler.getDaoSession());
|
||||
|
||||
int currentSampleTimestamp = (int)(Calendar.getInstance().getTimeInMillis() / 1000);
|
||||
|
||||
DaFitActivitySample sample = new DaFitActivitySample();
|
||||
sample.setDevice(device);
|
||||
sample.setUser(user);
|
||||
sample.setProvider(provider);
|
||||
sample.setTimestamp(currentSampleTimestamp);
|
||||
|
||||
sample.setRawKind(DaFitSampleProvider.ACTIVITY_NOT_MEASURED);
|
||||
sample.setDataSource(DaFitSampleProvider.SOURCE_STEPS_IDLE);
|
||||
|
||||
sample.setBatteryLevel(batteryCmd.level);
|
||||
sample.setSteps(0);
|
||||
sample.setDistanceMeters(0);
|
||||
sample.setCaloriesBurnt(0);
|
||||
|
||||
sample.setHeartRate(ActivitySample.NOT_MEASURED);
|
||||
sample.setBloodPressureSystolic(ActivitySample.NOT_MEASURED);
|
||||
sample.setBloodPressureDiastolic(ActivitySample.NOT_MEASURED);
|
||||
sample.setBloodOxidation(ActivitySample.NOT_MEASURED);
|
||||
|
||||
provider.addGBActivitySample(sample);
|
||||
broadcastSample(sample);
|
||||
|
||||
LOG.info("Adding an idle sample: " + sample.toString());
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
GB.toast(getContext(), "Error saving samples: " + ex.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
|
||||
GB.updateTransferNotification(null, "Data transfer failed", false, 0, getContext());
|
||||
}
|
||||
}
|
||||
|
||||
public void handleStepsHistory(int daysAgo, byte[] data, boolean isRealtime)
|
||||
{
|
||||
if (data.length != 9)
|
||||
throw new IllegalArgumentException();
|
||||
|
||||
byte[] bArr2 = new byte[3];
|
||||
System.arraycopy(data, 0, bArr2, 0, 3);
|
||||
int steps = BytesToInt24(bArr2);
|
||||
System.arraycopy(data, 3, bArr2, 0, 3);
|
||||
int distance = BytesToInt24(bArr2);
|
||||
System.arraycopy(data, 6, bArr2, 0, 3);
|
||||
int calories = BytesToInt24(bArr2);
|
||||
|
||||
Log.i("steps[" + daysAgo + "]", "steps=" + steps + ", distance=" + distance + ", calories=" + calories);
|
||||
|
||||
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
||||
User user = DBHelper.getUser(dbHandler.getDaoSession());
|
||||
Device device = DBHelper.getDevice(getDevice(), dbHandler.getDaoSession());
|
||||
|
||||
DaFitSampleProvider provider = new DaFitSampleProvider(getDevice(), dbHandler.getDaoSession());
|
||||
|
||||
Calendar thisSample = Calendar.getInstance();
|
||||
if (daysAgo != 0)
|
||||
{
|
||||
thisSample.add(Calendar.DATE, -daysAgo);
|
||||
thisSample.set(Calendar.HOUR_OF_DAY, 23);
|
||||
thisSample.set(Calendar.MINUTE, 59);
|
||||
thisSample.set(Calendar.SECOND, 59);
|
||||
thisSample.set(Calendar.MILLISECOND, 999);
|
||||
}
|
||||
else
|
||||
{
|
||||
// no change needed - use current time
|
||||
}
|
||||
|
||||
Calendar startOfDay = (Calendar) thisSample.clone();
|
||||
startOfDay.set(Calendar.HOUR_OF_DAY, 0);
|
||||
startOfDay.set(Calendar.MINUTE, 0);
|
||||
startOfDay.set(Calendar.SECOND, 0);
|
||||
startOfDay.set(Calendar.MILLISECOND, 0);
|
||||
|
||||
int startOfDayTimestamp = (int) (startOfDay.getTimeInMillis() / 1000);
|
||||
int thisSampleTimestamp = (int) (thisSample.getTimeInMillis() / 1000);
|
||||
|
||||
int previousSteps = 0;
|
||||
int previousDistance = 0;
|
||||
int previousCalories = 0;
|
||||
for (DaFitActivitySample sample : provider.getAllActivitySamples(startOfDayTimestamp, thisSampleTimestamp))
|
||||
{
|
||||
if (sample.getSteps() != ActivitySample.NOT_MEASURED)
|
||||
previousSteps += sample.getSteps();
|
||||
if (sample.getDistanceMeters() != ActivitySample.NOT_MEASURED)
|
||||
previousDistance += sample.getDistanceMeters();
|
||||
if (sample.getCaloriesBurnt() != ActivitySample.NOT_MEASURED)
|
||||
previousCalories += sample.getCaloriesBurnt();
|
||||
}
|
||||
|
||||
int newSteps = steps - previousSteps;
|
||||
int newDistance = distance - previousDistance;
|
||||
int newCalories = calories - previousCalories;
|
||||
|
||||
if (newSteps < 0 || newDistance < 0 || newCalories < 0)
|
||||
{
|
||||
LOG.warn("Ignoring a sample that would generate negative values: steps += " + newSteps + ", distance +=" + newDistance + ", calories += " + newCalories);
|
||||
}
|
||||
else if (newSteps != 0 || newDistance != 0 || newCalories != 0 || daysAgo == 0)
|
||||
{
|
||||
DaFitActivitySample sample = new DaFitActivitySample();
|
||||
sample.setDevice(device);
|
||||
sample.setUser(user);
|
||||
sample.setProvider(provider);
|
||||
sample.setTimestamp(thisSampleTimestamp);
|
||||
|
||||
sample.setRawKind(DaFitSampleProvider.ACTIVITY_NOT_MEASURED);
|
||||
sample.setDataSource(daysAgo == 0 ? DaFitSampleProvider.SOURCE_STEPS_REALTIME : DaFitSampleProvider.SOURCE_STEPS_SUMMARY);
|
||||
|
||||
sample.setBatteryLevel(ActivitySample.NOT_MEASURED);
|
||||
sample.setSteps(newSteps);
|
||||
sample.setDistanceMeters(newDistance);
|
||||
sample.setCaloriesBurnt(newCalories);
|
||||
|
||||
sample.setHeartRate(ActivitySample.NOT_MEASURED);
|
||||
sample.setBloodPressureSystolic(ActivitySample.NOT_MEASURED);
|
||||
sample.setBloodPressureDiastolic(ActivitySample.NOT_MEASURED);
|
||||
sample.setBloodOxidation(ActivitySample.NOT_MEASURED);
|
||||
|
||||
provider.addGBActivitySample(sample);
|
||||
if (isRealtime)
|
||||
{
|
||||
idleUpdateHandler.removeCallbacks(updateIdleStepsRunnable);
|
||||
idleUpdateHandler.postDelayed(updateIdleStepsRunnable, IDLE_STEPS_INTERVAL);
|
||||
broadcastSample(sample);
|
||||
}
|
||||
|
||||
LOG.info("Adding a sample: " + sample.toString());
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
GB.toast(getContext(), "Error saving samples: " + ex.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
|
||||
GB.updateTransferNotification(null, "Data transfer failed", false, 0, getContext());
|
||||
}
|
||||
}
|
||||
|
||||
public void handleSleepHistory(int daysAgo, byte[] data)
|
||||
{
|
||||
if (data.length % 3 != 0)
|
||||
throw new IllegalArgumentException();
|
||||
|
||||
int prevActivityType = DaFitSampleProvider.ACTIVITY_SLEEP_START;
|
||||
int prevSampleTimestamp = -1;
|
||||
|
||||
for(int i = 0; i < data.length / 3; i++)
|
||||
{
|
||||
int type = data[3*i];
|
||||
int start_h = data[3*i + 1];
|
||||
int start_m = data[3*i + 2];
|
||||
|
||||
Log.i("sleep[" + daysAgo + "][" + i + "]", "type=" + type + ", start_h=" + start_h + ", start_m=" + start_m);
|
||||
|
||||
// SleepAnalysis measures sleep fragment type by marking the END of the fragment.
|
||||
// The watch provides data by marking the START of the fragment.
|
||||
|
||||
// Additionally, ActivityAnalysis (used by the weekly view...) does AVERAGING when
|
||||
// adjacent samples are not of the same type..
|
||||
|
||||
// FIXME: The way Gadgetbridge does it seems kinda broken...
|
||||
|
||||
// This means that we have to convert the data when importing. Each sample gets
|
||||
// converted to two samples - one marking the beginning of the segment, and another
|
||||
// marking the end.
|
||||
|
||||
// Watch: SLEEP_LIGHT ... SLEEP_DEEP ... SLEEP_LIGHT ... SLEEP_SOBER
|
||||
// Gadgetbridge: ANYTHING,SLEEP_LIGHT ... SLEEP_LIGHT,SLEEP_DEEP ... SLEEP_DEEP,SLEEP_LIGHT ... SLEEP_LIGHT,ANYTHING
|
||||
// ^ ^- this is important, it MUST be sleep, to ensure proper detection
|
||||
// Time since the last -| of sleepStart, see SleepAnalysis.calculateSleepSessions
|
||||
// sample must be 0
|
||||
// (otherwise SleepAnalysis will include this fragment...)
|
||||
|
||||
// This means that when inserting samples:
|
||||
// * every sample is converted to (previous_sample_type, current_sample_type) happening
|
||||
// roughly at the same time (but in this order)
|
||||
// * the first sample is prefixed by unspecified activity
|
||||
// * the last sample (SOBER) is converted to unspecified activity
|
||||
|
||||
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
||||
User user = DBHelper.getUser(dbHandler.getDaoSession());
|
||||
Device device = DBHelper.getDevice(getDevice(), dbHandler.getDaoSession());
|
||||
|
||||
DaFitSampleProvider provider = new DaFitSampleProvider(getDevice(), dbHandler.getDaoSession());
|
||||
|
||||
Calendar thisSample = Calendar.getInstance();
|
||||
thisSample.add(Calendar.HOUR_OF_DAY, 4); // the clock assumes the sleep day changes at 20:00, so move the time forward to make the day correct
|
||||
thisSample.set(Calendar.MINUTE, 0);
|
||||
thisSample.add(Calendar.DATE, -daysAgo);
|
||||
|
||||
thisSample.set(Calendar.HOUR_OF_DAY, start_h);
|
||||
thisSample.set(Calendar.MINUTE, start_m);
|
||||
thisSample.set(Calendar.SECOND, 0);
|
||||
thisSample.set(Calendar.MILLISECOND, 0);
|
||||
int thisSampleTimestamp = (int) (thisSample.getTimeInMillis() / 1000);
|
||||
|
||||
int activityType;
|
||||
if (type == DaFitConstants.SLEEP_SOBER)
|
||||
activityType = DaFitSampleProvider.ACTIVITY_SLEEP_END;
|
||||
else if (type == DaFitConstants.SLEEP_LIGHT)
|
||||
activityType = DaFitSampleProvider.ACTIVITY_SLEEP_LIGHT;
|
||||
else if (type == DaFitConstants.SLEEP_RESTFUL)
|
||||
activityType = DaFitSampleProvider.ACTIVITY_SLEEP_RESTFUL;
|
||||
else
|
||||
throw new IllegalArgumentException("Invalid sleep type");
|
||||
|
||||
// Insert the end of previous segment sample
|
||||
DaFitActivitySample prevSegmentSample = new DaFitActivitySample();
|
||||
prevSegmentSample.setDevice(device);
|
||||
prevSegmentSample.setUser(user);
|
||||
prevSegmentSample.setProvider(provider);
|
||||
prevSegmentSample.setTimestamp(thisSampleTimestamp - 1);
|
||||
|
||||
prevSegmentSample.setRawKind(prevActivityType);
|
||||
prevSegmentSample.setDataSource(DaFitSampleProvider.SOURCE_SLEEP_SUMMARY);
|
||||
|
||||
prevSegmentSample.setBatteryLevel(ActivitySample.NOT_MEASURED);
|
||||
prevSegmentSample.setSteps(ActivitySample.NOT_MEASURED);
|
||||
prevSegmentSample.setDistanceMeters(ActivitySample.NOT_MEASURED);
|
||||
prevSegmentSample.setCaloriesBurnt(ActivitySample.NOT_MEASURED);
|
||||
|
||||
prevSegmentSample.setHeartRate(ActivitySample.NOT_MEASURED);
|
||||
prevSegmentSample.setBloodPressureSystolic(ActivitySample.NOT_MEASURED);
|
||||
prevSegmentSample.setBloodPressureDiastolic(ActivitySample.NOT_MEASURED);
|
||||
prevSegmentSample.setBloodOxidation(ActivitySample.NOT_MEASURED);
|
||||
|
||||
addGBActivitySampleIfNotExists(provider, prevSegmentSample);
|
||||
|
||||
// Insert the start of new segment sample
|
||||
DaFitActivitySample nextSegmentSample = new DaFitActivitySample();
|
||||
nextSegmentSample.setDevice(device);
|
||||
nextSegmentSample.setUser(user);
|
||||
nextSegmentSample.setProvider(provider);
|
||||
nextSegmentSample.setTimestamp(thisSampleTimestamp);
|
||||
|
||||
nextSegmentSample.setRawKind(activityType);
|
||||
nextSegmentSample.setDataSource(DaFitSampleProvider.SOURCE_SLEEP_SUMMARY);
|
||||
|
||||
nextSegmentSample.setBatteryLevel(ActivitySample.NOT_MEASURED);
|
||||
nextSegmentSample.setSteps(ActivitySample.NOT_MEASURED);
|
||||
nextSegmentSample.setDistanceMeters(ActivitySample.NOT_MEASURED);
|
||||
nextSegmentSample.setCaloriesBurnt(ActivitySample.NOT_MEASURED);
|
||||
|
||||
nextSegmentSample.setHeartRate(ActivitySample.NOT_MEASURED);
|
||||
nextSegmentSample.setBloodPressureSystolic(ActivitySample.NOT_MEASURED);
|
||||
nextSegmentSample.setBloodPressureDiastolic(ActivitySample.NOT_MEASURED);
|
||||
nextSegmentSample.setBloodOxidation(ActivitySample.NOT_MEASURED);
|
||||
|
||||
addGBActivitySampleIfNotExists(provider, nextSegmentSample);
|
||||
|
||||
// Set the activity type on all samples in this time period
|
||||
if (prevActivityType != DaFitSampleProvider.ACTIVITY_SLEEP_START)
|
||||
provider.updateActivityInRange(prevSampleTimestamp, thisSampleTimestamp, prevActivityType);
|
||||
|
||||
prevActivityType = activityType;
|
||||
if (prevActivityType == DaFitSampleProvider.ACTIVITY_SLEEP_END)
|
||||
prevActivityType = DaFitSampleProvider.ACTIVITY_SLEEP_START;
|
||||
prevSampleTimestamp = thisSampleTimestamp;
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
GB.toast(getContext(), "Error saving samples: " + ex.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
|
||||
GB.updateTransferNotification(null, "Data transfer failed", false, 0, getContext());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addGBActivitySampleIfNotExists(DaFitSampleProvider provider, DaFitActivitySample sample)
|
||||
{
|
||||
boolean alreadyHaveThisSample = false;
|
||||
for (DaFitActivitySample sample2 : provider.getAllActivitySamples(sample.getTimestamp() - 1, sample.getTimestamp() + 1))
|
||||
{
|
||||
if (sample2.getTimestamp() == sample2.getTimestamp() && sample2.getRawKind() == sample.getRawKind())
|
||||
alreadyHaveThisSample = true;
|
||||
}
|
||||
|
||||
if (!alreadyHaveThisSample)
|
||||
{
|
||||
provider.addGBActivitySample(sample);
|
||||
LOG.info("Adding a sample: " + sample.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -414,12 +874,18 @@ public class DaFitDeviceSupport extends AbstractBTLEDeviceSupport {
|
||||
|
||||
@Override
|
||||
public void onEnableRealtimeSteps(boolean enable) {
|
||||
// TODO
|
||||
// enabled all the time :D that's the only way to get more than a daily sum from this watch...
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnableRealtimeHeartRateMeasurement(boolean enable) {
|
||||
// TODO
|
||||
if (realTimeHeartRate == enable)
|
||||
return;
|
||||
realTimeHeartRate = enable; // will do another measurement immediately
|
||||
if (realTimeHeartRate)
|
||||
onHeartRateTest();
|
||||
else
|
||||
onAbortHeartRateTest();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -0,0 +1,196 @@
|
||||
/* Copyright (C) 2019 krzys_h
|
||||
|
||||
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.service.devices.dafit;
|
||||
|
||||
import android.bluetooth.BluetoothGatt;
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.UUID;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.Logging;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.dafit.DaFitConstants;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
public class FetchDataOperation extends AbstractBTLEOperation<DaFitDeviceSupport> {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(FetchDataOperation.class);
|
||||
|
||||
private boolean[] receivedSteps = new boolean[3];
|
||||
private boolean[] receivedSleep = new boolean[3];
|
||||
|
||||
private DaFitPacketIn packetIn = new DaFitPacketIn();
|
||||
|
||||
public FetchDataOperation(DaFitDeviceSupport support) {
|
||||
super(support);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void prePerform() {
|
||||
getDevice().setBusyTask(getContext().getString(R.string.busy_task_fetch_activity_data));
|
||||
getDevice().sendDeviceUpdateIntent(getContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doPerform() throws IOException {
|
||||
TransactionBuilder builder = performInitialized("FetchDataOperation");
|
||||
getSupport().sendPacket(builder, DaFitPacketOut.buildPacket(DaFitConstants.CMD_SYNC_PAST_SLEEP_AND_STEP, new byte[] { DaFitConstants.ARG_SYNC_YESTERDAY_SLEEP }));
|
||||
getSupport().sendPacket(builder, DaFitPacketOut.buildPacket(DaFitConstants.CMD_SYNC_PAST_SLEEP_AND_STEP, new byte[] { DaFitConstants.ARG_SYNC_DAY_BEFORE_YESTERDAY_SLEEP }));
|
||||
getSupport().sendPacket(builder, DaFitPacketOut.buildPacket(DaFitConstants.CMD_SYNC_SLEEP, new byte[0]));
|
||||
getSupport().sendPacket(builder, DaFitPacketOut.buildPacket(DaFitConstants.CMD_SYNC_PAST_SLEEP_AND_STEP, new byte[] { DaFitConstants.ARG_SYNC_YESTERDAY_STEPS }));
|
||||
getSupport().sendPacket(builder, DaFitPacketOut.buildPacket(DaFitConstants.CMD_SYNC_PAST_SLEEP_AND_STEP, new byte[] { DaFitConstants.ARG_SYNC_DAY_BEFORE_YESTERDAY_STEPS }));
|
||||
builder.read(getCharacteristic(DaFitConstants.UUID_CHARACTERISTIC_STEPS));
|
||||
builder.queue(getQueue());
|
||||
|
||||
updateProgressAndCheckFinish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
|
||||
if (!isOperationRunning())
|
||||
{
|
||||
LOG.error("onCharacteristicRead but operation is not running!");
|
||||
}
|
||||
else
|
||||
{
|
||||
UUID charUuid = characteristic.getUuid();
|
||||
if (charUuid.equals(DaFitConstants.UUID_CHARACTERISTIC_STEPS)) {
|
||||
byte[] data = characteristic.getValue();
|
||||
Log.i("TODAY STEPS", "data: " + Logging.formatBytes(data));
|
||||
decodeSteps(0, data);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return super.onCharacteristicRead(gatt, characteristic, status);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
|
||||
if (!isOperationRunning())
|
||||
{
|
||||
LOG.error("onCharacteristicChanged but operation is not running!");
|
||||
}
|
||||
else
|
||||
{
|
||||
UUID charUuid = characteristic.getUuid();
|
||||
if (charUuid.equals(DaFitConstants.UUID_CHARACTERISTIC_DATA_IN))
|
||||
{
|
||||
if (packetIn.putFragment(characteristic.getValue())) {
|
||||
Pair<Byte, byte[]> packet = DaFitPacketIn.parsePacket(packetIn.getPacket());
|
||||
packetIn = new DaFitPacketIn();
|
||||
if (packet != null) {
|
||||
byte packetType = packet.first;
|
||||
byte[] payload = packet.second;
|
||||
|
||||
if (handlePacket(packetType, payload))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return super.onCharacteristicChanged(gatt, characteristic);
|
||||
}
|
||||
|
||||
private boolean handlePacket(byte packetType, byte[] payload) {
|
||||
if (packetType == DaFitConstants.CMD_SYNC_SLEEP) {
|
||||
Log.i("TODAY SLEEP", "data: " + Logging.formatBytes(payload));
|
||||
decodeSleep(0, payload);
|
||||
return true;
|
||||
}
|
||||
if (packetType == DaFitConstants.CMD_SYNC_PAST_SLEEP_AND_STEP) {
|
||||
byte dataType = payload[0];
|
||||
byte[] data = new byte[payload.length - 1];
|
||||
System.arraycopy(payload, 1, data, 0, data.length);
|
||||
|
||||
// NOTE: Does this seem swapped to you? That's because IT IS! I took the constant names
|
||||
// from the official app, but as it turns out, the official app has a bug.
|
||||
// (and yes, you can see that data from yesterday appears as two days ago
|
||||
// in the app itself and all past data is getting messed up because of it)
|
||||
|
||||
if (dataType == DaFitConstants.ARG_SYNC_YESTERDAY_STEPS) {
|
||||
Log.i("2 DAYS AGO STEPS", "data: " + Logging.formatBytes(data));
|
||||
decodeSteps(2, data);
|
||||
return true;
|
||||
}
|
||||
else if (dataType == DaFitConstants.ARG_SYNC_DAY_BEFORE_YESTERDAY_STEPS) {
|
||||
Log.i("YESTERDAY STEPS", "data: " + Logging.formatBytes(data));
|
||||
decodeSteps(1, data);
|
||||
return true;
|
||||
}
|
||||
else if (dataType == DaFitConstants.ARG_SYNC_YESTERDAY_SLEEP) {
|
||||
Log.i("2 DAYS AGO SLEEP", "data: " + Logging.formatBytes(data));
|
||||
decodeSleep(2, data);
|
||||
return true;
|
||||
}
|
||||
else if (dataType == DaFitConstants.ARG_SYNC_DAY_BEFORE_YESTERDAY_SLEEP) {
|
||||
Log.i("YESTERDAY SLEEP", "data: " + Logging.formatBytes(data));
|
||||
decodeSleep(1, data);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void decodeSteps(int daysAgo, byte[] data)
|
||||
{
|
||||
getSupport().handleStepsHistory(daysAgo, data, false);
|
||||
receivedSteps[daysAgo] = true;
|
||||
updateProgressAndCheckFinish();
|
||||
}
|
||||
|
||||
private void decodeSleep(int daysAgo, byte[] data)
|
||||
{
|
||||
getSupport().handleSleepHistory(daysAgo, data);
|
||||
receivedSleep[daysAgo] = true;
|
||||
updateProgressAndCheckFinish();
|
||||
}
|
||||
|
||||
private void updateProgressAndCheckFinish()
|
||||
{
|
||||
int count = 0;
|
||||
int total = receivedSteps.length + receivedSleep.length;
|
||||
for(int i = 0; i < receivedSteps.length; i++)
|
||||
if (receivedSteps[i])
|
||||
++count;
|
||||
for(int i = 0; i < receivedSleep.length; i++)
|
||||
if (receivedSleep[i])
|
||||
++count;
|
||||
GB.updateTransferNotification(null, getContext().getString(R.string.busy_task_fetch_activity_data), true, 100 * count / total, getContext());
|
||||
if (count == total)
|
||||
operationFinished();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void operationFinished() {
|
||||
operationStatus = OperationStatus.FINISHED;
|
||||
if (getDevice() != null && getDevice().isConnected()) {
|
||||
unsetBusy();
|
||||
GB.signalActivityDataFinish();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user