Edge Agriculture Iot

Production-grade guide to edge agriculture iot covering architecture patterns, implementation strategies, testing approaches, and operational best practices for enterprise engineering teams.

Orientation Edge Agriculture IoT means running IoT data collection, filtering, and real-time control logic on compute resources physically co-located with the farm—edge gateways, ruggedized boxes, or field-deployable servers—rather than shipping every raw sensor reading to a central cloud. It matters when the field has intermittent or expensive connectivity, when irrigation or frost-protection decisions need sub-second response, and when bandwidth must be conserved by aggregating, filtering, or downsampling before anything leaves the farm. This page is about the concrete engineering of getting IoT workloads to run reliably at the edge of an agricultural operation, and the failure modes that only appear when the network drops, the sun hits a sensor wrong, or a control loop runs on stale data.

Deploying Sensor Nodes and Edge Gateways

Firmware & MQTT Bootstrap

Most field sensors run on ESP32 or STM32 platforms using the Eclipse Paho MQTT client. A minimal setup() that connects to a local broker and publishes soil‑moisture readings every 30 seconds looks like this:

#include <PubSubClient.h>
WiFiClient wifiClient;
PubSubClient client(wifiClient);

void setup() {
  WiFi.begin("farm-wifi", "wpa-pass");
  while (WiFi.status() != WL_CONNECTED) delay(500);

  client.setServer("edge-gateway.local", 1883);
  while (!client.connected()) {
    Serial.print("MQTT connect...");
    if (client.connect("soil-sensor-01")) Serial.println("ok");
    else delay(5000);
  }
}

void loop() {
  float m = read_moisture();
  char payload[32];
  snprintf(payload, sizeof(payload), "{\"moisture\":%.1f}", m);
  client.publish("agri/soil/moisture", payload);
  delay(30000);
}

On the gateway side, mosquitto is the default broker. Enable persistent session and offline buffering with:

mosquitto -c /etc/mosquitto/mosquitto.conf -d

and /etc/mosquitto/mosquitto.conf:

persistent_directory /var/lib/mosquitto/persistent
autosubscribe true
log_dest stdout

Systemd Service for Continuous Telemetry

Deploy the sensor firmware as a systemd service so it restarts after a power glitch:

[Unit]
Description=Edge Soil Moisture Sensor
After=network.target

[Service]
Type=simple
ExecStart=/usr/local/bin/soil-sensor
Restart=always
RestartSec=5
Environment=MQTT_BROKER=edge-gateway.local

[Install]
WantedBy=multi-user.target

Enable and start with systemctl enable --now soil-sensor.service. If the gateway loses WiFi, the service keeps running; when connectivity returns, the client auto‑reconnects and resumes publishing.

Filtering and Aggregating at the Edge

Telegraf Configuration for Field Data

Telegraf pulls MQTT topics and writes to local InfluxDB or a remote TSDB. A field‑specific config that discards saturated‑soil readings and only ships anomalies:

[[inputs.mqtt]]
  servers = ["tcp://edge-gateway.local:1883"]
  topic_pattern = "agri/soil/#"
  qos = 1
  data_format = "json"

[[inputs.mqtt.consumer]]
  topic = "agri/soil/moisture"
  # keep only readings below 40% (dry) or above 80% (flooded)
  [[inputs.mqtt.consumer.filter]]
    drop_rule = [
      {"if" = "{{ .moisture }} > 80.0", "then" = "discard"},
      {"if" = "{{ .moisture }} < 40.0", "then" = "discard"}
    ]

Run a dry‑run to verify parsing:

telegraf --config telegraf.conf --test

Discard and Retain Rules That Silence Noise

A common pitfall: discard_if evaluates the payload at ingestion time. If the JSON key is soil_moisture but the config references .moisture, the rule silently fails and every reading passes through. Verify with:

telegraf --config telegraf.conf --input-filter "agri/soil/moisture" --test

and inspect the output JSON to confirm the field name matches.

Real-Time Control Loops

Node-RED Flow for Irrigation Interlock

Node-RED on the edge gateway reacts to MQTT messages and sends commands to valve controllers over HTTP. A minimal flow:

  1. MQTT in → topic agri/irrigation/zone-A, payload {"open":true}
  2. Function node (JavaScript) checks a local cache file /data/last-rain-mm.json:
   const rain = JSON.parse(global.get("last-rain-mm")).total;
   if (rain > 2.0) { flow.stop(); return; }
   msg.payload = { zone: "A", open: true, ts: Date.now() };
   return msg;
  1. HTTP request node → POST http://valve-controller.local/state with body {"zone":"A","open":true}.

API Curl Commands for Valve Actuation

When testing or scripting from the command line:

curl -s -X POST http://valve-controller.local/state \
  -H "Content-Type: application/json" \
  -d '{"zone":"A","open":true}'

Expected 200 OK with {"status":"opened"}. A 409 Conflict means another edge node already holds the zone open—this happens when two gateways serve the same field without a distributed lock.

Clock Sync Across Nodes

Irrigation timing depends on synchronized clocks. Install chrony on every edge node:

apt-get update && apt-get install -y chrony

Edit /etc/chrony/chrony.conf:

server pool.ntp.org iburst
local stratum 10
allow all

Restart with systemctl restart chrony. Without this, a 200 ms drift across three nodes can cause overlapping valve opens, over‑watering a strip of crop.

Connectivity, Failover, and Network Edge Cases

wpa_supplicant Dual-Profile Setup

Farms often have a primary WiFi and a cellular backup. Create two /etc/wpa_supplicant/wpa_supplicant.conf profiles:

wifi-home.conf:

network={
  ssid "farm-wifi"
  psk "home-pass"
  priority 10
}

cellular-lte.conf:

network={
  ssid "LTE-APN"
  key_mgmt NONE
  identity "[email protected]"
  password "carrier-pass"
}

Switch profiles dynamically:

nmcli connection up wifi-home
nmcli connection down wifi-home
nmcli connection up cellular-lte

Policy-Based Routing for LTE/Satellite Switch

When the satellite link is more expensive but more reliable during storms, use ip route priorities:

# Default WiFi route
ip route add default via 192.168.1.1 dev wlan0 metric 100

# Satellite override when wlan0 drops
ip route add default via 10.0.0.1 dev wwan0 metric 200 pref 2

Monitor link status with:

watch -n 5 'ip route get 8.8.8.8 | head -n 1'

DNS Mismatch on Failover

When switching from LTE to satellite backhaul, internal MQTT hostnames may resolve to stale IPs. Fix with resolvconf:

apt-get install -y resolvconf
echo "nameserver 169.254.169.254" > /etc/resolvconf/run/interface/wwan0
resolvconf -u

Without this, mosquitto_sub -h edge-gateway.local returns Name or service not known after a link swap.

Sharp Edges: What Silently Fails

TLS Certificate Expiry on Constrained Nodes

Edge sensors often use self‑signed TLS to encrypt MQTT (`mosquitto_sub -h edge-gateway.local -t "agri/#" -q 1 --

This page was rewritten on 10 October 2026. It replaced a templated version whose text was largely shared with other pages in this section and was not specific to its own title. The new text was drafted with a locally run language model, checked by a separate reviewer model for specificity and for invented figures, and measured against its sibling pages for duplication before publication. If anything here is wrong, tell us at [email protected] and we will correct it.