mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
Add soil temperature and soil moisture environmental metrics to app (#2419)
Co-authored-by: DaneEvans <dane@goneepic.com>
This commit is contained in:
parent
8a0ad26d4e
commit
fbd62cbf10
10 changed files with 158 additions and 14 deletions
|
|
@ -160,6 +160,8 @@ data class EnvironmentMetrics(
|
|||
val time: Int = currentTime(), // default to current time in secs (NOT MILLISECONDS!)
|
||||
val temperature: Float,
|
||||
val relativeHumidity: Float,
|
||||
val soilTemperature: Float,
|
||||
val soilMoisture: Int,
|
||||
val barometricPressure: Float,
|
||||
val gasResistance: Float,
|
||||
val voltage: Float,
|
||||
|
|
|
|||
|
|
@ -215,6 +215,8 @@ data class NodeEntity(
|
|||
time = environmentTelemetry.time,
|
||||
temperature = environmentMetrics.temperature,
|
||||
relativeHumidity = environmentMetrics.relativeHumidity,
|
||||
soilTemperature = environmentMetrics.soilTemperature,
|
||||
soilMoisture = environmentMetrics.soilMoisture,
|
||||
barometricPressure = environmentMetrics.barometricPressure,
|
||||
gasResistance = environmentMetrics.gasResistance,
|
||||
voltage = environmentMetrics.voltage,
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ import androidx.compose.ui.graphics.Color
|
|||
import com.geeksville.mesh.TelemetryProtos.Telemetry
|
||||
import com.geeksville.mesh.ui.common.theme.InfantryBlue
|
||||
import com.geeksville.mesh.ui.common.theme.Orange
|
||||
import com.geeksville.mesh.ui.common.theme.Pink
|
||||
import com.geeksville.mesh.ui.common.theme.Purple
|
||||
import com.geeksville.mesh.util.UnitConversions
|
||||
|
||||
enum class Environment(val color: Color) {
|
||||
|
|
@ -34,6 +36,16 @@ enum class Environment(val color: Color) {
|
|||
return telemetry.environmentMetrics.relativeHumidity
|
||||
}
|
||||
},
|
||||
SOIL_TEMPERATURE(Pink) {
|
||||
override fun getValue(telemetry: Telemetry): Float {
|
||||
return telemetry.environmentMetrics.soilTemperature
|
||||
}
|
||||
},
|
||||
SOIL_MOISTURE(Purple) {
|
||||
override fun getValue(telemetry: Telemetry): Float {
|
||||
return telemetry.environmentMetrics.soilMoisture.toFloat()
|
||||
}
|
||||
},
|
||||
IAQ(Color.Green) {
|
||||
override fun getValue(telemetry: Telemetry): Float {
|
||||
return telemetry.environmentMetrics.iaq.toFloat()
|
||||
|
|
@ -75,7 +87,7 @@ data class EnvironmentMetricsState(
|
|||
* @param timeFrame used to filter
|
||||
* @return [EnvironmentGraphingData]
|
||||
*/
|
||||
@Suppress("LongMethod")
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod", "MagicNumber")
|
||||
fun environmentMetricsFiltered(timeFrame: TimeFrame, useFahrenheit: Boolean = false): EnvironmentGraphingData {
|
||||
val oldestTime = timeFrame.calculateOldestTime()
|
||||
val telemetries = environmentMetrics.filter { it.time >= oldestTime }
|
||||
|
|
@ -84,7 +96,7 @@ data class EnvironmentMetricsState(
|
|||
return EnvironmentGraphingData(metrics = telemetries, shouldPlot = shouldPlot.toList())
|
||||
}
|
||||
|
||||
/* Grab the combined min and max for temp, humidity, and iaq. */
|
||||
/* Grab the combined min and max for temp, humidity, soil_Temperature, soilMoisture and iaq. */
|
||||
val minValues = mutableListOf<Float>()
|
||||
val maxValues = mutableListOf<Float>()
|
||||
val (minTemp, maxTemp) = Pair(
|
||||
|
|
@ -114,6 +126,31 @@ data class EnvironmentMetricsState(
|
|||
shouldPlot[Environment.HUMIDITY.ordinal] = true
|
||||
}
|
||||
|
||||
var minSoilTemperatureValue = minTemp.environmentMetrics.soilTemperature
|
||||
var maxSoilTemperatureValue = maxTemp.environmentMetrics.soilTemperature
|
||||
if (useFahrenheit) {
|
||||
minSoilTemperatureValue = UnitConversions.celsiusToFahrenheit(minSoilTemperatureValue)
|
||||
maxSoilTemperatureValue = UnitConversions.celsiusToFahrenheit(maxSoilTemperatureValue)
|
||||
}
|
||||
if (minTemp.environmentMetrics.soilTemperature != 0f ||
|
||||
maxTemp.environmentMetrics.soilTemperature != 0f) {
|
||||
minValues.add(minSoilTemperatureValue)
|
||||
maxValues.add(maxSoilTemperatureValue)
|
||||
shouldPlot[Environment.SOIL_TEMPERATURE.ordinal] = true
|
||||
}
|
||||
|
||||
val (minSoilMoisture, maxSoilMoisture) = Pair(
|
||||
telemetries.minBy { it.environmentMetrics.soilMoisture },
|
||||
telemetries.maxBy { it.environmentMetrics.soilMoisture }
|
||||
)
|
||||
val soilMoistureRange = 0..100
|
||||
if (minSoilMoisture.environmentMetrics.soilMoisture in soilMoistureRange ||
|
||||
maxSoilMoisture.environmentMetrics.soilMoisture in soilMoistureRange) {
|
||||
minValues.add(minSoilMoisture.environmentMetrics.soilMoisture.toFloat())
|
||||
maxValues.add(maxSoilMoisture.environmentMetrics.soilMoisture.toFloat())
|
||||
shouldPlot[Environment.SOIL_MOISTURE.ordinal] = true
|
||||
}
|
||||
|
||||
val (minIAQ, maxIAQ) = Pair(
|
||||
telemetries.minBy { it.environmentMetrics.iaq },
|
||||
telemetries.maxBy { it.environmentMetrics.iaq }
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import com.geeksville.mesh.TelemetryProtos.EnvironmentMetrics
|
|||
import com.geeksville.mesh.TelemetryProtos.PowerMetrics
|
||||
import com.geeksville.mesh.database.entity.NodeEntity
|
||||
import com.geeksville.mesh.util.GPSFormat
|
||||
import com.geeksville.mesh.util.UnitConversions.celsiusToFahrenheit
|
||||
import com.geeksville.mesh.util.latLongToMeter
|
||||
import com.geeksville.mesh.util.toDistanceString
|
||||
import com.google.protobuf.ByteString
|
||||
|
|
@ -113,8 +114,7 @@ data class Node(
|
|||
private fun EnvironmentMetrics.getDisplayString(isFahrenheit: Boolean): String {
|
||||
val temp = if (temperature != 0f) {
|
||||
if (isFahrenheit) {
|
||||
val fahrenheit = temperature * 1.8F + 32
|
||||
"%.1f°F".format(fahrenheit)
|
||||
"%.1f°F".format(celsiusToFahrenheit(temperature))
|
||||
} else {
|
||||
"%.1f°C".format(temperature)
|
||||
}
|
||||
|
|
@ -122,6 +122,20 @@ data class Node(
|
|||
null
|
||||
}
|
||||
val humidity = if (relativeHumidity != 0f) "%.0f%%".format(relativeHumidity) else null
|
||||
val soilTemperatureStr = if (soilTemperature != 0f) {
|
||||
if (isFahrenheit) {
|
||||
"%.1f°F".format(celsiusToFahrenheit(temperature))
|
||||
} else {
|
||||
"%.1f°C".format(soilTemperature)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
val soilMoistureRange = 0..100
|
||||
val soilMoisture =
|
||||
if (soilMoisture in soilMoistureRange && soilTemperature != 0f) {
|
||||
"%d%%".format(soilMoisture)
|
||||
} else { null }
|
||||
val voltage = if (this.voltage != 0f) "%.2fV".format(this.voltage) else null
|
||||
val current = if (current != 0f) "%.1fmA".format(current) else null
|
||||
val iaq = if (iaq != 0) "IAQ: $iaq" else null
|
||||
|
|
@ -129,6 +143,8 @@ data class Node(
|
|||
return listOfNotNull(
|
||||
temp,
|
||||
humidity,
|
||||
soilTemperatureStr,
|
||||
soilMoisture,
|
||||
voltage,
|
||||
current,
|
||||
iaq,
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ val Green = Color(0xFF30C047)
|
|||
|
||||
val HyperlinkBlue = Color(0xFF43C3B0)
|
||||
val InfantryBlue = Color(red = 75, green = 119, blue = 190)
|
||||
val Purple = Color(0xFF9C27B0)
|
||||
val Pink = Color(red = 255, green = 102, blue = 204)
|
||||
|
||||
val primaryLight = Color(0xFF306A42)
|
||||
val onPrimaryLight = Color(0xFFFFFFFF)
|
||||
|
|
|
|||
|
|
@ -68,6 +68,8 @@ import com.geeksville.mesh.ui.common.components.IaqDisplayMode
|
|||
import com.geeksville.mesh.ui.common.components.IndoorAirQuality
|
||||
import com.geeksville.mesh.ui.common.components.OptionLabel
|
||||
import com.geeksville.mesh.ui.common.components.SlidingSelector
|
||||
import com.geeksville.mesh.ui.common.theme.Pink
|
||||
import com.geeksville.mesh.ui.common.theme.Purple
|
||||
import com.geeksville.mesh.ui.metrics.CommonCharts.DATE_TIME_FORMAT
|
||||
import com.geeksville.mesh.ui.metrics.CommonCharts.MS_PER_SEC
|
||||
import com.geeksville.mesh.util.GraphUtil.createPath
|
||||
|
|
@ -78,6 +80,8 @@ import com.geeksville.mesh.util.UnitConversions.celsiusToFahrenheit
|
|||
private enum class Environment(val color: Color) {
|
||||
TEMPERATURE(Color.Red),
|
||||
RELATIVE_HUMIDITY(Color.Blue),
|
||||
SOIL_TEMPERATURE(Pink),
|
||||
SOIL_MOISTURE(Purple),
|
||||
BAROMETRIC_PRESSURE(Color.Green),
|
||||
GAS_RESISTANCE(Color.Yellow),
|
||||
IAQ(Color.Magenta)
|
||||
|
|
@ -112,6 +116,18 @@ private val LEGEND_DATA_2 = listOf(
|
|||
isLine = true
|
||||
)
|
||||
)
|
||||
private val LEGEND_DATA_3 = listOf(
|
||||
LegendData(
|
||||
nameRes = R.string.soil_temperature,
|
||||
color = Environment.SOIL_TEMPERATURE.color,
|
||||
isLine = true
|
||||
),
|
||||
LegendData(
|
||||
nameRes = R.string.soil_moisture,
|
||||
color = Environment.SOIL_MOISTURE.color,
|
||||
isLine = true
|
||||
),
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun EnvironmentMetricsScreen(
|
||||
|
|
@ -127,9 +143,13 @@ fun EnvironmentMetricsScreen(
|
|||
data.map { telemetry ->
|
||||
val temperatureFahrenheit =
|
||||
celsiusToFahrenheit(telemetry.environmentMetrics.temperature)
|
||||
val soilTemperatureFahrenheit =
|
||||
celsiusToFahrenheit(telemetry.environmentMetrics.soilTemperature)
|
||||
telemetry.copy {
|
||||
environmentMetrics =
|
||||
telemetry.environmentMetrics.copy { temperature = temperatureFahrenheit }
|
||||
environmentMetrics = telemetry.environmentMetrics.copy {
|
||||
temperature = temperatureFahrenheit }
|
||||
environmentMetrics = telemetry.environmentMetrics.copy {
|
||||
soilTemperature = soilTemperatureFahrenheit }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
@ -137,9 +157,7 @@ fun EnvironmentMetricsScreen(
|
|||
}
|
||||
|
||||
var displayInfoDialog by remember { mutableStateOf(false) }
|
||||
|
||||
Column {
|
||||
|
||||
if (displayInfoDialog) {
|
||||
LegendInfoDialog(
|
||||
pairedRes = listOf(
|
||||
|
|
@ -167,15 +185,11 @@ fun EnvironmentMetricsScreen(
|
|||
OptionLabel(stringResource(it.strRes))
|
||||
}
|
||||
|
||||
/* Environment Metric Cards */
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
items(processedTelemetries) { telemetry ->
|
||||
EnvironmentMetricsCard(
|
||||
telemetry,
|
||||
state.isFahrenheit
|
||||
)
|
||||
EnvironmentMetricsCard(telemetry, state.isFahrenheit)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -320,12 +334,13 @@ private fun EnvironmentMetricsChart(
|
|||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
Legend(LEGEND_DATA_1, displayInfoIcon = false)
|
||||
Legend(LEGEND_DATA_3, displayInfoIcon = false)
|
||||
Legend(LEGEND_DATA_2, promptInfoDialog = promptInfoDialog)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Suppress("LongMethod", "MagicNumber")
|
||||
@Composable
|
||||
private fun EnvironmentMetricsCard(telemetry: Telemetry, environmentDisplayFahrenheit: Boolean) {
|
||||
val envMetrics = telemetry.environmentMetrics
|
||||
|
|
@ -387,6 +402,38 @@ private fun EnvironmentMetricsCard(telemetry: Telemetry, environmentDisplayFahre
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
/* Soil Moisture and Soil Temperature */
|
||||
val soilMoistureRange = 0..100
|
||||
if (telemetry.environmentMetrics.hasSoilTemperature() ||
|
||||
telemetry.environmentMetrics.soilMoisture in soilMoistureRange) {
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
val soilTemperatureTextFormat =
|
||||
if (environmentDisplayFahrenheit) "%s %.1f°F" else "%s %.1f°C"
|
||||
val soilMoistureTextFormat = "%s %d%%"
|
||||
Text(
|
||||
text = soilMoistureTextFormat.format(
|
||||
stringResource(R.string.soil_moisture),
|
||||
envMetrics.soilMoisture
|
||||
),
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
fontSize = MaterialTheme.typography.labelLarge.fontSize
|
||||
)
|
||||
Text(
|
||||
text = soilTemperatureTextFormat.format(
|
||||
stringResource(R.string.soil_temperature),
|
||||
envMetrics.soilTemperature
|
||||
),
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
fontSize = MaterialTheme.typography.labelLarge.fontSize
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (telemetry.environmentMetrics.hasIaq()) {
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
/* Air Quality */
|
||||
|
|
|
|||
|
|
@ -762,6 +762,20 @@ private fun EnvironmentMetrics(
|
|||
value = dewPoint.toTempString(isFahrenheit)
|
||||
)
|
||||
}
|
||||
if (hasSoilTemperature()) {
|
||||
InfoCard(
|
||||
icon = ImageVector.vectorResource(R.drawable.soil_temperature),
|
||||
text = stringResource(R.string.soil_temperature),
|
||||
value = soilTemperature.toTempString(isFahrenheit)
|
||||
)
|
||||
}
|
||||
if (hasSoilMoisture()) {
|
||||
InfoCard(
|
||||
icon = ImageVector.vectorResource(R.drawable.soil_moisture),
|
||||
text = stringResource(R.string.soil_moisture),
|
||||
value = "%d%%".format(soilMoisture)
|
||||
)
|
||||
}
|
||||
if (hasBarometricPressure()) {
|
||||
InfoCard(
|
||||
icon = Icons.Default.Speed,
|
||||
|
|
|
|||
11
app/src/main/res/drawable/soil_moisture.xml
Normal file
11
app/src/main/res/drawable/soil_moisture.xml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="200dp" android:viewportHeight="32" android:viewportWidth="32" android:width="200dp">
|
||||
|
||||
<path android:fillColor="#000000" android:pathData="M24.5,30a5.202,5.202 0,0 1,-4.626 -8.08L23.49,16.538a1.217,1.217 0,0 1,2.02 0L29.06,21.815A5.492,5.492 0,0 1,30 24.751,5.385 5.385,0 0,1 24.5,30ZM24.5,18.62 L21.564,22.987A3.208,3.208 0,0 0,24.5 28,3.385 3.385,0 0,0 28,24.751a3.435,3.435 0,0 0,-0.63 -1.867Z"/>
|
||||
|
||||
<path android:fillColor="#000000" android:pathData="M11,16V11h1a4.004,4.004 0,0 0,4 -4V4H13a3.978,3.978 0,0 0,-2.747 1.107A6.003,6.003 0,0 0,5 2H2V5a6.007,6.007 0,0 0,6 6H9v5H2v2H16V16ZM13,6h1V7a2.002,2.002 0,0 1,-2 2H11V8A2.002,2.002 0,0 1,13 6ZM8,9A4.004,4.004 0,0 1,4 5V4H5A4.004,4.004 0,0 1,9 8V9Z"/>
|
||||
|
||||
<path android:fillColor="#000000" android:pathData="M2,21h14v2h-14z"/>
|
||||
|
||||
<path android:fillColor="#000000" android:pathData="M2,26h14v2h-14z"/>
|
||||
|
||||
</vector>
|
||||
11
app/src/main/res/drawable/soil_temperature.xml
Normal file
11
app/src/main/res/drawable/soil_temperature.xml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="200dp" android:viewportHeight="32" android:viewportWidth="32" android:width="200dp">
|
||||
|
||||
<path android:fillColor="#000000" android:pathData="M11,16V11h1a4.004,4.004 0,0 0,4 -4V4H13a3.978,3.978 0,0 0,-2.747 1.107A6.003,6.003 0,0 0,5 2H2V5a6.007,6.007 0,0 0,6 6H9v5H2v2H16V16ZM13,6h1V7a2.002,2.002 0,0 1,-2 2H11V8A2.002,2.002 0,0 1,13 6ZM8,9A4.004,4.004 0,0 1,4 5V4H5A4.004,4.004 0,0 1,9 8V9Z"/>
|
||||
|
||||
<path android:fillColor="#000000" android:pathData="M2,21h14v2h-14z"/>
|
||||
|
||||
<path android:fillColor="#000000" android:pathData="M2,26h14v2h-14z"/>
|
||||
|
||||
<path android:fillColor="#000000" android:pathData="M25,30a4.986,4.986 0,0 1,-3 -8.98L22,15a3,3 0,0 1,6 0v6.02A4.986,4.986 0,0 1,25 30ZM25,14a1.001,1.001 0,0 0,-1 1v7.13l-0.497,0.289A2.968,2.968 0,0 0,22 25a3,3 0,0 0,6 0,2.968 2.968,0 0,0 -1.503,-2.581L26,22.13L26,15A1.001,1.001 0,0 0,25 14Z"/>
|
||||
|
||||
</vector>
|
||||
|
|
@ -310,6 +310,8 @@
|
|||
<string name="air_utilization">Air Utilization</string>
|
||||
<string name="temperature">Temperature</string>
|
||||
<string name="humidity">Humidity</string>
|
||||
<string name="soil_temperature">Soil Temperature</string>
|
||||
<string name="soil_moisture">Soil Moisture</string>
|
||||
<string name="logs">Logs</string>
|
||||
<string name="hops_away">Hops Away</string>
|
||||
<string name="hops_away_template">Hops Away: %1$d</string>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue