ESPHome: DIY Irrigation Controller With Internal Scheduler

Great! I already have it 100% working, I combined the great original project of @BrianHanifin , plus the functionalities that @raberrio added. Full operation of the buttons, scheduling possibilities and finally the inputs texts and numbers explained by you @VikingBlod , this last part is the one that made the difference since, unless I have not read it correctly, it did not appear in the previous projects, if it’s I’m sorry my fault. Tomorrow I will try to do the cosmetic part by adding the icons directly in the code and I will post them to share with everyone.

Once again THANKS

1 Like

Glad u did it :slight_smile:

Sorry for late answer

1 Like

Hi joaquin
Setting the timezone with words will not work. At least for me. Maybe esphome fixed it but when i wrote the code and checked, esphome accepted the format but when i looked at the logs, device stuck to UTC

Better to use that explicit format

Good luck

1 Like

Format is pretty straightforward:
Base hour + shift , date of summer time start , date of summer time end.

Date format is explained in the article better

1 Like

Hi @raberrio don’t worry for the delay. About time zone, yes it’s working I don’t have logs files right now but it was showing UTC -3, any way, if you don’t set time zone it will take it from HA. Based on time configuration at ESPhome Time configuration.

Thank for your time.

I’m looking to add days to mine too. Is your full code online somewhere?

Since this post I have written an article on my website sharing details on creating the required Home Assistant Entities and a Simplified User Interface. This should provide more of the missing information.

1 Like

Hi Brian, could you share more info on the electrical connect part? I see from the pictures that you power the sonoff with the 120V AC direct line in. What about the valve solenoids? Do send the positive from the 24VAC transformer to all the valve solenoids and splice the ground to the sonoff?
Thanks for your help.

Looks good! Thanks for sharing. I am taking some of it to make a custom controller with an ESP where I can add an antenna, DHT sensor and I hope I can also integrate weather forecast.

1 Like

Mario, since this post I have written an article on my website sharing details on creating the required Home Assistant Entities and a Simplified User Interface . This should provide more of the missing information.

Hi its been a while since my last post, I hope everyone find this proyect helpfull

Original idea by @BrianHanifin ESPHome: DIY Irrigation Controller With Internal Scheduler.

Plus Functionalities by @raberrio.

And Full operation of the buttons, scheduling possibilities and finally the inputs texts and numbers explained by @VikingBlod.

This goes in config.yaml

######RIEGO
input_text:

##HORAS
  irrigation_zone1_times:
    name: Horarios 1
    icon: mdi:chart-timeline

  irrigation_zone2_times:
    name: Horarios 2
    icon: mdi:chart-timeline

  irrigation_zone3_times:
    name: Horarios 3
    icon: mdi:chart-timeline

#  irrigation_zone4_times:
#    name: Horarios 4
#    icon: mdi:chart-timeline

##DIAS
  irrigation_zone1_days:
    name: Dias Zona 1
    icon: mdi:calendar-week

  irrigation_zone2_days:
    name: Dias Zona 2
    icon: mdi:calendar-week

  irrigation_zone3_days:
    name: Dias Zona 3
    icon: mdi:calendar-week

#  irrigation_zone4_days:
#    name: Dias Zona 4
#    icon: mdi:calendar-week

input_number:

##DURACION
  irrigation_zone1_duration:
    name: Duración Zona 1
    min: 0
    max: 30
    step: 1
    mode: slider
    icon: mdi:timer

  irrigation_zone2_duration:
    name: Duración Zona 2
    min: 0
    max: 30
    step: 1
    mode: slider
    icon: mdi:timer

  irrigation_zone3_duration:
    name: Duración Zona 3
    min: 0
    max: 30
    step: 1
    mode: slider
    icon: mdi:timer

  irrigation_zone4_duration:
    name: Duración Zona 4
    min: 0
    max: 30
    step: 1
    mode: slider
    icon: mdi:timer

This is esphome yaml

# MIT License: https://github.com/brianhanifin/Irrigation-with-display/blob/master/LICENSE
#
# Credit: @bruxy70 thank you for the significant head start!
# Personal project goals: https://github.com/brianhanifin/Home-Assistant-Config/issues/37
#
substitutions:
  project: Irrigation Controller2
  id: irrigation2

  <<: !include common/substitutions/gpio/sonoff4chpror2.yaml

esphome:
  name: riego_sonoff
  platform: ESP8266
  board: esp01_1m
  includes:
    - irrigation.h



wifi:
  ssid: "SSID"
  password: "PASSWoRd"

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

captive_portal:

<<: !include common/logger.yaml

# Enable Home Assistant API
api:

ota:


