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)
live-sensor-data
cpfeiffer 2015-06-06 00:40:16 +02:00
parent 2f0d00d645
commit 9e4e50be47
13 changed files with 155 additions and 31 deletions

View File

@ -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();

View File

@ -27,6 +27,8 @@ public interface EventHandler {
void onPhoneVersion(byte os);
void onSynchronizeActivityData();
void onReboot();
}

View File

@ -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
}
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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

View File

@ -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,

View File

@ -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;
}

View File

@ -54,6 +54,10 @@ public abstract class GBDeviceProtocol {
return null;
}
public byte[] encodeSynchronizeActivityData() {
return null;
}
public byte[] encodeReboot() {
return null;
}

View File

@ -105,5 +105,6 @@
<string name="pref_log_to_file">LogToFile</string>
<string name="pref_write_logfiles">Write Log Files (needs restart)</string>
<string name="initializing">initializing</string>
<string name="busy_task_fetch_activity_data">Fetching Activity Data</string>
</resources>