A comprehensive IoT-based greenhouse automation system that monitors environmental conditions and controls climate parameters automatically. The system consists of an ESP32-based hardware controller, a serverless AWS Lambda backend, and a React web application for real-time monitoring and control — deployed on Vercel with live data pushed via Pusher Channels.
- Overview
- Features
- Architecture
- Hardware Requirements
- Software Requirements
- Installation
- Configuration
- Usage
- API Documentation
- Contributing
- License
The Smart Greenhouse System provides automated climate control for greenhouse environments by monitoring temperature, humidity, soil moisture, air quality (CO2 and TVOC), and water tank levels. It automatically controls heating, ventilation, and irrigation systems to maintain optimal growing conditions.
- Temperature & Humidity: Real-time monitoring using AHT20 sensor
- Air Quality: CO2 and TVOC measurements via ENS160 sensor
- Soil Moisture: Capacitive soil moisture sensing with calibration
- Water Level: Ultrasonic sensor for water tank level monitoring
- Climate Control: Automatic heating and cooling based on temperature thresholds
- Humidity Management: Exhaust fan activation for humidity control
- Smart Irrigation: Automatic watering based on soil moisture levels
- Manual Override: Full manual control mode for all actuators
- WiFi Management: Easy WiFi configuration via captive portal (WiFiManager)
- AWS IoT Integration: Secure MQTT communication with AWS IoT Core
- Real-time Dashboard: Web-based monitoring and control interface with live updates via Pusher
- LCD Display: Local 20x4 character display for status information
- OTA Updates: Over-the-air firmware updates
- Offline Data Storage: Local data buffering when connectivity is lost
- Historical Data: Time-series data stored in DynamoDB and visualized in the dashboard
- Alerts: Automatic alerts saved to DynamoDB and pushed live to the frontend
The system uses a fully serverless backend — there is no always-on server. All backend logic runs in AWS Lambda functions triggered by IoT Rules or API Gateway.
┌─────────────────┐ MQTT (TLS) ┌──────────────────────┐
│ ESP32 Device │ ───────────────► │ AWS IoT Core │
│ (Firmware) │ ◄─────────────── │ (IoT Rules Engine) │
└─────────────────┘ MQTT commands └──────┬───────┬────────┘
│ │
IoT Rule trigger │ │ IoT Rule trigger
▼ ▼
┌─────────────────┐ ┌──────────────────┐
│ iot-data-handler│ │iot-alert-handler │
│ (Lambda) │ │ (Lambda) │
└────────┬────────┘ └────────┬─────────┘
│ │
DynamoDB │ │ DynamoDB
(sensor │ │ (alerts)
data) │ │
▼ ▼
┌──────────────────────────────────┐
│ Pusher Channels │
│ (real-time push to frontend) │
└──────────────────┬───────────────┘
│
┌──────────────────▼───────────────┐
│ React Frontend │
│ (Vercel — pusher-js + Amplify) │
└──────────────────▲───────────────┘
│ REST calls
┌──────────────────┴───────────────┐
│ API Gateway HTTP API │
│ → api-handler (Lambda) │
│ (devices / history / command) │
└──────────────────────────────────┘
- Multi-threaded FreeRTOS design
- Sensor reading and actuator control
- MQTT communication with AWS IoT Core
- Publishes sensor data to
greenhouse/{deviceId}/data - Publishes alerts to
greenhouse/{deviceId}/alerts - Subscribes to commands on
greenhouse/{deviceId}/commands - Local LCD interface
- Persistent configuration storage (NVS)
Three independent Lambda functions replace the previous always-on Node.js server:
| Function | Trigger | Responsibility |
|---|---|---|
iot-data-handler |
IoT Rule — greenhouse/+/data |
Save sensor data to DynamoDB and push live update via Pusher |
iot-alert-handler |
IoT Rule — greenhouse/+/alerts |
Save alert to DynamoDB and push alert via Pusher |
api-handler |
API Gateway HTTP API | REST API — device management, history, commands, alerts |
DynamoDB Tables:
GreenhouseUserDevices— user-to-device associationsGreenhouseSensorData— time-series sensor readingsGreenhouseAlerts— device alerts log
iot-data-handlerandiot-alert-handlerpush events to Pusher after saving to DynamoDB- The frontend subscribes to the
greenhouse-{deviceId}Pusher channel - Events:
sensor-data,device-status,device-alert - Replaces the previous WebSocket server entirely — no persistent server needed
- Real-time dashboard with live sensor data via
pusher-js - Interactive charts and graphs (Recharts)
- Device control interface
- User authentication (AWS Amplify + Cognito)
- Deployed on Vercel
- Microcontroller: ESP32 DOIT DevKit V1
- Temperature/Humidity: Adafruit AHT20 sensor
- Air Quality: ScioSense ENS160 sensor
- Display: 20x4 I2C LCD (Address: 0x27)
- Soil Moisture: Capacitive soil moisture sensor
- Water Level: HC-SR04 ultrasonic sensor
- Actuators:
- Water pump (relay-controlled)
- Exhaust fan (relay-controlled)
- Heater/halogen lamp (relay-controlled)
PIN 26 - Water Pump Relay
PIN 27 - Exhaust Fan Relay
PIN 14 - Heater Relay
PIN 5 - Ultrasonic Trigger
PIN 34 - Ultrasonic Echo
PIN 32 - Soil Moisture Analog
PIN 4 - WiFi Reset Button
- PlatformIO IDE or PlatformIO Core
- ESP32 Arduino framework
- Libraries (auto-installed via PlatformIO):
- Adafruit AHTX0
- ENS160 Driver
- LiquidCrystal I2C
- PubSubClient (MQTT)
- ArduinoJson
- WiFiManager
- Node.js 18.x or higher (AWS Lambda runtime)
- npm (for installing dependencies before zipping)
- Node.js 18.x or higher
- npm or yarn
- Modern web browser
- AWS Account with:
- AWS IoT Core (MQTT broker + IoT Rules)
- AWS Lambda (3 functions)
- AWS API Gateway HTTP API
- AWS Cognito (User Pool + App Client)
- Amazon DynamoDB (3 tables)
- Pusher account — free tier is sufficient (pusher.com)
- Vercel account for frontend hosting (vercel.com)
# Using PlatformIO IDE (VS Code Extension) or
pip install platformioCreate src/secrets.h with your AWS IoT credentials:
// WiFi Credentials (Optional - can use WiFiManager portal)
const char* WIFI_SSID = "your-wifi-ssid";
const char* WIFI_PASS = "your-wifi-password";
// AWS IoT Endpoint
const char* AWS_IOT_ENDPOINT = "your-endpoint.iot.region.amazonaws.com";
// Device Certificate
const char AWS_CERT_CRT[] PROGMEM = R"EOF(
-----BEGIN CERTIFICATE-----
your-device-certificate
-----END CERTIFICATE-----
)EOF";
// Device Private Key
const char AWS_CERT_PRIVATE[] PROGMEM = R"EOF(
-----BEGIN RSA PRIVATE KEY-----
your-private-key
-----END RSA PRIVATE KEY-----
)EOF";
// Amazon Root CA 1
const char AWS_CERT_CA[] PROGMEM = R"EOF(
-----BEGIN CERTIFICATE-----
your-root-ca
-----END CERTIFICATE-----
)EOF";cd /path/to/smart-greenhouse-system
platformio run --target upload
platformio device monitor # View serial outputCreate the following tables in DynamoDB:
| Table Name | Partition Key | Sort Key |
|---|---|---|
GreenhouseUserDevices |
userId (String) |
deviceId (String) |
GreenhouseSensorData |
deviceId (String) |
timestamp (Number) |
GreenhouseAlerts |
deviceId (String) |
timestamp (Number) |
Create two IoT Rules in AWS IoT Core:
Rule 1 — Sensor Data:
- SQL:
SELECT *, topic() AS topicName FROM 'greenhouse/+/data' - Action: Invoke Lambda →
iot-data-handler
Rule 2 — Alerts:
- SQL:
SELECT *, topic() AS topicName FROM 'greenhouse/+/alerts' - Action: Invoke Lambda →
iot-alert-handler
Each Lambda function is deployed independently. For each function under webapp/lambdas/:
# Example for iot-data-handler (repeat for iot-alert-handler and api-handler)
cd webapp/lambdas/iot-data-handler
npm install
# Zip and upload to AWS Lambda (Node.js 18.x runtime)
zip -r function.zip .
aws lambda update-function-code --function-name iot-data-handler --zip-file fileb://function.zipiot-data-handler and iot-alert-handler:
PUSHER_APP_ID=your-pusher-app-id
PUSHER_KEY=your-pusher-key
PUSHER_SECRET=your-pusher-secret
PUSHER_CLUSTER=your-pusher-cluster
HISTORY_TABLE=GreenhouseSensorData # iot-data-handler only
ALERTS_TABLE=GreenhouseAlerts # iot-alert-handler only
api-handler:
AWS_IOT_ENDPOINT=your-endpoint.iot.region.amazonaws.com
COGNITO_USER_POOL_ID=us-east-1_XXXXXXXXX
COGNITO_CLIENT_ID=XXXXXXXXXXXXXXXXXXXXXXXXXX
The api-handler uses the Lambda execution role for DynamoDB and IoT Data Plane access — no hardcoded AWS credentials needed. Attach an IAM policy to the role granting:
dynamodb:Query,dynamodb:PutItem,dynamodb:UpdateItem,dynamodb:DeleteItemon the three tablesiot:Publishonarn:aws:iot:*:*:topic/greenhouse/*/commands
Create an HTTP API in API Gateway and configure a default route ($default) pointing to the api-handler Lambda. Note the invoke URL — this is your VITE_API_URL.
cd webapp/frontend
# Install dependencies
npm install
# Copy and fill in environment variables
cp .env.example .envEdit .env:
# API Gateway HTTP API URL
VITE_API_URL=https://YOUR_API_GATEWAY_ID.execute-api.YOUR_REGION.amazonaws.com
# AWS Cognito
VITE_COGNITO_USER_POOL_ID=us-east-1_XXXXXXXXX
VITE_COGNITO_CLIENT_ID=XXXXXXXXXXXXXXXXXXXXXXXXXX
VITE_COGNITO_DOMAIN=your-domain.auth.us-east-1.amazoncognito.com
# Pusher Channels
VITE_PUSHER_KEY=your-pusher-app-key
VITE_PUSHER_CLUSTER=ap1npm run dev
# Navigate to https://localhost:5173npm install -g vercel
vercel deploy --prodSet the same environment variables (VITE_API_URL, VITE_COGNITO_*, VITE_PUSHER_*) in the Vercel project settings under Project → Settings → Environment Variables.
The ESP32 stores configuration in non-volatile storage (NVS). Default parameters:
FIRMWARE_VERSION = "1.0.0"
TEMP_MIN_NIGHT = 20.0°C // Heater activates below this
TEMP_MAX_DAY = 30.0°C // Fan activates above this
HUM_MAX = 75.0% // Fan activates above this
SOIL_DRY = 40% // Pump activates below this
SOIL_WET = 70% // Pump deactivates above this
TANK_EMPTY_DIST = 25cm // Empty tank threshold
TANK_FULL_DIST = 5cm // Full tank thresholdThese can be updated via the web dashboard or by sending an MQTT command to greenhouse/{deviceId}/commands.
First Time Setup:
- Power on the ESP32
- Look for WiFi network "SmartGreenhouse-XXXXXX"
- Connect and configure your WiFi credentials
- Device will save credentials and auto-reconnect
Reset WiFi:
- Hold the reset button (PIN 4) for 5 seconds
- Create an IoT Thing in AWS IoT Core
- Generate and download certificates
- Create an IoT Policy with appropriate permissions
- Attach policy to certificate
- Update
secrets.hwith endpoint and certificates
- Deploy the frontend to Vercel (or run locally with
npm run dev) - Navigate to your Vercel deployment URL (or
https://localhost:5173locally) - Sign in with AWS Cognito credentials
- Add your device using its
deviceId - View real-time sensor data pushed via Pusher and control devices via the dashboard
Automatic Mode (Default):
- System automatically controls actuators based on sensor readings and thresholds
- Optimal for hands-off operation
Manual Mode:
- Override automatic control
- Manually toggle pump, fan, and heater
- Useful for testing or special situations
The local LCD shows:
- Line 1: Temperature and Humidity
- Line 2: Soil Moisture and Water Level
- Line 3: CO2 and TVOC levels
- Line 4: WiFi and AWS connection status
Subscribe (Device receives commands):
greenhouse/{deviceId}/commands— Control and configuration commands
Publish (Device sends data):
greenhouse/{deviceId}/data— Sensor data (triggersiot-data-handlerLambda)greenhouse/{deviceId}/alerts— Alert messages (triggersiot-alert-handlerLambda)
All REST endpoints are served by the api-handler Lambda via API Gateway HTTP API. Every request (except OPTIONS) must include a valid Cognito ID token in the Authorization header.
GET /devices
Authorization: <Cognito ID token>
Response: [{ userId, deviceId, name, createdAt }, ...]
POST /devices
Authorization: <Cognito ID token>
Body: { "deviceId": "my-device", "name": "My Greenhouse" }
Response: { "success": true, "device": { ... } }
PUT /devices/{deviceId}
Authorization: <Cognito ID token>
Body: { "name": "New Name" }
Response: { "success": true }
DELETE /devices/{deviceId}
Authorization: <Cognito ID token>
Response: { "success": true }
GET /devices/{deviceId}/status
Authorization: <Cognito ID token>
Response: { deviceId, timestamp, temp, hum, soil, co2, tank_level, pump, fan, heater, mode, version }
GET /history/{deviceId}?start={unixTimestamp}&end={unixTimestamp}
Authorization: <Cognito ID token>
Response: [{ deviceId, timestamp, temp, hum, soil, co2, tank_level, ... }, ...]
Defaults to the last 24 hours if start/end are omitted.
GET /alerts/{deviceId}
Authorization: <Cognito ID token>
Response: [{ deviceId, timestamp, alert, message }, ...] (latest 20)
POST /command
Authorization: <Cognito ID token>
Body: {
"deviceId": "my-device",
"pump": true | false, // optional
"fan": true | false, // optional
"heater": true | false, // optional
"mode": "auto" | "manual", // optional
"temp_min": 18, // optional, 0–100
"temp_max": 32, // optional, 0–100
"hum_max": 80, // optional, 0–100
"soil_dry": 35, // optional, 0–100
"soil_wet": 65, // optional, 0–100
"tank_empty_dist": 25, // optional, 1–999
"tank_full_dist": 5 // optional, 1–999
}
Response: { "success": true }
Publishes the command as an MQTT message to greenhouse/{deviceId}/commands via IoT Core.
The frontend subscribes to the greenhouse-{deviceId} Pusher channel and listens for:
| Event | Payload | Source Lambda |
|---|---|---|
sensor-data |
Full sensor reading object | iot-data-handler |
device-status |
{ online: true } |
iot-data-handler |
device-alert |
{ alert, message, ... } |
iot-alert-handler |
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
- Built with PlatformIO and Arduino framework
- Uses AWS IoT Core, Lambda, API Gateway, DynamoDB, and Cognito
- Pusher Channels for serverless real-time push
- Vercel for frontend hosting
- React and Vite for modern web interface
- Various open-source libraries and sensors
For issues, questions, or contributions, please open an issue on GitHub.
Made with ❤️ for sustainable agriculture and IoT innovation