Guides · 26 min read

Arduino Smart Greenhouse Project: Sensors, Watering, Fan, Code

Build an Arduino smart greenhouse: DHT22, soil moisture and LDR sensing, relay and MOSFET control of fan, heater, pump and LEDs, hysteresis, LCD and ESP32 code.

An Arduino smart greenhouse project is an automation system in which an Arduino or ESP32 measures the air temperature, humidity, soil moisture and light in a greenhouse and runs the fan, heater, water pump and grow lights on its own. It's one of the most popular topics for science fairs, school competitions and technology classes because the result is easy to see: the pump starts when the soil dries out, the fan spins when it gets hot inside, and the LEDs come on in the evening. Better still, there's almost no difference in circuit or code between a model greenhouse built from a storage box and a real mini greenhouse on your balcony.

In this guide we start with what plants actually need, then build the model greenhouse step by step: the parts list and rough cost, wiring the sensors (DHT22, capacitive soil moisture sensor, LDR) and the actuators (fan, heater, pump and LEDs via relays and a MOSFET), the power plan and the pin table. Next we explain the threshold and hysteresis logic that stops the relays from clicking constantly, the complete Arduino code without delay(), and the 16x2 LCD. In the final sections you'll find how to monitor the greenhouse from your phone with an ESP32, a MicroPython version for the Raspberry Pi Pico, tips for school and science-fair presentations, and common mistakes.

DHT22 (AM2302)
DHT22 (AM2302)
LDR
LDR
I2C LCD 16x2
I2C LCD 16x2
LM2596
LM2596
Arduino Uno
Arduino Uno

How does a smart greenhouse work?

Every smart greenhouse runs the same four-step loop: measure, decide, act, display. The sensors measure the environment: how warm is it inside, how humid is the air, has the soil dried out, is there enough light? The Arduino compares these values with the rules you set: “if the temperature goes above 30 °C, run the fan”, “if soil moisture drops below 35%, water”. The actuators carry out the decision: relays switch the fan, heater and pump on and off, and a MOSFET sets the brightness of the LED strip. Finally, the LCD and your phone show you the current state of the greenhouse. The climate computers in real commercial greenhouses work with exactly this loop; they just use more sensors and bigger motors.

What is measured?SensorActuatorRule (example)
Air temperatureDHT22Fan and heaterFan starts at 30 °C, heater switches on at 16 °C
Relative humidityDHT22FanFan starts at 85% and stops at 75%
Soil moistureCapacitive soil moisture sensorMini submersible pumpWaters in 3-second pulses below 35%
LightLDRLED strip (MOSFET)When it gets dark, the LEDs stay on for at most 4 hours
Water in the tankFloat level switchPump lockoutIf the tank is empty, the pump never runs

What do plants need? Target temperature, humidity and light

Before you write any rules, you need to know what you're aiming for. Most plants want different temperatures by day and by night; cooling down a little at night helps the plant rest. If the humidity stays above 85% for long periods, water condenses on the leaves and fungal diseases such as mould and downy mildew spread quickly; that's why the fan in a greenhouse runs not only to cool things down but also to get rid of humidity. As for light, most vegetable seedlings want 14–16 hours a day, but they also need a dark period of at least 6–8 hours; leaving the LEDs on all night does the plant more harm than good. The values below are a good starting guide; adjust them for whatever you're growing.

PlantDayNightRelative humidityNote
Tomato21–27 °C15–18 °C60–75%Flowers may drop above 32 °C
Pepper21–29 °C16–20 °C60–75%More sensitive to cold than tomatoes
Lettuce and spinach15–20 °C10–15 °C60–70%Bolts early in heat and long days
Strawberry18–24 °C10–13 °C60–75%Grey mould risk rises in high humidity
Seedlings and germination20–25 °C18–20 °C70–85%Soil temperature matters more than air temperature
If the classroom is at 22 °C on demo day, a fan set to start at 30 °C will never turn. For the presentation, set the thresholds 2–3 degrees above room temperature so that when you warm the DHT22 in your palm, the judges see the fan start within seconds. For real use, go back to the values in the table.

Building the model greenhouse

The easiest and cheapest enclosure is a clear plastic storage box with a lid: a 40–60 litre box is enough for two or three pots, it's waterproof, and it's easy to drill with a craft knife or a heated tip. If you want a more realistic look, build a frame from wooden strips and clad it with 2–3 mm clear acrylic (Plexiglas) or PETG sheets; a greenhouse with a pitched roof looks much better at a presentation. Around 50×35×40 cm is a good starting size. Whichever enclosure you choose, the lid or one side wall has to open; you'll be reaching inside to water plants, move the sensor and tidy up cables.

  • Electronics outside: The inside of a greenhouse is humid, and water condenses on the walls in the morning. Put the Arduino, the relay module and the power supply in a separate box outside the greenhouse; only the sensors, fan, heater, pump hose and LEDs go inside.
  • Ventilation: Warm air rises, so mount the fan on the upper part of one of the short walls, blowing air out. Cut an air inlet the size of the fan in the lower part of the opposite wall and cover it with insect mesh; fresh air then comes in at the bottom and warm, humid air leaves at the top.
  • Heater: Mount the heater near the floor on an aluminium plate or a ceramic tile, at least 5 cm away from the plastic walls and the plants. The warm air circulates through the whole greenhouse as it rises.
  • LED lighting: Stick the LED strip to the inside of the lid, 15–30 cm above the plants. If you mount the strip on a thin aluminium profile, it dissipates heat better and the adhesive won't come loose over time.
  • Sensors: Place the DHT22 at plant height where it won't get direct sunlight or LED light. Bury the soil moisture sensor in the root zone, away from the edge of the pot. The LDR must not see the LEDs: put it outside the greenhouse, on the roof, facing the sky.
  • Water tank: A 1–2 litre container is enough for the pump and the float switch. Put drip trays under the pots; excess water collects in the tray instead of flooding the bottom of the box.
If the end of the hose is lower than the water level in the tank, water keeps flowing after the pump stops because of the siphon effect, and the pot overflows. Keep the tank lower than the pots, or prick a small air hole with a needle at the highest point of the hose; when the pump stops, air gets in through the hole and the flow stops immediately.

Parts list and cost

PartQtyJobPrice level
Arduino Uno R3 or ESP32 DevKit1The greenhouse's brainMedium
DHT22 (AM2302) temperature and humidity module1Air temperature and humidityLow
Capacitive soil moisture sensor (v1.2)1 (one per pot)Soil moistureLow
LDR (GL5528) and 10 kΩ resistor1 + 1Ambient lightVery low
Float water level switch1Locks out the pump when the tank is emptyLow
4-channel 5 V relay module (optocoupler, with JD-VCC)1Fan, heater and pumpLow
Logic-level N-channel MOSFET (IRLZ44N), 220 Ω and 100 kΩ resistors1 setLED strip brightness (PWM)Very low
3–6 V mini submersible pump and 1 m silicone hose1WateringLow
12 V 80 mm computer fan1–2Ventilation and humidity removalLow
12 V PTC heater (10–30 W) and thermal fuse1 + 1HeatingLow
12 V LED strip (grow light or cool white), about 1 m1Supplementary lightingLow
I2C 16x2 LCD1Status displayLow – medium
12 V 5 A power adapter and LM2596 buck converter1 + 1PowerMedium
1N5819 or 1N4007 diode, 470 µF capacitor, screw terminals, jumper wires–Protection and connectionsVery low
Clear storage box with lid or acrylic sheet, pots, drip trays–Greenhouse bodyMedium

Prices vary a lot between sellers, so here's a rough budget rather than exact figures. Excluding the board, the basic version (DHT22, soil moisture sensor, LDR, 2-channel relay, pump and a 5 V fan) usually costs around 25–40 dollars. The full version with the heater, LED strip, LCD, 12 V adapter, LM2596, float switch and enclosure usually comes to about 70–120 dollars; most of the budget goes on the board, the adapter, the display and the enclosure. If you have an Arduino starter kit, the board, breadboard, wires, LDR, resistors and often the LCD are already covered. On a tight budget, leave the heater and LEDs for a second stage; watering and ventilation on their own already make a meaningful, working project.

