How Do I Format Lambda for text_sensor - ESPHome Sprinkler

@bremby. I’ve copied the code to a second ESP8266. It works fine here.

A few questions for you;

  1. Which version of ESPHome are your working with? I’m using 2022.9.4
  2. Are there any errors in the ESPHome log?
  3. Which device are you using?

If you’re using a different version, update to the latest to see if the issue disappears. If you’re using the same, or a later version, please copy and paste your code so that I may take a look.

HI,

I’m running the lasted esphome version [v2022.9.4 ], I don’t any error, I’m using nodemuc V4

# Modify only the following eight lines.
  unit_id: A
  devicename_unit_id: a
  zone_1_name: Zone1
  zone_2_name: Zone2
  zone_3_name: Zone3
  zone_4_name: Zone4
  zone_5_name: Zone5
  zone_6_name: Zone6
  software_version: 2022 10 04 v25
  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
  zone_5_valve_id: valve_4
  zone_6_valve_id: valve_5
  #esphome_name: irrigation-valve-ctrl-unit-$devicename_unit_id
  #esphome_platform: ESP8266
  #esphome_board: d1_mini
  #esphome_comment: Six Valve Irrigation Control - $unit_id
  #esphome_project_name: Robert.Six Valve Irrigation Control - $unit_id
  #esphome_project_version: Six Valve Irrigation Ctrl - Unit $unit_id, $software_version
  devicename: irrigation_valve_controller_unit_$devicename_unit_id
  #upper_devicename: Six 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
packages:
  wifi: !include common/device_wifi.yaml
  device_base: !include common/device_base_ESP32.yaml
  home_assistant_api: !include common/device_api.yaml
  sensor_wifi_ip_address: !include common/sensor_wifi_ip_address.yaml  
# Enable logging
logger:
# Sync time with Home Assistant.
time:
  - platform: homeassistant
    id: homeassistant_time
# Text sensors with general information.
text_sensor:
  # 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)"
    #optimistisc: true
    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
# 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;'
  - platform: template
    id: $zone_5_valve_id
    name: $zone_5_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(4);"
    set_action:
      - sprinkler.set_valve_run_duration:
          id: $devicename
          valve_number: 4
          run_duration: !lambda 'return x;'
  - platform: template
    id: $zone_6_valve_id
    name: $zone_6_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(5);"
    set_action:
      - sprinkler.set_valve_run_duration:
          id: $devicename
          valve_number: 5
          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
      - valve_switch: $zone_5_name
        enable_switch: Enable $zone_5_name
        run_duration: 20s
        valve_switch_id: ${devicename}_5
      - valve_switch: $zone_6_name
        enable_switch: Enable $zone_6_name
        run_duration: 20s
        valve_switch_id: ${devicename}_6                
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: 21
      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: 22
      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: 23
      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: 19
      inverted: true
  - platform: gpio
    name: Relay Board Pin IN5
    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}_5
    on_turn_on:
      - text_sensor.template.publish:
          id: valve_status_$devicename_unit_id
          state: "$zone_5_name Active"
    on_turn_off:
      - text_sensor.template.publish:
          id: valve_status_$devicename_unit_id
          state: "Idle"
    pin:
      # GPIO Pin 4
      number: 18
      inverted: true      
  - platform: gpio
    name: Relay Board Pin IN6
    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}_6
    on_turn_on:
      - text_sensor.template.publish:
          id: valve_status_$devicename_unit_id
          state: "$zone_6_name Active"
    on_turn_off:
      - text_sensor.template.publish:
          id: valve_status_$devicename_unit_id
          state: "Idle"
    pin:
      # GPIO Pin 4
      number: 17
      inverted: true      

Looks like the instruction “substitutions:” is missing from the first line of the file. This would impact the lambda calculation at line 84. The first few lines should read;

