DIY 8 station irrigation based on ESPhome on KC868-A8 relay board

Hi,
Great work. I am looking for a solution where the controller can connect to Home Assistant via ie. ethernet. I see this board has ethernet connector. Could this be connected to Home Assistant with ethernet or the eth. connector is for other purposes?

I believe so yes. ESPhome supports a good number of Ethernet chips and the one on this board is the first one they mention in the ESPhome list.
I have used Ethernet with ESPhome fine on other boards, just not this one yet.

hello, I’m trying to build an automatic watering system based on slightly modified your code, unfortunately I keep getting the error “class esphome: :sprinkler : :Sprinkler:” has no member named “time_remaining”. Do you know what could be causing this?

My YAML

# Establish Substitutions
substitutions:
  device_name: sprinkler
  friendly_name: "Sprinkler"
  device_platform: ESP32
  device_board: esp32dev
  sensor_update_frequency: 1s
  sprinkler_name: esp32_sprinkler_ctrlr

esphome:
  name: sprinkler
  friendly_name: Sprinkler

esp32:
  board: esp32dev
  framework:
    type: arduino


# Enable logging
logger:
###################################################################################################
#####  I/O expander hub definition
###################################################################################################
mcp23017:
  - id: 'mcp23017_hub'
    address: 0x26
#nodemcu devkit v4.0
i2c:
  sda: 33
  scl: 32
  scan: True
  frequency: 400kHz

ethernet:
  type: LAN8720
  mdc_pin: GPIO23
  mdio_pin: GPIO18
  clk_mode: GPIO17_OUT
  phy_addr: 0
  power_pin: GPIO4
###################################################################################################
# 10x4 LCD config
display:
  - platform: lcd_pcf8574
    dimensions: 20x4
    address: 0x27
    update_interval: 1s
    lambda: |-
        switch (id(esp32_sprinkler_ctrlr).active_valve().value_or(-1)) {

          case -1:
          it.strftime(0, 0, "%a", id(homeassistant_time).now());
          it.strftime(10, 0, "%d-%m-%Y", id(homeassistant_time).now());
          it.strftime(12, 1,"%H:%M:%S", id(homeassistant_time).now());
          it.printf(0, 3, "Status: %s", id(esp32_sprinkler_ctrlr_status).state.c_str());
          
          if (id(esp32_sprinkler_ctrlr_status).state == "Paused") {
          it.printf(0, 2, "Zone %s", id(zone_active_sensor).state.c_str());
          it.printf(10, 2, "T: %s", id(zone_time_remaining_sensor).state.c_str());
          }

          break;
          
          default:
          it.printf(0, 0, "Zone %s:", id(zone_active_sensor).state.c_str());
          it.printf(8, 0, "%s", id(zone_active_sensor).state.c_str() ? "ON" : "OFF");
          it.printf(0, 1, "Time Letf: %s", id(zone_time_remaining_sensor).state.c_str());
          break;
        }

output:
  - platform: ledc
    pin: GPIO14
    id: sprinkler_backlight

light:
  - platform: monochromatic
    output: sprinkler_backlight
    name: "LCD Display Sprinkler Backlight"
    id: light_backlight
    restore_mode: ALWAYS_ON