Sensors: DHT22, soil moisture sensor and LDR

We recommend the DHT22 (AM2302) for a greenhouse. It costs a little more than the DHT11, but it measures temperature from −40 to 80 °C to ±0.5 °C and humidity across 0–100% to about ±2%; the DHT11 is only accurate to ±2 °C between 0 and 50 °C and can't see humidity above 90%. Since a greenhouse can go above 90% humidity in the morning and below freezing in winter, the DHT22 is a much better fit. The sensor can take a reading at most once every 2 seconds, so the code reads it every 2.5 seconds. If you put the sensor where the sun or the LEDs shine directly on it, its body heats up and it reads a few degrees above the air temperature; shading it with a white cover with holes prevents this. Wiring and library details are in the DHT11 and DHT22 temperature and humidity sensor article.

Soil moisture sensor module
Soil moisture sensor: the probe buried in the root zone reports how wet the soil is as an analogue voltage; choose the capacitive model for a long life in a greenhouse.

Choose a capacitive sensor for soil moisture. Cheap resistive sensors (YL-69, FC-28) have two metal prongs in direct contact with the soil, and if they're powered all the time they corrode within a few weeks because of electrolysis; they're not suitable for a system that will run for months. A capacitive sensor's electrode is coated, so no metal touches the soil. Its output is analogue and falls as the soil gets wetter: on an Uno, a sensor held in the air reads roughly 500–600, and dipped in water about 250–300. These numbers differ from sensor to sensor, so before uploading the main code, read your own sensor's values in air and in water in the Serial Monitor and write them into the SOIL_DRY_RAW and SOIL_WET_RAW constants. The code converts these two values to 0–100% with map(). More details are in the soil moisture sensor article.

For light, the LDR (GL5528) from your kit is enough. Wire the LDR as a voltage divider with a 10 kΩ resistor: 5V → LDR → A1 → 10 kΩ → GND. As the light increases, the LDR's resistance drops and the value on A1 rises; the code converts this value to 0–100% too, based on two numbers you measure in the dark and in sunlight. An LDR isn't a precise lux meter, but it reliably answers “has it got dark, is it cloudy, is it sunny?”. If you want more accurate light readings, you can move to an I2C light sensor such as the BH1750. Use the Voltage divider tool for the divider maths and the LDR light sensor article for details.

If the LDR can see the LEDs' light, the system falls into a loop where it switches itself on and off: it gets dark, the LEDs come on, the LDR thinks it's bright and switches them off, then sees darkness again. To prevent this, place the LDR outside the greenhouse, away from the LEDs, and use hysteresis in the code instead of a single threshold. The electronics on top of a capacitive soil sensor aren't waterproof either; don't bury it above the line marked on it, and seal the board's edges with hot glue or clear nail varnish.

Actuators: relay or MOSFET?

An Arduino pin can supply only about 20 mA, while the fan, heater, pump and LED strip draw hundreds of milliamps or even several amps. That's why every load is driven through either a relay or a MOSFET. A relay is a mechanical switch: it turns a load in a completely separate circuit on and off, AC or DC, but it can only switch on or off, it clicks every time, and its contacts have a limited life. A MOSFET is a silent electronic switch; it only works with DC loads, but because it can switch hundreds of times per second, it can set brightness and speed with PWM. In this project we use a 4-channel relay module for the heater, fan and pump, and a MOSFET for the LED strip's brightness.

LoadTypical valueDriverWhy?
12 V PTC heater10–30 W, about 1–2.5 ARelay (IN2)High current, only on/off needed
12 V 80 mm fan0.1–0.2 ARelay (IN1) or MOSFETA relay is simple; a MOSFET also allows PWM speed control
3–6 V mini submersible pump0.1–0.25 A, more at start-upRelay (IN3) or MOSFET with a diodeRuns in short pulses; motor noise must be suppressed
12 V LED strip (5050, 60 LEDs/m)About 1.2 A per metreLogic-level MOSFETPWM brightness control and silent operation
Mini submersible water pump
3–6 V mini submersible pump: it sits in the water tank, draws about 100–200 mA and pushes water through a hose to the pot; never run it dry.

Depending on the hose height, a mini submersible pump delivers roughly 1–1.5 litres per minute, so a 3-second pulse gives the pot 50–75 mL of water. That's why we don't run the pump continuously “until the soil is wet”, but in short pulses instead: water for 3 seconds, wait 60 seconds for the water to soak in, then measure again. If the sensor were read again before the water reached it, the soil would still look dry and the pump would run until the pot overflowed. A pump that runs dry heats up and fails within minutes; the float switch in the tank locks the pump out when the water runs out. Every time the motor stops, its coil produces a reverse voltage spike; a reverse-biased diode across the pump terminals (1N5819 or 1N4007, striped end to the + side) protects both the relay contacts and the Arduino from this noise. Pump selection and driving methods are covered in the mini water pump article.

Use a 12 V PTC heater for heating. The resistance of PTC (positive temperature coefficient) ceramic elements rises as they heat up, so their surface temperature limits itself at a set value; that's much safer than a bare glowing resistance wire. In a plastic enclosure, choose a low-temperature model whose surface is limited to around 80 °C. A 24 W heater draws 2 A at 12 V according to P = V × I; for the first few seconds while it's cold, the current is somewhat higher. For lighting, you can use a red-blue grow-light LED strip or a cool white LED strip; plants use mostly red (around 660 nm) and blue (around 450 nm) light for photosynthesis. Drive the strip with a logic-level MOSFET such as the IRLZ44N: the gate connects to D9 through a 220 Ω resistor and to GND through a 100 kΩ resistor; the strip's + goes to 12 V, its − to the MOSFET's drain, and the source to GND. Standard MOSFET modules such as the IRF520 don't fully turn on with a 5 V gate voltage and heat up with loads of several amps; details are in the using a MOSFET with Arduino article. You can use the Ohm's Law tool to calculate current and power.

The heater is the only real fire risk in the greenhouse. Mount it on a metal base where it can't touch the plastic walls, add a thermal fuse in series with the heater on the 12 V line, touching the plate it's mounted on, and test the system for hours before leaving it unattended. The fuse should open a little above the heater's limit temperature (for example 92 °C for an 80 °C PTC); a lower rating will blow for no reason during normal operation. Seedling heat mats and larger heaters usually run on mains voltage, and mains is lethal. Don't use mains in a model greenhouse; a 12 V PTC heater is enough. If you really need a mains-powered device, an electrician or a competent adult must do the wiring, and the relay board must sit in a closed, insulated box. Safe wiring rules are in the What is a relay module? article.

Power plan: 12 V and 5 V rails

The most common problems in smart greenhouses (“the Arduino resets when the pump runs”, “strange characters appear on the LCD”) come from the power supply. The cleanest solution is to take two rails from a single 12 V 5 A adapter. The 12 V rail feeds the heater, fans and LED strip directly, and also goes to the Arduino Uno's DC jack. The same 12 V also feeds an LM2596 buck converter set to 5 V; the relay coils, the pump and the LCD run from this 5 V rail. If you remove the JD-VCC jumper on the relay module, the coil current comes entirely from the LM2596 and the Arduino side only lights the optocouplers' LEDs. Power the sensors (DHT22, soil moisture sensor and LDR divider) from the Arduino's own 5V pin: analogRead() measures relative to the board's own 5 V, so powering the analogue sensors from the same voltage keeps the readings stable. Always connect the GNDs of the adapter, the LM2596 and the Arduino together.