globals:
  # ============================================================================= #
  # Irrigation time remaining
  - id: remaining_time1
    type: int
    restore_value: no
    initial_value: "300"
  - id: remaining_time2
    type: int
    restore_value: no
    initial_value: "300"
  - id: remaining_time3
    type: int
    restore_value: no
    initial_value: "300"
  - id: remaining_time4
    type: int
    restore_value: no
    initial_value: "300"

  # ============================================================================= #
  # Store previous values to verify change.
  - id: remaining_time1_previous
    type: int
    restore_value: no
    initial_value: "0"
  - id: remaining_time2_previous
    type: int
    restore_value: no
    initial_value: "0"
  - id: remaining_time3_previous
    type: int
    restore_value: no
    initial_value: "0"
  - id: remaining_time4_previous
    type: int
    restore_value: no
    initial_value: "0"

# Common housekeeping components.
output:
  <<: !include common/outputs/status_led.yaml

light:
  <<: !include common/lights/status_led.yaml


binary_sensor:
  - !include common/binary_sensors/status.yaml

  # ============================================================================= #
  # Buttons along the left side of the unit (R1, R2, R3, R4).
  - platform: gpio
    id: key1
    pin:
      number: $button1_gpio
      mode: INPUT_PULLUP
      inverted: True
    filters:
      - delayed_on: 100ms
      - delayed_off: 100ms
    on_click:
      min_length: 50ms
      max_length: 350ms
      then:
        - switch.toggle: irrigation_zone1

  - platform: gpio
    id: key2
    pin:
      number: $button2_gpio
      mode: INPUT_PULLUP
      inverted: True
    filters:
      - delayed_on: 100ms
      - delayed_off: 100ms
    on_click:
      min_length: 50ms
      max_length: 350ms
      then:
        - switch.toggle: irrigation_zone2

  - platform: gpio
    id: key3
    pin:
      number: $button3_gpio
      mode: INPUT_PULLUP
      inverted: True
    filters:
      - delayed_on: 100ms
      - delayed_off: 100ms
    on_click:
      min_length: 50ms
      max_length: 350ms
      then:
        - switch.toggle: irrigation_zone3

  - platform: gpio
    id: key4
    pin:
      number: $button4_gpio
      mode: INPUT_PULLUP
      inverted: True
    filters:
      - delayed_on: 100ms
      - delayed_off: 100ms
    on_click:
      min_length: 50ms
      max_length: 350ms
      then:
        - switch.toggle: irrigation_zone4

