Skip to content

Module M2 - Basic Irrigation

Files: packages/grow_irrigation.yaml (this module), packages/grow_core.yaml (grow_plant_count / grow_dripper_count / grow_dripper_flow_lph - Phase 1, and the sensor.grow_substrate_*_avg sensors used for the failsafe and the historical chart), packages/grow_actuators.yaml (switch.grow_actuator_irrigation_pump_1 / _2 - the pumps this module drives).

What’s implemented (Phase 4)

Replaces the old Growzelt Bewässerung - P1/P2, Cannabis Bewässerung - Notabschaltung, and Failsafe Bewässerung - 35 % automations with:

  • An unlimited, dynamically editable schedule per pump - see “Why schedule: instead of a fixed number of slots” below.
  • Manual trigger - a “Run Now” button on Dashboard 2, using its own configurable duration/zone.
  • User-configurable safety limits (R10): max runtime per event, and max events per day. Both have sensible defaults (60s, 8/day) but are yours to change.
  • Substrate-moisture failsafe: if the mapped substrate sensors’ average VWC drops below a configurable threshold (default 35%, matching the automation this replaces), all irrigation - scheduled and manual - is blocked until VWC recovers or you clear it by hand.
  • A pump watchdog safety net independent of the above, in case a script run is ever interrupted.
  • A historical chart (VWC + EC + both pumps’ on/off activity overlaid) so you can see how substrate moisture actually responds to each watering event, not just the live number.

Why schedule: instead of a fixed number of slots

The first cut of this module (shipped 2026-08-24) used 6 fixed event slots, each its own input_datetime/input_number/input_select. That’s a real Home Assistant limitation, not a design choice: input_* helpers are declared in YAML and their count is fixed at config time - there’s no way to let you add a 7th one from the dashboard without editing a package file and restarting, no matter how the dashboard around it looks. It also got cluttered fast (6 cards × 4 fields each).

The redesign uses Home Assistant’s native schedule: integration instead - the same “weekly time-block editor” used for things like thermostat schedules. Each schedule.* entity holds as many time blocks per day as you want, added/removed/resized with no YAML edit and no restart once it exists.

Important caveat, discovered 2026-08-25 after the first version of this redesign shipped: a schedule: entity defined in package YAML has no editor at all - its more-info dialog shows only generic History/Activity, nothing to click, confirmed from a screenshot on the live instance. Only a schedule helper created through the Home Assistant UI (Settings -> Devices & Services -> Helpers -> ”+ Create Helper” -> Schedule) gets the real weekly block editor, because editing a schedule’s blocks goes through a storage-backed config-entry API that a YAML-defined entity simply doesn’t have - the same category of limitation as HA’s other input_*/counter/ timer helper domains, which normally hide this because their value can still be set from a YAML-defined entity even though their definition technically can’t - but for schedule, the block list effectively is the definition, so there’s no value left to set from outside. This package therefore does not define schedule: itself anymore; you create the two schedule helpers once, by hand, and rename their Entity ID to match what the automations below expect - see the rollout checklist. Still 100% native Home Assistant (R1 unaffected, no pyscript/custom integration involved) - just “created via the UI” instead of “created via YAML”, the same one-time-manual-step category as pairing a Zigbee device or installing the apexcharts-card HACS card elsewhere in this project.

There’s one schedule entity per pump (schedule.grow_irrigation_pump_1_schedule / schedule.grow_irrigation_pump_2_schedule) rather than one shared schedule, because a schedule block doesn’t carry custom fields like “which zone” - separate schedules per pump keeps that simple, and lets both pumps run at once if their blocks happen to overlap (this replaces the old “Both” slot option, which is still available for the manual-run button).

How a scheduled event actually fires

automation.grow_irrigation_schedule_pump_1 (and the _pump_2 twin) triggers when that pump’s schedule entity turns on - i.e. a block just started. It reads the block’s own length from the entity’s next_event attribute (the time it will next turn back off) at that moment, so an arbitrary block of any length becomes that event’s requested duration - still passed through the same script.grow_irrigation_run_event used by the manual button, which hard-clamps it to “Max Runtime / Event” no matter how long the block itself was set to. Both the master switch (grow_irrigation_control_enabled) and this shared clamp apply identically regardless of how an event started.

P1 (ramp-up) / P2 (maintenance) shot sizing

