Skip to content

Commit

Permalink
5.6.0-beta
Browse files Browse the repository at this point in the history
  • Loading branch information
Samuli Määttä committed Dec 5, 2023
1 parent c68c75b commit 8ed5a90
Show file tree
Hide file tree
Showing 17 changed files with 1,410 additions and 161 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,21 @@

import com.polar.androidcommunications.api.ble.exceptions.BleAttributeError;
import com.polar.androidcommunications.api.ble.exceptions.BleDisconnected;
import com.polar.androidcommunications.api.ble.model.DisInfo;
import com.polar.androidcommunications.api.ble.model.gatt.BleGattBase;
import com.polar.androidcommunications.api.ble.model.gatt.BleGattTxInterface;
import com.polar.androidcommunications.common.ble.AtomicSet;
import com.polar.androidcommunications.common.ble.RxUtils;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;

import io.reactivex.rxjava3.core.BackpressureStrategy;
import io.reactivex.rxjava3.core.Flowable;
Expand All @@ -35,10 +42,15 @@ public class BleDisClient extends BleGattBase {
public static final UUID IEEE_11073_20601 = UUID.fromString("00002a2a-0000-1000-8000-00805f9b34fb");
public static final UUID PNP_ID = UUID.fromString("00002a50-0000-1000-8000-00805f9b34fb");

public static final String SYSTEM_ID_HEX = "SYSTEM_ID_HEX";

// store in map
private final HashMap<UUID, String> disInformation = new HashMap<>();
private final AtomicSet<FlowableEmitter<? super Pair<UUID, String>>> disObserverAtomicList = new AtomicSet<>();

private final Set<DisInfo> disInformationDataSet = new HashSet<>();
private final AtomicSet<FlowableEmitter<DisInfo>> disInfoObservers = new AtomicSet<>();

public BleDisClient(BleGattTxInterface txInterface) {
super(txInterface, DIS_SERVICE);
addCharacteristicRead(MODEL_NUMBER_STRING);
Expand All @@ -57,15 +69,26 @@ public void reset() {
super.reset();
synchronized (disInformation) {
disInformation.clear();
disInformationDataSet.clear();
}
RxUtils.postDisconnectedAndClearList(disObserverAtomicList);
RxUtils.postDisconnectedAndClearList(disInfoObservers);
}

@Override
public void processServiceData(final UUID characteristic, final byte[] data, int status, boolean notifying) {
if (status == 0) {
final String asciiRepresentation = new String(data, StandardCharsets.UTF_8);
synchronized (disInformation) {
disInformation.put(characteristic, new String(data, StandardCharsets.UTF_8));
disInformation.put(characteristic, asciiRepresentation);
}
synchronized (disInformationDataSet) {
if (characteristic.equals(BleDisClient.SYSTEM_ID)) {
final String hexRepresentation = systemIdBytesToHex(data);
disInformationDataSet.add(new DisInfo(BleDisClient.SYSTEM_ID_HEX, hexRepresentation));
} else {
disInformationDataSet.add(new DisInfo(characteristic.toString(), asciiRepresentation));
}
}
RxUtils.emitNext(disObserverAtomicList, object -> {
object.onNext(new Pair<>(characteristic, new String(data, StandardCharsets.UTF_8)));
Expand All @@ -75,6 +98,26 @@ public void processServiceData(final UUID characteristic, final byte[] data, int
}
}
});

RxUtils.emitNext(disInfoObservers, object -> {
disInformationDataSet.stream()
.filter(info -> (characteristic.equals(BleDisClient.SYSTEM_ID)
&& info.getKey().equals(BleDisClient.SYSTEM_ID_HEX))
|| (characteristic.toString().equals(info.getKey())))
.findFirst().ifPresent(object::onNext);

synchronized (disInformationDataSet) {
final Set<UUID> validUuids = disInformationDataSet.stream()
.map(DisInfo::getKey)
.filter(this::isValidUUIDString)
.map(UUID::fromString)
.collect(Collectors.toSet());

if (hasAllAvailableReadableCharacteristics(validUuids)) {
object.onComplete();
}
}
});
} else {
RxUtils.postError(disObserverAtomicList, new BleAttributeError("dis ", status));
}
Expand Down Expand Up @@ -118,5 +161,58 @@ public Flowable<Pair<UUID, String>> observeDisInfo(final boolean checkConnection
}
}, BackpressureStrategy.BUFFER).doFinally(() -> disObserverAtomicList.remove(observer[0]));
}

/**
* Produces: onNext, when a {@link DisInfo} has been read <BR>
* onCompleted, after all available {@link DisInfo} has been read <BR>
* onError, if client is not initially connected or ble disconnect's <BR>
*
* @param checkConnection, optionally check connection on subscribe <BR>
* @return Flowable stream emitting {@link DisInfo} <BR>
*/
public Flowable<DisInfo> observeDisInfoWithKeysAsStrings(final boolean checkConnection) {
final FlowableEmitter<DisInfo>[] observer = new FlowableEmitter[1];
return Flowable.create((FlowableOnSubscribe<DisInfo>) subscriber -> {
if (!checkConnection || BleDisClient.this.txInterface.isConnected()) {
observer[0] = subscriber;
disInfoObservers.add(subscriber);

synchronized (disInformationDataSet) {
for (DisInfo disInfo : disInformationDataSet) {
subscriber.onNext(disInfo);
}

final Set<UUID> validUuids = disInformationDataSet.stream()
.filter(disInfo -> isValidUUIDString(disInfo.getKey()))
.map(disInfo -> UUID.fromString(disInfo.getKey()))
.collect(Collectors.toSet());

if (hasAllAvailableReadableCharacteristics(validUuids)) {
subscriber.onComplete();
}
}
} else if (!subscriber.isCancelled()) {
subscriber.tryOnError(new BleDisconnected());
}
}, BackpressureStrategy.BUFFER)
.doFinally(() -> disInfoObservers.remove(observer[0]));
}

