polarofficial / polar-ble-sdk

Repository includes SDK and code examples. More info https://polar.com/en/developers
Other
447 stars 147 forks source link

Unexpected '0' in Filenames When Listing Offline Recordings #444

Closed KeisukeOgishima closed 3 months ago

KeisukeOgishima commented 4 months ago

Platform your question concerns:

Device:

Description: I am developing an iOS application using Flutter, integrating the Polar BLE SDK for interacting with Polar devices. I've run into a problem where the filenames obtained during the listing of offline recordings have an unexpected '0' character—for instance, "ACC0.REC" instead of "ACC.REC". This issue is causing significant disruption because my application depends on a specific file naming convention.

This issue comes up after:

Setting the offline recording trigger with the handleSetOfflineTrigger method in PolarManager. Calling listOfflineRecordings, which then returns filenames with the unexpected '0' in them, like "ACC0.REC". Through debugging, I've pinpointed that the error occurs when listOfflineRecordings in PolarManager.swift throws a deviceError with the description polarBleSdkInternalException(description: "Listing offline recording failed. Couldn't parse the pmd type from ACC0.REC"). Delving deeper into PolarBleApiimpl -> listOfflineRecordings, it's apparent that the problematic entry is being fetched, which is perplexing.

I wonder if this could be tied to a recent firmware update or if it's an isolated issue with the SDK. I would greatly appreciate any insights or advice on how to resolve this unanticipated filename format.

Relevant code excerpts from PolarManager.swift pertaining to this problem are as follows:

// PolarManager.swift

// Method to set offline recording trigger
func handleSetOfflineTrigger(triggerString: String, completion: @escaping (Bool) -> Void) {
        NSLog(“[Debug] Entered handleSetOfflineTrigger”)

        if case .connected(let deviceId) = self.deviceConnectionState {
            NSLog(“[Debug] Device is connected with ID: \(deviceId)“)

            if let triggerMode = stringToTriggerMode(triggerString) {
                NSLog(“[Debug] TriggerMode is valid”)

                let featuresToFetch = [“acc”, “gyro”, “magnetometer”, “hr”] // “ppi”, “ppg”,
                var fetchedFeatures: [PolarDeviceDataType: PolarSensorSetting?] = [:]

                let group = DispatchGroup()

                featuresToFetch.forEach { featureRaw in
                    group.enter()
                    getOfflineRecordingSettings(featureRaw: featureRaw) { fetchedSettings in
                        NSLog(“[Debug] In getOfflineRecordingSettings for feature: \(featureRaw)“)

                        var polarSensorSettings: [PolarSensorSetting.SettingType: UInt32]? = nil

                        if let fetchedSettings = fetchedSettings {
                            NSLog(“[Debug] Fetched settings for feature: \(featureRaw)“)

                            polarSensorSettings = [:]

                            if let settingsArray = fetchedSettings[“settings”] as? [[String: Any]] {
                                settingsArray.forEach { setting in
                                    if let type = setting[“type”] as? PolarSensorSetting.SettingType,
                                    let value = setting[“values”] as? [Int],
                                    let firstValue = value.first {
                                        polarSensorSettings?[type] = UInt32(firstValue)
                                    }
                                }
                            }
                        } else {
                            NSLog(“[Debug] Failed to fetch settings for feature: \(featureRaw)“)
                        }

                        if let feature = self.polarDeviceDataType(from: featureRaw) {
                            fetchedFeatures[feature] = polarSensorSettings != nil ? PolarSensorSetting(polarSensorSettings!) : nil
                        }

                        group.leave()
                    }
                }

                group.notify(queue: .main) {
                    NSLog(“[Debug] All features fetched”)

                    let trigger = PolarOfflineRecordingTrigger(triggerMode: triggerMode, triggerFeatures: fetchedFeatures)
                    NSLog(“[Debug] Trigger object created: \(trigger)“)

                    do {
                        let completable = try self.api.setOfflineRecordingTrigger(deviceId, trigger: trigger, secret: nil)
                        completable
                            .subscribe(onCompleted: {
                                NSLog(“[Debug] Successfully set offline recording trigger”)
                                completion(true)
                            }, onError: { error in
                                NSLog(“[Debug] Error setting offline recording trigger: \(error)“)
                                completion(false)
                            })
                            .disposed(by: self.disposeBag)
                    } catch {
                        NSLog(“[Debug] Exception while setting trigger”)
                        completion(false)
                    }
                }
            } else {
                NSLog(“[Debug] Invalid trigger string”)
                completion(false)
            }
        } else {
            NSLog(“[Debug] Device is not connected”)
            completion(false)
        }
    }

