Contents — 26 sections
Project Overview
A soil node that reads nitrogen, phosphorus and potassium over industrial Modbus, alongside pH, moisture and temperature — with a frank account of what these low-cost NPK probes actually measure.
Fertiliser is one of the largest costs and largest environmental impacts in agriculture, and most of it is applied by guesswork. Knowing the nutrient status of the soil lets you apply what the crop actually needs, where it needs it — the principle of precision agriculture. This node reads soil nitrogen, phosphorus and potassium, along with pH, moisture, temperature and conductivity, and reports them for site-specific nutrient management.
It is essential to be honest about what the low-cost NPK sensors on the market measure. A laboratory soil test extracts nutrients chemically and measures them precisely. These field probes are capacitive/conductivity sensors with a calibration model that estimates NPK from the soil's electrical properties. They are useful for detecting relative nutrient status and changes over time and space, but their absolute accuracy is limited and soil-dependent. Treated as a relative, comparative instrument they are genuinely useful; treated as a lab test they will mislead. This documentation makes that distinction central.
The sensor communicates over RS-485 Modbus-RTU, the industrial standard for rugged field instruments. This is a deliberately different communication layer from the consumer I²C and analogue sensors elsewhere in the catalogue, and learning it is valuable: RS-485 runs reliably over hundreds of metres of cheap twisted pair, tolerates electrical noise, and lets many sensors share one bus — exactly what a field of soil nodes needs.
The node is built for the field: rugged, low-power for solar operation, and reporting over a long-range link (LoRa) because farmland rarely has Wi-Fi. It maps nutrient status across a field so fertiliser can be varied by zone rather than applied uniformly — which is where the cost and environmental savings come from.
What this project does
- Reads soil nitrogen, phosphorus, potassium, pH, moisture, temperature and conductivity over RS-485 Modbus.
- Reports readings honestly as relative/comparative values, not laboratory measurements.
- Communicates over LoRa for long range without field Wi-Fi.
- Runs on solar power with deep-sleep duty cycling between readings.
- Maps nutrient variation across a field when multiple nodes are deployed.
- Flags large deviations that warrant a confirmatory laboratory test.
- Logs readings over the season for trend analysis.
Real-World Applications
| Setting | How it is used |
|---|---|
| Precision fertiliser management | Applying nutrients by zone based on relative status, cutting cost and runoff. |
| Field nutrient mapping | Building a spatial picture of variation across a field over a season. |
| Soil health monitoring | Tracking how nutrient status and pH change under a management practice. |
| Fertigation control | Feeding nutrient status into an automated fertigation system. |
| Research and demonstration plots | Comparing treatments with dense, continuous sensing between lab tests. |
| Learning industrial protocols | RS-485 and Modbus are the backbone of industrial and agricultural instrumentation. |
Deployment contexts where a build of this kind earns its keep.
Features & Capabilities
- RS-485 Modbus-RTU — the industrial standard, rugged over long cable runs and shared buses.
- Seven soil parameters from one probe: N, P, K, pH, moisture, temperature, EC.
- Honest framing — relative and comparative use, with lab confirmation for absolute decisions.
- LoRa long-range reporting for fields without connectivity.
- Solar-powered, deep-sleep operation for unattended seasonal deployment.
- Multi-node field mapping to guide variable-rate application.
- Temperature and moisture compensation, since both strongly affect the readings.
- Deviation flagging that prompts a lab test when a reading is surprising.
Difficulty, Time & Required Skills
| Attribute | Value |
|---|---|
| Difficulty level | Intermediate |
| Estimated completion time | 10–16 hours |
| Indicative build cost | ₹5,400 – ₹6,800 |
| Primary discipline | Agriculture |
| Reference platform | ESP32 DevKit V1 (ESP-WROOM-32) |
Skills you should have (or will pick up)
- Arduino C++ with Modbus-RTU
- RS-485 wiring and bus termination
- LoRa communication
- Solar power and deep-sleep design
- A clear understanding of what field NPK probes actually measure
Bill of Materials
Every part below is commonly available from Indian and international hobby-electronics suppliers. Prices are indicative 2026 retail figures in Indian rupees and will drift — treat them as a budgeting guide, not a quotation.
| Component | Key specification | Qty | Approx. cost |
|---|---|---|---|
| ESP32 DevKit V1 (ESP-WROOM-32) Wi-Fi transmit bursts peak near 500 mA — size the regulator accordingly. | Dual-core Xtensa LX6 @ 240 MHz, 520 KB SRAM, 4 MB flash, Wi-Fi 802.11 b/g/n + BLE 4.2, 34 GPIO, 18× 12-bit ADC, 2× 8-bit DAC | 1 | ₹450 |
| MAX485 RS-485 transceiver module Terminate both ends with 120 Ω and use twisted pair for long runs. | Half-duplex differential bus, up to 1200 m, 2.5 Mbps, 32 nodes | 1 | ₹60 |
| SX1278 LoRa 433 MHz module (Ra-02) Never power the radio without an antenna — the PA will destroy itself. | −148 dBm sensitivity, +20 dBm output, up to 10 km line of sight, SF7–SF12 | 1 | ₹480 |
| 20 W 12 V polycrystalline solar panel Rated watts assume 1000 W/m² — plan for 60–70 % of nameplate in real installs. | Vmp 17.5 V, Imp 1.14 A, Voc 21.6 V, 350 × 290 mm, aluminium frame | 1 | ₹1,200 |
| CN3791 MPPT solar charge controller Set the MPPT point to ~80 % of panel Voc for polycrystalline modules. | 4.5–28 V in, MPPT set by resistor divider, 2 A charge to a 1S Li-ion pack | 1 | ₹320 |
| 18650 Li-ion cell 3400 mAh + holder Never charge below 0 °C; always use a protected cell or a BMS. | 3.7 V nominal, 4.2 V full, 3400 mAh, ~12.6 Wh, 2 C discharge | 1 | ₹450 |
| Double-sided perfboard 7 × 9 cm + headers Solder female headers so the MCU can be swapped without desoldering. | FR-4, 0.1″ pitch, plated through-holes, 24 × 18 grid | 1 | ₹60 |
| IP65 ABS junction enclosure 158 × 90 × 60 mm Fit cable glands, not drilled holes, or the IP rating means nothing. | IP65, ABS, −20 to +80 °C, transparent lid, wall-mount lugs | 1 | ₹260 |
| 7-in-1 soil NPK/pH/EC/moisture/temp sensor These are conductivity-model sensors — relative accuracy, not laboratory precision. Buy accordingly. | RS-485 Modbus, stainless probe | 1 | ₹3,200 |
| 12 V supply for the sensor Boost from the battery or a separate solar rail. | Most industrial soil probes need 5–24 V, often 12 V | 1 | ₹200 |
| 120 Ω termination resistors | For the RS-485 bus ends | 2 | ₹40 |
| IP67 enclosure + cable glands | For unattended field deployment | 1 | ₹320 |
Estimated total: ₹7,040, excluding tools, shipping and consumables.
Tools and consumables
- Soldering iron (temperature controlled, 350 °C) with 0.8 mm 60/40 or lead-free solder
- Digital multimeter — continuity, DC volts and current ranges
- Wire strippers, flush cutters and a small set of precision screwdrivers
- Heat-shrink tubing and a heat gun (or a lighter, carefully)
- A laptop with a USB port and the toolchain listed above
Hardware Specifications
| Part | Specification | Supply | Interface | Reference |
|---|---|---|---|---|
| ESP32 DevKit V1 (ESP-WROOM-32) | Dual-core Xtensa LX6 @ 240 MHz, 520 KB SRAM, 4 MB flash, Wi-Fi 802.11 b/g/n + BLE 4.2, 34 GPIO, 18× 12-bit ADC, 2× 8-bit DAC | 3.3 V logic / 5 V USB | UART, SPI, I²C, I²S, CAN, PWM | Datasheet |
| MAX485 RS-485 transceiver module | Half-duplex differential bus, up to 1200 m, 2.5 Mbps, 32 nodes | 5 V | UART + DE/RE control | Datasheet |
| SX1278 LoRa 433 MHz module (Ra-02) | −148 dBm sensitivity, +20 dBm output, up to 10 km line of sight, SF7–SF12 | 3.3 V | SPI | Datasheet |
| 20 W 12 V polycrystalline solar panel | Vmp 17.5 V, Imp 1.14 A, Voc 21.6 V, 350 × 290 mm, aluminium frame | 12 V nominal | MC4 / screw terminals | Datasheet |
| CN3791 MPPT solar charge controller | 4.5–28 V in, MPPT set by resistor divider, 2 A charge to a 1S Li-ion pack | 4.5–28 V | Solder pads | Datasheet |
| 18650 Li-ion cell 3400 mAh + holder | 3.7 V nominal, 4.2 V full, 3400 mAh, ~12.6 Wh, 2 C discharge | 3.0–4.2 V | Holder / spot-welded tabs | Datasheet |
| Double-sided perfboard 7 × 9 cm + headers | FR-4, 0.1″ pitch, plated through-holes, 24 × 18 grid | — | — | Datasheet |
| IP65 ABS junction enclosure 158 × 90 × 60 mm | IP65, ABS, −20 to +80 °C, transparent lid, wall-mount lugs | — | — | Datasheet |
Consolidated electrical and interface specifications for every active part in the build.
Power Budget & Supply Sizing
Add up the typical active current of every part, then size the supply with at least 50 % headroom so transmit bursts and motor inrush never brown out the controller.
| Load | Supply rail | Typical current (mA) | Notes |
|---|---|---|---|
| ESP32 DevKit V1 (ESP-WROOM-32) | 3.3 V logic / 5 V USB | 160 | Wi-Fi transmit bursts peak near 500 mA — size the regulator accordingly. |
| MAX485 RS-485 transceiver module | 5 V | 5 | Terminate both ends with 120 Ω and use twisted pair for long runs. |
| SX1278 LoRa 433 MHz module (Ra-02) | 3.3 V | 120 | Never power the radio without an antenna — the PA will destroy itself. |
| 20 W 12 V polycrystalline solar panel | 12 V nominal | 1140 | Rated watts assume 1000 W/m² — plan for 60–70 % of nameplate in real installs. |
| CN3791 MPPT solar charge controller | 4.5–28 V | 2000 | Set the MPPT point to ~80 % of panel Voc for polycrystalline modules. |
Summed typical draw is 3425 mA. With a 1.5× design margin the supply should deliver at least 5200 mA continuously at the stated rail voltage.
Software Requirements & Development Environment
Reference toolchain: Arduino IDE 2.3.x with the ESP32 board package 3.x (or PlatformIO on VS Code). Anything newer normally works; anything older may lack the board definitions used here.
- Install the Arduino IDE 2.3.x (or PlatformIO if you prefer a real editor and dependency locking).
- Add
https://espressif.github.io/arduino-esp32/package_esp32_index.jsonunder File → Preferences → Additional Board Manager URLs, then install esp32 from the Boards Manager. - Set the correct port under Tools → Port. On Linux add yourself to the
dialoutgroup:sudo usermod -aG dialout $USERand log out and back in. - Open the Serial Monitor at 115200 baud — every sketch here logs its state there.
- Keep File → Preferences → Show verbose output during: compilation switched on while you are debugging build errors.
Required libraries
| Library | Why it is needed | Install |
|---|---|---|
| ModbusMaster 2.0.1 | Modbus-RTU master framing for RS-485 meters and drives. | Library Manager → "ModbusMaster" by Doc Walker |
| LoRa (sandeepmistry) 0.8.0 | SX127x radio configuration, packet TX/RX and callbacks. | Library Manager → "LoRa" by Sandeep Mistry |
| ArduinoJson 7.x | Zero-allocation JSON serialisation and parsing. | Library Manager → "ArduinoJson" by Benoit Blanchon |
| Preferences (NVS) bundled | Wear-levelled key/value storage in ESP32 flash for settings. | Bundled with the ESP32 core |
Block Diagram
The block diagram shows the functional decomposition of the system — what senses, what decides, what acts, and where the data ends up.
Circuit Diagram & Wiring
Every signal line in the build is shown below, followed by a pin-by-pin connection table you can work through with a multimeter in hand.
| Peripheral | Peripheral pin | Controller pin | Signal |
|---|---|---|---|
| MAX485 (to sensor) | RO / DI | GPIO 16 / 17 | UART to RS-485 |
| MAX485 DE/RE | DE+RE | GPIO 4 | Transmit/receive control |
| Battery/solar voltage | divider | GPIO 34 | Power monitoring |
| SX1278 LoRa | SPI + DIO0 | GPIO 5 18 19 23 / 26 | Long-range uplink |
| Sensor power gate | MOSFET | GPIO 25 | Powers the 12 V sensor only during a read |
| Status LED | Anode | GPIO 2 | Brief flash on read |
Wire one row at a time and tick it off — most "it does not work" reports trace back to a single swapped pair.
Wiring explanation
- The RS-485 A and B lines connect the MAX485 transceiver to the soil sensor. Twist the pair, and fit 120 Ω termination resistors at both ends of the bus — an unterminated RS-485 bus reflects signals and produces read errors, especially over long cable.
- The DE/RE pin controls transmit versus receive on the MAX485. It must be driven high before sending a Modbus request and low to receive the response; getting this timing wrong is the classic RS-485 bug.
- Most industrial soil probes need 12 V or more and draw significant current during a reading. Gate the sensor's 12 V supply through a MOSFET so it is only powered during a read — leaving it on drains a solar system quickly.
- The probe must be inserted to its full sensing depth in firm contact with soil. Air gaps around the probe corrupt all the readings, especially conductivity-derived ones.
- For solar operation, size the panel and battery for the sensor's read current plus the LoRa transmit bursts, with margin for cloudy days — the sensor is the largest load.
- Seal everything to IP67. This lives in a field, in the weather, for a season.
System Architecture
Read the stack from the bottom up: physical hardware, the firmware that drives it, the transport that moves data off the device, and the software a human actually looks at.
Working Principle
The first thing to understand is what these sensors actually do, because it determines how their output should be used. A laboratory soil test physically extracts nutrients with chemical reagents and measures the extracted amount — this is the reference method. A field NPK probe does something quite different: it measures the soil's electrical properties (capacitance and conductivity) and applies a calibration model to estimate nitrogen, phosphorus and potassium. The relationship between electrical properties and actual nutrient content is real but loose, soil-type dependent, and affected by moisture, temperature and salinity. So the probe reports an estimate with modest absolute accuracy, not a measurement.
This does not make them useless — it makes them a relative, comparative instrument. If two locations in a field read differently, that difference is meaningful even if neither absolute value is precise. If a location's reading changes over a season, that change is meaningful. What the probe supports well is detecting spatial variation (which part of the field is nutrient-poor) and temporal change (is the status improving under a treatment). What it does not support is a precise "your soil has X kg/ha of nitrogen" claim on which to base an exact fertiliser dose — that needs a lab test. Using the probe for the former and confirming with a lab test for the latter is the correct workflow, and the node's deviation flagging exists to prompt exactly that confirmation.
RS-485 with Modbus-RTU is the communication layer, and it is worth understanding because it is the industrial standard. RS-485 is a differential signalling scheme: data is sent as the voltage difference between two wires (A and B), so noise that affects both wires equally cancels out. This is why it runs reliably over hundreds of metres of cheap twisted pair in electrically noisy environments where a single-ended signal (like plain UART) would fail. It is half-duplex — the same pair carries data both ways — so a control line (DE/RE) switches the transceiver between transmit and receive. Modbus-RTU is the protocol on top: a simple master-slave scheme where the master (the ESP32) sends a request naming a slave address and register, and the slave responds. Many sensors can share one bus at different addresses.
The readings need compensation because the same soil reads differently under different conditions. Conductivity (and the NPK estimates derived from it) rises with temperature and with moisture — wet soil conducts better than dry, warm better than cold. A reading taken after rain differs from one taken in drought even with identical nutrient content. The node reads temperature and moisture alongside NPK and applies compensation, and — as importantly — timestamps readings so you compare like conditions.
The node is designed for unattended field deployment: solar-powered, deep-sleeping for hours between readings (soil nutrients change over days and weeks, not minutes), and reporting over LoRa because farmland has no Wi-Fi. LoRa trades data rate for range and power — it sends small packets tens of kilometres, on a coin-cell-scale energy budget, which is exactly the profile of an infrequent soil reading from a remote field.
Deployed as a network across a field, the nodes build a nutrient map. Because the probes are good at relative comparison, a map of readings reveals the field's variation even if absolute values are approximate — and that map is what enables variable-rate application: instead of spreading fertiliser uniformly, apply more where the map shows deficiency and less where it shows sufficiency. This is where precision agriculture saves money and reduces the runoff that pollutes waterways.
The maths behind it
Modbus-RTU frame and CRC
Master request (read holding registers):
[addr][0x03][reg_hi][reg_lo][count_hi][count_lo][crc_lo][crc_hi]
Slave response:
[addr][0x03][bytecount][data...][crc_lo][crc_hi]
CRC-16 (Modbus):
crc = 0xFFFF
for each byte: crc ^= byte
for 8 bits: if crc & 1: crc = (crc>>1) ^ 0xA001
else: crc >>= 1
DE/RE high to send, low to receive, with a short
turnaround delay so the last byte fully transmits.
Temperature/moisture compensation
Conductivity-derived readings rise with T and moisture:
EC_25 = EC_measured / (1 + 0.02·(T − 25))
(2 %/°C is a common soil EC temperature coefficient.)
Moisture affects the NPK estimate strongly — the probes
are calibrated at a reference moisture. Readings taken
far from that moisture are less reliable; note the
moisture with every reading and compare like with like.
The honest approach: report readings WITH their
temperature and moisture, and compare readings taken
under similar conditions.
Variable-rate application from a field map
For each zone z with relative nutrient index R_z (from the map):
application_z = base_rate · (target − R_z) / target, clamped ≥ 0
A deficient zone (low R_z) gets more; a sufficient zone
gets less or none.
Savings vs uniform application ≈
1 − (Σ application_z) / (n_zones · base_rate)
Typically 10–30 % fertiliser reduction with maintained
yield — the economic case for the whole system. But
CALIBRATE the map against lab tests before setting rates.
Program Flowchart
The firmware is a single cooperative loop. Nothing blocks for long, so networking, sensing and the user interface all stay responsive.
Assembly Instructions
Build on a breadboard first and only commit to solder once the whole system has run for an hour without a fault.
Step-by-Step Implementation Guide
Work through these in order. Each step ends in something you can observe, so a failure is always localised to the step you just finished.
Complete Source Code
The listing below is complete and compiles as written — there are no elided sections. Read the annotations under each block before you upload it.
/* ═══════════════════════════════════════════════════════════════
Soil NPK Sensor Node — ESP32 + RS-485 Modbus + LoRa
Reads a 7-in-1 soil probe (N, P, K, pH, EC, moisture, temperature)
over Modbus-RTU, applies temperature compensation, and reports over
LoRa. Solar-powered with deep-sleep duty cycling.
These probes give RELATIVE nutrient status, not lab measurements.
Use for mapping and trends; confirm absolute values with a lab test.
══════════════════════════════════════════════════════════════════ */
#include <ModbusMaster.h>
#include <LoRa.h>
#include <SPI.h>
#include <esp_sleep.h>
#include <math.h>
#define RS485_RX 16
#define RS485_TX 17
#define RS485_DE 4
#define SENSOR_PWR 25
#define LORA_CS 5
#define LORA_RST 14
#define LORA_DIO0 26
#define PIN_BATT 34
#define SENSOR_ADDR 0x01
#define SLEEP_HOURS 6
#define NODE_ID 1
ModbusMaster sensor;
struct Soil {
float moisture, tempC, ec, ph, n, p, k;
bool ok;
};
/* ── RS-485 direction control ───────────────────────────────── */
void preTx() { digitalWrite(RS485_DE, HIGH); }
void postTx() { digitalWrite(RS485_DE, LOW); }
Soil readSensor() {
Soil s = {};
digitalWrite(SENSOR_PWR, HIGH);
delay(2000); // industrial probes need warm-up
Serial2.begin(4800, SERIAL_8N1, RS485_RX, RS485_TX);
sensor.begin(SENSOR_ADDR, Serial2);
sensor.preTransmission(preTx);
sensor.postTransmission(postTx);
// Register map varies by sensor; this is a common layout.
// Registers: 0x00 moisture, 0x01 temp, 0x02 EC, 0x03 pH,
// 0x04 N, 0x05 P, 0x06 K.
uint8_t rc = sensor.readHoldingRegisters(0x0000, 7);
digitalWrite(SENSOR_PWR, LOW); // power off immediately after
if (rc != sensor.ku8MBSuccess) { s.ok = false; return s; }
s.moisture = sensor.getResponseBuffer(0) / 10.0f; // %
s.tempC = (int16_t)sensor.getResponseBuffer(1) / 10.0f;
s.ec = sensor.getResponseBuffer(2); // µS/cm
s.ph = sensor.getResponseBuffer(3) / 10.0f;
s.n = sensor.getResponseBuffer(4); // mg/kg (estimate!)
s.p = sensor.getResponseBuffer(5);
s.k = sensor.getResponseBuffer(6);
s.ok = true;
// Temperature-compensate EC to 25 °C (2 %/°C).
s.ec = s.ec / (1.0f + 0.02f * (s.tempC - 25.0f));
return s;
}
/* ── plausibility / deviation flag ──────────────────────────── */
bool plausible(const Soil &s) {
return s.moisture >= 0 && s.moisture <= 100 &&
s.ph >= 3 && s.ph <= 10 &&
s.n >= 0 && s.n <= 2000 && s.p >= 0 && s.p <= 2000 && s.k >= 0 && s.k <= 2000;
}
/* ── LoRa uplink ────────────────────────────────────────────── */
float batteryVolts() {
uint32_t acc = 0;
for (int i = 0; i < 8; i++) acc += analogRead(PIN_BATT);
return (acc / 8.0f / 4095.0f) * 3.3f * 2.0f * 1.05f;
}
void transmit(const Soil &s, bool deviation) {
LoRa.beginPacket();
LoRa.printf("{\"node\":%d,\"m\":%.1f,\"t\":%.1f,\"ec\":%.0f,\"ph\":%.1f,"
"\"n\":%.0f,\"p\":%.0f,\"k\":%.0f,\"batt\":%.2f,\"flag\":%d}",
NODE_ID, s.moisture, s.tempC, s.ec, s.ph, s.n, s.p, s.k,
batteryVolts(), deviation ? 1 : 0);
LoRa.endPacket();
}
/* ── setup runs once per wake ───────────────────────────────── */
void setup() {
Serial.begin(115200);
pinMode(RS485_DE, OUTPUT); digitalWrite(RS485_DE, LOW);
pinMode(SENSOR_PWR, OUTPUT); digitalWrite(SENSOR_PWR, LOW);
analogSetPinAttenuation(PIN_BATT, ADC_11db);
SPI.begin();
LoRa.setPins(LORA_CS, LORA_RST, LORA_DIO0);
if (!LoRa.begin(433E6)) { Serial.println("LoRa failed"); }
LoRa.setSpreadingFactor(10); // range vs airtime trade-off
LoRa.setTxPower(20);
Soil s = readSensor();
if (!s.ok) {
s = readSensor(); // one retry
}
bool deviation = false;
if (s.ok && plausible(s)) {
// Flag a reading that is far from a stored running expectation —
// a prompt to take a confirmatory laboratory sample.
// (A fuller version stores per-node history; here a simple range check.)
deviation = (s.ph < 5.5 || s.ph > 7.5 || s.n > 500 || s.k > 500);
transmit(s, deviation);
Serial.printf("moisture %.1f%% T %.1f EC %.0f pH %.1f N %.0f P %.0f K %.0f%s\n",
s.moisture, s.tempC, s.ec, s.ph, s.n, s.p, s.k,
deviation ? " [FLAG: confirm with lab]" : "");
} else {
Serial.println("Read failed or implausible — sensor or contact fault");
}
LoRa.sleep();
esp_sleep_enable_timer_wakeup((uint64_t)SLEEP_HOURS * 3600ULL * 1000000ULL);
Serial.flush();
esp_deep_sleep_start();
}
void loop() { /* never reached */ }
Configuration & Calibration
Configuration steps
- Read your specific sensor's Modbus documentation — the slave address, baud rate and register map all vary between manufacturers. The register layout in the code is a common one, not universal.
- Fit 120 Ω termination at both ends of the RS-485 bus, especially for long cable runs. Reflections on an unterminated bus cause intermittent read failures.
- Set the deep-sleep interval to match how fast your soil changes — hours to daily is appropriate; soil nutrients do not change by the minute.
- Set the LoRa frequency to your region's licence-free band (433 MHz shown; 868/915 MHz elsewhere) and respect the duty-cycle limits.
- Establish a per-node expected range from early readings so the deviation flag is meaningful, and pair the system with occasional lab tests to anchor the relative readings.
Calibration procedure
An uncalibrated sensor produces confident, precise, wrong numbers. Do this once per physical unit and record the constants.
Anchor against a lab test
Take a soil sample from beside the probe and send it for a laboratory NPK and pH analysis. Compare with the probe reading. This does not calibrate the probe to lab accuracy, but it tells you the offset and lets you interpret the probe's relative readings against a known point.
Check the probe contact
Insert the probe fully into firm, moist soil with no air gaps. Read, then reinsert nearby and read again — consistent readings mean good contact; scatter means air gaps or variable insertion depth are corrupting the readings.
Verify Modbus communication
Confirm you get valid responses with correct CRC. A common failure is the DE/RE timing — if reads fail intermittently, check the direction control and bus termination first.
Map the temperature effect
Read the same soil at different temperatures (morning and afternoon). The change shows the temperature sensitivity and validates your compensation. Uncompensated EC can shift 20 %+ across a day.
Network Architecture & Connectivity
Communication protocol
Two protocols in one node, each right for its job. RS-485 Modbus connects to the sensor — rugged, short-to-medium range, wired. LoRa carries the reading back to the farm — long range, wireless, low power. The node bridges the industrial sensor bus to the long-range radio.
LoRa's spreading factor trades data rate for range and airtime. A higher spreading factor reaches further and penetrates obstacles better but takes longer to transmit (and uses more airtime, which is duty-cycle limited). For infrequent small soil readings, a high spreading factor is the right choice.
| Topic / endpoint | Direction | Payload |
|---|---|---|
LoRa → gateway → farm/soil/node-N | node → gateway → broker | JSON: moisture, temp, EC, pH, N, P, K, batt, flag |
Message contract between the device and the broker.
Dashboard setup
The key output is a spatial field map: node readings plotted on the field geometry, interpolated between nodes, showing where nutrients are high and low. This map, calibrated against lab tests, drives variable-rate fertiliser application — the economic and environmental payoff of the whole system.
Security considerations
- Field telemetry is generally low-sensitivity, but authenticate the LoRa uplink so readings cannot be spoofed into driving wrong fertiliser rates.
- Keep the lab-test anchoring in the workflow — a system that acts on uncalibrated relative readings can misapply fertiliser expensively.
Testing Procedure & Expected Output
Test from the bottom up. Confirm power, then each sensor in isolation, then the integrated loop — the first failing step tells you exactly where to look.
| Test | What you should see |
|---|---|
| Read the sensor over Modbus | Valid responses with correct CRC for all seven parameters. |
| Insert in known-moist vs dry soil | The moisture reading tracks the difference; NPK estimates shift too, illustrating the moisture dependence. |
| Read at two temperatures | Raw EC shifts with temperature; compensated EC is stable — confirming the compensation. |
| Compare against a lab test | The probe and lab agree in direction and rough magnitude; absolute values differ, confirming the relative-instrument framing. |
| Test the deviation flag | An extreme reading (very high nutrient estimate or out-of-range pH) sets the flag prompting a lab confirmation. |
| Transmit over LoRa and receive at the gateway | The packet arrives with the full reading; range meets your field size at the chosen spreading factor. |
| Measure solar/battery over a few days | The battery holds through cloudy days with the sensor gated off between reads. |
| Deploy multiple nodes and build a map | Spatial variation is visible and consistent with known field differences. |
Bench-test checklist. If a row fails, stop and fix it before moving on.
Expected output
With everything wired and the firmware uploaded, the Serial Monitor at 115200 baud should look similar to the trace below. Values will differ; the shape of the output should not.
Troubleshooting: Common Errors & Fixes
Performance Optimisation
- Power the sensor only during a read (2–3 s including warm-up) and deep-sleep between — the probe is the dominant load and gating it is what makes solar operation viable.
- Read every few hours, not continuously. Soil nutrients change over days; frequent reads waste energy and add no information.
- Choose the LoRa spreading factor for your actual range need — higher than necessary wastes airtime and energy.
- Replace every
delay()with amillis()comparison — blocking delays are the single most common cause of dropped readings. - Sample sensors on a fixed cadence and publish on a slower one; you almost never need to transmit at the sampling rate.
- Move networking into its own FreeRTOS task so a slow DNS lookup cannot stall the control loop.
- Use
uint8_t/uint16_twhere the range allows; on an 8-bit AVR a 32-bit add costs four times as much. - Batch several samples into one MQTT publish. Radio time, not CPU time, dominates the energy budget.
- Set the MQTT keep-alive to a value that matches your reporting interval so the broker does not churn reconnections.
- For battery builds use deep sleep between samples: an ESP32 drops from ~160 mA awake to about 10 µA asleep, which is the difference between days and months of runtime.
- Profile before optimising — print
micros()deltas around each stage and fix the slowest one first.
Safety Precautions
- Interpret these sensors honestly: they estimate nutrient status from electrical properties and are relative instruments, not laboratory tests. Do not base an exact fertiliser dose on an uncalibrated probe reading — over- or under-application is both costly and environmentally harmful.
- Anchor the system with periodic laboratory soil tests, especially before making significant fertiliser decisions.
- Follow safe practice around any high-voltage field wiring and around fertiliser and agrochemicals themselves.
- Respect the LoRa duty-cycle and power limits for your region's licence-free band.
- Lithium cells vent and burn when abused. Only use protected cells or a proper BMS, never charge below 0 °C, and never leave a charging pack unattended on a wooden desk.
- Never power an RF module without its antenna fitted — the reflected power destroys the output stage. Check your local licence-free band and duty-cycle limits before transmitting.
- Wear eye protection when soldering or cutting, and solder in a ventilated space — rosin flux fumes are a respiratory irritant.
- Power the circuit through a bench supply with a current limit while you are testing. A 300 mA limit turns a wiring mistake into a beep instead of a dead board.
- Disconnect power before changing any wiring. Hot-plugging a sensor onto a live bus is the fastest way to lose a controller.
Maintenance
- Re-check every screw terminal and header after the first week — thermal cycling loosens connections that felt tight on day one.
- Log pack voltage. When resting voltage after a full charge drops below about 4.0 V, the cell is near end of life — replace it.
- Wash the panel every few weeks in dusty conditions; a visible dust film costs 15–25 % of the harvest.
- Keep the broker and dashboard containers patched, and rotate device credentials at least once a year.
- Recalibrate at the interval given in the calibration section, and keep the constants in a text file next to the firmware — not only in flash.
- Keep a short logbook of firmware versions and what changed. Six months later you will not remember why that constant is 1.083.
Future Improvements & Upgrades
A working v1 is a platform, not a finish line. These are the upgrades that add the most capability for the least rework.
- Add per-node history and machine-learning calibration that improves the relative-to-absolute mapping using accumulated lab-test anchors.
- Add variable-rate application integration that feeds the field map directly to a fertiliser spreader's controller.
- Add more nodes and interpolation for a denser, more accurate field map.
- Add weather and irrigation context so readings are interpreted alongside rainfall and irrigation events that affect them.
- Add a proper lab-comparison study for your soil type to characterise the probe's accuracy honestly and set expectations.
- Design a proper PCB. Once the breadboard version has run for a month, moving to a two-layer board removes the intermittent-contact failures that dominate prototype faults.
- Add over-the-air firmware updates so you never have to physically reach a deployed node again.
- Add persistent local storage (microSD or the on-chip flash) so a network outage does not create a hole in your data.
- Move configuration out of the source: a captive-portal setup page or a JSON config file makes the build reusable without a recompile.
- Add a battery and solar option so the unit survives a power cut and can be sited away from a socket.
- Write a small test harness that feeds synthetic sensor values through the decision logic, so you can validate thresholds without physically triggering the event.
Frequently Asked Questions
References & Learning Resources
These are the primary sources worth reading in full. Manufacturer datasheets always outrank forum posts when the two disagree.
- Modbus Application Protocol Specification V1.1b3Modbus Organization
- TIA/EIA-485-A — RS-485 differential signalling standardTI application note
- Adamchuk et al., "On-the-go soil sensors for precision agriculture"Computers and Electronics in Agriculture, 2004
- Kim et al., "Evaluation of on-the-go soil nitrate sensors — accuracy and limitations"Transactions of the ASABE
- LoRa and LoRaWAN — regional parameters and spreading factorsLoRa Alliance
- USDA NRCS — Soil Testing and interpretationUSDA NRCS