How Do I Format Lambda for text_sensor - ESPHome Sprinkler

I’ve flashed a D1 Mini with code that uses the new ESPHome Sprinkler Component to operate four sprinkler valves. My code exposes sprinkler controls, duration and multiplier settings to HA. I’ve also added a template that exposes the number of seconds remaining (time_remaining()) as a sensor in HA. Let’s call this a countdown timer. I’d like to see the sensor display minutes and seconds but I’m unable to determine how to code the template to show such.

As it stands, I’m able to show the number of remaining minutes or the number or remaining seconds. Not both! The section of code that adds the time_remaining sensor, in minutes, follows;

sensor:
  - platform: template
    name: ${valve_0_name} Duration
    id: ${valve_0_id}_duration
    unit_of_measurement: Min
    icon: mdi:timer
    accuracy_decimals: 0
    lambda: |-
       return id($devicename).time_remaining().value_or(0)/60;
    update_interval: 60s

If I want to see seconds I would use;


  - platform: template
    name: ${valve_0_name} Duration
    id: ${valve_0_id}_duration
    unit_of_measurement: Sec
    icon: mdi:timer
    accuracy_decimals: 0
    lambda: |-
       return id($devicename).time_remaining().value_or(0);
    update_interval: 1s

Any hints on how I can achieve what I’m looking for? The ability to display minutes and seconds.

On a side note: I noticed the D1 Mini outputs a “time remaining” message every second, regardless of whether a valve is in operation or not. I don’t think this is a good use of processing power or band-width and would like to see the messages passed to HA only when a valve is in operation, If you can shed light on how to deal with that as well, I’d really appreciate it.

Here is my full code - in case it’s needed.

# Based on ESPHome Sprinkler Controller - https://esphome.io/components/sprinkler.html
# Establish Substitutions
substitutions:
### Modify only the following eight lines.
    unit_id: A
    devicename_unit_id: a
    valve_0_name: Front Lawn
    valve_1_name: Front Garden
    valve_2_name: South Garden
    valve_3_name: Rear Garden
    software_version: 2022 09 14 v10
### DO NOT CHANGE ANYTHING BELOW THIS LINE ###
    valve_0_id: valve_0
    valve_1_id: valve_1
    valve_2_id: valve_2
    valve_3_id: valve_3
    esphome_name: irrigation-valve-ctrl-unit-${devicename_unit_id}
    esphome_platform: ESP8266
    esphome_board: d1_mini
    esphome_comment: Four Valve Irrigation Control (${unit_id})
    esphome_project_name: Robert.Four Valve Irrigation Control (${unit_id})
    esphome_project_version: Four Valve Irrigation Ctrl (${unit_id}), ${software_version}
    devicename: irrigation_valve_controller_unit_${devicename_unit_id}
    upper_devicename: Four Valve Irrigation Ctrl (${unit_id})
    irrigation_time_miltiplier_name: time_multiplier_${devicename_unit_id}
    upper_irrigation_time_multiplier_name: Time Multiplier (${unit_id})

#Define Project Deatils and ESP Board Type
esphome:
    name: $esphome_name
    platform: $esphome_platform
    board: $esphome_board
    comment: $esphome_comment
    project:
        name: $esphome_project_name
        version: $esphome_project_version
  

# WiFi connection, replace these with values for your WiFi.
wifi:
    ssid: !secret wifi_ssid
    password: !secret wifi_password

# Enable logging
logger:
  
# Enable over-the-air updates.
ota:
    password: !secret ota_password

# Enable Web server.
web_server:
    port: 80

# Sync time with Home Assistant.
time:
  - platform: homeassistant
    id: homeassistant_time

# Text sensors with general information.
text_sensor:
  # Expose ESPHome version as sensor.
  - platform: version
    name: ESPHome Version
  # Expose WiFi information as sensors.
  - platform: wifi_info
    ip_address:
      name: $devicename IP
    ssid:
      name: $devicename SSID
    bssid:
      name: $devicename BSSID

# Enable On-Board Status LED.
status_led:
  pin:
    # Pin D4
    number: GPIO2
    inverted: true

