From 8f9466ee1c2aa216d0d72581991692faf52d574d Mon Sep 17 00:00:00 2001 From: mkusnierz <> Date: Tue, 29 Oct 2019 22:34:31 +0100 Subject: [PATCH] Initial support for sleep data retrieval --- .../gadgetbridge/daogen/GBDaoGenerator.java | 19 ++- .../watchxplus/WatchXPlusDeviceSupport.java | 117 +++++++++++++----- 2 files changed, 102 insertions(+), 34 deletions(-) diff --git a/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java b/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java index ac073ec7e..a8ab19315 100644 --- a/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java +++ b/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java @@ -73,6 +73,7 @@ public class GBDaoGenerator { addZeTimeActivitySample(schema, user, device); addID115ActivitySample(schema, user, device); addWatchXPlusHealthActivitySample(schema, user, device); + addWatchXPlusHealthActivityKindOverlay(schema, user, device); addCalendarSyncState(schema, device); addAlarms(schema, user, device); @@ -337,7 +338,7 @@ public class GBDaoGenerator { addCommonActivitySampleProperties("AbstractActivitySample", activitySample, user, device); activitySample.addByteArrayProperty("rawWatchXPlusHealthData"); activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull().primaryKey(); -// activitySample.addIntProperty(SAMPLE_RAW_INTENSITY).notNull().codeBeforeGetterAndSetter(OVERRIDE); + activitySample.addIntProperty(SAMPLE_RAW_INTENSITY).notNull().codeBeforeGetterAndSetter(OVERRIDE); activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE); addHeartRateProperties(activitySample); activitySample.addIntProperty("distance"); @@ -345,6 +346,22 @@ public class GBDaoGenerator { return activitySample; } + private static Entity addWatchXPlusHealthActivityKindOverlay(Schema schema, Entity user, Entity device) { + Entity activityOverlay = addEntity(schema, "WatchXPlusHealthActivityOverlay"); + + activityOverlay.addIntProperty(TIMESTAMP_FROM).notNull().primaryKey(); + activityOverlay.addIntProperty(TIMESTAMP_TO).notNull().primaryKey(); + activityOverlay.addIntProperty(SAMPLE_RAW_KIND).notNull().primaryKey(); + Property deviceId = activityOverlay.addLongProperty("deviceId").primaryKey().notNull().getProperty(); + activityOverlay.addToOne(device, deviceId); + + Property userId = activityOverlay.addLongProperty("userId").notNull().getProperty(); + activityOverlay.addToOne(user, userId); + activityOverlay.addByteArrayProperty("rawWatchXPlusHealthData"); + + return activityOverlay; + } + private static void addCommonActivitySampleProperties(String superClass, Entity activitySample, Entity user, Entity device) { activitySample.setSuperclass(superClass); activitySample.addImport(MAIN_PACKAGE + ".devices.SampleProvider"); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lenovo/watchxplus/WatchXPlusDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lenovo/watchxplus/WatchXPlusDeviceSupport.java index 823a43a09..926fea157 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lenovo/watchxplus/WatchXPlusDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lenovo/watchxplus/WatchXPlusDeviceSupport.java @@ -37,7 +37,6 @@ import java.util.Arrays; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; -import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; @@ -55,6 +54,8 @@ import nodomain.freeyourgadget.gadgetbridge.devices.lenovo.DataType; import nodomain.freeyourgadget.gadgetbridge.devices.lenovo.watchxplus.WatchXPlusConstants; import nodomain.freeyourgadget.gadgetbridge.devices.lenovo.watchxplus.WatchXPlusSampleProvider; import nodomain.freeyourgadget.gadgetbridge.entities.WatchXPlusActivitySample; +import nodomain.freeyourgadget.gadgetbridge.entities.WatchXPlusHealthActivityOverlay; +import nodomain.freeyourgadget.gadgetbridge.entities.WatchXPlusHealthActivityOverlayDao; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; @@ -86,7 +87,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { private Map dataToFetch = new LinkedHashMap<>(); private int requestedDataTimestamp; - private Map dataSlots = new HashMap<>(); + private int dataSlots = 0; private DataType currentDataType; private byte ACK_CALIBRATION = 0; @@ -507,7 +508,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { WatchXPlusConstants.READ_VALUE)); // Fetch heart rate data samples count - requestDataCount(builder, DataType.HEART_RATE); + requestDataCount(DataType.HEART_RATE); builder.queue(getQueue()); } catch (IOException e) { @@ -715,11 +716,21 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { * 8. Receive frames with content. They are different than other frames, {@link WatchXPlusDeviceSupport#handleContentDataChunk} * ie. 0000000255-4F4C48-434241434444454648474747, 0001000247-474645-434240FFFFFFFFFFFFFFFFFF */ - private void requestDataCount(TransactionBuilder builder, DataType dataType) { - builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), - buildCommand(WatchXPlusConstants.CMD_RETRIEVE_DATA_COUNT, - WatchXPlusConstants.READ_VALUE, - dataType.getValue())); + private void requestDataCount(DataType dataType) { + + TransactionBuilder builder; + try { + builder = performInitialized("requestDataCount"); + + builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), + buildCommand(WatchXPlusConstants.CMD_RETRIEVE_DATA_COUNT, + WatchXPlusConstants.READ_VALUE, + dataType.getValue())); + + builder.queue(getQueue()); + } catch (IOException e) { + LOG.warn("Unable to send request to retrieve recorded data", e); + } } private void handleDataCount(byte[] value) { @@ -729,7 +740,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { DataType type = DataType.getType(dataType); LOG.info("Watch contains " + dataCount + " " + type + " entries"); - dataSlots.put(type, dataCount); + dataSlots = dataCount; dataToFetch.clear(); if (dataCount != 0) { requestDataDetails(dataToFetch.size(), type); @@ -775,7 +786,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { dataToFetch.put(timestamp, parts); - if (dataToFetch.size() == dataSlots.get(currentDataType)) { + if (dataToFetch.size() == dataSlots) { Map.Entry currentValue = dataToFetch.entrySet().iterator().next(); requestedDataTimestamp = currentValue.getKey(); requestDataContentForTimestamp(requestedDataTimestamp, currentDataType); @@ -830,10 +841,36 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { int dataType = Conversion.fromByteArr16(value[2], value[3]); int timezoneOffset = TimeZone.getDefault().getOffset(System.currentTimeMillis())/1000; DataType type = DataType.getType(dataType); - if (type == DataType.HEART_RATE) { - try (DBHandler dbHandler = GBApplication.acquireDB()) { - WatchXPlusSampleProvider provider = new WatchXPlusSampleProvider(getDevice(), dbHandler.getDaoSession()); - List samples = new ArrayList<>(); + try (DBHandler dbHandler = GBApplication.acquireDB()) { + WatchXPlusSampleProvider provider = new WatchXPlusSampleProvider(getDevice(), dbHandler.getDaoSession()); + List samples = new ArrayList<>(); + + if (DataType.SLEEP.equals(type)) { + WatchXPlusHealthActivityOverlayDao overlayDao = dbHandler.getDaoSession().getWatchXPlusHealthActivityOverlayDao(); + List overlayList = new ArrayList<>(); + + for (int i = 4; i < value.length; i+= 2) { + + int val = Conversion.fromByteArr16(value[i], value[i+1]); + if (65535 == val) { + break; + } + + int tsWithOffset = requestedDataTimestamp + (((((chunkNo * 16) / 2) + ((i - 4) / 2)) *5) * 60) - timezoneOffset; + LOG.debug(" requested timestamp " + requestedDataTimestamp + " chunkNo " + chunkNo + " Got data: " + new Date((long) tsWithOffset * 1000) + ", value: " + val); + WatchXPlusActivitySample sample = createSample(dbHandler, tsWithOffset); + sample.setTimestamp(tsWithOffset); + sample.setProvider(provider); + sample.setRawIntensity(val); + sample.setRawKind(val == 0 ? ActivityKind.TYPE_DEEP_SLEEP : ActivityKind.TYPE_LIGHT_SLEEP); + samples.add(sample); + overlayList.add(new WatchXPlusHealthActivityOverlay(sample.getTimestamp(), sample.getTimestamp()+300, sample.getRawKind(), sample.getDeviceId(), sample.getUserId(), sample.getRawWatchXPlusHealthData())); + } + overlayDao.insertOrReplaceInTx(overlayList); + provider.addGBActivitySamples(samples.toArray(new WatchXPlusActivitySample[0])); + + handleEndOfDataChunks(chunkNo, type); + } else if (DataType.HEART_RATE.equals(type)) { for (int i = 4; i < value.length; i++) { @@ -851,27 +888,40 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { samples.add(sample); } provider.addGBActivitySamples(samples.toArray(new WatchXPlusActivitySample[0])); - } catch (GBException ex) { - LOG.info((ex.getMessage())); - } catch (Exception ex) { - LOG.info(ex.getMessage()); - } - if(!dataToFetch.isEmpty() && chunkNo == dataToFetch.get(requestedDataTimestamp) - 1) { - dataToFetch.remove(requestedDataTimestamp); - removeDataContentForTimestamp(requestedDataTimestamp, currentDataType); - if (!dataToFetch.isEmpty()) { - Map.Entry currentValue = dataToFetch.entrySet().iterator().next(); - requestedDataTimestamp = currentValue.getKey(); - requestDataContentForTimestamp(requestedDataTimestamp, type); - } else { - dataSlots.put(type,0); - } - } else if (dataToFetch.isEmpty()) { - dataSlots.put(type,0); + handleEndOfDataChunks(chunkNo, type); + } else { + LOG.warn(" Got unsupported data package type: " + type); + } + } catch (GBException ex) { + LOG.info((ex.getMessage())); + } catch (Exception ex) { + LOG.info(ex.getMessage()); + } + + } + + private void handleEndOfDataChunks(int chunkNo, DataType type) { + if(!dataToFetch.isEmpty() && chunkNo == dataToFetch.get(requestedDataTimestamp) - 1) { + dataToFetch.remove(requestedDataTimestamp); + removeDataContentForTimestamp(requestedDataTimestamp, currentDataType); + if (!dataToFetch.isEmpty()) { + Map.Entry currentValue = dataToFetch.entrySet().iterator().next(); + requestedDataTimestamp = currentValue.getKey(); + requestDataContentForTimestamp(requestedDataTimestamp, type); + } else { + dataSlots = 0; + if(type.equals(DataType.HEART_RATE)) { + currentDataType = DataType.SLEEP; + requestDataCount(currentDataType); + } + } + } else if (dataToFetch.isEmpty()) { + dataSlots = 0; + if(type.equals(DataType.HEART_RATE)) { + currentDataType = DataType.SLEEP; + requestDataCount(currentDataType); } - } else { - LOG.warn(" Got unsupported data package type: " + type); } } @@ -1009,6 +1059,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { deviceId, userId, // User id null, // Raw Data ActivityKind.TYPE_UNKNOWN, // rawKind + ActivitySample.NOT_MEASURED, // rawIntensity ActivitySample.NOT_MEASURED, // Steps ActivitySample.NOT_MEASURED, // HR ActivitySample.NOT_MEASURED, // Distance