11 Calibration

11 Calibration

📸 Sensor-in-soil photos coming. The calculator below does the math; the procedure tells you which two readings to take.

Defaults are guesses; your soil is yours. The capacitive moisture sensors output an analog voltage that depends on potting mix, mix density, pot material, and even ambient temperature. Calibrate per zone: take a dry reading, take a wet reading, type both into the calculator below, and copy the threshold into irrigate.py. Five minutes per zone, three zones, fifteen minutes total — and the system stops over- and under-watering on day one.

Take two readings

The procedure is the same for every zone: read the sensor in dry soil (or air), read it again in saturated soil, write both numbers down. The calculator does the rest.

Run the calibration script first:

cd ~/irrigation
source venv/bin/activate
python3 calibrate.py

The script prints a live readout every 2 seconds — one line per zone, like this:

Lettuce: 14820 (1.85V)  |  Tomato: 21340 (2.67V)  |  Raised bed: 11200 (1.40V)

Higher value means drier soil; lower value means wetter soil (capacitive sensors invert the obvious mental model — more water in the soil drops the ADC reading, doesn’t raise it). Watch the readout while you take each measurement. Stop the script with Ctrl+C when both your readings are written down.

If you skipped page 09 or your script can’t find the sensor, the live readout will print ERROR instead of a number. That means the I2C bus isn’t seeing the ADS1115 chip. Re-run i2cdetect -y 1 (page 09 covers it); confirm address 48 is in the grid. If not, page 13’s I2C debug section walks the fix.

Step 1 — Dry reading

Hold the sensor in the air, OR insert it into bone-dry potting mix. Either works; some readers prefer the air reading because it’s cleaner and faster, but a sensor in dry mix gives you the real-world ceiling. Watch the live readout for 5-10 seconds; let the value settle (it’ll twitch by a hundred or so). Note the value.

Typical range: 22000-27000.

Repeat for each zone’s sensor. Don’t assume one sensor’s reading transfers to another — soil moisture sensors vary by a few thousand ADC counts even across the same brand-and-batch, and that variation matters when the threshold is close to the wet end.

Step 2 — Wet reading

Water the pot thoroughly until water drains from the bottom. Wait five minutes for the water to distribute evenly through the soil — if you read immediately after pouring, the sensor sees a localized wet spot, not the soil’s true saturated state. Insert the sensor (or leave it in if it’s already there — the calibration runs in-place). Watch the live readout for 5-10 seconds; let it settle. Note the value.

Typical range: 8000-14000.

Stop the script with Ctrl+C once both readings for this zone are in your notebook.

Calculate the threshold

Type your two readings into the calculator below. The threshold formula is wet + (dry − wet) × 0.40 — 40% of the way from your wet reading toward your dry reading, biased toward keeping soil moist. The math runs live; the result is the value you paste into irrigate.py.

Your last calibration auto-saves to this browser. If you have multiple browser tabs open on this page, the values sync across them — same trust contract as the planner on page 02.

Why 0.40? The 0.40 coefficient puts the trigger 40% of the way from your wet reading toward your dry reading — slightly drier than the midpoint (50%), biased toward keeping soil moist. If your plants stay too wet (mold, smell, leaves yellowing from the base up), edit the threshold in irrigate.py directly to a HIGHER number (waters less often). If they stay too dry (wilting between cycles, leaf curl), edit to a LOWER number (waters more often). The script doesn’t care where the number came from — it just compares the live moisture reading against dry_threshold and decides whether to open the valve.

Edit irrigate.py

Open the script in your editor:

nano ~/irrigation/irrigate.py

Find the ZONES block (around line 35; page 10’s section-by-section walkthrough explains it). For each zone, set "dry_threshold": <calculator output> from your calibration. Save with Ctrl+O, Enter, Ctrl+X.

What about wet_threshold?

Set wet_threshold to your wet reading + about 1500. The script stops watering once the moisture reading drops to this level — adding the buffer prevents the script from oversaturating the soil before the sensor catches up to the water that’s already in flight. Example: if your tomato wet reading was 11000, set wet_threshold = 12500.

This is the only place on the site that documents the wet_threshold rule; the calculator only outputs the dry threshold because that’s the load-bearing number, but the script still needs a wet target.

Per-zone calibration

Run the calculator three times — one per zone. Each zone’s soil and pot are different; lettuce in a small terracotta pot reads very differently from tomatoes in a 5-gallon plastic pot, and the raised bed reads differently again because the soil column is deeper and dries unevenly. Type the next zone’s values over the previous; the threshold updates live. (Or open this page in three browser tabs and the cross-tab sync paints all three at once.)

My first calibration showed wildly different readings between the same brand of sensor on the same day in the same potting mix — the per-zone-per-pot rule is real. Don’t assume the lettuce numbers will work for the tomatoes.

With each zone’s threshold tuned and saved into irrigate.py, page 12 walks the cron schedule that runs the script on a rhythm and the optional Flask web UI for checking status from your phone.