Updating a HA number helper from Esphome

Hi all, I am following the rocky road of learning to configure esphome and so far its been a royal pain.
I am trying to develop a water meter monitoring system since our cost has gone through the roof.
I have read multiple posts related to similar situations but none have really addressed the input number in a homeassistant service. I have searched thought the online manual but came out empty.
I already have a number helper in HA named input_number.total_watter_consumption_global. The HA service is not working and was hoping someone might be able to shed some light in what I am doing wrong. What I am trying to implement so far is a interval call that executes a script which updates this number helper with the current reading of the “water_meter_total”.
I chose to go this route of using a HA helper rather than a global saved to the flash since I didn’t see the advantages of the latter.
Here is my code. Any help would be appreciated.

substitutions:
  device_name: Water_Meter
esphome:
  name: esp32-water-meter
#   on_boot:
#     priority: -100
#     then:
#       - lambda: |-
#           ESP_LOGD("main", "Starting the on_boot");
#       - wait_until:
#           condition:
#               api.connected:
#       - wait_until:
#           condition:
#             not:
#               lambda: |-
#                 return isnan(id(ha_water_meter_total).state);
#       - script.execute: get_ha_var

# script:
#   - id: get_ha_var
#     then:
#       - lambda: |-
#           ESP_LOGD("main", "Starting the script get_ha_var");
#       - pulse_meter.set_total_pulses:
#           id: water_pulse_meter_gal_per_min
#           value: !lambda 'return id(ha_water_meter_total).state;'      

# Save water_meter_total into HA's helper input_number.total_watter_consumption_global
        #  ESP_LOGD("main", "Value of water_meter_total: %f saved into HA's total_watter_consumption_global", id(water_meter_total).state);
        #  id(ha_water_meter_total).publish_state(id(water_meter_total).state);

interval:
 - interval: 10s
   then:
     - script.execute: push_total_gallons_to_HA

esp32:
  board: nodemcu-32s
  framework:
    type: arduino

# Enable logging
logger:
  level: VERBOSE

# Enable Home Assistant API
api:
  # services:
  #   - service: set_total_water
  #     variables:
  #       value: float
  #     then:
  #       - lambda: |-
  #           id(water_meter_total).publish_state(value);
  #   - service: set_total
  #     variables:
  #       new_total: float
  #     then:
  #       - lambda: |-
  #           id(water_meter_total).publish_state(new_total);
  #           id(water_pulse_meter_gal_per_min).set_total_pulses(new_total);


ota:
  - platform: esphome
    on_begin:
      then:
        - logger.log: "OTA start"
    on_progress:
      then:
        - logger.log:
            format: "OTA progress %0.1f%%"
            args: ["x"]
    on_end:
      then:
        - logger.log: "OTA end"
    on_error:
      then:
        - logger.log:
            format: "OTA update error %d"
            args: ["x"]
    password: !secret ota_water_meter_password

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  
    # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: !secret ap_water_meter_ssid
    password: !secret ap_water_meter_password
  
captive_portal:   

sensor:
  - platform: wifi_signal
    name: "${device_name} - WiFi"
    unit_of_measurement: "dB"
    update_interval: 60s
    device_class: "signal_strength"
    state_class: "measurement"
 
  - platform: uptime
    name: "${device_name} - Uptime"
    icon: mdi:clock-outline
    update_interval: 60s
  
  - platform: homeassistant
    id: ha_water_meter_total
    entity_id: input_number.total_watter_consumption_global

  - platform: pulse_counter
    update_interval: 6s
    name: "Water Meter Pulse Counter"
    id: water_meter_pulse_counter
    pin:
      number: GPIO26
      allow_other_uses: true
      mode:
        input: true
        pullup: true
        
  - platform: pulse_meter
    name: "Water Pulse Meter - Gal/Min"
    unit_of_measurement: "gal/min"
    internal_filter_mode: "Edge"
    icon: "mdi:water"
    pin:
      number: GPIO26
      allow_other_uses: true
      mode:
        input: true
        pullup: true
    id: water_pulse_meter_gal_per_min
    total:
      id: water_meter_total
      name: "Water Meter Total"
      unit_of_measurement: "gal"
      accuracy_decimals: 2
      device_class: water
      state_class: total_increasing 
      filters:
        - multiply: 0.0748
        # - lambda: |-
        #     id(total_gallons) = id(init_meter_id).raw_state;
        #     return x;
