An IoT system that monitors and controls a small greenhouse. Two ESP32 boards talk to each other over ESP-NOW: a field node reads the sensors and drives the water pump and the fan, while a gateway node hosts a Wi-Fi access point with a web dashboard, applies the automatic control logic and uploads every reading to a Supabase (PostgreSQL) database.
Watering and ventilation are not driven by fixed thresholds. The user picks a plant and a growth stage from the database, and the limits for that stage (soil moisture, temperature, humidity, light level) become the active profile that the controller follows.
- Field node — DHT11 for air temperature and humidity, HW-080 soil moisture sensor, a light sensor, and a 2-channel relay board driving the pump and the fan. It sends a reading every two seconds and applies whatever commands it receives.
- Gateway node — receives the readings over ESP-NOW, runs the automatic logic, serves the
dashboard on its own access point (
GreenhouseESP, at192.168.4.1), and connects to the home Wi-Fi in parallel to reach Supabase. - Supabase — stores the plant catalogue, the per-stage requirements, the active zone and the full history of readings.
The two nodes are paired by MAC address. On boot the field node scans for the gateway's access point and locks onto the same Wi-Fi channel, which is required for ESP-NOW to work reliably.
The dashboard is a single scrolling page. These are its sections, in order:
| 1. Active profile | 2. Sensor readings | 3. Actuators and controls |
|---|---|---|
![]() |
![]() |
![]() |
| 4. Temperature and humidity charts | 5. Soil and light charts |
|---|---|
![]() |
![]() |
- Two-board ESP-NOW link, independent of the router and of any internet connection
- Local web dashboard served by the gateway itself, reachable from any phone on its access point
- Plant and growth-stage profiles loaded from the database; the active profile is what the controller follows
- Automatic mode: the pump runs while soil moisture is below the stage minimum, the fan runs while the temperature is above the stage maximum
- Manual mode: direct control of the pump and the fan from the dashboard
- Live charts for temperature, humidity, soil moisture and light, drawn on plain HTML canvas
- The last 20 readings are preloaded from the database on boot, so the charts are not empty after a power cut
- The selected profile and the mode survive a reboot, stored in the ESP32 flash (
Preferences) - Every reading is uploaded to Supabase every 10 seconds, including the actuator state
- The light reading is translated into three levels (dark, medium, bright) and compared against the level the selected plant needs, with a colour-coded badge on the dashboard
| Component | Role |
|---|---|
| 2 × ESP32 | Field node and gateway node |
| DHT11 | Air temperature and humidity |
| HW-080 | Soil moisture (analog) |
| Light sensor (analog) | Light level |
| SDR-05VVDC-SL-C, 2-channel relay | Switches the pump and the fan |
| JT-280T | Water pump |
| FAN-4010-PWN-12V | Fan |
Pins on the field node: soil GPIO34, light GPIO35, DHT11 GPIO32, fan relay GPIO27,
pump relay GPIO25. The relay board used here is active high.
| Table | Purpose |
|---|---|
plants |
Plant catalogue with the light level each plant needs |
plant_stages |
Per-stage limits: soil moisture min/max, max air humidity, temperature range |
active_zones |
Which plant and stage is currently growing in each zone |
greenhouse_readings |
Every uploaded reading with the actuator state at that moment |
The schema is in db/schema.sql. The gateway talks to Supabase over the
PostgREST API: GET for plants and stages, POST for new readings, PATCH for the active zone.
- Create the tables in a Supabase project using
db/schema.sql, and add at least one plant with its stages and one row inactive_zones. - Open
firmware/gateway_node/gateway_node.inoand fill in the placeholders at the top: your Wi-Fi SSID and password, the access point password, and your Supabase project URL and publishable key. - In both sketches, replace the two MAC addresses (
gatewayMacandfieldNodeMac) with the MAC addresses of your own boards. - Install the libraries:
DHT sensor library,ArduinoJson. - Flash
firmware/field_node/field_node.inoto the first board andfirmware/gateway_node/gateway_node.inoto the second. - Connect a phone or a laptop to the
GreenhouseESPaccess point and open192.168.4.1.
The soil moisture sensor is calibrated in soilRawToPercent() with the raw values measured for
this specific sensor, fully dry and fully wet. A different sensor will need its own two values.
- A grow lamp is present in the data structures and in the dashboard but is commented out: only the light sensor was installed, and the lamp with its relay was left for a later update. The light reading is therefore shown as an indication and compared against the plant's requirement, but nothing is switched on because of it
- The Wi-Fi credentials and the Supabase key live in the sketch, so they must be filled in before flashing and must not be committed
- A single zone is supported (
zone_id = 1) - In the screenshots the soil sensor reads 4095 (fully dry, 0%), because the readings were taken with the probe out of the soil
University team project of two people.
- Me: the final firmware for both nodes (ESP-NOW pairing and channel sync, the automatic control logic, persistence in flash, the Supabase REST integration and the history preloading), the database design in Supabase, and the physical build of the greenhouse.
- Emmanouil Kasimatis: the selection and wiring of the components, a first rough version of the firmware, and the web dashboard served by the gateway.
Copyright (c) 2026 Fotis Singiridis. All rights reserved.
This code is published for portfolio purposes: you are welcome to read it and run it locally, but it may not be reused, redistributed or used commercially without permission. See LICENSE.








