1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-11-30 14:02:56 +01:00

TLW64: Support fetching activity data (steps + sleep)

This commit is contained in:
115ek 2020-07-01 18:54:31 +02:00
parent 95da81dfcf
commit 943728c35c
5 changed files with 195 additions and 5 deletions

View File

@ -43,7 +43,7 @@ public class GBDaoGenerator {
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
Schema schema = new Schema(27, MAIN_PACKAGE + ".entities"); Schema schema = new Schema(28, MAIN_PACKAGE + ".entities");
Entity userAttributes = addUserAttributes(schema); Entity userAttributes = addUserAttributes(schema);
Entity user = addUserInfo(schema, userAttributes); Entity user = addUserInfo(schema, userAttributes);
@ -73,6 +73,7 @@ public class GBDaoGenerator {
addJYouActivitySample(schema, user, device); addJYouActivitySample(schema, user, device);
addWatchXPlusHealthActivitySample(schema, user, device); addWatchXPlusHealthActivitySample(schema, user, device);
addWatchXPlusHealthActivityKindOverlay(schema, user, device); addWatchXPlusHealthActivityKindOverlay(schema, user, device);
addTLW64ActivitySample(schema, user, device);
addHybridHRActivitySample(schema, user, device); addHybridHRActivitySample(schema, user, device);
addCalendarSyncState(schema, device); addCalendarSyncState(schema, device);
@ -393,6 +394,16 @@ public class GBDaoGenerator {
return activityOverlay; return activityOverlay;
} }
private static Entity addTLW64ActivitySample(Schema schema, Entity user, Entity device) {
Entity activitySample = addEntity(schema, "TLW64ActivitySample");
activitySample.implementsSerializable();
addCommonActivitySampleProperties("AbstractActivitySample", activitySample, user, device);
activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE);
activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull().codeBeforeGetterAndSetter(OVERRIDE);
activitySample.addIntProperty(SAMPLE_RAW_INTENSITY).notNull().codeBeforeGetterAndSetter(OVERRIDE);
return activitySample;
}
private static void addCommonActivitySampleProperties(String superClass, Entity activitySample, Entity user, Entity device) { private static void addCommonActivitySampleProperties(String superClass, Entity activitySample, Entity user, Entity device) {
activitySample.setSuperclass(superClass); activitySample.setSuperclass(superClass);
activitySample.addImport(MAIN_PACKAGE + ".devices.SampleProvider"); activitySample.addImport(MAIN_PACKAGE + ".devices.SampleProvider");

View File

@ -33,6 +33,8 @@ public final class TLW64Constants {
public static final byte CMD_USER_DATA = (byte) 0xa9; public static final byte CMD_USER_DATA = (byte) 0xa9;
public static final byte CMD_ALARM = (byte) 0xab; public static final byte CMD_ALARM = (byte) 0xab;
public static final byte CMD_FACTORY_RESET = (byte) 0xad; public static final byte CMD_FACTORY_RESET = (byte) 0xad;
public static final byte CMD_FETCH_STEPS = (byte) 0xb2;
public static final byte CMD_FETCH_SLEEP = (byte) 0xb3;
public static final byte CMD_NOTIFICATION = (byte) 0xc1; public static final byte CMD_NOTIFICATION = (byte) 0xc1;
public static final byte CMD_ICON = (byte) 0xc3; public static final byte CMD_ICON = (byte) 0xc3;
public static final byte CMD_DEVICE_SETTINGS = (byte) 0xd3; public static final byte CMD_DEVICE_SETTINGS = (byte) 0xd3;

View File

@ -66,17 +66,17 @@ public class TLW64Coordinator extends AbstractDeviceCoordinator {
@Override @Override
public boolean supportsActivityDataFetching() { public boolean supportsActivityDataFetching() {
return false; return true;
} }
@Override @Override
public boolean supportsActivityTracking() { public boolean supportsActivityTracking() {
return false; return true;
} }
@Override @Override
public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) { public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
return null; return new TLW64SampleProvider(device, session);
} }
@Override @Override

View File

@ -0,0 +1,68 @@
package nodomain.freeyourgadget.gadgetbridge.devices.tlw64;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import de.greenrobot.dao.AbstractDao;
import de.greenrobot.dao.Property;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.TLW64ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.entities.TLW64ActivitySampleDao;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
public class TLW64SampleProvider extends AbstractSampleProvider<TLW64ActivitySample> {
private GBDevice mDevice;
private DaoSession mSession;
public TLW64SampleProvider(GBDevice device, DaoSession session) {
super(device, session);
mSession = session;
mDevice = device;
}
@Override
public AbstractDao<TLW64ActivitySample, ?> getSampleDao() {
return getSession().getTLW64ActivitySampleDao();
}
@Nullable
@Override
protected Property getRawKindSampleProperty() {
return TLW64ActivitySampleDao.Properties.RawKind;
}
@NonNull
@Override
protected Property getTimestampSampleProperty() {
return TLW64ActivitySampleDao.Properties.Timestamp;
}
@NonNull
@Override
protected Property getDeviceIdentifierSampleProperty() {
return TLW64ActivitySampleDao.Properties.DeviceId;
}
@Override
public int normalizeType(int rawType) {
return rawType;
}
@Override
public int toRawActivityKind(int activityKind) {
return activityKind;
}
@Override
public float normalizeIntensity(int rawIntensity) {
return rawIntensity / (float) 4000.0;
}
@Override
public TLW64ActivitySample createActivitySample() {
return new TLW64ActivitySample();
}
}

View File

@ -34,15 +34,21 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Calendar; import java.util.Calendar;
import java.util.GregorianCalendar; import java.util.GregorianCalendar;
import java.util.List;
import java.util.UUID; import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity; import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
import nodomain.freeyourgadget.gadgetbridge.devices.tlw64.TLW64Constants; import nodomain.freeyourgadget.gadgetbridge.devices.tlw64.TLW64Constants;
import nodomain.freeyourgadget.gadgetbridge.devices.tlw64.TLW64SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.TLW64ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser; import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm; import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec; import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
@ -55,6 +61,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService; import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceBusyAction;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction; import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.IntentListener; import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.IntentListener;
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.battery.BatteryInfoProfile; import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.battery.BatteryInfoProfile;
@ -73,6 +80,9 @@ public class TLW64Support extends AbstractBTLEDeviceSupport {
private final GBDeviceEventVersionInfo versionCmd = new GBDeviceEventVersionInfo(); private final GBDeviceEventVersionInfo versionCmd = new GBDeviceEventVersionInfo();
public BluetoothGattCharacteristic ctrlCharacteristic = null; public BluetoothGattCharacteristic ctrlCharacteristic = null;
public BluetoothGattCharacteristic notifyCharacteristic = null; public BluetoothGattCharacteristic notifyCharacteristic = null;
private List<TLW64ActivitySample> samples = new ArrayList<>();
private byte crc = 0;
private int firstTimestamp = 0;
private final IntentListener mListener = new IntentListener() { private final IntentListener mListener = new IntentListener() {
@Override @Override
@ -167,6 +177,10 @@ public class TLW64Support extends AbstractBTLEDeviceSupport {
case TLW64Constants.CMD_FACTORY_RESET: case TLW64Constants.CMD_FACTORY_RESET:
LOG.info("Factory reset requested"); LOG.info("Factory reset requested");
return true; return true;
case TLW64Constants.CMD_FETCH_STEPS:
case TLW64Constants.CMD_FETCH_SLEEP:
handleActivityData(data);
return true;
case TLW64Constants.CMD_NOTIFICATION: case TLW64Constants.CMD_NOTIFICATION:
LOG.info("Notification is displayed"); LOG.info("Notification is displayed");
return true; return true;
@ -345,7 +359,7 @@ public class TLW64Support extends AbstractBTLEDeviceSupport {
@Override @Override
public void onFetchRecordedData(int dataTypes) { public void onFetchRecordedData(int dataTypes) {
sendFetchCommand(TLW64Constants.CMD_FETCH_STEPS);
} }
@Override @Override
@ -596,4 +610,99 @@ public class TLW64Support extends AbstractBTLEDeviceSupport {
LOG.warn("Unable to stop notification", e); LOG.warn("Unable to stop notification", e);
} }
} }
private void sendFetchCommand(byte type) {
samples.clear();
crc = 0;
firstTimestamp = 0;
try {
TransactionBuilder builder = performInitialized("fetchActivityData");
builder.add(new SetDeviceBusyAction(getDevice(), getContext().getString(R.string.busy_task_fetch_activity_data), getContext()));
byte[] msg = new byte[]{
type,
(byte) 0xfa
};
builder.write(ctrlCharacteristic, msg);
builder.queue(getQueue());
} catch (IOException e) {
GB.toast(getContext(), "Error fetching activity data: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
}
}
private void handleActivityData(byte[] data) {
if (data[1] == (byte) 0xfd) {
LOG.info("CRC received: " + (data[2] & 0xff) + ", calculated: " + (crc & 0xff));
if (data[2] != crc) {
GB.toast(getContext(), "Incorrect CRC. Try fetching data again.", Toast.LENGTH_LONG, GB.ERROR);
GB.updateTransferNotification(null, "Data transfer failed", false, 0, getContext());
if (getDevice().isBusy()) {
getDevice().unsetBusyTask();
getDevice().sendDeviceUpdateIntent(getContext());
}
} else if (samples.size() > 0) {
try (DBHandler dbHandler = GBApplication.acquireDB()) {
Long userId = DBHelper.getUser(dbHandler.getDaoSession()).getId();
Long deviceId = DBHelper.getDevice(getDevice(), dbHandler.getDaoSession()).getId();
TLW64SampleProvider provider = new TLW64SampleProvider(getDevice(), dbHandler.getDaoSession());
for (int i = 0; i < samples.size(); i++) {
samples.get(i).setDeviceId(deviceId);
samples.get(i).setUserId(userId);
if (data[0] == TLW64Constants.CMD_FETCH_STEPS) {
samples.get(i).setRawKind(ActivityKind.TYPE_ACTIVITY);
samples.get(i).setRawIntensity(samples.get(i).getSteps());
} else if (data[0] == TLW64Constants.CMD_FETCH_SLEEP) {
if (samples.get(i).getRawIntensity() < 7) {
samples.get(i).setRawKind(ActivityKind.TYPE_DEEP_SLEEP);
} else
samples.get(i).setRawKind(ActivityKind.TYPE_LIGHT_SLEEP);
}
provider.addGBActivitySample(samples.get(i));
}
LOG.info("Activity data saved");
if (data[0] == TLW64Constants.CMD_FETCH_STEPS) {
sendFetchCommand(TLW64Constants.CMD_FETCH_SLEEP);
} else {
GB.updateTransferNotification(null, "", false, 100, getContext());
if (getDevice().isBusy()) {
getDevice().unsetBusyTask();
GB.signalActivityDataFinish();
}
}
} catch (Exception ex) {
GB.toast(getContext(), "Error saving activity data: " + ex.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
GB.updateTransferNotification(null, "Data transfer failed", false, 0, getContext());
}
}
} else {
TLW64ActivitySample sample = new TLW64ActivitySample();
Calendar timestamp = GregorianCalendar.getInstance();
timestamp.set(Calendar.YEAR, data[1] * 256 + (data[2] & 0xff));
timestamp.set(Calendar.MONTH, (data[3] - 1) & 0xff);
timestamp.set(Calendar.DAY_OF_MONTH, data[4] & 0xff);
timestamp.set(Calendar.HOUR_OF_DAY, data[5] & 0xff);
timestamp.set(Calendar.SECOND, 0);
int startProgress = 0;
if (data[0] == TLW64Constants.CMD_FETCH_STEPS) {
timestamp.set(Calendar.MINUTE, 0);
sample.setSteps(data[6] * 256 + (data[7] & 0xff));
crc ^= (data[6] ^ data[7]);
} else if (data[0] == TLW64Constants.CMD_FETCH_SLEEP) {
timestamp.set(Calendar.MINUTE, data[6] & 0xff);
sample.setRawIntensity(data[7] * 256 + (data[8] & 0xff));
crc ^= (data[7] ^ data[8]);
startProgress = 33;
}
sample.setTimestamp((int) (timestamp.getTimeInMillis() / 1000L));
samples.add(sample);
if (firstTimestamp == 0)
firstTimestamp = sample.getTimestamp();
int progress = startProgress + 33 * (sample.getTimestamp() - firstTimestamp) /
((int) (Calendar.getInstance().getTimeInMillis() / 1000L) - firstTimestamp);
GB.updateTransferNotification(null, getContext().getString(R.string.busy_task_fetch_activity_data), true, progress, getContext());
}
}
} }