From 132dfc583a9babc1de8a28389ad77c70f4426704 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuli=20M=C3=A4=C3=A4tt=C3=A4?= Date: Tue, 5 Dec 2023 10:40:54 +0200 Subject: [PATCH] 5.6.0-beta1 --- PolarBleSdk.podspec | 2 +- .../api/ble/model/DisInfo.kt | 9 + .../ble/model/gatt/client/BleDisClient.java | 98 +++- .../api/ble/model/gatt/client/BleHtsClient.kt | 5 + .../OfflineRecordingUtility.kt | 17 +- .../sdk/api/PolarBleApiCallbackProvider.kt | 9 + .../polar/sdk/api/PolarBleApiDefaultImpl.kt | 2 +- .../polar/sdk/api/PolarOfflineRecordingApi.kt | 28 ++ .../api/model/PolarOfflineRecordingData.kt | 126 ++++- .../java/com/polar/sdk/impl/BDBleApiImpl.kt | 467 +++++++++++++++++- .../OfflineRecordingUtilityTest.kt | 54 ++ .../sdk/api/PolarBleApiDefaultImpl.swift | 2 +- .../sdk/api/PolarBleApiObservers.swift | 11 +- .../sdk/api/PolarOfflineRecordingApi.swift | 25 +- .../sdk/impl/PolarBleApiImpl.swift | 398 ++++++++++++++- .../api/model/gatt/client/BleDisClient.swift | 68 ++- .../OfflineRecordingUtils.swift | 17 +- .../OfflineRecordingUtilsTest.swift | 48 ++ .../project.pbxproj | 196 ++++---- 19 files changed, 1420 insertions(+), 162 deletions(-) create mode 100644 sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/DisInfo.kt create mode 100644 sources/Android/android-communications/library/src/test/java/com/polar/androidcommunications/api/ble/model/offlinerecording/OfflineRecordingUtilityTest.kt create mode 100644 sources/iOS/ios-communications/Tests/iOSCommunicationsTests/OfflineRecordingUtilsTest.swift diff --git a/PolarBleSdk.podspec b/PolarBleSdk.podspec index 58999c8e..8ddb067f 100644 --- a/PolarBleSdk.podspec +++ b/PolarBleSdk.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'PolarBleSdk' - s.version = '5.5.0' + s.version = '5.6.0-beta1' s.summary = 'SDK for Polar sensors' s.homepage = 'https://github.com/polarofficial/polar-ble-sdk' s.license = { :type => 'Custom', :file => 'Polar_SDK_License.txt' } diff --git a/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/DisInfo.kt b/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/DisInfo.kt new file mode 100644 index 00000000..3212f4cd --- /dev/null +++ b/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/DisInfo.kt @@ -0,0 +1,9 @@ +package com.polar.androidcommunications.api.ble.model + +/** + * DIS info key-value pair. + */ +data class DisInfo( + val key: String, + val value: String +) \ No newline at end of file diff --git a/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/BleDisClient.java b/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/BleDisClient.java index 942605f6..aa356479 100644 --- a/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/BleDisClient.java +++ b/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/BleDisClient.java @@ -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; @@ -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 disInformation = new HashMap<>(); private final AtomicSet>> disObserverAtomicList = new AtomicSet<>(); + private final Set disInformationDataSet = new HashSet<>(); + private final AtomicSet> disInfoObservers = new AtomicSet<>(); + public BleDisClient(BleGattTxInterface txInterface) { super(txInterface, DIS_SERVICE); addCharacteristicRead(MODEL_NUMBER_STRING); @@ -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))); @@ -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 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)); } @@ -118,5 +161,58 @@ public Flowable> observeDisInfo(final boolean checkConnection } }, BackpressureStrategy.BUFFER).doFinally(() -> disObserverAtomicList.remove(observer[0])); } + + /** + * Produces: onNext, when a {@link DisInfo} has been read
+ * onCompleted, after all available {@link DisInfo} has been read
+ * onError, if client is not initially connected or ble disconnect's
+ * + * @param checkConnection, optionally check connection on subscribe
+ * @return Flowable stream emitting {@link DisInfo}
+ */ + public Flowable observeDisInfoWithKeysAsStrings(final boolean checkConnection) { + final FlowableEmitter[] observer = new FlowableEmitter[1]; + return Flowable.create((FlowableOnSubscribe) subscriber -> { + if (!checkConnection || BleDisClient.this.txInterface.isConnected()) { + observer[0] = subscriber; + disInfoObservers.add(subscriber); + + synchronized (disInformationDataSet) { + for (DisInfo disInfo : disInformationDataSet) { + subscriber.onNext(disInfo); + } + + final Set 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; + } + } } diff --git a/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/BleHtsClient.kt b/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/BleHtsClient.kt index 9c16c08c..98c4cf21 100755 --- a/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/BleHtsClient.kt +++ b/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/gatt/client/BleHtsClient.kt @@ -37,6 +37,11 @@ class BleHtsClient(txInterface: BleGattTxInterface?) : private val htsObserverAtomicList = AtomicSet>() + override fun reset() { + super.reset() + RxUtils.postDisconnectedAndClearList(htsObserverAtomicList) + } + override fun processServiceData( characteristic: UUID?, data: ByteArray?, diff --git a/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/offlinerecording/OfflineRecordingUtility.kt b/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/offlinerecording/OfflineRecordingUtility.kt index 689c2fc3..979a2216 100644 --- a/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/offlinerecording/OfflineRecordingUtility.kt +++ b/sources/Android/android-communications/library/src/main/java/com/polar/androidcommunications/api/ble/model/offlinerecording/OfflineRecordingUtility.kt @@ -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") } } } diff --git a/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/PolarBleApiCallbackProvider.kt b/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/PolarBleApiCallbackProvider.kt index f95413cd..58125414 100644 --- a/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/PolarBleApiCallbackProvider.kt +++ b/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/PolarBleApiCallbackProvider.kt @@ -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 @@ -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] * diff --git a/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/PolarBleApiDefaultImpl.kt b/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/PolarBleApiDefaultImpl.kt index 1a8f4264..5a7b83af 100644 --- a/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/PolarBleApiDefaultImpl.kt +++ b/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/PolarBleApiDefaultImpl.kt @@ -25,6 +25,6 @@ object PolarBleApiDefaultImpl { */ @JvmStatic fun versionInfo(): String { - return "5.5.0" + return "5.6.0-beta" } } \ No newline at end of file diff --git a/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/PolarOfflineRecordingApi.kt b/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/PolarOfflineRecordingApi.kt index d167198a..d485aee2 100644 --- a/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/PolarOfflineRecordingApi.kt +++ b/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/PolarOfflineRecordingApi.kt @@ -102,6 +102,34 @@ interface PolarOfflineRecordingApi { */ fun getOfflineRecord(identifier: String, entry: PolarOfflineRecordingEntry, secret: PolarRecordingSecret? = null): Single + /** + * 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 + *

- onNext the found offline recording entry in [PolarOfflineRecordingEntry] + *

- onComplete the listing completed + *

- onError listing request failed + */ + fun listSplitOfflineRecordings(identifier: String): Flowable + + /** + * 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: + *

- onSuccess the offline recording data + *

- onError fetch recording request failed + */ + fun getSplitOfflineRecord(identifier: String, entry: PolarOfflineRecordingEntry, secret: PolarRecordingSecret? = null): Single + /** * Removes offline recording from the device * diff --git a/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/model/PolarOfflineRecordingData.kt b/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/model/PolarOfflineRecordingData.kt index daf91821..94614e12 100644 --- a/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/model/PolarOfflineRecordingData.kt +++ b/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/model/PolarOfflineRecordingData.kt @@ -16,7 +16,29 @@ sealed class PolarOfflineRecordingData(val startTime: Calendar, val settings: Po * @property startTime the time recording was started in UTC time * @property settings the settings used while recording */ - class AccOfflineRecording(val data: PolarAccelerometerData, startTime: Calendar, settings: PolarSensorSetting) : PolarOfflineRecordingData(startTime, settings) + class AccOfflineRecording( + val data: PolarAccelerometerData, + startTime: Calendar, + settings: PolarSensorSetting + ) : PolarOfflineRecordingData(startTime, settings) { + internal fun appendAccData( + existingRecording: AccOfflineRecording, + newData: PolarAccelerometerData, + settings: PolarSensorSetting + ): AccOfflineRecording { + val mergedSamples = mutableListOf() + mergedSamples.addAll(existingRecording.data.samples) + mergedSamples.addAll(newData.samples) + return AccOfflineRecording( + PolarAccelerometerData( + mergedSamples, + startTime.timeInMillis + ), + startTime, + settings + ) + } + } /** * Gyroscope Offline recording data @@ -25,7 +47,29 @@ sealed class PolarOfflineRecordingData(val startTime: Calendar, val settings: Po * @property startTime the time recording was started in UTC time * @property settings the settings used while recording */ - class GyroOfflineRecording(val data: PolarGyroData, startTime: Calendar, settings: PolarSensorSetting) : PolarOfflineRecordingData(startTime, settings) + class GyroOfflineRecording( + val data: PolarGyroData, + startTime: Calendar, + settings: PolarSensorSetting + ) : PolarOfflineRecordingData(startTime, settings) { + internal fun appendGyroData( + existingRecording: GyroOfflineRecording, + newData: PolarGyroData, + settings: PolarSensorSetting + ): GyroOfflineRecording { + val mergedSamples = mutableListOf() + mergedSamples.addAll(existingRecording.data.samples) + mergedSamples.addAll(newData.samples) + return GyroOfflineRecording( + PolarGyroData( + mergedSamples, + startTime.timeInMillis + ), + startTime, + settings + ) + } + } /** * Magnetometer offline recording data @@ -34,7 +78,28 @@ sealed class PolarOfflineRecordingData(val startTime: Calendar, val settings: Po * @property startTime the time recording was started in UTC time * @property settings the settings used while recording */ - class MagOfflineRecording(val data: PolarMagnetometerData, startTime: Calendar, settings: PolarSensorSetting) : PolarOfflineRecordingData(startTime, settings) + class MagOfflineRecording( + val data: PolarMagnetometerData, + startTime: Calendar, + settings: PolarSensorSetting? + ) : PolarOfflineRecordingData(startTime, settings) { + internal fun appendMagData( + existingRecording: MagOfflineRecording, + newData: PolarMagnetometerData + ): MagOfflineRecording { + val mergedSamples = mutableListOf() + mergedSamples.addAll(existingRecording.data.samples) + mergedSamples.addAll(newData.samples) + return MagOfflineRecording( + PolarMagnetometerData( + mergedSamples, + startTime.timeInMillis + ), + startTime, + settings + ) + } + } /** * PPG (Photoplethysmography) offline recording data @@ -43,7 +108,28 @@ sealed class PolarOfflineRecordingData(val startTime: Calendar, val settings: Po * @property startTime the time recording was started in UTC time * @property settings the settings used while recording */ - class PpgOfflineRecording(val data: PolarPpgData, startTime: Calendar, settings: PolarSensorSetting) : PolarOfflineRecordingData(startTime, settings) + class PpgOfflineRecording( + val data: PolarPpgData, + startTime: Calendar, + settings: PolarSensorSetting? + ) : PolarOfflineRecordingData(startTime, settings) { + internal fun appendPpgData( + existingRecording: PpgOfflineRecording, + newData: PolarPpgData + ): PpgOfflineRecording { + val mergedSamples = mutableListOf() + mergedSamples.addAll(existingRecording.data.samples) + mergedSamples.addAll(newData.samples) + return PpgOfflineRecording( + PolarPpgData( + mergedSamples, + newData.type + ), + startTime, + settings + ) + } + } /** * PPI (Peak-to-peak interval) offline recording data @@ -51,7 +137,21 @@ sealed class PolarOfflineRecordingData(val startTime: Calendar, val settings: Po * @property data ppi data * @property startTime the time recording was started in UTC time */ - class PpiOfflineRecording(val data: PolarPpiData, startTime: Calendar) : PolarOfflineRecordingData(startTime, null) + class PpiOfflineRecording(val data: PolarPpiData, startTime: Calendar) : + PolarOfflineRecordingData(startTime, null) { + internal fun appendPpiData( + existingRecording: PpiOfflineRecording, + newData: PolarPpiData + ): PpiOfflineRecording { + val mergedSamples = mutableListOf() + mergedSamples.addAll(existingRecording.data.samples) + mergedSamples.addAll(newData.samples) + return PpiOfflineRecording( + PolarPpiData(mergedSamples), + startTime + ) + } + } /** * Heart rate offline recording data @@ -59,5 +159,19 @@ sealed class PolarOfflineRecordingData(val startTime: Calendar, val settings: Po * @property data heart rate data * @property startTime the time recording was started in UTC time */ - class HrOfflineRecording(val data: PolarHrData, startTime: Calendar) : PolarOfflineRecordingData(startTime, null) + class HrOfflineRecording(val data: PolarHrData, startTime: Calendar) : + PolarOfflineRecordingData(startTime, null) { + internal fun appendHrData( + existingRecording: HrOfflineRecording, + newData: PolarHrData + ): HrOfflineRecording { + val mergedSamples = mutableListOf() + mergedSamples.addAll(existingRecording.data.samples) + mergedSamples.addAll(newData.samples) + return HrOfflineRecording( + PolarHrData(mergedSamples), + startTime + ) + } + } } diff --git a/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/impl/BDBleApiImpl.kt b/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/impl/BDBleApiImpl.kt index 064ffec6..9618f2d5 100644 --- a/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/impl/BDBleApiImpl.kt +++ b/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/impl/BDBleApiImpl.kt @@ -57,10 +57,8 @@ import com.polar.sdk.impl.utils.PolarTimeUtils.pbLocalTimeToJavaCalendar import fi.polar.remote.representation.protobuf.ExerciseSamples.PbExerciseSamples import fi.polar.remote.representation.protobuf.Types.* import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers -import io.reactivex.rxjava3.core.Completable -import io.reactivex.rxjava3.core.CompletableEmitter -import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.core.Single +import io.reactivex.rxjava3.core.* +import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.disposables.Disposable import io.reactivex.rxjava3.functions.BiFunction import io.reactivex.rxjava3.functions.Consumer @@ -555,12 +553,15 @@ class BDBleApiImpl private constructor(context: Context, features: Set { BleLogger.d(TAG, "Start offline recording listing in device: $identifier") - return fetchRecursively( + + fetchRecursively( client = client, path = "/U/0/", condition = { entry -> @@ -569,16 +570,54 @@ class BDBleApiImpl private constructor(context: Context, features: Set -> - val components = entry.first.split("/").toTypedArray() - val format = SimpleDateFormat("yyyyMMdd HHmmss", Locale.getDefault()) - val date = format.parse(components[3] + " " + components[5]) - ?: throw PolarInvalidArgument("Listing offline recording failed. Cannot parse create data from date ${components[3]} and time ${components[5]}") - val type = mapPmdClientFeatureToPolarFeature(mapOfflineRecordingFileNameToMeasurementType(components[6])) - PolarOfflineRecordingEntry(path = entry.first, size = entry.second, date = date, type = type) - }.onErrorResumeNext { throwable: Throwable -> Flowable.error(handleError(throwable)) } + ) + .map { entry: Pair -> + val components = entry.first.split("/").toTypedArray() + val format = SimpleDateFormat("yyyyMMdd HHmmss", Locale.getDefault()) + val date = format.parse(components[3] + " " + components[5]) + ?: throw PolarInvalidArgument( + "Listing offline recording failed. Cannot parse create data from date ${components[3]} and time ${components[5]}" + ) + val type = mapPmdClientFeatureToPolarFeature( + mapOfflineRecordingFileNameToMeasurementType(components[6]) + ) + PolarOfflineRecordingEntry( + path = entry.first, + size = entry.second, + date = date, + type = type + ) + } + .groupBy { entry -> entry.date } + .flatMap { groupedEntries -> + groupedEntries + .toList() + .flatMapPublisher { entriesList -> + var totalSize = 0 + entriesList.forEach { (_, size) -> + totalSize += size.toInt() + } + Flowable.fromIterable( + entriesList.map { + PolarOfflineRecordingEntry( + path = it.path.replace( + Regex("\\d+\\.REC$"), + ".REC" + ), + size = totalSize.toLong(), + date = it.date, + type = it.type + ) + } + ) + } + .distinct { entry -> entry.date } + .onErrorResumeNext { throwable: Throwable -> + Flowable.error(handleError(throwable)) + } + } } - else -> return Flowable.error(PolarOperationNotSupported()) + else -> Flowable.error(PolarServiceNotAvailable()) } } @@ -669,7 +708,380 @@ class BDBleApiImpl private constructor(context: Context, features: Set { + override fun getOfflineRecord( + identifier: String, + entry: PolarOfflineRecordingEntry, + secret: PolarRecordingSecret? + ): Single { + val session = try { + sessionPsFtpClientReady(identifier) + } catch (e: Exception) { + return Single.error(e) + } + val client = session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE) as BlePsFtpClient? + ?: return Single.error(PolarServiceNotAvailable()) + val fsType = getFileSystemType(session.polarDeviceType) + + var polarAccData: PolarOfflineRecordingData.AccOfflineRecording? = null + var polarGyroData: PolarOfflineRecordingData.GyroOfflineRecording? = null + var polarMagData: PolarOfflineRecordingData.MagOfflineRecording? = null + var polarPpgData: PolarOfflineRecordingData.PpgOfflineRecording? = null + var polarPpiData: PolarOfflineRecordingData.PpiOfflineRecording? = null + var polarHrData: PolarOfflineRecordingData.HrOfflineRecording? = null + return if (fsType == FileSystemType.SAGRFC2_FILE_SYSTEM) { + getSubRecordingCount(identifier, entry) + .flatMap { count -> + Single.create { emitter -> + // Old format + if (count == 0) { + val builder = PftpRequest.PbPFtpOperation.newBuilder() + builder.command = PftpRequest.PbPFtpOperation.Command.GET + builder.path = entry.path + + BleLogger.d(TAG, "Offline record get. Device: $identifier Path: ${entry.path} Secret used: ${secret != null}") + client.sendNotification(PftpNotification.PbPFtpHostToDevNotification.INITIALIZE_SESSION_VALUE, null) + .andThen(client.request(builder.build().toByteArray())) + .map { byteArrayOutputStream: ByteArrayOutputStream -> + val pmdSecret = secret?.let { mapPolarSecretToPmdSecret(it) } + OfflineRecordingData.parseDataFromOfflineFile(byteArrayOutputStream.toByteArray(), mapPolarFeatureToPmdClientMeasurementType(entry.type), pmdSecret) + } + .map { offlineRecData -> + val polarSettings = offlineRecData.recordingSettings?.let { mapPmdSettingsToPolarSettings(it, fromSelected = false) } + val startTime = offlineRecData.startTime + when (val offlineData = offlineRecData.data) { + is AccData -> { + polarSettings ?: throw PolarOfflineRecordingError("getOfflineRecord failed. Acc data is missing settings") + PolarOfflineRecordingData.AccOfflineRecording(mapPmdClientAccDataToPolarAcc(offlineData), startTime, polarSettings) + } + is GyrData -> { + polarSettings ?: throw PolarOfflineRecordingError("getOfflineRecord failed. Gyro data is missing settings") + PolarOfflineRecordingData.GyroOfflineRecording(mapPmdClientGyroDataToPolarGyro(offlineData), startTime, polarSettings) + } + is MagData -> { + polarSettings ?: throw PolarOfflineRecordingError("getOfflineRecord failed. Magnetometer data is missing settings") + PolarOfflineRecordingData.MagOfflineRecording(mapPmdClientMagDataToPolarMagnetometer(offlineData), startTime, polarSettings) + } + is PpgData -> { + polarSettings ?: throw PolarOfflineRecordingError("getOfflineRecord failed. Ppg data is missing settings") + PolarOfflineRecordingData.PpgOfflineRecording(mapPMDClientPpgDataToPolarPpg(offlineData), startTime, polarSettings) + } + is PpiData -> PolarOfflineRecordingData.PpiOfflineRecording(mapPMDClientPpiDataToPolarPpiData(offlineData), startTime) + is OfflineHrData -> PolarOfflineRecordingData.HrOfflineRecording(mapPMDClientOfflineHrDataToPolarHrData(offlineData), startTime) + else -> throw PolarOfflineRecordingError("Data type is not supported.") + } + }.onErrorResumeNext { throwable: Throwable -> Single.error(handleError(throwable)) } + .doFinally { + client.sendNotification( + PftpNotification.PbPFtpHostToDevNotification.TERMINATE_SESSION_VALUE, + null + ) + .onErrorComplete() + .subscribe() + } + .subscribe { polarOfflineRecordingData -> emitter.onSuccess(polarOfflineRecordingData) } + } + Observable.fromIterable(0 until count) + .flatMapSingle { subRecordingIndex -> + val subRecordingPath = if (entry.path.matches(Regex(".*\\.REC$"))) { + entry.path.replace( + Regex("(\\.REC)$"), + "$subRecordingIndex.REC" + ) + } else { + entry.path.replace( + Regex("""\d(?=\D*$)"""), + subRecordingIndex.toString() + ) + } + + val builder = PftpRequest.PbPFtpOperation.newBuilder() + builder.command = PftpRequest.PbPFtpOperation.Command.GET + builder.path = subRecordingPath.ifBlank { + entry.path + } + + BleLogger.d( + TAG, + "Offline record get. Device: $identifier Path: ${builder.path} Secret used: ${secret != null}" + ) + + client.sendNotification( + PftpNotification.PbPFtpHostToDevNotification.INITIALIZE_SESSION_VALUE, + null + ) + .andThen(client.request(builder.build().toByteArray())) + .flatMap { byteArrayOutputStream -> + val pmdSecret = + secret?.let { mapPolarSecretToPmdSecret(it) } + + val offlineRecordingData = + OfflineRecordingData.parseDataFromOfflineFile( + byteArrayOutputStream.toByteArray(), + mapPolarFeatureToPmdClientMeasurementType(entry.type), + pmdSecret + ) + + when (val offlineData = offlineRecordingData.data) { + is AccData -> { + val polarSettings = + offlineRecordingData.recordingSettings?.let { + mapPmdSettingsToPolarSettings( + it, + fromSelected = false + ) + } + ?: throw PolarOfflineRecordingError("getOfflineRecord failed. Acc data is missing settings") + + polarAccData?.let { existingData -> + polarAccData = existingData.appendAccData( + existingData, + mapPmdClientAccDataToPolarAcc(offlineData), + polarSettings + ) + } ?: run { + polarAccData = + PolarOfflineRecordingData.AccOfflineRecording( + mapPmdClientAccDataToPolarAcc(offlineData), + offlineRecordingData.startTime, + polarSettings + ) + } + } + is GyrData -> { + val polarSettings = + offlineRecordingData.recordingSettings?.let { + mapPmdSettingsToPolarSettings( + it, + fromSelected = false + ) + } + ?: throw PolarOfflineRecordingError("getOfflineRecord failed. Gyro data is missing settings") + + polarGyroData?.let { existingData -> + polarGyroData = existingData.appendGyroData( + existingData, + mapPmdClientGyroDataToPolarGyro(offlineData), + polarSettings + ) + } ?: run { + polarGyroData = + PolarOfflineRecordingData.GyroOfflineRecording( + mapPmdClientGyroDataToPolarGyro(offlineData), + offlineRecordingData.startTime, + polarSettings + ) + } + } + is MagData -> { + val polarSettings = + offlineRecordingData.recordingSettings?.let { + mapPmdSettingsToPolarSettings( + it, + fromSelected = false + ) + } + ?: throw PolarOfflineRecordingError("getOfflineRecord failed. Magnetometer data is missing settings") + + polarMagData?.let { existingData -> + polarMagData = existingData.appendMagData( + existingData, + mapPmdClientMagDataToPolarMagnetometer(offlineData) + ) + } ?: run { + polarMagData = + PolarOfflineRecordingData.MagOfflineRecording( + mapPmdClientMagDataToPolarMagnetometer(offlineData), + offlineRecordingData.startTime, + polarSettings + ) + } + } + is PpgData -> { + val polarSettings = + offlineRecordingData.recordingSettings?.let { + mapPmdSettingsToPolarSettings( + it, + fromSelected = false + ) + } + ?: throw PolarOfflineRecordingError("getOfflineRecord failed. Ppg data is missing settings") + + polarPpgData?.let { existingData -> + polarPpgData = existingData.appendPpgData( + existingData, + mapPMDClientPpgDataToPolarPpg(offlineData) + ) + } ?: run { + polarPpgData = + PolarOfflineRecordingData.PpgOfflineRecording( + mapPMDClientPpgDataToPolarPpg( + offlineData + ), + offlineRecordingData.startTime, + polarSettings + ) + } + } + is PpiData -> { + if (polarPpiData == null) { + polarPpiData = + PolarOfflineRecordingData.PpiOfflineRecording( + mapPMDClientPpiDataToPolarPpiData( + offlineData + ), + offlineRecordingData.startTime + ) + } else { + val existingData = polarPpiData + + polarPpiData = existingData?.appendPpiData( + existingData, + PolarOfflineRecordingData.PpiOfflineRecording( + mapPMDClientPpiDataToPolarPpiData( + offlineData + ), + offlineRecordingData.startTime + ).data + ) + } + } + is OfflineHrData -> { + polarHrData?.let { existingData -> + polarHrData = existingData.appendHrData( + existingData, + mapPMDClientOfflineHrDataToPolarHrData(offlineData) + ) + } ?: run { + polarHrData = + PolarOfflineRecordingData.HrOfflineRecording( + mapPMDClientOfflineHrDataToPolarHrData(offlineData), + offlineRecordingData.startTime + ) + } + } + else -> throw PolarOfflineRecordingError("Data type is not supported.") + } + Single.just(true) + } + } + .toList() + .subscribe( + { + polarPpiData?.let { + emitter.onSuccess( + polarPpiData as PolarOfflineRecordingData + ) + } + + polarPpgData?.let { + emitter.onSuccess( + polarPpgData as PolarOfflineRecordingData + ) + } + + polarAccData?.let { + emitter.onSuccess( + polarAccData as PolarOfflineRecordingData + ) + } + + polarGyroData?.let { + emitter.onSuccess( + polarGyroData as PolarOfflineRecordingData + ) + } + + polarMagData?.let { + emitter.onSuccess( + polarMagData as PolarOfflineRecordingData + ) + } + + polarHrData?.let { + emitter.onSuccess( + polarHrData as PolarOfflineRecordingData + ) + } + }, + { throwable -> + emitter.onError(throwable) + } + ) + } + } + } else { + Single.error(PolarOperationNotSupported()) + } + } + + private fun getSubRecordingCount( + identifier: String, + entry: PolarOfflineRecordingEntry + ): Single { + return Single.defer { + try { + val session = sessionPsFtpClientReady(identifier) + val client = + session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE) as BlePsFtpClient? + ?: throw PolarServiceNotAvailable() + + val builder = PftpRequest.PbPFtpOperation.newBuilder() + builder.command = PftpRequest.PbPFtpOperation.Command.GET + val directoryPath = entry.path.substring(0, entry.path.lastIndexOf("/") + 1) + builder.path = directoryPath + + client.request(builder.build().toByteArray()) + .map { byteArrayOutputStream -> + val directory = + PbPFtpDirectory.parseFrom(byteArrayOutputStream.toByteArray()) + val prefix = entry.path.substringAfterLast("/").substringBefore(".REC") + val matchingEntries = directory.entriesList.filter { it.name.startsWith(prefix) && Regex("\\d\\.").containsMatchIn(it.name) } + matchingEntries.size + } + .onErrorResumeNext { throwable: Throwable -> + Single.error(throwable) + } + } catch (error: Throwable) { + Single.error(error) + } + } + } + + override fun listSplitOfflineRecordings(identifier: String): Flowable { + val session = try { + sessionPsFtpClientReady(identifier) + } catch (error: Throwable) { + return Flowable.error(error) + } + val client = session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE) as BlePsFtpClient? ?: return Flowable.error(PolarServiceNotAvailable()) + + when (getFileSystemType(session.polarDeviceType)) { + FileSystemType.SAGRFC2_FILE_SYSTEM -> { + BleLogger.d(TAG, "Start split offline recording listing in device: $identifier") + return fetchRecursively( + client = client, + path = "/U/0/", + condition = { entry -> + entry.matches(Regex("^(\\d{8})(/)")) || + entry == "R/" || + entry.matches(Regex("^(\\d{6})(/)")) || + entry.contains(".REC") + } + ).map { entry: Pair -> + val components = entry.first.split("/").toTypedArray() + val format = SimpleDateFormat("yyyyMMdd HHmmss", Locale.getDefault()) + val date = format.parse(components[3] + " " + components[5]) + ?: throw PolarInvalidArgument("Listing offline recording failed. Cannot parse create data from date ${components[3]} and time ${components[5]}") + val type = mapPmdClientFeatureToPolarFeature(mapOfflineRecordingFileNameToMeasurementType(components[6])) + PolarOfflineRecordingEntry(path = entry.first, size = entry.second, date = date, type = type) + }.onErrorResumeNext { throwable: Throwable -> Flowable.error(handleError(throwable)) } + } + else -> return Flowable.error(PolarOperationNotSupported()) + } + } + + + override fun getSplitOfflineRecord(identifier: String, entry: PolarOfflineRecordingEntry, secret: PolarRecordingSecret?): Single { val session = try { sessionPsFtpClientReady(identifier) } catch (e: Exception) { @@ -683,7 +1095,7 @@ class BDBleApiImpl private constructor(context: Context, features: Set @@ -695,24 +1107,24 @@ class BDBleApiImpl private constructor(context: Context, features: Set { - polarSettings ?: throw PolarOfflineRecordingError("getOfflineRecord failed. Acc data is missing settings") + polarSettings ?: throw PolarOfflineRecordingError("getSplitOfflineRecord failed. Acc data is missing settings") PolarOfflineRecordingData.AccOfflineRecording(mapPmdClientAccDataToPolarAcc(offlineData), startTime, polarSettings) } is GyrData -> { - polarSettings ?: throw PolarOfflineRecordingError("getOfflineRecord failed. Gyro data is missing settings") + polarSettings ?: throw PolarOfflineRecordingError("getSplitOfflineRecord failed. Gyro data is missing settings") PolarOfflineRecordingData.GyroOfflineRecording(mapPmdClientGyroDataToPolarGyro(offlineData), startTime, polarSettings) } is MagData -> { - polarSettings ?: throw PolarOfflineRecordingError("getOfflineRecord failed. Magnetometer data is missing settings") + polarSettings ?: throw PolarOfflineRecordingError("getSplitOfflineRecord failed. Magnetometer data is missing settings") PolarOfflineRecordingData.MagOfflineRecording(mapPmdClientMagDataToPolarMagnetometer(offlineData), startTime, polarSettings) } is PpgData -> { - polarSettings ?: throw PolarOfflineRecordingError("getOfflineRecord failed. Ppg data is missing settings") + polarSettings ?: throw PolarOfflineRecordingError("getSplitOfflineRecord failed. Ppg data is missing settings") PolarOfflineRecordingData.PpgOfflineRecording(mapPMDClientPpgDataToPolarPpg(offlineData), startTime, polarSettings) } is PpiData -> PolarOfflineRecordingData.PpiOfflineRecording(mapPMDClientPpiDataToPolarPpiData(offlineData), startTime) is OfflineHrData -> PolarOfflineRecordingData.HrOfflineRecording(mapPMDClientOfflineHrDataToPolarHrData(offlineData), startTime) - else -> throw PolarOfflineRecordingError("Data type is not supported.") + else -> throw PolarOfflineRecordingError("getSplitOfflineRecord failed. Data type is not supported.") } }.onErrorResumeNext { throwable: Throwable -> Single.error(handleError(throwable)) } .doFinally { @@ -723,7 +1135,6 @@ class BDBleApiImpl private constructor(context: Context, features: Set { val bleDisClient = session.fetchClient(BleDisClient.DIS_SERVICE) as BleDisClient? if (bleDisClient != null) { - return@flatMap bleDisClient.observeDisInfo(true) + return@flatMap Flowable.merge(bleDisClient.observeDisInfo(true) .observeOn(AndroidSchedulers.mainThread()) .doOnNext { pair: android.util.Pair -> callback?.disInformationReceived(deviceId, pair.first!!, pair.second!!) - } + }, bleDisClient.observeDisInfoWithKeysAsStrings(true) + .observeOn(AndroidSchedulers.mainThread()) + .doOnNext { disInfo -> + callback?.disInformationReceived(deviceId, disInfo) + }) } } BlePsFtpUtils.RFC77_PFTP_SERVICE -> { diff --git a/sources/Android/android-communications/library/src/test/java/com/polar/androidcommunications/api/ble/model/offlinerecording/OfflineRecordingUtilityTest.kt b/sources/Android/android-communications/library/src/test/java/com/polar/androidcommunications/api/ble/model/offlinerecording/OfflineRecordingUtilityTest.kt new file mode 100644 index 00000000..7206902a --- /dev/null +++ b/sources/Android/android-communications/library/src/test/java/com/polar/androidcommunications/api/ble/model/offlinerecording/OfflineRecordingUtilityTest.kt @@ -0,0 +1,54 @@ +package com.polar.androidcommunications.api.ble.model.offlinerecording + +import com.polar.androidcommunications.api.ble.model.gatt.client.pmd.PmdMeasurementType +import com.polar.androidcommunications.api.ble.model.offlinerecording.OfflineRecordingUtility.mapOfflineRecordingFileNameToMeasurementType +import org.junit.Assert +import org.junit.Test + +class OfflineRecordingUtilityTest { + + @Test + fun `mapOfflineRecordingFileNameToMeasurementType() maps file names to correct measurement types`() { + Assert.assertEquals( + PmdMeasurementType.ACC, + mapOfflineRecordingFileNameToMeasurementType("ACC.REC") + ) + Assert.assertEquals( + PmdMeasurementType.GYRO, + mapOfflineRecordingFileNameToMeasurementType("GYRO.REC") + ) + Assert.assertEquals( + PmdMeasurementType.MAGNETOMETER, + mapOfflineRecordingFileNameToMeasurementType("MAG.REC") + ) + Assert.assertEquals( + PmdMeasurementType.PPG, + mapOfflineRecordingFileNameToMeasurementType("PPG.REC") + ) + Assert.assertEquals( + PmdMeasurementType.PPI, + mapOfflineRecordingFileNameToMeasurementType("PPI.REC") + ) + Assert.assertEquals( + PmdMeasurementType.OFFLINE_HR, + mapOfflineRecordingFileNameToMeasurementType("HR.REC") + ) + Assert.assertEquals( + PmdMeasurementType.ACC, + mapOfflineRecordingFileNameToMeasurementType("ACC0.REC") + ) + Assert.assertEquals( + PmdMeasurementType.GYRO, + mapOfflineRecordingFileNameToMeasurementType("GYRO5.REC") + ) + Assert.assertEquals( + PmdMeasurementType.MAGNETOMETER, + mapOfflineRecordingFileNameToMeasurementType("MAG18.REC") + ) + } + + @Test(expected = IllegalArgumentException::class) + fun `mapOfflineRecordingFileNameToMeasurementType() throws IllegalArgumentException if file name is not supported`() { + mapOfflineRecordingFileNameToMeasurementType("INVALID.REC") + } +} \ No newline at end of file diff --git a/sources/iOS/ios-communications/Sources/PolarBleSdk/sdk/api/PolarBleApiDefaultImpl.swift b/sources/iOS/ios-communications/Sources/PolarBleSdk/sdk/api/PolarBleApiDefaultImpl.swift index d4548d5f..fb9e9cde 100644 --- a/sources/iOS/ios-communications/Sources/PolarBleSdk/sdk/api/PolarBleApiDefaultImpl.swift +++ b/sources/iOS/ios-communications/Sources/PolarBleSdk/sdk/api/PolarBleApiDefaultImpl.swift @@ -18,6 +18,6 @@ public class PolarBleApiDefaultImpl { /// /// - Returns: version in format major.minor.patch public static func versionInfo() -> String { - return "5.5.0" + return "5.6.0-beta" } } diff --git a/sources/iOS/ios-communications/Sources/PolarBleSdk/sdk/api/PolarBleApiObservers.swift b/sources/iOS/ios-communications/Sources/PolarBleSdk/sdk/api/PolarBleApiObservers.swift index 7e4a7ade..0b1eb192 100644 --- a/sources/iOS/ios-communications/Sources/PolarBleSdk/sdk/api/PolarBleApiObservers.swift +++ b/sources/iOS/ios-communications/Sources/PolarBleSdk/sdk/api/PolarBleApiObservers.swift @@ -46,8 +46,17 @@ public protocol PolarBleApiDeviceInfoObserver: AnyObject { /// /// - Parameters: /// - identifier: Polar device id - /// - fwVersion: firmware version in format major.minor.patch + /// - uuid: CBUUID key + /// - value: String value func disInformationReceived(_ identifier: String, uuid: CBUUID, value: String) + + /// Received DIS info with String keys. + /// + /// - Parameters: + /// - identifier: Polar device id + /// - key: String key + /// - value: String value + func disInformationReceivedWithKeysAsStrings(_ identifier: String, key: String, value: String) } /// Heart rate observer diff --git a/sources/iOS/ios-communications/Sources/PolarBleSdk/sdk/api/PolarOfflineRecordingApi.swift b/sources/iOS/ios-communications/Sources/PolarBleSdk/sdk/api/PolarOfflineRecordingApi.swift index af9d2977..aa74427c 100644 --- a/sources/iOS/ios-communications/Sources/PolarBleSdk/sdk/api/PolarOfflineRecordingApi.swift +++ b/sources/iOS/ios-communications/Sources/PolarBleSdk/sdk/api/PolarOfflineRecordingApi.swift @@ -79,7 +79,30 @@ public protocol PolarOfflineRecordingApi { /// - success : the offline recording data /// - error: fetch recording request failed. see `PolarErrors` for possible errors invoked func getOfflineRecord(_ identifier: String, entry: PolarOfflineRecordingEntry, secret: PolarRecordingSecret?) -> Single< PolarOfflineRecordingData> - + + /// List split offline recordings stored in the device. + /// + /// - Parameters: + /// - identifier: polar device id + /// - Returns: Completable + /// - next : the found split offline recording entry + /// - completed: the listing completed + /// - error: see `PolarErrors` for possible errors invoked + func listSplitOfflineRecordings(_ identifier: String) -> Observable + + /// Fetch split recording from the device. + /// + /// Note, the fetching of the recording may take several seconds if the recording is big. + /// + /// - Parameters: + /// - identifier: polar device id + /// - entry: The split offline recording to be fetched + /// - secret: If the secret is provided in `startOfflineRecording` or `setOfflineRecordingTrigger` then the same secret must be provided when fetching the offline record + /// - Returns: Single + /// - success : the offline recording data + /// - error: fetch recording request failed. see `PolarErrors` for possible errors invoked + func getSplitOfflineRecord(_ identifier: String, entry: PolarOfflineRecordingEntry, secret: PolarRecordingSecret?) -> Single + /// Removes offline recording from the device /// /// - Parameters: diff --git a/sources/iOS/ios-communications/Sources/PolarBleSdk/sdk/impl/PolarBleApiImpl.swift b/sources/iOS/ios-communications/Sources/PolarBleSdk/sdk/impl/PolarBleApiImpl.swift index c3503a80..20776b0d 100644 --- a/sources/iOS/ios-communications/Sources/PolarBleSdk/sdk/impl/PolarBleApiImpl.swift +++ b/sources/iOS/ios-communications/Sources/PolarBleSdk/sdk/impl/PolarBleApiImpl.swift @@ -518,6 +518,12 @@ import UIKit .do(onNext: { (arg0) in self.deviceInfoObserver?.disInformationReceived(deviceId, uuid: arg0.0, value: arg0.1) }) + .flatMap { (arg0) -> Observable<(String, String)> in + return (client as! BleDisClient).readDisInfoWithKeysAsStrings(true) + .do(onNext: { (arg0) in + self.deviceInfoObserver?.disInformationReceivedWithKeysAsStrings(deviceId, key: arg0.0, value: arg0.1) + } + )} .map { (_) -> Any in return Any.self } @@ -1125,6 +1131,382 @@ extension PolarBleApiImpl: PolarBleApi { } func listOfflineRecordings(_ identifier: String) -> Observable { + do { + let session = try sessionFtpClientReady(identifier) + guard let client = session.fetchGattClient(BlePsFtpClient.PSFTP_SERVICE) as? BlePsFtpClient else { + throw PolarErrors.serviceNotFound + } + guard .sagRfc2FileSystem == BlePolarDeviceCapabilitiesUtility.fileSystemType(session.advertisementContent.polarDeviceType) else { + throw PolarErrors.operationNotSupported + } + + BleLogger.trace("Start offline recording listing in device: \(identifier)") + + return fetchRecursive("/U/0/", client: client, condition: { entry in + entry.matches("^([0-9]{8})(\\/)") || + entry.matches("^([0-9]{6})(\\/)") || + entry == "R/" || + entry.contains(".REC") + }) + .flatMap { entry -> Observable in + let components = entry.name.split(separator: "/") + let dateFormatter = DateFormatter() + dateFormatter.locale = Locale(identifier: "en_US_POSIX") + + if components[2].count == 8 && components[4].count == 6 { + dateFormatter.dateFormat = "yyyyMMddHHmmss" + } else { + dateFormatter.dateFormat = "yyyyMMddHHmm" + } + + guard let date = dateFormatter.date(from: String(components[2] + components[4])) else { + return Observable.error(PolarErrors.dateTimeFormatFailed(description: "Listing offline recording failed. Couldn't parse create data from date \(components[2]) and time \(components[4])")) + } + + guard let pmdMeasurementType = try? OfflineRecordingUtils.mapOfflineRecordingFileNameToMeasurementType(fileName: String(components[5])) else { + return Observable.error(PolarErrors.polarBleSdkInternalException(description: "Listing offline recording failed. Couldn't parse the pmd type from \(components[5])")) + } + + guard let type = try? PolarDataUtils.mapToPolarFeature(from: pmdMeasurementType) else { + return Observable.error(PolarErrors.polarBleSdkInternalException(description: "Listing offline recording failed. Couldn't parse the polar type from pmd type: \(pmdMeasurementType)")) + } + + let polarEntry = PolarOfflineRecordingEntry( + path: entry.name, + size: UInt(entry.size), + date: date, + type: type + ) + BleLogger.trace("Adding entry: \(polarEntry)") + return Observable.just(polarEntry) + } + .groupBy { entry in + entry.path.replacingOccurrences(of: "\\d+\\.REC$", with: ".REC", options: .regularExpression) + } + .flatMap { groupedEntries -> Observable in + return groupedEntries + .reduce([]) { (accumulator, entry) -> [PolarOfflineRecordingEntry] in + var updatedAccumulator = accumulator + updatedAccumulator.append(entry) + return updatedAccumulator + } + .flatMap { entriesList -> Observable in + guard let firstEntry = entriesList.first else { + return Observable.empty() + } + + var totalSize = 0 + entriesList.forEach { entry in + totalSize += Int(entry.size) + } + + let modifiedEntry = PolarOfflineRecordingEntry( + path: firstEntry.path, + size: UInt(totalSize), + date: firstEntry.date, + type: firstEntry.type + ) + BleLogger.trace("Merging entries: \(entriesList) into: \(modifiedEntry)") + return Observable.just(modifiedEntry) + } + } + } catch { + return Observable.error(error) + } + } + + func getOfflineRecord( + _ identifier: String, + entry: PolarOfflineRecordingEntry, + secret: PolarRecordingSecret? + ) -> Single { + do { + let session = try sessionFtpClientReady(identifier) + guard let client = session.fetchGattClient(BlePsFtpClient.PSFTP_SERVICE) as? BlePsFtpClient else { + throw PolarErrors.serviceNotFound + } + guard .sagRfc2FileSystem == BlePolarDeviceCapabilitiesUtility.fileSystemType(session.advertisementContent.polarDeviceType) else { + throw PolarErrors.operationNotSupported + } + + let subRecordingCountObservable = getSubRecordingCount(identifier: identifier, entry: entry).asObservable() + + return Single.create { single in + var polarAccData: PolarOfflineRecordingData? + var polarGyroData: PolarOfflineRecordingData? + var polarMagData: PolarOfflineRecordingData? + var polarPpgData: PolarOfflineRecordingData? + var polarPpiData: PolarOfflineRecordingData? + var polarHrData: PolarOfflineRecordingData? + + _ = subRecordingCountObservable + .flatMap { count -> Observable in + return Observable.range(start: 0, count: count) + .flatMap { subRecordingIndex -> Observable in + Observable.create { observer in + + let subRecordingPath: String + if entry.path.range(of: ".*\\.REC$", options: .regularExpression) != nil && count > 0 { + subRecordingPath = entry.path.replacingOccurrences(of: "\\d(?=\\.REC$)", with: "\(subRecordingIndex)", options: .regularExpression) + } else { + subRecordingPath = entry.path + } + + do { + var operation = Protocol_PbPFtpOperation() + operation.command = Protocol_PbPFtpOperation.Command.get + operation.path = subRecordingPath.isEmpty ? entry.path : subRecordingPath + let request = try operation.serializedData() + + BleLogger.trace("Offline record get. Device: \(identifier) Path: \(subRecordingPath) Secret used: \(secret != nil)") + + let notificationResult = client.sendNotification( + Protocol_PbPFtpHostToDevNotification.initializeSession.rawValue, + parameters: nil + ) + + let requestResult = notificationResult + .andThen(Single.deferred { client.request(request) }) + .map { dataResult in + do { + let pmdSecret = try secret.map { try PolarDataUtils.mapToPmdSecret(from: $0) } + let offlineRecordingData: OfflineRecordingData = try OfflineRecordingData.parseDataFromOfflineFile( + fileData: dataResult as Data, + type: PolarDataUtils.mapToPmdClientMeasurementType(from: entry.type), + secret: pmdSecret + ) + return offlineRecordingData + } catch { + throw PolarErrors.polarOfflineRecordingError(description: "Failed to parse data") + } + } + + _ = requestResult.subscribe( + onSuccess: { offlineRecordingData in + do { + let settings: PolarSensorSetting = offlineRecordingData.recordingSettings?.mapToPolarSettings() ?? PolarSensorSetting() + switch offlineRecordingData.data { + case let accData as AccData: + switch polarAccData { + case let .accOfflineRecordingData(existingData, startTime, existingSettings): + let newSamples = existingData.samples + accData.samples.map { (timeStamp: $0.timeStamp, x: $0.x, y: $0.y, z: $0.z) } + polarAccData = .accOfflineRecordingData( + data: (timeStamp: accData.timeStamp, samples: newSamples), + startTime: startTime, + settings: existingSettings + ) + observer.onNext(polarAccData!) + default: + polarAccData = .accOfflineRecordingData( + data: accData.mapToPolarData(), + startTime: offlineRecordingData.startTime, + settings: settings + ) + observer.onNext(polarAccData!) + } + case let gyroData as GyrData: + switch polarGyroData { + case let .gyroOfflineRecordingData(existingData, startTime, existingSettings): + let newSamples = existingData.samples + gyroData.samples.map { (timeStamp: $0.timeStamp, x: $0.x, y: $0.y, z: $0.z) } + polarGyroData = .gyroOfflineRecordingData( + data: (timeStamp: gyroData.timeStamp, samples: newSamples), + startTime: startTime, + settings: existingSettings + ) + observer.onNext(polarGyroData!) + default: + polarGyroData = .gyroOfflineRecordingData( + data: gyroData.mapToPolarData(), + startTime: offlineRecordingData.startTime, + settings: settings + ) + observer.onNext(polarGyroData!) + } + case let magData as MagData: + switch polarMagData { + case let .magOfflineRecordingData(existingData, startTime, existingSettings): + let newSamples = existingData.samples + magData.samples.map { (timeStamp: $0.timeStamp, x: $0.x, y: $0.y, z: $0.z) } + polarMagData = .magOfflineRecordingData( + data: (timeStamp: magData.timeStamp, samples: newSamples), + startTime: startTime, + settings: existingSettings + ) + observer.onNext(polarMagData!) + default: + polarMagData = .magOfflineRecordingData( + data: magData.mapToPolarData(), + startTime: offlineRecordingData.startTime, + settings: settings + ) + observer.onNext(polarMagData!) + } + case let ppgData as PpgData: + switch polarPpgData { + case let .ppgOfflineRecordingData(existingData, startTime, existingSettings): + let newSamples = existingData.samples + ppgData.samples.map { (timeStamp: $0.timeStamp, channelSamples: $0.ppgDataSamples) } + polarPpgData = .ppgOfflineRecordingData( + data: (samples: newSamples, type: existingData.type), + startTime: startTime, + settings: existingSettings + ) + observer.onNext(polarPpgData!) + default: + polarPpgData = .ppgOfflineRecordingData( + data: ppgData.mapToPolarData(), + startTime: offlineRecordingData.startTime, + settings: settings + ) + observer.onNext(polarPpgData!) + } + case let ppiData as PpiData: + switch polarPpiData { + case let .ppiOfflineRecordingData(existingData, startTime): + let newSamples = existingData.samples + ppiData.samples.map { + ( + hr: $0.hr, + ppInMs: $0.ppInMs, + ppErrorEstimate: $0.ppErrorEstimate, + blockerBit: $0.blockerBit, + skinContactStatus: $0.skinContactStatus, + skinContactSupported: $0.skinContactSupported + ) + } + polarPpiData = .ppiOfflineRecordingData( + data: (timeStamp: UInt64(startTime.timeIntervalSince1970), samples: newSamples), + startTime: startTime + ) + observer.onNext(polarPpiData!) + default: + polarPpiData = .ppiOfflineRecordingData( + data: (timeStamp: UInt64(offlineRecordingData.startTime.timeIntervalSince1970), samples: ppiData.samples.map { + ( + hr: $0.hr, + ppInMs: $0.ppInMs, + ppErrorEstimate: $0.ppErrorEstimate, + blockerBit: $0.blockerBit, + skinContactStatus: $0.skinContactStatus, + skinContactSupported: $0.skinContactSupported + ) + }), + startTime: offlineRecordingData.startTime + ) + observer.onNext(polarPpiData!) + } + case let hrData as OfflineHrData: + switch polarHrData { + case let .hrOfflineRecordingData(existingData, startTime): + let newSamples = existingData + hrData.samples.map { + ( + hr: $0.hr, + rrsMs: [], + rrAvailable: false, + contactStatus: false, + contactStatusSupported: false + ) + } + polarHrData = .hrOfflineRecordingData( + data: newSamples, + startTime: startTime + ) + observer.onNext(polarHrData!) + default: + polarHrData = .hrOfflineRecordingData( + data: hrData.samples.map { + ( + hr: $0.hr, + rrsMs: [], + rrAvailable: false, + contactStatus: false, + contactStatusSupported: false + ) + }, + startTime: offlineRecordingData.startTime + ) + observer.onNext(polarHrData!) + } + default: + observer.onError(PolarErrors.polarOfflineRecordingError(description: "GetOfflineRecording failed. Data type is not supported.")) + return + } + observer.onCompleted() + } catch { + observer.onError(error) + } + }, + onError: { error in + observer.onError(error) + } + ) + } catch { + observer.onError(error) + } + + return Disposables.create { + } + } + } + } + .ignoreElements() + .asCompletable() + .andThen(Single.deferred { + guard let data = polarAccData ?? polarGyroData ?? polarMagData ?? polarPpgData ?? polarPpiData ?? polarHrData else { + return Single.error(PolarErrors.polarOfflineRecordingError(description: "Invalid data")) + } + return Single.just(data) + }) + .subscribe( + onSuccess: { data in + single(.success(data)) + + }, + onError: { error in + single(.failure(error)) + } + ) + return Disposables.create { + } + } + } catch { + return Single.error(error) + } + } + + private func getSubRecordingCount(identifier: String, entry: PolarOfflineRecordingEntry) -> Single { + return Single.create { single in + do { + let session = try self.sessionFtpClientReady(identifier) + guard let client = session.fetchGattClient(BlePsFtpClient.PSFTP_SERVICE) as? BlePsFtpClient else { + single(.failure(PolarErrors.serviceNotFound)) + return Disposables.create() + } + + var operation = Protocol_PbPFtpOperation() + operation.command = Protocol_PbPFtpOperation.Command.get + let directoryPath = entry.path.components(separatedBy: "/").dropLast().joined(separator: "/") + "/" + operation.path = directoryPath + + _ = client.request(try operation.serializedData()) + .subscribe( + onSuccess: { content in + do { + let directory = try Protocol_PbPFtpDirectory(serializedData: content as Data) + single(.success(directory.entries.count)) + } catch { + single(.failure(error)) + } + }, + onFailure: { error in + single(.failure(error)) + } + ) + } catch { + single(.failure(error)) + } + return Disposables.create() + } + } + + func listSplitOfflineRecordings(_ identifier: String) -> Observable { do { let session = try sessionFtpClientReady(identifier) guard let client = session.fetchGattClient(BlePsFtpClient.PSFTP_SERVICE) as? BlePsFtpClient else { @@ -1145,7 +1527,7 @@ extension PolarBleApiImpl: PolarBleApi { let dateFormatter = DateFormatter() dateFormatter.locale = Locale(identifier: "en_US_POSIX") dateFormatter.dateFormat = "yyyyMMddHHmmss" - + guard let date = dateFormatter.date(from: String(components[2] + components[4])) else { throw PolarErrors.dateTimeFormatFailed(description: "Listing offline recording failed. Couldn't parse create data from date \(components[2]) and time \(components[4])") } @@ -1155,7 +1537,7 @@ extension PolarBleApiImpl: PolarBleApi { guard let type = try? PolarDataUtils.mapToPolarFeature(from: pmdMeasurementType) else { throw PolarErrors.polarBleSdkInternalException(description: "Listing offline recording failed. Couldn't parse the polar type from pmd type: \(pmdMeasurementType)") } - + return PolarOfflineRecordingEntry( path: entry.name, size: UInt(entry.size), @@ -1165,13 +1547,13 @@ extension PolarBleApiImpl: PolarBleApi { .catch({ (err) -> Observable in return Observable.error(PolarErrors.deviceError(description: "\(err)")) }) - + } catch let err { return Observable.error(err) } } - func getOfflineRecord(_ identifier: String, entry: PolarOfflineRecordingEntry, secret: PolarRecordingSecret?) -> RxSwift.Single { + func getSplitOfflineRecord(_ identifier: String, entry: PolarOfflineRecordingEntry, secret: PolarRecordingSecret?) -> RxSwift.Single { do { let session = try sessionFtpClientReady(identifier) guard let client = session.fetchGattClient(BlePsFtpClient.PSFTP_SERVICE) as? BlePsFtpClient else { @@ -1180,12 +1562,12 @@ extension PolarBleApiImpl: PolarBleApi { guard .sagRfc2FileSystem == BlePolarDeviceCapabilitiesUtility.fileSystemType(session.advertisementContent.polarDeviceType) else { return Single.error(PolarErrors.operationNotSupported) } - + var operation = Protocol_PbPFtpOperation() operation.command = Protocol_PbPFtpOperation.Command.get operation.path = entry.path let request = try operation.serializedData() - + BleLogger.trace("Offline record get. Device: $identifier Path: \(entry.path) Secret used: \(secret != nil)") return client.sendNotification(Protocol_PbPFtpHostToDevNotification.initializeSession.rawValue, parameters: nil) .andThen(client.request(request)) @@ -1194,7 +1576,7 @@ extension PolarBleApiImpl: PolarBleApi { if let s = secret { pmdSecret = try PolarDataUtils.mapToPmdSecret(from: s) } - + let type:PmdMeasurementType = PolarDataUtils.mapToPmdClientMeasurementType(from: entry.type) return try OfflineRecordingData.parseDataFromOfflineFile(fileData: data as Data, type:type, secret: pmdSecret) } @@ -1241,7 +1623,7 @@ extension PolarBleApiImpl: PolarBleApi { return Single.error(err) } } - + func removeOfflineRecord(_ identifier: String, entry: PolarOfflineRecordingEntry) -> Completable { BleLogger.trace("Remove offline record. Device: \(identifier) Path: \(entry.path)") do { diff --git a/sources/iOS/ios-communications/Sources/iOSCommunications/ble/api/model/gatt/client/BleDisClient.swift b/sources/iOS/ios-communications/Sources/iOSCommunications/ble/api/model/gatt/client/BleDisClient.swift index 655afe4b..b8484843 100644 --- a/sources/iOS/ios-communications/Sources/iOSCommunications/ble/api/model/gatt/client/BleDisClient.swift +++ b/sources/iOS/ios-communications/Sources/iOSCommunications/ble/api/model/gatt/client/BleDisClient.swift @@ -16,8 +16,12 @@ public class BleDisClient: BleGattClientBase { public static let IEEE_11073_20601 = CBUUID(string: "2a2a") public static let PNP_ID = CBUUID(string: "2a50") - var disInformation=[CBUUID : String]() - var observers = AtomicList>() + public static let SYSTEM_ID_HEX = String("SYSTEM_ID_HEX") + + var disInformation = [CBUUID : String]() + var observers = AtomicList>() + var disInformationStringKey = [String : String]() + var observersStringKey = AtomicList>() public init(gattServiceTransmitter: BleAttributeTransportProtocol){ super.init(serviceUuid: BleDisClient.DIS_SERVICE, gattServiceTransmitter: gattServiceTransmitter) @@ -36,21 +40,44 @@ public class BleDisClient: BleGattClientBase { override public func disconnected() { super.disconnected() disInformation.removeAll() - RxUtils.postErrorAndClearList(observers,error: BleGattException.gattDisconnected) + disInformationStringKey.removeAll() + RxUtils.postErrorAndClearList(observers, error: BleGattException.gattDisconnected) + RxUtils.postErrorAndClearList(observersStringKey, error: BleGattException.gattDisconnected) } override public func processServiceData(_ chr: CBUUID , data: Data , err: Int ){ if( err == 0 ){ - let stringValue = NSString(data: data, encoding: String.Encoding.ascii.rawValue) as String? - if stringValue != nil { - disInformation[chr] = stringValue + var asciiRepresentation = "" + var hexRepresentation = "" + if let stringValue = NSString(data: data, encoding: String.Encoding.ascii.rawValue) as String? { + asciiRepresentation = stringValue + } + disInformation[chr] = asciiRepresentation + if (chr == BleDisClient.SYSTEM_ID) { + hexRepresentation = data.map { String(format: "%02X", $0) }.joined() + disInformationStringKey[chr.uuidString] = hexRepresentation } else { - disInformation[chr] = "" + disInformationStringKey[chr.uuidString] = asciiRepresentation } RxUtils.emitNext(observers) { (observer) in + observer.obs.onNext((chr, asciiRepresentation)) let disList = self.disInformation - observer.obs.onNext((chr,stringValue ?? "")) if self.hasAllAvailableReadableCharacteristics(disList as [CBUUID : AnyObject]) { + observer.obs.onCompleted() + } + } + RxUtils.emitNext(observersStringKey) { observer in + if (chr == BleDisClient.SYSTEM_ID) { + // Reorder hex bytes to be in correct format + hexRepresentation = data.map { String(format: "%02X", $0) }.joined() + let reorderedHex = stride(from: 0, to: hexRepresentation.count, by: 2).map { + String(hexRepresentation[hexRepresentation.index(hexRepresentation.startIndex, offsetBy: $0).. Observable<(String, String)> { + var object: RxObserver<(String, String)>! + return Observable.create { observer in + object = RxObserver<(String, String)>.init(obs: observer) + if !checkConnection || self.gattServiceTransmitter?.isConnected() ?? false { + self.observersStringKey.append(object) + let disList = self.disInformation + + if disList.count != 0 { + let disList = self.disInformation + if self.hasAllAvailableReadableCharacteristics(disList as [CBUUID : AnyObject]) { + object.obs.onCompleted() + } + } + } else { + observer.onError(BleGattException.gattDisconnected) + } + return Disposables.create { + self.observersStringKey.remove { (item) -> Bool in + return item === object + } + } + } + } } diff --git a/sources/iOS/ios-communications/Sources/iOSCommunications/ble/api/model/offlinerecording/OfflineRecordingUtils.swift b/sources/iOS/ios-communications/Sources/iOSCommunications/ble/api/model/offlinerecording/OfflineRecordingUtils.swift index 88151de8..e5fe04f2 100644 --- a/sources/iOS/ios-communications/Sources/iOSCommunications/ble/api/model/offlinerecording/OfflineRecordingUtils.swift +++ b/sources/iOS/ios-communications/Sources/iOSCommunications/ble/api/model/offlinerecording/OfflineRecordingUtils.swift @@ -4,14 +4,15 @@ import Foundation class OfflineRecordingUtils { static func mapOfflineRecordingFileNameToMeasurementType(fileName: String) throws -> PmdMeasurementType { - switch(fileName) { - case "ACC.REC": return PmdMeasurementType.acc - case "GYRO.REC": return PmdMeasurementType.gyro - case "MAG.REC": return PmdMeasurementType.mgn - case "PPG.REC": return PmdMeasurementType.ppg - case "PPI.REC": return PmdMeasurementType.ppi - case "HR.REC": return PmdMeasurementType.offline_hr - default: throw BleGattException.gattDataError(description: "Unknown offline file \(fileName)") + let fileNameWithoutExtension = fileName.components(separatedBy: ".").first! + switch fileNameWithoutExtension.replacingOccurrences(of: "\\d+", with: "", options: .regularExpression) { + case "ACC": return .acc + case "GYRO": return .gyro + case "MAG": return .mgn + case "PPG": return .ppg + case "PPI": return .ppi + case "HR": return .offline_hr + default: throw BleGattException.gattDataError(description: "Unknown offline file \(fileName)") } } } diff --git a/sources/iOS/ios-communications/Tests/iOSCommunicationsTests/OfflineRecordingUtilsTest.swift b/sources/iOS/ios-communications/Tests/iOSCommunicationsTests/OfflineRecordingUtilsTest.swift new file mode 100644 index 00000000..c59f3db9 --- /dev/null +++ b/sources/iOS/ios-communications/Tests/iOSCommunicationsTests/OfflineRecordingUtilsTest.swift @@ -0,0 +1,48 @@ +import XCTest +@testable import iOSCommunications + +final class OfflineRecordingUtilsTest: XCTestCase { + + func testMapOfflineRecordingFileNameToMeasurementType() throws { + try XCTAssertEqual( + PmdMeasurementType.acc, + OfflineRecordingUtils.mapOfflineRecordingFileNameToMeasurementType(fileName: "ACC.REC") + ) + try XCTAssertEqual( + PmdMeasurementType.gyro, + OfflineRecordingUtils.mapOfflineRecordingFileNameToMeasurementType(fileName: "GYRO.REC") + ) + try XCTAssertEqual( + PmdMeasurementType.mgn, + OfflineRecordingUtils.mapOfflineRecordingFileNameToMeasurementType(fileName: "MAG.REC") + ) + try XCTAssertEqual( + PmdMeasurementType.ppg, + OfflineRecordingUtils.mapOfflineRecordingFileNameToMeasurementType(fileName: "PPG.REC") + ) + try XCTAssertEqual( + PmdMeasurementType.ppi, + OfflineRecordingUtils.mapOfflineRecordingFileNameToMeasurementType(fileName: "PPI.REC") + ) + try XCTAssertEqual( + PmdMeasurementType.offline_hr, + OfflineRecordingUtils.mapOfflineRecordingFileNameToMeasurementType(fileName: "HR.REC") + ) + try XCTAssertEqual( + PmdMeasurementType.acc, + OfflineRecordingUtils.mapOfflineRecordingFileNameToMeasurementType(fileName: "ACC0.REC") + ) + try XCTAssertEqual( + PmdMeasurementType.gyro, + OfflineRecordingUtils.mapOfflineRecordingFileNameToMeasurementType(fileName: "GYRO5.REC") + ) + try XCTAssertEqual( + PmdMeasurementType.mgn, + OfflineRecordingUtils.mapOfflineRecordingFileNameToMeasurementType(fileName: "MAG18.REC") + ) + try XCTAssertThrowsError( + OfflineRecordingUtils.mapOfflineRecordingFileNameToMeasurementType(fileName: "INVALID.REC"), + "Invalid file name" + ) + } +} diff --git a/sources/iOS/ios-communications/iOSCommunications.xcodeproj/project.pbxproj b/sources/iOS/ios-communications/iOSCommunications.xcodeproj/project.pbxproj index d1601171..9764ee01 100644 --- a/sources/iOS/ios-communications/iOSCommunications.xcodeproj/project.pbxproj +++ b/sources/iOS/ios-communications/iOSCommunications.xcodeproj/project.pbxproj @@ -7,10 +7,9 @@ objects = { /* Begin PBXBuildFile section */ - 4A2F2FA7290EDF4EAE1A56AE /* Pods_iOSCommunicationsTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E49A153AD74670578DE06664 /* Pods_iOSCommunicationsTests.framework */; }; - 55D1C0A3B32F76CA660EFD2A /* Pods_PolarBleSdkTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D47D59F9F5BDD7C5CAE92322 /* Pods_PolarBleSdkTests.framework */; }; - 567DB99D5C2D25156D13DF72 /* Pods_iOSCommunications.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8043C4840D20D2B29BDDF1D2 /* Pods_iOSCommunications.framework */; }; - 59D10112246C86A2421C02A6 /* Pods_PolarBleSdk.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7A6A7E684632AFC96C78147D /* Pods_PolarBleSdk.framework */; }; + 02D5AB36F15F75DA09CCACEE /* Pods_iOSCommunicationsTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 06C3A0AA35CE86822C15D6FF /* Pods_iOSCommunicationsTests.framework */; }; + 15CD2D528498F112F4B0A10A /* Pods_iOSCommunications.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 50A31602F66CD7A3DC4865D4 /* Pods_iOSCommunications.framework */; }; + 1653BFAA3A02C0D5196F85A1 /* Pods_PolarBleSdkTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5BC3FF2EBAE18753933AB5CC /* Pods_PolarBleSdkTests.framework */; }; 6C0C54FE1CFEB3C900FF23E9 /* TimeUtility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C0C54FD1CFEB3C900FF23E9 /* TimeUtility.swift */; }; 6C10792D1D6D9C4800931948 /* iOSCommunications.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6C34DE881CAA3F240083EB1F /* iOSCommunications.framework */; }; 6C2408F8220D9581001C1A84 /* CBScanningProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C32C6D6220AD3CA006615C3 /* CBScanningProtocol.swift */; }; @@ -100,7 +99,9 @@ 6CD12ACA201F126500F3A417 /* BleGattException.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CD12AC9201F126500F3A417 /* BleGattException.swift */; }; 6CEA10DF2175AA5B00E16FBF /* BlePsFtpUtility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C8BCD381CE5BCEA00A4C6A8 /* BlePsFtpUtility.swift */; }; 6CEA10E02175AA5B00E16FBF /* BlePsFtpClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C8BCD391CE5BCEA00A4C6A8 /* BlePsFtpClient.swift */; }; - A527E7C029ED53730059C22E /* BuildFile in Sources */ = {isa = PBXBuildFile; }; + 991DF6E9F75EF12E883C4EA9 /* Pods_PolarBleSdk.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 67685737F108059E6DCC7C84 /* Pods_PolarBleSdk.framework */; }; + A518C36D2AFA61C7001F3724 /* OfflineRecordingUtilsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = A518C36C2AFA61C7001F3724 /* OfflineRecordingUtilsTest.swift */; }; + A527E7C029ED53730059C22E /* (null) in Sources */ = {isa = PBXBuildFile; }; A52DB1DB2A1F59770073E795 /* communications_pftp_request.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = A52DB1DA2A1F59770073E795 /* communications_pftp_request.pb.swift */; }; A52DB1DC2A1F59770073E795 /* communications_pftp_request.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = A52DB1DA2A1F59770073E795 /* communications_pftp_request.pb.swift */; }; A52DB1DD2A1F59770073E795 /* communications_pftp_request.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = A52DB1DA2A1F59770073E795 /* communications_pftp_request.pb.swift */; }; @@ -127,6 +128,8 @@ A55604C7298B8B2700DB6EDB /* PolarH10OfflineExerciseApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = A55604C5298B8B2700DB6EDB /* PolarH10OfflineExerciseApi.swift */; }; A55604C9298B8ED700DB6EDB /* PolarSdkModeApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = A55604C8298B8ED700DB6EDB /* PolarSdkModeApi.swift */; }; A55604CA298B8ED700DB6EDB /* PolarSdkModeApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = A55604C8298B8ED700DB6EDB /* PolarSdkModeApi.swift */; }; + A55D31C32ADD5EAE005CD1B3 /* LedConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = A55D31C22ADD5EAE005CD1B3 /* LedConfig.swift */; }; + A55D31C42ADD5EAE005CD1B3 /* LedConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = A55D31C22ADD5EAE005CD1B3 /* LedConfig.swift */; }; A55E08AE2A273C770059E6C7 /* BleHtsClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = A55E08AD2A273C770059E6C7 /* BleHtsClient.swift */; }; A55E08B42A29EB0C0059E6C7 /* BleHtsClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = A55E08AD2A273C770059E6C7 /* BleHtsClient.swift */; }; A55E08B52A29EB0C0059E6C7 /* BleHtsClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = A55E08AD2A273C770059E6C7 /* BleHtsClient.swift */; }; @@ -263,7 +266,8 @@ A5E5BDBC295AC55F00188D55 /* OfflineRecordingError.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5E5BDB6295AC26500188D55 /* OfflineRecordingError.swift */; }; A5E5BDBD295AC56000188D55 /* OfflineRecordingError.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5E5BDB6295AC26500188D55 /* OfflineRecordingError.swift */; }; A5E75E95263BC9F700BEA32A /* BleGattClientBaseTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5E75E94263BC9F700BEA32A /* BleGattClientBaseTest.swift */; }; - BEF086C76A110FEA5EEC1C97 /* Pods_PolarBleSdkWatchOs.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E364BE40A2595B3DCE4A2068 /* Pods_PolarBleSdkWatchOs.framework */; }; + C2960FFF6B9C82D309F3DA55 /* Pods_PolarBleSdkWatchOs.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0AC5D6B4E602653B29DA45AB /* Pods_PolarBleSdkWatchOs.framework */; }; + FFAC59B3FB168A1ACC11B265 /* (null) in Frameworks */ = {isa = PBXBuildFile; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -297,12 +301,16 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 0DBA24EF644844CF88FAEE63 /* Pods-PolarBleSdkWatchOs.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PolarBleSdkWatchOs.debug.xcconfig"; path = "Target Support Files/Pods-PolarBleSdkWatchOs/Pods-PolarBleSdkWatchOs.debug.xcconfig"; sourceTree = ""; }; - 10D672F79E4386BC495B3744 /* Pods-PolarBleSdk.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PolarBleSdk.release.xcconfig"; path = "Target Support Files/Pods-PolarBleSdk/Pods-PolarBleSdk.release.xcconfig"; sourceTree = ""; }; - 1EA51FA16CB5DBBA0B0275E0 /* Pods-iOSCommunicationsTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOSCommunicationsTests.debug.xcconfig"; path = "Target Support Files/Pods-iOSCommunicationsTests/Pods-iOSCommunicationsTests.debug.xcconfig"; sourceTree = ""; }; - 2B213A431215CE2D415B89F4 /* Pods-iOSCommunications.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOSCommunications.release.xcconfig"; path = "Target Support Files/Pods-iOSCommunications/Pods-iOSCommunications.release.xcconfig"; sourceTree = ""; }; - 416D50465D8A75ED7273BD43 /* Pods-iOSCommunicationsTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOSCommunicationsTests.release.xcconfig"; path = "Target Support Files/Pods-iOSCommunicationsTests/Pods-iOSCommunicationsTests.release.xcconfig"; sourceTree = ""; }; - 51CB36630593ABF6364580EC /* Pods-PolarBleSdk.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PolarBleSdk.debug.xcconfig"; path = "Target Support Files/Pods-PolarBleSdk/Pods-PolarBleSdk.debug.xcconfig"; sourceTree = ""; }; + 06C3A0AA35CE86822C15D6FF /* Pods_iOSCommunicationsTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iOSCommunicationsTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 0AC5D6B4E602653B29DA45AB /* Pods_PolarBleSdkWatchOs.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PolarBleSdkWatchOs.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 10F8FA9649801D92DC238BC5 /* Pods-iOSCommunications.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOSCommunications.debug.xcconfig"; path = "Target Support Files/Pods-iOSCommunications/Pods-iOSCommunications.debug.xcconfig"; sourceTree = ""; }; + 2A4103FA6BEE00E8D8F168FB /* Pods-iOSCommunicationsTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOSCommunicationsTests.release.xcconfig"; path = "Target Support Files/Pods-iOSCommunicationsTests/Pods-iOSCommunicationsTests.release.xcconfig"; sourceTree = ""; }; + 3D2DB41A16042BBA2F91511C /* Pods-PolarBleSdkTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PolarBleSdkTests.release.xcconfig"; path = "Target Support Files/Pods-PolarBleSdkTests/Pods-PolarBleSdkTests.release.xcconfig"; sourceTree = ""; }; + 48B9DC7CC61129E41A71D0E5 /* Pods-PolarBleSdkTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PolarBleSdkTests.debug.xcconfig"; path = "Target Support Files/Pods-PolarBleSdkTests/Pods-PolarBleSdkTests.debug.xcconfig"; sourceTree = ""; }; + 50A31602F66CD7A3DC4865D4 /* Pods_iOSCommunications.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iOSCommunications.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 58C00FCF79B4B77F110308AC /* Pods-iOSCommunicationsTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOSCommunicationsTests.debug.xcconfig"; path = "Target Support Files/Pods-iOSCommunicationsTests/Pods-iOSCommunicationsTests.debug.xcconfig"; sourceTree = ""; }; + 5BC3FF2EBAE18753933AB5CC /* Pods_PolarBleSdkTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PolarBleSdkTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 67685737F108059E6DCC7C84 /* Pods_PolarBleSdk.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PolarBleSdk.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 6C0C54FD1CFEB3C900FF23E9 /* TimeUtility.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TimeUtility.swift; path = ble/common/TimeUtility.swift; sourceTree = ""; }; 6C1079201D6D6EA000931948 /* iOSCommunicationsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = iOSCommunicationsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 6C32C6D6220AD3CA006615C3 /* CBScanningProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = CBScanningProtocol.swift; path = ble/endpoints/corebluetooth/central/CBScanningProtocol.swift; sourceTree = ""; }; @@ -339,11 +347,9 @@ 6CB9DB982087188E00505269 /* RxUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = RxUtils.swift; path = ble/common/RxUtils.swift; sourceTree = ""; }; 6CCFE3FF1E5D76C100FAA131 /* BlePsdClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BlePsdClient.swift; path = ble/api/model/gatt/client/BlePsdClient.swift; sourceTree = ""; }; 6CD12AC9201F126500F3A417 /* BleGattException.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BleGattException.swift; path = ble/api/model/gatt/exceptions/BleGattException.swift; sourceTree = ""; }; - 7A6A7E684632AFC96C78147D /* Pods_PolarBleSdk.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PolarBleSdk.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 8043C4840D20D2B29BDDF1D2 /* Pods_iOSCommunications.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iOSCommunications.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 9C26E77C4960A2E29EFC1D3B /* Pods-iOSCommunications.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOSCommunications.debug.xcconfig"; path = "Target Support Files/Pods-iOSCommunications/Pods-iOSCommunications.debug.xcconfig"; sourceTree = ""; }; - 9FEC4FE98DF83A4D16FCCF43 /* Pods-PolarBleSdkWatchOs.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PolarBleSdkWatchOs.release.xcconfig"; path = "Target Support Files/Pods-PolarBleSdkWatchOs/Pods-PolarBleSdkWatchOs.release.xcconfig"; sourceTree = ""; }; - A387335BACA513642E1F8FFD /* Pods-PolarBleSdkTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PolarBleSdkTests.debug.xcconfig"; path = "Target Support Files/Pods-PolarBleSdkTests/Pods-PolarBleSdkTests.debug.xcconfig"; sourceTree = ""; }; + 8DC9134B59522E7C13F94CDB /* Pods-PolarBleSdkWatchOs.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PolarBleSdkWatchOs.release.xcconfig"; path = "Target Support Files/Pods-PolarBleSdkWatchOs/Pods-PolarBleSdkWatchOs.release.xcconfig"; sourceTree = ""; }; + 9C7161A2D387519B7BF5C3B1 /* Pods-PolarBleSdkWatchOs.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PolarBleSdkWatchOs.debug.xcconfig"; path = "Target Support Files/Pods-PolarBleSdkWatchOs/Pods-PolarBleSdkWatchOs.debug.xcconfig"; sourceTree = ""; }; + A518C36C2AFA61C7001F3724 /* OfflineRecordingUtilsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OfflineRecordingUtilsTest.swift; sourceTree = ""; }; A52DB1DA2A1F59770073E795 /* communications_pftp_request.pb.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = communications_pftp_request.pb.swift; path = ble/api/model/protobuf/communications_pftp_request.pb.swift; sourceTree = ""; }; A5346A3625F21858006752CA /* MockGattServiceTransmitterImpl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockGattServiceTransmitterImpl.swift; sourceTree = ""; }; A5346A4025F21866006752CA /* BlePsFtpClientTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlePsFtpClientTest.swift; sourceTree = ""; }; @@ -358,6 +364,7 @@ A55604C2298B893800DB6EDB /* PolarOnlineStreamingApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PolarOnlineStreamingApi.swift; sourceTree = ""; }; A55604C5298B8B2700DB6EDB /* PolarH10OfflineExerciseApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PolarH10OfflineExerciseApi.swift; sourceTree = ""; }; A55604C8298B8ED700DB6EDB /* PolarSdkModeApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PolarSdkModeApi.swift; sourceTree = ""; }; + A55D31C22ADD5EAE005CD1B3 /* LedConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LedConfig.swift; sourceTree = ""; }; A55E08AD2A273C770059E6C7 /* BleHtsClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BleHtsClient.swift; sourceTree = ""; }; A55E08B62A29FA560059E6C7 /* BleHtsClientTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BleHtsClientTest.swift; sourceTree = ""; }; A5743682291A7DB400E901A4 /* PolarTimeUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PolarTimeUtils.swift; sourceTree = ""; }; @@ -426,10 +433,9 @@ A5E5BDB6295AC26500188D55 /* OfflineRecordingError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OfflineRecordingError.swift; sourceTree = ""; }; A5E5BDB7295AC26500188D55 /* OfflineRecordingData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OfflineRecordingData.swift; sourceTree = ""; }; A5E75E94263BC9F700BEA32A /* BleGattClientBaseTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BleGattClientBaseTest.swift; sourceTree = ""; }; - D1CD9411C485BC658D115386 /* Pods-PolarBleSdkTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PolarBleSdkTests.release.xcconfig"; path = "Target Support Files/Pods-PolarBleSdkTests/Pods-PolarBleSdkTests.release.xcconfig"; sourceTree = ""; }; - D47D59F9F5BDD7C5CAE92322 /* Pods_PolarBleSdkTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PolarBleSdkTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - E364BE40A2595B3DCE4A2068 /* Pods_PolarBleSdkWatchOs.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PolarBleSdkWatchOs.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - E49A153AD74670578DE06664 /* Pods_iOSCommunicationsTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iOSCommunicationsTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + B9F49D564BD365C9954119B3 /* Pods-PolarBleSdk.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PolarBleSdk.debug.xcconfig"; path = "Target Support Files/Pods-PolarBleSdk/Pods-PolarBleSdk.debug.xcconfig"; sourceTree = ""; }; + C0A71A92725BFD8EDFFD59B2 /* Pods-iOSCommunications.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOSCommunications.release.xcconfig"; path = "Target Support Files/Pods-iOSCommunications/Pods-iOSCommunications.release.xcconfig"; sourceTree = ""; }; + CE49691066E61FF12ECA413A /* Pods-PolarBleSdk.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PolarBleSdk.release.xcconfig"; path = "Target Support Files/Pods-PolarBleSdk/Pods-PolarBleSdk.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -438,7 +444,7 @@ buildActionMask = 2147483647; files = ( 6C10792D1D6D9C4800931948 /* iOSCommunications.framework in Frameworks */, - 4A2F2FA7290EDF4EAE1A56AE /* Pods_iOSCommunicationsTests.framework in Frameworks */, + 02D5AB36F15F75DA09CCACEE /* Pods_iOSCommunicationsTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -446,7 +452,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 567DB99D5C2D25156D13DF72 /* Pods_iOSCommunications.framework in Frameworks */, + 15CD2D528498F112F4B0A10A /* Pods_iOSCommunications.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -454,7 +460,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - BEF086C76A110FEA5EEC1C97 /* Pods_PolarBleSdkWatchOs.framework in Frameworks */, + FFAC59B3FB168A1ACC11B265 /* (null) in Frameworks */, + C2960FFF6B9C82D309F3DA55 /* Pods_PolarBleSdkWatchOs.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -462,7 +469,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 59D10112246C86A2421C02A6 /* Pods_PolarBleSdk.framework in Frameworks */, + 991DF6E9F75EF12E883C4EA9 /* Pods_PolarBleSdk.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -471,26 +478,38 @@ buildActionMask = 2147483647; files = ( A5743690291B81F500E901A4 /* PolarBleSdk.framework in Frameworks */, - 55D1C0A3B32F76CA660EFD2A /* Pods_PolarBleSdkTests.framework in Frameworks */, + 1653BFAA3A02C0D5196F85A1 /* Pods_PolarBleSdkTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 2FCF06B71FC023F014C6F110 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 67685737F108059E6DCC7C84 /* Pods_PolarBleSdk.framework */, + 5BC3FF2EBAE18753933AB5CC /* Pods_PolarBleSdkTests.framework */, + 0AC5D6B4E602653B29DA45AB /* Pods_PolarBleSdkWatchOs.framework */, + 50A31602F66CD7A3DC4865D4 /* Pods_iOSCommunications.framework */, + 06C3A0AA35CE86822C15D6FF /* Pods_iOSCommunicationsTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; 427CAC3008CFEF6A777FB862 /* Pods */ = { isa = PBXGroup; children = ( - 51CB36630593ABF6364580EC /* Pods-PolarBleSdk.debug.xcconfig */, - 10D672F79E4386BC495B3744 /* Pods-PolarBleSdk.release.xcconfig */, - A387335BACA513642E1F8FFD /* Pods-PolarBleSdkTests.debug.xcconfig */, - D1CD9411C485BC658D115386 /* Pods-PolarBleSdkTests.release.xcconfig */, - 0DBA24EF644844CF88FAEE63 /* Pods-PolarBleSdkWatchOs.debug.xcconfig */, - 9FEC4FE98DF83A4D16FCCF43 /* Pods-PolarBleSdkWatchOs.release.xcconfig */, - 9C26E77C4960A2E29EFC1D3B /* Pods-iOSCommunications.debug.xcconfig */, - 2B213A431215CE2D415B89F4 /* Pods-iOSCommunications.release.xcconfig */, - 1EA51FA16CB5DBBA0B0275E0 /* Pods-iOSCommunicationsTests.debug.xcconfig */, - 416D50465D8A75ED7273BD43 /* Pods-iOSCommunicationsTests.release.xcconfig */, + B9F49D564BD365C9954119B3 /* Pods-PolarBleSdk.debug.xcconfig */, + CE49691066E61FF12ECA413A /* Pods-PolarBleSdk.release.xcconfig */, + 48B9DC7CC61129E41A71D0E5 /* Pods-PolarBleSdkTests.debug.xcconfig */, + 3D2DB41A16042BBA2F91511C /* Pods-PolarBleSdkTests.release.xcconfig */, + 9C7161A2D387519B7BF5C3B1 /* Pods-PolarBleSdkWatchOs.debug.xcconfig */, + 8DC9134B59522E7C13F94CDB /* Pods-PolarBleSdkWatchOs.release.xcconfig */, + 10F8FA9649801D92DC238BC5 /* Pods-iOSCommunications.debug.xcconfig */, + C0A71A92725BFD8EDFFD59B2 /* Pods-iOSCommunications.release.xcconfig */, + 58C00FCF79B4B77F110308AC /* Pods-iOSCommunicationsTests.debug.xcconfig */, + 2A4103FA6BEE00E8D8F168FB /* Pods-iOSCommunicationsTests.release.xcconfig */, ); path = Pods; sourceTree = ""; @@ -522,6 +541,7 @@ A5346A3625F21858006752CA /* MockGattServiceTransmitterImpl.swift */, A5AD08B625E67064002DA200 /* BleBasClientTest.swift */, A55E08B62A29FA560059E6C7 /* BleHtsClientTest.swift */, + A518C36C2AFA61C7001F3724 /* OfflineRecordingUtilsTest.swift */, ); path = iOSCommunicationsTests; sourceTree = ""; @@ -530,10 +550,11 @@ isa = PBXGroup; children = ( A5E46E7C27A7C33900F33C80 /* Sources */, + A55604C8298B8ED700DB6EDB /* PolarSdkModeApi.swift */, A5E46E7D27A7C34300F33C80 /* Tests */, 6C34DE891CAA3F240083EB1F /* Products */, 427CAC3008CFEF6A777FB862 /* Pods */, - 89B523EAEBEC744CC033111A /* Frameworks */, + 2FCF06B71FC023F014C6F110 /* Frameworks */, ); sourceTree = ""; }; @@ -709,18 +730,6 @@ name = exceptions; sourceTree = ""; }; - 89B523EAEBEC744CC033111A /* Frameworks */ = { - isa = PBXGroup; - children = ( - 7A6A7E684632AFC96C78147D /* Pods_PolarBleSdk.framework */, - D47D59F9F5BDD7C5CAE92322 /* Pods_PolarBleSdkTests.framework */, - E364BE40A2595B3DCE4A2068 /* Pods_PolarBleSdkWatchOs.framework */, - 8043C4840D20D2B29BDDF1D2 /* Pods_iOSCommunications.framework */, - E49A153AD74670578DE06664 /* Pods_iOSCommunicationsTests.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; A52DB1D92A1F594E0073E795 /* protobuf */ = { isa = PBXGroup; children = ( @@ -792,7 +801,6 @@ A5DE0953295B059900BC2E0B /* PolarOfflineRecordingApi.swift */, A55604C2298B893800DB6EDB /* PolarOnlineStreamingApi.swift */, A55604C5298B8B2700DB6EDB /* PolarH10OfflineExerciseApi.swift */, - A55604C8298B8ED700DB6EDB /* PolarSdkModeApi.swift */, ); path = api; sourceTree = ""; @@ -806,6 +814,7 @@ A5CD172C297BFC8400C62BC7 /* PolarRecordingSecret.swift */, A5A88BFB299A5CEA007B9C28 /* PolarOfflineRecordingTriggerMode.swift */, A54BC2A329DC471B003F022A /* PolarDiskSpaceData.swift */, + A55D31C22ADD5EAE005CD1B3 /* LedConfig.swift */, ); path = model; sourceTree = ""; @@ -914,11 +923,11 @@ isa = PBXNativeTarget; buildConfigurationList = 6C1079281D6D6EA000931948 /* Build configuration list for PBXNativeTarget "iOSCommunicationsTests" */; buildPhases = ( - 983DD0253CB8B6D40B276864 /* [CP] Check Pods Manifest.lock */, + 3AA8BAE18919DDDDA724A886 /* [CP] Check Pods Manifest.lock */, 6C10791C1D6D6EA000931948 /* Sources */, 6C10791D1D6D6EA000931948 /* Frameworks */, 6C10791E1D6D6EA000931948 /* Resources */, - 4EB978C7656CAD125481E069 /* [CP] Embed Pods Frameworks */, + 82517A42D52946AD376A6A06 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -934,7 +943,7 @@ isa = PBXNativeTarget; buildConfigurationList = 6C34DE901CAA3F240083EB1F /* Build configuration list for PBXNativeTarget "iOSCommunications" */; buildPhases = ( - 40B2CCBFAE24057A634622E4 /* [CP] Check Pods Manifest.lock */, + 8B9FB3A6FDC85302098797D5 /* [CP] Check Pods Manifest.lock */, 6C34DE831CAA3F240083EB1F /* Sources */, 6C34DE841CAA3F240083EB1F /* Frameworks */, 6C34DE851CAA3F240083EB1F /* Headers */, @@ -954,7 +963,7 @@ isa = PBXNativeTarget; buildConfigurationList = 6C39E8C8209B0755001F7E44 /* Build configuration list for PBXNativeTarget "PolarBleSdkWatchOs" */; buildPhases = ( - 39B67DE4491600189A0DD484 /* [CP] Check Pods Manifest.lock */, + 091DFCB88F36A98D7CF73E78 /* [CP] Check Pods Manifest.lock */, 6C39E8BE209B0754001F7E44 /* Sources */, 6C39E8BF209B0754001F7E44 /* Frameworks */, 6C39E8C0209B0754001F7E44 /* Headers */, @@ -973,7 +982,7 @@ isa = PBXNativeTarget; buildConfigurationList = 6C770B73206B779A00F3D51D /* Build configuration list for PBXNativeTarget "PolarBleSdk" */; buildPhases = ( - 1A9BB242B42B7D0AE7244F6F /* [CP] Check Pods Manifest.lock */, + 69A3E82BC9748A16B10F463E /* [CP] Check Pods Manifest.lock */, 6C770B67206B779900F3D51D /* Sources */, 6C770B68206B779900F3D51D /* Frameworks */, 6C770B69206B779900F3D51D /* Headers */, @@ -992,11 +1001,11 @@ isa = PBXNativeTarget; buildConfigurationList = A5743693291B81F500E901A4 /* Build configuration list for PBXNativeTarget "PolarBleSdkTests" */; buildPhases = ( - 8B88A1927E8A97D5DF0978AE /* [CP] Check Pods Manifest.lock */, + 974509E39AC89B86AC1AEFDC /* [CP] Check Pods Manifest.lock */, A5743688291B81F500E901A4 /* Sources */, A5743689291B81F500E901A4 /* Frameworks */, A574368A291B81F500E901A4 /* Resources */, - D0F2884920CD7EB338845C0E /* [CP] Embed Pods Frameworks */, + 827620E3407F35FB30FCE7AD /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -1101,7 +1110,7 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 1A9BB242B42B7D0AE7244F6F /* [CP] Check Pods Manifest.lock */ = { + 091DFCB88F36A98D7CF73E78 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1116,14 +1125,14 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-PolarBleSdk-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-PolarBleSdkWatchOs-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 39B67DE4491600189A0DD484 /* [CP] Check Pods Manifest.lock */ = { + 3AA8BAE18919DDDDA724A886 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1138,14 +1147,14 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-PolarBleSdkWatchOs-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-iOSCommunicationsTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 40B2CCBFAE24057A634622E4 /* [CP] Check Pods Manifest.lock */ = { + 69A3E82BC9748A16B10F463E /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1160,14 +1169,14 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-iOSCommunications-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-PolarBleSdk-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 4EB978C7656CAD125481E069 /* [CP] Embed Pods Frameworks */ = { + 82517A42D52946AD376A6A06 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1184,29 +1193,24 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-iOSCommunicationsTests/Pods-iOSCommunicationsTests-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - 8B88A1927E8A97D5DF0978AE /* [CP] Check Pods Manifest.lock */ = { + 827620E3407F35FB30FCE7AD /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-PolarBleSdkTests/Pods-PolarBleSdkTests-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; + name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-PolarBleSdkTests-checkManifestLockResult.txt", + "${PODS_ROOT}/Target Support Files/Pods-PolarBleSdkTests/Pods-PolarBleSdkTests-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-PolarBleSdkTests/Pods-PolarBleSdkTests-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - 983DD0253CB8B6D40B276864 /* [CP] Check Pods Manifest.lock */ = { + 8B9FB3A6FDC85302098797D5 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1221,28 +1225,33 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-iOSCommunicationsTests-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-iOSCommunications-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - D0F2884920CD7EB338845C0E /* [CP] Embed Pods Frameworks */ = { + 974509E39AC89B86AC1AEFDC /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-PolarBleSdkTests/Pods-PolarBleSdkTests-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - name = "[CP] Embed Pods Frameworks"; + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-PolarBleSdkTests/Pods-PolarBleSdkTests-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-PolarBleSdkTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-PolarBleSdkTests/Pods-PolarBleSdkTests-frameworks.sh\"\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -1274,6 +1283,7 @@ A57D0527292F360F002824D9 /* PmdTimeStampUtilsTest.swift in Sources */, A57D054D2934B745002824D9 /* PmdSettingTest.swift in Sources */, A5CD1734297C229D00C62BC7 /* PmdSecretTest.swift in Sources */, + A518C36D2AFA61C7001F3724 /* OfflineRecordingUtilsTest.swift in Sources */, A599EA9D2689AFE300414E26 /* PolarAdvDataUtilityTest.swift in Sources */, A5BE73652949E20D00CD55BB /* OfflineRecordingDataTest.swift in Sources */, ); @@ -1381,6 +1391,7 @@ A57D051F29226590002824D9 /* GyrData.swift in Sources */, A5CD1731297C03D400C62BC7 /* PmdSecret.swift in Sources */, A55604CA298B8ED700DB6EDB /* PolarSdkModeApi.swift in Sources */, + A55D31C42ADD5EAE005CD1B3 /* LedConfig.swift in Sources */, A57728B527CE860600855884 /* PolarBleApiObservers.swift in Sources */, A57D05372930932A002824D9 /* MagData.swift in Sources */, A57D05432930A8CA002824D9 /* EcgData.swift in Sources */, @@ -1470,6 +1481,7 @@ A57D051E2922658F002824D9 /* GyrData.swift in Sources */, A5CD1730297C03D400C62BC7 /* PmdSecret.swift in Sources */, A55604C9298B8ED700DB6EDB /* PolarSdkModeApi.swift in Sources */, + A55D31C32ADD5EAE005CD1B3 /* LedConfig.swift in Sources */, A57728B427CE860600855884 /* PolarBleApiObservers.swift in Sources */, A57D05362930932A002824D9 /* MagData.swift in Sources */, A57D05422930A8CA002824D9 /* EcgData.swift in Sources */, @@ -1528,7 +1540,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - A527E7C029ED53730059C22E /* BuildFile in Sources */, + A527E7C029ED53730059C22E /* (null) in Sources */, A5743696291B824300E901A4 /* PolarTimeUtilsTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1552,7 +1564,7 @@ /* Begin XCBuildConfiguration section */ 6C1079291D6D6EA000931948 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 1EA51FA16CB5DBBA0B0275E0 /* Pods-iOSCommunicationsTests.debug.xcconfig */; + baseConfigurationReference = 58C00FCF79B4B77F110308AC /* Pods-iOSCommunicationsTests.debug.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)"; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -1570,7 +1582,7 @@ }; 6C10792A1D6D6EA000931948 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 416D50465D8A75ED7273BD43 /* Pods-iOSCommunicationsTests.release.xcconfig */; + baseConfigurationReference = 2A4103FA6BEE00E8D8F168FB /* Pods-iOSCommunicationsTests.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)"; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -1721,7 +1733,7 @@ }; 6C34DE911CAA3F240083EB1F /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9C26E77C4960A2E29EFC1D3B /* Pods-iOSCommunications.debug.xcconfig */; + baseConfigurationReference = 10F8FA9649801D92DC238BC5 /* Pods-iOSCommunications.debug.xcconfig */; buildSettings = { BUILD_LIBRARY_FOR_DISTRIBUTION = YES; CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES; @@ -1761,7 +1773,7 @@ }; 6C34DE921CAA3F240083EB1F /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 2B213A431215CE2D415B89F4 /* Pods-iOSCommunications.release.xcconfig */; + baseConfigurationReference = C0A71A92725BFD8EDFFD59B2 /* Pods-iOSCommunications.release.xcconfig */; buildSettings = { BUILD_LIBRARY_FOR_DISTRIBUTION = YES; CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES; @@ -1800,7 +1812,7 @@ }; 6C39E8C9209B0755001F7E44 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 0DBA24EF644844CF88FAEE63 /* Pods-PolarBleSdkWatchOs.debug.xcconfig */; + baseConfigurationReference = 9C7161A2D387519B7BF5C3B1 /* Pods-PolarBleSdkWatchOs.debug.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = NO; BUILD_LIBRARY_FOR_DISTRIBUTION = YES; @@ -1857,7 +1869,7 @@ }; 6C39E8CA209B0755001F7E44 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9FEC4FE98DF83A4D16FCCF43 /* Pods-PolarBleSdkWatchOs.release.xcconfig */; + baseConfigurationReference = 8DC9134B59522E7C13F94CDB /* Pods-PolarBleSdkWatchOs.release.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = NO; BUILD_LIBRARY_FOR_DISTRIBUTION = YES; @@ -1909,7 +1921,7 @@ }; 6C770B71206B779A00F3D51D /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 51CB36630593ABF6364580EC /* Pods-PolarBleSdk.debug.xcconfig */; + baseConfigurationReference = B9F49D564BD365C9954119B3 /* Pods-PolarBleSdk.debug.xcconfig */; buildSettings = { BUILD_LIBRARY_FOR_DISTRIBUTION = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -1969,7 +1981,7 @@ }; 6C770B72206B779A00F3D51D /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 10D672F79E4386BC495B3744 /* Pods-PolarBleSdk.release.xcconfig */; + baseConfigurationReference = CE49691066E61FF12ECA413A /* Pods-PolarBleSdk.release.xcconfig */; buildSettings = { BUILD_LIBRARY_FOR_DISTRIBUTION = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -2025,7 +2037,7 @@ }; A5743694291B81F500E901A4 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = A387335BACA513642E1F8FFD /* Pods-PolarBleSdkTests.debug.xcconfig */; + baseConfigurationReference = 48B9DC7CC61129E41A71D0E5 /* Pods-PolarBleSdkTests.debug.xcconfig */; buildSettings = { CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; @@ -2064,7 +2076,7 @@ }; A5743695291B81F500E901A4 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = D1CD9411C485BC658D115386 /* Pods-PolarBleSdkTests.release.xcconfig */; + baseConfigurationReference = 3D2DB41A16042BBA2F91511C /* Pods-PolarBleSdkTests.release.xcconfig */; buildSettings = { CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";