Crop Steering for Home Assistant

An autonomous four-phase crop-steering irrigation controller. It runs the daily P0 → P1 → P2 → P3 cycle per zone from live VWC/EC probes, sequences your pump and valves safely, and steers each zone vegetative or generative — self-hosted, no subscription.

Home Assistant add-on + integration 1–24 zones pure decide() core, offline-tested irrigation only MIT

What it is

Two clean layers. The integration is the data layer — a setup wizard and ~100 entities, and it never touches hardware. The f2-control add-on is the engine — the only thing that opens a valve, gated by a hard kill switch. They talk over the Home Assistant REST API.

Integration custom_components/crop_steering/ • config-flow setup wizard • ~100 entities (number / switch / select / sensor) • per-zone sensor fusion • pure calculation helpers Never touches hardware The data layer. f2-control add-on — the engine addons/f2_control/ controller.py • REST poll every 60 s • build snapshot → decide() • sequence pump/mainline/valve • republish status, 30-min vitals KILL SWITCH input_boolean.f2_control_enabled · OFF = safe crop-steering-engine pure decide() core • phase logic + EC steering • safety + anti-lockout • no HA, no I/O — unit-tested setpoints + state (REST) imported by pump → mainline → zone valve → substrate

The feedback loop

sensors → entities → engine decision → hardware → substrate changes → sensors. Every 60-second poll can trigger a re-evaluation, per zone, independently.

Why two pieces

Keeping the decision logic pure and HA-free means the exact same decide() runs in the add-on and in an offline unit test — the behaviour you review is the behaviour that waters your plants.

The daily cycle

A “grow-day” is one photoperiod (lights-on → lights-on). Each zone walks four phases on its own clock — driven by how far the substrate actually dried back, not the wall clock. The size and aggressiveness of that curve is how you steer.

Lights ON — a new grow-day; counters resetLights OFF forces any zone to P3Emergency rescue shotif VWC drops below the zone floor (cap-exempt) P0 Morning dryback Hold all water. Record peak VWC,wait for a target % dryback —this sets up the whole day. P1 Ramp-up Progressive shots, growing each,until VWC hits the zone target.The morning rehydrate. P2 Maintenance Threshold top-ups whenever VWCdips below the EC-adjustedthreshold. EC tunes the timing. P3 Overnight dryback Planned watering stops; dry backthrough the dark. Emergency-onlyif VWC hits the floor. dryback hit / max waittarget · max shots · timeout~3 h before lights-offlights-on → loop to P0
One rule worth knowing: every “dryback” number is a percentage-point drop from the peak (how far it dries back by, never the value it dries back to), and the daily water/shot counters roll over at lights-on — the real start of a grow-day — not midnight.

The safety gate chain

Every shot, in any phase, clears this chain before a valve opens. Any failure blocks the shot and logs the reason (and surfaces it on the dashboard).

Field capacity is not a hard pre-shot gate — it's the ceiling the P1 ramp targets and a precondition for EC flushes. A hard anti-drown ceiling (~90% VWC) is what blocks a flood. The whole chain is dead unless the kill switch is ON.

Full feature list

Every feature below is in the shipping code — the integration (custom_components/crop_steering/), the add-on engine (addons/f2_control/) or the pure core (crop-steering-engine/).

ENGAutonomous P0–P3 engine7

Four-phase daily cycle, per zone
Every zone runs its own P0 morning-dryback → P1 ramp-up → P2 maintenance → P3 overnight cycle, timed to how the substrate actually behaves — not a clock. Row 1 can be ramping in P1 while Row 3 dries back in P3.
Dryback detection
Tracks each zone's peak VWC and a dryback-rate slope to drive the P0 wait and the predictive P3 start. Every dryback number is a percentage-point drop from the peak.
EC steering (substrate EC stacking)
The live EC ÷ target ratio nudges the P2 VWC threshold up or down, so the system feeds and dries to hit your EC, not just your moisture — the generative salt lever, run hands-off.
Optional PID EC loop
A real Kp/Ki/Kd controller (integral anti-windup, clamped) on pore-EC error as a drop-in upgrade to the stepped EC-steer. Off by default, behind one switch.
Vegetative / Generative steering
Set each zone toward leafy vegetative growth (high VWC, low EC, frequent small shots) or tight generative growth (deeper dryback, higher EC) — globally or per zone. A −100…+100 intent slider interpolates the P0 dryback target between the two.
Live shot sizing from real hardware
The add-on computes each shot's run-time from your real substrate volume and per-zone flow (plants × drippers × L/hr), so a %-of-substrate shot delivers exactly that. Published as sensor.crop_steering_p1/p2/p3_shot_duration_seconds.
Per-zone manual override
Flip a per-zone manual-override switch (with optional auto-expiry) to take one zone out of the auto phase logic without touching the others.

