From 1a492054cab59ff9ce5f02a0d77b6eb1eeece193 Mon Sep 17 00:00:00 2001 From: Martin WIRTH Date: Mon, 15 Jan 2018 11:37:13 +0100 Subject: [PATCH 01/18] Added retry mechanism parameter for LongWriteOperation --- .../mockrxandroidble/RxBleConnectionMock.java | 9 +++++ .../polidea/rxandroidble/RxBleConnection.java | 11 ++++++ .../LongWriteOperationBuilderImpl.java | 10 ++++- .../internal/connection/NoRetryStrategy.java | 11 ++++++ .../CharacteristicLongWriteOperation.java | 37 ++++++++++++++++++- .../operations/OperationsProvider.java | 1 + .../operations/OperationsProviderImpl.java | 2 + 7 files changed, 79 insertions(+), 2 deletions(-) create mode 100644 rxandroidble/src/main/java/com/polidea/rxandroidble/internal/connection/NoRetryStrategy.java diff --git a/mockrxandroidble/src/main/java/com/polidea/rxandroidble/mockrxandroidble/RxBleConnectionMock.java b/mockrxandroidble/src/main/java/com/polidea/rxandroidble/mockrxandroidble/RxBleConnectionMock.java index 979257ba6..a8f89cb6c 100644 --- a/mockrxandroidble/src/main/java/com/polidea/rxandroidble/mockrxandroidble/RxBleConnectionMock.java +++ b/mockrxandroidble/src/main/java/com/polidea/rxandroidble/mockrxandroidble/RxBleConnectionMock.java @@ -10,6 +10,7 @@ import com.polidea.rxandroidble.RxBleDeviceServices; import com.polidea.rxandroidble.exceptions.BleConflictingNotificationAlreadySetException; import com.polidea.rxandroidble.internal.connection.ImmediateSerializedBatchAckStrategy; +import com.polidea.rxandroidble.internal.connection.NoRetryStrategy; import com.polidea.rxandroidble.internal.util.ObservableUtil; import java.util.HashMap; @@ -283,6 +284,8 @@ public LongWriteOperationBuilder createNewLongWriteBuilder() { private WriteOperationAckStrategy writeOperationAckStrategy = // default new ImmediateSerializedBatchAckStrategy(); + private WriteOperationRetryStrategy writeOperationRetryStrategy = // default + new NoRetryStrategy(); @Override public LongWriteOperationBuilder setBytes(@NonNull byte[] bytes) { @@ -316,6 +319,12 @@ public LongWriteOperationBuilder setMaxBatchSize(int maxBatchSize) { return this; } + @Override + public LongWriteOperationBuilder setWriteOperationRetryStrategy(@NonNull WriteOperationRetryStrategy writeOperationRetryStrategy) { + this.writeOperationRetryStrategy = writeOperationRetryStrategy; + return this; + } + @Override public LongWriteOperationBuilder setWriteOperationAckStrategy(@NonNull WriteOperationAckStrategy writeOperationAckStrategy) { this.writeOperationAckStrategy = writeOperationAckStrategy; diff --git a/rxandroidble/src/main/java/com/polidea/rxandroidble/RxBleConnection.java b/rxandroidble/src/main/java/com/polidea/rxandroidble/RxBleConnection.java index ea8e8f08b..0021be950 100644 --- a/rxandroidble/src/main/java/com/polidea/rxandroidble/RxBleConnection.java +++ b/rxandroidble/src/main/java/com/polidea/rxandroidble/RxBleConnection.java @@ -27,6 +27,7 @@ import rx.Completable; import rx.Observable; import rx.Scheduler; +import rx.functions.Func2; /** * The BLE connection handle, supporting GATT operations. Operations are enqueued and the library makes sure that they are not @@ -131,6 +132,13 @@ interface LongWriteOperationBuilder { */ LongWriteOperationBuilder setMaxBatchSize(@IntRange(from = 1, to = GATT_MTU_MAXIMUM - GATT_WRITE_MTU_OVERHEAD) int maxBatchSize); + /** + * Setter for a retry strategy in case something goes wrong when writing data. + * @param writeOperationRetryStrategy the retry strategy + * @return the LongWriteOperationBuilder + */ + LongWriteOperationBuilder setWriteOperationRetryStrategy(@NonNull WriteOperationRetryStrategy writeOperationRetryStrategy); + /** * Setter for a strategy used to mark batch write completed. Only after previous batch has finished, the next (if any left) can be * written. @@ -442,6 +450,9 @@ Observable writeDescriptor(@NonNull UUID serviceUuid, @NonNull UUID char */ Observable writeDescriptor(@NonNull BluetoothGattDescriptor descriptor, @NonNull byte[] data); + interface WriteOperationRetryStrategy extends Func2 { + + } /** * Performs a GATT request connection priority operation, which requests a connection parameter diff --git a/rxandroidble/src/main/java/com/polidea/rxandroidble/internal/connection/LongWriteOperationBuilderImpl.java b/rxandroidble/src/main/java/com/polidea/rxandroidble/internal/connection/LongWriteOperationBuilderImpl.java index c71c15bd0..17ab674d6 100644 --- a/rxandroidble/src/main/java/com/polidea/rxandroidble/internal/connection/LongWriteOperationBuilderImpl.java +++ b/rxandroidble/src/main/java/com/polidea/rxandroidble/internal/connection/LongWriteOperationBuilderImpl.java @@ -23,6 +23,7 @@ public final class LongWriteOperationBuilderImpl implements RxBleConnection.Long private Observable writtenCharacteristicObservable; private PayloadSizeLimitProvider maxBatchSizeProvider; private RxBleConnection.WriteOperationAckStrategy writeOperationAckStrategy = new ImmediateSerializedBatchAckStrategy(); + private RxBleConnection.WriteOperationRetryStrategy writeOperationRetryStrategy = new NoRetryStrategy(); private byte[] bytes; @@ -63,6 +64,13 @@ public RxBleConnection.LongWriteOperationBuilder setMaxBatchSize(final int maxBa return this; } + @Override + public RxBleConnection.LongWriteOperationBuilder setWriteOperationRetryStrategy( + @NonNull RxBleConnection.WriteOperationRetryStrategy writeOperationRetryStrategy) { + this.writeOperationRetryStrategy = writeOperationRetryStrategy; + return this; + } + @Override public RxBleConnection.LongWriteOperationBuilder setWriteOperationAckStrategy( @NonNull RxBleConnection.WriteOperationAckStrategy writeOperationAckStrategy) { @@ -87,7 +95,7 @@ public Observable build() { public Observable call(BluetoothGattCharacteristic bluetoothGattCharacteristic) { return operationQueue.queue( operationsProvider.provideLongWriteOperation(bluetoothGattCharacteristic, - writeOperationAckStrategy, maxBatchSizeProvider, bytes) + writeOperationAckStrategy, writeOperationRetryStrategy, maxBatchSizeProvider, bytes) ); } }); diff --git a/rxandroidble/src/main/java/com/polidea/rxandroidble/internal/connection/NoRetryStrategy.java b/rxandroidble/src/main/java/com/polidea/rxandroidble/internal/connection/NoRetryStrategy.java new file mode 100644 index 000000000..da771af8b --- /dev/null +++ b/rxandroidble/src/main/java/com/polidea/rxandroidble/internal/connection/NoRetryStrategy.java @@ -0,0 +1,11 @@ +package com.polidea.rxandroidble.internal.connection; + +import com.polidea.rxandroidble.RxBleConnection; + +public class NoRetryStrategy implements RxBleConnection.WriteOperationRetryStrategy { + + @Override + public Boolean call(Integer integer, Throwable throwable) { + return false; + } +} diff --git a/rxandroidble/src/main/java/com/polidea/rxandroidble/internal/operations/CharacteristicLongWriteOperation.java b/rxandroidble/src/main/java/com/polidea/rxandroidble/internal/operations/CharacteristicLongWriteOperation.java index a108c0604..65589f4a2 100644 --- a/rxandroidble/src/main/java/com/polidea/rxandroidble/internal/operations/CharacteristicLongWriteOperation.java +++ b/rxandroidble/src/main/java/com/polidea/rxandroidble/internal/operations/CharacteristicLongWriteOperation.java @@ -8,6 +8,7 @@ import com.polidea.rxandroidble.ClientComponent; import com.polidea.rxandroidble.RxBleConnection.WriteOperationAckStrategy; +import com.polidea.rxandroidble.RxBleConnection.WriteOperationRetryStrategy; import com.polidea.rxandroidble.exceptions.BleDisconnectedException; import com.polidea.rxandroidble.exceptions.BleException; import com.polidea.rxandroidble.exceptions.BleGattCallbackTimeoutException; @@ -33,6 +34,7 @@ import rx.functions.Action0; import rx.functions.Action1; import rx.functions.Func1; +import rx.functions.Func2; public class CharacteristicLongWriteOperation extends QueueOperation { @@ -43,8 +45,10 @@ public class CharacteristicLongWriteOperation extends QueueOperation { private final BluetoothGattCharacteristic bluetoothGattCharacteristic; private final PayloadSizeLimitProvider batchSizeProvider; private final WriteOperationAckStrategy writeOperationAckStrategy; + private final WriteOperationRetryStrategy writeOperationRetryStrategy; private final byte[] bytesToWrite; private byte[] tempBatchArray; + private int retryCounter; CharacteristicLongWriteOperation( BluetoothGatt bluetoothGatt, @@ -54,6 +58,7 @@ public class CharacteristicLongWriteOperation extends QueueOperation { BluetoothGattCharacteristic bluetoothGattCharacteristic, PayloadSizeLimitProvider batchSizeProvider, WriteOperationAckStrategy writeOperationAckStrategy, + WriteOperationRetryStrategy writeOperationRetryStrategy, byte[] bytesToWrite) { this.bluetoothGatt = bluetoothGatt; this.rxBleGattCallback = rxBleGattCallback; @@ -62,12 +67,14 @@ public class CharacteristicLongWriteOperation extends QueueOperation { this.bluetoothGattCharacteristic = bluetoothGattCharacteristic; this.batchSizeProvider = batchSizeProvider; this.writeOperationAckStrategy = writeOperationAckStrategy; + this.writeOperationRetryStrategy = writeOperationRetryStrategy; this.bytesToWrite = bytesToWrite; + this.retryCounter = 0; } @Override protected void protectedRun(final Emitter emitter, final QueueReleaseInterface queueReleaseInterface) throws Throwable { - int batchSize = batchSizeProvider.getPayloadSizeLimit(); + final int batchSize = batchSizeProvider.getPayloadSizeLimit(); if (batchSize <= 0) { throw new IllegalArgumentException("batchSizeProvider value must be greater than zero (now: " + batchSize + ")"); @@ -87,9 +94,11 @@ protected void protectedRun(final Emitter emitter, final QueueReleaseInt timeoutObservable, timeoutConfiguration.timeoutScheduler ) + .doOnNext(resetRetryCounter()) .repeatWhen(bufferIsNotEmptyAndOperationHasBeenAcknowledgedAndNotUnsubscribed( writeOperationAckStrategy, byteBuffer, emitterWrapper )) + .retry(retryStrategy(byteBuffer, batchSize)) .toCompletable() .subscribe( new Action0() { @@ -209,4 +218,30 @@ public Boolean call(Object emission) { } }; } + + private Action1> resetRetryCounter() { + return new Action1>() { + @Override + public void call(ByteAssociation uuidByteAssociation) { + retryCounter = 0; + } + }; + } + + private Func2 retryStrategy(final ByteBuffer byteBuffer, final int batchSize) { + return new Func2() { + @Override + public Boolean call(Integer integer, Throwable throwable) { + // Individual counter for each batch payload. + retryCounter++; + final Boolean retry = writeOperationRetryStrategy.call(retryCounter, throwable); + if (!retry) { + return false; + } + // Reset buffer to last position + byteBuffer.position(byteBuffer.position() - batchSize); + return true; + } + }; + } } diff --git a/rxandroidble/src/main/java/com/polidea/rxandroidble/internal/operations/OperationsProvider.java b/rxandroidble/src/main/java/com/polidea/rxandroidble/internal/operations/OperationsProvider.java index 4526533d5..19d11dd9f 100644 --- a/rxandroidble/src/main/java/com/polidea/rxandroidble/internal/operations/OperationsProvider.java +++ b/rxandroidble/src/main/java/com/polidea/rxandroidble/internal/operations/OperationsProvider.java @@ -15,6 +15,7 @@ public interface OperationsProvider { CharacteristicLongWriteOperation provideLongWriteOperation( BluetoothGattCharacteristic bluetoothGattCharacteristic, RxBleConnection.WriteOperationAckStrategy writeOperationAckStrategy, + RxBleConnection.WriteOperationRetryStrategy writeOperationRetryStrategy, PayloadSizeLimitProvider maxBatchSizeProvider, byte[] bytes); diff --git a/rxandroidble/src/main/java/com/polidea/rxandroidble/internal/operations/OperationsProviderImpl.java b/rxandroidble/src/main/java/com/polidea/rxandroidble/internal/operations/OperationsProviderImpl.java index 75bfe2069..6d2e246dd 100644 --- a/rxandroidble/src/main/java/com/polidea/rxandroidble/internal/operations/OperationsProviderImpl.java +++ b/rxandroidble/src/main/java/com/polidea/rxandroidble/internal/operations/OperationsProviderImpl.java @@ -49,6 +49,7 @@ public class OperationsProviderImpl implements OperationsProvider { public CharacteristicLongWriteOperation provideLongWriteOperation( BluetoothGattCharacteristic bluetoothGattCharacteristic, RxBleConnection.WriteOperationAckStrategy writeOperationAckStrategy, + RxBleConnection.WriteOperationRetryStrategy writeOperationRetryStrategy, PayloadSizeLimitProvider maxBatchSizeProvider, byte[] bytes) { @@ -59,6 +60,7 @@ public CharacteristicLongWriteOperation provideLongWriteOperation( bluetoothGattCharacteristic, maxBatchSizeProvider, writeOperationAckStrategy, + writeOperationRetryStrategy, bytes); } From e752af47e92886d168ee9dc4b25be8c907751bb9 Mon Sep 17 00:00:00 2001 From: Martin WIRTH Date: Thu, 18 Jan 2018 19:23:33 +0100 Subject: [PATCH 02/18] Checkstyle --- .../rxandroidble/mockrxandroidble/RxBleConnectionMock.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mockrxandroidble/src/main/java/com/polidea/rxandroidble/mockrxandroidble/RxBleConnectionMock.java b/mockrxandroidble/src/main/java/com/polidea/rxandroidble/mockrxandroidble/RxBleConnectionMock.java index a8f89cb6c..9813aee9c 100644 --- a/mockrxandroidble/src/main/java/com/polidea/rxandroidble/mockrxandroidble/RxBleConnectionMock.java +++ b/mockrxandroidble/src/main/java/com/polidea/rxandroidble/mockrxandroidble/RxBleConnectionMock.java @@ -320,7 +320,8 @@ public LongWriteOperationBuilder setMaxBatchSize(int maxBatchSize) { } @Override - public LongWriteOperationBuilder setWriteOperationRetryStrategy(@NonNull WriteOperationRetryStrategy writeOperationRetryStrategy) { + public LongWriteOperationBuilder setWriteOperationRetryStrategy( + @NonNull WriteOperationRetryStrategy writeOperationRetryStrategy) { this.writeOperationRetryStrategy = writeOperationRetryStrategy; return this; } From d6f113e478bdd5ec6e76c0547bab489318830c4f Mon Sep 17 00:00:00 2001 From: Martin WIRTH Date: Mon, 22 Jan 2018 16:28:55 +0100 Subject: [PATCH 03/18] Change retry mechanism (#352) --- .../polidea/rxandroidble/RxBleConnection.java | 57 +++++++++++++++-- .../internal/connection/NoRetryStrategy.java | 12 +++- .../CharacteristicLongWriteOperation.java | 63 +++++++++++-------- 3 files changed, 100 insertions(+), 32 deletions(-) diff --git a/rxandroidble/src/main/java/com/polidea/rxandroidble/RxBleConnection.java b/rxandroidble/src/main/java/com/polidea/rxandroidble/RxBleConnection.java index 0021be950..de73edcae 100644 --- a/rxandroidble/src/main/java/com/polidea/rxandroidble/RxBleConnection.java +++ b/rxandroidble/src/main/java/com/polidea/rxandroidble/RxBleConnection.java @@ -12,6 +12,7 @@ import com.polidea.rxandroidble.exceptions.BleCannotSetCharacteristicNotificationException; import com.polidea.rxandroidble.exceptions.BleCharacteristicNotFoundException; import com.polidea.rxandroidble.exceptions.BleConflictingNotificationAlreadySetException; +import com.polidea.rxandroidble.exceptions.BleException; import com.polidea.rxandroidble.exceptions.BleGattCannotStartException; import com.polidea.rxandroidble.exceptions.BleGattException; import com.polidea.rxandroidble.exceptions.BleGattOperationType; @@ -27,7 +28,7 @@ import rx.Completable; import rx.Observable; import rx.Scheduler; -import rx.functions.Func2; +import rx.functions.Func1; /** * The BLE connection handle, supporting GATT operations. Operations are enqueued and the library makes sure that they are not @@ -134,6 +135,17 @@ interface LongWriteOperationBuilder { /** * Setter for a retry strategy in case something goes wrong when writing data. + * If any {@link BleException} is raised, a {@link WriteOperationRetryStrategy.LongWriteFailure} object will be emitted. + * {@link WriteOperationRetryStrategy.LongWriteFailure} contains both the {@link BleException} and the batch number + * for which the write request failed. + * The {@link WriteOperationRetryStrategy.LongWriteFailure} emitted by the writeOperationRetryStrategy will be used to retry + * the specified batch number write request. + * + * If this is not specified - the next batch of bytes is written right after the failed one, and the failed one is just dropped. + * + * It is expected that the Observable returned from the writeOperationRetryStrategy will emit exactly the same events as the source, + * however you may delay them at your pace. + * * @param writeOperationRetryStrategy the retry strategy * @return the LongWriteOperationBuilder */ @@ -168,6 +180,45 @@ interface LongWriteOperationBuilder { Observable build(); } + interface WriteOperationRetryStrategy extends Func1, + Observable> { + + class LongWriteFailure { + + final int batchNumber; + final BleException cause; + + /** + * Default constructor + * + * @param batchNumber the batch number on which the write request failed + * @param cause the failed cause of the write request + */ + public LongWriteFailure(int batchNumber, BleException cause) { + this.batchNumber = batchNumber; + this.cause = cause; + } + + /** + * Get the batch number of the failed write request + * + * @return the batch number + */ + public int getBatchNumber() { + return batchNumber; + } + + /** + * Get the failed cause of the write request + * + * @return a {@link BleException} + */ + public BleException getCause() { + return cause; + } + } + } + interface WriteOperationAckStrategy extends Observable.Transformer { } @@ -450,10 +501,6 @@ Observable writeDescriptor(@NonNull UUID serviceUuid, @NonNull UUID char */ Observable writeDescriptor(@NonNull BluetoothGattDescriptor descriptor, @NonNull byte[] data); - interface WriteOperationRetryStrategy extends Func2 { - - } - /** * Performs a GATT request connection priority operation, which requests a connection parameter * update on the remote device. NOTE: peripheral may silently decline request. diff --git a/rxandroidble/src/main/java/com/polidea/rxandroidble/internal/connection/NoRetryStrategy.java b/rxandroidble/src/main/java/com/polidea/rxandroidble/internal/connection/NoRetryStrategy.java index da771af8b..1cbb8dea7 100644 --- a/rxandroidble/src/main/java/com/polidea/rxandroidble/internal/connection/NoRetryStrategy.java +++ b/rxandroidble/src/main/java/com/polidea/rxandroidble/internal/connection/NoRetryStrategy.java @@ -2,10 +2,18 @@ import com.polidea.rxandroidble.RxBleConnection; +import rx.Observable; +import rx.functions.Func1; + public class NoRetryStrategy implements RxBleConnection.WriteOperationRetryStrategy { @Override - public Boolean call(Integer integer, Throwable throwable) { - return false; + public Observable call(Observable observable) { + return observable.flatMap(new Func1>() { + @Override + public Observable call(LongWriteFailure longWriteFailure) { + return Observable.error(longWriteFailure.getCause()); + } + }); } } diff --git a/rxandroidble/src/main/java/com/polidea/rxandroidble/internal/operations/CharacteristicLongWriteOperation.java b/rxandroidble/src/main/java/com/polidea/rxandroidble/internal/operations/CharacteristicLongWriteOperation.java index 65589f4a2..d28e60004 100644 --- a/rxandroidble/src/main/java/com/polidea/rxandroidble/internal/operations/CharacteristicLongWriteOperation.java +++ b/rxandroidble/src/main/java/com/polidea/rxandroidble/internal/operations/CharacteristicLongWriteOperation.java @@ -26,7 +26,6 @@ import java.util.UUID; import bleshadow.javax.inject.Named; - import rx.Emitter; import rx.Observable; import rx.Scheduler; @@ -34,7 +33,6 @@ import rx.functions.Action0; import rx.functions.Action1; import rx.functions.Func1; -import rx.functions.Func2; public class CharacteristicLongWriteOperation extends QueueOperation { @@ -48,7 +46,6 @@ public class CharacteristicLongWriteOperation extends QueueOperation { private final WriteOperationRetryStrategy writeOperationRetryStrategy; private final byte[] bytesToWrite; private byte[] tempBatchArray; - private int retryCounter; CharacteristicLongWriteOperation( BluetoothGatt bluetoothGatt, @@ -69,7 +66,6 @@ public class CharacteristicLongWriteOperation extends QueueOperation { this.writeOperationAckStrategy = writeOperationAckStrategy; this.writeOperationRetryStrategy = writeOperationRetryStrategy; this.bytesToWrite = bytesToWrite; - this.retryCounter = 0; } @Override @@ -94,11 +90,10 @@ protected void protectedRun(final Emitter emitter, final QueueReleaseInt timeoutObservable, timeoutConfiguration.timeoutScheduler ) - .doOnNext(resetRetryCounter()) .repeatWhen(bufferIsNotEmptyAndOperationHasBeenAcknowledgedAndNotUnsubscribed( writeOperationAckStrategy, byteBuffer, emitterWrapper )) - .retry(retryStrategy(byteBuffer, batchSize)) + .retryWhen(retryOperationStrategy(writeOperationRetryStrategy, byteBuffer, batchSize)) .toCompletable() .subscribe( new Action0() { @@ -219,28 +214,46 @@ public Boolean call(Object emission) { }; } - private Action1> resetRetryCounter() { - return new Action1>() { + private static Func1, Observable> retryOperationStrategy( + final WriteOperationRetryStrategy writeOperationRetryStrategy, + final ByteBuffer byteBuffer, + final int batchSize) { + return new Func1, Observable>() { @Override - public void call(ByteAssociation uuidByteAssociation) { - retryCounter = 0; + public Observable call(Observable observable) { + return observable + .flatMap(applyRetryStrategy()) + .map(replaceByteBufferPositionForRetry()); } - }; - } - private Func2 retryStrategy(final ByteBuffer byteBuffer, final int batchSize) { - return new Func2() { - @Override - public Boolean call(Integer integer, Throwable throwable) { - // Individual counter for each batch payload. - retryCounter++; - final Boolean retry = writeOperationRetryStrategy.call(retryCounter, throwable); - if (!retry) { - return false; - } - // Reset buffer to last position - byteBuffer.position(byteBuffer.position() - batchSize); - return true; + @NonNull + private Func1> applyRetryStrategy() { + return new Func1>() { + @Override + public Observable call(Throwable cause) { + if (!(cause instanceof BleException)) { + return Observable.error(cause); + } + + final int failedBatchNumber = (byteBuffer.position() - batchSize) / batchSize; + final WriteOperationRetryStrategy.LongWriteFailure longWriteFailure = + new WriteOperationRetryStrategy.LongWriteFailure(failedBatchNumber, (BleException) cause); + + return writeOperationRetryStrategy.call(Observable.just(longWriteFailure)); + } + }; + } + + @NonNull + private Func1 replaceByteBufferPositionForRetry() { + return new Func1() { + @Override + public Object call(WriteOperationRetryStrategy.LongWriteFailure longWriteFailure) { + final int newBufferPosition = longWriteFailure.getBatchNumber() * batchSize; + byteBuffer.position(newBufferPosition); + return longWriteFailure; + } + }; } }; } From ce02a02d84c2b98a0a56102cdc8bcfec77468714 Mon Sep 17 00:00:00 2001 From: Martin WIRTH Date: Tue, 23 Jan 2018 08:57:42 +0100 Subject: [PATCH 04/18] Call WriteOperationRetryStrategy once per subscription (#352) --- .../polidea/rxandroidble/RxBleConnection.java | 5 +- .../CharacteristicLongWriteOperation.java | 50 ++++++++++++------- 2 files changed, 34 insertions(+), 21 deletions(-) diff --git a/rxandroidble/src/main/java/com/polidea/rxandroidble/RxBleConnection.java b/rxandroidble/src/main/java/com/polidea/rxandroidble/RxBleConnection.java index de73edcae..1621c767a 100644 --- a/rxandroidble/src/main/java/com/polidea/rxandroidble/RxBleConnection.java +++ b/rxandroidble/src/main/java/com/polidea/rxandroidble/RxBleConnection.java @@ -14,6 +14,7 @@ import com.polidea.rxandroidble.exceptions.BleConflictingNotificationAlreadySetException; import com.polidea.rxandroidble.exceptions.BleException; import com.polidea.rxandroidble.exceptions.BleGattCannotStartException; +import com.polidea.rxandroidble.exceptions.BleGattCharacteristicException; import com.polidea.rxandroidble.exceptions.BleGattException; import com.polidea.rxandroidble.exceptions.BleGattOperationType; import com.polidea.rxandroidble.internal.Priority; @@ -186,7 +187,7 @@ interface WriteOperationRetryStrategy extends Func1, Observable> retryOperat final int batchSize) { return new Func1, Observable>() { @Override - public Observable call(Observable observable) { - return observable - .flatMap(applyRetryStrategy()) - .map(replaceByteBufferPositionForRetry()); + public Observable call(Observable emittedOnWriteFailure) { + return writeOperationRetryStrategy.call(emittedOnWriteFailure + .flatMap(canRetryError()) + .map(toLongWriteFailureObject()) + ) + .doOnNext(replaceByteBufferPositionForRetry()); } @NonNull - private Func1> applyRetryStrategy() { - return new Func1>() { + private Func1 toLongWriteFailureObject() { + return new Func1() { @Override - public Observable call(Throwable cause) { - if (!(cause instanceof BleException)) { - return Observable.error(cause); - } - + public WriteOperationRetryStrategy.LongWriteFailure call(Throwable throwable) { final int failedBatchNumber = (byteBuffer.position() - batchSize) / batchSize; - final WriteOperationRetryStrategy.LongWriteFailure longWriteFailure = - new WriteOperationRetryStrategy.LongWriteFailure(failedBatchNumber, (BleException) cause); - - return writeOperationRetryStrategy.call(Observable.just(longWriteFailure)); + return new WriteOperationRetryStrategy.LongWriteFailure( + failedBatchNumber, + (BleGattCharacteristicException) throwable + ); } }; } @NonNull - private Func1 replaceByteBufferPositionForRetry() { - return new Func1() { + private Action1 replaceByteBufferPositionForRetry() { + return new Action1() { @Override - public Object call(WriteOperationRetryStrategy.LongWriteFailure longWriteFailure) { + public void call(WriteOperationRetryStrategy.LongWriteFailure longWriteFailure) { final int newBufferPosition = longWriteFailure.getBatchNumber() * batchSize; byteBuffer.position(newBufferPosition); - return longWriteFailure; + } + }; + } + + @NonNull + private Func1> canRetryError() { + return new Func1>() { + @Override + public Observable call(Throwable throwable) { + if (!(throwable instanceof BleGattCharacteristicException)) { + return Observable.error(throwable); + } + return Observable.just(throwable); } }; } From f54f4aee71961e5520edbc19abe723ea5f8a36a8 Mon Sep 17 00:00:00 2001 From: Martin WIRTH Date: Tue, 23 Jan 2018 10:12:56 +0100 Subject: [PATCH 05/18] Checkstyle: remove unused import (#352) --- .../internal/operations/CharacteristicLongWriteOperation.java | 1 - 1 file changed, 1 deletion(-) diff --git a/rxandroidble/src/main/java/com/polidea/rxandroidble/internal/operations/CharacteristicLongWriteOperation.java b/rxandroidble/src/main/java/com/polidea/rxandroidble/internal/operations/CharacteristicLongWriteOperation.java index 1061823cf..735396945 100644 --- a/rxandroidble/src/main/java/com/polidea/rxandroidble/internal/operations/CharacteristicLongWriteOperation.java +++ b/rxandroidble/src/main/java/com/polidea/rxandroidble/internal/operations/CharacteristicLongWriteOperation.java @@ -7,7 +7,6 @@ import android.support.annotation.NonNull; import com.polidea.rxandroidble.ClientComponent; -import com.polidea.rxandroidble.RxBleConnection; import com.polidea.rxandroidble.RxBleConnection.WriteOperationAckStrategy; import com.polidea.rxandroidble.RxBleConnection.WriteOperationRetryStrategy; import com.polidea.rxandroidble.exceptions.BleDisconnectedException; From 563e6751725e0168fa3e0eefa3c225fcb3483520 Mon Sep 17 00:00:00 2001 From: Dariusz Seweryn Date: Thu, 25 Jan 2018 18:33:07 +0100 Subject: [PATCH 06/18] Minor refactoring to use .compose() interface. --- .../polidea/rxandroidble/RxBleConnection.java | 20 +++++++++---------- .../CharacteristicLongWriteOperation.java | 4 ++-- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/rxandroidble/src/main/java/com/polidea/rxandroidble/RxBleConnection.java b/rxandroidble/src/main/java/com/polidea/rxandroidble/RxBleConnection.java index 1621c767a..e4dfa68d6 100644 --- a/rxandroidble/src/main/java/com/polidea/rxandroidble/RxBleConnection.java +++ b/rxandroidble/src/main/java/com/polidea/rxandroidble/RxBleConnection.java @@ -1,5 +1,7 @@ package com.polidea.rxandroidble; +import static com.polidea.rxandroidble.RxBleConnection.WriteOperationRetryStrategy.*; + import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattDescriptor; @@ -29,7 +31,6 @@ import rx.Completable; import rx.Observable; import rx.Scheduler; -import rx.functions.Func1; /** * The BLE connection handle, supporting GATT operations. Operations are enqueued and the library makes sure that they are not @@ -135,15 +136,13 @@ interface LongWriteOperationBuilder { LongWriteOperationBuilder setMaxBatchSize(@IntRange(from = 1, to = GATT_MTU_MAXIMUM - GATT_WRITE_MTU_OVERHEAD) int maxBatchSize); /** - * Setter for a retry strategy in case something goes wrong when writing data. - * If any {@link BleException} is raised, a {@link WriteOperationRetryStrategy.LongWriteFailure} object will be emitted. - * {@link WriteOperationRetryStrategy.LongWriteFailure} contains both the {@link BleException} and the batch number - * for which the write request failed. - * The {@link WriteOperationRetryStrategy.LongWriteFailure} emitted by the writeOperationRetryStrategy will be used to retry - * the specified batch number write request. - * + * Setter for a retry strategy in case something goes wrong when writing data. If any {@link BleException} is raised, + * a {@link LongWriteFailure} object will be emitted. {@link LongWriteFailure} contains both the {@link BleException} and the batch + * number for which the write request failed. The {@link LongWriteFailure} emitted by the writeOperationRetryStrategy will be used + * to retry the specified batch number write request. + *
* If this is not specified - the next batch of bytes is written right after the failed one, and the failed one is just dropped. - * + *
* It is expected that the Observable returned from the writeOperationRetryStrategy will emit exactly the same events as the source, * however you may delay them at your pace. * @@ -181,8 +180,7 @@ interface LongWriteOperationBuilder { Observable build(); } - interface WriteOperationRetryStrategy extends Func1, - Observable> { + interface WriteOperationRetryStrategy extends Observable.Transformer { class LongWriteFailure { diff --git a/rxandroidble/src/main/java/com/polidea/rxandroidble/internal/operations/CharacteristicLongWriteOperation.java b/rxandroidble/src/main/java/com/polidea/rxandroidble/internal/operations/CharacteristicLongWriteOperation.java index 735396945..16b2f5a47 100644 --- a/rxandroidble/src/main/java/com/polidea/rxandroidble/internal/operations/CharacteristicLongWriteOperation.java +++ b/rxandroidble/src/main/java/com/polidea/rxandroidble/internal/operations/CharacteristicLongWriteOperation.java @@ -222,10 +222,10 @@ private static Func1, Observable> retryOperat return new Func1, Observable>() { @Override public Observable call(Observable emittedOnWriteFailure) { - return writeOperationRetryStrategy.call(emittedOnWriteFailure + return emittedOnWriteFailure .flatMap(canRetryError()) .map(toLongWriteFailureObject()) - ) + .compose(writeOperationRetryStrategy) .doOnNext(replaceByteBufferPositionForRetry()); } From 03effa54a7a3e8e2c699d12e065d91fb65b8cbf7 Mon Sep 17 00:00:00 2001 From: Martin WIRTH Date: Mon, 5 Feb 2018 08:46:26 +0100 Subject: [PATCH 07/18] Change methods names and fix issue for batch number retry (#352) --- .../CharacteristicLongWriteOperation.java | 42 +++++++++---------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/rxandroidble/src/main/java/com/polidea/rxandroidble/internal/operations/CharacteristicLongWriteOperation.java b/rxandroidble/src/main/java/com/polidea/rxandroidble/internal/operations/CharacteristicLongWriteOperation.java index 16b2f5a47..98e95b7b0 100644 --- a/rxandroidble/src/main/java/com/polidea/rxandroidble/internal/operations/CharacteristicLongWriteOperation.java +++ b/rxandroidble/src/main/java/com/polidea/rxandroidble/internal/operations/CharacteristicLongWriteOperation.java @@ -94,7 +94,7 @@ protected void protectedRun(final Emitter emitter, final QueueReleaseInt .repeatWhen(bufferIsNotEmptyAndOperationHasBeenAcknowledgedAndNotUnsubscribed( writeOperationAckStrategy, byteBuffer, emitterWrapper )) - .retryWhen(retryOperationStrategy(writeOperationRetryStrategy, byteBuffer, batchSize)) + .retryWhen(errorIsRetryableAndAccordingTo(writeOperationRetryStrategy, byteBuffer, batchSize)) .toCompletable() .subscribe( new Action0() { @@ -215,7 +215,7 @@ public Boolean call(Object emission) { }; } - private static Func1, Observable> retryOperationStrategy( + private static Func1, Observable> errorIsRetryableAndAccordingTo ( final WriteOperationRetryStrategy writeOperationRetryStrategy, final ByteBuffer byteBuffer, final int batchSize) { @@ -223,28 +223,31 @@ private static Func1, Observable> retryOperat @Override public Observable call(Observable emittedOnWriteFailure) { return emittedOnWriteFailure - .flatMap(canRetryError()) - .map(toLongWriteFailureObject()) + .flatMap(toLongWriteFailureOrError()) .compose(writeOperationRetryStrategy) - .doOnNext(replaceByteBufferPositionForRetry()); + .doOnNext(repositionByteBufferForRetry()); } @NonNull - private Func1 toLongWriteFailureObject() { - return new Func1() { + private Func1> toLongWriteFailureOrError() { + return new Func1>() { @Override - public WriteOperationRetryStrategy.LongWriteFailure call(Throwable throwable) { - final int failedBatchNumber = (byteBuffer.position() - batchSize) / batchSize; - return new WriteOperationRetryStrategy.LongWriteFailure( + public Observable call(Throwable throwable) { + if (!(throwable instanceof BleGattCharacteristicException)) { + return Observable.error(throwable); + } + final int failedBatchNumber = calculateFailedBatchNumber(byteBuffer, batchSize); + WriteOperationRetryStrategy.LongWriteFailure longWriteFailure = new WriteOperationRetryStrategy.LongWriteFailure( failedBatchNumber, (BleGattCharacteristicException) throwable ); + return Observable.just(longWriteFailure); } }; } @NonNull - private Action1 replaceByteBufferPositionForRetry() { + private Action1 repositionByteBufferForRetry() { return new Action1() { @Override public void call(WriteOperationRetryStrategy.LongWriteFailure longWriteFailure) { @@ -254,17 +257,12 @@ public void call(WriteOperationRetryStrategy.LongWriteFailure longWriteFailure) }; } - @NonNull - private Func1> canRetryError() { - return new Func1>() { - @Override - public Observable call(Throwable throwable) { - if (!(throwable instanceof BleGattCharacteristicException)) { - return Observable.error(throwable); - } - return Observable.just(throwable); - } - }; + private int calculateFailedBatchNumber(ByteBuffer byteBuffer, int batchSize) { + if (byteBuffer.hasRemaining()) { + return (byteBuffer.position() - batchSize) / batchSize; + } else { + return byteBuffer.position() / batchSize; + } } }; } From 8570954993db95a9476aab47bc86796aa58dd295 Mon Sep 17 00:00:00 2001 From: Martin WIRTH Date: Tue, 6 Feb 2018 07:42:42 +0100 Subject: [PATCH 08/18] Remove static import for inner class LongWriteFailure and checkstyle. --- .../com/polidea/rxandroidble/RxBleConnection.java | 12 ++++++------ .../operations/CharacteristicLongWriteOperation.java | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/rxandroidble/src/main/java/com/polidea/rxandroidble/RxBleConnection.java b/rxandroidble/src/main/java/com/polidea/rxandroidble/RxBleConnection.java index e4dfa68d6..1cc19096d 100644 --- a/rxandroidble/src/main/java/com/polidea/rxandroidble/RxBleConnection.java +++ b/rxandroidble/src/main/java/com/polidea/rxandroidble/RxBleConnection.java @@ -1,7 +1,5 @@ package com.polidea.rxandroidble; -import static com.polidea.rxandroidble.RxBleConnection.WriteOperationRetryStrategy.*; - import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattDescriptor; @@ -137,9 +135,10 @@ interface LongWriteOperationBuilder { /** * Setter for a retry strategy in case something goes wrong when writing data. If any {@link BleException} is raised, - * a {@link LongWriteFailure} object will be emitted. {@link LongWriteFailure} contains both the {@link BleException} and the batch - * number for which the write request failed. The {@link LongWriteFailure} emitted by the writeOperationRetryStrategy will be used - * to retry the specified batch number write request. + * a {@link WriteOperationRetryStrategy.LongWriteFailure} object will be emitted. + * {@link WriteOperationRetryStrategy.LongWriteFailure} contains both the {@link BleException} and the batch number + * for which the write request failed. The {@link WriteOperationRetryStrategy.LongWriteFailure} emitted by the + * writeOperationRetryStrategy will be used to retry the specified batch number write request. *
* If this is not specified - the next batch of bytes is written right after the failed one, and the failed one is just dropped. *
@@ -180,7 +179,8 @@ interface LongWriteOperationBuilder { Observable build(); } - interface WriteOperationRetryStrategy extends Observable.Transformer { + interface WriteOperationRetryStrategy extends Observable.Transformer { class LongWriteFailure { diff --git a/rxandroidble/src/main/java/com/polidea/rxandroidble/internal/operations/CharacteristicLongWriteOperation.java b/rxandroidble/src/main/java/com/polidea/rxandroidble/internal/operations/CharacteristicLongWriteOperation.java index 98e95b7b0..07f5c0d68 100644 --- a/rxandroidble/src/main/java/com/polidea/rxandroidble/internal/operations/CharacteristicLongWriteOperation.java +++ b/rxandroidble/src/main/java/com/polidea/rxandroidble/internal/operations/CharacteristicLongWriteOperation.java @@ -215,7 +215,7 @@ public Boolean call(Object emission) { }; } - private static Func1, Observable> errorIsRetryableAndAccordingTo ( + private static Func1, Observable> errorIsRetryableAndAccordingTo( final WriteOperationRetryStrategy writeOperationRetryStrategy, final ByteBuffer byteBuffer, final int batchSize) { From f979bfa02aaa8f225d743cfbcc3401c758540a3a Mon Sep 17 00:00:00 2001 From: Martin WIRTH Date: Thu, 8 Feb 2018 10:48:42 +0100 Subject: [PATCH 09/18] Change when reposition of byte buffer is done (#352) --- .../operations/CharacteristicLongWriteOperation.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rxandroidble/src/main/java/com/polidea/rxandroidble/internal/operations/CharacteristicLongWriteOperation.java b/rxandroidble/src/main/java/com/polidea/rxandroidble/internal/operations/CharacteristicLongWriteOperation.java index 07f5c0d68..162210870 100644 --- a/rxandroidble/src/main/java/com/polidea/rxandroidble/internal/operations/CharacteristicLongWriteOperation.java +++ b/rxandroidble/src/main/java/com/polidea/rxandroidble/internal/operations/CharacteristicLongWriteOperation.java @@ -224,8 +224,8 @@ private static Func1, Observable> errorIsRetr public Observable call(Observable emittedOnWriteFailure) { return emittedOnWriteFailure .flatMap(toLongWriteFailureOrError()) - .compose(writeOperationRetryStrategy) - .doOnNext(repositionByteBufferForRetry()); + .doOnNext(repositionByteBufferForRetry()) + .compose(writeOperationRetryStrategy); } @NonNull @@ -259,7 +259,7 @@ public void call(WriteOperationRetryStrategy.LongWriteFailure longWriteFailure) private int calculateFailedBatchNumber(ByteBuffer byteBuffer, int batchSize) { if (byteBuffer.hasRemaining()) { - return (byteBuffer.position() - batchSize) / batchSize; + return (byteBuffer.position() / batchSize) - 1; } else { return byteBuffer.position() / batchSize; } From d39f6ad62e9895a6a89febdf0894a007f140a06f Mon Sep 17 00:00:00 2001 From: Martin WIRTH Date: Thu, 15 Feb 2018 09:32:29 +0100 Subject: [PATCH 10/18] Update long write unit tests with retry operation strategy --- .../OperationCharacteristicLongWriteTest.groovy | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/rxandroidble/src/test/groovy/com/polidea/rxandroidble/internal/operations/OperationCharacteristicLongWriteTest.groovy b/rxandroidble/src/test/groovy/com/polidea/rxandroidble/internal/operations/OperationCharacteristicLongWriteTest.groovy index 58dc12d28..0f094b54e 100644 --- a/rxandroidble/src/test/groovy/com/polidea/rxandroidble/internal/operations/OperationCharacteristicLongWriteTest.groovy +++ b/rxandroidble/src/test/groovy/com/polidea/rxandroidble/internal/operations/OperationCharacteristicLongWriteTest.groovy @@ -9,12 +9,10 @@ import com.polidea.rxandroidble.exceptions.BleGattCannotStartException import com.polidea.rxandroidble.exceptions.BleGattOperationType import com.polidea.rxandroidble.internal.serialization.QueueReleaseInterface import com.polidea.rxandroidble.internal.connection.ImmediateSerializedBatchAckStrategy +import com.polidea.rxandroidble.internal.connection.NoRetryStrategy import com.polidea.rxandroidble.internal.connection.RxBleGattCallback import com.polidea.rxandroidble.internal.util.ByteAssociation import com.polidea.rxandroidble.internal.util.MockOperationTimeoutConfiguration -import java.nio.ByteBuffer -import java.util.concurrent.TimeUnit -import java.util.concurrent.atomic.AtomicInteger import rx.Observable import rx.functions.Func1 import rx.internal.schedulers.ImmediateScheduler @@ -25,6 +23,10 @@ import spock.lang.Shared import spock.lang.Specification import spock.lang.Unroll +import java.nio.ByteBuffer +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicInteger + public class OperationCharacteristicLongWriteTest extends Specification { private static long DEFAULT_WRITE_DELAY = 1 @@ -35,6 +37,7 @@ public class OperationCharacteristicLongWriteTest extends Specification { RxBleGattCallback mockCallback = Mock RxBleGattCallback BluetoothGattCharacteristic mockCharacteristic = Mock BluetoothGattCharacteristic RxBleConnection.WriteOperationAckStrategy writeOperationAckStrategy + RxBleConnection.WriteOperationRetryStrategy writeOperationRetryStrategy def testSubscriber = new TestSubscriber() TestScheduler testScheduler = new TestScheduler() TestScheduler timeoutScheduler = new TestScheduler() @@ -364,11 +367,13 @@ public class OperationCharacteristicLongWriteTest extends Specification { } private void givenWillWriteNextBatchImmediatelyAfterPrevious() { - writeOperationAckStrategy = new ImmediateSerializedBatchAckStrategy(); + writeOperationAckStrategy = new ImmediateSerializedBatchAckStrategy() + writeOperationRetryStrategy = new NoRetryStrategy() } private AcknowledgementTrigger givenWillTriggerWriteAcknowledgement() { def trigger = new AcknowledgementTrigger() + this.writeOperationRetryStrategy = new NoRetryStrategy() this.writeOperationAckStrategy = trigger return trigger } @@ -525,6 +530,7 @@ public class OperationCharacteristicLongWriteTest extends Specification { mockCharacteristic, { maxBatchSize }, writeOperationAckStrategy, + writeOperationRetryStrategy, testData ) } From cdddd295fa2c4134de0b18612f782ec80195dbed Mon Sep 17 00:00:00 2001 From: Martin WIRTH Date: Sat, 3 Mar 2018 12:54:12 +0100 Subject: [PATCH 11/18] Add unit test for long write retry strategy --- ...perationCharacteristicLongWriteTest.groovy | 76 ++++++++++++++++++- 1 file changed, 74 insertions(+), 2 deletions(-) diff --git a/rxandroidble/src/test/groovy/com/polidea/rxandroidble/internal/operations/OperationCharacteristicLongWriteTest.groovy b/rxandroidble/src/test/groovy/com/polidea/rxandroidble/internal/operations/OperationCharacteristicLongWriteTest.groovy index 0f094b54e..190dcc63f 100644 --- a/rxandroidble/src/test/groovy/com/polidea/rxandroidble/internal/operations/OperationCharacteristicLongWriteTest.groovy +++ b/rxandroidble/src/test/groovy/com/polidea/rxandroidble/internal/operations/OperationCharacteristicLongWriteTest.groovy @@ -6,11 +6,12 @@ import android.bluetooth.BluetoothGattCharacteristic import com.polidea.rxandroidble.RxBleConnection import com.polidea.rxandroidble.exceptions.BleGattCallbackTimeoutException import com.polidea.rxandroidble.exceptions.BleGattCannotStartException +import com.polidea.rxandroidble.exceptions.BleGattCharacteristicException import com.polidea.rxandroidble.exceptions.BleGattOperationType -import com.polidea.rxandroidble.internal.serialization.QueueReleaseInterface import com.polidea.rxandroidble.internal.connection.ImmediateSerializedBatchAckStrategy import com.polidea.rxandroidble.internal.connection.NoRetryStrategy import com.polidea.rxandroidble.internal.connection.RxBleGattCallback +import com.polidea.rxandroidble.internal.serialization.QueueReleaseInterface import com.polidea.rxandroidble.internal.util.ByteAssociation import com.polidea.rxandroidble.internal.util.MockOperationTimeoutConfiguration import rx.Observable @@ -45,7 +46,9 @@ public class OperationCharacteristicLongWriteTest extends Specification { PublishSubject> onCharacteristicWriteSubject = PublishSubject.create() QueueReleaseInterface mockQueueReleaseInterface = Mock QueueReleaseInterface CharacteristicLongWriteOperation objectUnderTest - @Shared Exception testException = new Exception("testException") + @Shared + Exception testException = new Exception("testException") + BleGattCharacteristicException bleGattCharacteristicException = Mock BleGattCharacteristicException def setup() { mockCharacteristic.getUuid() >> mockCharacteristicUUID @@ -226,6 +229,26 @@ public class OperationCharacteristicLongWriteTest extends Specification { 1 * mockCharacteristic.setValue(_) >> true } + def "should call next BluetoothGatt.writeCharacteristic() after the previous RxBleGattCallback.onCharacteristicWrite() emits an error and operation is retried"() { + given: + RetryWriteOperation retryWriteOperation = givenWillRetryWriteOperation() + givenCharacteristicWriteOkButEventuallyFailsToWrite(1) + prepareObjectUnderTest(20, byteArray(60)) + + when: + objectUnderTest.run(mockQueueReleaseInterface).subscribe(testSubscriber) + advanceTimeForWritesToComplete(2) + + then: + 2 * mockCharacteristic.setValue(_) >> true + + when: + retryWriteOperation.triggerRetry() + + then: + 1 * mockCharacteristic.setValue(_) >> true + } + def "should release QueueReleaseInterface after successful write"() { given: @@ -378,6 +401,13 @@ public class OperationCharacteristicLongWriteTest extends Specification { return trigger } + private RetryWriteOperation givenWillRetryWriteOperation() { + def retry = new RetryWriteOperation() + this.writeOperationRetryStrategy = retry + this.writeOperationAckStrategy = new ImmediateSerializedBatchAckStrategy() + return retry + } + class AcknowledgementTrigger implements RxBleConnection.WriteOperationAckStrategy { private final PublishSubject triggerSubject = PublishSubject.create() @@ -400,6 +430,28 @@ public class OperationCharacteristicLongWriteTest extends Specification { } } + class RetryWriteOperation implements RxBleConnection.WriteOperationRetryStrategy { + + private final PublishSubject triggerSubject = PublishSubject.create() + + public void triggerRetry() { + triggerSubject.with { + onNext(true) + onCompleted() + } + } + + @Override + Observable call( + Observable longWriteFailureObservable) { + return longWriteFailureObservable.flatMap({ longWriteFailure -> + return triggerSubject.map({ aBoolean -> + return longWriteFailure + }) + }) + } + } + private static byte[] byteArray(int size) { byte[] bytes = new byte[size]; for (int i = 0; i < size; i++) { @@ -503,6 +555,26 @@ public class OperationCharacteristicLongWriteTest extends Specification { } } + private givenCharacteristicWriteOkButEventuallyFailsToWrite(int failingWriteIndex) { + AtomicInteger writeIndex = new AtomicInteger(0) + + mockGatt.writeCharacteristic(mockCharacteristic) >> { BluetoothGattCharacteristic characteristic -> + UUID uuid = characteristic.getUuid() + byte[] returnBytes = new byte[0] + + testScheduler.createWorker().schedule({ + int currentIndex = writeIndex.getAndIncrement() + if (currentIndex == failingWriteIndex) { + onCharacteristicWriteSubject.onError(bleGattCharacteristicException) + } else { + onCharacteristicWriteSubject.onNext(new ByteAssociation(uuid, returnBytes)) + } + }, DEFAULT_WRITE_DELAY, TimeUnit.SECONDS) + + true + } + } + private givenCharacteristicWriteOkButEventuallyStalls(int failingWriteIndex) { AtomicInteger writeIndex = new AtomicInteger(0) From ef0bfe2541e2bdb47b8622903b0d45f10b092269 Mon Sep 17 00:00:00 2001 From: Martin WIRTH Date: Mon, 5 Mar 2018 09:03:16 +0100 Subject: [PATCH 12/18] Unit test: assert no errors when long write is retried (#352) --- .../operations/OperationCharacteristicLongWriteTest.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rxandroidble/src/test/groovy/com/polidea/rxandroidble/internal/operations/OperationCharacteristicLongWriteTest.groovy b/rxandroidble/src/test/groovy/com/polidea/rxandroidble/internal/operations/OperationCharacteristicLongWriteTest.groovy index 190dcc63f..20adf839e 100644 --- a/rxandroidble/src/test/groovy/com/polidea/rxandroidble/internal/operations/OperationCharacteristicLongWriteTest.groovy +++ b/rxandroidble/src/test/groovy/com/polidea/rxandroidble/internal/operations/OperationCharacteristicLongWriteTest.groovy @@ -246,7 +246,7 @@ public class OperationCharacteristicLongWriteTest extends Specification { retryWriteOperation.triggerRetry() then: - 1 * mockCharacteristic.setValue(_) >> true + testSubscriber.assertNoErrors() } def "should release QueueReleaseInterface after successful write"() { From cf207e20162914bf6e25f0a79199c18e11d7f7cf Mon Sep 17 00:00:00 2001 From: "pawel.urban" Date: Fri, 9 Mar 2018 14:24:32 +0100 Subject: [PATCH 13/18] - Added tests for retries during long write operations - Fixed issue that caused the last batch to be written with incorrect data (empty byte array) - Added support for errors that were occurring when operation was started (not from the gatt callback) --- .../polidea/rxandroidble/RxBleConnection.java | 11 +- .../CharacteristicLongWriteOperation.java | 13 +- ...perationCharacteristicLongWriteTest.groovy | 146 ++++++++++++++++-- 3 files changed, 139 insertions(+), 31 deletions(-) diff --git a/rxandroidble/src/main/java/com/polidea/rxandroidble/RxBleConnection.java b/rxandroidble/src/main/java/com/polidea/rxandroidble/RxBleConnection.java index 1cc19096d..44ad86430 100644 --- a/rxandroidble/src/main/java/com/polidea/rxandroidble/RxBleConnection.java +++ b/rxandroidble/src/main/java/com/polidea/rxandroidble/RxBleConnection.java @@ -14,7 +14,6 @@ import com.polidea.rxandroidble.exceptions.BleConflictingNotificationAlreadySetException; import com.polidea.rxandroidble.exceptions.BleException; import com.polidea.rxandroidble.exceptions.BleGattCannotStartException; -import com.polidea.rxandroidble.exceptions.BleGattCharacteristicException; import com.polidea.rxandroidble.exceptions.BleGattException; import com.polidea.rxandroidble.exceptions.BleGattOperationType; import com.polidea.rxandroidble.internal.Priority; @@ -135,7 +134,7 @@ interface LongWriteOperationBuilder { /** * Setter for a retry strategy in case something goes wrong when writing data. If any {@link BleException} is raised, - * a {@link WriteOperationRetryStrategy.LongWriteFailure} object will be emitted. + * a {@link WriteOperationRetryStrategy.LongWriteFailure} object is emitted. * {@link WriteOperationRetryStrategy.LongWriteFailure} contains both the {@link BleException} and the batch number * for which the write request failed. The {@link WriteOperationRetryStrategy.LongWriteFailure} emitted by the * writeOperationRetryStrategy will be used to retry the specified batch number write request. @@ -185,7 +184,7 @@ interface WriteOperationRetryStrategy extends Observable.Transformer>() { @Override public Observable call(Throwable throwable) { - if (!(throwable instanceof BleGattCharacteristicException)) { + if (!(throwable instanceof BleGattCharacteristicException || throwable instanceof BleGattCannotStartException)) { return Observable.error(throwable); } final int failedBatchNumber = calculateFailedBatchNumber(byteBuffer, batchSize); WriteOperationRetryStrategy.LongWriteFailure longWriteFailure = new WriteOperationRetryStrategy.LongWriteFailure( failedBatchNumber, - (BleGattCharacteristicException) throwable + (BleGattException) throwable ); return Observable.just(longWriteFailure); } @@ -251,18 +252,14 @@ private Action1 repositionByteBuff return new Action1() { @Override public void call(WriteOperationRetryStrategy.LongWriteFailure longWriteFailure) { - final int newBufferPosition = longWriteFailure.getBatchNumber() * batchSize; + final int newBufferPosition = longWriteFailure.getBatchNumber() * batchSize - batchSize; byteBuffer.position(newBufferPosition); } }; } private int calculateFailedBatchNumber(ByteBuffer byteBuffer, int batchSize) { - if (byteBuffer.hasRemaining()) { - return (byteBuffer.position() / batchSize) - 1; - } else { - return byteBuffer.position() / batchSize; - } + return byteBuffer.position() / batchSize; } }; } diff --git a/rxandroidble/src/test/groovy/com/polidea/rxandroidble/internal/operations/OperationCharacteristicLongWriteTest.groovy b/rxandroidble/src/test/groovy/com/polidea/rxandroidble/internal/operations/OperationCharacteristicLongWriteTest.groovy index 20adf839e..8293b94b8 100644 --- a/rxandroidble/src/test/groovy/com/polidea/rxandroidble/internal/operations/OperationCharacteristicLongWriteTest.groovy +++ b/rxandroidble/src/test/groovy/com/polidea/rxandroidble/internal/operations/OperationCharacteristicLongWriteTest.groovy @@ -203,50 +203,164 @@ public class OperationCharacteristicLongWriteTest extends Specification { 1 * mockCharacteristic.setValue(_) >> true } - def "should call next BluetoothGatt.writeCharacteristic() after the previous RxBleGattCallback.onCharacteristicWrite() emits and operation is acknowledged"() { + def "should attempt to write next batch after the previous has completed and has been acknowledged - no retry strategy"() { given: + this.writeOperationRetryStrategy = new NoRetryStrategy() AcknowledgementTrigger writeAckTrigger = givenWillTriggerWriteAcknowledgement() givenEachCharacteristicWriteOkAfterDefaultDelay() - prepareObjectUnderTest(20, byteArray(60)) + prepareObjectUnderTest(2, [0x1, 0x1, 0x2, 0x2, 0x3, 0x3] as byte[]) when: objectUnderTest.run(mockQueueReleaseInterface).subscribe(testSubscriber) advanceTimeForWritesToComplete(1) then: - 1 * mockCharacteristic.setValue(_) >> true + 1 * mockCharacteristic.setValue([0x1, 0x1] as byte[]) >> true when: - advanceTimeForWrites(0) + writeAckTrigger.acknowledgeWrite() then: - 0 * mockCharacteristic.setValue(_) >> true + 1 * mockCharacteristic.setValue([0x2, 0x2] as byte[]) >> true + } + + def "should not attempt to write next batch or rewrite after the previous has failed - no retry strategy"() { + given: + this.writeOperationRetryStrategy = new NoRetryStrategy() + this.writeOperationAckStrategy = new ImmediateSerializedBatchAckStrategy() + prepareObjectUnderTest(2, [0x1, 0x1, 0x2, 0x2, 0x3, 0x3] as byte[]) when: - writeAckTrigger.acknowledgeWrite() + objectUnderTest.run(mockQueueReleaseInterface).subscribe(testSubscriber) then: - 1 * mockCharacteristic.setValue(_) >> true + 1 * mockCharacteristic.setValue([0x1, 0x1] as byte[]) >> true + 1 * mockGatt.writeCharacteristic(mockCharacteristic) >> false + testSubscriber.assertError(BleGattCannotStartException) } - def "should call next BluetoothGatt.writeCharacteristic() after the previous RxBleGattCallback.onCharacteristicWrite() emits an error and operation is retried"() { + def "attempt to rewrite the failed batch if the strategy has emitted the LongWriteFailure - first batch"() { given: - RetryWriteOperation retryWriteOperation = givenWillRetryWriteOperation() - givenCharacteristicWriteOkButEventuallyFailsToWrite(1) - prepareObjectUnderTest(20, byteArray(60)) + this.writeOperationRetryStrategy = givenWillRetryWriteOperation() + this.writeOperationAckStrategy = new ImmediateSerializedBatchAckStrategy() + prepareObjectUnderTest(2, [0x1, 0x1, 0x2, 0x2, 0x3, 0x3] as byte[]) when: objectUnderTest.run(mockQueueReleaseInterface).subscribe(testSubscriber) - advanceTimeForWritesToComplete(2) then: - 2 * mockCharacteristic.setValue(_) >> true + 1 * mockCharacteristic.setValue([0x1, 0x1] as byte[]) >> true + 1 * mockGatt.writeCharacteristic(mockCharacteristic) >> false + testSubscriber.assertNoTerminalEvent() when: - retryWriteOperation.triggerRetry() + writeOperationRetryStrategy.triggerRetry() then: - testSubscriber.assertNoErrors() + 1 * mockCharacteristic.setValue([0x1, 0x1] as byte[]) >> true + 1 * mockGatt.writeCharacteristic(mockCharacteristic) >> { BluetoothGattCharacteristic characteristic -> + onCharacteristicWriteSubject.onNext(new ByteAssociation(characteristic.getUuid(), [] as byte[])) + true + } + + then: + 1 * mockCharacteristic.setValue([0x2, 0x2] as byte[]) >> true + 1 * mockGatt.writeCharacteristic(mockCharacteristic) >> { BluetoothGattCharacteristic characteristic -> + onCharacteristicWriteSubject.onNext(new ByteAssociation(characteristic.getUuid(), [] as byte[])) + true + } + + then: + 1 * mockCharacteristic.setValue([0x3, 0x3] as byte[]) >> true + 1 * mockGatt.writeCharacteristic(mockCharacteristic) >> { BluetoothGattCharacteristic characteristic -> + onCharacteristicWriteSubject.onNext(new ByteAssociation(characteristic.getUuid(), [] as byte[])) + true + } + + testSubscriber.assertValueEquals([0x1, 0x1, 0x2, 0x2, 0x3, 0x3] as byte[]) + testSubscriber.assertCompleted() + } + + def "attempt to rewrite the failed batch if the strategy has emitted the LongWriteFailure - mid batch"() { + given: + this.writeOperationRetryStrategy = givenWillRetryWriteOperation() + this.writeOperationAckStrategy = new ImmediateSerializedBatchAckStrategy() + prepareObjectUnderTest(2, [0x1, 0x1, 0x2, 0x2, 0x3, 0x3] as byte[]) + + when: + objectUnderTest.run(mockQueueReleaseInterface).subscribe(testSubscriber) + + then: + 1 * mockCharacteristic.setValue([0x1, 0x1] as byte[]) >> true + 1 * mockGatt.writeCharacteristic(mockCharacteristic) >> { BluetoothGattCharacteristic characteristic -> + onCharacteristicWriteSubject.onNext(new ByteAssociation(characteristic.getUuid(), [] as byte[])) + true + } + + then: + 1 * mockCharacteristic.setValue([0x2, 0x2] as byte[]) >> true + 1 * mockGatt.writeCharacteristic(mockCharacteristic) >> false + + when: + writeOperationRetryStrategy.triggerRetry() + + then: + 1 * mockCharacteristic.setValue([0x2, 0x2] as byte[]) >> true + 1 * mockGatt.writeCharacteristic(mockCharacteristic) >> { BluetoothGattCharacteristic characteristic -> + onCharacteristicWriteSubject.onNext(new ByteAssociation(characteristic.getUuid(), [] as byte[])) + true + } + + then: + 1 * mockCharacteristic.setValue([0x3, 0x3] as byte[]) >> true + 1 * mockGatt.writeCharacteristic(mockCharacteristic) >> { BluetoothGattCharacteristic characteristic -> + onCharacteristicWriteSubject.onNext(new ByteAssociation(characteristic.getUuid(), [] as byte[])) + true + } + + testSubscriber.assertValueEquals([0x1, 0x1, 0x2, 0x2, 0x3, 0x3] as byte[]) + testSubscriber.assertCompleted() + } + + def "attempt to rewrite the failed batch if the strategy has emitted the LongWriteFailure - last batch"() { + given: + this.writeOperationRetryStrategy = givenWillRetryWriteOperation() + this.writeOperationAckStrategy = new ImmediateSerializedBatchAckStrategy() + prepareObjectUnderTest(2, [0x1, 0x1, 0x2, 0x2, 0x3, 0x3] as byte[]) + + when: + objectUnderTest.run(mockQueueReleaseInterface).subscribe(testSubscriber) + + then: + 1 * mockCharacteristic.setValue([0x1, 0x1] as byte[]) >> true + 1 * mockGatt.writeCharacteristic(mockCharacteristic) >> { BluetoothGattCharacteristic characteristic -> + onCharacteristicWriteSubject.onNext(new ByteAssociation(characteristic.getUuid(), [] as byte[])) + true + } + + then: + 1 * mockCharacteristic.setValue([0x2, 0x2] as byte[]) >> true + 1 * mockGatt.writeCharacteristic(mockCharacteristic) >> { BluetoothGattCharacteristic characteristic -> + onCharacteristicWriteSubject.onNext(new ByteAssociation(characteristic.getUuid(), [] as byte[])) + true + } + + then: + 1 * mockCharacteristic.setValue([0x3, 0x3] as byte[]) >> true + 1 * mockGatt.writeCharacteristic(mockCharacteristic) >> false + + when: + writeOperationRetryStrategy.triggerRetry() + + then: + 1 * mockCharacteristic.setValue([0x3, 0x3] as byte[]) >> true + 1 * mockGatt.writeCharacteristic(mockCharacteristic) >> { BluetoothGattCharacteristic characteristic -> + onCharacteristicWriteSubject.onNext(new ByteAssociation(characteristic.getUuid(), [] as byte[])) + true + } + + testSubscriber.assertValueEquals([0x1, 0x1, 0x2, 0x2, 0x3, 0x3] as byte[]) + testSubscriber.assertCompleted() } def "should release QueueReleaseInterface after successful write"() { @@ -396,7 +510,6 @@ public class OperationCharacteristicLongWriteTest extends Specification { private AcknowledgementTrigger givenWillTriggerWriteAcknowledgement() { def trigger = new AcknowledgementTrigger() - this.writeOperationRetryStrategy = new NoRetryStrategy() this.writeOperationAckStrategy = trigger return trigger } @@ -404,7 +517,6 @@ public class OperationCharacteristicLongWriteTest extends Specification { private RetryWriteOperation givenWillRetryWriteOperation() { def retry = new RetryWriteOperation() this.writeOperationRetryStrategy = retry - this.writeOperationAckStrategy = new ImmediateSerializedBatchAckStrategy() return retry } From 8d3f2feee85482f7031bccfb40e4b74c82d8232e Mon Sep 17 00:00:00 2001 From: "pawel.urban" Date: Fri, 9 Mar 2018 14:48:53 +0100 Subject: [PATCH 14/18] - Fixed documentation incorrectly stating that the particular batch is skipped --- .../src/main/java/com/polidea/rxandroidble/RxBleConnection.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rxandroidble/src/main/java/com/polidea/rxandroidble/RxBleConnection.java b/rxandroidble/src/main/java/com/polidea/rxandroidble/RxBleConnection.java index 44ad86430..7ff8647eb 100644 --- a/rxandroidble/src/main/java/com/polidea/rxandroidble/RxBleConnection.java +++ b/rxandroidble/src/main/java/com/polidea/rxandroidble/RxBleConnection.java @@ -139,7 +139,7 @@ interface LongWriteOperationBuilder { * for which the write request failed. The {@link WriteOperationRetryStrategy.LongWriteFailure} emitted by the * writeOperationRetryStrategy will be used to retry the specified batch number write request. *
- * If this is not specified - the next batch of bytes is written right after the failed one, and the failed one is just dropped. + * If this is not specified - if batch write fails, the long write operation is stopped and whole operation is interrupted. *
* It is expected that the Observable returned from the writeOperationRetryStrategy will emit exactly the same events as the source, * however you may delay them at your pace. From b9c0ba2c4438d548a10b024644fef8aa51baad81 Mon Sep 17 00:00:00 2001 From: "pawel.urban" Date: Fri, 9 Mar 2018 15:42:34 +0100 Subject: [PATCH 15/18] - Fixed positioning of the buffer for uneven data lengths. --- .../CharacteristicLongWriteOperation.java | 2 +- ...perationCharacteristicLongWriteTest.groovy | 41 +++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/rxandroidble/src/main/java/com/polidea/rxandroidble/internal/operations/CharacteristicLongWriteOperation.java b/rxandroidble/src/main/java/com/polidea/rxandroidble/internal/operations/CharacteristicLongWriteOperation.java index 841cef6c6..09d89318b 100644 --- a/rxandroidble/src/main/java/com/polidea/rxandroidble/internal/operations/CharacteristicLongWriteOperation.java +++ b/rxandroidble/src/main/java/com/polidea/rxandroidble/internal/operations/CharacteristicLongWriteOperation.java @@ -259,7 +259,7 @@ public void call(WriteOperationRetryStrategy.LongWriteFailure longWriteFailure) } private int calculateFailedBatchNumber(ByteBuffer byteBuffer, int batchSize) { - return byteBuffer.position() / batchSize; + return (int) Math.ceil(byteBuffer.position() / (float) batchSize); } }; } diff --git a/rxandroidble/src/test/groovy/com/polidea/rxandroidble/internal/operations/OperationCharacteristicLongWriteTest.groovy b/rxandroidble/src/test/groovy/com/polidea/rxandroidble/internal/operations/OperationCharacteristicLongWriteTest.groovy index 8293b94b8..212845105 100644 --- a/rxandroidble/src/test/groovy/com/polidea/rxandroidble/internal/operations/OperationCharacteristicLongWriteTest.groovy +++ b/rxandroidble/src/test/groovy/com/polidea/rxandroidble/internal/operations/OperationCharacteristicLongWriteTest.groovy @@ -363,6 +363,47 @@ public class OperationCharacteristicLongWriteTest extends Specification { testSubscriber.assertCompleted() } + def "attempt to rewrite the failed batch if the strategy has emitted the LongWriteFailure - last batch, uneven count"() { + given: + this.writeOperationRetryStrategy = givenWillRetryWriteOperation() + this.writeOperationAckStrategy = new ImmediateSerializedBatchAckStrategy() + prepareObjectUnderTest(2, [0x1, 0x1, 0x2, 0x2, 0x3] as byte[]) + + when: + objectUnderTest.run(mockQueueReleaseInterface).subscribe(testSubscriber) + + then: + 1 * mockCharacteristic.setValue([0x1, 0x1] as byte[]) >> true + 1 * mockGatt.writeCharacteristic(mockCharacteristic) >> { BluetoothGattCharacteristic characteristic -> + onCharacteristicWriteSubject.onNext(new ByteAssociation(characteristic.getUuid(), [] as byte[])) + true + } + + then: + 1 * mockCharacteristic.setValue([0x2, 0x2] as byte[]) >> true + 1 * mockGatt.writeCharacteristic(mockCharacteristic) >> { BluetoothGattCharacteristic characteristic -> + onCharacteristicWriteSubject.onNext(new ByteAssociation(characteristic.getUuid(), [] as byte[])) + true + } + + then: + 1 * mockCharacteristic.setValue([0x3] as byte[]) >> true + 1 * mockGatt.writeCharacteristic(mockCharacteristic) >> false + + when: + writeOperationRetryStrategy.triggerRetry() + + then: + 1 * mockCharacteristic.setValue([0x3] as byte[]) >> true + 1 * mockGatt.writeCharacteristic(mockCharacteristic) >> { BluetoothGattCharacteristic characteristic -> + onCharacteristicWriteSubject.onNext(new ByteAssociation(characteristic.getUuid(), [] as byte[])) + true + } + + testSubscriber.assertValueEquals([0x1, 0x1, 0x2, 0x2, 0x3] as byte[]) + testSubscriber.assertCompleted() + } + def "should release QueueReleaseInterface after successful write"() { given: From db06717af14d1458fa3ff5698132359fabc489e2 Mon Sep 17 00:00:00 2001 From: "pawel.urban" Date: Fri, 9 Mar 2018 16:16:05 +0100 Subject: [PATCH 16/18] Changed indexing scheme for long write retry notifications. --- .../polidea/rxandroidble/RxBleConnection.java | 16 ++++++++-------- .../CharacteristicLongWriteOperation.java | 10 +++++----- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/rxandroidble/src/main/java/com/polidea/rxandroidble/RxBleConnection.java b/rxandroidble/src/main/java/com/polidea/rxandroidble/RxBleConnection.java index 7ff8647eb..ae893136e 100644 --- a/rxandroidble/src/main/java/com/polidea/rxandroidble/RxBleConnection.java +++ b/rxandroidble/src/main/java/com/polidea/rxandroidble/RxBleConnection.java @@ -183,27 +183,27 @@ interface WriteOperationRetryStrategy extends Observable.Transformer call(Throwable t if (!(throwable instanceof BleGattCharacteristicException || throwable instanceof BleGattCannotStartException)) { return Observable.error(throwable); } - final int failedBatchNumber = calculateFailedBatchNumber(byteBuffer, batchSize); + final int failedBatchIndex = calculateFailedBatchIndex(byteBuffer, batchSize); WriteOperationRetryStrategy.LongWriteFailure longWriteFailure = new WriteOperationRetryStrategy.LongWriteFailure( - failedBatchNumber, + failedBatchIndex, (BleGattException) throwable ); return Observable.just(longWriteFailure); @@ -252,14 +252,14 @@ private Action1 repositionByteBuff return new Action1() { @Override public void call(WriteOperationRetryStrategy.LongWriteFailure longWriteFailure) { - final int newBufferPosition = longWriteFailure.getBatchNumber() * batchSize - batchSize; + final int newBufferPosition = longWriteFailure.getBatchIndex() * batchSize - batchSize; byteBuffer.position(newBufferPosition); } }; } - private int calculateFailedBatchNumber(ByteBuffer byteBuffer, int batchSize) { - return (int) Math.ceil(byteBuffer.position() / (float) batchSize); + private int calculateFailedBatchIndex(ByteBuffer byteBuffer, int batchSize) { + return (int) Math.ceil(byteBuffer.position() / (float) batchSize) - 1; } }; } From 6dd6dd2f902f23ba9050eba3d9a2f2818869c736 Mon Sep 17 00:00:00 2001 From: "pawel.urban" Date: Fri, 9 Mar 2018 16:24:41 +0100 Subject: [PATCH 17/18] Fixed buffer repositioning, based on a zero based counter. --- .../internal/operations/CharacteristicLongWriteOperation.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rxandroidble/src/main/java/com/polidea/rxandroidble/internal/operations/CharacteristicLongWriteOperation.java b/rxandroidble/src/main/java/com/polidea/rxandroidble/internal/operations/CharacteristicLongWriteOperation.java index fecdbc916..9a181ffd8 100644 --- a/rxandroidble/src/main/java/com/polidea/rxandroidble/internal/operations/CharacteristicLongWriteOperation.java +++ b/rxandroidble/src/main/java/com/polidea/rxandroidble/internal/operations/CharacteristicLongWriteOperation.java @@ -252,7 +252,7 @@ private Action1 repositionByteBuff return new Action1() { @Override public void call(WriteOperationRetryStrategy.LongWriteFailure longWriteFailure) { - final int newBufferPosition = longWriteFailure.getBatchIndex() * batchSize - batchSize; + final int newBufferPosition = longWriteFailure.getBatchIndex() * batchSize; byteBuffer.position(newBufferPosition); } }; From 0635c2846c2b2c9dae7c14ec985ea59ff16adb7a Mon Sep 17 00:00:00 2001 From: "pawel.urban" Date: Mon, 12 Mar 2018 10:44:30 +0100 Subject: [PATCH 18/18] Added documentation for the retry strategy. --- .../java/com/polidea/rxandroidble/RxBleConnection.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/rxandroidble/src/main/java/com/polidea/rxandroidble/RxBleConnection.java b/rxandroidble/src/main/java/com/polidea/rxandroidble/RxBleConnection.java index ae893136e..cced48559 100644 --- a/rxandroidble/src/main/java/com/polidea/rxandroidble/RxBleConnection.java +++ b/rxandroidble/src/main/java/com/polidea/rxandroidble/RxBleConnection.java @@ -178,6 +178,15 @@ interface LongWriteOperationBuilder { Observable build(); } + /** + * Retry strategy allows retrying a long write operation. There are two supported scenarios: + * - Once the failure happens you may re-emit the failure you've received, applying your own transformations like a delay or any other, + * aiming to postpone the retry procedure. + * - If that Observable calls {@code onComplete} or {@code onError} then {@code retry} will call + * {@code onCompleted} or {@code onError} on the child subscription. The emission will be forwarded as an operation result. + * + * For general documentation related to retrying please refer to http://reactivex.io/documentation/operators/retry.html + */ interface WriteOperationRetryStrategy extends Observable.Transformer {