1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-11-28 12:56:49 +01:00

Huami devices: Fix seldom fetch failures (better support for app level ble feedback)

Fixes #1264
This commit is contained in:
cpfeiffer 2018-09-14 20:11:27 +02:00
parent b6d68207cb
commit d6f5e36e12
9 changed files with 132 additions and 15 deletions

View File

@ -108,6 +108,16 @@ public abstract class AbstractBTLEOperation<T extends AbstractBTLEDeviceSupport>
return builder; return builder;
} }
public TransactionBuilder createTransactionBuilder(String taskName) {
TransactionBuilder builder = getSupport().createTransactionBuilder(taskName);
builder.setGattCallback(this);
return builder;
}
public void performImmediately(TransactionBuilder builder) throws IOException {
mSupport.performImmediately(builder);
}
protected Context getContext() { protected Context getContext() {
return mSupport.getContext(); return mSupport.getContext();
} }

View File

@ -107,6 +107,11 @@ public final class BtLEQueue {
if (LOG.isDebugEnabled()) { if (LOG.isDebugEnabled()) {
LOG.debug("About to run action: " + action); LOG.debug("About to run action: " + action);
} }
if (action instanceof GattListenerAction) {
// this special action overwrites the transaction gatt listener (if any), it must
// always be the last action in the transaction
internalGattCallback.setTransactionGattCallback(((GattListenerAction)action).getGattCallback());
}
if (action.run(mBluetoothGatt)) { if (action.run(mBluetoothGatt)) {
// check again, maybe due to some condition, action did not need to write, so we can't wait // check again, maybe due to some condition, action did not need to write, so we can't wait
boolean waitForResult = action.expectsResult(); boolean waitForResult = action.expectsResult();

View File

@ -0,0 +1,5 @@
package nodomain.freeyourgadget.gadgetbridge.service.btle;
public interface GattListenerAction {
GattCallback getGattCallback();
}

View File

@ -110,4 +110,8 @@ public class TransactionBuilder {
public Transaction getTransaction() { public Transaction getTransaction() {
return mTransaction; return mTransaction;
} }
public String getTaskName() {
return mTransaction.getTaskName();
}
} }

View File

@ -0,0 +1,33 @@
package nodomain.freeyourgadget.gadgetbridge.service.btle.actions;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import java.util.Objects;
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractGattCallback;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEQueue;
import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCallback;
import nodomain.freeyourgadget.gadgetbridge.service.btle.GattListenerAction;
public abstract class AbstractGattListenerWriteAction extends WriteAction implements GattListenerAction {
private final BtLEQueue queue;
public AbstractGattListenerWriteAction(BtLEQueue queue, BluetoothGattCharacteristic characteristic, byte[] value) {
super(characteristic, value);
this.queue = queue;
Objects.requireNonNull(queue, "queue must not be null");
}
@Override
public GattCallback getGattCallback() {
return new AbstractGattCallback() {
@Override
public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
return AbstractGattListenerWriteAction.this.onCharacteristicChanged(gatt, characteristic);
}
};
}
protected abstract boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic);
}

View File

@ -18,6 +18,11 @@ package nodomain.freeyourgadget.gadgetbridge.service.btle.actions;
import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGatt;
/**
* An action that will cause the queue to sleep for the specified time.
* Note that this is usually a bad idea, since it will not be able to process messages
* during that time. It is also likely to cause race conditions.
*/
public class WaitAction extends PlainAction { public class WaitAction extends PlainAction {
private final int mMillis; private final int mMillis;

View File

@ -16,6 +16,8 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. */ along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip.operations; package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip.operations;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.widget.Toast; import android.widget.Toast;
@ -30,17 +32,21 @@ import java.util.Calendar;
import java.util.Date; 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.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbip.AmazfitBipService; import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbip.AmazfitBipService;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService; import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions; import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.AbstractGattListenerWriteAction;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.WaitAction; import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.WaitAction;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip.AmazfitBipSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip.AmazfitBipSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.AbstractFetchOperation; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.AbstractFetchOperation;
import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils; import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
public class AmazfitBipFetchLogsOperation extends AbstractFetchOperation { public class AmazfitBipFetchLogsOperation extends AbstractFetchOperation {
private static final Logger LOG = LoggerFactory.getLogger(AmazfitBipFetchLogsOperation.class); private static final Logger LOG = LoggerFactory.getLogger(AmazfitBipFetchLogsOperation.class);
@ -72,15 +78,38 @@ public class AmazfitBipFetchLogsOperation extends AbstractFetchOperation {
return; return;
} }
final String taskName = StringUtils.ensureNotNull(builder.getTaskName());
GregorianCalendar sinceWhen = BLETypeConversions.createCalendar(); GregorianCalendar sinceWhen = BLETypeConversions.createCalendar();
sinceWhen.add(Calendar.DAY_OF_MONTH, -10); sinceWhen.add(Calendar.DAY_OF_MONTH, -10);
builder.write(characteristicFetch, BLETypeConversions.join(new byte[]{ byte[] fetchBytes = BLETypeConversions.join(new byte[]{
HuamiService.COMMAND_ACTIVITY_DATA_START_DATE, HuamiService.COMMAND_ACTIVITY_DATA_START_DATE,
AmazfitBipService.COMMAND_ACTIVITY_DATA_TYPE_DEBUGLOGS}, AmazfitBipService.COMMAND_ACTIVITY_DATA_TYPE_DEBUGLOGS},
getSupport().getTimeBytes(sinceWhen, TimeUnit.MINUTES))); getSupport().getTimeBytes(sinceWhen, TimeUnit.MINUTES));
builder.add(new WaitAction(1000)); // TODO: actually wait for the success-reply builder.add(new AbstractGattListenerWriteAction(getQueue(), characteristicFetch, fetchBytes) {
builder.notify(characteristicActivityData, true); @Override
builder.write(characteristicFetch, new byte[]{HuamiService.COMMAND_FETCH_DATA}); protected boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
UUID characteristicUUID = characteristic.getUuid();
if (HuamiService.UUID_UNKNOWN_CHARACTERISTIC4.equals(characteristicUUID)) {
byte[] value = characteristic.getValue();
if (ArrayUtils.equals(value, HuamiService.RESPONSE_ACTIVITY_DATA_START_DATE_SUCCESS, 0)) {
handleActivityMetadata(value);
TransactionBuilder newBuilder = createTransactionBuilder(taskName + " Step 2");
newBuilder.notify(characteristicActivityData, true);
newBuilder.write(characteristicFetch, new byte[]{HuamiService.COMMAND_FETCH_DATA});
try {
performImmediately(newBuilder);
} catch (IOException ex) {
GB.toast(getContext(), "Error fetching debug logs: " + ex.getMessage(), Toast.LENGTH_LONG, GB.ERROR, ex);
}
return true;
} else {
handleActivityMetadata(value);
}
}
return false;
}
});
} }
@Override @Override