###################################################################################################
###################################################################################################
# Enable Home Assistant APIs
api:
  reboot_timeout: 0s
  encryption:
    key: "FkNGBbOhmbWTgfvvZuVbM51UuFlb4EIb+dFi7whRA/g="
  services:
    - service: set_multiplier
      variables:
        multiplier: float
      then:
        - sprinkler.set_multiplier:
            id: esp32_sprinkler_ctrlr
            multiplier: !lambda 'return multiplier;'
    - service: start_full_cycle
      then:
        - sprinkler.start_full_cycle: esp32_sprinkler_ctrlr
    - service: start_single_valve
      variables:
        valve: int
      then:
        - sprinkler.start_single_valve:
            id: esp32_sprinkler_ctrlr
            valve_number: !lambda 'return valve;'
    - service: next_valve
      then:
        - sprinkler.next_valve: esp32_sprinkler_ctrlr
    - service: previous_valve
      then:
        - sprinkler.previous_valve: esp32_sprinkler_ctrlr
    - service: shutdown
      then:
        - sprinkler.shutdown: esp32_sprinkler_ctrlr
    - service: pause
      then:
        - sprinkler.pause: esp32_sprinkler_ctrlr
    - service: resume
      then:
        - sprinkler.resume: esp32_sprinkler_ctrlr
    - service: resume_or_full_cycle
      then:
        - sprinkler.resume_or_start_full_cycle: esp32_sprinkler_ctrlr
    - service: repeat_2
      then:
        - sprinkler.set_repeat:
            id: esp32_sprinkler_ctrlr
            repeat: 2  # would run three cycles
    - service: repeat_3
      then:
        - sprinkler.set_repeat:
            id: esp32_sprinkler_ctrlr
            repeat: 3  # would run three cycles
      
ota:
  password: "7001db87bfa399a3e551f7206250c3dd"

# Main sprinkler code
sprinkler:
  - id: esp32_sprinkler_ctrlr
    main_switch: "Master Run/Stop"
    auto_advance_switch: "Zones Auto Advance"
    reverse_switch: "Zones Reverse"
    valve_open_delay: 2s
    valves:
      - valve_switch: "Zone 1"
        enable_switch: "Zone 1 Enable"
        run_duration: 900s
        valve_switch_id: zone_valve_sw1
      - valve_switch: "Zone 2"
        enable_switch: "Zone 2 Enable"
        run_duration: 900s
        valve_switch_id: zone_valve_sw2
      - valve_switch: "Zone 3"
        enable_switch: "Zone 3 Enable"
        run_duration: 900s
        valve_switch_id: zone_valve_sw3
      - valve_switch: "Zone 4"
        enable_switch: "Zone 4 Enable"
        run_duration: 900s
        valve_switch_id: zone_valve_sw4
      - valve_switch: "Zone 5"
        enable_switch: "Zone 5 Enable"
        run_duration: 900s
        valve_switch_id: zone_valve_sw5
      - valve_switch: "Zone 6"
        enable_switch: "Zone 6 Enable"
        run_duration: 900s
        valve_switch_id: zone_valve_sw6

# Valve control outputs config via I/O expander       
switch:
#################################################################################################
####### CONTROL SWITCH
#################################################################################################
  - platform: template
    id: esp32_sprinkler_ctrlr_run
    name: "Sprinkler Controller Run"
    optimistic: true
    on_turn_on:
      - text_sensor.template.publish:
          id: esp32_sprinkler_ctrlr_status
          state: "Running"
      - sprinkler.resume_or_start_full_cycle: esp32_sprinkler_ctrlr
      - switch.turn_off: esp32_sprinkler_ctrlr_pause
      - switch.turn_off: esp32_sprinkler_ctrlr_stop
  - platform: template
    id: esp32_sprinkler_ctrlr_stop
    name: "Sprinkler Controller Stop"
    optimistic: true
    on_turn_on:
      - text_sensor.template.publish:
          id: esp32_sprinkler_ctrlr_status
          state: "Stopped"
      - sprinkler.shutdown: esp32_sprinkler_ctrlr
      - switch.turn_off: esp32_sprinkler_ctrlr_pause
      - switch.turn_off: esp32_sprinkler_ctrlr_run
  - platform: template
    id: esp32_sprinkler_ctrlr_pause
    name: "Sprinkler Controller Pause"
    optimistic: true
    
    turn_on_action:
      - delay: 500ms
      - lambda: |-
          if(id(esp32_sprinkler_ctrlr_status).state != "Running")
          {
            id(esp32_sprinkler_ctrlr_pause).turn_off();
          }
      - lambda: |-
          if(id(esp32_sprinkler_ctrlr_status).state == "Running")
          {
            id(esp32_sprinkler_ctrlr_status).publish_state("Paused");
            id(esp32_sprinkler_ctrlr).pause();
          }
    on_turn_off:
      lambda: |-
        if(id(esp32_sprinkler_ctrlr_status).state == "Paused")
        {
          id(esp32_sprinkler_ctrlr_status).publish_state("Running");
          id(esp32_sprinkler_ctrlr).resume();
        } 
  - platform: template
    id: esp32_sprinkler_ctrlr_resume
    name: "Sprinkler Controller Resume"
    optimistic: true
     
