ESPHome Config File


Referring page: Custom Sprinkler Controller

This is the ESPHome based config file for my custom sprinkler controller. It is built around a modified implementation of the esphome sprinkler controller component.

I wanted more flexibility to select the days of the week to run certain zones so the code below is designed to primarily work with the sprinkler controller queue function.

This device is setup to run independently from Home Assistant. Zone start & stop times are all executed directly from the device.

substitutions: # set up unique values for this hardware instance
  name_of_device: nachio
  id_prefix: nachio
  name_prefix: Nachio
  
  sensor_update_frequency: 1s
  uom: Min # this overrides the uom in sprinkler -> run_duration 

esphome:
  name: $name_of_device
  friendly_name:  ${name_prefix}
  min_version: 2024.11.0
  name_add_mac_suffix: false
  on_boot:
    priority: -100
    then:
      - text_sensor.template.publish:
          id: controller_status
          state: "Idle"
      # Set multiplier to 60, convert seconds to minutes
      - sprinkler.set_multiplier:
          id: ${name_of_device}_sprinklers
          multiplier: 60




esp32:
  board: esp32dev
  framework:
    type: esp-idf

# Enable logging
logger:
  level: DEBUG

# Enable Home Assistant API
api:
  encryption:
      key: !secret api_key
  reboot_timeout: 1min
  services:
    - service: start_valve
      then:
        - script.execute: start_valve_dynamic


# Allow Over-The-Air updates
ota:
  - platform: esphome

wifi:
  #ssid: Aether
  #password: $ap_password
  ssid: !secret wifi_ssid
  password: !secret wifi_password


  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Nachio Fallback Hotspot"
    password: !secret wifi_password

captive_portal:

# Enable Web server.
web_server:
  port: 80



# Status LED
light:
  - platform: status_led
    name: "Nachio LED"
    restore_mode: ALWAYS_OFF
    pin:
      number: GPIO23


globals:
  - id: zone_index
    type: int
    restore_value: no
    initial_value: '0'

  - id: zone_duration
    type: float
    restore_value: no
    initial_value: '0'


  - id: sprinkler_queue_time_remaining
    type: int  # seconds
    initial_value: '0'


  #These are all used to determine the queue size
  - id: sprinkler_queue_size
    type: int
    initial_value: '0'

  - id: valve_1_active
    type: bool
    initial_value: 'false'

  - id: valve_2_active
    type: bool
    initial_value: 'false'

  - id: valve_3_active
    type: bool
    initial_value: 'false'

  - id: valve_4_active
    type: bool
    initial_value: 'false'

  - id: valve_5_active
    type: bool
    initial_value: 'false'

  - id: valve_6_active
    type: bool
    initial_value: 'false'

  - id: valve_7_active
    type: bool
    initial_value: 'false'

  - id: valve_8_active
    type: bool
    initial_value: 'false'


number:
  - platform: template
    id: var_run_duration_minutes
    name: "Var Run Duration Minutes"
    min_value: 1
    max_value: 20
    step: 1
    unit_of_measurement: "min"
    mode: box
    optimistic: true

  - platform: template
    name: "Sprinkler Valve Number"
    id: valve_number_input
    min_value: 0
    max_value: 7  # Adjust to match your number of valves minus 1
    step: 1
    mode: box
    optimistic: true


###############################################
# MAIN SPRINKLER CONTROLLER
###############################################
sprinkler:
  - id: ${id_prefix}_sprinklers
    main_switch: "Sprinkler Cycle Active"
    auto_advance_switch: "Auto Advance"
    queue_enable_switch: "Enable Queue"
    standby_switch: "System Standby" #To be used for rain skips
      #this seems to cause error when activated
      #on_turn_on:
      #  then:
      #    - text_sensor.template.publish:
      #        id: controller_status
      #        state: "Running"   
      #on_turn_off:
      #  then:
      #    - text_sensor.template.publish:
      #        id: controller_status
      #        state: "Idle"
    
    valve_overlap: 3s
    valves:
      - valve_switch: "Zone 1 Active"
        #enable_switch: "Enable z1"
        run_duration_number: 
          id: zone_1_run_duration
          name: "Zone 1 Run Duration"
          icon: "mdi:timer-outline"
          initial_value: 1
          unit_of_measurement: min
        valve_switch_id: ${id_prefix}_valve01

      - valve_switch: "Zone 2 Active"
        #enable_switch: "Enable z2"
        run_duration_number: 
          id: zone_2_run_duration
          name: "Zone 2 Run Duration"
          icon: "mdi:timer-outline"
          initial_value: 1
          unit_of_measurement: min
        valve_switch_id: ${id_prefix}_valve02

      - valve_switch: "Zone 3 Active"
        #enable_switch: "Enable z3"
        run_duration_number: 
          id: zone_3_run_duration
          name: "Zone 3 Run Duration"
          icon: "mdi:timer-outline"
          initial_value: 1
          unit_of_measurement: min
        valve_switch_id: ${id_prefix}_valve03

      - valve_switch: "Zone 4 Active"
        #enable_switch: "Enable z4"
        run_duration_number: 
          id: zone_4_run_duration
          name: "Zone 4 Run Duration"
          icon: "mdi:timer-outline"
          initial_value: 1
          unit_of_measurement: min
        valve_switch_id: ${id_prefix}_valve04

      - valve_switch: "Zone 5 Active"
        #enable_switch: "Enable z5"
        run_duration_number: 
          id: zone_5_run_duration
          name: "Zone 5 Run Duration"
          icon: "mdi:timer-outline"
          initial_value: 1
          unit_of_measurement: min
        valve_switch_id: ${id_prefix}_valve05

      - valve_switch: "Zone 6 Active"
        #enable_switch: "Enable z6"
        run_duration_number: 
          id: zone_6_run_duration
          name: "Zone 6 Run Duration"
          icon: "mdi:timer-outline"
          initial_value: 1
          unit_of_measurement: min
        valve_switch_id: ${id_prefix}_valve06

      - valve_switch: "Zone 7 Active"
        #enable_switch: "Enable z7"
        run_duration_number: 
          id: zone_7_run_duration
          name: "Zone 7 Run Duration"
          icon: "mdi:timer-outline"
          initial_value: 1
          unit_of_measurement: min
        valve_switch_id: ${id_prefix}_valve07

      - valve_switch: "Zone 8 Active"
        #enable_switch: "Enable z8"
        run_duration_number: 
          id: zone_8_run_duration
          name: "Zone 8 Run Duration"
          icon: "mdi:timer-outline"
          initial_value: 1
          unit_of_measurement: min
        valve_switch_id: ${id_prefix}_valve08


button:
  - platform: template
    id: sprinkler_pause
    name: "Pause"
    icon: "mdi:pause"
    on_press:
      then:
        - text_sensor.template.publish:
            id: controller_status
            state: "Paused"           
        - sprinkler.pause: ${id_prefix}_sprinklers

  - platform: template
    id: sprinkler_resume
    name: "Resume"
    icon: "mdi:play"
    on_press:
      then:
        - text_sensor.template.publish:
            id: controller_status
            state: "Running"           
        - sprinkler.resume: ${id_prefix}_sprinklers

  - platform: template
    id: sprinkler_stop
    name: "Stop"
    icon: "mdi:stop"
    on_press:
      then:
        - text_sensor.template.publish:
            id: controller_status
            state: "Idle"           
        - sprinkler.shutdown: ${id_prefix}_sprinklers


