fix #3: Fix handling of manually configured power and hr zones

This commit is contained in:
Tim Kluge 2024-12-08 23:34:41 +01:00
parent d9b8fac445
commit bc782e7f90
6 changed files with 56 additions and 53 deletions

View File

@ -13,8 +13,8 @@ android {
applicationId = "de.timklge.karoopowerbar" applicationId = "de.timklge.karoopowerbar"
minSdk = 26 minSdk = 26
targetSdk = 33 targetSdk = 33
versionCode = 3 versionCode = 4
versionName = "1.1.0" versionName = "1.1.1"
} }
buildTypes { buildTypes {

View File

@ -3,9 +3,9 @@
"packageName": "de.timklge.karoopowerbar", "packageName": "de.timklge.karoopowerbar",
"iconUrl": "https://github.com/timklge/karoo-powerbar/releases/latest/download/karoo-powerbar.png", "iconUrl": "https://github.com/timklge/karoo-powerbar/releases/latest/download/karoo-powerbar.png",
"latestApkUrl": "https://github.com/timklge/karoo-powerbar/releases/latest/download/app-release.apk", "latestApkUrl": "https://github.com/timklge/karoo-powerbar/releases/latest/download/app-release.apk",
"latestVersion": "1.1.0", "latestVersion": "1.1.1",
"latestVersionCode": 3, "latestVersionCode": 4,
"developer": "timklge", "developer": "timklge",
"description": "Adds a colored power bar to the bottom of the screen", "description": "Adds a colored power bar to the bottom of the screen",
"releaseNotes": "Add options to add secondary power bar and to hide bar when not riding" "releaseNotes": "Add options to add secondary power bar and to hide bar when not riding. Fix manually set up power/hr zones."
} }

View File

@ -10,7 +10,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class KarooPowerbarExtension : KarooExtension("karoo-powerbar", "1.1.0") { class KarooPowerbarExtension : KarooExtension("karoo-powerbar", "1.1.1") {
companion object { companion object {
const val TAG = "karoo-powerbar" const val TAG = "karoo-powerbar"

View File

@ -27,6 +27,7 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlin.math.roundToInt
fun remap(value: Double, fromMin: Double, fromMax: Double, toMin: Double, toMax: Double): Double { fun remap(value: Double, fromMin: Double, fromMax: Double, toMin: Double, toMax: Double): Double {
return (value - fromMin) * (toMax - toMin) / (fromMax - fromMin) + toMin return (value - fromMin) * (toMax - toMin) / (fromMax - fromMin) + toMin
@ -102,11 +103,11 @@ class Window(
Log.i(TAG, "Karoo system service connected: $connected") Log.i(TAG, "Karoo system service connected: $connected")
} }
powerbar.progressColor = context.resources.getColor(R.color.zoneAerobic) powerbar.progressColor = context.resources.getColor(R.color.zone7)
powerbar.progress = 0.0 powerbar.progress = 0.0
powerbar.invalidate() powerbar.invalidate()
Log.i(KarooPowerbarExtension.TAG, "Streaming $selectedSource") Log.i(TAG, "Streaming $selectedSource")
when (selectedSource){ when (selectedSource){
SelectedSource.POWER -> streamPower(PowerStreamSmoothing.RAW) SelectedSource.POWER -> streamPower(PowerStreamSmoothing.RAW)
@ -124,7 +125,7 @@ class Window(
} }
} }
} catch (e: Exception) { } catch (e: Exception) {
Log.e(KarooPowerbarExtension.TAG, e.toString()) Log.e(TAG, e.toString())
} }
} }
@ -139,20 +140,21 @@ class Window(
.map { (userProfile, hr) -> StreamData(userProfile, hr) } .map { (userProfile, hr) -> StreamData(userProfile, hr) }
.distinctUntilChanged() .distinctUntilChanged()
.collect { streamData -> .collect { streamData ->
val value = streamData.value.roundToInt()
val color = context.getColor( val color = context.getColor(
streamData.userProfile.getUserHrZone(streamData.value.toInt())?.colorResource streamData.userProfile.getZone(streamData.userProfile.heartRateZones, value)?.colorResource
?: R.color.zoneAerobic ?: R.color.zone7
) )
val minHr = streamData.userProfile.restingHr val minHr = streamData.userProfile.restingHr
val maxHr = streamData.userProfile.maxHr val maxHr = streamData.userProfile.maxHr
val progress = val progress =
remap(streamData.value, minHr.toDouble(), maxHr.toDouble(), 0.0, 1.0) remap(value.toDouble(), minHr.toDouble(), maxHr.toDouble(), 0.0, 1.0)
powerbar.progressColor = color powerbar.progressColor = color
powerbar.progress = progress powerbar.progress = progress
powerbar.invalidate() powerbar.invalidate()
Log.d(KarooPowerbarExtension.TAG, "Hr: ${streamData.value} min: $minHr max: $maxHr") Log.d(TAG, "Hr: $value min: $minHr max: $maxHr")
} }
} }
@ -173,20 +175,21 @@ class Window(
.map { (userProfile, power) -> StreamData(userProfile, power) } .map { (userProfile, power) -> StreamData(userProfile, power) }
.distinctUntilChanged() .distinctUntilChanged()
.collect { streamData -> .collect { streamData ->
val value = streamData.value.roundToInt()
val color = context.getColor( val color = context.getColor(
streamData.userProfile.getUserPowerZone(streamData.value.toInt())?.colorResource streamData.userProfile.getZone(streamData.userProfile.powerZones, value)?.colorResource
?: R.color.zoneAerobic ?: R.color.zone7
) )
val minPower = streamData.userProfile.powerZones.first().min val minPower = streamData.userProfile.powerZones.first().min
val maxPower = streamData.userProfile.powerZones.last().min + 50 val maxPower = streamData.userProfile.powerZones.last().min + 50
val progress = val progress =
remap(streamData.value, minPower.toDouble(), maxPower.toDouble(), 0.0, 1.0) remap(value.toDouble(), minPower.toDouble(), maxPower.toDouble(), 0.0, 1.0)
powerbar.progressColor = color powerbar.progressColor = color
powerbar.progress = progress powerbar.progress = progress
powerbar.invalidate() powerbar.invalidate()
Log.d(KarooPowerbarExtension.TAG, "Power: ${streamData.value} min: $minPower max: $maxPower") Log.d(TAG, "Power: ${value} min: $minPower max: $maxPower")
} }
} }
@ -197,7 +200,7 @@ class Window(
rootView.invalidate() rootView.invalidate()
(rootView.parent as ViewGroup).removeAllViews() (rootView.parent as ViewGroup).removeAllViews()
} catch (e: Exception) { } catch (e: Exception) {
Log.d(KarooPowerbarExtension.TAG, e.toString()) Log.d(TAG, e.toString())
} }
} }
} }