LoadRailApprox. current
PTC heater (24 W)12 V2 A (more at the first moment when cold)
LED strip (1 m)12 V1–1.2 A
Two 80 mm fans12 V0.2–0.4 A
Arduino Uno and sensors (via the DC jack)12 VAbout 0.06–0.08 A
LM2596 input (while the 5 V side draws 1 A)12 VAbout 0.5 A
Relay module (three relays energised)5 V0.2–0.25 A
Mini submersible pump5 V0.1–0.25 A (more at start-up)
I2C LCD (backlight on)5 V20–30 mA

Even if everything runs at once, the total current is around 4–4.5 A, so a 12 V 5 A (60 W) adapter leaves enough headroom. In practice the heater and the temperature fan never run at the same time, because there's a wide gap between their thresholds. Before connecting the LM2596 to the circuit, measure its output with a multimeter and set it to 5.0 V with the trimmer on the board; out of the box it may pass almost the full 12 V straight through. You can power the Arduino from USB while uploading code and from the DC jack in the field; having both connected at the same time is fine for the Uno. Details are in the Arduino power supply guide.

Wiring and pin table

The table below shows the pins used by the three programs in this guide. On the Uno we connected the MOSFET that drives the LED strip to D9, which supports PWM; on the Uno, PWM is only available on D3, D5, D6, D9, D10 and D11. The float switch and the relays don't need PWM. Because the LCD uses only the I2C bus (A4 and A5), it takes very few pins; on the Uno, A2, A3 and a few digital pins remain free for a second soil sensor or a button. On the ESP32 we connected the analogue sensors to ADC1 pins (GPIO 34 and 35), which keep working while Wi-Fi is on. You can see your board's pin layout in the Pinout tool.

ConnectionArduino UnoESP32Raspberry Pi Pico
DHT22 DATAD2GPIO 4GP15
Float switch (other lead to GND)D3GPIO 13GP14
Relay IN3: pumpD4GPIO 25GP18
Relay IN1: fanD7GPIO 26GP16
Relay IN2: heaterD8GPIO 27GP17
MOSFET gate (via 220 Ω): LEDD9GPIO 18GP19
Soil moisture sensor AOUTA0GPIO 34GP26
LDR divider midpointA1GPIO 35 (divider from 3V3)GP27 (divider from 3V3)
I2C LCD SDA / SCLA4 / A5GPIO 21 / 22GP4 / GP5
Sensor supply5V3V33V3
The ESP32 and Pico pins run at 3.3 V and are not 5 V tolerant. On these boards, power the DHT22, the soil moisture sensor and the LDR divider from the 3V3 pin so that no more than 3.3 V reaches the analogue pin. An active-LOW 5 V relay module sometimes doesn't fully release with a 3.3 V HIGH signal; if a relay stays energised, power VCC from 3.3 V and JD-VCC from 5 V on a module with JD-VCC, or buy a module with 3.3 V triggering. For the LED strip, choose a MOSFET that turns fully on at a 3.3 V gate voltage (for example the IRLB8721).

Thresholds and hysteresis: why do relays chatter?

Imagine controlling the fan with a single threshold: “run it if the temperature is above 30 °C, otherwise stop it”. As the temperature wobbles between 29.9 and 30.1 °C (which happens a lot because of sensor noise), the relay switches on and off with every reading. Both the relay contacts and the fan motor wear out, and with the heater, the wiring and the adapter take an unnecessary hit at every switch-on. The solution is hysteresis: you use two separate thresholds for switching on and off. The fan starts at 30 °C but only stops when the temperature has fallen to 27 °C. In the 3-degree dead band in between, the system keeps its last state. Room thermostats for boilers and fridges work on the same principle.

ControlSwitches onSwitches offDead band
Fan (temperature)≥ 30 °C≤ 27 °C3 °C
Fan (humidity)≥ 85%≤ 75%10%
Heater≤ 16 °C≥ 19 °C3 °C
WateringSoil ≤ 35%Soil ≥ 50%15%
LED lightingLight ≤ 25%Light ≥ 35% or after 4 hours10%

The width of the dead band is a trade-off: too narrow and the relay still switches often; too wide and the temperature drifts too far from the target. Around 2–3 °C for temperature, 8–10% for humidity and 10–15% for soil moisture is a good start. We also left a wide gap between 19 °C, where the heater switches off, and 30 °C, where the fan starts, so the heater and the temperature fan never work against each other. In the code, hysteresis lives in a small hysteresis() function so you don't have to write it again for every control: if the on threshold is greater than the off threshold it means “switch on when rising” (fan); if it's smaller, “switch on when falling” (heater, watering, LEDs). If you also want to smooth out spikes in the sensor data, have a look at the sensor data filtering article.

Code structure: measure, decide, act, display

The most important rule in the greenhouse code is that no task waits for another. If you waited a whole minute with delay(60000) after the pump, the fan wouldn't run and the display wouldn't update during that time. That's why the code is made of small functions that measure time with millis(), each responsible for a single job; loop() calls them in turn thousands of times per second, and each function only does something when its time has come. Watering is a small three-state state machine: wait (IDLE), water (PUMPING) and let the water soak in (SOAKING). All settings sit as constants at the top of the code, so you don't have to dig through the code to change a threshold. You'll find the reasoning behind this approach in the millis() and delay() and state machines on Arduino articles. The code also includes these safety measures:

  • Safe start-up: The “off” value is written to the relay pins before pinMode() is called, so the relays don't click on even for an instant while the Arduino boots.
  • Sensor failure: If the DHT22 can't be read three times in a row, the code switches to a safe state: it turns the heater off, runs the fan and shows “Sensor hatasi” (sensor error) on the LCD. A system that doesn't know the temperature must never leave the heater on.
  • Tank protection: When the float switch says the tank is empty, the pump doesn't start at all, and if it's running it stops immediately.
  • Overflow protection: If soil moisture doesn't reach 50% after six pulses in one watering cycle, the sensor may have come out of the soil or the hose may have come off; the code stops watering and shows “Sulama hatasi” (watering fault). The warning clears by itself once the soil is wet again.
  • Data logging: Every 10 seconds the code writes the readings to the Serial Monitor as a comma-separated (CSV) line; you can copy these lines and draw charts in Excel.

Step-by-step assembly and testing

  1. Prepare the enclosure: cut the holes for the fan, air inlet, hose and cables, and place the electronics box outside the greenhouse.
  2. Set up the power rails: connect the 12 V adapter to a screw terminal, set the LM2596 to 5.0 V with a multimeter and join all the GNDs.
  3. Connect the I2C LCD and print some text on it. If the display shows nothing, try the scanner sketch and the contrast trimmer from the I2C LCD article.
  4. Add the sensors one at a time and test them in the Serial Monitor: note the DHT22 readings, the soil sensor's raw value in air and in water, and the LDR's value in the dark and in sunlight.
  5. Test the relays first without loads, watching their LEDs and listening for the click. Then connect the fan, the pump (with a full tank) and finally the heater.
  6. Connect the LED strip through the MOSFET and change the analogWrite() value to see the brightness change.
  7. Upload the main code, enter your calibration values into the constants and run the system for a few hours under supervision: any wrong watering, a relay that keeps clicking, or a part that gets too hot?
Don't try to wire everything at once and get it all working in one go. When six modules don't work together, finding the fault can take hours; modules tested one at a time usually work on the first try once combined.

Complete code for the Arduino Uno

To compile the code, install Adafruit's DHT sensor library (together with its dependency, Adafruit Unified Sensor) and Frank de Brabander's LiquidCrystal I2C library from the Library Manager; Wire comes with the Arduino IDE. The LCD's address is usually 0x27, and 0x3F on some modules. Before uploading, write the calibration values you measured into the SOIL_DRY_RAW, SOIL_WET_RAW, LDR_DARK_RAW and LDR_BRIGHT_RAW constants. If your float switch opens rather than closes with water, set TANK_OK to HIGH; if your relay module is active HIGH, swap the RELAY_ON and RELAY_OFF values.

