Skip to main content
Selora Homes Selora Homes
Recipe · v1.0.0 · Released Aug 28, 2026

Irrigation Rain Skip

Installs a Home Assistant automation that checks the day's forecast each morning and skips your irrigation when enough rain is on the way, then re-enables it when the forecast is dry. Add a speaker and it tells you when it skips a day.

Sprinklers that run the morning after a downpour are the classic wasted water, and the classic sign of a system that does not look at the sky. This recipe gives yours a forecast. Each morning, before the usual watering time, it reads the day’s expected rainfall and, if enough is coming, disables the irrigation so the schedule sits the day out. When the forecast is dry it re-enables the schedule, so a skipped day never turns into a skipped week.

The rain threshold is yours to set, in millimetres of expected rainfall for the day. A few millimetres is enough to skip on, since that much rain does the watering for you; raise it if your garden needs more, lower it to skip on the lightest shower. The check runs at a time you set, early enough to land before a normal morning schedule.

Pointing it at the right switch

The recipe disables and re-enables the controller through a switch, so point it at the one that turns the schedule or the whole controller on and off, not a single zone’s run switch. That way a skip stops the day’s program cleanly.

As a second line of defence, the recipe only ever switches back on what it switched off itself. It records that it skipped a day, and a dry morning restores the controller only when that record is set. Nothing gets turned on that the recipe did not turn off, so a switch picked by mistake cannot be started by a dry forecast.

Add speakers as the optional media-player role and the recipe tells you on the mornings it skips, so a dry-looking day that the forecast says will turn wet is not a mystery when the sprinklers stay off.

Files Selora AI's pipeline reads to install this recipe. The manifest declares roles, inputs, and integrations; the package files are Jinja-templated YAML applied to Home Assistant.

# Irrigation Rain Skip — each morning, read the day's forecast rainfall and
# disable irrigation when enough rain is coming, re-enabling it on a dry day.
#
# The forecast comes from a weather entity (Met.no by default) via
# weather.get_forecasts, read into a response variable and the day's
# precipitation pulled from the first daily entry. The irrigation role is a
# switch, and the recipe expects the schedule or master switch: turn_off skips
# the day, turn_on restores it. The weather entity id is an input so a home
# whose entity is named differently, or on OpenWeatherMap, can point at it.
slug: irrigation-rain-skip
version: 1.0.0
title: Irrigation Rain Skip
tagline: A wet forecast skips the day's watering, and a dry one puts the schedule back.
description: >-
  Checks the day's forecast rainfall each morning and disables irrigation when
  enough rain is expected, re-enabling it when the forecast is dry. Reads the
  forecast from the Met.no weather integration with no account or API key.
  Optionally announces the days it skips.
author: Selora AI
released: "2026-08-28"
min_integration_version: "0.12.0"
tags: [outdoors, irrigation, weather]
roles:
  # Irrigation schedule / master switch — multi-select. kind switch.
  # min_count 1. Point at the schedule or controller switch, not a zone run
  # switch, so turn_off skips rather than starting a zone. The role id says
  # control, not zones, because naming it after zones invited picking exactly
  # the entities that must not be picked. The skipped flag in the package is
  # the real guard: the recipe only ever switches back on what it switched off.
  - id: irrigation_control
    title: Irrigation control
    kind: switch
    min_count: 1
    # One master or schedule switch, as the description says. The skip records
    # whether it turned the control off; with several picked, a mix of on and
    # off controls could not be restored from a single flag.
    max_count: 1
    selection: required
    description: >-
      The irrigation schedule or master switch, not an individual zone. The
      recipe switches it off to skip a rainy day and back on once the rain has
      passed.
  # Optional announcement speakers over TTS. media_player. min_count 0.
  - id: announce_speakers
    title: Announcement speakers
    kind: media_player
    min_count: 0
    selection: required
    description: >-
      Optional. Speakers that announce a skipped day over TTS. Leave empty to
      run silently.