substitutions:
# Modify only the following eight lines.
  unit_id: A
  devicename_unit_id: a
  zone_1_name: Zone1
  zone_2_name: Zone2
  zone_3_name: Zone3
  zone_4_name: Zone4
  zone_5_name: Zone5
  zone_6_name: Zone6
  software_version: 2022 10 04 v25
  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
  zone_5_valve_id: valve_4
  zone_6_valve_id: valve_5
  #esphome_name: irrigation-valve-ctrl-unit-$devicename_unit_id
  #esphome_platform: ESP8266
  #esphome_board: d1_mini
  #esphome_comment: Six Valve Irrigation Control - $unit_id
  #esphome_project_name: Robert.Six Valve Irrigation Control - $unit_id
  #esphome_project_version: Six Valve Irrigation Ctrl - Unit $unit_id, $software_version
  devicename: irrigation_valve_controller_unit_$devicename_unit_id
  #upper_devicename: Six Valve Irrigation Ctrl - $unit_id
  uom: Min

Also, I note the use of packages for some of the text_sensors. One of which relates to the HA api: Did you notice there’s also an api: entry on line 104. I wonder if this entry will cause an issue?

Mmmm, I still get the error

Compiling /data/sprinkler/.pioenvs/sprinkler/lib64d/WiFi/WiFiGeneric.cpp.o
/config/esphome/sprinkler.yaml: In lambda function:
/config/esphome/sprinkler.yaml:98: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:98: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'
Compiling /data/sprinkler/.pioenvs/sprinkler/lib64d/WiFi/WiFiMulti.cpp.o
*** [/data/sprinkler/.pioenvs/sprinkler/src/main.cpp.o] Error 1

however, starting from your code I’m able to make it working without the percent sensor

@bremby , I can’t explain what’s going on. The only difference I see between what you and I use is the processor. Perhaps that is the cause - different libraries are being called. If you haven’t already, search Google for a possible solution.

@bremby , the error you encountered appears to be device related. I just acquired an ESP32. When I compile my code for that device, I see the same error. Did you ever get this resolved?

I found a solution for the error you experienced when trying to compile my code, specifically the section regarding “Exposing Progress Percent” as a sensor. There’s an error in the lambda section.

The line reading;

std::string progress_percent_as_string = std::to_string(progress_percent);

should read;

std::string progress_percent_as_string = esphome::to_string(progress_percent);

Note the instance of std::to_string is changed to esphome::to_string

Regards

thank you @rcblackwell

@rcblackwell , where did you define the .valve_run_duration_adjusted? I didn’t find it in the standard sprinkler documentation Sprinkler Controller — ESPHome

It’s defined by the sprinkler code in esphome. Check out classes and class reference

Got it, thank you

# Expose Progress Percent as a sensor.
  - platform: template
    id: progress_percent_sprinkler
    name: "Progress %"
    update_interval: $sensor_update_frequency
    icon: "mdi:progress-clock"
    lambda: |-
      int progress_percent = round(((id(esp32_sprinkler_ctrlr).valve_run_duration_adjusted(id(esp32_sprinkler_ctrlr).active_valve().alue_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 progress_percent_as_string = esphome::to_string(progress_percent);
      return progress_percent_as_string;

I still have an issue

Compiling /data/sprinkler/.pioenvs/sprinkler/src/main.cpp.o
/config/esphome/sprinkler.yaml: In lambda function:
/config/esphome/sprinkler.yaml:523:14: error: could not convert 'progress_percent_as_string' from 'std::__cxx11::string {aka std::__cxx11::basic_string<char>}' to 'esphome::optional<float>'
       return progress_percent_as_string;
              ^
/config/esphome/sprinkler.yaml: In lambda function:
/config/esphome/sprinkler.yaml:103:89: error: request for member 'remaining' in 'homeassistant_sprinklercountdown->esphome::homeassistant::HomeassistantSensor::<anonymous>.esphome::sensor::Sensor::state', which is of non-class type 'float'
           it.printf(0, 3, "Tempo Restante: %.1f", id(homeassistant_sprinklercountdown).state.remaining);
                                                                                         ^
*** [/data/sprinkler/.pioenvs/sprinkler/src/main.cpp.o] Error 1

There’s an error in the first line of your lambda. The line reads;

      int progress_percent = round(((id(esp32_sprinkler_ctrlr).valve_run_duration_adjusted(id(esp32_sprinkler_ctrlr).active_valve().alue_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))));

the first value_or(0) - the “v” in value is missing. The line should read;

      int 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))));

