From e839c43542f714cfc48d4fb052f796280aed1c74 Mon Sep 17 00:00:00 2001 From: Robert-0410 <62630290+Robert-0410@users.noreply.github.com> Date: Tue, 18 Mar 2025 17:37:16 -0700 Subject: [PATCH] feat: Graph Barometric Pressure (#1684) * refactor: condensed the process of drawing the lines in the env metrics * feat: only include iaq in the env metric graph when we have actual values * feat: plotting barometric pressure when we have actual values * detekt: spacing after ( --------- Co-authored-by: James Rich <2199651+jamesarich@users.noreply.github.com> --- .../mesh/ui/components/CommonCharts.kt | 1 - .../mesh/ui/components/EnvironmentMetrics.kt | 204 +++++++++--------- .../mesh/ui/components/PowerMetrics.kt | 4 +- .../com/geeksville/mesh/ui/theme/Color.kt | 3 +- app/src/main/res/values/strings.xml | 1 + 5 files changed, 112 insertions(+), 101 deletions(-) diff --git a/app/src/main/java/com/geeksville/mesh/ui/components/CommonCharts.kt b/app/src/main/java/com/geeksville/mesh/ui/components/CommonCharts.kt index f6a9f89f5..d2d75921b 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/components/CommonCharts.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/components/CommonCharts.kt @@ -66,7 +66,6 @@ object CommonCharts { val DATE_TIME_FORMAT: DateFormat = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM) const val MS_PER_SEC = 1000L const val MAX_PERCENT_VALUE = 100f - val INFANTRY_BLUE = Color(75, 119, 190) } private const val LINE_ON = 10f diff --git a/app/src/main/java/com/geeksville/mesh/ui/components/EnvironmentMetrics.kt b/app/src/main/java/com/geeksville/mesh/ui/components/EnvironmentMetrics.kt index c50dd33a8..575a7a31b 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/components/EnvironmentMetrics.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/components/EnvironmentMetrics.kt @@ -62,16 +62,36 @@ import com.geeksville.mesh.model.MetricsViewModel import com.geeksville.mesh.model.TimeFrame import com.geeksville.mesh.ui.components.CommonCharts.MS_PER_SEC import com.geeksville.mesh.ui.components.CommonCharts.DATE_TIME_FORMAT -import com.geeksville.mesh.ui.components.CommonCharts.INFANTRY_BLUE +import com.geeksville.mesh.ui.theme.InfantryBlue +import com.geeksville.mesh.ui.theme.Orange import com.geeksville.mesh.util.GraphUtil.createPath import com.geeksville.mesh.util.GraphUtil.drawPathWithGradient private enum class Environment(val color: Color) { - TEMPERATURE(Color.Red), - HUMIDITY(INFANTRY_BLUE), - IAQ(Color.Green) + TEMPERATURE(Color.Red) { + override fun getValue(telemetry: Telemetry): Float { + return telemetry.environmentMetrics.temperature + } + }, + HUMIDITY(InfantryBlue) { + override fun getValue(telemetry: Telemetry): Float { + return telemetry.environmentMetrics.relativeHumidity + } + }, + IAQ(Color.Green) { + override fun getValue(telemetry: Telemetry): Float { + return telemetry.environmentMetrics.iaq.toFloat() + } + }, + BAROMETRIC_PRESSURE(Orange) { + override fun getValue(telemetry: Telemetry): Float { + return telemetry.environmentMetrics.barometricPressure + } + }; + + abstract fun getValue(telemetry: Telemetry): Float } -private val LEGEND_DATA = listOf( +private val LEGEND_DATA_1 = listOf( LegendData( nameRes = R.string.temperature, color = Environment.TEMPERATURE.color, @@ -82,11 +102,18 @@ private val LEGEND_DATA = listOf( color = Environment.HUMIDITY.color, isLine = true ), +) +private val LEGEND_DATA_2 = listOf( LegendData( nameRes = R.string.iaq, color = Environment.IAQ.color, isLine = true ), + LegendData( + nameRes = R.string.baro_pressure, + color = Environment.BAROMETRIC_PRESSURE.color, + isLine = true + ) ) @Composable @@ -160,7 +187,7 @@ fun EnvironmentMetricsScreen( } } -@Suppress("LongMethod", "CyclomaticComplexMethod") +@Suppress("LongMethod", "CyclomaticComplexMethod", "ComplexCondition") @Composable private fun EnvironmentMetricsChart( modifier: Modifier = Modifier, @@ -202,23 +229,44 @@ private fun EnvironmentMetricsChart( telemetries.maxBy { it.environmentMetrics.relativeHumidity } ) } + val minValues = mutableListOf( + minTemp.environmentMetrics.temperature, + minHumidity.environmentMetrics.relativeHumidity + ) + val maxValues = mutableListOf( + maxTemp.environmentMetrics.temperature, + maxHumidity.environmentMetrics.relativeHumidity + ) val (minIAQ, maxIAQ) = remember(key1 = telemetries) { Pair( telemetries.minBy { it.environmentMetrics.iaq }, telemetries.maxBy { it.environmentMetrics.iaq } ) } - val min = minOf( - minTemp.environmentMetrics.temperature, - minHumidity.environmentMetrics.relativeHumidity, - minIAQ.environmentMetrics.iaq.toFloat() - ) - val max = maxOf( - maxTemp.environmentMetrics.temperature, - maxHumidity.environmentMetrics.relativeHumidity, - maxIAQ.environmentMetrics.iaq.toFloat() - ) - val diff = max - min + var plotIAQ = false + if (minIAQ.environmentMetrics.iaq != 0 && maxIAQ.environmentMetrics.iaq != 0) { + minValues.add(minIAQ.environmentMetrics.iaq.toFloat()) + maxValues.add(maxIAQ.environmentMetrics.iaq.toFloat()) + plotIAQ = true + } + + val min = minValues.minOf { it } + val max = maxValues.maxOf { it } + var diff = max - min + + val (minPressure, maxPressure) = remember(key1 = telemetries) { + Pair( + telemetries.minBy { it.environmentMetrics.barometricPressure }, + telemetries.maxBy { it.environmentMetrics.barometricPressure } + ) + } + var plotPressure = false + val pressureDiff = + maxPressure.environmentMetrics.barometricPressure - minPressure.environmentMetrics.barometricPressure + if (minPressure.environmentMetrics.barometricPressure != 0.0F && + maxPressure.environmentMetrics.barometricPressure != 0.0F) { + plotPressure = true + } val scrollState = rememberScrollState() val screenWidth = LocalConfiguration.current.screenWidthDp @@ -227,6 +275,14 @@ private fun EnvironmentMetricsChart( } Row { + if (plotPressure) { + YAxisLabels( + modifier = modifier.weight(weight = .1f), + Environment.BAROMETRIC_PRESSURE.color, + minValue = minPressure.environmentMetrics.barometricPressure, + maxValue = maxPressure.environmentMetrics.barometricPressure + ) + } Box( contentAlignment = Alignment.TopStart, modifier = Modifier @@ -250,89 +306,42 @@ private fun EnvironmentMetricsChart( val height = size.height val width = size.width - /* Temperature */ - var index = 0 + var index: Int var first: Int - while (index < telemetries.size) { - first = index - val path = Path() - index = createPath( - telemetries = telemetries, - index = index, - path = path, - oldestTime = oldest.time, - timeRange = timeDiff, - width = width, - timeThreshold = selectedTime.timeThreshold() - ) { i -> - val telemetry = telemetries.getOrNull(i) ?: telemetries.last() - val ratio = (telemetry.environmentMetrics.temperature - min) / diff - val y = height - (ratio * height) - return@createPath y + for (metric in Environment.entries) { + if (metric == Environment.IAQ && !plotIAQ || + metric == Environment.BAROMETRIC_PRESSURE && !plotPressure) { + continue } - drawPathWithGradient( - path = path, - color = Environment.TEMPERATURE.color, - height = height, - x1 = ((telemetries[index - 1].time - oldest.time).toFloat() / timeDiff) * width, - x2 = ((telemetries[first].time - oldest.time).toFloat() / timeDiff) * width - ) - } - - /* Relative Humidity */ - index = 0 - while (index < telemetries.size) { - first = index - val path = Path() - index = createPath( - telemetries = telemetries, - index = index, - path = path, - oldestTime = oldest.time, - timeRange = timeDiff, - width = width, - timeThreshold = selectedTime.timeThreshold() - ) { i -> - val telemetry = telemetries.getOrNull(i) ?: telemetries.last() - val ratio = (telemetry.environmentMetrics.relativeHumidity - min) / diff - val y = height - (ratio * height) - return@createPath y + if (metric == Environment.BAROMETRIC_PRESSURE) { + diff = pressureDiff } - drawPathWithGradient( - path = path, - color = Environment.HUMIDITY.color, - height = height, - x1 = ((telemetries[index - 1].time - oldest.time).toFloat() / timeDiff) * width, - x2 = ((telemetries[first].time - oldest.time).toFloat() / timeDiff) * width - ) - } - - /* Air Quality */ - index = 0 - while (index < telemetries.size) { - first = index - val path = Path() - index = createPath( - telemetries = telemetries, - index = index, - path = path, - oldestTime = oldest.time, - timeRange = timeDiff, - width = width, - timeThreshold = selectedTime.timeThreshold() - ) { i -> - val telemetry = telemetries.getOrNull(i) ?: telemetries.last() - val ratio = (telemetry.environmentMetrics.iaq - min) / diff - val y = height - (ratio * height) - return@createPath y + index = 0 + while (index < telemetries.size) { + first = index + val path = Path() + index = createPath( + telemetries = telemetries, + index = index, + path = path, + oldestTime = oldest.time, + timeRange = timeDiff, + width = width, + timeThreshold = selectedTime.timeThreshold() + ) { i -> + val telemetry = telemetries.getOrNull(i) ?: telemetries.last() + val ratio = (metric.getValue(telemetry) - min) / diff + val y = height - (ratio * height) + return@createPath y + } + drawPathWithGradient( + path = path, + color = metric.color, + height = height, + x1 = ((telemetries[index - 1].time - oldest.time).toFloat() / timeDiff) * width, + x2 = ((telemetries[first].time - oldest.time).toFloat() / timeDiff) * width + ) } - drawPathWithGradient( - path = path, - color = Environment.IAQ.color, - height = height, - x1 = ((telemetries[index - 1].time - oldest.time).toFloat() / timeDiff) * width, - x2 = ((telemetries[first].time - oldest.time).toFloat() / timeDiff) * width - ) } } } @@ -346,7 +355,8 @@ private fun EnvironmentMetricsChart( Spacer(modifier = Modifier.height(16.dp)) - Legend(LEGEND_DATA, promptInfoDialog = promptInfoDialog) + Legend(LEGEND_DATA_1, displayInfoIcon = false) + Legend(LEGEND_DATA_2, promptInfoDialog = promptInfoDialog) Spacer(modifier = Modifier.height(16.dp)) } diff --git a/app/src/main/java/com/geeksville/mesh/ui/components/PowerMetrics.kt b/app/src/main/java/com/geeksville/mesh/ui/components/PowerMetrics.kt index 4bc7c530e..413b772b0 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/components/PowerMetrics.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/components/PowerMetrics.kt @@ -64,13 +64,13 @@ import com.geeksville.mesh.model.MetricsViewModel import com.geeksville.mesh.model.TimeFrame import com.geeksville.mesh.ui.components.CommonCharts.MS_PER_SEC import com.geeksville.mesh.ui.components.CommonCharts.DATE_TIME_FORMAT -import com.geeksville.mesh.ui.components.CommonCharts.INFANTRY_BLUE +import com.geeksville.mesh.ui.theme.InfantryBlue import com.geeksville.mesh.util.GraphUtil import com.geeksville.mesh.util.GraphUtil.createPath @Suppress("MagicNumber") private enum class Power(val color: Color, val min: Float, val max: Float) { - CURRENT(INFANTRY_BLUE, -500f, 500f), + CURRENT(InfantryBlue, -500f, 500f), VOLTAGE(Color.Red, 0f, 20f); /** * Difference between the metrics `max` and `min` values. diff --git a/app/src/main/java/com/geeksville/mesh/ui/theme/Color.kt b/app/src/main/java/com/geeksville/mesh/ui/theme/Color.kt index e8f0ca87e..a60a3a293 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/theme/Color.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/theme/Color.kt @@ -35,4 +35,5 @@ val LightRed = Color(0xFFFFB3B3) val MeshtasticGreen = Color(0xFF67EA94) val HyperlinkBlue = Color(0xFF43C3B0) -val Orange = Color(255, 153, 0) \ No newline at end of file +val InfantryBlue = Color(red = 75, green = 119, blue = 190) +val Orange = Color(red = 247, green = 147, blue = 26) \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e7742242c..3b128c096 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -341,6 +341,7 @@ Low battery notifications Low battery: %s Low battery notifications (favorite nodes) + Barometric Pressure Mesh via UDP enabled UDP Config