View File

@ -16,6 +16,8 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. */ along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations; package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.text.format.DateUtils; import android.text.format.DateUtils;
import android.widget.Toast; import android.widget.Toast;
@ -24,9 +26,11 @@ import org.slf4j.LoggerFactory;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar; import java.util.Calendar;
import java.util.GregorianCalendar; import java.util.GregorianCalendar;
import java.util.List; import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBApplication;
@ -34,6 +38,7 @@ import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper; import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService; import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbip.AmazfitBipService;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandSampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device; import nodomain.freeyourgadget.gadgetbridge.entities.Device;
@ -41,10 +46,14 @@ import nodomain.freeyourgadget.gadgetbridge.entities.MiBandActivitySample;
import nodomain.freeyourgadget.gadgetbridge.entities.User; import nodomain.freeyourgadget.gadgetbridge.entities.User;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions; import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.AbstractGattListenerWriteAction;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.PlainAction;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.WaitAction; import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.WaitAction;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils; import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
/** /**
* An operation that fetches activity data. For every fetch, a new operation must * An operation that fetches activity data. For every fetch, a new operation must
@ -68,11 +77,34 @@ public class FetchActivityOperation extends AbstractFetchOperation {
@Override @Override
protected void startFetching(TransactionBuilder builder) { protected void startFetching(TransactionBuilder builder) {
final String taskName = StringUtils.ensureNotNull(builder.getTaskName());
GregorianCalendar sinceWhen = getLastSuccessfulSyncTime(); GregorianCalendar sinceWhen = getLastSuccessfulSyncTime();
builder.write(characteristicFetch, BLETypeConversions.join(new byte[] { HuamiService.COMMAND_ACTIVITY_DATA_START_DATE, HuamiService.COMMAND_ACTIVITY_DATA_TYPE_ACTIVTY }, getSupport().getTimeBytes(sinceWhen, TimeUnit.MINUTES))); byte[] fetchBytes = BLETypeConversions.join(new byte[] { HuamiService.COMMAND_ACTIVITY_DATA_START_DATE, HuamiService.COMMAND_ACTIVITY_DATA_TYPE_ACTIVTY }, getSupport().getTimeBytes(sinceWhen, TimeUnit.MINUTES));
builder.add(new WaitAction(1000)); // TODO: actually wait for the success-reply builder.add(new AbstractGattListenerWriteAction(getQueue(), characteristicFetch, fetchBytes) {
builder.notify(characteristicActivityData, true); @Override
builder.write(characteristicFetch, new byte[] { HuamiService.COMMAND_FETCH_DATA}); protected boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
UUID characteristicUUID = characteristic.getUuid();
if (HuamiService.UUID_UNKNOWN_CHARACTERISTIC4.equals(characteristicUUID)) {
byte[] value = characteristic.getValue();
if (ArrayUtils.equals(value, HuamiService.RESPONSE_ACTIVITY_DATA_START_DATE_SUCCESS, 0)) {
handleActivityMetadata(value);
TransactionBuilder newBuilder = createTransactionBuilder(taskName + " Step 2");
newBuilder.notify(characteristicActivityData, true);
newBuilder.write(characteristicFetch, new byte[] { HuamiService.COMMAND_FETCH_DATA});
try {
performImmediately(newBuilder);
} catch (IOException ex) {
GB.toast(getContext(), "Error fetching activity data: " + ex.getMessage(), Toast.LENGTH_LONG, GB.ERROR, ex);
}
return true;
} else {
handleActivityMetadata(value);
}
}
return false;
}
});
} }
protected void handleActivityFetchFinish(boolean success) { protected void handleActivityFetchFinish(boolean success) {

View File

@ -137,12 +137,6 @@ public class InitOperation extends AbstractBTLEOperation<HuamiSupport> {
} }
} }
private TransactionBuilder createTransactionBuilder(String task) {
TransactionBuilder builder = getSupport().createTransactionBuilder(task);
builder.setGattCallback(this);
return builder;
}
private byte[] getMD5(byte[] message) throws NoSuchAlgorithmException { private byte[] getMD5(byte[] message) throws NoSuchAlgorithmException {
MessageDigest md5 = MessageDigest.getInstance("MD5"); MessageDigest md5 = MessageDigest.getInstance("MD5");
return md5.digest(message); return md5.digest(message);