private String systemIdBytesToHex(final byte[] bytes) {
final StringBuilder hex = new StringBuilder(2 * bytes.length);
for (int i = bytes.length - 1; i >= 0; i--) {
hex.append(String.format("%02X", bytes[i]));
}
return hex.toString();
}

private boolean isValidUUIDString(final String s) {
try {
UUID.fromString(s);
return true;
} catch (final IllegalArgumentException e) {
return false;
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ class BleHtsClient(txInterface: BleGattTxInterface?) :

private val htsObserverAtomicList = AtomicSet<FlowableEmitter<in TemperatureMeasurement>>()

override fun reset() {
super.reset()
RxUtils.postDisconnectedAndClearList(htsObserverAtomicList)
}

override fun processServiceData(
characteristic: UUID?,
data: ByteArray?,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ import com.polar.androidcommunications.api.ble.model.gatt.client.pmd.PmdMeasurem
internal object OfflineRecordingUtility {

fun mapOfflineRecordingFileNameToMeasurementType(fileName: String): PmdMeasurementType {
return when (fileName) {
"ACC.REC" -> PmdMeasurementType.ACC
"GYRO.REC" -> PmdMeasurementType.GYRO
"MAG.REC" -> PmdMeasurementType.MAGNETOMETER
"PPG.REC" -> PmdMeasurementType.PPG
"PPI.REC" -> PmdMeasurementType.PPI
"HR.REC" -> PmdMeasurementType.OFFLINE_HR
else -> throw Exception("Unknown offline file $fileName")
val fileNameWithoutExtension = fileName.substringBeforeLast(".")
return when (fileNameWithoutExtension.replace(Regex("\\d+"), "")) {
"ACC" -> PmdMeasurementType.ACC
"GYRO" -> PmdMeasurementType.GYRO
"MAG" -> PmdMeasurementType.MAGNETOMETER
"PPG" -> PmdMeasurementType.PPG
"PPI" -> PmdMeasurementType.PPI
"HR" -> PmdMeasurementType.OFFLINE_HR
else -> throw IllegalArgumentException("Unknown offline file $fileName")
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright © 2019 Polar Electro Oy. All rights reserved.
package com.polar.sdk.api

import com.polar.androidcommunications.api.ble.model.DisInfo
import com.polar.sdk.api.PolarBleApi.PolarDeviceDataType
import com.polar.sdk.api.model.PolarDeviceInfo
import com.polar.sdk.api.model.PolarHrData
Expand Down Expand Up @@ -83,6 +84,14 @@ interface PolarBleApiCallbackProvider {
*/
fun disInformationReceived(identifier: String, uuid: UUID, value: String)

/**
* DIS information received. Requires feature [PolarBleApi.PolarBleSdkFeature.FEATURE_DEVICE_INFO]
*
* @param identifier Polar device id or bt address
* @param disInfo [DisInfo] key-value pair
*/
fun disInformationReceived(identifier: String, disInfo: DisInfo)

/**
* Battery level received. Requires feature [PolarBleApi.PolarBleSdkFeature.FEATURE_BATTERY_INFO]
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@ object PolarBleApiDefaultImpl {
*/
@JvmStatic
fun versionInfo(): String {
return "5.5.0"
return "5.6.0-beta"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,34 @@ interface PolarOfflineRecordingApi {
*/
fun getOfflineRecord(identifier: String, entry: PolarOfflineRecordingEntry, secret: PolarRecordingSecret? = null): Single<PolarOfflineRecordingData>

/**
* List split offline recordings stored in the device.
*
* @param identifier Polar device id found printed on the sensor/device or bt address
* @return [Flowable] stream
* <BR></BR> - onNext the found offline recording entry in [PolarOfflineRecordingEntry]
* <BR></BR> - onComplete the listing completed
* <BR></BR> - onError listing request failed
*/
fun listSplitOfflineRecordings(identifier: String): Flowable<PolarOfflineRecordingEntry>

/**
* Fetch split recording from the Polar device.
*
* Note, the fetching of the recording may take several seconds if the recording is big.
*
* @param identifier Polar device id found printed on the sensor/device or bt address
* @param entry The offline recording to be fetched
* @param secret If the secret is provided in [startOfflineRecording] or [setOfflineRecordingTrigger]
* then the same secret must be provided when fetching the offline record
*
* @return [Single]
* Produces:
* <BR></BR> - onSuccess the offline recording data
* <BR></BR> - onError fetch recording request failed
*/
fun getSplitOfflineRecord(identifier: String, entry: PolarOfflineRecordingEntry, secret: PolarRecordingSecret? = null): Single<PolarOfflineRecordingData>

/**
* Removes offline recording from the device
*
Expand Down
Loading

0 comments on commit 8ed5a90

Please sign in to comment.