fix(train): the Loop set up, its clock in UTC as Polar's SDK parses it
doFirstTimeUse (SDK 8.3.0) parses the device time as yyyy-MM-dd'T'HH:mm:ss'Z' and refused the offset and fractions of a second the app sent (ISO_OFFSET_DATE_TIME), so no band was ever set up: "The band didn't answer". The test's clock had neither; it has both now, and checks the time against the SDK's pattern. What fails in the Loop's bridge is logged now (tag Loop), as the app says only that the band didn't answer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
58f9b3a472
commit
cb486af671
3 changed files with 21 additions and 3 deletions
|
|
@ -1,6 +1,7 @@
|
||||||
package app.halkyon.train
|
package app.halkyon.train
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.util.Log
|
||||||
import com.polar.androidcommunications.api.ble.model.DisInfo
|
import com.polar.androidcommunications.api.ble.model.DisInfo
|
||||||
import com.polar.androidcommunications.api.ble.model.gatt.client.ChargeState
|
import com.polar.androidcommunications.api.ble.model.gatt.client.ChargeState
|
||||||
import com.polar.androidcommunications.api.ble.model.gatt.client.PowerSourcesState
|
import com.polar.androidcommunications.api.ble.model.gatt.client.PowerSourcesState
|
||||||
|
|
@ -77,6 +78,7 @@ class LoopBridge(private val context: Context) : MethodChannel.MethodCallHandler
|
||||||
} catch (error: SecurityException) {
|
} catch (error: SecurityException) {
|
||||||
sink.error("refused", error.message, null)
|
sink.error("refused", error.message, null)
|
||||||
} catch (error: Exception) {
|
} catch (error: Exception) {
|
||||||
|
Log.w("Loop", "The search failed", error)
|
||||||
sink.error("failed", error.message, null)
|
sink.error("failed", error.message, null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -161,6 +163,8 @@ class LoopBridge(private val context: Context) : MethodChannel.MethodCallHandler
|
||||||
} catch (error: SecurityException) {
|
} catch (error: SecurityException) {
|
||||||
result.error("refused", error.message, null)
|
result.error("refused", error.message, null)
|
||||||
} catch (error: Exception) {
|
} catch (error: Exception) {
|
||||||
|
// The app says only that the band didn't answer; what failed is here.
|
||||||
|
Log.w("Loop", "${call.method} failed", error)
|
||||||
result.error("failed", error.message, null)
|
result.error("failed", error.message, null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,10 @@ package app.halkyon.train
|
||||||
|
|
||||||
import com.polar.sdk.api.model.PolarFirstTimeUseConfig
|
import com.polar.sdk.api.model.PolarFirstTimeUseConfig
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
|
import java.time.ZoneOffset
|
||||||
import java.time.ZonedDateTime
|
import java.time.ZonedDateTime
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The app's figures for a Loop's first-time use (ADR 0030, D34) as Polar's
|
* The app's figures for a Loop's first-time use (ADR 0030, D34) as Polar's
|
||||||
|
|
@ -22,6 +24,9 @@ object LoopSetup {
|
||||||
"pro" to 60,
|
"pro" to 60,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/** The device time as Polar's SDK reads it. */
|
||||||
|
private val deviceTimeFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ENGLISH)
|
||||||
|
|
||||||
/** Whether a device found under [name] is a Loop. */
|
/** Whether a device found under [name] is a Loop. */
|
||||||
fun isLoop(name: String?): Boolean = name != null && name.trim().startsWith("Polar Loop", ignoreCase = true)
|
fun isLoop(name: String?): Boolean = name != null && name.trim().startsWith("Polar Loop", ignoreCase = true)
|
||||||
|
|
||||||
|
|
@ -43,7 +48,9 @@ object LoopSetup {
|
||||||
restingHeartRate = number("restingHeartRate").toInt(),
|
restingHeartRate = number("restingHeartRate").toInt(),
|
||||||
trainingBackground =
|
trainingBackground =
|
||||||
trainingBackgrounds[figures["trainingBackground"]] ?: throw IllegalArgumentException("trainingBackground missing"),
|
trainingBackgrounds[figures["trainingBackground"]] ?: throw IllegalArgumentException("trainingBackground missing"),
|
||||||
deviceTime = now.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME),
|
// UTC to the second: doFirstTimeUse parses it as yyyy-MM-dd'T'HH:mm:ss'Z'
|
||||||
|
// (SDK 8.3.0), refusing an offset and fractions of a second.
|
||||||
|
deviceTime = now.withZoneSameInstant(ZoneOffset.UTC).format(deviceTimeFormat),
|
||||||
typicalDay =
|
typicalDay =
|
||||||
when (figures["typicalDay"]) {
|
when (figures["typicalDay"]) {
|
||||||
"mostlySitting" -> PolarFirstTimeUseConfig.TypicalDay.MOSTLY_SITTING
|
"mostlySitting" -> PolarFirstTimeUseConfig.TypicalDay.MOSTLY_SITTING
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,11 @@ package app.halkyon.train
|
||||||
|
|
||||||
import com.polar.sdk.api.model.PolarFirstTimeUseConfig
|
import com.polar.sdk.api.model.PolarFirstTimeUseConfig
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
|
import java.time.LocalDateTime
|
||||||
import java.time.ZoneOffset
|
import java.time.ZoneOffset
|
||||||
import java.time.ZonedDateTime
|
import java.time.ZonedDateTime
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
|
import java.util.Locale
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Assert.assertFalse
|
import org.junit.Assert.assertFalse
|
||||||
import org.junit.Assert.assertThrows
|
import org.junit.Assert.assertThrows
|
||||||
|
|
@ -13,7 +16,8 @@ import org.junit.Test
|
||||||
// A Loop's first-time use from the app's figures (ADR 0030, D34): Polar's
|
// A Loop's first-time use from the app's figures (ADR 0030, D34): Polar's
|
||||||
// names for the app's, and nothing sent that the SDK would refuse.
|
// names for the app's, and nothing sent that the SDK would refuse.
|
||||||
class LoopSetupTest {
|
class LoopSetupTest {
|
||||||
private val now = ZonedDateTime.of(2026, 10, 28, 18, 30, 0, 0, ZoneOffset.ofHours(1))
|
// Fractions of a second and an offset, as the phone's clock gives them.
|
||||||
|
private val now = ZonedDateTime.of(2026, 10, 28, 18, 30, 0, 792_484_000, ZoneOffset.ofHours(1))
|
||||||
|
|
||||||
private val figures =
|
private val figures =
|
||||||
mapOf<String, Any?>(
|
mapOf<String, Any?>(
|
||||||
|
|
@ -43,7 +47,10 @@ class LoopSetupTest {
|
||||||
assertEquals(50, config.trainingBackground)
|
assertEquals(50, config.trainingBackground)
|
||||||
assertEquals(PolarFirstTimeUseConfig.TypicalDay.MOSTLY_STANDING, config.typicalDay)
|
assertEquals(PolarFirstTimeUseConfig.TypicalDay.MOSTLY_STANDING, config.typicalDay)
|
||||||
assertEquals(480, config.sleepGoalMinutes)
|
assertEquals(480, config.sleepGoalMinutes)
|
||||||
assertEquals("2026-10-28T18:30:00+01:00", config.deviceTime)
|
// UTC to the second, as doFirstTimeUse parses it (SDK 8.3.0): it refused
|
||||||
|
// the offset and the fractions, and v0.1.0 never set a band up.
|
||||||
|
assertEquals("2026-10-28T17:30:00Z", config.deviceTime)
|
||||||
|
LocalDateTime.parse(config.deviceTime, DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ENGLISH))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue