Motivation

I wanted to know the actual temperature and humidity on my balcony. Weather apps use data from the nearest official station, which can be a few kilometers away and at a different altitude. So I built my own sensor and put it outside.


The Problem Statement

Requirements:
- Real-time temperature & humidity monitoring (outdoor balcony)
- Remote access via multiple platforms (web, mobile, desktop)
- No cloud vendor lock-in (local network only)
- 24/7 continuous operation
- Minimal maintenance
- Production-grade reliability

Constraints:
- Available components (ESP8266, DHT-22, BMP180)
- Keep it simple (KISS principle)
- No over-engineering


Hardware Design

Component Selection

| Component | Reason |
|-----------|--------|
| **ESP8266 NodeMCU** | Low-cost Wi-Fi + processing in one module |
| **DHT-22** | Stable, ±0.5°C accuracy, widely available |
| **BMP180** | I2C pressure sensor, reliable, simple |
| **5V DC Power** | USB-powered, simple external adapter, zero complexity |
| **Protective Enclosure** | Weatherproof case for exposed balcony deployment |

Schematic

┌─────────────────────────┐
│   5V DC Power Supply    │
│   (USB or external)     │
└────────────┬────────────┘
             │
             ├─→ [ESP8266 NodeMCU]
             │      ├─ GPIO14 (D5) ──→ DHT22 data
             │      ├─ GPIO4 (D2)  ──→ BMP180 SDA (I2C)
             │      ├─ GPIO5 (D1)  ──→ BMP180 SCL (I2C)
             │      └─ A0 ─────────→ Battery voltage (optional)
             │
             └─→ [Protective Weatherproof Case]

Why Mains Power?

Initially considered battery-powered. Decision: **mains-powered is better for this use case:**

-  24/7 continuous operation (no downtime waiting for charge)
-  Zero maintenance (no battery replacement, no degradation curve)
-  Simpler architecture (optimize for heat, not power)
- Cost-effective (5V adapter costs $5)


Firmware Development

Architecture Decision: Light Sleep Mode

Initial approach: Run at full power constantly.
**Problem:** Unnecessary heat generation, CPU always active.

**Solution:** Implement WiFi Light Sleep mode:

void setup() {
  WiFi.setSleepMode(WIFI_LIGHT_SLEEP);  // Called ONCE at startup
  // ... rest of setup
}

void loop() {
  if (currentMillis - previousMillis >= 10000) {  // Every 10 seconds
    readSensors();
    delay(9000);  // Light sleep for 9 seconds
  }
  delay(50);  // Small delay to prevent CPU thrashing
}

Results:
- Active time: ~1 second per 10-second cycle
- Light sleep: 0.9 mA (WiFi stays connected)
- Average consumption: ~9 mA
- CPU temperature: Significantly reduced

**Key insight:** Light Sleep ≠ Deep Sleep. WiFi connection persists, crucial for real-time monitoring.


Multi-Environment Build Configuration

Used PlatformIO for clean separation:

[env:production]
# Real sensors, optimized for reliability

[env:debug]
build_flags = -DDEBUG_MODE
# Mock data for testing without hardware

Why this matters:
- Test Android/Windows apps without physical ESP
- Develop firmware features in isolation
- Simulate edge cases (low battery, sensor failures)
- CI/CD can run both configurations

---

Code Highlights

Graceful Sensor Handling

#ifdef DEBUG_MODE
  dhtAvailable = true;
  bmpAvailable = true;
#else
  // Real sensor initialization with error handling
  dht.begin();
  float testTemp = dht.readTemperature();
  dhtAvailable = !isnan(testTemp);
    // Similar for BMP180...
#endif

Why: If sensors fail, the...

Read more »