Code
Every component β firmware, data pipeline, database schema, AI planning prompts, and this website β is available for inspection, replication, and contribution.
Repositories
| Repository | What | Language |
|---|---|---|
| verdify-firmware | ESP32 ESPHome firmware β state machine, sensors, controls | YAML + C++ lambdas |
| verdify-platform | Ingestor, dispatcher, scripts, migrations, Docker stack | Python + SQL |
| verdify-site | This website β Quartz + Obsidian vault | Markdown |
Repositories will be published once sanitized for credentials.
ESP32 Firmware
The greenhouse controller runs ESPHome 2026.3.0 on a Kincony KC868-E16P board. Six YAML files define the entire system:
| File | Lines | What it does |
|---|---|---|
greenhouse-v2.yaml | 380 | Top-level: WiFi, SNTP, MQTT, OTA, buttons, boot sequence |
hardware.yaml | 292 | IΒ²C bus, UART/RS485, Modbus controllers (9 devices), PCF8574 I/O expanders, relay definitions |
sensors.yaml | 1,250+ | 6 air probes, 3 soil probes, COβ, light, water flow, hydro quality, derived metrics (VPD, enthalpy, DLI), solar position |
globals.yaml | 584 | State variables, setpoint storage, counters, accumulators |
controls.yaml | 1,100+ | 42-state climate machine, irrigation scheduler, mister pulse-rotation, grow light DLI automation, occupancy-triggered lighting, economiser |
tunables.yaml | 851 | 57 number entities + 8 switch entities exposed to the AI planner |
Key Design Decisions
The controller is autonomous. If the AI, database, and network all fail simultaneously, the ESP32 continues operating on its last-known setpoints. If those are lost, it falls back to hardcoded safe defaults. The AI makes it smarter β it doesnβt make it dependent.
Setpoints, not relay commands. The AI adjusts boundaries (temperature bands, VPD thresholds, mister timing). The firmware makes real-time equipment decisions at 5-second resolution within those boundaries. This separation means the AI can be slow (minutes) while the controller is fast (seconds).
Everything is tunable. 57 numeric parameters and 8 boolean switches are exposed as ESPHome number/switch entities. The AI can adjust any of them at any time via aioesphomeapi or HTTP pull.
Data Pipeline
ESP32 (aioesphomeapi) βββ Ingestor (Python) βββ TimescaleDB
β
Tempest (HA API) ββββββββββββββ€
Shelly EM50 (HTTP) ββββββββββββ€
Open-Meteo (HTTP) βββββββββββββ€
Frigate (MQTT) ββββββββββββββββ
Ingestor Architecture
One Python process, four async coroutines:
| Coroutine | Purpose |
|---|---|
esp32_loop() | Persistent aioesphomeapi connection. Subscribes to all entity state changes. Buffers readings. Shares the connection for setpoint pushes. |
flush_loop() | Every 2 minutes, batch-writes buffered sensor data to the climate table. Merges outdoor weather onto the same rows. |
task_loop() | 10 periodic tasks on independent schedules: setpoint dispatcher (5min), alert monitor (5min), Shelly sync (5min), Tempest sync (5min), HA sensor sync (5min), forecast sync (1hr), reactive planner (5min), matview refresh (5min), grow light tracker (daily), water flow sync (5min). |
mqtt_loop() | Subscribes to Frigate occupancy topic. Writes to system_state table. Pushes occupancy to ESP32 for mister/fog inhibit. |
Database
29 tables in TimescaleDB (PostgreSQL 16 + hypertable extensions):
- Hypertables (time-series, compressed at 7 days):
climate,equipment_state,energy,setpoint_changes,setpoint_plan,weather_forecast,diagnostics - Crop tables (event-driven):
crops,crop_events,observations,treatments,harvests - Planning (AI-generated):
plan_journal,planner_lessons - Config/reference:
sensor_registry,equipment_assets,irrigation_schedule,nutrient_recipes,daily_summary
34 views and 14 functions provide derived analytics β from v_greenhouse_now (single-row snapshot of everything) to fn_glazing_transmission(azimuth) (physics-based light model).
Full schema: Data Model
AI Planning Prompts
The setpoint planner runs as an OpenClaw isolated agentTurn cron job. The prompt is ~4,000 tokens and instructs the agent to:
- Validate the previous plan (score 1β10, extract lessons)
- Gather 20+ data sections via
gather-plan-context.sh - Check active learned lessons
- Reason across temperature, VPD, misting, economiser, irrigation, lighting, safety
- Write time-keyed waypoints to
setpoint_plan - Record a journal entry with hypothesis and experiment
- Post summary to Slack
The prompt includes the full firmware state machine reference so the planner understands what each setpoint actually controls at the hardware level.
Full planning architecture: AI Planning Loop
This Website
Built with Quartz 4.5.2 β a static site generator designed for Obsidian vaults. The content lives in markdown files with YAML frontmatter, wikilinks, and embedded Grafana panels.
| Component | Technology |
|---|---|
| Static site generator | Quartz 4.5.2 |
| Content format | Obsidian-compatible Markdown |
| Dashboard embeds | Grafana solo panels via iframe |
| Hosting | nginx (Alpine) container β Traefik β Cloudflare |
| Build | npx quartz build β docker recreate |
| Branding removal | nginx sub_filter CSS injection |
The entire site rebuilds in under 3 seconds. Content changes deploy with one command: /srv/verdify/scripts/rebuild-site.sh.
Replicate This
To build a similar system, you need:
| Component | Cost | Purpose |
|---|---|---|
| ESP32 board (Kincony KC868-E16P or similar) | ~$80 | Controller with relay outputs + RS485 |
| Climate probes (RS485 Modbus) | ~$15-30 each | Temperature + humidity per zone |
| Soil probes (DFRobot SEN0600/SEN0601) | ~$20-30 each | Soil moisture, temp, EC |
| Linux VM or Raspberry Pi | ~$0-50 | Runs ingestor, DB, Grafana, agent |
| TimescaleDB (Docker) | Free | Time-series database |
| Grafana OSS (Docker) | Free | Dashboards |
| ESPHome | Free | Firmware framework |
| OpenClaw | Free | AI agent runtime |
| Weather station (optional, Tempest) | ~$300 | Outdoor conditions + forecast validation |
Total hardware cost: ~$200-500 depending on sensor count. All software is free and open source.
OpenClaw β Β· ESPHome β Β· TimescaleDB β Β· Grafana β Β· Quartz β