Classic crop-steering terminology: right after lights-on, substrate is typically dry from the overnight dry-back, so you want bigger shots to bring VWC up to its daily max quickly (P1 / ramp-up); for the rest of the day you only want small maintenance shots to hold VWC in a band without overwatering (P2 / maintenance). This is a different axis than “Pump 1” vs “Pump 2” (which zone) - P1/P2 here is which part of the day it is, and applies to whichever pump’s schedule block just fired.

  • input_datetime.grow_irrigation_p1_window_start / grow_irrigation_p1_window_end define the ramp-up window (default 06:00-09:00); everything outside it is P2. Handles a window that wraps past midnight too.
  • input_number.grow_irrigation_p1_max_duration_sec (default 90s, bigger) and grow_irrigation_p2_max_duration_sec (default 20s, smaller) are the two shot-size caps.
  • binary_sensor.grow_irrigation_p1_window_active shows which one is currently in effect.
  • This still works alongside the schedule, not instead of it - you still draw blocks in each pump’s schedule for when to water; P1/P2 caps how big a shot gets to be based on when it actually runs (real time at execution, not when the block was drawn).
  • This is not a live-VWC feedback loop (i.e. “keep shooting until VWC hits its max, then hold it there”) - that’s full crop-steering territory, deliberately deferred per the Phase 0 plan (Section 2’s R1 note on the “Crop-Steering-Zustandsmaschine” being more than Grundfunktion scope). This gives you manual control over shot size by time of day, which covers the “more water to reach max VWC, less to maintain it” need without that full complexity.

Safety limits (R10)

  • input_number.grow_irrigation_max_duration_sec - the absolute safety ceiling, applied on top of whichever P1/P2 cap applies (tightest of the three wins). Whatever duration a schedule block or the manual run requests, the actual run time is min(requested, P1_or_P2_cap, max_duration_sec) - this clamp happens inside the script itself, so an oversized schedule block (or a misconfigured P1/P2 cap) can never run longer than this ceiling.
  • input_number.grow_irrigation_max_events_per_day - enforced via a native HA counter: entity (counter.grow_irrigation_events_today), which survives restarts (restore: true) and resets at midnight (automation.grow_irrigation_daily_counter_reset). Once the limit is hit, every further event (scheduled or manual, either pump) is blocked and logged until the reset.
  • binary_sensor.grow_irrigation_daily_limit_reached - convenience sensor the dashboard uses to show a warning banner when the limit is hit.

Failsafe (substrate moisture)

  • input_number.grow_irrigation_failsafe_vwc_threshold - default 35%, matching the automation this module replaces.
  • Compared against sensor.grow_substrate_vwc_average (Phase 1, averages whichever substrate sensors are mapped and included). Note: this sensor’s entity_id had a naming bug until 2026-08-25 (see docs/docs/troubleshooting.md / CHANGELOG 0.7.2) - if your failsafe never seems to trip or the Live Substrate Readings tiles say “entity not found”, you’re on the pre-fix version; re-deploy packages/grow_core.yaml and rename the 3 live entities as described there.
  • If VWC is below threshold: input_boolean.grow_irrigation_failsafe_tripped turns on, a single persistent_notification fires, and all irrigation is blocked - scheduled and manual - until it clears.
  • Auto-recovery with hysteresis: clears automatically once VWC rises to threshold + 5 percentage points (avoids flapping right at the edge), logging and notifying once on recovery.
  • Manual clear: a “Clear Failsafe” button on the dashboard (script.grow_irrigation_reset_failsafe) for when you’ve fixed the underlying issue yourself and don’t want to wait for the sensor to catch up, or when no substrate sensors are mapped at all (in which case the failsafe simply never trips - Phase 1 substrate sensors are optional, and irrigation isn’t blocked just because you don’t have them).

VWC Lock (upper-bound schedule gate, 2026-08-27)

The failsafe above stops watering when substrate is too dry. VWC Lock is the complementary opposite: it stops the schedule from watering when substrate is already wet enough, so you don’t keep dosing past your target and risk runoff/oxygen-starved roots.

  • input_boolean.grow_irrigation_vwc_lock_enabled - off by default; the feature does nothing until you turn it on.
  • input_number.grow_irrigation_vwc_lock_threshold - default 65%, compared against sensor.grow_substrate_vwc_average.
  • binary_sensor.grow_irrigation_vwc_locked - on when VWC is at or above the threshold. Stateless, no hysteresis by design (matches the feature as requested) - it re-evaluates fresh every time, unlike the failsafe’s auto-recovery-with-hysteresis behavior. If the substrate average is unknown/unavailable, this reads as not locked (fails open, so a missing sensor doesn’t silently block watering).
  • Applies only to the two schedule.grow_irrigation_pump_* automations. Manual Run is deliberately unaffected - if you tap “Run Now” you clearly want water right then regardless of what the schedule would decide.
  • When a scheduled block fires while locked, the pump does not run, and a “SKIPPED: VWC Lock active” line is written to input_text.grow_irrigation_last_decision so you can see it happened rather than wondering why a block didn’t fire.
  • Surfaced as a 🔒 banner on Dashboard 2’s Irrigation & Substrate view whenever both the toggle is on and the lock is currently engaged.

