mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-11-28 12:56:49 +01:00
Refactoring of the Pebble Health steps data receiver.
Added logic to deal with pebble health sleep data. Added database helper to change the type of a range of samples (needed for sleep data). Fixes to the Pebble Health sample provider.
This commit is contained in:
parent
d62946df63
commit
20c4e49fe1
@ -242,4 +242,20 @@ public class ActivityDatabaseHandler extends SQLiteOpenHelper implements DBHandl
|
||||
builder.append(')');
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changeStoredSamplesType(int timestampFrom, int timestampTo, byte kind, SampleProvider provider) {
|
||||
try (SQLiteDatabase db = this.getReadableDatabase()) {
|
||||
String sql = "UPDATE " + TABLE_GBACTIVITYSAMPLES + " SET " + KEY_TYPE +"= ? WHERE "
|
||||
+ KEY_PROVIDER + " = ? AND "
|
||||
+ KEY_TIMESTAMP + " >= ? AND "+ KEY_TIMESTAMP + " < ? ;"; //do not use BETWEEN because the range is inclusive in that case!
|
||||
|
||||
SQLiteStatement statement = db.compileStatement(sql);
|
||||
statement.bindLong(1, kind);
|
||||
statement.bindLong(2, provider.getID());
|
||||
statement.bindLong(3, timestampFrom);
|
||||
statement.bindLong(4, timestampTo);
|
||||
statement.execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -29,4 +29,7 @@ public interface DBHandler {
|
||||
void addGBActivitySamples(ActivitySample[] activitySamples);
|
||||
|
||||
SQLiteDatabase getWritableDatabase();
|
||||
|
||||
void changeStoredSamplesType(int timestampFrom, int timestampTo, byte kind, SampleProvider provider);
|
||||
|
||||
}
|
||||
|
@ -4,17 +4,38 @@ import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
|
||||
public class HealthSampleProvider implements SampleProvider {
|
||||
public static final byte TYPE_DEEP_SLEEP = 5;
|
||||
public static final byte TYPE_LIGHT_SLEEP = 4;
|
||||
public static final byte TYPE_ACTIVITY = -1;
|
||||
|
||||
protected final float movementDivisor = 8000f;
|
||||
|
||||
@Override
|
||||
public int normalizeType(byte rawType) {
|
||||
switch (rawType) {
|
||||
case TYPE_DEEP_SLEEP:
|
||||
return ActivityKind.TYPE_DEEP_SLEEP;
|
||||
case TYPE_LIGHT_SLEEP:
|
||||
return ActivityKind.TYPE_LIGHT_SLEEP;
|
||||
case TYPE_ACTIVITY:
|
||||
default:
|
||||
return ActivityKind.TYPE_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte toRawActivityKind(int activityKind) {
|
||||
return ActivityKind.TYPE_UNKNOWN;
|
||||
switch (activityKind) {
|
||||
case ActivityKind.TYPE_ACTIVITY:
|
||||
return TYPE_ACTIVITY;
|
||||
case ActivityKind.TYPE_DEEP_SLEEP:
|
||||
return TYPE_DEEP_SLEEP;
|
||||
case ActivityKind.TYPE_LIGHT_SLEEP:
|
||||
return TYPE_LIGHT_SLEEP;
|
||||
case ActivityKind.TYPE_UNKNOWN: // fall through
|
||||
default:
|
||||
return TYPE_ACTIVITY;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,96 +0,0 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble;
|
||||
|
||||
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.UUID;
|
||||
|
||||
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 DatalogSessionHealth extends DatalogSession {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DatalogSessionHealth.class);
|
||||
|
||||
public DatalogSessionHealth(byte id, UUID uuid, int tag, byte item_type, short item_size) {
|
||||
super(id, uuid, tag, item_type, item_size);
|
||||
taginfo = "(health)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleMessage(ByteBuffer datalogMessage, int length) {
|
||||
LOG.info(GB.hexdump(datalogMessage.array(), datalogMessage.position(), length));
|
||||
|
||||
int timestamp;
|
||||
byte unknownC, recordLength, recordNum;
|
||||
short unknownA;
|
||||
int beginOfPacketPosition, beginOfSamplesPosition;
|
||||
|
||||
byte steps, orientation; //possibly
|
||||
short intensity; // possibly
|
||||
|
||||
int initialPosition = datalogMessage.position();
|
||||
if (0 == (length % itemSize)) { // one datalog message may contain several packets
|
||||
for (int packet = 0; packet < (length / itemSize); packet++) {
|
||||
beginOfPacketPosition = initialPosition + packet * itemSize;
|
||||
datalogMessage.position(beginOfPacketPosition);
|
||||
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
|
||||
}
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
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.UUID;
|
||||
|
||||
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.model.ActivityKind;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
class DatalogSessionHealthSleep extends DatalogSession {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DatalogSessionHealthSleep.class);
|
||||
|
||||
public DatalogSessionHealthSleep(byte id, UUID uuid, int tag, byte item_type, short item_size) {
|
||||
super(id, uuid, tag, item_type, item_size);
|
||||
taginfo = "(health - sleep)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleMessage(ByteBuffer datalogMessage, int length) {
|
||||
LOG.info("DATALOG " + taginfo + GB.hexdump(datalogMessage.array(), datalogMessage.position(), length));
|
||||
|
||||
int initialPosition = datalogMessage.position();
|
||||
int beginOfRecordPosition;
|
||||
short recordVersion; //probably
|
||||
|
||||
if (0 != (length % itemSize))
|
||||
return false;//malformed message?
|
||||
|
||||
int recordCount = length / itemSize;
|
||||
SleepRecord[] sleepRecords = new SleepRecord[recordCount];
|
||||
|
||||
for (int recordIdx = 0; recordIdx < recordCount; recordIdx++) {
|
||||
beginOfRecordPosition = initialPosition + recordIdx * itemSize;
|
||||
datalogMessage.position(beginOfRecordPosition);//we may not consume all the bytes of a record
|
||||
recordVersion = datalogMessage.getShort();
|
||||
if (recordVersion!=1)
|
||||
return false;//we don't know how to deal with the data TODO: this is not ideal because we will get the same message again and again since we NACK it
|
||||
|
||||
sleepRecords[recordIdx] = new SleepRecord(datalogMessage.getInt(),
|
||||
datalogMessage.getInt(),
|
||||
datalogMessage.getInt(),
|
||||
datalogMessage.getInt());
|
||||
|
||||
}
|
||||
|
||||
store(sleepRecords);
|
||||
return true;//ACK by default
|
||||
}
|
||||
|
||||
private void store(SleepRecord[] sleepRecords) {
|
||||
DBHandler dbHandler = null;
|
||||
SampleProvider sampleProvider = new HealthSampleProvider();
|
||||
GB.toast("We don't know how to store deep sleep from the pebble yet.", Toast.LENGTH_LONG, GB.INFO);
|
||||
try {
|
||||
dbHandler = GBApplication.acquireDB();
|
||||
for (SleepRecord sleepRecord: sleepRecords) {
|
||||
dbHandler.changeStoredSamplesType(sleepRecord.bedTimeStart,sleepRecord.bedTimeEnd, sampleProvider.toRawActivityKind(ActivityKind.TYPE_LIGHT_SLEEP),sampleProvider);
|
||||
}
|
||||
}catch (Exception ex) {
|
||||
LOG.debug(ex.getMessage());
|
||||
} finally {
|
||||
if (dbHandler != null) {
|
||||
dbHandler.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class SleepRecord {
|
||||
int offsetUTC; //probably
|
||||
int bedTimeStart;
|
||||
int bedTimeEnd;
|
||||
int deepSleepSeconds;
|
||||
|
||||
public SleepRecord(int offsetUTC, int bedTimeStart, int bedTimeEnd, int deepSleepSeconds) {
|
||||
this.offsetUTC = offsetUTC;
|
||||
this.bedTimeStart = bedTimeStart;
|
||||
this.bedTimeEnd = bedTimeEnd;
|
||||
this.deepSleepSeconds = deepSleepSeconds;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,115 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble;
|
||||
|
||||
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.UUID;
|
||||
|
||||
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 DatalogSessionHealthSteps extends DatalogSession {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DatalogSessionHealthSteps.class);
|
||||
|
||||
public DatalogSessionHealthSteps(byte id, UUID uuid, int tag, byte item_type, short item_size) {
|
||||
super(id, uuid, tag, item_type, item_size);
|
||||
taginfo = "(health - steps)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleMessage(ByteBuffer datalogMessage, int length) {
|
||||
LOG.info("DATALOG " + taginfo + GB.hexdump(datalogMessage.array(), datalogMessage.position(), length));
|
||||
|
||||
int timestamp;
|
||||
byte recordLength, recordNum;
|
||||
short recordVersion; //probably
|
||||
int beginOfPacketPosition, beginOfRecordPosition;
|
||||
|
||||
int initialPosition = datalogMessage.position();
|
||||
if (0 != (length % itemSize))
|
||||
return false;//malformed message?
|
||||
|
||||
int packetCount = length / itemSize;
|
||||
|
||||
for (int packetIdx = 0; packetIdx < packetCount; packetIdx++) {
|
||||
beginOfPacketPosition = initialPosition + packetIdx * itemSize;
|
||||
datalogMessage.position(beginOfPacketPosition);//we may not consume all the records of a packet
|
||||
|
||||
recordVersion = datalogMessage.getShort();
|
||||
|
||||
if (recordVersion != 5)
|
||||
return false; //we don't know how to deal with the data TODO: this is not ideal because we will get the same message again and again since we NACK it
|
||||
|
||||
timestamp = datalogMessage.getInt();
|
||||
datalogMessage.get(); //unknown, throw away
|
||||
recordLength = datalogMessage.get();
|
||||
recordNum = datalogMessage.get();
|
||||
|
||||
beginOfRecordPosition = datalogMessage.position();
|
||||
StepsRecord[] stepsRecords = new StepsRecord[recordNum];
|
||||
|
||||
for (int recordIdx = 0; recordIdx < recordNum; recordIdx++) {
|
||||
datalogMessage.position(beginOfRecordPosition + recordIdx * recordLength); //we may not consume all the bytes of a record
|
||||
stepsRecords[recordIdx] = new StepsRecord(timestamp, datalogMessage.get(), datalogMessage.get(), datalogMessage.getShort(), datalogMessage.get(), datalogMessage.get());
|
||||
timestamp += 60;
|
||||
}
|
||||
|
||||
store(stepsRecords);
|
||||
}
|
||||
return true;//ACK by default
|
||||
}
|
||||
|
||||
private void store(StepsRecord[] stepsRecords) {
|
||||
|
||||
DBHandler dbHandler = null;
|
||||
SampleProvider sampleProvider = new HealthSampleProvider();
|
||||
|
||||
ActivitySample[] samples = new ActivitySample[stepsRecords.length];
|
||||
for (int j = 0; j < stepsRecords.length; j++) {
|
||||
StepsRecord stepsRecord = stepsRecords[j];
|
||||
samples[j] = new GBActivitySample(
|
||||
sampleProvider,
|
||||
stepsRecord.timestamp,
|
||||
stepsRecord.intensity,
|
||||
(short) (stepsRecord.steps & 0xff),
|
||||
sampleProvider.toRawActivityKind(ActivityKind.TYPE_ACTIVITY));
|
||||
}
|
||||
|
||||
try {
|
||||
dbHandler = GBApplication.acquireDB();
|
||||
dbHandler.addGBActivitySamples(samples);
|
||||
} catch (Exception ex) {
|
||||
LOG.debug(ex.getMessage());
|
||||
} finally {
|
||||
if (dbHandler != null) {
|
||||
dbHandler.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class StepsRecord {
|
||||
int timestamp;
|
||||
byte steps;
|
||||
byte orientation;
|
||||
short intensity;
|
||||
|
||||
public StepsRecord(int timestamp, byte steps, byte orientation, short intensity, byte throwAway1, byte throwAway2) {
|
||||
this.timestamp = timestamp;
|
||||
this.steps = steps;
|
||||
this.orientation = orientation;
|
||||
this.intensity = intensity;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1821,7 +1821,9 @@ public class PebbleProtocol extends GBDeviceProtocol {
|
||||
LOG.info("DATALOG OPENSESSION. id=" + (id & 0xff) + ", App UUID=" + uuid.toString() + ", log_tag=" + log_tag + ", item_type=" + item_type + ", itemSize=" + item_size);
|
||||
if (!mDatalogSessions.containsKey(id)) {
|
||||
if (uuid.equals(UUID_ZERO) && log_tag == 81) {
|
||||
mDatalogSessions.put(id, new DatalogSessionHealth(id, uuid, log_tag, item_type, item_size));
|
||||
mDatalogSessions.put(id, new DatalogSessionHealthSteps(id, uuid, log_tag, item_type, item_size));
|
||||
} else if (uuid.equals(UUID_ZERO) && log_tag == 83) {
|
||||
mDatalogSessions.put(id, new DatalogSessionHealthSleep(id, uuid, log_tag, item_type, item_size));
|
||||
} else {
|
||||
mDatalogSessions.put(id, new DatalogSession(id, uuid, log_tag, item_type, item_size));
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user