// Method to list offline recordings
func listOfflineRecordings(completion: @escaping (Result<String, Error>) -> Void) {
        if case .connected(let deviceId) = deviceConnectionState {

            self.offlineRecordingEntries.isFetching = true

            api.listOfflineRecordings(deviceId)
                .observe(on: MainScheduler.instance)
                .do(
                    onDispose: { [weak self] in
                        self?.offlineRecordingEntries.isFetching = false
                    }
                )
                .subscribe { [weak self] event in
                    switch event {
                    case .next(let entry):
                        if !(self?.offlineRecordingEntries.entries.contains(where: { $0.path == entry.path }) ?? false) {
                            self?.offlineRecordingEntries.entries.append(entry)
                        } else {
                            NSLog(“: \(entry)“)
                        }
                    case .error(let err):
                        completion(.failure(err))
                    case .completed:
                        if let entries = self?.offlineRecordingEntries.entries {
                            let encodableEntries = entries.map { EncodablePolarOfflineRecordingEntry(from: $0) }
                            if let entriesData = try? JSONEncoder().encode(encodableEntries),
                            let entriesJson = String(data: entriesData, encoding: .utf8) {
                                completion(.success(entriesJson))
                            } else {
                                completion(.failure(NSError(domain: “PolarManagerError”, code: 1002, userInfo: [NSLocalizedDescriptionKey: “cannot encode”])))
                            }
                        } else {
                            completion(.failure(NSError(domain: “PolarManagerError”, code: 1003, userInfo: [NSLocalizedDescriptionKey: “no entry”])))
                        }
                    }
                }
                .disposed(by: disposeBag)
        } else {
            completion(.failure(NSError(domain: “PolarManagerError”, code: 1004, userInfo: [NSLocalizedDescriptionKey: “no connection”])))
        }
    }

スクリーンショット 2024-02-25 21 38 28

KeisukeOgishima commented 4 months ago

Temporary Workaround for Unexpected '0' in Filenames When Listing Offline Recordings

Description:

In the course of developing an iOS application that integrates with the Polar BLE SDK, I encountered an issue where filenames obtained during the listing of offline recordings included an unexpected '0' character (e.g., "ACC0.REC" instead of "ACC.REC"). Despite efforts to resolve this by downgrading the SDK version and resetting the Polar Verity Sense to factory settings, the issue persisted.

Recognizing that this is not an ideal solution, I have implemented a temporary workaround by directly modifying the SDK code. While I am disappointed not to have identified the root cause of the erroneous filename creation, I wanted to document this workaround for the benefit of anyone who might encounter a similar issue.

Location of the modification:

Pods/PolarBleSdk/sources/iOS/ios-communications/Sources/PolarBleSdk/sdk/impl/PolarBleApiImpl.swift

Code Modification:

In the listOfflineRecordings(_ identifier: String) -> Observable<PolarOfflineRecordingEntry> function, the following changes were made to address the issue:

.map { (entry) -> PolarOfflineRecordingEntry in
    let components = entry.name.split(separator: "/")
    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])")
    }

    let pattern = "([A-Z]+)[0-9]*\\.REC"
    let regex = try? NSRegularExpression(pattern: pattern, options: [])
    let originalFileName = String(components[5])
    let range = NSRange(originalFileName.startIndex..<originalFileName.endIndex, in: originalFileName)

    let correctedFileName = regex?.stringByReplacingMatches(in: originalFileName, options: [], range: range, withTemplate: "$1.REC") ?? originalFileName

    guard let pmdMeasurementType = try? OfflineRecordingUtils.mapOfflineRecordingFileNameToMeasurementType(fileName: correctedFileName) else {
        throw PolarErrors.polarBleSdkInternalException(description: "Listing offline recording failed. Couldn't parse the pmd type from \(correctedFileName)")
    }

    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),
        date: date,
        type: type)
}

The above code utilizes a regular expression to remove any unintended numeric characters that appear before the ".REC" file extension. This ensures the filenames conform to the expected format without the additional '0' character.

Acknowledgement: I am aware that modifying SDK code directly is not recommended due to potential issues with updates and compatibility. However, this temporary workaround allows development to proceed while a more permanent solution is sought. I hope to identify the underlying cause of this issue in the future and welcome any insights or suggestions from the community.

This description outlines the issue, the attempted solutions, the temporary workaround implemented, and an acknowledgment of the potential downsides of modifying SDK code directly.

jimmyzumthurm commented 4 months ago

Hi @KeisukeOgishima , This is indeed a known regression issue of release 2.2.5 that has been fixed internally already. Hotfix 2.2.6 that removes unexpected 0 in filename will come probably during next week. Sorry for the troubles.

KeisukeOgishima commented 4 months ago

Hi @jimmyzumthurm,

Thank you for the prompt update on the issue and the efforts to resolve it. I'm pleased to hear that a fix has been implemented internally. Looking forward to the release of Hotfix 2.2.6, and I will make sure to close this issue once the unexpected 0 in the filename is confirmed to be resolved.

I continue to be impressed by the development of this fantastic system and have great respect for everyone involved in its evolution. Your hard work is truly appreciated!

Best regards,

orestesgaolin commented 4 months ago

Thanks for the notice! I was wondering if it would be possible to retract the 2.2.5 update from Polar Flow? We already see couple of users on 2.2.5 firmware, and they have been complaining about offline recording synchronization not working for them. In the meantime we're communicating to our users that they should not update the firmware of the Verity Sense to 2.2.5.

orestesgaolin commented 4 months ago

I was wondering if the fix is going to be included in SDK or will it require firmware upgrade as well?

jimmyzumthurm commented 4 months ago

Hi @orestesgaolin , The fix will unfortunately require a firmware upgrade that should be available this week.

jimmyzumthurm commented 3 months ago

Hi @orestesgaolin and @KeisukeOgishima , Firmware 2.2.6 should not be available with Flow App or Flow Sync. Thank you for your patience. I'll close this issue for now.