#################################################################################################
####### I/0  SWITCH
#################################################################################################
  - platform: gpio
    id: zone_valve_sw1
    name: "MCP23017 Pin B2"
    pin:
      mcp23xxx: mcp23017_hub
      # Use pin B2
      number: 10
      # One of INPUT or OUTPUT
      mode:
        output: true
      inverted: true
    internal: true
  - platform: gpio
    id: zone_valve_sw2
    name: "MCP23017 Pin B3"
    pin:
      mcp23xxx: mcp23017_hub
      # Use pin B3
      number: 11
      # One of INPUT or OUTPUT
      mode:
        output: true
      inverted: true
    internal: true
  - platform: gpio
    id: zone_valve_sw3
    name: "MCP23017 Pin B4"
    pin:
      mcp23xxx: mcp23017_hub
      # Use pin B4
      number: 12
      # One of INPUT or OUTPUT
      mode:
        output: true
      inverted: true
    internal: true
  - platform: gpio
    id: zone_valve_sw4
    name: "MCP23017 Pin B5"
    pin:
      mcp23xxx: mcp23017_hub
      # Use pin B5
      number: 13
      # One of INPUT or OUTPUT
      mode:
        output: true
      inverted: true
    internal: true
  - platform: gpio
    id: zone_valve_sw5
    name: "MCP23017 Pin B6"
    pin:
      mcp23xxx: mcp23017_hub
      # Use pin B6
      number: 14
      # One of INPUT or OUTPUT
      mode:
        output: true
      inverted: true
    internal: true
  - platform: gpio
    id: zone_valve_sw6
    name: "MCP23017 Pin B7"
    pin:
      mcp23xxx: mcp23017_hub
      # Use pin B7
      number: 15
      # One of INPUT or OUTPUT
      mode:
        output: true
      inverted: true
    internal: true
  #- platform: template #this switch doesn't work properly. Can pause via HA frontend, but will not resume...  Via Services does work...
  #  id: pause_switch
  #  name: "Pause Irrigation Switch"
  #  turn_on_action:
  #    then:
  #      - sprinkler.pause: esp32_sprinkler_ctrlr
  #  turn_off_action:
  #    then:
  #      - sprinkler.resume: esp32_sprinkler_ctrlr    

number:
# Configuration to set multiplier via number
  - platform: template
    id: sprinkler_ctrlr_multiplier
    name: "Run Duration Multiplier"
    min_value: 1.0
    max_value: 3.0
    step: 0.1
    mode: box
    lambda: "return id(esp32_sprinkler_ctrlr).multiplier();"
    set_action:
      - sprinkler.set_multiplier:
          id: esp32_sprinkler_ctrlr
          multiplier: !lambda 'return x;'  
          
# Configure repeat
  - platform: template
    id: sprinkler_ctrlr_repeat_cycles
    name: "Sprinkler Repeat Cycles"
    min_value: 0
    max_value: 300
    step: 1
    mode: box
    lambda: "return id(esp32_sprinkler_ctrlr).repeat();"
    set_action:
      - sprinkler.set_repeat:
          id: esp32_sprinkler_ctrlr
          repeat: !lambda 'return x;'

# Configuration to set valve run duration via number
  - platform: template
    id: sprinkler_valve_1_duration
    name: "Zone 1 Duration"
    icon: mdi:timer
    unit_of_measurement: Min
    min_value: 1
    max_value: 120
    step: 1.0
    update_interval: $sensor_update_frequency
    mode: box
    lambda: "return id(esp32_sprinkler_ctrlr).valve_run_duration(0) / 60;"
    set_action:
      - sprinkler.set_valve_run_duration:
          id: esp32_sprinkler_ctrlr
          valve_number: 0
          run_duration: !lambda "return x * 60;"
          
  - platform: template
    id: sprinkler_valve_2_duration
    name: "Zone 2 Duration"
    icon: mdi:timer
    unit_of_measurement: Min
    min_value: 1
    max_value: 120
    step: 1.0
    update_interval: $sensor_update_frequency
    mode: box
    lambda: "return id(esp32_sprinkler_ctrlr).valve_run_duration(1) / 60;"
    set_action:
      - sprinkler.set_valve_run_duration:
          id: esp32_sprinkler_ctrlr
          valve_number: 1
          run_duration: !lambda "return x * 60;" 

  - platform: template
    id: sprinkler_valve_3_duration
    name: "Zone 3 Duration"
    icon: mdi:timer
    unit_of_measurement: Min
    min_value: 1
    max_value: 120
    step: 1.0
    update_interval: $sensor_update_frequency
    mode: box
    lambda: "return id(esp32_sprinkler_ctrlr).valve_run_duration(2) / 60;"
    set_action:
      - sprinkler.set_valve_run_duration:
          id: esp32_sprinkler_ctrlr
          valve_number: 2
          run_duration: !lambda "return x * 60;" 
          
  - platform: template
    id: sprinkler_valve_4_duration
    name: "Zone 4 Duration"
    icon: mdi:timer
    unit_of_measurement: Min
    min_value: 1
    max_value: 120
    step: 1.0
    update_interval: $sensor_update_frequency
    mode: box
    lambda: "return id(esp32_sprinkler_ctrlr).valve_run_duration(3) / 60;"
    set_action:
      - sprinkler.set_valve_run_duration:
          id: esp32_sprinkler_ctrlr
          valve_number: 3
          run_duration: !lambda "return x * 60;"  
          
  - platform: template
    id: sprinkler_valve_5_duration
    name: "Zone 5 Duration"
    icon: mdi:timer
    unit_of_measurement: Min
    min_value: 1
    max_value: 120
    step: 1.0
    update_interval: $sensor_update_frequency
    mode: box
    lambda: "return id(esp32_sprinkler_ctrlr).valve_run_duration(4) / 60;"
    set_action:
      - sprinkler.set_valve_run_duration:
          id: esp32_sprinkler_ctrlr
          valve_number: 4
          run_duration: !lambda "return x * 60;"
          
  - platform: template
    id: sprinkler_valve_6_duration
    name: "Zone 6 Duration"
    icon: mdi:timer
    unit_of_measurement: Min
    min_value: 1
    max_value: 120
    step: 1.0
    update_interval: $sensor_update_frequency
    mode: box
    lambda: "return id(esp32_sprinkler_ctrlr).valve_run_duration(5) / 60;"
    set_action:
      - sprinkler.set_valve_run_duration:
          id: esp32_sprinkler_ctrlr
          valve_number: 5
          run_duration: !lambda "return x * 60;" 
          
sensor:
### SENSORS
  - platform: template
    name: "Cycle Total Time Sensor"
    icon: mdi:progress-clock    
    unit_of_measurement: 'Min'
    accuracy_decimals: 0
    update_interval: $sensor_update_frequency  
    lambda: "return id(esp32_sprinkler_ctrlr).valve_run_duration(0)/60 + id(esp32_sprinkler_ctrlr).valve_run_duration(1)/60;"
    #lambda: |-
    #  {% set ns = namespace(states=[]) %}
    #  {% for s in states.sensor %}
    #    {% if s.object_id.startswith('valve_run_') and s.object_id.endswith('_duration') %}
    #      {% set ns.states = ns.states + [ s.state | float ] %}
    #    {% endif %}
    #  {% endfor %}
    #  {{ ns.states | sum | round(2) }}