C++ (Arduino IDE)
1#include <Wire.h>
2#include <LiquidCrystal_I2C.h>
3#include <DHT.h>
4
5// Pinler / Pins
6const byte DHT_PIN = 2, FLOAT_PIN = 3, PUMP_RELAY = 4, FAN_RELAY = 7, HEATER_RELAY = 8;
7const byte LED_PIN = 9;                          // MOSFET gate (PWM)
8const byte SOIL_PIN = A0, LDR_PIN = A1;
9
10// Kalibrasyon: kendi ölçümlerini yaz / Calibration: write your own readings
11const int SOIL_DRY_RAW = 520, SOIL_WET_RAW = 260;   // havada ve suda / in air and in water
12const int LDR_DARK_RAW = 60, LDR_BRIGHT_RAW = 900;  // karanlık ve güneş / dark and sunlight
13
14// Eşikler (açma, kapama) / Thresholds (on, off)
15const float FAN_ON_C = 30, FAN_OFF_C = 27;       // sıcaklıkta fan / fan on heat
16const float HUM_ON = 85, HUM_OFF = 75;           // nemde fan / fan on humidity
17const float HEAT_ON_C = 16, HEAT_OFF_C = 19;     // ısıtıcı / heater
18const float SOIL_DRY = 35, SOIL_OK = 50;         // sulama başla, dur / start, stop watering
19const float DARK_ON = 25, DARK_OFF = 35;         // ışık % / light %
20
21// Zamanlar / Timing
22const unsigned long READ_MS = 2500;              // DHT22: en az 2 sn / at least 2 s
23const unsigned long PUMP_MS = 3000;              // tek sulama darbesi / one watering pulse
24const unsigned long SOAK_MS = 60000;             // suyun yayılması / let the water soak in
25const byte MAX_PULSES = 6;                       // fazlası arıza / more means a fault
26const unsigned long EXTRA_LIGHT_MS = 4UL * 3600000UL;  // günü 4 saat uzat / extend the day by 4 h
27const byte LED_LEVEL = 200;                      // LED parlaklığı 0-255 / LED brightness 0-255
28const byte RELAY_ON = LOW, RELAY_OFF = HIGH;     // aktif LOW röle / active-LOW relay
29const byte TANK_OK = LOW;                        // şamandıra suyla kapanır / float closes with water
30
31DHT dht(DHT_PIN, DHT22);
32LiquidCrystal_I2C lcd(0x27, 16, 2);
33
34float tempC = NAN, hum = NAN, soil = 0, light = 0;
35byte dhtErrors = 0, pulses = 0, page = 0;
36bool fanByTemp = false, fanByHum = false, fanOn = false, heaterOn = false;
37bool needWater = false, pumpFault = false, isDark = false, ledOn = false, tankOk = true;
38unsigned long lastRead = 0, lastLcd = 0, lastLog = 0, pumpAt = 0, darkSince = 0;
39
40enum PumpState { IDLE, PUMPING, SOAKING };
41PumpState pumpState = IDLE;
42
43// onAt > offAt: yükselince aç (fan) / turn on when rising (fan)
44// onAt < offAt: düşünce aç (ısıtıcı) / turn on when falling (heater)
45bool hysteresis(bool state, float value, float onAt, float offAt) {
46  if (onAt > offAt) {
47    if (value >= onAt) return true;
48    if (value <= offAt) return false;
49  } else {
50    if (value <= onAt) return true;
51    if (value >= offAt) return false;
52  }
53  return state;                                  // ölü bölgede durumu koru / keep state in the dead band
54}
55
56void setRelay(byte pin, bool on) {
57  digitalWrite(pin, on ? RELAY_ON : RELAY_OFF);
58}
59
60float readPercent(byte pin, int raw0, int raw100) {
61  long sum = 0;
62  for (byte i = 0; i < 10; i++) sum += analogRead(pin);   // 10 ölçüm ortalaması / average of 10
63  long pct = map(sum / 10, raw0, raw100, 0, 100);
64  return constrain(pct, 0, 100);
65}
66
67void setup() {
68  Serial.begin(9600);
69  setRelay(PUMP_RELAY, false);   pinMode(PUMP_RELAY, OUTPUT);    // açılışta kapalı / off at boot
70  setRelay(FAN_RELAY, false);    pinMode(FAN_RELAY, OUTPUT);
71  setRelay(HEATER_RELAY, false); pinMode(HEATER_RELAY, OUTPUT);
72  pinMode(LED_PIN, OUTPUT);
73  pinMode(FLOAT_PIN, INPUT_PULLUP);
74  dht.begin();
75  lcd.init();
76  lcd.backlight();
77  lcd.print(F("BlueGrays Sera"));
78  Serial.println(F("sn,sicaklik,nem,toprak,isik,fan,isitici,pompa,led"));  // CSV başlığı / CSV header
79  delay(2000);                                   // DHT22 açılış süresi / DHT22 start-up time
80  readSensors();
81  lastRead = millis();
82}
83
84void loop() {
85  unsigned long now = millis();
86  if (now - lastRead >= READ_MS) {
87    lastRead = now;
88    readSensors();
89  }
90  controlClimate();
91  controlLight(now);
92  controlPump(now);
93  updateLcd(now);
94  logCsv(now);
95}
96
97// 1) Ölç / Measure
98void readSensors() {
99  float t = dht.readTemperature();
100  float h = dht.readHumidity();
101  if (isnan(t) || isnan(h)) {
102    if (dhtErrors < 255) dhtErrors++;            // okuma hatası / read error
103  } else {
104    tempC = t;
105    hum = h;
106    dhtErrors = 0;
107  }
108  soil = readPercent(SOIL_PIN, SOIL_DRY_RAW, SOIL_WET_RAW);
109  light = readPercent(LDR_PIN, LDR_DARK_RAW, LDR_BRIGHT_RAW);
110  tankOk = digitalRead(FLOAT_PIN) == TANK_OK;
111}
112
113// 2) Fan ve ısıtıcı / Fan and heater
114void controlClimate() {
115  if (dhtErrors >= 3) {                          // sensör yok: güvenli durum / no sensor: safe state
116    heaterOn = false;
117    fanByTemp = true;
118  } else {
119    fanByTemp = hysteresis(fanByTemp, tempC, FAN_ON_C, FAN_OFF_C);
120    fanByHum = hysteresis(fanByHum, hum, HUM_ON, HUM_OFF);
121    heaterOn = hysteresis(heaterOn, tempC, HEAT_ON_C, HEAT_OFF_C);
122  }
123  fanOn = fanByTemp || fanByHum;
124  setRelay(FAN_RELAY, fanOn);
125  setRelay(HEATER_RELAY, heaterOn);
126}
127
128// 3) LED aydınlatma / LED grow light
129void controlLight(unsigned long now) {
130  bool wasDark = isDark;
131  isDark = hysteresis(isDark, light, DARK_ON, DARK_OFF);
132  if (isDark && !wasDark) darkSince = now;      // akşam oldu / dusk detected
133  ledOn = isDark && (now - darkSince < EXTRA_LIGHT_MS);
134  analogWrite(LED_PIN, ledOn ? LED_LEVEL : 0);
135}
136
137// 4) Sulama: darbe + bekleme / Watering: pulse + soak
138void controlPump(unsigned long now) {
139  needWater = hysteresis(needWater, soil, SOIL_DRY, SOIL_OK);
140  if (!needWater) {
141    pulses = 0;
142    pumpFault = false;                           // toprak ıslandı / soil is wet again
143  }
144  switch (pumpState) {
145    case IDLE:
146      if (needWater && tankOk && !pumpFault) {
147        if (pulses >= MAX_PULSES) {
148          pumpFault = true;                      // nem yükselmiyor / moisture is not rising
149        } else {
150          pumpState = PUMPING;
151          pumpAt = now;
152          pulses++;
153        }
154      }
155      break;
156    case PUMPING:
157      if (now - pumpAt >= PUMP_MS || !tankOk) {
158        pumpState = SOAKING;
159        pumpAt = now;
160      }
161      break;
162    case SOAKING:
163      if (now - pumpAt >= SOAK_MS) pumpState = IDLE;
164      break;
165  }
166  setRelay(PUMP_RELAY, pumpState == PUMPING);
167}
168
169// 5) LCD: üç sayfa / three pages
170void updateLcd(unsigned long now) {
171  if (now - lastLcd < 3000) return;
172  lastLcd = now;
173  page = (page + 1) % 3;
174  lcd.clear();
175  if (page == 0) {
176    lcd.print(F("Sicaklik: "));
177    if (isnan(tempC)) lcd.print(F("--")); else lcd.print(tempC, 1);
178    lcd.print('C');
179    lcd.setCursor(0, 1);
180    lcd.print(F("Hava nemi: %"));
181    if (isnan(hum)) lcd.print(F("--")); else lcd.print(hum, 0);
182  } else if (page == 1) {
183    lcd.print(F("Toprak: %"));
184    lcd.print(soil, 0);
185    lcd.setCursor(0, 1);
186    lcd.print(F("Isik: %"));
187    lcd.print(light, 0);
188  } else {
189    lcd.print(F("F:"));  lcd.print(fanOn);
190    lcd.print(F(" I:")); lcd.print(heaterOn);
191    lcd.print(F(" P:")); lcd.print(pumpState == PUMPING);
192    lcd.print(F(" L:")); lcd.print(ledOn);
193    lcd.setCursor(0, 1);
194    if (dhtErrors >= 3) lcd.print(F("Sensor hatasi"));
195    else if (!tankOk) lcd.print(F("Depo bos!"));
196    else if (pumpFault) lcd.print(F("Sulama hatasi"));
197    else lcd.print(F("Sistem normal"));
198  }
199}
200
201// 6) Seri port CSV kaydı / Serial CSV log
202void logCsv(unsigned long now) {
203  if (now - lastLog < 10000) return;
204  lastLog = now;
205  Serial.print(now / 1000);  Serial.print(',');
206  Serial.print(tempC, 1);    Serial.print(',');
207  Serial.print(hum, 0);      Serial.print(',');
208  Serial.print(soil, 0);     Serial.print(',');
209  Serial.print(light, 0);    Serial.print(',');
210  Serial.print(fanOn);       Serial.print(',');
211  Serial.print(heaterOn);    Serial.print(',');
212  Serial.print(pumpState == PUMPING); Serial.print(',');
213  Serial.println(ledOn);
214}
FunctionJobHow often?
readSensors()Reads the DHT22, soil moisture, LDR and float switch; converts analogue values to 0–100% using the average of 10 readingsEvery 2.5 seconds
controlClimate()Drives the fan with temperature or humidity hysteresis and the heater with temperature hysteresis; switches to a safe state if the sensor failsEvery loop
controlLight()Turns the LEDs on with PWM when it gets dark and switches them off after at most 4 hoursEvery loop
controlPump()Watering state machine: water 3 s, wait 60 s, measure again; with tank and overflow protectionEvery loop
updateLcd()Cycles through three pages: air readings, soil moisture and light, device statesEvery 3 seconds
logCsv()Writes readings and states to the Serial Monitor as a CSV lineEvery 10 seconds