SENSensors & fusion4

Multi-probe sensor fusion
Map as many VWC/EC probes per zone as you like; the integration fuses each zone to one value (average + outlier rejection, a downward guard for VWC) and publishes sensor.crop_steering_vwc_zone_N / ec_zone_N. The engine steers on that fused value — one lying or dropped-out probe can't fire or block a shot.
Per-zone fused sensors + status
Each zone publishes a fused VWC (%) and EC (mS/cm) sensor plus a plain-English status: Optimal · Dry – Needs Water · Saturated · Sensor Error · Disabled. Plus all-zone averages.
EC ratio & adjusted threshold sensors
Surfaces the current EC-vs-target ratio and the P2 VWC threshold after the EC adjustment, so you can see why the engine is feeding when it is.
Water-usage tracking
Per-zone daily and weekly water (litres), per-zone shot count today, and global daily usage — the room's running tally.

SAFSafety & anti-lockout12

Hard kill switch
The whole engine is gated by input_boolean.f2_control_enabled. OFF = safe: it reads, decides and notifies but never opens a valve. On shutdown it safe-offs the pump, mainline and every valve.
Source-water quality gate
Irrigation is held while source pH or EC are out of band, with a last-known-good grace window and fail-closed behaviour if a feed probe dies — it won't push bad water into your slabs.
Fail-closed hardware sequencing
Pump → mainline → zone-valve with valve-close read-back. A failed pump/mainline/valve call aborts the shot, emergency-stops the pump, and is not counted — so state never lies after an error.
Anti-lockout high-EC flush
High pore-EC never silently locks a zone out: if the feed is dilutive the engine flushes to drive EC down rather than blocking. It only holds when the feed can't help or the slab is saturated.
Daily volume cap (budget, not wall)
A per-zone daily volume ceiling stops runaway watering — but genuine emergency rescue shots are exempt, so a truly dry plant is never denied water by a budget.
Max shot-duration cap
A hard ceiling on any single shot's run-time (default 900 s) so a substrate/flow fat-finger can't turn one watering into a multi-hour flood.
Anti-short-cycle interval
P2 EC-correction shots respect a minimum interval, so a no-runoff nibble can't stack EC and rapid-fire the pump.
Minimum daily water floor
An optional guaranteed mL-per-plant-per-day baseline, front-loaded and sensor-independent — a lying or dead VWC probe can't quietly starve a plant. Only a hard anti-drown ceiling holds it.
Lights-on watering watchdog
If an enabled zone gets no water for watchdog_hours (default 3) during lights-on while VWC is below threshold, it forces a shot — a starvation backstop independent of the phase logic.
Cross-zone under-drink flag
A zone drinking under 40% of the room median is surfaced as an alert — catches a stuck valve, a channelling row or a lying probe before it costs you a plant.
Blocked-dripper protection
A per-zone guard that abandons repeated emergency shots once a failure threshold is hit, instead of machine-gunning the pump at a blocked dripper. Toggleable per zone.
Setup health checks (HA Repairs)
Misconfiguration — missing kill switch, engine offline, a zone with no sensor, a fused sensor unavailable — shows up as read-only fix-it cards in Settings → Repairs that clear themselves when resolved.

SETSetup & configuration8

No-YAML config-flow wizard
A setup wizard with entity-picker dropdowns maps your valves, sensors and shared hardware and builds every entity — no YAML to hand-edit. Every field has a tooltip.
crop_steering.env auto-load
Alternatively load a single crop_steering.env file: it auto-detects the zone count, parses hardware and parameters, and warns about any missing entities.
1–24 zones
Configure anywhere from one zone to twenty-four, auto-detected from the .env or chosen in the wizard.
Global and per-zone tuning
Tune every P0/P1/P2/P3 setpoint, EC targets by phase and mode, field capacity, max EC, watchdog and substrate/dripper properties — globally, with per-zone overrides for plant count, daily volume, shot multiplier and all phase tunables.
Crop type & growth stage profiles
Pick a crop type (Cannabis variants, Tomato, Lettuce, Basil, Custom), growth stage and per-zone crop profile to drive sensible default EC targets.
Reconfigure without reinstall
Re-open the options menu to reload the .env, edit parameters, re-map zones or change features later — no reinstall, no wiping state.
Configure once
The add-on reads lights hours and zone count from the integration, so you set them once in the UI; no drift between the two.
Transparent in-place upgrades
Per-zone runtime state persists across restarts and add-on rebuilds, and old installs load in place with safe defaults — never wiping your progress or forcing a re-setup.

