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:
parent
95da81dfcf
commit
943728c35c
@ -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");
|
||||||
|
@ -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;
|
||||||
|
@ -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
|
||||||
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user