#  - platform: template
#    name: "Zone Active Sensor"
#    id: zone_active_sensor
#    unit_of_measurement: ''
#    accuracy_decimals: 0
#    #Valves are numbered from 0-7 internally which is an issue when displaying !
#    lambda: |-
#      if(id(esp32_sprinkler_ctrlr_status).state == "Stopped")
#      {
#        return id(esp32_sprinkler_ctrlr).active_valve().value_or(NAN);
#      }
#      else
#      {
#        return id(esp32_sprinkler_ctrlr).active_valve().value_or(NAN) + 1;
#      }
#    update_interval: $sensor_update_frequency
  - platform: homeassistant
    id: homeassistant_sprinklercountdown
    entity_id: timer.sprinklercountdown
    internal: false

text_sensor:
    - platform: template
      id: esp32_sprinkler_ctrlr_status
      name: "Sprinklers Status"
      update_interval: $sensor_update_frequency
    - platform: template
      name: "Zone Active Sensor"
      id: zone_active_sensor
      #unit_of_measurement: ''
      #accuracy_decimals: 0
      #Valves are numbered from 0-7 internally which is an issue when displaying !
      lambda: |-
        if(id(esp32_sprinkler_ctrlr_status).state == "Stopped")
        {
          int zone_active = id(esp32_sprinkler_ctrlr).active_valve().value_or(NAN);
          std::string zone_active_as_string = esphome::to_string(zone_active);
          return zone_active_as_string;
        }
        else
        {
          int zone_active = id(esp32_sprinkler_ctrlr).active_valve().value_or(NAN) + 1;
          std::string zone_active_as_string = esphome::to_string(zone_active);
          return zone_active_as_string;
        }
      update_interval: $sensor_update_frequency

#  # Expose Valve Progress Percent as a sensor.
    - platform: template
      id: progress_percent_valve
      name: "Zone Progress"
      #unit_of_measurement: '%'
      #accuracy_decimals: 0
      update_interval: $sensor_update_frequency
      icon: "mdi:progress-clock"
      lambda: |-
        int valve_progress_percent = round(((id(esp32_sprinkler_ctrlr).valve_run_duration_adjusted(id(esp32_sprinkler_ctrlr).active_valve().value_or(0)) - id(esp32_sprinkler_ctrlr).time_remaining().value_or(0)) * 100 / id(esp32_sprinkler_ctrlr).valve_run_duration_adjusted(id(esp32_sprinkler_ctrlr).active_valve().value_or(0))));
        std::string valve_progress_percent_as_string = esphome::to_string(valve_progress_percent);
        return valve_progress_percent_as_string;

# # Expose Zone Time Remaining
    - platform: template
      name: "Zone Time Remaining Sensor"
      id: zone_time_remaining_sensor
      icon: mdi:progress-clock    
      #unit_of_measurement: 'Min'
      #accuracy_decimals: 0
      update_interval: $sensor_update_frequency
      lambda: |-
        if(id(esp32_sprinkler_ctrlr_status).state != "Paused")
        {
          int seconds = round(id(esp32_sprinkler_ctrlr).time_remaining().value_or(0));
          int days = seconds / (24 * 3600);
          seconds = seconds % (24 * 3600);
          int hours = seconds / 3600;
          seconds = seconds % 3600;
          int minutes = seconds /  60;
          seconds = seconds % 60;
          return {
            ((days ? String(days) + "d " : "") +
            (hours ? String(hours) + "h " : "") +
            (minutes ? String(minutes) + "m " : "") +
            (String(seconds) + "s")
            ).c_str()};
        }
        else
        {
          return {};
        }

### Input to managet the display backlight
    - platform: homeassistant    
      id: display_sprinkler_backlight
      entity_id: input_number.sprinklerbacklightlevel
      internal: true
      on_value:
        then:
          - output.turn_on: sprinkler_backlight
          - output.set_level:
              id: sprinkler_backlight
              level: !lambda |-
                return atoi(id(display_sprinkler_backlight).state.c_str()) / 100.0;

# Time source config
time:
  - platform: homeassistant
    id: homeassistant_time
    timezone: Europe/Rome
    ```