From 9e4e50be4736046b4be23f399d53c10f31f7b581 Mon Sep 17 00:00:00 2001 From: cpfeiffer Date: Sat, 6 Jun 2015 00:40:16 +0200 Subject: [PATCH] Initial work on synchronizing activity data with feedback. A device now has a busy flag (set during synchronization). While busy, no other communication with the device shall occur (TODO) Refactors the non-bluetooth actions a bit #45 Next step: make use of the busy state in ControlCenter (show a busy cursor) and in BluetoothCommunicationService (to not call other operations while busy) --- .../gadgetbridge/AbstractBTDeviceSupport.java | 6 +++ .../gadgetbridge/EventHandler.java | 2 + .../freeyourgadget/gadgetbridge/GBDevice.java | 42 ++++++++++++++++- .../gadgetbridge/btle/BtLEAction.java | 4 -- .../btle/CheckInitializedAction.java | 7 +-- .../gadgetbridge/btle/PlainAction.java | 19 ++++++++ .../btle/SetDeviceBusyAction.java | 36 +++++++++++++++ .../gadgetbridge/btle/WaitAction.java | 8 +--- .../gadgetbridge/miband/MiBandService.java | 3 +- .../gadgetbridge/miband/MiBandSupport.java | 46 ++++++++++++++++--- .../miband/SetDeviceStateAction.java | 8 +--- .../protocol/GBDeviceProtocol.java | 4 ++ app/src/main/res/values/strings.xml | 1 + 13 files changed, 155 insertions(+), 31 deletions(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/btle/PlainAction.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/btle/SetDeviceBusyAction.java diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/AbstractBTDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/AbstractBTDeviceSupport.java index abb3ca3d..ea209f2b 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/AbstractBTDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/AbstractBTDeviceSupport.java @@ -150,6 +150,12 @@ public abstract class AbstractBTDeviceSupport extends AbstractDeviceSupport { sendToDevice(bytes); } + @Override + public void onSynchronizeActivityData() { + byte[] bytes = gbDeviceProtocol.encodeSynchronizeActivityData(); + sendToDevice(bytes); + } + @Override public void onReboot() { byte[] bytes = gbDeviceProtocol.encodeReboot(); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/EventHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/EventHandler.java index ed69d603..a4efbbe2 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/EventHandler.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/EventHandler.java @@ -27,6 +27,8 @@ public interface EventHandler { void onPhoneVersion(byte os); + void onSynchronizeActivityData(); + void onReboot(); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBDevice.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBDevice.java index 9ddbdb28..9e600963 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBDevice.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBDevice.java @@ -36,6 +36,7 @@ public class GBDevice implements Parcelable { private short mBatteryLevel = BATTERY_UNKNOWN; private String mBatteryState; private short mRssi = RSSI_UNKNOWN; + private String mBusyTask; public GBDevice(String address, String name, DeviceType deviceType) { mAddress = address; @@ -54,6 +55,8 @@ public class GBDevice implements Parcelable { mBatteryLevel = (short) in.readInt(); mBatteryState = in.readString(); mRssi = (short) in.readInt(); + mBusyTask = in.readString(); + validate(); } @@ -68,6 +71,7 @@ public class GBDevice implements Parcelable { dest.writeInt(mBatteryLevel); dest.writeString(mBatteryState); dest.writeInt(mRssi); + dest.writeString(mBusyTask); } private void validate() { @@ -116,6 +120,42 @@ public class GBDevice implements Parcelable { return mState == State.CONNECTING; } + public boolean isBusy() { + return mBusyTask != null; + } + + public String getBusyTask() { + return mBusyTask; + } + + /** + * Marks the device as busy, performing a certain task. While busy, no other operations will + * be performed on the device. + * + * Note that nested busy tasks are not supported, every single call to #setBusyTask() + * or unsetBusy() has an effect. + * @param task a textual name of the task to be performed, possibly displayed to the user + */ + public void setBusyTask(String task) { + if (task == null) { + throw new IllegalArgumentException("busy task must not be null"); + } + if (mBusyTask != null) { + LOG.warn("Attempt to mark device as busy with: " + task + ", but is already busy with: " + mBusyTask); + } + mBusyTask = task; + } + + /** + * Marks the device as not busy anymore. + */ + public void unsetBusyTask() { + if (mBusyTask == null) { + LOG.error("Attempt to mark device as not busy anymore, but was not busy before."); + } + mBusyTask = null; + } + public State getState() { return mState; } @@ -132,6 +172,7 @@ public class GBDevice implements Parcelable { setBatteryState(null); setFirmwareVersion(null); setRssi(RSSI_UNKNOWN); + unsetBusyTask(); } public String getStateString() { @@ -249,5 +290,4 @@ public class GBDevice implements Parcelable { INITIALIZING, INITIALIZED } - } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/btle/BtLEAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/btle/BtLEAction.java index 3d07647a..6985aa5a 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/btle/BtLEAction.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/btle/BtLEAction.java @@ -14,10 +14,6 @@ import android.bluetooth.BluetoothGattCharacteristic; public abstract class BtLEAction { private final BluetoothGattCharacteristic characteristic; - public BtLEAction() { - this(null); - } - public BtLEAction(BluetoothGattCharacteristic characteristic) { this.characteristic = characteristic; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/btle/CheckInitializedAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/btle/CheckInitializedAction.java index eafdf750..3ecd7bb1 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/btle/CheckInitializedAction.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/btle/CheckInitializedAction.java @@ -12,7 +12,7 @@ import nodomain.freeyourgadget.gadgetbridge.GBDevice; * sequence (transaction). It will abort the entire initialization sequence * by returning false, when the device is already initialized. */ -public class CheckInitializedAction extends BtLEAction { +public class CheckInitializedAction extends PlainAction { private static final Logger LOG = LoggerFactory.getLogger(CheckInitializedAction.class); private final GBDevice device; @@ -29,9 +29,4 @@ public class CheckInitializedAction extends BtLEAction { } return continueWithOtherInitActions; } - - @Override - public boolean expectsResult() { - return false; - } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/btle/PlainAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/btle/PlainAction.java new file mode 100644 index 00000000..d5455fed --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/btle/PlainAction.java @@ -0,0 +1,19 @@ +package nodomain.freeyourgadget.gadgetbridge.btle; + +import android.bluetooth.BluetoothGatt; + +/** + * An abstract non-BTLE action. It performs no bluetooth operation, + * does not have a BluetoothGattCharacteristic instance and expects no result. + */ +public abstract class PlainAction extends BtLEAction { + + public PlainAction() { + super(null); + } + + @Override + public boolean expectsResult() { + return false; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/btle/SetDeviceBusyAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/btle/SetDeviceBusyAction.java new file mode 100644 index 00000000..05f68013 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/btle/SetDeviceBusyAction.java @@ -0,0 +1,36 @@ +package nodomain.freeyourgadget.gadgetbridge.btle; + +import android.bluetooth.BluetoothGatt; +import android.content.Context; + +import nodomain.freeyourgadget.gadgetbridge.GBDevice; + +public class SetDeviceBusyAction extends PlainAction { + private final GBDevice device; + private final Context context; + private final String busyTask; + + /** + * When run, will mark the device as busy (or not busy). + * @param device the device to mark + * @param busyTask the task name to set as busy task, or null to mark as not busy + * @param context + */ + public SetDeviceBusyAction(GBDevice device, String busyTask, Context context) { + this.device = device; + this.busyTask = busyTask; + this.context = context; + } + + @Override + public boolean run(BluetoothGatt gatt) { + device.setBusyTask(busyTask); + device.sendDeviceUpdateIntent(context); + return true; + } + + @Override + public String toString() { + return getClass().getName() + ": " + busyTask; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/btle/WaitAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/btle/WaitAction.java index f8e4484d..29fb412e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/btle/WaitAction.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/btle/WaitAction.java @@ -2,7 +2,7 @@ package nodomain.freeyourgadget.gadgetbridge.btle; import android.bluetooth.BluetoothGatt; -public class WaitAction extends BtLEAction { +public class WaitAction extends PlainAction { private int mMillis; @@ -19,10 +19,4 @@ public class WaitAction extends BtLEAction { return false; } } - - @Override - public boolean expectsResult() { - // no BT communication at all, no result - return false; - } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/MiBandService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/MiBandService.java index 633be8ea..a181c692 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/MiBandService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/MiBandService.java @@ -132,12 +132,13 @@ public class MiBandService { public static final byte COMMAND_STOP_MOTOR_VIBRATE = 0x13; public static final byte COMMAND_CONFIRM_ACTIVITY_DATA_TRANSFER_COMPLETE = 0xa; + + public static final byte COMMAND_FETCH_DATA = 0x6; /* public static final byte COMMAND_FACTORY_RESET = 0x9t; - public static final byte COMMAND_FETCH_DATA = 0x6t; public static final byte COMMAND_GET_SENSOR_DATA = 0x12t diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/MiBandSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/MiBandSupport.java index 08b461cb..e4e1c925 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/MiBandSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/MiBandSupport.java @@ -20,7 +20,9 @@ import nodomain.freeyourgadget.gadgetbridge.GBActivitySample; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBCommand; import nodomain.freeyourgadget.gadgetbridge.GBDevice.State; +import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.btle.AbstractBTLEDeviceSupport; +import nodomain.freeyourgadget.gadgetbridge.btle.SetDeviceBusyAction; import nodomain.freeyourgadget.gadgetbridge.btle.TransactionBuilder; import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.DEFAULT_VALUE_FLASH_COLOUR; @@ -147,10 +149,10 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport { builder.queue(getQueue()); } - private static final byte[] startVibrate = new byte[]{MiBandService.COMMAND_SEND_NOTIFICATION, 1}; - private static final byte[] stopVibrate = new byte[]{MiBandService.COMMAND_STOP_MOTOR_VIBRATE}; - private static final byte[] reboot = new byte[]{MiBandService.COMMAND_REBOOT}; - private static final byte[] fetch = new byte[]{6}; + private static final byte[] startVibrate = new byte[] { MiBandService.COMMAND_SEND_NOTIFICATION, 1 }; + private static final byte[] stopVibrate = new byte[] { MiBandService.COMMAND_STOP_MOTOR_VIBRATE }; + private static final byte[] reboot = new byte[]{ MiBandService.COMMAND_REBOOT }; + private static final byte[] fetch = new byte[]{ MiBandService.COMMAND_FETCH_DATA }; private byte[] getNotification(long vibrateDuration, int vibrateTimes, int flashTimes, int flashColour, int originalColour, long flashDuration) { byte[] vibrate = new byte[]{MiBandService.COMMAND_SEND_NOTIFICATION, (byte) 1}; @@ -357,8 +359,20 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport { @Override public void onReboot() { + try { + TransactionBuilder builder = performInitialized("Reboot"); + builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT), reboot); + builder.queue(getQueue()); + } catch (IOException ex) { + LOG.error("Unable to reboot MI", ex); + } + } + + @Override + public void onSynchronizeActivityData() { try { TransactionBuilder builder = performInitialized("fetch activity data"); + builder.add(new SetDeviceBusyAction(getDevice(), getContext().getString(R.string.busy_task_fetch_activity_data), getContext())); builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT), fetch); builder.queue(getQueue()); } catch (IOException ex) { @@ -487,7 +501,7 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport { flushActivityDataHolder(); } } else { - // the lenght of the chunk is not what we expect. We need to make sense of this data + // the length of the chunk is not what we expect. We need to make sense of this data LOG.warn("GOT UNEXPECTED ACTIVITY DATA WITH LENGTH: " + value.length + ", EXPECTED LENGTH: " + this.activityDataRemainingBytes); for (byte b: value){ LOG.warn("DATA: " + String.format("0x%8x", b)); @@ -530,11 +544,31 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport { LOG.warn("Could not write to the control point."); } LOG.info("handleControlPoint got status:" + status); + + if (getDevice().isBusy()) { + if (isActivityDataSyncFinished(value)) { + unsetBusy(); + } + } for (byte b: value){ LOG.info("handleControlPoint GOT DATA:" + String.format("0x%8x", b)); } - } + + private boolean isActivityDataSyncFinished(byte[] value) { + if (value.length == 9) { + if (value[0] == 0xa && value[1] == 0xf && value[2] == 5 && value[7] == 0 && value[8] == 0) { + return true; + } + } + return false; + } + + private void unsetBusy() { + getDevice().unsetBusyTask(); + getDevice().sendDeviceUpdateIntent(getContext()); + } + private void sendAckDataTransfer(Calendar time, int bytesTransferred) { byte[] ack = new byte[]{ MiBandService.COMMAND_CONFIRM_ACTIVITY_DATA_TRANSFER_COMPLETE, diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/SetDeviceStateAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/SetDeviceStateAction.java index 673e2a46..194d9e54 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/SetDeviceStateAction.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/SetDeviceStateAction.java @@ -5,8 +5,9 @@ import android.content.Context; import nodomain.freeyourgadget.gadgetbridge.GBDevice; import nodomain.freeyourgadget.gadgetbridge.btle.BtLEAction; +import nodomain.freeyourgadget.gadgetbridge.btle.PlainAction; -public class SetDeviceStateAction extends BtLEAction { +public class SetDeviceStateAction extends PlainAction { private final GBDevice device; private final GBDevice.State deviceState; private final Context context; @@ -24,11 +25,6 @@ public class SetDeviceStateAction extends BtLEAction { return true; } - @Override - public boolean expectsResult() { - return false; - } - public Context getContext() { return context; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/protocol/GBDeviceProtocol.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/protocol/GBDeviceProtocol.java index 8bc295f5..f91e777c 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/protocol/GBDeviceProtocol.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/protocol/GBDeviceProtocol.java @@ -54,6 +54,10 @@ public abstract class GBDeviceProtocol { return null; } + public byte[] encodeSynchronizeActivityData() { + return null; + } + public byte[] encodeReboot() { return null; } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 52d30e88..70025e0c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -105,5 +105,6 @@ LogToFile Write Log Files (needs restart) initializing + Fetching Activity Data