#########################################################
# BEGIN SWITCHES
#########################################################
# Device relay outputs
#"internal: true" => causes these switches to not show in HA
switch:
  - platform: gpio
    pin: GPIO32
    name: ${id_prefix}_valve01
    id: ${id_prefix}_valve01
    on_turn_on:   
      then:
        - text_sensor.template.publish:
            id: controller_status
            state: "Running"
        - lambda: |-
            id(valve_1_active) = true; 
    on_turn_off:
      then:
        - text_sensor.template.publish:
            id: controller_status
            state: "Idle"
        - lambda: |-
            if (id(valve_1_active)) {
              id(sprinkler_queue_size) -= 1;
              float runtime_min = id(zone_1_run_duration).state;
              int runtime_sec = (int)(runtime_min * 60.0);
              id(sprinkler_queue_time_remaining) -= runtime_sec;
              if (id(sprinkler_queue_time_remaining) < 0)
                id(sprinkler_queue_time_remaining) = 0;
              id(valve_1_active) = false;
            }  
    restore_mode: ALWAYS_OFF
    internal: true

  - platform: gpio
    pin: GPIO33
    name: ${id_prefix}_valve02
    id: ${id_prefix}_valve02
    on_turn_on:
      then:
        - text_sensor.template.publish:
            id: controller_status
            state: "Running"   
        - lambda: |-
            id(valve_2_active) = true; 
    on_turn_off:
      then:
        - text_sensor.template.publish:
            id: controller_status
            state: "Idle"     
        - lambda: |-
            if (id(valve_2_active)) {
              id(sprinkler_queue_size) -= 1;
              float runtime_min = id(zone_2_run_duration).state;
              int runtime_sec = (int)(runtime_min * 60.0);
              id(sprinkler_queue_time_remaining) -= runtime_sec;
              if (id(sprinkler_queue_time_remaining) < 0)
                id(sprinkler_queue_time_remaining) = 0;
              id(valve_2_active) = false;
            }  
    restore_mode: ALWAYS_OFF
    internal: true

  - platform: gpio
    pin: GPIO25
    name: ${id_prefix}_valve03
    id: ${id_prefix}_valve03
    on_turn_on:
      then:
        - text_sensor.template.publish:
            id: controller_status
            state: "Running"
        - lambda: |-
            id(valve_3_active) = true; 
    on_turn_off:
      then:
        - text_sensor.template.publish:
            id: controller_status
            state: "Idle"
        - lambda: |-
            if (id(valve_3_active)) {
              id(sprinkler_queue_size) -= 1;
              float runtime_min = id(zone_3_run_duration).state;
              int runtime_sec = (int)(runtime_min * 60.0);
              id(sprinkler_queue_time_remaining) -= runtime_sec;
              if (id(sprinkler_queue_time_remaining) < 0)
                id(sprinkler_queue_time_remaining) = 0;
              id(valve_3_active) = false;
            }    
    restore_mode: ALWAYS_OFF
    internal: true

  - platform: gpio
    pin: GPIO26
    name: ${id_prefix}_valve04
    id: ${id_prefix}_valve04
    on_turn_on:
      then:
        - text_sensor.template.publish:
            id: controller_status
            state: "Running"   
        - lambda: |-
            id(valve_4_active) = true; 
    on_turn_off:
      then:
        - text_sensor.template.publish:
            id: controller_status
            state: "Idle"
        - lambda: |-
            if (id(valve_4_active)) {
              id(sprinkler_queue_size) -= 1;
              float runtime_min = id(zone_4_run_duration).state;
              int runtime_sec = (int)(runtime_min * 60.0);
              id(sprinkler_queue_time_remaining) -= runtime_sec;
              if (id(sprinkler_queue_time_remaining) < 0)
                id(sprinkler_queue_time_remaining) = 0;
              id(valve_4_active) = false;
            }  
    restore_mode: ALWAYS_OFF
    internal: true

  - platform: gpio
    pin: GPIO27
    name: ${id_prefix}_valve05
    id: ${id_prefix}_valve05
    on_turn_on:
      then:
        - text_sensor.template.publish:
            id: controller_status
            state: "Running"   
        - lambda: |-
            id(valve_5_active) = true; 
    on_turn_off:
      then:
        - text_sensor.template.publish:
            id: controller_status
            state: "Idle"
        - lambda: |-
            if (id(valve_5_active)) {
              id(sprinkler_queue_size) -= 1;
              float runtime_min = id(zone_5_run_duration).state;
              int runtime_sec = (int)(runtime_min * 60.0);
              id(sprinkler_queue_time_remaining) -= runtime_sec;
              if (id(sprinkler_queue_time_remaining) < 0)
                id(sprinkler_queue_time_remaining) = 0;
              id(valve_5_active) = false;
            }      
    restore_mode: ALWAYS_OFF
    internal: true

  - platform: gpio
    pin: GPIO14
    name: ${id_prefix}_valve06
    id: ${id_prefix}_valve06
    on_turn_on:
      then:
        - text_sensor.template.publish:
            id: controller_status
            state: "Running"   
        - lambda: |-
            id(valve_6_active) = true; 
    on_turn_off:
      then:
        - text_sensor.template.publish:
            id: controller_status
            state: "Idle"
        - lambda: |-
            if (id(valve_6_active)) {
              id(sprinkler_queue_size) -= 1;
              float runtime_min = id(zone_6_run_duration).state;
              int runtime_sec = (int)(runtime_min * 60.0);
              id(sprinkler_queue_time_remaining) -= runtime_sec;
              if (id(sprinkler_queue_time_remaining) < 0)
                id(sprinkler_queue_time_remaining) = 0;
              id(valve_6_active) = false;
            }  
    restore_mode: ALWAYS_OFF
    internal: true

  - platform: gpio
    pin: GPIO12
    name: ${id_prefix}_valve07
    id: ${id_prefix}_valve07
    on_turn_on:
      then:
        - text_sensor.template.publish:
            id: controller_status
            state: "Running"   
        - lambda: |-
            id(valve_7_active) = true; 
    on_turn_off:
      then:
        - text_sensor.template.publish:
            id: controller_status
            state: "Idle"
        - lambda: |-
            if (id(valve_7_active)) {
              id(sprinkler_queue_size) -= 1;
              float runtime_min = id(zone_7_run_duration).state;
              int runtime_sec = (int)(runtime_min * 60.0);
              id(sprinkler_queue_time_remaining) -= runtime_sec;
              if (id(sprinkler_queue_time_remaining) < 0)
                id(sprinkler_queue_time_remaining) = 0;
              id(valve_7_active) = false;
            }  
    restore_mode: ALWAYS_OFF
    internal: true

  - platform: gpio
    pin: GPIO13
    name: ${id_prefix}_valve08
    id: ${id_prefix}_valve08
    on_turn_on:
      then:
        - text_sensor.template.publish:
            id: controller_status
            state: "Running"   
        - lambda: |-
            id(valve_8_active) = true; 
    on_turn_off:
      then:
        - text_sensor.template.publish:
            id: controller_status
            state: "Idle"
        - lambda: |-
            if (id(valve_8_active)) {
              id(sprinkler_queue_size) -= 1;
              float runtime_min = id(zone_8_run_duration).state;
              int runtime_sec = (int)(runtime_min * 60.0);
              id(sprinkler_queue_time_remaining) -= runtime_sec;
              if (id(sprinkler_queue_time_remaining) < 0)
                id(sprinkler_queue_time_remaining) = 0;
              id(valve_8_active) = false;
            }  
    restore_mode: ALWAYS_OFF
    internal: true

  - platform: shutdown
    name: "Nachio Shutdown"
    entity_category: config


 
  #########################################################
  # Begin day of week switches per zone - Used to indicate the days of the week a zone should be run (7zones * 7days = 49 switches)
  #########################################################
  
  # Zone 1 - Front Lawn
  - platform: template
    id: ${id_prefix}_z1_0_sun
    name: z1_0 run sun
    icon: mdi:calendar-range
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true

  - platform: template
    id: ${id_prefix}_z1_1_mon
    name: z1_1 run mon
    icon: mdi:calendar-range
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true

  - platform: template
    id: ${id_prefix}_z1_2_tue
    name: z1_2 run tue
    icon: mdi:calendar-range
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true

  - platform: template
    id: ${id_prefix}_z1_3_wed
    name: z1_3 run wed
    icon: mdi:calendar-range
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true

  - platform: template
    id: ${id_prefix}_z1_4_thu
    name: z1_4 run thu
    icon: mdi:calendar-range
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true

  - platform: template
    id: ${id_prefix}_z1_5_fri
    name: z1_5 run fri
    icon: mdi:calendar-range
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true

  - platform: template
    id: ${id_prefix}_z1_6_sat
    name: z1_6 run sat
    icon: mdi:calendar-range
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true

  - platform: template
    id: ${id_prefix}_z1_adjust_duration
    name: z1 - Adjust Duration
    icon: mdi:calendar-range
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true


  # Zone 2 - Front Patio
  - platform: template
    id: ${id_prefix}_z2_0_sun
    name: z2_0 run sun
    icon: mdi:calendar-range
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true

  - platform: template
    id: ${id_prefix}_z2_1_mon
    name: z2_1 run mon
    icon: mdi:calendar-range
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true

  - platform: template
    id: ${id_prefix}_z2_2_tue
    name: z2_2 run tue
    icon: mdi:calendar-range
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true

  - platform: template
    id: ${id_prefix}_z2_3_wed
    name: z2_3 run wed
    icon: mdi:calendar-range
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true

  - platform: template
    id: ${id_prefix}_z2_4_thu
    name: z2_4 run thu
    icon: mdi:calendar-range
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true

  - platform: template
    id: ${id_prefix}_z2_5_fri
    name: z2_5 run fri
    icon: mdi:calendar-range
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true

  - platform: template
    id: ${id_prefix}_z2_6_sat
    name: z2_6 run sat
    icon: mdi:calendar-range
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true

  - platform: template
    id: ${id_prefix}_z2_adjust_duration
    name: z2 - Adjust Duration
    icon: mdi:calendar-range
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true


  # Zone 3 - Back Lawn1
  - platform: template
    id: ${id_prefix}_z3_0_sun
    name: z3_0 run sun
    icon: mdi:calendar-range
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true

  - platform: template
    id: ${id_prefix}_z3_1_mon
    name: z3_1 run mon
    icon: mdi:calendar-range
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true

  - platform: template
    id: ${id_prefix}_z3_2_tue
    name: z3_2 run tue
    icon: mdi:calendar-range
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true

  - platform: template
    id: ${id_prefix}_z3_3_wed
    name: z3_3 run wed
    icon: mdi:calendar-range
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true

  - platform: template
    id: ${id_prefix}_z3_4_thu
    name: z3_4 run thu
    icon: mdi:calendar-range
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true

  - platform: template
    id: ${id_prefix}_z3_5_fri
    name: z3_5 run fri
    icon: mdi:calendar-range
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true

  - platform: template
    id: ${id_prefix}_z3_6_sat
    name: z3_6 run sat
    icon: mdi:calendar-range
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true

  - platform: template
    id: ${id_prefix}_z3_adjust_duration
    name: z3 - Adjust Duration
    icon: mdi:calendar-range
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true


  # Zone 4 - Back Lawn2
  - platform: template
    id: ${id_prefix}_z4_0_sun
    name: z4_0 run sun
    icon: mdi:calendar-range
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true

  - platform: template
    id: ${id_prefix}_z4_1_mon
    name: z4_1 run mon
    icon: mdi:calendar-range
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true

  - platform: template
    id: ${id_prefix}_z4_2_tue
    name: z4_2 run tue
    icon: mdi:calendar-range
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true

  - platform: template
    id: ${id_prefix}_z4_3_wed
    name: z4_3 run wed
    icon: mdi:calendar-range
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true

  - platform: template
    id: ${id_prefix}_z4_4_thu
    name: z4_4 run thu
    icon: mdi:calendar-range
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true

  - platform: template
    id: ${id_prefix}_z4_5_fri
    name: z4_5 run fri
    icon: mdi:calendar-range
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true

  - platform: template
    id: ${id_prefix}_z4_6_sat
    name: z4_6 run sat
    icon: mdi:calendar-range
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true

  - platform: template
    id: ${id_prefix}_z4_adjust_duration
    name: z4 - Adjust Duration
    icon: mdi:calendar-range
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true


  # Zone 5 - Roses & side Yard
  - platform: template
    id: ${id_prefix}_z5_0_sun
    name: z5_0 run sun
    icon: mdi:calendar-range
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true

  - platform: template
    id: ${id_prefix}_z5_1_mon
    name: z5_1 run mon
    icon: mdi:calendar-range
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true

  - platform: template
    id: ${id_prefix}_z5_2_tue
    name: z5_2 run tue
    icon: mdi:calendar-range
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true

  - platform: template
    id: ${id_prefix}_z5_3_wed
    name: z5_3 run wed
    icon: mdi:calendar-range
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true

  - platform: template
    id: ${id_prefix}_z5_4_thu
    name: z5_4 run thu
    icon: mdi:calendar-range
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true

  - platform: template
    id: ${id_prefix}_z5_5_fri
    name: z5_5 run fri
    icon: mdi:calendar-range
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true

  - platform: template
    id: ${id_prefix}_z5_6_sat
    name: z5_6 run sat
    icon: mdi:calendar-range
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true

  - platform: template
    id: ${id_prefix}_z5_adjust_duration
    name: z5 - Adjust Duration
    icon: mdi:calendar-range
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true


  # Zone 6 - Garden & back fence
  - platform: template
    id: ${id_prefix}_z6_0_sun
    name: z6_0 run sun
    icon: mdi:calendar-range
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true

  - platform: template
    id: ${id_prefix}_z6_1_mon
    name: z6_1 run mon
    icon: mdi:calendar-range
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true

  - platform: template
    id: ${id_prefix}_z6_2_tue
    name: z6_2 run tue
    icon: mdi:calendar-range
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true

  - platform: template
    id: ${id_prefix}_z6_3_wed
    name: z6_3 run wed
    icon: mdi:calendar-range
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true

  - platform: template
    id: ${id_prefix}_z6_4_thu
    name: z6_4 run thu
    icon: mdi:calendar-range
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true

  - platform: template
    id: ${id_prefix}_z6_5_fri
    name: z6_5 run fri
    icon: mdi:calendar-range
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true

  - platform: template
    id: ${id_prefix}_z6_6_sat
    name: z6_6 run sat
    icon: mdi:calendar-range
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true

  - platform: template
    id: ${id_prefix}_z6_adjust_duration
    name: z6 - Adjust Duration
    icon: mdi:calendar-range
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true


  # Zone 7 - Left Driveway
  - platform: template
    id: ${id_prefix}_z7_0_sun
    name: z7_0 run sun
    icon: mdi:calendar-range
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true

  - platform: template
    id: ${id_prefix}_z7_1_mon
    name: z7_1 run mon
    icon: mdi:calendar-range
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true

  - platform: template
    id: ${id_prefix}_z7_2_tue
    name: z7_2 run tue
    icon: mdi:calendar-range
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true

  - platform: template
    id: ${id_prefix}_z7_3_wed
    name: z7_3 run wed
    icon: mdi:calendar-range
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true

  - platform: template
    id: ${id_prefix}_z7_4_thu
    name: z7_4 run thu
    icon: mdi:calendar-range
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true

  - platform: template
    id: ${id_prefix}_z7_5_fri
    name: z7_5 run fri
    icon: mdi:calendar-range
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true

  - platform: template
    id: ${id_prefix}_z7_6_sat
    name: z7_6 run sat
    icon: mdi:calendar-range
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true

  - platform: template
    id: ${id_prefix}_z7_adjust_duration
    name: z7 - Adjust Duration
    icon: mdi:calendar-range
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true

  ######################################
  # BEGIN SWITCHES TO DISABLE ZONES - USED TO TEMPORARILY DISABLE A ZONE
  ######################################
  - platform: template
    id: ${id_prefix}_z1_disable
    name: z1 - Disable
    icon: mdi:water-off
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true

  - platform: template
    id: ${id_prefix}_z2_disable
    name: z2 - Disable
    icon: mdi:water-off
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true

  - platform: template
    id: ${id_prefix}_z3_disable
    name: z3 - Disable
    icon: mdi:water-off
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true

  - platform: template
    id: ${id_prefix}_z4_disable
    name: z4 - Disable
    icon: mdi:water-off
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true

  - platform: template
    id: ${id_prefix}_z5_disable
    name: z5 - Disable
    icon: mdi:water-off
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true

  - platform: template
    id: ${id_prefix}_z6_disable
    name: z6 - Disable
    icon: mdi:water-off
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true

  - platform: template
    id: ${id_prefix}_z7_disable
    name: z7 - Disable
    icon: mdi:water-off
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true

  - platform: template
    id: ${id_prefix}_z8_disable
    name: z8 - Disable
    icon: mdi:water-off
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true

  - platform: restart
    name: "Restart"

  #RAIN SKIP Switch 
  # USE STANDBY IN PLACE OF RAIN_SKIP?
  - platform: template
    name: "Rain Skip Lock"
    id: ${id_prefix}_rain_skip_lock
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true
    on_turn_on:
      then:
        - text_sensor.template.publish:
            id: controller_status
            state: "Rain Skip"   
    on_turn_off:
      then:
        - text_sensor.template.publish:
            id: controller_status
            state: "Idle"