switch:
  #- !include common/switches/restart.yaml

  # ============================================================================= #
  # Virtual Zone Switches which toggle the relay, and store the current state.
  - platform: template
    name: Irrigation Zone1
    id: irrigation_zone1
    icon: mdi:sprinkler-variant
    lambda: return id(relay1).state;
    optimistic: true
    turn_on_action:
      # Turn on if not disabled.
      if:
        condition:
          lambda: return id(irrigation_zone1_duration) > 0;
        then:
          - switch.turn_on: relay1
    turn_off_action:
      - switch.turn_off: relay1

  - platform: template
    name: Irrigation Zone2
    id: irrigation_zone2
    icon: mdi:sprinkler-variant
    lambda: return id(relay2).state;
    optimistic: true
    turn_on_action:
      # Turn on if not disabled.
      if:
        condition:
          lambda: return id(irrigation_zone2_duration) > 0;
        then:
          - switch.turn_on: relay2
    turn_off_action:
      - switch.turn_off: relay2

  - platform: template
    name: Irrigation Zone3
    id: irrigation_zone3
    icon: mdi:sprinkler-variant
    lambda: return id(relay3).state;
    optimistic: true
    turn_on_action:
      # Turn on if not disabled.
      if:
        condition:
          lambda: return id(irrigation_zone3_duration) > 0;
        then:
          - switch.turn_on: relay3
    turn_off_action:
      - switch.turn_off: relay3

  - platform: template
    name: Irrigation Zone4
    id: irrigation_zone4
    icon: mdi:sprinkler-variant
    lambda: return id(relay4).state;
    optimistic: true
    turn_on_action:
      # Turn on if not disabled.
      if:
        condition:
          lambda: return id(irrigation_zone4_duration) > 0;
        then:
          - switch.turn_on: relay4
    turn_off_action:
      - switch.turn_off: relay4

  # ============================================================================= #
  # Relays which trigger solenoids
  - platform: gpio
    id: relay1
    pin: $relay1_gpio
    restore_mode: ALWAYS_OFF
    on_turn_on:
      then:
        # Start the countdown timer.
        - globals.set:
            id: remaining_time1
            value: !lambda return id(irrigation_zone1_duration).state * 60;
        # Show the remaining time.
        - sensor.template.publish:
            id: irrigation_zone1_remaining
            state: !lambda return id(irrigation_zone1_duration).state;
        # Show the "Next Time" as "now".
        - text_sensor.template.publish:
            id: irrigation_zone1_next
            state: "ahora"
            # state NOW on original code, change to your preferred language
    on_turn_off:
      then:
        - sensor.template.publish:
            id: irrigation_zone1_remaining
            state: "0"
        # Update the next scheduled run time.
        - text_sensor.template.publish:
            id: irrigation_zone1_next
            state: !lambda |-
              return update_next_runtime(id(irrigation_zone1_times).state, id(irrigation_zone1_days).state);

  - platform: gpio
    id: relay2
    pin: $relay2_gpio
    restore_mode: ALWAYS_OFF
    on_turn_on:
      then:
        # Start the countdown timer.
        - globals.set:
            id: remaining_time2
            value: !lambda return id(irrigation_zone2_duration).state * 60;
        # Show the remaining time.
        - sensor.template.publish:
            id: irrigation_zone2_remaining
            state: !lambda return id(irrigation_zone2_duration).state;
        # Show the "Next Time" as "now".
        - text_sensor.template.publish:
            id: irrigation_zone2_next
            state: "ahora"
    on_turn_off:
      then:
        - sensor.template.publish:
            id: irrigation_zone2_remaining
            state: "0"
        # Update the next scheduled run time.
        - text_sensor.template.publish:
            id: irrigation_zone2_next
            state: !lambda |-
              return update_next_runtime(id(irrigation_zone2_times).state, id(irrigation_zone2_days).state);

  - platform: gpio
    id: relay3
    pin: $relay3_gpio
    restore_mode: ALWAYS_OFF
    on_turn_on:
      then:
        # Start the countdown timer.
        - globals.set:
            id: remaining_time3
            value: !lambda return id(irrigation_zone3_duration).state * 60;
        # Show the remaining time.
        - sensor.template.publish:
            id: irrigation_zone3_remaining
            state: !lambda return id(irrigation_zone3_duration).state;
        # Show the "Next Time" as "now".
        - text_sensor.template.publish:
            id: irrigation_zone3_next
            state: "ahora"
    on_turn_off:
      then:
        - sensor.template.publish:
            id: irrigation_zone3_remaining
            state: "0"
        # Update the next scheduled run time.
        - text_sensor.template.publish:
            id: irrigation_zone3_next
            state: !lambda |-
              return update_next_runtime(id(irrigation_zone3_times).state, id(irrigation_zone3_days).state);

  - platform: gpio
    id: relay4
    pin: $relay4_gpio
    restore_mode: ALWAYS_OFF
    on_turn_on:
      then:
        # Start the countdown timer.
        - globals.set:
            id: remaining_time4
            value: !lambda return id(irrigation_zone4_duration).state * 60;
        # Show the remaining time.
        - sensor.template.publish:
            id: irrigation_zone4_remaining
            state: !lambda return id(irrigation_zone4_duration).state;
        # Show the "Next Time" as "now".
        - text_sensor.template.publish:
            id: irrigation_zone4_next
            state: "ahora"
    on_turn_off:
      then:
        - sensor.template.publish:
            id: irrigation_zone4_remaining
            state: "0"
        # Update the next scheduled run time.
        - text_sensor.template.publish:
            id: irrigation_zone4_next
            state: !lambda |-
              return update_next_runtime(id(irrigation_zone4_times).state, id(irrigation_zone4_days).state);

