From 5137168f0d53e14f19672ab5706e8ae6290e842f Mon Sep 17 00:00:00 2001 From: timklge <2026103+timklge@users.noreply.github.com> Date: Fri, 24 Jan 2025 22:55:34 +0100 Subject: [PATCH] fix #19: Add energy output trigger (#20) * fix #19: Add energy output trigger * Fix kJ instead of J --- README.md | 2 +- app/build.gradle.kts | 4 +- app/manifest.json | 6 +- .../karooreminder/KarooReminderExtension.kt | 69 ++++++++++++++++++- .../karooreminder/screens/DetailScreen.kt | 2 + 5 files changed, 74 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 26b24b7..22120c4 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![GitHub Downloads (specific asset, all releases)](https://img.shields.io/github/downloads/timklge/karoo-reminder/app-release.apk)](https://github.com/timklge/karoo-reminder/releases) ![GitHub License](https://img.shields.io/github/license/timklge/karoo-reminder) -Karoo extension that displays in-ride alerts based on custom triggers. Reminders can be set to activate after a specific time interval, distance traveled, or when a sensor value is outside a defined range (e.g., heart rate exceeds zone 2). +Karoo extension that displays in-ride alerts based on custom triggers. Reminders can be set to activate after a specific time interval, distance traveled, energy output or when a sensor value is outside a defined range (e.g., heart rate exceeds zone 2). ![Reminder List](list.png) ![Reminder Detail](detail.png) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 462956e..99766d7 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -15,8 +15,8 @@ android { applicationId = "de.timklge.karooreminder" minSdk = 26 targetSdk = 34 - versionCode = 11 - versionName = "1.1.2" + versionCode = 12 + versionName = "1.1.3" } signingConfigs { diff --git a/app/manifest.json b/app/manifest.json index 4746837..a200606 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -3,9 +3,9 @@ "packageName": "de.timklge.karooreminder", "iconUrl": "https://github.com/timklge/karoo-reminder/releases/latest/download/karoo-reminder.png", "latestApkUrl": "https://github.com/timklge/karoo-reminder/releases/latest/download/app-release.apk", - "latestVersion": "1.1.2", - "latestVersionCode": 11, + "latestVersion": "1.1.3", + "latestVersionCode": 12, "developer": "timklge", "description": "Shows in-ride alerts after a given time interval, distance or HR / power / speed / cadence out of range", - "releaseNotes": "Increased beep frequency to make sounds louder on K2, make colors brighter" + "releaseNotes": "Add energy output trigger" } \ No newline at end of file diff --git a/app/src/main/kotlin/de/timklge/karooreminder/KarooReminderExtension.kt b/app/src/main/kotlin/de/timklge/karooreminder/KarooReminderExtension.kt index 9ee809a..4e56a6f 100644 --- a/app/src/main/kotlin/de/timklge/karooreminder/KarooReminderExtension.kt +++ b/app/src/main/kotlin/de/timklge/karooreminder/KarooReminderExtension.kt @@ -22,12 +22,14 @@ import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.ClosedReceiveChannelException import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterNot import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.launch @@ -36,6 +38,7 @@ import kotlinx.serialization.json.Json enum class ReminderTrigger(val id: String, val label: String) { ELAPSED_TIME("elapsed_time", "Elapsed Time"), DISTANCE("distance", "Distance"), + ENERGY_OUTPUT("energy_output", "Energy Output"), HR_LIMIT_MAXIMUM_EXCEEDED("hr_limit_max", "HR above value"), HR_LIMIT_MINIMUM_EXCEEDED("hr_limit_min", "HR below value"), POWER_LIMIT_MAXIMUM_EXCEEDED("power_limit_min", "Power above value"), @@ -50,6 +53,7 @@ enum class ReminderTrigger(val id: String, val label: String) { HR_LIMIT_MINIMUM_EXCEEDED, POWER_LIMIT_MINIMUM_EXCEEDED, SPEED_LIMIT_MINIMUM_EXCEEDED, CADENCE_LIMIT_MINIMUM_EXCEEDED -> "<" HR_LIMIT_MAXIMUM_EXCEEDED, POWER_LIMIT_MAXIMUM_EXCEEDED, SPEED_LIMIT_MAXIMUM_EXCEEDED, CADENCE_LIMIT_MAXIMUM_EXCEEDED -> ">" ELAPSED_TIME, DISTANCE -> "" + ENERGY_OUTPUT -> "" } } @@ -61,11 +65,30 @@ enum class ReminderTrigger(val id: String, val label: String) { POWER_LIMIT_MAXIMUM_EXCEEDED, POWER_LIMIT_MINIMUM_EXCEEDED -> "W" SPEED_LIMIT_MAXIMUM_EXCEEDED, SPEED_LIMIT_MINIMUM_EXCEEDED -> if(imperial) "mph" else "km/h" CADENCE_LIMIT_MAXIMUM_EXCEEDED, CADENCE_LIMIT_MINIMUM_EXCEEDED -> "rpm" + ENERGY_OUTPUT -> "kJ" } } } -class KarooReminderExtension : KarooExtension("karoo-reminder", "1.1.2") { +fun Flow.allIntermediateInts(): Flow = flow { + var lastValue: Int? = null + + collect { value -> + if (lastValue != null){ + val start = (lastValue!! + 1).coerceAtMost(value) + + for (i in start..value) { + emit(i) + } + } else { + emit(value) + } + + lastValue = value + } +} + +class KarooReminderExtension : KarooExtension("karoo-reminder", "1.1.3") { companion object { const val TAG = "karoo-reminder" @@ -229,6 +252,46 @@ class KarooReminderExtension : KarooExtension("karoo-reminder", "1.1.2") { } } jobs.add(elapsedTimeJob) + + val energyOutputJob = CoroutineScope(Dispatchers.IO).launch { + val preferences = applicationContext.dataStore.data.map { remindersJson -> + try { + Json.decodeFromString>( + remindersJson[preferencesKey] ?: defaultReminders + ) + } catch(e: Throwable){ + Log.e(TAG,"Failed to read preferences", e) + mutableListOf() + } + } + + karooSystem.streamDataFlow(DataType.Type.ENERGY_OUTPUT) + .mapNotNull { (it as? StreamState.Streaming)?.dataPoint?.singleValue } + .map { it.toInt() } + .distinctUntilChanged() + .filterNot { it == 0 } + .allIntermediateInts() + .combine(preferences) { energyOutput, reminders -> energyOutput to reminders} + .distinctUntilChanged { old, new -> old.first == new.first } + .collectLatest { (energyOutput, reminders) -> + val rs = reminders + .filter { reminder -> reminder.trigger == ReminderTrigger.ENERGY_OUTPUT && reminder.isActive && energyOutput % reminder.interval == 0 } + + for (reminder in rs){ + Log.i(TAG, "Energy output reminder: ${reminder.name}") + reminderChannel.send(DisplayedReminder(reminder.tone, ReminderTrigger.ENERGY_OUTPUT, InRideAlert( + id = "reminder-${reminder.id}-${energyOutput}", + detail = reminder.text, + title = reminder.name, + autoDismissMs = if(reminder.isAutoDismiss) reminder.autoDismissSeconds * 1000L else null, + icon = R.drawable.timer, + textColor = reminder.displayForegroundColor?.getTextColor() ?: R.color.black, + backgroundColor = reminder.displayForegroundColor?.colorRes ?: R.color.hRed + ))) + } + } + } + jobs.add(energyOutputJob) } data class StreamData(val value: Double, val reminders: MutableList? = null, val imperial: Boolean = false) @@ -251,7 +314,7 @@ class KarooReminderExtension : KarooExtension("karoo-reminder", "1.1.2") { ReminderTrigger.POWER_LIMIT_MAXIMUM_EXCEEDED, ReminderTrigger.POWER_LIMIT_MINIMUM_EXCEEDED -> DataType.Type.SMOOTHED_3S_AVERAGE_POWER ReminderTrigger.SPEED_LIMIT_MAXIMUM_EXCEEDED, ReminderTrigger.SPEED_LIMIT_MINIMUM_EXCEEDED -> DataType.Type.SMOOTHED_3S_AVERAGE_SPEED ReminderTrigger.CADENCE_LIMIT_MAXIMUM_EXCEEDED, ReminderTrigger.CADENCE_LIMIT_MINIMUM_EXCEEDED -> DataType.Type.SMOOTHED_3S_AVERAGE_CADENCE - ReminderTrigger.DISTANCE, ReminderTrigger.ELAPSED_TIME -> error("Unsupported trigger type: $triggerType") + ReminderTrigger.DISTANCE, ReminderTrigger.ELAPSED_TIME, ReminderTrigger.ENERGY_OUTPUT -> error("Unsupported trigger type: $triggerType") } karooSystem.streamDataFlow(dataType) @@ -277,7 +340,7 @@ class KarooReminderExtension : KarooExtension("karoo-reminder", "1.1.2") { ReminderTrigger.HR_LIMIT_MINIMUM_EXCEEDED, ReminderTrigger.POWER_LIMIT_MINIMUM_EXCEEDED, ReminderTrigger.CADENCE_LIMIT_MINIMUM_EXCEEDED, ReminderTrigger.SPEED_LIMIT_MINIMUM_EXCEEDED -> value < reminderValue - ReminderTrigger.DISTANCE, ReminderTrigger.ELAPSED_TIME -> error("Unsupported trigger type: $triggerType") + else -> error("Unsupported trigger type: $triggerType") } reminder.isActive && reminder.trigger == triggerType && triggerIsMet diff --git a/app/src/main/kotlin/de/timklge/karooreminder/screens/DetailScreen.kt b/app/src/main/kotlin/de/timklge/karooreminder/screens/DetailScreen.kt index 379c8f0..664c9f3 100644 --- a/app/src/main/kotlin/de/timklge/karooreminder/screens/DetailScreen.kt +++ b/app/src/main/kotlin/de/timklge/karooreminder/screens/DetailScreen.kt @@ -129,6 +129,7 @@ fun DetailScreen(isCreating: Boolean, reminder: Reminder, onSubmit: (updatedRemi ReminderTrigger.SPEED_LIMIT_MINIMUM_EXCEEDED -> 20.toString() ReminderTrigger.CADENCE_LIMIT_MAXIMUM_EXCEEDED -> 120.toString() ReminderTrigger.CADENCE_LIMIT_MINIMUM_EXCEEDED -> 60.toString() + ReminderTrigger.ENERGY_OUTPUT -> 200.toString() } } } @@ -148,6 +149,7 @@ fun DetailScreen(isCreating: Boolean, reminder: Reminder, onSubmit: (updatedRemi ReminderTrigger.SPEED_LIMIT_MINIMUM_EXCEEDED -> Text("Minimum speed") ReminderTrigger.CADENCE_LIMIT_MAXIMUM_EXCEEDED -> Text("Maximum cadence") ReminderTrigger.CADENCE_LIMIT_MINIMUM_EXCEEDED -> Text("Minimum cadence") + ReminderTrigger.ENERGY_OUTPUT -> Text("Energy Output") } }, suffix = {