script:
  - id:  push_total_gallons_to_HA
    mode: queued
    then:
      - homeassistant.service:
          service: input_number.set_value
          data_template:
            value: "{{id(water_meter_total).state}}"  
            # value: "{{states('water_meter_total')}}"  
            entity_id: input_number.total_watter_consumption_global
  
      - lambda: |-
          ESP_LOGD("main", "Value of water_meter_total is %f ", id(water_meter_total).state);
          ESP_LOGD("main", "Value of HA total_water_consumption_global is %f ", id(ha_water_meter_total).state);

Did you enable home assistant actions (was services) in the ESPHome integration configure button ?

Thanks for your response. Yes, I have already enabled that.

Instead of using a HA helper, I would just use the ESPHome number component. Just seems easier imo.

Is this number visible by Home Assistant? The reason I used the input_number helper was because I will need to read this number in a Home Assistant automation which will indicate how much water each irrigation zone is consuming.
From the code above this is the code I am not sure it works, because I’m not sure it does anything.

script:
  - id:  push_total_gallons_to_HA
    mode: queued
    then:
      - homeassistant.service:
          service: input_number.set_value
          data_template:
            value: "{{id(water_meter_total).state}}"  
            # value: "{{states('water_meter_total')}}"  
            entity_id: input_number.total_watter_consumption_global

Idk where you were getting reference information but, you’re making this far more complicated than it actually is.

You just need to set up the pulse meter/counter and the pulse total part of the config and thats it…

The script and HA sensor your importing, all the stuff you’ve got commented out is completely unnecessary.

Sensor values, state changes, etc, etc are sent to HA all by itself. Its not necessary to set that up manually with scripts or on_boot automations and will likely just cause more problems and more troubleshooting…

Try takong bite sizes on projects and get a feel for how things work instead of trying to get it all done in 1 go.

1 Like

The reference information I got from all the other posts and tried to modify them for my needs.
The issue I have is that after a power cycle of the ESP32 the total number of gallons gets reset, so the idea was to save the “water_meter_total” into the input_number helper, every 10 seconds, and on the new power cycle of ESP32 to have “water_meter_total” restored from input_number helper.
This saving of the “water_meter_total” into the input_number helper is what is no working.
If I am to re-enable this line of code:

  # return id(ha_water_meter_total).publish_state(id(water_meter_total).state);

then I can see that ha_water_meter_total gets update with the value of water_meter_total since I log it with:

      - lambda: |-
          ESP_LOGD("main", "Value of water_meter_total is %f ", id(water_meter_total).state);
          ESP_LOGD("main", "Value of HA total_water_consumption_global is %f ", id(ha_water_meter_total).state);

however the actual total_watter_consumption_global from HA doesn’t get updated. Reading online that I will have to use a homeassistant service in order to get the update, that’s what I tried to use above.
However that doesn’t work. I must be doing something wrong in the configuration of that service.
I couldn’t find any online reference for the options available for “service: input_number.set_value” and just tried things, however nothing worked.
Was either using this approach or writing to flash the total gallons every few seconds and restore it upon reboot. I choose to use the first one since I would not wear out the flash.
Forgot to mention that everything related to the sensors pulse_counter and pulse_meter already shows correctly in HA. That’s what I started with so I did take the bite size approach already. Is just this last part that I couldn’t get it to work yet.