inputs:
  # The weather entity read for the forecast. Met.no's default entity is
  # weather.forecast_home; change it to match a differently named entity or an
  # OpenWeatherMap one.
  - id: weather_entity
    type: string
    label: Weather entity
    description: >-
      The weather entity the forecast is read from.
    default: "weather.forecast_home"
  # Expected rainfall for the day, in millimetres, at or above which the day is
  # skipped. A few millimetres does the watering for you.
  - id: rain_threshold_mm
    type: number
    label: Rain threshold (mm)
    description: >-
      Expected rainfall for the day at or above which irrigation is skipped.
    default: 3
    min: 0
    max: 100
  # When the morning forecast check runs. Set it before your normal watering
  # time.
  - id: check_time
    type: string
    label: Check time
    description: >-
      The time each morning the forecast is checked. Set it before your watering
      schedule.
    default: "05:00:00"
  # TTS engine entity for the optional spoken announcement. Auto-resolved by the
  # tts_engine resolver from the home's configured tts.* entities, so the wizard
  # never shows this field. tts.speak works without a paid Home Assistant Cloud
  # subscription. The resolver returns an empty string when the home has no TTS
  # engine, and the template then skips the announcement. The default below is
  # only used by the offline recipe-validation gate.
  - id: tts_engine
    type: string
    label: Text-to-Speech engine
    description: >-
      Speech engine entity used for the spoken announcement. Resolved
      automatically from the Text-to-Speech engines configured in Home
      Assistant.
    resolver: tts_engine
    default: "tts.piper"
integrations:
  # Met.no provides the daily forecast from the home's location with no account
  # or API key, so no auto_setup fields are needed.
  - domain: met
    title: Met.no
