Skip to content

Commit

Permalink
Polar Ble SDK v.5.2.0
Browse files Browse the repository at this point in the history
**iOS SDK updates:**
PolarBleApiObserver.deviceDisconnected() is called with a pairingError in the case of 'Peer removed pairing information'.
  • Loading branch information
Samuli Määttä committed Aug 11, 2023
1 parent 43d4a38 commit c878848
Show file tree
Hide file tree
Showing 13 changed files with 265 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.core.SingleOnSubscribe;
import io.reactivex.rxjava3.schedulers.Schedulers;
import com.polar.androidcommunications.api.ble.model.proto.CommunicationsPftpRequest;

/**
* Polar simple file transfer client declaration.
Expand All @@ -50,6 +51,9 @@ public class BlePsFtpClient extends BleGattBase {
private final AtomicInteger notificationPacketsWritten = new AtomicInteger(0);
private final AtomicInteger packetsCount = new AtomicInteger(5); // default every 5th packet is written with response
private static final int PROTOCOL_TIMEOUT_SECONDS = 90;
private static final int PROTOCOL_TIMEOUT_EXTENDED_SECONDS = 900;

private final List<String> extendedWriteTimeoutFilePaths = Collections.singletonList("/SYNCPART.TGZ");

/**
* true = uses attribute operation WRITE
Expand Down Expand Up @@ -232,10 +236,10 @@ public Single<ByteArrayOutputStream> request(final byte[] header, Scheduler sche
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try {
txInterface.transmitMessages(BlePsFtpUtils.RFC77_PFTP_SERVICE, BlePsFtpUtils.RFC77_PFTP_MTU_CHARACTERISTIC, requestData, false);
waitPacketsWritten(packetsWritten, mtuWaiting, requestData.size());
waitPacketsWritten(packetsWritten, mtuWaiting, requestData.size(), PROTOCOL_TIMEOUT_SECONDS);
requestData.clear();
// start waiting for packets
readResponse(outputStream);
readResponse(outputStream, PROTOCOL_TIMEOUT_SECONDS);
emitter.onSuccess(outputStream);
} catch (InterruptedException ex) {
BleLogger.e(TAG, "Request interrupted. Exception: " + ex.getMessage());
Expand All @@ -259,14 +263,14 @@ public Single<ByteArrayOutputStream> request(final byte[] header, Scheduler sche
.subscribeOn(scheduler);
}

private void waitPacketsWritten(final AtomicInteger written, AtomicBoolean waiting, int count) throws InterruptedException, BleDisconnected, BlePsFtpUtils.PftpOperationTimeout {
private void waitPacketsWritten(final AtomicInteger written, AtomicBoolean waiting, int count, long timeoutSeconds) throws InterruptedException, BleDisconnected, BlePsFtpUtils.PftpOperationTimeout {
try {
waiting.set(true);
while (written.get() < count) {
synchronized (written) {
if (written.get() != count) {
int was = written.get();
written.wait(PROTOCOL_TIMEOUT_SECONDS * 1000);
written.wait(timeoutSeconds * 1000);
if (was == written.get()) {
if (!txInterface.isConnected()) {
throw new BleDisconnected("Connection lost during waiting packets to be written");
Expand Down Expand Up @@ -318,6 +322,7 @@ public Flowable<Long> write(final byte[] header, final ByteArrayInputStream data
int next = 0;
long totalPayload = totalStream.available();
BlePsFtpUtils.Rfc76SequenceNumber sequenceNumber = new BlePsFtpUtils.Rfc76SequenceNumber();
final long timeoutSeconds = getWriteTimeoutForFilePath(CommunicationsPftpRequest.PbPFtpOperation.parseFrom(header).getPath());
do {
byte[] airPacket;
int counter = 0;
Expand All @@ -332,7 +337,7 @@ public Flowable<Long> write(final byte[] header, final ByteArrayInputStream data
if (useAttributeLevelResponse.get()) {
counter = 1;
packetsWrittenWithResponse.set(0);
waitPacketsWritten(packetsWrittenWithResponse, mtuWaiting, 1);
waitPacketsWritten(packetsWrittenWithResponse, mtuWaiting, 1, timeoutSeconds);
packetsWritten.set(0);
counter = 0;
} else {
Expand Down Expand Up @@ -375,7 +380,7 @@ public Flowable<Long> write(final byte[] header, final ByteArrayInputStream data
currentOperationWrite.set(false);
ByteArrayOutputStream response = new ByteArrayOutputStream();
try {
readResponse(response);
readResponse(response, timeoutSeconds);
} catch (InterruptedException ex) {
// catch interrupted as it cannot be rethrown onwards
BleLogger.e(TAG, "write interrupted while reading response");
Expand All @@ -401,6 +406,15 @@ public Flowable<Long> write(final byte[] header, final ByteArrayInputStream data
.serialize();
}

private long getWriteTimeoutForFilePath(final String filePath) {
for (final String path : extendedWriteTimeoutFilePaths) {
if (filePath.startsWith(path)) {
return PROTOCOL_TIMEOUT_EXTENDED_SECONDS;
}
}
return PROTOCOL_TIMEOUT_SECONDS;
}

public Single<ByteArrayOutputStream> query(final int id, final byte[] parameters) {
return query(id, parameters, Schedulers.newThread());
}
Expand All @@ -411,10 +425,10 @@ private void handleMtuInterrupted(boolean dataAvailable, int lastRequest) {
byte[] cancelPacket = new byte[]{0x00, 0x00, 0x00};
try {
if (mtuWaiting.get()) {
waitPacketsWritten(packetsWritten, mtuWaiting, lastRequest);
waitPacketsWritten(packetsWritten, mtuWaiting, lastRequest, PROTOCOL_TIMEOUT_SECONDS);
}
txInterface.transmitMessages(BlePsFtpUtils.RFC77_PFTP_SERVICE, BlePsFtpUtils.RFC77_PFTP_MTU_CHARACTERISTIC, Collections.singletonList(cancelPacket), useAttributeLevelResponse.get());
waitPacketsWritten(packetsWritten, mtuWaiting, 1);
waitPacketsWritten(packetsWritten, mtuWaiting, 1, PROTOCOL_TIMEOUT_SECONDS);
BleLogger.d(TAG, "MTU interrupted. Stream cancel has been successfully send");
} catch (Throwable throwable) {
BleLogger.e(TAG, "Exception while trying to cancel streaming");
Expand Down Expand Up @@ -446,9 +460,9 @@ public Single<ByteArrayOutputStream> query(final int id, final byte[] parameters
ByteArrayOutputStream response = new ByteArrayOutputStream();
try {
txInterface.transmitMessages(BlePsFtpUtils.RFC77_PFTP_SERVICE, BlePsFtpUtils.RFC77_PFTP_MTU_CHARACTERISTIC, requs, false);
waitPacketsWritten(packetsWritten, mtuWaiting, requs.size());
waitPacketsWritten(packetsWritten, mtuWaiting, requs.size(), PROTOCOL_TIMEOUT_SECONDS);
requs.clear();
readResponse(response);
readResponse(response, PROTOCOL_TIMEOUT_SECONDS);
emitter.onSuccess(response);
} catch (InterruptedException ex) {
// Note RX throws InterruptedException when the stream is not completed, and it is unsubscribed
Expand Down Expand Up @@ -500,7 +514,7 @@ public Completable sendNotification(final int id, final byte[] parameters, Sched
BlePsFtpUtils.Rfc76SequenceNumber sequenceNumber = new BlePsFtpUtils.Rfc76SequenceNumber();
List<byte[]> requs = BlePsFtpUtils.buildRfc76MessageFrameAll(totalStream, mtuSize.get(), sequenceNumber);
txInterface.transmitMessages(BlePsFtpUtils.RFC77_PFTP_SERVICE, BlePsFtpUtils.RFC77_PFTP_H2D_CHARACTERISTIC, requs, false);
waitPacketsWritten(notificationPacketsWritten, notificationWaiting, requs.size());
waitPacketsWritten(notificationPacketsWritten, notificationWaiting, requs.size(), PROTOCOL_TIMEOUT_SECONDS);
emitter.onComplete();
} else {
BleLogger.e(TAG, "Send notification id: " + id + " failed. PS-FTP notification not enabled");
Expand Down Expand Up @@ -616,7 +630,7 @@ public Completable waitPsFtpClientReady(final boolean checkConnection, Scheduler
}

@VisibleForTesting
void readResponse(ByteArrayOutputStream outputStream) throws Exception {
void readResponse(ByteArrayOutputStream outputStream, final long timeoutSeconds) throws Exception {
long status = 0;
int next = 0;
BlePsFtpUtils.Rfc76SequenceNumber sequenceNumber = new BlePsFtpUtils.Rfc76SequenceNumber();
Expand All @@ -626,7 +640,7 @@ void readResponse(ByteArrayOutputStream outputStream) throws Exception {
if (txInterface.isConnected()) {
synchronized (mtuInputQueue) {
if (mtuInputQueue.isEmpty()) {
mtuInputQueue.wait(PROTOCOL_TIMEOUT_SECONDS * 1000L);
mtuInputQueue.wait(timeoutSeconds * 1000L);
}
}
} else {
Expand All @@ -640,7 +654,7 @@ void readResponse(ByteArrayOutputStream outputStream) throws Exception {
if (response.status == BlePsFtpUtils.RFC76_STATUS_MORE) {
byte[] cancelPacket = new byte[]{0x00, 0x00, 0x00};
txInterface.transmitMessages(BlePsFtpUtils.RFC77_PFTP_SERVICE, BlePsFtpUtils.RFC77_PFTP_MTU_CHARACTERISTIC, Collections.singletonList(cancelPacket), true);
waitPacketsWritten(packetsWritten, mtuWaiting, 1);
waitPacketsWritten(packetsWritten, mtuWaiting, 1, timeoutSeconds);
BleLogger.d(TAG, "Sequence number mismatch. Stream cancel has been successfully send");
}
throw new BlePsFtpUtils.PftpResponseError("Air packet lost!", 303);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
syntax = "proto2";

package com.polar.androidcommunications.api.ble.model.proto;

message PbPFtpOperation {
enum Command {
GET = 0;
PUT = 1;
MERGE = 2;
REMOVE = 3;
}
required Command command = 1;
required string path = 2;
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@ object PolarBleApiDefaultImpl {
*/
@JvmStatic
fun versionInfo(): String {
return "5.1.0"
return "5.2.0"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ internal class BlePsFtpClientTest {
val frame11 = byteArrayOf(0xB7.toByte(), 0x82.toByte(), 0x08.toByte(), 0x0A.toByte(), 0x06.toByte(), 0x08.toByte(), 0x03.toByte(), 0x10.toByte(), 0x00.toByte(), 0x18.toByte(), 0x02.toByte(), 0x8A.toByte(), 0x01.toByte(), 0x0D.toByte(), 0x0A.toByte(), 0x09.toByte(), 0x5A.toByte(), 0x48.toByte(), 0x5F.toByte(), 0x4A.toByte())
val frame12 = byteArrayOf(0xC3.toByte(), 0x5A.toByte(), 0x48.toByte(), 0x5F.toByte(), 0x4A.toByte(), 0x41.toByte(), 0x10.toByte(), 0x09.toByte())
val output = ByteArrayOutputStream()
val timeoutSeconds = 90L

// Act
blePsFtpClient.processServiceData(RFC77_PFTP_MTU_CHARACTERISTIC, frame0, 0, true)
Expand All @@ -113,7 +114,7 @@ internal class BlePsFtpClientTest {
blePsFtpClient.processServiceData(RFC77_PFTP_MTU_CHARACTERISTIC, frame10, 0, true)
blePsFtpClient.processServiceData(RFC77_PFTP_MTU_CHARACTERISTIC, frame11, 0, true)
blePsFtpClient.processServiceData(RFC77_PFTP_MTU_CHARACTERISTIC, frame12, 0, true)
blePsFtpClient.readResponse(output)
blePsFtpClient.readResponse(output, timeoutSeconds)

// Assert
val expectedArray = (frame0.drop(1) +
Expand Down
1 change: 1 addition & 0 deletions sources/iOS/ios-communications/Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ def swift_protobuf_pod
end

target 'iOSCommunications' do
swift_protobuf_pod
rx_swift_pod
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ public class PolarBleApiDefaultImpl {
///
/// - Returns: version in format major.minor.patch
public static func versionInfo() -> String {
return "5.1.0"
return "5.2.0"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ public protocol PolarBleApiObserver: AnyObject {
/// If PolarBleApi#disconnectFromPolarDevice is not called, a new connection attempt is dispatched automatically.
///
/// - Parameter identifier: Polar device info
func deviceDisconnected(_ identifier: PolarDeviceInfo)
/// - Parameter pairingError: If true, it indicates that the disconnection was caused by a pairing error. In this case, try removing the pairing from the system settings.
func deviceDisconnected(_ identifier: PolarDeviceInfo, pairingError: Bool)
}

/// Bluetooth state observer.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,9 @@ import UIKit
case .sessionOpenPark where session.previousState == .sessionOpen: fallthrough
case .sessionClosed where session.previousState == .sessionOpen: fallthrough
case .sessionClosed where session.previousState == .sessionClosing:
self.observer?.deviceDisconnected(info)
self.observer?.deviceDisconnected(info, pairingError: false)
case .sessionOpenPark where session.previousState == .sessionOpening:
self.observer?.deviceDisconnected(info, pairingError: true)
case .sessionOpening:
self.observer?.deviceConnecting(info)
case .sessionClosed: fallthrough
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ public struct Protocol_PbPFtpOperation {

#if swift(>=4.2)

extension Protocol_PbPFtpOperation.Command: CaseIterable {
extension Communications_PbPFtpOperation.Command {
// Support synthesized by the compiler.
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -704,8 +704,9 @@ public class BlePmdClient: BleGattClientBase {
var more = resp.more
while (more) {
let parameters = try self.pmdCpResponseQueue.poll(60)
more = parameters[0] != 0
resp.parameters.append(parameters.subdata(in: 1..<parameters.count))
let moreResponse = PmdControlPointResponse(parameters)
more = moreResponse.more
resp.parameters.append(Data(moreResponse.parameters))
}
return resp
}
Expand Down
Loading

0 comments on commit c878848

Please sign in to comment.