I went ahead and implemented the code changes to use the number component. Everything works however on reboot of ESP32, the number component which is supposed to hold the last value, gets reset as well.
Since the number helper doesn’t get reset on reboot, I will have to come back to using it instead.
So my request for help with the above mentioned code still stands.

Use restore_value: true in the number config.

1 Like

Thank you for the suggestion, but it doesn’t work. Upon power cycle the number component still gets set to 0. Isn’t “restore_value” having to do with restoring from flash? I am not doing any writes to the flash so that it can be restored from there. Am I missing something?

Here is my latest configuration with number component:

esphome:
  name: esp32-water-meter
interval:
 - interval: 10s
   then:
     - script.execute: push_total_gallons_to_HA

esp32:
  board: nodemcu-32s
  framework:
    type: arduino

# Enable logging
logger:
  level: VERBOSE

api:

ota:
  - platform: esphome
    on_begin:
      then:
        - logger.log: "OTA start"
    on_progress:
      then:
        - logger.log:
            format: "OTA progress %0.1f%%"
            args: ["x"]
    on_end:
      then:
        - logger.log: "OTA end"
    on_error:
      then:
        - logger.log:
            format: "OTA update error %d"
            args: ["x"]
    password: !secret ota_water_meter_password

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  
    # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: !secret ap_water_meter_ssid
    password: !secret ap_water_meter_password
  
captive_portal:   

number:
  - platform: template
    name: "Total Gallons Consumed"
    icon: "mdi:cup-water"
    unit_of_measurement: Gallons
    min_value: 0
    max_value: 3.402823466E+38
    id: total_accumulated_gallons
    step: 0.0001
    device_class: water
    update_interval: never
    optimistic: true
    restore_value: true

sensor:
  - platform: wifi_signal
    name: "${device_name} - WiFi"
    unit_of_measurement: "dB"
    update_interval: 60s
    device_class: "signal_strength"
    state_class: "measurement"
 
  - platform: uptime
    name: "${device_name} - Uptime"
    icon: mdi:clock-outline
    update_interval: 60s

  - platform: pulse_counter
    update_interval: 6s
    name: "Water Meter Pulse Counter"
    id: water_meter_pulse_counter
    pin:
      number: GPIO26
      allow_other_uses: true
      mode:
        input: true
        pullup: true
        
  - platform: pulse_meter
    name: "Water Pulse Meter - Gal/Min"
    unit_of_measurement: "gal/min"
    internal_filter_mode: "Edge"
    icon: "mdi:water"
    pin:
      number: GPIO26
      allow_other_uses: true
      mode:
        input: true
        pullup: true
    id: water_pulse_meter_gal_per_min
    total:
      id: water_meter_total
      name: "Water Meter Total"
      unit_of_measurement: "gal"
      accuracy_decimals: 2
      device_class: water
      state_class: total_increasing 
      filters:
        - multiply: 0.0748

script:
  - id:  push_total_gallons_to_HA
    mode: queued
    then:
      - lambda: |-
          ESP_LOGD("main", "Value of water_meter_total is %f ", id(water_meter_total).state);
          ESP_LOGD("main", "Value of HA total_water_consumption_global is %f ", id(total_accumulated_gallons).state);
          if (!isnan(id(water_meter_total).state))
          {
            return id(total_accumulated_gallons).publish_state(id(water_meter_total).state);
          }        

The number should be saved to flash automatically. You can control how often it’s saved by adjusting the flash_write_interval.

Instead of publishing the number, you could try and set the value explicitly.

auto call = id(total_accumulated_gallons).make_call();
call.set_value(id(water_meter_total).state);
call.perform();

If that also fails, you could perhaps use a global that get saved to flash, and set the number on_boot using the global.

Thank you, i’ll give it a try, even though I would have like to stay away from writing to the flash.

Ok, so I implemented the change to use a global which is supposed to be saved to flash every minute. Unfortunately it doesn’t work.


esphome:
  name: esp32-water-meter
 
globals:
  - id: total_gallons
    type: float
    restore_value: yes 