package_files:
  - package/helpers/skip_state.yaml.j2
  - package/automations/rain_skip.yaml.j2
{# Irrigation Rain Skip: a single morning automation.

   It calls weather.get_forecasts on the weather entity for the daily forecast,
   reads the day's precipitation from the first entry into a variable, then a
   choose either disables the irrigation (rain at or above the threshold) and
   announces it, or re-enables it (a dry day). The weather entity id and the
   threshold are rendered into the runtime templates at install time, so the
   forecast lookup uses the home's actual entity.

   The dry branch also requires a forecast that actually returned a reading, so
   a failed or empty lookup leaves an existing skip in place rather than reading
   as a dry day and restoring the schedule blind.

   The flag is raised only when the control was actually on, so the recipe owns
   the skip only when it caused it. A control already off for the winter, or
   stopped by the homeowner, is left alone and never claimed, which is what
   makes the dry-day branch safe to act on the flag at all.

   The dry-day branch re-enables only when the recipe is the one that disabled
   it, tracked by a flag helper. The role accepts any switch, and on most
   irrigation integrations a zone switch starts watering that zone when it is
   turned on, with nothing here to turn it back off. Without the flag a dry
   morning would switch on whatever was picked, so a homeowner who chose zone
   switches rather than the schedule switch would have every zone running
   unattended. With it, the recipe can only ever undo its own skip. #}
{% set su = slug | replace('-', '_') %}
{% set we = inputs.weather_entity %}
{% set skipped = 'input_boolean.' ~ su ~ '_skipped' %}
{% set control = roles.irrigation_control[0] %}
automation:
  - id: selora_recipe_{{ su }}_rain_skip
    alias: "Irrigation Rain Skip: check forecast"
    description: >-
      Each morning, check the day's forecast and skip irrigation if enough rain
      is expected, otherwise restore a schedule this recipe skipped.
    mode: single
    trigger:
      - platform: time
        at: "{{ inputs.check_time }}"
      # A check_time that passes while Home Assistant is down is lost, leaving
      # irrigation running into a wet day or a recipe-owned skip sitting on
      # through a dry one. Re-asking on startup is safe precisely because both
      # branches are guarded: the skip only acts on a control that is on, and
      # the restore only on a skip this recipe owns and a forecast that actually
      # returned a reading.
      - platform: homeassistant
        event: start
    action:
      - service: weather.get_forecasts
        target:
          entity_id: {{ we }}
        data:
          type: daily
        response_variable: forecast
      - variables:
          # forecast.precipitation is in the weather entity's precipitation_unit,
          # which is inches on an imperial Home Assistant. The threshold input is
          # millimetres, so convert before comparing: without this a 3 mm
          # threshold silently becomes 3 inches and an ordinary wet forecast
          # never skips a thing.
          precip: "{% raw %}{% set fc = forecast.get('{% endraw %}{{ we }}{% raw %}', {}).get('forecast', []) %}{% set fall = (fc[0].precipitation | float(0)) if fc else 0 %}{{ (fall * 25.4) if state_attr('{% endraw %}{{ we }}{% raw %}', 'precipitation_unit') == 'in' else fall }}{% endraw %}"
          # Whether the call actually came back with a usable reading. An empty
          # forecast, or an entry carrying no precipitation, must not be read as
          # a dry day: precip falls back to 0, and the restore branch would then
          # switch the irrigation back on knowing nothing about the weather.
          # Leaving a skip in place for a day costs a day of watering; lifting
          # it blind can water straight through a storm.
          forecast_ok: "{% raw %}{% set fc = forecast.get('{% endraw %}{{ we }}{% raw %}', {}).get('forecast', []) %}{{ fc | count > 0 and 'precipitation' in fc[0] and fc[0].precipitation is not none }}{% endraw %}"
      - choose:
          - conditions:
              - condition: template
                value_template: "{% raw %}{{ precip | float(0) >= {% endraw %}{{ inputs.rain_threshold_mm }}{% raw %} }}{% endraw %}"
            sequence:
              # Only skip, and only claim the skip, when the irrigation was
              # actually set to run. A control that is already off is a seasonal
              # shutdown or the homeowner's own decision; raising the flag there
              # would make the next dry morning switch their irrigation back on
              # for them, which is exactly what the flag exists to prevent.
              # Announcing is inside the guard too, so the recipe never reports
              # skipping a watering that was never going to happen.
              - if:
                  - condition: state
                    entity_id: {{ control }}
                    state: "on"
                then:
                  - service: switch.turn_off
                    target:
                      entity_id: {{ control }}
                  - service: input_boolean.turn_on
                    target:
                      entity_id: {{ skipped }}
{% if roles.announce_speakers and inputs.tts_engine %}
                  - service: tts.speak
                    target:
                      entity_id: {{ inputs.tts_engine }}
                    data:
                      media_player_entity_id:
{% for player in roles.announce_speakers %}
                        - {{ player }}
{% endfor %}
                      message: "{% raw %}Rain is expected today, so today's watering is skipped.{% endraw %}"
{% endif %}
        default:
          - if:
              - condition: template
                value_template: "{% raw %}{{ forecast_ok | bool(false) }}{% endraw %}"
              - condition: state
                entity_id: {{ skipped }}
                state: "on"
            then:
              - service: switch.turn_on
                target:
                  entity_id: {{ control }}
              - service: input_boolean.turn_off
                target:
                  entity_id: {{ skipped }}
{# Irrigation Rain Skip helper: a flag marking that the recipe is the one that
   switched the irrigation off for a rainy day. The dry-day branch requires it
   before switching anything back on, so the recipe can only ever undo its own
   skip and never starts irrigation the homeowner had stopped themselves. It
   survives a restart, so a skip in progress is still cleared the next dry
   morning. #}
input_boolean:
  {{ slug | replace('-', '_') }}_skipped:
    name: "Irrigation Rain Skip: skipped for rain"
    icon: mdi:weather-pouring

Type to search across

↑↓ navigate ↵ open
⌘K search