Smart Sprinkler Controller
by peternickerson2006 in Outside > Backyard
699 Views, 4 Favorites, 0 Comments
Smart Sprinkler Controller
Introduction
According to the US EPA, a household with an automatic landscape irrigation system that isn't properly maintained and operated can waste up to 25,000 gallons of water per year — and as much as 50% of all outdoor water use is lost to wind, evaporation, and runoff from inefficient irrigation. That statistic hit me hard when I looked at my water bill and realized my dumb timer-based sprinkler system was running rain or shine, every day, whether the lawn needed it or not.
So I built a smarter one.
This project combines an ESP32 microcontroller, a capacitive soil moisture sensor, a gorgeous 1.28" round GC9A01 display, and live weather data from OpenWeatherMap to create a sprinkler controller that actually thinks before it waters. But the part I'm most proud of isn't the hardware — it's the math. Instead of just checking whether the soil is dry and turning on a relay, this system implements the FAO-56 Penman-Monteith evapotranspiration equation, the same scientific standard used by agronomists and water utilities worldwide, to calculate exactly how much water evaporated from your lawn today — and therefore exactly how long to run the sprinkler to put it back.
The result is a wall-mounted controller with a sleek round display showing four different information screens (soil moisture, weather, schedule, and the ET math), a live web dashboard accessible from any phone or browser on your network, and a relay output ready to connect to a real sprinkler valve. It skips watering automatically if it's raining, if rain is forecast, if the wind is too high, or if the soil is already wet enough. And if you hold the button for five seconds, it activates a secret screensaver. You're welcome.
This is currently a proof-of-concept. The relay wiring and valve integration are straightforward to add, but in my build the software and control logic are fully complete and operational — I just haven't plumbed it into an actual valve yet. Everything described here works exactly as written.
What This Project Does
- Reads soil moisture every 5 seconds via a capacitive sensor
- Fetches current weather and 12-hour forecasts from OpenWeatherMap every 10 minutes
- Calculates daily evapotranspiration using the FAO-56 Penman-Monteith equation (temperature, humidity, wind, cloud cover, latitude, elevation, day of year)
- Applies a monthly seasonal grass coefficient (Kc) to determine actual crop water need
- Calculates exactly how many minutes to run the sprinkler to replace what evaporated
- Skips watering if: it's raining, rain probability > 50%, rain forecast > 2mm, wind > 15 mph, soil already wet, or not in the scheduled watering window
- Displays four information screens on a round GC9A01 display, navigated with a potentiometer knob
- Serves a live web dashboard over WiFi with WebSocket real-time updates
- Controls a relay to switch a sprinkler valve (active-low, connects to any standard 24V sprinkler valve via a relay module)
- Manual override via physical button or web dashboard
- Adjustable watering window and weather override from the web UI
- Secret screensaver easter egg (hold button 5 seconds)
Built in Fort Collins, Colorado — elevation 1,525 meters, semi-arid high plains, where water is expensive and the sun will absolutely evaporate your lawn if you don't pay attention.
Supplies
Electronics
Components, Notes, and Approx. Cost
ESP32 development board (any generic 30-pin or 38-pin):
WROOM-32 or similar ($16, 3 pack)
Waveshare 1.28" Round LCD, GC9A01 driver, 240×240px
Must be GC9A01 — not all round displays are ($20)
Capacitive soil moisture sensor
Capacitive, NOT resistive — resistive ones corrode ($3–5)
Capacitive Touch Sensor (Button)
Any tactile button, 12mm panel-mount looks clean ($1)
10K potentiometer (linear)
For the view-switching knob ($10 pre-soldered)
At least 1A ($5–8)
Jumper wires + breadboard (for prototyping)
($3–5)
Enclosure material
See Step 9 (Varies)
Total: approximately $33–$49 for electronics alone, $50–$75 with enclosure materials.
Accounts & API Keys (Free)
- OpenWeatherMap API key — free tier supports 1,000 calls/day, well within our 10-minute polling interval. Sign up at openweathermap.org.
Software & Tools
- Arduino IDE (2.x recommended) or PlatformIO
- ESP32 board support package (by Espressif)
- Soldering iron + solder
- Wire strippers
Files are attached below
Make sure that in the same folder as the Smart_Sprinkler.ino you also include a folder called "data" where you include the index.html (Instructables doesn't allow html uploads so you will have to change the file type). The data folder is also where you will include your 240x240 .bmp photos for the screen saver.
Understanding the System
Before touching a single wire, it helps to understand how the pieces fit together. Here's the full data flow:
Sensors In: The capacitive soil sensor outputs an analog voltage on GPIO 34. The ESP32's ADC reads this and maps it to a 0–100% moisture percentage using calibration values you set once (dry reading and wet reading). Ten readings are averaged to smooth out the ESP32's notoriously noisy ADC.
Weather In: Every 10 minutes the ESP32 calls two OpenWeatherMap endpoints — the current weather API (temperature, humidity, wind, cloud cover) and the 5-day forecast API limited to 4 entries (covering the next 12 hours). It extracts rain probability and forecasted precipitation in mm.
The Brain: The ET calculation runs after every weather update. It takes temperature, humidity, wind speed, cloud cover, your latitude, elevation, and the current day of year, and runs the full FAO-56 Penman-Monteith formula to produce a daily evaporation value in mm/day. That gets multiplied by the monthly grass coefficient and converted to inches, then divided by your sprinkler's application rate (inches/hour) to get the exact number of minutes to run.
Decisions: Every 5 seconds the code checks all conditions: Is the soil dry? Are we in the allowed time window? No rain? Wind okay? Has enough time passed since the last watering? If all five conditions are true, the relay turns on for the calculated number of minutes (bounded between 2 and 30 minutes as a safety cap).
Output: GPIO 26 drives the relay module (active-low — LOW = relay energized = valve open). The round display shows one of four screens. The web dashboard updates via WebSocket every second.
Wiring It Up
All connections use 3.3V logic (the ESP32 is not 5V tolerant on most pins). The relay module is powered from the ESP32's 5V pin and takes a 3.3V-compatible control signal just fine.
Display (GC9A01, SPI)
You must configure TFT_eSPI to match these exact pin assignments before the display will work (see Step 3).
- GC9A01 MOSI / SDA → ESP32 GPIO 23 (SPI data)
- GC9A01 SCLK / SCL → ESP32 GPIO 18 (SPI clock)
- GC9A01 CS → ESP32 GPIO 5 (Chip select)
- GC9A01 DC / RS → ESP32 GPIO 2 (Data/command)
- GC9A01 RST / RES → ESP32 GPIO 4 (Reset)
- GC9A01 BL → ESP32 GPIO 22 (Backlight — HIGH = on)
- GC9A01 VCC → ESP32 3.3V
- GC9A01 GND → ESP32 GND
Soil Sensor
- Sensor AOUT → ESP32 GPIO 34 (Analog input, ADC1 — do NOT use ADC2 pins, those conflict with WiFi)
- Sensor VCC → ESP32 3.3V
- Sensor GND → ESP32 GND
Relay Module
- Relay IN → ESP32 GPIO 26 (Control signal)
- Relay VCC → ESP32 5V (VIN pin — the relay coil needs 5V)
- Relay GND → ESP32 GND
The relay's COM and NO terminals connect between your 24V sprinkler transformer and the valve solenoid. When GPIO 26 goes LOW, the relay closes and the valve opens.
Button
- One leg → ESP32 GPIO 27
- Other leg → GND
The code uses INPUT_PULLUP so no external resistor is needed.
Potentiometer
- Left outer leg → ESP32 3.3V
- Right outer leg → ESP32 GND
- Center wiper leg → ESP32 GPIO 32 (ADC1 channel 4)
If the knob cycles screens backwards, just swap the 3.3V and GND legs — no code change needed.
Configuring TFT_eSPI
TFT_eSPI requires you to edit its User_Setup.h file directly — this is not done through code but through the library file itself.
- Install TFT_eSPI through the Arduino Library Manager (search "TFT_eSPI" by Bodmer).
- Navigate to your Arduino libraries folder. On Windows this is typically Documents/Arduino/libraries/TFT_eSPI/. On Mac it's ~/Documents/Arduino/libraries/TFT_eSPI/.
- Open User_Setup.h in any text editor.
- Find and comment out any existing driver definition (look for lines like #define ILI9341_DRIVER), then add:
- Find the pin definition section and set:
- Make sure SPI frequency is set appropriately (40MHz works well for this display):
- Save User_Setup.h. These settings are now baked into the library for this project.
Important: Every time you update TFT_eSPI through the library manager, User_Setup.h gets overwritten. Keep a copy of your settings somewhere so you can re-apply them quickly.
Getting Your OpenWeatherMap API Key
This project uses OpenWeatherMap to fetch current weather conditions and a 12-hour rain forecast. The free tier is completely sufficient — it allows 1,000 API calls per day, and this project only makes about 144 calls per day (one every 10 minutes). You will never hit the limit.
Creating Your Account
Go to openweathermap.org and click "Sign In" in the top right, then choose "Create an Account." Fill in your name, email, and a password. After confirming your email you will be logged into the dashboard.
Getting Your API Key
Once logged in, click your username in the top right corner and select "My API Keys" from the dropdown. You will see a default key has already been created for you — it will be a long string of letters and numbers that looks something like this:
eaff286c9668a4059029849f2706f85
Copy that key. This is what you will paste into the weatherApiKey field in the code. If you prefer to create a named key for this project, click the "Generate" button, give it a name like "smart-sprinkler," and use that one instead.
Important: New API keys take up to 2 hours to activate. If the ESP32 boots up and the weather fields on the display show dashes or the serial monitor shows HTTP error 401, just wait and try again. This is normal and not a bug in your code.
Finding Your City String
The city is passed to the API as a URL-encoded string. The format is:
CityName,CountryCode
For example:
- Fort Collins, Colorado → Fort%20Collins,US
- New York City → New%20York%20City,US
- Los Angeles → Los%20Angeles,US
- London, UK → London,GB
Spaces become %20 in the URL. The country code is the two-letter ISO code for your country (US, GB, CA, AU, DE, etc.). Find the right code by searching "ISO country codes" if you are outside the US.
Paste your formatted city string into the myCity field in the code:
const char* myCity = "Fort%20Collins,US";
Testing Your Key Before Flashing
You can verify your API key works by pasting this URL into any browser (replace YOUR_KEY and YOUR_CITY with your actual values):
http://api.openweathermap.org/data/2.5/weather?q=YOUR_CITY,US&appid=YOUR_KEY&units=imperial
If you see a wall of JSON data with temperature and weather information, your key is working. If you see an error message saying "Invalid API key," either the key hasn't activated yet or there is a typo — double-check that you copied the full key with no extra spaces.
Installing Libraries and Flashing the Code
Install These Libraries
Through Arduino IDE's Library Manager (Sketch → Include Library → Manage Libraries):
- TFT_eSPI by Bodmer — the display driver
- ArduinoJson by Benoit Blanchon — for parsing weather API responses
⚠️ Do NOT install "Arduino_JSON" (the one by Arduino). It has a naming conflict with ArduinoJson that causes compile errors on ESP32. If you have it installed, remove it.
These two must be installed manually from GitHub (Library Manager versions are outdated):
- ESPAsyncWebServer by me-no-dev — github.com/me-no-dev/ESPAsyncWebServer
- AsyncTCP by me-no-dev — github.com/me-no-dev/AsyncTCP
To install manually: download the ZIP from GitHub, then in Arduino IDE go to Sketch → Include Library → Add .ZIP Library.
WiFi, HTTPClient, SPI, LittleFS, time.h, and math.h are all included with the ESP32 board package and don't need separate installation.
Configure Your Settings
At the top of the main .ino file, fill in your details:
For the timezone string, search "POSIX timezone string" + your city/region to find the correct value. For example: US Eastern is EST5EDT,M3.2.0,M11.1.0, US Pacific is PST8PDT,M3.2.0,M11.1.0.
Flash the Code
- Select your ESP32 board (Tools → Board → ESP32 Arduino → ESP32 Dev Module or your specific variant).
- Set Upload Speed to 921600.
- Click Upload.
On the first boot, watch the Serial Monitor at 115200 baud. You'll see the WiFi connection, NTP time sync, and the first weather fetch. The display will show a boot splash screen, then transition to the soil moisture screen.
Setting Up LittleFS (Dashboard + Screensaver Images)
The web dashboard (index.html) and the screensaver images are stored on the ESP32's flash filesystem using LittleFS. You upload them using the ESP32 Sketch Data Upload plugin.
Install the LittleFS Upload Plugin
- Download the ESP32 LittleFS Data Upload plugin from GitHub: github.com/lorol/arduino-esp32fs-plugin
- Place the downloaded .jar file in your Arduino tools folder: Documents/Arduino/tools/ESP32FS/tool/
- Restart Arduino IDE. You should now see Tools → ESP32 LittleFS Data Upload.
Prepare the /data Folder
Create a folder called data inside your sketch folder (same folder as your .ino file). Place these files inside it:
- index.html — the web dashboard (the full HTML file is provided with this project)
- Your screensaver images as 240×240 pixel 24-bit BMP files
Screensaver Image Requirements
- Format: 24-bit BMP (not 8-bit, not 32-bit)
- Size: exactly 240×240 pixels
- Filename: must match the array in the code (default: chud.bmp, monkey.bmp, angry.bmp, confused.bmp, horse.bmp, smile.bmp, ropopo.bmp)
- You can use any images you like — drop any 240×240 BMPs in and update the pics[] array in the code to match
To convert images to the right format in Photoshop or GIMP: resize to 240×240, then export/save as BMP with 24 bits per pixel selected.
Upload to the ESP32
- Make sure the correct ESP32 board and port are still selected.
- Go to Tools → ESP32 LittleFS Data Upload.
- Wait for the upload to complete (it will say "LittleFS Image Uploaded" when done).
You only need to re-upload the data folder if you change the HTML or swap screensaver images — regular code changes are uploaded normally through the main Upload button.
Calibrating the Soil Sensor
Capacitive soil sensors need two calibration readings: one for completely dry soil and one for saturated soil. These map the raw ADC output (0–4095) to the 0–100% scale shown on the display.
Getting Your Readings
- Flash the code and open the Serial Monitor at 115200 baud.
- Hold the sensor in open air, completely dry. Watch the serial output for lines like [sensor] soil: X% — or temporarily add a raw print. The raw value is what you need. With most sensors, the dry reading will be near 3000–4095.
- Submerge the sensor tip in a glass of water (don't submerge above the white line on the PCB — that's the electronics). The wet reading will typically be around 1200–1800.
- Update these lines in the code:
- Re-flash. The constrain() call in the code ensures readings never go below 0% or above 100% even if your calibration isn't perfect.
Tip: Temporary Raw Reading Print
To see the raw ADC value, temporarily add this to the loop() function:
Remove it before the final flash.
The Web Dashboard
Once the ESP32 is running and connected to WiFi, open a browser on any device on your network and navigate to the ESP32's IP address. You'll see it printed in the Serial Monitor on boot: [web] dashboard at http://192.168.x.x
The dashboard is a dark-themed, real-time control terminal that updates every second via WebSocket — no page refreshing needed.
What You'll See
Soil Moisture Card: A circular SVG gauge showing current moisture percentage with color coding (red below 30%, yellow 30–40%, green above 40%). When watering is active, the card pulses with a blue glow, animated water drops fall, and a progress bar shows time remaining.
Weather Card: Current temperature, conditions description, wind speed (turns red if above the 15 mph threshold), humidity, 12-hour rain probability (color coded green/yellow/red), and forecasted precipitation in mm. A "Raining Now" badge appears when the API reports active rain.
Watering Schedule Card: Shows the configured watering window, a countdown to when the window next opens, time since the last watering cycle, and cooldown status.
Conditions Card: Five LED indicators — each one glows green when its condition is met, red when it isn't. Conditions: soil dry enough, no rain expected, wind acceptable, in watering window, all systems ready. Real-time sensor values are shown next to each indicator.
Evapotranspiration Card: The nerd panel. Shows today's calculated ET₀ in inches/day, the seasonal crop coefficient (Kc), the resulting crop water need, a bar chart of all 12 months' Kc values with the current month highlighted in cyan, and the final calculated watering duration.
Controls Card: Three sections — Manual Water (Start/Stop buttons), Watering Window (dropdowns to set the start and end hour, with a Save button), and Weather Override (a toggle that disables all weather checks if you need to force a watering cycle regardless of forecasts).
Accessing It Remotely
The dashboard is only accessible on your local WiFi network — it's not exposed to the internet. If you want remote access, you can set up a VPN to your home network or use a reverse proxy, but that's outside the scope of this project.
The Science — FAO-56 Penman-Monteith Evapotranspiration
This is what separates this project from "soil sensor + timer." Most DIY smart sprinklers ask one question: is the soil wet enough? This one asks a better question: how much water did the lawn actually lose today, and exactly how much do we need to put back?
The answer comes from the FAO-56 Penman-Monteith equation, published by the United Nations Food and Agriculture Organization and used globally as the standard reference for irrigation scheduling.
What It Calculates
The equation combines four weather inputs (temperature, humidity, wind speed, cloud cover) with three location inputs (latitude, elevation, day of year) to produce ET₀ — the reference evapotranspiration in mm/day. This represents how much water would evaporate from a standard reference surface (well-watered grass) under today's conditions.
Here's what each input contributes:
Temperature drives the vapor pressure deficit — the difference between how much water vapor the air could hold at that temperature versus how much it actually holds. Hot, dry air has a high deficit and pulls more water out of the soil and leaves.
Humidity directly determines the actual vapor pressure. High humidity means the air is already nearly saturated, so less water evaporates from the lawn.
Wind speed (converted from the 10-meter measurement height to 2 meters, which is what the formula expects) increases evaporation by continuously replacing the humid air near the leaf surface with drier air from above.
Cloud cover determines how much solar radiation reaches the ground. The code calculates theoretical extraterrestrial radiation for your latitude and day of year using astronomical formulas, then scales it down by cloud cover to estimate actual net radiation at the surface.
Latitude and elevation affect the atmospheric pressure (used to calculate the psychrometric constant) and the solar geometry calculations.
From ET₀ to Minutes
Once ET₀ is calculated, two more steps convert it to an actual watering duration:
- Seasonal Kc (crop coefficient): Grass doesn't always need the same amount of water relative to ET₀. In peak summer it needs close to 100% of ET₀. In winter, much less. The code uses a table of monthly Kc values representing cool-season turf grass (like the bluegrass/fescue common in Colorado): the values range from 0.60 in December to 1.00 in June and July. Multiplying ET₀ by Kc gives the actual crop water need in mm/day.
- Application rate: Your sprinkler heads deliver water at a known rate — typically 0.4–0.6 inches/hour for rotor heads, 1.0–1.5 in/hour for fixed spray heads. Dividing the daily water need (in inches) by the application rate (inches/hour) and multiplying by 60 gives the minutes to run. The result is clamped between 2 and 30 minutes as a sanity check.
Why This Matters for Water Conservation
A simple soil-threshold system will water whenever the soil drops below its trigger point, regardless of conditions. On a hot, windy, sunny day it might need to run twice. On a cool, cloudy, calm day it might not need to run at all even if the soil reads on the low end. The ET approach automatically accounts for all of this — it waters less on days with low evaporation demand and more on days with high demand, matching the lawn's actual needs rather than approximating them.
The Enclosure
The enclosure in the project photos is a custom 3D-printed case with a brushed finish and a wood accent strip along the bottom — a small design detail that makes it look like something you'd actually want mounted on your wall rather than a prototype.
Dimensions to Design Around
- The round GC9A01 display is 37.5mm in diameter at the glass, with an overall PCB size of about 42mm × 42mm
- The button should be a panel-mount style with a nut that clamps through the front panel
- The potentiometer knob needs a D-shaft hole and enough depth behind the panel for the pot body
- The ESP32, relay module, and wiring need space behind the display — plan for at least 25–30mm of depth
- Leave a notch or hole on the back/side for the USB power cable and for the relay terminal wires
Material Suggestions
FDM 3D print (PLA or PETG): Easiest option. Use 20% infill, 3 walls. Sand and prime for a smooth finish. A brushed metallic spray paint over gray primer gives a convincing brushed aluminum look.
Laser-cut acrylic: Clean and professional-looking if you have access to a laser cutter. Stack layers to create depth. The round display cutout is easy to do precisely.
Repurposed project box: Any plastic or metal project enclosure from Amazon or a local electronics shop works. Cut the display opening with a step drill and a file.
Wood accent: Even a thin strip of bamboo or walnut veneer glued along the bottom edge of a printed enclosure dramatically elevates the look for minimal effort.
Mounting
The project is designed to be wall-mounted. Add two small keyhole slots on the back of the enclosure to hang it on screws, or use 3M Command strips for a no-holes solution.
The Secret Screensaver Easter Egg
Because serious engineering projects should have at least one completely ridiculous feature:
Hold the physical button for 5 seconds while on any screen. The display will switch to a slideshow of 240×240 BMP images stored in LittleFS, cycling through them every 3 seconds. The default set are a collection of chaotic meme-style images. Tap the button once (short press) to exit the screensaver and return to the dashboard.
To customize the images, drop your own 240×240 24-bit BMP files into the /data folder, update the pics[] array in the code with your filenames, and re-upload via LittleFS Data Upload:
The BMP loader handles both top-down and bottom-up encoded BMPs, loads them in 24-row chunks to avoid running out of RAM, and pushes them directly to the display using TFT_eSPI's pushImage() for speed.
This feature exists for two reasons: first, because it was fun to build, and second, because the ability to display arbitrary full-color images on a round screen opens up some genuinely useful possibilities — plant identification guides, watering zone maps, or QR codes linking to the web dashboard.
Tuning the Watering Logic
All the key parameters are defined at the top of the code with clear comments, so you don't need to dig through the logic to adjust things. Here are the ones most worth tuning for your setup:
The watering window can also be adjusted live from the web dashboard without needing to reflash.
The Seasonal Kc Table
If you're growing something other than cool-season turf grass (fescue, bluegrass), adjust the monthly crop coefficient table in the code. Warm-season grasses like Bermuda and Zoysia have different seasonal patterns. Vegetable gardens, flower beds, and trees all have different Kc curves. The FAO publishes Kc values for hundreds of crops if you want to get precise.
Conclusion
This project started as a reaction to watching water run down the driveway during a rainstorm because the timer didn't know any better, and to a water bill that didn't need to be as high as it was. The EPA estimates improper irrigation wastes up to 25,000 gallons per household per year — and the frustrating part is that most of that waste is completely preventable with information that's freely available: soil readings, a weather forecast, and a bit of math.
The FAO-56 evapotranspiration approach elevates this beyond a simple "is the soil dry?" check into something that actually models what's happening in your lawn. It's the same science that professional irrigation engineers use, running on a $7 microcontroller.
What's Next
The natural next step is connecting the relay to an actual 24V sprinkler valve and running it through a full watering season to gather real data on water savings. Multiple soil sensors across a larger area would allow zone-by-zone decisions. A history log saved to LittleFS would enable tracking watering patterns over time. And the web dashboard could be extended with a chart of soil moisture and ET over the past 24 hours.
The hardware cost is intentionally low — under $75 for a complete system that makes genuinely intelligent watering decisions. That's a single month's water savings in a lot of parts of the country.