sensor:
  - !include common/sensors/uptime.yaml
  - !include common/sensors/wifi_signal.yaml

  # ============================================================================= #
  # Retrieve durations settings from the Home Assistant UI.
  - platform: homeassistant
    id: ui_zone1_duration
    entity_id: input_number.irrigation_zone1_duration
    on_value:
      #if:
      #  condition:
      #    api.connected:
      then:
        - sensor.template.publish:
            id: irrigation_zone1_duration
            state: !lambda return id(ui_zone1_duration).state;

  - platform: homeassistant
    id: ui_zone2_duration
    entity_id: input_number.irrigation_zone2_duration
    on_value:
      #if:
      #  condition:
      #    api.connected:
      then:
      - sensor.template.publish:
          id: irrigation_zone2_duration
          state: !lambda return id(ui_zone2_duration).state;

  - platform: homeassistant
    id: ui_zone3_duration
    entity_id: input_number.irrigation_zone3_duration
    on_value:
      #if:
      #  condition:
      #    api.connected:
      then:
      - sensor.template.publish:
          id: irrigation_zone3_duration
          state: !lambda return id(ui_zone3_duration).state;

  - platform: homeassistant
    id: ui_zone4_duration
    entity_id: input_number.irrigation_zone4_duration
    on_value:
      #if:
      #  condition:
      #    api.connected:
      then:
      - sensor.template.publish:
          id: irrigation_zone4_duration
          state: !lambda return id(ui_zone4_duration).state;

  # ============================================================================= #
  # Store durations.
  - platform: template
    name: Duración riego Zona 1
    id: irrigation_zone1_duration
    accuracy_decimals: 0
    unit_of_measurement: min
    icon: mdi:camera-timer

  - platform: template
    name: Duración riego Zona 2
    id: irrigation_zone2_duration
    accuracy_decimals: 0
    unit_of_measurement: min
    icon: mdi:camera-timer

  - platform: template
    name: Duración riego Zona 3
    id: irrigation_zone3_duration
    accuracy_decimals: 0
    unit_of_measurement: min
    icon: mdi:camera-timer

  - platform: template
    name: Duración riego Zona 4
    id: irrigation_zone4_duration
    accuracy_decimals: 0
    unit_of_measurement: min
    icon: mdi:camera-timer

  # ============================================================================= #
  # Countdown sensors.
  - platform: template
    name: Zona 1 tiempo restante
    id: irrigation_zone1_remaining
    lambda: "return 0;"
    accuracy_decimals: 0
    unit_of_measurement: min
    icon: mdi:timer
    on_value:
      then:
        - if:
            condition:
              lambda: return id(remaining_time1) == 0;
            then:
              - switch.turn_off: relay1

  - platform: template
    name: Zona 2 tiempo restante
    id: irrigation_zone2_remaining
    lambda: "return 0;"
    accuracy_decimals: 0
    unit_of_measurement: min
    icon: mdi:timer
    on_value:
      then:
        - if:
            condition:
              lambda: return id(remaining_time2) == 0;
            then:
              - switch.turn_off: relay2

  - platform: template
    name: Zona 3 tiempo restante
    id: irrigation_zone3_remaining
    lambda: "return 0;"
    accuracy_decimals: 0
    unit_of_measurement: min
    icon: mdi:timer
    on_value:
      then:
        - if:
            condition:
              lambda: return id(remaining_time3) == 0;
            then:
              - switch.turn_off: relay3

  - platform: template
    name: Zona 4 tiempo restante
    id: irrigation_zone4_remaining
    lambda: "return 0;"
    accuracy_decimals: 0
    unit_of_measurement: min
    icon: mdi:timer
    on_value:
      then:
        - if:
            condition:
              lambda: return id(remaining_time4) == 0;
            then:
              - switch.turn_off: relay4

text_sensor:
  # ============================================================================= #
  # Retrieve list of times from the Home Assistant UI.
  - platform: homeassistant
    id: ui_zone1_times
    entity_id: input_text.irrigation_zone1_times
    on_value:
      #if:
      #  condition:
      #    api.connected:
      then:
        #- delay: 10sec
        - text_sensor.template.publish:
            id: irrigation_zone1_times
            state: !lambda return id(ui_zone1_times).state;

  - platform: homeassistant
    id: ui_zone2_times
    entity_id: input_text.irrigation_zone2_times
    on_value:
      #if:
      #  condition:
      #    api.connected:
      then:
        #- delay: 10sec
        - text_sensor.template.publish:
            id: irrigation_zone2_times
            state: !lambda return id(ui_zone2_times).state;

  - platform: homeassistant
    id: ui_zone3_times
    entity_id: input_text.irrigation_zone3_times
    on_value:
      #if:
      #  condition:
      #    api.connected:
      then:
        #- delay: 10sec
        - text_sensor.template.publish:
            id: irrigation_zone3_times
            state: !lambda return id(ui_zone3_times).state;

  - platform: homeassistant
    id: ui_zone4_times
    entity_id: input_text.irrigation_zone4_times
    on_value:
      #if:
      #  condition:
      #    api.connected:
      then:
        #- delay: 10sec
        - text_sensor.template.publish:
            id: irrigation_zone4_times
            state: !lambda return id(ui_zone4_times).state;
  # ============================================================================= #
  # Store time lists.
  - platform: template
    name: Zona 1 Horarios
    id: irrigation_zone1_times
    on_value:
      then:
        # Update the next scheduled run time.
        - text_sensor.template.publish:
            id: irrigation_zone1_next
            state: !lambda |-
              return update_next_runtime(id(irrigation_zone1_times).state, id(irrigation_zone1_days).state);
  - platform: template
    name: Zona 2 Horarios
    id: irrigation_zone2_times
    on_value:
      then:
        # Update the next scheduled run time.
        - text_sensor.template.publish:
            id: irrigation_zone2_next
            state: !lambda |-
              return update_next_runtime(id(irrigation_zone2_times).state, id(irrigation_zone2_days).state);

  - platform: template
    name: Zona 3 Horarios
    id: irrigation_zone3_times
    on_value:
      then:
        # Update the next scheduled run time.
        - text_sensor.template.publish:
            id: irrigation_zone3_next
            state: !lambda |-
              return update_next_runtime(id(irrigation_zone3_times).state, id(irrigation_zone3_days).state);

  - platform: template
    name: Zona 4 Horarios
    id: irrigation_zone4_times
    on_value:
      then:
        # Update the next scheduled run time.
        - text_sensor.template.publish:
            id: irrigation_zone4_next
            state: !lambda |-
              return update_next_runtime(id(irrigation_zone4_times).state, id(irrigation_zone4_days).state);