#########################################################
# BEGIN DATETIMES - USED TO SPECIFY THE DAILY START TIMES
#########################################################
datetime:
  - platform: template
    name: 01 mon Start Time
    entity_category: config
    id: ${id_prefix}_mon_st
    type: time
    optimistic: true
    restore_value: true

  - platform: template
    name: 02 tue Start Time
    entity_category: config
    id: ${id_prefix}_tue_st
    type: time
    optimistic: true
    restore_value: true

  - platform: template
    name: 03 wed Start Time
    entity_category: config
    id: ${id_prefix}_wed_st
    type: time
    optimistic: true
    restore_value: true

  - platform: template
    name: 04 thu Start Time
    entity_category: config
    id: ${id_prefix}_thu_st
    type: time
    optimistic: true
    restore_value: true

  - platform: template
    name: 05 fri Start Time
    entity_category: config
    id: ${id_prefix}_fri_st
    type: time
    optimistic: true
    restore_value: true

  - platform: template
    name: 06 sat Start Time
    entity_category: config
    id: ${id_prefix}_sat_st
    type: time
    optimistic: true
    restore_value: true

  - platform: template
    name: 00 sun Start Time
    entity_category: config
    id: ${id_prefix}_sun_st
    type: time
    optimistic: true
    restore_value: true


