mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-11-29 13:26:50 +01:00
Display calibration button for Watch X Plus
Use calibration activity from Watch9 for now Partial heart rate history handling (WIP)
This commit is contained in:
parent
34d9bccb86
commit
4601d827ae
@ -59,6 +59,7 @@ import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSett
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
|
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
|
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.watch9.Watch9CalibrationActivity;
|
import nodomain.freeyourgadget.gadgetbridge.devices.watch9.Watch9CalibrationActivity;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.watchxplus.WatchXPlusCalibrationActivity;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
|
import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||||
@ -320,11 +321,17 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
|
|||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
holder.calibrateDevice.setVisibility(device.isInitialized() && device.getType() == DeviceType.WATCH9 ? View.VISIBLE : View.GONE);
|
holder.calibrateDevice.setVisibility(device.isInitialized() && (device.getType() == DeviceType.WATCH9 || device.getType() == DeviceType.WATCHXPLUS) ? View.VISIBLE : View.GONE);
|
||||||
holder.calibrateDevice.setOnClickListener(new View.OnClickListener() {
|
holder.calibrateDevice.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
Intent startIntent = new Intent(context, Watch9CalibrationActivity.class);
|
Intent startIntent;
|
||||||
|
if(device.getType() == DeviceType.WATCHXPLUS) {
|
||||||
|
startIntent = new Intent(context, WatchXPlusCalibrationActivity.class);
|
||||||
|
} else {
|
||||||
|
startIntent = new Intent(context, Watch9CalibrationActivity.class);
|
||||||
|
}
|
||||||
|
|
||||||
startIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
|
startIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
|
||||||
context.startActivity(startIntent);
|
context.startActivity(startIntent);
|
||||||
}
|
}
|
||||||
|
@ -56,7 +56,9 @@ public final class WatchXPlusConstants {
|
|||||||
public static final byte[] CMD_ALARM_SETTINGS = new byte[]{0x01, 0x0A};
|
public static final byte[] CMD_ALARM_SETTINGS = new byte[]{0x01, 0x0A};
|
||||||
public static final byte[] CMD_WEATHER_SET = new byte[]{0x01, 0x10};
|
public static final byte[] CMD_WEATHER_SET = new byte[]{0x01, 0x10};
|
||||||
public static final byte[] CMD_BATTERY_INFO = new byte[]{0x01, 0x14};
|
public static final byte[] CMD_BATTERY_INFO = new byte[]{0x01, 0x14};
|
||||||
public static final byte[] CMD_HEARTRATE_INFO = new byte[]{0x15, 0x03};
|
public static final byte[] CMD_RETRIEVE_DATA = new byte[]{(byte)0xF0, 0x10};
|
||||||
|
public static final byte[] CMD_RETRIEVE_DATA_DETAILS = new byte[]{(byte)0xF0, 0x11};
|
||||||
|
public static final byte[] HEART_RATE_DATA_TYPE = new byte[]{0x00, 0x02};
|
||||||
|
|
||||||
public static final byte[] CMD_NOTIFICATION_TASK = new byte[]{0x03, 0x01};
|
public static final byte[] CMD_NOTIFICATION_TASK = new byte[]{0x03, 0x01};
|
||||||
public static final byte[] CMD_NOTIFICATION_TEXT_TASK = new byte[]{0x03, 0x06};
|
public static final byte[] CMD_NOTIFICATION_TEXT_TASK = new byte[]{0x03, 0x06};
|
||||||
@ -73,12 +75,14 @@ public final class WatchXPlusConstants {
|
|||||||
public static final byte[] RESP_BUTTON_INDICATOR = new byte[]{0x04, 0x03, 0x11};
|
public static final byte[] RESP_BUTTON_INDICATOR = new byte[]{0x04, 0x03, 0x11};
|
||||||
public static final byte[] RESP_ALARM_INDICATOR = new byte[]{-0x80, 0x01, 0x0A};
|
public static final byte[] RESP_ALARM_INDICATOR = new byte[]{-0x80, 0x01, 0x0A};
|
||||||
public static final byte[] RESP_DAY_STEPS_INDICATOR = new byte[]{0x08, 0x10, 0x03};
|
public static final byte[] RESP_DAY_STEPS_INDICATOR = new byte[]{0x08, 0x10, 0x03};
|
||||||
public static final byte[] RESP_HEARTRATE = new byte[]{0x08, 0x15, 0x02};
|
public static final byte[] RESP_HEARTRATE = new byte[]{-0x80, 0x15, 0x03};
|
||||||
|
|
||||||
public static final byte[] RESP_FIRMWARE_INFO = new byte[]{0x08, 0x01, 0x02};
|
public static final byte[] RESP_FIRMWARE_INFO = new byte[]{0x08, 0x01, 0x02};
|
||||||
public static final byte[] RESP_TIME_SETTINGS = new byte[]{0x08, 0x01, 0x08};
|
public static final byte[] RESP_TIME_SETTINGS = new byte[]{0x08, 0x01, 0x08};
|
||||||
public static final byte[] RESP_BATTERY_INFO = new byte[]{0x08, 0x01, 0x14};
|
public static final byte[] RESP_BATTERY_INFO = new byte[]{0x08, 0x01, 0x14};
|
||||||
public static final byte[] RESP_NOTIFICATION_SETTINGS = new byte[]{0x01, 0x03, 0x02};
|
public static final byte[] RESP_NOTIFICATION_SETTINGS = new byte[]{0x01, 0x03, 0x02};
|
||||||
|
public static final byte[] RESP_HEART_RATE_DATA = new byte[]{0x08, (byte)0xF0, 0x10};
|
||||||
|
public static final byte[] RESP_HEART_RATE_DATA_DETAILS = new byte[]{0x08, (byte)0xF0, 0x11};
|
||||||
|
|
||||||
public static final String ACTION_ENABLE = "action.watch9.enable";
|
public static final String ACTION_ENABLE = "action.watch9.enable";
|
||||||
|
|
||||||
|
@ -35,6 +35,7 @@ import java.io.IOException;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.GregorianCalendar;
|
import java.util.GregorianCalendar;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@ -485,10 +486,12 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE),
|
builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE),
|
||||||
buildCommand(WatchXPlusConstants.CMD_DAY_STEPS_INFO,
|
buildCommand(WatchXPlusConstants.CMD_DAY_STEPS_INFO,
|
||||||
WatchXPlusConstants.READ_VALUE));
|
WatchXPlusConstants.READ_VALUE));
|
||||||
// TODO: Watch does not return heart rate data after this command. Check why
|
|
||||||
// builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE),
|
// Fetch heart rate data samples count
|
||||||
// buildCommand(WatchXPlusConstants.CMD_HEARTRATE_INFO,
|
builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE),
|
||||||
// WatchXPlusConstants.READ_VALUE));
|
buildCommand(WatchXPlusConstants.CMD_RETRIEVE_DATA,
|
||||||
|
WatchXPlusConstants.READ_VALUE,
|
||||||
|
WatchXPlusConstants.HEART_RATE_DATA_TYPE));
|
||||||
|
|
||||||
performImmediately(builder);
|
performImmediately(builder);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
@ -577,22 +580,22 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
try {
|
try {
|
||||||
TransactionBuilder builder = performInitialized("setWeather");
|
TransactionBuilder builder = performInitialized("setWeather");
|
||||||
|
|
||||||
byte[] command = WatchXPlusConstants.CMD_WEATHER_SET;
|
byte[] command = WatchXPlusConstants.CMD_WEATHER_SET;
|
||||||
byte[] weatherInfo = new byte[5];
|
byte[] weatherInfo = new byte[5];
|
||||||
|
|
||||||
// bArr[8] = (byte) (this.mWeatherType >> 8);
|
// First two bytes are controlling the icon
|
||||||
// bArr[9] = (byte) this.mWeatherType;
|
weatherInfo[0] = 0x00;
|
||||||
// bArr[10] = (byte) this.mLowTemp;
|
weatherInfo[1] = 0x00;
|
||||||
// bArr[11] = (byte) this.mHighTemp;
|
weatherInfo[2] = (byte) weatherSpec.todayMinTemp;
|
||||||
// bArr[12] = (byte) this.mCurrentTemp;
|
weatherInfo[3] = (byte) weatherSpec.todayMaxTemp;
|
||||||
|
weatherInfo[4] = (byte) weatherSpec.currentTemp;
|
||||||
builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE),
|
builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE),
|
||||||
buildCommand(command,
|
buildCommand(command,
|
||||||
WatchXPlusConstants.KEEP_ALIVE,
|
WatchXPlusConstants.KEEP_ALIVE,
|
||||||
weatherInfo));
|
weatherInfo));
|
||||||
performImmediately(builder);
|
performImmediately(builder);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
LOG.warn("Unable to set time", e);
|
LOG.warn("Unable to set weather", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -620,11 +623,15 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
isCalibrationActive = false;
|
isCalibrationActive = false;
|
||||||
} else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_DAY_STEPS_INDICATOR, 5)) {
|
} else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_DAY_STEPS_INDICATOR, 5)) {
|
||||||
handleStepsInfo(value);
|
handleStepsInfo(value);
|
||||||
} else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_HEARTRATE, 5)) {
|
} else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_HEART_RATE_DATA, 5)) {
|
||||||
LOG.info(" Received Heart rate history");
|
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 (value.length == 7 && value[5] == 0) {
|
} else if (value.length == 7 && value[5] == 0) {
|
||||||
// Not sure if that's necessary
|
// Not sure if that's necessary. There is no response for ACK in original app logs
|
||||||
handleAck();
|
// handleAck();
|
||||||
} else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_NOTIFICATION_SETTINGS, 5)) {
|
} else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_NOTIFICATION_SETTINGS, 5)) {
|
||||||
LOG.info(" Received notification settings status");
|
LOG.info(" Received notification settings status");
|
||||||
} else {
|
} else {
|
||||||
@ -641,6 +648,36 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void handleHeartRateDetails(byte[] value) {
|
||||||
|
calculateHeartRateDetails(value);
|
||||||
|
LOG.info("Got Heart rate details");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleHeartRateDataCount(byte[] value) {
|
||||||
|
|
||||||
|
int dataCount = Conversion.fromByteArr16(value[10], value[11]);
|
||||||
|
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);
|
||||||
|
|
||||||
|
builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE),
|
||||||
|
buildCommand(WatchXPlusConstants.CMD_RETRIEVE_DATA_DETAILS,
|
||||||
|
WatchXPlusConstants.READ_VALUE,
|
||||||
|
req));
|
||||||
|
|
||||||
|
}
|
||||||
|
performImmediately(builder);
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.warn("Unable to response to ACK", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void handleAck() {
|
private void handleAck() {
|
||||||
try {
|
try {
|
||||||
TransactionBuilder builder = performInitialized("handleAck");
|
TransactionBuilder builder = performInitialized("handleAck");
|
||||||
@ -723,6 +760,44 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
super.dispose();
|
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:
|
||||||
|
return 1.0d * Math.pow(10.0d, -6.0d) * ((double) i2);
|
||||||
|
case 2:
|
||||||
|
return 1.0d * Math.pow(10.0d, -3.0d) * ((double) i2);
|
||||||
|
case 3:
|
||||||
|
return (double) (1 * i2);
|
||||||
|
case 4:
|
||||||
|
return 10.0d * Math.pow(10.0d, -6.0d) * ((double) i2);
|
||||||
|
case 5:
|
||||||
|
return 10.0d * Math.pow(10.0d, -3.0d) * ((double) i2);
|
||||||
|
case 6:
|
||||||
|
return (double) (10 * i2);
|
||||||
|
default:
|
||||||
|
return (double) (10 * i2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static class Conversion {
|
private static class Conversion {
|
||||||
static byte toBcd8(@IntRange(from = 0, to = 99) int value) {
|
static byte toBcd8(@IntRange(from = 0, to = 99) int value) {
|
||||||
int high = (value / 10) << 4;
|
int high = (value / 10) << 4;
|
||||||
|
Loading…
Reference in New Issue
Block a user