From 2f8207abf98361578f418124098b965ab87968d4 Mon Sep 17 00:00:00 2001 From: danielegobbetti Date: Sun, 7 Feb 2016 09:27:51 +0100 Subject: [PATCH] Initial support for reading pebble health steps/activity data. --- CHANGELOG.md | 3 + .../gadgetbridge/devices/SampleProvider.java | 1 + .../devices/pebble/HealthSampleProvider.java | 31 ++++++ .../devices/pebble/PebbleCoordinator.java | 2 +- .../devices/pebble/DatalogHandler.java | 24 ++++ .../devices/pebble/DatalogHandlerHealth.java | 104 ++++++++++++++++++ .../devices/pebble/PebbleProtocol.java | 34 ++++-- 7 files changed, 190 insertions(+), 9 deletions(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/HealthSampleProvider.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogHandler.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogHandlerHealth.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e5655724..c5970c9e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ ###Changelog +####Version (next) +* Pebble: Support reading Pebble Health steps/activity data + ####Version 0.7.4 * Refactored the settings activity: User details are now generic instead of miband specific. Old settings are preserved. * Pebble: Fix regression with broken active reconnect since 0.7.0 diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/SampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/SampleProvider.java index 5ce64b901..ad99cfea9 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/SampleProvider.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/SampleProvider.java @@ -5,6 +5,7 @@ public interface SampleProvider { byte PROVIDER_PEBBLE_MORPHEUZ = 1; byte PROVIDER_PEBBLE_GADGETBRIDGE = 2; byte PROVIDER_PEBBLE_MISFIT = 3; + byte PROVIDER_PEBBLE_HEALTH = 4; byte PROVIDER_UNKNOWN = 100; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/HealthSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/HealthSampleProvider.java new file mode 100644 index 000000000..5da0e230c --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/HealthSampleProvider.java @@ -0,0 +1,31 @@ +package nodomain.freeyourgadget.gadgetbridge.devices.pebble; + +import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider; +import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; + +public class HealthSampleProvider implements SampleProvider { + + protected final float movementDivisor = 8000f; + + @Override + public int normalizeType(byte rawType) { + return ActivityKind.TYPE_UNKNOWN; + } + + @Override + public byte toRawActivityKind(int activityKind) { + return ActivityKind.TYPE_UNKNOWN; + } + + + @Override + public float normalizeIntensity(short rawIntensity) { + return rawIntensity / movementDivisor; + } + + + @Override + public byte getID() { + return SampleProvider.PROVIDER_PEBBLE_HEALTH; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleCoordinator.java index 80450f722..917e19e5c 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleCoordinator.java @@ -49,7 +49,7 @@ public class PebbleCoordinator extends AbstractDeviceCoordinator { SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(GBApplication.getContext()); if (sharedPrefs.getBoolean("pebble_force_untested", false)) { //return new PebbleGadgetBridgeSampleProvider(); - return new MisfitSampleProvider(); + return new HealthSampleProvider(); } else { return new MorpheuzSampleProvider(); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogHandler.java new file mode 100644 index 000000000..f4f251a59 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogHandler.java @@ -0,0 +1,24 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble; + +import java.nio.ByteBuffer; + +public class DatalogHandler { + protected final PebbleProtocol mPebbleProtocol; + protected final int mTag; + + DatalogHandler(int tag, PebbleProtocol pebbleProtocol) { + mTag = tag; + mPebbleProtocol = pebbleProtocol; + } + + public int getTag() { + return mTag; + } + + public String getTagInfo() { return null; } + + public boolean handleMessage(ByteBuffer datalogMessage, int length) { + return true;//ack the datalog transmission + } + +} \ No newline at end of file diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogHandlerHealth.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogHandlerHealth.java new file mode 100644 index 000000000..58e371298 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogHandlerHealth.java @@ -0,0 +1,104 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble; + + +import android.database.sqlite.SQLiteDatabase; +import android.widget.Toast; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.ByteBuffer; +import java.util.Date; + +import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; +import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider; +import nodomain.freeyourgadget.gadgetbridge.devices.pebble.HealthSampleProvider; +import nodomain.freeyourgadget.gadgetbridge.impl.GBActivitySample; +import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; +import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; +import nodomain.freeyourgadget.gadgetbridge.util.GB; + +public class DatalogHandlerHealth extends DatalogHandler { + + private final int preambleLength = 10; + private final int packetLength = 99; + + private static final Logger LOG = LoggerFactory.getLogger(DatalogHandlerHealth.class); + + public DatalogHandlerHealth(int tag, PebbleProtocol pebbleProtocol) { + super(tag, pebbleProtocol); + } + + @Override + public String getTagInfo() { + return "(health)"; + } + + @Override + public boolean handleMessage(ByteBuffer datalogMessage, int length) { + LOG.info(GB.hexdump(datalogMessage.array(), preambleLength, length-preambleLength)); + + int unknownPacketPreamble, timestamp; + byte unknownC, recordLength, recordNum; + short unknownA; + int beginOfPacketPosition, beginOfSamplesPosition; + + byte steps, orientation; //possibly + short intensity; // possibly + + if (0 == ((length - preambleLength) % packetLength)) { // one datalog message may contain several packets + for (int packet = 0; packet < ((length - preambleLength) / packetLength); packet++) { + beginOfPacketPosition = preambleLength + packet*packetLength; + datalogMessage.position(beginOfPacketPosition); + unknownPacketPreamble = datalogMessage.getInt(); + unknownA = datalogMessage.getShort(); + timestamp = datalogMessage.getInt(); + unknownC = datalogMessage.get(); + recordLength = datalogMessage.get(); + recordNum = datalogMessage.get(); + + beginOfSamplesPosition = datalogMessage.position(); + DBHandler dbHandler = null; + try { + dbHandler = GBApplication.acquireDB(); + try (SQLiteDatabase db = dbHandler.getWritableDatabase()) { // explicitly keep the db open while looping over the samples + + ActivitySample[] samples = new ActivitySample[recordNum]; + SampleProvider sampleProvider = new HealthSampleProvider(); + + for (int j = 0; j < recordNum; j++) { + datalogMessage.position(beginOfSamplesPosition + j*recordLength); + steps = datalogMessage.get(); + orientation = datalogMessage.get(); + if (j<(recordNum-1)) { + //TODO:apparently last minute data do not contain intensity. I guess we are reading it wrong but this approach is our best bet ATM + intensity = datalogMessage.getShort(); + } else { + intensity = 0; + } + samples[j] = new GBActivitySample( + sampleProvider, + timestamp, + intensity, + (short) (steps & 0xff), + (byte) ActivityKind.TYPE_ACTIVITY); + timestamp += 60; + } + + dbHandler.addGBActivitySamples(samples); + } + } catch (Exception ex) { + LOG.debug(ex.getMessage()); + return false;//NACK, so that we get the data again + }finally { + if (dbHandler != null) { + dbHandler.release(); + } + } + + } + } + return true;//ACK by default + } +} \ No newline at end of file diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleProtocol.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleProtocol.java index 6bd4bf3b1..17bcf5498 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleProtocol.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleProtocol.java @@ -366,6 +366,12 @@ public class PebbleProtocol extends GBDeviceProtocol { } + private static final Map mDatalogHandlers = new HashMap<>(); + + { + mDatalogHandlers.put(81,new DatalogHandlerHealth(81, PebbleProtocol.this)); + } + private final HashMap mDatalogSessions = new HashMap<>(); private static byte[] encodeSimpleMessage(short endpoint, byte command) { @@ -1781,6 +1787,7 @@ public class PebbleProtocol extends GBDeviceProtocol { } private GBDeviceEventSendBytes decodeDatalog(ByteBuffer buf, short length) { + boolean ack = true; byte command = buf.get(); byte id = buf.get(); switch (command) { @@ -1797,13 +1804,19 @@ public class PebbleProtocol extends GBDeviceProtocol { if (datalogSession != null) { String taginfo = ""; if (datalogSession.uuid.equals(UUID_ZERO)) { - if (datalogSession.tag >= 78 && datalogSession.tag <= 80) { - taginfo = "(analytics?)"; - } else if (datalogSession.tag >= 81 && datalogSession.tag <= 83) { - taginfo = "(health?)"; - doHexdump = true; + DatalogHandler datalogHandler = mDatalogHandlers.get(datalogSession.tag); + if (datalogHandler != null) { + taginfo = datalogHandler.getTagInfo(); + ack = datalogHandler.handleMessage(buf, length); } else { - taginfo = "(unknown)"; + if (datalogSession.tag >= 78 && datalogSession.tag <= 80) { + taginfo = "(analytics?)"; + } else if (datalogSession.tag == 83) { + taginfo = "(health?)"; + doHexdump = true; + } else { + taginfo = "(unknown)"; + } } } LOG.info("DATALOG UUID=" + datalogSession.uuid + ", tag=" + datalogSession.tag + taginfo + ", item_size=" + datalogSession.item_size + ", item_type=" + datalogSession.item_type); @@ -1837,9 +1850,14 @@ public class PebbleProtocol extends GBDeviceProtocol { LOG.info("unknown DATALOG command: " + (command & 0xff)); break; } - LOG.info("sending ACK (0x85)"); GBDeviceEventSendBytes sendBytes = new GBDeviceEventSendBytes(); - sendBytes.encodedBytes = encodeDatalog(id, DATALOG_ACK); + if(ack) { + LOG.info("sending ACK (0x85)"); + sendBytes.encodedBytes = encodeDatalog(id, DATALOG_ACK); + } else { + LOG.info("sending NACK (0x86)"); + sendBytes.encodedBytes = encodeDatalog(id, DATALOG_NACK); + } return sendBytes; }