#########################################################
# BEGIN SENSORS
#########################################################
sensor:
  - platform: wifi_signal # Reports the WiFi signal strength/RSSI in dB
    name: "WiFi Signal dB"
    id: wifi_signal_db
    update_interval: 60s
    entity_category: "diagnostic"

  - platform: copy # Reports the WiFi signal strength in %
    source_id: wifi_signal_db
    name: "WiFi Signal Percent"
    filters:
      - lambda: return min(max(2 * (x + 100.0), 0.0), 100.0);
    unit_of_measurement: "%"
    entity_category: "diagnostic"

  - platform: uptime
    id: uptime_seconds
    name: Uptime
    entity_category: "diagnostic"
    

 
  #########################################################
  # BEGIN rain skip sensors
  #########################################################
  - platform: homeassistant
    id: forecasted_rain
    entity_id: sensor.rain_forecast_san_jose
    name: "${id_prefix} Rain Forecast San Jose"
    accuracy_decimals: 2
    internal: false   # This makes it visible in HA

  - platform: homeassistant
    id: actual_rain
    entity_id: sensor.rain_today_san_jose
    name: "${id_prefix} Rain Today San Jose" 
    accuracy_decimals: 2
    internal: false

  #########################################################
  # BEGIN Temperature Sensors
  #########################################################
  - platform: homeassistant
    id: hi_temp_today
    entity_id: sensor.max_temperature_san_jose_forecast0
    name: "${id_prefix} Max Temperature San Jose Forecast0" 
    accuracy_decimals: 0
    internal: false

  - platform: homeassistant
    id: hi_temp_tomorrow
    entity_id: sensor.max_temperature_san_jose_forecast1
    name: "${id_prefix} Max Temperature San Jose Forecast1"
    accuracy_decimals: 0
    internal: false

  - platform: homeassistant
    id: hi_temp_2days
    entity_id: sensor.max_temperature_san_jose_forecast2
    name: "${id_prefix} Max Temperature San Jose Forecast2"
    accuracy_decimals: 0
    internal: false

  - platform: homeassistant
    id: temp_now
    entity_id: sensor.outdoor_temperature_merged
    name: "${id_prefix} Outdoor Temperature Merged"
    accuracy_decimals: 1
    internal: false


  - platform: template
    name: "Weighted Avg Temp"
    id: weighted_avg_temp
    #unit_of_measurement: "min"
    update_interval: 15min
    lambda: |-
      float fcst_today = id(hi_temp_today).state;
      float fcst_t1 = id(hi_temp_tomorrow).state;
      float fcst_t2 = id(hi_temp_2days).state;

      float weighted_avg = (fcst_today * 0.5) + (fcst_t1 * 0.3) + (fcst_t2 * 0.2);
      return roundf(weighted_avg * 100.0)/100.0;
    #  return weighted_avg; #orig line 2025-0712


  - platform: template
    name: "Duration Adjustment"
    id: run_duration_adjustment
    unit_of_measurement: "min"
    update_interval: 15min
    lambda: |-
      float weighted_avg = id(weighted_avg_temp).state;
      float adjustment = 0.0;

      // Adjustment based on weighted avg of today & next two day's forecast
      if (weighted_avg > 82) {
        adjustment += floor((weighted_avg - 82) / 4.0);  // +1 min per 4°F over 82
      } else if (weighted_avg < 72) {
        adjustment -= floor((72 - weighted_avg) / 4.0) * 2;  // -2 min per 4°F under 72
      } else {
        adjustment += 0.0;  // No adjustment for temperatures between 72–82
      }

      return adjustment;



#This needs a conditional to check if zone is using the adjustment
  - platform: template
    name: "Active Valve Total Runtime"
    id: active_valve_total_runtime
    unit_of_measurement: "min"
    update_interval: 10s
    lambda: |-
      float adj = id(run_duration_adjustment).state;

      if (id(nachio_valve01).state) return max(1.0f, min((id(zone_1_run_duration).state + adj), 30.0f));
      if (id(nachio_valve02).state) return max(1.0f, min((id(zone_2_run_duration).state + adj), 30.0f));
      if (id(nachio_valve03).state) return max(1.0f, min((id(zone_3_run_duration).state + adj), 30.0f));
      if (id(nachio_valve04).state) return max(1.0f, min((id(zone_4_run_duration).state + adj), 30.0f));
      if (id(nachio_valve05).state) return max(1.0f, min((id(zone_5_run_duration).state + adj), 30.0f));
      if (id(nachio_valve06).state) return max(1.0f, min((id(zone_6_run_duration).state + adj), 30.0f));
      if (id(nachio_valve07).state) return max(1.0f, min((id(zone_7_run_duration).state + adj), 30.0f));
      return 0.0f;


  # These manage queue info
  #- platform: template
  #  id: sprinkler_queue_count
  #  name: "Sprinkler Queue Count"
  #  unit_of_measurement: "zones"
  #  icon: "mdi:format-list-numbered"
  #  update_interval: 10s
  #  lambda: |-
  #    return id(sprinkler_queue_size);

  - platform: template
    name: "Sprinkler Queue Count"
    accuracy_decimals: 0
    lambda: |-
      return id(sprinkler_queue_size);
    update_interval: 10s

  - platform: template
    name: "Sprinkler Queue Time Remaining"
    unit_of_measurement: "min"
    accuracy_decimals: 1
    lambda: |-
      return id(sprinkler_queue_time_remaining) / 60.0;
    update_interval: 10s




binary_sensor:
  - platform: status
    name: "Connection Status"


# set up time and check every minute (or second) for scheduled actions
time:
  - platform: homeassistant
    id: ha_time
    timezone: America/Los_Angeles
    on_time:
      # Every 1 minute
      - seconds: 0
        minutes: /1
        then:
          - script.execute: script1_trigger
      #Rain Skip
      - seconds: 0
        minutes: /15  # Evaluate every 15 minutes
        then:
          - script.execute: check_rain_skip_conditions

    on_time_sync:
      then:
        - logger.log: "Synchronized system clock"




  ##### Begin RAIN SKIP ##### 
  #- platform: homeassistant
  #  id: ha_time
  #  on_time:
  #    - seconds: 0
  #      minutes: /15  # Evaluate every 15 minutes
  #      then:
  #        - script.execute: check_rain_skip_conditions

  ##### End RAIN SKIP  #####


###############################################################
# BEGIN SCRIPTS
###############################################################