Thank you, but I can’t make it work :frowning:

  - platform: template
    id: progress_percent_sprinkler
    name: "Progress %"
    update_interval: $sensor_update_frequency
    icon: "mdi:progress-clock"
    lambda: |-
      int 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 progress_percent_as_string = esphome::to_string(progress_percent);
      return progress_percent_as_string;
Compiling /data/sprinkler/.pioenvs/sprinkler/src/main.cpp.o
/config/esphome/sprinkler.yaml: In lambda function:
/config/esphome/sprinkler.yaml:534:14: error: could not convert 'progress_percent_as_string' from 'std::__cxx11::string {aka std::__cxx11::basic_string<char>}' to 'esphome::optional<float>'
       return progress_percent_as_string;
              ^
*** [/data/sprinkler/.pioenvs/sprinkler/src/main.cpp.o] Error 1

I don’t know if it makes sense, but I it work if I remove the row at all

  - platform: template
    id: progress_percent_valve
    name: "Progress %"
    unit_of_measurement: '%'
    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))));
      return valve_progress_percent;

No. The output is displayed in HA via a text_sensor The value calculated by the lambda: needs to be converted from an integer to a string. This is what the line is attempting to do.

Can you either a) paste your entire yaml code into a this message string or, b) send it to me via PM. I’d like to see what’s setup and what might be happening to cause the error.

# Establish Substitutions
substitutions:
  device_name: sprinkler
  friendly_name: "Sprinkler"
  device_platform: ESP32
  device_board: esp32dev
  device_ip:X.X.X.X
  sensor_update_frequency: 1s
packages:
  wifi: !include common/device_wifi.yaml
  device_base: !include common/device_base_ESP32.yaml
  home_assistant_api: !include common/device_api.yaml
  sensor_wifi_ip_address: !include common/sensor_wifi_ip_address.yaml  
# Enable logging
logger:
###################################################################################################
#####  I/O expander hub definition
###################################################################################################
mcp23017:
  - id: 'mcp23017_hub'
    address: 0x20
#nodemcu devkit v4.0
i2c:
  sda: 21
  scl: 22
  scan: True
  frequency: 100kHz