The first LCD page shows temperature and air humidity, the second soil moisture and light level, and the third the state of the devices: “F:1 I:0 P:0 L:1” means the fan is running, the heater (I = ısıtıcı) and pump are off and the LEDs are on. The bottom line shows one of the messages “Sistem normal” (all OK), “Depo bos!” (tank empty), “Sulama hatasi” (watering fault) or “Sensor hatasi” (sensor error); you can easily translate these strings in the code. Compiled, this code uses about 31% of the Uno's program memory and 25% of its RAM, so there's plenty of room to add a second soil sensor, a buzzer or an RTC clock module. Because the Uno has no real clock, the LED rule works as “at most 4 hours after dusk”, which is a good way to extend short winter days. If you want the LEDs to come on at set times each day, add an RTC module or move to the ESP32 version below.

Monitoring and watering from your phone with an ESP32

ESP32 development board
ESP32: its Wi-Fi lets you monitor the greenhouse from your phone, and the time it gets from the internet lets it run the LEDs by the real clock.

The Uno has no Wi-Fi. If you want to see the greenhouse's status on your phone and water remotely, swap the brain for an ESP32. The code below moves the same control logic to the ESP32 and starts a small web server. With your phone on the same Wi-Fi network, open the IP address shown in the Serial Monitor in your browser and you'll see the temperature, humidity, soil moisture, light, tank status and whether each device is on or off; the page refreshes itself every 10 seconds. The Water now button runs the pump once for 3 seconds if there's water in the tank. At start-up the ESP32 gets the time from the internet (NTP), so the LEDs only come on between 06:00 and 20:00 and only when it's dark outside, and the plants rest at night.