Lifetime delivered-volume tracking (2026-08-27)

input_number.grow_irrigation_total_volume_l accumulates the estimated volume (see “Water volume” below) of every event this module runs - scheduled and manual alike, both pumps - starting from when you first deploy this version. It’s automation-managed (the run script increments it as the last step of every FINISHED event) - you shouldn’t need to edit it by hand except to reset it. This is what feeds the Energy module’s nutrient-cost tracking (docs/docs/module-energy.md): every liter delivered is treated as nutrient solution dosed, per the confirmed scope for this feature.

Crop-Steering Presets (Phase 5, R12, 2026-08-28)

Per the Phase 0 plan, Phase 5 (“Substratüberwachung & Crop-Steering-Basis”) calls for VWC/EC/Temp trend visualization plus generative/vegetative preset templates (R12). The trend-graph half was already delivered as a byproduct of the 2026-08-25 redesign (see “Historical chart” below); this adds the remaining piece.

input_select.grow_irrigation_crop_steering_preset offers two named presets - Vegetative (wetter, more frequent watering, promotes vegetative growth) and Generative (drier, bigger dry-back between waterings, promotes flowering/generative growth). Selecting a value in the dropdown changes nothing by itself - tapping the “Apply Preset” button (script.grow_irrigation_apply_crop_steering_preset) does a one-time bulk write of 5 existing fields to that preset’s starting values:

FieldVegetativeGenerative
VWC Lock Threshold70%55%
Failsafe VWC Threshold40%28%
P1 (Ramp-Up) Max Runtime90s60s
P2 (Maintenance) Max Runtime25s12s
Max Events / Day106

These are proposed starting values (confirmed with the user), not a fixed recipe for your specific genetics/substrate - every field stays freely, individually adjustable afterward, exactly like before applying a preset. Applying does not touch the Absolute Max Runtime ceiling (grow_irrigation_max_duration_sec) or the master Automatic Control switch - a preset only affects “how wet to keep the substrate and how often”, never the hard safety limits or whether automation is active at all. Applying logs a line to input_text.grow_irrigation_last_decision and fires a persistent_notification naming exactly what changed, same visibility pattern as every other action in this module.

This is deliberately the “Crop-Steering-Basis”, not the full “Crop-Steering-Zustandsmaschine” (a live VWC-feedback state machine that waters until a target VWC is reached, then holds it there) - the Phase 0 plan’s own risk table calls that out as more complexity than native YAML-only automations (R1) should take on, and it remains deliberately deferred. Applying a preset is a single dashboard-button action, not a running automation - it never re-applies itself, never overrides a value you change afterward, and there is no “currently active preset” state tracked anywhere (the select just remembers your last choice as a convenience for next time).

Pump watchdog (independent safety net)

automation.grow_irrigation_pump_watchdog is deliberately not part of the run script - it’s a separate automation that watches both irrigation pump switches directly, unaffected by anything above. If either has been on for longer than max_duration_sec + 30s, it force-turns the pump off, logs a WATCHDOG entry to grow_irrigation_last_decision, and sends a notification. This exists for the “Hard-Timeout je Event” risk from the Phase 0 plan’s risk table (Section 6.4) - script interruption (HA restart mid-event, power loss, etc.) should never be able to leave a pump running unattended.

Historical chart (VWC / EC / pump activity)

Dashboard 2’s Irrigation & Substrate view has a time-range-selectable chart (input_select.grow_irrigation_chart_range, same 1h/6h/24h/3d/7d pattern as the Climate & VPD chart) plotting sensor.grow_substrate_vwc_average and sensor.grow_substrate_ec_average on their own visible y-axes, with both switch.grow_actuator_irrigation_pump_1 / _2 overlaid as filled step areas (on a hidden 0-1 axis each) so you can see exactly when each pump ran against how VWC/EC moved in response - the actual point of a substrate chart for irrigation, not just a live number. EC is plotted unconditionally since sensor.grow_substrate_ec_average already exists from Phase 1; it will simply show flat/unavailable if you don’t have EC-capable substrate probes mapped.

Water volume

