fix #19: Add energy output trigger (#20)

* fix #19: Add energy output trigger

* Fix kJ instead of J
This commit is contained in:
timklge 2025-01-24 22:55:34 +01:00 committed by GitHub
parent f8f93f7ece
commit 5137168f0d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 74 additions and 9 deletions

View File

@ -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 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) ![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 List](list.png)
![Reminder Detail](detail.png) ![Reminder Detail](detail.png)

View File

@ -15,8 +15,8 @@ android {
applicationId = "de.timklge.karooreminder" applicationId = "de.timklge.karooreminder"
minSdk = 26 minSdk = 26
targetSdk = 34 targetSdk = 34
versionCode = 11 versionCode = 12
versionName = "1.1.2" versionName = "1.1.3"
} }
signingConfigs { signingConfigs {

View File

@ -3,9 +3,9 @@
"packageName": "de.timklge.karooreminder", "packageName": "de.timklge.karooreminder",
"iconUrl": "https://github.com/timklge/karoo-reminder/releases/latest/download/karoo-reminder.png", "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", "latestApkUrl": "https://github.com/timklge/karoo-reminder/releases/latest/download/app-release.apk",
"latestVersion": "1.1.2", "latestVersion": "1.1.3",
"latestVersionCode": 11, "latestVersionCode": 12,
"developer": "timklge", "developer": "timklge",
"description": "Shows in-ride alerts after a given time interval, distance or HR / power / speed / cadence out of range", "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"
} }

View File

@ -22,12 +22,14 @@ import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.ClosedReceiveChannelException import kotlinx.coroutines.channels.ClosedReceiveChannelException
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNot import kotlinx.coroutines.flow.filterNot
import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -36,6 +38,7 @@ import kotlinx.serialization.json.Json
enum class ReminderTrigger(val id: String, val label: String) { enum class ReminderTrigger(val id: String, val label: String) {
ELAPSED_TIME("elapsed_time", "Elapsed Time"), ELAPSED_TIME("elapsed_time", "Elapsed Time"),
DISTANCE("distance", "Distance"), DISTANCE("distance", "Distance"),
ENERGY_OUTPUT("energy_output", "Energy Output"),
HR_LIMIT_MAXIMUM_EXCEEDED("hr_limit_max", "HR above value"), HR_LIMIT_MAXIMUM_EXCEEDED("hr_limit_max", "HR above value"),
HR_LIMIT_MINIMUM_EXCEEDED("hr_limit_min", "HR below value"), HR_LIMIT_MINIMUM_EXCEEDED("hr_limit_min", "HR below value"),
POWER_LIMIT_MAXIMUM_EXCEEDED("power_limit_min", "Power above 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_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 -> ">" HR_LIMIT_MAXIMUM_EXCEEDED, POWER_LIMIT_MAXIMUM_EXCEEDED, SPEED_LIMIT_MAXIMUM_EXCEEDED, CADENCE_LIMIT_MAXIMUM_EXCEEDED -> ">"
ELAPSED_TIME, DISTANCE -> "" 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" POWER_LIMIT_MAXIMUM_EXCEEDED, POWER_LIMIT_MINIMUM_EXCEEDED -> "W"
SPEED_LIMIT_MAXIMUM_EXCEEDED, SPEED_LIMIT_MINIMUM_EXCEEDED -> if(imperial) "mph" else "km/h" SPEED_LIMIT_MAXIMUM_EXCEEDED, SPEED_LIMIT_MINIMUM_EXCEEDED -> if(imperial) "mph" else "km/h"
CADENCE_LIMIT_MAXIMUM_EXCEEDED, CADENCE_LIMIT_MINIMUM_EXCEEDED -> "rpm" CADENCE_LIMIT_MAXIMUM_EXCEEDED, CADENCE_LIMIT_MINIMUM_EXCEEDED -> "rpm"
ENERGY_OUTPUT -> "kJ"
} }
} }
} }
class KarooReminderExtension : KarooExtension("karoo-reminder", "1.1.2") { fun Flow<Int>.allIntermediateInts(): Flow<Int> = 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 { companion object {
const val TAG = "karoo-reminder" const val TAG = "karoo-reminder"
@ -229,6 +252,46 @@ class KarooReminderExtension : KarooExtension("karoo-reminder", "1.1.2") {
} }
} }
jobs.add(elapsedTimeJob) jobs.add(elapsedTimeJob)
val energyOutputJob = CoroutineScope(Dispatchers.IO).launch {
val preferences = applicationContext.dataStore.data.map { remindersJson ->
try {
Json.decodeFromString<MutableList<Reminder>>(
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<Reminder>? = null, val imperial: Boolean = false) data class StreamData(val value: Double, val reminders: MutableList<Reminder>? = 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.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.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.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) 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.HR_LIMIT_MINIMUM_EXCEEDED, ReminderTrigger.POWER_LIMIT_MINIMUM_EXCEEDED,
ReminderTrigger.CADENCE_LIMIT_MINIMUM_EXCEEDED, ReminderTrigger.SPEED_LIMIT_MINIMUM_EXCEEDED -> value < reminderValue 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 reminder.isActive && reminder.trigger == triggerType && triggerIsMet

View File

@ -129,6 +129,7 @@ fun DetailScreen(isCreating: Boolean, reminder: Reminder, onSubmit: (updatedRemi
ReminderTrigger.SPEED_LIMIT_MINIMUM_EXCEEDED -> 20.toString() ReminderTrigger.SPEED_LIMIT_MINIMUM_EXCEEDED -> 20.toString()
ReminderTrigger.CADENCE_LIMIT_MAXIMUM_EXCEEDED -> 120.toString() ReminderTrigger.CADENCE_LIMIT_MAXIMUM_EXCEEDED -> 120.toString()
ReminderTrigger.CADENCE_LIMIT_MINIMUM_EXCEEDED -> 60.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.SPEED_LIMIT_MINIMUM_EXCEEDED -> Text("Minimum speed")
ReminderTrigger.CADENCE_LIMIT_MAXIMUM_EXCEEDED -> Text("Maximum cadence") ReminderTrigger.CADENCE_LIMIT_MAXIMUM_EXCEEDED -> Text("Maximum cadence")
ReminderTrigger.CADENCE_LIMIT_MINIMUM_EXCEEDED -> Text("Minimum cadence") ReminderTrigger.CADENCE_LIMIT_MINIMUM_EXCEEDED -> Text("Minimum cadence")
ReminderTrigger.ENERGY_OUTPUT -> Text("Energy Output")
} }
}, },
suffix = { suffix = {