From 34d9bccb863ee93003c3876cb3d775fd245ab052 Mon Sep 17 00:00:00 2001 From: mkusnierz <> Date: Fri, 18 Oct 2019 23:36:56 +0200 Subject: [PATCH] Retrieve steps count Tried to set weather, but without success Handle ACK response Enabled alarms --- .../watchxplus/WatchXPlusConstants.java | 7 +- .../WatchXPlusDeviceCoordinator.java | 15 ++- .../watchxplus/WatchXPlusDeviceSupport.java | 96 ++++++++++++++++++- 3 files changed, 106 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watchxplus/WatchXPlusConstants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watchxplus/WatchXPlusConstants.java index 8069b8361..89558c21e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watchxplus/WatchXPlusConstants.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watchxplus/WatchXPlusConstants.java @@ -54,7 +54,9 @@ public final class WatchXPlusConstants { public static final byte[] CMD_AUTHORIZATION_TASK = new byte[]{0x01, 0x05}; public static final byte[] CMD_TIME_SETTINGS = new byte[]{0x01, 0x08}; 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_BATTERY_INFO = new byte[]{0x01, 0x14}; + public static final byte[] CMD_HEARTRATE_INFO = new byte[]{0x15, 0x03}; public static final byte[] CMD_NOTIFICATION_TASK = new byte[]{0x03, 0x01}; public static final byte[] CMD_NOTIFICATION_TEXT_TASK = new byte[]{0x03, 0x06}; @@ -65,15 +67,18 @@ public final class WatchXPlusConstants { public static final byte[] CMD_DO_NOT_DISTURB_SETTINGS = new byte[]{0x03, 0x61}; public static final byte[] CMD_FITNESS_GOAL_SETTINGS = new byte[]{0x10, 0x02}; + public static final byte[] CMD_DAY_STEPS_INFO = new byte[]{0x10, 0x03}; public static final byte[] RESP_AUTHORIZATION_TASK = new byte[]{0x01, 0x01, 0x05}; 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_DAY_STEPS_INDICATOR = new byte[]{0x08, 0x10, 0x03}; + public static final byte[] RESP_HEARTRATE = new byte[]{0x08, 0x15, 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_BATTERY_INFO = new byte[]{0x08, 0x01, 0x14}; - public static final byte[] RESP_NOTIFICATION_SETTINGS = new byte[]{0x08, 0x03, 0x02}; + public static final byte[] RESP_NOTIFICATION_SETTINGS = new byte[]{0x01, 0x03, 0x02}; public static final String ACTION_ENABLE = "action.watch9.enable"; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watchxplus/WatchXPlusDeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watchxplus/WatchXPlusDeviceCoordinator.java index a85ba3882..121c505f0 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watchxplus/WatchXPlusDeviceCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watchxplus/WatchXPlusDeviceCoordinator.java @@ -8,17 +8,16 @@ import android.net.Uri; import android.os.Build; import android.os.ParcelUuid; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import java.util.Collection; import java.util.Collections; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import nodomain.freeyourgadget.gadgetbridge.GBException; import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler; import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider; -import nodomain.freeyourgadget.gadgetbridge.devices.watchxplus.WatchXPlusConstants; -import nodomain.freeyourgadget.gadgetbridge.devices.watchxplus.WatchXPlusPairingActivity; import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; import nodomain.freeyourgadget.gadgetbridge.entities.Device; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; @@ -76,7 +75,7 @@ public class WatchXPlusDeviceCoordinator extends AbstractDeviceCoordinator { @Override public boolean supportsActivityDataFetching() { - return false; + return true; } @Override @@ -101,7 +100,7 @@ public class WatchXPlusDeviceCoordinator extends AbstractDeviceCoordinator { @Override public int getAlarmSlotCount() { - return 0; + return 3; } @Override @@ -111,7 +110,7 @@ public class WatchXPlusDeviceCoordinator extends AbstractDeviceCoordinator { @Override public boolean supportsHeartRateMeasurement(GBDevice device) { - return false; + return true; } @Override @@ -141,7 +140,7 @@ public class WatchXPlusDeviceCoordinator extends AbstractDeviceCoordinator { @Override public boolean supportsWeather() { - return false; + return true; } @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/watchxplus/WatchXPlusDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/watchxplus/WatchXPlusDeviceSupport.java index 3a1ccaa1c..fb39077e4 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/watchxplus/WatchXPlusDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/watchxplus/WatchXPlusDeviceSupport.java @@ -478,6 +478,22 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { @Override public void onFetchRecordedData(int dataTypes) { + TransactionBuilder builder = null; + try { + builder = performInitialized("fetchData"); + + builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), + buildCommand(WatchXPlusConstants.CMD_DAY_STEPS_INFO, + WatchXPlusConstants.READ_VALUE)); +// TODO: Watch does not return heart rate data after this command. Check why +// builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), +// buildCommand(WatchXPlusConstants.CMD_HEARTRATE_INFO, +// WatchXPlusConstants.READ_VALUE)); + + performImmediately(builder); + } catch (IOException e) { + LOG.warn("Unable to retrieve recorded data", e); + } } @Override @@ -558,7 +574,26 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { @Override public void onSendWeather(WeatherSpec weatherSpec) { + try { + TransactionBuilder builder = performInitialized("setWeather"); + byte[] command = WatchXPlusConstants.CMD_WEATHER_SET; + byte[] weatherInfo = new byte[5]; + +// bArr[8] = (byte) (this.mWeatherType >> 8); +// bArr[9] = (byte) this.mWeatherType; +// bArr[10] = (byte) this.mLowTemp; +// bArr[11] = (byte) this.mHighTemp; +// bArr[12] = (byte) this.mCurrentTemp; + + builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), + buildCommand(command, + WatchXPlusConstants.KEEP_ALIVE, + weatherInfo)); + performImmediately(builder); + } catch (IOException e) { + LOG.warn("Unable to set time", e); + } } @Override @@ -576,23 +611,71 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_TIME_SETTINGS, 5)) { handleTime(value); } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_BUTTON_INDICATOR, 5)) { - LOG.info("Unhandled action: Button pressed"); +// It looks like WatchXPlus doesn't send this action + LOG.info(" Unhandled action: Button pressed"); } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_ALARM_INDICATOR, 5)) { - LOG.info("Alarm active: id=" + value[8]); + LOG.info(" Alarm active: id=" + value[8]); } else if (isCalibrationActive && value.length == 7 && value[4] == ACK_CALIBRATION) { setTime(BLETypeConversions.createCalendar()); isCalibrationActive = false; + } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_DAY_STEPS_INDICATOR, 5)) { + handleStepsInfo(value); + } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_HEARTRATE, 5)) { + LOG.info(" Received Heart rate history"); + } else if (value.length == 7 && value[5] == 0) { +// Not sure if that's necessary + handleAck(); + } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_NOTIFICATION_SETTINGS, 5)) { + LOG.info(" Received notification settings status"); + } else { + LOG.info(" Unhandled value change for characteristic: " + characteristicUUID); + logMessageContent(characteristic.getValue()); } return true; } else { - LOG.info("Unhandled characteristic changed: " + characteristicUUID); + LOG.info(" Unhandled characteristic changed: " + characteristicUUID); logMessageContent(characteristic.getValue()); } return false; } + private void handleAck() { + try { + TransactionBuilder builder = performInitialized("handleAck"); + + builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), +// TODO: Below value is ACK status. Find out which value is correct + buildCommand((byte)0x00)); + performImmediately(builder); + } catch (IOException e) { + LOG.warn("Unable to response to ACK", e); + } + } + +// 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); + + result[2] = (byte) (result.length + 1); + result[3] = WatchXPlusConstants.REQUEST; + result[4] = (byte) sequenceNumber++; + result[5] = action; + result[result.length - 1] = calculateChecksum(result); + + return result; + } + + private void handleStepsInfo(byte[] value) { + int steps = Conversion.fromByteArr16(value[8], value[9]); + if (LOG.isDebugEnabled()) { + LOG.debug(" Received steps count: " + steps); + } +// TODO: save steps to DB + } + private byte[] buildCommand(byte[] command, byte action) { return buildCommand(command, action, null); } @@ -656,6 +739,13 @@ 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++) { + intValue += (value[i2] & 255) << (((value.length - 1) - i2) * 8); + } + return intValue; + } static byte[] toByteArr32(int value) { return new byte[]{(byte) (value >> 24),