A hydroponic monitoring system that ingests sensor readings, applies intelligent classification rules, and provides real-time alerts through a React dashboard.
Frontend: https://hydro-sense-monitor.3jn045m1dbz0y.eu-central-1.cs.amazonlightsail.com/
Backend API: https://hydro-sense-monitor.3jn045m1dbz0y.eu-central-1.cs.amazonlightsail.com/api/v1
https://documenter.getpostman.com/view/21567880/2sB2x2Ju3M
hydro-sense-monitor/
βββ backend/
β βββ app/
β β βββ api/routes/ # FastAPI endpoints
β β βββ core/ # Configuration
β β βββ tests/ # Backend test suite
β β βββ main.py # Application entry point
β β βββ schemas.py # Pydantic models
β βββ pyproject.toml # Python dependencies
βββ frontend/
β βββ app/
β β βββ common/components/ # Reusable UI components
β β βββ routes/ # Page components
β β βββ lib/ # API client & utilities
β βββ tests/ # Frontend test suite
β βββ package.json # Node dependencies
βββ docker/ # Container configurations
βββ .github/workflows/ # CI/CD pipeline
βββ README.md
You can run HydroSense Monitor using either Docker Compose or CLI commands for development.
The easiest way to run the entire stack with a single command:
# Start all services (Traefik, Backend, Frontend)
docker compose up --buildTo stop all services:
docker compose downBackend Environment Variables
Rename .example.env file to .env in backend/ directory and fill in the necessary information:
PROJECT_NAME="HydroSense Monitor API"
BACKEND_CORS_ORIGINS=http://localhost:3000,http://127.0.0.1:3000,http://localhost:5173Frontend Environment Variables
Rename .example.env file to .env in frontend/ directory and fill in the necessary information:
VITE_API_URL=http://127.0.0.1:8000/api/v1
VITE_SHOW_DEV_TOOLS=trueYou can set VITE_SHOW_DEV_TOOLS=false to hide development tools (Generate Random Reading)
cd backend
uv sync
uv run uvicorn backend.app.main:app --reloadServer runs at http://localhost:8000
cd frontend
bun install
bun devFrontend available at http://localhost:5173
Submit sensor readings and receive health classification.
Request:
{
"unitId": "greenhouse-123",
"timestamp": "2025-05-24T12:34:56Z",
"readings": { "pH": 6.5, "temp": 22.1, "ec": 1.2 }
}Response:
{
"status": "OK",
"classification": "Healthy"
}Retrieve the last 10 readings classified as "Needs Attention" for a specific unit.
Response:
{
"unitId": "greenhouse-123",
"alerts": [
{
"unitId": "greenhouse-123",
"timestamp": "2025-05-24T12:34:56Z",
"readings": { "pH": 4.2, "temp": 22.1, "ec": 1.2 },
"classification": "Needs Attention"
}
],
"unitExists": true,
"totalReadings": 15
}Get overview of all hydroponic units with health status and recent activity.
Response:
{
"units": [
{
"unitId": "greenhouse-123",
"lastReading": { /* Latest sensor reading */ },
"totalReadings": 25,
"alertsCount": 3,
"healthStatus": "warning"
}
],
"totalUnits": 5
}System health verification endpoint.
Response:
{
"status": "Server is running!"
}- Healthy: pH 5.5 - 7.0 (inclusive)
- Needs Attention: pH < 5.5 or pH > 7.0
This range provides the sweet spot for nutrient availability in hydroponic systems. If the pH goes too high or too low, plants lose the ability to take up nutrients - it's like the nutrients are there but out of reach.
Units are categorized based on recent alert frequency:
- Healthy: 0 alerts in last 10 readings
- Warning: 1-3 alerts in last 10 readings
- Critical: 4+ alerts in last 10 readings
Uses in-memory storage for simplicity and speed. The data structure uses a list for readings and a dict for quick alert lookups by unitId. For production environment, consider:
- PostgreSQL/Supabase for persistent storage
- Redis for caching frequently accessed readings
- Message queues (RabbitMQ) for high-volume sensor inputs
Implementation: A minimal "View All Units" dashboard that displays a grid of all hydroponic units with color-coded health indicators, enabling growers to instantly assess their entire operation.
Value: This bird's-eye view eliminates the need to check each unit individually and helps prioritize which units need immediate attention. The interface includes:
- Modal Interface: Clean overlay with unit grid layout
- Health Status Indicators: Color-coded badges (green/yellow/red)
- Quick Actions: Click any unit to jump directly to its alert details
- Pagination: Handles large numbers of units efficiently
- Real-time Refresh: One-click update of all unit statuses
This small addition lets you zoom out from single units to see the big picture of your whole operation, making life way easier for growers/commercial hydroponic farms.
cd backend
uv run pytest app/tests/ -vCoverage includes:
- Payload validation and error handling
- pH classification logic accuracy
- Alert storage and retrieval functionality
- API endpoint behavior and responses
cd frontend
bun testCoverage includes:
- Component rendering and user interactions
- Color logic for health status indicators
- Alert fetching and error handling
- Random reading generation and API integration
- Units Health Dashboard functionality
Beyond standard test coverage, two critical production scenarios are covered:
-
Out-of-Order Timestamps (
test_out_of_order_timestamps)- Problem: Sensors may send readings with non-chronological timestamps due to network delays, buffering, or clock sync issues
- Solution: System accepts and stores readings correctly while maintaining proper temporal ordering for alerts
-
Malformed JSON Payloads (
test_malformed_json_payload)- Problem: Invalid JSON from network corruption, client bugs, or malicious requests could crash the system
- Solution: Robust error handling with appropriate HTTP status codes and descriptive error messages without exposing internal details
Automated GitHub Actions workflow provides:
- Testing: Full backend (pytest) and frontend (Jest) test suites
- Building: Docker containerization for both services
- Deployment: AWS Lightsail deployment with multi-container orchestration
- Quality Assurance: Linting and type checking
- What it counts: The total number of sensor readings ever submitted for that unit
- Behavior: Always increases, never decreases
- All units show "1" because each has submitted only one reading so far
- What it counts: The total number of readings classified as "Needs Attention" (pH < 5.5 or pH > 7.0) for that unit
- Behavior: Can only increase or stay the same, never decreases
-
Based on recent performance - only the last 10 readings:
- π’ HEALTHY: 0 alerts in last 10 readings
- π‘ WARNING: 1-3 alerts in last 10 readings
- π΄ CRITICAL: 4+ alerts in last 10 readings
If tomato-row-6unit (currently showing pH 4.6) continues sending readings:
- After 5 more readings: Total Readings: 6
- If 3 are healthy (pH 5.5-7.0): Alerts Count: 3 (original 1 + 2 new alerts)
- Health Status would depend on the last 10 readings pattern
The counts provide historical context (total performance) while the health status shows current condition (recent performance).
Create a Healthy Unit (0 alerts in last 10)
POST /api/v1/sensor - Submit 10 healthy readings
Result: After 10 readings with pH 5.5-7.0, unit shows π’ HEALTHY
POST /api/v1/sensor - Add 2 bad readings
// Reading 11: Bad pH (alert)
{
"unitId": "tomato-row-6",
"timestamp": "2025-06-05T10:11:00Z",
"readings": {
"pH": 5.2, // < 5.5 = Alert!
"temp": 22.5,
"ec": 1.8
}
}
// Reading 12: Another bad pH
{
"unitId": "tomato-row-6",
"timestamp": "2025-06-05T10:12:00Z",
"readings": {
"pH": 7.8, // > 7.0 = Alert!
"temp": 22.5,
"ec": 1.8
}
}Result: Last 10 readings (3-12) have 2 alerts β π‘ WARNING
POST /api/v1/sensor - Add more bad readings
// Readings 13-16: All bad pH values
{
"unitId": "tomato-row-6",
"timestamp": "2025-06-05T10:13:00Z",
"readings": {
"pH": 4.8, // alert!
"temp": 22.5,
"ec": 1.8
}
}Result: Last 10 readings (7-16) have 5 alerts β π΄ CRITICAL
Begin recovery with tomato-row-6 unit.
POST /api/v1/sensor - Add good readings
{
"unitId": "tomato-row-6",
"timestamp": "2025-06-05T11:00:00Z",
"readings": {
"pH": 6.2, // Healthy!
"temp": 22.5,
"ec": 1.8
}
}After adding 7 healthy readings: Last 10 have only 3 alerts β π‘ WARNING
After adding 10 healthy readings: Last 10 have 0 alerts β π’ HEALTHY