Skip to content

Commit fbd62cb

Browse files
Add soil temperature and soil moisture environmental metrics to app (#2419)
Co-authored-by: DaneEvans <dane@goneepic.com>
1 parent 8a0ad26 commit fbd62cb

File tree

10 files changed

+158
-14
lines changed

10 files changed

+158
-14
lines changed

app/src/main/java/com/geeksville/mesh/NodeInfo.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,8 @@ data class EnvironmentMetrics(
160160
val time: Int = currentTime(), // default to current time in secs (NOT MILLISECONDS!)
161161
val temperature: Float,
162162
val relativeHumidity: Float,
163+
val soilTemperature: Float,
164+
val soilMoisture: Int,
163165
val barometricPressure: Float,
164166
val gasResistance: Float,
165167
val voltage: Float,

app/src/main/java/com/geeksville/mesh/database/entity/NodeEntity.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,8 @@ data class NodeEntity(
215215
time = environmentTelemetry.time,
216216
temperature = environmentMetrics.temperature,
217217
relativeHumidity = environmentMetrics.relativeHumidity,
218+
soilTemperature = environmentMetrics.soilTemperature,
219+
soilMoisture = environmentMetrics.soilMoisture,
218220
barometricPressure = environmentMetrics.barometricPressure,
219221
gasResistance = environmentMetrics.gasResistance,
220222
voltage = environmentMetrics.voltage,

app/src/main/java/com/geeksville/mesh/model/EnvironmentMetricsState.kt

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import androidx.compose.ui.graphics.Color
2121
import com.geeksville.mesh.TelemetryProtos.Telemetry
2222
import com.geeksville.mesh.ui.common.theme.InfantryBlue
2323
import com.geeksville.mesh.ui.common.theme.Orange
24+
import com.geeksville.mesh.ui.common.theme.Pink
25+
import com.geeksville.mesh.ui.common.theme.Purple
2426
import com.geeksville.mesh.util.UnitConversions
2527

2628
enum class Environment(val color: Color) {
@@ -34,6 +36,16 @@ enum class Environment(val color: Color) {
3436
return telemetry.environmentMetrics.relativeHumidity
3537
}
3638
},
39+
SOIL_TEMPERATURE(Pink) {
40+
override fun getValue(telemetry: Telemetry): Float {
41+
return telemetry.environmentMetrics.soilTemperature
42+
}
43+
},
44+
SOIL_MOISTURE(Purple) {
45+
override fun getValue(telemetry: Telemetry): Float {
46+
return telemetry.environmentMetrics.soilMoisture.toFloat()
47+
}
48+
},
3749
IAQ(Color.Green) {
3850
override fun getValue(telemetry: Telemetry): Float {
3951
return telemetry.environmentMetrics.iaq.toFloat()
@@ -75,7 +87,7 @@ data class EnvironmentMetricsState(
7587
* @param timeFrame used to filter
7688
* @return [EnvironmentGraphingData]
7789
*/
78-
@Suppress("LongMethod")
90+
@Suppress("LongMethod", "CyclomaticComplexMethod", "MagicNumber")
7991
fun environmentMetricsFiltered(timeFrame: TimeFrame, useFahrenheit: Boolean = false): EnvironmentGraphingData {
8092
val oldestTime = timeFrame.calculateOldestTime()
8193
val telemetries = environmentMetrics.filter { it.time >= oldestTime }
@@ -84,7 +96,7 @@ data class EnvironmentMetricsState(
8496
return EnvironmentGraphingData(metrics = telemetries, shouldPlot = shouldPlot.toList())
8597
}
8698

87-
/* Grab the combined min and max for temp, humidity, and iaq. */
99+
/* Grab the combined min and max for temp, humidity, soil_Temperature, soilMoisture and iaq. */
88100
val minValues = mutableListOf<Float>()
89101
val maxValues = mutableListOf<Float>()
90102
val (minTemp, maxTemp) = Pair(
@@ -114,6 +126,31 @@ data class EnvironmentMetricsState(
114126
shouldPlot[Environment.HUMIDITY.ordinal] = true
115127
}
116128

129+
var minSoilTemperatureValue = minTemp.environmentMetrics.soilTemperature
130+
var maxSoilTemperatureValue = maxTemp.environmentMetrics.soilTemperature
131+
if (useFahrenheit) {
132+
minSoilTemperatureValue = UnitConversions.celsiusToFahrenheit(minSoilTemperatureValue)
133+
maxSoilTemperatureValue = UnitConversions.celsiusToFahrenheit(maxSoilTemperatureValue)
134+
}
135+
if (minTemp.environmentMetrics.soilTemperature != 0f ||
136+
maxTemp.environmentMetrics.soilTemperature != 0f) {
137+
minValues.add(minSoilTemperatureValue)
138+
maxValues.add(maxSoilTemperatureValue)
139+
shouldPlot[Environment.SOIL_TEMPERATURE.ordinal] = true
140+
}
141+
142+
val (minSoilMoisture, maxSoilMoisture) = Pair(
143+
telemetries.minBy { it.environmentMetrics.soilMoisture },
144+
telemetries.maxBy { it.environmentMetrics.soilMoisture }
145+
)
146+
val soilMoistureRange = 0..100
147+
if (minSoilMoisture.environmentMetrics.soilMoisture in soilMoistureRange ||
148+
maxSoilMoisture.environmentMetrics.soilMoisture in soilMoistureRange) {
149+
minValues.add(minSoilMoisture.environmentMetrics.soilMoisture.toFloat())
150+
maxValues.add(maxSoilMoisture.environmentMetrics.soilMoisture.toFloat())
151+
shouldPlot[Environment.SOIL_MOISTURE.ordinal] = true
152+
}
153+
117154
val (minIAQ, maxIAQ) = Pair(
118155
telemetries.minBy { it.environmentMetrics.iaq },
119156
telemetries.maxBy { it.environmentMetrics.iaq }

app/src/main/java/com/geeksville/mesh/model/Node.kt

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import com.geeksville.mesh.TelemetryProtos.EnvironmentMetrics
2727
import com.geeksville.mesh.TelemetryProtos.PowerMetrics
2828
import com.geeksville.mesh.database.entity.NodeEntity
2929
import com.geeksville.mesh.util.GPSFormat
30+
import com.geeksville.mesh.util.UnitConversions.celsiusToFahrenheit
3031
import com.geeksville.mesh.util.latLongToMeter
3132
import com.geeksville.mesh.util.toDistanceString
3233
import com.google.protobuf.ByteString
@@ -113,22 +114,37 @@ data class Node(
113114
private fun EnvironmentMetrics.getDisplayString(isFahrenheit: Boolean): String {
114115
val temp = if (temperature != 0f) {
115116
if (isFahrenheit) {
116-
val fahrenheit = temperature * 1.8F + 32
117-
"%.1f°F".format(fahrenheit)
117+
"%.1f°F".format(celsiusToFahrenheit(temperature))
118118
} else {
119119
"%.1f°C".format(temperature)
120120
}
121121
} else {
122122
null
123123
}
124124
val humidity = if (relativeHumidity != 0f) "%.0f%%".format(relativeHumidity) else null
125+
val soilTemperatureStr = if (soilTemperature != 0f) {
126+
if (isFahrenheit) {
127+
"%.1f°F".format(celsiusToFahrenheit(temperature))
128+
} else {
129+
"%.1f°C".format(soilTemperature)
130+
}
131+
} else {
132+
null
133+
}
134+
val soilMoistureRange = 0..100
135+
val soilMoisture =
136+
if (soilMoisture in soilMoistureRange && soilTemperature != 0f) {
137+
"%d%%".format(soilMoisture)
138+
} else { null }
125139
val voltage = if (this.voltage != 0f) "%.2fV".format(this.voltage) else null
126140
val current = if (current != 0f) "%.1fmA".format(current) else null
127141
val iaq = if (iaq != 0) "IAQ: $iaq" else null
128142

129143
return listOfNotNull(
130144
temp,
131145
humidity,
146+
soilTemperatureStr,
147+
soilMoisture,
132148
voltage,
133149
current,
134150
iaq,

app/src/main/java/com/geeksville/mesh/ui/common/theme/Color.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ val Green = Color(0xFF30C047)
2828

2929
val HyperlinkBlue = Color(0xFF43C3B0)
3030
val InfantryBlue = Color(red = 75, green = 119, blue = 190)
31+
val Purple = Color(0xFF9C27B0)
32+
val Pink = Color(red = 255, green = 102, blue = 204)
3133

3234
val primaryLight = Color(0xFF306A42)
3335
val onPrimaryLight = Color(0xFFFFFFFF)

app/src/main/java/com/geeksville/mesh/ui/metrics/EnvironmentMetrics.kt

Lines changed: 57 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ import com.geeksville.mesh.ui.common.components.IaqDisplayMode
6868
import com.geeksville.mesh.ui.common.components.IndoorAirQuality
6969
import com.geeksville.mesh.ui.common.components.OptionLabel
7070
import com.geeksville.mesh.ui.common.components.SlidingSelector
71+
import com.geeksville.mesh.ui.common.theme.Pink
72+
import com.geeksville.mesh.ui.common.theme.Purple
7173
import com.geeksville.mesh.ui.metrics.CommonCharts.DATE_TIME_FORMAT
7274
import com.geeksville.mesh.ui.metrics.CommonCharts.MS_PER_SEC
7375
import com.geeksville.mesh.util.GraphUtil.createPath
@@ -78,6 +80,8 @@ import com.geeksville.mesh.util.UnitConversions.celsiusToFahrenheit
7880
private enum class Environment(val color: Color) {
7981
TEMPERATURE(Color.Red),
8082
RELATIVE_HUMIDITY(Color.Blue),
83+
SOIL_TEMPERATURE(Pink),
84+
SOIL_MOISTURE(Purple),
8185
BAROMETRIC_PRESSURE(Color.Green),
8286
GAS_RESISTANCE(Color.Yellow),
8387
IAQ(Color.Magenta)
@@ -112,6 +116,18 @@ private val LEGEND_DATA_2 = listOf(
112116
isLine = true
113117
)
114118
)
119+
private val LEGEND_DATA_3 = listOf(
120+
LegendData(
121+
nameRes = R.string.soil_temperature,
122+
color = Environment.SOIL_TEMPERATURE.color,
123+
isLine = true
124+
),
125+
LegendData(
126+
nameRes = R.string.soil_moisture,
127+
color = Environment.SOIL_MOISTURE.color,
128+
isLine = true
129+
),
130+
)
115131

116132
@Composable
117133
fun EnvironmentMetricsScreen(
@@ -127,19 +143,21 @@ fun EnvironmentMetricsScreen(
127143
data.map { telemetry ->
128144
val temperatureFahrenheit =
129145
celsiusToFahrenheit(telemetry.environmentMetrics.temperature)
146+
val soilTemperatureFahrenheit =
147+
celsiusToFahrenheit(telemetry.environmentMetrics.soilTemperature)
130148
telemetry.copy {
131-
environmentMetrics =
132-
telemetry.environmentMetrics.copy { temperature = temperatureFahrenheit }
149+
environmentMetrics = telemetry.environmentMetrics.copy {
150+
temperature = temperatureFahrenheit }
151+
environmentMetrics = telemetry.environmentMetrics.copy {
152+
soilTemperature = soilTemperatureFahrenheit }
133153
}
134154
}
135155
} else {
136156
data
137157
}
138158

139159
var displayInfoDialog by remember { mutableStateOf(false) }
140-
141160
Column {
142-
143161
if (displayInfoDialog) {
144162
LegendInfoDialog(
145163
pairedRes = listOf(
@@ -167,15 +185,11 @@ fun EnvironmentMetricsScreen(
167185
OptionLabel(stringResource(it.strRes))
168186
}
169187

170-
/* Environment Metric Cards */
171188
LazyColumn(
172189
modifier = Modifier.fillMaxSize()
173190
) {
174191
items(processedTelemetries) { telemetry ->
175-
EnvironmentMetricsCard(
176-
telemetry,
177-
state.isFahrenheit
178-
)
192+
EnvironmentMetricsCard(telemetry, state.isFahrenheit)
179193
}
180194
}
181195
}
@@ -320,12 +334,13 @@ private fun EnvironmentMetricsChart(
320334
Spacer(modifier = Modifier.height(16.dp))
321335

322336
Legend(LEGEND_DATA_1, displayInfoIcon = false)
337+
Legend(LEGEND_DATA_3, displayInfoIcon = false)
323338
Legend(LEGEND_DATA_2, promptInfoDialog = promptInfoDialog)
324339

325340
Spacer(modifier = Modifier.height(16.dp))
326341
}
327342

328-
@Suppress("LongMethod")
343+
@Suppress("LongMethod", "MagicNumber")
329344
@Composable
330345
private fun EnvironmentMetricsCard(telemetry: Telemetry, environmentDisplayFahrenheit: Boolean) {
331346
val envMetrics = telemetry.environmentMetrics
@@ -387,6 +402,38 @@ private fun EnvironmentMetricsCard(telemetry: Telemetry, environmentDisplayFahre
387402
)
388403
}
389404
}
405+
406+
/* Soil Moisture and Soil Temperature */
407+
val soilMoistureRange = 0..100
408+
if (telemetry.environmentMetrics.hasSoilTemperature() ||
409+
telemetry.environmentMetrics.soilMoisture in soilMoistureRange) {
410+
Spacer(modifier = Modifier.height(4.dp))
411+
Row(
412+
modifier = Modifier.fillMaxWidth(),
413+
horizontalArrangement = Arrangement.SpaceBetween
414+
) {
415+
val soilTemperatureTextFormat =
416+
if (environmentDisplayFahrenheit) "%s %.1f°F" else "%s %.1f°C"
417+
val soilMoistureTextFormat = "%s %d%%"
418+
Text(
419+
text = soilMoistureTextFormat.format(
420+
stringResource(R.string.soil_moisture),
421+
envMetrics.soilMoisture
422+
),
423+
color = MaterialTheme.colorScheme.onSurface,
424+
fontSize = MaterialTheme.typography.labelLarge.fontSize
425+
)
426+
Text(
427+
text = soilTemperatureTextFormat.format(
428+
stringResource(R.string.soil_temperature),
429+
envMetrics.soilTemperature
430+
),
431+
color = MaterialTheme.colorScheme.onSurface,
432+
fontSize = MaterialTheme.typography.labelLarge.fontSize
433+
)
434+
}
435+
}
436+
390437
if (telemetry.environmentMetrics.hasIaq()) {
391438
Spacer(modifier = Modifier.height(4.dp))
392439
/* Air Quality */

app/src/main/java/com/geeksville/mesh/ui/node/NodeDetail.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -762,6 +762,20 @@ private fun EnvironmentMetrics(
762762
value = dewPoint.toTempString(isFahrenheit)
763763
)
764764
}
765+
if (hasSoilTemperature()) {
766+
InfoCard(
767+
icon = ImageVector.vectorResource(R.drawable.soil_temperature),
768+
text = stringResource(R.string.soil_temperature),
769+
value = soilTemperature.toTempString(isFahrenheit)
770+
)
771+
}
772+
if (hasSoilMoisture()) {
773+
InfoCard(
774+
icon = ImageVector.vectorResource(R.drawable.soil_moisture),
775+
text = stringResource(R.string.soil_moisture),
776+
value = "%d%%".format(soilMoisture)
777+
)
778+
}
765779
if (hasBarometricPressure()) {
766780
InfoCard(
767781
icon = Icons.Default.Speed,
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="200dp" android:viewportHeight="32" android:viewportWidth="32" android:width="200dp">
2+
3+
<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"/>
4+
5+
<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"/>
6+
7+
<path android:fillColor="#000000" android:pathData="M2,21h14v2h-14z"/>
8+
9+
<path android:fillColor="#000000" android:pathData="M2,26h14v2h-14z"/>
10+
11+
</vector>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="200dp" android:viewportHeight="32" android:viewportWidth="32" android:width="200dp">
2+
3+
<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"/>
4+
5+
<path android:fillColor="#000000" android:pathData="M2,21h14v2h-14z"/>
6+
7+
<path android:fillColor="#000000" android:pathData="M2,26h14v2h-14z"/>
8+
9+
<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"/>
10+
11+
</vector>

app/src/main/res/values/strings.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,8 @@
310310
<string name="air_utilization">Air Utilization</string>
311311
<string name="temperature">Temperature</string>
312312
<string name="humidity">Humidity</string>
313+
<string name="soil_temperature">Soil Temperature</string>
314+
<string name="soil_moisture">Soil Moisture</string>
313315
<string name="logs">Logs</string>
314316
<string name="hops_away">Hops Away</string>
315317
<string name="hops_away_template">Hops Away: %1$d</string>

0 commit comments

Comments
 (0)