sensor:
  # Uptime sensor.
  - platform: uptime
    name: ${upper_devicename} Uptime

  # WiFi Signal sensor.
  - platform: wifi_signal
    name: ${upper_devicename} WiFi Signal
    update_interval: 60s

  - platform: template
    name: ${valve_0_name} Duration
    id: ${valve_0_id}_duration
    unit_of_measurement: Min
    icon: mdi:timer
    accuracy_decimals: 0
    lambda: |-
       return id($devicename).valve_run_duration_adjusted(0)/60;
    update_interval: 60s

  - platform: template
    name: ${valve_1_name} Duration
    id: ${valve_1_id}_duration
    unit_of_measurement: Min
    icon: mdi:timer
    accuracy_decimals: 0
    lambda: |-
       return id($devicename).valve_run_duration_adjusted(1)/60;
    update_interval: 60s

  - platform: template
    name: Time Remaining
    id: time_remaining
    unit_of_measurement: Sec
    icon: mdi:progress-clock
    accuracy_decimals: 2
    lambda: |-
      return id($devicename).time_remaining().value_or(0)/60;
    update_interval: 60s

# Enable Home Assistant API
api:
  encryption:
    key: !secret api_encryption
  password: !secret api_password

# Set multiplier via number - Sprinklers
number:
  - platform: template
    id: $irrigation_time_miltiplier_name
    name: $upper_irrigation_time_multiplier_name
    min_value: 0.1
    max_value: 10.0
    step: 0.1
    mode: box # Defines how the number should be displayed in the UI
    lambda: "return id($devicename).multiplier();"
    set_action:
      - sprinkler.set_multiplier:
          id: $devicename
          multiplier: !lambda 'return x;'
  - platform: template
    id: ${valve_0_id}
    name: ${valve_0_name}
    min_value: 60
    max_value: 720
    step: 1
    mode: box # Defines how the number should be displayed in the UI
    lambda: "return id($devicename).valve_run_duration(0);"
    set_action:
      - sprinkler.set_valve_run_duration:
          id: $devicename
          valve_number: 0
          run_duration: !lambda 'return x;'
  - platform: template
    id: ${valve_1_id}
    name: ${valve_1_name}
    min_value: 60
    max_value: 720
    step: 1
    mode: box # Defines how the number should be displayed in the UI
    lambda: "return id($devicename).valve_run_duration(1);"
    set_action:
      - sprinkler.set_valve_run_duration:
          id: $devicename
          valve_number: 1
          run_duration: !lambda 'return x;'
  - platform: template
    id: ${valve_2_id}
    name: ${valve_2_name}
    min_value: 60
    max_value: 720
    step: 1
    mode: box # Defines how the number should be displayed in the UI
    lambda: "return id($devicename).valve_run_duration(2);"
    set_action:
      - sprinkler.set_valve_run_duration:
          id: $devicename
          valve_number: 2
          run_duration: !lambda 'return x;'
  - platform: template
    id: ${valve_3_id}
    name: ${valve_3_name}
    min_value: 60
    max_value: 720
    step: 1
    mode: box # Defines how the number should be displayed in the UI
    lambda: "return id($devicename).valve_run_duration(3);"
    set_action:
      - sprinkler.set_valve_run_duration:
          id: $devicename
          valve_number: 3
          run_duration: !lambda 'return x;'

sprinkler:
  - id: $devicename
    main_switch: Start/Pause/Resume (${unit_id})
    auto_advance_switch: Auto Advance (${unit_id})
    valve_open_delay: 2s
    valves:
      - valve_switch: ${valve_0_name}
        enable_switch: Enable ${valve_0_name}
        run_duration: 60s
        valve_switch_id: ${devicename}_0
      - valve_switch: ${valve_1_name}
        enable_switch: Enable ${valve_1_name}
        run_duration: 60s
        valve_switch_id: ${devicename}_1
      - valve_switch: ${valve_2_name}
        enable_switch: Enable ${valve_2_name}
        run_duration: 60s
        valve_switch_id: ${devicename}_2
      - valve_switch: ${valve_3_name}
        enable_switch: Enable ${valve_3_name}
        run_duration: 60s
        valve_switch_id: ${devicename}_3

button:
  - platform: restart
    id: reset${devicename_unit_id}
    name: Reset $devicename
  - platform: template
    id: sprinkler_shutdown${devicename_unit_id}
    name: Stop (${unit_id})
    on_press:
      then:
        - sprinkler.shutdown: $devicename