preferences:
  flash_write_interval: 1min

esp32:
  board: nodemcu-32s
  framework:
    type: arduino

# Enable logging
logger:
  level: VERBOSE

# Enable Home Assistant API
api:
 
ota:
  - platform: esphome
    on_begin:
      then:
        - logger.log: "OTA start"
    on_progress:
      then:
        - logger.log:
            format: "OTA progress %0.1f%%"
            args: ["x"]
    on_end:
      then:
        - logger.log: "OTA end"
    on_error:
      then:
        - logger.log:
            format: "OTA update error %d"
            args: ["x"]
    password: !secret ota_water_meter_password

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  
    # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: !secret ap_water_meter_ssid
    password: !secret ap_water_meter_password
  
captive_portal:   


interval:
 - interval: 10s
   then:
     - script.execute: push_total_gallons_to_HA

sensor:
  - platform: wifi_signal
    name: "${device_name} - WiFi"
    unit_of_measurement: "dB"
    update_interval: 60s
    device_class: "signal_strength"
    state_class: "measurement"
 
  - platform: uptime
    name: "${device_name} - Uptime"
    icon: mdi:clock-outline
    update_interval: 60s
  
  - platform: pulse_counter
    update_interval: 6s
    name: "Water Meter Pulse Counter"
    id: water_meter_pulse_counter
    pin:
      number: GPIO26
      allow_other_uses: true
      mode:
        input: true
        pullup: true
        
  - platform: pulse_meter
    name: "Water Pulse Meter - Gal/Min"
    unit_of_measurement: "gal/min"
    internal_filter_mode: "Edge"
    icon: "mdi:water"
    pin:
      number: GPIO26
      allow_other_uses: true
      mode:
        input: true
        pullup: true
    id: water_pulse_meter_gal_per_min
    total:
      id: water_meter_total
      name: "Water Meter Total"
      unit_of_measurement: "gal"
      accuracy_decimals: 2
      device_class: water
      state_class: total_increasing 
      filters:
        - multiply: 0.0748
        
script:
  - id:  push_total_gallons_to_HA
    mode: queued
    then:
      - lambda: |-
          ESP_LOGD("main", "Before - Value of water_meter_total is %f ", id(water_meter_total).state);
          ESP_LOGD("main", "Before - Value of HA total_gallons global is %f ", id(total_gallons));
          if (!isnan(id(water_meter_total).state))
          {
            id(total_gallons) = id(water_meter_total).state;
          } 
          else
          {
            id(water_meter_total ).publish_state(id(total_gallons));
          }       
          ESP_LOGD("main", "After - Value of water_meter_total is %f ", id(water_meter_total).state);
          ESP_LOGD("main", "After - Value of HA total_gallons global is %f ", id(total_gallons));
  

I blew air in the water sensor to simulate water flow. I got a reading of 1.496gal rounded by the display to 1.5gal. I could see from the log that the value of the global is set to 1.496gal
image

I sat at this reading for 10minutes, so I should have had 10 saves to the flash of the value 1.496.
image

Then I power cycle the ESP32 and I observed the first read from the flash of the global is 0.111901:


image

I was expecting only the global to have the value saved, but it looks like both the global “total_gallons” and water_meter_total got “saved” but when read back both are at 0.111901, nowhere close to the last value of 1.5

Am I missing anything from the configuration?

It seems to me that you’re going about this in a very round-about way.

Why use the interval to call a script? Why not just use the on_value action in the sensor?

Anyway, as you’re using the pulse_meter you should use the pulse_meter.set_total_pulses command on_boot.

esphome:
  on_boot:
    then:
      - pulse_meter.set_total_pulses:
          id: water_pulse_meter
          value: !lambda "return id(total_pulses);"

globals:
  - id: total_pulses
    type: int
    restore_value: yes 

