From 5b7239fa018f88cf0ab61a4299ca04f3b3875267 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuli=20M=C3=A4=C3=A4tt=C3=A4?= <samuli.maatta@polar.com> Date: Wed, 31 Jul 2024 12:54:48 +0300 Subject: [PATCH] Update PolarBleSdk.podspec version 5.6.0 squash Update PolarBleSdk.podspec version 5.6.0 - Fixes build issue "The compiler is unable to type-check this expression in reasonable time..." --- PolarBleSdk.podspec | 2 +- .../sdk/impl/PolarBleApiImpl.swift | 703 ++++++++++-------- 2 files changed, 375 insertions(+), 330 deletions(-) diff --git a/PolarBleSdk.podspec b/PolarBleSdk.podspec index 58999c8e..bb16d636 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' 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/iOS/ios-communications/Sources/PolarBleSdk/sdk/impl/PolarBleApiImpl.swift b/sources/iOS/ios-communications/Sources/PolarBleSdk/sdk/impl/PolarBleApiImpl.swift index 817e6f7f..4c70e605 100644 --- a/sources/iOS/ios-communications/Sources/PolarBleSdk/sdk/impl/PolarBleApiImpl.swift +++ b/sources/iOS/ios-communications/Sources/PolarBleSdk/sdk/impl/PolarBleApiImpl.swift @@ -1248,293 +1248,143 @@ extension PolarBleApiImpl: PolarBleApi { } } - func getOfflineRecord( - _ identifier: String, - entry: PolarOfflineRecordingEntry, - secret: PolarRecordingSecret? - ) -> Single<PolarOfflineRecordingData> { - 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? - var polarTemperatureData: PolarOfflineRecordingData? - - var lastTimestamp: UInt64 = 0 - _ = subRecordingCountObservable - .flatMap { count -> Observable<PolarOfflineRecordingData> in - return Observable.range(start: 0, count: count) - .flatMap { subRecordingIndex -> Observable<PolarOfflineRecordingData> 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<Any>.parseDataFromOfflineFile( - fileData: dataResult as Data, - type: PolarDataUtils.mapToPmdClientMeasurementType(from: entry.type), - secret: pmdSecret, - lastTimestamp: lastTimestamp - ) - 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: - lastTimestamp = accData.samples.last?.timeStamp ?? 0 - 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( - (timeStamp: accData.timeStamp, samples: newSamples), - startTime: startTime, - settings: existingSettings - ) - observer.onNext(polarAccData!) - default: - polarAccData = .accOfflineRecordingData( - accData.mapToPolarData(), - startTime: offlineRecordingData.startTime, - settings: settings - ) - observer.onNext(polarAccData!) - } - case let gyroData as GyrData: - lastTimestamp = gyroData.samples.last?.timeStamp ?? 0 - 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( - (timeStamp: gyroData.timeStamp, samples: newSamples), - startTime: startTime, - settings: existingSettings - ) - observer.onNext(polarGyroData!) - default: - polarGyroData = .gyroOfflineRecordingData( - gyroData.mapToPolarData(), - startTime: offlineRecordingData.startTime, - settings: settings - ) - observer.onNext(polarGyroData!) - } - case let magData as MagData: - lastTimestamp = magData.samples.last?.timeStamp ?? 0 - 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( - (timeStamp: magData.timeStamp, samples: newSamples), - startTime: startTime, - settings: existingSettings - ) - observer.onNext(polarMagData!) - default: - polarMagData = .magOfflineRecordingData( - magData.mapToPolarData(), - startTime: offlineRecordingData.startTime, - settings: settings - ) - observer.onNext(polarMagData!) - } - case let ppgData as PpgData: - lastTimestamp = ppgData.samples.last?.timeStamp ?? 0 - switch polarPpgData { - case let .ppgOfflineRecordingData(existingData, startTime, existingSettings): - let newSamples = existingData.samples + ppgData.samples.map { (timeStamp: $0.timeStamp, channelSamples: $0.ppgDataSamples) } - polarPpgData = .ppgOfflineRecordingData( - (samples: newSamples, type: existingData.type), - startTime: startTime, - settings: existingSettings - ) - observer.onNext(polarPpgData!) - default: - polarPpgData = .ppgOfflineRecordingData( - 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( - (timeStamp: UInt64(startTime.timeIntervalSince1970), samples: newSamples), - startTime: startTime - ) - observer.onNext(polarPpiData!) - default: - polarPpiData = .ppiOfflineRecordingData( - (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( - newSamples, - startTime: startTime - ) - observer.onNext(polarHrData!) - default: - polarHrData = .hrOfflineRecordingData( - hrData.samples.map { - ( - hr: $0.hr, - rrsMs: [], - rrAvailable: false, - contactStatus: false, - contactStatusSupported: false - ) - }, - startTime: offlineRecordingData.startTime - ) - observer.onNext(polarHrData!) - } - case let temperatureData as TemperatureData: - switch PolarTemperatureData { - case let .temperatureOfflineRecordingData(existingData, startTime): - let newSamples = existingData + temperatureData.samples.map { - ( - temperature: $0.temperature, - timeStamp: $0.timeStamp - ) - } - polarTemperatureData = .temperatureOfflineRecordingData( - newSamples, - startTime: startTime - ) - observer.onNext(polarTemperatureData!) - default: - polarTemperatureData = .temperatureOfflineRecordingData( - temperatureData.samples.map { - ( - temperature: $0.temperature, - timeStamp: $0.timeStamp - ) - }, - startTime: offlineRecordingData.startTime - ) - observer.onNext(polarTemperatureData!) - } - 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 ?? polarTemperatureData 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) - } + func getOfflineRecord( + _ identifier: String, + entry: PolarOfflineRecordingEntry, + secret: PolarRecordingSecret? + ) -> Single<PolarOfflineRecordingData> { + 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? + var polarTemperatureData: PolarOfflineRecordingData? + + let lastTimestamp: UInt64 = 0 + + let processingObservable = subRecordingCountObservable + .flatMap { count -> Observable<PolarOfflineRecordingData> in + return Observable.range(start: 0, count: count) + .flatMap { subRecordingIndex -> Observable<PolarOfflineRecordingData> 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 = .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 -> OfflineRecordingData<Any> in + do { + let pmdSecret = try secret.map { try PolarDataUtils.mapToPmdSecret(from: $0) } + return try OfflineRecordingData<Any>.parseDataFromOfflineFile( + fileData: dataResult as Data, + type: PolarDataUtils.mapToPmdClientMeasurementType(from: entry.type), + secret: pmdSecret, + lastTimestamp: lastTimestamp + ) + } catch { + throw PolarErrors.polarOfflineRecordingError(description: "Failed to parse data") + } + } + + _ = requestResult.subscribe( + onSuccess: { offlineRecordingData in + let settings: PolarSensorSetting = offlineRecordingData.recordingSettings?.mapToPolarSettings() ?? PolarSensorSetting() + + switch offlineRecordingData.data { + case let accData as AccData: + polarAccData = self.processAccData(accData, polarAccData, offlineRecordingData, settings) + observer.onNext(polarAccData!) + case let gyroData as GyrData: + polarGyroData = self.processGyroData(gyroData, polarGyroData, offlineRecordingData, settings) + observer.onNext(polarGyroData!) + case let magData as MagData: + polarMagData = self.processMagData(magData, polarMagData, offlineRecordingData, settings) + observer.onNext(polarMagData!) + case let ppgData as PpgData: + polarPpgData = self.processPpgData(ppgData, polarPpgData, offlineRecordingData, settings) + observer.onNext(polarPpgData!) + case let ppiData as PpiData: + polarPpiData = self.processPpiData(ppiData, polarPpiData, offlineRecordingData) + observer.onNext(polarPpiData!) + case let hrData as OfflineHrData: + polarHrData = self.processHrData(hrData, polarHrData, offlineRecordingData) + observer.onNext(polarHrData!) + case let temperatureData as TemperatureData: + polarTemperatureData = self.processTemperatureData(temperatureData, polarTemperatureData, offlineRecordingData) + observer.onNext(polarTemperatureData!) + default: + observer.onError(PolarErrors.polarOfflineRecordingError(description: "GetOfflineRecording failed. Data type is not supported.")) + return + } + observer.onCompleted() + }, + onFailure: { error in + observer.onError(error) + } + ) + } catch { + observer.onError(error) + } + + return Disposables.create { } + } + } + } + .ignoreElements() + .asCompletable() + + _ = processingObservable.andThen(Single.deferred { + let data = polarAccData ?? polarGyroData ?? polarMagData ?? polarPpgData ?? polarPpiData ?? polarHrData ?? polarTemperatureData + if let validData = data { + return Single.just(validData) + } else { + return Single.error(PolarErrors.polarOfflineRecordingError(description: "Invalid data")) + } + }) + .subscribe( + onSuccess: { data in + single(.success(data)) + }, + onFailure: { error in + single(.failure(error)) + } + ) + + return Disposables.create { } + } + } catch { + return Single.error(error) + } } private func getSubRecordingCount(identifier: String, entry: PolarOfflineRecordingEntry) -> Single<Int> { @@ -1545,12 +1395,12 @@ extension PolarBleApiImpl: PolarBleApi { 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 @@ -1618,7 +1468,7 @@ extension PolarBleApiImpl: PolarBleApi { return Observable.error(err) } } - + func getSplitOfflineRecord(_ identifier: String, entry: PolarOfflineRecordingEntry, secret: PolarRecordingSecret?) -> RxSwift.Single<PolarOfflineRecordingData> { do { let session = try sessionFtpClientReady(identifier) @@ -1704,14 +1554,14 @@ extension PolarBleApiImpl: PolarBleApi { guard .sagRfc2FileSystem == BlePolarDeviceCapabilitiesUtility.fileSystemType(session.advertisementContent.polarDeviceType) else { return Completable.error(PolarErrors.operationNotSupported) } - + return removeOfflineFilesRecursively(client, entry.path, deleteIfMatchesRegex: "/\\d{8}/") - + } catch let err { return Completable.error(err) } } - + private func removeOfflineFilesRecursively(_ client: BlePsFtpClient, _ deletePath: String, deleteIfMatchesRegex: String? = nil) -> Completable { do { if(deleteIfMatchesRegex != nil) { @@ -1719,9 +1569,9 @@ extension PolarBleApiImpl: PolarBleApi { return Completable.error(PolarErrors.polarOfflineRecordingError(description: "Not valid offline recording path to delete \(deletePath)")) } } - + var parentDir: String = "" - + if (deletePath.last == "/") { if let lastSlashIndex = deletePath.dropLast().lastIndex(of: "/") { parentDir = String(deletePath[...lastSlashIndex]) @@ -1731,12 +1581,12 @@ extension PolarBleApiImpl: PolarBleApi { parentDir = String(deletePath[...lastSlashIndex]) } } - + var operation = Protocol_PbPFtpOperation() operation.command = .get operation.path = parentDir let request = try operation.serializedData() - + return client.request(request) .flatMapCompletable { content -> Completable in do { @@ -1757,8 +1607,8 @@ extension PolarBleApiImpl: PolarBleApi { let request = try removeOperation.serializedData() return client.request(request).asCompletable() } - - + + } catch { return Completable.error(PolarErrors.messageDecodeFailed) } @@ -1767,24 +1617,24 @@ extension PolarBleApiImpl: PolarBleApi { return Completable.error(err) } } - + func startOfflineRecording(_ identifier: String, feature: PolarDeviceDataType, settings: PolarSensorSetting?, secret: PolarRecordingSecret?) -> RxSwift.Completable { do { let session = try sessionPmdClientReady(identifier) guard let client = session.fetchGattClient(BlePmdClient.PMD_SERVICE) as? BlePmdClient else { return Completable.error(PolarErrors.serviceNotFound) } - + var pmdSecret: PmdSecret? = nil if let s = secret { pmdSecret = try PolarDataUtils.mapToPmdSecret(from: s) } - + return client.startMeasurement( PolarDataUtils.mapToPmdClientMeasurementType(from:feature), settings: (settings ?? PolarSensorSetting()) .map2PmdSetting(), PmdRecordingType.offline, pmdSecret) } catch let err { return Completable.error(err) } } - + func stopOfflineRecording(_ identifier: String, feature: PolarDeviceDataType) -> Completable { do { let session = try sessionPmdClientReady(identifier) @@ -1795,26 +1645,26 @@ extension PolarBleApiImpl: PolarBleApi { return Completable.error(err) } } - + func setOfflineRecordingTrigger(_ identifier: String, trigger: PolarOfflineRecordingTrigger, secret: PolarRecordingSecret?) -> Completable { do { let session = try sessionPmdClientReady(identifier) guard let client = session.fetchGattClient(BlePmdClient.PMD_SERVICE) as? BlePmdClient else { return Completable.error(PolarErrors.serviceNotFound) } - + BleLogger.trace("Setup offline recording trigger. Trigger mode: \(trigger.triggerMode) Trigger features: \(trigger.triggerFeatures.map{ "\($0)" }.joined(separator: ",")) Device: \(identifier) Secret used: \(secret != nil)") - + let pmdOfflineTrigger = try PolarDataUtils.mapToPmdOfflineTrigger(from: trigger) var pmdSecret: PmdSecret? = nil if let s = secret { pmdSecret = try PolarDataUtils.mapToPmdSecret(from: s) } - + return client.setOfflineRecordingTrigger(offlineRecordingTrigger: pmdOfflineTrigger, secret: pmdSecret) } catch let err { return Completable.error(err) } } - + func getOfflineRecordingTriggerSetup(_ identifier: String) -> Single<PolarOfflineRecordingTrigger> { do { let session = try sessionPmdClientReady(identifier) @@ -1826,24 +1676,24 @@ extension PolarBleApiImpl: PolarBleApi { return Single.error(err) } } - + func getAvailableOnlineStreamDataTypes(_ identifier: String) -> Single<Set<PolarDeviceDataType>> { do { let session = try sessionPmdClientReady(identifier) - + // TODO, properly check the situation pmd client is not available but hr client is guard let pmdClient = session.fetchGattClient(BlePmdClient.PMD_SERVICE) as? BlePmdClient else { return Single.error(PolarErrors.serviceNotFound) } - + let bleHrClient = session.fetchGattClient(BleHrClient.HR_SERVICE) as? BleHrClient - + return pmdClient.readFeature(true) .map { pmdFeature -> Set<PolarDeviceDataType> in var deviceData: Set<PolarDeviceDataType> = Set() - + if (bleHrClient != nil ) { deviceData.insert(PolarDeviceDataType.hr) } - + if (pmdFeature.contains(PmdMeasurementType.ecg)) { deviceData.insert(PolarDeviceDataType.ecg) } @@ -1874,7 +1724,7 @@ extension PolarBleApiImpl: PolarBleApi { return Single.error(err) } } - + func startEcgStreaming(_ identifier: String, settings: PolarSensorSetting) -> Observable<PolarEcgData> { return startStreaming(identifier, type: .ecg, settings: settings) { (client) -> Observable<PolarEcgData> in return client.observeEcg() @@ -1883,7 +1733,7 @@ extension PolarBleApiImpl: PolarBleApi { } } } - + func startAccStreaming(_ identifier: String, settings: PolarSensorSetting) -> Observable<PolarAccData> { return startStreaming(identifier, type: .acc, settings: settings) { (client) -> Observable<PolarAccData> in return client.observeAcc() @@ -1892,7 +1742,7 @@ extension PolarBleApiImpl: PolarBleApi { } } } - + func startGyroStreaming(_ identifier: String, settings: PolarSensorSetting) -> Observable<PolarGyroData> { return startStreaming(identifier, type: .gyro, settings: settings) { (client) -> Observable<PolarGyroData> in return client.observeGyro() @@ -1901,7 +1751,7 @@ extension PolarBleApiImpl: PolarBleApi { } } } - + func startMagnetometerStreaming(_ identifier: String, settings: PolarSensorSetting) -> Observable<PolarMagnetometerData> { return startStreaming(identifier, type: .mgn, settings: settings) { (client) -> Observable<PolarMagnetometerData> in return client.observeMagnetometer().map { @@ -1909,7 +1759,7 @@ extension PolarBleApiImpl: PolarBleApi { } } } - + func startOhrStreaming(_ identifier: String, settings: PolarSensorSetting) -> Observable<PolarOhrData> { return startStreaming(identifier, type: .ppg, settings: settings) { (client) -> Observable<PolarOhrData> in return client.observePpg() @@ -1918,7 +1768,7 @@ extension PolarBleApiImpl: PolarBleApi { } } } - + func startPpgStreaming(_ identifier: String, settings: PolarSensorSetting) -> Observable<PolarPpgData> { return startStreaming(identifier, type: .ppg, settings: settings) { (client) -> Observable<PolarPpgData> in return client.observePpg() @@ -1927,7 +1777,7 @@ extension PolarBleApiImpl: PolarBleApi { } } } - + func startPpiStreaming(_ identifier: String) -> Observable<PolarPpiData> { return startStreaming(identifier, type: .ppi, settings: PolarSensorSetting()) { (client) -> Observable<PolarPpiData> in return client.observePpi() @@ -1936,7 +1786,7 @@ extension PolarBleApiImpl: PolarBleApi { } } } - + func startOhrPPIStreaming(_ identifier: String) -> Observable<PolarPpiData> { return startStreaming(identifier, type: .ppi, settings: PolarSensorSetting()) { (client) -> Observable<PolarPpiData> in return client.observePpi() @@ -1945,7 +1795,7 @@ extension PolarBleApiImpl: PolarBleApi { } } } - + func startHrStreaming(_ identifier: String) -> Observable<PolarHrData> { do { let session = try sessionServiceReady(identifier, service: BleHrClient.HR_SERVICE) @@ -1976,19 +1826,19 @@ extension PolarBleApiImpl: PolarBleApi { guard let client = session.fetchGattClient(BlePsFtpClient.PSFTP_SERVICE) as? BlePsFtpClient else { return Single.error(PolarErrors.operationNotSupported) } - + let fsType = BlePolarDeviceCapabilitiesUtility.fileSystemType(session.advertisementContent.polarDeviceType) - + let beforeFetch = (fsType == .h10FileSystem) ? client.sendNotification(Protocol_PbPFtpHostToDevNotification.initializeSession.rawValue, parameters: nil) .andThen(client.sendNotification(Protocol_PbPFtpHostToDevNotification.startSync.rawValue, parameters: nil)) : Completable.empty() - + let afterFetch = (fsType == .h10FileSystem) ? client.sendNotification(Protocol_PbPFtpHostToDevNotification.stopSync.rawValue, parameters: nil) .andThen(client.sendNotification(Protocol_PbPFtpHostToDevNotification.terminateSession.rawValue, parameters: nil)) : Completable.empty() - + var operation = Protocol_PbPFtpOperation() operation.command = Protocol_PbPFtpOperation.Command.get operation.path = entry.path @@ -2024,7 +1874,7 @@ extension PolarBleApiImpl: PolarBleApi { return Single.error(err) } } - + func fetchStoredExerciseList(_ identifier: String) -> Observable<PolarExerciseEntry> { do { let session = try sessionFtpClientReady(identifier) @@ -2107,7 +1957,7 @@ extension PolarBleApiImpl: PolarBleApi { func doFactoryReset(_ identifier: String, preservePairingInformation: Bool) -> Completable { do { let session = try sessionFtpClientReady(identifier) - + guard let client = session.fetchGattClient(BlePsFtpClient.PSFTP_SERVICE) as? BlePsFtpClient else { return Completable.error(PolarErrors.serviceNotFound) } @@ -2378,6 +2228,201 @@ extension PolarBleApiImpl: PolarBleApi { } } + private func processAccData( + _ accData: AccData, + _ existingData: PolarOfflineRecordingData?, + _ offlineRecordingData: OfflineRecordingData<Any>, + _ settings: PolarSensorSetting + ) -> PolarOfflineRecordingData { + switch existingData { + 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) } + return .accOfflineRecordingData( + (timeStamp: accData.timeStamp, samples: newSamples), + startTime: startTime, + settings: existingSettings + ) + default: + return .accOfflineRecordingData( + accData.mapToPolarData(), + startTime: offlineRecordingData.startTime, + settings: settings + ) + } + } + + private func processGyroData( + _ gyroData: GyrData, + _ existingData: PolarOfflineRecordingData?, + _ offlineRecordingData: OfflineRecordingData<Any>, + _ settings: PolarSensorSetting + ) -> PolarOfflineRecordingData { + switch existingData { + 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) } + return .gyroOfflineRecordingData( + (timeStamp: gyroData.timeStamp, samples: newSamples), + startTime: startTime, + settings: existingSettings + ) + default: + return .gyroOfflineRecordingData( + gyroData.mapToPolarData(), + startTime: offlineRecordingData.startTime, + settings: settings + ) + } + } + + private func processMagData( + _ magData: MagData, + _ existingData: PolarOfflineRecordingData?, + _ offlineRecordingData: OfflineRecordingData<Any>, + _ settings: PolarSensorSetting + ) -> PolarOfflineRecordingData { + switch existingData { + 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) } + return .magOfflineRecordingData( + (timeStamp: magData.timeStamp, samples: newSamples), + startTime: startTime, + settings: existingSettings + ) + default: + return .magOfflineRecordingData( + magData.mapToPolarData(), + startTime: offlineRecordingData.startTime, + settings: settings + ) + } + } + + private func processPpgData( + _ ppgData: PpgData, + _ existingData: PolarOfflineRecordingData?, + _ offlineRecordingData: OfflineRecordingData<Any>, + _ settings: PolarSensorSetting + ) -> PolarOfflineRecordingData { + switch existingData { + case let .ppgOfflineRecordingData(existingData, startTime, existingSettings): + let newSamples = existingData.samples + ppgData.samples.map { (timeStamp: $0.timeStamp, channelSamples: $0.ppgDataSamples) } + return .ppgOfflineRecordingData( + (samples: newSamples, type: existingData.type), + startTime: startTime, + settings: existingSettings + ) + default: + return .ppgOfflineRecordingData( + ppgData.mapToPolarData(), + startTime: offlineRecordingData.startTime, + settings: settings + ) + } + } + + private func processPpiData( + _ ppiData: PpiData, + _ existingData: PolarOfflineRecordingData?, + _ offlineRecordingData: OfflineRecordingData<Any> + ) -> PolarOfflineRecordingData { + switch existingData { + 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 + ) + } + return .ppiOfflineRecordingData( + (timeStamp: UInt64(startTime.timeIntervalSince1970), samples: newSamples), + startTime: startTime + ) + default: + return .ppiOfflineRecordingData( + (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 + ) + } + } + + private func processHrData( + _ hrData: OfflineHrData, + _ existingData: PolarOfflineRecordingData?, + _ offlineRecordingData: OfflineRecordingData<Any> + ) -> PolarOfflineRecordingData { + switch existingData { + case let .hrOfflineRecordingData(existingData, startTime): + let newSamples = existingData + hrData.samples.map { + ( + hr: $0.hr, + rrsMs: [], + rrAvailable: false, + contactStatus: false, + contactStatusSupported: false + ) + } + return .hrOfflineRecordingData( + newSamples, + startTime: startTime + ) + default: + return .hrOfflineRecordingData( + hrData.samples.map { + ( + hr: $0.hr, + rrsMs: [], + rrAvailable: false, + contactStatus: false, + contactStatusSupported: false + ) + }, + startTime: offlineRecordingData.startTime + ) + } + } + + private func processTemperatureData( + _ temperatureData: TemperatureData, + _ existingData: PolarOfflineRecordingData?, + _ offlineRecordingData: OfflineRecordingData<Any> + ) -> PolarOfflineRecordingData { + switch existingData { + case let .temperatureOfflineRecordingData(existingData, startTime): + let newSamples = existingData.samples + temperatureData.samples.map { + ( + timeStamp: $0.timeStamp, + temperature: $0.temperature + ) + } + let updatedData: PolarTemperatureData = ( + timeStamp: newSamples.last?.timeStamp ?? existingData.timeStamp, + samples: newSamples + ) + return .temperatureOfflineRecordingData( + updatedData, + startTime: startTime + ) + default: + return .temperatureOfflineRecordingData( + temperatureData.mapToPolarData(), + startTime: offlineRecordingData.startTime + ) + } + } + private func querySettings(_ identifier: String, type: PmdMeasurementType, recordingType: PmdRecordingType) -> Single<PolarSensorSetting> { do { let session = try sessionPmdClientReady(identifier)