switch:
# Hidden switches.
# Switches that control sprinkler valve relays
  - platform: gpio
    name: Relay Board Pin IN1
    restore_mode: RESTORE_DEFAULT_OFF # Prevents GPIO pin from going high during boot
    internal: true # Prevents GPIO switch NAME from showing up in Home Assistant
    id: ${devicename}_0
    pin:
        # GPIO Pin 12
        number: D6
        inverted: true
  - platform: gpio
    name: Relay Board Pin IN2
    restore_mode: RESTORE_DEFAULT_OFF # Prevents GPIO pin from going high during boot
    internal: true # Prevents GPIO switch NAME from showing up in Home Assistant
    id: ${devicename}_1
    pin:
        # GPIO Pin 13
        number: D7
        inverted: true
  - platform: gpio
    name: Relay Board Pin IN3
    restore_mode: RESTORE_DEFAULT_OFF # Prevents GPIO pin from going high during boot
    internal: true # Prevents GPIO switch NAME from showing up in Home Assistant
    id: ${devicename}_2
    pin:
        # GPIO Pin 14
        number: D5
        inverted: true
  - platform: gpio
    name: Relay Board Pin IN4
    restore_mode: RESTORE_DEFAULT_OFF # Prevents GPIO pin from going high during boot
    internal: true # Prevents GPIO switch NAME from showing up in Home Assistant
    id: ${devicename}_3
    pin:
        # GPIO Pin 4
        number: D2
        inverted: true

Regards

You might want to expose the time remaining as text_sensor instead of a sensor. and format the string at your convenience.

I’m not familiar with text_sensors thus some research is in order. Thanks for the tip.

I’m running into issues trying to set up a text_sensor to display remaining seconds/minutes. I’ve tried several lambda configurations without luck. My latest code throws an error on compile;

Compiling /data/irrigation-valve-ctrl-unit-a/.pioenvs/irrigation-valve-ctrl-unit-a/src/main.cpp.o
/config/esphome/irrigation-valve-ctrl-unit-a.yaml: In lambda function:
/config/esphome/irrigation-valve-ctrl-unit-a.yaml:80:75: error: return-statement with a value, in function returning 'void' [-fpermissive]
   80 |           return id($devicename).time_remaining().value_or(0);
      |              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~            ^  
*** [/data/irrigation-valve-ctrl-unit-a/.pioenvs/irrigation-valve-ctrl-unit-a/src/main.cpp.o] Error 1
========================== [FAILED] Took 7.75 seconds ==========================

Despite searching for a solution on Google, I’m unable to figure out what’s wrong.

The block of code where the trouble occurs reads;

  - platform: template
    name: Time Remaining
    icon: mdi:progress-clock
    on_value:
      then:
        lambda: |-
          return id($devicename).time_remaining().value_or(0);     

Any help in pointing me in the right direction would be appreciated.

I’m far to be a programmer, but this might work. please give it a try.

            lambda |-
              int seconds = round(id($devicename).time_remaining().value_or(0));
              seconds = seconds % (24 * 3600);
              int hours = seconds / 3600;
              seconds = seconds % 3600;
              int minutes = seconds /  60;
              seconds = seconds % 60;
              return (
                (hours ? String(hours) + "h " : "") +
                (minutes ? String(minutes) + "m " : "") +
                (String(seconds) + "s")
              ).c_str();

Thank you for jumping in to help. The above code produced two errors. The first, a colon missing after lambda, was corrected. The other seems related to format but I don’t know how to address it. The error is;

Compiling /data/irrigation-valve-ctrl-unit-a/.pioenvs/irrigation-valve-ctrl-unit-a/src/main.cpp.o
/config/esphome/irrigation-valve-ctrl-unit-a.yaml: In lambda function:
/config/esphome/irrigation-valve-ctrl-unit-a.yaml:92:14: error: could not convert 'operator+(String&&, String&&)(operator+(String&&, const T&) [with T = char [2]; <template-parameter-1-2> = void]("s")).String::c_str()' from 'const char*' to 'esphome::optional<std::__cxx11::basic_string<char> >'
   88 |       return (
      |              ~
   89 |         (hours ? String(hours) + "h " : "") +
      |         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   90 |         (minutes ? String(minutes) + "m " : "") +
      |         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   91 |         (String(seconds) + "s")
      |         ~~~~~~~~~~~~~~~~~~~~~~~
   92 |       ).c_str();
      |       ~~~~~~~^~
      |              |
      |              const char*
*** [/data/irrigation-valve-ctrl-unit-a/.pioenvs/irrigation-valve-ctrl-unit-a/src/main.cpp.o] Error 1
========================== [FAILED] Took 7.74 seconds ==========================

Thanks for assisting.

