mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-11-27 12:26:48 +01:00
Initial support for reading pebble health steps/activity data.
This commit is contained in:
parent
299b850a67
commit
2f8207abf9
@ -1,5 +1,8 @@
|
|||||||
###Changelog
|
###Changelog
|
||||||
|
|
||||||
|
####Version (next)
|
||||||
|
* Pebble: Support reading Pebble Health steps/activity data
|
||||||
|
|
||||||
####Version 0.7.4
|
####Version 0.7.4
|
||||||
* Refactored the settings activity: User details are now generic instead of miband specific. Old settings are preserved.
|
* 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
|
* Pebble: Fix regression with broken active reconnect since 0.7.0
|
||||||
|
@ -5,6 +5,7 @@ public interface SampleProvider {
|
|||||||
byte PROVIDER_PEBBLE_MORPHEUZ = 1;
|
byte PROVIDER_PEBBLE_MORPHEUZ = 1;
|
||||||
byte PROVIDER_PEBBLE_GADGETBRIDGE = 2;
|
byte PROVIDER_PEBBLE_GADGETBRIDGE = 2;
|
||||||
byte PROVIDER_PEBBLE_MISFIT = 3;
|
byte PROVIDER_PEBBLE_MISFIT = 3;
|
||||||
|
byte PROVIDER_PEBBLE_HEALTH = 4;
|
||||||
|
|
||||||
byte PROVIDER_UNKNOWN = 100;
|
byte PROVIDER_UNKNOWN = 100;
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -49,7 +49,7 @@ public class PebbleCoordinator extends AbstractDeviceCoordinator {
|
|||||||
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(GBApplication.getContext());
|
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(GBApplication.getContext());
|
||||||
if (sharedPrefs.getBoolean("pebble_force_untested", false)) {
|
if (sharedPrefs.getBoolean("pebble_force_untested", false)) {
|
||||||
//return new PebbleGadgetBridgeSampleProvider();
|
//return new PebbleGadgetBridgeSampleProvider();
|
||||||
return new MisfitSampleProvider();
|
return new HealthSampleProvider();
|
||||||
} else {
|
} else {
|
||||||
return new MorpheuzSampleProvider();
|
return new MorpheuzSampleProvider();
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
@ -366,6 +366,12 @@ public class PebbleProtocol extends GBDeviceProtocol {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final Map<Integer, DatalogHandler> mDatalogHandlers = new HashMap<>();
|
||||||
|
|
||||||
|
{
|
||||||
|
mDatalogHandlers.put(81,new DatalogHandlerHealth(81, PebbleProtocol.this));
|
||||||
|
}
|
||||||
|
|
||||||
private final HashMap<Byte, DatalogSession> mDatalogSessions = new HashMap<>();
|
private final HashMap<Byte, DatalogSession> mDatalogSessions = new HashMap<>();
|
||||||
|
|
||||||
private static byte[] encodeSimpleMessage(short endpoint, byte command) {
|
private static byte[] encodeSimpleMessage(short endpoint, byte command) {
|
||||||
@ -1781,6 +1787,7 @@ public class PebbleProtocol extends GBDeviceProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private GBDeviceEventSendBytes decodeDatalog(ByteBuffer buf, short length) {
|
private GBDeviceEventSendBytes decodeDatalog(ByteBuffer buf, short length) {
|
||||||
|
boolean ack = true;
|
||||||
byte command = buf.get();
|
byte command = buf.get();
|
||||||
byte id = buf.get();
|
byte id = buf.get();
|
||||||
switch (command) {
|
switch (command) {
|
||||||
@ -1797,13 +1804,19 @@ public class PebbleProtocol extends GBDeviceProtocol {
|
|||||||
if (datalogSession != null) {
|
if (datalogSession != null) {
|
||||||
String taginfo = "";
|
String taginfo = "";
|
||||||
if (datalogSession.uuid.equals(UUID_ZERO)) {
|
if (datalogSession.uuid.equals(UUID_ZERO)) {
|
||||||
if (datalogSession.tag >= 78 && datalogSession.tag <= 80) {
|
DatalogHandler datalogHandler = mDatalogHandlers.get(datalogSession.tag);
|
||||||
taginfo = "(analytics?)";
|
if (datalogHandler != null) {
|
||||||
} else if (datalogSession.tag >= 81 && datalogSession.tag <= 83) {
|
taginfo = datalogHandler.getTagInfo();
|
||||||
taginfo = "(health?)";
|
ack = datalogHandler.handleMessage(buf, length);
|
||||||
doHexdump = true;
|
|
||||||
} else {
|
} 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);
|
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));
|
LOG.info("unknown DATALOG command: " + (command & 0xff));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
LOG.info("sending ACK (0x85)");
|
|
||||||
GBDeviceEventSendBytes sendBytes = new GBDeviceEventSendBytes();
|
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;
|
return sendBytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user