Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -748,6 +748,7 @@
<string name="rainfall_24h">Rain (24h)</string>
<string name="weight">Weight</string>
<string name="radiation">Radiation</string>
<string name="one_wire_temperature">1-Wire Temp</string>
<string name="store_forward_config"><![CDATA[Store & Forward Config]]></string>
<string name="indoor_air_quality_iaq">Indoor Air Quality (IAQ)</string>
<string name="url">URL</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ object GraphColors {
val Lime = Color(0xFFCDDC39)
val Indigo = Color(0xFF3F51B5)
val DeepOrange = Color(0xFFFF5722)
val Magenta = Color(0xFFE040FB)
val SkyBlue = Color(0xFF03A9F4)
val Chartreuse = Color(0xFF76FF03)
val Coral = Color(0xFFFF6E40)
}

object StatusColors {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import org.meshtastic.core.resources.ic_radioactive
import org.meshtastic.core.resources.ic_soil_moisture
import org.meshtastic.core.resources.ic_soil_temperature
import org.meshtastic.core.resources.lux
import org.meshtastic.core.resources.one_wire_temperature
import org.meshtastic.core.resources.pressure
import org.meshtastic.core.resources.radiation
import org.meshtastic.core.resources.soil_moisture
Expand Down Expand Up @@ -222,6 +223,18 @@ internal fun EnvironmentMetrics(
),
)
}
// 1-Wire temperature sensors (up to 8 channels)
one_wire_temperature
.filterNot { it.isNaN() }
.forEachIndexed { idx, temp ->
add(
DrawableMetricInfo(
label = Res.string.one_wire_temperature,
value = "${idx + 1}: ${temp.toTempString(isFahrenheit)}",
icon = Res.drawable.ic_soil_temperature,
),
)
}
}
}
FlowRow(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ data class LegendData(
val color: Color,
val isLine: Boolean = false,
val metricKey: Any? = null,
/** When non-null, overrides the resolved [nameRes] string in the legend label. */
val labelOverride: String? = null,
)

data class InfoDialogData(val titleRes: StringResource, val definitionRes: StringResource, val color: Color)
Expand All @@ -153,11 +155,12 @@ fun Legend(
) {
legendData.forEachIndexed { index, data ->
val isVisible = index !in hiddenSet
val label = data.labelOverride ?: stringResource(data.nameRes)
if (onToggle != null) {
FilterChip(
selected = isVisible,
onClick = { onToggle(index) },
label = { Text(text = stringResource(data.nameRes), style = MaterialTheme.typography.labelSmall) },
label = { Text(text = label, style = MaterialTheme.typography.labelSmall) },
leadingIcon = { LegendIndicator(color = data.color, isLine = data.isLine) },
modifier = Modifier.padding(horizontal = 2.dp),
)
Expand All @@ -166,7 +169,7 @@ fun Legend(
LegendIndicator(color = data.color, isLine = data.isLine)
Spacer(modifier = Modifier.width(4.dp))
Text(
text = stringResource(data.nameRes),
text = label,
color = MaterialTheme.colorScheme.onSurface,
fontSize = MaterialTheme.typography.labelSmall.fontSize,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import org.meshtastic.core.resources.baro_pressure
import org.meshtastic.core.resources.humidity
import org.meshtastic.core.resources.iaq
import org.meshtastic.core.resources.lux
import org.meshtastic.core.resources.one_wire_temperature
import org.meshtastic.core.resources.radiation
import org.meshtastic.core.resources.soil_moisture
import org.meshtastic.core.resources.soil_temperature
Expand Down Expand Up @@ -112,6 +113,27 @@ private val LEGEND_DATA_3 =
),
)

private val LEGEND_DATA_4 =
listOf(
Environment.ONE_WIRE_TEMP_1,
Environment.ONE_WIRE_TEMP_2,
Environment.ONE_WIRE_TEMP_3,
Environment.ONE_WIRE_TEMP_4,
Environment.ONE_WIRE_TEMP_5,
Environment.ONE_WIRE_TEMP_6,
Environment.ONE_WIRE_TEMP_7,
Environment.ONE_WIRE_TEMP_8,
)
.mapIndexed { index, entry ->
LegendData(
nameRes = Res.string.one_wire_temperature,
labelOverride = "1-Wire Temp ${index + 1}",
color = entry.color,
isLine = true,
metricKey = entry,
)
}

@Suppress("LongMethod", "CyclomaticComplexMethod")
@Composable
fun EnvironmentMetricsChart(
Expand All @@ -132,7 +154,7 @@ fun EnvironmentMetricsChart(
val onSurfaceColor = MaterialTheme.colorScheme.onSurface

val allLegendData =
(LEGEND_DATA_1 + LEGEND_DATA_2 + LEGEND_DATA_3).filter {
(LEGEND_DATA_1 + LEGEND_DATA_2 + LEGEND_DATA_3 + LEGEND_DATA_4).filter {
graphData.shouldPlot[(it.metricKey as? Environment)?.ordinal ?: 0]
}

Expand All @@ -143,7 +165,7 @@ fun EnvironmentMetricsChart(
hiddenIndices.mapNotNull { allLegendData.getOrNull(it)?.metricKey as? Environment }.toSet()
}

val colorToLabel = allLegendData.associate { it.color to stringResource(it.nameRes) }
val colorToLabel = allLegendData.associate { it.color to (it.labelOverride ?: stringResource(it.nameRes)) }

val showPressure =
shouldPlot[Environment.BAROMETRIC_PRESSURE.ordinal] && Environment.BAROMETRIC_PRESSURE !in hiddenMetrics
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import org.meshtastic.core.resources.humidity
import org.meshtastic.core.resources.iaq
import org.meshtastic.core.resources.iaq_definition
import org.meshtastic.core.resources.lux
import org.meshtastic.core.resources.one_wire_temperature
import org.meshtastic.core.resources.radiation
import org.meshtastic.core.resources.rainfall_1h
import org.meshtastic.core.resources.rainfall_24h
Expand Down Expand Up @@ -443,6 +444,39 @@ private fun RainfallDisplay(envMetrics: org.meshtastic.proto.EnvironmentMetrics)
}
}

@Composable
private fun OneWireTemperatureDisplay(
envMetrics: org.meshtastic.proto.EnvironmentMetrics,
environmentDisplayFahrenheit: Boolean,
) {
val sensors = envMetrics.one_wire_temperature.filterNot { it.isNaN() }
if (sensors.isEmpty()) return
val oneWireEntries =
listOf(
Environment.ONE_WIRE_TEMP_1,
Environment.ONE_WIRE_TEMP_2,
Environment.ONE_WIRE_TEMP_3,
Environment.ONE_WIRE_TEMP_4,
Environment.ONE_WIRE_TEMP_5,
Environment.ONE_WIRE_TEMP_6,
Environment.ONE_WIRE_TEMP_7,
Environment.ONE_WIRE_TEMP_8,
)
val textFormat = if (environmentDisplayFahrenheit) "%s %d: %.1f°F" else "%s %d: %.1f°C"
sensors.forEachIndexed { idx, temp ->
val color = oneWireEntries.getOrNull(idx)?.color ?: Environment.ONE_WIRE_TEMP_1.color
Row(verticalAlignment = Alignment.CenterVertically) {
MetricIndicator(color)
Spacer(Modifier.width(4.dp))
Text(
text = formatString(textFormat, stringResource(Res.string.one_wire_temperature), idx + 1, temp),
color = MaterialTheme.colorScheme.onSurface,
fontSize = MaterialTheme.typography.labelLarge.fontSize,
)
}
}
}

@Composable
private fun EnvironmentMetricsCard(
telemetry: Telemetry,
Expand Down Expand Up @@ -484,6 +518,7 @@ private fun EnvironmentMetricsContent(telemetry: Telemetry, environmentDisplayFa
RadiationDisplay(envMetrics)
WindDisplay(envMetrics)
RainfallDisplay(envMetrics)
OneWireTemperatureDisplay(envMetrics, environmentDisplayFahrenheit)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,24 @@ package org.meshtastic.feature.node.metrics

import androidx.compose.ui.graphics.Color
import org.meshtastic.core.model.util.UnitConversions
import org.meshtastic.core.ui.theme.GraphColors.Amber
import org.meshtastic.core.ui.theme.GraphColors.Blue
import org.meshtastic.core.ui.theme.GraphColors.Chartreuse
import org.meshtastic.core.ui.theme.GraphColors.Coral
import org.meshtastic.core.ui.theme.GraphColors.Cyan
import org.meshtastic.core.ui.theme.GraphColors.DeepOrange
import org.meshtastic.core.ui.theme.GraphColors.Gold
import org.meshtastic.core.ui.theme.GraphColors.Green
import org.meshtastic.core.ui.theme.GraphColors.Indigo
import org.meshtastic.core.ui.theme.GraphColors.InfantryBlue
import org.meshtastic.core.ui.theme.GraphColors.LightGreen
import org.meshtastic.core.ui.theme.GraphColors.Lime
import org.meshtastic.core.ui.theme.GraphColors.Magenta
import org.meshtastic.core.ui.theme.GraphColors.Orange
import org.meshtastic.core.ui.theme.GraphColors.Pink
import org.meshtastic.core.ui.theme.GraphColors.Purple
import org.meshtastic.core.ui.theme.GraphColors.Red
import org.meshtastic.core.ui.theme.GraphColors.SkyBlue
import org.meshtastic.core.ui.theme.GraphColors.Teal
import org.meshtastic.proto.Telemetry

Expand Down Expand Up @@ -66,7 +74,39 @@ enum class Environment(val color: Color) {
override fun getValue(telemetry: Telemetry) = telemetry.environment_metrics?.wind_speed
},
RADIATION(Lime) {
override fun getValue(telemetry: Telemetry) = telemetry.environment_metrics?.radiation
override fun getValue(telemetry: Telemetry): Float? = telemetry.environment_metrics?.radiation
},
ONE_WIRE_TEMP_1(Amber) {
override fun getValue(telemetry: Telemetry): Float? =
telemetry.environment_metrics?.one_wire_temperature?.getOrNull(0)
},
ONE_WIRE_TEMP_2(DeepOrange) {
override fun getValue(telemetry: Telemetry): Float? =
telemetry.environment_metrics?.one_wire_temperature?.getOrNull(1)
},
ONE_WIRE_TEMP_3(Indigo) {
override fun getValue(telemetry: Telemetry): Float? =
telemetry.environment_metrics?.one_wire_temperature?.getOrNull(2)
},
ONE_WIRE_TEMP_4(LightGreen) {
override fun getValue(telemetry: Telemetry): Float? =
telemetry.environment_metrics?.one_wire_temperature?.getOrNull(3)
},
ONE_WIRE_TEMP_5(Magenta) {
override fun getValue(telemetry: Telemetry): Float? =
telemetry.environment_metrics?.one_wire_temperature?.getOrNull(4)
},
ONE_WIRE_TEMP_6(SkyBlue) {
override fun getValue(telemetry: Telemetry): Float? =
telemetry.environment_metrics?.one_wire_temperature?.getOrNull(5)
},
ONE_WIRE_TEMP_7(Chartreuse) {
override fun getValue(telemetry: Telemetry): Float? =
telemetry.environment_metrics?.one_wire_temperature?.getOrNull(6)
},
ONE_WIRE_TEMP_8(Coral) {
override fun getValue(telemetry: Telemetry): Float? =
telemetry.environment_metrics?.one_wire_temperature?.getOrNull(7)
}, ;

abstract fun getValue(telemetry: Telemetry): Float?
Expand Down Expand Up @@ -205,6 +245,33 @@ data class EnvironmentMetricsState(val environmentMetrics: List<Telemetry> = emp
shouldPlot[Environment.RADIATION.ordinal] = true
}

// 1-Wire temperature sensors (up to 8 channels, Fahrenheit-aware)
val oneWireEntries =
listOf(
Environment.ONE_WIRE_TEMP_1,
Environment.ONE_WIRE_TEMP_2,
Environment.ONE_WIRE_TEMP_3,
Environment.ONE_WIRE_TEMP_4,
Environment.ONE_WIRE_TEMP_5,
Environment.ONE_WIRE_TEMP_6,
Environment.ONE_WIRE_TEMP_7,
Environment.ONE_WIRE_TEMP_8,
)
oneWireEntries.forEach { entry ->
val values = telemetries.mapNotNull { entry.getValue(it)?.takeIf { v -> !v.isNaN() } }
if (values.isNotEmpty()) {
var minVal = values.minOf { it }
var maxVal = values.maxOf { it }
if (useFahrenheit) {
minVal = UnitConversions.celsiusToFahrenheit(minVal)
maxVal = UnitConversions.celsiusToFahrenheit(maxVal)
}
minValues.add(minVal)
maxValues.add(maxVal)
shouldPlot[entry.ordinal] = true
}
}

val min = if (minValues.isEmpty()) 0f else minValues.minOf { it }
val max = if (maxValues.isEmpty()) 1f else maxValues.maxOf { it }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ open class MetricsViewModel(
temperature = em.temperature?.let { UnitConversions.celsiusToFahrenheit(it) },
soil_temperature =
em.soil_temperature?.let { UnitConversions.celsiusToFahrenheit(it) },
one_wire_temperature =
em.one_wire_temperature.map { UnitConversions.celsiusToFahrenheit(it) },
),
)
}
Expand Down Expand Up @@ -381,21 +383,25 @@ open class MetricsViewModel(
}

fun saveEnvironmentMetricsCSV(uri: MeshtasticUri, data: List<Telemetry>) {
val oneWireHeaders = (1..ONE_WIRE_SENSOR_COUNT).joinToString(",") { "\"oneWireTemp$it\"" }
exportCsv(
uri = uri,
header =
"\"date\",\"time\",\"temperature\",\"relativeHumidity\",\"barometricPressure\"," +
"\"gasResistance\",\"iaq\",\"windSpeed\",\"windDirection\",\"soilTemperature\"," +
"\"soilMoisture\"\n",
"\"soilMoisture\",$oneWireHeaders\n",
rows = data,
epochSeconds = { it.time.toLong() },
) { t ->
val em = t.environment_metrics
val owt = em?.one_wire_temperature ?: emptyList()
val oneWireValues =
(0 until ONE_WIRE_SENSOR_COUNT).joinToString(",") { i -> "\"${owt.getOrNull(i) ?: ""}\"" }
"\"${em?.temperature ?: ""}\",\"${em?.relative_humidity ?: ""}\"," +
"\"${em?.barometric_pressure ?: ""}\",\"${em?.gas_resistance ?: ""}\"," +
"\"${em?.iaq ?: ""}\",\"${em?.wind_speed ?: ""}\"," +
"\"${em?.wind_direction ?: ""}\",\"${em?.soil_temperature ?: ""}\"," +
"\"${em?.soil_moisture ?: ""}\""
"\"${em?.soil_moisture ?: ""}\",$oneWireValues"
}
}

Expand Down Expand Up @@ -457,4 +463,8 @@ open class MetricsViewModel(
}

protected fun decodeBase64(base64: String): ByteArray = base64.decodeBase64()?.toByteArray() ?: ByteArray(0)

companion object {
private const val ONE_WIRE_SENSOR_COUNT = 8
}
}