script:

    # Step1: Check to see of the day today and the time now, match a predefined weekday start time 
  - id: script1_trigger
    then:
      lambda: |-
        int dow = id(ha_time).now().day_of_week;
        int hour = id(ha_time).now().hour;
        int minute = id(ha_time).now().minute;
        ESP_LOGD("custom", "beginning script1");
        
        bool run_now = false;

        if (dow == 1) run_now = (hour == id(${id_prefix}_sun_st).hour && minute == id(${id_prefix}_sun_st).minute);
        if (dow == 2) run_now = (hour == id(${id_prefix}_mon_st).hour && minute == id(${id_prefix}_mon_st).minute);
        if (dow == 3) run_now = (hour == id(${id_prefix}_tue_st).hour && minute == id(${id_prefix}_tue_st).minute);
        if (dow == 4) run_now = (hour == id(${id_prefix}_wed_st).hour && minute == id(${id_prefix}_wed_st).minute);
        if (dow == 5) run_now = (hour == id(${id_prefix}_thu_st).hour && minute == id(${id_prefix}_thu_st).minute);
        if (dow == 6) run_now = (hour == id(${id_prefix}_fri_st).hour && minute == id(${id_prefix}_fri_st).minute);
        if (dow == 7) run_now = (hour == id(${id_prefix}_sat_st).hour && minute == id(${id_prefix}_sat_st).minute);

        ESP_LOGD("custom", "Today is day %d (0=Sun, 1=Mon, ..., 6=Sat)", dow);
        ESP_LOGD("custom", "Current time: %02d:%02d:%02d", hour, minute, id(ha_time).now().second);
        ESP_LOGD("custom", "tue scheduled time: %02d:%02d", id(${id_prefix}_tue_st).hour, id(${id_prefix}_tue_st).minute);

        if (run_now) {
          ESP_LOGD("custom", "completed script1, moving to script2");
          id(script2_1_check_active_zones_today).execute();
        }

    #Step2: Identify zones that are set to run today
  - id: script2_1_check_active_zones_today
    then:
      - lambda: |-
          auto weekday = id(ha_time).now().day_of_week;  // 0=sun, 1=mon, ..., 6=sat

          bool should_run = false;

          if (weekday == 1) {  // sun
            should_run = (
              id(nachio_z1_0_sun).state || id(nachio_z2_0_sun).state ||
              id(nachio_z3_0_sun).state || id(nachio_z4_0_sun).state ||
              id(nachio_z5_0_sun).state || id(nachio_z6_0_sun).state ||
              id(nachio_z7_0_sun).state
            );
          } else if (weekday == 2) {  // tue
            should_run = (
              id(nachio_z1_1_mon).state || id(nachio_z2_1_mon).state ||
              id(nachio_z3_1_mon).state || id(nachio_z4_1_mon).state ||
              id(nachio_z5_1_mon).state || id(nachio_z6_1_mon).state ||
              id(nachio_z7_1_mon).state
            );
          } else if (weekday == 3) {  // tue
            should_run = (
              id(nachio_z1_2_tue).state || id(nachio_z2_2_tue).state ||
              id(nachio_z3_2_tue).state || id(nachio_z4_2_tue).state ||
              id(nachio_z5_2_tue).state || id(nachio_z6_2_tue).state ||
              id(nachio_z7_2_tue).state
            );
          } else if (weekday == 4) {  // wed
            should_run = (
              id(nachio_z1_3_wed).state || id(nachio_z2_3_wed).state ||
              id(nachio_z3_3_wed).state || id(nachio_z4_3_wed).state ||
              id(nachio_z5_3_wed).state || id(nachio_z6_3_wed).state ||
              id(nachio_z7_3_wed).state
            );
          } else if (weekday == 5) {  // thu
            should_run = (
              id(nachio_z1_4_thu).state || id(nachio_z2_4_thu).state ||
              id(nachio_z3_4_thu).state || id(nachio_z4_4_thu).state ||
              id(nachio_z5_4_thu).state || id(nachio_z6_4_thu).state ||
              id(nachio_z7_4_thu).state
            );
          } else if (weekday == 6) {  // fri
            should_run = (
              id(nachio_z1_5_fri).state || id(nachio_z2_5_fri).state ||
              id(nachio_z3_5_fri).state || id(nachio_z4_5_fri).state ||
              id(nachio_z5_5_fri).state || id(nachio_z6_5_fri).state ||
              id(nachio_z7_5_fri).state
            );
          } else if (weekday == 7) {  // sat
            should_run = (
              id(nachio_z1_6_sat).state || id(nachio_z2_6_sat).state ||
              id(nachio_z3_6_sat).state || id(nachio_z4_6_sat).state ||
              id(nachio_z5_6_sat).state || id(nachio_z6_6_sat).state ||
              id(nachio_z7_6_sat).state
            );
          }

          if (should_run) {
            ESP_LOGD("custom", "At least one zone is active for today. Executing script2_2_check_zone_day");
            id(script2_2_check_zone_day).execute();
            id(script3_start_queue).execute();
          } else {
            ESP_LOGD("custom", "No zones active for today.");
          }




  - id: script2_2_check_zone_day
    then:
      - lambda: |-
          int weekday = id(ha_time).now().day_of_week;
          switch (weekday) {
            case 1:
              id(script2_2_check_zone_sun).execute();
              break;
            case 2:
              id(script2_2_check_zone_mon).execute();
              break;
            case 3:
              id(script2_2_check_zone_tue).execute();
              break;
            case 4:
              id(script2_2_check_zone_wed).execute();
              break;
            case 5:
              id(script2_2_check_zone_thu).execute();
              break;
            case 6:
              id(script2_2_check_zone_fri).execute();
              break;
            case 7:
              id(script2_2_check_zone_sat).execute();
              break;
          }


  - id: script2_2_check_zone_sun
    then:
      - lambda: |-
          if (id(nachio_z1_0_sun).state && !id(nachio_z1_disable).state) {
            id(zone_index) = 0;
            if (id(nachio_z1_adjust_duration).state) {
              id(zone_duration) = (id(zone_1_run_duration).state + id(run_duration_adjustment).state)* 60;
            } else {
              id(zone_duration) = id(zone_1_run_duration).state * 60;
            }
            id(script2_3_queue_zone).execute();
          }
          if (id(nachio_z2_0_sun).state && !id(nachio_z2_disable).state) {
            id(zone_index) = 1;
            if (id(nachio_z2_adjust_duration).state) {
              id(zone_duration) = (id(zone_2_run_duration).state + id(run_duration_adjustment).state)* 60;
            } else {
              id(zone_duration) = id(zone_2_run_duration).state * 60;
            }
            id(script2_3_queue_zone).execute();
          }
          if (id(nachio_z3_0_sun).state && !id(nachio_z3_disable).state) {
            id(zone_index) = 2;
            if (id(nachio_z3_adjust_duration).state) {
              id(zone_duration) = (id(zone_3_run_duration).state + id(run_duration_adjustment).state)* 60;
            } else {
              id(zone_duration) = id(zone_3_run_duration).state * 60;
            }
            id(script2_3_queue_zone).execute();
          }
          if (id(nachio_z4_0_sun).state && !id(nachio_z4_disable).state) {
            id(zone_index) = 3;
            if (id(nachio_z4_adjust_duration).state) {
              id(zone_duration) = (id(zone_4_run_duration).state + id(run_duration_adjustment).state)* 60;
            } else {
              id(zone_duration) = id(zone_4_run_duration).state * 60;
            }
            id(script2_3_queue_zone).execute();
          }
          if (id(nachio_z5_0_sun).state && !id(nachio_z5_disable).state) {
            id(zone_index) = 4;
            if (id(nachio_z5_adjust_duration).state) {
              id(zone_duration) = (id(zone_5_run_duration).state + id(run_duration_adjustment).state)* 60;
            } else {
              id(zone_duration) = id(zone_5_run_duration).state * 60;
            }
            id(script2_3_queue_zone).execute();
          }
          if (id(nachio_z6_0_sun).state && !id(nachio_z6_disable).state) {
            id(zone_index) = 5;
            if (id(nachio_z6_adjust_duration).state) {
              id(zone_duration) = (id(zone_6_run_duration).state + id(run_duration_adjustment).state)* 60;
            } else {
              id(zone_duration) = id(zone_6_run_duration).state * 60;
            }
            id(script2_3_queue_zone).execute();
          }
          if (id(nachio_z7_0_sun).state && !id(nachio_z7_disable).state) {
            id(zone_index) = 6;
            if (id(nachio_z7_adjust_duration).state) {
              id(zone_duration) = (id(zone_7_run_duration).state + id(run_duration_adjustment).state)* 60;
            } else {
              id(zone_duration) = id(zone_7_run_duration).state * 60;
            }
            id(script2_3_queue_zone).execute();
          }
          //if (id(nachio_z8_0_sun).state) {
          //  id(zone_index) = 7;
          //  id(zone_duration) = id(zone_8_run_duration).state * 60;
          //  id(script2_3_queue_zone).execute();
          //}


  - id: script2_2_check_zone_mon
    then:
      - lambda: |-
          if (id(nachio_z1_1_mon).state && !id(nachio_z1_disable).state) {
            id(zone_index) = 0;
            if (id(nachio_z1_adjust_duration).state) {
              id(zone_duration) = (id(zone_1_run_duration).state + id(run_duration_adjustment).state)* 60;
            } else {
              id(zone_duration) = id(zone_1_run_duration).state * 60;
            }
            id(script2_3_queue_zone).execute();
          }
          if (id(nachio_z2_1_mon).state && !id(nachio_z2_disable).state) {
            id(zone_index) = 1;
            if (id(nachio_z2_adjust_duration).state) {
              id(zone_duration) = (id(zone_2_run_duration).state + id(run_duration_adjustment).state)* 60;
            } else {
              id(zone_duration) = id(zone_2_run_duration).state * 60;
            }
            id(script2_3_queue_zone).execute();
          }
          if (id(nachio_z3_1_mon).state && !id(nachio_z3_disable).state) {
            id(zone_index) = 2;
            if (id(nachio_z3_adjust_duration).state) {
              id(zone_duration) = (id(zone_3_run_duration).state + id(run_duration_adjustment).state)* 60;
            } else {
              id(zone_duration) = id(zone_3_run_duration).state * 60;
            }
            id(script2_3_queue_zone).execute();
          }
          if (id(nachio_z4_1_mon).state && !id(nachio_z4_disable).state) {
            id(zone_index) = 3;
            if (id(nachio_z4_adjust_duration).state) {
              id(zone_duration) = (id(zone_4_run_duration).state + id(run_duration_adjustment).state)* 60;
            } else {
              id(zone_duration) = id(zone_4_run_duration).state * 60;
            }
            id(script2_3_queue_zone).execute();
          }
          if (id(nachio_z5_1_mon).state && !id(nachio_z5_disable).state) {
            id(zone_index) = 4;
            if (id(nachio_z5_adjust_duration).state) {
              id(zone_duration) = (id(zone_5_run_duration).state + id(run_duration_adjustment).state)* 60;
            } else {
              id(zone_duration) = id(zone_5_run_duration).state * 60;
            }
            id(script2_3_queue_zone).execute();
          }
          if (id(nachio_z6_1_mon).state && !id(nachio_z6_disable).state) {
            id(zone_index) = 5;
            if (id(nachio_z6_adjust_duration).state) {
              id(zone_duration) = (id(zone_6_run_duration).state + id(run_duration_adjustment).state)* 60;
            } else {
              id(zone_duration) = id(zone_6_run_duration).state * 60;
            }
            id(script2_3_queue_zone).execute();
          }
          if (id(nachio_z7_1_mon).state && !id(nachio_z7_disable).state) {
            id(zone_index) = 6;
            if (id(nachio_z7_adjust_duration).state) {
              id(zone_duration) = (id(zone_7_run_duration).state + id(run_duration_adjustment).state)* 60;
            } else {
              id(zone_duration) = id(zone_7_run_duration).state * 60;
            }
            id(script2_3_queue_zone).execute();
          }
          //if (id(nachio_z8_1_mon).state && !id(nachio_z8_disable).state) {
          //  id(zone_index) = 7;
          //  if (id(nachio_z2_adjust_duration).state) {
          //    id(zone_duration) = (id(zone_2_run_duration).state + id(run_duration_adjustment).state)* 60;
          //} else {
          //    id(zone_duration) = id(zone_2_run_duration).state * 60;
          //}
          //  id(script2_3_queue_zone).execute();
          //}

  - id: script2_2_check_zone_tue
    then:
      - lambda: |-
          if (id(nachio_z1_2_tue).state && !id(nachio_z1_disable).state) {
            id(zone_index) = 0;
            if (id(nachio_z1_adjust_duration).state) {
              id(zone_duration) = (id(zone_1_run_duration).state + id(run_duration_adjustment).state)* 60;
            } else {
              id(zone_duration) = id(zone_1_run_duration).state * 60;
            }
            id(script2_3_queue_zone).execute();
          }
          if (id(nachio_z2_2_tue).state && !id(nachio_z2_disable).state) {
            id(zone_index) = 1;
            if (id(nachio_z2_adjust_duration).state) {
              id(zone_duration) = (id(zone_2_run_duration).state + id(run_duration_adjustment).state)* 60;
            } else {
              id(zone_duration) = id(zone_2_run_duration).state * 60;
            }
            id(script2_3_queue_zone).execute();
          }
          if (id(nachio_z3_2_tue).state && !id(nachio_z3_disable).state) {
            id(zone_index) = 2;
            if (id(nachio_z3_adjust_duration).state) {
              id(zone_duration) = (id(zone_3_run_duration).state + id(run_duration_adjustment).state)* 60;
            } else {
              id(zone_duration) = id(zone_3_run_duration).state * 60;
            }
            id(script2_3_queue_zone).execute();
          }
          if (id(nachio_z4_2_tue).state && !id(nachio_z4_disable).state) {
            id(zone_index) = 3;
            if (id(nachio_z4_adjust_duration).state) {
              id(zone_duration) = (id(zone_4_run_duration).state + id(run_duration_adjustment).state)* 60;
            } else {
              id(zone_duration) = id(zone_4_run_duration).state * 60;
            }
            id(script2_3_queue_zone).execute();
          }
          if (id(nachio_z5_2_tue).state && !id(nachio_z5_disable).state) {
            id(zone_index) = 4;
            if (id(nachio_z5_adjust_duration).state) {
              id(zone_duration) = (id(zone_5_run_duration).state + id(run_duration_adjustment).state)* 60;
            } else {
              id(zone_duration) = id(zone_5_run_duration).state * 60;
            }
            id(script2_3_queue_zone).execute();
          }
          if (id(nachio_z6_2_tue).state && !id(nachio_z6_disable).state) {
            id(zone_index) = 5;
            if (id(nachio_z6_adjust_duration).state) {
              id(zone_duration) = (id(zone_6_run_duration).state + id(run_duration_adjustment).state)* 60;
            } else {
              id(zone_duration) = id(zone_6_run_duration).state * 60;
            }
            id(script2_3_queue_zone).execute();
          }
          if (id(nachio_z7_2_tue).state && !id(nachio_z7_disable).state) {
            id(zone_index) = 6;
            if (id(nachio_z7_adjust_duration).state) {
              id(zone_duration) = (id(zone_7_run_duration).state + id(run_duration_adjustment).state)* 60;
            } else {
              id(zone_duration) = id(zone_7_run_duration).state * 60;
            }
            id(script2_3_queue_zone).execute();
          }
          //if (id(nachio_z8_2_tue).state) {
          //  id(zone_index) = 7;
          //  id(zone_duration) = id(zone_8_run_duration).state * 60;
          //  id(script2_3_queue_zone).execute();
          //}

  - id: script2_2_check_zone_wed
    then:
      - lambda: |-
          if (id(nachio_z1_3_wed).state && !id(nachio_z1_disable).state) {
            id(zone_index) = 0;
            if (id(nachio_z1_adjust_duration).state) {
              id(zone_duration) = (id(zone_1_run_duration).state + id(run_duration_adjustment).state)* 60;
            } else {
              id(zone_duration) = id(zone_1_run_duration).state * 60;
            }
            id(script2_3_queue_zone).execute();
          }
          if (id(nachio_z2_3_wed).state && !id(nachio_z2_disable).state) {
            id(zone_index) = 1;
            if (id(nachio_z2_adjust_duration).state) {
              id(zone_duration) = (id(zone_2_run_duration).state + id(run_duration_adjustment).state)* 60;
            } else {
              id(zone_duration) = id(zone_2_run_duration).state * 60;
            }
            id(script2_3_queue_zone).execute();
          }
          if (id(nachio_z3_3_wed).state && !id(nachio_z3_disable).state) {
            id(zone_index) = 2;
            if (id(nachio_z3_adjust_duration).state) {
              id(zone_duration) = (id(zone_3_run_duration).state + id(run_duration_adjustment).state)* 60;
            } else {
              id(zone_duration) = id(zone_3_run_duration).state * 60;
            }
            id(script2_3_queue_zone).execute();
          }
          if (id(nachio_z4_3_wed).state && !id(nachio_z4_disable).state) {
            id(zone_index) = 3;
            if (id(nachio_z4_adjust_duration).state) {
              id(zone_duration) = (id(zone_4_run_duration).state + id(run_duration_adjustment).state)* 60;
            } else {
              id(zone_duration) = id(zone_4_run_duration).state * 60;
            }
            id(script2_3_queue_zone).execute();
          }
          if (id(nachio_z5_3_wed).state && !id(nachio_z5_disable).state) {
            id(zone_index) = 4;
            if (id(nachio_z5_adjust_duration).state) {
              id(zone_duration) = (id(zone_5_run_duration).state + id(run_duration_adjustment).state)* 60;
            } else {
              id(zone_duration) = id(zone_5_run_duration).state * 60;
            }
            id(script2_3_queue_zone).execute();
          }
          if (id(nachio_z6_3_wed).state && !id(nachio_z6_disable).state) {
            id(zone_index) = 5;
            if (id(nachio_z6_adjust_duration).state) {
              id(zone_duration) = (id(zone_6_run_duration).state + id(run_duration_adjustment).state)* 60;
            } else {
              id(zone_duration) = id(zone_6_run_duration).state * 60;
            }
            id(script2_3_queue_zone).execute();
          }
          if (id(nachio_z7_3_wed).state && !id(nachio_z7_disable).state) {
            id(zone_index) = 6;
            if (id(nachio_z7_adjust_duration).state) {
              id(zone_duration) = (id(zone_7_run_duration).state + id(run_duration_adjustment).state)* 60;
            } else {
              id(zone_duration) = id(zone_7_run_duration).state * 60;
            }
            id(script2_3_queue_zone).execute();
          }
          //if (id(nachio_z8_3_wed).state) {
          //  id(zone_index) = 7;
          //  id(zone_duration) = id(zone_8_run_duration).state * 60;
          //  id(script2_3_queue_zone).execute();
          //}

  - id: script2_2_check_zone_thu
    then:
      - lambda: |-
          if (id(nachio_z1_4_thu).state && !id(nachio_z1_disable).state) {
            id(zone_index) = 0;
            if (id(nachio_z1_adjust_duration).state) {
              id(zone_duration) = (id(zone_1_run_duration).state + id(run_duration_adjustment).state)* 60;
            } else {
              id(zone_duration) = id(zone_1_run_duration).state * 60;
            }
            id(script2_3_queue_zone).execute();
          }
          if (id(nachio_z2_4_thu).state && !id(nachio_z2_disable).state) {
            id(zone_index) = 1;
            if (id(nachio_z2_adjust_duration).state) {
              id(zone_duration) = (id(zone_2_run_duration).state + id(run_duration_adjustment).state)* 60;
            } else {
              id(zone_duration) = id(zone_2_run_duration).state * 60;
            }
            id(script2_3_queue_zone).execute();
          }
          if (id(nachio_z3_4_thu).state && !id(nachio_z3_disable).state) {
            id(zone_index) = 2;
            if (id(nachio_z3_adjust_duration).state) {
              id(zone_duration) = (id(zone_3_run_duration).state + id(run_duration_adjustment).state)* 60;
            } else {
              id(zone_duration) = id(zone_3_run_duration).state * 60;
            }
            id(script2_3_queue_zone).execute();
          }
          if (id(nachio_z4_4_thu).state && !id(nachio_z4_disable).state) {
            id(zone_index) = 3;
            if (id(nachio_z4_adjust_duration).state) {
              id(zone_duration) = (id(zone_4_run_duration).state + id(run_duration_adjustment).state)* 60;
            } else {
              id(zone_duration) = id(zone_4_run_duration).state * 60;
            }
            id(script2_3_queue_zone).execute();
          }
          if (id(nachio_z5_4_thu).state && !id(nachio_z5_disable).state) {
            id(zone_index) = 4;
            if (id(nachio_z5_adjust_duration).state) {
              id(zone_duration) = (id(zone_5_run_duration).state + id(run_duration_adjustment).state)* 60;
            } else {
              id(zone_duration) = id(zone_5_run_duration).state * 60;
            }
            id(script2_3_queue_zone).execute();
          }
          if (id(nachio_z6_4_thu).state && !id(nachio_z6_disable).state) {
            id(zone_index) = 5;
            if (id(nachio_z6_adjust_duration).state) {
              id(zone_duration) = (id(zone_6_run_duration).state + id(run_duration_adjustment).state)* 60;
            } else {
              id(zone_duration) = id(zone_6_run_duration).state * 60;
            }
            id(script2_3_queue_zone).execute();
          }
          if (id(nachio_z7_4_thu).state && !id(nachio_z7_disable).state) {
            id(zone_index) = 6;
            if (id(nachio_z7_adjust_duration).state) {
              id(zone_duration) = (id(zone_7_run_duration).state + id(run_duration_adjustment).state)* 60;
            } else {
              id(zone_duration) = id(zone_7_run_duration).state * 60;
            }
            id(script2_3_queue_zone).execute();
          }
          //if (id(nachio_z8_4_thu).state) {
          //  id(zone_index) = 7;
          //  id(zone_duration) = id(zone_8_run_duration).state * 60;
          //  id(script2_3_queue_zone).execute();
          //}


  - id: script2_2_check_zone_fri
    then:
      - lambda: |-
          if (id(nachio_z1_5_fri).state && !id(nachio_z1_disable).state) {
            id(zone_index) = 0;
            if (id(nachio_z1_adjust_duration).state) {
              id(zone_duration) = (id(zone_1_run_duration).state + id(run_duration_adjustment).state)* 60;
            } else {
              id(zone_duration) = id(zone_1_run_duration).state * 60;
            }
            id(script2_3_queue_zone).execute();
          }
          if (id(nachio_z2_5_fri).state && !id(nachio_z2_disable).state) {
            id(zone_index) = 1;
            if (id(nachio_z2_adjust_duration).state) {
              id(zone_duration) = (id(zone_2_run_duration).state + id(run_duration_adjustment).state)* 60;
            } else {
              id(zone_duration) = id(zone_2_run_duration).state * 60;
            }
            id(script2_3_queue_zone).execute();
          }
          if (id(nachio_z3_5_fri).state && !id(nachio_z3_disable).state) {
            id(zone_index) = 2;
            if (id(nachio_z3_adjust_duration).state) {
              id(zone_duration) = (id(zone_3_run_duration).state + id(run_duration_adjustment).state)* 60;
            } else {
              id(zone_duration) = id(zone_3_run_duration).state * 60;
            }
            id(script2_3_queue_zone).execute();
          }
          if (id(nachio_z4_5_fri).state && !id(nachio_z4_disable).state) {
            id(zone_index) = 3;
            if (id(nachio_z4_adjust_duration).state) {
              id(zone_duration) = (id(zone_4_run_duration).state + id(run_duration_adjustment).state)* 60;
            } else {
              id(zone_duration) = id(zone_4_run_duration).state * 60;
            }
            id(script2_3_queue_zone).execute();
          }
          if (id(nachio_z5_5_fri).state && !id(nachio_z5_disable).state) {
            id(zone_index) = 4;
            if (id(nachio_z5_adjust_duration).state) {
              id(zone_duration) = (id(zone_5_run_duration).state + id(run_duration_adjustment).state)* 60;
            } else {
              id(zone_duration) = id(zone_5_run_duration).state * 60;
            }
            id(script2_3_queue_zone).execute();
          }
          if (id(nachio_z6_5_fri).state && !id(nachio_z6_disable).state) {
            id(zone_index) = 5;
            if (id(nachio_z6_adjust_duration).state) {
              id(zone_duration) = (id(zone_6_run_duration).state + id(run_duration_adjustment).state)* 60;
            } else {
              id(zone_duration) = id(zone_6_run_duration).state * 60;
            }
            id(script2_3_queue_zone).execute();
          }
          if (id(nachio_z7_5_fri).state && !id(nachio_z7_disable).state) {
            id(zone_index) = 6;
            if (id(nachio_z7_adjust_duration).state) {
              id(zone_duration) = (id(zone_7_run_duration).state + id(run_duration_adjustment).state)* 60;
            } else {
              id(zone_duration) = id(zone_7_run_duration).state * 60;
            }
            id(script2_3_queue_zone).execute();
          }
          //if (id(nachio_z8_5_fri).state) {
          //  id(zone_index) = 7;
          //  id(zone_duration) = id(zone_8_run_duration).state * 60;
          //  id(script2_3_queue_zone).execute();
          //}

  - id: script2_2_check_zone_sat
    then:
      - lambda: |-
          if (id(nachio_z1_6_sat).state && !id(nachio_z1_disable).state) {
            id(zone_index) = 0;
            if (id(nachio_z1_adjust_duration).state) {
              id(zone_duration) = (id(zone_1_run_duration).state + id(run_duration_adjustment).state)* 60;
            } else {
              id(zone_duration) = id(zone_1_run_duration).state * 60;
            }
            id(script2_3_queue_zone).execute();
          }
          if (id(nachio_z2_6_sat).state && !id(nachio_z2_disable).state) {
            id(zone_index) = 1;
            if (id(nachio_z2_adjust_duration).state) {
              id(zone_duration) = (id(zone_2_run_duration).state + id(run_duration_adjustment).state)* 60;
            } else {
              id(zone_duration) = id(zone_2_run_duration).state * 60;
            }
            id(script2_3_queue_zone).execute();
          }
          if (id(nachio_z3_6_sat).state && !id(nachio_z2_disable).state) {
            id(zone_index) = 2;
            if (id(nachio_z3_adjust_duration).state) {
              id(zone_duration) = (id(zone_3_run_duration).state + id(run_duration_adjustment).state)* 60;
            } else {
              id(zone_duration) = id(zone_3_run_duration).state * 60;
            }
            id(script2_3_queue_zone).execute();
          }
          if (id(nachio_z4_6_sat).state && !id(nachio_z4_disable).state) {
            id(zone_index) = 3;
            if (id(nachio_z4_adjust_duration).state) {
              id(zone_duration) = (id(zone_4_run_duration).state + id(run_duration_adjustment).state)* 60;
            } else {
              id(zone_duration) = id(zone_4_run_duration).state * 60;
            }
            id(script2_3_queue_zone).execute();
          }
          if (id(nachio_z5_6_sat).state && !id(nachio_z5_disable).state) {
            id(zone_index) = 4;
            if (id(nachio_z5_adjust_duration).state) {
              id(zone_duration) = (id(zone_5_run_duration).state + id(run_duration_adjustment).state)* 60;
            } else {
              id(zone_duration) = id(zone_5_run_duration).state * 60;
            }
            id(script2_3_queue_zone).execute();
          }
          if (id(nachio_z6_6_sat).state && !id(nachio_z6_disable).state) {
            id(zone_index) = 5;
            if (id(nachio_z6_adjust_duration).state) {
              id(zone_duration) = (id(zone_6_run_duration).state + id(run_duration_adjustment).state)* 60;
            } else {
              id(zone_duration) = id(zone_6_run_duration).state * 60;
            }
            id(script2_3_queue_zone).execute();
          }
          if (id(nachio_z7_6_sat).state && !id(nachio_z7_disable).state) {
            id(zone_index) = 6;
            if (id(nachio_z7_adjust_duration).state) {
              id(zone_duration) = (id(zone_7_run_duration).state + id(run_duration_adjustment).state)* 60;
            } else {
              id(zone_duration) = id(zone_7_run_duration).state * 60;
            }
            id(script2_3_queue_zone).execute();
          }
          //if (id(nachio_z8_6_sat).state) {
          //  id(zone_index) = 7;
          //  id(zone_duration) = id(zone_8_run_duration).state * 60;
          //  id(script2_3_queue_zone).execute();
          //}

  # Add zone to queue
  - id: script2_3_queue_zone
    mode: queued
    then:
      - lambda: |-
          id(sprinkler_queue_size) += 1;
          // Assuming the zone runtime is stored as minutes
          float runtime_min = id(zone_duration);
          int runtime_sec = (int)(runtime_min * 60.0);
          id(sprinkler_queue_time_remaining) += runtime_sec;
      - sprinkler.queue_valve:
          id: nachio_sprinklers
          valve_number: !lambda |-
            return id(zone_index);
          run_duration: !lambda |-
            return id(zone_duration);

  # start the sprinkler queue
  - id: script3_start_queue
    then:
      #- lambda: |-
      #    auto time = id(ha_time).now();
      #    char buffer[32];
      #    snprintf(buffer, sizeof(buffer), "%04d-%02d-%02d %02d:%02d:%02d",
      #        time.year, time.month, time.day_of_month,
      #        time.hour, time.minute, time.second);
      #    id(_last_run_str) = std::string(buffer);
      - sprinkler.start_from_queue: ${id_prefix}_sprinklers
      - lambda: |-
          ESP_LOGI("main", "Sprinkler queue has begun!");


  #Used to manually run a zone from Home Assistant
  - id: start_valve_dynamic
    then:
      - lambda: |-
          id(sprinkler_queue_size) += 1;
          auto sprinkler = id(${id_prefix}_sprinklers);
          int valve_num = (int) id(valve_number_input).state;
          int duration = (int) id(var_run_duration_minutes).state * 60;
          sprinkler->start_single_valve(valve_num, duration);



  #### BEGIN Rain Skip related scripts ######
  - id: check_rain_skip_conditions
    mode: queued
    then:
      - lambda: |-
          float forecast = id(forecasted_rain).state;
          float actual = id(actual_rain).state;

          if (forecast >= 0.2 || actual >= 0.2) {
            id(${id_prefix}_rain_skip_lock).turn_on();

            if (actual >= 0.8) {
              id(rain_skip_duration_48h).execute();
            } else {
              id(rain_skip_duration_24h).execute();
            }
          }

  - id: rain_skip_duration_24h
    then:
      - delay: 24h
      - switch.turn_off: ${id_prefix}_rain_skip_lock

  - id: rain_skip_duration_48h
    then:
      - delay: 48h
      - switch.turn_off: ${id_prefix}_rain_skip_lock