Yeah, I got the same error when I insert that code on the text_sensor. but if I update the sensor from somewhere else with id(zone_1_time_remaining).publish_state() it works perfectly.

I’m also working on a 8 zones Sprinkler Controller using this component and this might be interesting for you:

This is a section of my dashboard
Screenshot 2022-09-19 152500

I created a sensor to display the percentage of the valve run and used your idea to have the human readable time remaining next to it.

The percentage sensor also updates the text_sensor solving the issue with the compile error.

This is my sensor code:

  - platform: template
    id: zone1_progress_percentage
    name: "Zone 1 Progress Percentage"
    update_interval: 1s
    lambda: |-
      if(id(lawn_sprinkler_ctrlr).active_valve().value_or(-1) == 0)
      {
        int seconds = round(id(lawn_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;
        id(zone_1_time_remaining).publish_state((
          (days ? String(days) + "d " : "") +
          (hours ? String(hours) + "h " : "") +
         (minutes ? String(minutes) + "m " : "") +
         (String(seconds) + "s")
          ).c_str());
        return ((id(lawn_sprinkler_ctrlr).valve_run_duration_adjusted(0) - id(lawn_sprinkler_ctrlr).time_remaining().value_or(0)) * 100 / id(lawn_sprinkler_ctrlr).valve_run_duration_adjusted(0));
      }
      if (id(lawn_sprinkler_ctrlr).active_valve().has_value() == false && id(lawn_sprinkler_ctrlr_status).state == "Idle")
      {
        int seconds = round(id(lawn_sprinkler_ctrlr).valve_run_duration_adjusted(0));
        int days = seconds / (24 * 3600);
        seconds = seconds % (24 * 3600);
        int hours = seconds / 3600;
        seconds = seconds % 3600;
        int minutes = seconds /  60;
        seconds = seconds % 60;
        id(zone_1_time_remaining).publish_state((
          (days ? String(days) + "d " : "") +
          (hours ? String(hours) + "h " : "") +
         (minutes ? String(minutes) + "m " : "") +
         (String(seconds) + "s")
          ).c_str());
        return 0;
      }
      else if(id(zone1_progress_percentage).state > 1 && id(lawn_sprinkler_ctrlr_status).state != "Paused")
      {
        int seconds = 0;
        int days = seconds / (24 * 3600);
        seconds = seconds % (24 * 3600);
        int hours = seconds / 3600;
        seconds = seconds % 3600;
        int minutes = seconds /  60;
        seconds = seconds % 60;
        id(zone_1_time_remaining).publish_state((
          (days ? String(days) + "d " : "") +
          (hours ? String(hours) + "h " : "") +
         (minutes ? String(minutes) + "m " : "") +
         (String(seconds) + "s")
          ).c_str());
        return 100;
      }

This is my text_sensor:

  - platform: template
    id: zone_1_time_remaining
    name: "Zone 1 Time Remaining"

You also are going to need another text_sensor that displays the status of the controller. It is still a work in progress so it has some bugs. If you can also help improve this, it will be great.

  - platform: template
    id: lawn_sprinkler_ctrlr_status
    name: "Lawn Sprinklers Status"
    update_interval: 1s

This last text_sensor relays on this three switch:

  - platform: template
    id: lawn_sprinkler_ctrlr_run
    name: "Sprinkler Controller Run"
    optimistic: true
    on_turn_on:
      - text_sensor.template.publish:
          id: lawn_sprinkler_ctrlr_status
          state: "Running"
      - sprinkler.resume_or_start_full_cycle: lawn_sprinkler_ctrlr
      - switch.turn_off: lawn_sprinkler_ctrlr_pause
    
  - platform: template
    id: lawn_sprinkler_ctrlr_pause
    name: "Sprinkler Controller Pause"
    optimistic: true
    turn_on_action:
      
      - delay: 500ms
      - lambda: |-
          if(id(lawn_sprinkler_ctrlr_status).state != "Running")
          {
            id(lawn_sprinkler_ctrlr_pause).turn_off();
          }
      - lambda: |-
          if(id(lawn_sprinkler_ctrlr_status).state == "Running")
          {
            id(lawn_sprinkler_ctrlr_status).publish_state("Paused");
            id(lawn_sprinkler_ctrlr).pause();
          }

    on_turn_off:
      lambda: |-
        if(id(lawn_sprinkler_ctrlr_status).state == "Paused")
        {
          id(lawn_sprinkler_ctrlr_status).publish_state("Running");
          id(lawn_sprinkler_ctrlr).resume();
        }


  - platform: template
    id: lawn_sprinkler_ctrlr_stop
    name: "Sprinkler Controller Stop"
    turn_on_action:
      - text_sensor.template.publish:
          id: lawn_sprinkler_ctrlr_status
          state: "Idle"
      - sprinkler.resume: lawn_sprinkler_ctrlr
      - delay: 25ms
      - sprinkler.shutdown: lawn_sprinkler_ctrlr
      - switch.turn_off: lawn_sprinkler_ctrlr_pause
      - switch.turn_off: lawn_sprinkler_ctrlr_run

I hope this can be helpfull in your proyect.

@SebaVT, thank you for confirming the error. and how to resolve it. I like the direction you’re heading. especially the dashboard layout. It’s exactly what I’m looking for. I’ll adopt your approach to make things easier for both of us as I move. That means rewriting some code, adapting most of your naming conventions. I don’t have much time to work on these changes over the next few days so it may be Friday before I post an update.

Thanks again!

I should use $devicename as you did.

I find substitutions work very well. If you change to using $devicename (and other substitutions) you’ll find it easier to update code. Updating or changing the name in a substitution list is easier than searching for and changing multiple instances of a name in ones code.

Forgot to mention; like yourself I’m heading towards an eight zone sprinkler controller. I’m currently coding two four zone units. That’s why I’m using substitutions named ‘unit_id: A’ and ‘devicename_unit_id: a’. I’ll I need to do is copy my code and change these values to ‘B’ and ‘b’ respectively to create a second controller.

@SebaVT , I found time to modify my code, adding some of your thoughts to mine. After adding yours I noticed that “Lawn Sprinkler Status” was not updated when the device is started/restarted. To overcome this shortfall, I’ve added the following code under the esphome: section;

esphome:
    on_boot:
      priority: 800
      then:
        - text_sensor.template.publish:
            id: lawn_sprinkler_ctrlr_status
            state: "Idle" 

This addition also corrected the “Zone 1 Time Remaining” value showing as unknown when the device starts.

On another note: Is there really a need to add time remaining and percentage complete for each sprinkler? Doing so would result in 16 additional sensors on an 8 sprinkler controller. Since only one sprinkler can be active at any give point is time, one set of statistics should be suitable. To keep the number of sensors and length code at minimum, I’m going to add only one set of sensors at this point in time. What are your thoughts?

@rcblackwell I actually had that piece of code before posting it, I just forgot to share it here. The only difference is I set it with priority: -100 which sets that status after the controller is 100% bootup.

I also added this on the on_boot section:

      - sprinkler.set_multiplier:
          id: lawn_sprinkler_ctrlr
          multiplier: !lambda "return id(multiplier);"
      - sprinkler.set_valve_run_duration:
          id: lawn_sprinkler_ctrlr
          valve_number: 0
          run_duration: !lambda "return id(z1_duration)* 60;"

This is to load the global variables that storage the multiplier and the valve_run_duration in case the controller reboots.

globals:
  - id: multiplier
    type: int
    restore_value: true
    initial_value: '1'

  - id: z1_duration
    type: int
    restore_value: true
    initial_value: '15'

Blockquote
On another note : Is there really a need to add time remaining and percentage complete for each sprinkler? Doing so would result in 16 additional sensors on an 8 sprinkler controller. Since only one sprinkler can be active at any give point is time, one set of statistics should be suitable. To keep the number of sensors and length code at minimum, I’m going to add only one set of sensors at this point in time. What are your thoughts?

I’m planning to create an Irrigation routine that depends on the precipitation rates for each zone, rainfall and moisture sensors. The idea is to water the lawn as much efficiently as possible, and for that I will need each zone to have their own set of sensors to calculate the statistics.

I already compiled with all the 8 zones and the ESP32 still has a lot of flash and RAM available.

HI @SebaVT ,

I’m trying to use your code but I have some troubles with duration and remaining time, can you share your full code?

@bremby, the code referenced in the error you posted would be placed under the switch: section of SebaVT’s code. I’ve used ideas from his code to develop my own, I’m posting it here for your reference;

# Based on ESPHome Sprinkler Controller - https://esphome.io/components/sprinkler.html

# Change Log
# 2022 09 20 v13
  # - Corrected YAML spacing. Replaced four spaces with two.
  # - Corrected valve number sequenceing in number: template: section.
  # -  Added code for Sprinkler Status, Time Remaining and Percentage Complete
# 2022 09 21 v14
  # - Revised numbering of zone_x_name: substitution - 1 to 4 instead of 0 - 3
  # - Renamed zone_x_id: substitution to zone_x_valve_id:
  # - Revised numbering of zone_x_valve_id: substitution - 1 to 4 instead of 0 - 3
  # - Rename lawn_sprinkler_ctrlr_status to valve_status
  # - Add on_boot function to set value of valve_status
# 2022 09 23 v15
  # - Remove the word "Unit" from substitutions
# 2022 09 24 v16
  # - Add "Time Remaining" attribute for all zones
  # - Remove Mulitpier adjustment. as it defaults to 1 at boot
  # - Remove Global for setting run time. Set valve run_duration to 1200
# 2022 09 30 v19
  # - Add "Progress %" attribute for all zones
  # - Add sensor_update_frequency as a substitution for update_interval
# 2022 10 02 v20
  # - Replace occurances of ${devicename_unit_id} with $devicename_unit_id
  # - Replace occurances of ${unit_id} with $unit_id
  # - Replace occurances of ${software_version} with $software_version
  # - Rename Start/Resume to Start/Stop/Resume
  # - Removed Cancel button
  # - Add Timer icons to durations
  # - Add Pause icon to Pause button
# 2022 10 04 v21
  # - Add icons to Time Remaining, Progress % and Status sensors
# 2022 10 04 v22
  # - Convert Time Remaining sensor to text_sensor
# 2022 10 04 v23
  # - Convert Percentage Remaining sensor to text_sensor
# 2022 10 04 v24
  # - Add UOM for Number sensors - Set to "Sec"
  # - Remove " Duration (Seconds)" from number template name:
# 2022 10 06 v25
  # Convert seconds input to minutes

# Establish Substitutions
substitutions:
# Modify only the following eight lines.
  unit_id: A
  devicename_unit_id: a
  zone_1_name: Front Lawn
  zone_2_name: Front Garden
  zone_3_name: South Garden
  zone_4_name: Rear Garden
  software_version: 2022 10 04 v24
  sensor_update_frequency: 1s
# DO NOT CHANGE ANYTHING BELOW THIS LINE ###
  zone_1_valve_id: valve_0
  zone_2_valve_id: valve_1
  zone_3_valve_id: valve_2
  zone_4_valve_id: valve_3
  esphome_name: irrigation-valve-ctrl-unit-$devicename_unit_id
  esphome_platform: ESP8266
  esphome_board: d1_mini
  esphome_comment: Four Valve Irrigation Control - $unit_id
  esphome_project_name: Robert.Four Valve Irrigation Control - $unit_id
  esphome_project_version: Four Valve Irrigation Ctrl - Unit $unit_id, $software_version
  devicename: irrigation_valve_controller_unit_$devicename_unit_id
  upper_devicename: Four Valve Irrigation Ctrl - $unit_id
  uom: Min

#Define Project Deatils and ESP Board Type
esphome:
  name: $esphome_name
  platform: $esphome_platform
  board: $esphome_board
  comment: $esphome_comment
  project:
    name: $esphome_project_name
    version: $esphome_project_version
  on_boot:
    priority: -100
    then:
      # Set default state for Valve Status
      - text_sensor.template.publish:
          id: valve_status_$devicename_unit_id
          state: "Idle"
      # Set multiplier to 60, convert seconds to minutes
      - sprinkler.set_multiplier:
          id: $devicename
          multiplier: 60

# WiFi connection, replace these with values for your WiFi.
wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

# Enable logging
logger:
  
# Enable over-the-air updates.
ota:
  password: !secret ota_password

# Enable Web server.
web_server:
  port: 80

# Sync time with Home Assistant.
time:
  - platform: homeassistant
    id: homeassistant_time

# Text sensors with general information.
text_sensor:
  # Expose ESPHome version as sensor.
  - platform: version
    name: Version

  # Expose WiFi information as sensor.
  - platform: wifi_info
    ip_address:
      name: $devicename IP
    ssid:
      name: $devicename SSID
    bssid:
      name: $devicename BSSID

  # Expose Time Remaining as a sensor.
  - platform: template
    id: time_remaining_$devicename_unit_id
    name: "Time Remaining ($unit_id)"
    update_interval: $sensor_update_frequency
    icon: "mdi:timer-sand"
    lambda: |-
      int seconds = round(id($devicename).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()};

  # Expose Progress Percent as a sensor.
  - platform: template
    id: progress_percent_$devicename_unit_id
    name: "Progress % ($unit_id)"
    update_interval: $sensor_update_frequency
    icon: "mdi:progress-clock"
    lambda: |-
      int progress_percent = round(((id($devicename).valve_run_duration_adjusted(id($devicename).active_valve().value_or(0)) - id($devicename).time_remaining().value_or(0)) * 100 / id($devicename).valve_run_duration_adjusted(id($devicename).active_valve().value_or(0))));
      std::string progress_percent_as_string = std::to_string(progress_percent);
      return progress_percent_as_string;

  # Expose Valve Status as a sensor.
  - platform: template
    id: valve_status_$devicename_unit_id
    name: "Status ($unit_id)"
    icon: "mdi:information-variant"

# Enable On-Board Status LED.
status_led:
  pin:
    # Pin D4
    number: GPIO2
    inverted: true

sensor:
  # Uptime sensor.
  - platform: uptime
    name: $upper_devicename Uptime

  # WiFi Signal sensor.
  - platform: wifi_signal
    name: $upper_devicename WiFi Signal
    update_interval: 60s

# Enable Home Assistant APIs
api:
#  encryption:
#    key: !secret api_encryption
#  password: !secret api_password

number:
  - platform: template
    id: $zone_1_valve_id
    name: $zone_1_name
    min_value: 1
    max_value: 60
    step: 1
    unit_of_measurement: $uom
    icon: "mdi:timer-outline"
    mode: box # Defines how the number should be displayed in the UI
    lambda: "return id($devicename).valve_run_duration(0);"
    set_action:
      - sprinkler.set_valve_run_duration:
          id: $devicename
          valve_number: 0
          run_duration: !lambda 'return x;'
  - platform: template
    id: $zone_2_valve_id
    name: $zone_2_name
    min_value: 1
    max_value: 60
    step: 1
    unit_of_measurement: $uom
    icon: "mdi:timer-outline"
    mode: box # Defines how the number should be displayed in the UI
    lambda: "return id($devicename).valve_run_duration(1);"
    set_action:
      - sprinkler.set_valve_run_duration:
          id: $devicename
          valve_number: 1
          run_duration: !lambda 'return x;'
  - platform: template
    id: $zone_3_valve_id
    name: $zone_3_name
    min_value: .5
    max_value: 60
    step: 1
    unit_of_measurement: $uom
    icon: "mdi:timer-outline"
    mode: box # Defines how the number should be displayed in the UI
    lambda: "return id($devicename).valve_run_duration(2);"
    set_action:
      - sprinkler.set_valve_run_duration:
          id: $devicename
          valve_number: 2
          run_duration: !lambda 'return x;'
  - platform: template
    id: $zone_4_valve_id
    name: $zone_4_name
    min_value: 1
    max_value: 60
    step: 1
    unit_of_measurement: $uom
    icon: "mdi:timer-outline"
    mode: box # Defines how the number should be displayed in the UI
    lambda: "return id($devicename).valve_run_duration(3);"
    set_action:
      - sprinkler.set_valve_run_duration:
          id: $devicename
          valve_number: 3
          run_duration: !lambda 'return x;'

sprinkler:
  - id: $devicename
    main_switch:
      name: "Start/Stop/Resume ($unit_id)"
      id: main_switch
          
    auto_advance_switch: "Auto Advance ($unit_id)"
    valve_open_delay: 2s
    valves:
      - valve_switch: $zone_1_name
        enable_switch: Enable $zone_1_name
        run_duration: 20s
        valve_switch_id: ${devicename}_1
      - valve_switch: $zone_2_name
        enable_switch: Enable $zone_2_name
        run_duration: 20s
        valve_switch_id: ${devicename}_2
      - valve_switch: $zone_3_name
        enable_switch: Enable $zone_3_name
        run_duration: 20s
        valve_switch_id: ${devicename}_3
      - valve_switch: $zone_4_name
        enable_switch: Enable $zone_4_name
        run_duration: 20s
        valve_switch_id: ${devicename}_4

button:

  - platform: template
    id: sprinkler_pause
    name: "Pause ($unit_id)"
    icon: "mdi:pause"
    on_press:
      then:
        - text_sensor.template.publish:
            id: valve_status_$devicename_unit_id
            state: "Paused"
        - sprinkler.pause: $devicename
        
switch:

# Hidden switches.
# Switches that control sprinkler valve relays
  - platform: gpio
    name: Relay Board Pin IN1
    restore_mode: RESTORE_DEFAULT_OFF # Prevents GPIO pin from going high during boot
    internal: true # Prevents GPIO switch NAME from showing up in Home Assistant
    id: ${devicename}_1
    on_turn_on:
      - text_sensor.template.publish:
          id: valve_status_$devicename_unit_id
          state: "$zone_1_name Active"
    on_turn_off:
      - text_sensor.template.publish:
          id: valve_status_$devicename_unit_id
          state: "Idle"
    pin:
      # GPIO Pin 12
      number: D6
      inverted: true
  - platform: gpio
    name: Relay Board Pin IN2
    restore_mode: RESTORE_DEFAULT_OFF # Prevents GPIO pin from going high during boot
    internal: true # Prevents GPIO switch NAME from showing up in Home Assistant
    id: ${devicename}_2
    on_turn_on:
      - text_sensor.template.publish:
          id: valve_status_$devicename_unit_id
          state: "$zone_2_name Active"
    on_turn_off:
      - text_sensor.template.publish:
          id: valve_status_$devicename_unit_id
          state: "Idle"
    pin:
      # GPIO Pin 13
      number: D7
      inverted: true
  - platform: gpio
    name: Relay Board Pin IN3
    restore_mode: RESTORE_DEFAULT_OFF # Prevents GPIO pin from going high during boot
    internal: true # Prevents GPIO switch NAME from showing up in Home Assistant
    id: ${devicename}_3
    on_turn_on:
      - text_sensor.template.publish:
          id: valve_status_$devicename_unit_id
          state: "$zone_3_name Active"
    on_turn_off:
      - text_sensor.template.publish:
          id: valve_status_$devicename_unit_id
          state: "Idle"
    pin:
      # GPIO Pin 14
      number: D5
      inverted: true
  - platform: gpio
    name: Relay Board Pin IN4
    restore_mode: RESTORE_DEFAULT_OFF # Prevents GPIO pin from going high during boot
    internal: true # Prevents GPIO switch NAME from showing up in Home Assistant
    id: ${devicename}_4
    on_turn_on:
      - text_sensor.template.publish:
          id: valve_status_$devicename_unit_id
          state: "$zone_4_name Active"
    on_turn_off:
      - text_sensor.template.publish:
          id: valve_status_$devicename_unit_id
          state: "Idle"
    pin:
      # GPIO Pin 4
      number: D2
      inverted: true

The integrations output looks like:

Thank you for sharing,
I still have some issues, any ideas?


@bremby , That optimistic statement should not be there. My mistake. Delete that line and it should work okay.

I updated my previous posting with a copy of my working code.

Sorry,

I got one more error

/config/esphome/sprinkler.yaml: In lambda function:
/config/esphome/sprinkler.yaml:104:48: error: 'to_string' is not a member of 'std'
       std::string progress_percent_as_string = std::to_string(progress_percent);
                                                ^
/config/esphome/sprinkler.yaml:104:48: note: suggested alternatives:
In file included from src/esphome/components/api/proto.h:5:0,
                 from src/esphome/components/api/api_pb2.h:5,
                 from src/esphome/components/api/api_connection.h:4,
                 from src/esphome.h:3,
                 from src/main.cpp:3:
src/esphome/core/helpers.h:259:20: note:   'esphome::to_string'
 inline std::string to_string(const std::string &val) { return val; }
                    ^
src/esphome/core/helpers.h:259:20: note:   'esphome::to_string'
*** [/data/sprinkler/.pioenvs/sprinkler/src/main.cpp.o] Error 1

as far I understand it is related to that

    lambda: |-
      int progress_percent = round(((id($devicename).valve_run_duration_adjusted(id($devicename).active_valve().value_or(0)) - id($devicename).time_remaining().value_or(0)) * 100 / id($devicename).valve_run_duration_adjusted(id($devicename).active_valve().value_or(0))));
      std::string progress_percent_as_string = std::to_string(progress_percent);
      return progress_percent_as_string;

The lambda statement works okay for me. I’ve seen similar errors when I’ve incorrectly indented code lines. I suspect a spacing error may be the cause here. Could you copy the code from my previous post, paste into your yaml file and try again.

I did, same error

Hmm, I’ll have to think about that for a bit as it works here.