View File

@ -2,38 +2,36 @@ package de.timklge.karoopowerbar
import io.hammerhead.karooext.models.UserProfile import io.hammerhead.karooext.models.UserProfile
enum class PowerZone(val colorResource: Int) { enum class Zone(val colorResource: Int){
ACTIVE_RECOVERY(R.color.zoneActiveRecovery), Zone0(R.color.zone0),
ENDURANCE(R.color.zoneEndurance), Zone1(R.color.zone1),
TEMPO(R.color.zoneTempo), Zone2(R.color.zone2),
THRESHOLD(R.color.zoneThreshold), Zone3(R.color.zone3),
VO2_MAX(R.color.zoneVO2Max), Zone4(R.color.zone4),
AEROBIC_CAPACITY(R.color.zoneAerobic), Zone5(R.color.zone5),
ANAEROBIC_CAPACITY(R.color.zoneAnaerobic), Zone6(R.color.zone6),
Zone7(R.color.zone7),
Zone8(R.color.zone8),
} }
enum class HrZone(val colorResource: Int) { val zones = mapOf(
ACTIVE_RECOVERY(R.color.zoneActiveRecovery), 1 to listOf(Zone.Zone7),
ENDURANCE(R.color.zoneEndurance), 2 to listOf(Zone.Zone1, Zone.Zone7),
TEMPO(R.color.zoneTempo), 3 to listOf(Zone.Zone1, Zone.Zone3, Zone.Zone7),
THRESHOLD(R.color.zoneThreshold), 4 to listOf(Zone.Zone1, Zone.Zone3, Zone.Zone5, Zone.Zone7),
VO2_MAX(R.color.zoneAerobic), 5 to listOf(Zone.Zone1, Zone.Zone2, Zone.Zone3, Zone.Zone5, Zone.Zone7),
} 6 to listOf(Zone.Zone1, Zone.Zone2, Zone.Zone3, Zone.Zone5, Zone.Zone7, Zone.Zone8),
7 to listOf(Zone.Zone1, Zone.Zone2, Zone.Zone3, Zone.Zone5, Zone.Zone6, Zone.Zone7, Zone.Zone8),
8 to listOf(Zone.Zone0, Zone.Zone1, Zone.Zone2, Zone.Zone3, Zone.Zone5, Zone.Zone6, Zone.Zone7, Zone.Zone8),
9 to listOf(Zone.Zone0, Zone.Zone1, Zone.Zone2, Zone.Zone3, Zone.Zone4, Zone.Zone5, Zone.Zone6, Zone.Zone7, Zone.Zone8)
)
fun UserProfile.getUserPowerZone(power: Int): PowerZone? { fun UserProfile.getZone(userZones: List<UserProfile.Zone>, value: Int): Zone? {
powerZones.forEachIndexed { index, zone -> val zoneList = zones[userZones.size] ?: return null
if (power in zone.min..zone.max) {
return PowerZone.entries[index]
}
}
return null userZones.forEachIndexed { index, zone ->
} if (value in zone.min..zone.max) {
return zoneList.getOrNull(index) ?: Zone.Zone7
fun UserProfile.getUserHrZone(hr: Int): HrZone? {
heartRateZones.forEachIndexed { index, zone ->
if (hr in zone.min..zone.max) {
return HrZone.entries[index]
} }
} }

View File

@ -3,11 +3,13 @@
<color name="colorPrimary">#6200EE</color> <color name="colorPrimary">#6200EE</color>
<color name="colorPrimaryDark">#2600B3</color> <color name="colorPrimaryDark">#2600B3</color>
<color name="zoneActiveRecovery">#00B988</color> <color name="zone0">#2386D9</color>
<color name="zoneEndurance">#60EEB2</color> <color name="zone1">#00B988</color>
<color name="zoneTempo">#FFF500</color> <color name="zone2">#60EEB2</color>
<color name="zoneThreshold">#FB8C65</color> <color name="zone3">#FFF500</color>
<color name="zoneVO2Max">#FE581F</color> <color name="zone4">#FDC84C</color>
<color name="zoneAerobic">#D60404</color> <color name="zone5">#FB8C65</color>
<color name="zoneAnaerobic">#B700A2</color> <color name="zone6">#FE581F</color>
<color name="zone7">#D60404</color>
<color name="zone8">#B700A2</color>
</resources> </resources>