polarofficial / polar-ble-sdk

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

rewriting fetch exercise to loop past errors until successful read #392

Open smyffanon opened 10 months ago

smyffanon commented 10 months ago

Platform your question concerns:

Device:

Description: I'm trying to work with this issue using the example app where reading an exercise will cause an error and I must keep retrying to get the exercise off - I've had occasions where the 10th time I tried (list, read, wait - list, read, wait - list, read, ....) the exercise came off, and sometimes I gave up after 30th or 40th time - this is usually my sleeping rr intervals, I try to get the exercise from waking around 4AM to leaving for work between 8 to 9.

I tried to modify the example app to keep retrying with no manual intervention until the exercise gets read, but I've not been a programmer for 10 years now, and never used Java / Kotlin.

My logic was

  1. read loop starts (variable readIt controls this)
  2. list loop starts (variable gotList controls this) 2a. exit list loop when exercises listed
  3. try to read the exercise
  4. if exercise not read, go back to 1 (because often the read error invalidates the listing & a valid list needs to be fetched)
  5. exit on successful read

With my mods, the app just fails on my phone (no logcat messages) my code is below - I suspect I need to do stuff in the error handling that's way beyond my current Java / Kotlin. If I"m close, please comment - if it's way beyond my current Java,Kotlin, comment that too.

There's some extra code in there to name the output file so I can feed the output to some R routines, starts with the comment //sks

The very top part is mostly just copied from the get a list code, put inside a loop to ensure I get a valid list before trying to read.


            var readIt = false
            while(! readIt) {
            var gotList = false
            val isDispLisn = listExercisesDisposable?.isDisposed ?: true
            while(! gotList) {
                if (isDispLisn) {
                    exerciseEntries.clear()
                    listExercisesDisposable = api.listExercises(deviceId)
                        .observeOn(AndroidSchedulers.mainThread())
                        .subscribe(
                            { polarExerciseEntry: PolarExerciseEntry ->
                                Log.d(TAG, "next: ${polarExerciseEntry.date} path: ${polarExerciseEntry.path} id: ${polarExerciseEntry.identifier}")
                                exerciseEntries.add(polarExerciseEntry)
                            },
                            { error: Throwable ->
                                val errorDescription = "Failed to list exercises. Reason: $error"
                                Log.w(TAG, errorDescription)
                                showSnackbar(errorDescription)
                            },
                            {
                                val completedOk = "Exercise listing completed. Listed ${exerciseEntries.count()} exercises on device $deviceId."
                                Log.d(TAG, completedOk)
                                gotList = true
                                showSnackbar(completedOk)
                            }
                        )
                } else {
                    Log.d(TAG, "Listing of exercise entries is in progress at the moment.")
                }
            }

            val isDisposed = fetchExerciseDisposable?.isDisposed ?: true
                if (isDisposed) {
                    if (exerciseEntries.isNotEmpty()) {
                        toggleButtonDown(fetchExerciseButton, R.string.reading_exercise)
                        // just for the example purpose read the entry which is first on the exerciseEntries list
                        fetchExerciseDisposable = api.fetchExercise(deviceId, exerciseEntries.first())
                            .observeOn(AndroidSchedulers.mainThread())
                            .doFinally {
                                readIt = true
                                toggleButtonUp(fetchExerciseButton, R.string.read_exercise)
                            }
                            .subscribe(
                                { polarExerciseData: PolarExerciseData ->
                                    Log.d(TAG, "Exercise data count: ${polarExerciseData.hrSamples.size} samples: ${polarExerciseData.hrSamples}")
                                    var onComplete = "Exercise has ${polarExerciseData.hrSamples.size} hr samples.\n\n"
                                    if (polarExerciseData.hrSamples.size >= 3)
                                        onComplete += "HR data {${polarExerciseData.hrSamples[0]}, ${polarExerciseData.hrSamples[1]}, ${polarExerciseData.hrSamples[2]} ...}"
                                    showDialog("Exercise data read", onComplete)

                                    //sks output file
                                    //val dayLetter = listOf('M', 'T', 'W', 'R', 'F', 'S', 'U')
                                    val dayLetter: List<Char> = "mtwrfsu".toList()
                                    val current = LocalDateTime.now()
                                    val myDay = dayLetter[current.dayOfWeek.value -1]
                                    var flfmt = DateTimeFormatter.ofPattern("yyyyMMdd_'${myDay}'_HHmmss")
                                    var fname = current.format(flfmt)

                                    //val dtfmt = DateTimeFormatter.ofPattern("'date=@'dd_MM_yyyy HH:mm:ss'@'")
                                    //fname = fname + "; " + current.format(dtfmt)

                                    flfmt = DateTimeFormatter.ofPattern("'\"; date=\"'dd/MM/yyy HH:mm:ss'\"'\n")
                                    var forR = "file=\"" + deviceId + "/" + fname + current.format(flfmt)

                                    //var myExternalFile:File = File(getExternalFilesDir(null),formatted)
                                    val path = getExternalFilesDir(null)
                                    val letDirectory = File(path,"")
                                    letDirectory.mkdirs()
                                    val RRfile = File(letDirectory, fname)
                                    val flistFile = File(letDirectory, "for_flist")
                                    try {
                                        flistFile.appendText(forR)
                                        val itr = polarExerciseData.hrSamples.listIterator()
                                        while(itr.hasNext()){
                                            RRfile.appendText(itr.next().toString())
                                            RRfile.appendText("\n")
                                        }} catch (e: IOException) {
                                        e.printStackTrace()
                                    }
                                    // sks end

                                },
                                { error: Throwable ->
                                    val errorDescription = "Failed to read exercise. Reason: $error"
                                    Log.e(TAG, errorDescription)
                                    showSnackbar(errorDescription)
                                }
                            )
                    } else {
                        val helpTitle = "Reading exercise is not possible"
                        val helpMessage = "Either device has no exercise entries or you haven't list them yet. Please, create an exercise or use the \"LIST EXERCISES\" " +
                                "button to list exercises on device."
                        showDialog(helpTitle, helpMessage)
                    }
                } else {
                    Log.d(TAG, "Reading of exercise is in progress at the moment.")
                }
            }
        }
samulimaa commented 8 months ago

Hello, it's difficult to say the cause without the logcat output but to accomplish the same I would use RxJava like this:

val retryCount = 100
val retryDelayMillis = 100

fetchExerciseDisposable = api.fetchExercise(deviceId, exerciseEntries.first())
    .observeOn(AndroidSchedulers.mainThread())
    .doFinally {
        readIt = true
        toggleButtonUp(fetchExerciseButton, R.string.read_exercise)
    }
    .retryWhen { errors ->
        errors.zipWith(Observable.range(1, retryCount + 1)) { error, count ->
            if (count <= retryCount) {
                Log.w(TAG, "Retrying attempt $count after error: $error")
                count
            } else {
                throw error
            }
        }.flatMap { count ->
            Observable.timer(count * retryDelayMillis.toLong(), TimeUnit.MILLISECONDS)
        }
    }
    .subscribe(
        { polarExerciseData: PolarExerciseData ->
            // Handle successful data here
        },
        { error: Throwable ->
            // Handle error here
            val errorDescription = "Failed to read exercise after $retryCount attempts. Reason: $error"
            Log.w(TAG, errorDescription)
            showSnackbar(errorDescription)
        }
    )

It can still need some tweaking but can't say extract reason as the log output is not konwn.