##### END Rain Skip related scripts ######


###############################################
# Text sensors with general information.
###############################################
text_sensor:
#  - platform: version # Expose ESPHome version as sensor.
#    name: $name_of_device ESPHome Version
#    hide_timestamp: false
  - platform: wifi_info
    ip_address:
      name: "${name_of_device}_IP"
    ssid:
      name: "${name_of_device}_SSID"
    bssid:
      name: "${name_of_device}_BSSID"



  - platform: template
    name: "Uptime (Formatted)"
    lambda: |-
        int seconds = (int) id(uptime_seconds).state;
        int days = seconds / 86400;
        int hours = (seconds % 86400) / 3600;
        int minutes = (seconds % 3600) / 60;
        return to_string(days) + "d " + to_string(hours) + "h " + to_string(minutes) + "m";
    entity_category: "diagnostic"


  - platform: template
    id: controller_status
    name: ${id_prefix} Status
    update_interval: never
    icon: "mdi:information-variant"

  - platform: template
    name: "Active Zone"
    id: active_valve_name
    update_interval: 10s
    lambda: |-
      if (id(nachio_valve01).state) return std::string("Front Lawn");
      if (id(nachio_valve02).state) return std::string("Front Patio");
      if (id(nachio_valve03).state) return std::string("Back Lawn1");
      if (id(nachio_valve04).state) return std::string("Back Lawn2");
      if (id(nachio_valve05).state) return std::string("Roses & Sideyard");
      if (id(nachio_valve06).state) return std::string("Garden & Dahlias");
      if (id(nachio_valve07).state) return std::string("Left Driveway");
      if (id(nachio_valve08).state) return std::string("Unassigned");

      return std::string("None");

  - platform: template
    id: time_remaining
    name: "Current Zone Time Remaining"
    update_interval: 10s
    icon: "mdi:timer-sand"
    lambda: |-
      int remaining = id(nachio_sprinklers).time_remaining_active_valve().value_or(0);

      int days = remaining / 86400;
      int hours = (remaining % 86400) / 3600;
      int minutes = (remaining % 3600) / 60;
      int seconds = remaining % 60;

      char buffer[32];
      snprintf(buffer, sizeof(buffer), "%s%s%dm %ds",
        (days > 0 ? (std::to_string(days) + "d ").c_str() : ""),
        (hours > 0 ? (std::to_string(hours) + "h ").c_str() : ""),
        minutes, seconds
      );
      return std::string(buffer);