v1.0 ยท Arduino Library Manager

Your soil sensor is
lying to you.

pH reads 6.8 no matter what. NPK values jump randomly. It's not the sensor. It's your code. FarmSense fixes that.

Get Started
Library Manager โ†’ FarmSense

Three lines. Done.

Supply your read function. Let FarmSense handle the filtering and calibration mapping.

01_Basic_Usage.ino
#include <FarmSense.h>

// Transport-agnostic: wrap your Modbus/I2C/ADC call
float readPH() { return modbus.readRegister(0x0006) / 100.0; }

FarmSenseChannel ph(PARAM_PH, readPH);

void setup() {
    ph.begin();
}

void loop() {
    float val = ph.read(); // Filtered & Calibrated
    delay(500);
}
Every read() runs the full pipeline: raw fetch โ†’ Adaptive Kalman Filter โ†’ segmented calibration interpolation โ†’ clamp/OOR check.

Pass-through reading is not enough.

Noisy RS485 lines. Non-linear pH responses. Extrapolation errors. Here's what this library does about it.

K

Adaptive Kalman Filter

Locks onto stable readings. Only reacts to real changes in soil conditions, suppressing RS485 noise via innovation gating.

C

Multi-Point Calibration

Segmented linear interpolation (up to 10 points). Your sensor's pH curve isn't linear and we don't pretend it is.

S

Persistent Storage

EEPROM on AVR, Preferences (NVS) on ESP32. Calibration survives power cycles automatically.

D

Dual Calibration Methods

Calibrate live in the field via serial, or load offline batch data (CSV) from previous lab tests.

!

CLAMP by Default

Extrapolating saturated pH sensors is dangerous. FarmSense clamps at known boundaries and flags out-of-range.

fn

Transport Agnostic

Modbus RTU? I2C? Plain ADC? Just pass a function pointer. The processing pipeline doesn't care how you get the data.

See the difference.

Feature Raw Pass-through Other Libraries FarmSense
Noise filtering โœ— Simple averaging โœ“ Adaptive Kalman
Out-of-range safety โœ— โœ— Extrapolates blindly โœ“ CLAMP + Flag
Calibration saved โœ— Lost on reboot โœ— Manual EEPROM โœ“ Auto NVS/EEPROM
Non-linear correction โœ— โœ— Linear only โœ“ 10-point segmented
Offline batch calibration โœ— โœ— โœ“ Array / CSV / File
Transport mechanism โœ“ Yes โœ— Hardware specific โœ“ Agnostic (Func ptr)
Cross-platform โœ— ESP32 or AVR only โœ“ Both + fallback

8-in-1 Ready.

Each channel manages its own Kalman state, calibration map, and storage namespace independently.

Enum Parameter Typical Unit
PARAM_TEMPERATURETemperatureยฐC
PARAM_MOISTUREMoisture%
PARAM_CONDUCTIVITYConductivity (EC)ยตS/cm
PARAM_PHpHpH
PARAM_NITROGENNitrogen (N)mg/kg
PARAM_PHOSPHORUSPhosphorus (P)mg/kg
PARAM_KALIUMKalium (K)mg/kg
PARAM_FERTILITYFertility Indexโ€”

Calibrate via Serial.

Place the probe in a buffer solution and send commands. Changes persist automatically.

cal:pH:4.01 Map current raw pH reading to 4.01.
cal:T:25.0 Map current raw temperature to 25.0ยฐC.
reset Wipe all calibration data for matching channel.
Integration
// In your loop(), pass commands to channels:
if (Serial.available()) {
    String cmd = Serial.readStringUntil('\n');
    ph.processCommand(cmd);
    temp.processCommand(cmd);
}

API.

FarmSenseChannel
void begin()
Initialize channel. Loads persisted calibration and seeds the Kalman filter.
float read()
Full pipeline: raw read โ†’ Kalman filter โ†’ calibration โ†’ clamp/flag. Returns processed value.
float readRaw()
Direct raw reading bypassing filter and calibration.
bool isCalibrated() const
Returns true if โ‰ฅ2 calibration points exist.
bool isOutOfRange() const
Returns true if the last reading was outside the calibrated boundaries.
bool addCalibrationPoint(float referenceValue)
Method A: Reads current raw value and maps it to the given reference.
bool loadCalibrationCSV(const String& csvBlock) Batch
Method B: Parses CSV string (channel,raw,ref) and loads matching rows.
void setKalmanQ(float Q) Tuning
Process noise Q. Higher = trust measurements more. Default: 0.01
void setKalmanRho(float rho) Tuning
Innovation threshold. Below this, micro-correction only. Default: 0.5
void setOutOfRangeBehavior(OutOfRangeBehavior behavior)
Set behavior: OOR_CLAMP (default), OOR_FLAG_ONLY, OOR_EXTRAPOLATE.
void saveCalibration() Persistence
Save calibration to EEPROM (AVR) or NVS (ESP32).
bool processCommand(const String& cmd)
Parse serial command (e.g. cal:pH:4.01). Returns true if it matches this channel.

What you actually get.

1

Ship faster

Stop rewriting smoothing filters and calibration math. Install and focus on your product.

2

Deploy and forget

Persistent calibration survives reboots. Setup once in the field, runs for years.

3

Scale without rewriting

Because it's transport-agnostic, you can swap Modbus libraries or change hardware without touching the calibration logic.