# ============================================================================= #
  # Retrieve list of days from the Home Assistant UI.
  - platform: homeassistant
    id: ui_zone1_days
    entity_id: input_text.irrigation_zone1_days
    on_value:
      #if:
      #  condition:
      #    api.connected:
      then:
        #- delay: 10sec
        - text_sensor.template.publish:
            id: irrigation_zone1_days
            state: !lambda return id(ui_zone1_days).state;

  - platform: homeassistant
    id: ui_zone2_days
    entity_id: input_text.irrigation_zone2_days
    on_value:
      #if:
      #  condition:
      #    api.connected:
      then:
        #- delay: 10sec
        - text_sensor.template.publish:
            id: irrigation_zone2_days
            state: !lambda return id(ui_zone2_days).state;

  - platform: homeassistant
    id: ui_zone3_days
    entity_id: input_text.irrigation_zone3_days
    on_value:
      #if:
      #  condition:
      #    api.connected:
      then:
        #- delay: 10sec
        - text_sensor.template.publish:
            id: irrigation_zone3_days
            state: !lambda return id(ui_zone3_days).state;

  - platform: homeassistant
    id: ui_zone4_days
    entity_id: input_text.irrigation_zone4_days
    on_value:
      #if:
      #  condition:
      #    api.connected:
      then:
        #- delay: 10sec
        - text_sensor.template.publish:
            id: irrigation_zone4_days
            state: !lambda return id(ui_zone4_days).state;
  # ============================================================================= #
  # Store time lists.
  - platform: template
    name: Zona 1 Días
    id: irrigation_zone1_days
    on_value:
      then:
        # Update the next scheduled run time.
        - text_sensor.template.publish:
            id: irrigation_zone1_next
            state: !lambda |-
              return update_next_runtime(id(irrigation_zone1_times).state, id(irrigation_zone1_days).state);

  - platform: template
    name: Zona 2 Días
    id: irrigation_zone2_days
    on_value:
      then:
        # Update the next scheduled run time.
        - text_sensor.template.publish:
            id: irrigation_zone2_next
            state: !lambda |-
              return update_next_runtime(id(irrigation_zone2_times).state, id(irrigation_zone2_days).state);

  - platform: template
    name: Zona 3 Días
    id: irrigation_zone3_days
    on_value:
      then:
        # Update the next scheduled run time.
        - text_sensor.template.publish:
            id: irrigation_zone3_next
            state: !lambda |-
              return update_next_runtime(id(irrigation_zone3_times).state, id(irrigation_zone3_days).state);

  - platform: template
    name: Zona 4 Días
    id: irrigation_zone4_days
    on_value:
      then:
        # Update the next scheduled run time.
        - text_sensor.template.publish:
            id: irrigation_zone4_next
            state: !lambda |-
              return update_next_runtime(id(irrigation_zone4_times).state, id(irrigation_zone4_days).state);

  # ============================================================================= #
  # Set the next scheduled time.
  - platform: template
    name: Zona 1 siguiente riego
    id: irrigation_zone1_next
    icon: mdi:calendar-clock

  - platform: template
    name: Zona 2 siguiente riego
    id: irrigation_zone2_next
    icon: mdi:calendar-clock

  - platform: template
    name: Zona 3 siguiente riego
    id: irrigation_zone3_next
    icon: mdi:calendar-clock

  - platform: template
    name: Zona 4 siguiente riego
    id: irrigation_zone4_next
    icon: mdi:calendar-clock