###################################################################################################
# 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 0: //valve0, zone1, internal valve numbers not zone_valve_swX numbers
          it.printf(0, 0, "Zone1: %s", id(zone_valve_sw1).state ? "ON" : "OFF");
          if (id(zone_valve_sw1).state) {
          it.printf(0, 1, "Mins: %2d of", id(esp32_sprinkler_ctrlr).time_remaining().value_or(0) / 60 );
          it.printf(12, 1, "%2d", id(esp32_sprinkler_ctrlr).valve_run_duration_adjusted(0) / 60);
          } else {
          it.printf(0, 1, "Mins Set %2d     ", id(esp32_sprinkler_ctrlr).valve_run_duration_adjusted(0) / 60);
          }
          break;
          
          case 1: //valve1, zone2, 
          it.printf(0, 0, "Zone2: %s", id(zone_valve_sw2).state ? "ON" : "OFF");
          if (id(zone_valve_sw2).state) {
          it.printf(0, 1, "Mins: %2d of", id(esp32_sprinkler_ctrlr).time_remaining().value_or(0) / 60);
          it.printf(12, 1, "%2d", id(esp32_sprinkler_ctrlr).valve_run_duration_adjusted(1) / 60);
          } else {
          it.printf(0, 1, "Mins Set %2d     ", id(esp32_sprinkler_ctrlr).valve_run_duration_adjusted(1) / 60);
          }
          break;

          case 2:  //valve2, zone3,
          it.printf(0, 0, "Zone3: %s", id(zone_valve_sw3).state ? "ON" : "OFF");
          if (id(zone_valve_sw3).state) {
          it.printf(0, 1, "Mins: %2d of", id(esp32_sprinkler_ctrlr).time_remaining().value_or(0) / 60);
          it.printf(12, 1, "%2d", id(esp32_sprinkler_ctrlr).valve_run_duration_adjusted(2) / 60);
          } else {
          it.printf(0, 1, "Mins Set %2d     ", id(esp32_sprinkler_ctrlr).valve_run_duration_adjusted(2) / 60);
          }
          break;
          
          case 3:  //valve3, zone4
          it.printf(0, 0, "Zone4: %s", id(zone_valve_sw4).state ? "ON" : "OFF");
          if (id(zone_valve_sw4).state) {
          it.printf(0, 1, "Mins: %2d of", id(esp32_sprinkler_ctrlr).time_remaining().value_or(0) / 60);
          it.printf(12, 1, "%2d", id(esp32_sprinkler_ctrlr).valve_run_duration_adjusted(3) / 60);
          } else {
          it.printf(0, 1, "Mins Set %2d     ", id(esp32_sprinkler_ctrlr).valve_run_duration_adjusted(3) / 60);
          }
          break;

          case 4: //valve4, zone5
          it.printf(0, 0, "Zone5: %s", id(zone_valve_sw5).state ? "ON" : "OFF");
          if (id(zone_valve_sw5).state) {
          it.printf(0, 1, "Mins: %2d of", id(esp32_sprinkler_ctrlr).time_remaining().value_or(0) / 60);
          it.printf(12, 1, "%2d", id(esp32_sprinkler_ctrlr).valve_run_duration_adjusted(4) / 60);
          } else {
          it.printf(0, 1, "Mins Set %2d     ", id(esp32_sprinkler_ctrlr).valve_run_duration_adjusted(4) / 60);
          }
          break;

          case 5:  //valve5, zone6
          it.printf(0, 0, "Zone6: %s", id(zone_valve_sw6).state ? "ON" : "OFF");
          if (id(zone_valve_sw6).state) {
          it.printf(0, 1, "Mins: %2d of", id(esp32_sprinkler_ctrlr).time_remaining().value_or(0) / 60);
          it.printf(12, 1, "%2d", id(esp32_sprinkler_ctrlr).valve_run_duration_adjusted(5) / 60);
          } else {
          it.printf(0, 1, "Mins Set %2d     ", id(esp32_sprinkler_ctrlr).valve_run_duration_adjusted(5) / 60);
          }
          break;

          case -1:
          it.printf(0, 1, "Status: %s", id(esp32_sprinkler_ctrlr_status).state.c_str());
          it.strftime(12, 0,"%H:%M:%S", id(homeassistant_time).now());
          break;

          default:
          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
  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
# 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 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")
      {
        return id(esp32_sprinkler_ctrlr).time_remaining().value_or(0) / 60;
      }
      else
      {
        return {};
      }
#    lambda: |-
#      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()};

#  # Expose 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))));
      return valve_progress_percent;
#      std::string progress_percent_as_string = esphome::to_string(progress_percent);

  - platform: template
    name: "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


### 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

First observation - the code has been placed under the “sensor:” section. What happens when you place it under the “text_sensor:” section?

@bremby , Okay, I see what you’re doing now!

Your set this up under “sensor:” rather than “text_sensor”. In respect to your previous question, yes, dropping the troublesome line will work in that case. As a matter of fact, the line would have to be removed in order for the sensor to work.

I omitted that line and placed your code under the “sensor:” section of my code. In order for it to work in my setup, I changed “id(esp32_sprinkler_ctrlr)” to “id($devicename)”, which is the name of my device. Other than that, everything else was left alone. It works for me. Here’s the code I used;

#  # Expose 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($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))));
      return valve_progress_percent;

Were $devicename is the name of my device.

Nothing else jumps out at me. I’ll take a deeper look for you but won’t be able to do so until Monday afternoon or Tuesday,

@rcblackwell thank you so much for your patience and your support, I really appreciated the time you spent helping me. You are right, my mistake was the wrong placement of the sensor. Moving the configuration as text_sensor works as well as you described.

do you have any idea on how to “sum” the remaining time across all the valves? I would like to have the remaining time and the progress not for the single valve, but to measure the entire cycle

regards

@bremby , you’re welcome.

Not at this time. I’ve given it some thought though. There is a number of things to consider. How would total run time be calculated - is a single zone or are all zones being run? How would extended run times (Repeats) be handled? Should valve delay times be considered? And, possibly a couple of other things, yet unthought of.