C++ (Arduino IDE)
1#include <WiFi.h>
2#include <WebServer.h>
3#include <DHT.h>
4#include <time.h>
5
6const char* ssid = "AgAdi";                      // Wi-Fi adın / your Wi-Fi name
7const char* password = "Sifre123";               // Wi-Fi şifren / your Wi-Fi password
8
9// Pinler / Pins
10const int DHT_PIN = 4, FLOAT_PIN = 13, PUMP_RELAY = 25, FAN_RELAY = 26, HEATER_RELAY = 27;
11const int LED_PIN = 18;                          // MOSFET gate (PWM)
12const int SOIL_PIN = 34, LDR_PIN = 35;           // ADC1: Wi-Fi açıkken çalışır / works with Wi-Fi on
13
14// Kalibrasyon (0-4095): kendin ölç / Calibration (0-4095): measure your own
15const int SOIL_DRY_RAW = 2800, SOIL_WET_RAW = 1200;
16const int LDR_DARK_RAW = 200, LDR_BRIGHT_RAW = 3800;
17
18// Eşikler (açma, kapama) / Thresholds (on, off)
19const float FAN_ON_C = 30, FAN_OFF_C = 27, HUM_ON = 85, HUM_OFF = 75;
20const float HEAT_ON_C = 16, HEAT_OFF_C = 19, SOIL_DRY = 35, SOIL_OK = 50;
21const float DARK_ON = 25, DARK_OFF = 35;
22const int LIGHT_FROM = 6, LIGHT_TO = 20;         // LED yalnız 06:00-20:00 / LEDs only 06:00-20:00
23
24const unsigned long READ_MS = 2500, PUMP_MS = 3000, SOAK_MS = 60000;
25const unsigned long EXTRA_LIGHT_MS = 4UL * 3600000UL;  // saat yoksa yedek kural / fallback without a clock
26const int MAX_PULSES = 6, LED_LEVEL = 200;
27const int RELAY_ON = LOW, RELAY_OFF = HIGH;      // aktif LOW röle / active-LOW relay
28const int TANK_OK = LOW;                         // şamandıra suyla kapanır / float closes with water
29
30DHT dht(DHT_PIN, DHT22);
31WebServer server(80);
32
33float tempC = NAN, hum = NAN, soil = 0, light = 0;
34int dhtErrors = 0, pulses = 0;
35bool fanByTemp = false, fanByHum = false, fanOn = false, heaterOn = false;
36bool needWater = false, pumpFault = false, isDark = false, ledOn = false, tankOk = true;
37unsigned long lastRead = 0, pumpAt = 0, darkSince = 0;
38
39enum PumpState { IDLE, PUMPING, SOAKING };
40PumpState pumpState = IDLE;
41
42bool hysteresis(bool state, float value, float onAt, float offAt) {
43  if (onAt > offAt) {                            // yükselince aç / turn on when rising
44    if (value >= onAt) return true;
45    if (value <= offAt) return false;
46  } else {                                       // düşünce aç / turn on when falling
47    if (value <= onAt) return true;
48    if (value >= offAt) return false;
49  }
50  return state;
51}
52
53void setRelay(int pin, bool on) {
54  digitalWrite(pin, on ? RELAY_ON : RELAY_OFF);
55}
56
57float readPercent(int pin, int raw0, int raw100) {
58  long sum = 0;
59  for (int i = 0; i < 16; i++) sum += analogRead(pin);    // 16 ölçüm ortalaması / average of 16
60  long pct = map(sum / 16, raw0, raw100, 0, 100);
61  return constrain(pct, 0, 100);
62}
63
64int hourNow() {
65  struct tm t;
66  if (!getLocalTime(&t, 0)) return -1;           // saat henüz yok / time not set yet
67  return t.tm_hour;
68}
69
70void readSensors() {
71  float t = dht.readTemperature();
72  float h = dht.readHumidity();
73  if (isnan(t) || isnan(h)) {
74    dhtErrors++;
75  } else {
76    tempC = t;
77    hum = h;
78    dhtErrors = 0;
79  }
80  soil = readPercent(SOIL_PIN, SOIL_DRY_RAW, SOIL_WET_RAW);
81  light = readPercent(LDR_PIN, LDR_DARK_RAW, LDR_BRIGHT_RAW);
82  tankOk = digitalRead(FLOAT_PIN) == TANK_OK;
83}
84
85void controlClimate() {
86  if (dhtErrors >= 3) {                          // sensör yok: güvenli durum / no sensor: safe state
87    heaterOn = false;
88    fanByTemp = true;
89  } else {
90    fanByTemp = hysteresis(fanByTemp, tempC, FAN_ON_C, FAN_OFF_C);
91    fanByHum = hysteresis(fanByHum, hum, HUM_ON, HUM_OFF);
92    heaterOn = hysteresis(heaterOn, tempC, HEAT_ON_C, HEAT_OFF_C);
93  }
94  fanOn = fanByTemp || fanByHum;
95  setRelay(FAN_RELAY, fanOn);
96  setRelay(HEATER_RELAY, heaterOn);
97}
98
99void controlLight(unsigned long now) {
100  bool wasDark = isDark;
101  isDark = hysteresis(isDark, light, DARK_ON, DARK_OFF);
102  if (isDark && !wasDark) darkSince = now;
103  int h = hourNow();
104  if (h >= 0) ledOn = isDark && h >= LIGHT_FROM && h < LIGHT_TO;   // saat kuralı / clock rule
105  else ledOn = isDark && (now - darkSince < EXTRA_LIGHT_MS);       // yedek kural / fallback rule
106  analogWrite(LED_PIN, ledOn ? LED_LEVEL : 0);
107}
108
109void controlPump(unsigned long now) {
110  needWater = hysteresis(needWater, soil, SOIL_DRY, SOIL_OK);
111  if (!needWater) {
112    pulses = 0;
113    pumpFault = false;
114  }
115  if (pumpState == IDLE && needWater && tankOk && !pumpFault) {
116    if (pulses >= MAX_PULSES) {
117      pumpFault = true;                          // nem yükselmiyor / moisture is not rising
118    } else {
119      pumpState = PUMPING;
120      pumpAt = now;
121      pulses++;
122    }
123  } else if (pumpState == PUMPING && (now - pumpAt >= PUMP_MS || !tankOk)) {
124    pumpState = SOAKING;
125    pumpAt = now;
126  } else if (pumpState == SOAKING && now - pumpAt >= SOAK_MS) {
127    pumpState = IDLE;
128  }
129  setRelay(PUMP_RELAY, pumpState == PUMPING);
130}
131
132String row(const char* name, const String& value) {
133  return String("<tr><td>") + name + "</td><td><b>" + value + "</b></td></tr>";
134}
135
136String onOff(bool on) {
137  return on ? "ON" : "OFF";
138}
139
140void handleRoot() {
141  String html = "<!DOCTYPE html><html><head><meta charset='utf-8'>"
142                "<meta name='viewport' content='width=device-width,initial-scale=1'>"
143                "<meta http-equiv='refresh' content='10'><title>BlueGrays Sera</title>"
144                "<style>body{font-family:sans-serif;max-width:420px;margin:auto;padding:12px}"
145                "td{padding:8px;border-bottom:1px solid #ddd}"
146                "a{display:block;margin-top:16px;padding:16px;border-radius:10px;background:#2e7d32;"
147                "color:#fff;text-align:center;text-decoration:none;font-size:18px}</style>"
148                "</head><body><h2>BlueGrays Sera</h2><table>";
149  html += row("Sıcaklık / Temperature", String(tempC, 1) + " °C");
150  html += row("Hava nemi / Humidity", "%" + String(hum, 0));
151  html += row("Toprak nemi / Soil", "%" + String(soil, 0));
152  html += row("Işık / Light", "%" + String(light, 0));
153  html += row("Fan", onOff(fanOn));
154  html += row("Isıtıcı / Heater", onOff(heaterOn));
155  html += row("Pompa / Pump", onOff(pumpState == PUMPING));
156  html += row("LED", onOff(ledOn));
157  html += row("Depo / Tank", tankOk ? "OK" : "BOŞ / EMPTY");
158  if (pumpFault) html += row("Uyarı / Warning", "Sulama hatası / Watering fault");
159  html += "</table><a href='/water'>Şimdi sula / Water now</a></body></html>";
160  server.send(200, "text/html", html);
161}
162
163void handleWater() {                             // elle tek darbe / one manual pulse
164  if (pumpState == IDLE && tankOk) {
165    pumpState = PUMPING;
166    pumpAt = millis();
167  }
168  server.sendHeader("Location", "/");
169  server.send(303);
170}
171
172void setup() {
173  Serial.begin(115200);
174  setRelay(PUMP_RELAY, false);   pinMode(PUMP_RELAY, OUTPUT);
175  setRelay(FAN_RELAY, false);    pinMode(FAN_RELAY, OUTPUT);
176  setRelay(HEATER_RELAY, false); pinMode(HEATER_RELAY, OUTPUT);
177  pinMode(LED_PIN, OUTPUT);
178  pinMode(FLOAT_PIN, INPUT_PULLUP);
179  dht.begin();
180  WiFi.begin(ssid, password);
181  while (WiFi.status() != WL_CONNECTED) {
182    delay(500);
183    Serial.print('.');
184  }
185  Serial.println();
186  Serial.println(WiFi.localIP());                // bu adresi telefonda aç / open this address on your phone
187  configTime(3 * 3600, 0, "pool.ntp.org");       // Türkiye UTC+3; kendi saat dilimini yaz / set your own time zone
188  server.on("/", handleRoot);
189  server.on("/water", handleWater);
190  server.begin();
191  readSensors();
192  lastRead = millis();
193}
194
195void loop() {
196  server.handleClient();
197  unsigned long now = millis();
198  if (now - lastRead >= READ_MS) {
199    lastRead = now;
200    readSensors();
201  }
202  controlClimate();
203  controlLight(now);
204  controlPump(now);
205}

Here's what changes compared with the Uno code: the ESP32's ADC is 12-bit and returns values from 0 to 4095, so you need to redo the calibration; since you power the sensors from 3V3, the raw values will be different too. The time zone is set in the configTime() line; we wrote 3 × 3600 seconds for Turkey (UTC+3). For your own zone with daylight saving, replace that line with configTzTime() and a POSIX time-zone string, for example configTzTime("GMT0BST,M3.5.0/1,M10.5.0", "pool.ntp.org") for the UK. If there's no internet or the time hasn't been fetched yet, the LED rule falls back to the Uno's “at most 4 hours” rule. The page has no password, so anyone on your home network could start the pump; adding a simple password with the WebServer library's authenticate() function is a good habit. If you want an LCD on the ESP32 as well, you can copy the updateLcd() function from the Uno code as it is; SDA is GPIO 21 and SCL is GPIO 22.