# Update the countdown timers every 5 seconds.
interval:
  - interval: 5s
    then:
      - lambda: |-
          if (id(remaining_time1) > 0) {
            // Store the previous time.
            id(remaining_time1_previous) = id(remaining_time1);
            // When the relay is on.
            if (id(relay1).state) {
              // Decrement the timer.
              id(remaining_time1) -= 5;
              // Turn off the relay when the time reaches zero... or the remaining time fails a sanity check!
              //if (id(remaining_time1) <= 0 || id(irrigation_zone1_remaining).state > id(irrigation_zone1_duration).state){
              if (id(remaining_time1) <= 0) {
                id(relay1).turn_off();
                id(remaining_time1) = 0;
              }
            }
            // Update the remaining time display.
            if (id(remaining_time1_previous) != id(remaining_time1)) {
              id(irrigation_zone1_remaining).publish_state( (id(remaining_time1)/60) + 1 );
            }
          }

          if (id(remaining_time2) > 0) {
            id(remaining_time2_previous) = id(remaining_time2);
            if (id(relay2).state) {
              id(remaining_time2) -= 5;
              if (id(remaining_time2) <= 0) {
                id(relay2).turn_off();
                id(remaining_time2) = 0;
              }
            }
            if (id(remaining_time2_previous) != id(remaining_time2)) {
              id(irrigation_zone2_remaining).publish_state( (id(remaining_time2)/60) + 1 );
            }
          }

          if (id(remaining_time3) > 0) {
            id(remaining_time3_previous) = id(remaining_time3);
            if (id(relay3).state) {
              id(remaining_time3) -= 5;
              if (id(remaining_time3) <= 0) {
                id(relay3).turn_off();
                id(remaining_time3) = 0;
              }
            }
            if (id(remaining_time3_previous) != id(remaining_time3)) {
              id(irrigation_zone3_remaining).publish_state( (id(remaining_time3)/60) + 1 );
            }
          }

          if (id(remaining_time4) > 0) {
            id(remaining_time4_previous) = id(remaining_time4);
            if (id(relay4).state) {
              id(remaining_time4) -= 5;
              if (id(remaining_time4) <= 0) {
                id(relay4).turn_off();
                id(remaining_time4) = 0;
              }
            }
            if (id(remaining_time4_previous) != id(remaining_time4)) {
              id(irrigation_zone4_remaining).publish_state( (id(remaining_time4)/60) + 1 );
            }
          }
# Time based automations.
time:
  - platform: homeassistant
    id: homeassistant_time
    timezone: America/Buenos_Aires
    on_time:
      - seconds: 0
        minutes: /1
        then:
          - lambda: |-
              if (scheduled_runtime(id(irrigation_zone1_next).state.c_str())) {
                id(irrigation_zone1).turn_on();
              }
              if (scheduled_runtime(id(irrigation_zone2_next).state.c_str())) {
                id(irrigation_zone2).turn_on();
              }
              if (scheduled_runtime(id(irrigation_zone3_next).state.c_str())) {
                id(irrigation_zone3).turn_on();
              }
              if (scheduled_runtime(id(irrigation_zone4_next).state.c_str())) {
                id(irrigation_zone4).turn_on();
              }

this is irrigation.h

#include "esphome.h"
using namespace std;

// Declare functions before calling them.
bool scheduled_runtime(string);
string update_next_runtime(string, string);

bool scheduled_runtime(string time) {
  // Retrieve the current time.
  auto time_now = id(homeassistant_time).now();
  int time_hour = time_now.hour;
  int time_minute = time_now.minute;
  int time_wday = time_now.day_of_week; //added for day scheduling

  //Prevent program crash - functions were created specting a time formated string
  // if you pass "now", program crashes in certain cases.
  if (time == "ahora") { //"now" in English, customize according to your prefs.
    return false;
  }

  // Split the hour and minutes.
  int next_hour = atoi(time.substr(0,2).c_str());
  int next_minute = atoi(time.substr(3,2).c_str());  int next_wday = 0;
  string day = time.substr(6,3).c_str(); //day text is added to original string

// Converting days to week numbers, change text based on your Language
    if (day == "Lun" || day == "lun" || day == "LUN") {
      next_wday = 2;
    } else if (day == "Mar" || day == "mar" || day == "MAR") {
      next_wday = 3;
    } else if (day == "Mie" || day == "mie" || day == "MIE") {
      next_wday = 4;
    } else if (day == "Jue" || day == "jue" || day == "JUE") {
      next_wday = 5;
    } else if (day == "Vie" || day == "vie" || day == "VIE") {
      next_wday = 6;
    } else if (day == "Sab" || day == "sab" || day == "SAB") {
      next_wday = 7;
    } else if (day == "Dom" || day == "dom" || day == "DOM") {
      next_wday = 1;
    } else if (day == "Hoy") { //Today in English
      next_wday = time_wday;
    }

  //ESP_LOGD("scheduled_runtime()", "now: %i:%i, wday: %i", next_hour, next_minute, time_wday);
  // return (time_hour == next_hour && time_minute == next_minute);
  return (time_hour == next_hour && time_minute == next_minute && time_wday == next_wday); //added wday to condition
}