sensor:  
  - platform: pulse_meter
    name: "Water Pulse Meter - Gal/Min"
    id: water_pulse_meter
    unit_of_measurement: "gal/min"
    internal_filter_mode: "Edge"
    icon: "mdi:water"
    pin:
      number: GPIO26
      allow_other_uses: true
      mode:
        input: true
        pullup: true
    id: water_pulse_meter_gal_per_min
    total:
      id: water_meter_total
      name: "Water Meter Total"
      unit_of_measurement: "gal"
      accuracy_decimals: 2
      device_class: water
      state_class: total_increasing 
      on_raw_value:
      	- lambda: |-
      	    id(total_pulses) = x;
      filters:
        - multiply: 0.0748
1 Like

Have a look at ESPHome 2024.08.b1, it looks like they’re adding number entity (and others) from ha to ESPHome, not sure if that will solve this

Thank you, I’ll take a look.

Hello.

I took you advice and implemented your changes, but I had to make a few of mine.
I cannot use water_pulse_meter actually water_pulse_meter_gal_per_minute because that’s not what I’m trying to restore. water_pulse_meter_gal_per_minute gets set to 0 if no pulses are detected for a minute.
What I want to save is water_meter_total and for that I have no .set_total_pulses option. so I had to use a lambda expression.
That being say, not matter what I try in “on_boot”, water_meter_total doesn’t get restored. I tried with/without priority, with/without wait_until, it looks like the lambda expression doesn’t get executed. That is also confirmed by the fact that I don’t see the log " on_boot:Restoring globals"
I am out of ideas. Here is the code again. :frowning:



esphome:
  name: esp32-water-meter
  on_boot:
    priority: -100
    then:
      - wait_until:
           api.connected:
      - lambda: |-
          ESP_LOGD("main", "on_boot:Restoring globals");
          id(water_meter_total).state = id(total_pulse) * 0.0748;


globals:
  - id: total_pulse
    type: float
    restore_value: yes 

preferences:
  flash_write_interval: 1min

esp32:
  board: nodemcu-32s
  framework:
    type: arduino

# Enable logging
logger:
  level: VERBOSE

# Enable Home Assistant API
api:


ota:
  - platform: esphome
    on_begin:
      then:
        - logger.log: "OTA start"
    on_progress:
      then:
        - logger.log:
            format: "OTA progress %0.1f%%"
            args: ["x"]
    on_end:
      then:
        - logger.log: "OTA end"
    on_error:
      then:
        - logger.log:
            format: "OTA update error %d"
            args: ["x"]
    password: !secret ota_water_meter_password

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  
    # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: !secret ap_water_meter_ssid
    password: !secret ap_water_meter_password
  
captive_portal:   


sensor:
  - platform: wifi_signal
    name: "${device_name} - WiFi"
    unit_of_measurement: "dB"
    update_interval: 60s
    device_class: "signal_strength"
    state_class: "measurement"
 
  - platform: uptime
    name: "${device_name} - Uptime"
    icon: mdi:clock-outline
    update_interval: 60s
  
  - platform: pulse_counter
    update_interval: 6s
    name: "Water Meter Pulse Counter"
    id: water_meter_pulse_counter
    pin:
      number: GPIO26
      allow_other_uses: true
      mode:
        input: true
        pullup: true
        
  - platform: pulse_meter
    name: "Water Pulse Meter - Gal/Min"
    unit_of_measurement: "gal/min"
    internal_filter_mode: "Edge"
    icon: "mdi:water"
    pin:
      number: GPIO26
      allow_other_uses: true
      mode:
        input: true
        pullup: true
    id: water_pulse_meter_gal_per_min
    total:
      id: water_meter_total
      name: "Water Meter Total"
      unit_of_measurement: "gal"
      accuracy_decimals: 2
      device_class: water
      state_class: total_increasing 
      filters:
        - multiply: 0.0748

      on_raw_value:
        - lambda: |-
            ESP_LOGD("main", "Saving water_meter_total in total_pulse.");
            id(total_pulse) = id(water_meter_total).state;
            ESP_LOGD("main", "Value of water_pulse_meter_gal_per_min is %f ", id(water_pulse_meter_gal_per_min).state);
            ESP_LOGD("main", "Value of water_meter_total is %f ", id(water_meter_total).state);
            ESP_LOGD("main", "Value of total_pulse is %f ", id(total_pulse));
            ESP_LOGD("main", "Value of total gallons is %f ", id(total_pulse) * 0.0748);