sensor.grow_irrigation_manual_run_volume shows the estimated volume (in liters) for the current manual-run duration, using grow_dripper_count * grow_dripper_flow_lph * (duration_sec / 3600) from the Phase 1 helpers in packages/grow_core.yaml. The same formula is used internally for every event (scheduled or manual) and written into the _last_decision log line for that run, so you always know how much water each event used without needing a dedicated sensor per slot.

Dashboard layout note (2026-08-25)

The first cut of the redesigned view nested a 2-column type: grid inside an already-narrow Lovelace masonry column, which truncated long row labels (“Failsafe VWC Threshold” showing as “Fa…”) to the point of being unreadable. Fixed by removing that inner grid - cards now stack vertically in view order and let Lovelace’s own masonry layout handle column placement across the page width, the same approach the other 3 views on this dashboard already use. The only remaining grid card is the small 2-tile pump row, which is the right use case for it (small square tiles benefit from an explicit grid; wide entities rows with real text don’t).

Rollout checklist

  1. Deploy packages/grow_irrigation.yaml and packages/grow_core.yaml, check config, restart (new helpers/counter entities need one the first time). packages/grow_irrigation.yaml no longer defines schedule: entities - see step 2.
  2. Create the two schedule helpers manually (one-time, via the UI, not YAML) - see “Why schedule: instead of a fixed number of slots” above for why this step can’t be automated away:
    • Settings -> Devices & Services -> Helpers -> ”+ Create Helper” -> Schedule -> name it anything, save.
    • Open it -> gear icon -> set Entity ID to exactly schedule.grow_irrigation_pump_1_schedule.
    • Repeat for Pump 2 with entity_id schedule.grow_irrigation_pump_2_schedule.
    • If you already created a schedule: entity via the earlier YAML-based version of this package, delete it first (Settings -> Helpers -> it will show there once no longer defined by the package) so the two entity_ids above are free to use.
  3. Re-import dashboards/dashboard_2_control_panel.yaml to get the redesigned “Irrigation & Substrate” view.
  4. One-time cleanup if you deployed the original 6-slot version: the old input_boolean/input_datetime/input_number/input_select event-slot helpers (grow_irrigation_event_1..6_*) are no longer defined by this package and will show as orphaned/unavailable in Settings -> Helpers - safe to delete.
  5. If the “Live Substrate Readings” tiles or the failsafe never seem to work, confirm the 3 sensor.grow_substrate_*_average entities exist with exactly that entity_id (Settings -> Entities, search “grow_substrate”) - rename any that don’t match via that entity’s gear icon -> Entity ID field (same fix pattern as the actuator entity_id issue from Phase 3 - see docs/docs/module-climate.md “Known issue”).
  6. Confirm grow_dripper_count and grow_dripper_flow_lph (Dashboard 1, Phase 1) match your real hardware - the volume estimates depend on them.
  7. Set your safety limits (max runtime/event, max events/day), the P1/P2 window and shot-size caps, and the failsafe VWC threshold to values you’re comfortable with. 7a. If you want the schedule to stop watering once substrate is wet enough, set input_number.grow_irrigation_vwc_lock_threshold and turn on input_boolean.grow_irrigation_vwc_lock_enabled. Leave it off if you don’t want this behavior - Manual Run is never affected either way.
  8. Disable any old Growzelt Bewässerung - P1/P2, Cannabis Bewässerung - Notabschaltung, and Failsafe Bewässerung - 35 % automations, so they don’t fight this module over the same pumps.
  9. Configure your schedules by tapping each schedule.grow_irrigation_pump_* entity on the dashboard (now that it’s a UI-created helper, this opens the real block editor) and adding blocks, with grow_irrigation_control_enabled still off, and test with the “Run Now” manual button first.
  10. Once you’re confident, turn grow_irrigation_control_enabled on.
  11. Watch Dashboard 3’s “Irrigation Configuration & Automation Status” and “Device Activity” logbook cards, plus the new VWC/EC chart, for the first day or two to confirm events run when/how expected, the right P1/P2 phase is applying, and substrate moisture actually responds.
  12. Optional - Crop-Steering Presets: on the Irrigation & Substrate view, pick “Vegetative” or “Generative” from the new preset dropdown and tap “Apply Preset” whenever you want a one-tap starting point for VWC Lock Threshold, Failsafe VWC Threshold, P1/P2 shot sizes, and Max Events/Day - see “Crop-Steering Presets” above for the exact values. Nothing changes until you tap Apply, and every field stays adjustable afterward - skip this entirely if you’d rather keep tuning those 5 fields by hand.