From fd7a0cb124074c7c71d4305cc7e548b5aa4943fd Mon Sep 17 00:00:00 2001 From: Vadim Kaushan Date: Mon, 30 Jul 2018 23:23:13 +0300 Subject: [PATCH] ID115: fetching activity data --- .../devices/id115/ID115Constants.java | 3 + .../devices/id115/ID115Coordinator.java | 6 +- .../devices/id115/ID115SampleProvider.java | 61 +++++++ .../devices/id115/FetchActivityOperation.java | 160 ++++++++++++++++++ .../service/devices/id115/ID115Support.java | 9 +- 5 files changed, 235 insertions(+), 4 deletions(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/id115/ID115SampleProvider.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/id115/FetchActivityOperation.java diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/id115/ID115Constants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/id115/ID115Constants.java index 6b4e3c985..e7be5e6ec 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/id115/ID115Constants.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/id115/ID115Constants.java @@ -44,6 +44,9 @@ public class ID115Constants { public static final byte CMD_KEY_NOTIFY_STOP = 0x02; public static final byte CMD_KEY_NOTIFY_MSG = 0x03; + // CMD_ID_HEALTH_DATA + public static final byte CMD_KEY_FETCH_ACTIVITY_TODAY = 0x03; + // CMD_ID_DEVICE_RESTART public static final byte CMD_KEY_REBOOT = 0x01; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/id115/ID115Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/id115/ID115Coordinator.java index 7e7c174bc..e920287fa 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/id115/ID115Coordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/id115/ID115Coordinator.java @@ -65,17 +65,17 @@ public class ID115Coordinator extends AbstractDeviceCoordinator { @Override public boolean supportsActivityDataFetching() { - return false; + return true; } @Override public boolean supportsActivityTracking() { - return false; + return true; } @Override public SampleProvider getSampleProvider(GBDevice device, DaoSession session) { - return null; + return new ID115SampleProvider(device, session); } @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/id115/ID115SampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/id115/ID115SampleProvider.java new file mode 100644 index 000000000..83115e350 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/id115/ID115SampleProvider.java @@ -0,0 +1,61 @@ +package nodomain.freeyourgadget.gadgetbridge.devices.id115; + +import android.support.annotation.NonNull; +import android.support.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.ID115ActivitySample; +import nodomain.freeyourgadget.gadgetbridge.entities.ID115ActivitySampleDao; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; + +public class ID115SampleProvider extends AbstractSampleProvider { + public ID115SampleProvider(GBDevice device, DaoSession session) { + super(device, session); + } + + @Override + public AbstractDao getSampleDao() { + return getSession().getID115ActivitySampleDao(); + } + + @Nullable + @Override + protected Property getRawKindSampleProperty() { + return ID115ActivitySampleDao.Properties.RawKind; + } + + @NonNull + @Override + protected Property getTimestampSampleProperty() { + return ID115ActivitySampleDao.Properties.Timestamp; + } + + @NonNull + @Override + protected Property getDeviceIdentifierSampleProperty() { + return ID115ActivitySampleDao.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; + } + + @Override + public ID115ActivitySample createActivitySample() { + return new ID115ActivitySample(); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/id115/FetchActivityOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/id115/FetchActivityOperation.java new file mode 100644 index 000000000..060ba8b45 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/id115/FetchActivityOperation.java @@ -0,0 +1,160 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.id115; + +import android.widget.Toast; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.GregorianCalendar; + +import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; +import nodomain.freeyourgadget.gadgetbridge.database.DBHelper; +import nodomain.freeyourgadget.gadgetbridge.devices.id115.ID115Constants; +import nodomain.freeyourgadget.gadgetbridge.devices.id115.ID115SampleProvider; +import nodomain.freeyourgadget.gadgetbridge.entities.ID115ActivitySample; +import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; +import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; +import nodomain.freeyourgadget.gadgetbridge.util.GB; + +public class FetchActivityOperation extends AbstractID115Operation { + private static final Logger LOG = LoggerFactory.getLogger(FetchActivityOperation.class); + private byte expectedCmd; + private byte expectedSeq; + private ArrayList packets; + + protected FetchActivityOperation(ID115Support support) { + super(support); + } + + @Override + boolean isHealthOperation() { + return true; + } + + @Override + protected void doPerform() throws IOException { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + outputStream.write(ID115Constants.CMD_ID_HEALTH_DATA); + outputStream.write(ID115Constants.CMD_KEY_FETCH_ACTIVITY_TODAY); + outputStream.write(0x01); + outputStream.write(0x00); + outputStream.write(0x00); + byte cmd[] = outputStream.toByteArray(); + + expectedCmd = ID115Constants.CMD_KEY_FETCH_ACTIVITY_TODAY; + expectedSeq = 1; + packets = new ArrayList<>(); + + TransactionBuilder builder = performInitialized("send activity fetch request"); + builder.write(controlCharacteristic, cmd); + builder.queue(getQueue()); + } + + @Override + void handleResponse(byte[] data) { + if (!isOperationRunning()) { + LOG.error("ignoring notification because operation is not running. Data length: " + data.length); + getSupport().logMessageContent(data); + return; + } + + if (data.length < 4) { + LOG.warn("short GATT response"); + return; + } + + if (data[0] == ID115Constants.CMD_ID_HEALTH_DATA) { + if (data[1] == (byte)0xEE) { + LOG.info("Activity data transfer has finished."); + parseAndStore(); + operationFinished(); + } else { + if ((data[1] != expectedCmd) || (data[2] != expectedSeq)) { + GB.toast(getContext(), "Error fetching ID115 activity data, you may need to connect and disconnect", Toast.LENGTH_LONG, GB.ERROR); + operationFinished(); + return; + } + expectedSeq += 1; + + byte payload[] = new byte[data.length - 4]; + System.arraycopy(data, 4, payload, 0, payload.length); + packets.add(payload); + } + } + } + + void parseAndStore() { + if (packets.size() <= 1) { + return; + } + + byte[] header = packets.get(0); + int year = ((header[1] & 0xFF) << 8) | (header[0] & 0xFF); + int month = header[2] & 0xFF; + int day = header[3] & 0xFF; + int sampleDurationMinutes = header[6] & 0xFF; + Calendar calendar = new GregorianCalendar(year, month - 1, day); + int ts = (int)(calendar.getTimeInMillis() / 1000); + int dt = sampleDurationMinutes * 60; + + ArrayList samples = new ArrayList<>(); + + for (int i = 2; i < packets.size(); i++) { + byte[] packet = packets.get(i); + for (int j = 0; j <= packet.length - 5; j += 5) { + byte[] sampleData = new byte[5]; + System.arraycopy(packet, j, sampleData, 0, sampleData.length); + + ID115ActivitySample sample = parseSample(sampleData); + if (sample != null) { + sample.setTimestamp(ts); + sample.setRawKind(ActivityKind.TYPE_ACTIVITY); + samples.add(sample); + } + ts += dt; + } + } + + try (DBHandler dbHandler = GBApplication.acquireDB()) { + ID115ActivitySample[] sampleArray = samples.toArray(new ID115ActivitySample[0]); + long userId = DBHelper.getUser(dbHandler.getDaoSession()).getId(); + long deviceId = DBHelper.getDevice(getDevice(), dbHandler.getDaoSession()).getId(); + for (ID115ActivitySample sample: sampleArray) { + sample.setUserId(userId); + sample.setDeviceId(deviceId); + } + + ID115SampleProvider provider = new ID115SampleProvider(getDevice(), dbHandler.getDaoSession()); + provider.addGBActivitySamples(sampleArray); + } catch (Exception ex) { + GB.toast(getContext(), "Error saving activity data: " + ex.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR); + } + } + + ID115ActivitySample parseSample(byte[] data) { + int d01 = ((data[1] & 0xFF) << 8) | (data[0] & 0xFF); + int d12 = ((data[2] & 0xFF) << 8) | (data[1] & 0xFF); + int d23 = ((data[3] & 0xFF) << 8) | (data[2] & 0xFF); + int d34 = ((data[4] & 0xFF) << 8) | (data[3] & 0xFF); + int stepCount = (d01 >> 2) & 0xFFF; + int activeTime = (d12 >> 6) & 0xF; + int calories = (d23 >> 2) & 0x3FF; + int distance = (d34 >> 4); + + if (stepCount == 0) { + return null; + } + + ID115ActivitySample sample = new ID115ActivitySample(); + sample.setSteps(stepCount); + sample.setActiveTimeMinutes(activeTime); + sample.setCaloriesBurnt(calories); + sample.setDistanceMeters(distance); + return sample; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/id115/ID115Support.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/id115/ID115Support.java index 1fe8dd4ef..95dfb2337 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/id115/ID115Support.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/id115/ID115Support.java @@ -64,6 +64,9 @@ public class ID115Support extends AbstractBTLEDeviceSupport { .setGoal(builder) .setInitialized(builder); + getDevice().setFirmwareVersion("N/A"); + getDevice().setFirmwareVersion2("N/A"); + return builder; } @@ -167,7 +170,11 @@ public class ID115Support extends AbstractBTLEDeviceSupport { @Override public void onFetchRecordedData(int dataTypes) { - + try { + new FetchActivityOperation(this).perform(); + } catch (IOException ex) { + LOG.error("Unable to fetch ID115 activity data", ex); + } } @Override