Also then “on_raw_value” is not working. Instead of getting water_meter_total= 1,2,3… , I get the water_meter_total * 0.0748
See image:

I finally figured it out. @zenzay42 you were correct about pulse_meter.set_total_pulses;
Here is the final code if anyone needs a reference.
P.S. set logger to NONE and remove the debug code


esphome:
  name: esp32-water-meter
  on_boot:
    then:
      - pulse_meter.set_total_pulses:
          id: water_pulse_meter_gal_per_min
          value: !lambda "return id(total_pulses);"
          
globals:
  - id: total_pulses
    type: float
    restore_value: yes 

preferences:
  flash_write_interval: 1min

esp32:
  board: nodemcu-32s
  framework:
    type: arduino

# Enable logging
logger:
  level: VERBOSE

# Enable Home Assistant API
api:


ota:
  - platform: esphome
    on_begin:
      then:
        - logger.log: "OTA start"
    on_progress:
      then:
        - logger.log:
            format: "OTA progress %0.1f%%"
            args: ["x"]
    on_end:
      then:
        - logger.log: "OTA end"
    on_error:
      then:
        - logger.log:
            format: "OTA update error %d"
            args: ["x"]
    password: !secret ota_water_meter_password

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  
    # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: !secret ap_water_meter_ssid
    password: !secret ap_water_meter_password
  
captive_portal:   


sensor:
  - platform: wifi_signal
    name: "${device_name} - WiFi"
    unit_of_measurement: "dB"
    update_interval: 60s
    device_class: "signal_strength"
    state_class: "measurement"
 
  - platform: uptime
    name: "${device_name} - Uptime"
    icon: mdi:clock-outline
    update_interval: 60s
  
  - platform: pulse_counter
    update_interval: 6s
    name: "Water Meter Pulse Counter"
    id: water_meter_pulse_counter
    pin:
      number: GPIO26
      allow_other_uses: true
      mode:
        input: true
        pullup: true
        
  - platform: pulse_meter
    name: "Water Pulse Meter - Gal/Min"
    unit_of_measurement: "gal/min"
    internal_filter_mode: "Edge"
    icon: "mdi:water"
    pin:
      number: GPIO26
      allow_other_uses: true
      mode:
        input: true
        pullup: true
    id: water_pulse_meter_gal_per_min
    total:
      id: water_meter_total
      name: "Water Meter Total"
      unit_of_measurement: "gal"
      accuracy_decimals: 2
      device_class: water
      state_class: total_increasing 
      filters:
        - multiply: 0.0748
        - lambda: |-
            ESP_LOGD("main", "Saving water_meter_total in total_pulses.");
            id(total_pulses) = id(water_meter_total).raw_state;
            ESP_LOGD("main", "Value of water_pulse_meter_gal_per_min is %f ", id(water_pulse_meter_gal_per_min).state);
            ESP_LOGD("main", "Value of water_meter_total is %f ", id(water_meter_total).state);
            ESP_LOGD("main", "Value of total_pulses is %f ", id(total_pulses));
            ESP_LOGD("main", "Value of total gallons is %f ", id(total_pulses) * 0.0748);
            return x;

           

Were you trying to store the running total on the esp board as well? I dont think I understood your question to begin with. I just assumed you were storing that data in HA like just about everyone does because it adds up a total and stores that data as well. For example, here is mine. It just takes the data from the esp node and its then tracked and seperated by time frames.

Screenshot from 2024-08-16 07-23-44