ESP32 Controller
ESPHome 2026.3.0 on a Kincony KC868-E16P board. 198 entities across sensors, relays, switches, tunables, and diagnostics. The ESP32 evaluates two independent axes — temperature (6 states) and humidity (7 states) — every 5 seconds, producing 42 possible state combinations. Each maps to a specific relay pattern.
Firmware Structure
| File | Lines | Content |
|---|---|---|
greenhouse-v2.yaml | 380 | Top-level: WiFi, SNTP, MQTT, buttons |
hardware.yaml | 292 | I²C, UART, Modbus, relay definitions |
sensors.yaml | 1,202 | All sensors, derived metrics, DLI accumulator |
globals.yaml | 584 | State variables, setpoints, counters |
controls.yaml | 1,050 | Climate + irrigation + mister state machines |
tunables.yaml | 851 | HA number/switch/button entities (57 numbers, 8 switches) |
Temperature States
| State | Condition | Equipment |
|---|---|---|
| HEAT_S2 | temp < temp_low - d_heat_stage_2 (55°F) | Heat1 + Heat2 (gas) |
| HEAT_S1 | temp < temp_low (58°F) | Heat1 (electric) |
| TEMP_IDLE | temp_low ≤ temp ≤ temp_high | Nothing |
| COOL_S1 | temp > temp_high (82°F) | Fan1 (lead) + vent |
| COOL_S2 | temp > temp_high + d_cool_s2 (85°F) | Both fans + vent |
| COOL_S3 | temp > 87°F | Emergency + evaporative fog |
Humidity States (Inverted Hierarchy)
Misters first, fog as escalation. Misters are cheap, targeted, and work with the vent open.
| State | Condition | Equipment |
|---|---|---|
| HUM_IDLE | VPD in band | Nothing |
| DEHUM | VPD < vpd_low (0.5 kPa) | Vent opens |
| HUMID_S1 | VPD > vpd_high + hysteresis (2.30 kPa) | Mister pulse rotation (highest-VPD zone) |
| HUMID_S2 | VPD sustained despite S1 | Aggressive all-zone mister rotation |
| HUMID_S3 | VPD still critical | FOG (1,644W) — aerosol, vent closes |
Mister Pulse Model
S1: Pick highest-VPD zone
→ mister_pulse_on_s (60s) burst
→ mister_pulse_gap_s (45s) gap
→ re-read VPDs
→ repeat
S2: Rotate all 3 zones
→ driest zone gets mister_vpd_weight (1.5×) burst
→ 30s gaps between zones
→ re-rank each rotation
Hard rule: Never more than one mister zone simultaneously (water pressure constraint).
Key Interactions
| Temp State | Humidity State | Behavior |
|---|---|---|
| COOL_S1 | HUMID_S1 | Misters pulse freely. Fog suppressed (vent open = aerosol wasted). |
| COOL_S1+ | HUMID_S2 | Aggressive mister rotation. Fog only as S3 escalation. |
| COOL_S3 | HUMID_S3 | Fog WITH vent open — evaporative cooling benefit. |
| TEMP_IDLE | HUMID_S1+ | Both misters and fog available. |
| HEAT_S1+ | HUM_IDLE | Heaters only. No humidity conflict. |
Safety Rails
| Parameter | Value | Purpose |
|---|---|---|
| safety_min | 45°F | Emergency heat below this |
| safety_max | 95°F | Emergency cooling above this |
| safety_vpd_min | 0.3 kPa | Emergency dehumidification |
| safety_vpd_max | 3.0 kPa | Emergency humidification |
Key Design Decisions
-
ESP32 owns real-time control. The AI adjusts setpoints (boundaries), never relay states directly. The firmware makes real-time tradeoffs at 5-second resolution.
-
Dual-path setpoint delivery. Push via aioesphomeapi (immediate) + pull via HTTP
/setpoints(300s fallback). If both fail, hardcoded defaults keep the greenhouse safe. -
Inverted humidity hierarchy. Misters first (cheap, targeted, work with vent open), fog as S3 escalation (expensive, vent must close). This was a significant change from the original firmware which used fog first.
-
Pulse-rotation model. Never more than one mister zone simultaneously. VPD-weighted burst allocation. 60s on / 45s gap is the tuned sweet spot.
-
Autonomous safety. Safety rails (45–95°F, 0.3–3.0 kPa VPD) override all setpoints. The controller operates independently of the AI layer.
Autonomy
The ESP32 operates independently. If the AI planning layer (Iris) goes offline and both push and pull paths fail, the controller falls back to hardcoded default setpoints and continues managing the greenhouse. Iris makes it smarter, but it doesn’t depend on Iris to survive.