string update_next_runtime(string time_list, string days_list) {
  // Initialize variables.
  vector<string> times;
  vector<string> next_time;
  vector<string> days;      //added for day scheduling
  vector<string> next_day; //added for day scheduling
  char * token;
  char * token2;   //to work on day string
  //bool single_time = false;
  //bool single_day = false;
  string updated_next_time;
  string updated_next_day;

  // Split the list of run times into an array.
  token = strtok(&time_list[0], ",");
  while (token != NULL) {
    times.push_back(token);
    token = strtok(NULL, ",");
  }
  // Split the list of run days into an array.
  token2 = strtok(&days_list[0], ",");
  while (token2 != NULL) {
    days.push_back(token2);
    token2 = strtok(NULL, ",");
  }

  // Need to delete this in order to day-time integration works
  // Stop now if the list does not contain more than one time.
  //if (times.size() <= 1) {
    //return time_list;
    //updated_next_time = time_list;
    //single_time = true;
  //}
  // Stop now if the list does not contain more than one day.
  //if (days.size() <= 1) {
    //updated_next_day = days_list;
    //single_day = true;
  //}

  // Retrieve the current time.
  auto time_now = id(homeassistant_time).now();
  int time_hour = time_now.hour;
  int time_minute = time_now.minute;
  int time_wday = time_now.day_of_week;

  // Initialize variables.
  int next_hour = 0;
  int next_minute = 0;
  int index = 0;
  int loop_count = 0;
  int time_count = times.size()-1;

  // Compare the list of times with the current time, and return the next in the list.
  //ESP_LOGD("update_next_runtime", "now: %i:%i", hour, minute);
  //if (!single_time) {
  for (string time : times) {
    // Retrieve the next scheduled time from the list.
    next_hour = atoi(time.substr(0,2).c_str());
    next_minute = atoi(time.substr(3,2).c_str());

    //ESP_LOGD("update_next_runtime", "next_hour: %s", time.c_str());
    if (time_hour < next_hour || (time_hour == next_hour && time_minute < next_minute)) {
      // Return this time if the next hour is greater than the current hour.
      //return times[loop_count].c_str();
      //break;
      updated_next_time = times[loop_count].c_str();
      break;
    // When we reach the end of our schedule for the day, return the first time of tomorrow.
    } else if (time_count == loop_count) {
      //return times[0].c_str();
      //break;
      updated_next_time = times[0].c_str();
      break;
    }

    // Increment the loop counter and array index.
    loop_count += 1;
    index += 2;
  }
  //}

  int loop2_count = 0;
  int day_count = days.size()-1;
  int next_wday = 0;
  int index2 = 0;

  //if (!single_day) {
  for (string day : days) {
    // Retrieve the next scheduled day from the list. Set your preferred language. Check correct correlations with day numbers
    if (day == "Lun" || day == "lun" || day == "LUN") {
      next_wday = 2;
    } else if (day == "Mar" || day == "mar" || day == "MAR") {
      next_wday = 3;
    } else if (day == "Mie" || day == "mie" || day == "MIE") {
      next_wday = 4;
    } else if (day == "Jue" || day == "jue" || day == "JUE") {
      next_wday = 5;
    } else if (day == "Vie" || day == "vie" || day == "VIE") {
      next_wday = 6;
    } else if (day == "Sab" || day == "sab" || day == "SAB") {
      next_wday = 7;
    } else if (day == "Dom" || day == "dom" || day == "DOM") {
      next_wday = 1;
    }

    //ESP_LOGD("update_next_runtime", "next_hour: %s", time.c_str());
    if (time_wday == next_wday && (time_hour < next_hour || (time_hour == next_hour && time_minute < next_minute))) {
      // Return this day if the next day is today AND there is still a scheduled time for today.
      //updated_next_day = days[loop2_count].c_str();
      updated_next_day = "Hoy"; //Today
      break;
      // If the next day is not today, also the next time is the first of day
    } else if (time_wday < next_wday) {
      updated_next_day = days[loop2_count].c_str();      updated_next_time = times[0].c_str();
      break;
      // When we reach the end of our schedule for the week, return the first day of next week.
    } else if (day_count == loop2_count) {
      updated_next_day = days[0].c_str();
      break;
    }

    // Increment the loop counter and array index.
    loop2_count += 1;
    index2 += 2;
  }
  //}

  return updated_next_time + " " + updated_next_day;
3 Likes

Very impressive improvement!

1 Like

Thanks! Your post was my inspiration :wink: and thanks to @raberrio and @VikingBlod for Share their customs or changes.

Greeting from :argentina:

2 Likes

Hi folks,
awesome work!!!

@joaquin68 I think there is a missing } in the irrigation.h code you posted and the latest function is not working. you should add a } at the end

