mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-11-27 12:26:48 +01:00
Added blood pressure measurement request and response handling.
Added heart rate history fetching (watch doesn't return data yet). Steps data is saved to DB.
This commit is contained in:
parent
4728d5b4d0
commit
7f085681c3
@ -72,6 +72,7 @@ public class GBDaoGenerator {
|
||||
addXWatchActivitySample(schema, user, device);
|
||||
addZeTimeActivitySample(schema, user, device);
|
||||
addID115ActivitySample(schema, user, device);
|
||||
addWatchXPlusHealthActivitySample(schema, user, device);
|
||||
|
||||
addCalendarSyncState(schema, device);
|
||||
addAlarms(schema, user, device);
|
||||
@ -330,6 +331,20 @@ public class GBDaoGenerator {
|
||||
return activitySample;
|
||||
}
|
||||
|
||||
private static Entity addWatchXPlusHealthActivitySample(Schema schema, Entity user, Entity device) {
|
||||
Entity activitySample = addEntity(schema, "WatchXPlusActivitySample");
|
||||
activitySample.implementsSerializable();
|
||||
addCommonActivitySampleProperties("AbstractActivitySample", activitySample, user, device);
|
||||
activitySample.addByteArrayProperty("rawWatchXPlusHealthData");
|
||||
activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull().primaryKey();
|
||||
// activitySample.addIntProperty(SAMPLE_RAW_INTENSITY).notNull().codeBeforeGetterAndSetter(OVERRIDE);
|
||||
activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE);
|
||||
addHeartRateProperties(activitySample);
|
||||
activitySample.addIntProperty("distance");
|
||||
activitySample.addIntProperty("calories");
|
||||
return activitySample;
|
||||
}
|
||||
|
||||
private static void addCommonActivitySampleProperties(String superClass, Entity activitySample, Entity user, Entity device) {
|
||||
activitySample.setSuperclass(superClass);
|
||||
activitySample.addImport(MAIN_PACKAGE + ".devices.SampleProvider");
|
||||
|
@ -34,9 +34,11 @@ public final class WatchXPlusConstants extends LenovoWatchConstants {
|
||||
public static final int NOTIFICATION_CHANNEL_PHONE_CALL = 10;
|
||||
|
||||
public static final byte[] CMD_WEATHER_SET = new byte[]{0x01, 0x10};
|
||||
public static final byte[] CMD_RETRIEVE_DATA = new byte[]{(byte)0xF0, 0x10};
|
||||
public static final byte[] CMD_RETRIEVE_DATA_COUNT = new byte[]{(byte)0xF0, 0x10};
|
||||
public static final byte[] CMD_RETRIEVE_DATA_DETAILS = new byte[]{(byte)0xF0, 0x11};
|
||||
public static final byte[] CMD_RETRIEVE_DATA_CONTENT = new byte[]{(byte)0xF0, 0x12};
|
||||
public static final byte[] HEART_RATE_DATA_TYPE = new byte[]{0x00, 0x02};
|
||||
public static final byte[] CMD_BLOOD_PRESSURE_MEASURE = new byte[]{0x05, 0x0D};
|
||||
|
||||
public static final byte[] CMD_NOTIFICATION_TEXT_TASK = new byte[]{0x03, 0x06};
|
||||
public static final byte[] CMD_NOTIFICATION_SETTINGS = new byte[]{0x03, 0x02};
|
||||
@ -49,7 +51,9 @@ public final class WatchXPlusConstants extends LenovoWatchConstants {
|
||||
public static final byte[] RESP_DAY_STEPS_INDICATOR = new byte[]{0x08, 0x10, 0x03};
|
||||
public static final byte[] RESP_HEARTRATE = new byte[]{-0x80, 0x15, 0x03};
|
||||
|
||||
public static final byte[] RESP_HEART_RATE_DATA = new byte[]{0x08, (byte)0xF0, 0x10};
|
||||
public static final byte[] RESP_HEART_RATE_DATA_COUNT = new byte[]{0x08, (byte)0xF0, 0x10};
|
||||
public static final byte[] RESP_HEART_RATE_DATA_DETAILS = new byte[]{0x08, (byte)0xF0, 0x11};
|
||||
public static final byte[] RESP_HEART_RATE_DATA_CONTENT = new byte[]{0x08, (byte)0xF0, 0x12};
|
||||
public static final byte[] RESP_BP_MEASURE_STARTED = new byte[]{0x08, 0x05, 0x0D};
|
||||
|
||||
}
|
||||
|
@ -81,12 +81,12 @@ public class WatchXPlusDeviceCoordinator extends AbstractDeviceCoordinator {
|
||||
|
||||
@Override
|
||||
public boolean supportsActivityTracking() {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
|
||||
return null;
|
||||
return new WatchXPlusSampleProvider(device, session);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -0,0 +1,68 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.lenovo.watchxplus;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import de.greenrobot.dao.AbstractDao;
|
||||
import de.greenrobot.dao.Property;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractSampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.WatchXPlusActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.WatchXPlusActivitySampleDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
|
||||
public class WatchXPlusSampleProvider extends AbstractSampleProvider<WatchXPlusActivitySample> {
|
||||
|
||||
private GBDevice mDevice;
|
||||
private DaoSession mSession;
|
||||
|
||||
public WatchXPlusSampleProvider(GBDevice device, DaoSession session) {
|
||||
super(device, session);
|
||||
|
||||
mSession = session;
|
||||
mDevice = device;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int normalizeType(int rawType) {
|
||||
return rawType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int toRawActivityKind(int activityKind) {
|
||||
return activityKind;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float normalizeIntensity(int rawIntensity) {
|
||||
return rawIntensity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WatchXPlusActivitySample createActivitySample() {
|
||||
return new WatchXPlusActivitySample();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractDao<WatchXPlusActivitySample, ?> getSampleDao() {
|
||||
return getSession().getWatchXPlusActivitySampleDao();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected Property getRawKindSampleProperty() {
|
||||
return WatchXPlusActivitySampleDao.Properties.RawKind;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Property getTimestampSampleProperty() {
|
||||
return WatchXPlusActivitySampleDao.Properties.Timestamp;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Property getDeviceIdentifierSampleProperty() {
|
||||
return WatchXPlusActivitySampleDao.Properties.DeviceId;
|
||||
}
|
||||
}
|
@ -37,13 +37,23 @@ import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
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.GBDeviceEventBatteryInfo;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lenovo.watchxplus.WatchXPlusConstants;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lenovo.watchxplus.WatchXPlusSampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.WatchXPlusActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
|
||||
@ -70,6 +80,10 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport {
|
||||
private int sequenceNumber = 0;
|
||||
private boolean isCalibrationActive = false;
|
||||
|
||||
private List<Integer> heartRateDataToFetch = new ArrayList<>();
|
||||
private int requestedHeartRateTimestamp;
|
||||
private int heartRateDataSlots;
|
||||
|
||||
private byte ACK_CALIBRATION = 0;
|
||||
|
||||
private final GBDeviceEventVersionInfo versionInfo = new GBDeviceEventVersionInfo();
|
||||
@ -170,19 +184,19 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport {
|
||||
parts++;
|
||||
}
|
||||
for (int messageIndex = 0; messageIndex < parts; messageIndex++) {
|
||||
if(messageIndex+1 != parts || remainder == 0) {
|
||||
if (messageIndex + 1 != parts || remainder == 0) {
|
||||
messagePart = new byte[11];
|
||||
} else {
|
||||
messagePart = new byte[remainder+2];
|
||||
messagePart = new byte[remainder + 2];
|
||||
}
|
||||
|
||||
System.arraycopy(text, messageIndex*9, messagePart, 2, messagePart.length-2);
|
||||
System.arraycopy(text, messageIndex * 9, messagePart, 2, messagePart.length - 2);
|
||||
|
||||
if(messageIndex+1 == parts) {
|
||||
if (messageIndex + 1 == parts) {
|
||||
messageIndex = 0xFF;
|
||||
}
|
||||
messagePart[0] = (byte)notificationChannel;
|
||||
messagePart[1] = (byte)messageIndex;
|
||||
messagePart[0] = (byte) notificationChannel;
|
||||
messagePart[1] = (byte) messageIndex;
|
||||
builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE),
|
||||
buildCommand(command,
|
||||
WatchXPlusConstants.KEEP_ALIVE,
|
||||
@ -247,7 +261,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport {
|
||||
}
|
||||
}
|
||||
|
||||
private void sendCalibrationData(@IntRange(from=0,to=23)int hour, @IntRange(from=0,to=59)int minute, @IntRange(from=0,to=59)int second) {
|
||||
private void sendCalibrationData(@IntRange(from = 0, to = 23) int hour, @IntRange(from = 0, to = 59) int minute, @IntRange(from = 0, to = 59) int second) {
|
||||
try {
|
||||
isCalibrationActive = true;
|
||||
TransactionBuilder builder = performInitialized("calibrate");
|
||||
@ -488,10 +502,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport {
|
||||
WatchXPlusConstants.READ_VALUE));
|
||||
|
||||
// Fetch heart rate data samples count
|
||||
builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE),
|
||||
buildCommand(WatchXPlusConstants.CMD_RETRIEVE_DATA,
|
||||
WatchXPlusConstants.READ_VALUE,
|
||||
WatchXPlusConstants.HEART_RATE_DATA_TYPE));
|
||||
requestHeartRateDataCount(builder);
|
||||
|
||||
builder.queue(getQueue());
|
||||
} catch (IOException e) {
|
||||
@ -572,7 +583,22 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport {
|
||||
|
||||
@Override
|
||||
public void onTestNewFunction() {
|
||||
requestBloodPressureMeasurement();
|
||||
}
|
||||
|
||||
private void requestBloodPressureMeasurement() {
|
||||
try {
|
||||
TransactionBuilder builder = performInitialized("bpMeasure");
|
||||
|
||||
byte[] command = WatchXPlusConstants.CMD_BLOOD_PRESSURE_MEASURE;
|
||||
|
||||
builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE),
|
||||
buildCommand(command,
|
||||
WatchXPlusConstants.TASK, new byte[]{0x01}));
|
||||
builder.queue(getQueue());
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Unable to request BP Measure", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -607,7 +633,9 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport {
|
||||
UUID characteristicUUID = characteristic.getUuid();
|
||||
if (WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE.equals(characteristicUUID)) {
|
||||
byte[] value = characteristic.getValue();
|
||||
if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_FIRMWARE_INFO, 5)) {
|
||||
if (value[0] != 0x23) {
|
||||
handleHeartRateContentDataChunk(value);
|
||||
} else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_FIRMWARE_INFO, 5)) {
|
||||
handleFirmwareInfo(value);
|
||||
} else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_BATTERY_INFO, 5)) {
|
||||
handleBatteryState(value);
|
||||
@ -623,12 +651,17 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport {
|
||||
isCalibrationActive = false;
|
||||
} else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_DAY_STEPS_INDICATOR, 5)) {
|
||||
handleStepsInfo(value);
|
||||
} else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_HEART_RATE_DATA, 5)) {
|
||||
} else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_HEART_RATE_DATA_COUNT, 5)) {
|
||||
LOG.info(" Received Heart rate data count");
|
||||
handleHeartRateDataCount(value);
|
||||
} else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_HEART_RATE_DATA_DETAILS, 5)) {
|
||||
LOG.info(" Received Heart rate data details");
|
||||
handleHeartRateDetails(value);
|
||||
} else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_HEART_RATE_DATA_CONTENT, 5)) {
|
||||
LOG.info(" Received Heart rate data content");
|
||||
handleHeartRateContentAck(value);
|
||||
} else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_BP_MEASURE_STARTED, 5)) {
|
||||
handleBpMeasureResult(value);
|
||||
} else if (value.length == 7 && value[5] == 0) {
|
||||
LOG.info(" Received ACK");
|
||||
// Not sure if that's necessary. There is no response for ACK in original app logs
|
||||
@ -649,33 +682,145 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport {
|
||||
return false;
|
||||
}
|
||||
|
||||
private void handleHeartRateDetails(byte[] value) {
|
||||
calculateHeartRateDetails(value);
|
||||
LOG.info("Got Heart rate details");
|
||||
/**
|
||||
* Heart rate history retrieve flow:
|
||||
* 1. Request for heart rate data slots count. CMD_RETRIEVE_DATA_COUNT, {@link WatchXPlusDeviceSupport#requestHeartRateDataCount}
|
||||
* 2. Extract data count from response. RESP_HEART_RATE_DATA_COUNT, {@link WatchXPlusDeviceSupport#handleHeartRateDataCount}
|
||||
* 3. Request for N data slot details. CMD_RETRIEVE_DATA_DETAILS, {@link WatchXPlusDeviceSupport#requestHeartRateDetails}
|
||||
* 4. Timestamp of slot is returned, save it for later use. RESP_HEART_RATE_DATA_DETAILS, {@link WatchXPlusDeviceSupport#handleHeartRateDetails}
|
||||
* 5. Repeat step 3-4 until all slots details retrieved.
|
||||
* 6. Request for M data content by timestamp. CMD_RETRIEVE_DATA_CONTENT, {@link WatchXPlusDeviceSupport#requestHeartRateContentForTimestamp}
|
||||
* 7. Receive kind of pre-flight response. RESP_HEART_RATE_DATA_CONTENT, {@link WatchXPlusDeviceSupport#handleHeartRateContentAck}
|
||||
* 8. Receive frames with content. They are different than other frames, {@link WatchXPlusDeviceSupport#handleHeartRateContentDataChunk}
|
||||
* ie. 0000000255-4F4C48-434241434444454648474747, 0001000247-474645-434240FFFFFFFFFFFFFFFFFF
|
||||
*/
|
||||
private void requestHeartRateDataCount(TransactionBuilder builder) {
|
||||
builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE),
|
||||
buildCommand(WatchXPlusConstants.CMD_RETRIEVE_DATA_COUNT,
|
||||
WatchXPlusConstants.READ_VALUE,
|
||||
WatchXPlusConstants.HEART_RATE_DATA_TYPE));
|
||||
}
|
||||
|
||||
private void handleHeartRateDataCount(byte[] value) {
|
||||
|
||||
int dataCount = Conversion.fromByteArr16(value[10], value[11]);
|
||||
LOG.info("Watch contains " + dataCount + " heart rate entries");
|
||||
this.heartRateDataSlots = dataCount;
|
||||
heartRateDataToFetch.clear();
|
||||
if (dataCount != 0) {
|
||||
requestHeartRateDetails(heartRateDataToFetch.size());
|
||||
}
|
||||
}
|
||||
|
||||
private void requestHeartRateDetails(int i) {
|
||||
try {
|
||||
TransactionBuilder builder = performInitialized("requestHeartRate");
|
||||
byte[] heartRateDataType = WatchXPlusConstants.HEART_RATE_DATA_TYPE;
|
||||
|
||||
LOG.info("Watch contains " + dataCount + " heart rate entries");
|
||||
// Request all data samples
|
||||
for (int i = 0; i < dataCount; i++) {
|
||||
byte[] index = Conversion.toByteArr16(i);
|
||||
byte[] req = BLETypeConversions.join(heartRateDataType, index);
|
||||
byte[] index = Conversion.toByteArr16(i);
|
||||
byte[] req = BLETypeConversions.join(heartRateDataType, index);
|
||||
|
||||
builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE),
|
||||
buildCommand(WatchXPlusConstants.CMD_RETRIEVE_DATA_DETAILS,
|
||||
WatchXPlusConstants.READ_VALUE,
|
||||
req));
|
||||
builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE),
|
||||
buildCommand(WatchXPlusConstants.CMD_RETRIEVE_DATA_DETAILS,
|
||||
WatchXPlusConstants.READ_VALUE,
|
||||
req));
|
||||
|
||||
}
|
||||
builder.queue(getQueue());
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Unable to response to ACK", e);
|
||||
LOG.warn("Unable to request data", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleHeartRateDetails(byte[] value) {
|
||||
LOG.info("Got Heart rate details");
|
||||
int timestamp = Conversion.fromByteArr16(value[8], value[9], value[10], value[11]);
|
||||
int dataLength = Conversion.fromByteArr16(value[12], value[13]);
|
||||
int samplingInterval = (int) onSamplingInterval(value[14] >> 4, Conversion.fromByteArr16((byte) (value[14] & 15), value[15]));
|
||||
int mtu = Conversion.fromByteArr16(value[16]);
|
||||
int parts = dataLength / 16;
|
||||
if (dataLength % 16 > 0) {
|
||||
parts++;
|
||||
}
|
||||
|
||||
LOG.info("timestamp (UTC): " + timestamp);
|
||||
LOG.info("timestamp (UTC): " + new Date((long) timestamp * 1000));
|
||||
LOG.info("dataLength (data length): " + dataLength);
|
||||
LOG.info("samplingInterval (per time): " + samplingInterval);
|
||||
LOG.info("mtu (mtu): " + mtu);
|
||||
LOG.info("parts: " + parts);
|
||||
|
||||
heartRateDataToFetch.add(timestamp);
|
||||
|
||||
if (heartRateDataToFetch.size() == heartRateDataSlots) {
|
||||
requestHeartRateContentForTimestamp(heartRateDataToFetch.get(0));
|
||||
} else {
|
||||
requestHeartRateDetails(heartRateDataToFetch.size());
|
||||
}
|
||||
}
|
||||
|
||||
private void requestHeartRateContentForTimestamp(int timestamp) {
|
||||
byte[] heartRateDataType = WatchXPlusConstants.HEART_RATE_DATA_TYPE;
|
||||
byte[] command = WatchXPlusConstants.CMD_RETRIEVE_DATA_CONTENT;
|
||||
|
||||
try {
|
||||
TransactionBuilder builder = performInitialized("content");
|
||||
byte[] ts = Conversion.toByteArr32(timestamp);
|
||||
byte[] req = BLETypeConversions.join(heartRateDataType, ts);
|
||||
req = BLETypeConversions.join(req, Conversion.toByteArr16(0));
|
||||
requestedHeartRateTimestamp = timestamp;
|
||||
builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE),
|
||||
buildCommand(command,
|
||||
WatchXPlusConstants.READ_VALUE,
|
||||
req));
|
||||
builder.queue(getQueue());
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Unable to request heart rate content", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void handleHeartRateContentAck(byte[] value) {
|
||||
LOG.info(" Received heart rate data content start");
|
||||
}
|
||||
|
||||
private void handleHeartRateContentDataChunk(byte[] value) {
|
||||
int chunkNo = Conversion.fromByteArr16(value[0], value[1]);
|
||||
int dataType = Conversion.fromByteArr16(value[2], value[2]);
|
||||
int timezoneOffset = TimeZone.getDefault().getOffset(System.currentTimeMillis());
|
||||
if (dataType != 2) {
|
||||
LOG.warn(" Got unsupported data package type: " + dataType);
|
||||
} else {
|
||||
for (int i = 4; i < value.length; i++) {
|
||||
|
||||
int val = Conversion.fromByteArr16(value[i]);
|
||||
if (255 == val) {
|
||||
break;
|
||||
}
|
||||
int tsWithOffset = requestedHeartRateTimestamp + (((((chunkNo * 16) + i) - 4) * 2) * 60) - timezoneOffset;
|
||||
LOG.info(" Got HR data: " + new Date(tsWithOffset) + ", value: " + val);
|
||||
}
|
||||
|
||||
heartRateDataToFetch.remove(0);
|
||||
if (!heartRateDataToFetch.isEmpty()) {
|
||||
requestHeartRateContentForTimestamp(heartRateDataToFetch.get(0));
|
||||
} else {
|
||||
heartRateDataSlots = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void handleBpMeasureResult(byte[] value) {
|
||||
|
||||
if (value.length < 11) {
|
||||
LOG.info(" BP Measure started. Waiting for result");
|
||||
} else {
|
||||
LOG.info(" Received BP live data");
|
||||
int high = Conversion.fromByteArr16(value[8], value[9]);
|
||||
int low = Conversion.fromByteArr16(value[10], value[11]);
|
||||
int timestamp = Conversion.fromByteArr16(value[12], value[13], value[14], value[15]);
|
||||
|
||||
LOG.info(" Calculated BP data: timestamp: " + timestamp + ", high: " + high + ", low: " + low);
|
||||
}
|
||||
}
|
||||
|
||||
@ -684,14 +829,14 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport {
|
||||
TransactionBuilder builder = performInitialized("handleAck");
|
||||
|
||||
builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE),
|
||||
buildCommand((byte)0x00));
|
||||
buildCommand((byte) 0x00));
|
||||
builder.queue(getQueue());
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Unable to response to ACK", e);
|
||||
}
|
||||
}
|
||||
|
||||
// This is only for ACK response
|
||||
// This is only for ACK response
|
||||
private byte[] buildCommand(byte action) {
|
||||
byte[] result = new byte[7];
|
||||
System.arraycopy(WatchXPlusConstants.CMD_HEADER, 0, result, 0, 5);
|
||||
@ -707,10 +852,103 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport {
|
||||
|
||||
private void handleStepsInfo(byte[] value) {
|
||||
int steps = Conversion.fromByteArr16(value[8], value[9]);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug(" Received steps count: " + steps);
|
||||
LOG.debug(" Received steps count: " + steps);
|
||||
|
||||
// This code is from MakibesHR3DeviceSupport
|
||||
Calendar date = GregorianCalendar.getInstance();
|
||||
int timestamp = (int) (date.getTimeInMillis() / 1000);
|
||||
|
||||
// We need to subtract the day's total step count thus far.
|
||||
int dayStepCount = this.getStepsOnDay(timestamp);
|
||||
|
||||
int newSteps = (steps - dayStepCount);
|
||||
|
||||
if (newSteps > 0) {
|
||||
LOG.debug("adding " + newSteps + " steps");
|
||||
|
||||
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
||||
WatchXPlusSampleProvider provider = new WatchXPlusSampleProvider(getDevice(), dbHandler.getDaoSession());
|
||||
|
||||
WatchXPlusActivitySample sample = createSample(dbHandler, timestamp);
|
||||
sample.setTimestamp(timestamp);
|
||||
// sample.setRawKind(record.type);
|
||||
sample.setSteps(newSteps);
|
||||
// sample.setDistance(record.distance);
|
||||
// sample.setCalories(record.calories);
|
||||
// sample.setDistance(record.distance);
|
||||
// sample.setHeartRate((record.maxHeartRate - record.minHeartRate) / 2); //TODO: Find an alternative approach for Day Summary Heart Rate
|
||||
// sample.setRawHPlusHealthData(record.getRawData());
|
||||
|
||||
sample.setProvider(provider);
|
||||
provider.addGBActivitySample(sample);
|
||||
} catch (GBException ex) {
|
||||
LOG.info((ex.getMessage()));
|
||||
} catch (Exception ex) {
|
||||
LOG.info(ex.getMessage());
|
||||
}
|
||||
}
|
||||
// TODO: save steps to DB
|
||||
}
|
||||
|
||||
/**
|
||||
* @param timeStamp Time stamp at some point during the requested day.
|
||||
*/
|
||||
private int getStepsOnDay(int timeStamp) {
|
||||
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
||||
|
||||
Calendar dayStart = new GregorianCalendar();
|
||||
Calendar dayEnd = new GregorianCalendar();
|
||||
|
||||
this.getDayStartEnd(timeStamp, dayStart, dayEnd);
|
||||
|
||||
WatchXPlusSampleProvider provider = new WatchXPlusSampleProvider(this.getDevice(), dbHandler.getDaoSession());
|
||||
|
||||
List<WatchXPlusActivitySample> samples = provider.getAllActivitySamples(
|
||||
(int) (dayStart.getTimeInMillis() / 1000L),
|
||||
(int) (dayEnd.getTimeInMillis() / 1000L));
|
||||
|
||||
int totalSteps = 0;
|
||||
|
||||
for (WatchXPlusActivitySample sample : samples) {
|
||||
totalSteps += sample.getSteps();
|
||||
}
|
||||
|
||||
return totalSteps;
|
||||
|
||||
} catch (Exception ex) {
|
||||
LOG.error(ex.getMessage());
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param timeStamp seconds
|
||||
*/
|
||||
private void getDayStartEnd(int timeStamp, Calendar start, Calendar end) {
|
||||
final int DAY = (24 * 60 * 60);
|
||||
|
||||
int timeStampStart = ((timeStamp / DAY) * DAY);
|
||||
int timeStampEnd = (timeStampStart + DAY);
|
||||
|
||||
start.setTimeInMillis(timeStampStart * 1000L);
|
||||
end.setTimeInMillis(timeStampEnd * 1000L);
|
||||
}
|
||||
|
||||
private WatchXPlusActivitySample createSample(DBHandler dbHandler, int timestamp) {
|
||||
Long userId = DBHelper.getUser(dbHandler.getDaoSession()).getId();
|
||||
Long deviceId = DBHelper.getDevice(getDevice(), dbHandler.getDaoSession()).getId();
|
||||
WatchXPlusActivitySample sample = new WatchXPlusActivitySample(
|
||||
timestamp, // ts
|
||||
deviceId, userId, // User id
|
||||
null, // Raw Data
|
||||
ActivityKind.TYPE_UNKNOWN, // rawKind
|
||||
ActivitySample.NOT_MEASURED, // Steps
|
||||
ActivitySample.NOT_MEASURED, // HR
|
||||
ActivitySample.NOT_MEASURED, // Distance
|
||||
ActivitySample.NOT_MEASURED // Calories
|
||||
);
|
||||
|
||||
return sample;
|
||||
}
|
||||
|
||||
private byte[] buildCommand(byte[] command, byte action) {
|
||||
@ -743,7 +981,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport {
|
||||
}
|
||||
|
||||
private void handleFirmwareInfo(byte[] value) {
|
||||
versionInfo.fwVersion = String.format(Locale.US,"%d.%d.%d", value[8], value[9], value[10]);
|
||||
versionInfo.fwVersion = String.format(Locale.US, "%d.%d.%d", value[8], value[9], value[10]);
|
||||
handleGBDeviceEvent(versionInfo);
|
||||
}
|
||||
|
||||
@ -760,25 +998,6 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
private static void calculateHeartRateDetails(byte[] bArr) {
|
||||
|
||||
int timestamp = Conversion.fromByteArr16(bArr[8], bArr[9], bArr[10], bArr[11]);
|
||||
int dataLength = Conversion.fromByteArr16(bArr[12], bArr[13]);
|
||||
int samplingInterval = (int) onSamplingInterval(bArr[14] >> 4, Conversion.fromByteArr16((byte) (bArr[14] & 15), bArr[15]));
|
||||
int mtu = Conversion.fromByteArr16(bArr[16]);
|
||||
int parts = dataLength / 16;
|
||||
if (dataLength % 16 > 0) {
|
||||
parts++;
|
||||
}
|
||||
|
||||
LOG.info("timestamp (UTC): " + timestamp);
|
||||
LOG.info("timestamp (UTC): " + new Date(timestamp));
|
||||
LOG.info("dataLength (data length): " + dataLength);
|
||||
LOG.info("samplingInterval (per time): " + samplingInterval);
|
||||
LOG.info("mtu (mtu): " + mtu);
|
||||
LOG.info("parts: " + parts);
|
||||
}
|
||||
|
||||
private static double onSamplingInterval(int i, int i2) {
|
||||
switch (i) {
|
||||
case 1:
|
||||
@ -814,6 +1033,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport {
|
||||
static byte[] toByteArr16(int value) {
|
||||
return new byte[]{(byte) (value >> 8), (byte) value};
|
||||
}
|
||||
|
||||
static int fromByteArr16(byte... value) {
|
||||
int intValue = 0;
|
||||
for (int i2 = 0; i2 < value.length; i2++) {
|
||||
|
Loading…
Reference in New Issue
Block a user