1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-11-27 20:36:51 +01:00

HPlus: Basic support for data synchronization

This commit is contained in:
João Paulo Barraca 2017-01-02 00:58:37 +00:00
parent a135f51d31
commit 1fb4ee8a8f
13 changed files with 1093 additions and 506 deletions

View File

@ -60,6 +60,7 @@ public class GBDaoGenerator {
addPebbleHealthActivityKindOverlay(schema, user, device); addPebbleHealthActivityKindOverlay(schema, user, device);
addPebbleMisfitActivitySample(schema, user, device); addPebbleMisfitActivitySample(schema, user, device);
addPebbleMorpheuzActivitySample(schema, user, device); addPebbleMorpheuzActivitySample(schema, user, device);
addHPlusHealthActivityKindOverlay(schema, user, device);
addHPlusHealthActivitySample(schema, user, device); addHPlusHealthActivitySample(schema, user, device);
new DaoGenerator().generateAll(schema, "app/src/main/java"); new DaoGenerator().generateAll(schema, "app/src/main/java");
@ -226,15 +227,31 @@ public class GBDaoGenerator {
Entity activitySample = addEntity(schema, "HPlusHealthActivitySample"); Entity activitySample = addEntity(schema, "HPlusHealthActivitySample");
addCommonActivitySampleProperties("AbstractActivitySample", activitySample, user, device); addCommonActivitySampleProperties("AbstractActivitySample", activitySample, user, device);
activitySample.addByteArrayProperty("rawHPlusHealthData"); activitySample.addByteArrayProperty("rawHPlusHealthData");
activitySample.addIntProperty("rawHPlusCalories").notNull(); activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull().primaryKey();
activitySample.addIntProperty("rawHPlusDistance").notNull();
activitySample.addIntProperty(SAMPLE_RAW_INTENSITY).notNull().codeBeforeGetterAndSetter(OVERRIDE); activitySample.addIntProperty(SAMPLE_RAW_INTENSITY).notNull().codeBeforeGetterAndSetter(OVERRIDE);
activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE); activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE);
activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull().codeBeforeGetterAndSetter(OVERRIDE);
addHeartRateProperties(activitySample); addHeartRateProperties(activitySample);
activitySample.addIntProperty("distance");
activitySample.addIntProperty("calories");
return activitySample; return activitySample;
} }
private static Entity addHPlusHealthActivityKindOverlay(Schema schema, Entity user, Entity device) {
Entity activityOverlay = addEntity(schema, "HPlusHealthActivityOverlay");
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("rawHPlusHealthData");
return activityOverlay;
}
private static void addCommonActivitySampleProperties(String superClass, Entity activitySample, Entity user, Entity device) { private static void addCommonActivitySampleProperties(String superClass, Entity activitySample, Entity user, Entity device) {
activitySample.setSuperclass(superClass); activitySample.setSuperclass(superClass);
activitySample.addImport(MAIN_PACKAGE + ".devices.SampleProvider"); activitySample.addImport(MAIN_PACKAGE + ".devices.SampleProvider");

View File

@ -1,12 +1,11 @@
package nodomain.freeyourgadget.gadgetbridge.devices.hplus; package nodomain.freeyourgadget.gadgetbridge.devices.hplus;
/*
* @author João Paulo Barraca <jpbarraca@gmail.com>
*/
import java.util.UUID; import java.util.UUID;
/**
* Message constants reverse-engineered by João Paulo Barraca, jpbarraca@gmail.com.
*
* @author João Paulo Barraca <jpbarraca@gmail.com>
*/
public final class HPlusConstants { public final class HPlusConstants {
public static final UUID UUID_CHARACTERISTIC_CONTROL = UUID.fromString("14702856-620a-3973-7c78-9cfff0876abd"); public static final UUID UUID_CHARACTERISTIC_CONTROL = UUID.fromString("14702856-620a-3973-7c78-9cfff0876abd");
@ -14,66 +13,86 @@ public final class HPlusConstants {
public static final UUID UUID_SERVICE_HP = UUID.fromString("14701820-620a-3973-7c78-9cfff0876abd"); public static final UUID UUID_SERVICE_HP = UUID.fromString("14701820-620a-3973-7c78-9cfff0876abd");
public static final byte PREF_VALUE_COUNTRY_CN = 1; public static final byte ARG_COUNTRY_CN = 1;
public static final byte PREF_VALUE_COUNTRY_OTHER = 2; public static final byte ARG_COUNTRY_OTHER = 2;
public static final byte PREF_VALUE_CLOCK_24H = 0; public static final byte ARG_TIMEMODE_24H = 0;
public static final byte PREF_VALUE_CLOCK_12H = 1; public static final byte ARG_TIMEMODE_12H = 1;
public static final byte PREF_VALUE_UNIT_METRIC = 0; public static final byte ARG_UNIT_METRIC = 0;
public static final byte PREF_VALUE_UNIT_IMPERIAL = 1; public static final byte ARG_UNIT_IMPERIAL = 1;
public static final byte PREF_VALUE_GENDER_MALE = 0; public static final byte ARG_GENDER_MALE = 0;
public static final byte PREF_VALUE_GENDER_FEMALE = 1; public static final byte ARG_GENDER_FEMALE = 1;
public static final byte PREF_VALUE_HEARTRATE_MEASURE_ON = 11; public static final byte ARG_HEARTRATE_MEASURE_ON = 11;
public static final byte PREF_VALUE_HEARTRATE_MEASURE_OFF = 22; public static final byte ARG_HEARTRATE_MEASURE_OFF = 22;
public static final byte PREF_VALUE_HEARTRATE_ALLDAY_ON = 10; public static final byte ARG_HEARTRATE_ALLDAY_ON = 10;
public static final byte PREF_VALUE_HEARTRATE_ALLDAY_OFF = -1; public static final byte ARG_HEARTRATE_ALLDAY_OFF = -1;
public static final byte INCOMING_CALL_STATE_DISABLED_THRESHOLD = 0x7B; public static final byte INCOMING_CALL_STATE_DISABLED_THRESHOLD = 0x7B;
public static final byte INCOMING_CALL_STATE_ENABLED = (byte) 0xAA; public static final byte INCOMING_CALL_STATE_ENABLED = (byte) 0xAA;
public static final byte[] COMMAND_SET_PREF_START = new byte[]{0x4f, 0x5a}; public static final byte[] CMD_SET_PREF_START = new byte[]{0x4f, 0x5a};
public static final byte[] COMMAND_SET_PREF_START1 = new byte[]{0x4d}; public static final byte[] CMD_SET_PREF_START1 = new byte[]{0x4d};
public static final byte COMMAND_SET_PREF_COUNTRY = 0x22; public static final byte CMD_SET_LANGUAGE = 0x22;
public static final byte COMMAND_SET_PREF_TIMEMODE = 0x47; public static final byte CMD_SET_TIMEMODE = 0x47;
public static final byte COMMAND_SET_PREF_UNIT = 0x48; public static final byte CMD_SET_UNITS = 0x48;
public static final byte COMMAND_SET_PREF_SEX = 0x2d; public static final byte CMD_SET_GENDER = 0x2d;
public static final byte COMMAND_SET_PREF_DATE = 0x08; public static final byte CMD_SET_DATE = 0x08;
public static final byte COMMAND_SET_PREF_TIME = 0x09; public static final byte CMD_SET_TIME = 0x09;
public static final byte COMMAND_SET_PREF_WEEK = 0x2a; public static final byte CMD_SET_WEEK = 0x2a;
public static final byte COMMAND_SET_PREF_SIT = 0x1e; public static final byte CMD_SET_PREF_SIT = 0x1e;
public static final byte COMMAND_SET_PREF_WEIGHT = 0x05; public static final byte CMD_SET_WEIGHT = 0x05;
public static final byte COMMAND_SET_PREF_HEIGHT = 0x04; public static final byte CMD_HEIGHT = 0x04;
public static final byte COMMAND_SET_PREF_AGE = 0x2c; public static final byte CMD_SET_AGE = 0x2c;
public static final byte COMMAND_SET_PREF_GOAL = 0x26; public static final byte CMD_SET_GOAL = 0x26;
public static final byte COMMAND_SET_PREF_SCREENTIME = 0x0b; public static final byte CMD_SET_SCREENTIME = 0x0b;
public static final byte COMMAND_SET_PREF_BLOOD = 0x4e; //?? public static final byte CMD_SET_BLOOD = 0x4e; //??
public static final byte COMMAND_SET_PREF_FINDME = 0x0a;
public static final byte COMMAND_SET_PREF_SAVE = 0x17; public static final byte CMD_SET_FINDME = 0x0a;
public static final byte COMMAND_SET_PREF_END = 0x4f; public static final byte ARG_FINDME_ON = 0x01;
public static final byte COMMAND_SET_DISPLAY_ALERT = 0x23; public static final byte ARG_FINDME_OFF = 0x02;
public static final byte COMMAND_SET_PREF_ALLDAYHR = 53;
public static final byte COMMAND_SET_INCOMING_CALL = 0x41; public static final byte CMD_GET_VERSION = 0x17;
public static final byte COMMAND_SET_CONF_SAVE = 0x17; public static final byte CMD_SET_END = 0x4f;
public static final byte COMMAND_SET_CONF_END = 0x4f; public static final byte CMD_SET_INCOMING_CALL_NUMBER = 0x23;
public static final byte COMMAND_SET_PREFS = 0x50; public static final byte CMD_SET_ALLDAY_HRM = 0x35;
public static final byte COMMAND_SET_SIT_INTERVAL = 0x51; public static final byte CMD_ACTION_INCOMING_CALL = 0x41;
public static final byte CMD_SET_CONF_END = 0x4f;
public static final byte CMD_SET_PREFS = 0x50;
public static final byte CMD_SET_SIT_INTERVAL = 0x51;
public static final byte[] COMMAND_FACTORY_RESET = new byte[] {-74, 90}; public static final byte[] COMMAND_FACTORY_RESET = new byte[] {-74, 90};
//Actions to device //Actions to device
public static final byte COMMAND_ACTION_INCOMING_SOCIAL = 0x31; public static final byte CMD_GET_ACTIVE_DAY = 0x27;
public static final byte COMMAND_ACTION_INCOMING_SMS = 0x40; public static final byte CMD_GET_DAY_DATA = 0x15;
public static final byte COMMAND_ACTION_DISPLAY_TEXT = 0x43; public static final byte CMD_GET_SLEEP = 0x19;
public static final byte[] COMMAND_ACTION_INCOMING_CALL = new byte[] {6, -86}; public static final byte CMD_GET_CURR_DATA = 0x16;
public static final byte COMMAND_ACTION_DISPLAY_TEXT_CENTER = 0x23; public static final byte CMD_GET_DEVICE_ID = 0x24;
public static final byte COMMAND_ACTION_DISPLAY_TEXT_NAME = 0x3F;
public static final byte COMMAND_ACTION_DISPLAY_TEXT_NAME_CN = 0x3E; //Text in GB2312?
public static final byte CMD_ACTION_INCOMING_SOCIAL = 0x31;
//public static final byte COMMAND_ACTION_INCOMING_SMS = 0x40;
public static final byte CMD_ACTION_DISPLAY_TEXT = 0x43;
public static final byte CMD_ACTION_DISPLAY_TEXT_NAME = 0x3F;
public static final byte CMD_ACTION_DISPLAY_TEXT_NAME_CN = 0x3E; //Text in GB2312?
public static final byte[] CMD_ACTION_HELLO = new byte[]{0x01, 0};
public static final byte CMD_SHUTDOWN = 91;
public static final byte ARG_SHUTDOWN_EN = 90;
public static final byte CMD_FACTORY_RESET = -74;
public static final byte ARG_FACTORY_RESET_EN = 90;
public static final byte CMD_SET_INCOMING_MESSAGE = 0x07;
public static final byte CMD_SET_INCOMING_CALL = 0x06;
public static final byte ARG_INCOMING_CALL = (byte) -86;
public static final byte ARG_INCOMING_MESSAGE = (byte) -86;
//Incoming Messages //Incoming Messages
public static final byte DATA_STATS = 0x33; public static final byte DATA_STATS = 0x33;
@ -81,9 +100,14 @@ public final class HPlusConstants {
public static final byte DATA_DAY_SUMMARY = 0x38; public static final byte DATA_DAY_SUMMARY = 0x38;
public static final byte DATA_DAY_SUMMARY_ALT = 0x39; public static final byte DATA_DAY_SUMMARY_ALT = 0x39;
public static final byte DATA_SLEEP = 0x1A; public static final byte DATA_SLEEP = 0x1A;
public static final byte DATA_INCOMING_CALL_STATE = 0x18; public static final byte DATA_VERSION = 0x18;
public static final byte DB_TYPE_DAY_SLOT_SUMMARY = 1;
public static final byte DB_TYPE_DAY_SUMMARY = 2;
public static final byte DB_TYPE_INSTANT_STATS = 3;
public static final byte DB_TYPE_SLEEP_STATS = 4;
public static final String PREF_HPLUS_SCREENTIME = "hplus_screentime"; public static final String PREF_HPLUS_SCREENTIME = "hplus_screentime";
public static final String PREF_HPLUS_ALLDAYHR = "hplus_alldayhr"; public static final String PREF_HPLUS_ALLDAYHR = "hplus_alldayhr";

View File

@ -13,6 +13,7 @@ import android.os.Build;
import android.os.ParcelUuid; import android.os.ParcelUuid;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import de.greenrobot.dao.query.QueryBuilder;
import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.GBException; import nodomain.freeyourgadget.gadgetbridge.GBException;
import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.R;
@ -20,9 +21,9 @@ import nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsActivity;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler; import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.UserInfo;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device; import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.entities.HPlusHealthActivitySampleDao;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate; import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
@ -44,20 +45,15 @@ public class HPlusCoordinator extends AbstractDeviceCoordinator {
@Override @Override
@TargetApi(Build.VERSION_CODES.LOLLIPOP) @TargetApi(Build.VERSION_CODES.LOLLIPOP)
public Collection<? extends ScanFilter> createBLEScanFilters() { public Collection<? extends ScanFilter> createBLEScanFilters() {
ParcelUuid mi2Service = new ParcelUuid(HPlusConstants.UUID_SERVICE_HP); ParcelUuid hpService = new ParcelUuid(HPlusConstants.UUID_SERVICE_HP);
ScanFilter filter = new ScanFilter.Builder().setServiceUuid(mi2Service).build(); ScanFilter filter = new ScanFilter.Builder().setServiceUuid(hpService).build();
return Collections.singletonList(filter); return Collections.singletonList(filter);
} }
@NonNull @NonNull
@Override @Override
public DeviceType getSupportedType(GBDeviceCandidate candidate) { public DeviceType getSupportedType(GBDeviceCandidate candidate) {
if (candidate.supportsService(HPlusConstants.UUID_SERVICE_HP)) {
return DeviceType.HPLUS;
}
String name = candidate.getDevice().getName(); String name = candidate.getDevice().getName();
LOG.debug("Looking for: " + name);
if (name != null && name.startsWith("HPLUS")) { if (name != null && name.startsWith("HPLUS")) {
return DeviceType.HPLUS; return DeviceType.HPLUS;
} }
@ -97,7 +93,7 @@ public class HPlusCoordinator extends AbstractDeviceCoordinator {
@Override @Override
public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) { public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
return new HPlusSampleProvider(device, session); return new HPlusHealthSampleProvider(device, session);
} }
@Override @Override
@ -137,7 +133,9 @@ public class HPlusCoordinator extends AbstractDeviceCoordinator {
@Override @Override
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException { protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
// nothing to delete, yet Long deviceId = device.getId();
QueryBuilder<?> qb = session.getHPlusHealthActivitySampleDao().queryBuilder();
qb.where(HPlusHealthActivitySampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
} }
public static int getFitnessGoal(String address) throws IllegalArgumentException { public static int getFitnessGoal(String address) throws IllegalArgumentException {
@ -181,9 +179,9 @@ public class HPlusCoordinator extends AbstractDeviceCoordinator {
ActivityUser activityUser = new ActivityUser(); ActivityUser activityUser = new ActivityUser();
if (activityUser.getGender() == ActivityUser.GENDER_MALE) if (activityUser.getGender() == ActivityUser.GENDER_MALE)
return HPlusConstants.PREF_VALUE_GENDER_MALE; return HPlusConstants.ARG_GENDER_MALE;
return HPlusConstants.PREF_VALUE_GENDER_FEMALE; return HPlusConstants.ARG_GENDER_FEMALE;
} }
public static int getGoal(String address) { public static int getGoal(String address) {
@ -197,7 +195,7 @@ public class HPlusCoordinator extends AbstractDeviceCoordinator {
} }
public static byte getAllDayHR(String address) { public static byte getAllDayHR(String address) {
return (byte) (prefs.getInt(HPlusConstants.PREF_HPLUS_ALLDAYHR + "_" + address, 10) & 0xFF); return (byte) (prefs.getInt(HPlusConstants.PREF_HPLUS_ALLDAYHR + "_" + address, HPlusConstants.ARG_HEARTRATE_ALLDAY_ON) & 0xFF);
} }
public static byte getSocial(String address) { public static byte getSocial(String address) {

View File

@ -0,0 +1,152 @@
package nodomain.freeyourgadget.gadgetbridge.devices.hplus;
/*
* @author João Paulo Barraca &lt;jpbarraca@gmail.com&gt;
*/
import android.support.annotation.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import de.greenrobot.dao.AbstractDao;
import de.greenrobot.dao.Property;
import de.greenrobot.dao.query.QueryBuilder;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.entities.HPlusHealthActivityOverlay;
import nodomain.freeyourgadget.gadgetbridge.entities.HPlusHealthActivityOverlayDao;
import nodomain.freeyourgadget.gadgetbridge.entities.HPlusHealthActivitySample;
import nodomain.freeyourgadget.gadgetbridge.entities.HPlusHealthActivitySampleDao;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
public class HPlusHealthSampleProvider extends AbstractSampleProvider<HPlusHealthActivitySample> {
private static final Logger LOG = LoggerFactory.getLogger(HPlusHealthSampleProvider.class);
private GBDevice mDevice;
private DaoSession mSession;
public HPlusHealthSampleProvider(GBDevice device, DaoSession session) {
super(device, session);
mSession = session;
mDevice = device;
}
public int getID() {
return SampleProvider.PROVIDER_HPLUS;
}
public int normalizeType(int rawType) {
return rawType;
}
public int toRawActivityKind(int activityKind) {
return activityKind;
}
@NonNull
@Override
protected Property getTimestampSampleProperty() {
return HPlusHealthActivitySampleDao.Properties.Timestamp;
}
@Override
public HPlusHealthActivitySample createActivitySample() {
return new HPlusHealthActivitySample();
}
@Override
protected Property getRawKindSampleProperty() {
return HPlusHealthActivitySampleDao.Properties.RawKind;
}
@Override
public float normalizeIntensity(int rawIntensity) {
return rawIntensity; //TODO: Calculate actual value
}
@NonNull
@Override
protected Property getDeviceIdentifierSampleProperty() {
return HPlusHealthActivitySampleDao.Properties.DeviceId;
}
@Override
public AbstractDao<HPlusHealthActivitySample, ?> getSampleDao() {
return getSession().getHPlusHealthActivitySampleDao();
}
@Override
public List<HPlusHealthActivitySample> getAllActivitySamples(int timestamp_from, int timestamp_to) {
List<HPlusHealthActivitySample> samples = super.getGBActivitySamples(timestamp_from, timestamp_to, ActivityKind.TYPE_ALL);
Device dbDevice = DBHelper.findDevice(getDevice(), getSession());
if (dbDevice == null) {
return Collections.emptyList();
}
QueryBuilder<HPlusHealthActivityOverlay> qb = getSession().getHPlusHealthActivityOverlayDao().queryBuilder();
qb.where(HPlusHealthActivityOverlayDao.Properties.DeviceId.eq(dbDevice.getId()), HPlusHealthActivityOverlayDao.Properties.TimestampFrom.ge(timestamp_from - 24 * 60 * 60))
.where(HPlusHealthActivityOverlayDao.Properties.TimestampTo.le(timestamp_to));
List<HPlusHealthActivityOverlay> overlayRecords = qb.build().list();
for (HPlusHealthActivityOverlay overlay : overlayRecords) {
insertVirtualItem(samples, overlay.getTimestampFrom(), overlay.getDeviceId(), overlay.getUserId());
insertVirtualItem(samples, overlay.getTimestampTo() - 1, overlay.getDeviceId(), overlay.getUserId());
for (HPlusHealthActivitySample sample : samples) {
if (overlay.getTimestampFrom() <= sample.getTimestamp() && sample.getTimestamp() < overlay.getTimestampTo()) {
sample.setRawKind(overlay.getRawKind());
}
}
}
detachFromSession();
LOG.debug("Returning " + samples.size() + " samples processed by " + overlayRecords.size() + " overlays");
Collections.sort(samples, new Comparator<HPlusHealthActivitySample>() {
public int compare(HPlusHealthActivitySample one, HPlusHealthActivitySample other) {
return one.getTimestamp() - other.getTimestamp();
}
});
return samples;
}
private List<HPlusHealthActivitySample> insertVirtualItem(List<HPlusHealthActivitySample> samples, int timestamp, long deviceId, long userId){
HPlusHealthActivitySample sample = new HPlusHealthActivitySample(
timestamp, // ts
deviceId,
userId, // User id
null, // Raw Data
ActivityKind.TYPE_UNKNOWN,
ActivitySample.NOT_MEASURED, // Intensity
ActivitySample.NOT_MEASURED, // Steps
ActivitySample.NOT_MEASURED, // HR
ActivitySample.NOT_MEASURED, // Distance
ActivitySample.NOT_MEASURED // Calories
);
sample.setProvider(this);
samples.add(sample);
return samples;
}
}

View File

@ -1,82 +0,0 @@
package nodomain.freeyourgadget.gadgetbridge.devices.hplus;
/*
* @author João Paulo Barraca &lt;jpbarraca@gmail.com&gt;
*/
import android.content.Context;
import android.support.annotation.NonNull;
import java.util.Collections;
import java.util.List;
import de.greenrobot.dao.AbstractDao;
import de.greenrobot.dao.query.QueryBuilder;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.entities.HPlusHealthActivitySample;
import nodomain.freeyourgadget.gadgetbridge.entities.HPlusHealthActivitySampleDao;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
public class HPlusSampleProvider extends AbstractSampleProvider<HPlusHealthActivitySample> {
private GBDevice mDevice;
private DaoSession mSession;
public HPlusSampleProvider(GBDevice device, DaoSession session) {
super(device, session);
mSession = session;
mDevice = device;;
}
public int getID() {
return SampleProvider.PROVIDER_HPLUS;
}
public int normalizeType(int rawType) {
return rawType;
}
public int toRawActivityKind(int activityKind) {
return activityKind;
}
@NonNull
@Override
protected de.greenrobot.dao.Property getTimestampSampleProperty() {
return HPlusHealthActivitySampleDao.Properties.Timestamp;
}
@Override
public HPlusHealthActivitySample createActivitySample() {
return new HPlusHealthActivitySample();
}
@Override
protected de.greenrobot.dao.Property getRawKindSampleProperty() {
return HPlusHealthActivitySampleDao.Properties.RawKind;
}
@Override
public float normalizeIntensity(int rawIntensity) {
return rawIntensity; //TODO: Calculate actual value
}
@NonNull
@Override
protected de.greenrobot.dao.Property getDeviceIdentifierSampleProperty() {
return HPlusHealthActivitySampleDao.Properties.DeviceId;
}
@Override
public AbstractDao<HPlusHealthActivitySample, ?> getSampleDao() {
return getSession().getHPlusHealthActivitySampleDao();
}
}

View File

@ -0,0 +1,38 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.hplus;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
/**
* Created by jpbarraca on 30/12/2016.
*/
public class HPlusDataRecord {
public final static int TYPE_SLEEP = 1;
public int activityKind = ActivityKind.TYPE_UNKNOWN;
public int timestamp;
public byte[] rawData;
public HPlusDataRecord(byte[] data){
rawData = data;
}
public byte[] getRawData() {
return rawData;
}
public class RecordInterval {
public int timestampFrom;
public int timestampTo;
public int activityKind;
RecordInterval(int timestampFrom, int timestampTo, int activityKind) {
this.timestampFrom = timestampFrom;
this.timestampTo = timestampTo;
this.activityKind = activityKind;
}
}
}

View File

@ -0,0 +1,40 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.hplus;
/*
* @author João Paulo Barraca &lt;jpbarraca@gmail.com&gt;
*/
import java.util.Calendar;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
public class HPlusDataRecordDay extends HPlusDataRecord {
int slot;
int steps;
int secondsInactive;
int heartRate;
public HPlusDataRecordDay(byte[] data) {
super(data);
int a = (data[4] & 0xFF) * 256 + (data[5] & 0xFF);
if (a >= 144) {
throw new IllegalArgumentException("Invalid Slot Number");
}
slot = a; // 10 minute slots as an offset from 0:00 AM
heartRate = data[1] & 0xFF; //Average Heart Rate ?
if(heartRate == 255)
heartRate = ActivityKind.TYPE_NOT_MEASURED;
steps = (data[2] & 0xFF) * 256 + data[3] & 0xFF; // Steps in this period
//?? data[6];
secondsInactive = data[7] & 0xFF; // ?
int now = (int) (Calendar.getInstance().getTimeInMillis() / (3600 * 24 * 1000L));
timestamp = now * 3600 * 24 + (slot / 6 * 3600 + slot % 6 * 10);
}
}

View File

@ -0,0 +1,69 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.hplus;
/*
* @author João Paulo Barraca &lt;jpbarraca@gmail.com&gt;
*/
import java.util.Calendar;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
public class HPlusDataRecordRealtime extends HPlusDataRecord {
int distance;
int calories;
int heartRate;
byte battery;
int activeTime;
public HPlusDataRecordRealtime(byte[] data) {
super(data);
if (data.length < 15) {
throw new IllegalArgumentException("Invalid data packet");
}
timestamp = (int) (Calendar.getInstance().getTimeInMillis() / 1000);
distance = 10 * ((data[4] & 0xFF) * 256 + (data[3] & 0xFF)); // meters
int x = (data[6] & 0xFF) * 256 + data[5] & 0xFF;
int y = (data[8] & 0xFF) * 256 + data[7] & 0xFF;
battery = data[9];
calories = x + y; // KCal
heartRate = data[11] & 0xFF; // BPM
activeTime = (data[14] & 0xFF * 256) + (data[13] & 0xFF);
}
public void computeActivity(HPlusDataRecordRealtime prev){
if(prev == null)
return;
int deltaDistance = distance - prev.distance;
if(deltaDistance <= 0)
return;
int deltaTime = timestamp - prev.timestamp;
if(deltaTime <= 0)
return;
double speed = deltaDistance / deltaTime;
if(speed >= 1.6) // ~6 KM/h
activityKind = ActivityKind.TYPE_ACTIVITY;
}
public boolean same(HPlusDataRecordRealtime other){
if(other == null)
return false;
return distance == other.distance && calories == other.calories && heartRate == other.heartRate && battery == other.battery;
}
}

View File

@ -0,0 +1,79 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.hplus;
/*
* @author João Paulo Barraca &lt;jpbarraca@gmail.com&gt;
*/
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
public class HPlusDataRecordSleep extends HPlusDataRecord {
private static final Logger LOG = LoggerFactory.getLogger(HPlusDataRecordSleep.class);
int type = TYPE_SLEEP;
int bedTimeStart;
int bedTimeEnd;
int deepSleepMinutes;
int lightSleepMinutes;
int enterSleepMinutes;
int spindleMinutes;
int remSleepMinutes;
int wakeupMinutes;
int wakeupCount;
public HPlusDataRecordSleep(byte[] data) {
super(data);
int year = (data[2] & 0xFF) * 256 + (data[1] & 0xFF);
int month = data[3] & 0xFF;
int day = data[4] & 0xFF;
if (year < 2000) //Attempt to recover from bug from device.
year += 1900;
if (year < 2000 || month > 12 || day <= 0 || day > 31) {
throw new IllegalArgumentException("Invalid record date: " + year + "-" + month + "-" + day);
}
enterSleepMinutes = ((data[6] & 0xFF) * 256 + (data[5] & 0xFF));
spindleMinutes = ((data[8] & 0xFF) * 256 + (data[7] & 0xFF));
deepSleepMinutes = ((data[10] & 0xFF) * 256 + (data[9] & 0xFF));
remSleepMinutes = ((data[12] & 0xFF) * 256 + (data[11] & 0xFF));
wakeupMinutes = ((data[14] & 0xFF) * 256 + (data[13] & 0xFF));
wakeupCount = ((data[16] & 0xFF) * 256 + (data[15] & 0xFF));
int hour = data[17] & 0xFF;
int minute = data[18] & 0xFF;
Calendar sleepStart = Calendar.getInstance();
sleepStart.set(Calendar.YEAR, year);
sleepStart.set(Calendar.MONTH, month - 1);
sleepStart.set(Calendar.DAY_OF_MONTH, day);
sleepStart.set(Calendar.HOUR, hour);
sleepStart.set(Calendar.MINUTE, minute);
sleepStart.set(Calendar.SECOND, 0);
sleepStart.set(Calendar.MILLISECOND, 0);
bedTimeStart = (int) (sleepStart.getTimeInMillis() / 1000);
bedTimeEnd = (enterSleepMinutes + spindleMinutes + deepSleepMinutes + remSleepMinutes + wakeupMinutes) * 60 + bedTimeStart;
lightSleepMinutes = enterSleepMinutes + spindleMinutes + remSleepMinutes;
timestamp = bedTimeStart;
}
public List<RecordInterval> getIntervals() {
List<RecordInterval> intervals = new ArrayList<RecordInterval>();
int ts = bedTimeStart + lightSleepMinutes * 60;
intervals.add(new RecordInterval(bedTimeStart, ts, ActivityKind.TYPE_LIGHT_SLEEP));
intervals.add(new RecordInterval(ts, bedTimeEnd, ActivityKind.TYPE_DEEP_SLEEP));
return intervals;
}
}

View File

@ -0,0 +1,59 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.hplus;
/*
* @author João Paulo Barraca &lt;jpbarraca@gmail.com&gt;
*/
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Calendar;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
public class HPlusDataRecordSteps extends HPlusDataRecord{
private static final Logger LOG = LoggerFactory.getLogger(HPlusDataRecordSteps.class);
int steps;
int distance;
HPlusDataRecordSteps(byte[] data) {
super(data);
int year = (data[10] & 0xFF) * 256 + (data[9] & 0xFF);
int month = data[11] & 0xFF;
int day = data[12] & 0xFF;
if (year < 2000 || month > 12 || day > 31) {
throw new IllegalArgumentException("Invalid record date "+year+"-"+month+"-"+day);
}
steps = (data[2] & 0xFF) * 256 + (data[1] & 0xFF);
distance = (data[4] & 0xFF) * 256 + (data[3] & 0xFF);
/*
unknown fields
short s12 = (short)(data[5] + data[6] * 256);
short s13 = (short)(data[7] + data[8] * 256);
short s16 = (short)(data[13]) + data[14] * 256);
short s17 = data[15];
short s18 = data[16];
*/
Calendar date = Calendar.getInstance();
date.set(Calendar.YEAR, year);
date.set(Calendar.MONTH, month - 1);
date.set(Calendar.DAY_OF_MONTH, day);
date.set(Calendar.HOUR, 23);
date.set(Calendar.MINUTE, 59);
date.set(Calendar.SECOND, 59);
date.set(Calendar.MILLISECOND, 999);
timestamp = (int) (date.getTimeInMillis() / 1000);
}
public int getType(int ts){
return ActivityKind.TYPE_UNKNOWN;
}
}

View File

@ -0,0 +1,453 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.hplus;
/*
* @author João Paulo Barraca &lt;jpbarraca@gmail.com&gt;
*/
import android.content.Context;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.GBException;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.hplus.HPlusConstants;
import nodomain.freeyourgadget.gadgetbridge.devices.hplus.HPlusHealthSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.entities.HPlusHealthActivityOverlay;
import nodomain.freeyourgadget.gadgetbridge.entities.HPlusHealthActivityOverlayDao;
import nodomain.freeyourgadget.gadgetbridge.entities.HPlusHealthActivitySample;
import nodomain.freeyourgadget.gadgetbridge.entities.User;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceIoThread;
public class HPlusHandlerThread extends GBDeviceIoThread {
private int SYNC_PERIOD = 60 * 10;
private int SYNC_RETRY_PERIOD = 6;
private int SLEEP_SYNC_PERIOD = 12 * 60 * 60;
private int SLEEP_RETRY_PERIOD = 30;
private int HELLO_INTERVAL = 30;
private static final Logger LOG = LoggerFactory.getLogger(HPlusHandlerThread.class);
private boolean mQuit = false;
private HPlusSupport mHPlusSupport;
private int mLastSlotReceived = 0;
private int mLastSlotRequested = 0;
private Calendar mLastSleepDayReceived = Calendar.getInstance();
private Calendar mHelloTime = Calendar.getInstance();
private Calendar mGetDaySlotsTime = Calendar.getInstance();
private Calendar mGetSleepTime = Calendar.getInstance();
private Object waitObject = new Object();
private HPlusDataRecordRealtime prevRealTimeRecord = null;
public HPlusHandlerThread(GBDevice gbDevice, Context context, HPlusSupport hplusSupport) {
super(gbDevice, context);
mQuit = false;
mHPlusSupport = hplusSupport;
mLastSleepDayReceived.setTimeInMillis(0);
mGetSleepTime.setTimeInMillis(0);
mGetDaySlotsTime.setTimeInMillis(0);
}
@Override
public void run() {
mQuit = false;
sync();
boolean starting = true;
long waitTime = 0;
while (!mQuit) {
//LOG.debug("Waiting " + (waitTime));
if (waitTime > 0) {
synchronized (waitObject) {
try {
waitObject.wait(waitTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
if (mQuit) {
break;
}
Calendar now = Calendar.getInstance();
if (now.compareTo(mHelloTime) > 0) {
sendHello();
}
if (now.compareTo(mGetDaySlotsTime) > 0) {
requestNextDaySlots();
}
if (now.compareTo(mGetSleepTime) > 0) {
requestNextSleepData();
}
now = Calendar.getInstance();
waitTime = Math.min(Math.min(mGetDaySlotsTime.getTimeInMillis(), mGetSleepTime.getTimeInMillis()), mHelloTime.getTimeInMillis()) - now.getTimeInMillis();
}
}
@Override
public void quit() {
mQuit = true;
synchronized (waitObject) {
waitObject.notify();
}
}
public void sync() {
mGetSleepTime.setTimeInMillis(0);
mGetDaySlotsTime.setTimeInMillis(0);
TransactionBuilder builder = new TransactionBuilder("startSyncDayStats");
builder.write(mHPlusSupport.ctrlCharacteristic, new byte[]{HPlusConstants.CMD_GET_SLEEP});
builder.write(mHPlusSupport.ctrlCharacteristic, new byte[]{HPlusConstants.CMD_GET_DAY_DATA});
builder.write(mHPlusSupport.ctrlCharacteristic, new byte[]{HPlusConstants.CMD_GET_ACTIVE_DAY});
builder.write(mHPlusSupport.ctrlCharacteristic, new byte[]{HPlusConstants.CMD_GET_DEVICE_ID});
builder.write(mHPlusSupport.ctrlCharacteristic, new byte[]{HPlusConstants.CMD_GET_VERSION});
builder.write(mHPlusSupport.ctrlCharacteristic, new byte[]{HPlusConstants.CMD_GET_CURR_DATA});
builder.write(mHPlusSupport.ctrlCharacteristic, new byte[]{HPlusConstants.CMD_SET_ALLDAY_HRM, HPlusConstants.ARG_HEARTRATE_ALLDAY_ON});
builder.queue(mHPlusSupport.getQueue());
synchronized (waitObject) {
waitObject.notify();
}
}
public void sendHello() {
mHelloTime = Calendar.getInstance();
mHelloTime.add(Calendar.SECOND, HELLO_INTERVAL);
TransactionBuilder builder = new TransactionBuilder("hello");
builder.write(mHPlusSupport.ctrlCharacteristic, HPlusConstants.CMD_ACTION_HELLO);
builder.queue(mHPlusSupport.getQueue());
}
public void processIncomingDaySlotData(byte[] data) {
HPlusDataRecordDay record;
try{
record = new HPlusDataRecordDay(data);
} catch(IllegalArgumentException e){
LOG.debug((e.getMessage()));
return;
}
if ((record.slot == 0 && mLastSlotReceived == 0) || (record.slot == mLastSlotReceived + 1)) {
mLastSlotReceived = record.slot;
try (DBHandler dbHandler = GBApplication.acquireDB()) {
HPlusHealthSampleProvider provider = new HPlusHealthSampleProvider(getDevice(), dbHandler.getDaoSession());
Long userId = DBHelper.getUser(dbHandler.getDaoSession()).getId();
Long deviceId = DBHelper.getDevice(getDevice(), dbHandler.getDaoSession()).getId();
HPlusHealthActivitySample sample = new HPlusHealthActivitySample(
record.timestamp, // ts
deviceId, userId, // User id
record.getRawData(), // Raw Data
ActivityKind.TYPE_UNKNOWN,
ActivitySample.NOT_MEASURED, // Intensity
record.steps, // Steps
record.heartRate, // HR
ActivitySample.NOT_MEASURED, // Distance
ActivitySample.NOT_MEASURED // Calories
);
sample.setProvider(provider);
provider.addGBActivitySample(sample);
} catch (GBException ex) {
LOG.debug((ex.getMessage()));
} catch (Exception ex) {
LOG.debug(ex.getMessage());
}
if (record.slot >= mLastSlotRequested) {
synchronized (waitObject) {
mGetDaySlotsTime.setTimeInMillis(0);
waitObject.notify();
}
}
}
}
private void requestNextDaySlots() {
LOG.debug("Request Next Slot: Got: " + mLastSlotReceived + " Request: " + mLastSlotRequested);
//Sync Day Stats
byte hour = (byte) ((mLastSlotReceived) / 6);
byte nextHour = (byte) (hour + 1);
byte nextMinute = 0;
if (nextHour == (byte) Calendar.getInstance().get(Calendar.HOUR_OF_DAY)) {
nextMinute = (byte) Calendar.getInstance().get(Calendar.MINUTE);
}
byte minute = (byte) ((mLastSlotReceived % 6) * 10);
mLastSlotRequested = (nextHour) * 6 + Math.round(nextMinute / 10);
if (nextHour >= 24 && nextMinute > 0) { // 24 * 6
LOG.debug("Reached End of the Day");
mLastSlotRequested = 0;
mLastSlotReceived = 0;
mGetDaySlotsTime = Calendar.getInstance();
mGetDaySlotsTime.add(Calendar.SECOND, SYNC_PERIOD);
return;
}
if (nextHour > Calendar.getInstance().get(Calendar.HOUR_OF_DAY)) {
LOG.debug("Day data is up to date");
mGetDaySlotsTime = Calendar.getInstance();
mGetDaySlotsTime.add(Calendar.SECOND, SYNC_PERIOD);
return;
}
LOG.debug("Making new Request From " + hour + ":" + minute + " to " + nextHour + ":" + nextMinute);
byte[] msg = new byte[]{39, hour, minute, nextHour, nextMinute}; //Request the entire day
TransactionBuilder builder = new TransactionBuilder("getNextDaySlot");
builder.write(mHPlusSupport.ctrlCharacteristic, msg);
builder.queue(mHPlusSupport.getQueue());
mGetDaySlotsTime = Calendar.getInstance();
mGetDaySlotsTime.add(Calendar.SECOND, SYNC_RETRY_PERIOD);
}
public void processIncomingSleepData(byte[] data){
LOG.debug("Processing Sleep Data");
HPlusDataRecordSleep record;
try{
record = new HPlusDataRecordSleep(data);
} catch(IllegalArgumentException e){
LOG.debug((e.getMessage()));
return;
}
mLastSleepDayReceived.setTimeInMillis(record.bedTimeStart * 1000L);
try (DBHandler dbHandler = GBApplication.acquireDB()) {
DaoSession session = dbHandler.getDaoSession();
Long userId = DBHelper.getUser(session).getId();
Long deviceId = DBHelper.getDevice(getDevice(), session).getId();
HPlusHealthActivityOverlayDao overlayDao = session.getHPlusHealthActivityOverlayDao();
HPlusHealthSampleProvider provider = new HPlusHealthSampleProvider(getDevice(), dbHandler.getDaoSession());
//Insert the Overlays
List<HPlusHealthActivityOverlay> overlayList = new ArrayList<>();
List<HPlusDataRecord.RecordInterval> intervals = record.getIntervals();
for(HPlusDataRecord.RecordInterval interval : intervals){
overlayList.add(new HPlusHealthActivityOverlay(interval.timestampFrom, interval.timestampTo, interval.activityKind, deviceId, userId, null));
}
overlayDao.insertOrReplaceInTx(overlayList);
//Store the data
HPlusHealthActivitySample sample = new HPlusHealthActivitySample(
record.timestamp, // ts
deviceId, userId, // User id
record.getRawData(), // Raw Data
record.activityKind,
ActivitySample.NOT_MEASURED, // Intensity
ActivitySample.NOT_MEASURED, // Steps
ActivitySample.NOT_MEASURED, // HR
ActivitySample.NOT_MEASURED, // Distance
ActivitySample.NOT_MEASURED // Calories
);
sample.setProvider(provider);
provider.addGBActivitySample(sample);
} catch (Exception ex) {
LOG.debug(ex.getMessage());
}
mGetSleepTime = Calendar.getInstance();
mGetSleepTime.add(Calendar.SECOND, SLEEP_SYNC_PERIOD);
}
private void requestNextSleepData() {
LOG.debug("Request New Sleep Data");
mGetSleepTime = Calendar.getInstance();
mGetSleepTime.add(Calendar.SECOND, SLEEP_RETRY_PERIOD);
TransactionBuilder builder = new TransactionBuilder("requestSleepStats");
builder.write(mHPlusSupport.ctrlCharacteristic, new byte[]{HPlusConstants.CMD_GET_SLEEP});
builder.queue(mHPlusSupport.getQueue());
}
public void processRealtimeStats(byte[] data) {
LOG.debug("Processing Real time Stats");
HPlusDataRecordRealtime record;
try{
record = new HPlusDataRecordRealtime(data);
} catch(IllegalArgumentException e){
LOG.debug((e.getMessage()));
return;
}
if(record.same(prevRealTimeRecord))
return;
prevRealTimeRecord = record;
getDevice().setBatteryLevel(record.battery);
getDevice().sendDeviceUpdateIntent(getContext());
//Skip when measuring
if(record.heartRate == 255) {
getDevice().setFirmwareVersion2("---");
getDevice().sendDeviceUpdateIntent(getContext());
return;
}
getDevice().setFirmwareVersion2(""+record.heartRate);
getDevice().sendDeviceUpdateIntent(getContext());
try (DBHandler dbHandler = GBApplication.acquireDB()) {
DaoSession session = dbHandler.getDaoSession();
HPlusHealthSampleProvider provider = new HPlusHealthSampleProvider(getDevice(), dbHandler.getDaoSession());
HPlusHealthActivityOverlayDao overlayDao = session.getHPlusHealthActivityOverlayDao();
Long userId = DBHelper.getUser(dbHandler.getDaoSession()).getId();
Long deviceId = DBHelper.getDevice(getDevice(), dbHandler.getDaoSession()).getId();
HPlusHealthActivitySample sample = new HPlusHealthActivitySample(
record.timestamp, // ts
deviceId, userId, // User id
record.getRawData(), // Raw Data
record.activityKind,
ActivitySample.NOT_MEASURED, // Intensity
ActivitySample.NOT_MEASURED, // Steps
record.heartRate, // HR
record.distance, // Distance
record.calories // Calories
);
sample.setProvider(provider);
provider.addGBActivitySample(sample);
if(record.activeTime > 0){
//TODO: Register ACTIVITY Time
//Insert the Overlays
//List<HPlusHealthActivityOverlay> overlayList = new ArrayList<>();
//overlayList.add(new HPlusHealthActivityOverlay(record.timestamp - record.activeTime * 60, record.timestamp, ActivityKind.TYPE_ACTIVITY, deviceId, userId, null));
//overlayDao.insertOrReplaceInTx(overlayList);
}
} catch (GBException ex) {
LOG.debug((ex.getMessage()));
} catch (Exception ex) {
LOG.debug(ex.getMessage());
}
}
public void processStepStats(byte[] data) {
LOG.debug("Processing Step Stats");
HPlusDataRecordSteps record;
try{
record = new HPlusDataRecordSteps(data);
} catch(IllegalArgumentException e){
LOG.debug((e.getMessage()));
return;
}
try (DBHandler dbHandler = GBApplication.acquireDB()) {
HPlusHealthSampleProvider provider = new HPlusHealthSampleProvider(getDevice(), dbHandler.getDaoSession());
Long userId = DBHelper.getUser(dbHandler.getDaoSession()).getId();
Long deviceId = DBHelper.getDevice(getDevice(), dbHandler.getDaoSession()).getId();
HPlusHealthActivitySample sample = new HPlusHealthActivitySample(
record.timestamp, // ts
deviceId, userId, // User id
record.getRawData(), // Raw Data
ActivityKind.TYPE_UNKNOWN,
ActivitySample.NOT_MEASURED, // Intensity
record.steps, // Steps
ActivitySample.NOT_MEASURED, // HR
record.distance, // Distance
ActivitySample.NOT_MEASURED // Calories
);
sample.setProvider(provider);
provider.addGBActivitySample(sample);
} catch (GBException ex) {
LOG.debug((ex.getMessage()));
} catch (Exception ex) {
LOG.debug(ex.getMessage());
}
}
public boolean processVersion(byte[] data) {
LOG.debug("Process Version");
int major = data[2] & 0xFF;
int minor = data[1] & 0xFF;
getDevice().setFirmwareVersion(major + "." + minor);
getDevice().sendDeviceUpdateIntent(getContext());
return true;
}
public static HPlusHealthActivitySample createActivitySample(Device device, User user, int timestampInSeconds, SampleProvider provider) {
HPlusHealthActivitySample sample = new HPlusHealthActivitySample();
sample.setDevice(device);
sample.setUser(user);
sample.setTimestamp(timestampInSeconds);
sample.setProvider(provider);
return sample;
}
}

View File

@ -1,86 +0,0 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.hplus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Calendar;
public class HPlusSleepRecord {
private long bedTimeStart;
private long bedTimeEnd;
private int deepSleepSeconds;
private int spindleSeconds;
private int remSleepSeconds;
private int wakeupTime;
private int wakeupCount;
private int enterSleepSeconds;
private byte[] rawData;
HPlusSleepRecord(byte[] data) {
rawData = data;
int year = data[2] * 256 + data[1];
int month = data[3];
int day = data[4];
enterSleepSeconds = data[6] * 256 + data[5];
spindleSeconds = data[8] * 256 + data[7];
deepSleepSeconds = data[10] * 256 + data[9];
remSleepSeconds = data[12] * 256 + data[11];
wakeupTime = data[14] * 256 + data[13];
wakeupCount = data[16] * 256 + data[15];
int hour = data[17];
int minute = data[18];
Calendar c = Calendar.getInstance();
c.set(Calendar.YEAR, year);
c.set(Calendar.MONTH, month);
c.set(Calendar.DAY_OF_MONTH, day);
c.set(Calendar.HOUR, hour);
c.set(Calendar.MINUTE, minute);
c.set(Calendar.SECOND, 0);
c.set(Calendar.MILLISECOND, 0);
bedTimeStart = (c.getTimeInMillis() / 1000L);
bedTimeEnd = bedTimeStart + enterSleepSeconds + spindleSeconds + deepSleepSeconds + remSleepSeconds + wakeupTime;
}
byte[] getRawData() {
return rawData;
}
public long getBedTimeStart() {
return bedTimeStart;
}
public long getBedTimeEnd() {
return bedTimeEnd;
}
public int getDeepSleepSeconds() {
return deepSleepSeconds;
}
public int getSpindleSeconds() {
return spindleSeconds;
}
public int getRemSleepSeconds() {
return remSleepSeconds;
}
public int getWakeupTime() {
return wakeupTime;
}
public int getWakeupCount() {
return wakeupCount;
}
public int getEnterSleepSeconds() {
return enterSleepSeconds;
}
}

View File

@ -23,22 +23,10 @@ import java.util.ArrayList;
import java.util.Calendar; import java.util.Calendar;
import java.util.UUID; import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.GBException;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.hplus.HPlusConstants; import nodomain.freeyourgadget.gadgetbridge.devices.hplus.HPlusConstants;
import nodomain.freeyourgadget.gadgetbridge.devices.hplus.HPlusCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.hplus.HPlusCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.hplus.HPlusSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.entities.HPlusHealthActivitySample;
import nodomain.freeyourgadget.gadgetbridge.entities.User;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm; import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec; import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
@ -58,12 +46,13 @@ import nodomain.freeyourgadget.gadgetbridge.util.GB;
public class HPlusSupport extends AbstractBTLEDeviceSupport { public class HPlusSupport extends AbstractBTLEDeviceSupport {
private static final Logger LOG = LoggerFactory.getLogger(HPlusSupport.class); private static final Logger LOG = LoggerFactory.getLogger(HPlusSupport.class);
private BluetoothGattCharacteristic ctrlCharacteristic = null; public BluetoothGattCharacteristic ctrlCharacteristic = null;
private BluetoothGattCharacteristic measureCharacteristic = null; public BluetoothGattCharacteristic measureCharacteristic = null;
private byte[] lastDataStats = null; private int[] lastDataStats = null;
private final GBDeviceEventVersionInfo versionCmd = new GBDeviceEventVersionInfo(); private final GBDeviceEventVersionInfo versionCmd = new GBDeviceEventVersionInfo();
private HPlusHandlerThread syncHelper;
private final BroadcastReceiver mReceiver = new BroadcastReceiver() { private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override @Override
@ -85,55 +74,68 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
IntentFilter intentFilter = new IntentFilter(); IntentFilter intentFilter = new IntentFilter();
broadcastManager.registerReceiver(mReceiver, intentFilter); broadcastManager.registerReceiver(mReceiver, intentFilter);
} }
@Override @Override
public void dispose() { public void dispose() {
LOG.debug("Dispose");
LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(getContext()); LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(getContext());
broadcastManager.unregisterReceiver(mReceiver); broadcastManager.unregisterReceiver(mReceiver);
super.dispose(); super.dispose();
if(syncHelper != null)
syncHelper.quit();
} }
@Override @Override
protected TransactionBuilder initializeDevice(TransactionBuilder builder) { protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
LOG.debug("Initializing");
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
measureCharacteristic = getCharacteristic(HPlusConstants.UUID_CHARACTERISTIC_MEASURE); measureCharacteristic = getCharacteristic(HPlusConstants.UUID_CHARACTERISTIC_MEASURE);
ctrlCharacteristic = getCharacteristic(HPlusConstants.UUID_CHARACTERISTIC_CONTROL); ctrlCharacteristic = getCharacteristic(HPlusConstants.UUID_CHARACTERISTIC_CONTROL);
getDevice().setFirmwareVersion("N/A");
getDevice().setFirmwareVersion2("N/A");
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext())); syncHelper = new HPlusHandlerThread(getDevice(), getContext(), this);
getDevice().setFirmwareVersion("0");
getDevice().setFirmwareVersion2("0");
//Initialize device //Initialize device
syncPreferences(builder); //Sync preferences sendUserInfo(builder); //Sync preferences
setSIT(builder); //Sync SIT Interval setSIT(builder); //Sync SIT Interval
setCurrentDate(builder); // Sync Current Date setCurrentDate(builder); // Sync Current Date
setCurrentTime(builder); // Sync Current Time setCurrentTime(builder); // Sync Current Time
builder.notify(getCharacteristic(HPlusConstants.UUID_CHARACTERISTIC_MEASURE), true); requestDeviceInfo(builder);
setInitialized(builder);
syncHelper.start();
builder.notify(getCharacteristic(HPlusConstants.UUID_CHARACTERISTIC_MEASURE), true);
builder.setGattCallback(this); builder.setGattCallback(this);
builder.notify(measureCharacteristic, true); builder.notify(measureCharacteristic, true);
setInitialized(builder); //LOG.debug("Initialization Done");
return builder; return builder;
} }
private HPlusSupport sendUserInfo(TransactionBuilder builder) { private HPlusSupport sendUserInfo(TransactionBuilder builder) {
builder.write(ctrlCharacteristic, HPlusConstants.COMMAND_SET_PREF_START); builder.write(ctrlCharacteristic, HPlusConstants.CMD_SET_PREF_START);
builder.write(ctrlCharacteristic, HPlusConstants.COMMAND_SET_PREF_START1); builder.write(ctrlCharacteristic, HPlusConstants.CMD_SET_PREF_START1);
syncPreferences(builder); syncPreferences(builder);
builder.write(ctrlCharacteristic, new byte[]{HPlusConstants.COMMAND_SET_CONF_SAVE}); builder.write(ctrlCharacteristic, new byte[]{HPlusConstants.CMD_SET_CONF_END});
builder.write(ctrlCharacteristic, new byte[]{HPlusConstants.COMMAND_SET_CONF_END});
return this; return this;
} }
private HPlusSupport syncPreferences(TransactionBuilder transaction) { private HPlusSupport syncPreferences(TransactionBuilder transaction) {
LOG.info("Attempting to sync preferences..."); LOG.info("Attempting to sync preferences with: " + getDevice().getAddress());
byte gender = HPlusCoordinator.getUserGender(getDevice().getAddress()); byte gender = HPlusCoordinator.getUserGender(getDevice().getAddress());
byte age = HPlusCoordinator.getUserAge(getDevice().getAddress()); byte age = HPlusCoordinator.getUserAge(getDevice().getAddress());
@ -159,7 +161,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
byte timemode = HPlusCoordinator.getTimeMode((getDevice().getAddress())); byte timemode = HPlusCoordinator.getTimeMode((getDevice().getAddress()));
transaction.write(ctrlCharacteristic, new byte[]{ transaction.write(ctrlCharacteristic, new byte[]{
HPlusConstants.COMMAND_SET_PREFS, HPlusConstants.CMD_SET_PREFS,
gender, gender,
age, age,
bodyHeight, bodyHeight,
@ -174,20 +176,24 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
social, social,
allDayHeart, allDayHeart,
wrist, wrist,
0,
alertTimeHour, alertTimeHour,
alertTimeMinute, alertTimeMinute,
unit, unit,
timemode timemode
}); });
setAllDayHeart(transaction);
return this; return this;
} }
private HPlusSupport setCountry(TransactionBuilder transaction) { private HPlusSupport setLanguage(TransactionBuilder transaction) {
LOG.info("Attempting to set country..."); LOG.info("Attempting to set language...");
byte value = HPlusCoordinator.getCountry(getDevice().getAddress()); byte value = HPlusCoordinator.getCountry(getDevice().getAddress());
transaction.write(ctrlCharacteristic, new byte[]{ transaction.write(ctrlCharacteristic, new byte[]{
HPlusConstants.COMMAND_SET_PREF_COUNTRY, HPlusConstants.CMD_SET_LANGUAGE,
value value
}); });
return this; return this;
@ -199,7 +205,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
byte value = HPlusCoordinator.getTimeMode(getDevice().getAddress()); byte value = HPlusCoordinator.getTimeMode(getDevice().getAddress());
transaction.write(ctrlCharacteristic, new byte[]{ transaction.write(ctrlCharacteristic, new byte[]{
HPlusConstants.COMMAND_SET_PREF_TIMEMODE, HPlusConstants.CMD_SET_TIMEMODE,
value value
}); });
return this; return this;
@ -211,7 +217,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
byte value = HPlusCoordinator.getUnit(getDevice().getAddress()); byte value = HPlusCoordinator.getUnit(getDevice().getAddress());
transaction.write(ctrlCharacteristic, new byte[]{ transaction.write(ctrlCharacteristic, new byte[]{
HPlusConstants.COMMAND_SET_PREF_UNIT, HPlusConstants.CMD_SET_UNITS,
value value
}); });
return this; return this;
@ -226,7 +232,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
int day = c.get(Calendar.DAY_OF_MONTH); int day = c.get(Calendar.DAY_OF_MONTH);
transaction.write(ctrlCharacteristic, new byte[]{ transaction.write(ctrlCharacteristic, new byte[]{
HPlusConstants.COMMAND_SET_PREF_DATE, HPlusConstants.CMD_SET_DATE,
(byte) ((year / 256) & 0xff), (byte) ((year / 256) & 0xff),
(byte) (year % 256), (byte) (year % 256),
(byte) (month + 1), (byte) (month + 1),
@ -242,7 +248,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
Calendar c = Calendar.getInstance(); Calendar c = Calendar.getInstance();
transaction.write(ctrlCharacteristic, new byte[]{ transaction.write(ctrlCharacteristic, new byte[]{
HPlusConstants.COMMAND_SET_PREF_TIME, HPlusConstants.CMD_SET_TIME,
(byte) c.get(Calendar.HOUR_OF_DAY), (byte) c.get(Calendar.HOUR_OF_DAY),
(byte) c.get(Calendar.MINUTE), (byte) c.get(Calendar.MINUTE),
(byte) c.get(Calendar.SECOND) (byte) c.get(Calendar.SECOND)
@ -258,7 +264,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
Calendar c = Calendar.getInstance(); Calendar c = Calendar.getInstance();
transaction.write(ctrlCharacteristic, new byte[]{ transaction.write(ctrlCharacteristic, new byte[]{
HPlusConstants.COMMAND_SET_PREF_WEEK, HPlusConstants.CMD_SET_WEEK,
(byte) c.get(Calendar.DAY_OF_WEEK) (byte) c.get(Calendar.DAY_OF_WEEK)
}); });
return this; return this;
@ -274,7 +280,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
Calendar now = Calendar.getInstance(); Calendar now = Calendar.getInstance();
transaction.write(ctrlCharacteristic, new byte[]{ transaction.write(ctrlCharacteristic, new byte[]{
HPlusConstants.COMMAND_SET_SIT_INTERVAL, HPlusConstants.CMD_SET_SIT_INTERVAL,
(byte) ((startTime / 256) & 0xff), (byte) ((startTime / 256) & 0xff),
(byte) (startTime % 256), (byte) (startTime % 256),
(byte) ((endTime / 256) & 0xff), (byte) ((endTime / 256) & 0xff),
@ -302,7 +308,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
byte value = HPlusCoordinator.getUserWeight(getDevice().getAddress()); byte value = HPlusCoordinator.getUserWeight(getDevice().getAddress());
transaction.write(ctrlCharacteristic, new byte[]{ transaction.write(ctrlCharacteristic, new byte[]{
HPlusConstants.COMMAND_SET_PREF_WEIGHT, HPlusConstants.CMD_SET_WEIGHT,
value value
}); });
@ -314,7 +320,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
byte value = HPlusCoordinator.getUserHeight(getDevice().getAddress()); byte value = HPlusCoordinator.getUserHeight(getDevice().getAddress());
transaction.write(ctrlCharacteristic, new byte[]{ transaction.write(ctrlCharacteristic, new byte[]{
HPlusConstants.COMMAND_SET_PREF_HEIGHT, HPlusConstants.CMD_HEIGHT,
value value
}); });
@ -327,19 +333,19 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
byte value = HPlusCoordinator.getUserAge(getDevice().getAddress()); byte value = HPlusCoordinator.getUserAge(getDevice().getAddress());
transaction.write(ctrlCharacteristic, new byte[]{ transaction.write(ctrlCharacteristic, new byte[]{
HPlusConstants.COMMAND_SET_PREF_AGE, HPlusConstants.CMD_SET_AGE,
value value
}); });
return this; return this;
} }
private HPlusSupport setSex(TransactionBuilder transaction) { private HPlusSupport setGender(TransactionBuilder transaction) {
LOG.info("Attempting to set Sex..."); LOG.info("Attempting to set Gender...");
byte value = HPlusCoordinator.getUserGender(getDevice().getAddress()); byte value = HPlusCoordinator.getUserGender(getDevice().getAddress());
transaction.write(ctrlCharacteristic, new byte[]{ transaction.write(ctrlCharacteristic, new byte[]{
HPlusConstants.COMMAND_SET_PREF_SEX, HPlusConstants.CMD_SET_GENDER,
value value
}); });
@ -352,7 +358,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
int value = HPlusCoordinator.getGoal(getDevice().getAddress()); int value = HPlusCoordinator.getGoal(getDevice().getAddress());
transaction.write(ctrlCharacteristic, new byte[]{ transaction.write(ctrlCharacteristic, new byte[]{
HPlusConstants.COMMAND_SET_PREF_GOAL, HPlusConstants.CMD_SET_GOAL,
(byte) ((value / 256) & 0xff), (byte) ((value / 256) & 0xff),
(byte) (value % 256) (byte) (value % 256)
@ -366,7 +372,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
byte value = HPlusCoordinator.getScreenTime(getDevice().getAddress()); byte value = HPlusCoordinator.getScreenTime(getDevice().getAddress());
transaction.write(ctrlCharacteristic, new byte[]{ transaction.write(ctrlCharacteristic, new byte[]{
HPlusConstants.COMMAND_SET_PREF_SCREENTIME, HPlusConstants.CMD_SET_SCREENTIME,
value value
}); });
@ -378,7 +384,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
byte value = HPlusCoordinator.getAllDayHR(getDevice().getAddress()); byte value = HPlusCoordinator.getAllDayHR(getDevice().getAddress());
transaction.write(ctrlCharacteristic, new byte[]{ transaction.write(ctrlCharacteristic, new byte[]{
HPlusConstants.COMMAND_SET_PREF_ALLDAYHR, HPlusConstants.CMD_SET_ALLDAY_HRM,
value value
}); });
@ -407,8 +413,11 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
private HPlusSupport requestDeviceInfo(TransactionBuilder builder) { private HPlusSupport requestDeviceInfo(TransactionBuilder builder) {
LOG.debug("Requesting Device Info!"); LOG.debug("Requesting Device Info!");
BluetoothGattCharacteristic deviceName = getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_GAP_DEVICE_NAME);
builder.read(deviceName); // HPlus devices seem to report some information in an alternative manner
builder.write(ctrlCharacteristic, new byte[]{HPlusConstants.CMD_GET_DEVICE_ID});
builder.write(ctrlCharacteristic, new byte[]{HPlusConstants.CMD_GET_VERSION});
return this; return this;
} }
@ -435,7 +444,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
public void onNotification(NotificationSpec notificationSpec) { public void onNotification(NotificationSpec notificationSpec) {
LOG.debug("Got Notification"); LOG.debug("Got Notification");
//TODO: Show different notifications acccording to source as Band supports this //TODO: Show different notifications acccording to source as Band supports this
showText(notificationSpec.body); showText(notificationSpec.title, notificationSpec.body);
} }
@ -515,7 +524,8 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
@Override @Override
public void onFetchActivityData() { public void onFetchActivityData() {
if(syncHelper != null)
syncHelper.sync();
} }
@Override @Override
@ -531,7 +541,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
TransactionBuilder builder = new TransactionBuilder("HeartRateTest"); TransactionBuilder builder = new TransactionBuilder("HeartRateTest");
builder.write(ctrlCharacteristic, new byte[]{HPlusConstants.COMMAND_SET_PREF_ALLDAYHR, 0x10}); //Set Real Time... ? builder.write(ctrlCharacteristic, new byte[]{HPlusConstants.CMD_SET_ALLDAY_HRM, 0x0A}); //Set Real Time... ?
builder.queue(getQueue()); builder.queue(getQueue());
} }
@ -545,11 +555,11 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
byte state; byte state;
if (enable) if (enable)
state = HPlusConstants.PREF_VALUE_HEARTRATE_ALLDAY_ON; state = HPlusConstants.ARG_HEARTRATE_ALLDAY_ON;
else else
state = HPlusConstants.PREF_VALUE_HEARTRATE_ALLDAY_OFF; state = HPlusConstants.ARG_HEARTRATE_ALLDAY_OFF;
builder.write(ctrlCharacteristic, new byte[]{HPlusConstants.COMMAND_SET_PREF_ALLDAYHR, state}); builder.write(ctrlCharacteristic, new byte[]{HPlusConstants.CMD_SET_ALLDAY_HRM, state});
builder.queue(getQueue()); builder.queue(getQueue());
} }
@ -561,12 +571,12 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
TransactionBuilder builder = performInitialized("findMe"); TransactionBuilder builder = performInitialized("findMe");
byte[] msg = new byte[2]; byte[] msg = new byte[2];
msg[0] = HPlusConstants.COMMAND_SET_PREF_FINDME; msg[0] = HPlusConstants.CMD_SET_FINDME;
if (start) if (start)
msg[1] = 1; msg[1] = HPlusConstants.ARG_FINDME_ON;
else else
msg[1] = 0; msg[1] = HPlusConstants.ARG_FINDME_OFF;
builder.write(ctrlCharacteristic, msg); builder.write(ctrlCharacteristic, msg);
builder.queue(getQueue()); builder.queue(getQueue());
@ -586,7 +596,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
TransactionBuilder builder = performInitialized("vibration"); TransactionBuilder builder = performInitialized("vibration");
byte[] msg = new byte[15]; byte[] msg = new byte[15];
msg[0] = HPlusConstants.COMMAND_SET_DISPLAY_ALERT; msg[0] = HPlusConstants.CMD_SET_INCOMING_CALL_NUMBER;
for (int i = 0; i < msg.length - 1; i++) for (int i = 0; i < msg.length - 1; i++)
msg[i + 1] = (byte) "GadgetBridge".charAt(i); msg[i + 1] = (byte) "GadgetBridge".charAt(i);
@ -630,17 +640,17 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
} }
private void showIncomingCall(String name, String number){ private void showIncomingCall(String name, String number) {
LOG.debug("Show Incoming Call"); LOG.debug("Show Incoming Call");
try { try {
TransactionBuilder builder = performInitialized("incomingCallIcon"); TransactionBuilder builder = performInitialized("incomingCallIcon");
//Enable call notifications //Enable call notifications
builder.write(ctrlCharacteristic, new byte[] {HPlusConstants.COMMAND_SET_INCOMING_CALL, 1 }); builder.write(ctrlCharacteristic, new byte[]{HPlusConstants.CMD_ACTION_INCOMING_CALL, 1});
//Show Call Icon //Show Call Icon
builder.write(ctrlCharacteristic, HPlusConstants.COMMAND_ACTION_INCOMING_CALL); builder.write(ctrlCharacteristic, new byte[]{HPlusConstants.CMD_ACTION_INCOMING_CALL, HPlusConstants.ARG_INCOMING_CALL});
//builder = performInitialized("incomingCallText"); //builder = performInitialized("incomingCallText");
builder.queue(getQueue()); builder.queue(getQueue());
@ -660,11 +670,11 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
for (int i = 0; i < msg.length; i++) for (int i = 0; i < msg.length; i++)
msg[i] = ' '; msg[i] = ' ';
for(int i = 0; i < number.length() && i < (msg.length - 1); i++) for (int i = 0; i < number.length() && i < (msg.length - 1); i++)
msg[i + 1] = (byte) number.charAt(i); msg[i + 1] = (byte) number.charAt(i);
msg[0] = HPlusConstants.COMMAND_ACTION_DISPLAY_TEXT_CENTER; msg[0] = HPlusConstants.CMD_SET_INCOMING_CALL_NUMBER;
builder.write(ctrlCharacteristic, msg); builder.write(ctrlCharacteristic, msg);
builder.queue(getQueue()); builder.queue(getQueue());
@ -682,10 +692,10 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
for (int i = 0; i < msg.length; i++) for (int i = 0; i < msg.length; i++)
msg[i] = ' '; msg[i] = ' ';
for(int i = 0; i < name.length() && i < (msg.length - 1); i++) for (int i = 0; i < name.length() && i < (msg.length - 1); i++)
msg[i + 1] = (byte) name.charAt(i); msg[i + 1] = (byte) name.charAt(i);
msg[0] = HPlusConstants.COMMAND_ACTION_DISPLAY_TEXT_NAME; msg[0] = HPlusConstants.CMD_ACTION_DISPLAY_TEXT_NAME;
builder.write(ctrlCharacteristic, msg); builder.write(ctrlCharacteristic, msg);
try { try {
@ -694,17 +704,18 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
e.printStackTrace(); e.printStackTrace();
} }
msg[0] = HPlusConstants.COMMAND_ACTION_DISPLAY_TEXT_NAME_CN; msg[0] = HPlusConstants.CMD_ACTION_DISPLAY_TEXT_NAME_CN;
builder.write(ctrlCharacteristic, msg); builder.write(ctrlCharacteristic, msg);
builder.queue(getQueue()); builder.queue(getQueue());
}catch(IOException e){ } catch (IOException e) {
GB.toast(getContext(), "Error showing incoming call: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR); GB.toast(getContext(), "Error showing incoming call: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
} }
} }
private void showText(String message) { private void showText(String message) {
showText(null, message); showText(null, message);
} }
@ -719,7 +730,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
for (int i = 0; i < msg.length; i++) for (int i = 0; i < msg.length; i++)
msg[i] = ' '; msg[i] = ' ';
msg[0] = HPlusConstants.COMMAND_ACTION_DISPLAY_TEXT; msg[0] = HPlusConstants.CMD_ACTION_DISPLAY_TEXT;
String message = ""; String message = "";
@ -737,7 +748,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
int length = message.length() / 17; int length = message.length() / 17;
builder.write(ctrlCharacteristic, new byte[]{HPlusConstants.COMMAND_ACTION_INCOMING_SOCIAL, (byte) 255}); builder.write(ctrlCharacteristic, new byte[]{HPlusConstants.CMD_ACTION_INCOMING_SOCIAL, (byte) 255});
int remaining; int remaining;
@ -773,7 +784,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
builder.write(ctrlCharacteristic, msg); builder.write(ctrlCharacteristic, msg);
builder.queue(getQueue()); builder.queue(getQueue());
}catch(IOException e){ } catch (IOException e) {
GB.toast(getContext(), "Error showing device Notification: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR); GB.toast(getContext(), "Error showing device Notification: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
} }
@ -799,17 +810,24 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
return true; return true;
switch (data[0]) { switch (data[0]) {
case HPlusConstants.DATA_STATS: case HPlusConstants.DATA_VERSION:
return processDataStats(data); syncHelper.processVersion(data);
case HPlusConstants.DATA_SLEEP: case HPlusConstants.DATA_STATS: {
return processSleepStats(data); syncHelper.processRealtimeStats(data);
case HPlusConstants.DATA_STEPS: return true;
return processStepStats(data); }
case HPlusConstants.DATA_SLEEP: {
syncHelper.processIncomingSleepData(data);
return true;
}
case HPlusConstants.DATA_STEPS:{
syncHelper.processStepStats(data);
return true;
}
case HPlusConstants.DATA_DAY_SUMMARY: case HPlusConstants.DATA_DAY_SUMMARY:
case HPlusConstants.DATA_DAY_SUMMARY_ALT: case HPlusConstants.DATA_DAY_SUMMARY_ALT:
return processDaySummary(data); syncHelper.processIncomingDaySlotData(data);
case HPlusConstants.DATA_INCOMING_CALL_STATE: return true;
return processIncomingCallState(data);
default: default:
LOG.info("Unhandled characteristic changed: " + characteristicUUID); LOG.info("Unhandled characteristic changed: " + characteristicUUID);
@ -817,197 +835,5 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
return false; return false;
} }
private boolean processIncomingCallState(byte[] data){
LOG.debug("Process Incoming Call State");
//Disabled now
return true;
}
/*
Receives a message containing the status of the day.
*/
private boolean processDaySummary(byte[] data) {
LOG.debug("Process Day Summary");
int a = data[4] * 256 + data[5];
if (a < 144) {
int slot = a * 2; // 10 minute slots as an offset from 0:00 AM
int avgHR = data[1]; //Average Heart Rate ?
int steps = data[2] * 256 + data[3]; // Steps in this period
//?? data[6];
int timeInactive = data[7]; // ?
LOG.debug("Day Stats: Slot: " + slot + " HR: " + avgHR + " Steps: " + steps + " TimeInactive: " + timeInactive);
try (DBHandler handler = GBApplication.acquireDB()) {
DaoSession session = handler.getDaoSession();
Device device = DBHelper.getDevice(getDevice(), session);
User user = DBHelper.getUser(session);
int ts = (int) (System.currentTimeMillis() / 1000);
HPlusSampleProvider provider = new HPlusSampleProvider(gbDevice, session);
//TODO: Store Sample. How?
//provider.addGBActivitySample(record);
} catch (GBException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
} else
LOG.error("Invalid day stats");
return true;
}
private boolean processStepStats(byte[] data) {
LOG.debug("Process Step Stats");
if (data.length < 19) {
LOG.error("Invalid Steps Message Length " + data.length);
return false;
}
/*
This is a dump of the entire day.
*/
int year = data[9] + data[10] * 256;
short month = data[11];
short day = data[12];
int steps = data[2] * 256 + data[1];
float distance = ((float) (data[3] + data[4] * 256) / 100.0f);
/*
unknown fields
short s12 = (short)(data[5] + data[6] * 256);
short s13 = (short)(data[7] + data[8] * 256);
short s16 = (short)(data[13]) + data[14] * 256);
short s17 = data[15];
short s18 = data[16];
*/
LOG.debug("Step Stats: Year: " + year + " Month: " + month + " Day:");
try (DBHandler handler = GBApplication.acquireDB()) {
DaoSession session = handler.getDaoSession();
Device device = DBHelper.getDevice(getDevice(), session);
User user = DBHelper.getUser(session);
int ts = (int) (System.currentTimeMillis() / 1000);
HPlusSampleProvider provider = new HPlusSampleProvider(gbDevice, session);
//TODO: Store Sample. How?
//provider.addGBActivitySample(record);
} catch (GBException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return true;
}
private boolean processSleepStats(byte[] data) {
LOG.debug("Process Sleep Stats");
if (data.length < 19) {
LOG.error("Invalid Sleep Message Length " + data.length);
return false;
}
HPlusSleepRecord record = new HPlusSleepRecord(data);
try (DBHandler handler = GBApplication.acquireDB()) {
DaoSession session = handler.getDaoSession();
Device device = DBHelper.getDevice(getDevice(), session);
User user = DBHelper.getUser(session);
int ts = (int) (System.currentTimeMillis() / 1000);
HPlusSampleProvider provider = new HPlusSampleProvider(gbDevice, session);
//TODO: Store Sample. How?
//provider.addGBActivitySample(record);
} catch (GBException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return true;
}
private boolean processDataStats(byte[] data) {
//TODO: Store Calories and Distance. How?
LOG.debug("Process Data Stats");
if (data.length < 15) {
LOG.error("Invalid Stats Message Length " + data.length);
return false;
}
//Ignore duplicate packets
if(data.equals(lastDataStats))
return true;
lastDataStats = data.clone();
double distance = ((int) data[4] * 256 + data[3]) / 100.0;
int x = (int) data[6] * 256 + data[5];
int y = (int) data[8] * 256 + data[7];
int calories = x + y;
int bpm = (data[11] == -1) ? HPlusHealthActivitySample.NOT_MEASURED : data[11];
try (DBHandler handler = GBApplication.acquireDB()) {
DaoSession session = handler.getDaoSession();
Device device = DBHelper.getDevice(getDevice(), session);
User user = DBHelper.getUser(session);
int ts = (int) (System.currentTimeMillis() / 1000);
HPlusSampleProvider provider = new HPlusSampleProvider(gbDevice, session);
if (bpm != HPlusHealthActivitySample.NOT_MEASURED) {
HPlusHealthActivitySample sample = createActivitySample(device, user, ts, provider);
sample.setHeartRate(bpm);
sample.setSteps(0);
sample.setRawIntensity(ActivitySample.NOT_MEASURED);
sample.setRawKind(ActivityKind.TYPE_ACTIVITY); // to make it visible in the charts TODO: add a MANUAL kind for that?
provider.addGBActivitySample(sample);
}
} catch (GBException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return true;
}
public HPlusHealthActivitySample createActivitySample(Device device, User user, int timestampInSeconds, SampleProvider provider) {
HPlusHealthActivitySample sample = new HPlusHealthActivitySample();
sample.setDevice(device);
sample.setUser(user);
sample.setTimestamp(timestampInSeconds);
sample.setProvider(provider);
return sample;
}
} }