from
return updated_next_time + " " + updated_next_day;

to

return updated_next_time + " " + updated_next_day;
}
1 Like

HI @joaquin68 ,

I think you should add the initial state as suggested by @VikingBlod to avoid reboot loop

input_text:
irrigation_zone1_times:
name: Zone 1 Time Periods
initial: 01:00
irrigation_zone2_times:
name: Zone 2 Time Periods
initial: 01:00
irrigation_zone1_days:
name: Zone 1 Days of the Week
initial: Mon,Wed,Fri
irrigation_zone2_days:
name: Zone 2 Days of the Week
initial: Mon,Wed,Fri

EDIT: got it. I need to define once and the remove it to avoid issues with reboot

2 Likes

Hi @VikingBlod ,

how did you change the card?

I can’t make the slider working
image

and I would like to understand if you are able to insert the day and the time in the UI

input_text:
  irrigation_zone1_times:
    name: Zone 1 Time Periods
    icon: mdi:chart-timeline
  irrigation_zone1_days:
    name: Zone 1 Days of the Week
    icon: mdi:calendar-week
input_number:
  irrigation_zone1_duration:
    name: Zone 1 Duration
    icon: mdi:timer
    min: 2
    max: 60
    step: 2    

This is how I define the values. And the following is how I configured my Lovelace Yaml.

- type: vertical-stack
  cards:
    - type: entities
      entities:
        - entity: input_text.irrigation_zone1_days
        - entity: input_text.irrigation_zone1_times
        - entity: input_number.irrigation_zone1_duration
        - entity: sensor.zone_1_next_watering
      title: Backyard Zone 1 - Schedule
    - type: entities
      entities:
        - entity: switch.irrigation_zone_1
          secondary_info: last-changed
          name: Zone 1 Watering
      title: Backyard Zone 1 - Watering
    - type: history-graph
      entities:
        - entity: switch.irrigation_zone_1
      hours_to_show: 24
      refresh_interval: 0

The above gives me the following;

1 Like

Thank you!!!
I figured out that Entities card has the expected behavior VS the Entity card

i will play more now with your sample. thank you for sharing

1 Like

Hi,

I managed to configure it. Thank you all for this amazing job
I replaced the sonoff with an ESP32 and I control 6 zones with a relay board

I have 2 additional questions:

  1. if I want to run a zone more than one time the same day, should I configure the time with the comma separator? (eg. what I did in zone 2)

  2. In the ESP32 log I see this INFO Detected timezone 'CET' with UTC offset 1 and daylight savings time from 27 March 02:00:00 to 30 October 03:00:00, but on the scheduled time the zone doesn’t start. any idea on what I’m missing?

if i manually trig the switch (see Zone 1), sounds good

but the next irrigation is ignored
as you can see Zone 2 is not triggered, and Zone 3 schedule is wrong, it should be Thu not Mon

I tested both the timezone format, same issue

    timezone: Europe/Rome
    timezone: CET-1CEST-2,M3.5.0/02:00:00,M10.5.0/03:00:00

regards

EDIT. I think I found the first issue, Day and Time need a comma as separator without spaces

eg

Mon,Tue,Wed,Thu,Fri,Sat,Sun 
22:01,22:10

but the next irrigation still doesn’t toggle the switch :frowning:

1 Like

HI,

I understood the issue
when you replace the days name in the integration.h file to match your language, you have to put attention to the text specially to match the latest case (Today)

else if (day == "Tod") { //Today in English

because the substitution is taking only 3 char, you have to use only the first 3 char in your language

string day = time.substr(6,3).c_str(); //day text is added to original string

for English “Today” is “Tod”

// Converting days to week numbers, change text based on your Language
    if (day == "Mon" || day == "mon" || day == "MON") {
      next_wday = 2;
    } else if (day == "Tue" || day == "tue" || day == "TUE") {
      next_wday = 3;
    } else if (day == "Wed" || day == "wed" || day == "WED") {
      next_wday = 4;
    } else if (day == "Thu" || day == "thu" || day == "THU") {
      next_wday = 5;
    } else if (day == "Fri" || day == "fri" || day == "FRI") {
      next_wday = 6;
    } else if (day == "Sat" || day == "sat" || day == "SAT") {
      next_wday = 7;
    } else if (day == "Sun" || day == "sun" || day == "SUN") {
      next_wday = 1;
    } else if (day == "Tod") { //Today in English
      next_wday = time_wday;
    }
1 Like