If there's no Wi-Fi at school, replace the WiFi.begin() line and the waiting loop after it with WiFi.softAP("BlueGrays-Sera", "12345678") so the ESP32 creates its own network; your phone joins that network and opens 192.168.4.1. In that case there's no internet, so the time can't be fetched and the LEDs use the fallback rule.
MethodAccess from outside the homeDifficultyBest for
ESP32 web server (the code in this guide)No, same network onlyMediumSchool presentations, demos without internet
Blynk IoTYesEasyReady-made gauges, charts and notifications
Telegram botYesMedium“Tank empty” and “temperature too high” alerts
Google SheetsYesMediumWeeks of data logging and charts
MQTT and Home AssistantYes, needs setupAdvancedReal home and garden automation

For a school presentation, the ESP32 web server is the safest choice: it doesn't depend on a cloud service and works on its own network even if the hall has no internet. If you also want to check in from outside the home, Blynk IoT with its ready-made gauges and charts is the easiest route. To get a message on your phone when the tank runs dry or the temperature gets dangerous, see the ESP32 Telegram bot guide; to collect weeks of data for your report, ESP32 with Google Sheets; and to connect the greenhouse to real home automation, MQTT and Home Assistant.

MicroPython version for the Raspberry Pi Pico

You can also run the same greenhouse with a Raspberry Pi Pico in MicroPython. The Pico's MicroPython firmware comes with a DHT driver built in, so you don't need to copy any extra files. The analogue inputs are only GP26, GP27 and GP28, and read_u16() returns values from 0 to 65535; measure your calibration numbers accordingly. Because the Pico runs at 3.3 V, power the sensors from 3V3 and the relay module from VBUS (USB's 5 V) or the LM2596 rail. The code below contains all the fan, heater, LED and watering rules and prints the values to Thonny's shell. If you save the file on the Pico as main.py, the board runs even when it's not connected to a computer. If you're using the Wi-Fi-enabled Pico W, you can add the method from the Pico W web server article to monitor the values from your phone.

MicroPython
1from machine import Pin, ADC, PWM
2import dht
3import time
4
5sensor = dht.DHT22(Pin(15))              # GP15: DHT22 DATA
6soil_adc = ADC(26)                       # GP26: toprak nem AOUT / soil moisture AOUT
7ldr_adc = ADC(27)                        # GP27: LDR bölücü (3V3) / LDR divider (3V3)
8tank = Pin(14, Pin.IN, Pin.PULL_UP)      # şamandıra: suyla 0 / float switch: 0 with water
9fan = Pin(16, Pin.OUT, value=1)          # aktif LOW röleler kapalı başlar / active-LOW relays start off
10heater = Pin(17, Pin.OUT, value=1)
11pump = Pin(18, Pin.OUT, value=1)
12led = PWM(Pin(19))                       # MOSFET gate
13led.freq(1000)
14
15SOIL_DRY_RAW, SOIL_WET_RAW = 50000, 22000    # kendin ölç / measure your own
16LDR_DARK_RAW, LDR_BRIGHT_RAW = 2000, 60000
17EXTRA_LIGHT_MS = 4 * 3600 * 1000             # günü 4 saat uzat / extend the day by 4 h
18
19
20def percent(adc, raw0, raw100):
21    raw = sum(adc.read_u16() for _ in range(10)) // 10
22    pct = (raw - raw0) * 100 // (raw100 - raw0)
23    return max(0, min(100, pct))
24
25
26def hysteresis(state, value, on_at, off_at):
27    if on_at > off_at:                   # yükselince aç / turn on when rising
28        if value >= on_at:
29            return True
30        if value <= off_at:
31            return False
32    else:                                # düşünce aç / turn on when falling
33        if value <= on_at:
34            return True
35        if value >= off_at:
36            return False
37    return state                         # ölü bölge / dead band
38
39
40def relay(pin, on):
41    pin.value(0 if on else 1)
42
43
44t = h = None
45errors = pulses = 0
46fan_t = fan_h = heat_on = need_water = dark = led_on = False
47pump_state, pump_at, dark_since = "idle", 0, 0
48soil = light = 0
49tank_ok = True
50time.sleep(2)                            # DHT22 açılış süresi / DHT22 start-up time
51last_read = time.ticks_add(time.ticks_ms(), -2500)
52
53while True:
54    now = time.ticks_ms()
55    if time.ticks_diff(now, last_read) >= 2500:      # 2,5 sn'de bir ölç / measure every 2.5 s
56        last_read = now
57        try:
58            sensor.measure()
59            t, h = sensor.temperature(), sensor.humidity()
60            errors = 0
61        except OSError:
62            errors += 1                  # okuma hatası / read error
63        soil = percent(soil_adc, SOIL_DRY_RAW, SOIL_WET_RAW)
64        light = percent(ldr_adc, LDR_DARK_RAW, LDR_BRIGHT_RAW)
65        tank_ok = tank.value() == 0
66
67        if errors >= 3 or t is None:     # güvenli durum / safe state
68            heat_on, fan_t = False, True
69        else:
70            fan_t = hysteresis(fan_t, t, 30, 27)
71            fan_h = hysteresis(fan_h, h, 85, 75)
72            heat_on = hysteresis(heat_on, t, 16, 19)
73        relay(fan, fan_t or fan_h)
74        relay(heater, heat_on)
75
76        was_dark = dark
77        dark = hysteresis(dark, light, 25, 35)
78        if dark and not was_dark:
79            dark_since = now             # akşam oldu / dusk detected
80        led_on = dark and time.ticks_diff(now, dark_since) < EXTRA_LIGHT_MS
81        led.duty_u16(50000 if led_on else 0)
82
83        need_water = hysteresis(need_water, soil, 35, 50)
84        if not need_water:
85            pulses = 0
86        print("T", t, "N", h, "Toprak", soil, "Isik", light, "Pompa", pump_state,
87              "Depo OK" if tank_ok else "Depo BOS", "HATA" if pulses >= 6 else "")
88
89    # Sulama: 3 sn darbe, 60 sn bekleme / Watering: 3 s pulse, 60 s soak
90    if pump_state == "idle" and need_water and tank_ok and pulses < 6:
91        pump_state, pump_at = "pump", now
92        pulses += 1
93    elif pump_state == "pump" and (time.ticks_diff(now, pump_at) >= 3000 or not tank_ok):
94        pump_state, pump_at = "soak", now
95    elif pump_state == "soak" and time.ticks_diff(now, pump_at) >= 60000:
96        pump_state = "idle"
97    relay(pump, pump_state == "pump")
98    time.sleep_ms(100)

Tips for school and science-fair presentations

Smart greenhouses are such a popular topic that judges see dozens of similar models. What will make you stand out is not just showing that the system works, but answering a question with measurements. At a science fair (such as TÜBİTAK 4006 in Turkey), your aim, method and expected result need to be clear; research competitions (such as TÜBİTAK 2204) expect a comparative experiment with data. A good research question for this greenhouse is: “How much water does watering based on soil moisture save compared with watering at the same time every day, and how does it affect plant growth?”

  • Set up two groups: Split seedlings of the same species and the same height into two groups. Water one group with the sensor system and the other by hand with a fixed amount every day; keep light and temperature the same for both groups.
  • Measure and record: Keep a table of the weekly amount of water (by measuring the tank, in mL), plant height (cm) and the number of leaves. The CSV output from the code or a Google Sheets log gives you the temperature and humidity chart for free.
  • Draw a block diagram: Sensors on the left, the Arduino in the middle, actuators on the right; label the arrows with rule sentences (“soil < 35% → pump”). The judges won't read your code, but they will remember this diagram.
  • Explain hysteresis: Two charts showing how many times a single-threshold fan and a hysteresis fan switch on and off in an hour are the best way to show the judges the engineering side of your project.
  • Write a live demo script: Warm the DHT22 in your palm to start the fan, move the soil sensor into a dry pot to show the pump pulses, cover the LDR with your hand to show the LEDs coming on. Keep each demo to 10–20 seconds.
  • Link it to real life: Talk about real problems such as water and energy use in greenhouses, fungal diseases caused by excess humidity and growers' workload; show with numbers which of them your system addresses.
  • Have a backup plan: Film the working system; bring spare wires, a spare pump and an adapter; write your calibration values and Wi-Fi settings on a piece of paper.
  • Prepare for the judges' questions: Have short answers ready for “What happens if a sensor fails?”, “What happens to the plants in a power cut?” and “How would you scale this up to a real greenhouse?”. The safe state and overflow protection in the code are the best answers to these questions.
If you're entering a competition with themes, agriculture and food technology is the most natural fit for this project (in TÜBİTAK 4006 it's the Agriculture and Livestock Technologies theme). For more ideas, cost tables and judging criteria, see the Arduino science fair project ideas article.

