Implementing Soil Sensor Monitoring via Mobile Applications
A soil sensor is typically Modbus RTU or SDI-12 on wired interface, or LoRaWAN/NB-IoT wireless. Popular models: Sentek Drill & Drop (SDI-12), Vegetronix VH400 (analog 0-3V), TEROS 12 (SDI-12), Decagon 5TM — all with different output formats. Mobile applications receive this data through IoT gateway or LoRaWAN Network Server — never directly.
What the Sensor Measures and How to Interpret It
Three primary soil parameters:
VWC (Volumetric Water Content) — soil moisture percentage. Range 0-100%; in practice for most soils, working range is 10-40%. Sensor measures soil dielectric permittivity, conversion to VWC uses Topp formula or manufacturer calibration data for specific soil type.
EC (Electrical Conductivity) — conductivity in mS/cm. Indicates salinity and nutrient concentration. Normal for most crops: 0.5-2.0 mS/cm. Above 4 mS/cm — plant stress.
Soil Temperature — critical for seed germination (most crops won't germinate below 8-10°C) and microbial activity.
Applications should show not raw values but agronomic interpretation. "Moisture 19% at FC=45% for sandy loam" — this is dry. Without soil type and field capacity (FC) context, the number is meaningless.
Data Retrieval: LoRaWAN via ChirpStack
ChirpStack is open-source LoRaWAN Network and Application Server. REST API and gRPC interface:
// Kotlin, Retrofit for ChirpStack API
interface ChirpStackApi {
@GET("api/devices/{devEui}/events")
suspend fun getDeviceEvents(
@Header("Grpc-Metadata-Authorization") token: String,
@Path("devEui") devEui: String,
@Query("limit") limit: Int = 100,
): DeviceEventsResponse
}
data class DeviceEvent(
val publishedAt: String,
val data: String, // Base64-encoded payload
val rxInfo: List<RxInfo>,
)
fun decodePayload(base64Data: String): SoilReading {
val bytes = Base64.decode(base64Data, Base64.DEFAULT)
// Decoding depends on sensor manufacturer encoding
// TEROS 12 Cayenne LPP format:
val vwc = ((bytes[1].toInt() and 0xFF) shl 8 or (bytes[2].toInt() and 0xFF)) / 100.0
val temp = ((bytes[4].toInt() and 0xFF) shl 8 or (bytes[5].toInt() and 0xFF)) / 100.0 - 40
val ec = ((bytes[7].toInt() and 0xFF) shl 8 or (bytes[8].toInt() and 0xFF)) / 100.0
return SoilReading(vwc = vwc, temperature = temp, electricalConductivity = ec)
}
For real-time via MQTT — ChirpStack publishes events to topics like application/{appId}/device/{devEui}/event/up.
Dashboard: Multiple Sensors Per Field
Standard setup — 3-5 sensors at depth horizons (10, 30, 60, 90 cm) at one measurement point. Dashboard shows moisture profile by depth — vertical bar chart is more efficient than a list:
Widget buildMoistureProfile(List<SoilLayerReading> layers) {
return Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
// Depth axis
Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: layers.map((l) => Text('${l.depthCm} cm')).toList(),
),
const SizedBox(width: 8),
Expanded(
child: Column(
children: layers.map((layer) {
final isLow = layer.vwc < layer.fieldCapacity * 0.5;
return Container(
margin: const EdgeInsets.symmetric(vertical: 2),
height: 32,
child: LinearProgressIndicator(
value: layer.vwc / 60.0, // normalize to 60% max
backgroundColor: Colors.grey.shade200,
color: isLow ? Colors.orange : Colors.blue,
),
);
}).toList(),
),
),
],
),
);
}
Trends and Irrigation Threshold
Main analytical function — show when moisture dropped to irrigation threshold and when returned to target level after watering. This helps agronomist confirm irrigation system worked correctly.
Graph from fl_chart with horizontal threshold line:
LineChartData buildTrendChart(List<SoilReading> readings, double threshold) {
return LineChartData(
extraLinesData: ExtraLinesData(
horizontalLines: [
HorizontalLine(
y: threshold,
color: Colors.orange,
strokeWidth: 1.5,
dashArray: [5, 5],
label: HorizontalLineLabel(
show: true,
labelResolver: (_) => 'Irrigation Threshold',
),
),
],
),
lineBarsData: [
LineChartBarData(
spots: readings
.map((r) => FlSpot(r.timestamp.toDouble(), r.vwc))
.toList(),
isCurved: true,
color: Colors.blue,
dotData: const FlDotData(show: false),
),
],
);
}
Alerts
Two alert types for soil sensors: by moisture threshold (below X% — needs irrigation) and by EC (above Y mS/cm — salinity risk). Delivery via FCM. Important: filter moisture alerts by time of day and weekday — if it just rained, "needs irrigation" alert is redundant. Backend should consider weather data or forecast.
Developing soil sensor monitoring application with LoRaWAN integration, moisture profiles and alerts: 3-5 weeks. Cost calculated individually.







