I have a new Home Assistant install (on a raspberry pi, from the official Home Assistant image, version 2025.12.2).
Over time I’m planning to automate a lot of things, at the moment I’m aiming to automate an irrigation system. This is modelled on one I previously had in OpenHab (moving away from Openhab), and will be based around an ESPHome set of switches (which are still shipping to me, so I’m using dummy switches in the meantime).
I’ve been extensively using AI (Grok and Gemini) to help me configure this, which works reasonably well, until it doesn’t. Both of them are in a loop with the problem I currently have, and I think that’s because the configuration style in this area has changed quite a bit recently, and so they’re reading conflicting documents and giving bad advice.
What I’m trying to do is to start the way I want to continue - to break my configuration into packages organised around a section of functionality, and to use the newest syntax for each element, not use deprecated syntax.
That means using template sensors for my sensors. I’m having a lot of difficulty getting a syntax that will let me include a sensors yaml file from each of my packages - at the moment it’s only reading one, and ignoring the other. It’s possible that I just have a syntax or inclusion error, however both AIs claim it’s because I can’t use “include_dir_named” on my packages without getting conflicts.
My folder structure is:
config
configuration.yaml
packages
irrigation.yaml
weather.yaml
irrigation
some other yaml files
sensors.yaml
weather
some other yaml files
sensors.yaml
The behaviour I have is that when I copy all my sensors into the config/sensors/weather/sensors.yaml file (i.e. all sensors in a single file), they correctly parse and work. When I split them between config/sensors/weather/sensors.yaml and config/sensors/irrigation/sensors.yaml (i.e. two separate includes both for sensors), only those in weather load.
I have tried with a few different syntaxes, in particular I’ve tried with the top level of the sensors.yaml being:
template:
- sensors:
- name: .....
Or
template:
sensors:
- name: ....
Both are behaving the same. Any assistance with pointing me in the right direction would be greatly appreciated. It feels like it’s just going to be a simple syntax thing, but I can’t for the life of me work out what it is. The AIs have variously suggested reverting to the legacy sensor syntax (not a good idea), using instead a different include (perhaps include_dir_list, but that would mean reindenting a lot of the yaml, and probably changing the packaging approach), or just putting them all in one file (which works, but feels like it’s giving up instead of doing things properly).
The actual files are as below.
# configuration.yaml
# Loads default set of integrations. Do not remove.
default_config:
# Load frontend themes from the themes folder
frontend:
themes: !include_dir_merge_named themes
scene: !include scenes.yaml
# This tells HA to look for files in the packages folder.
# Each file (like irrigation.yaml) becomes a package.
homeassistant:
packages: !include_dir_named packages
# irrigation.yaml
# ----------------------------------------------------------------------
# PACKAGE: Irrigation
# INTENT: Manifest file loading all irrigation components
# ----------------------------------------------------------------------
homeassistant:
packages: !include_dir_named irrigation
# weather.yaml
# ----------------------------------------------------------------------
# PACKAGE: Weather
# INTENT: Manifest file loading all weather components
# ----------------------------------------------------------------------
homeassistant:
packages: !include_dir_named weather
# ----------------------------------------------------------------------
# PACKAGE: Irrigation
# FILE: sensors.yaml
# INTENT: The watering adjustors based on seasons and recent rainfall,
# and configuration for how often we apply fertiliser to each line type
# Annoyingly the internal name that home assistant uses is based on the name, not
# the unique id provided. So you can't choose a human friendly name for the sensor
# whilst also having a coding-friendly id. Crazy, but there it is. You would think
# the unique_id field would do that, but apparently it doesn't. I think I can torture
# it to do what I want though by first creating a name field that will slugify to the
# unique id I want, then change that name field after the sensor has been instantiated.
#
# This file is currently refusing to load, Gemini claims it's an include
# problem. Whilst working through that, all these sensors are copied into the
# weather sensors file.
# ----------------------------------------------------------------------
template:
- sensor:
- name: "Seasonal Watering Multiplier"
unique_id: seasonal_watering_multiplier
unit_of_measurement: "%"
state: >-
{{ [150,180,180,130,60,35,20,20,35,60,90,120][now().month-1] }}
# J F M A M J J A S O N D
- name: "Landscape Water Every"
unique_id: landscape_water_every
state: >-
{{ [5,5,7,10,15,0,0,0,15,10,7,5][now().month-1] }}
# J F M A M J J A S O N D
- name: "Fruit Water Every"
unique_id: fruit_water_every
state: >-
{{ [2,2,2,3,4,6,0,0,0,5,3,2][now().month-1] }}
# J F M A M J J A S O N D
- name: "Landscape Fertilise Every"
unique_id: landscape_fertilise_every
state: >-
{{ [4,4,4,4,4,4,0,0,0,0,0,4][now().month-1] }}
# J F M A M J J A S O N D
- name: "Fruit Fertilise Every"
unique_id: fruit_fertilise_every
state: >-
{{ [2,2,2,2,2,3,0,0,0,0,0,2][now().month-1] }}
# J F M A M J J A S O N D
# Day of Year Helper
- name: "Day of Year"
unique_id: day_of_year
state: "{{ now().timetuple().tm_yday }}"
# ----------------------------------------------------------------------
# RAIN LONG ADJUST % → Used for trees, lawns (soil reservoir)
# ----------------------------------------------------------------------
- name: "Rain Long Adjust %"
unique_id: rain_long_adjust
unit_of_measurement: "%"
state: >
{% set m = now().month %}
{% set ratchet = [3,3,3,3,2,2,1,1,1,2,3,3][m-1] %}
{% set r2 = states('sensor.rain_last_2_days')|float(0) %}
{% set r5 = states('sensor.rain_last_5_days')|float(0) %}
{% set r15 = states('sensor.rain_last_15_days')|float(0) %}
{% set r30 = states('sensor.rain_last_30_days')|float(0) %}
{% if r2 > 3 * ratchet %} 0
{% elif r5 < 1 * ratchet %}
{% if r30 < 3 * ratchet %} 180
{% elif r15 < 3 * ratchet %} 150
{% elif r15 < 5 * ratchet %} 120
{% elif r15 < 7 * ratchet %} 110
{% else %} 100
{% endif %}
{% elif r5 < 2 * ratchet %} 100
{% elif r5 < 4 * ratchet %} 80
{% elif r5 < 6 * ratchet %} 60
{% else %} 0
{% endif %}
# ----------------------------------------------------------------------
# RAIN SHORT ADJUST % → Used for raised beds, vege garden
# ----------------------------------------------------------------------
- name: "Rain Short Adjust %"
unique_id: rain_short_adjust
unit_of_measurement: "%"
state: >
{% set m = now().month %}
{% set ratchet = [3,3,3,3,2,2,1,1,1,2,3,3][m-1] %}
{% set r2 = states('sensor.rain_last_2_days')|float(0) %}
{% if r2 > 7 * ratchet %} 0
{% elif r2 < 2 * ratchet %} 100
{% elif r2 < 5 * ratchet %} 35
{% else %} 65
{% endif %}
# ----------------------------------------------------------------------
# DYNAMIC GROUP: ALL IRRIGATION VALVES (For Emergency Stop)
# Intent: Automatically finds all entities that start with 'switch.zone_'
# ----------------------------------------------------------------------
- name: "All Irrigation Valves List"
unique_id: all_irrigation_valves_list
state: "{{ now().timestamp() }}" # Forces regular update
attributes:
entity_ids: >
{% set zone_switches = states.switch
| selectattr('entity_id', 'match', 'switch.zone_.*')
| map(attribute='entity_id') | list %}
{{ zone_switches + ['switch.fertiliser_injector'] | sort }} # Add injector explicitly
# ----------------------------------------------------------------------
# DYNAMIC GROUP: LANDSCAPE TREES VALVES
# Intent: Finds all entities that start with 'switch.zone_landscape_'
# ----------------------------------------------------------------------
- name: "Landscape Trees Valves List"
unique_id: landscape_valves_list
state: "{{ now().timestamp() }}"
attributes:
entity_ids: >
{% set pattern = 'switch.zone_landscape_' %}
{{ states.switch
| selectattr('entity_id', 'match', pattern ~ '.*')
| map(attribute='entity_id') | list | sort }}
# ----------------------------------------------------------------------
# DYNAMIC GROUP: FRUIT TREES VALVES
# Intent: Finds all entities that start with 'switch.zone_fruit_'
# ----------------------------------------------------------------------
- name: "Fruit Trees Valves List"
unique_id: fruit_valves_list
state: "{{ now().timestamp() }}"
attributes:
entity_ids: >
{% set pattern = 'switch.zone_fruit_' %}
{{ states.switch
| selectattr('entity_id', 'match', pattern ~ '.*')
| map(attribute='entity_id') | list | sort }}
# ----------------------------------------------------------------------
# PACKAGE: Weather
# FILE: sensors.yaml
# INTENT: Sensors that accumulate rainfall over time - last day, last 2 days, last 5 days
# Annoyingly the internal name that home assistant uses is based on the name, not
# the unique id provided. So you can't choose a human friendly name for the sensor
# whilst also having a coding-friendly id. Crazy, but there it is. You would think
# the unique_id field would do that, but apparently it doesn't. I think I can torture
# it to do what I want though by first creating a name field that will slugify to the
# unique id I want, then change that name field after the sensor has been instantiated.
#
# The irrigation sensors file is currently refusing to load, Gemini claims it's an include
# problem. Whilst working through that, all those sensors are copied into this
# weather sensors file.
# ----------------------------------------------------------------------
# ------- Rain accumulation sensors ---------
# Robustness: float(0) ensures no errors if history is missing
template:
- sensor:
- name: "Rain Last 2 Days"
unique_id: rain_last_2_days
unit_of_measurement: "mm"
device_class: precipitation
state: >-
{{ [states('sensor.rain_accumulation_daily')|float(0),
states('sensor.rain_accumulation')|float(0)] | sum | round(1) }}
- name: "Rain Last 3 Days"
unique_id: rain_last_3_days
unit_of_measurement: "mm"
state: >-
{{ [states('sensor.rain_accumulation_daily')|float(0),
states('sensor.rain_accumulation_2_days_ago')|float(0),
states('sensor.rain_accumulation')|float(0)] | sum | round(1) }}
- name: "Rain Last 5 Days"
unique_id: rain_last_5_days
unit_of_measurement: "mm"
state: >-
{{ [states('sensor.rain_accumulation_daily')|float(0),
states('sensor.rain_accumulation_2_days_ago')|float(0),
states('sensor.rain_accumulation_3_days_ago')|float(0),
states('sensor.rain_accumulation_4_days_ago')|float(0),
states('sensor.rain_accumulation')|float(0)] | sum | round(1) }}
- name: "Rain Last 15 Days"
unique_id: rain_last_15_days
unit_of_measurement: "mm"
state: >-
{% set s = 'sensor.rain_accumulation' %}
{% set days = 14 %}
{% set total = states(s)|float(0) %}
{% for i in range(days) %}
{% set total = total + states(s ~ '_' ~ i ~ '_days_ago')|float(0) %}
{% endfor %}
{{ total | round(1) }}
- name: "Rain Last 30 Days"
unique_id: rain_last_30_days
unit_of_measurement: "mm"
state: >-
{% set s = 'sensor.rain_accumulation' %}
{% set days = 29 %}
{% set total = states(s)|float(0) %}
{% for i in range(days) %}
{% set total = total + states(s ~ '_' ~ i ~ '_days_ago')|float(0) %}
{% endfor %}
{{ total | round(1) }}
- name: "Seasonal Watering Multiplier"
unique_id: seasonal_watering_multiplier
unit_of_measurement: "%"
state: >-
{{ [150,180,180,130,60,35,20,20,35,60,90,120][now().month-1] }}
# J F M A M J J A S O N D
- name: "Landscape Trees Water Every Days"
unique_id: landscape_water_every
state: >-
{{ [5,5,7,10,15,0,0,0,15,10,7,5][now().month-1] }}
# J F M A M J J A S O N D
- name: "Fruit Trees Water Every Days"
unique_id: fruit_water_every
state: >-
{{ [2,2,2,3,4,6,0,0,0,5,3,2][now().month-1] }}
# J F M A M J J A S O N D
- name: "Landscape Trees Fertilise Every Days"
unique_id: landscape_fertilise_every
state: >-
{{ [4,4,4,4,4,4,0,0,0,0,0,4][now().month-1] }}
# J F M A M J J A S O N D
- name: "Fruit Trees Fertilise Every Days"
unique_id: fruit_fertilise_every
state: >-
{{ [2,2,2,2,2,3,0,0,0,0,0,2][now().month-1] }}
# J F M A M J J A S O N D
# Day of Year Helper
- name: "Day of Year"
unique_id: day_of_year
state: "{{ now().timetuple().tm_yday }}"
# ----------------------------------------------------------------------
# RAIN LONG ADJUST % → Used for trees, lawns (soil reservoir)
# ----------------------------------------------------------------------
- name: "Rain Long Adjust %"
unique_id: rain_long_adjust
unit_of_measurement: "%"
state: >
{% set m = now().month %}
{% set ratchet = [3,3,3,3,2,2,1,1,1,2,3,3][m-1] %}
{% set r2 = states('sensor.rain_last_2_days')|float(0) %}
{% set r5 = states('sensor.rain_last_5_days')|float(0) %}
{% set r15 = states('sensor.rain_last_15_days')|float(0) %}
{% set r30 = states('sensor.rain_last_30_days')|float(0) %}
{% if r2 > 3 * ratchet %} 0
{% elif r5 < 1 * ratchet %}
{% if r30 < 3 * ratchet %} 180
{% elif r15 < 3 * ratchet %} 150
{% elif r15 < 5 * ratchet %} 120
{% elif r15 < 7 * ratchet %} 110
{% else %} 100
{% endif %}
{% elif r5 < 2 * ratchet %} 100
{% elif r5 < 4 * ratchet %} 80
{% elif r5 < 6 * ratchet %} 60
{% else %} 0
{% endif %}
# ----------------------------------------------------------------------
# RAIN SHORT ADJUST % → Used for raised beds, vege garden
# ----------------------------------------------------------------------
- name: "Rain Short Adjust %"
unique_id: rain_short_adjust
unit_of_measurement: "%"
state: >
{% set m = now().month %}
{% set ratchet = [3,3,3,3,2,2,1,1,1,2,3,3][m-1] %}
{% set r2 = states('sensor.rain_last_2_days')|float(0) %}
{% if r2 > 7 * ratchet %} 0
{% elif r2 < 2 * ratchet %} 100
{% elif r2 < 5 * ratchet %} 35
{% else %} 65
{% endif %}
# ----------------------------------------------------------------------
# DYNAMIC GROUP: ALL IRRIGATION VALVES (For Emergency Stop)
# Intent: Automatically finds all entities that start with 'switch.zone_'
# ----------------------------------------------------------------------
- name: "All Irrigation Valves List"
unique_id: all_irrigation_valves_list
state: "{{ now().timestamp() }}" # Forces regular update
attributes:
entity_ids: >
{% set zone_switches = states.switch
| selectattr('entity_id', 'match', 'switch.zone_.*')
| map(attribute='entity_id') | list %}
{{ zone_switches + ['switch.fertiliser_injector'] | sort }} # Add injector explicitly
# ----------------------------------------------------------------------
# DYNAMIC GROUP: LANDSCAPE TREES VALVES
# Intent: Finds all entities that start with 'switch.zone_landscape_'
# ----------------------------------------------------------------------
- name: "Landscape Trees Valves List"
unique_id: landscape_valves_list
state: "{{ now().timestamp() }}"
attributes:
entity_ids: >
{% set pattern = 'switch.zone_landscape_' %}
{{ states.switch
| selectattr('entity_id', 'match', pattern ~ '.*')
| map(attribute='entity_id') | list | sort }}
# ----------------------------------------------------------------------
# DYNAMIC GROUP: FRUIT TREES VALVES
# Intent: Finds all entities that start with 'switch.zone_fruit_'
# ----------------------------------------------------------------------
- name: "Fruit Trees Valves List"
unique_id: fruit_valves_list
state: "{{ now().timestamp() }}"
attributes:
entity_ids: >
{% set pattern = 'switch.zone_fruit_' %}
{{ states.switch
| selectattr('entity_id', 'match', pattern ~ '.*')
| map(attribute='entity_id') | list | sort }}