A fully autonomous indoor plant monitoring and control system built from scratch — spanning embedded firmware, cloud backend, and a native Android application. Designed and developed as a solo end-to-end project.
| Dashboard | Controls | Settings |
|---|---|---|
![]() |
![]() |
![]() |
| Live sensor readings & per-plant soil moisture | System kill switches & pump overrides | Configurable thresholds for all automation logic |
- ESP32 firmware (Arduino C++)
- Backend REST + SQLite server (Node.js, Express)
- Android app (Java, OkHttp + Gson)
- Real-time states + historical data
- Security with API key header
- Local dev tests passed
┌─────────────────────────┐
│ Android App │ ← Java, Android Studio
│ Dashboard / Controls │
│ / Settings │
└────────────┬────────────┘
│ HTTPS REST + API Key
▼
┌─────────────────────────┐
│ Node.js / Express │ ← Ubuntu server, PM2
│ SQLite Database │
│ Cloudflare Tunnel │ ← api.example.org
└────────────┬────────────┘
│ HTTP POST / GET
▼
┌─────────────────────────┐
│ ESP32-S3 │ ← C++ / Arduino framework
│ Sensors · Relays · NTP │
└─────────────────────────┘
- Soil moisture monitoring via ADS1115 ADC (3 capacitive sensors)
- Temperature & humidity via DHT11
- Automated watering with hysteresis (35%-45% threshold per plant)
- NTP-synced lighting (22h on / 2h off, timezone-aware)
- Ventilation control (hysteresis via temperature + humidity)
- WiFi with auto-reconnect
- REST API (sensor data + configuration)
- SQLite persistence (readings + settings)
- PM2 managed process
- Cloudflare Tunnel support
- API key authentication
- Dashboard: Real-time sensors, pump status, color-coded soil levels
- Controls: Kill switches + pump overrides with live indicators
- Settings: Full remote configuration of automation thresholds
| Component | Role |
|---|---|
| ESP32-S3 | Main microcontroller |
| ADS1115 | 16-bit ADC for soil sensors |
| Capacitive Soil Sensors (×3) | Per-plant moisture |
| DHT11 | Temperature & humidity |
| Relay Module (×5) | Pump + light + fan control |
| Water Pumps (×3) | Automated irrigation |
| Grow Light | NTP-scheduled |
| Ventilation Fan | Hysteresis-controlled |
Estimated cost: ~40€
- Node.js ≥ 18 (for local dev)
- Docker & Docker Compose (for containerized deployment)
- Android Studio (for app)
- Arduino IDE (for firmware)
git clone https://github.com/orfeastops/auto-grow-system.git
cd auto-grow-system
npm install
cp .env.example .env
# Edit .env: API_KEY=your-secret, PORT=3000
npm startOne-liner:
docker-compose up -dThis automatically:
- Builds the Node.js backend
- Creates SQLite database
- Exposes API on
http://localhost:3000 - Includes health checks
View logs:
docker-compose logs -f greenhouse-apiStop:
docker-compose downProduction (with PM2):
pm2 start server --name greenhouse-api
pm2 save
pm2 startupCloudflare Tunnel (public access):
cloudflared tunnel create greenhouse
cloudflared tunnel route dns greenhouse api.yourdomain.org
cloudflared tunnel run greenhouse# Open in Android Studio, update ApiClient.BASE_URL
# Build and install
./gradlew assembleDebugUpload arduino main code.cc via Arduino IDE to ESP32-S3.
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /api/data |
API Key | ESP32 sensor upload |
| GET | /api/data/latest |
API Key | Latest snapshot |
| GET | /api/data/history?hours=N |
API Key | Historical data |
| GET | /api/settings |
API Key | Read settings |
| POST | /api/settings |
API Key | Update setting |
| GET | /api/health |
Open | Health check |
Example:
curl -H 'x-api-key: secret' http://localhost:3000/api/settings
curl -X POST -H 'x-api-key: secret' -H 'Content-Type: application/json' \
-d '{"key":"light_enabled","value":"0"}' http://localhost:3000/api/settings- Store
API_KEYin.env(never commit) - Use
.env.exampletemplate with placeholders greenhouse.dbexcluded via.gitignore- HTTPS via Cloudflare Tunnel for production
- Rotate keys periodically
.gitignore # Excludes .env, node_modules, *.db
.env.example # Environment template
README.md # This file
package.json # Dependencies
server # Express backend
arduino main code.cc # ESP32 firmware
app/ # Android application
html/ # Web UI (React)
Dashboard.jpg # Screenshots
Controls.jpg
settings.jpg
Local verification passed with all endpoints tested via curl.
Smoke test:
PORT=3001 API_KEY=test npm start &
curl -H 'x-api-key: test' http://localhost:3001/api/health
curl -H 'x-api-key: test' http://localhost:3001/api/settings- Push notifications (moisture alerts)
- Historical charts in app
- OTA firmware updates
- Multi-room support
- OAuth2 authentication
- Docker support
- Firebase Cloud Messaging
| Layer | Technology |
|---|---|
| Microcontroller | ESP32-S3, C++ (Arduino) |
| Sensors | ADS1115, DHT11 |
| Backend | Node.js, Express, SQLite |
| Process Mgmt | PM2 |
| Tunneling | Cloudflare Tunnel |
| Mobile | Android (Java) |
Orfeas — Solo end-to-end design, build, testing.
- GitHub: @orfeastops
- Repo: github.com/orfeastops/auto-grow-system
Open source. Adapt freely for your projects.
cd /home/linux/Desktop/greenhouseapp/auto-grow-systemnpm installcp .env.example .env- Edit
.env:API_KEY=your-secret-keyPORT=3000
node server
pm i -g pm2
pm2 start server --name greenhouse-api --update-env
pm2 saveConfigure cloudflared as tunnel:
cloudflared tunnel create greenhouse
cloudflared tunnel route dns greenhouse api.example.org
cloudflared tunnel run greenhouse
APP URL: https://api.example.org
the backend expects x-api-key header on /api/data,/api/settings,/api/data/* endpoints.
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /api/data | API Key | ESP32 sends sensor state |
| GET | /api/data/latest | API Key | Latest sensor snapshot |
| GET | /api/data/history?hours=N | API Key | History (default 24h) |
| GET | /api/settings | API Key | Read settings |
| POST | /api/settings | API Key | Write setting |
| GET | /api/health | open | Health check |
- Open
app/in Android Studio. - Set
ApiClient.BASE_URLto your backend host (e.g.https://api.example.org). - Make sure
API_KEYis the same as in server.env. - Build & run.
For questions or issues, please open an issue on GitHub.