Common mistakes and how to fix them

SymptomLikely causeFix
The Arduino resets or the LCD garbles when the pump runsPump and relays powered from the Arduino's 5V pin, motor noisePower the pump and relay coils from the LM2596, join the GNDs, add a reverse diode and a 470 µF capacitor at the pump
The relays keep clickingA single threshold is used or the readings are noisyUse separate on and off thresholds (hysteresis) and average the analogue readings
Water keeps flowing after the pump stopsSiphon effect: the hose end is below the tank's water levelPlace the tank lower or make a small air hole at the top of the hose
Soil moisture is always 0% or always 100%Wrong calibration values, or AOUT on the wrong pinMeasure the sensor's raw value in air and in water and write them into the constants
The LEDs flash on and offThe LDR can see the LEDs' lightMove the LDR outside the greenhouse, facing the sky
The DHT22 returns nanWrong pin, no pull-up resistor, or read more often than every 2 secondsCheck the pin, use a module or add 10 kΩ between DATA and VCC
The temperature reads higher than it shouldSun or LED light shines directly on the sensorShade it with a white cover with holes and move it away from the heater
Water droplets and mould on the wallsHumidity too high, ventilation too weakLower the humidity threshold, enlarge the air inlet, water less
On the ESP32, the soil reading goes wrong when Wi-Fi startsThe sensor is on an ADC2 pinMove it to an ADC1 pin between GPIO 32 and 39
With an ESP32, the relay never releasesA 3.3 V HIGH can't switch off an active-LOW 5 V moduleSeparate JD-VCC or use a relay module with 3.3 V triggering

A smart greenhouse project combines reading sensors, calibration, deciding with hysteresis, driving relays and MOSFETs, power planning and wireless monitoring in a single build, and in the end it grows something truly alive: a plant. Start with watering and ventilation, test each part on its own, then add the heater, the LEDs and the phone. In the BlueGrays app you'll find step-by-step projects with circuit diagrams and code for measuring temperature and humidity with a DHT11, reading light with an LDR, a smart watering station and a temperature logger with a DHT22.

Frequently asked questions

What do I need for a smart greenhouse project?

For a basic smart greenhouse, an Arduino Uno or ESP32, a DHT22 temperature and humidity sensor, a capacitive soil moisture sensor, an LDR and a 10 kΩ resistor, a relay module, a mini submersible pump and a fan are enough. The full version adds a 12 V PTC heater, an LED strip driven by a MOSFET, an I2C LCD, a float level switch, a 12 V 5 A adapter and an LM2596. For the body, a clear storage box with a lid is the easiest and cheapest solution.

How does a smart greenhouse work?

A smart greenhouse works in a measure, decide, act, display loop. Sensors measure temperature, humidity, soil moisture and light; the Arduino compares these values with thresholds and switches the fan, heater, pump and LEDs on and off through relays or a MOSFET. The results are shown on an LCD and, with an ESP32, on your phone.

Should I use a DHT11 or a DHT22 in a greenhouse?

Choose the DHT22. The DHT11 is only accurate to ±2 °C between 0 and 50 °C and can't see humidity above 90%, while a greenhouse can go above 90% humidity in the morning and below freezing in winter. The DHT22 measures from −40 to 80 °C to ±0.5 °C and humidity to about ±2%, and its code is almost identical to the DHT11's.

How much does an Arduino greenhouse project cost?

Prices vary by seller, but excluding the board, the basic version (DHT22, soil moisture sensor, LDR, relay, pump and fan) usually costs around 25–40 dollars. The full version with a heater, LED strip, LCD, 12 V adapter and enclosure comes to about 70–120 dollars. If you already have an Arduino starter kit, the cost drops noticeably.

How can I monitor my greenhouse from my phone?

The easiest way is to swap the brain for an ESP32 and run a web server; you see the values and start the pump from your phone's browser on the same Wi-Fi network. For access from outside the home you can use Blynk IoT, for alert messages a Telegram bot, and for long-term data logging Google Sheets.

How do I present a smart greenhouse at a science fair?

Instead of just showing a working model, set a measurable research question; for example, measure over several weeks with two groups of plants how much water sensor-based watering saves compared with timed watering. Present the water use, plant height and temperature-humidity charts in tables and graphs, explain the system with a block diagram and prepare a short script for the live demo.

See all projects
Temperature and Humidity with DHT11
#55Intermediate

Temperature and Humidity with DHT11

How warm is your room, and is the air humid or dry? The DHT11 sensor measures temperature and humidity, and the Arduino writes the values on a two-line LCD screen, refreshing every couple of seconds.

4.7(47)
73
Measuring Light Intensity with an LDR
#11Beginner

Measuring Light Intensity with an LDR

Want to see how bright your room is as a number? The LDR, a small part that reacts to light, measures the light around it, and the Arduino writes the value to your computer screen every half second. C

4.6(81)
139
Reading Light Intensity with the ESP32 LDR Module
#113Beginner

Reading Light Intensity with the ESP32 LDR Module

Real-time monitoring of the LDR module's raw ADC value and relative light percentage calculated from dark and light reference values in the Serial Monitor. This percentage is not a lux measurement. Teaches the module’s calibration logic. It is ideal to complete this before Project 10, as it is used to determine the threshold value.

4.6(36)
55
Smart Irrigation Station
#83Advanced
Premium

Smart Irrigation Station

A gardener that waters your plants on its own, so they don't dry out while you're on vacation. A soil moisture sensor measures how wet the soil is; when it gets dry, the Arduino runs the water pump fo

5.0(42)
57
ESP32 DHT22 Temperature and Humidity (with LCD Display)
#117Intermediate

ESP32 DHT22 Temperature and Humidity (with LCD Display)

This project measures ambient temperature and humidity using a DHT22 sensor and displays the data in real time on a 16x2 I2C LCD. Data is read from the sensor using the one-wire protocol via the Adafruit DHT library. This project serves as the foundation for IoT weather stations, smart home automation, or environmental monitoring systems in greenhouses. Communication with the DHT22 is established via the ESP32's GPIO 4 pin.

4.8(38)
56
ESP32 Temperature Data Logger (DHT22 + DS1307 RTC + microSD + OLED)
#144Advanced
Premium

ESP32 Temperature Data Logger (DHT22 + DS1307 RTC + microSD + OLED)

Build a timestamped temperature logger with ESP32! Read DHT22 temperature/humidity every minute, get real-world time from DS1307 RTC, and log to microSD card as CSV. The OLED displays live temperature (large font), humidity percentage, timestamp, and log counter. The /temp_log.csv file on the SD card opens in Excel, Google Sheets, or Python pandas for analysis — perfect for greenhouse monitoring, home/room temperature tracking, laboratory data collection, food storage conditions, or beehiv

4.7(7)
12
ESP32 Smart Lighting (PIR + LDR + Relay + OLED)
#141Advanced
Premium

ESP32 Smart Lighting (PIR + LDR + Relay + OLED)

Turn your hallway, bathroom, or pantry into smart lighting with ESP32! Using a PIR motion sensor, LDR light sensor, and a relay module, we build the classic "light turns on when motion is detected in the dark" scenario — if the environment is already bright, motion does NOT trigger the light, saving energy during the day. The light stays on for 30 seconds; if motion continues, the timer resets and the light stays on longer. The SSD1306 OLED shows large-font status (ON / DAYTIME / WAITING), a cou

4.3(6)
11