ROOMMulti-room1

Fully isolated additional rooms — autonomously steered
Add the integration again per grow room and the one add-on drives it too. Each room is fully isolated with its own zones, sensors, pump/mainline/valves, reservoir feed gate, photoperiod, setpoints, Repairs and durable state, namespaced crop_steering_<room>_*. A new room comes up behind its own fail-safe-OFF kill switch (switch.crop_steering_<room>_engine_enabled) until you arm it. View any room's dashboard with ?room=<slug> (e.g. f2.html?room=f1).

DASHDashboards & operator surface6

Operator console in the sidebar
The full console is served straight from the add-on over Home Assistant ingress as an authenticated sidebar panel — no manual file copy. Tabs: Overview · Substrate · Zones · Steering · Analyze · Climate · Floorplan.
Mobile vitals page
A phone-first one-pager with per-zone VWC and EC vs target, room climate, and pump/light/zone controls.
Live / Demo toggle
A built-in Demo mode runs on baked-in perfect-grow mock data so you can look around with nothing connected; Live drives your real Home Assistant data.
Click-to-fix advisories
Issues link straight to the exact control that resolves them, and a feed-lockout diagnostic names the precise gate stopping an under-watered zone (dosing/fill/flush, source-water gate, EC ceiling, daily-volume cap, manual override, zone disabled, kill switch).
Phone vitals & alerts
A periodic 30-minute operator summary plus urgent alerts go to your notify service.
Service events for automation
Phase transitions, irrigation shots, transition-condition checks, manual overrides and custom shots all fire Home Assistant events you can hook automations onto.

What it does not do

Kept honest on purpose — so you know exactly what you're running.

No climate control
It is irrigation only. It reads climate sensors for context and the dashboard shows VPD/CO₂/DLI, but the engine does not drive AC, dehumidifiers or lights.
No machine-learning / adaptive self-tuning
There is no ML predictor, Kalman fusion, adaptive Vmax detection or crop-profile AI. An experimental “intelligence” layer that aimed at those was retired; the integration still exposes a few inert *_intelligence_* switches that the live engine ignores. sensor.crop_steering_ai_heartbeat is a liveness ping, not a self-tuning brain.
Facility tiles don't scope per room
?room=<slug> scopes the crop-steering core (zones, phases, VWC/EC, setpoints, advisories, switches) of f2.html / overview.html. But dashboard tiles wired to non-crop_steering_ facility entities (camera, tank, climate probes) stay on the default room.

The dashboards

The add-on serves these over Home Assistant ingress as a sidebar panel (Live), and they also run standalone on mock data (Demo). The crop_steering_* entities are generic; a new facility maps its own sensors/valves — see docs/DASHBOARDS.md for the full populate guide.

PageWhat it isModeNotes
f2.htmlFlagship operator consoleLive + DemoOverview · Substrate · Zones · Steering · Analyze · Climate · Floorplan. Reads ~150 entities; writes setpoints + zone toggles.
overview.htmlMobile one-pagerLive + DemoPer-zone VWC/EC vs target, climate, pump/light/zone controls. Phone-first.
crop_steering.htmlEarlier full consoleLive + DemoZone-centric cards, inline setpoint editing, alerts. Superseded by f2.html but still live.
crop_steering_tune.htmlSetpoint Tune editorLiveVisual editor for the full P0–P3 per-zone setpoint matrix; writes number entities.
setpoints.htmlGrow recipe plannerlocalStorageDay/phase timeline of light, climate, irrigation and feed targets. Optional push-to-HA.
system-map.html3D system mapStaticThree.js diagram of the two layers, entity contract and hardware sequence. No HA needed.
floorplan/index.html3D facility floor planStaticThree.js facility layout, embedded in the Overview/Floorplan tab.
Crop Steering for Home Assistant · generated system guide · irrigation only · MIT. Engine logic is unit-tested in crop-steering-engine/tests/.